From 72244a874a6c24899af14cdf5c541c8c9c326642 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 4 Jun 2024 15:40:30 +0200 Subject: [PATCH 001/595] docs(framework:skip) Fix typo in docstrings (#3545) --- src/py/flwr/client/client_app.py | 2 +- src/py/flwr/server/server_app.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/py/flwr/client/client_app.py b/src/py/flwr/client/client_app.py index c9d337700147..82539834eaad 100644 --- a/src/py/flwr/client/client_app.py +++ b/src/py/flwr/client/client_app.py @@ -221,7 +221,7 @@ def _registration_error(fn_name: str) -> ValueError: >>> def client_fn(cid) -> Client: >>> return FlowerClient().to_client() >>> - >>> app = ClientApp() + >>> app = ClientApp( >>> client_fn=client_fn, >>> ) diff --git a/src/py/flwr/server/server_app.py b/src/py/flwr/server/server_app.py index ea2eb3fd1a69..43b3bcce3f36 100644 --- a/src/py/flwr/server/server_app.py +++ b/src/py/flwr/server/server_app.py @@ -39,7 +39,7 @@ class ServerApp: >>> server_config = ServerConfig(num_rounds=3) >>> strategy = FedAvg() >>> - >>> app = ServerApp() + >>> app = ServerApp( >>> server_config=server_config, >>> strategy=strategy, >>> ) @@ -106,7 +106,7 @@ def main_decorator(main_fn: ServerAppCallable) -> ServerAppCallable: >>> server_config = ServerConfig(num_rounds=3) >>> strategy = FedAvg() >>> - >>> app = ServerApp() + >>> app = ServerApp( >>> server_config=server_config, >>> strategy=strategy, >>> ) From 097b803394ce761a0c5daa1eff5e71b06e421b25 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:22:58 +0200 Subject: [PATCH 002/595] feat(datasets) Add label distribution visualization (#3451) Co-authored-by: jafermarq --- datasets/flwr_datasets/__init__.py | 3 + datasets/flwr_datasets/metrics/__init__.py | 23 ++ datasets/flwr_datasets/metrics/utils.py | 78 +++++ datasets/flwr_datasets/metrics/utils_test.py | 89 ++++++ .../flwr_datasets/visualization/__init__.py | 24 ++ .../flwr_datasets/visualization/bar_plot.py | 143 +++++++++ .../comparison_label_distribution.py | 237 +++++++++++++++ .../flwr_datasets/visualization/constants.py | 19 ++ .../visualization/heatmap_plot.py | 106 +++++++ .../visualization/label_distribution.py | 279 ++++++++++++++++++ datasets/flwr_datasets/visualization/utils.py | 36 +++ datasets/pyproject.toml | 2 + 12 files changed, 1039 insertions(+) create mode 100644 datasets/flwr_datasets/metrics/__init__.py create mode 100644 datasets/flwr_datasets/metrics/utils.py create mode 100644 datasets/flwr_datasets/metrics/utils_test.py create mode 100644 datasets/flwr_datasets/visualization/__init__.py create mode 100644 datasets/flwr_datasets/visualization/bar_plot.py create mode 100644 datasets/flwr_datasets/visualization/comparison_label_distribution.py create mode 100644 datasets/flwr_datasets/visualization/constants.py create mode 100644 datasets/flwr_datasets/visualization/heatmap_plot.py create mode 100644 datasets/flwr_datasets/visualization/label_distribution.py create mode 100644 datasets/flwr_datasets/visualization/utils.py diff --git a/datasets/flwr_datasets/__init__.py b/datasets/flwr_datasets/__init__.py index 2d6ecb414498..d084780102ce 100644 --- a/datasets/flwr_datasets/__init__.py +++ b/datasets/flwr_datasets/__init__.py @@ -17,12 +17,15 @@ from flwr_datasets import partitioner, preprocessor from flwr_datasets import utils as utils +from flwr_datasets import visualization from flwr_datasets.common.version import package_version as _package_version from flwr_datasets.federated_dataset import FederatedDataset __all__ = [ "FederatedDataset", "partitioner", + "metrics", + "visualization", "preprocessor", "utils", ] diff --git a/datasets/flwr_datasets/metrics/__init__.py b/datasets/flwr_datasets/metrics/__init__.py new file mode 100644 index 000000000000..807860b5b7b8 --- /dev/null +++ b/datasets/flwr_datasets/metrics/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Metrics package.""" + + +from flwr_datasets.metrics.utils import compute_counts, compute_frequency + +__all__ = [ + "compute_counts", + "compute_frequency", +] diff --git a/datasets/flwr_datasets/metrics/utils.py b/datasets/flwr_datasets/metrics/utils.py new file mode 100644 index 000000000000..a11e7193fbde --- /dev/null +++ b/datasets/flwr_datasets/metrics/utils.py @@ -0,0 +1,78 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Utils for metrics computation.""" + + +from typing import List, Union + +import pandas as pd + + +def compute_counts( + labels: Union[List[int], List[str]], unique_labels: Union[List[int], List[str]] +) -> pd.Series: + """Compute the count of labels when taking into account all possible labels. + + Also known as absolute frequency. + + Parameters + ---------- + labels: Union[List[int], List[str]] + The labels from the datasets. + unique_labels: Union[List[int], List[str]] + The reference all unique label. Needed to avoid missing any label, instead + having the value equal to zero for them. + + Returns + ------- + label_counts: pd.Series + The pd.Series with label as indices and counts as values. + """ + if len(unique_labels) != len(set(unique_labels)): + raise ValueError("unique_labels must contain unique elements only.") + labels_series = pd.Series(labels) + label_counts = labels_series.value_counts() + label_counts_with_zeros = pd.Series(index=unique_labels, data=0) + label_counts_with_zeros = label_counts_with_zeros.add( + label_counts, fill_value=0 + ).astype(int) + return label_counts_with_zeros + + +def compute_frequency( + labels: Union[List[int], List[str]], unique_labels: Union[List[int], List[str]] +) -> pd.Series: + """Compute the distribution of labels when taking into account all possible labels. + + Also known as relative frequency. + + Parameters + ---------- + labels: Union[List[int], List[str]] + The labels from the datasets. + unique_labels: Union[List[int], List[str]] + The reference all unique label. Needed to avoid missing any label, instead + having the value equal to zero for them. + + Returns + ------- + The pd.Series with label as indices and probabilities as values. + """ + counts = compute_counts(labels, unique_labels) + if len(labels) == 0: + counts = counts.astype(float) + return counts + counts = counts.divide(len(labels)) + return counts diff --git a/datasets/flwr_datasets/metrics/utils_test.py b/datasets/flwr_datasets/metrics/utils_test.py new file mode 100644 index 000000000000..687aa67dbde6 --- /dev/null +++ b/datasets/flwr_datasets/metrics/utils_test.py @@ -0,0 +1,89 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for metrics utils.""" +# pylint: disable=no-self-use + + +import unittest + +import pandas as pd +from parameterized import parameterized + +from flwr_datasets.metrics.utils import compute_counts, compute_frequency + + +class TestMetricsUtils(unittest.TestCase): + """Test metrics utils.""" + + @parameterized.expand( # type: ignore + [ + ([1, 2, 2, 3], [1, 2, 3, 4], pd.Series([1, 2, 1, 0], index=[1, 2, 3, 4])), + ([], [1, 2, 3], pd.Series([0, 0, 0], index=[1, 2, 3])), + ([1, 1, 2], [1, 2, 3, 4], pd.Series([2, 1, 0, 0], index=[1, 2, 3, 4])), + ] + ) + def test_compute_counts(self, labels, unique_labels, expected) -> None: + """Test if the counts are computed correctly.""" + result = compute_counts(labels, unique_labels) + pd.testing.assert_series_equal(result, expected) + + @parameterized.expand( # type: ignore + [ + ( + [1, 1, 2, 2, 2, 3], + [1, 2, 3, 4], + pd.Series([0.3333, 0.5, 0.1667, 0.0], index=[1, 2, 3, 4]), + ), + ([], [1, 2, 3], pd.Series([0.0, 0.0, 0.0], index=[1, 2, 3])), + ( + ["a", "b", "b", "c"], + ["a", "b", "c", "d"], + pd.Series([0.25, 0.50, 0.25, 0.0], index=["a", "b", "c", "d"]), + ), + ] + ) + def test_compute_distribution(self, labels, unique_labels, expected) -> None: + """Test if the distributions are computed correctly.""" + result = compute_frequency(labels, unique_labels) + pd.testing.assert_series_equal(result, expected, atol=0.001) + + @parameterized.expand( # type: ignore + [ + (["a", "b", "b", "c"], ["a", "b", "c"]), + ([1, 2, 2, 3, 3, 3, 4], [1, 2, 3, 4]), + ] + ) + def test_distribution_sum_to_one(self, labels, unique_labels) -> None: + """Test if distributions sum up to one.""" + result = compute_frequency(labels, unique_labels) + self.assertAlmostEqual(result.sum(), 1.0) + + def test_compute_counts_non_unique_labels(self) -> None: + """Test if not having the unique labels raises ValueError.""" + labels = [1, 2, 3] + unique_labels = [1, 2, 2, 3] + with self.assertRaises(ValueError): + compute_counts(labels, unique_labels) + + def test_compute_distribution_non_unique_labels(self) -> None: + """Test if not having the unique labels raises ValueError.""" + labels = [1, 1, 2, 3] + unique_labels = [1, 1, 2, 3] + with self.assertRaises(ValueError): + compute_frequency(labels, unique_labels) + + +if __name__ == "__main__": + unittest.main() diff --git a/datasets/flwr_datasets/visualization/__init__.py b/datasets/flwr_datasets/visualization/__init__.py new file mode 100644 index 000000000000..801a38dcafc6 --- /dev/null +++ b/datasets/flwr_datasets/visualization/__init__.py @@ -0,0 +1,24 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Visualization package.""" + + +from .comparison_label_distribution import plot_comparison_label_distribution +from .label_distribution import plot_label_distributions + +__all__ = [ + "plot_label_distributions", + "plot_comparison_label_distribution", +] diff --git a/datasets/flwr_datasets/visualization/bar_plot.py b/datasets/flwr_datasets/visualization/bar_plot.py new file mode 100644 index 000000000000..6326b24a9695 --- /dev/null +++ b/datasets/flwr_datasets/visualization/bar_plot.py @@ -0,0 +1,143 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Label distribution bar plotting.""" + + +from typing import Any, Dict, Optional, Tuple, Union + +import numpy as np +import pandas as pd +from matplotlib import colors as mcolors +from matplotlib import pyplot as plt +from matplotlib.axes import Axes + + +# pylint: disable=too-many-arguments,too-many-locals,too-many-branches +def _plot_bar( + dataframe: pd.DataFrame, + axis: Optional[Axes], + figsize: Optional[Tuple[float, float]], + title: str, + colormap: Optional[Union[str, mcolors.Colormap]], + partition_id_axis: str, + size_unit: str, + legend: bool, + legend_title: Optional[str], + plot_kwargs: Optional[Dict[str, Any]], + legend_kwargs: Optional[Dict[str, Any]], +) -> Axes: + + if axis is None: + if figsize is None: + figsize = _initialize_figsize( + partition_id_axis=partition_id_axis, num_partitions=dataframe.shape[0] + ) + _, axis = plt.subplots(figsize=figsize) + + # Handle plot_kwargs + if plot_kwargs is None: + plot_kwargs = {} + + kind = "bar" if partition_id_axis == "x" else "barh" + if "kind" not in plot_kwargs: + plot_kwargs["kind"] = kind + + # Handle non-optional parameters + plot_kwargs["title"] = title + + # Handle optional parameters + if colormap is not None: + plot_kwargs["colormap"] = colormap + elif "colormap" not in plot_kwargs: + plot_kwargs["colormap"] = "RdYlGn" + + if "xlabel" not in plot_kwargs and "ylabel" not in plot_kwargs: + xlabel, ylabel = _initialize_xy_labels( + size_unit=size_unit, partition_id_axis=partition_id_axis + ) + plot_kwargs["xlabel"] = xlabel + plot_kwargs["ylabel"] = ylabel + + # Make the x ticks readable (they appear 90 degrees rotated by default) + if "rot" not in plot_kwargs: + plot_kwargs["rot"] = 0 + + # Handle hard-coded parameters + # Legend is handled separately (via axes.legend call not in the plot()) + if "legend" not in plot_kwargs: + plot_kwargs["legend"] = False + + # Make the bar plot stacked + if "stacked" not in plot_kwargs: + plot_kwargs["stacked"] = True + + axis = dataframe.plot( + ax=axis, + **plot_kwargs, + ) + + if legend: + if legend_kwargs is None: + legend_kwargs = {} + + if legend_title is not None: + legend_kwargs["title"] = legend_title + elif "title" not in legend_kwargs: + legend_kwargs["title"] = "Labels" + + if "loc" not in legend_kwargs: + legend_kwargs["loc"] = "outside center right" + + if "bbox_to_anchor" not in legend_kwargs: + max_len_label_str = max([len(str(column)) for column in dataframe.columns]) + shift = min(0.05 + max_len_label_str / 100, 0.15) + legend_kwargs["bbox_to_anchor"] = (1.0 + shift, 0.5) + + handles, legend_labels = axis.get_legend_handles_labels() + _ = axis.figure.legend( + handles=handles[::-1], labels=legend_labels[::-1], **legend_kwargs + ) + + # Heuristic to make the partition id on xticks non-overlapping + if partition_id_axis == "x": + xticklabels = axis.get_xticklabels() + if len(xticklabels) > 20: + # Make every other xtick label not visible + for i, label in enumerate(xticklabels): + if i % 2 == 1: + label.set_visible(False) + return axis + + +def _initialize_figsize( + partition_id_axis: str, + num_partitions: int, +) -> Tuple[float, float]: + figsize = (0.0, 0.0) + if partition_id_axis == "x": + figsize = (6.4, 4.8) + elif partition_id_axis == "y": + figsize = (6.4, np.sqrt(num_partitions)) + return figsize + + +def _initialize_xy_labels(size_unit: str, partition_id_axis: str) -> Tuple[str, str]: + xlabel = "Partition ID" + ylabel = "Count" if size_unit == "absolute" else "Percent %" + + if partition_id_axis == "y": + xlabel, ylabel = ylabel, xlabel + + return xlabel, ylabel diff --git a/datasets/flwr_datasets/visualization/comparison_label_distribution.py b/datasets/flwr_datasets/visualization/comparison_label_distribution.py new file mode 100644 index 000000000000..d59f8c47986d --- /dev/null +++ b/datasets/flwr_datasets/visualization/comparison_label_distribution.py @@ -0,0 +1,237 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Comparison of label distribution plotting.""" + + +from typing import Any, Dict, List, Optional, Tuple, Union + +import matplotlib.colors as mcolors +import matplotlib.pyplot as plt +import pandas as pd +from matplotlib.axes import Axes +from matplotlib.figure import Figure + +from flwr_datasets.partitioner import Partitioner +from flwr_datasets.visualization.constants import PLOT_TYPES +from flwr_datasets.visualization.label_distribution import plot_label_distributions + + +# pylint: disable=too-many-arguments,too-many-locals +def plot_comparison_label_distribution( + partitioner_list: List[Partitioner], + label_name: Union[str, List[str]], + plot_type: str = "bar", + size_unit: str = "percent", + max_num_partitions: Optional[Union[int]] = 30, + partition_id_axis: str = "y", + figsize: Optional[Tuple[float, float]] = None, + subtitle: str = "Comparison of Per Partition Label Distribution", + titles: Optional[List[str]] = None, + cmap: Optional[Union[str, mcolors.Colormap]] = None, + legend: bool = False, + legend_title: Optional[str] = None, + verbose_labels: bool = True, + plot_kwargs_list: Optional[List[Optional[Dict[str, Any]]]] = None, + legend_kwargs: Optional[Dict[str, Any]] = None, +) -> Tuple[Figure, List[Axes], List[pd.DataFrame]]: + """Compare the label distribution across multiple partitioners. + + Parameters + ---------- + partitioner_list : List[Partitioner] + List of partitioners to be compared. + label_name : Union[str, List[str]] + Column name or list of column names identifying labels for each partitioner. + plot_type : str + Type of plot, either "bar" or "heatmap". + size_unit : str + "absolute" for raw counts, or "percent" to normalize values to 100%. + max_num_partitions : Optional[int] + Maximum number of partitions to include in the plot. If None, all partitions + are included. + partition_id_axis : str + Axis on which the partition IDs will be marked, either "x" or "y". + figsize : Optional[Tuple[float, float]] + Size of the figure. If None, a default size is calculated. + subtitle : str + Subtitle for the figure. Defaults to "Comparison of Per Partition Label + Distribution" + titles : Optional[List[str]] + Titles for each subplot. If None, no titles are set. + cmap : Optional[Union[str, mcolors.Colormap]] + Colormap for determining the colorspace of the plot. + legend : bool + Whether to include a legend. If True, it will be included right-hand side after + all the plots. + legend_title : Optional[str] + Title for the legend. If None, the defaults will be takes based on the type of + plot. + verbose_labels : bool + Whether to use verbose versions of the labels. + plot_kwargs_list: Optional[List[Optional[Dict[str, Any]]]] + List of plot_kwargs. Any key value pair that can be passed to a plot function + that are not supported directly. In case of the parameter doubling + (e.g. specifying cmap here too) the chosen value will be taken from the + explicit arguments (e.g. cmap specified as an argument to this function not + the value in this dictionary). + legend_kwargs: Optional[Dict[str, Any]] + Any key value pair that can be passed to a figure.legend in case of bar plot or + cbar_kws in case of heatmap that are not supported directly. In case of + parameter doubling (e.g. specifying legend_title here too) the + chosen value will be taken from the explicit arguments (e.g. legend_title + specified as an argument to this function not the value in this dictionary). + + Returns + ------- + fig : Figure + The figure object containing the plots. + axes_list : List[Axes] + List of Axes objects for the plots. + dataframe_list : List[pd.DataFrame] + List of DataFrames used for each plot. + + Examples + -------- + Compare the difference of using different alpha (concentration) parameters in + DirichletPartitioner. + + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import DirichletPartitioner + >>> from flwr_datasets.visualization import plot_comparison_label_distribution + >>> + >>> partitioner_list = [] + >>> alpha_list = [10_000.0, 100.0, 1.0, 0.1, 0.01, 0.00001] + >>> for alpha in alpha_list: + >>> fds = FederatedDataset( + >>> dataset="cifar10", + >>> partitioners={ + >>> "train": DirichletPartitioner( + >>> num_partitions=20, + >>> partition_by="label", + >>> alpha=alpha, + >>> min_partition_size=0, + >>> ), + >>> }, + >>> ) + >>> partitioner_list.append(fds.partitioners["train"]) + >>> fig, axes, dataframe_list = plot_comparison_label_distribution( + >>> partitioner_list=partitioner_list, + >>> label_name="label", + >>> titles=[f"Concentration = {alpha}" for alpha in alpha_list], + >>> ) + """ + num_partitioners = len(partitioner_list) + if isinstance(label_name, str): + label_name = [label_name] * num_partitioners + elif isinstance(label_name, List): + pass + else: + raise TypeError( + f"Label name has to be of type List[str] or str but given " + f"{type(label_name)}" + ) + figsize = _initialize_comparison_figsize(figsize, num_partitioners) + fig, axes = plt.subplots(1, num_partitioners, layout="constrained", figsize=figsize) + + if titles is None: + titles = ["" for _ in range(num_partitioners)] + + if plot_kwargs_list is None: + plot_kwargs_list = [None] * num_partitioners + + dataframe_list = [] + for idx, (partitioner, single_label_name, plot_kwargs) in enumerate( + zip(partitioner_list, label_name, plot_kwargs_list) + ): + if idx == (num_partitioners - 1): + *_, dataframe = plot_label_distributions( + partitioner=partitioner, + label_name=single_label_name, + plot_type=plot_type, + size_unit=size_unit, + partition_id_axis=partition_id_axis, + axis=axes[idx], + max_num_partitions=max_num_partitions, + cmap=cmap, + legend=legend, + legend_title=legend_title, + verbose_labels=verbose_labels, + plot_kwargs=plot_kwargs, + legend_kwargs=legend_kwargs, + ) + dataframe_list.append(dataframe) + else: + *_, dataframe = plot_label_distributions( + partitioner=partitioner, + label_name=single_label_name, + plot_type=plot_type, + size_unit=size_unit, + partition_id_axis=partition_id_axis, + axis=axes[idx], + max_num_partitions=max_num_partitions, + cmap=cmap, + legend=False, + plot_kwargs=plot_kwargs, + ) + dataframe_list.append(dataframe) + + # Do not use the xlabel and ylabel on each subplot plot + # (instead use global = per figure xlabel and ylabel) + for idx, axis in enumerate(axes): + axis.set_xlabel("") + axis.set_ylabel("") + axis.set_title(titles[idx]) + for axis in axes[1:]: + axis.set_yticks([]) + + # Set up figure xlabel and ylabel + xlabel, ylabel = _initialize_comparison_xy_labels(plot_type, partition_id_axis) + fig.supxlabel(xlabel) + fig.supylabel(ylabel) + fig.suptitle(subtitle) + + fig.tight_layout() + return fig, axes, dataframe_list + + +def _initialize_comparison_figsize( + figsize: Optional[Tuple[float, float]], num_partitioners: int +) -> Tuple[float, float]: + if figsize is not None: + return figsize + x_value = 4 + (num_partitioners - 1) * 2 + y_value = 4.8 + figsize = (x_value, y_value) + return figsize + + +def _initialize_comparison_xy_labels( + plot_type: str, partition_id_axis: str +) -> Tuple[str, str]: + if plot_type == "bar": + xlabel = "Partition ID" + ylabel = "Class distribution" + elif plot_type == "heatmap": + xlabel = "Partition ID" + ylabel = "Label" + else: + raise ValueError( + f"Invalid plot_type: {plot_type}. Must be one of {PLOT_TYPES}." + ) + + if partition_id_axis == "y": + xlabel, ylabel = ylabel, xlabel + + return xlabel, ylabel diff --git a/datasets/flwr_datasets/visualization/constants.py b/datasets/flwr_datasets/visualization/constants.py new file mode 100644 index 000000000000..b3c9cd2a7400 --- /dev/null +++ b/datasets/flwr_datasets/visualization/constants.py @@ -0,0 +1,19 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Constants for plot types and size units.""" + +PLOT_TYPES = ("bar", "heatmap") +SIZE_UNITS = ("absolute", "percent") +AXIS_TYPES = ("x", "y") diff --git a/datasets/flwr_datasets/visualization/heatmap_plot.py b/datasets/flwr_datasets/visualization/heatmap_plot.py new file mode 100644 index 000000000000..2e593a79368e --- /dev/null +++ b/datasets/flwr_datasets/visualization/heatmap_plot.py @@ -0,0 +1,106 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Label distribution heatmap plotting.""" + + +from typing import Any, Dict, Optional, Tuple, Union + +import numpy as np +import pandas as pd +import seaborn as sns +from matplotlib import colors as mcolors +from matplotlib import pyplot as plt +from matplotlib.axes import Axes + + +# pylint: disable=too-many-arguments,too-many-locals +def _plot_heatmap( + dataframe: pd.DataFrame, + axis: Optional[Axes], + figsize: Optional[Tuple[float, float]], + title: str, + colormap: Optional[Union[str, mcolors.Colormap]], + partition_id_axis: str, + size_unit: str, + legend: bool, + legend_title: Optional[str], + plot_kwargs: Optional[Dict[str, Any]], + legend_kwargs: Optional[Dict[str, Any]], +) -> Axes: + + if axis is None: + if figsize is None: + figsize = _initialize_figsize( + partition_id_axis=partition_id_axis, + num_partitions=dataframe.shape[0], + num_labels=dataframe.shape[1], + ) + _, axis = plt.subplots(figsize=figsize) + + # Handle plot_kwargs + if plot_kwargs is None: + plot_kwargs = {} + + # Handle optional parameters + if colormap is not None: + plot_kwargs["cmap"] = colormap + elif "cmap" not in plot_kwargs: + plot_kwargs["cmap"] = sns.light_palette("seagreen", as_cmap=True) + + if "fmt" not in plot_kwargs: + plot_kwargs["fmt"] = ",d" if size_unit == "absolute" else "0.2f" + + if legend_kwargs is None: + legend_kwargs = {} + if legend: + plot_kwargs["cbar"] = True + + if legend_title is not None: + legend_kwargs["label"] = legend_title + else: + legend_kwargs["label"] = _initialize_cbar_title(size_unit) + else: + plot_kwargs["cbar"] = False + + if partition_id_axis == "x": + dataframe = dataframe.T + + sns.heatmap( + dataframe, + ax=axis, + **plot_kwargs, + cbar_kws=legend_kwargs, + ) + axis.set_title(title) + return axis + + +def _initialize_figsize( + partition_id_axis: str, + num_partitions: int, + num_labels: int, +) -> Tuple[float, float]: + + figsize = (0.0, 0.0) + if partition_id_axis == "x": + figsize = (3 * np.sqrt(num_partitions), np.sqrt(num_labels)) + elif partition_id_axis == "y": + figsize = (3 * np.sqrt(num_labels), np.sqrt(num_partitions)) + + return figsize + + +def _initialize_cbar_title(size_unit: str) -> Optional[str]: + return "Count" if size_unit == "absolute" else "Percent %" diff --git a/datasets/flwr_datasets/visualization/label_distribution.py b/datasets/flwr_datasets/visualization/label_distribution.py new file mode 100644 index 000000000000..f959fbd856ee --- /dev/null +++ b/datasets/flwr_datasets/visualization/label_distribution.py @@ -0,0 +1,279 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Label distribution plotting.""" + + +import warnings +from typing import Any, Dict, Optional, Tuple, Union + +import matplotlib.colors as mcolors +import pandas as pd +from matplotlib.axes import Axes +from matplotlib.figure import Figure + +from flwr_datasets.metrics import compute_counts +from flwr_datasets.partitioner import Partitioner +from flwr_datasets.visualization.bar_plot import _plot_bar +from flwr_datasets.visualization.heatmap_plot import _plot_heatmap +from flwr_datasets.visualization.utils import _validate_parameters + +# pylint: disable=too-many-arguments,too-many-locals + + +def plot_label_distributions( + partitioner: Partitioner, + label_name: str, + plot_type: str = "bar", + size_unit: str = "absolute", + max_num_partitions: Optional[int] = None, + partition_id_axis: str = "x", + axis: Optional[Axes] = None, + figsize: Optional[Tuple[float, float]] = None, + title: str = "Per Partition Label Distribution", + cmap: Optional[Union[str, mcolors.Colormap]] = None, + legend: bool = False, + legend_title: Optional[str] = None, + verbose_labels: bool = True, + plot_kwargs: Optional[Dict[str, Any]] = None, + legend_kwargs: Optional[Dict[str, Any]] = None, +) -> Tuple[Figure, Axes, pd.DataFrame]: + """Plot the label distribution of the partitions. + + Parameters + ---------- + partitioner : Partitioner + Partitioner with an assigned dataset. + label_name : str + Column name identifying label based on which the plot will be created. + plot_type : str + Type of plot, either "bar" or "heatmap". + size_unit : str + "absolute" or "percent". "absolute" - (number of samples). "percent" - + normalizes each value, so they sum up to 100%. + max_num_partitions : Optional[int] + The number of partitions that will be used. If left None, then all partitions + will be used. + partition_id_axis : str + "x" or "y". The axis on which the partition_id will be marked. + axis : Optional[Axes] + Matplotlib Axes object to plot on. + figsize : Optional[Tuple[float, float]] + Size of the figure. + title : str + Title of the plot. + cmap : Optional[Union[str, mcolors.Colormap]] + Colormap for determining the colorspace of the plot. + legend : bool + Include the legend. + legend_title : Optional[str] + Title for the legend. If None, the defaults will be takes based on the type of + plot. + verbose_labels : bool + Whether to use verbose versions of the labels. + plot_kwargs: Optional[Dict[str, Any]] + Any key value pair that can be passed to a plot function that are not supported + directly. In case of the parameter doubling (e.g. specifying cmap here too) the + chosen value will be taken from the explicit arguments (e.g. cmap specified as + an argument to this function not the value in this dictionary). + legend_kwargs: Optional[Dict[str, Any]] + Any key value pair that can be passed to a figure.legend in case of bar plot or + cbar_kws in case of heatmap that are not supported directly. In case of the + parameter doubling (e.g. specifying legend_title here too) the + chosen value will be taken from the explicit arguments (e.g. legend_title + specified as an argument to this function not the value in this dictionary). + + Returns + ------- + fig : Figure + The figure object. + axis : Axes + The Axes object with the plot. + dataframe : pd.DataFrame + The DataFrame where each row represents the partition id and each column + represents the class. + + Examples + -------- + Visualize the label distribution resulting from DirichletPartitioner. + + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import DirichletPartitioner + >>> from flwr_datasets.visualization import plot_label_distributions + >>> + >>> fds = FederatedDataset( + >>> dataset="cifar10", + >>> partitioners={ + >>> "train": DirichletPartitioner( + >>> num_partitions=20, + >>> partition_by="label", + >>> alpha=0.3, + >>> min_partition_size=0, + >>> ), + >>> }, + >>> ) + >>> partitioner = fds.partitioners["train"] + >>> figure, axis, dataframe = plot_label_distributions( + >>> partitioner=partitioner, + >>> label_name="label", + >>> legend=True, + >>> verbose_labels=True, + >>> ) + + Alternatively you can visualize each partition in terms of fraction of the data + available on that partition instead of the absolute count + + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import DirichletPartitioner + >>> from flwr_datasets.visualization import plot_label_distributions + >>> + >>> fds = FederatedDataset( + >>> dataset="cifar10", + >>> partitioners={ + >>> "train": DirichletPartitioner( + >>> num_partitions=20, + >>> partition_by="label", + >>> alpha=0.3, + >>> min_partition_size=0, + >>> ), + >>> }, + >>> ) + >>> partitioner = fds.partitioners["train"] + >>> figure, axis, dataframe = plot_label_distributions( + >>> partitioner=partitioner, + >>> label_name="label", + >>> size_unit="percent", + >>> legend=True, + >>> verbose_labels=True, + >>> ) + >>> + + You can also visualize the data as a heatmap by changing the `plot_type` from + default "bar" to "heatmap" + + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import DirichletPartitioner + >>> from flwr_datasets.visualization import plot_label_distributions + >>> + >>> fds = FederatedDataset( + >>> dataset="cifar10", + >>> partitioners={ + >>> "train": DirichletPartitioner( + >>> num_partitions=20, + >>> partition_by="label", + >>> alpha=0.3, + >>> min_partition_size=0, + >>> ), + >>> }, + >>> ) + >>> partitioner = fds.partitioners["train"] + >>> figure, axis, dataframe = plot_label_distributions( + >>> partitioner=partitioner, + >>> label_name="label", + >>> size_unit="percent", + >>> plot_type="heatmap", + >>> legend=True, + >>> plot_kwargs={"annot": True}, + >>> ) + + You can also visualize the returned DataFrame in Jupyter Notebook + >>> dataframe.style.background_gradient(axis=None) + """ + _validate_parameters(plot_type, size_unit, partition_id_axis) + + if label_name not in partitioner.dataset.column_names: + raise ValueError( + f"The specified 'label_name': '{label_name}' is not present in the " + f"dataset. The dataset contains columns {partitioner.dataset.column_names}." + ) + + if max_num_partitions is None: + max_num_partitions = partitioner.num_partitions + else: + max_num_partitions = min(max_num_partitions, partitioner.num_partitions) + assert isinstance(max_num_partitions, int) + partitions = [partitioner.load_partition(i) for i in range(max_num_partitions)] + + partition = partitions[0] + try: + # Unique labels are needed to represent the correct count of each class + # (some of the classes can have zero samples that's why this + # adjustment is needed) + unique_labels = partition.features[label_name].str2int( + partition.features[label_name].names + ) + except AttributeError: # If the label_name is not formally a Label + unique_labels = partitioner.dataset.unique(label_name) + + partition_id_to_label_absolute_size = { + pid: compute_counts(partition[label_name], unique_labels) + for pid, partition in enumerate(partitions) + } + + dataframe = pd.DataFrame.from_dict( + partition_id_to_label_absolute_size, orient="index" + ) + dataframe.index.name = "Partition ID" + + if size_unit == "percent": + dataframe = dataframe.div(dataframe.sum(axis=1), axis=0) * 100.0 + + if verbose_labels: + # Adjust the column name values of the dataframe + # (these values are used for as labels in bar plot and columns/rows ticks + # in heatmap) + current_labels = dataframe.columns + try: + legend_names = partition.features[label_name].int2str( + [int(v) for v in current_labels] + ) + dataframe.columns = legend_names + except AttributeError: + warnings.warn( + "The verbose label names can not be established. " + "The column specified by 'label_name' needs to be of type " + "'ClassLabel'", + stacklevel=1, + ) + + if plot_type == "bar": + axis = _plot_bar( + dataframe, + axis, + figsize, + title, + cmap, + partition_id_axis, + size_unit, + legend, + legend_title, + plot_kwargs, + legend_kwargs, + ) + elif plot_type == "heatmap": + axis = _plot_heatmap( + dataframe, + axis, + figsize, + title, + cmap, + partition_id_axis, + size_unit, + legend, + legend_title, + plot_kwargs, + legend_kwargs, + ) + assert axis is not None + return axis.figure, axis, dataframe diff --git a/datasets/flwr_datasets/visualization/utils.py b/datasets/flwr_datasets/visualization/utils.py new file mode 100644 index 000000000000..c2f1846de20e --- /dev/null +++ b/datasets/flwr_datasets/visualization/utils.py @@ -0,0 +1,36 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Plotting utils.""" + + +from flwr_datasets.visualization.constants import AXIS_TYPES, PLOT_TYPES, SIZE_UNITS + + +def _validate_parameters( + plot_type: str, size_unit: str, partition_id_axis: str +) -> None: + if plot_type not in PLOT_TYPES: + raise ValueError( + f"Invalid plot_type: {plot_type}. Must be one of {PLOT_TYPES}." + ) + if size_unit not in SIZE_UNITS: + raise ValueError( + f"Invalid size_unit: {size_unit}. Must be one of {SIZE_UNITS}." + ) + if partition_id_axis not in AXIS_TYPES: + raise ValueError( + f"Invalid partition_id_axis: {partition_id_axis}. " + f"Must be one of {AXIS_TYPES}." + ) diff --git a/datasets/pyproject.toml b/datasets/pyproject.toml index 36c0aef5ec2c..f874d4f0ce51 100644 --- a/datasets/pyproject.toml +++ b/datasets/pyproject.toml @@ -59,6 +59,8 @@ pillow = { version = ">=6.2.1", optional = true } soundfile = { version = ">=0.12.1", optional = true } librosa = { version = ">=0.10.0.post2", optional = true } tqdm ="^4.66.1" +matplotlib = "^3.7.5" +seaborn = "^0.13.0" [tool.poetry.dev-dependencies] isort = "==5.13.2" From 24f3b44b69c57f69e6d7146aeef5bd49e438165e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 11:36:09 +0000 Subject: [PATCH 003/595] chore(deps): bump docker/login-action from 3.1.0 to 3.2.0 (#3542) Bumps [docker/login-action](https://github.com/docker/login-action) from 3.1.0 to 3.2.0. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/e92390c5fb421da1463c202d546fed0ec5c39f20...0d4c9c5ea7693da7b068278f7b52bda2a190a446) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Robert Steiner --- .github/workflows/_docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_docker-build.yml b/.github/workflows/_docker-build.yml index 608158fc0a5a..3508b1a7b3f6 100644 --- a/.github/workflows/_docker-build.yml +++ b/.github/workflows/_docker-build.yml @@ -79,7 +79,7 @@ jobs: uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to Docker Hub - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: username: ${{ secrets.dockerhub-user }} password: ${{ secrets.dockerhub-token }} @@ -134,7 +134,7 @@ jobs: uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Login to Docker Hub - uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 + uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 with: username: ${{ secrets.dockerhub-user }} password: ${{ secrets.dockerhub-token }} From c58c1b795bd8cbf2c0717f6c09423569e3de3b56 Mon Sep 17 00:00:00 2001 From: mohammadnaseri Date: Sat, 8 Jun 2024 11:10:00 +0100 Subject: [PATCH 004/595] docs(framework:skip) Add built-in mods to client doc (#3559) --- src/py/flwr/client/__init__.py | 1 + src/py/flwr/client/mod/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/client/__init__.py b/src/py/flwr/client/__init__.py index fd8647dbaf2e..b4da71302cb4 100644 --- a/src/py/flwr/client/__init__.py +++ b/src/py/flwr/client/__init__.py @@ -28,6 +28,7 @@ "Client", "ClientApp", "ClientFn", + "mod", "NumPyClient", "run_client_app", "run_supernode", diff --git a/src/py/flwr/client/mod/__init__.py b/src/py/flwr/client/mod/__init__.py index 1cd79fa944fe..1774e4b8ca0a 100644 --- a/src/py/flwr/client/mod/__init__.py +++ b/src/py/flwr/client/mod/__init__.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Mods.""" +"""Flower Built-in Mods.""" from .centraldp_mods import adaptiveclipping_mod, fixedclipping_mod From 353e9e8711da9bf508e6817d9f1792b0a2d8375b Mon Sep 17 00:00:00 2001 From: Daniel Nata Nugraha Date: Sat, 8 Jun 2024 13:02:43 +0200 Subject: [PATCH 005/595] break(framework) Merge gRPC-rere and REST SuperLink options (#3527) Co-authored-by: jafermarq Co-authored-by: Daniel J. Beutel --- e2e/test_driver.sh | 18 +++-- src/py/flwr/server/app.py | 156 ++++++++++++++++++++------------------ 2 files changed, 94 insertions(+), 80 deletions(-) diff --git a/e2e/test_driver.sh b/e2e/test_driver.sh index 7a50764ab0ca..373a2cf327a7 100755 --- a/e2e/test_driver.sh +++ b/e2e/test_driver.sh @@ -22,7 +22,8 @@ esac case "$2" in rest) - rest_arg="--rest" + rest_arg_superlink="--fleet-api-type rest" + rest_arg_supernode="--rest" server_address="http://localhost:9093" server_app_address="127.0.0.1:9091" db_arg="--database :flwr-in-memory-state:" @@ -31,7 +32,8 @@ case "$2" in client_auth_2="" ;; sqlite) - rest_arg="" + rest_arg_superlink="" + rest_arg_supernode="" server_address="127.0.0.1:9092" server_app_address="127.0.0.1:9091" db_arg="--database $(date +%s).db" @@ -41,7 +43,8 @@ case "$2" in ;; client-auth) ./generate.sh - rest_arg="" + rest_arg_superlink="" + rest_arg_supernode="" server_address="127.0.0.1:9092" server_app_address="127.0.0.1:9091" db_arg="--database :flwr-in-memory-state:" @@ -52,7 +55,8 @@ case "$2" in client_auth_2="--auth-supernode-private-key keys/client_credentials_2 --auth-supernode-public-key keys/client_credentials_2.pub" ;; *) - rest_arg="" + rest_arg_superlink="" + rest_arg_supernode="" server_address="127.0.0.1:9092" server_app_address="127.0.0.1:9091" db_arg="--database :flwr-in-memory-state:" @@ -62,15 +66,15 @@ case "$2" in ;; esac -timeout 2m flower-superlink $server_arg $db_arg $rest_arg $server_auth & +timeout 2m flower-superlink $server_arg $db_arg $rest_arg_superlink $server_auth & sl_pid=$! sleep 3 -timeout 2m flower-client-app client:app $client_arg $rest_arg --server $server_address $client_auth_1 & +timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --server $server_address $client_auth_1 & cl1_pid=$! sleep 3 -timeout 2m flower-client-app client:app $client_arg $rest_arg --server $server_address $client_auth_2 & +timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --server $server_address $client_auth_2 & cl2_pid=$! sleep 3 diff --git a/src/py/flwr/server/app.py b/src/py/flwr/server/app.py index 147ec5fb0f65..c050c983134b 100644 --- a/src/py/flwr/server/app.py +++ b/src/py/flwr/server/app.py @@ -230,6 +230,7 @@ def run_driver_api() -> None: grpc_server.wait_for_termination() +# pylint: disable=too-many-locals def run_fleet_api() -> None: """Run Flower server (Fleet API).""" log(INFO, "Starting Flower server (Fleet API)") @@ -248,6 +249,25 @@ def run_fleet_api() -> None: grpc_servers = [] bckg_threads = [] + address_arg = args.fleet_api_address + parsed_address = parse_address(address_arg) + if not parsed_address: + sys.exit(f"Fleet IP address ({address_arg}) cannot be parsed.") + host, port, is_v6 = parsed_address + address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}" + + num_workers = args.fleet_api_num_workers + if num_workers != 1: + log( + WARN, + "The Fleet API currently supports only 1 worker. " + "You have specified %d workers. " + "Support for multiple workers will be added in future releases. " + "Proceeding with a single worker.", + args.fleet_api_num_workers, + ) + num_workers = 1 + # Start Fleet API if args.fleet_api_type == TRANSPORT_TYPE_REST: if ( @@ -256,20 +276,19 @@ def run_fleet_api() -> None: and importlib.util.find_spec("uvicorn") ) is None: sys.exit(MISSING_EXTRA_REST) - address_arg = args.rest_fleet_api_address - parsed_address = parse_address(address_arg) - if not parsed_address: - sys.exit(f"Fleet IP address ({address_arg}) cannot be parsed.") - host, port, _ = parsed_address + + _, ssl_certfile, ssl_keyfile = ( + certificates if certificates is not None else (None, None, None) + ) fleet_thread = threading.Thread( target=_run_fleet_api_rest, args=( host, port, - args.ssl_keyfile, - args.ssl_certfile, + ssl_keyfile, + ssl_certfile, state_factory, - args.rest_fleet_api_workers, + num_workers, ), ) fleet_thread.start() @@ -314,11 +333,15 @@ def run_superlink() -> None: args = _parse_args_run_superlink().parse_args() # Parse IP address - parsed_address = parse_address(args.driver_api_address) - if not parsed_address: + parsed_driver_address = parse_address(args.driver_api_address) + if not parsed_driver_address: sys.exit(f"Driver IP address ({args.driver_api_address}) cannot be parsed.") - host, port, is_v6 = parsed_address - address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}" + driver_host, driver_port, driver_is_v6 = parsed_driver_address + driver_address = ( + f"[{driver_host}]:{driver_port}" + if driver_is_v6 + else f"{driver_host}:{driver_port}" + ) # Obtain certificates certificates = _try_obtain_certificates(args) @@ -328,13 +351,38 @@ def run_superlink() -> None: # Start Driver API driver_server: grpc.Server = run_driver_api_grpc( - address=address, + address=driver_address, state_factory=state_factory, certificates=certificates, ) grpc_servers = [driver_server] bckg_threads = [] + if not args.fleet_api_address: + args.fleet_api_address = ( + ADDRESS_FLEET_API_GRPC_RERE + if args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE + else ADDRESS_FLEET_API_REST + ) + parsed_fleet_address = parse_address(args.fleet_api_address) + if not parsed_fleet_address: + sys.exit(f"Fleet IP address ({args.fleet_api_address}) cannot be parsed.") + fleet_host, fleet_port, fleet_is_v6 = parsed_fleet_address + fleet_address = ( + f"[{fleet_host}]:{fleet_port}" if fleet_is_v6 else f"{fleet_host}:{fleet_port}" + ) + + num_workers = args.fleet_api_num_workers + if num_workers != 1: + log( + WARN, + "The Fleet API currently supports only 1 worker. " + "You have specified %d workers. " + "Support for multiple workers will be added in future releases. " + "Proceeding with a single worker.", + args.fleet_api_num_workers, + ) + num_workers = 1 # Start Fleet API if args.fleet_api_type == TRANSPORT_TYPE_REST: @@ -344,35 +392,25 @@ def run_superlink() -> None: and importlib.util.find_spec("uvicorn") ) is None: sys.exit(MISSING_EXTRA_REST) - address_arg = args.rest_fleet_api_address - parsed_address = parse_address(address_arg) + _, ssl_certfile, ssl_keyfile = ( certificates if certificates is not None else (None, None, None) ) - if not parsed_address: - sys.exit(f"Fleet IP address ({address_arg}) cannot be parsed.") - host, port, _ = parsed_address + fleet_thread = threading.Thread( target=_run_fleet_api_rest, args=( - host, - port, + fleet_host, + fleet_port, ssl_keyfile, ssl_certfile, state_factory, - args.rest_fleet_api_workers, + num_workers, ), ) fleet_thread.start() bckg_threads.append(fleet_thread) elif args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE: - address_arg = args.grpc_rere_fleet_api_address - parsed_address = parse_address(address_arg) - if not parsed_address: - sys.exit(f"Fleet IP address ({address_arg}) cannot be parsed.") - host, port, is_v6 = parsed_address - address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}" - maybe_keys = _try_setup_client_authentication(args, certificates) interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None if maybe_keys is not None: @@ -395,7 +433,7 @@ def run_superlink() -> None: interceptors = [AuthenticateServerInterceptor(state)] fleet_server = _run_fleet_api_grpc_rere( - address=address, + address=fleet_address, state_factory=state_factory, certificates=certificates, interceptors=interceptors, @@ -596,7 +634,7 @@ def _run_fleet_api_rest( ssl_keyfile: Optional[str], ssl_certfile: Optional[str], state_factory: StateFactory, - workers: int, + num_workers: int, ) -> None: """Run Driver API (REST-based).""" try: @@ -605,12 +643,7 @@ def _run_fleet_api_rest( from flwr.server.superlink.fleet.rest_rere.rest_api import app as fast_api_app except ModuleNotFoundError: sys.exit(MISSING_EXTRA_REST) - if workers != 1: - raise ValueError( - f"The supported number of workers for the Fleet API (REST server) is " - f"1. Instead given {workers}. The functionality of >1 workers will be " - f"added in the future releases." - ) + log(INFO, "Starting Flower REST server") # See: https://www.starlette.io/applications/#accessing-the-app-instance @@ -624,7 +657,7 @@ def _run_fleet_api_rest( access_log=True, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, - workers=workers, + workers=num_workers, ) @@ -732,50 +765,27 @@ def _add_args_common(parser: argparse.ArgumentParser) -> None: def _add_args_driver_api(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--driver-api-address", - help="Driver API (gRPC) server address (IPv4, IPv6, or a domain name)", + help="Driver API (gRPC) server address (IPv4, IPv6, or a domain name).", default=ADDRESS_DRIVER_API, ) def _add_args_fleet_api(parser: argparse.ArgumentParser) -> None: # Fleet API transport layer type - ex_group = parser.add_mutually_exclusive_group() - ex_group.add_argument( - "--grpc-rere", - action="store_const", - dest="fleet_api_type", - const=TRANSPORT_TYPE_GRPC_RERE, + parser.add_argument( + "--fleet-api-type", default=TRANSPORT_TYPE_GRPC_RERE, - help="Start a Fleet API server (gRPC-rere)", - ) - ex_group.add_argument( - "--rest", - action="store_const", - dest="fleet_api_type", - const=TRANSPORT_TYPE_REST, - help="Start a Fleet API server (REST, experimental)", - ) - - # Fleet API gRPC-rere options - grpc_rere_group = parser.add_argument_group( - "Fleet API (gRPC-rere) server options", "" - ) - grpc_rere_group.add_argument( - "--grpc-rere-fleet-api-address", - help="Fleet API (gRPC-rere) server address (IPv4, IPv6, or a domain name)", - default=ADDRESS_FLEET_API_GRPC_RERE, + type=str, + choices=[TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_REST], + help="Start a gRPC-rere or REST (experimental) Fleet API server.", ) - - # Fleet API REST options - rest_group = parser.add_argument_group("Fleet API (REST) server options", "") - rest_group.add_argument( - "--rest-fleet-api-address", - help="Fleet API (REST) server address (IPv4, IPv6, or a domain name)", - default=ADDRESS_FLEET_API_REST, + parser.add_argument( + "--fleet-api-address", + help="Fleet API server address (IPv4, IPv6, or a domain name).", ) - rest_group.add_argument( - "--rest-fleet-api-workers", - help="Set the number of concurrent workers for the Fleet API REST server.", - type=int, + parser.add_argument( + "--fleet-api-num-workers", default=1, + type=int, + help="Set the number of concurrent workers for the Fleet API server.", ) From 70844c4d5d27689e83300ffae49393b82f9391f9 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 8 Jun 2024 13:22:56 +0200 Subject: [PATCH 006/595] feat(framework) Deprecate `--server`, introduce `--superlink` to connect `ServerApp` and `SuperNode` to `SuperLink` (#3518) Co-authored-by: Daniel J. Beutel --- doc/source/how-to-authenticate-supernodes.rst | 2 +- doc/source/how-to-enable-ssl-connections.rst | 2 +- doc/source/how-to-run-flower-using-docker.rst | 13 +++---- doc/source/how-to-upgrade-to-flower-next.rst | 6 ++-- e2e/docker/compose.yaml | 4 +-- e2e/test_driver.sh | 6 ++-- src/py/flwr/client/supernode/app.py | 34 ++++++++++++++++--- src/py/flwr/server/run_serverapp.py | 34 ++++++++++++++++--- 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/doc/source/how-to-authenticate-supernodes.rst b/doc/source/how-to-authenticate-supernodes.rst index 9eb5e010ea4b..73987261b29f 100644 --- a/doc/source/how-to-authenticate-supernodes.rst +++ b/doc/source/how-to-authenticate-supernodes.rst @@ -59,7 +59,7 @@ Use the following terminal command to start an authenticated :code:`SuperNode`: flower-client-app client:app --root-certificates certificates/ca.crt - --server 127.0.0.1:9092 + --superlink 127.0.0.1:9092 --auth-supernode-private-key keys/client_credentials --auth-supernode-public-key keys/client_credentials.pub diff --git a/doc/source/how-to-enable-ssl-connections.rst b/doc/source/how-to-enable-ssl-connections.rst index 870f4b0f64c9..1828f4ed3258 100644 --- a/doc/source/how-to-enable-ssl-connections.rst +++ b/doc/source/how-to-enable-ssl-connections.rst @@ -59,7 +59,7 @@ Use the following terminal command to start a client (SuperNode) that uses the p flower-client-app client:app --root-certificates certificates/ca.crt - --server 127.0.0.1:9092 + --superlink 127.0.0.1:9092 When setting :code:`root_certificates`, the client expects a file path to PEM-encoded root certificates. diff --git a/doc/source/how-to-run-flower-using-docker.rst b/doc/source/how-to-run-flower-using-docker.rst index 375857b85b71..cffcd18129b5 100644 --- a/doc/source/how-to-run-flower-using-docker.rst +++ b/doc/source/how-to-run-flower-using-docker.rst @@ -245,7 +245,7 @@ Now that we have built the SuperNode image, we can finally run it. $ docker run --rm flwr_supernode:0.0.1 \ --insecure \ - --server 192.168.1.100:9092 + --superlink 192.168.1.100:9092 Let's break down each part of this command: @@ -261,7 +261,7 @@ Let's break down each part of this command: `SSL `__ when deploying to a production environment. -* | ``--server 192.168.1.100:9092``: This option specifies the address of the SuperLinks Fleet +* | ``--superlink 192.168.1.100:9092``: This option specifies the address of the SuperLinks Fleet | API to connect to. Remember to update it with your SuperLink IP. .. note:: @@ -288,8 +288,9 @@ certificate within the container. Use the ``--root-certificates`` flag when star .. code-block:: bash + $ docker run --rm --volume ./ca.crt:/app/ca.crt flwr_supernode:0.0.1 \ - --server 192.168.1.100:9092 \ + --superlink 192.168.1.100:9092 \ --root-certificates ca.crt Flower ServerApp @@ -361,7 +362,7 @@ Now that we have built the ServerApp image, we can finally run it. $ docker run --rm flwr_serverapp:0.0.1 \ --insecure \ - --server 192.168.1.100:9091 + --superlink 192.168.1.100:9091 Let's break down each part of this command: @@ -377,7 +378,7 @@ Let's break down each part of this command: `SSL `__ when deploying to a production environment. -* | ``--server 192.168.1.100:9091``: This option specifies the address of the SuperLinks Driver +* | ``--superlink 192.168.1.100:9091``: This option specifies the address of the SuperLinks Driver | API to connect to. Remember to update it with your SuperLink IP. .. note:: @@ -404,7 +405,7 @@ certificate within the container. Use the ``--root-certificates`` flags when sta .. code-block:: bash $ docker run --rm --volume ./ca.crt:/app/ca.crt flwr_serverapp:0.0.1 \ - --server 192.168.1.100:9091 \ + --superlink 192.168.1.100:9091 \ --root-certificates ca.crt Advanced Docker options diff --git a/doc/source/how-to-upgrade-to-flower-next.rst b/doc/source/how-to-upgrade-to-flower-next.rst index eebe894f56ec..a17756247566 100644 --- a/doc/source/how-to-upgrade-to-flower-next.rst +++ b/doc/source/how-to-upgrade-to-flower-next.rst @@ -185,17 +185,17 @@ Deployment # In a new terminal window, start a long-running secure SuperNode $ flower-client-app client:app \ --root-certificates \ - --server 127.0.0.1:9092 + --superlink 127.0.0.1:9092 # In another terminal window, start another long-running secure SuperNode (at least 2 SuperNodes are required) $ flower-client-app client:app \ --root-certificates \ - --server 127.0.0.1:9092 + --superlink 127.0.0.1:9092 # In yet another terminal window, run the ServerApp (this starts the actual training run) $ flower-server-app server:app \ --root-certificates \ - --server 127.0.0.1:9091 + --superlink 127.0.0.1:9091 Simulation in CLI ~~~~~~~~~~~~~~~~~ diff --git a/e2e/docker/compose.yaml b/e2e/docker/compose.yaml index 073ca9f60a57..c31bc81692f2 100644 --- a/e2e/docker/compose.yaml +++ b/e2e/docker/compose.yaml @@ -19,7 +19,7 @@ services: resources: limits: cpus: '2' - command: [ "--insecure", "--server", "superlink:9092" ] + command: [ "--insecure", "--superlink", "superlink:9092" ] depends_on: - superlink @@ -27,7 +27,7 @@ services: serverapp: build: dockerfile: serverapp.Dockerfile - command: [ "--insecure", "--server", "superlink:9091" ] + command: [ "--insecure", "--superlink", "superlink:9091" ] # enforce dependency for graceful execution depends_on: - superlink diff --git a/e2e/test_driver.sh b/e2e/test_driver.sh index 373a2cf327a7..e177863bab78 100755 --- a/e2e/test_driver.sh +++ b/e2e/test_driver.sh @@ -70,15 +70,15 @@ timeout 2m flower-superlink $server_arg $db_arg $rest_arg_superlink $server_auth sl_pid=$! sleep 3 -timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --server $server_address $client_auth_1 & +timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --superlink $server_address $client_auth_1 & cl1_pid=$! sleep 3 -timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --server $server_address $client_auth_2 & +timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --superlink $server_address $client_auth_2 & cl2_pid=$! sleep 3 -timeout 2m flower-server-app server:app $client_arg --dir $server_dir --server $server_app_address & +timeout 2m flower-server-app server:app $client_arg --dir $server_dir --superlink $server_app_address & pid=$! wait $pid diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index ac58e9aa4a81..336cd818d718 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -30,11 +30,13 @@ from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event from flwr.common.exit_handlers import register_exit_handlers -from flwr.common.logger import log +from flwr.common.logger import log, warn_deprecated_feature from flwr.common.object_ref import load_app, validate from ..app import _start_client_internal +ADDRESS_FLEET_API_GRPC_RERE = "0.0.0.0:9092" + def run_supernode() -> None: """Run Flower SuperNode.""" @@ -63,6 +65,23 @@ def run_client_app() -> None: args = _parse_args_run_client_app().parse_args() + if args.server != ADDRESS_FLEET_API_GRPC_RERE: + warn = "Passing flag --server is deprecated. Use --superlink instead." + warn_deprecated_feature(warn) + + if args.superlink != ADDRESS_FLEET_API_GRPC_RERE: + # if `--superlink` also passed, then + # warn user that this argument overrides what was passed with `--server` + log( + WARN, + "Both `--server` and `--superlink` were passed. " + "`--server` will be ignored. Connecting to the Superlink Fleet API " + "at %s.", + args.superlink, + ) + else: + args.superlink = args.server + root_certificates = _get_certificates(args) log( DEBUG, @@ -73,7 +92,7 @@ def run_client_app() -> None: authentication_keys = _try_setup_client_authentication(args) _start_client_internal( - server_address=args.server, + server_address=args.superlink, load_client_app_fn=load_fn, transport="rest" if args.rest else "grpc-rere", root_certificates=root_certificates, @@ -100,7 +119,7 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]: WARN, "Option `--insecure` was set. " "Starting insecure HTTP client connected to %s.", - args.server, + args.superlink, ) root_certificates = None else: @@ -114,7 +133,7 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]: DEBUG, "Starting secure HTTPS client connected to %s " "with the following certificates: %s.", - args.server, + args.superlink, cert_path, ) return root_certificates @@ -213,9 +232,14 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: ) parser.add_argument( "--server", - default="0.0.0.0:9092", + default=ADDRESS_FLEET_API_GRPC_RERE, help="Server address", ) + parser.add_argument( + "--superlink", + default=ADDRESS_FLEET_API_GRPC_RERE, + help="SuperLink Fleet API (gRPC-rere) address (IPv4, IPv6, or a domain name)", + ) parser.add_argument( "--max-retries", type=int, diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 9cc7974d34da..0879dd6054d0 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -22,12 +22,14 @@ from typing import Optional from flwr.common import Context, EventType, RecordSet, event -from flwr.common.logger import log, update_console_handler +from flwr.common.logger import log, update_console_handler, warn_deprecated_feature from flwr.common.object_ref import load_app from .driver import Driver, GrpcDriver from .server_app import LoadServerAppError, ServerApp +ADDRESS_DRIVER_API = "0.0.0.0:9091" + def run( driver: Driver, @@ -76,6 +78,23 @@ def run_server_app() -> None: args = _parse_args_run_server_app().parse_args() + if args.server != ADDRESS_DRIVER_API: + warn = "Passing flag --server is deprecated. Use --superlink instead." + warn_deprecated_feature(warn) + + if args.superlink != ADDRESS_DRIVER_API: + # if `--superlink` also passed, then + # warn user that this argument overrides what was passed with `--server` + log( + WARN, + "Both `--server` and `--superlink` were passed. " + "`--server` will be ignored. Connecting to the Superlink Driver API " + "at %s.", + args.superlink, + ) + else: + args.superlink = args.server + update_console_handler( level=DEBUG if args.verbose else INFO, timestamps=args.verbose, @@ -95,7 +114,7 @@ def run_server_app() -> None: WARN, "Option `--insecure` was set. " "Starting insecure HTTP client connected to %s.", - args.server, + args.superlink, ) root_certificates = None else: @@ -109,7 +128,7 @@ def run_server_app() -> None: DEBUG, "Starting secure HTTPS client connected to %s " "with the following certificates: %s.", - args.server, + args.superlink, cert_path, ) @@ -130,7 +149,7 @@ def run_server_app() -> None: # Initialize GrpcDriver driver = GrpcDriver( - driver_service_address=args.server, + driver_service_address=args.superlink, root_certificates=root_certificates, fab_id=args.fab_id, fab_version=args.fab_version, @@ -175,9 +194,14 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser: ) parser.add_argument( "--server", - default="0.0.0.0:9091", + default=ADDRESS_DRIVER_API, help="Server address", ) + parser.add_argument( + "--superlink", + default=ADDRESS_DRIVER_API, + help="SuperLink Driver API (gRPC-rere) address (IPv4, IPv6, or a domain name)", + ) parser.add_argument( "--dir", default="", From 018dd45142a2d2375e1bfe12489ab1208bfafebc Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 8 Jun 2024 16:23:21 +0200 Subject: [PATCH 007/595] feat(framework) Introduce `RunTracker` (#3561) Co-authored-by: Charles Beauville --- src/py/flwr/client/app.py | 65 +++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 20 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index d7c05d8afbb2..4e09c53c2b00 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -14,8 +14,10 @@ # ============================================================================== """Flower client app.""" +import signal import sys import time +from dataclasses import dataclass from logging import DEBUG, ERROR, INFO, WARN from typing import Callable, ContextManager, Optional, Tuple, Type, Union @@ -37,7 +39,7 @@ ) from flwr.common.logger import log, warn_deprecated_feature from flwr.common.message import Error -from flwr.common.retry_invoker import RetryInvoker, exponential +from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential from .grpc_client.connection import grpc_connection from .grpc_rere_client.connection import grpc_request_response @@ -263,6 +265,29 @@ def _load_client_app() -> ClientApp: transport, server_address ) + run_tracker = _RunTracker() + + def _on_sucess(retry_state: RetryState) -> None: + if retry_state.tries > 1: + log( + INFO, + "Connection successful after %.2f seconds and %s tries.", + retry_state.elapsed_time, + retry_state.tries, + ) + if run_tracker.create_node: + run_tracker.create_node() + + def _on_backoff(retry_state: RetryState) -> None: + if retry_state.tries == 1: + log(WARN, "Connection attempt failed, retrying...") + else: + log( + DEBUG, + "Connection attempt failed, retrying in %.2f seconds", + retry_state.actual_wait, + ) + retry_invoker = RetryInvoker( wait_gen_factory=exponential, recoverable_exceptions=connection_error_type, @@ -278,25 +303,8 @@ def _load_client_app() -> ClientApp: if retry_state.tries > 1 else None ), - on_success=lambda retry_state: ( - log( - INFO, - "Connection successful after %.2f seconds and %s tries.", - retry_state.elapsed_time, - retry_state.tries, - ) - if retry_state.tries > 1 - else None - ), - on_backoff=lambda retry_state: ( - log(WARN, "Connection attempt failed, retrying...") - if retry_state.tries == 1 - else log( - DEBUG, - "Connection attempt failed, retrying in %.2f seconds", - retry_state.actual_wait, - ) - ), + on_success=_on_sucess, + on_backoff=_on_backoff, ) node_state = NodeState() @@ -579,3 +587,20 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[ ) return connection, address, error_type + + +@dataclass +class _RunTracker: + create_node: Optional[Callable[[], None]] = None + interrupt: bool = False + + def register_signal_handler(self) -> None: + """Register handlers for exit signals.""" + + def signal_handler(sig, frame): # type: ignore + # pylint: disable=unused-argument + self.interrupt = True + raise StopIteration from None + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) From 111eecc6231f0e7010d6a42e01db38dc475cf019 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 9 Jun 2024 17:08:45 +0200 Subject: [PATCH 008/595] refactor(framework:skip) Add no-op indent level to `_start_client_internal` (#3564) --- src/py/flwr/client/app.py | 155 +++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 76 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 4e09c53c2b00..eb07a732439d 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -327,90 +327,93 @@ def _on_backoff(retry_state: RetryState) -> None: create_node() # pylint: disable=not-callable while True: - # Receive - message = receive() - if message is None: - time.sleep(3) # Wait for 3s before asking again - continue - - log(INFO, "") - if len(message.metadata.group_id) > 0: + if True: # pylint: disable=using-constant-test + # Receive + message = receive() + if message is None: + time.sleep(3) # Wait for 3s before asking again + continue + + log(INFO, "") + if len(message.metadata.group_id) > 0: + log( + INFO, + "[RUN %s, ROUND %s]", + message.metadata.run_id, + message.metadata.group_id, + ) log( INFO, - "[RUN %s, ROUND %s]", - message.metadata.run_id, - message.metadata.group_id, + "Received: %s message %s", + message.metadata.message_type, + message.metadata.message_id, ) - log( - INFO, - "Received: %s message %s", - message.metadata.message_type, - message.metadata.message_id, - ) - - # Handle control message - out_message, sleep_duration = handle_control_message(message) - if out_message: - send(out_message) - break - - # Register context for this run - node_state.register_context(run_id=message.metadata.run_id) - - # Retrieve context for this run - context = node_state.retrieve_context(run_id=message.metadata.run_id) - - # Create an error reply message that will never be used to prevent - # the used-before-assignment linting error - reply_message = message.create_error_reply( - error=Error(code=ErrorCode.UNKNOWN, reason="Unknown") - ) - - # Handle app loading and task message - try: - # Load ClientApp instance - client_app: ClientApp = load_client_app_fn() - - # Execute ClientApp - reply_message = client_app(message=message, context=context) - except Exception as ex: # pylint: disable=broad-exception-caught - - # Legacy grpc-bidi - if transport in ["grpc-bidi", None]: - log(ERROR, "Client raised an exception.", exc_info=ex) - # Raise exception, crash process - raise ex - - # Don't update/change NodeState - - e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION - # Reason example: ":<'division by zero'>" - reason = str(type(ex)) + ":<'" + str(ex) + "'>" - exc_entity = "ClientApp" - if isinstance(ex, LoadClientAppError): - reason = ( - "An exception was raised when attempting to load " - "`ClientApp`" - ) - e_code = ErrorCode.LOAD_CLIENT_APP_EXCEPTION - exc_entity = "SuperNode" - log(ERROR, "%s raised an exception", exc_entity, exc_info=ex) + # Handle control message + out_message, sleep_duration = handle_control_message(message) + if out_message: + send(out_message) + break - # Create error message - reply_message = message.create_error_reply( - error=Error(code=e_code, reason=reason) + # Register context for this run + node_state.register_context(run_id=message.metadata.run_id) + + # Retrieve context for this run + context = node_state.retrieve_context( + run_id=message.metadata.run_id ) - else: - # No exception, update node state - node_state.update_context( - run_id=message.metadata.run_id, - context=context, + + # Create an error reply message that will never be used to prevent + # the used-before-assignment linting error + reply_message = message.create_error_reply( + error=Error(code=ErrorCode.UNKNOWN, reason="Unknown") ) - # Send - send(reply_message) - log(INFO, "Sent reply") + # Handle app loading and task message + try: + # Load ClientApp instance + client_app: ClientApp = load_client_app_fn() + + # Execute ClientApp + reply_message = client_app(message=message, context=context) + except Exception as ex: # pylint: disable=broad-exception-caught + + # Legacy grpc-bidi + if transport in ["grpc-bidi", None]: + log(ERROR, "Client raised an exception.", exc_info=ex) + # Raise exception, crash process + raise ex + + # Don't update/change NodeState + + e_code = ErrorCode.CLIENT_APP_RAISED_EXCEPTION + # Ex fmt: ":<'division by zero'>" + reason = str(type(ex)) + ":<'" + str(ex) + "'>" + exc_entity = "ClientApp" + if isinstance(ex, LoadClientAppError): + reason = ( + "An exception was raised when attempting to load " + "`ClientApp`" + ) + e_code = ErrorCode.LOAD_CLIENT_APP_EXCEPTION + exc_entity = "SuperNode" + + log(ERROR, "%s raised an exception", exc_entity, exc_info=ex) + + # Create error message + reply_message = message.create_error_reply( + error=Error(code=e_code, reason=reason) + ) + else: + # No exception, update node state + node_state.update_context( + run_id=message.metadata.run_id, + context=context, + ) + + # Send + send(reply_message) + log(INFO, "Sent reply") # Unregister node if delete_node is not None: From 8d08e12c4caf3de02fcad57642e66f6da77b7a80 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 9 Jun 2024 17:26:06 +0200 Subject: [PATCH 009/595] fix(framework:skip) Remove create_node call on reconnect (#3566) --- src/py/flwr/client/app.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index eb07a732439d..7085df418e76 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -266,6 +266,7 @@ def _load_client_app() -> ClientApp: ) run_tracker = _RunTracker() + _ = run_tracker def _on_sucess(retry_state: RetryState) -> None: if retry_state.tries > 1: @@ -275,8 +276,6 @@ def _on_sucess(retry_state: RetryState) -> None: retry_state.elapsed_time, retry_state.tries, ) - if run_tracker.create_node: - run_tracker.create_node() def _on_backoff(retry_state: RetryState) -> None: if retry_state.tries == 1: @@ -594,7 +593,6 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[ @dataclass class _RunTracker: - create_node: Optional[Callable[[], None]] = None interrupt: bool = False def register_signal_handler(self) -> None: From e182983e25130d777f5897b077d32a47a7274e09 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 10 Jun 2024 11:34:32 +0200 Subject: [PATCH 010/595] feat(framework) Allow clients to exit gracefully (#3090) Co-authored-by: Heng Pan Co-authored-by: Javier --- src/py/flwr/client/app.py | 28 +++++++++++++------ .../client/grpc_rere_client/connection.py | 2 -- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 7085df418e76..7294600288aa 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -265,10 +265,10 @@ def _load_client_app() -> ClientApp: transport, server_address ) - run_tracker = _RunTracker() - _ = run_tracker + app_state_tracker = _AppStateTracker() def _on_sucess(retry_state: RetryState) -> None: + app_state_tracker.is_connected = True if retry_state.tries > 1: log( INFO, @@ -278,6 +278,7 @@ def _on_sucess(retry_state: RetryState) -> None: ) def _on_backoff(retry_state: RetryState) -> None: + app_state_tracker.is_connected = False if retry_state.tries == 1: log(WARN, "Connection attempt failed, retrying...") else: @@ -308,7 +309,7 @@ def _on_backoff(retry_state: RetryState) -> None: node_state = NodeState() - while True: + while not app_state_tracker.interrupt: sleep_duration: int = 0 with connection( address, @@ -325,8 +326,9 @@ def _on_backoff(retry_state: RetryState) -> None: if create_node is not None: create_node() # pylint: disable=not-callable - while True: - if True: # pylint: disable=using-constant-test + app_state_tracker.register_signal_handler() + while not app_state_tracker.interrupt: + try: # Receive message = receive() if message is None: @@ -397,7 +399,10 @@ def _on_backoff(retry_state: RetryState) -> None: e_code = ErrorCode.LOAD_CLIENT_APP_EXCEPTION exc_entity = "SuperNode" - log(ERROR, "%s raised an exception", exc_entity, exc_info=ex) + if not app_state_tracker.interrupt: + log( + ERROR, "%s raised an exception", exc_entity, exc_info=ex + ) # Create error message reply_message = message.create_error_reply( @@ -414,13 +419,19 @@ def _on_backoff(retry_state: RetryState) -> None: send(reply_message) log(INFO, "Sent reply") + except StopIteration: + sleep_duration = 0 + break + # Unregister node - if delete_node is not None: + if delete_node is not None and app_state_tracker.is_connected: delete_node() # pylint: disable=not-callable if sleep_duration == 0: log(INFO, "Disconnect and shut down") + del app_state_tracker break + # Sleep and reconnect afterwards log( INFO, @@ -592,8 +603,9 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[ @dataclass -class _RunTracker: +class _AppStateTracker: interrupt: bool = False + is_connected: bool = False def register_signal_handler(self) -> None: """Register handlers for exit signals.""" diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index 8ef8e7ebf62a..9579d5830165 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -193,8 +193,6 @@ def delete_node() -> None: # Stop the ping-loop thread ping_stop_event.set() - if ping_thread is not None: - ping_thread.join() # Call FleetAPI delete_node_request = DeleteNodeRequest(node=node) From 578da36fd4352e749c9a07dbe90dc08c7fcdc04b Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Mon, 10 Jun 2024 11:40:18 +0200 Subject: [PATCH 011/595] docs(framework) Update documentation dependencies (#3565) --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a045987367f6..1350cb7ee03c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,7 @@ jupyterlab = "==4.0.12" rope = "==1.11.0" semver = "==3.0.2" sphinx = "==6.2.1" -sphinx-intl = "==2.1.0" +sphinx-intl = "==2.2.0" myst-parser = "==1.0.0" sphinx-design = "==0.5.0" sphinx-copybutton = "==0.5.2" @@ -113,7 +113,7 @@ sphinxcontrib-mermaid = "==0.9.2" sphinxcontrib-youtube = "==1.4.1" furo = "==2023.9.10" sphinx-reredirects = "==0.1.3" -nbsphinx = "==0.9.3" +nbsphinx = "==0.9.4" nbstripout = "==0.6.1" ruff = "==0.1.9" sphinx-argparse = "==0.4.0" From 5b730571c6b53606d9c6062bf66a7ae7dfeb4a70 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Mon, 10 Jun 2024 11:55:41 +0200 Subject: [PATCH 012/595] docs(framework) Remove 'Creating New Messages' docs page (#3563) --- doc/source/conf.py | 3 +- ...contributor-how-to-create-new-messages.rst | 153 ------------------ doc/source/index.rst | 1 - 3 files changed, 2 insertions(+), 155 deletions(-) delete mode 100644 doc/source/contributor-how-to-create-new-messages.rst diff --git a/doc/source/conf.py b/doc/source/conf.py index feb173c0efa8..174465c153b7 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -248,7 +248,8 @@ def find_test_modules(package_path): "quickstart-mxnet": "index.html", "tutorial-quickstart-mxnet": "index.html", "example-mxnet-walk-through": "index.html", - "ref-api/flwr.simulation.run_simulation_from_cli.html": "index.html", + "ref-api/flwr.simulation.run_simulation_from_cli": "index.html", + "contributor-how-to-create-new-messages": "index.html", } # -- Options for HTML output ------------------------------------------------- diff --git a/doc/source/contributor-how-to-create-new-messages.rst b/doc/source/contributor-how-to-create-new-messages.rst deleted file mode 100644 index 3f1849bdce47..000000000000 --- a/doc/source/contributor-how-to-create-new-messages.rst +++ /dev/null @@ -1,153 +0,0 @@ -Creating New Messages -===================== - -This is a simple guide for creating a new type of message between the server and clients in Flower. - -Let's suppose we have the following example functions in :code:`server.py` and :code:`numpy_client.py`... - -Server's side: - -.. code-block:: python - - def example_request(self, client: ClientProxy) -> Tuple[str, int]: - question = "Could you find the sum of the list, Bob?" - l = [1, 2, 3] - return client.request(question, l) - -Client's side: - -.. code-block:: python - - def example_response(self, question: str, l: List[int]) -> Tuple[str, int]: - response = "Here you go Alice!" - answer = sum(question) - return response, answer - -Let's now see what we need to implement in order to get this simple function between the server and client to work! - - -Message Types for Protocol Buffers ----------------------------------- - -The first thing we need to do is to define a message type for the RPC system in :code:`transport.proto`. -Note that we have to do it for both the request and response messages. For more details on the syntax of proto3, please see the `official documentation `_. - -Within the :code:`ServerMessage` block: - -.. code-block:: proto - - message ExampleIns{ - string question=1; - repeated int64 l=2; - } - oneof msg { - ReconnectIns reconnect_ins = 1; - GetPropertiesIns get_properties_ins = 2; - GetParametersIns get_parameters_ins = 3; - FitIns fit_ins = 4; - EvaluateIns evaluate_ins = 5; - ExampleIns example_ins = 6; - } - -Within the ClientMessage block: - -.. code-block:: proto - - message ExampleRes{ - string response = 1; - int64 answer = 2; - } - - oneof msg { - DisconnectRes disconnect_res = 1; - GetPropertiesRes get_properties_res = 2; - GetParametersRes get_parameters_res = 3; - FitRes fit_res = 4; - EvaluateRes evaluate_res = 5; - ExampleRes examples_res = 6; - } - -Make sure to also add a field of the newly created message type in :code:`oneof msg`. - -Once that is done, we will compile the file with: - -.. code-block:: shell - - $ python -m flwr_tool.protoc - -If it compiles successfully, you should see the following message: - -.. code-block:: shell - - Writing mypy to flwr/proto/transport_pb2.pyi - Writing mypy to flwr/proto/transport_pb2_grpc.pyi - - -Serialization and Deserialization Functions --------------------------------------------- - -Our next step is to add functions to serialize and deserialize Python datatypes to or from our defined RPC message types. You should add these functions in :code:`serde.py`. - -The four functions: - -.. code-block:: python - - def example_msg_to_proto(question: str, l: List[int]) -> ServerMessage.ExampleIns: - return ServerMessage.ExampleIns(question=question, l=l) - - - def example_msg_from_proto(msg: ServerMessage.ExampleIns) -> Tuple[str, List[int]]: - return msg.question, msg.l - - - def example_res_to_proto(response: str, answer: int) -> ClientMessage.ExampleRes: - return ClientMessage.ExampleRes(response=response, answer=answer) - - - def example_res_from_proto(res: ClientMessage.ExampleRes) -> Tuple[str, int]: - return res.response, res.answer - - -Sending the Message from the Server ------------------------------------ - -Now write the request function in your Client Proxy class (e.g., :code:`grpc_client_proxy.py`) using the serde functions you just created: - -.. code-block:: python - - def request(self, question: str, l: List[int]) -> Tuple[str, int]: - request_msg = serde.example_msg_to_proto(question, l) - client_msg: ClientMessage = self.bridge.request( - ServerMessage(example_ins=request_msg) - ) - response, answer = serde.example_res_from_proto(client_msg.examples_res) - return response, answer - - -Receiving the Message by the Client ------------------------------------ - -Last step! Modify the code in :code:`message_handler.py` to check the field of your message and call the :code:`example_response` function. Remember to use the serde functions! - -Within the handle function: - -.. code-block:: python - - if server_msg.HasField("example_ins"): - return _example_response(client, server_msg.example_ins), 0, True - -And add a new function: - -.. code-block:: python - - def _example_response(client: Client, msg: ServerMessage.ExampleIns) -> ClientMessage: - question,l = serde.evaluate_ins_from_proto(msg) - response, answer = client.example_response(question,l) - example_res = serde.example_res_to_proto(response,answer) - return ClientMessage(examples_res=example_res) - -Hopefully, when you run your program you will get the intended result! - -.. code-block:: shell - - ('Here you go Alice!', 6) diff --git a/doc/source/index.rst b/doc/source/index.rst index df41d9d4ccb0..f62c5ebf4786 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -164,7 +164,6 @@ The Flower community welcomes contributions. The following docs are intended to contributor-how-to-install-development-versions contributor-how-to-set-up-a-virtual-env contributor-how-to-develop-in-vscode-dev-containers - contributor-how-to-create-new-messages contributor-how-to-write-documentation contributor-how-to-release-flower contributor-how-to-contribute-translations From d917bd41b2a0db927745a19bf0154c32ba5fdd07 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 10 Jun 2024 21:29:32 +0200 Subject: [PATCH 013/595] docs(framework) Add Flower 1.9 changelog (#3567) Co-authored-by: Daniel J. Beutel --- doc/source/ref-changelog.md | 96 ++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/doc/source/ref-changelog.md b/doc/source/ref-changelog.md index c742b8cd9cbe..58fc8b4f69b1 100644 --- a/doc/source/ref-changelog.md +++ b/doc/source/ref-changelog.md @@ -1,12 +1,104 @@ # Changelog -## Unreleased +## v1.9.0 (2024-06-10) + +### Thanks to our contributors + +We would like to give our special thanks to all the contributors who made the new version of Flower possible (in `git shortlog` order): + +`Adam Narozniak`, `Charles Beauville`, `Chong Shen Ng`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Heng Pan`, `Javier`, `Mahdi Beitollahi`, `Robert Steiner`, `Taner Topal`, `Yan Gao`, `bapic`, `mohammadnaseri` ### What's new? +- **Introduce built-in authentication (preview)** ([#2946](https://github.com/adap/flower/pull/2946), [#3388](https://github.com/adap/flower/pull/3388), [#2948](https://github.com/adap/flower/pull/2948), [#2917](https://github.com/adap/flower/pull/2917), [#3386](https://github.com/adap/flower/pull/3386), [#3308](https://github.com/adap/flower/pull/3308), [#3001](https://github.com/adap/flower/pull/3001), [#3409](https://github.com/adap/flower/pull/3409), [#2999](https://github.com/adap/flower/pull/2999), [#2979](https://github.com/adap/flower/pull/2979), [#3389](https://github.com/adap/flower/pull/3389), [#3503](https://github.com/adap/flower/pull/3503), [#3366](https://github.com/adap/flower/pull/3366), [#3357](https://github.com/adap/flower/pull/3357)) + + Flower 1.9 introduces the first build-in version of client node authentication. In previous releases, users often wrote glue code to connect Flower to external authentication systems. With this release, the SuperLink can authenticate SuperNodes using a built-in authentication system. A new [how-to guide](https://flower.ai/docs/framework/how-to-authenticate-supernodes.html) and a new [code example](https://github.com/adap/flower/tree/main/examples/flower-authentication) help you to get started. + + This is the first preview release of the Flower-native authentication system. Many additional features are on the roadmap for upcoming Flower releases - stay tuned. + +- **Introduce end-to-end Docker support** ([#3483](https://github.com/adap/flower/pull/3483), [#3266](https://github.com/adap/flower/pull/3266), [#3390](https://github.com/adap/flower/pull/3390), [#3283](https://github.com/adap/flower/pull/3283), [#3285](https://github.com/adap/flower/pull/3285), [#3391](https://github.com/adap/flower/pull/3391), [#3403](https://github.com/adap/flower/pull/3403), [#3458](https://github.com/adap/flower/pull/3458), [#3533](https://github.com/adap/flower/pull/3533), [#3453](https://github.com/adap/flower/pull/3453), [#3486](https://github.com/adap/flower/pull/3486), [#3290](https://github.com/adap/flower/pull/3290)) + + Full Flower Next Docker support is here! With the release of Flower 1.9, Flower provides stable Docker images for the Flower SuperLink, the Flower SuperNode, and the Flower `ServerApp`. This set of images enables you to run all Flower components in Docker. Check out the new [how-to guide](https://flower.ai/docs/framework/how-to-run-flower-using-docker.html) to get stated. + +- **Re-architect Flower Next simulation engine** ([#3307](https://github.com/adap/flower/pull/3307), [#3355](https://github.com/adap/flower/pull/3355), [#3272](https://github.com/adap/flower/pull/3272), [#3273](https://github.com/adap/flower/pull/3273), [#3417](https://github.com/adap/flower/pull/3417), [#3281](https://github.com/adap/flower/pull/3281), [#3343](https://github.com/adap/flower/pull/3343), [#3326](https://github.com/adap/flower/pull/3326)) + + Flower Next simulations now use a new in-memory `Driver` that improves the reliability of simulations, especially in notebook environments. This is a significant step towards a complete overhaul of the Flower Next simulation architecture. + +- **Upgrade simulation engine** ([#3354](https://github.com/adap/flower/pull/3354), [#3378](https://github.com/adap/flower/pull/3378), [#3262](https://github.com/adap/flower/pull/3262), [#3435](https://github.com/adap/flower/pull/3435), [#3501](https://github.com/adap/flower/pull/3501), [#3482](https://github.com/adap/flower/pull/3482), [#3494](https://github.com/adap/flower/pull/3494)) + + The Flower Next simulation engine comes with improved and configurable logging. The Ray-based simulation backend in Flower 1.9 was updated to use Ray 2.10. + +- **Introduce FedPFT baseline** ([#3268](https://github.com/adap/flower/pull/3268)) + + FedPFT allows you to perform one-shot Federated Learning by leveraging widely available foundational models, dramatically reducing communication costs while delivering high performing models. This is work led by Mahdi Beitollahi from Huawei Noah's Ark Lab (Montreal, Canada). Read all the details in their paper: "Parametric Feature Transfer: One-shot Federated Learning with Foundation Models" ([arxiv](https://arxiv.org/abs/2402.01862)) + +- **Launch additional** `flwr new` **templates for Apple MLX, Hugging Face Transformers, scikit-learn and TensorFlow** ([#3291](https://github.com/adap/flower/pull/3291), [#3139](https://github.com/adap/flower/pull/3139), [#3284](https://github.com/adap/flower/pull/3284), [#3251](https://github.com/adap/flower/pull/3251), [#3376](https://github.com/adap/flower/pull/3376), [#3287](https://github.com/adap/flower/pull/3287)) + + The `flwr` CLI's `flwr new` command is starting to become everone's favorite way of creating new Flower projects. This release introduces additional `flwr new` templates for Apple MLX, Hugging Face Transformers, scikit-learn and TensorFlow. In addition to that, existing templates also received updates. + +- **Refine** `RecordSet` **API** ([#3209](https://github.com/adap/flower/pull/3209), [#3331](https://github.com/adap/flower/pull/3331), [#3334](https://github.com/adap/flower/pull/3334), [#3335](https://github.com/adap/flower/pull/3335), [#3375](https://github.com/adap/flower/pull/3375), [#3368](https://github.com/adap/flower/pull/3368)) + + `RecordSet` is part of the Flower Next low-level API preview release. In Flower 1.9, `RecordSet` received a number of usability improvements that make it easier to build `RecordSet`-based `ServerApp`s and `ClientApp`s. + +- **Beautify logging** ([#3379](https://github.com/adap/flower/pull/3379), [#3430](https://github.com/adap/flower/pull/3430), [#3461](https://github.com/adap/flower/pull/3461), [#3360](https://github.com/adap/flower/pull/3360), [#3433](https://github.com/adap/flower/pull/3433)) + + Logs received a substantial update. Not only are logs now much nicer to look at, but they are also more configurable. + +- **Improve reliability** ([#3564](https://github.com/adap/flower/pull/3564), [#3561](https://github.com/adap/flower/pull/3561), [#3566](https://github.com/adap/flower/pull/3566), [#3462](https://github.com/adap/flower/pull/3462), [#3225](https://github.com/adap/flower/pull/3225), [#3514](https://github.com/adap/flower/pull/3514), [#3535](https://github.com/adap/flower/pull/3535), [#3372](https://github.com/adap/flower/pull/3372)) + + Flower 1.9 includes reliability improvements across many parts of the system. One example is a much improved SuperNode shutdown procedure. + +- **Update Swift and C++ SDKs** ([#3321](https://github.com/adap/flower/pull/3321), [#2763](https://github.com/adap/flower/pull/2763)) + + In the C++ SDK, communication-related code is now separate from main client logic. A new abstract class `Communicator` has been introduced alongside a gRPC implementation of it. + +- **Improve testing, tooling and CI/CD infrastructure** ([#3294](https://github.com/adap/flower/pull/3294), [#3282](https://github.com/adap/flower/pull/3282), [#3311](https://github.com/adap/flower/pull/3311), [#2878](https://github.com/adap/flower/pull/2878), [#3333](https://github.com/adap/flower/pull/3333), [#3255](https://github.com/adap/flower/pull/3255), [#3349](https://github.com/adap/flower/pull/3349), [#3400](https://github.com/adap/flower/pull/3400), [#3401](https://github.com/adap/flower/pull/3401), [#3399](https://github.com/adap/flower/pull/3399), [#3346](https://github.com/adap/flower/pull/3346), [#3398](https://github.com/adap/flower/pull/3398), [#3397](https://github.com/adap/flower/pull/3397), [#3347](https://github.com/adap/flower/pull/3347), [#3502](https://github.com/adap/flower/pull/3502), [#3387](https://github.com/adap/flower/pull/3387), [#3542](https://github.com/adap/flower/pull/3542), [#3396](https://github.com/adap/flower/pull/3396), [#3496](https://github.com/adap/flower/pull/3496), [#3465](https://github.com/adap/flower/pull/3465), [#3473](https://github.com/adap/flower/pull/3473), [#3484](https://github.com/adap/flower/pull/3484), [#3521](https://github.com/adap/flower/pull/3521), [#3363](https://github.com/adap/flower/pull/3363), [#3497](https://github.com/adap/flower/pull/3497), [#3464](https://github.com/adap/flower/pull/3464), [#3495](https://github.com/adap/flower/pull/3495), [#3478](https://github.com/adap/flower/pull/3478), [#3271](https://github.com/adap/flower/pull/3271)) + + As always, the Flower tooling, testing, and CI/CD infrastructure has received many updates. + +- **Improve documentation** ([#3530](https://github.com/adap/flower/pull/3530), [#3539](https://github.com/adap/flower/pull/3539), [#3425](https://github.com/adap/flower/pull/3425), [#3520](https://github.com/adap/flower/pull/3520), [#3286](https://github.com/adap/flower/pull/3286), [#3516](https://github.com/adap/flower/pull/3516), [#3523](https://github.com/adap/flower/pull/3523), [#3545](https://github.com/adap/flower/pull/3545), [#3498](https://github.com/adap/flower/pull/3498), [#3439](https://github.com/adap/flower/pull/3439), [#3440](https://github.com/adap/flower/pull/3440), [#3382](https://github.com/adap/flower/pull/3382), [#3559](https://github.com/adap/flower/pull/3559), [#3432](https://github.com/adap/flower/pull/3432), [#3278](https://github.com/adap/flower/pull/3278), [#3371](https://github.com/adap/flower/pull/3371), [#3519](https://github.com/adap/flower/pull/3519), [#3267](https://github.com/adap/flower/pull/3267), [#3204](https://github.com/adap/flower/pull/3204), [#3274](https://github.com/adap/flower/pull/3274)) + + As always, the Flower documentation has received many updates. Notable new pages include: + + - [How-to upgrate to Flower Next (Flower Next migration guide)](https://flower.ai/docs/framework/how-to-upgrade-to-flower-next.html) + + - [How-to run Flower using Docker](https://flower.ai/docs/framework/how-to-run-flower-using-docker.html) + + - [Flower Mods reference](https://flower.ai/docs/framework/ref-api/flwr.client.mod.html#module-flwr.client.mod) + +- **General updates to Flower Examples** ([#3205](https://github.com/adap/flower/pull/3205), [#3226](https://github.com/adap/flower/pull/3226), [#3211](https://github.com/adap/flower/pull/3211), [#3252](https://github.com/adap/flower/pull/3252), [#3427](https://github.com/adap/flower/pull/3427), [#3410](https://github.com/adap/flower/pull/3410), [#3426](https://github.com/adap/flower/pull/3426), [#3228](https://github.com/adap/flower/pull/3228), [#3342](https://github.com/adap/flower/pull/3342), [#3200](https://github.com/adap/flower/pull/3200), [#3202](https://github.com/adap/flower/pull/3202), [#3394](https://github.com/adap/flower/pull/3394), [#3488](https://github.com/adap/flower/pull/3488), [#3329](https://github.com/adap/flower/pull/3329), [#3526](https://github.com/adap/flower/pull/3526), [#3392](https://github.com/adap/flower/pull/3392), [#3474](https://github.com/adap/flower/pull/3474), [#3269](https://github.com/adap/flower/pull/3269)) + + As always, Flower code examples have received many updates. + +- **General improvements** ([#3532](https://github.com/adap/flower/pull/3532), [#3318](https://github.com/adap/flower/pull/3318), [#3565](https://github.com/adap/flower/pull/3565), [#3296](https://github.com/adap/flower/pull/3296), [#3305](https://github.com/adap/flower/pull/3305), [#3246](https://github.com/adap/flower/pull/3246), [#3224](https://github.com/adap/flower/pull/3224), [#3475](https://github.com/adap/flower/pull/3475), [#3297](https://github.com/adap/flower/pull/3297), [#3317](https://github.com/adap/flower/pull/3317), [#3429](https://github.com/adap/flower/pull/3429), [#3196](https://github.com/adap/flower/pull/3196), [#3534](https://github.com/adap/flower/pull/3534), [#3240](https://github.com/adap/flower/pull/3240), [#3365](https://github.com/adap/flower/pull/3365), [#3407](https://github.com/adap/flower/pull/3407), [#3563](https://github.com/adap/flower/pull/3563), [#3344](https://github.com/adap/flower/pull/3344), [#3330](https://github.com/adap/flower/pull/3330), [#3436](https://github.com/adap/flower/pull/3436), [#3300](https://github.com/adap/flower/pull/3300), [#3327](https://github.com/adap/flower/pull/3327), [#3254](https://github.com/adap/flower/pull/3254), [#3253](https://github.com/adap/flower/pull/3253), [#3419](https://github.com/adap/flower/pull/3419), [#3289](https://github.com/adap/flower/pull/3289), [#3208](https://github.com/adap/flower/pull/3208), [#3245](https://github.com/adap/flower/pull/3245), [#3319](https://github.com/adap/flower/pull/3319), [#3203](https://github.com/adap/flower/pull/3203), [#3423](https://github.com/adap/flower/pull/3423), [#3352](https://github.com/adap/flower/pull/3352), [#3292](https://github.com/adap/flower/pull/3292), [#3261](https://github.com/adap/flower/pull/3261)) + +### Deprecations + +- **Deprecate Python 3.8 support** + + Python 3.8 will stop receiving security fixes in [October 2024](https://devguide.python.org/versions/). Support for Python 3.8 is now deprecated and will be removed in an upcoming release. + +- **Deprecate (experimental)** `flower-driver-api` **and** `flower-fleet-api` ([#3416](https://github.com/adap/flower/pull/3416), [#3420](https://github.com/adap/flower/pull/3420)) + + Flower 1.9 deprecates the two (experimental) commands `flower-driver-api` and `flower-fleet-api`. Both commands will be removed in an upcoming release. Use `flower-superlink` instead. + +- **Deprecate** `--server` **in favor of** `--superlink` ([#3518](https://github.com/adap/flower/pull/3518)) + + The commands `flower-server-app` and `flower-client-app` should use `--superlink` instead of the now deprecated `--server`. Support for `--server` will be removed in a future release. + ### Incompatible changes -None +- **Replace** `flower-superlink` **CLI option** `--certificates` **with** `--ssl-ca-certfile` **,** `--ssl-certfile` **and** `--ssl-keyfile` ([#3512](https://github.com/adap/flower/pull/3512), [#3408](https://github.com/adap/flower/pull/3408)) + + SSL-related `flower-superlink` CLI arguments were restructured in an incompatible way. Instead of passing a single `--certificates` flag with three values, you now need to pass three flags (`--ssl-ca-certfile`, `--ssl-certfile` and `--ssl-keyfile`) with one value each. Check out the [SSL connections](https://flower.ai/docs/framework/how-to-enable-ssl-connections.html) documentation page for details. + +- **Remove SuperLink** `--vce` **option** ([#3513](https://github.com/adap/flower/pull/3513)) + + Instead of separately starting a SuperLink and a `ServerApp` for simulation, simulations must now be started using the single `flower-simulation` command. + +- **Merge** `--grpc-rere` **and** `--rest` **SuperLink options** ([#3527](https://github.com/adap/flower/pull/3527)) + + To simplify the usage of `flower-superlink`, previously separate sets of CLI options for gRPC and REST were merged into one unified set of options. Consult the [Flower CLI reference documentation](https://flower.ai/docs/framework/ref-api-cli.html) for details. ## v1.8.0 (2024-04-03) From 04ce922560253ec2764e41eaca4f8b8313a6a29f Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 10 Jun 2024 23:15:43 +0200 Subject: [PATCH 014/595] refactor(*:skip) Bump development version to Flower 1.10.0 (#3571) Co-authored-by: Daniel J. Beutel --- baselines/doc/source/conf.py | 2 +- dev/build-docker-image-matrix.py | 2 +- doc/source/conf.py | 2 +- doc/source/how-to-install-flower.rst | 2 +- examples/doc/source/conf.py | 2 +- pyproject.toml | 2 +- src/py/flwr/cli/config_utils_test.py | 8 ++++---- src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl | 2 +- src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl | 2 +- src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl | 2 +- .../flwr/cli/new/templates/app/pyproject.numpy.toml.tpl | 2 +- .../flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl | 2 +- .../flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl | 2 +- .../cli/new/templates/app/pyproject.tensorflow.toml.tpl | 2 +- 14 files changed, 17 insertions(+), 17 deletions(-) diff --git a/baselines/doc/source/conf.py b/baselines/doc/source/conf.py index a9525c44ab7b..8184bd223ea7 100644 --- a/baselines/doc/source/conf.py +++ b/baselines/doc/source/conf.py @@ -37,7 +37,7 @@ author = "The Flower Authors" # The full version, including alpha/beta/rc tags -release = "1.8.0" +release = "1.9.0" # -- General configuration --------------------------------------------------- diff --git a/dev/build-docker-image-matrix.py b/dev/build-docker-image-matrix.py index 5b9c63434bfb..51d7fd0083d1 100644 --- a/dev/build-docker-image-matrix.py +++ b/dev/build-docker-image-matrix.py @@ -1,5 +1,5 @@ """ -Usage: python dev/build-docker-image-matrix.py --flwr-version +Usage: python dev/build-docker-image-matrix.py --flwr-version """ import argparse diff --git a/doc/source/conf.py b/doc/source/conf.py index 174465c153b7..85bfe9404521 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -86,7 +86,7 @@ author = "The Flower Authors" # The full version, including alpha/beta/rc tags -release = "1.9.0" +release = "1.10.0" # -- General configuration --------------------------------------------------- diff --git a/doc/source/how-to-install-flower.rst b/doc/source/how-to-install-flower.rst index 964b23125c0b..725a7468090c 100644 --- a/doc/source/how-to-install-flower.rst +++ b/doc/source/how-to-install-flower.rst @@ -48,7 +48,7 @@ Verify installation The following command can be used to verify if Flower was successfully installed. If everything worked, it should print the version of Flower to the command line:: python -c "import flwr;print(flwr.__version__)" - 1.8.0 + 1.9.0 Advanced installation options diff --git a/examples/doc/source/conf.py b/examples/doc/source/conf.py index b9c18fba2e18..47847a0fd767 100644 --- a/examples/doc/source/conf.py +++ b/examples/doc/source/conf.py @@ -29,7 +29,7 @@ author = "The Flower Authors" # The full version, including alpha/beta/rc tags -release = "1.9.0" +release = "1.10.0" # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 1350cb7ee03c..a1a21a6b94cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "flwr" -version = "1.9.0" +version = "1.10.0" description = "Flower: A Friendly Federated Learning Framework" license = "Apache-2.0" authors = ["The Flower Authors "] diff --git a/src/py/flwr/cli/config_utils_test.py b/src/py/flwr/cli/config_utils_test.py index b47206249dfc..b24425cd08f4 100644 --- a/src/py/flwr/cli/config_utils_test.py +++ b/src/py/flwr/cli/config_utils_test.py @@ -39,7 +39,7 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: ] license = {text = "Apache License (2.0)"} dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", ] @@ -64,7 +64,7 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: "description": "", "authors": [{"email": "hello@flower.ai", "name": "The Flower Authors"}], "license": {"text": "Apache License (2.0)"}, - "dependencies": ["flwr[simulation]>=1.8.0,<2.0", "numpy>=1.21.0"], + "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, "flower": { "publisher": "flwrlabs", @@ -114,7 +114,7 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: ] license = {text = "Apache License (2.0)"} dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", ] @@ -139,7 +139,7 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: "description": "", "authors": [{"email": "hello@flower.ai", "name": "The Flower Authors"}], "license": {"text": "Apache License (2.0)"}, - "dependencies": ["flwr[simulation]>=1.8.0,<2.0", "numpy>=1.21.0"], + "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, "flower": { "publisher": "flwrlabs", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 6a235b7b15cf..71004f3421cd 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -11,7 +11,7 @@ authors = [ ] license = { text = "Apache License (2.0)" } dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets>=0.0.2,<1.0.0", "torch==2.2.1", "transformers>=4.30.0,<5.0" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index 1d32cfd77481..c5463e08b92c 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -11,7 +11,7 @@ authors = [ ] license = {text = "Apache License (2.0)"} dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "jax==0.4.26", "jaxlib==0.4.26", "scikit-learn==1.4.2", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index 321dfaab41cc..a850135a1fc5 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -11,7 +11,7 @@ authors = [ ] license = { text = "Apache License (2.0)" } dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", "mlx==0.10.0", "numpy==1.24.4", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index 6b1c40d12561..d49015eb567f 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -11,7 +11,7 @@ authors = [ ] license = { text = "Apache License (2.0)" } dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", ] diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index df404d178495..b56c0041b96c 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -11,7 +11,7 @@ authors = [ ] license = { text = "Apache License (2.0)" } dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", "torch==2.2.1", "torchvision==0.17.1", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 7ee655967c4a..6f914ae659b1 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -11,7 +11,7 @@ authors = [ ] license = { text = "Apache License (2.0)" } dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", "scikit-learn>=1.1.1", ] diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index f453bce668fa..4ecd16143dcc 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -11,7 +11,7 @@ authors = [ ] license = { text = "Apache License (2.0)" } dependencies = [ - "flwr[simulation]>=1.8.0,<2.0", + "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", "tensorflow>=2.11.1", ] From 2f99e54080167d08343c702c9e025fff0b4b733a Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Tue, 11 Jun 2024 09:29:23 +0200 Subject: [PATCH 015/595] docs(framework) Add latest Hosted Weblate translation updates (#3570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 박태현 --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 176 +++++++++++++------ 1 file changed, 127 insertions(+), 49 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 17960b663150..3eda16eebb71 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -8,15 +8,16 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-28 11:47+0200\n" -"PO-Revision-Date: 2024-05-14 21:01+0000\n" -"Last-Translator: \"Young D. Kwon\" \n" +"PO-Revision-Date: 2024-06-11 06:22+0000\n" +"Last-Translator: 박태현 \n" +"Language-Team: Korean \n" "Language: ko\n" -"Language-Team: Korean \n" -"Plural-Forms: nplurals=1; plural=0;\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" +"X-Generator: Weblate 5.6-dev\n" "Generated-By: Babel 2.15.0\n" #: ../../source/contributor-explanation-architecture.rst:2 @@ -66,21 +67,27 @@ msgid "" "is what you need. In this guide, we will explain what images exist and " "how to build them locally." msgstr "" +"Flower는 'Docker Hub '_에서 미리 만들어진 " +"Docker 이미지들을 제공합니다. 해당 이미지들은 SuperLink, ServerNode 또는 " +"ServerApp을 실행하는 데 필요한 모든 dependencies를 포함합니다. 필요한 경우 " +"다른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해 처음부터 사용자 " +"정의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 " +"이미지들과 이들을 로컬에서 빌드하는 방법에 대해 설명하겠습니다." #: ../../source/contributor-how-to-build-docker-images.rst:9 msgid "" "Before we can start, we need to meet a few prerequisites in our local " "development environment." -msgstr "" +msgstr "시작하기 전에, 로컬 개발 환경에서 몇 가지 전제 조건을 충족해야 합니다." #: ../../source/contributor-how-to-build-docker-images.rst:11 msgid "Clone the flower repository." -msgstr "" +msgstr "Flower 리포지토리를 복제합니다." #: ../../source/contributor-how-to-build-docker-images.rst:17 #: ../../source/how-to-run-flower-using-docker.rst:144 msgid "Verify the Docker daemon is running." -msgstr "" +msgstr "Docker 데몬이 실행 중인지 확인하십시오." #: ../../source/contributor-how-to-build-docker-images.rst:19 #: ../../source/how-to-run-flower-using-docker.rst:146 @@ -88,6 +95,8 @@ msgid "" "Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." msgstr "" +":doc:Run Flower using Docker 의 첫 번째 " +"섹션을 따라 주십시오. 해당 부분을 더 자세히 설명해 줍니다." #: ../../source/contributor-how-to-build-docker-images.rst:23 msgid "" @@ -97,6 +106,11 @@ msgid "" "dependencies, Python and Python tools. The SuperLink image is based on " "the base image, but it additionally installs the SuperLink using ``pip``." msgstr "" +"현재, Flower는 \"base\" 이미지 그리고 \"superlink\" 이미지를 제공합니다. " +"base 이미지는 이름에서 알 수 있듯이 SuperLink가 필요로 하는 기본 " +"dependencies를 포함하고 있습니다. 여기에는 시스템 dependencies, Python 및 " +"Python 도구가 포함됩니다. SuperLink 이미지는 base 이미지를 기반으로 하지만 " +"\"pip\"을 사용하여 SuperLink를 추가로 설치합니다." #: ../../source/contributor-how-to-build-docker-images.rst:28 msgid "" @@ -104,6 +118,8 @@ msgid "" "respective Dockerfiles. You can find them in the subdirectories of " "``src/docker``." msgstr "" +"이미지들을 조합하는 빌드 instruction들은 해당 Dockerfile에 있습니다. \"src/" +"docker\" 의 하위 디렉토리에서 찾을 수 있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:31 msgid "" @@ -115,93 +131,101 @@ msgid "" " image. All available build arguments for each image are listed in one of" " the tables below." msgstr "" +"base 이미지와 SuperLink 이미지 둘 다 빌드 argument들을 통해 구성됩니다. 빌드 " +"argument들을 통해, 빌드를 더 유연하게 만들 수 있습니다. 예를 들어, base " +"이미지에서 \"PYTHON_VERSION\" 빌드 argument를 사용하여 Python 버전을 지정할 " +"수 있습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 " +"합니다. 각 이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 " +"있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:38 msgid "Building the base image" -msgstr "" +msgstr "base 이미지 빌드" #: ../../source/contributor-how-to-build-docker-images.rst:44 #: ../../source/contributor-how-to-build-docker-images.rst:86 msgid "Build argument" -msgstr "" +msgstr "빌드 argument" #: ../../source/contributor-how-to-build-docker-images.rst:45 #: ../../source/contributor-how-to-build-docker-images.rst:87 msgid "Description" -msgstr "" +msgstr "설명" #: ../../source/contributor-how-to-build-docker-images.rst:46 #: ../../source/contributor-how-to-build-docker-images.rst:88 msgid "Required" -msgstr "" +msgstr "필수" #: ../../source/contributor-how-to-build-docker-images.rst:47 #: ../../source/contributor-how-to-build-docker-images.rst:89 msgid "Example" -msgstr "" +msgstr "예시" #: ../../source/contributor-how-to-build-docker-images.rst:48 #: ../../source/contributor-how-to-build-docker-images.rst:94 msgid "``PYTHON_VERSION``" -msgstr "" +msgstr "``PYTHON_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:49 msgid "Version of ``python`` to be installed." -msgstr "" +msgstr "설치 된 ``python`` 버전." #: ../../source/contributor-how-to-build-docker-images.rst:50 #: ../../source/contributor-how-to-build-docker-images.rst:54 #: ../../source/contributor-how-to-build-docker-images.rst:58 #: ../../source/contributor-how-to-build-docker-images.rst:108 msgid "Yes" -msgstr "" +msgstr "예" #: ../../source/contributor-how-to-build-docker-images.rst:51 msgid "``3.11``" -msgstr "" +msgstr "``3.11``" #: ../../source/contributor-how-to-build-docker-images.rst:52 msgid "``PIP_VERSION``" -msgstr "" +msgstr "``PIP_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:53 msgid "Version of ``pip`` to be installed." -msgstr "" +msgstr "설치 된 ``pip`` 버전." #: ../../source/contributor-how-to-build-docker-images.rst:55 msgid "``23.0.1``" -msgstr "" +msgstr "``23.0.1``" #: ../../source/contributor-how-to-build-docker-images.rst:56 msgid "``SETUPTOOLS_VERSION``" -msgstr "" +msgstr "``SETUPTOOLS_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:57 msgid "Version of ``setuptools`` to be installed." -msgstr "" +msgstr "설치 된 ``setuptools`` 버전." #: ../../source/contributor-how-to-build-docker-images.rst:59 msgid "``69.0.2``" -msgstr "" +msgstr "``69.0.2``" #: ../../source/contributor-how-to-build-docker-images.rst:60 #: ../../source/contributor-how-to-build-docker-images.rst:98 msgid "``UBUNTU_VERSION``" -msgstr "" +msgstr "``UBUNTU_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:61 msgid "Version of the official Ubuntu Docker image." -msgstr "" +msgstr "공식 Ubuntu Docker 이미지 버전." #: ../../source/contributor-how-to-build-docker-images.rst:62 msgid "Defaults to ``22.04``." -msgstr "" +msgstr "``22.04``이 기본값." #: ../../source/contributor-how-to-build-docker-images.rst:65 msgid "" "The following example creates a base image with Python 3.11.0, pip 23.0.1" " and setuptools 69.0.2:" msgstr "" +"다음 예시에서는 Python 3.11.0, pip 23.0.1 그리고 setuptools 69.0.2의 base " +"이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:76 msgid "" @@ -209,68 +233,73 @@ msgid "" "the build arguments as well as the name and tag can be adapted to your " "needs. These values serve as examples only." msgstr "" +"이미지의 이름은 ``flwr_base``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌드 " +"argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 " +"뿐입니다." #: ../../source/contributor-how-to-build-docker-images.rst:80 msgid "Building the SuperLink image" -msgstr "" +msgstr "SuperLink 이미지 빌드" #: ../../source/contributor-how-to-build-docker-images.rst:90 msgid "``BASE_REPOSITORY``" -msgstr "" +msgstr "``BASE_REPOSITORY``" #: ../../source/contributor-how-to-build-docker-images.rst:91 msgid "The repository name of the base image." -msgstr "" +msgstr "base 이미지의 리포지토리 이름." #: ../../source/contributor-how-to-build-docker-images.rst:92 msgid "Defaults to ``flwr/base``." -msgstr "" +msgstr "``flwr/base``이 기본값." #: ../../source/contributor-how-to-build-docker-images.rst:95 msgid "The Python version of the base image." -msgstr "" +msgstr "base 이미지의 Python 버전." #: ../../source/contributor-how-to-build-docker-images.rst:96 msgid "Defaults to ``py3.11``." -msgstr "" +msgstr "``py3.11``이 기본값." #: ../../source/contributor-how-to-build-docker-images.rst:99 msgid "The Ubuntu version of the base image." -msgstr "" +msgstr "base 이미지의 Ubuntu 버전." #: ../../source/contributor-how-to-build-docker-images.rst:100 msgid "Defaults to ``ubuntu22.04``." -msgstr "" +msgstr "``ubuntu22.04``이 기본값." #: ../../source/contributor-how-to-build-docker-images.rst:102 msgid "``FLWR_PACKAGE``" -msgstr "" +msgstr "``FLWR_PACKAGE``" #: ../../source/contributor-how-to-build-docker-images.rst:103 msgid "The PyPI package to install." -msgstr "" +msgstr "설치 할 PyPI 패키지." #: ../../source/contributor-how-to-build-docker-images.rst:104 msgid "Defaults to ``flwr``." -msgstr "" +msgstr "``flwr``이 기본값." #: ../../source/contributor-how-to-build-docker-images.rst:106 msgid "``FLWR_VERSION``" -msgstr "" +msgstr "``FLWR_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:107 msgid "Version of Flower to be installed." -msgstr "" +msgstr "설치 된 Flower 버전." #: ../../source/contributor-how-to-build-docker-images.rst:109 msgid "``1.8.0``" -msgstr "" +msgstr "``1.8.0``" #: ../../source/contributor-how-to-build-docker-images.rst:112 msgid "" "The following example creates a SuperLink image with the official Flower " "base image py3.11-ubuntu22.04 and Flower 1.8.0:" msgstr "" +"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base " +"이미지로 SuperLink 이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:122 msgid "" @@ -278,6 +307,9 @@ msgid "" "that the build arguments as well as the name and tag can be adapted to " "your needs. These values serve as examples only." msgstr "" +"이미지의 이름은 ``flwr_superlink``이고 태그는 ``0.1.0``입니다. 필요에 따라 " +"빌드 argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 " +"뿐입니다." #: ../../source/contributor-how-to-build-docker-images.rst:125 msgid "" @@ -285,14 +317,17 @@ msgid "" "base image, all you need to do is set the ``BASE_REPOSITORY``, " "``PYTHON_VERSION`` and ``UBUNTU_VERSION`` build arguments." msgstr "" +"공식 Flower base 이미지 대신 자체 base 이미지를 사용 하길 원한다면, " +"``BASE_REPOSITORY``, ``PYTHON_VERSION`` 및 ``UBUNTU_VERSION`` 빌드 " +"argument들을 설정해야 합니다." #: ../../source/contributor-how-to-build-docker-images.rst:138 msgid "After creating the image, we can test whether the image is working:" -msgstr "" +msgstr "이미지 생성 후에, 이미지가 작동하는지 테스트할 수 있습니다:" #: ../../source/contributor-how-to-contribute-translations.rst:2 msgid "Contribute translations" -msgstr "" +msgstr "번역 기여" #: ../../source/contributor-how-to-contribute-translations.rst:4 msgid "" @@ -305,6 +340,12 @@ msgid "" "also be a great opportunity for those wanting to become open source " "contributors with little prerequisites." msgstr "" +"`Flower 1.5 `_ 부터 문서 페이지에 번역을 도입했지만, 아시다시피 " +"번역이 불안전한 경우가 많습니다. 만일 영어 이외의 언어를 사용한다면, 많은 " +"사람들이 Federated Learning에 접근할 수 있도록 번역 작업에 기여함으로써 " +"저희의 노력에 도움을 주실 수 있습니다! 이는 전제 조건이 거의 없는 오픈 소스 " +"기여자가 되고자 하는 사람들에게 좋은 기회가 될 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:13 msgid "" @@ -312,10 +353,12 @@ msgid "" "`_, this " "where most of the work will happen." msgstr "" +"번역 프로젝트는 `Weblate `_에서 공개적으로 진행되며, 대부분의 작업이 이곳에서 이루어집니다." #: ../../source/contributor-how-to-contribute-translations.rst:18 msgid "Contribute to existing languages" -msgstr "" +msgstr "기존 언어에 기여하기" #: ../../source/contributor-how-to-contribute-translations.rst:23 msgid "" @@ -325,6 +368,10 @@ msgid "" " profile settings can be found `here " "`_." msgstr "" +"기여를 하기 위해 가장 먼저 해야 할 일은 해당 `page `_에서 무료 Weblate 계정을 만드는 것입니다. 프로필 " +"설정에 대한 자세한 정보는 `here `_를 참조하세요." #: ../../source/contributor-how-to-contribute-translations.rst:29 msgid "" @@ -333,12 +380,15 @@ msgid "" "docs/framework/>`_. Here, you should see the different existing languages" " that can be found on the website." msgstr "" +"Weblate에 로그인한 후, `Flower Framework project `_로 이동할 수 있습니다. 여기에서 웹사이트에 " +"있는 다양한 기존 언어들을 확인할 수 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:34 msgid "" "Once you have selected the language you want to contribute to, you should" " see a similar interface to this:" -msgstr "" +msgstr "기여하고자 하는 언어를 선택하면, 다음과 같은 인터페이스가 나타납니다:" #: ../../source/contributor-how-to-contribute-translations.rst:39 msgid "" @@ -347,10 +397,13 @@ msgid "" "will automatically bring you to the translation interface for " "untranslated strings." msgstr "" +"여기서 가장 간단한 옵션은 오른쪽 상단(``Translation status`` 부분)에 있는 " +"``Translate`` 버튼을 클릭하는 것 입니다. 번역되지 않은 문장에 대한 번역 " +"인터페이스로 자동으로 이동합니다." #: ../../source/contributor-how-to-contribute-translations.rst:43 msgid "This is what the interface looks like:" -msgstr "" +msgstr "인터페이스는 다음과 같습니다:" #: ../../source/contributor-how-to-contribute-translations.rst:47 msgid "" @@ -361,6 +414,11 @@ msgid "" "your translation to suggestions for other users to view), or ``Skip`` (to" " go to the next untranslated string without saving anything)." msgstr "" +"번역문을 상단의 텍스트 상자에 입력한 후, 번역이 만족스러우면 ``Save and " +"continue``(번역을 저장하고 다음 미번역 문장으로 이동), ``Save and stay``(" +"번역을 저장하고 해당 페이지에 머무르기), ``Suggest`` (다른 사용자가 볼 수 " +"있도록 번역을 제안 항목에 추가), ``Skip``(아무것도 저장하지 않고 다음 미번역 " +"문장으로 이동) 중 하나를 선택하면 됩니다." #: ../../source/contributor-how-to-contribute-translations.rst:54 msgid "" @@ -370,13 +428,17 @@ msgid "" "translations in ``Other languages``, and the ``History`` of translations " "for this string." msgstr "" +"번역에 도움을 주기위해 하단에서 `주변 문자열``, ``의견``(다른 기여자의), ``" +"자동 제안``(기계 번역의), ``다른 언어``의 번역 및 해당 문장의 " +"번역``히스토리``를 볼 수 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:59 msgid "" "On the right, under the ``String information`` section, you can also " "click the link under ``Source string location`` in order to view the " "source of the doc file containing the string." -msgstr "" +msgstr "오른쪽의 ``문자열 정보``에서 ``원본 문자열 위치``를 클릭하여 해당 문장이 " +"포함된 문서의 파일 소스를 볼 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:63 msgid "" @@ -384,10 +446,12 @@ msgid "" "this `in-depth guide " "`_." msgstr "" +"Weblate를 통한 번역에 대한 자세한 정보는 `in-depth guide `_를 확인하세요." #: ../../source/contributor-how-to-contribute-translations.rst:67 msgid "Add new languages" -msgstr "" +msgstr "새 언어 추가" #: ../../source/contributor-how-to-contribute-translations.rst:69 msgid "" @@ -395,6 +459,9 @@ msgid "" "either on `Slack `_, or by opening an issue" " on our `GitHub repo `_." msgstr "" +"새 언어를 추가하려면, `Slack `에 문의하거나 `" +"GitHub repo `_에서 issue에 들어가 문의 해야 " +"합니다." #: ../../source/contributor-how-to-create-new-messages.rst:2 msgid "Creating New Messages" @@ -511,7 +578,7 @@ msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:2 msgid "Develop in VSCode Dev Containers" -msgstr "" +msgstr "VSCode Dev Container에서 개발" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:4 msgid "" @@ -520,6 +587,10 @@ msgid "" "tests. For this purpose we are using the VSCode Remote Containers " "extension. What is it? Read the following quote:" msgstr "" +"Flower 프레임워크 작업시, 모든 기여자들이 코드 포맷팅이나 테스트 실행을 위해 " +"동일한 개발 환경을 사용하길 원합니다. 이를 위해 VSCode Remote Containers " +"확장을 사용하고 있습니다. 그것이 무엇인지 알아보기 위해 다음 인용문을 " +"읽어보세요:" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:7 msgid "" @@ -533,6 +604,14 @@ msgid "" "separate tools, libraries, or runtimes needed for working with a " "codebase." msgstr "" +"Visual Studio Code Remote - 컨테이너 확장을 사용하면 Docker 컨테이너를 모든 " +"기능을 갖춘 개발 환경으로 사용할 수 있습니다. 이 확장 기능을 사용하면 " +"컨테이너 내부(또는 컨테이너에 마운트된)의 모든 폴더를 열고 Visual Studio " +"Code의 모든 기능을 활용할 수 있습니다. 프로젝트에 있는 :code:`devcontainer." +"json` 파일은 잘 정의된 도구와 런타임 스택을 사용하여 개발 컨테이너에 액세스(" +"또는 생성)하는 방법을 VS Code에 알려줍니다. 이 컨테이너는 애플리케이션을 " +"실행하거나 코드베이스 작업에 필요한 도구, 라이브러리 또는 런타임을 분리하는 " +"데 사용할 수 있습니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:9 msgid "" @@ -21224,4 +21303,3 @@ msgstr "" #~ msgid "|2c13f726c8c843fc8aae997bf906125b|" #~ msgstr "" - From 6ffcf006f80ee72f4436d2008cbb24a3d857f4d8 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Tue, 11 Jun 2024 09:35:04 +0200 Subject: [PATCH 016/595] docs(framework) Add latest Hosted Weblate translation updates (#3572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 박태현 --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 10975 ++++++++--------- 1 file changed, 4950 insertions(+), 6025 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 3eda16eebb71..68440f928f08 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -30,9 +30,11 @@ msgstr "엣지 클라이언트 엔진" #: ../../source/contributor-explanation-architecture.rst:7 msgid "" -"`Flower `_ core framework architecture with Edge " -"Client Engine" -msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 엣지 클라이언트 엔진" +"`Flower `_ core framework architecture with Edge Client " +"Engine" +msgstr "" +"`Flower `_의 핵심 프레임워크 아키텍처와 엣지 클라이언트 엔" +"진" #: ../../source/contributor-explanation-architecture.rst:13 msgid "Virtual Client Engine" @@ -42,7 +44,9 @@ msgstr "가상 클라이언트 엔진" msgid "" "`Flower `_ core framework architecture with Virtual " "Client Engine" -msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 가상 클라이언트 엔진" +msgstr "" +"`Flower `_의 핵심 프레임워크 아키텍처와 가상 클라이언트 엔" +"진" #: ../../source/contributor-explanation-architecture.rst:21 msgid "Virtual Client Engine and Edge Client Engine in the same workload" @@ -50,9 +54,11 @@ msgstr "동일 작업에서 가상 클라이언트 엔진과 엣지 클라이언 #: ../../source/contributor-explanation-architecture.rst:23 msgid "" -"`Flower `_ core framework architecture with both " -"Virtual Client Engine and Edge Client Engine" -msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 가상 및 엣지 클라이언트 엔진" +"`Flower `_ core framework architecture with both Virtual " +"Client Engine and Edge Client Engine" +msgstr "" +"`Flower `_의 핵심 프레임워크 아키텍처와 가상 및 엣지 클라" +"이언트 엔진" #: ../../source/contributor-how-to-build-docker-images.rst:2 msgid "How to build Docker Flower images locally" @@ -60,19 +66,18 @@ msgstr "Docker Flower 이미지를 Locally 구축하는 방법" #: ../../source/contributor-how-to-build-docker-images.rst:4 msgid "" -"Flower provides pre-made docker images on `Docker Hub " -"`_ that include all necessary dependencies" -" for running the SuperLink. You can also build your own custom docker " -"images from scratch with a different version of Python or Ubuntu if that " -"is what you need. In this guide, we will explain what images exist and " -"how to build them locally." +"Flower provides pre-made docker images on `Docker Hub `_ that include all necessary dependencies for running the " +"SuperLink. You can also build your own custom docker images from scratch " +"with a different version of Python or Ubuntu if that is what you need. In " +"this guide, we will explain what images exist and how to build them locally." msgstr "" "Flower는 'Docker Hub '_에서 미리 만들어진 " "Docker 이미지들을 제공합니다. 해당 이미지들은 SuperLink, ServerNode 또는 " -"ServerApp을 실행하는 데 필요한 모든 dependencies를 포함합니다. 필요한 경우 " -"다른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해 처음부터 사용자 " -"정의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 " -"이미지들과 이들을 로컬에서 빌드하는 방법에 대해 설명하겠습니다." +"ServerApp을 실행하는 데 필요한 모든 dependencies를 포함합니다. 필요한 경우 다" +"른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해 처음부터 사용자 정" +"의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 이미지들과 " +"이들을 로컬에서 빌드하는 방법에 대해 설명하겠습니다." #: ../../source/contributor-how-to-build-docker-images.rst:9 msgid "" @@ -92,51 +97,49 @@ msgstr "Docker 데몬이 실행 중인지 확인하십시오." #: ../../source/contributor-how-to-build-docker-images.rst:19 #: ../../source/how-to-run-flower-using-docker.rst:146 msgid "" -"Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." +"Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." msgstr "" -":doc:Run Flower using Docker 의 첫 번째 " -"섹션을 따라 주십시오. 해당 부분을 더 자세히 설명해 줍니다." +":doc:Run Flower using Docker 의 첫 번째 섹션" +"을 따라 주십시오. 해당 부분을 더 자세히 설명해 줍니다." #: ../../source/contributor-how-to-build-docker-images.rst:23 msgid "" -"Currently, Flower provides two images, a ``base`` image and a " -"``superlink`` image. The base image, as the name suggests, contains basic" -" dependencies that the SuperLink needs. This includes system " -"dependencies, Python and Python tools. The SuperLink image is based on " -"the base image, but it additionally installs the SuperLink using ``pip``." +"Currently, Flower provides two images, a ``base`` image and a ``superlink`` " +"image. The base image, as the name suggests, contains basic dependencies " +"that the SuperLink needs. This includes system dependencies, Python and " +"Python tools. The SuperLink image is based on the base image, but it " +"additionally installs the SuperLink using ``pip``." msgstr "" "현재, Flower는 \"base\" 이미지 그리고 \"superlink\" 이미지를 제공합니다. " -"base 이미지는 이름에서 알 수 있듯이 SuperLink가 필요로 하는 기본 " -"dependencies를 포함하고 있습니다. 여기에는 시스템 dependencies, Python 및 " -"Python 도구가 포함됩니다. SuperLink 이미지는 base 이미지를 기반으로 하지만 " -"\"pip\"을 사용하여 SuperLink를 추가로 설치합니다." +"base 이미지는 이름에서 알 수 있듯이 SuperLink가 필요로 하는 기본 dependencies" +"를 포함하고 있습니다. 여기에는 시스템 dependencies, Python 및 Python 도구가 " +"포함됩니다. SuperLink 이미지는 base 이미지를 기반으로 하지만 \"pip\"을 사용하" +"여 SuperLink를 추가로 설치합니다." #: ../../source/contributor-how-to-build-docker-images.rst:28 msgid "" "The build instructions that assemble the images are located in the " -"respective Dockerfiles. You can find them in the subdirectories of " -"``src/docker``." +"respective Dockerfiles. You can find them in the subdirectories of ``src/" +"docker``." msgstr "" "이미지들을 조합하는 빌드 instruction들은 해당 Dockerfile에 있습니다. \"src/" "docker\" 의 하위 디렉토리에서 찾을 수 있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:31 msgid "" -"Both, base and SuperLink image are configured via build arguments. " -"Through build arguments, we can make our build more flexible. For " -"example, in the base image, we can specify the version of Python to " -"install using the ``PYTHON_VERSION`` build argument. Some of the build " -"arguments have default values, others must be specified when building the" -" image. All available build arguments for each image are listed in one of" -" the tables below." +"Both, base and SuperLink image are configured via build arguments. Through " +"build arguments, we can make our build more flexible. For example, in the " +"base image, we can specify the version of Python to install using the " +"``PYTHON_VERSION`` build argument. Some of the build arguments have default " +"values, others must be specified when building the image. All available " +"build arguments for each image are listed in one of the tables below." msgstr "" "base 이미지와 SuperLink 이미지 둘 다 빌드 argument들을 통해 구성됩니다. 빌드 " -"argument들을 통해, 빌드를 더 유연하게 만들 수 있습니다. 예를 들어, base " -"이미지에서 \"PYTHON_VERSION\" 빌드 argument를 사용하여 Python 버전을 지정할 " -"수 있습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 " -"합니다. 각 이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 " -"있습니다." +"argument들을 통해, 빌드를 더 유연하게 만들 수 있습니다. 예를 들어, base 이미" +"지에서 \"PYTHON_VERSION\" 빌드 argument를 사용하여 Python 버전을 지정할 수 있" +"습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 합니" +"다. 각 이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:38 msgid "Building the base image" @@ -221,21 +224,21 @@ msgstr "``22.04``이 기본값." #: ../../source/contributor-how-to-build-docker-images.rst:65 msgid "" -"The following example creates a base image with Python 3.11.0, pip 23.0.1" -" and setuptools 69.0.2:" +"The following example creates a base image with Python 3.11.0, pip 23.0.1 " +"and setuptools 69.0.2:" msgstr "" -"다음 예시에서는 Python 3.11.0, pip 23.0.1 그리고 setuptools 69.0.2의 base " -"이미지를 만듭니다:" +"다음 예시에서는 Python 3.11.0, pip 23.0.1 그리고 setuptools 69.0.2의 base 이" +"미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:76 msgid "" -"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that " -"the build arguments as well as the name and tag can be adapted to your " -"needs. These values serve as examples only." +"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that the " +"build arguments as well as the name and tag can be adapted to your needs. " +"These values serve as examples only." msgstr "" "이미지의 이름은 ``flwr_base``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌드 " -"argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 " -"뿐입니다." +"argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 뿐입" +"니다." #: ../../source/contributor-how-to-build-docker-images.rst:80 msgid "Building the SuperLink image" @@ -298,28 +301,28 @@ msgid "" "The following example creates a SuperLink image with the official Flower " "base image py3.11-ubuntu22.04 and Flower 1.8.0:" msgstr "" -"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base " -"이미지로 SuperLink 이미지를 만듭니다:" +"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base 이미지" +"로 SuperLink 이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:122 msgid "" -"The name of image is ``flwr_superlink`` and the tag ``0.1.0``. Remember " -"that the build arguments as well as the name and tag can be adapted to " -"your needs. These values serve as examples only." +"The name of image is ``flwr_superlink`` and the tag ``0.1.0``. Remember that " +"the build arguments as well as the name and tag can be adapted to your " +"needs. These values serve as examples only." msgstr "" -"이미지의 이름은 ``flwr_superlink``이고 태그는 ``0.1.0``입니다. 필요에 따라 " -"빌드 argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 " -"뿐입니다." +"이미지의 이름은 ``flwr_superlink``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌" +"드 argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 뿐" +"입니다." #: ../../source/contributor-how-to-build-docker-images.rst:125 msgid "" -"If you want to use your own base image instead of the official Flower " -"base image, all you need to do is set the ``BASE_REPOSITORY``, " -"``PYTHON_VERSION`` and ``UBUNTU_VERSION`` build arguments." +"If you want to use your own base image instead of the official Flower base " +"image, all you need to do is set the ``BASE_REPOSITORY``, ``PYTHON_VERSION`` " +"and ``UBUNTU_VERSION`` build arguments." msgstr "" "공식 Flower base 이미지 대신 자체 base 이미지를 사용 하길 원한다면, " -"``BASE_REPOSITORY``, ``PYTHON_VERSION`` 및 ``UBUNTU_VERSION`` 빌드 " -"argument들을 설정해야 합니다." +"``BASE_REPOSITORY``, ``PYTHON_VERSION`` 및 ``UBUNTU_VERSION`` 빌드 argument들" +"을 설정해야 합니다." #: ../../source/contributor-how-to-build-docker-images.rst:138 msgid "After creating the image, we can test whether the image is working:" @@ -331,27 +334,27 @@ msgstr "번역 기여" #: ../../source/contributor-how-to-contribute-translations.rst:4 msgid "" -"Since `Flower 1.5 `_ we have introduced translations to " -"our doc pages, but, as you might have noticed, the translations are often" -" imperfect. If you speak languages other than English, you might be able " -"to help us in our effort to make Federated Learning accessible to as many" -" people as possible by contributing to those translations! This might " -"also be a great opportunity for those wanting to become open source " -"contributors with little prerequisites." +"Since `Flower 1.5 `_ we have introduced translations to our doc pages, " +"but, as you might have noticed, the translations are often imperfect. If you " +"speak languages other than English, you might be able to help us in our " +"effort to make Federated Learning accessible to as many people as possible " +"by contributing to those translations! This might also be a great " +"opportunity for those wanting to become open source contributors with little " +"prerequisites." msgstr "" "`Flower 1.5 `_ 부터 문서 페이지에 번역을 도입했지만, 아시다시피 " -"번역이 불안전한 경우가 많습니다. 만일 영어 이외의 언어를 사용한다면, 많은 " -"사람들이 Federated Learning에 접근할 수 있도록 번역 작업에 기여함으로써 " -"저희의 노력에 도움을 주실 수 있습니다! 이는 전제 조건이 거의 없는 오픈 소스 " -"기여자가 되고자 하는 사람들에게 좋은 기회가 될 수도 있습니다." +"html#v1-5-0-2023-08-31>`_ 부터 문서 페이지에 번역을 도입했지만, 아시다시피 번" +"역이 불안전한 경우가 많습니다. 만일 영어 이외의 언어를 사용한다면, 많은 사람" +"들이 Federated Learning에 접근할 수 있도록 번역 작업에 기여함으로써 저희의 노" +"력에 도움을 주실 수 있습니다! 이는 전제 조건이 거의 없는 오픈 소스 기여자가 " +"되고자 하는 사람들에게 좋은 기회가 될 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:13 msgid "" -"Our translation project is publicly available over on `Weblate " -"`_, this " -"where most of the work will happen." +"Our translation project is publicly available over on `Weblate `_, this where most of " +"the work will happen." msgstr "" "번역 프로젝트는 `Weblate `_에서 공개적으로 진행되며, 대부분의 작업이 이곳에서 이루어집니다." @@ -362,23 +365,22 @@ msgstr "기존 언어에 기여하기" #: ../../source/contributor-how-to-contribute-translations.rst:23 msgid "" -"The first thing you will need to do in order to contribute is to create a" -" free Weblate account on this `page " -"`_. More information about" -" profile settings can be found `here " +"The first thing you will need to do in order to contribute is to create a " +"free Weblate account on this `page `_. More information about profile settings can be found `here " "`_." msgstr "" "기여를 하기 위해 가장 먼저 해야 할 일은 해당 `page `_에서 무료 Weblate 계정을 만드는 것입니다. 프로필 " -"설정에 대한 자세한 정보는 `here `_에서 무료 Weblate 계정을 만드는 것입니다. 프로필 설" +"정에 대한 자세한 정보는 `here `_를 참조하세요." #: ../../source/contributor-how-to-contribute-translations.rst:29 msgid "" -"Once you are signed in to Weblate, you can navigate to the `Flower " -"Framework project `_. Here, you should see the different existing languages" -" that can be found on the website." +"Once you are signed in to Weblate, you can navigate to the `Flower Framework " +"project `_. " +"Here, you should see the different existing languages that can be found on " +"the website." msgstr "" "Weblate에 로그인한 후, `Flower Framework project `_로 이동할 수 있습니다. 여기에서 웹사이트에 " @@ -386,20 +388,20 @@ msgstr "" #: ../../source/contributor-how-to-contribute-translations.rst:34 msgid "" -"Once you have selected the language you want to contribute to, you should" -" see a similar interface to this:" +"Once you have selected the language you want to contribute to, you should " +"see a similar interface to this:" msgstr "기여하고자 하는 언어를 선택하면, 다음과 같은 인터페이스가 나타납니다:" #: ../../source/contributor-how-to-contribute-translations.rst:39 msgid "" "The most straight forward option here is to click on the ``Translate`` " -"button on the top right (in the ``Translation status`` section). This " -"will automatically bring you to the translation interface for " -"untranslated strings." +"button on the top right (in the ``Translation status`` section). This will " +"automatically bring you to the translation interface for untranslated " +"strings." msgstr "" "여기서 가장 간단한 옵션은 오른쪽 상단(``Translation status`` 부분)에 있는 " -"``Translate`` 버튼을 클릭하는 것 입니다. 번역되지 않은 문장에 대한 번역 " -"인터페이스로 자동으로 이동합니다." +"``Translate`` 버튼을 클릭하는 것 입니다. 번역되지 않은 문장에 대한 번역 인터" +"페이스로 자동으로 이동합니다." #: ../../source/contributor-how-to-contribute-translations.rst:43 msgid "This is what the interface looks like:" @@ -407,44 +409,44 @@ msgstr "인터페이스는 다음과 같습니다:" #: ../../source/contributor-how-to-contribute-translations.rst:47 msgid "" -"You input your translation in the text box at the top and then, once you " -"are happy with it, you either press ``Save and continue`` (to save the " -"translation and go to the next untranslated string), ``Save and stay`` " -"(to save the translation and stay on the same page), ``Suggest`` (to add " -"your translation to suggestions for other users to view), or ``Skip`` (to" -" go to the next untranslated string without saving anything)." +"You input your translation in the text box at the top and then, once you are " +"happy with it, you either press ``Save and continue`` (to save the " +"translation and go to the next untranslated string), ``Save and stay`` (to " +"save the translation and stay on the same page), ``Suggest`` (to add your " +"translation to suggestions for other users to view), or ``Skip`` (to go to " +"the next untranslated string without saving anything)." msgstr "" "번역문을 상단의 텍스트 상자에 입력한 후, 번역이 만족스러우면 ``Save and " -"continue``(번역을 저장하고 다음 미번역 문장으로 이동), ``Save and stay``(" -"번역을 저장하고 해당 페이지에 머무르기), ``Suggest`` (다른 사용자가 볼 수 " -"있도록 번역을 제안 항목에 추가), ``Skip``(아무것도 저장하지 않고 다음 미번역 " -"문장으로 이동) 중 하나를 선택하면 됩니다." +"continue``(번역을 저장하고 다음 미번역 문장으로 이동), ``Save and stay``(번역" +"을 저장하고 해당 페이지에 머무르기), ``Suggest`` (다른 사용자가 볼 수 있도록 " +"번역을 제안 항목에 추가), ``Skip``(아무것도 저장하지 않고 다음 미번역 문장으" +"로 이동) 중 하나를 선택하면 됩니다." #: ../../source/contributor-how-to-contribute-translations.rst:54 msgid "" "In order to help with the translations, you can see on the bottom the " "``Nearby strings``, the ``Comments`` (from other contributors), the " "``Automatic suggestions`` (from machine translation engines), the " -"translations in ``Other languages``, and the ``History`` of translations " -"for this string." +"translations in ``Other languages``, and the ``History`` of translations for " +"this string." msgstr "" -"번역에 도움을 주기위해 하단에서 `주변 문자열``, ``의견``(다른 기여자의), ``" -"자동 제안``(기계 번역의), ``다른 언어``의 번역 및 해당 문장의 " -"번역``히스토리``를 볼 수 있습니다." +"번역에 도움을 주기위해 하단에서 `주변 문자열``, ``의견``(다른 기여자의), ``자" +"동 제안``(기계 번역의), ``다른 언어``의 번역 및 해당 문장의 번역``히스토리``" +"를 볼 수 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:59 msgid "" -"On the right, under the ``String information`` section, you can also " -"click the link under ``Source string location`` in order to view the " -"source of the doc file containing the string." -msgstr "오른쪽의 ``문자열 정보``에서 ``원본 문자열 위치``를 클릭하여 해당 문장이 " -"포함된 문서의 파일 소스를 볼 수도 있습니다." +"On the right, under the ``String information`` section, you can also click " +"the link under ``Source string location`` in order to view the source of the " +"doc file containing the string." +msgstr "" +"오른쪽의 ``문자열 정보``에서 ``원본 문자열 위치``를 클릭하여 해당 문장이 포함" +"된 문서의 파일 소스를 볼 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:63 msgid "" -"For more information about translating using Weblate, you can check out " -"this `in-depth guide " -"`_." +"For more information about translating using Weblate, you can check out this " +"`in-depth guide `_." msgstr "" "Weblate를 통한 번역에 대한 자세한 정보는 `in-depth guide `_를 확인하세요." @@ -455,12 +457,12 @@ msgstr "새 언어 추가" #: ../../source/contributor-how-to-contribute-translations.rst:69 msgid "" -"If you want to add a new language, you will first have to contact us, " -"either on `Slack `_, or by opening an issue" -" on our `GitHub repo `_." +"If you want to add a new language, you will first have to contact us, either " +"on `Slack `_, or by opening an issue on our " +"`GitHub repo `_." msgstr "" -"새 언어를 추가하려면, `Slack `에 문의하거나 `" -"GitHub repo `_에서 issue에 들어가 문의 해야 " +"새 언어를 추가하려면, `Slack `에 문의하거나 " +"`GitHub repo `_에서 issue에 들어가 문의 해야 " "합니다." #: ../../source/contributor-how-to-create-new-messages.rst:2 @@ -469,14 +471,14 @@ msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:4 msgid "" -"This is a simple guide for creating a new type of message between the " -"server and clients in Flower." +"This is a simple guide for creating a new type of message between the server " +"and clients in Flower." msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:6 msgid "" -"Let's suppose we have the following example functions in " -":code:`server.py` and :code:`numpy_client.py`..." +"Let's suppose we have the following example functions in :code:`server.py` " +"and :code:`numpy_client.py`..." msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:8 @@ -489,8 +491,8 @@ msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:26 msgid "" -"Let's now see what we need to implement in order to get this simple " -"function between the server and client to work!" +"Let's now see what we need to implement in order to get this simple function " +"between the server and client to work!" msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:30 @@ -499,11 +501,11 @@ msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:32 msgid "" -"The first thing we need to do is to define a message type for the RPC " -"system in :code:`transport.proto`. Note that we have to do it for both " -"the request and response messages. For more details on the syntax of " -"proto3, please see the `official documentation `_." +"The first thing we need to do is to define a message type for the RPC system " +"in :code:`transport.proto`. Note that we have to do it for both the request " +"and response messages. For more details on the syntax of proto3, please see " +"the `official documentation `_." msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:35 @@ -516,8 +518,8 @@ msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:70 msgid "" -"Make sure to also add a field of the newly created message type in " -":code:`oneof msg`." +"Make sure to also add a field of the newly created message type in :code:" +"`oneof msg`." msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:72 @@ -549,8 +551,8 @@ msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:114 msgid "" -"Now write the request function in your Client Proxy class (e.g., " -":code:`grpc_client_proxy.py`) using the serde functions you just created:" +"Now write the request function in your Client Proxy class (e.g., :code:" +"`grpc_client_proxy.py`) using the serde functions you just created:" msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:128 @@ -559,9 +561,9 @@ msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:130 msgid "" -"Last step! Modify the code in :code:`message_handler.py` to check the " -"field of your message and call the :code:`example_response` function. " -"Remember to use the serde functions!" +"Last step! Modify the code in :code:`message_handler.py` to check the field " +"of your message and call the :code:`example_response` function. Remember to " +"use the serde functions!" msgstr "" #: ../../source/contributor-how-to-create-new-messages.rst:132 @@ -582,50 +584,48 @@ msgstr "VSCode Dev Container에서 개발" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:4 msgid "" -"When working on the Flower framework we want to ensure that all " -"contributors use the same developer environment to format code or run " -"tests. For this purpose we are using the VSCode Remote Containers " -"extension. What is it? Read the following quote:" +"When working on the Flower framework we want to ensure that all contributors " +"use the same developer environment to format code or run tests. For this " +"purpose we are using the VSCode Remote Containers extension. What is it? " +"Read the following quote:" msgstr "" "Flower 프레임워크 작업시, 모든 기여자들이 코드 포맷팅이나 테스트 실행을 위해 " -"동일한 개발 환경을 사용하길 원합니다. 이를 위해 VSCode Remote Containers " -"확장을 사용하고 있습니다. 그것이 무엇인지 알아보기 위해 다음 인용문을 " -"읽어보세요:" +"동일한 개발 환경을 사용하길 원합니다. 이를 위해 VSCode Remote Containers 확장" +"을 사용하고 있습니다. 그것이 무엇인지 알아보기 위해 다음 인용문을 읽어보세요:" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:7 msgid "" -"The Visual Studio Code Remote - Containers extension lets you use a " -"Docker container as a fully-featured development environment. It allows " -"you to open any folder inside (or mounted into) a container and take " -"advantage of Visual Studio Code's full feature set. A " -":code:`devcontainer.json` file in your project tells VS Code how to " -"access (or create) a development container with a well-defined tool and " -"runtime stack. This container can be used to run an application or to " -"separate tools, libraries, or runtimes needed for working with a " -"codebase." +"The Visual Studio Code Remote - Containers extension lets you use a Docker " +"container as a fully-featured development environment. It allows you to open " +"any folder inside (or mounted into) a container and take advantage of Visual " +"Studio Code's full feature set. A :code:`devcontainer.json` file in your " +"project tells VS Code how to access (or create) a development container with " +"a well-defined tool and runtime stack. This container can be used to run an " +"application or to separate tools, libraries, or runtimes needed for working " +"with a codebase." msgstr "" "Visual Studio Code Remote - 컨테이너 확장을 사용하면 Docker 컨테이너를 모든 " -"기능을 갖춘 개발 환경으로 사용할 수 있습니다. 이 확장 기능을 사용하면 " -"컨테이너 내부(또는 컨테이너에 마운트된)의 모든 폴더를 열고 Visual Studio " -"Code의 모든 기능을 활용할 수 있습니다. 프로젝트에 있는 :code:`devcontainer." -"json` 파일은 잘 정의된 도구와 런타임 스택을 사용하여 개발 컨테이너에 액세스(" -"또는 생성)하는 방법을 VS Code에 알려줍니다. 이 컨테이너는 애플리케이션을 " -"실행하거나 코드베이스 작업에 필요한 도구, 라이브러리 또는 런타임을 분리하는 " -"데 사용할 수 있습니다." +"기능을 갖춘 개발 환경으로 사용할 수 있습니다. 이 확장 기능을 사용하면 컨테이" +"너 내부(또는 컨테이너에 마운트된)의 모든 폴더를 열고 Visual Studio Code의 모" +"든 기능을 활용할 수 있습니다. 프로젝트에 있는 :code:`devcontainer.json` 파일" +"은 잘 정의된 도구와 런타임 스택을 사용하여 개발 컨테이너에 액세스(또는 생성)" +"하는 방법을 VS Code에 알려줍니다. 이 컨테이너는 애플리케이션을 실행하거나 코" +"드베이스 작업에 필요한 도구, 라이브러리 또는 런타임을 분리하는 데 사용할 수 " +"있습니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:9 msgid "" -"Workspace files are mounted from the local file system or copied or " -"cloned into the container. Extensions are installed and run inside the " -"container, where they have full access to the tools, platform, and file " -"system. This means that you can seamlessly switch your entire development" -" environment just by connecting to a different container." +"Workspace files are mounted from the local file system or copied or cloned " +"into the container. Extensions are installed and run inside the container, " +"where they have full access to the tools, platform, and file system. This " +"means that you can seamlessly switch your entire development environment " +"just by connecting to a different container." msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:11 msgid "" -"Source: `Official VSCode documentation " -"`_" +"Source: `Official VSCode documentation `_" msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:15 @@ -634,42 +634,40 @@ msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:17 msgid "" -"Configuring and setting up the :code:`Dockerfile` as well the " -"configuration for the devcontainer can be a bit more involved. The good " -"thing is you don't have to do it. Usually it should be enough to install " -"`Docker `_ on your system and " -"ensure its available on your command line. Additionally, install the " -"`VSCode Containers Extension `_." +"Configuring and setting up the :code:`Dockerfile` as well the configuration " +"for the devcontainer can be a bit more involved. The good thing is you don't " +"have to do it. Usually it should be enough to install `Docker `_ on your system and ensure its available on " +"your command line. Additionally, install the `VSCode Containers Extension " +"`_." msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:19 msgid "" -"Now you should be good to go. When starting VSCode, it will ask you to " -"run in the container environment and - if you confirm - automatically " -"build the container and use it. To manually instruct VSCode to use the " -"devcontainer, you can, after installing the extension, click the green " -"area in the bottom left corner of your VSCode window and select the " -"option *(Re)Open Folder in Container*." +"Now you should be good to go. When starting VSCode, it will ask you to run " +"in the container environment and - if you confirm - automatically build the " +"container and use it. To manually instruct VSCode to use the devcontainer, " +"you can, after installing the extension, click the green area in the bottom " +"left corner of your VSCode window and select the option *(Re)Open Folder in " +"Container*." msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:21 msgid "" -"In some cases your setup might be more involved. For those cases consult " -"the following sources:" +"In some cases your setup might be more involved. For those cases consult the " +"following sources:" msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:23 msgid "" -"`Developing inside a Container " -"`_" +"`Developing inside a Container `_" msgstr "" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:24 msgid "" -"`Remote development in Containers " -"`_" +"`Remote development in Containers `_" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:2 @@ -686,9 +684,9 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:10 msgid "" -"Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency " -"in ``pyproject.toml`` and then reinstall (don't forget to delete " -"``poetry.lock`` (``rm poetry.lock``) before running ``poetry install``)." +"Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency in " +"``pyproject.toml`` and then reinstall (don't forget to delete ``poetry." +"lock`` (``rm poetry.lock``) before running ``poetry install``)." msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:12 @@ -705,8 +703,8 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:15 msgid "" -"Install ``flwr`` from a local copy of the Flower source code via " -"``pyproject.toml``:" +"Install ``flwr`` from a local copy of the Flower source code via ``pyproject." +"toml``:" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:17 @@ -715,8 +713,8 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:18 msgid "" -"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] " -"}`` (with extras)" +"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] }`` " +"(with extras)" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:20 @@ -725,8 +723,8 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:22 msgid "" -"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (without" -" extras)" +"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (without " +"extras)" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:23 @@ -770,8 +768,7 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:39 msgid "" -"``pip install flwr@git+https://github.com/adap/flower.git`` (without " -"extras)" +"``pip install flwr@git+https://github.com/adap/flower.git`` (without extras)" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:40 @@ -792,8 +789,8 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" -"@branch-name`` (with extras)" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" +"name`` (with extras)" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:49 @@ -808,20 +805,20 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:53 msgid "" -"https://colab.research.google.com/github/adap/flower/blob/main/doc/source" -"/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/main/doc/source/" +"tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:55 msgid "" -"Open a development version of the same notebook from branch `branch-name`" -" by changing ``main`` to ``branch-name`` (right after ``blob``):" +"Open a development version of the same notebook from branch `branch-name` by " +"changing ``main`` to ``branch-name`` (right after ``blob``):" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:57 msgid "" -"https://colab.research.google.com/github/adap/flower/blob/branch-" -"name/doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/branch-name/doc/" +"source/tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:59 @@ -830,8 +827,8 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:61 msgid "" -"In the vertical icon grid on the left hand side, select ``Files`` > " -"``Upload to session storage``" +"In the vertical icon grid on the left hand side, select ``Files`` > ``Upload " +"to session storage``" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:62 @@ -840,9 +837,9 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:63 msgid "" -"Change ``!pip install -q 'flwr[simulation]' torch torchvision " -"matplotlib`` to ``!pip install -q 'flwr-1.8.0-py3-none-" -"any.whl[simulation]' torch torchvision matplotlib``" +"Change ``!pip install -q 'flwr[simulation]' torch torchvision matplotlib`` " +"to ``!pip install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch " +"torchvision matplotlib``" msgstr "" #: ../../source/contributor-how-to-release-flower.rst:2 @@ -861,9 +858,8 @@ msgstr "" #: ../../source/contributor-how-to-release-flower.rst:9 msgid "" -"The version number of a release is stated in ``pyproject.toml``. To " -"release a new version of Flower, the following things need to happen (in " -"that order):" +"The version number of a release is stated in ``pyproject.toml``. To release " +"a new version of Flower, the following things need to happen (in that order):" msgstr "" #: ../../source/contributor-how-to-release-flower.rst:11 @@ -875,25 +871,26 @@ msgstr "" #: ../../source/contributor-how-to-release-flower.rst:12 msgid "" -"Once the changelog has been updated with all the changes, run ``./dev" -"/prepare-release-changelog.sh v``, where ```` " -"is the version stated in ``pyproject.toml`` (notice the ``v`` added " -"before it). This will replace the ``Unreleased`` header of the changelog " -"by the version and current date, and it will add a thanking message for " -"the contributors. Open a pull request with those changes." +"Once the changelog has been updated with all the changes, run ``./dev/" +"prepare-release-changelog.sh v``, where ```` is " +"the version stated in ``pyproject.toml`` (notice the ``v`` added before it). " +"This will replace the ``Unreleased`` header of the changelog by the version " +"and current date, and it will add a thanking message for the contributors. " +"Open a pull request with those changes." msgstr "" #: ../../source/contributor-how-to-release-flower.rst:13 msgid "" "Once the pull request is merged, tag the release commit with the version " -"number as soon as the PR is merged: ``git tag v`` (notice " -"the ``v`` added before the version number), then ``git push --tags``. " -"This will create a draft release on GitHub containing the correct " -"artifacts and the relevant part of the changelog." +"number as soon as the PR is merged: ``git tag v`` (notice the " +"``v`` added before the version number), then ``git push --tags``. This will " +"create a draft release on GitHub containing the correct artifacts and the " +"relevant part of the changelog." msgstr "" #: ../../source/contributor-how-to-release-flower.rst:14 -msgid "Check the draft release on GitHub, and if everything is good, publish it." +msgid "" +"Check the draft release on GitHub, and if everything is good, publish it." msgstr "" #: ../../source/contributor-how-to-release-flower.rst:17 @@ -918,8 +915,8 @@ msgstr "" #: ../../source/contributor-how-to-release-flower.rst:25 msgid "" -"Merge the pull request on the same day (i.e., before a new nightly " -"release gets published to PyPI)." +"Merge the pull request on the same day (i.e., before a new nightly release " +"gets published to PyPI)." msgstr "" #: ../../source/contributor-how-to-release-flower.rst:28 @@ -932,8 +929,8 @@ msgstr "" #: ../../source/contributor-how-to-release-flower.rst:33 msgid "" -"PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases" -" MUST use one of the following naming patterns:" +"PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases " +"MUST use one of the following naming patterns:" msgstr "" #: ../../source/contributor-how-to-release-flower.rst:35 @@ -980,17 +977,17 @@ msgstr "" #: ../../source/contributor-how-to-release-flower.rst:50 msgid "" -"`PyPA Choosing a versioning scheme " -"`_" +"`PyPA Choosing a versioning scheme `_" msgstr "" #: ../../source/contributor-how-to-release-flower.rst:52 msgid "" -"Note that the approach defined by PyPA is not compatible with SemVer " -"2.0.0 spec, for details consult the `Semantic Versioning Specification " -"`_ (specifically item " -"11 on precedence)." +"Note that the approach defined by PyPA is not compatible with SemVer 2.0.0 " +"spec, for details consult the `Semantic Versioning Specification `_ (specifically item 11 on " +"precedence)." msgstr "" #: ../../source/contributor-how-to-release-flower.rst:55 @@ -998,14 +995,15 @@ msgid "Pre-release classification" msgstr "" #: ../../source/contributor-how-to-release-flower.rst:57 -msgid "Should the next pre-release be called alpha, beta, or release candidate?" +msgid "" +"Should the next pre-release be called alpha, beta, or release candidate?" msgstr "" #: ../../source/contributor-how-to-release-flower.rst:59 msgid "" -"RC: feature complete, no known issues (apart from issues that are " -"classified as \"won't fix\" for the next stable release) - if no issues " -"surface this will become the next stable release" +"RC: feature complete, no known issues (apart from issues that are classified " +"as \"won't fix\" for the next stable release) - if no issues surface this " +"will become the next stable release" msgstr "" #: ../../source/contributor-how-to-release-flower.rst:60 @@ -1024,8 +1022,8 @@ msgstr "" msgid "" "It is recommended to run your Python setup within a virtual environment. " "This guide shows three different examples how to create a virtual " -"environment with pyenv virtualenv, poetry, or Anaconda. You can follow " -"the instructions or choose your preferred setup." +"environment with pyenv virtualenv, poetry, or Anaconda. You can follow the " +"instructions or choose your preferred setup." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:9 @@ -1035,17 +1033,15 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:11 #: ../../source/how-to-install-flower.rst:8 msgid "" -"Flower requires at least `Python 3.8 `_, " -"but `Python 3.10 `_ or above is " -"recommended." +"Flower requires at least `Python 3.8 `_, but " +"`Python 3.10 `_ or above is recommended." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:14 msgid "" -"Due to a known incompatibility with `ray " -"`_, we currently recommend utilizing at " -"most `Python 3.11 `_ for running Flower " -"simulations." +"Due to a known incompatibility with `ray `_, " +"we currently recommend utilizing at most `Python 3.11 `_ for running Flower simulations." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:19 @@ -1054,10 +1050,10 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:21 msgid "" -"One of the recommended virtual environment is `pyenv " -"`_/`virtualenv `_. Please see `Flower examples " -"`_ for details." +"One of the recommended virtual environment is `pyenv `_/`virtualenv `_. " +"Please see `Flower examples `_ for details." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:23 @@ -1080,15 +1076,15 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:46 msgid "" -"The Flower examples are based on `Poetry `_ to manage dependencies. After installing Poetry you " -"simply create a virtual environment with:" +"The Flower examples are based on `Poetry `_ " +"to manage dependencies. After installing Poetry you simply create a virtual " +"environment with:" msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:52 msgid "" -"If you open a new terminal you can activate the previously created " -"virtual environment with the following command:" +"If you open a new terminal you can activate the previously created virtual " +"environment with the following command:" msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:60 @@ -1097,10 +1093,10 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:62 msgid "" -"If you prefer to use Anaconda for your virtual environment then install " -"and setup the `conda `_ package. After setting it up you can " -"create a virtual environment with:" +"If you prefer to use Anaconda for your virtual environment then install and " +"setup the `conda `_ package. After setting it up you can create a virtual " +"environment with:" msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:68 @@ -1113,8 +1109,8 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:78 msgid "" -"As soon as you created your virtual environment you clone one of the " -"`Flower examples `_." +"As soon as you created your virtual environment you clone one of the `Flower " +"examples `_." msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:2 @@ -1127,18 +1123,17 @@ msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:8 msgid "" -"The Flower documentation lives in the ``doc`` directory. The Sphinx-based" -" documentation system supports both reStructuredText (``.rst`` files) and" -" Markdown (``.md`` files)." +"The Flower documentation lives in the ``doc`` directory. The Sphinx-based " +"documentation system supports both reStructuredText (``.rst`` files) and " +"Markdown (``.md`` files)." msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:10 #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:169 msgid "" -"Note that, in order to build the documentation locally (with ``poetry run" -" make html``, like described below), `Pandoc " -"`_ needs to be installed on the " -"system." +"Note that, in order to build the documentation locally (with ``poetry run " +"make html``, like described below), `Pandoc `_ needs to be installed on the system." msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:14 @@ -1181,10 +1176,10 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:4 msgid "" -"We welcome contributions to Flower! However, it is not always easy to " -"know where to start. We therefore put together a few recommendations on " -"where to start to increase your chances of getting your PR accepted into " -"the Flower codebase." +"We welcome contributions to Flower! However, it is not always easy to know " +"where to start. We therefore put together a few recommendations on where to " +"start to increase your chances of getting your PR accepted into the Flower " +"codebase." msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:11 @@ -1193,9 +1188,9 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:13 msgid "" -"Until the Flower core library matures it will be easier to get PR's " -"accepted if they only touch non-core areas of the codebase. Good " -"candidates to get started are:" +"Until the Flower core library matures it will be easier to get PR's accepted " +"if they only touch non-core areas of the codebase. Good candidates to get " +"started are:" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:17 @@ -1216,24 +1211,23 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:25 msgid "" -"If you are not familiar with Flower Baselines, you should probably check-" -"out our `contributing guide for baselines " -"`_." +"If you are not familiar with Flower Baselines, you should probably check-out " +"our `contributing guide for baselines `_." msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:27 msgid "" -"You should then check out the open `issues " -"`_" -" for baseline requests. If you find a baseline that you'd like to work on" -" and that has no assignees, feel free to assign it to yourself and start " -"working on it!" +"You should then check out the open `issues `_ for baseline " +"requests. If you find a baseline that you'd like to work on and that has no " +"assignees, feel free to assign it to yourself and start working on it!" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:31 msgid "" -"Otherwise, if you don't find a baseline you'd like to work on, be sure to" -" open a new issue with the baseline request template!" +"Otherwise, if you don't find a baseline you'd like to work on, be sure to " +"open a new issue with the baseline request template!" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:34 @@ -1243,8 +1237,8 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:36 msgid "" "We wish we had more time to write usage examples because we believe they " -"help users to get started with building what they want to build. Here are" -" a few ideas where we'd be happy to accept a PR:" +"help users to get started with building what they want to build. Here are a " +"few ideas where we'd be happy to accept a PR:" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:40 @@ -1265,10 +1259,10 @@ msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:4 msgid "" -"Include SecAgg, SecAgg+, and LightSecAgg protocol. The LightSecAgg " -"protocol has not been implemented yet, so its diagram and abstraction may" -" not be accurate in practice. The SecAgg protocol can be considered as a " -"special case of the SecAgg+ protocol." +"Include SecAgg, SecAgg+, and LightSecAgg protocol. The LightSecAgg protocol " +"has not been implemented yet, so its diagram and abstraction may not be " +"accurate in practice. The SecAgg protocol can be considered as a special " +"case of the SecAgg+ protocol." msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:8 @@ -1279,15 +1273,15 @@ msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:161 msgid "" "In this implementation, each client will be assigned with a unique index " -"(int) for secure aggregation, and thus many python dictionaries used have" -" keys of int type rather than ClientProxy type." +"(int) for secure aggregation, and thus many python dictionaries used have " +"keys of int type rather than ClientProxy type." msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:65 #: ../../source/contributor-ref-secure-aggregation-protocols.rst:198 msgid "" -"The Flower server will execute and process received results in the " -"following order:" +"The Flower server will execute and process received results in the following " +"order:" msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:159 @@ -1304,15 +1298,15 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:4 msgid "" -"This guide is for people who want to get involved with Flower, but who " -"are not used to contributing to GitHub projects." +"This guide is for people who want to get involved with Flower, but who are " +"not used to contributing to GitHub projects." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:6 msgid "" -"If you're familiar with how contributing on GitHub works, you can " -"directly checkout our :doc:`getting started guide for contributors " -"`." +"If you're familiar with how contributing on GitHub works, you can directly " +"checkout our :doc:`getting started guide for contributors `." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:10 @@ -1328,15 +1322,15 @@ msgid "" "Git is a distributed version control tool. This allows for an entire " "codebase's history to be stored and every developer's machine. It is a " "software that will need to be installed on your local machine, you can " -"follow this `guide `_ to set it up." +"follow this `guide `_ to set it up." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:16 msgid "" "GitHub, itself, is a code hosting platform for version control and " -"collaboration. It allows for everyone to collaborate and work from " -"anywhere on remote repositories." +"collaboration. It allows for everyone to collaborate and work from anywhere " +"on remote repositories." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:18 @@ -1347,10 +1341,10 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:20 msgid "" -"The idea behind the generic Git and GitHub workflow boils down to this: " -"you download code from a remote repository on GitHub, make changes " -"locally and keep track of them using Git and then you upload your new " -"history back to GitHub." +"The idea behind the generic Git and GitHub workflow boils down to this: you " +"download code from a remote repository on GitHub, make changes locally and " +"keep track of them using Git and then you upload your new history back to " +"GitHub." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:32 @@ -1359,18 +1353,18 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:24 msgid "" -"A fork is a personal copy of a GitHub repository. To create one for " -"Flower, you must navigate to ``_ (while " -"connected to your GitHub account) and click the ``Fork`` button situated " -"on the top right of the page." +"A fork is a personal copy of a GitHub repository. To create one for Flower, " +"you must navigate to ``_ (while connected to " +"your GitHub account) and click the ``Fork`` button situated on the top right " +"of the page." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:29 msgid "" "You can change the name if you want, but this is not necessary as this " -"version of Flower will be yours and will sit inside your own account " -"(i.e., in your own list of repositories). Once created, you should see on" -" the top left corner that you are looking at your own version of Flower." +"version of Flower will be yours and will sit inside your own account (i.e., " +"in your own list of repositories). Once created, you should see on the top " +"left corner that you are looking at your own version of Flower." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:47 @@ -1380,9 +1374,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:35 msgid "" "The next step is to download the forked repository on your machine to be " -"able to make changes to it. On your forked repository page, you should " -"first click on the ``Code`` button on the right, this will give you the " -"ability to copy the HTTPS link of the repository." +"able to make changes to it. On your forked repository page, you should first " +"click on the ``Code`` button on the right, this will give you the ability to " +"copy the HTTPS link of the repository." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:41 @@ -1393,8 +1387,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "" -"This will create a ``flower/`` (or the name of your fork if you renamed " -"it) folder in the current working directory." +"This will create a ``flower/`` (or the name of your fork if you renamed it) " +"folder in the current working directory." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:66 @@ -1407,10 +1401,10 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:56 msgid "" -"And here we will need to add an origin to our repository. The origin is " -"the \\ of the remote fork repository. To obtain it, we can do as " -"previously mentioned by going to our fork repository on our GitHub " -"account and copying the link." +"And here we will need to add an origin to our repository. The origin is the " +"\\ of the remote fork repository. To obtain it, we can do as " +"previously mentioned by going to our fork repository on our GitHub account " +"and copying the link." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:61 @@ -1430,16 +1424,16 @@ msgid "" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:76 -msgid "The following diagram visually explains what we did in the previous steps:" +msgid "" +"The following diagram visually explains what we did in the previous steps:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:80 msgid "" -"The upstream is the GitHub remote address of the parent repository (in " -"this case Flower), i.e. the one we eventually want to contribute to and " -"therefore need an up-to-date history of. The origin is just the GitHub " -"remote address of the forked repository we created, i.e. the copy (fork) " -"in our own account." +"The upstream is the GitHub remote address of the parent repository (in this " +"case Flower), i.e. the one we eventually want to contribute to and therefore " +"need an up-to-date history of. The origin is just the GitHub remote address " +"of the forked repository we created, i.e. the copy (fork) in our own account." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:84 @@ -1455,9 +1449,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:95 msgid "" "This can be achieved by following this :doc:`getting started guide for " -"contributors ` (note " -"that you won't need to clone the repository). Once you are able to write " -"code and test it, you can finally start making changes!" +"contributors ` (note that " +"you won't need to clone the repository). Once you are able to write code and " +"test it, you can finally start making changes!" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:100 @@ -1466,8 +1460,7 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:102 msgid "" -"Before making any changes make sure you are up-to-date with your " -"repository:" +"Before making any changes make sure you are up-to-date with your repository:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:108 @@ -1480,15 +1473,13 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:115 msgid "" -"To make the history cleaner and easier to work with, it is good practice " -"to create a new branch for each feature/project that needs to be " -"implemented." +"To make the history cleaner and easier to work with, it is good practice to " +"create a new branch for each feature/project that needs to be implemented." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:118 msgid "" -"To do so, just run the following command inside the repository's " -"directory:" +"To do so, just run the following command inside the repository's directory:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 @@ -1496,7 +1487,8 @@ msgid "**Make changes**" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 -msgid "Write great code and create wonderful changes using your favorite editor!" +msgid "" +"Write great code and create wonderful changes using your favorite editor!" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:138 @@ -1505,9 +1497,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:128 msgid "" -"Don't forget to test and format your code! Otherwise your code won't be " -"able to be merged into the Flower repository. This is done so the " -"codebase stays consistent and easy to understand." +"Don't forget to test and format your code! Otherwise your code won't be able " +"to be merged into the Flower repository. This is done so the codebase stays " +"consistent and easy to understand." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:131 @@ -1520,8 +1512,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:141 msgid "" -"Before creating a commit that will update your history, you must specify " -"to Git which files it needs to take into account." +"Before creating a commit that will update your history, you must specify to " +"Git which files it needs to take into account." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:143 @@ -1530,9 +1522,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:149 msgid "" -"To check which files have been modified compared to the last version " -"(last commit) and to see which files are staged for commit, you can use " -"the :code:`git status` command." +"To check which files have been modified compared to the last version (last " +"commit) and to see which files are staged for commit, you can use the :code:" +"`git status` command." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:160 @@ -1547,9 +1539,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:159 msgid "" -"The \\ is there to explain to others what the commit " -"does. It should be written in an imperative style and be concise. An " -"example would be :code:`git commit -m \"Add images to README\"`." +"The \\ is there to explain to others what the commit does. " +"It should be written in an imperative style and be concise. An example would " +"be :code:`git commit -m \"Add images to README\"`." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:171 @@ -1558,9 +1550,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:163 msgid "" -"Once we have committed our changes, we have effectively updated our local" -" history, but GitHub has no way of knowing this unless we push our " -"changes to our origin's remote address:" +"Once we have committed our changes, we have effectively updated our local " +"history, but GitHub has no way of knowing this unless we push our changes to " +"our origin's remote address:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:170 @@ -1579,8 +1571,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:177 msgid "" -"Once you have pushed changes, on the GitHub webpage of your repository " -"you should see the following message:" +"Once you have pushed changes, on the GitHub webpage of your repository you " +"should see the following message:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:181 @@ -1594,29 +1586,29 @@ msgid "" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:187 -msgid "At the top you have an explanation of which branch will be merged where:" +msgid "" +"At the top you have an explanation of which branch will be merged where:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:191 msgid "" -"In this example you can see that the request is to merge the branch " -"``doc-fixes`` from my forked repository to branch ``main`` from the " -"Flower repository." +"In this example you can see that the request is to merge the branch ``doc-" +"fixes`` from my forked repository to branch ``main`` from the Flower " +"repository." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:193 msgid "" "The title should be changed to adhere to the :ref:`pr_title_format` " -"guidelines, otherwise it won't be possible to merge the PR. So in this " -"case, a correct title might be ``docs(framework:skip) Fix typos``." +"guidelines, otherwise it won't be possible to merge the PR. So in this case, " +"a correct title might be ``docs(framework:skip) Fix typos``." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:196 msgid "" -"The input box in the middle is there for you to describe what your PR " -"does and to link it to existing issues. We have placed comments (that " -"won't be rendered once the PR is opened) to guide you through the " -"process." +"The input box in the middle is there for you to describe what your PR does " +"and to link it to existing issues. We have placed comments (that won't be " +"rendered once the PR is opened) to guide you through the process." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:199 @@ -1626,14 +1618,14 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:201 msgid "" "At the bottom you will find the button to open the PR. This will notify " -"reviewers that a new PR has been opened and that they should look over it" -" to merge or to request changes." +"reviewers that a new PR has been opened and that they should look over it to " +"merge or to request changes." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:204 msgid "" -"If your PR is not yet ready for review, and you don't want to notify " -"anyone, you have the option to create a draft pull request:" +"If your PR is not yet ready for review, and you don't want to notify anyone, " +"you have the option to create a draft pull request:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:209 @@ -1643,8 +1635,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "" "Once the PR has been opened (as draft or not), you can still push new " -"commits to it the same way we did before, by making changes to the branch" -" associated with the PR." +"commits to it the same way we did before, by making changes to the branch " +"associated with the PR." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:231 @@ -1653,14 +1645,14 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:212 msgid "" -"Once the PR has been opened or once the draft PR has been marked as " -"ready, a review from code owners will be automatically requested:" +"Once the PR has been opened or once the draft PR has been marked as ready, a " +"review from code owners will be automatically requested:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:216 msgid "" -"Code owners will then look into the code, ask questions, request changes " -"or validate the PR." +"Code owners will then look into the code, ask questions, request changes or " +"validate the PR." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:218 @@ -1669,8 +1661,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:222 msgid "" -"To resolve them, just push the necessary changes to the branch associated" -" with the PR:" +"To resolve them, just push the necessary changes to the branch associated " +"with the PR:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:226 @@ -1679,8 +1671,7 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:230 msgid "" -"Once all the conversations have been resolved, you can re-request a " -"review." +"Once all the conversations have been resolved, you can re-request a review." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:251 @@ -1689,8 +1680,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:234 msgid "" -"If all the automatic tests have passed and reviewers have no more changes" -" to request, they can approve the PR and merge it." +"If all the automatic tests have passed and reviewers have no more changes to " +"request, they can approve the PR and merge it." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:238 @@ -1713,14 +1704,14 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:259 msgid "" -"For our documentation, we've started to use the `Diàtaxis framework " -"`_." +"For our documentation, we've started to use the `Diàtaxis framework `_." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:261 msgid "" -"Our \"How to\" guides should have titles that continue the sentence \"How" -" to …\", for example, \"How to upgrade to Flower 1.0\"." +"Our \"How to\" guides should have titles that continue the sentence \"How to " +"…\", for example, \"How to upgrade to Flower 1.0\"." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:263 @@ -1731,8 +1722,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:265 msgid "" -"This issue is about changing the title of a doc from present continuous " -"to present simple." +"This issue is about changing the title of a doc from present continuous to " +"present simple." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:267 @@ -1771,8 +1762,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:280 msgid "" -"Build the docs and `check the result `_" +"Build the docs and `check the result `_" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:283 @@ -1781,10 +1772,10 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:285 msgid "" -"You might have noticed that the file name still reflects the old wording." -" If we just change the file, then we break all existing links to it - it " -"is **very important** to avoid that, breaking links can harm our search " -"engine ranking." +"You might have noticed that the file name still reflects the old wording. If " +"we just change the file, then we break all existing links to it - it is " +"**very important** to avoid that, breaking links can harm our search engine " +"ranking." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:288 @@ -1801,8 +1792,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:293 msgid "" -"This will cause a redirect from ``saving-progress.html`` to ``save-" -"progress.html``, old links will continue to work." +"This will cause a redirect from ``saving-progress.html`` to ``save-progress." +"html``, old links will continue to work." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:296 @@ -1826,8 +1817,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:306 msgid "" -"Commit the changes (commit messages are always imperative: \"Do " -"something\", in this case \"Change …\")" +"Commit the changes (commit messages are always imperative: \"Do something\", " +"in this case \"Change …\")" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:307 @@ -1836,8 +1827,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:308 msgid "" -"Open a PR (as shown above) with title ``docs(framework) Update how-to " -"guide title``" +"Open a PR (as shown above) with title ``docs(framework) Update how-to guide " +"title``" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:309 @@ -1859,15 +1850,14 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:316 msgid "" -"Once you have made your first PR, and want to contribute more, be sure to" -" check out the following :" +"Once you have made your first PR, and want to contribute more, be sure to " +"check out the following :" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:318 msgid "" -":doc:`Good first contributions `, where you should particularly look into the " -":code:`baselines` contributions." +":doc:`Good first contributions `, " +"where you should particularly look into the :code:`baselines` contributions." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:322 @@ -1885,17 +1875,16 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:335 msgid "" -"(or ``(:skip) `` to ignore the PR in the " -"changelog)" +"(or ``(:skip) `` to ignore the PR in the changelog)" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:337 msgid "" -"Where ```` needs to be in ``{ci, fix, feat, docs, refactor, " -"break}``, ```` should be in ``{framework, baselines, datasets, " -"examples, or '*' when modifying multiple projects which requires the " -"':skip' flag to be used}``, and ```` starts with a capitalised " -"verb in the imperative mood." +"Where ```` needs to be in ``{ci, fix, feat, docs, refactor, break}``, " +"```` should be in ``{framework, baselines, datasets, examples, or " +"'*' when modifying multiple projects which requires the ':skip' flag to be " +"used}``, and ```` starts with a capitalised verb in the imperative " +"mood." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:341 @@ -1966,14 +1955,15 @@ msgid "(Optional) `pyenv `_" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:10 -msgid "(Optional) `pyenv-virtualenv `_" +msgid "" +"(Optional) `pyenv-virtualenv `_" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:12 msgid "" "Flower uses :code:`pyproject.toml` to manage dependencies and configure " -"development tools (the ones which support it). Poetry is a build tool " -"which supports `PEP 517 `_." +"development tools (the ones which support it). Poetry is a build tool which " +"supports `PEP 517 `_." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:18 @@ -1994,14 +1984,14 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:27 msgid "" -"Install `homebrew `_. Don't forget the post-" -"installation actions to add `brew` to your PATH." +"Install `homebrew `_. Don't forget the post-installation " +"actions to add `brew` to your PATH." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:28 msgid "" -"Install `xz` (to install different Python versions) and `pandoc` to build" -" the docs::" +"Install `xz` (to install different Python versions) and `pandoc` to build " +"the docs::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:34 @@ -2010,8 +2000,8 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:35 msgid "" -"Ensure you system (Ubuntu 22.04+) is up-to-date, and you have all " -"necessary packages::" +"Ensure you system (Ubuntu 22.04+) is up-to-date, and you have all necessary " +"packages::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:44 @@ -2026,31 +2016,31 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:52 msgid "" -"Let's create the Python environment for all-things Flower. If you wish to" -" use :code:`pyenv`, we provide two convenience scripts that you can use. " -"If you prefer using something else than :code:`pyenv`, create a new " +"Let's create the Python environment for all-things Flower. If you wish to " +"use :code:`pyenv`, we provide two convenience scripts that you can use. If " +"you prefer using something else than :code:`pyenv`, create a new " "environment, activate and skip to the last point where all packages are " "installed." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:54 msgid "" -"If you don't have :code:`pyenv` installed, the following script that will" -" install it, set it up, and create the virtual environment (with " -":code:`Python 3.8.17` by default)::" +"If you don't have :code:`pyenv` installed, the following script that will " +"install it, set it up, and create the virtual environment (with :code:" +"`Python 3.8.17` by default)::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:58 msgid "" "If you already have :code:`pyenv` installed (along with the :code:`pyenv-" -"virtualenv` plugin), you can use the following convenience script (with " -":code:`Python 3.8.17` by default)::" +"virtualenv` plugin), you can use the following convenience script (with :" +"code:`Python 3.8.17` by default)::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:62 msgid "" -"3. Install the Flower package in development mode (think :code:`pip " -"install -e`) along with all necessary dependencies::" +"3. Install the Flower package in development mode (think :code:`pip install -" +"e`) along with all necessary dependencies::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:69 @@ -2060,9 +2050,9 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:71 msgid "" "The Flower repository contains a number of convenience scripts to make " -"recurring development tasks easier and less error-prone. See the " -":code:`/dev` subdirectory for a full list. The following scripts are " -"amongst the most important ones:" +"recurring development tasks easier and less error-prone. See the :code:`/" +"dev` subdirectory for a full list. The following scripts are amongst the " +"most important ones:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:77 @@ -2087,10 +2077,10 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:108 msgid "" -"Developers may integrate a pre-commit hook into their workflow utilizing " -"the `pre-commit `_ library. The pre-" -"commit hook is configured to execute two primary operations: " -"``./dev/format.sh`` and ``./dev/test.sh`` scripts." +"Developers may integrate a pre-commit hook into their workflow utilizing the " +"`pre-commit `_ library. The pre-commit hook " +"is configured to execute two primary operations: ``./dev/format.sh`` and ``./" +"dev/test.sh`` scripts." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:110 @@ -2098,26 +2088,27 @@ msgid "There are multiple ways developers can use this:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:112 -msgid "Install the pre-commit hook to your local git directory by simply running:" +msgid "" +"Install the pre-commit hook to your local git directory by simply running:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:118 msgid "" -"Each ``git commit`` will trigger the execution of formatting and " -"linting/test scripts." +"Each ``git commit`` will trigger the execution of formatting and linting/" +"test scripts." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:119 msgid "" -"If in a hurry, bypass the hook using ``--no-verify`` with the ``git " -"commit`` command. ::" +"If in a hurry, bypass the hook using ``--no-verify`` with the ``git commit`` " +"command. ::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:124 msgid "" "For developers who prefer not to install the hook permanently, it is " -"possible to execute a one-time check prior to committing changes by using" -" the following command:" +"possible to execute a one-time check prior to committing changes by using " +"the following command:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:130 @@ -2132,10 +2123,10 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:135 msgid "" -"Developers could run the full set of Github Actions workflows under their" -" local environment by using `Act `_. " -"Please refer to the installation instructions under the linked repository" -" and run the next command under Flower main cloned repository folder::" +"Developers could run the full set of Github Actions workflows under their " +"local environment by using `Act `_. Please " +"refer to the installation instructions under the linked repository and run " +"the next command under Flower main cloned repository folder::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:142 @@ -2150,14 +2141,14 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:149 msgid "" -"Flower uses Poetry to build releases. The necessary command is wrapped in" -" a simple script::" +"Flower uses Poetry to build releases. The necessary command is wrapped in a " +"simple script::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:154 msgid "" -"The resulting :code:`.whl` and :code:`.tar.gz` releases will be stored in" -" the :code:`/dist` subdirectory." +"The resulting :code:`.whl` and :code:`.tar.gz` releases will be stored in " +"the :code:`/dist` subdirectory." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:159 @@ -2166,9 +2157,9 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:161 msgid "" -"Flower's documentation uses `Sphinx `_. " -"There's no convenience script to re-build the documentation yet, but it's" -" pretty easy::" +"Flower's documentation uses `Sphinx `_. There's " +"no convenience script to re-build the documentation yet, but it's pretty " +"easy::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:167 @@ -2181,14 +2172,13 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:4 msgid "" -"This tutorial will show you how to use Flower to build a federated " -"version of an existing machine learning workload with `FedBN " -"`_, a federated training strategy " -"designed for non-iid data. We are using PyTorch to train a Convolutional " -"Neural Network(with Batch Normalization layers) on the CIFAR-10 dataset. " -"When applying FedBN, only few changes needed compared to :doc:`Example: " -"PyTorch - From Centralized To Federated `." +"This tutorial will show you how to use Flower to build a federated version " +"of an existing machine learning workload with `FedBN `_, a federated training strategy designed for non-iid data. We " +"are using PyTorch to train a Convolutional Neural Network(with Batch " +"Normalization layers) on the CIFAR-10 dataset. When applying FedBN, only few " +"changes needed compared to :doc:`Example: PyTorch - From Centralized To " +"Federated `." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:9 @@ -2198,10 +2188,10 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:10 msgid "" -"All files are revised based on :doc:`Example: PyTorch - From Centralized " -"To Federated `. The only " -"thing to do is modifying the file called :code:`cifar.py`, revised part " -"is shown below:" +"All files are revised based on :doc:`Example: PyTorch - From Centralized To " +"Federated `. The only thing " +"to do is modifying the file called :code:`cifar.py`, revised part is shown " +"below:" msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:13 @@ -2217,10 +2207,10 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:47 msgid "" -"So far this should all look fairly familiar if you've used PyTorch " -"before. Let's take the next step and use what we've built to create a " -"federated learning system within FedBN, the system consists of one server" -" and two clients." +"So far this should all look fairly familiar if you've used PyTorch before. " +"Let's take the next step and use what we've built to create a federated " +"learning system within FedBN, the system consists of one server and two " +"clients." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:51 @@ -2231,25 +2221,25 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:53 msgid "" "If you have read :doc:`Example: PyTorch - From Centralized To Federated " -"`, the following parts are" -" easy to follow, only :code:`get_parameters` and :code:`set_parameters` " -"function in :code:`client.py` needed to revise. If not, please read the " -":doc:`Example: PyTorch - From Centralized To Federated `. first." +"`, the following parts are " +"easy to follow, only :code:`get_parameters` and :code:`set_parameters` " +"function in :code:`client.py` needed to revise. If not, please read the :doc:" +"`Example: PyTorch - From Centralized To Federated `. first." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:56 msgid "" -"Our example consists of one *server* and two *clients*. In FedBN, " -":code:`server.py` keeps unchanged, we can start the server directly." +"Our example consists of one *server* and two *clients*. In FedBN, :code:" +"`server.py` keeps unchanged, we can start the server directly." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:62 msgid "" -"Finally, we will revise our *client* logic by changing " -":code:`get_parameters` and :code:`set_parameters` in :code:`client.py`, " -"we will exclude batch normalization parameters from model parameter list " -"when sending to or receiving from the server." +"Finally, we will revise our *client* logic by changing :code:" +"`get_parameters` and :code:`set_parameters` in :code:`client.py`, we will " +"exclude batch normalization parameters from model parameter list when " +"sending to or receiving from the server." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:85 @@ -2258,9 +2248,9 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:91 msgid "" -"in each window (make sure that the server is still running before you do " -"so) and see your (previously centralized) PyTorch project run federated " -"learning with FedBN strategy across two clients. Congratulations!" +"in each window (make sure that the server is still running before you do so) " +"and see your (previously centralized) PyTorch project run federated learning " +"with FedBN strategy across two clients. Congratulations!" msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:94 @@ -2272,13 +2262,12 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:96 msgid "" -"The full source code for this example can be found `here " -"`_. Our example is of course somewhat over-" -"simplified because both clients load the exact same dataset, which isn't " -"realistic. You're now prepared to explore this topic further. How about " -"using different subsets of CIFAR-10 on each client? How about adding more" -" clients?" +"The full source code for this example can be found `here `_. Our " +"example is of course somewhat over-simplified because both clients load the " +"exact same dataset, which isn't realistic. You're now prepared to explore " +"this topic further. How about using different subsets of CIFAR-10 on each " +"client? How about adding more clients?" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:2 @@ -2288,23 +2277,22 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:4 #: ../../source/tutorial-quickstart-jax.rst:10 msgid "" -"This tutorial will show you how to use Flower to build a federated " -"version of an existing JAX workload. We are using JAX to train a linear " -"regression model on a scikit-learn dataset. We will structure the example" -" similar to our `PyTorch - From Centralized To Federated " -"`_ walkthrough. First, we build a centralized " -"training approach based on the `Linear Regression with JAX " -"`_" -" tutorial`. Then, we build upon the centralized training code to run the " -"training in a federated fashion." +"This tutorial will show you how to use Flower to build a federated version " +"of an existing JAX workload. We are using JAX to train a linear regression " +"model on a scikit-learn dataset. We will structure the example similar to " +"our `PyTorch - From Centralized To Federated `_ walkthrough. " +"First, we build a centralized training approach based on the `Linear " +"Regression with JAX `_ tutorial`. Then, we build upon the centralized " +"training code to run the training in a federated fashion." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:10 #: ../../source/tutorial-quickstart-jax.rst:16 msgid "" -"Before we start building our JAX example, we need install the packages " -":code:`jax`, :code:`jaxlib`, :code:`scikit-learn`, and :code:`flwr`:" +"Before we start building our JAX example, we need install the packages :code:" +"`jax`, :code:`jaxlib`, :code:`scikit-learn`, and :code:`flwr`:" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:18 @@ -2315,10 +2303,10 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:20 #: ../../source/tutorial-quickstart-jax.rst:26 msgid "" -"We begin with a brief description of the centralized training code based " -"on a :code:`Linear Regression` model. If you want a more in-depth " -"explanation of what's going on then have a look at the official `JAX " -"documentation `_." +"We begin with a brief description of the centralized training code based on " +"a :code:`Linear Regression` model. If you want a more in-depth explanation " +"of what's going on then have a look at the official `JAX documentation " +"`_." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:23 @@ -2326,53 +2314,51 @@ msgstr "" msgid "" "Let's create a new file called :code:`jax_training.py` with all the " "components required for a traditional (centralized) linear regression " -"training. First, the JAX packages :code:`jax` and :code:`jaxlib` need to " -"be imported. In addition, we need to import :code:`sklearn` since we use " -":code:`make_regression` for the dataset and :code:`train_test_split` to " -"split the dataset into a training and test set. You can see that we do " -"not yet import the :code:`flwr` package for federated learning. This will" -" be done later." +"training. First, the JAX packages :code:`jax` and :code:`jaxlib` need to be " +"imported. In addition, we need to import :code:`sklearn` since we use :code:" +"`make_regression` for the dataset and :code:`train_test_split` to split the " +"dataset into a training and test set. You can see that we do not yet import " +"the :code:`flwr` package for federated learning. This will be done later." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:37 #: ../../source/tutorial-quickstart-jax.rst:43 msgid "" -"The :code:`load_data()` function loads the mentioned training and test " -"sets." +"The :code:`load_data()` function loads the mentioned training and test sets." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:47 #: ../../source/tutorial-quickstart-jax.rst:53 msgid "" -"The model architecture (a very simple :code:`Linear Regression` model) is" -" defined in :code:`load_model()`." +"The model architecture (a very simple :code:`Linear Regression` model) is " +"defined in :code:`load_model()`." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:59 #: ../../source/tutorial-quickstart-jax.rst:65 msgid "" -"We now need to define the training (function :code:`train()`), which " -"loops over the training set and measures the loss (function " -":code:`loss_fn()`) for each batch of training examples. The loss function" -" is separate since JAX takes derivatives with a :code:`grad()` function " -"(defined in the :code:`main()` function and called in :code:`train()`)." +"We now need to define the training (function :code:`train()`), which loops " +"over the training set and measures the loss (function :code:`loss_fn()`) for " +"each batch of training examples. The loss function is separate since JAX " +"takes derivatives with a :code:`grad()` function (defined in the :code:" +"`main()` function and called in :code:`train()`)." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:77 #: ../../source/tutorial-quickstart-jax.rst:83 msgid "" -"The evaluation of the model is defined in the function " -":code:`evaluation()`. The function takes all test examples and measures " -"the loss of the linear regression model." +"The evaluation of the model is defined in the function :code:`evaluation()`. " +"The function takes all test examples and measures the loss of the linear " +"regression model." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:88 #: ../../source/tutorial-quickstart-jax.rst:94 msgid "" "Having defined the data loading, model architecture, training, and " -"evaluation we can put everything together and train our model using JAX. " -"As already mentioned, the :code:`jax.grad()` function is defined in " -":code:`main()` and passed to :code:`train()`." +"evaluation we can put everything together and train our model using JAX. As " +"already mentioned, the :code:`jax.grad()` function is defined in :code:" +"`main()` and passed to :code:`train()`." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:105 @@ -2383,9 +2369,9 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:111 #: ../../source/tutorial-quickstart-jax.rst:117 msgid "" -"So far this should all look fairly familiar if you've used JAX before. " -"Let's take the next step and use what we've built to create a simple " -"federated learning system consisting of one server and two clients." +"So far this should all look fairly familiar if you've used JAX before. Let's " +"take the next step and use what we've built to create a simple federated " +"learning system consisting of one server and two clients." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:115 @@ -2396,24 +2382,24 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:117 #: ../../source/tutorial-quickstart-jax.rst:123 msgid "" -"The concept of federating an existing workload is always the same and " -"easy to understand. We have to start a *server* and then use the code in " -":code:`jax_training.py` for the *clients* that are connected to the " -"*server*. The *server* sends model parameters to the clients. The " -"*clients* run the training and update the parameters. The updated " -"parameters are sent back to the *server*, which averages all received " -"parameter updates. This describes one round of the federated learning " -"process, and we repeat this for multiple rounds." +"The concept of federating an existing workload is always the same and easy " +"to understand. We have to start a *server* and then use the code in :code:" +"`jax_training.py` for the *clients* that are connected to the *server*. The " +"*server* sends model parameters to the clients. The *clients* run the " +"training and update the parameters. The updated parameters are sent back to " +"the *server*, which averages all received parameter updates. This describes " +"one round of the federated learning process, and we repeat this for multiple " +"rounds." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:123 #: ../../source/example-pytorch-from-centralized-to-federated.rst:181 #: ../../source/tutorial-quickstart-jax.rst:129 msgid "" -"Our example consists of one *server* and two *clients*. Let's set up " -":code:`server.py` first. The *server* needs to import the Flower package " -":code:`flwr`. Next, we use the :code:`start_server` function to start a " -"server and tell it to perform three rounds of federated learning." +"Our example consists of one *server* and two *clients*. Let's set up :code:" +"`server.py` first. The *server* needs to import the Flower package :code:" +"`flwr`. Next, we use the :code:`start_server` function to start a server and " +"tell it to perform three rounds of federated learning." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:133 @@ -2425,25 +2411,24 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:139 #: ../../source/tutorial-quickstart-jax.rst:145 msgid "" -"Finally, we will define our *client* logic in :code:`client.py` and build" -" upon the previously defined JAX training in :code:`jax_training.py`. Our" -" *client* needs to import :code:`flwr`, but also :code:`jax` and " -":code:`jaxlib` to update the parameters on our JAX model:" +"Finally, we will define our *client* logic in :code:`client.py` and build " +"upon the previously defined JAX training in :code:`jax_training.py`. Our " +"*client* needs to import :code:`flwr`, but also :code:`jax` and :code:" +"`jaxlib` to update the parameters on our JAX model:" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:154 #: ../../source/tutorial-quickstart-jax.rst:160 msgid "" -"Implementing a Flower *client* basically means implementing a subclass of" -" either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. " -"Our implementation will be based on :code:`flwr.client.NumPyClient` and " -"we'll call it :code:`FlowerClient`. :code:`NumPyClient` is slightly " -"easier to implement than :code:`Client` if you use a framework with good " -"NumPy interoperability (like JAX) because it avoids some of the " -"boilerplate that would otherwise be necessary. :code:`FlowerClient` needs" -" to implement four methods, two methods for getting/setting model " -"parameters, one method for training the model, and one method for testing" -" the model:" +"Implementing a Flower *client* basically means implementing a subclass of " +"either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. Our " +"implementation will be based on :code:`flwr.client.NumPyClient` and we'll " +"call it :code:`FlowerClient`. :code:`NumPyClient` is slightly easier to " +"implement than :code:`Client` if you use a framework with good NumPy " +"interoperability (like JAX) because it avoids some of the boilerplate that " +"would otherwise be necessary. :code:`FlowerClient` needs to implement four " +"methods, two methods for getting/setting model parameters, one method for " +"training the model, and one method for testing the model:" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:161 @@ -2455,8 +2440,7 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 #: ../../source/tutorial-quickstart-jax.rst:166 msgid "" -"set the model parameters on the local model that are received from the " -"server" +"set the model parameters on the local model that are received from the server" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:161 @@ -2468,8 +2452,8 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:220 #: ../../source/tutorial-quickstart-jax.rst:168 msgid "" -"loop over the list of model parameters received as NumPy " -":code:`ndarray`'s (think list of neural network layers)" +"loop over the list of model parameters received as NumPy :code:`ndarray`'s " +"(think list of neural network layers)" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:163 @@ -2484,8 +2468,8 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:222 #: ../../source/tutorial-quickstart-jax.rst:170 msgid "" -"get the model parameters and return them as a list of NumPy " -":code:`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" +"get the model parameters and return them as a list of NumPy :code:" +"`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:167 @@ -2503,8 +2487,8 @@ msgstr "" #: ../../source/tutorial-quickstart-jax.rst:172 #: ../../source/tutorial-quickstart-jax.rst:176 msgid "" -"update the parameters of the local model with the parameters received " -"from the server" +"update the parameters of the local model with the parameters received from " +"the server" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:167 @@ -2540,21 +2524,20 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:174 #: ../../source/tutorial-quickstart-jax.rst:180 msgid "" -"The challenging part is to transform the JAX model parameters from " -":code:`DeviceArray` to :code:`NumPy ndarray` to make them compatible with" -" `NumPyClient`." +"The challenging part is to transform the JAX model parameters from :code:" +"`DeviceArray` to :code:`NumPy ndarray` to make them compatible with " +"`NumPyClient`." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:176 #: ../../source/tutorial-quickstart-jax.rst:182 msgid "" -"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make" -" use of the functions :code:`train()` and :code:`evaluate()` previously " +"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make " +"use of the functions :code:`train()` and :code:`evaluate()` previously " "defined in :code:`jax_training.py`. So what we really do here is we tell " -"Flower through our :code:`NumPyClient` subclass which of our already " -"defined functions to call for training and evaluation. We included type " -"annotations to give you a better understanding of the data types that get" -" passed around." +"Flower through our :code:`NumPyClient` subclass which of our already defined " +"functions to call for training and evaluation. We included type annotations " +"to give you a better understanding of the data types that get passed around." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:245 @@ -2571,8 +2554,8 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:274 #: ../../source/tutorial-quickstart-jax.rst:280 msgid "" -"in each window (make sure that the server is still running before you do " -"so) and see your JAX project run federated learning across two clients. " +"in each window (make sure that the server is still running before you do so) " +"and see your JAX project run federated learning across two clients. " "Congratulations!" msgstr "" @@ -2580,16 +2563,16 @@ msgstr "" #: ../../source/tutorial-quickstart-jax.rst:285 msgid "" "The source code of this example was improved over time and can be found " -"here: `Quickstart JAX `_. Our example is somewhat over-simplified because both " +"here: `Quickstart JAX `_. Our example is somewhat over-simplified because both " "clients load the same dataset." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:282 #: ../../source/tutorial-quickstart-jax.rst:288 msgid "" -"You're now prepared to explore this topic further. How about using a more" -" sophisticated model or using a different dataset? How about adding more " +"You're now prepared to explore this topic further. How about using a more " +"sophisticated model or using a different dataset? How about adding more " "clients?" msgstr "" @@ -2599,32 +2582,31 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:4 msgid "" -"This tutorial will show you how to use Flower to build a federated " -"version of an existing machine learning workload. We are using PyTorch to" -" train a Convolutional Neural Network on the CIFAR-10 dataset. First, we " -"introduce this machine learning task with a centralized training approach" -" based on the `Deep Learning with PyTorch " -"`_ " -"tutorial. Then, we build upon the centralized training code to run the " -"training in a federated fashion." +"This tutorial will show you how to use Flower to build a federated version " +"of an existing machine learning workload. We are using PyTorch to train a " +"Convolutional Neural Network on the CIFAR-10 dataset. First, we introduce " +"this machine learning task with a centralized training approach based on the " +"`Deep Learning with PyTorch `_ tutorial. Then, we build upon the centralized " +"training code to run the training in a federated fashion." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:12 msgid "" -"We begin with a brief description of the centralized CNN training code. " -"If you want a more in-depth explanation of what's going on then have a " -"look at the official `PyTorch tutorial " -"`_." +"We begin with a brief description of the centralized CNN training code. If " +"you want a more in-depth explanation of what's going on then have a look at " +"the official `PyTorch tutorial `_." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:15 msgid "" "Let's create a new file called :code:`cifar.py` with all the components " -"required for a traditional (centralized) training on CIFAR-10. First, all" -" required packages (such as :code:`torch` and :code:`torchvision`) need " -"to be imported. You can see that we do not import any package for " -"federated learning. You can keep all these imports as they are even when " -"we add the federated learning components at a later point." +"required for a traditional (centralized) training on CIFAR-10. First, all " +"required packages (such as :code:`torch` and :code:`torchvision`) need to be " +"imported. You can see that we do not import any package for federated " +"learning. You can keep all these imports as they are even when we add the " +"federated learning components at a later point." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:32 @@ -2636,22 +2618,22 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:56 msgid "" -"The :code:`load_data()` function loads the CIFAR-10 training and test " -"sets. The :code:`transform` normalized the data after loading." +"The :code:`load_data()` function loads the CIFAR-10 training and test sets. " +"The :code:`transform` normalized the data after loading." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:74 msgid "" -"We now need to define the training (function :code:`train()`) which loops" -" over the training set, measures the loss, backpropagates it, and then " -"takes one optimizer step for each batch of training examples." +"We now need to define the training (function :code:`train()`) which loops " +"over the training set, measures the loss, backpropagates it, and then takes " +"one optimizer step for each batch of training examples." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:76 msgid "" -"The evaluation of the model is defined in the function :code:`test()`. " -"The function loops over all test samples and measures the loss of the " -"model based on the test dataset." +"The evaluation of the model is defined in the function :code:`test()`. The " +"function loops over all test samples and measures the loss of the model " +"based on the test dataset." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:136 @@ -2662,60 +2644,59 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:163 msgid "" -"So far, this should all look fairly familiar if you've used PyTorch " -"before. Let's take the next step and use what we've built to create a " -"simple federated learning system consisting of one server and two " -"clients." +"So far, this should all look fairly familiar if you've used PyTorch before. " +"Let's take the next step and use what we've built to create a simple " +"federated learning system consisting of one server and two clients." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:169 msgid "" -"The simple machine learning project discussed in the previous section " -"trains the model on a single dataset (CIFAR-10), we call this centralized" -" learning. This concept of centralized learning, as shown in the previous" -" section, is probably known to most of you, and many of you have used it " -"previously. Normally, if you'd want to run machine learning workloads in " -"a federated fashion, then you'd have to change most of your code and set " -"everything up from scratch. This can be a considerable effort." +"The simple machine learning project discussed in the previous section trains " +"the model on a single dataset (CIFAR-10), we call this centralized learning. " +"This concept of centralized learning, as shown in the previous section, is " +"probably known to most of you, and many of you have used it previously. " +"Normally, if you'd want to run machine learning workloads in a federated " +"fashion, then you'd have to change most of your code and set everything up " +"from scratch. This can be a considerable effort." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:173 msgid "" -"However, with Flower you can evolve your pre-existing code into a " -"federated learning setup without the need for a major rewrite." +"However, with Flower you can evolve your pre-existing code into a federated " +"learning setup without the need for a major rewrite." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:175 msgid "" -"The concept is easy to understand. We have to start a *server* and then " -"use the code in :code:`cifar.py` for the *clients* that are connected to " -"the *server*. The *server* sends model parameters to the clients. The " -"*clients* run the training and update the parameters. The updated " -"parameters are sent back to the *server* which averages all received " -"parameter updates. This describes one round of the federated learning " -"process and we repeat this for multiple rounds." +"The concept is easy to understand. We have to start a *server* and then use " +"the code in :code:`cifar.py` for the *clients* that are connected to the " +"*server*. The *server* sends model parameters to the clients. The *clients* " +"run the training and update the parameters. The updated parameters are sent " +"back to the *server* which averages all received parameter updates. This " +"describes one round of the federated learning process and we repeat this for " +"multiple rounds." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:197 msgid "" -"Finally, we will define our *client* logic in :code:`client.py` and build" -" upon the previously defined centralized training in :code:`cifar.py`. " -"Our *client* needs to import :code:`flwr`, but also :code:`torch` to " -"update the parameters on our PyTorch model:" +"Finally, we will define our *client* logic in :code:`client.py` and build " +"upon the previously defined centralized training in :code:`cifar.py`. Our " +"*client* needs to import :code:`flwr`, but also :code:`torch` to update the " +"parameters on our PyTorch model:" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:213 msgid "" -"Implementing a Flower *client* basically means implementing a subclass of" -" either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. " -"Our implementation will be based on :code:`flwr.client.NumPyClient` and " -"we'll call it :code:`CifarClient`. :code:`NumPyClient` is slightly easier" -" to implement than :code:`Client` if you use a framework with good NumPy " -"interoperability (like PyTorch or TensorFlow/Keras) because it avoids " -"some of the boilerplate that would otherwise be necessary. " -":code:`CifarClient` needs to implement four methods, two methods for " -"getting/setting model parameters, one method for training the model, and " -"one method for testing the model:" +"Implementing a Flower *client* basically means implementing a subclass of " +"either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. Our " +"implementation will be based on :code:`flwr.client.NumPyClient` and we'll " +"call it :code:`CifarClient`. :code:`NumPyClient` is slightly easier to " +"implement than :code:`Client` if you use a framework with good NumPy " +"interoperability (like PyTorch or TensorFlow/Keras) because it avoids some " +"of the boilerplate that would otherwise be necessary. :code:`CifarClient` " +"needs to implement four methods, two methods for getting/setting model " +"parameters, one method for training the model, and one method for testing " +"the model:" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 @@ -2732,40 +2713,39 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:232 msgid "" -"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make" -" use of the functions :code:`train()` and :code:`test()` previously " -"defined in :code:`cifar.py`. So what we really do here is we tell Flower " -"through our :code:`NumPyClient` subclass which of our already defined " -"functions to call for training and evaluation. We included type " -"annotations to give you a better understanding of the data types that get" -" passed around." +"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make " +"use of the functions :code:`train()` and :code:`test()` previously defined " +"in :code:`cifar.py`. So what we really do here is we tell Flower through " +"our :code:`NumPyClient` subclass which of our already defined functions to " +"call for training and evaluation. We included type annotations to give you a " +"better understanding of the data types that get passed around." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:280 msgid "" "All that's left to do it to define a function that loads both model and " -"data, creates a :code:`CifarClient`, and starts this client. You load " -"your data and model by using :code:`cifar.py`. Start :code:`CifarClient` " -"with the function :code:`fl.client.start_client()` by pointing it at the " -"same IP address we used in :code:`server.py`:" +"data, creates a :code:`CifarClient`, and starts this client. You load your " +"data and model by using :code:`cifar.py`. Start :code:`CifarClient` with the " +"function :code:`fl.client.start_client()` by pointing it at the same IP " +"address we used in :code:`server.py`:" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:307 msgid "" -"in each window (make sure that the server is running before you do so) " -"and see your (previously centralized) PyTorch project run federated " -"learning across two clients. Congratulations!" +"in each window (make sure that the server is running before you do so) and " +"see your (previously centralized) PyTorch project run federated learning " +"across two clients. Congratulations!" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:312 msgid "" "The full source code for this example: `PyTorch: From Centralized To " -"Federated (Code) `_. Our example is, of course, " -"somewhat over-simplified because both clients load the exact same " -"dataset, which isn't realistic. You're now prepared to explore this topic" -" further. How about using different subsets of CIFAR-10 on each client? " -"How about adding more clients?" +"Federated (Code) `_. Our example is, of course, somewhat over-" +"simplified because both clients load the exact same dataset, which isn't " +"realistic. You're now prepared to explore this topic further. How about " +"using different subsets of CIFAR-10 on each client? How about adding more " +"clients?" msgstr "" #: ../../source/explanation-differential-privacy.rst:2 @@ -2776,19 +2756,18 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:3 msgid "" -"The information in datasets like healthcare, financial transactions, user" -" preferences, etc., is valuable and has the potential for scientific " -"breakthroughs and provides important business insights. However, such " -"data is also sensitive and there is a risk of compromising individual " -"privacy." +"The information in datasets like healthcare, financial transactions, user " +"preferences, etc., is valuable and has the potential for scientific " +"breakthroughs and provides important business insights. However, such data " +"is also sensitive and there is a risk of compromising individual privacy." msgstr "" #: ../../source/explanation-differential-privacy.rst:6 msgid "" "Traditional methods like anonymization alone would not work because of " -"attacks like Re-identification and Data Linkage. That's where " -"differential privacy comes in. It provides the possibility of analyzing " -"data while ensuring the privacy of individuals." +"attacks like Re-identification and Data Linkage. That's where differential " +"privacy comes in. It provides the possibility of analyzing data while " +"ensuring the privacy of individuals." msgstr "" #: ../../source/explanation-differential-privacy.rst:12 @@ -2797,8 +2776,8 @@ msgid "" "instance, Alice's data). Differential Privacy (DP) guarantees that any " "analysis (M), like calculating the average income, will produce nearly " "identical results for both datasets (O and O' would be similar). This " -"preserves group patterns while obscuring individual details, ensuring the" -" individual's information remains hidden in the crowd." +"preserves group patterns while obscuring individual details, ensuring the " +"individual's information remains hidden in the crowd." msgstr "" #: ../../source/explanation-differential-privacy.rst:-1 @@ -2809,8 +2788,7 @@ msgstr "" msgid "" "One of the most commonly used mechanisms to achieve DP is adding enough " "noise to the output of the analysis to mask the contribution of each " -"individual in the data while preserving the overall accuracy of the " -"analysis." +"individual in the data while preserving the overall accuracy of the analysis." msgstr "" #: ../../source/explanation-differential-privacy.rst:25 @@ -2821,12 +2799,12 @@ msgstr "" msgid "" "Differential Privacy (DP) provides statistical guarantees against the " "information an adversary can infer through the output of a randomized " -"algorithm. It provides an unconditional upper bound on the influence of a" -" single individual on the output of the algorithm by adding noise [1]. A " -"randomized mechanism M provides (:math:`\\epsilon`, " -":math:`\\delta`)-differential privacy if for any two neighboring " -"databases, D :sub:`1` and D :sub:`2`, that differ in only a single " -"record, and for all possible outputs S ⊆ Range(A):" +"algorithm. It provides an unconditional upper bound on the influence of a " +"single individual on the output of the algorithm by adding noise [1]. A " +"randomized mechanism M provides (:math:`\\epsilon`, :math:`\\delta`)-" +"differential privacy if for any two neighboring databases, D :sub:`1` and D :" +"sub:`2`, that differ in only a single record, and for all possible outputs S " +"⊆ Range(A):" msgstr "" #: ../../source/explanation-differential-privacy.rst:32 @@ -2840,11 +2818,11 @@ msgid "" "The :math:`\\epsilon` parameter, also known as the privacy budget, is a " "metric of privacy loss. It also controls the privacy-utility trade-off; " "lower :math:`\\epsilon` values indicate higher levels of privacy but are " -"likely to reduce utility as well. The :math:`\\delta` parameter accounts " -"for a small probability on which the upper bound :math:`\\epsilon` does " -"not hold. The amount of noise needed to achieve differential privacy is " -"proportional to the sensitivity of the output, which measures the maximum" -" change in the output due to the inclusion or removal of a single record." +"likely to reduce utility as well. The :math:`\\delta` parameter accounts for " +"a small probability on which the upper bound :math:`\\epsilon` does not " +"hold. The amount of noise needed to achieve differential privacy is " +"proportional to the sensitivity of the output, which measures the maximum " +"change in the output due to the inclusion or removal of a single record." msgstr "" #: ../../source/explanation-differential-privacy.rst:45 @@ -2855,15 +2833,14 @@ msgstr "" msgid "" "DP can be utilized in machine learning to preserve the privacy of the " "training data. Differentially private machine learning algorithms are " -"designed in a way to prevent the algorithm to learn any specific " -"information about any individual data points and subsequently prevent the" -" model from revealing sensitive information. Depending on the stage at " -"which noise is introduced, various methods exist for applying DP to " -"machine learning algorithms. One approach involves adding noise to the " -"training data (either to the features or labels), while another method " -"entails injecting noise into the gradients of the loss function during " -"model training. Additionally, such noise can be incorporated into the " -"model's output." +"designed in a way to prevent the algorithm to learn any specific information " +"about any individual data points and subsequently prevent the model from " +"revealing sensitive information. Depending on the stage at which noise is " +"introduced, various methods exist for applying DP to machine learning " +"algorithms. One approach involves adding noise to the training data (either " +"to the features or labels), while another method entails injecting noise " +"into the gradients of the loss function during model training. Additionally, " +"such noise can be incorporated into the model's output." msgstr "" #: ../../source/explanation-differential-privacy.rst:53 @@ -2875,40 +2852,40 @@ msgid "" "Federated learning is a data minimization approach that allows multiple " "parties to collaboratively train a model without sharing their raw data. " "However, federated learning also introduces new privacy challenges. The " -"model updates between parties and the central server can leak information" -" about the local data. These leaks can be exploited by attacks such as " +"model updates between parties and the central server can leak information " +"about the local data. These leaks can be exploited by attacks such as " "membership inference and property inference attacks, or model inversion " "attacks." msgstr "" #: ../../source/explanation-differential-privacy.rst:58 msgid "" -"DP can play a crucial role in federated learning to provide privacy for " -"the clients' data." +"DP can play a crucial role in federated learning to provide privacy for the " +"clients' data." msgstr "" #: ../../source/explanation-differential-privacy.rst:60 msgid "" -"Depending on the granularity of privacy provision or the location of " -"noise addition, different forms of DP exist in federated learning. In " -"this explainer, we focus on two approaches of DP utilization in federated" -" learning based on where the noise is added: at the server (also known as" -" the center) or at the client (also known as the local)." +"Depending on the granularity of privacy provision or the location of noise " +"addition, different forms of DP exist in federated learning. In this " +"explainer, we focus on two approaches of DP utilization in federated " +"learning based on where the noise is added: at the server (also known as the " +"center) or at the client (also known as the local)." msgstr "" #: ../../source/explanation-differential-privacy.rst:63 msgid "" -"**Central Differential Privacy**: DP is applied by the server and the " -"goal is to prevent the aggregated model from leaking information about " -"each client's data." +"**Central Differential Privacy**: DP is applied by the server and the goal " +"is to prevent the aggregated model from leaking information about each " +"client's data." msgstr "" #: ../../source/explanation-differential-privacy.rst:65 msgid "" "**Local Differential Privacy**: DP is applied on the client side before " -"sending any information to the server and the goal is to prevent the " -"updates that are sent to the server from leaking any information about " -"the client's data." +"sending any information to the server and the goal is to prevent the updates " +"that are sent to the server from leaking any information about the client's " +"data." msgstr "" #: ../../source/explanation-differential-privacy.rst:-1 @@ -2919,24 +2896,24 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:69 msgid "" -"In this approach, which is also known as user-level DP, the central " -"server is responsible for adding noise to the globally aggregated " -"parameters. It should be noted that trust in the server is required." +"In this approach, which is also known as user-level DP, the central server " +"is responsible for adding noise to the globally aggregated parameters. It " +"should be noted that trust in the server is required." msgstr "" #: ../../source/explanation-differential-privacy.rst:76 msgid "" -"While there are various ways to implement central DP in federated " -"learning, we concentrate on the algorithms proposed by [2] and [3]. The " -"overall approach is to clip the model updates sent by the clients and add" -" some amount of noise to the aggregated model. In each iteration, a " -"random set of clients is chosen with a specific probability for training." -" Each client performs local training on its own data. The update of each " -"client is then clipped by some value `S` (sensitivity `S`). This would " -"limit the impact of any individual client which is crucial for privacy " -"and often beneficial for robustness. A common approach to achieve this is" -" by restricting the `L2` norm of the clients' model updates, ensuring " -"that larger updates are scaled down to fit within the norm `S`." +"While there are various ways to implement central DP in federated learning, " +"we concentrate on the algorithms proposed by [2] and [3]. The overall " +"approach is to clip the model updates sent by the clients and add some " +"amount of noise to the aggregated model. In each iteration, a random set of " +"clients is chosen with a specific probability for training. Each client " +"performs local training on its own data. The update of each client is then " +"clipped by some value `S` (sensitivity `S`). This would limit the impact of " +"any individual client which is crucial for privacy and often beneficial for " +"robustness. A common approach to achieve this is by restricting the `L2` " +"norm of the clients' model updates, ensuring that larger updates are scaled " +"down to fit within the norm `S`." msgstr "" #: ../../source/explanation-differential-privacy.rst:-1 @@ -2945,11 +2922,11 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:89 msgid "" -"Afterwards, the Gaussian mechanism is used to add noise in order to " -"distort the sum of all clients' updates. The amount of noise is scaled to" -" the sensitivity value to obtain a privacy guarantee. The Gaussian " -"mechanism is used with a noise sampled from `N (0, σ²)` where `σ = ( " -"noise_scale * S ) / (number of sampled clients)`." +"Afterwards, the Gaussian mechanism is used to add noise in order to distort " +"the sum of all clients' updates. The amount of noise is scaled to the " +"sensitivity value to obtain a privacy guarantee. The Gaussian mechanism is " +"used with a noise sampled from `N (0, σ²)` where `σ = ( noise_scale * S ) / " +"(number of sampled clients)`." msgstr "" #: ../../source/explanation-differential-privacy.rst:94 @@ -2958,29 +2935,29 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:96 msgid "" -"There are two forms of clipping commonly used in Central DP: Fixed " -"Clipping and Adaptive Clipping." +"There are two forms of clipping commonly used in Central DP: Fixed Clipping " +"and Adaptive Clipping." msgstr "" #: ../../source/explanation-differential-privacy.rst:98 msgid "" -"**Fixed Clipping** : A predefined fix threshold is set for the magnitude " -"of clients' updates. Any update exceeding this threshold is clipped back " -"to the threshold value." +"**Fixed Clipping** : A predefined fix threshold is set for the magnitude of " +"clients' updates. Any update exceeding this threshold is clipped back to the " +"threshold value." msgstr "" #: ../../source/explanation-differential-privacy.rst:100 msgid "" -"**Adaptive Clipping** : The clipping threshold dynamically adjusts based " -"on the observed update distribution [4]. It means that the clipping value" -" is tuned during the rounds with respect to the quantile of the update " -"norm distribution." +"**Adaptive Clipping** : The clipping threshold dynamically adjusts based on " +"the observed update distribution [4]. It means that the clipping value is " +"tuned during the rounds with respect to the quantile of the update norm " +"distribution." msgstr "" #: ../../source/explanation-differential-privacy.rst:102 msgid "" -"The choice between fixed and adaptive clipping depends on various factors" -" such as privacy requirements, data distribution, model complexity, and " +"The choice between fixed and adaptive clipping depends on various factors " +"such as privacy requirements, data distribution, model complexity, and " "others." msgstr "" @@ -2993,9 +2970,9 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:107 msgid "" "In this approach, each client is responsible for performing DP. Local DP " -"avoids the need for a fully trusted aggregator, but it should be noted " -"that local DP leads to a decrease in accuracy but better privacy in " -"comparison to central DP." +"avoids the need for a fully trusted aggregator, but it should be noted that " +"local DP leads to a decrease in accuracy but better privacy in comparison to " +"central DP." msgstr "" #: ../../source/explanation-differential-privacy.rst:116 @@ -3005,16 +2982,16 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:118 msgid "" "Each client adds noise to the local updates before sending them to the " -"server. To achieve (:math:`\\epsilon`, :math:`\\delta`)-DP, considering " -"the sensitivity of the local model to be ∆, Gaussian noise is applied " -"with a noise scale of σ where:" +"server. To achieve (:math:`\\epsilon`, :math:`\\delta`)-DP, considering the " +"sensitivity of the local model to be ∆, Gaussian noise is applied with a " +"noise scale of σ where:" msgstr "" #: ../../source/explanation-differential-privacy.rst:120 msgid "" "\\small\n" -"\\frac{∆ \\times \\sqrt{2 \\times " -"\\log\\left(\\frac{1.25}{\\delta}\\right)}}{\\epsilon}\n" +"\\frac{∆ \\times \\sqrt{2 \\times \\log\\left(\\frac{1.25}{\\delta}\\right)}}" +"{\\epsilon}\n" "\n" msgstr "" @@ -3041,18 +3018,18 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:135 msgid "" -"[2] McMahan et al. Learning Differentially Private Recurrent Language " -"Models." +"[2] McMahan et al. Learning Differentially Private Recurrent Language Models." msgstr "" #: ../../source/explanation-differential-privacy.rst:137 msgid "" -"[3] Geyer et al. Differentially Private Federated Learning: A Client " -"Level Perspective." +"[3] Geyer et al. Differentially Private Federated Learning: A Client Level " +"Perspective." msgstr "" #: ../../source/explanation-differential-privacy.rst:139 -msgid "[4] Galen et al. Differentially Private Learning with Adaptive Clipping." +msgid "" +"[4] Galen et al. Differentially Private Learning with Adaptive Clipping." msgstr "" #: ../../source/explanation-federated-evaluation.rst:2 @@ -3063,8 +3040,8 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:4 msgid "" "There are two main approaches to evaluating models in federated learning " -"systems: centralized (or server-side) evaluation and federated (or " -"client-side) evaluation." +"systems: centralized (or server-side) evaluation and federated (or client-" +"side) evaluation." msgstr "" #: ../../source/explanation-federated-evaluation.rst:8 @@ -3089,11 +3066,10 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:60 msgid "" -"The :code:`Strategy` abstraction provides a method called " -":code:`evaluate` that can directly be used to evaluate the current global" -" model parameters. The current server implementation calls " -":code:`evaluate` after parameter aggregation and before federated " -"evaluation (see next paragraph)." +"The :code:`Strategy` abstraction provides a method called :code:`evaluate` " +"that can directly be used to evaluate the current global model parameters. " +"The current server implementation calls :code:`evaluate` after parameter " +"aggregation and before federated evaluation (see next paragraph)." msgstr "" #: ../../source/explanation-federated-evaluation.rst:65 @@ -3106,8 +3082,8 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:70 msgid "" -"Client-side evaluation happens in the :code:`Client.evaluate` method and " -"can be configured from the server side." +"Client-side evaluation happens in the :code:`Client.evaluate` method and can " +"be configured from the server side." msgstr "" #: ../../source/explanation-federated-evaluation.rst:101 @@ -3122,40 +3098,39 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:105 msgid "" -":code:`fraction_evaluate`: a :code:`float` defining the fraction of " -"clients that will be selected for evaluation. If " -":code:`fraction_evaluate` is set to :code:`0.1` and :code:`100` clients " -"are connected to the server, then :code:`10` will be randomly selected " -"for evaluation. If :code:`fraction_evaluate` is set to :code:`0.0`, " -"federated evaluation will be disabled." +":code:`fraction_evaluate`: a :code:`float` defining the fraction of clients " +"that will be selected for evaluation. If :code:`fraction_evaluate` is set " +"to :code:`0.1` and :code:`100` clients are connected to the server, then :" +"code:`10` will be randomly selected for evaluation. If :code:" +"`fraction_evaluate` is set to :code:`0.0`, federated evaluation will be " +"disabled." msgstr "" #: ../../source/explanation-federated-evaluation.rst:106 msgid "" -":code:`min_evaluate_clients`: an :code:`int`: the minimum number of " -"clients to be selected for evaluation. If :code:`fraction_evaluate` is " -"set to :code:`0.1`, :code:`min_evaluate_clients` is set to 20, and " -":code:`100` clients are connected to the server, then :code:`20` clients " -"will be selected for evaluation." +":code:`min_evaluate_clients`: an :code:`int`: the minimum number of clients " +"to be selected for evaluation. If :code:`fraction_evaluate` is set to :code:" +"`0.1`, :code:`min_evaluate_clients` is set to 20, and :code:`100` clients " +"are connected to the server, then :code:`20` clients will be selected for " +"evaluation." msgstr "" #: ../../source/explanation-federated-evaluation.rst:107 msgid "" ":code:`min_available_clients`: an :code:`int` that defines the minimum " -"number of clients which need to be connected to the server before a round" -" of federated evaluation can start. If fewer than " -":code:`min_available_clients` are connected to the server, the server " -"will wait until more clients are connected before it continues to sample " -"clients for evaluation." +"number of clients which need to be connected to the server before a round of " +"federated evaluation can start. If fewer than :code:`min_available_clients` " +"are connected to the server, the server will wait until more clients are " +"connected before it continues to sample clients for evaluation." msgstr "" #: ../../source/explanation-federated-evaluation.rst:108 msgid "" ":code:`on_evaluate_config_fn`: a function that returns a configuration " -"dictionary which will be sent to the selected clients. The function will " -"be called during each round and provides a convenient way to customize " -"client-side evaluation from the server side, for example, to configure " -"the number of validation steps performed." +"dictionary which will be sent to the selected clients. The function will be " +"called during each round and provides a convenient way to customize client-" +"side evaluation from the server side, for example, to configure the number " +"of validation steps performed." msgstr "" #: ../../source/explanation-federated-evaluation.rst:135 @@ -3164,9 +3139,8 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:137 msgid "" -"Model parameters can also be evaluated during training. " -":code:`Client.fit` can return arbitrary evaluation results as a " -"dictionary:" +"Model parameters can also be evaluated during training. :code:`Client.fit` " +"can return arbitrary evaluation results as a dictionary:" msgstr "" #: ../../source/explanation-federated-evaluation.rst:177 @@ -3175,10 +3149,10 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:179 msgid "" -"For a full code example that uses both centralized and federated " -"evaluation, see the *Advanced TensorFlow Example* (the same approach can " -"be applied to workloads implemented in any other framework): " -"https://github.com/adap/flower/tree/main/examples/advanced-tensorflow" +"For a full code example that uses both centralized and federated evaluation, " +"see the *Advanced TensorFlow Example* (the same approach can be applied to " +"workloads implemented in any other framework): https://github.com/adap/" +"flower/tree/main/examples/advanced-tensorflow" msgstr "" #: ../../source/fed/0000-20200102-fed-template.md:10 @@ -3352,9 +3326,9 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:37 msgid "" -"ensure community participants can successfully drive changes to " -"completion across one or more releases while stakeholders are adequately " -"represented throughout the process" +"ensure community participants can successfully drive changes to completion " +"across one or more releases while stakeholders are adequately represented " +"throughout the process" msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:39 @@ -3382,54 +3356,54 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:49 msgid "" "For far-fetching changes or features proposed to Flower, an abstraction " -"beyond a single GitHub issue or pull request is required to understand " -"and communicate upcoming changes to the project." +"beyond a single GitHub issue or pull request is required to understand and " +"communicate upcoming changes to the project." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:51 msgid "" -"The purpose of this process is to reduce the amount of \"tribal " -"knowledge\" in our community. By moving decisions from Slack threads, " -"video calls, and hallway conversations into a well-tracked artifact, this" -" process aims to enhance communication and discoverability." +"The purpose of this process is to reduce the amount of \"tribal knowledge\" " +"in our community. By moving decisions from Slack threads, video calls, and " +"hallway conversations into a well-tracked artifact, this process aims to " +"enhance communication and discoverability." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:55 msgid "" -"Roughly any larger, user-facing enhancement should follow the Enhancement" -" process. If an enhancement would be described in either written or " -"verbal communication to anyone besides the author or developer, then " -"consider creating an Enhancement Doc." +"Roughly any larger, user-facing enhancement should follow the Enhancement " +"process. If an enhancement would be described in either written or verbal " +"communication to anyone besides the author or developer, then consider " +"creating an Enhancement Doc." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:57 msgid "" -"Similarly, any technical effort (refactoring, major architectural change)" -" that will impact a large section of the development community should " -"also be communicated widely. The Enhancement process is suited for this " -"even if it will have zero impact on the typical user or operator." +"Similarly, any technical effort (refactoring, major architectural change) " +"that will impact a large section of the development community should also be " +"communicated widely. The Enhancement process is suited for this even if it " +"will have zero impact on the typical user or operator." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:61 msgid "" -"For small changes and additions, going through the Enhancement process " -"would be time-consuming and unnecessary. This includes, for example, " -"adding new Federated Learning algorithms, as these only add features " -"without changing how Flower works or is used." +"For small changes and additions, going through the Enhancement process would " +"be time-consuming and unnecessary. This includes, for example, adding new " +"Federated Learning algorithms, as these only add features without changing " +"how Flower works or is used." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:63 msgid "" "Enhancements are different from feature requests, as they are already " -"providing a laid-out path for implementation and are championed by " -"members of the community." +"providing a laid-out path for implementation and are championed by members " +"of the community." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:67 msgid "" "An Enhancement is captured in a Markdown file that follows a defined " -"template and a workflow to review and store enhancement docs for " -"reference — the Enhancement Doc." +"template and a workflow to review and store enhancement docs for reference " +"— the Enhancement Doc." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:69 @@ -3481,8 +3455,8 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:92 msgid "" -"**fed-number** (Required) The `fed-number` of the last Flower Enhancement" -" Doc + 1. With this number, it becomes easy to reference other proposals." +"**fed-number** (Required) The `fed-number` of the last Flower Enhancement " +"Doc + 1. With this number, it becomes easy to reference other proposals." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:94 @@ -3491,20 +3465,20 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:96 msgid "" -"**status** (Required) The current status of the proposal. See " -"[workflow](#workflow) for the possible states." +"**status** (Required) The current status of the proposal. See [workflow]" +"(#workflow) for the possible states." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:98 msgid "" -"**authors** (Required) A list of authors of the proposal. This is simply " -"the GitHub ID." +"**authors** (Required) A list of authors of the proposal. This is simply the " +"GitHub ID." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:100 msgid "" -"**creation-date** (Required) The date that the proposal was first " -"submitted in a PR." +"**creation-date** (Required) The date that the proposal was first submitted " +"in a PR." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:102 @@ -3515,8 +3489,8 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:104 msgid "" -"**see-also** (Optional) A list of other proposals that are relevant to " -"this one." +"**see-also** (Optional) A list of other proposals that are relevant to this " +"one." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:106 @@ -3524,7 +3498,8 @@ msgid "**replaces** (Optional) A list of proposals that this one replaces." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:108 -msgid "**superseded-by** (Optional) A list of proposals that this one supersedes." +msgid "" +"**superseded-by** (Optional) A list of proposals that this one supersedes." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:111 @@ -3534,40 +3509,40 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:113 msgid "" "The idea forming the enhancement should already have been discussed or " -"pitched in the community. As such, it needs a champion, usually the " -"author, who shepherds the enhancement. This person also has to find " -"committers to Flower willing to review the proposal." +"pitched in the community. As such, it needs a champion, usually the author, " +"who shepherds the enhancement. This person also has to find committers to " +"Flower willing to review the proposal." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:115 msgid "" "New enhancements are checked in with a file name in the form of `NNNN-" -"YYYYMMDD-enhancement-title.md`, with `NNNN` being the Flower Enhancement " -"Doc number, to `enhancements`. All enhancements start in `provisional` " -"state as part of a pull request. Discussions are done as part of the pull" -" request review." +"YYYYMMDD-enhancement-title.md`, with `NNNN` being the Flower Enhancement Doc " +"number, to `enhancements`. All enhancements start in `provisional` state as " +"part of a pull request. Discussions are done as part of the pull request " +"review." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:117 msgid "" -"Once an enhancement has been reviewed and approved, its status is changed" -" to `implementable`. The actual implementation is then done in separate " -"pull requests. These pull requests should mention the respective " -"enhancement as part of their description. After the implementation is " -"done, the proposal status is changed to `implemented`." +"Once an enhancement has been reviewed and approved, its status is changed to " +"`implementable`. The actual implementation is then done in separate pull " +"requests. These pull requests should mention the respective enhancement as " +"part of their description. After the implementation is done, the proposal " +"status is changed to `implemented`." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:119 msgid "" -"Under certain conditions, other states are possible. An Enhancement has " -"the following states:" +"Under certain conditions, other states are possible. An Enhancement has the " +"following states:" msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:121 msgid "" "`provisional`: The enhancement has been proposed and is actively being " -"defined. This is the starting state while the proposal is being fleshed " -"out and actively defined and discussed." +"defined. This is the starting state while the proposal is being fleshed out " +"and actively defined and discussed." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:122 @@ -3581,13 +3556,14 @@ msgid "" msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:124 -msgid "`deferred`: The enhancement is proposed but not actively being worked on." +msgid "" +"`deferred`: The enhancement is proposed but not actively being worked on." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:125 msgid "" -"`rejected`: The authors and reviewers have decided that this enhancement " -"is not moving forward." +"`rejected`: The authors and reviewers have decided that this enhancement is " +"not moving forward." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:126 @@ -3600,16 +3576,16 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:131 msgid "" -"Adding an additional process to the ones already provided by GitHub " -"(Issues and Pull Requests) adds more complexity and can be a barrier for " -"potential first-time contributors." +"Adding an additional process to the ones already provided by GitHub (Issues " +"and Pull Requests) adds more complexity and can be a barrier for potential " +"first-time contributors." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:133 msgid "" "Expanding the proposal template beyond the single-sentence description " -"currently required in the features issue template may be a heavy burden " -"for non-native English speakers." +"currently required in the features issue template may be a heavy burden for " +"non-native English speakers." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:137 @@ -3619,12 +3595,12 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:139 msgid "" "Using GitHub Issues for these kinds of enhancements is doable. One could " -"use, for example, tags, to differentiate and filter them from other " -"issues. The main issue is in discussing and reviewing an enhancement: " -"GitHub issues only have a single thread for comments. Enhancements " -"usually have multiple threads of discussion at the same time for various " -"parts of the doc. Managing these multiple discussions can be confusing " -"when using GitHub Issues." +"use, for example, tags, to differentiate and filter them from other issues. " +"The main issue is in discussing and reviewing an enhancement: GitHub issues " +"only have a single thread for comments. Enhancements usually have multiple " +"threads of discussion at the same time for various parts of the doc. " +"Managing these multiple discussions can be confusing when using GitHub " +"Issues." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:141 @@ -3633,12 +3609,11 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:143 msgid "" -"Google Docs allow for multiple threads of discussions. But as Google Docs" -" are hosted outside the project, their discoverability by the community " -"needs to be taken care of. A list of links to all proposals has to be " -"managed and made available for the community. Compared to shipping " -"proposals as part of Flower's repository, the potential for missing links" -" is much higher." +"Google Docs allow for multiple threads of discussions. But as Google Docs " +"are hosted outside the project, their discoverability by the community needs " +"to be taken care of. A list of links to all proposals has to be managed and " +"made available for the community. Compared to shipping proposals as part of " +"Flower's repository, the potential for missing links is much higher." msgstr "" #: ../../source/fed/index.md:1 @@ -3651,8 +3626,8 @@ msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:4 msgid "" -"The Flower server does not prescribe a way to aggregate evaluation " -"results, but it enables the user to fully customize result aggregation." +"The Flower server does not prescribe a way to aggregate evaluation results, " +"but it enables the user to fully customize result aggregation." msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:8 @@ -3661,9 +3636,9 @@ msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:10 msgid "" -"The same :code:`Strategy`-customization approach can be used to aggregate" -" custom evaluation results coming from individual clients. Clients can " -"return custom metrics to the server by returning a dictionary:" +"The same :code:`Strategy`-customization approach can be used to aggregate " +"custom evaluation results coming from individual clients. Clients can return " +"custom metrics to the server by returning a dictionary:" msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:36 @@ -3678,10 +3653,9 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:4 msgid "" -"Flower has built-in support for authenticated SuperNodes that you can use" -" to verify the identities of each SuperNode connecting to a SuperLink. " -"Flower node authentication works similar to how GitHub SSH authentication" -" works:" +"Flower has built-in support for authenticated SuperNodes that you can use to " +"verify the identities of each SuperNode connecting to a SuperLink. Flower " +"node authentication works similar to how GitHub SSH authentication works:" msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:7 @@ -3690,8 +3664,7 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:8 msgid "" -"Using ECDH, both SuperNode and SuperLink independently derive a shared " -"secret" +"Using ECDH, both SuperNode and SuperLink independently derive a shared secret" msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:9 @@ -3706,22 +3679,21 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:12 msgid "" -"We recommend you to check out the complete `code example " -"`_ demonstrating federated learning with Flower in an " -"authenticated setting." +"We recommend you to check out the complete `code example `_ demonstrating " +"federated learning with Flower in an authenticated setting." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:15 msgid "" -"This guide covers a preview feature that might change in future versions " -"of Flower." +"This guide covers a preview feature that might change in future versions of " +"Flower." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:18 msgid "" -"For increased security, node authentication can only be used when " -"encrypted connections (SSL/TLS) are enabled." +"For increased security, node authentication can only be used when encrypted " +"connections (SSL/TLS) are enabled." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:21 @@ -3731,13 +3703,12 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:23 msgid "" "To enable node authentication, first you need to configure SSL/TLS " -"connections to secure the SuperLink<>SuperNode communication. You can " -"find the complete guide `here `_. After configuring secure connections, you" -" can enable client authentication in a long-running Flower " -":code:`SuperLink`. Use the following terminal command to start a Flower " -":code:`SuperNode` that has both secure connections and node " -"authentication enabled:" +"connections to secure the SuperLink<>SuperNode communication. You can find " +"the complete guide `here `_. After configuring secure connections, you can enable " +"client authentication in a long-running Flower :code:`SuperLink`. Use the " +"following terminal command to start a Flower :code:`SuperNode` that has both " +"secure connections and node authentication enabled:" msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:36 @@ -3746,35 +3717,35 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:38 msgid "" -"The first flag :code:`--auth-list-public-keys` expects a path to a CSV " -"file storing all known node public keys. You need to store all known node" -" public keys that are allowed to participate in a federation in one CSV " -"file (:code:`.csv`)." +"The first flag :code:`--auth-list-public-keys` expects a path to a CSV file " +"storing all known node public keys. You need to store all known node public " +"keys that are allowed to participate in a federation in one CSV file (:code:" +"`.csv`)." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:40 msgid "" "A valid CSV file storing known node public keys should list the keys in " "OpenSSH format, separated by commas and without any comments. For an " -"example, refer to our code sample, which contains a CSV file with two " -"known node public keys." +"example, refer to our code sample, which contains a CSV file with two known " +"node public keys." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:42 msgid "" -"The second and third flags :code:`--auth-superlink-private-key` and :code" -":`--auth-superlink-public-key` expect paths to the server's private and " -"public keys. For development purposes, you can generate a private and " -"public key pair using :code:`ssh-keygen -t ecdsa -b 384`." +"The second and third flags :code:`--auth-superlink-private-key` and :code:`--" +"auth-superlink-public-key` expect paths to the server's private and public " +"keys. For development purposes, you can generate a private and public key " +"pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:45 msgid "" "In Flower 1.9, there is no support for dynamically removing, editing, or " -"adding known node public keys to the SuperLink. To change the set of " -"known nodes, you need to shut the server down, edit the CSV file, and " -"start the server again. Support for dynamically changing the set of known" -" nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." +"adding known node public keys to the SuperLink. To change the set of known " +"nodes, you need to shut the server down, edit the CSV file, and start the " +"server again. Support for dynamically changing the set of known nodes is on " +"the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:51 @@ -3784,18 +3755,18 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:53 msgid "" "Similar to the long-running Flower server (:code:`SuperLink`), you can " -"easily enable node authentication in the long-running Flower client " -"(:code:`SuperNode`). Use the following terminal command to start an " -"authenticated :code:`SuperNode`:" +"easily enable node authentication in the long-running Flower client (:code:" +"`SuperNode`). Use the following terminal command to start an authenticated :" +"code:`SuperNode`:" msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:64 msgid "" -"The :code:`--auth-supernode-private-key` flag expects a path to the " -"node's private key file and the :code:`--auth-supernode-public-key` flag " -"expects a path to the node's public key file. For development purposes, " -"you can generate a private and public key pair using :code:`ssh-keygen -t" -" ecdsa -b 384`." +"The :code:`--auth-supernode-private-key` flag expects a path to the node's " +"private key file and the :code:`--auth-supernode-public-key` flag expects a " +"path to the node's public key file. For development purposes, you can " +"generate a private and public key pair using :code:`ssh-keygen -t ecdsa -b " +"384`." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:68 @@ -3804,13 +3775,12 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:70 msgid "" -"The system's security relies on the credentials of the SuperLink and each" -" SuperNode. Therefore, it is imperative to safeguard and safely store the" -" credentials to avoid security risks such as Public Key Infrastructure " -"(PKI) impersonation attacks. The node authentication mechanism also " -"involves human interaction, so please ensure that all of the " -"communication is done in a secure manner, using trusted communication " -"methods." +"The system's security relies on the credentials of the SuperLink and each " +"SuperNode. Therefore, it is imperative to safeguard and safely store the " +"credentials to avoid security risks such as Public Key Infrastructure (PKI) " +"impersonation attacks. The node authentication mechanism also involves human " +"interaction, so please ensure that all of the communication is done in a " +"secure manner, using trusted communication methods." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:75 @@ -3822,10 +3792,10 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:77 msgid "" -"You should now have learned how to start a long-running Flower server " -"(:code:`SuperLink`) and client (:code:`SuperNode`) with node " -"authentication enabled. You should also know the significance of the " -"private key and store it safely to minimize security risks." +"You should now have learned how to start a long-running Flower server (:code:" +"`SuperLink`) and client (:code:`SuperNode`) with node authentication " +"enabled. You should also know the significance of the private key and store " +"it safely to minimize security risks." msgstr "" #: ../../source/how-to-configure-clients.rst:2 @@ -3835,9 +3805,9 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:4 msgid "" "Along with model parameters, Flower can send configuration values to " -"clients. Configuration values can be used for various purposes. They are," -" for example, a popular way to control client-side hyperparameters from " -"the server." +"clients. Configuration values can be used for various purposes. They are, " +"for example, a popular way to control client-side hyperparameters from the " +"server." msgstr "" #: ../../source/how-to-configure-clients.rst:7 @@ -3846,34 +3816,34 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:9 msgid "" -"Configuration values are represented as a dictionary with ``str`` keys " -"and values of type ``bool``, ``bytes``, ``double`` (64-bit precision " -"float), ``int``, or ``str`` (or equivalent types in different languages)." -" Here is an example of a configuration dictionary in Python:" +"Configuration values are represented as a dictionary with ``str`` keys and " +"values of type ``bool``, ``bytes``, ``double`` (64-bit precision float), " +"``int``, or ``str`` (or equivalent types in different languages). Here is an " +"example of a configuration dictionary in Python:" msgstr "" #: ../../source/how-to-configure-clients.rst:20 msgid "" "Flower serializes these configuration dictionaries (or *config dict* for " -"short) to their ProtoBuf representation, transports them to the client " -"using gRPC, and then deserializes them back to Python dictionaries." +"short) to their ProtoBuf representation, transports them to the client using " +"gRPC, and then deserializes them back to Python dictionaries." msgstr "" #: ../../source/how-to-configure-clients.rst:24 msgid "" -"Currently, there is no support for directly sending collection types " -"(e.g., ``Set``, ``List``, ``Map``) as values in configuration " -"dictionaries. There are several workarounds to send collections as values" -" by converting them to one of the supported value types (and converting " -"them back on the client-side)." +"Currently, there is no support for directly sending collection types (e.g., " +"``Set``, ``List``, ``Map``) as values in configuration dictionaries. There " +"are several workarounds to send collections as values by converting them to " +"one of the supported value types (and converting them back on the client-" +"side)." msgstr "" #: ../../source/how-to-configure-clients.rst:26 msgid "" "One can, for example, convert a list of floating-point numbers to a JSON " -"string, then send the JSON string using the configuration dictionary, and" -" then convert the JSON string back to a list of floating-point numbers on" -" the client." +"string, then send the JSON string using the configuration dictionary, and " +"then convert the JSON string back to a list of floating-point numbers on the " +"client." msgstr "" #: ../../source/how-to-configure-clients.rst:30 @@ -3882,49 +3852,50 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:32 msgid "" -"The easiest way to send configuration values to clients is to use a " -"built-in strategy like :code:`FedAvg`. Built-in strategies support so-" -"called configuration functions. A configuration function is a function " -"that the built-in strategy calls to get the configuration dictionary for " -"the current round. It then forwards the configuration dictionary to all " -"the clients selected during that round." +"The easiest way to send configuration values to clients is to use a built-in " +"strategy like :code:`FedAvg`. Built-in strategies support so-called " +"configuration functions. A configuration function is a function that the " +"built-in strategy calls to get the configuration dictionary for the current " +"round. It then forwards the configuration dictionary to all the clients " +"selected during that round." msgstr "" #: ../../source/how-to-configure-clients.rst:34 msgid "" "Let's start with a simple example. Imagine we want to send (a) the batch " -"size that the client should use, (b) the current global round of " -"federated learning, and (c) the number of epochs to train on the client-" -"side. Our configuration function could look like this:" +"size that the client should use, (b) the current global round of federated " +"learning, and (c) the number of epochs to train on the client-side. Our " +"configuration function could look like this:" msgstr "" #: ../../source/how-to-configure-clients.rst:47 msgid "" "To make the built-in strategies use this function, we can pass it to " -"``FedAvg`` during initialization using the parameter " -":code:`on_fit_config_fn`:" +"``FedAvg`` during initialization using the parameter :code:" +"`on_fit_config_fn`:" msgstr "" #: ../../source/how-to-configure-clients.rst:56 -msgid "One the client side, we receive the configuration dictionary in ``fit``:" +msgid "" +"One the client side, we receive the configuration dictionary in ``fit``:" msgstr "" #: ../../source/how-to-configure-clients.rst:67 msgid "" "There is also an `on_evaluate_config_fn` to configure evaluation, which " -"works the same way. They are separate functions because one might want to" -" send different configuration values to `evaluate` (for example, to use a" -" different batch size)." +"works the same way. They are separate functions because one might want to " +"send different configuration values to `evaluate` (for example, to use a " +"different batch size)." msgstr "" #: ../../source/how-to-configure-clients.rst:69 msgid "" -"The built-in strategies call this function every round (that is, every " -"time `Strategy.configure_fit` or `Strategy.configure_evaluate` runs). " -"Calling `on_evaluate_config_fn` every round allows us to vary/change the " -"config dict over consecutive rounds. If we wanted to implement a " -"hyperparameter schedule, for example, to increase the number of local " -"epochs during later rounds, we could do the following:" +"The built-in strategies call this function every round (that is, every time " +"`Strategy.configure_fit` or `Strategy.configure_evaluate` runs). Calling " +"`on_evaluate_config_fn` every round allows us to vary/change the config dict " +"over consecutive rounds. If we wanted to implement a hyperparameter " +"schedule, for example, to increase the number of local epochs during later " +"rounds, we could do the following:" msgstr "" #: ../../source/how-to-configure-clients.rst:82 @@ -3943,13 +3914,12 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:89 msgid "" -"This can be achieved by customizing an existing strategy or by " -":doc:`implementing a custom strategy from scratch `. Here's a nonsensical example that customizes :code:`FedAvg`" -" by adding a custom ``\"hello\": \"world\"`` configuration key/value pair" -" to the config dict of a *single client* (only the first client in the " -"list, the other clients in this round to not receive this \"special\" " -"config value):" +"This can be achieved by customizing an existing strategy or by :doc:" +"`implementing a custom strategy from scratch `. " +"Here's a nonsensical example that customizes :code:`FedAvg` by adding a " +"custom ``\"hello\": \"world\"`` configuration key/value pair to the config " +"dict of a *single client* (only the first client in the list, the other " +"clients in this round to not receive this \"special\" config value):" msgstr "" #: ../../source/how-to-configure-logging.rst:2 @@ -3959,16 +3929,16 @@ msgstr "" #: ../../source/how-to-configure-logging.rst:4 msgid "" "The Flower logger keeps track of all core events that take place in " -"federated learning workloads. It presents information by default " -"following a standard message format:" +"federated learning workloads. It presents information by default following a " +"standard message format:" msgstr "" #: ../../source/how-to-configure-logging.rst:13 msgid "" -"containing relevant information including: log message level (e.g. " -":code:`INFO`, :code:`DEBUG`), a timestamp, the line where the logging " -"took place from, as well as the log message itself. In this way, the " -"logger would typically display information on your terminal as follows:" +"containing relevant information including: log message level (e.g. :code:" +"`INFO`, :code:`DEBUG`), a timestamp, the line where the logging took place " +"from, as well as the log message itself. In this way, the logger would " +"typically display information on your terminal as follows:" msgstr "" #: ../../source/how-to-configure-logging.rst:34 @@ -3979,21 +3949,20 @@ msgstr "" msgid "" "By default, the Flower log is outputted to the terminal where you launch " "your Federated Learning workload from. This applies for both gRPC-based " -"federation (i.e. when you do :code:`fl.server.start_server`) and when " -"using the :code:`VirtualClientEngine` (i.e. when you do " -":code:`fl.simulation.start_simulation`). In some situations you might " -"want to save this log to disk. You can do so by calling the " -"`fl.common.logger.configure() " -"`_" -" function. For example:" +"federation (i.e. when you do :code:`fl.server.start_server`) and when using " +"the :code:`VirtualClientEngine` (i.e. when you do :code:`fl.simulation." +"start_simulation`). In some situations you might want to save this log to " +"disk. You can do so by calling the `fl.common.logger.configure() `_ function. " +"For example:" msgstr "" #: ../../source/how-to-configure-logging.rst:53 msgid "" -"With the above, Flower will record the log you see on your terminal to " -":code:`log.txt`. This file will be created in the same directory as were " -"you are running the code from. If we inspect we see the log above is also" -" recorded but prefixing with :code:`identifier` each line:" +"With the above, Flower will record the log you see on your terminal to :code:" +"`log.txt`. This file will be created in the same directory as were you are " +"running the code from. If we inspect we see the log above is also recorded " +"but prefixing with :code:`identifier` each line:" msgstr "" #: ../../source/how-to-configure-logging.rst:74 @@ -4002,15 +3971,15 @@ msgstr "" #: ../../source/how-to-configure-logging.rst:76 msgid "" -"You might expand the information shown by default with the Flower logger " -"by adding more messages relevant to your application. You can achieve " -"this easily as follows." +"You might expand the information shown by default with the Flower logger by " +"adding more messages relevant to your application. You can achieve this " +"easily as follows." msgstr "" #: ../../source/how-to-configure-logging.rst:102 msgid "" -"In this way your logger will show, in addition to the default messages, " -"the ones introduced by the clients as specified above." +"In this way your logger will show, in addition to the default messages, the " +"ones introduced by the clients as specified above." msgstr "" #: ../../source/how-to-configure-logging.rst:128 @@ -4019,15 +3988,14 @@ msgstr "" #: ../../source/how-to-configure-logging.rst:130 msgid "" -"The :code:`fl.common.logger.configure` function, also allows specifying a" -" host to which logs can be pushed (via :code:`POST`) through a native " -"Python :code:`logging.handler.HTTPHandler`. This is a particularly useful" -" feature in :code:`gRPC`-based Federated Learning workloads where " -"otherwise gathering logs from all entities (i.e. the server and the " -"clients) might be cumbersome. Note that in Flower simulation, the server " -"automatically displays all logs. You can still specify a " -":code:`HTTPHandler` should you wish to backup or analyze the logs " -"somewhere else." +"The :code:`fl.common.logger.configure` function, also allows specifying a " +"host to which logs can be pushed (via :code:`POST`) through a native Python :" +"code:`logging.handler.HTTPHandler`. This is a particularly useful feature " +"in :code:`gRPC`-based Federated Learning workloads where otherwise gathering " +"logs from all entities (i.e. the server and the clients) might be " +"cumbersome. Note that in Flower simulation, the server automatically " +"displays all logs. You can still specify a :code:`HTTPHandler` should you " +"wish to backup or analyze the logs somewhere else." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:2 @@ -4036,24 +4004,23 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:4 msgid "" -"This guide describes how to a SSL-enabled secure Flower server " -"(:code:`SuperLink`) can be started and how a Flower client " -"(:code:`SuperNode`) can establish a secure connections to it." +"This guide describes how to a SSL-enabled secure Flower server (:code:" +"`SuperLink`) can be started and how a Flower client (:code:`SuperNode`) can " +"establish a secure connections to it." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:7 msgid "" -"A complete code example demonstrating a secure connection can be found " -"`here `_." +"A complete code example demonstrating a secure connection can be found `here " +"`_." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:10 msgid "" -"The code example comes with a :code:`README.md` file which explains how " -"to start it. Although it is already SSL-enabled, it might be less " -"descriptive on how it does so. Stick to this guide for a deeper " -"introduction to the topic." +"The code example comes with a :code:`README.md` file which explains how to " +"start it. Although it is already SSL-enabled, it might be less descriptive " +"on how it does so. Stick to this guide for a deeper introduction to the " +"topic." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:16 @@ -4063,27 +4030,27 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:18 msgid "" "Using SSL-enabled connections requires certificates to be passed to the " -"server and client. For the purpose of this guide we are going to generate" -" self-signed certificates. As this can become quite complex we are going " -"to ask you to run the script in :code:`examples/advanced-" -"tensorflow/certificates/generate.sh` with the following command sequence:" +"server and client. For the purpose of this guide we are going to generate " +"self-signed certificates. As this can become quite complex we are going to " +"ask you to run the script in :code:`examples/advanced-tensorflow/" +"certificates/generate.sh` with the following command sequence:" msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:29 msgid "" -"This will generate the certificates in :code:`examples/advanced-" -"tensorflow/.cache/certificates`." +"This will generate the certificates in :code:`examples/advanced-tensorflow/." +"cache/certificates`." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:31 msgid "" -"The approach for generating SSL certificates in the context of this " -"example can serve as an inspiration and starting point, but it should not" -" be used as a reference for production environments. Please refer to " -"other sources regarding the issue of correctly generating certificates " -"for production environments. For non-critical prototyping or research " -"projects, it might be sufficient to use the self-signed certificates " -"generated using the scripts mentioned in this guide." +"The approach for generating SSL certificates in the context of this example " +"can serve as an inspiration and starting point, but it should not be used as " +"a reference for production environments. Please refer to other sources " +"regarding the issue of correctly generating certificates for production " +"environments. For non-critical prototyping or research projects, it might be " +"sufficient to use the self-signed certificates generated using the scripts " +"mentioned in this guide." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:39 @@ -4092,15 +4059,15 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:41 msgid "" -"Use the following terminal command to start a sever (SuperLink) that uses" -" the previously generated certificates:" +"Use the following terminal command to start a sever (SuperLink) that uses " +"the previously generated certificates:" msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:47 msgid "" "When providing certificates, the server expects a tuple of three " -"certificates paths: CA certificate, server certificate and server private" -" key." +"certificates paths: CA certificate, server certificate and server private " +"key." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:51 @@ -4109,21 +4076,21 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:53 msgid "" -"Use the following terminal command to start a client (SuperNode) that " -"uses the previously generated certificates:" +"Use the following terminal command to start a client (SuperNode) that uses " +"the previously generated certificates:" msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:61 msgid "" -"When setting :code:`root_certificates`, the client expects a file path to" -" PEM-encoded root certificates." +"When setting :code:`root_certificates`, the client expects a file path to " +"PEM-encoded root certificates." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:67 msgid "" -"You should now have learned how to generate self-signed certificates " -"using the given script, start an SSL-enabled server and have a client " -"establish a secure connection to it." +"You should now have learned how to generate self-signed certificates using " +"the given script, start an SSL-enabled server and have a client establish a " +"secure connection to it." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:72 @@ -4132,8 +4099,8 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:74 msgid "" -"These additional sources might be relevant if you would like to dive " -"deeper into the topic of certificates:" +"These additional sources might be relevant if you would like to dive deeper " +"into the topic of certificates:" msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:76 @@ -4150,12 +4117,12 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:4 msgid "" -"The strategy abstraction enables implementation of fully custom " -"strategies. A strategy is basically the federated learning algorithm that" -" runs on the server. Strategies decide how to sample clients, how to " -"configure clients for training, how to aggregate updates, and how to " -"evaluate models. Flower provides a few built-in strategies which are " -"based on the same API described below." +"The strategy abstraction enables implementation of fully custom strategies. " +"A strategy is basically the federated learning algorithm that runs on the " +"server. Strategies decide how to sample clients, how to configure clients " +"for training, how to aggregate updates, and how to evaluate models. Flower " +"provides a few built-in strategies which are based on the same API described " +"below." msgstr "" #: ../../source/how-to-implement-strategies.rst:11 @@ -4164,11 +4131,10 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:13 msgid "" -"All strategy implementation are derived from the abstract base class " -":code:`flwr.server.strategy.Strategy`, both built-in implementations and " -"third party implementations. This means that custom strategy " -"implementations have the exact same capabilities at their disposal as " -"built-in ones." +"All strategy implementation are derived from the abstract base class :code:" +"`flwr.server.strategy.Strategy`, both built-in implementations and third " +"party implementations. This means that custom strategy implementations have " +"the exact same capabilities at their disposal as built-in ones." msgstr "" #: ../../source/how-to-implement-strategies.rst:18 @@ -4179,9 +4145,9 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:75 msgid "" -"Creating a new strategy means implementing a new :code:`class` (derived " -"from the abstract base class :code:`Strategy`) that implements for the " -"previously shown abstract methods:" +"Creating a new strategy means implementing a new :code:`class` (derived from " +"the abstract base class :code:`Strategy`) that implements for the previously " +"shown abstract methods:" msgstr "" #: ../../source/how-to-implement-strategies.rst:100 @@ -4198,37 +4164,35 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:182 msgid "" -":code:`initialize_parameters` is called only once, at the very beginning " -"of an execution. It is responsible for providing the initial global model" -" parameters in a serialized form (i.e., as a :code:`Parameters` object)." +":code:`initialize_parameters` is called only once, at the very beginning of " +"an execution. It is responsible for providing the initial global model " +"parameters in a serialized form (i.e., as a :code:`Parameters` object)." msgstr "" #: ../../source/how-to-implement-strategies.rst:184 msgid "" -"Built-in strategies return user-provided initial parameters. The " -"following example shows how initial parameters can be passed to " -":code:`FedAvg`:" +"Built-in strategies return user-provided initial parameters. The following " +"example shows how initial parameters can be passed to :code:`FedAvg`:" msgstr "" #: ../../source/how-to-implement-strategies.rst:209 msgid "" "The Flower server will call :code:`initialize_parameters`, which either " -"returns the parameters that were passed to :code:`initial_parameters`, or" -" :code:`None`. If no parameters are returned from " -":code:`initialize_parameters` (i.e., :code:`None`), the server will " -"randomly select one client and ask it to provide its parameters. This is " -"a convenience feature and not recommended in practice, but it can be " -"useful for prototyping. In practice, it is recommended to always use " -"server-side parameter initialization." +"returns the parameters that were passed to :code:`initial_parameters`, or :" +"code:`None`. If no parameters are returned from :code:" +"`initialize_parameters` (i.e., :code:`None`), the server will randomly " +"select one client and ask it to provide its parameters. This is a " +"convenience feature and not recommended in practice, but it can be useful " +"for prototyping. In practice, it is recommended to always use server-side " +"parameter initialization." msgstr "" #: ../../source/how-to-implement-strategies.rst:213 msgid "" "Server-side parameter initialization is a powerful mechanism. It can be " -"used, for example, to resume training from a previously saved checkpoint." -" It is also the fundamental capability needed to implement hybrid " -"approaches, for example, to fine-tune a pre-trained model using federated" -" learning." +"used, for example, to resume training from a previously saved checkpoint. It " +"is also the fundamental capability needed to implement hybrid approaches, " +"for example, to fine-tune a pre-trained model using federated learning." msgstr "" #: ../../source/how-to-implement-strategies.rst:216 @@ -4237,17 +4201,17 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:218 msgid "" -":code:`configure_fit` is responsible for configuring the upcoming round " -"of training. What does *configure* mean in this context? Configuring a " -"round means selecting clients and deciding what instructions to send to " -"these clients. The signature of :code:`configure_fit` makes this clear:" +":code:`configure_fit` is responsible for configuring the upcoming round of " +"training. What does *configure* mean in this context? Configuring a round " +"means selecting clients and deciding what instructions to send to these " +"clients. The signature of :code:`configure_fit` makes this clear:" msgstr "" #: ../../source/how-to-implement-strategies.rst:231 msgid "" "The return value is a list of tuples, each representing the instructions " -"that will be sent to a particular client. Strategy implementations " -"usually perform the following steps in :code:`configure_fit`:" +"that will be sent to a particular client. Strategy implementations usually " +"perform the following steps in :code:`configure_fit`:" msgstr "" #: ../../source/how-to-implement-strategies.rst:233 @@ -4266,19 +4230,18 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:236 msgid "" "More sophisticated implementations can use :code:`configure_fit` to " -"implement custom client selection logic. A client will only participate " -"in a round if the corresponding :code:`ClientProxy` is included in the " -"list returned from :code:`configure_fit`." +"implement custom client selection logic. A client will only participate in a " +"round if the corresponding :code:`ClientProxy` is included in the list " +"returned from :code:`configure_fit`." msgstr "" #: ../../source/how-to-implement-strategies.rst:240 msgid "" "The structure of this return value provides a lot of flexibility to the " "user. Since instructions are defined on a per-client basis, different " -"instructions can be sent to each client. This enables custom strategies " -"to train, for example, different models on different clients, or use " -"different hyperparameters on different clients (via the :code:`config` " -"dict)." +"instructions can be sent to each client. This enables custom strategies to " +"train, for example, different models on different clients, or use different " +"hyperparameters on different clients (via the :code:`config` dict)." msgstr "" #: ../../source/how-to-implement-strategies.rst:243 @@ -4287,24 +4250,23 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:245 msgid "" -":code:`aggregate_fit` is responsible for aggregating the results returned" -" by the clients that were selected and asked to train in " -":code:`configure_fit`." +":code:`aggregate_fit` is responsible for aggregating the results returned by " +"the clients that were selected and asked to train in :code:`configure_fit`." msgstr "" #: ../../source/how-to-implement-strategies.rst:258 msgid "" "Of course, failures can happen, so there is no guarantee that the server " -"will get results from all the clients it sent instructions to (via " -":code:`configure_fit`). :code:`aggregate_fit` therefore receives a list " -"of :code:`results`, but also a list of :code:`failures`." +"will get results from all the clients it sent instructions to (via :code:" +"`configure_fit`). :code:`aggregate_fit` therefore receives a list of :code:" +"`results`, but also a list of :code:`failures`." msgstr "" #: ../../source/how-to-implement-strategies.rst:260 msgid "" -":code:`aggregate_fit` returns an optional :code:`Parameters` object and a" -" dictionary of aggregated metrics. The :code:`Parameters` return value is" -" optional because :code:`aggregate_fit` might decide that the results " +":code:`aggregate_fit` returns an optional :code:`Parameters` object and a " +"dictionary of aggregated metrics. The :code:`Parameters` return value is " +"optional because :code:`aggregate_fit` might decide that the results " "provided are not sufficient for aggregation (e.g., too many failures)." msgstr "" @@ -4314,42 +4276,40 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:265 msgid "" -":code:`configure_evaluate` is responsible for configuring the upcoming " -"round of evaluation. What does *configure* mean in this context? " -"Configuring a round means selecting clients and deciding what " -"instructions to send to these clients. The signature of " -":code:`configure_evaluate` makes this clear:" +":code:`configure_evaluate` is responsible for configuring the upcoming round " +"of evaluation. What does *configure* mean in this context? Configuring a " +"round means selecting clients and deciding what instructions to send to " +"these clients. The signature of :code:`configure_evaluate` makes this clear:" msgstr "" #: ../../source/how-to-implement-strategies.rst:278 msgid "" "The return value is a list of tuples, each representing the instructions " -"that will be sent to a particular client. Strategy implementations " -"usually perform the following steps in :code:`configure_evaluate`:" +"that will be sent to a particular client. Strategy implementations usually " +"perform the following steps in :code:`configure_evaluate`:" msgstr "" #: ../../source/how-to-implement-strategies.rst:281 msgid "" -"Pair each :code:`ClientProxy` with the same :code:`EvaluateIns` holding " -"the current global model :code:`parameters` and :code:`config` dict" +"Pair each :code:`ClientProxy` with the same :code:`EvaluateIns` holding the " +"current global model :code:`parameters` and :code:`config` dict" msgstr "" #: ../../source/how-to-implement-strategies.rst:283 msgid "" "More sophisticated implementations can use :code:`configure_evaluate` to " -"implement custom client selection logic. A client will only participate " -"in a round if the corresponding :code:`ClientProxy` is included in the " -"list returned from :code:`configure_evaluate`." +"implement custom client selection logic. A client will only participate in a " +"round if the corresponding :code:`ClientProxy` is included in the list " +"returned from :code:`configure_evaluate`." msgstr "" #: ../../source/how-to-implement-strategies.rst:287 msgid "" "The structure of this return value provides a lot of flexibility to the " "user. Since instructions are defined on a per-client basis, different " -"instructions can be sent to each client. This enables custom strategies " -"to evaluate, for example, different models on different clients, or use " -"different hyperparameters on different clients (via the :code:`config` " -"dict)." +"instructions can be sent to each client. This enables custom strategies to " +"evaluate, for example, different models on different clients, or use " +"different hyperparameters on different clients (via the :code:`config` dict)." msgstr "" #: ../../source/how-to-implement-strategies.rst:291 @@ -4359,24 +4319,24 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:293 msgid "" ":code:`aggregate_evaluate` is responsible for aggregating the results " -"returned by the clients that were selected and asked to evaluate in " -":code:`configure_evaluate`." +"returned by the clients that were selected and asked to evaluate in :code:" +"`configure_evaluate`." msgstr "" #: ../../source/how-to-implement-strategies.rst:306 msgid "" "Of course, failures can happen, so there is no guarantee that the server " -"will get results from all the clients it sent instructions to (via " -":code:`configure_evaluate`). :code:`aggregate_evaluate` therefore " -"receives a list of :code:`results`, but also a list of :code:`failures`." +"will get results from all the clients it sent instructions to (via :code:" +"`configure_evaluate`). :code:`aggregate_evaluate` therefore receives a list " +"of :code:`results`, but also a list of :code:`failures`." msgstr "" #: ../../source/how-to-implement-strategies.rst:308 msgid "" -":code:`aggregate_evaluate` returns an optional :code:`float` (loss) and a" -" dictionary of aggregated metrics. The :code:`float` return value is " -"optional because :code:`aggregate_evaluate` might decide that the results" -" provided are not sufficient for aggregation (e.g., too many failures)." +":code:`aggregate_evaluate` returns an optional :code:`float` (loss) and a " +"dictionary of aggregated metrics. The :code:`float` return value is optional " +"because :code:`aggregate_evaluate` might decide that the results provided " +"are not sufficient for aggregation (e.g., too many failures)." msgstr "" #: ../../source/how-to-implement-strategies.rst:311 @@ -4386,17 +4346,17 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:313 msgid "" ":code:`evaluate` is responsible for evaluating model parameters on the " -"server-side. Having :code:`evaluate` in addition to " -":code:`configure_evaluate`/:code:`aggregate_evaluate` enables strategies " -"to perform both servers-side and client-side (federated) evaluation." +"server-side. Having :code:`evaluate` in addition to :code:" +"`configure_evaluate`/:code:`aggregate_evaluate` enables strategies to " +"perform both servers-side and client-side (federated) evaluation." msgstr "" #: ../../source/how-to-implement-strategies.rst:323 msgid "" -"The return value is again optional because the strategy might not need to" -" implement server-side evaluation or because the user-defined " -":code:`evaluate` method might not complete successfully (e.g., it might " -"fail to load the server-side evaluation data)." +"The return value is again optional because the strategy might not need to " +"implement server-side evaluation or because the user-defined :code:" +"`evaluate` method might not complete successfully (e.g., it might fail to " +"load the server-side evaluation data)." msgstr "" #: ../../source/how-to-install-flower.rst:2 @@ -4418,8 +4378,7 @@ msgstr "" #: ../../source/how-to-install-flower.rst:17 msgid "" -"Stable releases are available on `PyPI " -"`_::" +"Stable releases are available on `PyPI `_::" msgstr "" #: ../../source/how-to-install-flower.rst:21 @@ -4438,14 +4397,14 @@ msgstr "" #: ../../source/how-to-install-flower.rst:31 msgid "" -"If you have not added ``conda-forge`` to your channels, you will first " -"need to run the following::" +"If you have not added ``conda-forge`` to your channels, you will first need " +"to run the following::" msgstr "" #: ../../source/how-to-install-flower.rst:36 msgid "" -"Once the ``conda-forge`` channel has been enabled, ``flwr`` can be " -"installed with ``conda``::" +"Once the ``conda-forge`` channel has been enabled, ``flwr`` can be installed " +"with ``conda``::" msgstr "" #: ../../source/how-to-install-flower.rst:40 @@ -4459,8 +4418,8 @@ msgstr "" #: ../../source/how-to-install-flower.rst:48 msgid "" "The following command can be used to verify if Flower was successfully " -"installed. If everything worked, it should print the version of Flower to" -" the command line::" +"installed. If everything worked, it should print the version of Flower to " +"the command line::" msgstr "" #: ../../source/how-to-install-flower.rst:55 @@ -4481,15 +4440,15 @@ msgstr "" #: ../../source/how-to-install-flower.rst:65 msgid "" -"New (possibly unstable) versions of Flower are sometimes available as " -"pre-release versions (alpha, beta, release candidate) before the stable " -"release happens::" +"New (possibly unstable) versions of Flower are sometimes available as pre-" +"release versions (alpha, beta, release candidate) before the stable release " +"happens::" msgstr "" #: ../../source/how-to-install-flower.rst:69 msgid "" -"For simulations that use the Virtual Client Engine, ``flwr`` pre-releases" -" should be installed with the ``simulation`` extra::" +"For simulations that use the Virtual Client Engine, ``flwr`` pre-releases " +"should be installed with the ``simulation`` extra::" msgstr "" #: ../../source/how-to-install-flower.rst:74 @@ -4498,14 +4457,14 @@ msgstr "" #: ../../source/how-to-install-flower.rst:76 msgid "" -"The latest (potentially unstable) changes in Flower are available as " -"nightly releases::" +"The latest (potentially unstable) changes in Flower are available as nightly " +"releases::" msgstr "" #: ../../source/how-to-install-flower.rst:80 msgid "" -"For simulations that use the Virtual Client Engine, ``flwr-nightly`` " -"should be installed with the ``simulation`` extra::" +"For simulations that use the Virtual Client Engine, ``flwr-nightly`` should " +"be installed with the ``simulation`` extra::" msgstr "" #: ../../source/how-to-monitor-simulation.rst:2 @@ -4514,17 +4473,17 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:4 msgid "" -"Flower allows you to monitor system resources while running your " -"simulation. Moreover, the Flower simulation engine is powerful and " -"enables you to decide how to allocate resources per client manner and " -"constrain the total usage. Insights from resource consumption can help " -"you make smarter decisions and speed up the execution time." +"Flower allows you to monitor system resources while running your simulation. " +"Moreover, the Flower simulation engine is powerful and enables you to decide " +"how to allocate resources per client manner and constrain the total usage. " +"Insights from resource consumption can help you make smarter decisions and " +"speed up the execution time." msgstr "" #: ../../source/how-to-monitor-simulation.rst:6 msgid "" -"The specific instructions assume you are using macOS and have the " -"`Homebrew `_ package manager installed." +"The specific instructions assume you are using macOS and have the `Homebrew " +"`_ package manager installed." msgstr "" #: ../../source/how-to-monitor-simulation.rst:10 @@ -4533,10 +4492,10 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:16 msgid "" -"`Prometheus `_ is used for data collection, while" -" `Grafana `_ will enable you to visualize the " -"collected data. They are both well integrated with `Ray " -"`_ which Flower uses under the hood." +"`Prometheus `_ is used for data collection, while " +"`Grafana `_ will enable you to visualize the collected " +"data. They are both well integrated with `Ray `_ which " +"Flower uses under the hood." msgstr "" #: ../../source/how-to-monitor-simulation.rst:18 @@ -4555,22 +4514,21 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:34 msgid "" -"Open the respective configuration files and change them. Depending on " -"your device, use one of the two following commands:" +"Open the respective configuration files and change them. Depending on your " +"device, use one of the two following commands:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:44 msgid "" -"and then delete all the text in the file and paste a new Prometheus " -"config you see below. You may adjust the time intervals to your " -"requirements:" +"and then delete all the text in the file and paste a new Prometheus config " +"you see below. You may adjust the time intervals to your requirements:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:59 msgid "" -"Now after you have edited the Prometheus configuration, do the same with " -"the Grafana configuration files. Open those using one of the following " -"commands as before:" +"Now after you have edited the Prometheus configuration, do the same with the " +"Grafana configuration files. Open those using one of the following commands " +"as before:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:69 @@ -4581,8 +4539,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:84 msgid "" -"Congratulations, you just downloaded all the necessary software needed " -"for metrics tracking. Now, let’s start it." +"Congratulations, you just downloaded all the necessary software needed for " +"metrics tracking. Now, let’s start it." msgstr "" #: ../../source/how-to-monitor-simulation.rst:88 @@ -4597,8 +4555,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:97 msgid "" -"Please include the following argument in your Python code when starting a" -" simulation." +"Please include the following argument in your Python code when starting a " +"simulation." msgstr "" #: ../../source/how-to-monitor-simulation.rst:108 @@ -4607,8 +4565,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:110 msgid "" -"Shortly after the simulation starts, you should see the following logs in" -" your terminal:" +"Shortly after the simulation starts, you should see the following logs in " +"your terminal:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:117 @@ -4617,17 +4575,17 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:119 msgid "" -"It's a Ray Dashboard. You can navigate to Metrics (on the left panel, the" -" lowest option)." +"It's a Ray Dashboard. You can navigate to Metrics (on the left panel, the " +"lowest option)." msgstr "" #: ../../source/how-to-monitor-simulation.rst:121 msgid "" -"Or alternatively, you can just see them in Grafana by clicking on the " -"right-up corner, “View in Grafana”. Please note that the Ray dashboard is" -" only accessible during the simulation. After the simulation ends, you " -"can only use Grafana to explore the metrics. You can start Grafana by " -"going to ``http://localhost:3000/``." +"Or alternatively, you can just see them in Grafana by clicking on the right-" +"up corner, “View in Grafana”. Please note that the Ray dashboard is only " +"accessible during the simulation. After the simulation ends, you can only " +"use Grafana to explore the metrics. You can start Grafana by going to " +"``http://localhost:3000/``." msgstr "" #: ../../source/how-to-monitor-simulation.rst:123 @@ -4643,18 +4601,18 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:134 msgid "" -"You must understand how the Ray library works to efficiently allocate " -"system resources to simulation clients on your own." +"You must understand how the Ray library works to efficiently allocate system " +"resources to simulation clients on your own." msgstr "" #: ../../source/how-to-monitor-simulation.rst:136 msgid "" "Initially, the simulation (which Ray handles under the hood) starts by " "default with all the available resources on the system, which it shares " -"among the clients. It doesn't mean it divides it equally among all of " -"them, nor that the model training happens at all of them simultaneously. " -"You will learn more about that in the later part of this blog. You can " -"check the system resources by running the following:" +"among the clients. It doesn't mean it divides it equally among all of them, " +"nor that the model training happens at all of them simultaneously. You will " +"learn more about that in the later part of this blog. You can check the " +"system resources by running the following:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:143 @@ -4663,8 +4621,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:155 msgid "" -"However, you can overwrite the defaults. When starting a simulation, do " -"the following (you don't need to overwrite all of them):" +"However, you can overwrite the defaults. When starting a simulation, do the " +"following (you don't need to overwrite all of them):" msgstr "" #: ../../source/how-to-monitor-simulation.rst:175 @@ -4673,19 +4631,19 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:205 msgid "" -"Now comes the crucial part. Ray will start a new client only when it has " -"all the required resources (such that they run in parallel) when the " -"resources allow." +"Now comes the crucial part. Ray will start a new client only when it has all " +"the required resources (such that they run in parallel) when the resources " +"allow." msgstr "" #: ../../source/how-to-monitor-simulation.rst:207 msgid "" -"In the example above, only one client will be run, so your clients won't " -"run concurrently. Setting :code:`client_num_gpus = 0.5` would allow " -"running two clients and therefore enable them to run concurrently. Be " -"careful not to require more resources than available. If you specified " -":code:`client_num_gpus = 2`, the simulation wouldn't start (even if you " -"had 2 GPUs but decided to set 1 in :code:`ray_init_args`)." +"In the example above, only one client will be run, so your clients won't run " +"concurrently. Setting :code:`client_num_gpus = 0.5` would allow running two " +"clients and therefore enable them to run concurrently. Be careful not to " +"require more resources than available. If you specified :code:" +"`client_num_gpus = 2`, the simulation wouldn't start (even if you had 2 GPUs " +"but decided to set 1 in :code:`ray_init_args`)." msgstr "" #: ../../source/how-to-monitor-simulation.rst:212 ../../source/ref-faq.rst:2 @@ -4698,22 +4656,21 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:216 msgid "" -"A: The timeframe might not be properly set. The setting is in the top " -"right corner (\"Last 30 minutes\" by default). Please change the " -"timeframe to reflect the period when the simulation was running." +"A: The timeframe might not be properly set. The setting is in the top right " +"corner (\"Last 30 minutes\" by default). Please change the timeframe to " +"reflect the period when the simulation was running." msgstr "" #: ../../source/how-to-monitor-simulation.rst:218 msgid "" -"Q: I see “Grafana server not detected. Please make sure the Grafana " -"server is running and refresh this page” after going to the Metrics tab " -"in Ray Dashboard." +"Q: I see “Grafana server not detected. Please make sure the Grafana server " +"is running and refresh this page” after going to the Metrics tab in Ray " +"Dashboard." msgstr "" #: ../../source/how-to-monitor-simulation.rst:220 msgid "" -"A: You probably don't have Grafana running. Please check the running " -"services" +"A: You probably don't have Grafana running. Please check the running services" msgstr "" #: ../../source/how-to-monitor-simulation.rst:226 @@ -4724,8 +4681,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:228 msgid "" -"A: Either the simulation has already finished, or you still need to start" -" Prometheus." +"A: Either the simulation has already finished, or you still need to start " +"Prometheus." msgstr "" #: ../../source/how-to-monitor-simulation.rst:232 @@ -4748,9 +4705,8 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:4 msgid "" -"The simplest way to get started with Flower is by using the pre-made " -"Docker images, which you can find on `Docker Hub " -"`__." +"The simplest way to get started with Flower is by using the pre-made Docker " +"images, which you can find on `Docker Hub `__." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:7 @@ -4759,26 +4715,25 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:14 msgid "" -"If you do not see the version of Docker but instead get an error saying " -"that the command was not found, you will need to install Docker first. " -"You can find installation instruction `here `_." +"If you do not see the version of Docker but instead get an error saying that " +"the command was not found, you will need to install Docker first. You can " +"find installation instruction `here `_." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:20 msgid "" -"On Linux, Docker commands require ``sudo`` privilege. If you want to " -"avoid using ``sudo``, you can follow the `Post-installation steps " -"`_ on the " -"official Docker website." +"On Linux, Docker commands require ``sudo`` privilege. If you want to avoid " +"using ``sudo``, you can follow the `Post-installation steps `_ on the official Docker " +"website." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:26 msgid "" -"To ensure optimal performance and compatibility, the SuperLink, SuperNode" -" and ServerApp image must have the same version when running together. " -"This guarantees seamless integration and avoids potential conflicts or " -"issues that may arise from using different versions." +"To ensure optimal performance and compatibility, the SuperLink, SuperNode " +"and ServerApp image must have the same version when running together. This " +"guarantees seamless integration and avoids potential conflicts or issues " +"that may arise from using different versions." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:31 @@ -4795,27 +4750,25 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:42 msgid "" -"The command pulls the Docker image with the tag ``1.8.0`` from Docker " -"Hub. The tag specifies the Flower version. In this case, Flower 1.8.0. " -"The ``--rm`` flag tells Docker to remove the container after it exits." +"The command pulls the Docker image with the tag ``1.8.0`` from Docker Hub. " +"The tag specifies the Flower version. In this case, Flower 1.8.0. The ``--" +"rm`` flag tells Docker to remove the container after it exits." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:48 msgid "" "By default, the Flower SuperLink keeps state in-memory. When using the " -"Docker flag ``--rm``, the state is not persisted between container " -"starts. We will show below how to save the state in a file on your host " -"system." +"Docker flag ``--rm``, the state is not persisted between container starts. " +"We will show below how to save the state in a file on your host system." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:52 msgid "" -"The ``-p :`` flag tells Docker to map the ports " -"``9091``/``9092`` of the host to ``9091``/``9092`` of the container, " -"allowing you to access the Driver API on ``http://localhost:9091`` and " -"the Fleet API on ``http://localhost:9092``. Lastly, any flag that comes " -"after the tag is passed to the Flower SuperLink. Here, we are passing the" -" flag ``--insecure``." +"The ``-p :`` flag tells Docker to map the ports ``9091``/" +"``9092`` of the host to ``9091``/``9092`` of the container, allowing you to " +"access the Driver API on ``http://localhost:9091`` and the Fleet API on " +"``http://localhost:9092``. Lastly, any flag that comes after the tag is " +"passed to the Flower SuperLink. Here, we are passing the flag ``--insecure``." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:59 @@ -4823,10 +4776,10 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:354 msgid "" "The ``--insecure`` flag enables insecure communication (using HTTP, not " -"HTTPS) and should only be used for testing purposes. We strongly " -"recommend enabling `SSL `__ when " -"deploying to a production environment." +"HTTPS) and should only be used for testing purposes. We strongly recommend " +"enabling `SSL `__ when deploying to a " +"production environment." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:64 @@ -4841,21 +4794,20 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:73 msgid "" -"If you want to persist the state of the SuperLink on your host system, " -"all you need to do is specify a path where you want to save the file on " -"your host system and a name for the database file. In the example below, " -"we tell Docker via the flag ``--volume`` to mount the user's home " -"directory (``~/`` on your host) into the ``/app/`` directory of the " -"container. Furthermore, we use the flag ``--database`` to specify the " -"name of the database file." +"If you want to persist the state of the SuperLink on your host system, all " +"you need to do is specify a path where you want to save the file on your " +"host system and a name for the database file. In the example below, we tell " +"Docker via the flag ``--volume`` to mount the user's home directory (``~/`` " +"on your host) into the ``/app/`` directory of the container. Furthermore, we " +"use the flag ``--database`` to specify the name of the database file." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:86 msgid "" "As soon as the SuperLink starts, the file ``state.db`` is created in the " -"user's home directory on your host system. If the file already exists, " -"the SuperLink tries to restore the state from the file. To start the " -"SuperLink with an empty database, simply remove the ``state.db`` file." +"user's home directory on your host system. If the file already exists, the " +"SuperLink tries to restore the state from the file. To start the SuperLink " +"with an empty database, simply remove the ``state.db`` file." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:91 @@ -4866,25 +4818,25 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:93 msgid "" -"To enable SSL, you will need a PEM-encoded root certificate, a PEM-" -"encoded private key and a PEM-encoded certificate chain." +"To enable SSL, you will need a PEM-encoded root certificate, a PEM-encoded " +"private key and a PEM-encoded certificate chain." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:97 msgid "" -"For testing purposes, you can generate your own self-signed certificates." -" The `Enable SSL connections `__ page contains a section that" -" will guide you through the process." +"For testing purposes, you can generate your own self-signed certificates. " +"The `Enable SSL connections `__ page contains a section that will " +"guide you through the process." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:101 msgid "" -"Assuming all files we need are in the local ``certificates`` directory, " -"we can use the flag ``--volume`` to mount the local directory into the " -"``/app/`` directory of the container. This allows the SuperLink to access" -" the files within the container. Finally, we pass the names of the " -"certificates to the SuperLink with the ``--certificates`` flag." +"Assuming all files we need are in the local ``certificates`` directory, we " +"can use the flag ``--volume`` to mount the local directory into the ``/app/" +"`` directory of the container. This allows the SuperLink to access the files " +"within the container. Finally, we pass the names of the certificates to the " +"SuperLink with the ``--certificates`` flag." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:113 @@ -4893,31 +4845,31 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:115 msgid "" -"The SuperNode Docker image comes with a pre-installed version of Flower " -"and serves as a base for building your own SuperNode image." +"The SuperNode Docker image comes with a pre-installed version of Flower and " +"serves as a base for building your own SuperNode image." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:120 msgid "" "The SuperNode Docker image currently works only with the 1.9.0-nightly " -"release. A stable version will be available when Flower 1.9.0 (stable) " -"gets released (ETA: May). A SuperNode nightly image must be paired with " -"the corresponding SuperLink and ServerApp nightly images released on the " -"same day. To ensure the versions are in sync, using the concrete tag, " -"e.g., ``1.9.0.dev20240501`` instead of ``nightly`` is recommended." +"release. A stable version will be available when Flower 1.9.0 (stable) gets " +"released (ETA: May). A SuperNode nightly image must be paired with the " +"corresponding SuperLink and ServerApp nightly images released on the same " +"day. To ensure the versions are in sync, using the concrete tag, e.g., " +"``1.9.0.dev20240501`` instead of ``nightly`` is recommended." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:126 msgid "" -"We will use the ``quickstart-pytorch`` example, which you can find in the" -" Flower repository, to illustrate how you can dockerize your ClientApp." +"We will use the ``quickstart-pytorch`` example, which you can find in the " +"Flower repository, to illustrate how you can dockerize your ClientApp." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:134 msgid "" "Before we can start, we need to meet a few prerequisites in our local " -"development environment. You can skip the first part if you want to run " -"your ClientApp instead of the ``quickstart-pytorch`` example." +"development environment. You can skip the first part if you want to run your " +"ClientApp instead of the ``quickstart-pytorch`` example." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:138 @@ -4935,24 +4887,24 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:163 msgid "" -"First, we need to create a ``requirements.txt`` file in the directory " -"where the ``ClientApp`` code is located. In the file, we list all the " -"dependencies that the ClientApp requires." +"First, we need to create a ``requirements.txt`` file in the directory where " +"the ``ClientApp`` code is located. In the file, we list all the dependencies " +"that the ClientApp requires." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:175 msgid "" -"Note that `flwr `__ is already installed " -"in the ``flwr/supernode`` base image, so you only need to include other " -"package dependencies in your ``requirements.txt``, such as ``torch``, " +"Note that `flwr `__ is already installed in " +"the ``flwr/supernode`` base image, so you only need to include other package " +"dependencies in your ``requirements.txt``, such as ``torch``, " "``tensorflow``, etc." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:179 msgid "" -"Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` " -"example, create a new file called ``Dockerfile.supernode`` in ``examples" -"/quickstart-pytorch``." +"Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` example, " +"create a new file called ``Dockerfile.supernode`` in ``examples/quickstart-" +"pytorch``." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:182 @@ -4963,15 +4915,15 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:196 msgid "" -"In the first two lines, we instruct Docker to use the SuperNode image " -"tagged ``nightly`` as a base image and set our working directory to " -"``/app``. The following instructions will now be executed in the ``/app``" -" directory. Next, we install the ClientApp dependencies by copying the " -"``requirements.txt`` file into the image and run ``pip install``. In the " -"last two lines, we copy the ``client.py`` module into the image and set " -"the entry point to ``flower-client-app`` with the argument " -"``client:app``. The argument is the object reference of the ClientApp " -"(``:``) that will be run inside the ClientApp." +"In the first two lines, we instruct Docker to use the SuperNode image tagged " +"``nightly`` as a base image and set our working directory to ``/app``. The " +"following instructions will now be executed in the ``/app`` directory. Next, " +"we install the ClientApp dependencies by copying the ``requirements.txt`` " +"file into the image and run ``pip install``. In the last two lines, we copy " +"the ``client.py`` module into the image and set the entry point to ``flower-" +"client-app`` with the argument ``client:app``. The argument is the object " +"reference of the ClientApp (``:``) that will be run " +"inside the ClientApp." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:205 @@ -4980,8 +4932,8 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:207 msgid "" -"Next, we build the SuperNode Docker image by running the following " -"command in the directory where Dockerfile and ClientApp code are located." +"Next, we build the SuperNode Docker image by running the following command " +"in the directory where Dockerfile and ClientApp code are located." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:214 @@ -5012,8 +4964,8 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:232 #: ../../source/how-to-run-flower-using-docker.rst:348 msgid "" -"``--rm``: This option specifies that the container should be " -"automatically removed when it stops." +"``--rm``: This option specifies that the container should be automatically " +"removed when it stops." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:233 @@ -5027,8 +4979,8 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--server 192.168.1.100:9092``: This option specifies the address of the" -" SuperLinks Fleet" +"``--server 192.168.1.100:9092``: This option specifies the address of the " +"SuperLinks Fleet" msgstr "" #: ../../source/how-to-run-flower-using-docker.rst @@ -5037,10 +4989,10 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:248 msgid "" -"To test running Flower locally, you can create a `bridge network " -"`__, use the ``--network`` argument and pass the " -"name of the Docker network to run your SuperNodes." +"To test running Flower locally, you can create a `bridge network `__, use the ``--network`` argument and pass the name of the Docker " +"network to run your SuperNodes." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:252 @@ -5057,11 +5009,10 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:264 msgid "" -"Assuming the certificate already exists locally, we can use the flag " -"``--volume`` to mount the local certificate into the container's " -"``/app/`` directory. This allows the SuperNode to access the certificate " -"within the container. Use the ``--certificates`` flag when starting the " -"container." +"Assuming the certificate already exists locally, we can use the flag ``--" +"volume`` to mount the local certificate into the container's ``/app/`` " +"directory. This allows the SuperNode to access the certificate within the " +"container. Use the ``--certificates`` flag when starting the container." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:275 @@ -5070,22 +5021,22 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:277 msgid "" -"The procedure for building and running a ServerApp image is almost " -"identical to the SuperNode image." +"The procedure for building and running a ServerApp image is almost identical " +"to the SuperNode image." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:279 msgid "" -"Similar to the SuperNode image, the ServerApp Docker image comes with a " -"pre-installed version of Flower and serves as a base for building your " -"own ServerApp image." +"Similar to the SuperNode image, the ServerApp Docker image comes with a pre-" +"installed version of Flower and serves as a base for building your own " +"ServerApp image." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:282 msgid "" -"We will use the same ``quickstart-pytorch`` example as we do in the " -"Flower SuperNode section. If you have not already done so, please follow " -"the `SuperNode Prerequisites`_ before proceeding." +"We will use the same ``quickstart-pytorch`` example as we do in the Flower " +"SuperNode section. If you have not already done so, please follow the " +"`SuperNode Prerequisites`_ before proceeding." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:287 @@ -5096,8 +5047,8 @@ msgstr "" msgid "" "First, we need to create a Dockerfile in the directory where the " "``ServerApp`` code is located. If you use the ``quickstart-pytorch`` " -"example, create a new file called ``Dockerfile.serverapp`` in ``examples" -"/quickstart-pytorch``." +"example, create a new file called ``Dockerfile.serverapp`` in ``examples/" +"quickstart-pytorch``." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:302 @@ -5108,14 +5059,13 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:313 msgid "" -"In the first two lines, we instruct Docker to use the ServerApp image " -"tagged ``1.8.0`` as a base image and set our working directory to " -"``/app``. The following instructions will now be executed in the ``/app``" -" directory. In the last two lines, we copy the ``server.py`` module into " -"the image and set the entry point to ``flower-server-app`` with the " -"argument ``server:app``. The argument is the object reference of the " -"ServerApp (``:``) that will be run inside the " -"ServerApp container." +"In the first two lines, we instruct Docker to use the ServerApp image tagged " +"``1.8.0`` as a base image and set our working directory to ``/app``. The " +"following instructions will now be executed in the ``/app`` directory. In " +"the last two lines, we copy the ``server.py`` module into the image and set " +"the entry point to ``flower-server-app`` with the argument ``server:app``. " +"The argument is the object reference of the ServerApp (``:" +"``) that will be run inside the ServerApp container." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:321 @@ -5124,8 +5074,8 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:323 msgid "" -"Next, we build the ServerApp Docker image by running the following " -"command in the directory where Dockerfile and ServerApp code are located." +"Next, we build the ServerApp Docker image by running the following command " +"in the directory where Dockerfile and ServerApp code are located." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:330 @@ -5149,16 +5099,16 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--server 192.168.1.100:9091``: This option specifies the address of the" -" SuperLinks Driver" +"``--server 192.168.1.100:9091``: This option specifies the address of the " +"SuperLinks Driver" msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:363 msgid "" -"To test running Flower locally, you can create a `bridge network " -"`__, use the ``--network`` argument and pass the " -"name of the Docker network to run your ServerApps." +"To test running Flower locally, you can create a `bridge network `__, use the ``--network`` argument and pass the name of the Docker " +"network to run your ServerApps." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:367 @@ -5175,11 +5125,10 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:379 msgid "" -"Assuming the certificate already exists locally, we can use the flag " -"``--volume`` to mount the local certificate into the container's " -"``/app/`` directory. This allows the ServerApp to access the certificate " -"within the container. Use the ``--certificates`` flag when starting the " -"container." +"Assuming the certificate already exists locally, we can use the flag ``--" +"volume`` to mount the local certificate into the container's ``/app/`` " +"directory. This allows the ServerApp to access the certificate within the " +"container. Use the ``--certificates`` flag when starting the container." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:390 @@ -5193,8 +5142,8 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:395 msgid "" "If you want to use a different version of Flower, for example Flower " -"nightly, you can do so by changing the tag. All available versions are on" -" `Docker Hub `__." +"nightly, you can do so by changing the tag. All available versions are on " +"`Docker Hub `__." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:400 @@ -5205,9 +5154,9 @@ msgstr "" msgid "" "It may happen that we update the images behind the tags. Such updates " "usually include security updates of system dependencies that should not " -"change the functionality of Flower. However, if you want to ensure that " -"you always use the same image, you can specify the hash of the image " -"instead of the tag." +"change the functionality of Flower. However, if you want to ensure that you " +"always use the same image, you can specify the hash of the image instead of " +"the tag." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:407 @@ -5237,63 +5186,60 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:8 msgid "" "Simulating Federated Learning workloads is useful for a multitude of use-" -"cases: you might want to run your workload on a large cohort of clients " -"but without having to source, configure and mange a large number of " -"physical devices; you might want to run your FL workloads as fast as " -"possible on the compute systems you have access to without having to go " -"through a complex setup process; you might want to validate your " -"algorithm on different scenarios at varying levels of data and system " -"heterogeneity, client availability, privacy budgets, etc. These are among" -" some of the use-cases where simulating FL workloads makes sense. Flower " -"can accommodate these scenarios by means of its `VirtualClientEngine " -"`_ or " -"VCE." +"cases: you might want to run your workload on a large cohort of clients but " +"without having to source, configure and mange a large number of physical " +"devices; you might want to run your FL workloads as fast as possible on the " +"compute systems you have access to without having to go through a complex " +"setup process; you might want to validate your algorithm on different " +"scenarios at varying levels of data and system heterogeneity, client " +"availability, privacy budgets, etc. These are among some of the use-cases " +"where simulating FL workloads makes sense. Flower can accommodate these " +"scenarios by means of its `VirtualClientEngine `_ or VCE." msgstr "" #: ../../source/how-to-run-simulations.rst:10 msgid "" -"The :code:`VirtualClientEngine` schedules, launches and manages `virtual`" -" clients. These clients are identical to `non-virtual` clients (i.e. the " -"ones you launch via the command `flwr.client.start_client `_) in the sense that they can be configure by " -"creating a class inheriting, for example, from `flwr.client.NumPyClient " -"`_ and therefore behave in an " -"identical way. In addition to that, clients managed by the " -":code:`VirtualClientEngine` are:" +"The :code:`VirtualClientEngine` schedules, launches and manages `virtual` " +"clients. These clients are identical to `non-virtual` clients (i.e. the ones " +"you launch via the command `flwr.client.start_client `_) in the sense that they can be configure by creating a " +"class inheriting, for example, from `flwr.client.NumPyClient `_ and therefore behave in an identical way. In " +"addition to that, clients managed by the :code:`VirtualClientEngine` are:" msgstr "" #: ../../source/how-to-run-simulations.rst:12 msgid "" -"resource-aware: this means that each client gets assigned a portion of " -"the compute and memory on your system. You as a user can control this at " -"the beginning of the simulation and allows you to control the degree of " +"resource-aware: this means that each client gets assigned a portion of the " +"compute and memory on your system. You as a user can control this at the " +"beginning of the simulation and allows you to control the degree of " "parallelism of your Flower FL simulation. The fewer the resources per " "client, the more clients can run concurrently on the same hardware." msgstr "" #: ../../source/how-to-run-simulations.rst:13 msgid "" -"self-managed: this means that you as a user do not need to launch clients" -" manually, instead this gets delegated to :code:`VirtualClientEngine`'s " +"self-managed: this means that you as a user do not need to launch clients " +"manually, instead this gets delegated to :code:`VirtualClientEngine`'s " "internals." msgstr "" #: ../../source/how-to-run-simulations.rst:14 msgid "" -"ephemeral: this means that a client is only materialized when it is " -"required in the FL process (e.g. to do `fit() `_). The object is destroyed afterwards," -" releasing the resources it was assigned and allowing in this way other " -"clients to participate." +"ephemeral: this means that a client is only materialized when it is required " +"in the FL process (e.g. to do `fit() `_). The object is destroyed afterwards, releasing the resources it was " +"assigned and allowing in this way other clients to participate." msgstr "" #: ../../source/how-to-run-simulations.rst:16 msgid "" "The :code:`VirtualClientEngine` implements `virtual` clients using `Ray " "`_, an open-source framework for scalable Python " -"workloads. In particular, Flower's :code:`VirtualClientEngine` makes use " -"of `Actors `_ to " -"spawn `virtual` clients and run their workload." +"workloads. In particular, Flower's :code:`VirtualClientEngine` makes use of " +"`Actors `_ to spawn " +"`virtual` clients and run their workload." msgstr "" #: ../../source/how-to-run-simulations.rst:20 @@ -5302,12 +5248,11 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:22 msgid "" -"Running Flower simulations still require you to define your client class," -" a strategy, and utility functions to download and load (and potentially " -"partition) your dataset. With that out of the way, launching your " -"simulation is done with `start_simulation `_ and a minimal example looks" -" as follows:" +"Running Flower simulations still require you to define your client class, a " +"strategy, and utility functions to download and load (and potentially " +"partition) your dataset. With that out of the way, launching your simulation " +"is done with `start_simulation `_ and a minimal example looks as follows:" msgstr "" #: ../../source/how-to-run-simulations.rst:44 @@ -5316,16 +5261,16 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:45 msgid "" -"By default the VCE has access to all system resources (i.e. all CPUs, all" -" GPUs, etc) since that is also the default behavior when starting Ray. " -"However, in some settings you might want to limit how many of your system" -" resources are used for simulation. You can do this via the " -":code:`ray_init_args` input argument to :code:`start_simulation` which " -"the VCE internally passes to Ray's :code:`ray.init` command. For a " -"complete list of settings you can configure check the `ray.init " -"`_" -" documentation. Do not set :code:`ray_init_args` if you want the VCE to " -"use all your system's CPUs and GPUs." +"By default the VCE has access to all system resources (i.e. all CPUs, all " +"GPUs, etc) since that is also the default behavior when starting Ray. " +"However, in some settings you might want to limit how many of your system " +"resources are used for simulation. You can do this via the :code:" +"`ray_init_args` input argument to :code:`start_simulation` which the VCE " +"internally passes to Ray's :code:`ray.init` command. For a complete list of " +"settings you can configure check the `ray.init `_ documentation. Do not set :" +"code:`ray_init_args` if you want the VCE to use all your system's CPUs and " +"GPUs." msgstr "" #: ../../source/how-to-run-simulations.rst:62 @@ -5334,20 +5279,19 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:63 msgid "" -"By default the :code:`VirtualClientEngine` assigns a single CPU core (and" -" nothing else) to each virtual client. This means that if your system has" -" 10 cores, that many virtual clients can be concurrently running." +"By default the :code:`VirtualClientEngine` assigns a single CPU core (and " +"nothing else) to each virtual client. This means that if your system has 10 " +"cores, that many virtual clients can be concurrently running." msgstr "" #: ../../source/how-to-run-simulations.rst:65 msgid "" -"More often than not, you would probably like to adjust the resources your" -" clients get assigned based on the complexity (i.e. compute and memory " -"footprint) of your FL workload. You can do so when starting your " -"simulation by setting the argument `client_resources` to " -"`start_simulation `_." -" Two keys are internally used by Ray to schedule and spawn workloads (in " -"our case Flower clients):" +"More often than not, you would probably like to adjust the resources your " +"clients get assigned based on the complexity (i.e. compute and memory " +"footprint) of your FL workload. You can do so when starting your simulation " +"by setting the argument `client_resources` to `start_simulation `_. Two keys are internally used " +"by Ray to schedule and spawn workloads (in our case Flower clients):" msgstr "" #: ../../source/how-to-run-simulations.rst:67 @@ -5368,21 +5312,21 @@ msgstr "" msgid "" "While the :code:`client_resources` can be used to control the degree of " "concurrency in your FL simulation, this does not stop you from running " -"dozens, hundreds or even thousands of clients in the same round and " -"having orders of magnitude more `dormant` (i.e. not participating in a " -"round) clients. Let's say you want to have 100 clients per round but your" -" system can only accommodate 8 clients concurrently. The " -":code:`VirtualClientEngine` will schedule 100 jobs to run (each " -"simulating a client sampled by the strategy) and then will execute them " -"in a resource-aware manner in batches of 8." +"dozens, hundreds or even thousands of clients in the same round and having " +"orders of magnitude more `dormant` (i.e. not participating in a round) " +"clients. Let's say you want to have 100 clients per round but your system " +"can only accommodate 8 clients concurrently. The :code:`VirtualClientEngine` " +"will schedule 100 jobs to run (each simulating a client sampled by the " +"strategy) and then will execute them in a resource-aware manner in batches " +"of 8." msgstr "" #: ../../source/how-to-run-simulations.rst:91 msgid "" "To understand all the intricate details on how resources are used to " -"schedule FL clients and how to define custom resources, please take a " -"look at the `Ray documentation `_." +"schedule FL clients and how to define custom resources, please take a look " +"at the `Ray documentation `_." msgstr "" #: ../../source/how-to-run-simulations.rst:94 @@ -5391,22 +5335,22 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:96 msgid "" -"A few ready-to-run complete examples for Flower simulation in " -"Tensorflow/Keras and PyTorch are provided in the `Flower repository " -"`_. You can run them on Google Colab too:" +"A few ready-to-run complete examples for Flower simulation in Tensorflow/" +"Keras and PyTorch are provided in the `Flower repository `_. You can run them on Google Colab too:" msgstr "" #: ../../source/how-to-run-simulations.rst:98 msgid "" -"`Tensorflow/Keras Simulation " -"`_: 100 clients collaboratively train a MLP model on MNIST." +"`Tensorflow/Keras Simulation `_: 100 clients collaboratively train a MLP " +"model on MNIST." msgstr "" #: ../../source/how-to-run-simulations.rst:99 msgid "" -"`PyTorch Simulation `_: 100 clients collaboratively train a CNN model on " +"`PyTorch Simulation `_: 100 clients collaboratively train a CNN model on " "MNIST." msgstr "" @@ -5416,9 +5360,9 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:106 msgid "" -"Flower's :code:`VirtualClientEngine` allows you to run FL simulations " -"across multiple compute nodes. Before starting your multi-node simulation" -" ensure that you:" +"Flower's :code:`VirtualClientEngine` allows you to run FL simulations across " +"multiple compute nodes. Before starting your multi-node simulation ensure " +"that you:" msgstr "" #: ../../source/how-to-run-simulations.rst:108 @@ -5431,29 +5375,29 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:110 msgid "" -"Have a copy of your dataset in all nodes (more about this in " -":ref:`simulation considerations `)" +"Have a copy of your dataset in all nodes (more about this in :ref:" +"`simulation considerations `)" msgstr "" #: ../../source/how-to-run-simulations.rst:111 msgid "" -"Pass :code:`ray_init_args={\"address\"=\"auto\"}` to `start_simulation " -"`_ so the " -":code:`VirtualClientEngine` attaches to a running Ray instance." +"Pass :code:`ray_init_args={\"address\"=\"auto\"}` to `start_simulation `_ so the :code:" +"`VirtualClientEngine` attaches to a running Ray instance." msgstr "" #: ../../source/how-to-run-simulations.rst:112 msgid "" -"Start Ray on you head node: on the terminal type :code:`ray start " -"--head`. This command will print a few lines, one of which indicates how " -"to attach other nodes to the head node." +"Start Ray on you head node: on the terminal type :code:`ray start --head`. " +"This command will print a few lines, one of which indicates how to attach " +"other nodes to the head node." msgstr "" #: ../../source/how-to-run-simulations.rst:113 msgid "" -"Attach other nodes to the head node: copy the command shown after " -"starting the head and execute it on terminal of a new node: for example " -":code:`ray start --address='192.168.1.132:6379'`" +"Attach other nodes to the head node: copy the command shown after starting " +"the head and execute it on terminal of a new node: for example :code:`ray " +"start --address='192.168.1.132:6379'`" msgstr "" #: ../../source/how-to-run-simulations.rst:115 @@ -5464,9 +5408,9 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:117 msgid "" -"Once your simulation is finished, if you'd like to dismantle your cluster" -" you simply need to run the command :code:`ray stop` in each node's " -"terminal (including the head node)." +"Once your simulation is finished, if you'd like to dismantle your cluster " +"you simply need to run the command :code:`ray stop` in each node's terminal " +"(including the head node)." msgstr "" #: ../../source/how-to-run-simulations.rst:120 @@ -5481,21 +5425,19 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:124 msgid "" -"User :code:`ray status` to check all nodes connected to your head node as" -" well as the total resources available to the " -":code:`VirtualClientEngine`." +"User :code:`ray status` to check all nodes connected to your head node as " +"well as the total resources available to the :code:`VirtualClientEngine`." msgstr "" #: ../../source/how-to-run-simulations.rst:126 msgid "" -"When attaching a new node to the head, all its resources (i.e. all CPUs, " -"all GPUs) will be visible by the head node. This means that the " -":code:`VirtualClientEngine` can schedule as many `virtual` clients as " -"that node can possible run. In some settings you might want to exclude " -"certain resources from the simulation. You can do this by appending " -"`--num-cpus=` and/or `--num-" -"gpus=` in any :code:`ray start` command (including " -"when starting the head)" +"When attaching a new node to the head, all its resources (i.e. all CPUs, all " +"GPUs) will be visible by the head node. This means that the :code:" +"`VirtualClientEngine` can schedule as many `virtual` clients as that node " +"can possible run. In some settings you might want to exclude certain " +"resources from the simulation. You can do this by appending `--num-" +"cpus=` and/or `--num-gpus=` in any :" +"code:`ray start` command (including when starting the head)" msgstr "" #: ../../source/how-to-run-simulations.rst:132 @@ -5504,19 +5446,19 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:135 msgid "" -"We are actively working on these fronts so to make it trivial to run any " -"FL workload with Flower simulation." +"We are actively working on these fronts so to make it trivial to run any FL " +"workload with Flower simulation." msgstr "" #: ../../source/how-to-run-simulations.rst:138 msgid "" -"The current VCE allows you to run Federated Learning workloads in " -"simulation mode whether you are prototyping simple scenarios on your " -"personal laptop or you want to train a complex FL pipeline across " -"multiple high-performance GPU nodes. While we add more capabilities to " -"the VCE, the points below highlight some of the considerations to keep in" -" mind when designing your FL pipeline with Flower. We also highlight a " -"couple of current limitations in our implementation." +"The current VCE allows you to run Federated Learning workloads in simulation " +"mode whether you are prototyping simple scenarios on your personal laptop or " +"you want to train a complex FL pipeline across multiple high-performance GPU " +"nodes. While we add more capabilities to the VCE, the points below highlight " +"some of the considerations to keep in mind when designing your FL pipeline " +"with Flower. We also highlight a couple of current limitations in our " +"implementation." msgstr "" #: ../../source/how-to-run-simulations.rst:141 @@ -5525,17 +5467,16 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:143 msgid "" -"The VCE assigns a share of GPU memory to a client that specifies the key " -":code:`num_gpus` in :code:`client_resources`. This being said, Ray (used " +"The VCE assigns a share of GPU memory to a client that specifies the key :" +"code:`num_gpus` in :code:`client_resources`. This being said, Ray (used " "internally by the VCE) is by default:" msgstr "" #: ../../source/how-to-run-simulations.rst:146 msgid "" -"not aware of the total VRAM available on the GPUs. This means that if you" -" set :code:`num_gpus=0.5` and you have two GPUs in your system with " -"different (e.g. 32GB and 8GB) VRAM amounts, they both would run 2 clients" -" concurrently." +"not aware of the total VRAM available on the GPUs. This means that if you " +"set :code:`num_gpus=0.5` and you have two GPUs in your system with different " +"(e.g. 32GB and 8GB) VRAM amounts, they both would run 2 clients concurrently." msgstr "" #: ../../source/how-to-run-simulations.rst:147 @@ -5554,17 +5495,16 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:150 msgid "" "If you want to run several independent Flower simulations on the same " -"machine you need to mask-out your GPUs with " -":code:`CUDA_VISIBLE_DEVICES=\"\"` when launching your " -"experiment." +"machine you need to mask-out your GPUs with :code:" +"`CUDA_VISIBLE_DEVICES=\"\"` when launching your experiment." msgstr "" #: ../../source/how-to-run-simulations.rst:153 msgid "" -"In addition, the GPU resource limits passed to :code:`client_resources` " -"are not `enforced` (i.e. they can be exceeded) which can result in the " -"situation of client using more VRAM than the ratio specified when " -"starting the simulation." +"In addition, the GPU resource limits passed to :code:`client_resources` are " +"not `enforced` (i.e. they can be exceeded) which can result in the situation " +"of client using more VRAM than the ratio specified when starting the " +"simulation." msgstr "" #: ../../source/how-to-run-simulations.rst:156 @@ -5573,31 +5513,29 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:158 msgid "" -"When `using a GPU with TensorFlow " -"`_ nearly your entire GPU memory of" -" all your GPUs visible to the process will be mapped. This is done by " -"TensorFlow for optimization purposes. However, in settings such as FL " -"simulations where we want to split the GPU into multiple `virtual` " -"clients, this is not a desirable mechanism. Luckily we can disable this " -"default behavior by `enabling memory growth " -"`_." +"When `using a GPU with TensorFlow `_ " +"nearly your entire GPU memory of all your GPUs visible to the process will " +"be mapped. This is done by TensorFlow for optimization purposes. However, in " +"settings such as FL simulations where we want to split the GPU into multiple " +"`virtual` clients, this is not a desirable mechanism. Luckily we can disable " +"this default behavior by `enabling memory growth `_." msgstr "" #: ../../source/how-to-run-simulations.rst:160 msgid "" -"This would need to be done in the main process (which is where the server" -" would run) and in each Actor created by the VCE. By means of " -":code:`actor_kwargs` we can pass the reserved key `\"on_actor_init_fn\"` " -"in order to specify a function to be executed upon actor initialization. " -"In this case, to enable GPU growth for TF workloads. It would look as " -"follows:" +"This would need to be done in the main process (which is where the server " +"would run) and in each Actor created by the VCE. By means of :code:" +"`actor_kwargs` we can pass the reserved key `\"on_actor_init_fn\"` in order " +"to specify a function to be executed upon actor initialization. In this " +"case, to enable GPU growth for TF workloads. It would look as follows:" msgstr "" #: ../../source/how-to-run-simulations.rst:179 msgid "" "This is precisely the mechanism used in `Tensorflow/Keras Simulation " -"`_ example." +"`_ " +"example." msgstr "" #: ../../source/how-to-run-simulations.rst:183 @@ -5606,26 +5544,25 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:185 msgid "" -"The VCE does not currently offer a way to control on which node a " -"particular `virtual` client is executed. In other words, if more than a " -"single node have the resources needed by a client to run, then any of " -"those nodes could get the client workload scheduled onto. Later in the FL" -" process (i.e. in a different round) the same client could be executed by" -" a different node. Depending on how your clients access their datasets, " -"this might require either having a copy of all dataset partitions on all " -"nodes or a dataset serving mechanism (e.g. using nfs, a database) to " -"circumvent data duplication." +"The VCE does not currently offer a way to control on which node a particular " +"`virtual` client is executed. In other words, if more than a single node " +"have the resources needed by a client to run, then any of those nodes could " +"get the client workload scheduled onto. Later in the FL process (i.e. in a " +"different round) the same client could be executed by a different node. " +"Depending on how your clients access their datasets, this might require " +"either having a copy of all dataset partitions on all nodes or a dataset " +"serving mechanism (e.g. using nfs, a database) to circumvent data " +"duplication." msgstr "" #: ../../source/how-to-run-simulations.rst:187 msgid "" -"By definition virtual clients are `stateless` due to their ephemeral " -"nature. A client state can be implemented as part of the Flower client " -"class but users need to ensure this saved to persistent storage (e.g. a " -"database, disk) and that can be retrieve later by the same client " -"regardless on which node it is running from. This is related to the point" -" above also since, in some way, the client's dataset could be seen as a " -"type of `state`." +"By definition virtual clients are `stateless` due to their ephemeral nature. " +"A client state can be implemented as part of the Flower client class but " +"users need to ensure this saved to persistent storage (e.g. a database, " +"disk) and that can be retrieve later by the same client regardless on which " +"node it is running from. This is related to the point above also since, in " +"some way, the client's dataset could be seen as a type of `state`." msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:2 @@ -5634,9 +5571,9 @@ msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:4 msgid "" -"Flower does not automatically save model updates on the server-side. This" -" how-to guide describes the steps to save (and load) model checkpoints in" -" Flower." +"Flower does not automatically save model updates on the server-side. This " +"how-to guide describes the steps to save (and load) model checkpoints in " +"Flower." msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:8 @@ -5645,16 +5582,15 @@ msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:10 msgid "" -"Model updates can be persisted on the server-side by customizing " -":code:`Strategy` methods. Implementing custom strategies is always an " -"option, but for many cases it may be more convenient to simply customize " -"an existing strategy. The following code example defines a new " -":code:`SaveModelStrategy` which customized the existing built-in " -":code:`FedAvg` strategy. In particular, it customizes " -":code:`aggregate_fit` by calling :code:`aggregate_fit` in the base class " -"(:code:`FedAvg`). It then continues to save returned (aggregated) weights" -" before it returns those aggregated weights to the caller (i.e., the " -"server):" +"Model updates can be persisted on the server-side by customizing :code:" +"`Strategy` methods. Implementing custom strategies is always an option, but " +"for many cases it may be more convenient to simply customize an existing " +"strategy. The following code example defines a new :code:`SaveModelStrategy` " +"which customized the existing built-in :code:`FedAvg` strategy. In " +"particular, it customizes :code:`aggregate_fit` by calling :code:" +"`aggregate_fit` in the base class (:code:`FedAvg`). It then continues to " +"save returned (aggregated) weights before it returns those aggregated " +"weights to the caller (i.e., the server):" msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:47 @@ -5663,25 +5599,25 @@ msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:49 msgid "" -"Similar to the previous example but with a few extra steps, we'll show " -"how to store a PyTorch checkpoint we'll use the ``torch.save`` function. " -"Firstly, ``aggregate_fit`` returns a ``Parameters`` object that has to be" -" transformed into a list of NumPy ``ndarray``'s, then those are " -"transformed into the PyTorch ``state_dict`` following the ``OrderedDict``" -" class structure." +"Similar to the previous example but with a few extra steps, we'll show how " +"to store a PyTorch checkpoint we'll use the ``torch.save`` function. " +"Firstly, ``aggregate_fit`` returns a ``Parameters`` object that has to be " +"transformed into a list of NumPy ``ndarray``'s, then those are transformed " +"into the PyTorch ``state_dict`` following the ``OrderedDict`` class " +"structure." msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:85 msgid "" -"To load your progress, you simply append the following lines to your " -"code. Note that this will iterate over all saved checkpoints and load the" -" latest one:" +"To load your progress, you simply append the following lines to your code. " +"Note that this will iterate over all saved checkpoints and load the latest " +"one:" msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:97 msgid "" -"Return/use this object of type ``Parameters`` wherever necessary, such as" -" in the ``initial_parameters`` when defining a ``Strategy``." +"Return/use this object of type ``Parameters`` wherever necessary, such as in " +"the ``initial_parameters`` when defining a ``Strategy``." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:2 @@ -5690,10 +5626,10 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:4 msgid "" -"Flower 1.0 is here. Along with new features, Flower 1.0 provides a stable" -" foundation for future growth. Compared to Flower 0.19 (and other 0.x " -"series releases), there are a few breaking changes that make it necessary" -" to change the code of existing 0.x-series projects." +"Flower 1.0 is here. Along with new features, Flower 1.0 provides a stable " +"foundation for future growth. Compared to Flower 0.19 (and other 0.x series " +"releases), there are a few breaking changes that make it necessary to change " +"the code of existing 0.x-series projects." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:8 @@ -5703,8 +5639,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:10 msgid "" -"Here's how to update an existing installation to Flower 1.0 using either " -"pip or Poetry:" +"Here's how to update an existing installation to Flower 1.0 using either pip " +"or Poetry:" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:12 @@ -5731,13 +5667,14 @@ msgid "" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:19 -msgid "``flwr = \"^1.0.0\"`` (when using ``start_server`` and ``start_client``)" +msgid "" +"``flwr = \"^1.0.0\"`` (when using ``start_server`` and ``start_client``)" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:20 msgid "" -"``flwr = { version = \"^1.0.0\", extras = [\"simulation\"] }`` (when " -"using ``start_simulation``)" +"``flwr = { version = \"^1.0.0\", extras = [\"simulation\"] }`` (when using " +"``start_simulation``)" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:24 @@ -5768,8 +5705,7 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:34 msgid "" "Flower 1.0 (keyword arguments): " -"``start_client(server_address=\"127.0.0.1:8080\", " -"client=FlowerClient())``" +"``start_client(server_address=\"127.0.0.1:8080\", client=FlowerClient())``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:37 @@ -5807,9 +5743,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:48 msgid "" -"Flower 1.0: ``start_server(..., " -"config=flwr.server.ServerConfig(num_rounds=3, round_timeout=600.0), " -"...)``" +"Flower 1.0: ``start_server(..., config=flwr.server." +"ServerConfig(num_rounds=3, round_timeout=600.0), ...)``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:50 @@ -5821,9 +5756,9 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:51 msgid "" "Remove ``force_final_distributed_eval`` parameter from calls to " -"``start_server``. Distributed evaluation on all clients can be enabled by" -" configuring the strategy to sample all clients for evaluation after the " -"last round of training." +"``start_server``. Distributed evaluation on all clients can be enabled by " +"configuring the strategy to sample all clients for evaluation after the last " +"round of training." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:52 @@ -5840,12 +5775,12 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:57 msgid "" -"Strategy initialization: if the strategy relies on the default values for" -" ``fraction_fit`` and ``fraction_evaluate``, set ``fraction_fit`` and " +"Strategy initialization: if the strategy relies on the default values for " +"``fraction_fit`` and ``fraction_evaluate``, set ``fraction_fit`` and " "``fraction_evaluate`` manually to ``0.1``. Projects that do not manually " "create a strategy (by calling ``start_server`` or ``start_simulation`` " -"without passing a strategy instance) should now manually initialize " -"FedAvg with ``fraction_fit`` and ``fraction_evaluate`` set to ``0.1``." +"without passing a strategy instance) should now manually initialize FedAvg " +"with ``fraction_fit`` and ``fraction_evaluate`` set to ``0.1``." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:58 @@ -5877,15 +5812,14 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:67 msgid "" -"Flower 0.19: ``def evaluate(parameters: NDArrays) -> " -"Optional[Tuple[float, Dict[str, Scalar]]]:``" +"Flower 0.19: ``def evaluate(parameters: NDArrays) -> Optional[Tuple[float, " +"Dict[str, Scalar]]]:``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:68 msgid "" -"Flower 1.0: ``def evaluate(server_round: int, parameters: NDArrays, " -"config: Dict[str, Scalar]) -> Optional[Tuple[float, Dict[str, " -"Scalar]]]:``" +"Flower 1.0: ``def evaluate(server_round: int, parameters: NDArrays, config: " +"Dict[str, Scalar]) -> Optional[Tuple[float, Dict[str, Scalar]]]:``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:71 @@ -5894,11 +5828,10 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:73 msgid "" -"The type of parameter ``failures`` has changed from " -"``List[BaseException]`` to ``List[Union[Tuple[ClientProxy, FitRes], " -"BaseException]]`` (in ``aggregate_fit``) and " -"``List[Union[Tuple[ClientProxy, EvaluateRes], BaseException]]`` (in " -"``aggregate_evaluate``)" +"The type of parameter ``failures`` has changed from ``List[BaseException]`` " +"to ``List[Union[Tuple[ClientProxy, FitRes], BaseException]]`` (in " +"``aggregate_fit``) and ``List[Union[Tuple[ClientProxy, EvaluateRes], " +"BaseException]]`` (in ``aggregate_evaluate``)" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:74 @@ -5915,8 +5848,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:77 msgid "" -"Flower 1.0: ``def evaluate(self, server_round: int, parameters: " -"Parameters) -> Optional[Tuple[float, Dict[str, Scalar]]]:``" +"Flower 1.0: ``def evaluate(self, server_round: int, parameters: Parameters) -" +"> Optional[Tuple[float, Dict[str, Scalar]]]:``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:80 @@ -5932,9 +5865,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:84 msgid "" "Remove \"placeholder\" methods from subclasses of ``Client`` or " -"``NumPyClient``. If you, for example, use server-side evaluation, then " -"empty placeholder implementations of ``evaluate`` are no longer " -"necessary." +"``NumPyClient``. If you, for example, use server-side evaluation, then empty " +"placeholder implementations of ``evaluate`` are no longer necessary." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:85 @@ -5951,11 +5883,11 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:91 msgid "" -"Most official `Flower code examples " -"`_ are already updated" -" to Flower 1.0, they can serve as a reference for using the Flower 1.0 " -"API. If there are further questions, `join the Flower Slack " -"`_ and use the channel ``#questions``." +"Most official `Flower code examples `_ are already updated to Flower 1.0, they can serve as a " +"reference for using the Flower 1.0 API. If there are further questions, " +"`join the Flower Slack `_ and use the channel " +"``#questions``." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:2 @@ -5964,19 +5896,17 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:4 msgid "" -"Welcome to the migration guide for updating Flower to Flower Next! " -"Whether you're a seasoned user or just getting started, this guide will " -"help you smoothly transition your existing setup to take advantage of the" -" latest features and improvements in Flower Next, starting from version " -"1.8." +"Welcome to the migration guide for updating Flower to Flower Next! Whether " +"you're a seasoned user or just getting started, this guide will help you " +"smoothly transition your existing setup to take advantage of the latest " +"features and improvements in Flower Next, starting from version 1.8." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:9 msgid "" "This guide shows how to reuse pre-``1.8`` Flower code with minimum code " -"changes by using the *compatibility layer* in Flower Next. In another " -"guide, we will show how to run Flower Next end-to-end with pure Flower " -"Next APIs." +"changes by using the *compatibility layer* in Flower Next. In another guide, " +"we will show how to run Flower Next end-to-end with pure Flower Next APIs." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:13 @@ -5985,8 +5915,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:48 msgid "" -"Here's how to update an existing installation of Flower to Flower Next " -"with ``pip``:" +"Here's how to update an existing installation of Flower to Flower Next with " +"``pip``:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:54 @@ -5995,8 +5925,7 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:61 msgid "" -"Ensure you set the following version constraint in your " -"``requirements.txt``" +"Ensure you set the following version constraint in your ``requirements.txt``" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:71 @@ -6016,21 +5945,19 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:86 msgid "" -"Ensure you set the following version constraint in your " -"``pyproject.toml``:" +"Ensure you set the following version constraint in your ``pyproject.toml``:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:102 msgid "" "In Flower Next, the *infrastructure* and *application layers* have been " -"decoupled. Instead of starting a client in code via ``start_client()``, " -"you create a |clientapp_link|_ and start it via the command line. Instead" -" of starting a server in code via ``start_server()``, you create a " -"|serverapp_link|_ and start it via the command line. The long-running " +"decoupled. Instead of starting a client in code via ``start_client()``, you " +"create a |clientapp_link|_ and start it via the command line. Instead of " +"starting a server in code via ``start_server()``, you create a |" +"serverapp_link|_ and start it via the command line. The long-running " "components of server and client are called SuperLink and SuperNode. The " -"following non-breaking changes that require manual updates and allow you " -"to run your project both in the traditional way and in the Flower Next " -"way:" +"following non-breaking changes that require manual updates and allow you to " +"run your project both in the traditional way and in the Flower Next way:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:109 @@ -6039,8 +5966,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:110 msgid "" -"Wrap your existing client with |clientapp_link|_ instead of launching it " -"via |startclient_link|_. Here's an example:" +"Wrap your existing client with |clientapp_link|_ instead of launching it via " +"|startclient_link|_. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:132 @@ -6049,8 +5976,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:133 msgid "" -"Wrap your existing strategy with |serverapp_link|_ instead of starting " -"the server via |startserver_link|_. Here's an example:" +"Wrap your existing strategy with |serverapp_link|_ instead of starting the " +"server via |startserver_link|_. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:154 @@ -6059,23 +5986,21 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:155 msgid "" -"Run the ``SuperLink`` using |flowernext_superlink_link|_ before running, " -"in sequence, |flowernext_clientapp_link|_ (2x) and " -"|flowernext_serverapp_link|_. There is no need to execute `client.py` and" -" `server.py` as Python scripts." +"Run the ``SuperLink`` using |flowernext_superlink_link|_ before running, in " +"sequence, |flowernext_clientapp_link|_ (2x) and |flowernext_serverapp_link|" +"_. There is no need to execute `client.py` and `server.py` as Python scripts." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:158 msgid "" -"Here's an example to start the server without HTTPS (only for " -"prototyping):" +"Here's an example to start the server without HTTPS (only for prototyping):" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:174 msgid "" "Here's another example to start with HTTPS. Use the ``--certificates`` " -"command line argument to pass paths to (CA certificate, server " -"certificate, and server private key)." +"command line argument to pass paths to (CA certificate, server certificate, " +"and server private key)." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:201 @@ -6084,24 +6009,24 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:202 msgid "" -"Wrap your existing client and strategy with |clientapp_link|_ and " -"|serverapp_link|_, respectively. There is no need to use |startsim_link|_" -" anymore. Here's an example:" +"Wrap your existing client and strategy with |clientapp_link|_ and |" +"serverapp_link|_, respectively. There is no need to use |startsim_link|_ " +"anymore. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:232 msgid "" "Run |flower_simulation_link|_ in CLI and point to the ``server_app`` / " -"``client_app`` object in the code instead of executing the Python script." -" Here's an example (assuming the ``server_app`` and ``client_app`` " -"objects are in a ``sim.py`` module):" +"``client_app`` object in the code instead of executing the Python script. " +"Here's an example (assuming the ``server_app`` and ``client_app`` objects " +"are in a ``sim.py`` module):" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:249 msgid "" "Set default resources for each |clientapp_link|_ using the ``--backend-" -"config`` command line argument instead of setting the " -"``client_resources`` argument in |startsim_link|_. Here's an example:" +"config`` command line argument instead of setting the ``client_resources`` " +"argument in |startsim_link|_. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:275 @@ -6110,19 +6035,19 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:276 msgid "" -"Run |runsim_link|_ in your notebook instead of |startsim_link|_. Here's " -"an example:" +"Run |runsim_link|_ in your notebook instead of |startsim_link|_. Here's an " +"example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:319 msgid "" -"Some official `Flower code examples `_ " -"are already updated to Flower Next so they can serve as a reference for " -"using the Flower Next API. If there are further questions, `join the " -"Flower Slack `_ and use the channel " -"``#questions``. You can also `participate in Flower Discuss " -"`_ where you can find us answering questions," -" or share and learn from others about migrating to Flower Next." +"Some official `Flower code examples `_ are " +"already updated to Flower Next so they can serve as a reference for using " +"the Flower Next API. If there are further questions, `join the Flower Slack " +"`_ and use the channel ``#questions``. You " +"can also `participate in Flower Discuss `_ where " +"you can find us answering questions, or share and learn from others about " +"migrating to Flower Next." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:325 @@ -6146,16 +6071,16 @@ msgstr "" #: ../../source/how-to-use-built-in-mods.rst:4 msgid "" -"**Note: This tutorial covers experimental features. The functionality and" -" interfaces may change in future versions.**" +"**Note: This tutorial covers experimental features. The functionality and " +"interfaces may change in future versions.**" msgstr "" #: ../../source/how-to-use-built-in-mods.rst:6 msgid "" -"In this tutorial, we will learn how to utilize built-in mods to augment " -"the behavior of a ``ClientApp``. Mods (sometimes also called Modifiers) " -"allow us to perform operations before and after a task is processed in " -"the ``ClientApp``." +"In this tutorial, we will learn how to utilize built-in mods to augment the " +"behavior of a ``ClientApp``. Mods (sometimes also called Modifiers) allow us " +"to perform operations before and after a task is processed in the " +"``ClientApp``." msgstr "" #: ../../source/how-to-use-built-in-mods.rst:9 @@ -6164,9 +6089,9 @@ msgstr "" #: ../../source/how-to-use-built-in-mods.rst:11 msgid "" -"A Mod is a callable that wraps around a ``ClientApp``. It can manipulate " -"or inspect the incoming ``Message`` and the resulting outgoing " -"``Message``. The signature for a ``Mod`` is as follows:" +"A Mod is a callable that wraps around a ``ClientApp``. It can manipulate or " +"inspect the incoming ``Message`` and the resulting outgoing ``Message``. The " +"signature for a ``Mod`` is as follows:" msgstr "" #: ../../source/how-to-use-built-in-mods.rst:18 @@ -6243,16 +6168,16 @@ msgstr "" #: ../../source/how-to-use-built-in-mods.rst:82 msgid "" -"Each mod has a chance to inspect and modify the incoming ``Message`` " -"before passing it to the next mod, and likewise with the outgoing " -"``Message`` before returning it up the stack." +"Each mod has a chance to inspect and modify the incoming ``Message`` before " +"passing it to the next mod, and likewise with the outgoing ``Message`` " +"before returning it up the stack." msgstr "" #: ../../source/how-to-use-built-in-mods.rst:87 msgid "" "By following this guide, you have learned how to effectively use mods to " -"enhance your ``ClientApp``'s functionality. Remember that the order of " -"mods is crucial and affects how the input and output are processed." +"enhance your ``ClientApp``'s functionality. Remember that the order of mods " +"is crucial and affects how the input and output are processed." msgstr "" #: ../../source/how-to-use-built-in-mods.rst:89 @@ -6265,25 +6190,25 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:3 msgid "" -"This guide explains how you can utilize differential privacy in the " -"Flower framework. If you are not yet familiar with differential privacy, " -"you can refer to :doc:`explanation-differential-privacy`." +"This guide explains how you can utilize differential privacy in the Flower " +"framework. If you are not yet familiar with differential privacy, you can " +"refer to :doc:`explanation-differential-privacy`." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:7 msgid "" "Differential Privacy in Flower is in a preview phase. If you plan to use " -"these features in a production environment with sensitive data, feel free" -" contact us to discuss your requirements and to receive guidance on how " -"to best use these features." +"these features in a production environment with sensitive data, feel free " +"contact us to discuss your requirements and to receive guidance on how to " +"best use these features." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:12 msgid "" -"This approach consists of two seprate phases: clipping of the updates and" -" adding noise to the aggregated model. For the clipping phase, Flower " -"framework has made it possible to decide whether to perform clipping on " -"the server side or the client side." +"This approach consists of two seprate phases: clipping of the updates and " +"adding noise to the aggregated model. For the clipping phase, Flower " +"framework has made it possible to decide whether to perform clipping on the " +"server side or the client side." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:15 @@ -6291,16 +6216,16 @@ msgid "" "**Server-side Clipping**: This approach has the advantage of the server " "enforcing uniform clipping across all clients' updates and reducing the " "communication overhead for clipping values. However, it also has the " -"disadvantage of increasing the computational load on the server due to " -"the need to perform the clipping operation for all clients." +"disadvantage of increasing the computational load on the server due to the " +"need to perform the clipping operation for all clients." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:16 msgid "" -"**Client-side Clipping**: This approach has the advantage of reducing the" -" computational overhead on the server. However, it also has the " -"disadvantage of lacking centralized control, as the server has less " -"control over the clipping process." +"**Client-side Clipping**: This approach has the advantage of reducing the " +"computational overhead on the server. However, it also has the disadvantage " +"of lacking centralized control, as the server has less control over the " +"clipping process." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:21 @@ -6311,10 +6236,10 @@ msgstr "" msgid "" "For central DP with server-side clipping, there are two :code:`Strategy` " "classes that act as wrappers around the actual :code:`Strategy` instance " -"(for example, :code:`FedAvg`). The two wrapper classes are " -":code:`DifferentialPrivacyServerSideFixedClipping` and " -":code:`DifferentialPrivacyServerSideAdaptiveClipping` for fixed and " -"adaptive clipping." +"(for example, :code:`FedAvg`). The two wrapper classes are :code:" +"`DifferentialPrivacyServerSideFixedClipping` and :code:" +"`DifferentialPrivacyServerSideAdaptiveClipping` for fixed and adaptive " +"clipping." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:-1 @@ -6323,11 +6248,10 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:31 msgid "" -"The code sample below enables the :code:`FedAvg` strategy to use server-" -"side fixed clipping using the " -":code:`DifferentialPrivacyServerSideFixedClipping` wrapper class. The " -"same approach can be used with " -":code:`DifferentialPrivacyServerSideAdaptiveClipping` by adjusting the " +"The code sample below enables the :code:`FedAvg` strategy to use server-side " +"fixed clipping using the :code:`DifferentialPrivacyServerSideFixedClipping` " +"wrapper class. The same approach can be used with :code:" +"`DifferentialPrivacyServerSideAdaptiveClipping` by adjusting the " "corresponding input parameters." msgstr "" @@ -6338,12 +6262,12 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:53 msgid "" "For central DP with client-side clipping, the server sends the clipping " -"value to selected clients on each round. Clients can use existing Flower " -":code:`Mods` to perform the clipping. Two mods are available for fixed " -"and adaptive client-side clipping: :code:`fixedclipping_mod` and " -":code:`adaptiveclipping_mod` with corresponding server-side wrappers " -":code:`DifferentialPrivacyClientSideFixedClipping` and " -":code:`DifferentialPrivacyClientSideAdaptiveClipping`." +"value to selected clients on each round. Clients can use existing Flower :" +"code:`Mods` to perform the clipping. Two mods are available for fixed and " +"adaptive client-side clipping: :code:`fixedclipping_mod` and :code:" +"`adaptiveclipping_mod` with corresponding server-side wrappers :code:" +"`DifferentialPrivacyClientSideFixedClipping` and :code:" +"`DifferentialPrivacyClientSideAdaptiveClipping`." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:-1 @@ -6353,24 +6277,24 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:63 msgid "" "The code sample below enables the :code:`FedAvg` strategy to use " -"differential privacy with client-side fixed clipping using both the " -":code:`DifferentialPrivacyClientSideFixedClipping` wrapper class and, on " -"the client, :code:`fixedclipping_mod`:" +"differential privacy with client-side fixed clipping using both the :code:" +"`DifferentialPrivacyClientSideFixedClipping` wrapper class and, on the " +"client, :code:`fixedclipping_mod`:" msgstr "" #: ../../source/how-to-use-differential-privacy.rst:80 msgid "" -"In addition to the server-side strategy wrapper, the :code:`ClientApp` " -"needs to configure the matching :code:`fixedclipping_mod` to perform the " -"client-side clipping:" +"In addition to the server-side strategy wrapper, the :code:`ClientApp` needs " +"to configure the matching :code:`fixedclipping_mod` to perform the client-" +"side clipping:" msgstr "" #: ../../source/how-to-use-differential-privacy.rst:97 msgid "" -"To utilize local differential privacy (DP) and add noise to the client " -"model parameters before transmitting them to the server in Flower, you " -"can use the `LocalDpMod`. The following hyperparameters need to be set: " -"clipping norm value, sensitivity, epsilon, and delta." +"To utilize local differential privacy (DP) and add noise to the client model " +"parameters before transmitting them to the server in Flower, you can use the " +"`LocalDpMod`. The following hyperparameters need to be set: clipping norm " +"value, sensitivity, epsilon, and delta." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:-1 @@ -6383,10 +6307,9 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:122 msgid "" -"Please note that the order of mods, especially those that modify " -"parameters, is important when using multiple modifiers. Typically, " -"differential privacy (DP) modifiers should be the last to operate on " -"parameters." +"Please note that the order of mods, especially those that modify parameters, " +"is important when using multiple modifiers. Typically, differential privacy " +"(DP) modifiers should be the last to operate on parameters." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:125 @@ -6395,13 +6318,12 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:126 msgid "" -"For ensuring data instance-level privacy during local model training on " -"the client side, consider leveraging privacy engines such as Opacus and " -"TensorFlow Privacy. For examples of using Flower with these engines, " -"please refer to the Flower examples directory (`Opacus " -"`_, `Tensorflow" -" Privacy `_)." +"For ensuring data instance-level privacy during local model training on the " +"client side, consider leveraging privacy engines such as Opacus and " +"TensorFlow Privacy. For examples of using Flower with these engines, please " +"refer to the Flower examples directory (`Opacus `_, `Tensorflow Privacy `_)." msgstr "" #: ../../source/how-to-use-strategies.rst:2 @@ -6410,15 +6332,15 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:4 msgid "" -"Flower allows full customization of the learning process through the " -":code:`Strategy` abstraction. A number of built-in strategies are " -"provided in the core framework." +"Flower allows full customization of the learning process through the :code:" +"`Strategy` abstraction. A number of built-in strategies are provided in the " +"core framework." msgstr "" #: ../../source/how-to-use-strategies.rst:6 msgid "" -"There are three ways to customize the way Flower orchestrates the " -"learning process on the server side:" +"There are three ways to customize the way Flower orchestrates the learning " +"process on the server side:" msgstr "" #: ../../source/how-to-use-strategies.rst:8 @@ -6441,15 +6363,15 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:16 msgid "" -"Flower comes with a number of popular federated learning strategies " -"built-in. A built-in strategy can be instantiated as follows:" +"Flower comes with a number of popular federated learning strategies built-" +"in. A built-in strategy can be instantiated as follows:" msgstr "" #: ../../source/how-to-use-strategies.rst:25 msgid "" -"This creates a strategy with all parameters left at their default values " -"and passes it to the :code:`start_server` function. It is usually " -"recommended to adjust a few parameters during instantiation:" +"This creates a strategy with all parameters left at their default values and " +"passes it to the :code:`start_server` function. It is usually recommended to " +"adjust a few parameters during instantiation:" msgstr "" #: ../../source/how-to-use-strategies.rst:42 @@ -6466,28 +6388,27 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:47 msgid "" "The server can pass new configuration values to the client each round by " -"providing a function to :code:`on_fit_config_fn`. The provided function " -"will be called by the strategy and must return a dictionary of " -"configuration key values pairs that will be sent to the client. It must " -"return a dictionary of arbitrary configuration values :code:`client.fit`" -" and :code:`client.evaluate` functions during each round of federated " -"learning." +"providing a function to :code:`on_fit_config_fn`. The provided function will " +"be called by the strategy and must return a dictionary of configuration key " +"values pairs that will be sent to the client. It must return a dictionary of " +"arbitrary configuration values :code:`client.fit` and :code:`client." +"evaluate` functions during each round of federated learning." msgstr "" #: ../../source/how-to-use-strategies.rst:75 msgid "" "The :code:`on_fit_config_fn` can be used to pass arbitrary configuration " "values from server to client, and poetentially change these values each " -"round, for example, to adjust the learning rate. The client will receive " -"the dictionary returned by the :code:`on_fit_config_fn` in its own " -":code:`client.fit()` function." +"round, for example, to adjust the learning rate. The client will receive the " +"dictionary returned by the :code:`on_fit_config_fn` in its own :code:`client." +"fit()` function." msgstr "" #: ../../source/how-to-use-strategies.rst:78 msgid "" -"Similar to :code:`on_fit_config_fn`, there is also " -":code:`on_evaluate_config_fn` to customize the configuration sent to " -":code:`client.evaluate()`" +"Similar to :code:`on_fit_config_fn`, there is also :code:" +"`on_evaluate_config_fn` to customize the configuration sent to :code:`client." +"evaluate()`" msgstr "" #: ../../source/how-to-use-strategies.rst:81 @@ -6496,15 +6417,15 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:83 msgid "" -"Server-side evaluation can be enabled by passing an evaluation function " -"to :code:`evaluate_fn`." +"Server-side evaluation can be enabled by passing an evaluation function to :" +"code:`evaluate_fn`." msgstr "" #: ../../source/how-to-use-strategies.rst:89 msgid "" -"Writing a fully custom strategy is a bit more involved, but it provides " -"the most flexibility. Read the `Implementing Strategies `_ guide to learn more." +"Writing a fully custom strategy is a bit more involved, but it provides the " +"most flexibility. Read the `Implementing Strategies `_ guide to learn more." msgstr "" #: ../../source/index.rst:34 @@ -6590,8 +6511,8 @@ msgstr "" msgid "" "The user guide is targeted at researchers and developers who want to use " "Flower to bring existing machine learning workloads into a federated " -"setting. One of Flower's design goals was to make this simple. Read on to" -" learn more." +"setting. One of Flower's design goals was to make this simple. Read on to " +"learn more." msgstr "" #: ../../source/index.rst:30 @@ -6600,21 +6521,20 @@ msgstr "" #: ../../source/index.rst:32 msgid "" -"A learning-oriented series of federated learning tutorials, the best " -"place to start." +"A learning-oriented series of federated learning tutorials, the best place " +"to start." msgstr "" #: ../../source/index.rst:61 msgid "" -"QUICKSTART TUTORIALS: :doc:`PyTorch ` | " -":doc:`TensorFlow ` | :doc:`🤗 Transformers" -" ` | :doc:`JAX ` | :doc:`Pandas ` | :doc:`fastai " -"` | :doc:`PyTorch Lightning ` | :doc:`scikit-learn ` | :doc:`XGBoost ` | " -":doc:`Android ` | :doc:`iOS `" +"QUICKSTART TUTORIALS: :doc:`PyTorch ` | :doc:" +"`TensorFlow ` | :doc:`🤗 Transformers " +"` | :doc:`JAX ` | :" +"doc:`Pandas ` | :doc:`fastai ` | :doc:`PyTorch Lightning ` | :doc:`scikit-learn ` | :doc:" +"`XGBoost ` | :doc:`Android ` | :doc:`iOS `" msgstr "" #: ../../source/index.rst:63 @@ -6627,8 +6547,8 @@ msgstr "" #: ../../source/index.rst:76 msgid "" -"Problem-oriented how-to guides show step-by-step how to achieve a " -"specific goal." +"Problem-oriented how-to guides show step-by-step how to achieve a specific " +"goal." msgstr "" #: ../../source/index.rst:110 @@ -6659,8 +6579,8 @@ msgstr "" #: ../../source/index.rst:150 msgid "" -"The Flower community welcomes contributions. The following docs are " -"intended to help along the way." +"The Flower community welcomes contributions. The following docs are intended " +"to help along the way." msgstr "" #: ../../source/ref-api-cli.rst:2 @@ -6767,8 +6687,8 @@ msgstr "" #: ../../source/ref-api/flwr.client.rst:25::1 msgid "" -":py:obj:`start_numpy_client `\\ \\(\\*\\," -" server\\_address\\, client\\)" +":py:obj:`start_numpy_client `\\ \\(\\*\\, " +"server\\_address\\, client\\)" msgstr "" #: ../../source/ref-api/flwr.client.rst:25::1 @@ -6795,8 +6715,7 @@ msgstr "" #: ../../source/ref-api/flwr.client.rst:34::1 msgid "" -":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, " -"mods\\]\\)" +":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, mods\\]\\)" msgstr "" #: ../../source/ref-api/flwr.client.rst:34::1 @@ -6919,7 +6838,8 @@ msgid "Get the run context from this client." msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 -msgid ":py:obj:`get_parameters `\\ \\(ins\\)" +msgid "" +":py:obj:`get_parameters `\\ \\(ins\\)" msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 @@ -6930,7 +6850,8 @@ msgid "Return the current local model parameters." msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 -msgid ":py:obj:`get_properties `\\ \\(ins\\)" +msgid "" +":py:obj:`get_properties `\\ \\(ins\\)" msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 @@ -7044,9 +6965,9 @@ msgstr "" #: flwr.client.client.Client.evaluate:3 of msgid "" -"The evaluation instructions containing (global) model parameters received" -" from the server and a dictionary of configuration values used to " -"customize the local evaluation process." +"The evaluation instructions containing (global) model parameters received " +"from the server and a dictionary of configuration values used to customize " +"the local evaluation process." msgstr "" #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit @@ -7113,15 +7034,15 @@ msgstr "" #: flwr.client.client.Client.fit:3 of msgid "" -"The training instructions containing (global) model parameters received " -"from the server and a dictionary of configuration values used to " -"customize the local training process." +"The training instructions containing (global) model parameters received from " +"the server and a dictionary of configuration values used to customize the " +"local training process." msgstr "" #: flwr.client.client.Client.fit:8 of msgid "" -"The training result containing updated parameters and other details such " -"as the number of local training examples used for training." +"The training result containing updated parameters and other details such as " +"the number of local training examples used for training." msgstr "" #: flwr.client.client.Client.get_parameters:3 of @@ -7190,15 +7111,15 @@ msgstr "" #: flwr.client.client_app.ClientApp:16 of msgid "" -"If the above code is in a Python module called `client`, it can be " -"started as follows:" +"If the above code is in a Python module called `client`, it can be started " +"as follows:" msgstr "" #: flwr.client.client_app.ClientApp:21 of msgid "" -"In this `client:app` example, `client` refers to the Python module " -"`client.py` in which the previous code lives in and `app` refers to the " -"global attribute `app` that points to an object of type `ClientApp`." +"In this `client:app` example, `client` refers to the Python module `client." +"py` in which the previous code lives in and `app` refers to the global " +"attribute `app` that points to an object of type `ClientApp`." msgstr "" #: flwr.client.client_app.ClientApp.evaluate:1::1 of @@ -7239,7 +7160,8 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 -msgid ":py:obj:`fit `\\ \\(parameters\\, config\\)" +msgid "" +":py:obj:`fit `\\ \\(parameters\\, config\\)" msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 @@ -7270,8 +7192,7 @@ msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid "" -":py:obj:`set_context `\\ " -"\\(context\\)" +":py:obj:`set_context `\\ \\(context\\)" msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 @@ -7299,10 +7220,10 @@ msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:5 of msgid "" -"Configuration parameters which allow the server to influence evaluation " -"on the client. It can be used to communicate arbitrary values from the " -"server to the client, for example, to influence the number of examples " -"used for evaluation." +"Configuration parameters which allow the server to influence evaluation on " +"the client. It can be used to communicate arbitrary values from the server " +"to the client, for example, to influence the number of examples used for " +"evaluation." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:11 of @@ -7310,14 +7231,13 @@ msgid "" "* **loss** (*float*) -- The evaluation loss of the model on the local " "dataset. * **num_examples** (*int*) -- The number of examples used for " "evaluation. * **metrics** (*Dict[str, Scalar]*) -- A dictionary mapping " -"arbitrary string keys to values of type bool, bytes, float, int, or " -"str. It can be used to communicate arbitrary values back to the server." +"arbitrary string keys to values of type bool, bytes, float, int, or str. " +"It can be used to communicate arbitrary values back to the server." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:11 of msgid "" -"**loss** (*float*) -- The evaluation loss of the model on the local " -"dataset." +"**loss** (*float*) -- The evaluation loss of the model on the local dataset." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:12 of @@ -7327,33 +7247,32 @@ msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:13 #: flwr.client.numpy_client.NumPyClient.fit:13 of msgid "" -"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary " -"string keys to values of type bool, bytes, float, int, or str. It can be " -"used to communicate arbitrary values back to the server." +"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary string " +"keys to values of type bool, bytes, float, int, or str. It can be used to " +"communicate arbitrary values back to the server." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:19 of msgid "" -"The previous return type format (int, float, float) and the extended " -"format (int, float, float, Dict[str, Scalar]) have been deprecated and " -"removed since Flower 0.19." +"The previous return type format (int, float, float) and the extended format " +"(int, float, float, Dict[str, Scalar]) have been deprecated and removed " +"since Flower 0.19." msgstr "" #: flwr.client.numpy_client.NumPyClient.fit:5 of msgid "" -"Configuration parameters which allow the server to influence training on " -"the client. It can be used to communicate arbitrary values from the " -"server to the client, for example, to set the number of (local) training " -"epochs." +"Configuration parameters which allow the server to influence training on the " +"client. It can be used to communicate arbitrary values from the server to " +"the client, for example, to set the number of (local) training epochs." msgstr "" #: flwr.client.numpy_client.NumPyClient.fit:11 of msgid "" "* **parameters** (*NDArrays*) -- The locally updated model parameters. * " "**num_examples** (*int*) -- The number of examples used for training. * " -"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary " -"string keys to values of type bool, bytes, float, int, or str. It can " -"be used to communicate arbitrary values back to the server." +"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary string " +"keys to values of type bool, bytes, float, int, or str. It can be used to " +"communicate arbitrary values back to the server." msgstr "" #: flwr.client.numpy_client.NumPyClient.fit:11 of @@ -7366,26 +7285,25 @@ msgstr "" #: flwr.client.numpy_client.NumPyClient.get_parameters:3 of msgid "" -"Configuration parameters requested by the server. This can be used to " -"tell the client which parameters are needed along with some Scalar " -"attributes." +"Configuration parameters requested by the server. This can be used to tell " +"the client which parameters are needed along with some Scalar attributes." msgstr "" #: flwr.client.numpy_client.NumPyClient.get_parameters:8 of -msgid "**parameters** -- The local model parameters as a list of NumPy ndarrays." +msgid "" +"**parameters** -- The local model parameters as a list of NumPy ndarrays." msgstr "" #: flwr.client.numpy_client.NumPyClient.get_properties:3 of msgid "" -"Configuration parameters requested by the server. This can be used to " -"tell the client which properties are needed along with some Scalar " -"attributes." +"Configuration parameters requested by the server. This can be used to tell " +"the client which properties are needed along with some Scalar attributes." msgstr "" #: flwr.client.numpy_client.NumPyClient.get_properties:8 of msgid "" -"**properties** -- A dictionary mapping arbitrary string keys to values of" -" type bool, bytes, float, int, or str. It can be used to communicate " +"**properties** -- A dictionary mapping arbitrary string keys to values of " +"type bool, bytes, float, int, or str. It can be used to communicate " "arbitrary property values back to the server." msgstr "" @@ -7404,8 +7322,7 @@ msgstr "" #: flwr.client.app.start_client:3 flwr.client.app.start_numpy_client:9 of msgid "" "The IPv4 or IPv6 address of the server. If the Flower server runs on the " -"same machine on port 8080, then `server_address` would be " -"`\"[::]:8080\"`." +"same machine on port 8080, then `server_address` would be `\"[::]:8080\"`." msgstr "" #: flwr.client.app.start_client:7 of @@ -7414,52 +7331,52 @@ msgstr "" #: flwr.client.app.start_client:9 of msgid "" -"An implementation of the abstract base class `flwr.client.Client` " -"(default: None)" +"An implementation of the abstract base class `flwr.client.Client` (default: " +"None)" msgstr "" #: flwr.client.app.start_client:12 flwr.client.app.start_numpy_client:15 of msgid "" -"The maximum length of gRPC messages that can be exchanged with the Flower" -" server. The default should be sufficient for most models. Users who " -"train very large models might need to increase this value. Note that the " -"Flower server needs to be started with the same value (see " -"`flwr.server.start_server`), otherwise it will not know about the " -"increased limit and block larger messages." +"The maximum length of gRPC messages that can be exchanged with the Flower " +"server. The default should be sufficient for most models. Users who train " +"very large models might need to increase this value. Note that the Flower " +"server needs to be started with the same value (see `flwr.server." +"start_server`), otherwise it will not know about the increased limit and " +"block larger messages." msgstr "" #: flwr.client.app.start_client:19 flwr.client.app.start_numpy_client:22 of msgid "" "The PEM-encoded root certificates as a byte string or a path string. If " -"provided, a secure connection using the certificates will be established " -"to an SSL-enabled Flower server." +"provided, a secure connection using the certificates will be established to " +"an SSL-enabled Flower server." msgstr "" #: flwr.client.app.start_client:23 flwr.client.app.start_numpy_client:26 of msgid "" -"Starts an insecure gRPC connection when True. Enables HTTPS connection " -"when False, using system certificates if `root_certificates` is None." +"Starts an insecure gRPC connection when True. Enables HTTPS connection when " +"False, using system certificates if `root_certificates` is None." msgstr "" #: flwr.client.app.start_client:26 flwr.client.app.start_numpy_client:29 of msgid "" "Configure the transport layer. Allowed values: - 'grpc-bidi': gRPC, " -"bidirectional streaming - 'grpc-rere': gRPC, request-response " -"(experimental) - 'rest': HTTP (experimental)" +"bidirectional streaming - 'grpc-rere': gRPC, request-response (experimental) " +"- 'rest': HTTP (experimental)" msgstr "" #: flwr.client.app.start_client:31 of msgid "" "The maximum number of times the client will try to connect to the server " -"before giving up in case of a connection error. If set to None, there is " -"no limit to the number of tries." +"before giving up in case of a connection error. If set to None, there is no " +"limit to the number of tries." msgstr "" #: flwr.client.app.start_client:35 of msgid "" -"The maximum duration before the client stops trying to connect to the " -"server in case of connection error. If set to None, there is no limit to " -"the total time." +"The maximum duration before the client stops trying to connect to the server " +"in case of connection error. If set to None, there is no limit to the total " +"time." msgstr "" #: flwr.client.app.start_client:42 flwr.client.app.start_numpy_client:37 of @@ -7480,10 +7397,9 @@ msgstr "" #: flwr.client.app.start_numpy_client:5 of msgid "" -"This function is deprecated since 1.7.0. Use " -":code:`flwr.client.start_client` instead and first convert your " -":code:`NumPyClient` to type :code:`flwr.client.Client` by executing its " -":code:`to_client()` method." +"This function is deprecated since 1.7.0. Use :code:`flwr.client." +"start_client` instead and first convert your :code:`NumPyClient` to type :" +"code:`flwr.client.Client` by executing its :code:`to_client()` method." msgstr "" #: flwr.client.app.start_numpy_client:13 of @@ -7495,7 +7411,8 @@ msgid "common" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 -msgid ":py:obj:`array_from_numpy `\\ \\(ndarray\\)" +msgid "" +":py:obj:`array_from_numpy `\\ \\(ndarray\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 @@ -7504,7 +7421,8 @@ msgid "Create Array from NumPy ndarray." msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 -msgid ":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" +msgid "" +":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 @@ -7546,7 +7464,8 @@ msgid "Log 'msg % args' with the integer severity 'level'." msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 -msgid ":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" +msgid "" +":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 @@ -7590,8 +7509,7 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`Array `\\ \\(dtype\\, shape\\, stype\\, " -"data\\)" +":py:obj:`Array `\\ \\(dtype\\, shape\\, stype\\, data\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7650,8 +7568,7 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`EvaluateIns `\\ \\(parameters\\, " -"config\\)" +":py:obj:`EvaluateIns `\\ \\(parameters\\, config\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7709,7 +7626,8 @@ msgid "A dataclass that stores information about an error that occurred." msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 -msgid ":py:obj:`GetParametersIns `\\ \\(config\\)" +msgid "" +":py:obj:`GetParametersIns `\\ \\(config\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7729,7 +7647,8 @@ msgid "Response when asked to return parameters." msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 -msgid ":py:obj:`GetPropertiesIns `\\ \\(config\\)" +msgid "" +":py:obj:`GetPropertiesIns `\\ \\(config\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7779,8 +7698,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`Metadata `\\ \\(run\\_id\\, " -"message\\_id\\, src\\_node\\_id\\, ...\\)" +":py:obj:`Metadata `\\ \\(run\\_id\\, message\\_id\\, " +"src\\_node\\_id\\, ...\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7805,8 +7724,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -"alias of :py:class:`~numpy.ndarray`\\ [:py:obj:`~typing.Any`, " -":py:class:`~numpy.dtype`\\ [:py:obj:`~typing.Any`]]" +"alias of :py:class:`~numpy.ndarray`\\ [:py:obj:`~typing.Any`, :py:class:" +"`~numpy.dtype`\\ [:py:obj:`~typing.Any`]]" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7883,21 +7802,21 @@ msgstr "" #: flwr.common.record.parametersrecord.Array:6 of msgid "" -"A string representing the data type of the serialised object (e.g. " -"`np.float32`)" +"A string representing the data type of the serialised object (e.g. `np." +"float32`)" msgstr "" #: flwr.common.record.parametersrecord.Array:8 of msgid "" -"A list representing the shape of the unserialized array-like object. This" -" is used to deserialize the data (depending on the serialization method) " -"or simply as a metadata field." +"A list representing the shape of the unserialized array-like object. This is " +"used to deserialize the data (depending on the serialization method) or " +"simply as a metadata field." msgstr "" #: flwr.common.record.parametersrecord.Array:12 of msgid "" -"A string indicating the type of serialisation mechanism used to generate " -"the bytes in `data` from an array-like or tensor-like object." +"A string indicating the type of serialisation mechanism used to generate the " +"bytes in `data` from an array-like or tensor-like object." msgstr "" #: flwr.common.record.parametersrecord.Array:15 of @@ -7943,14 +7862,12 @@ msgstr "" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid "" -":py:obj:`get_parameters_res " -"`\\" +":py:obj:`get_parameters_res `\\" msgstr "" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid "" -":py:obj:`get_properties_res " -"`\\" +":py:obj:`get_properties_res `\\" msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:2 @@ -7967,14 +7884,14 @@ msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`GET_PROPERTIES_NOT_IMPLEMENTED " -"`\\" +":py:obj:`GET_PROPERTIES_NOT_IMPLEMENTED `\\" msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`GET_PARAMETERS_NOT_IMPLEMENTED " -"`\\" +":py:obj:`GET_PARAMETERS_NOT_IMPLEMENTED `\\" msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 @@ -7983,8 +7900,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`EVALUATE_NOT_IMPLEMENTED " -"`\\" +":py:obj:`EVALUATE_NOT_IMPLEMENTED `\\" msgstr "" #: ../../source/ref-api/flwr.common.ConfigsRecord.rst:2 @@ -7993,12 +7910,12 @@ msgstr "" #: flwr.common.record.configsrecord.ConfigsRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " -"[:py:class:`str`, :py:class:`int` | :py:class:`float` | :py:class:`str` |" -" :py:class:`bytes` | :py:class:`bool` | :py:class:`~typing.List`\\ " -"[:py:class:`int`] | :py:class:`~typing.List`\\ [:py:class:`float`] | " -":py:class:`~typing.List`\\ [:py:class:`str`] | :py:class:`~typing.List`\\" -" [:py:class:`bytes`] | :py:class:`~typing.List`\\ [:py:class:`bool`]]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" +"`str`, :py:class:`int` | :py:class:`float` | :py:class:`str` | :py:class:" +"`bytes` | :py:class:`bool` | :py:class:`~typing.List`\\ [:py:class:`int`] | :" +"py:class:`~typing.List`\\ [:py:class:`float`] | :py:class:`~typing.List`\\ [:" +"py:class:`str`] | :py:class:`~typing.List`\\ [:py:class:`bytes`] | :py:class:" +"`~typing.List`\\ [:py:class:`bool`]]" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -8044,7 +7961,8 @@ msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 #: flwr.common.record.typeddict.TypedDict.pop:1 of -msgid "If key is not found, d is returned if given, otherwise KeyError is raised." +msgid "" +"If key is not found, d is returned if given, otherwise KeyError is raised." msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -8072,12 +7990,11 @@ msgstr "" #: flwr.common.context.Context:3 of msgid "" -"Holds records added by the entity in a given run and that will stay " -"local. This means that the data it holds will never leave the system it's" -" running from. This can be used as an intermediate storage or scratchpad " -"when executing mods. It can also be used as a memory to access at " -"different points during the lifecycle of this entity (e.g. across " -"multiple rounds)" +"Holds records added by the entity in a given run and that will stay local. " +"This means that the data it holds will never leave the system it's running " +"from. This can be used as an intermediate storage or scratchpad when " +"executing mods. It can also be used as a memory to access at different " +"points during the lifecycle of this entity (e.g. across multiple rounds)" msgstr "" #: ../../source/ref-api/flwr.common.Context.rst:28::1 @@ -8184,21 +8101,19 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`split `\\ \\(\\[sep\\, " -"maxsplit\\]\\)" +":py:obj:`split `\\ \\(\\[sep\\, maxsplit\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rsplit:1 flwr.common.EventType.split:1 of msgid "" -"Return a list of the substrings in the string, using sep as the separator" -" string." +"Return a list of the substrings in the string, using sep as the separator " +"string." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rsplit `\\ \\(\\[sep\\, " -"maxsplit\\]\\)" +":py:obj:`rsplit `\\ \\(\\[sep\\, maxsplit\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8256,14 +8171,13 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the number of non-overlapping occurrences of substring sub in " -"string S[start:end]." +"Return the number of non-overlapping occurrences of substring sub in string " +"S[start:end]." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`expandtabs `\\ " -"\\(\\[tabsize\\]\\)" +":py:obj:`expandtabs `\\ \\(\\[tabsize\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8279,12 +8193,13 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the lowest index in S where substring sub is found, such that sub " -"is contained within S[start:end]." +"Return the lowest index in S where substring sub is found, such that sub is " +"contained within S[start:end]." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid ":py:obj:`partition `\\ \\(sep\\, \\/\\)" +msgid "" +":py:obj:`partition `\\ \\(sep\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8300,8 +8215,7 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`ljust `\\ \\(width\\[\\, " -"fillchar\\]\\)" +":py:obj:`ljust `\\ \\(width\\[\\, fillchar\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8335,20 +8249,19 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the highest index in S where substring sub is found, such that sub" -" is contained within S[start:end]." +"Return the highest index in S where substring sub is found, such that sub is " +"contained within S[start:end]." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rindex `\\ \\(sub\\[\\, " -"start\\[\\, end\\]\\]\\)" +":py:obj:`rindex `\\ \\(sub\\[\\, start\\[\\, " +"end\\]\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rjust `\\ \\(width\\[\\, " -"fillchar\\]\\)" +":py:obj:`rjust `\\ \\(width\\[\\, fillchar\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8366,7 +8279,8 @@ msgid "Return a copy of the string with trailing whitespace removed." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid ":py:obj:`rpartition `\\ \\(sep\\, \\/\\)" +msgid "" +":py:obj:`rpartition `\\ \\(sep\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8386,7 +8300,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.strip:1 of -msgid "Return a copy of the string with leading and trailing whitespace removed." +msgid "" +"Return a copy of the string with leading and trailing whitespace removed." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8401,7 +8316,8 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid ":py:obj:`translate `\\ \\(table\\, \\/\\)" +msgid "" +":py:obj:`translate `\\ \\(table\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8420,8 +8336,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`startswith `\\ \\(prefix\\[\\," -" start\\[\\, end\\]\\]\\)" +":py:obj:`startswith `\\ \\(prefix\\[\\, " +"start\\[\\, end\\]\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8440,8 +8356,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`removeprefix `\\ " -"\\(prefix\\, \\/\\)" +":py:obj:`removeprefix `\\ \\(prefix\\, " +"\\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8451,8 +8367,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`removesuffix `\\ " -"\\(suffix\\, \\/\\)" +":py:obj:`removesuffix `\\ \\(suffix\\, " +"\\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8556,7 +8472,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isidentifier:1 of -msgid "Return True if the string is a valid Python identifier, False otherwise." +msgid "" +"Return True if the string is a valid Python identifier, False otherwise." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8575,8 +8492,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.zfill:1 of msgid "" -"Pad a numeric string with zeros on the left, to fill a field of the given" -" width." +"Pad a numeric string with zeros on the left, to fill a field of the given " +"width." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8586,7 +8503,8 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid "Return a formatted version of S, using substitutions from args and kwargs." +msgid "" +"Return a formatted version of S, using substitutions from args and kwargs." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8611,67 +8529,65 @@ msgid ":py:obj:`PING `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_CLIENT_ENTER `\\" +msgid "" +":py:obj:`START_CLIENT_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_CLIENT_LEAVE `\\" +msgid "" +":py:obj:`START_CLIENT_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_SERVER_ENTER `\\" +msgid "" +":py:obj:`START_SERVER_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_SERVER_LEAVE `\\" +msgid "" +":py:obj:`START_SERVER_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_DRIVER_API_ENTER " -"`\\" +":py:obj:`RUN_DRIVER_API_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_DRIVER_API_LEAVE " -"`\\" +":py:obj:`RUN_DRIVER_API_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_FLEET_API_ENTER " -"`\\" +":py:obj:`RUN_FLEET_API_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_FLEET_API_LEAVE " -"`\\" +":py:obj:`RUN_FLEET_API_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERLINK_ENTER " -"`\\" +":py:obj:`RUN_SUPERLINK_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERLINK_LEAVE " -"`\\" +":py:obj:`RUN_SUPERLINK_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_SIMULATION_ENTER " -"`\\" +":py:obj:`START_SIMULATION_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_SIMULATION_LEAVE " -"`\\" +":py:obj:`START_SIMULATION_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of @@ -8683,47 +8599,43 @@ msgid ":py:obj:`DRIVER_DISCONNECT `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_DRIVER_ENTER `\\" +msgid "" +":py:obj:`START_DRIVER_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_DRIVER_LEAVE `\\" +msgid "" +":py:obj:`START_DRIVER_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_CLIENT_APP_ENTER " -"`\\" +":py:obj:`RUN_CLIENT_APP_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_CLIENT_APP_LEAVE " -"`\\" +":py:obj:`RUN_CLIENT_APP_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SERVER_APP_ENTER " -"`\\" +":py:obj:`RUN_SERVER_APP_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SERVER_APP_LEAVE " -"`\\" +":py:obj:`RUN_SERVER_APP_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERNODE_ENTER " -"`\\" +":py:obj:`RUN_SUPERNODE_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERNODE_LEAVE " -"`\\" +":py:obj:`RUN_SUPERNODE_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:3 of @@ -8734,14 +8646,15 @@ msgstr "" #: flwr.common.EventType.center:3 flwr.common.EventType.ljust:3 #: flwr.common.EventType.rjust:3 of -msgid "Padding is done using the specified fill character (default is a space)." +msgid "" +"Padding is done using the specified fill character (default is a space)." msgstr "" #: flwr.common.EventType.count:1 of msgid "" -"Return the number of non-overlapping occurrences of substring sub in " -"string S[start:end]. Optional arguments start and end are interpreted as" -" in slice notation." +"Return the number of non-overlapping occurrences of substring sub in string " +"S[start:end]. Optional arguments start and end are interpreted as in slice " +"notation." msgstr "" #: flwr.common.EventType.encode:3 of @@ -8760,17 +8673,16 @@ msgstr "" msgid "" "The error handling scheme to use for encoding errors. The default is " "'strict' meaning that encoding errors raise a UnicodeEncodeError. Other " -"possible values are 'ignore', 'replace' and 'xmlcharrefreplace' as well " -"as any other name registered with codecs.register_error that can handle " +"possible values are 'ignore', 'replace' and 'xmlcharrefreplace' as well as " +"any other name registered with codecs.register_error that can handle " "UnicodeEncodeErrors." msgstr "" #: flwr.common.EventType.endswith:1 of msgid "" "Return True if S ends with the specified suffix, False otherwise. With " -"optional start, test S beginning at that position. With optional end, " -"stop comparing S at that position. suffix can also be a tuple of strings " -"to try." +"optional start, test S beginning at that position. With optional end, stop " +"comparing S at that position. suffix can also be a tuple of strings to try." msgstr "" #: flwr.common.EventType.expandtabs:3 of @@ -8779,8 +8691,8 @@ msgstr "" #: flwr.common.EventType.find:1 flwr.common.EventType.index:1 of msgid "" -"Return the lowest index in S where substring sub is found, such that sub " -"is contained within S[start:end]. Optional arguments start and end are " +"Return the lowest index in S where substring sub is found, such that sub is " +"contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" @@ -8790,8 +8702,8 @@ msgstr "" #: flwr.common.EventType.format:1 of msgid "" -"Return a formatted version of S, using substitutions from args and " -"kwargs. The substitutions are identified by braces ('{' and '}')." +"Return a formatted version of S, using substitutions from args and kwargs. " +"The substitutions are identified by braces ('{' and '}')." msgstr "" #: flwr.common.EventType.format_map:1 of @@ -8806,80 +8718,80 @@ msgstr "" #: flwr.common.EventType.isalnum:3 of msgid "" -"A string is alpha-numeric if all characters in the string are alpha-" -"numeric and there is at least one character in the string." +"A string is alpha-numeric if all characters in the string are alpha-numeric " +"and there is at least one character in the string." msgstr "" #: flwr.common.EventType.isalpha:3 of msgid "" -"A string is alphabetic if all characters in the string are alphabetic and" -" there is at least one character in the string." +"A string is alphabetic if all characters in the string are alphabetic and " +"there is at least one character in the string." msgstr "" #: flwr.common.EventType.isascii:3 of msgid "" -"ASCII characters have code points in the range U+0000-U+007F. Empty " -"string is ASCII too." +"ASCII characters have code points in the range U+0000-U+007F. Empty string " +"is ASCII too." msgstr "" #: flwr.common.EventType.isdecimal:3 of msgid "" -"A string is a decimal string if all characters in the string are decimal " -"and there is at least one character in the string." +"A string is a decimal string if all characters in the string are decimal and " +"there is at least one character in the string." msgstr "" #: flwr.common.EventType.isdigit:3 of msgid "" -"A string is a digit string if all characters in the string are digits and" -" there is at least one character in the string." +"A string is a digit string if all characters in the string are digits and " +"there is at least one character in the string." msgstr "" #: flwr.common.EventType.isidentifier:3 of msgid "" -"Call keyword.iskeyword(s) to test whether string s is a reserved " -"identifier, such as \"def\" or \"class\"." +"Call keyword.iskeyword(s) to test whether string s is a reserved identifier, " +"such as \"def\" or \"class\"." msgstr "" #: flwr.common.EventType.islower:3 of msgid "" -"A string is lowercase if all cased characters in the string are lowercase" -" and there is at least one cased character in the string." +"A string is lowercase if all cased characters in the string are lowercase " +"and there is at least one cased character in the string." msgstr "" #: flwr.common.EventType.isnumeric:3 of msgid "" -"A string is numeric if all characters in the string are numeric and there" -" is at least one character in the string." +"A string is numeric if all characters in the string are numeric and there is " +"at least one character in the string." msgstr "" #: flwr.common.EventType.isprintable:3 of msgid "" -"A string is printable if all of its characters are considered printable " -"in repr() or if it is empty." +"A string is printable if all of its characters are considered printable in " +"repr() or if it is empty." msgstr "" #: flwr.common.EventType.isspace:3 of msgid "" -"A string is whitespace if all characters in the string are whitespace and" -" there is at least one character in the string." +"A string is whitespace if all characters in the string are whitespace and " +"there is at least one character in the string." msgstr "" #: flwr.common.EventType.istitle:3 of msgid "" -"In a title-cased string, upper- and title-case characters may only follow" -" uncased characters and lowercase characters only cased ones." +"In a title-cased string, upper- and title-case characters may only follow " +"uncased characters and lowercase characters only cased ones." msgstr "" #: flwr.common.EventType.isupper:3 of msgid "" -"A string is uppercase if all cased characters in the string are uppercase" -" and there is at least one cased character in the string." +"A string is uppercase if all cased characters in the string are uppercase " +"and there is at least one cased character in the string." msgstr "" #: flwr.common.EventType.join:3 of msgid "" -"The string whose method is called is inserted in between each given " -"string. The result is returned as a new string." +"The string whose method is called is inserted in between each given string. " +"The result is returned as a new string." msgstr "" #: flwr.common.EventType.join:6 of @@ -8897,9 +8809,9 @@ msgid "" "ordinals (integers) or characters to Unicode ordinals, strings or None. " "Character keys will be then converted to ordinals. If there are two " "arguments, they must be strings of equal length, and in the resulting " -"dictionary, each character in x will be mapped to the character at the " -"same position in y. If there is a third argument, it must be a string, " -"whose characters will be mapped to None in the result." +"dictionary, each character in x will be mapped to the character at the same " +"position in y. If there is a third argument, it must be a string, whose " +"characters will be mapped to None in the result." msgstr "" #: flwr.common.EventType.partition:3 of @@ -8917,8 +8829,8 @@ msgstr "" #: flwr.common.EventType.removeprefix:3 of msgid "" -"If the string starts with the prefix string, return string[len(prefix):]." -" Otherwise, return a copy of the original string." +"If the string starts with the prefix string, return string[len(prefix):]. " +"Otherwise, return a copy of the original string." msgstr "" #: flwr.common.EventType.removesuffix:3 of @@ -8940,22 +8852,22 @@ msgstr "" #: flwr.common.EventType.replace:7 of msgid "" -"If the optional argument count is given, only the first count occurrences" -" are replaced." +"If the optional argument count is given, only the first count occurrences " +"are replaced." msgstr "" #: flwr.common.EventType.rfind:1 flwr.common.EventType.rindex:1 of msgid "" -"Return the highest index in S where substring sub is found, such that sub" -" is contained within S[start:end]. Optional arguments start and end are " +"Return the highest index in S where substring sub is found, such that sub is " +"contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" #: flwr.common.EventType.rpartition:3 of msgid "" -"This will search for the separator in the string, starting at the end. If" -" the separator is found, returns a 3-tuple containing the part before the" -" separator, the separator itself, and the part after it." +"This will search for the separator in the string, starting at the end. If " +"the separator is found, returns a 3-tuple containing the part before the " +"separator, the separator itself, and the part after it." msgstr "" #: flwr.common.EventType.rpartition:7 of @@ -8974,9 +8886,9 @@ msgstr "" #: flwr.common.EventType.rsplit:6 flwr.common.EventType.split:6 of msgid "" -"When set to None (the default value), will split on any whitespace " -"character (including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard" -" empty strings from the result." +"When set to None (the default value), will split on any whitespace character " +"(including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard empty " +"strings from the result." msgstr "" #: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of @@ -8985,8 +8897,8 @@ msgstr "" #: flwr.common.EventType.rsplit:10 flwr.common.EventType.split:10 of msgid "" -"Maximum number of splits (starting from the left). -1 (the default value)" -" means no limit." +"Maximum number of splits (starting from the left). -1 (the default value) " +"means no limit." msgstr "" #: flwr.common.EventType.rsplit:13 of @@ -8996,28 +8908,27 @@ msgstr "" #: flwr.common.EventType.split:13 of msgid "" "Note, str.split() is mainly useful for data that has been intentionally " -"delimited. With natural text that includes punctuation, consider using " -"the regular expression module." +"delimited. With natural text that includes punctuation, consider using the " +"regular expression module." msgstr "" #: flwr.common.EventType.splitlines:3 of msgid "" -"Line breaks are not included in the resulting list unless keepends is " -"given and true." +"Line breaks are not included in the resulting list unless keepends is given " +"and true." msgstr "" #: flwr.common.EventType.startswith:1 of msgid "" "Return True if S starts with the specified prefix, False otherwise. With " -"optional start, test S beginning at that position. With optional end, " -"stop comparing S at that position. prefix can also be a tuple of strings " -"to try." +"optional start, test S beginning at that position. With optional end, stop " +"comparing S at that position. prefix can also be a tuple of strings to try." msgstr "" #: flwr.common.EventType.title:3 of msgid "" -"More specifically, words start with uppercased characters and all " -"remaining cased characters have lower case." +"More specifically, words start with uppercased characters and all remaining " +"cased characters have lower case." msgstr "" #: flwr.common.EventType.translate:5 of @@ -9026,15 +8937,15 @@ msgstr "" #: flwr.common.EventType.translate:4 of msgid "" -"Translation table, which must be a mapping of Unicode ordinals to Unicode" -" ordinals, strings, or None." +"Translation table, which must be a mapping of Unicode ordinals to Unicode " +"ordinals, strings, or None." msgstr "" #: flwr.common.EventType.translate:7 of msgid "" "The table must implement lookup/indexing via __getitem__, for instance a " -"dictionary or list. If this operation raises LookupError, the character " -"is left untouched. Characters mapped to None are deleted." +"dictionary or list. If this operation raises LookupError, the character is " +"left untouched. Characters mapped to None are deleted." msgstr "" #: flwr.common.EventType.zfill:3 of @@ -9124,14 +9035,14 @@ msgstr "" #: flwr.common.message.Message:5 of msgid "" -"Holds records either sent by another entity (e.g. sent by the server-side" -" logic to a client, or vice-versa) or that will be sent to it." +"Holds records either sent by another entity (e.g. sent by the server-side " +"logic to a client, or vice-versa) or that will be sent to it." msgstr "" #: flwr.common.message.Message:8 of msgid "" -"A dataclass that captures information about an error that took place when" -" processing another message." +"A dataclass that captures information about an error that took place when " +"processing another message." msgstr "" #: ../../source/ref-api/flwr.common.Message.rst:35::1 @@ -9147,8 +9058,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.Message.rst:35::1 msgid "" -":py:obj:`create_reply `\\ " -"\\(content\\[\\, ttl\\]\\)" +":py:obj:`create_reply `\\ \\(content\\[\\, " +"ttl\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.Message.rst:35::1 @@ -9202,18 +9113,18 @@ msgstr "" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of msgid "" -"Time-to-live for this message in seconds. If unset, it will be set based " -"on the remaining time for the received message before it expires. This " -"follows the equation: ttl = msg.meta.ttl - (reply.meta.created_at - " -"msg.meta.created_at)" +"Time-to-live for this message in seconds. If unset, it will be set based on " +"the remaining time for the received message before it expires. This follows " +"the equation: ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta." +"created_at)" msgstr "" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of msgid "" -"Time-to-live for this message in seconds. If unset, it will be set based " -"on the remaining time for the received message before it expires. This " -"follows the equation:" +"Time-to-live for this message in seconds. If unset, it will be set based on " +"the remaining time for the received message before it expires. This follows " +"the equation:" msgstr "" #: flwr.common.message.Message.create_error_reply:9 @@ -9223,9 +9134,9 @@ msgstr "" #: flwr.common.message.Message.create_reply:3 of msgid "" -"The method generates a new `Message` as a reply to this message. It " -"inherits 'run_id', 'src_node_id', 'dst_node_id', and 'message_type' from " -"this message and sets 'reply_to_message' to the ID of this message." +"The method generates a new `Message` as a reply to this message. It inherits " +"'run_id', 'src_node_id', 'dst_node_id', and 'message_type' from this message " +"and sets 'reply_to_message' to the ID of this message." msgstr "" #: flwr.common.message.Message.create_reply:7 of @@ -9257,11 +9168,13 @@ msgid "MessageTypeLegacy" msgstr "" #: ../../source/ref-api/flwr.common.MessageTypeLegacy.rst:29::1 -msgid ":py:obj:`GET_PARAMETERS `\\" +msgid "" +":py:obj:`GET_PARAMETERS `\\" msgstr "" #: ../../source/ref-api/flwr.common.MessageTypeLegacy.rst:29::1 -msgid ":py:obj:`GET_PROPERTIES `\\" +msgid "" +":py:obj:`GET_PROPERTIES `\\" msgstr "" #: flwr.common.Metadata.created_at:1::1 @@ -9291,8 +9204,8 @@ msgstr "" #: flwr.common.message.Metadata:13 of msgid "" -"An identifier for grouping messages. In some settings, this is used as " -"the FL round." +"An identifier for grouping messages. In some settings, this is used as the " +"FL round." msgstr "" #: flwr.common.message.Metadata:16 of @@ -9306,9 +9219,9 @@ msgstr "" #: flwr.common.message.Metadata:21 of msgid "" -"An identifier that can be used when loading a particular data partition " -"for a ClientApp. Making use of this identifier is more relevant when " -"conducting simulations." +"An identifier that can be used when loading a particular data partition for " +"a ClientApp. Making use of this identifier is more relevant when conducting " +"simulations." msgstr "" #: flwr.common.Metadata.created_at:1::1 of @@ -9377,10 +9290,9 @@ msgstr "" #: flwr.common.record.metricsrecord.MetricsRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " -"[:py:class:`str`, :py:class:`int` | :py:class:`float` | " -":py:class:`~typing.List`\\ [:py:class:`int`] | :py:class:`~typing.List`\\" -" [:py:class:`float`]]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" +"`str`, :py:class:`int` | :py:class:`float` | :py:class:`~typing.List`\\ [:py:" +"class:`int`] | :py:class:`~typing.List`\\ [:py:class:`float`]]" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -9435,16 +9347,15 @@ msgstr "" #: flwr.common.record.parametersrecord.ParametersRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " -"[:py:class:`str`, :py:class:`~flwr.common.record.parametersrecord.Array`]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" +"`str`, :py:class:`~flwr.common.record.parametersrecord.Array`]" msgstr "" #: flwr.common.record.parametersrecord.ParametersRecord:3 of msgid "" -"A dataclass storing named Arrays in order. This means that it holds " -"entries as an OrderedDict[str, Array]. ParametersRecord objects can be " -"viewed as an equivalent to PyTorch's state_dict, but holding serialised " -"tensors instead." +"A dataclass storing named Arrays in order. This means that it holds entries " +"as an OrderedDict[str, Array]. ParametersRecord objects can be viewed as an " +"equivalent to PyTorch's state_dict, but holding serialised tensors instead." msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -9452,7 +9363,8 @@ msgid ":py:obj:`clear `\\ \\(\\)" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of -msgid ":py:obj:`count_bytes `\\ \\(\\)" +msgid "" +":py:obj:`count_bytes `\\ \\(\\)" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -9483,9 +9395,9 @@ msgstr "" #: flwr.common.record.parametersrecord.ParametersRecord.count_bytes:3 of msgid "" -"Note that a small amount of Bytes might also be included in this counting" -" that correspond to metadata of the serialized object (e.g. of NumPy " -"array) needed for deseralization." +"Note that a small amount of Bytes might also be included in this counting " +"that correspond to metadata of the serialized object (e.g. of NumPy array) " +"needed for deseralization." msgstr "" #: ../../source/ref-api/flwr.common.ReconnectIns.rst:2 @@ -9519,7 +9431,8 @@ msgid "Dictionary holding MetricsRecord instances." msgstr "" #: flwr.common.RecordSet.configs_records:1::1 of -msgid ":py:obj:`parameters_records `\\" +msgid "" +":py:obj:`parameters_records `\\" msgstr "" #: flwr.common.RecordSet.configs_records:1::1 @@ -9541,14 +9454,12 @@ msgstr "" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid "" -":py:obj:`get_parameters_ins " -"`\\" +":py:obj:`get_parameters_ins `\\" msgstr "" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid "" -":py:obj:`get_properties_ins " -"`\\" +":py:obj:`get_properties_ins `\\" msgstr "" #: ../../source/ref-api/flwr.common.Status.rst:2 @@ -9585,8 +9496,8 @@ msgstr "" #: logging.Logger.log:3 of msgid "" -"To pass exception information, use the keyword argument exc_info with a " -"true value, e.g." +"To pass exception information, use the keyword argument exc_info with a true " +"value, e.g." msgstr "" #: logging.Logger.log:6 of @@ -9718,8 +9629,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.rst:40::1 msgid "" -":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\," -" round\\_timeout\\]\\)" +":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\, " +"round\\_timeout\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.rst:40::1 @@ -9728,7 +9639,8 @@ msgid "Flower server config." msgstr "" #: ../../source/ref-api/flwr.server.rst:40::1 -msgid ":py:obj:`SimpleClientManager `\\ \\(\\)" +msgid "" +":py:obj:`SimpleClientManager `\\ \\(\\)" msgstr "" #: ../../source/ref-api/flwr.server.rst:40::1 @@ -9770,7 +9682,8 @@ msgid "Return all available clients." msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of -msgid ":py:obj:`num_available `\\ \\(\\)" +msgid "" +":py:obj:`num_available `\\ \\(\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -9793,8 +9706,8 @@ msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of msgid "" -":py:obj:`sample `\\ " -"\\(num\\_clients\\[\\, min\\_num\\_clients\\, criterion\\]\\)" +":py:obj:`sample `\\ \\(num\\_clients\\[\\, " +"min\\_num\\_clients\\, criterion\\]\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -9805,7 +9718,8 @@ msgid "Sample a number of Flower ClientProxy instances." msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of -msgid ":py:obj:`unregister `\\ \\(client\\)" +msgid "" +":py:obj:`unregister `\\ \\(client\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -9837,8 +9751,7 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.register:6 of msgid "" "**success** -- Indicating if registration was successful. False if " -"ClientProxy is already registered or can not be registered for any " -"reason." +"ClientProxy is already registered or can not be registered for any reason." msgstr "" #: flwr.server.client_manager.ClientManager.unregister:3 @@ -9852,8 +9765,8 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 of msgid "" -":py:obj:`create_message `\\ " -"\\(content\\, message\\_type\\, ...\\[\\, ttl\\]\\)" +":py:obj:`create_message `\\ \\(content\\, " +"message\\_type\\, ...\\[\\, ttl\\]\\)" msgstr "" #: flwr.server.driver.driver.Driver.create_message:1 @@ -9883,8 +9796,7 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 of msgid "" -":py:obj:`push_messages `\\ " -"\\(messages\\)" +":py:obj:`push_messages `\\ \\(messages\\)" msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 @@ -9905,20 +9817,20 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:3 of msgid "" -"This method constructs a new `Message` with given content and metadata. " -"The `run_id` and `src_node_id` will be set automatically." +"This method constructs a new `Message` with given content and metadata. The " +"`run_id` and `src_node_id` will be set automatically." msgstr "" #: flwr.server.driver.driver.Driver.create_message:6 of msgid "" -"The content for the new message. This holds records that are to be sent " -"to the destination node." +"The content for the new message. This holds records that are to be sent to " +"the destination node." msgstr "" #: flwr.server.driver.driver.Driver.create_message:9 of msgid "" -"The type of the message, defining the action to be executed on the " -"receiving end." +"The type of the message, defining the action to be executed on the receiving " +"end." msgstr "" #: flwr.server.driver.driver.Driver.create_message:12 of @@ -9927,17 +9839,16 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:14 of msgid "" -"The ID of the group to which this message is associated. In some " -"settings, this is used as the FL round." +"The ID of the group to which this message is associated. In some settings, " +"this is used as the FL round." msgstr "" #: flwr.server.driver.driver.Driver.create_message:17 of msgid "" -"Time-to-live for the round trip of this message, i.e., the time from " -"sending this message to receiving a reply. It specifies in seconds the " -"duration for which the message and its potential reply are considered " -"valid. If unset, the default TTL (i.e., `common.DEFAULT_TTL`) will be " -"used." +"Time-to-live for the round trip of this message, i.e., the time from sending " +"this message to receiving a reply. It specifies in seconds the duration for " +"which the message and its potential reply are considered valid. If unset, " +"the default TTL (i.e., `common.DEFAULT_TTL`) will be used." msgstr "" #: flwr.server.driver.driver.Driver.create_message:23 of @@ -9948,12 +9859,13 @@ msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:3 of msgid "" -"This method is used to collect messages from the SuperLink that " -"correspond to a set of given message IDs." +"This method is used to collect messages from the SuperLink that correspond " +"to a set of given message IDs." msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:6 of -msgid "An iterable of message IDs for which reply messages are to be retrieved." +msgid "" +"An iterable of message IDs for which reply messages are to be retrieved." msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:9 of @@ -9962,8 +9874,8 @@ msgstr "" #: flwr.server.driver.driver.Driver.push_messages:3 of msgid "" -"This method takes an iterable of messages and sends each message to the " -"node specified in `dst_node_id`." +"This method takes an iterable of messages and sends each message to the node " +"specified in `dst_node_id`." msgstr "" #: flwr.server.driver.driver.Driver.push_messages:6 @@ -9973,26 +9885,27 @@ msgstr "" #: flwr.server.driver.driver.Driver.push_messages:9 of msgid "" -"**message_ids** -- An iterable of IDs for the messages that were sent, " -"which can be used to pull replies." +"**message_ids** -- An iterable of IDs for the messages that were sent, which " +"can be used to pull replies." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:3 of msgid "" -"This method sends a list of messages to their destination node IDs and " -"then waits for the replies. It continues to pull replies until either all" -" replies are received or the specified timeout duration is exceeded." +"This method sends a list of messages to their destination node IDs and then " +"waits for the replies. It continues to pull replies until either all replies " +"are received or the specified timeout duration is exceeded." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:9 of msgid "" "The timeout duration in seconds. If specified, the method will wait for " -"replies for this duration. If `None`, there is no time limit and the " -"method will wait until replies for all messages are received." +"replies for this duration. If `None`, there is no time limit and the method " +"will wait until replies for all messages are received." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:14 of -msgid "**replies** -- An iterable of reply messages received from the SuperLink." +msgid "" +"**replies** -- An iterable of reply messages received from the SuperLink." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:18 @@ -10004,10 +9917,10 @@ msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:19 of msgid "" -"This method uses `push_messages` to send the messages and `pull_messages`" -" to collect the replies. If `timeout` is set, the method may not return " -"replies for all sent messages. A message remains valid until its TTL, " -"which is not affected by `timeout`." +"This method uses `push_messages` to send the messages and `pull_messages` to " +"collect the replies. If `timeout` is set, the method may not return replies " +"for all sent messages. A message remains valid until its TTL, which is not " +"affected by `timeout`." msgstr "" #: ../../source/ref-api/flwr.server.History.rst:2 @@ -10016,9 +9929,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_loss_centralized " -"`\\ \\(server\\_round\\, " -"loss\\)" +":py:obj:`add_loss_centralized `\\ " +"\\(server\\_round\\, loss\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1 @@ -10028,9 +9940,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_loss_distributed " -"`\\ \\(server\\_round\\, " -"loss\\)" +":py:obj:`add_loss_distributed `\\ " +"\\(server\\_round\\, loss\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10040,9 +9951,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_centralized " -"`\\ \\(server\\_round\\, " -"metrics\\)" +":py:obj:`add_metrics_centralized `\\ \\(server\\_round\\, metrics\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10052,9 +9962,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_distributed " -"`\\ \\(server\\_round\\, " -"metrics\\)" +":py:obj:`add_metrics_distributed `\\ \\(server\\_round\\, metrics\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10064,9 +9973,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_distributed_fit " -"`\\ \\(server\\_round\\," -" ...\\)" +":py:obj:`add_metrics_distributed_fit `\\ \\(server\\_round\\, ...\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10117,8 +10025,8 @@ msgstr "" #: flwr.server.server.Server.client_manager:1::1 of msgid "" -":py:obj:`disconnect_all_clients " -"`\\ \\(timeout\\)" +":py:obj:`disconnect_all_clients `\\ \\(timeout\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -10148,8 +10056,8 @@ msgstr "" #: flwr.server.server.Server.client_manager:1::1 of msgid "" -":py:obj:`fit_round `\\ \\(server\\_round\\," -" timeout\\)" +":py:obj:`fit_round `\\ \\(server\\_round\\, " +"timeout\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -10169,7 +10077,8 @@ msgid "Set the max_workers used by ThreadPoolExecutor." msgstr "" #: flwr.server.server.Server.client_manager:1::1 of -msgid ":py:obj:`set_strategy `\\ \\(strategy\\)" +msgid "" +":py:obj:`set_strategy `\\ \\(strategy\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -10204,8 +10113,8 @@ msgstr "" #: flwr.server.server_config.ServerConfig:3 of msgid "" -"All attributes have default values which allows users to configure just " -"the ones they care about." +"All attributes have default values which allows users to configure just the " +"ones they care about." msgstr "" #: ../../source/ref-api/flwr.server.ServerConfig.rst:29::1 @@ -10230,14 +10139,13 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of msgid "" -":py:obj:`num_available `\\" -" \\(\\)" +":py:obj:`num_available `\\ " +"\\(\\)" msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of msgid "" -":py:obj:`register `\\ " -"\\(client\\)" +":py:obj:`register `\\ \\(client\\)" msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of @@ -10260,8 +10168,8 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.wait_for:3 of msgid "" -"Blocks until the requested number of clients is available or until a " -"timeout is reached. Current timeout default: 1 day." +"Blocks until the requested number of clients is available or until a timeout " +"is reached. Current timeout default: 1 day." msgstr "" #: flwr.server.client_manager.SimpleClientManager.wait_for:6 of @@ -10302,8 +10210,8 @@ msgstr "" #: flwr.server.app.start_server:5 of msgid "" -"A server implementation, either `flwr.server.Server` or a subclass " -"thereof. If no instance is provided, then `start_server` will create one." +"A server implementation, either `flwr.server.Server` or a subclass thereof. " +"If no instance is provided, then `start_server` will create one." msgstr "" #: flwr.server.app.start_server:9 flwr.simulation.app.start_simulation:28 of @@ -10314,41 +10222,41 @@ msgstr "" #: flwr.server.app.start_server:12 of msgid "" -"An implementation of the abstract base class " -"`flwr.server.strategy.Strategy`. If no strategy is provided, then " -"`start_server` will use `flwr.server.strategy.FedAvg`." +"An implementation of the abstract base class `flwr.server.strategy." +"Strategy`. If no strategy is provided, then `start_server` will use `flwr." +"server.strategy.FedAvg`." msgstr "" #: flwr.server.app.start_server:16 of msgid "" -"An implementation of the abstract base class `flwr.server.ClientManager`." -" If no implementation is provided, then `start_server` will use " -"`flwr.server.client_manager.SimpleClientManager`." +"An implementation of the abstract base class `flwr.server.ClientManager`. If " +"no implementation is provided, then `start_server` will use `flwr.server." +"client_manager.SimpleClientManager`." msgstr "" #: flwr.server.app.start_server:21 of msgid "" -"The maximum length of gRPC messages that can be exchanged with the Flower" -" clients. The default should be sufficient for most models. Users who " -"train very large models might need to increase this value. Note that the " -"Flower clients need to be started with the same value (see " -"`flwr.client.start_client`), otherwise clients will not know about the " -"increased limit and block larger messages." +"The maximum length of gRPC messages that can be exchanged with the Flower " +"clients. The default should be sufficient for most models. Users who train " +"very large models might need to increase this value. Note that the Flower " +"clients need to be started with the same value (see `flwr.client." +"start_client`), otherwise clients will not know about the increased limit " +"and block larger messages." msgstr "" #: flwr.server.app.start_server:28 of msgid "" -"Tuple containing root certificate, server certificate, and private key to" -" start a secure SSL-enabled server. The tuple is expected to have three " -"bytes elements in the following order: * CA certificate. * " -"server certificate. * server private key." +"Tuple containing root certificate, server certificate, and private key to " +"start a secure SSL-enabled server. The tuple is expected to have three bytes " +"elements in the following order: * CA certificate. * server " +"certificate. * server private key." msgstr "" #: flwr.server.app.start_server:28 of msgid "" -"Tuple containing root certificate, server certificate, and private key to" -" start a secure SSL-enabled server. The tuple is expected to have three " -"bytes elements in the following order:" +"Tuple containing root certificate, server certificate, and private key to " +"start a secure SSL-enabled server. The tuple is expected to have three bytes " +"elements in the following order:" msgstr "" #: flwr.server.app.start_server:32 of @@ -10381,8 +10289,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`Bulyan `\\ \\(\\*\\, " -"fraction\\_fit\\, fraction\\_evaluate\\, ...\\)" +":py:obj:`Bulyan `\\ \\(\\*\\, fraction\\_fit\\, " +"fraction\\_evaluate\\, ...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10414,9 +10322,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyClientSideAdaptiveClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyClientSideAdaptiveClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10427,9 +10334,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyServerSideAdaptiveClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyServerSideAdaptiveClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10440,9 +10346,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyClientSideFixedClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyClientSideFixedClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10453,9 +10358,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyServerSideFixedClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyServerSideFixedClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10500,8 +10404,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FedAvgAndroid `\\ " -"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" +":py:obj:`FedAvgAndroid `\\ \\(\\*\\[\\, " +"fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10550,8 +10454,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FedTrimmedAvg `\\ " -"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" +":py:obj:`FedTrimmedAvg `\\ \\(\\*\\[\\, " +"fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10605,9 +10509,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FaultTolerantFedAvg " -"`\\ \\(\\*\\[\\, " -"fraction\\_fit\\, ...\\]\\)" +":py:obj:`FaultTolerantFedAvg `\\ " +"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10617,8 +10520,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`Krum `\\ \\(\\*\\[\\, " -"fraction\\_fit\\, fraction\\_evaluate\\, ...\\]\\)" +":py:obj:`Krum `\\ \\(\\*\\[\\, fraction\\_fit\\, " +"fraction\\_evaluate\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10628,8 +10531,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`QFedAvg `\\ \\(\\*\\[\\, " -"q\\_param\\, qffl\\_learning\\_rate\\, ...\\]\\)" +":py:obj:`QFedAvg `\\ \\(\\*\\[\\, q\\_param\\, " +"qffl\\_learning\\_rate\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10797,8 +10700,8 @@ msgstr "" #: flwr.server.strategy.bulyan.Bulyan:27 of msgid "" -"Byzantine resilient aggregation rule that is used as the first step of " -"the Bulyan (e.g., Krum)" +"Byzantine resilient aggregation rule that is used as the first step of the " +"Bulyan (e.g., Krum)" msgstr "" #: flwr.server.strategy.bulyan.Bulyan:29 of @@ -10807,9 +10710,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1 @@ -10836,9 +10738,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -10917,9 +10818,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -10936,9 +10836,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -10955,8 +10854,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\" -" \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -10987,9 +10886,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1 @@ -11009,9 +10907,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dpfedavg_adaptive.DPFedAvgAdaptive.aggregate_fit:1 @@ -11023,9 +10920,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -11036,9 +10932,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -11058,15 +10953,15 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.evaluate:1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.evaluate:1 of -msgid "Evaluate model parameters using an evaluation function from the strategy." +msgid "" +"Evaluate model parameters using an evaluation function from the strategy." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -11105,9 +11000,9 @@ msgstr "" msgid "" "**evaluate_configuration** -- A list of tuples. Each tuple in the list " "identifies a `ClientProxy` and the `EvaluateIns` for this particular " -"`ClientProxy`. If a particular `ClientProxy` is not included in this " -"list, it means that this `ClientProxy` will not participate in the next " -"round of federated evaluation." +"`ClientProxy`. If a particular `ClientProxy` is not included in this list, " +"it means that this `ClientProxy` will not participate in the next round of " +"federated evaluation." msgstr "" #: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst:2 @@ -11127,16 +11022,14 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -11148,24 +11041,21 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:1 of msgid "" -"Configure the next round of training incorporating Differential Privacy " -"(DP)." +"Configure the next round of training incorporating Differential Privacy (DP)." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -11178,25 +11068,23 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:3 of msgid "" -"Configuration of the next training round includes information related to " -"DP, such as clip norm and noise stddev." +"Configuration of the next training round includes information related to DP, " +"such as clip norm and noise stddev." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:13 #: flwr.server.strategy.strategy.Strategy.configure_fit:10 of msgid "" -"**fit_configuration** -- A list of tuples. Each tuple in the list " -"identifies a `ClientProxy` and the `FitIns` for this particular " -"`ClientProxy`. If a particular `ClientProxy` is not included in this " -"list, it means that this `ClientProxy` will not participate in the next " -"round of federated learning." +"**fit_configuration** -- A list of tuples. Each tuple in the list identifies " +"a `ClientProxy` and the `FitIns` for this particular `ClientProxy`. If a " +"particular `ClientProxy` is not included in this list, it means that this " +"`ClientProxy` will not participate in the next round of federated learning." msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideAdaptiveClipping.rst:2 @@ -11213,9 +11101,8 @@ msgstr "" msgid "" "In comparison to `DifferentialPrivacyServerSideAdaptiveClipping`, which " "performs clipping on the server-side, " -"`DifferentialPrivacyClientSideAdaptiveClipping` expects clipping to " -"happen on the client-side, usually by using the built-in " -"`adaptiveclipping_mod`." +"`DifferentialPrivacyClientSideAdaptiveClipping` expects clipping to happen " +"on the client-side, usually by using the built-in `adaptiveclipping_mod`." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:10 @@ -11251,22 +11138,23 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:19 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:12 #: of -msgid "The desired quantile of updates which should be clipped. Defaults to 0.5." +msgid "" +"The desired quantile of updates which should be clipped. Defaults to 0.5." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:21 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:14 #: of msgid "" -"The learning rate for the clipping norm adaptation. Defaults to 0.2. " -"Andrew et al. recommends to set to 0.2." +"The learning rate for the clipping norm adaptation. Defaults to 0.2. Andrew " +"et al. recommends to set to 0.2." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:24 #: of msgid "" -"The stddev of the noise added to the count of updates currently below the" -" estimate. Andrew et al. recommends to set to `expected_num_records/20`" +"The stddev of the noise added to the count of updates currently below the " +"estimate. Andrew et al. recommends to set to `expected_num_records/20`" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:30 @@ -11280,8 +11168,8 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:34 #: of msgid "" -"Wrap the strategy with the " -"`DifferentialPrivacyClientSideAdaptiveClipping` wrapper:" +"Wrap the strategy with the `DifferentialPrivacyClientSideAdaptiveClipping` " +"wrapper:" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:40 @@ -11292,17 +11180,17 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -11316,33 +11204,33 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ " +"\\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideFixedClipping.rst:2 @@ -11359,16 +11247,16 @@ msgstr "" msgid "" "In comparison to `DifferentialPrivacyServerSideFixedClipping`, which " "performs clipping on the server-side, " -"`DifferentialPrivacyClientSideFixedClipping` expects clipping to happen " -"on the client-side, usually by using the built-in `fixedclipping_mod`." +"`DifferentialPrivacyClientSideFixedClipping` expects clipping to happen on " +"the client-side, usually by using the built-in `fixedclipping_mod`." msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:12 #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:5 #: of msgid "" -"The noise multiplier for the Gaussian mechanism for model updates. A " -"value of 1.0 or higher is recommended for strong privacy." +"The noise multiplier for the Gaussian mechanism for model updates. A value " +"of 1.0 or higher is recommended for strong privacy." msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 @@ -11392,17 +11280,17 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 @@ -11414,33 +11302,33 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ \\(server\\_round\\, " +"parameters\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideAdaptiveClipping.rst:2 @@ -11450,9 +11338,8 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:17 #: of msgid "" -"The standard deviation of the noise added to the count of updates below " -"the estimate. Andrew et al. recommends to set to " -"`expected_num_records/20`" +"The standard deviation of the noise added to the count of updates below the " +"estimate. Andrew et al. recommends to set to `expected_num_records/20`" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:27 @@ -11465,49 +11352,49 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ " +"\\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideFixedClipping.rst:2 @@ -11517,24 +11404,23 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:19 #: of msgid "" -"Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping " -"wrapper" +"Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping wrapper" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 @@ -11546,33 +11432,33 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ \\(server\\_round\\, " +"parameters\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_fit:3 @@ -11587,17 +11473,15 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -11619,17 +11503,15 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -11642,25 +11524,22 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst:2 @@ -11713,28 +11592,26 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit `\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit `\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11745,23 +11622,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdam.rst:2 @@ -11780,9 +11654,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11793,9 +11666,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11812,22 +11684,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -11844,17 +11713,16 @@ msgstr "" #: of msgid "" "Fraction of clients used during training. In case `min_fit_clients` is " -"larger than `fraction_fit * available_clients`, `min_fit_clients` will " -"still be sampled. Defaults to 1.0." +"larger than `fraction_fit * available_clients`, `min_fit_clients` will still " +"be sampled. Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavg.FedAvg:9 flwr.server.strategy.fedprox.FedProx:41 #: of msgid "" -"Fraction of clients used during validation. In case " -"`min_evaluate_clients` is larger than `fraction_evaluate * " -"available_clients`, `min_evaluate_clients` will still be sampled. " -"Defaults to 1.0." +"Fraction of clients used during validation. In case `min_evaluate_clients` " +"is larger than `fraction_evaluate * available_clients`, " +"`min_evaluate_clients` will still be sampled. Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavg.FedAvg:33 of @@ -11863,9 +11731,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11876,9 +11743,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11895,22 +11761,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\" -" \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAvgAndroid.rst:2 @@ -11920,24 +11784,22 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`bytes_to_ndarray " -"`\\ \\(tensor\\)" +":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -11948,16 +11810,14 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -11971,16 +11831,15 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`ndarray_to_bytes " -"`\\ \\(ndarray\\)" +":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -11991,33 +11850,29 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`ndarrays_to_parameters " -"`\\ " -"\\(ndarrays\\)" +":py:obj:`ndarrays_to_parameters `\\ \\(ndarrays\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`parameters_to_ndarrays " -"`\\ " -"\\(parameters\\)" +":py:obj:`parameters_to_ndarrays `\\ \\(parameters\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -12036,8 +11891,7 @@ msgstr "" #: flwr.server.strategy.fedavgm.FedAvgM:25 of msgid "" -"Server-side learning rate used in server-side optimization. Defaults to " -"1.0." +"Server-side learning rate used in server-side optimization. Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavgm.FedAvgM:28 of @@ -12046,9 +11900,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12059,9 +11912,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12078,22 +11930,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12103,9 +11952,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12121,9 +11969,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12140,22 +11987,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12173,9 +12017,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12186,9 +12029,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12205,22 +12047,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\" -" \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedProx.rst:2 @@ -12233,9 +12073,9 @@ msgstr "" #: flwr.server.strategy.fedprox.FedProx:5 of msgid "" -"The strategy in itself will not be different than FedAvg, the client " -"needs to be adjusted. A proximal term needs to be added to the loss " -"function during the training:" +"The strategy in itself will not be different than FedAvg, the client needs " +"to be adjusted. A proximal term needs to be added to the loss function " +"during the training:" msgstr "" #: flwr.server.strategy.fedprox.FedProx:9 of @@ -12268,15 +12108,14 @@ msgstr "" msgid "" "The weight of the proximal term used in the optimization. 0.0 makes this " "strategy equivalent to FedAvg, and the higher the coefficient, the more " -"regularization will be used (that is, the client parameters will need to " -"be closer to the server parameters during training)." +"regularization will be used (that is, the client parameters will need to be " +"closer to the server parameters during training)." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12287,9 +12126,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12306,22 +12144,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12343,15 +12178,13 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -12362,15 +12195,13 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -12382,23 +12213,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbBagging.rst:2 @@ -12408,9 +12236,8 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1 @@ -12424,8 +12251,7 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -12439,16 +12265,14 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -12462,25 +12286,22 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbCyclic.rst:2 @@ -12490,33 +12311,29 @@ msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ \\(server\\_round\\," -" results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 @@ -12529,25 +12346,22 @@ msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbNnAvg.rst:2 @@ -12557,36 +12371,31 @@ msgstr "" #: flwr.server.strategy.fedxgb_nn_avg.FedXgbNnAvg:5 of msgid "" "This strategy is deprecated, but a copy of it is available in Flower " -"Baselines: " -"https://github.com/adap/flower/tree/main/baselines/hfedxgboost." +"Baselines: https://github.com/adap/flower/tree/main/baselines/hfedxgboost." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit " -"`\\ \\(server\\_round\\, " -"results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12597,23 +12406,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedYogi.rst:2 @@ -12634,9 +12440,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12647,9 +12452,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12666,22 +12470,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12695,15 +12496,14 @@ msgstr "" #: flwr.server.strategy.krum.Krum:17 of msgid "" -"Number of clients to keep before averaging (MultiKrum). Defaults to 0, in" -" that case classical Krum is applied." +"Number of clients to keep before averaging (MultiKrum). Defaults to 0, in " +"that case classical Krum is applied." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12719,9 +12519,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12738,16 +12537,14 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12762,9 +12559,8 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of @@ -12775,9 +12571,8 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of @@ -12794,22 +12589,19 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12820,9 +12612,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1 @@ -12846,9 +12637,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 @@ -12873,9 +12663,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 @@ -12885,17 +12674,18 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:5 of msgid "" -"Successful updates from the previously selected and configured clients. " -"Each pair of `(ClientProxy, FitRes` constitutes a successful update from " -"one of the previously selected clients. Not that not all previously " -"selected clients are necessarily included in this list: a client might " -"drop out and not submit a result. For each client that did not submit an " -"update, there should be an `Exception` in `failures`." +"Successful updates from the previously selected and configured clients. Each " +"pair of `(ClientProxy, FitRes` constitutes a successful update from one of " +"the previously selected clients. Not that not all previously selected " +"clients are necessarily included in this list: a client might drop out and " +"not submit a result. For each client that did not submit an update, there " +"should be an `Exception` in `failures`." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:13 #: flwr.server.strategy.strategy.Strategy.aggregate_fit:13 of -msgid "Exceptions that occurred while the server was waiting for client updates." +msgid "" +"Exceptions that occurred while the server was waiting for client updates." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:16 of @@ -12906,23 +12696,22 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_fit:5 of msgid "" -"Successful updates from the previously selected and configured clients. " -"Each pair of `(ClientProxy, FitRes)` constitutes a successful update from" -" one of the previously selected clients. Not that not all previously " -"selected clients are necessarily included in this list: a client might " -"drop out and not submit a result. For each client that did not submit an " -"update, there should be an `Exception` in `failures`." +"Successful updates from the previously selected and configured clients. Each " +"pair of `(ClientProxy, FitRes)` constitutes a successful update from one of " +"the previously selected clients. Not that not all previously selected " +"clients are necessarily included in this list: a client might drop out and " +"not submit a result. For each client that did not submit an update, there " +"should be an `Exception` in `failures`." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_fit:17 of msgid "" "**parameters** -- If parameters are returned, then the server will treat " -"these as the new global model parameters (i.e., it will replace the " -"previous parameters with the ones returned from this method). If `None` " -"is returned (e.g., because there were only failures and no viable " -"results) then the server will no update the previous model parameters, " -"the updates received in this round are discarded, and the global model " -"parameters remain the same." +"these as the new global model parameters (i.e., it will replace the previous " +"parameters with the ones returned from this method). If `None` is returned " +"(e.g., because there were only failures and no viable results) then the " +"server will no update the previous model parameters, the updates received in " +"this round are discarded, and the global model parameters remain the same." msgstr "" #: flwr.server.strategy.strategy.Strategy.evaluate:3 of @@ -12933,9 +12722,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.evaluate:11 of msgid "" -"**evaluation_result** -- The evaluation result, usually a Tuple " -"containing loss and a dictionary containing task-specific metrics (e.g., " -"accuracy)." +"**evaluation_result** -- The evaluation result, usually a Tuple containing " +"loss and a dictionary containing task-specific metrics (e.g., accuracy)." msgstr "" #: flwr.server.strategy.strategy.Strategy.initialize_parameters:6 of @@ -12993,17 +12781,17 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:3 #: of msgid "" -"The SecAgg+ protocol ensures the secure summation of integer vectors " -"owned by multiple parties, without accessing any individual integer " -"vector. This workflow allows the server to compute the weighted average " -"of model parameters across all clients, ensuring individual contributions" -" remain private. This is achieved by clients sending both, a weighting " -"factor and a weighted version of the locally updated parameters, both of " -"which are masked for privacy. Specifically, each client uploads \"[w, w *" -" params]\" with masks, where weighting factor 'w' is the number of " -"examples ('num_examples') and 'params' represents the model parameters " -"('parameters') from the client's `FitRes`. The server then aggregates " -"these contributions to compute the weighted average of model parameters." +"The SecAgg+ protocol ensures the secure summation of integer vectors owned " +"by multiple parties, without accessing any individual integer vector. This " +"workflow allows the server to compute the weighted average of model " +"parameters across all clients, ensuring individual contributions remain " +"private. This is achieved by clients sending both, a weighting factor and a " +"weighted version of the locally updated parameters, both of which are masked " +"for privacy. Specifically, each client uploads \"[w, w * params]\" with " +"masks, where weighting factor 'w' is the number of examples ('num_examples') " +"and 'params' represents the model parameters ('parameters') from the " +"client's `FitRes`. The server then aggregates these contributions to compute " +"the weighted average of model parameters." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:14 @@ -13040,39 +12828,39 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:22 #: of msgid "" -"Only the aggregated model parameters are exposed and passed to " -"`Strategy.aggregate_fit`, ensuring individual data privacy." +"Only the aggregated model parameters are exposed and passed to `Strategy." +"aggregate_fit`, ensuring individual data privacy." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:25 #: of msgid "" -"The number of shares into which each client's private key is split under " -"the SecAgg+ protocol. If specified as a float, it represents the " -"proportion of all selected clients, and the number of shares will be set " -"dynamically in the run time. A private key can be reconstructed from " -"these shares, allowing for the secure aggregation of model updates. Each " -"client sends one share to each of its neighbors while retaining one." +"The number of shares into which each client's private key is split under the " +"SecAgg+ protocol. If specified as a float, it represents the proportion of " +"all selected clients, and the number of shares will be set dynamically in " +"the run time. A private key can be reconstructed from these shares, allowing " +"for the secure aggregation of model updates. Each client sends one share to " +"each of its neighbors while retaining one." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:25 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:32 #: of msgid "" -"The minimum number of shares required to reconstruct a client's private " -"key, or, if specified as a float, it represents the proportion of the " -"total number of shares needed for reconstruction. This threshold ensures " -"privacy by allowing for the recovery of contributions from dropped " -"clients during aggregation, without compromising individual client data." +"The minimum number of shares required to reconstruct a client's private key, " +"or, if specified as a float, it represents the proportion of the total " +"number of shares needed for reconstruction. This threshold ensures privacy " +"by allowing for the recovery of contributions from dropped clients during " +"aggregation, without compromising individual client data." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:31 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:38 #: of msgid "" -"The maximum value of the weight that can be assigned to any single " -"client's update during the weighted average calculation on the server " -"side, e.g., in the FedAvg algorithm." +"The maximum value of the weight that can be assigned to any single client's " +"update during the weighted average calculation on the server side, e.g., in " +"the FedAvg algorithm." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:35 @@ -13080,8 +12868,8 @@ msgstr "" #: of msgid "" "The range within which model parameters are clipped before quantization. " -"This parameter ensures each model parameter is bounded within " -"[-clipping_range, clipping_range], facilitating quantization." +"This parameter ensures each model parameter is bounded within [-" +"clipping_range, clipping_range], facilitating quantization." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:39 @@ -13099,31 +12887,32 @@ msgstr "" #: of msgid "" "The range of values from which random mask entries are uniformly sampled " -"([0, modulus_range-1]). `modulus_range` must be less than 4294967296. " -"Please use 2**n values for `modulus_range` to prevent overflow issues." +"([0, modulus_range-1]). `modulus_range` must be less than 4294967296. Please " +"use 2**n values for `modulus_range` to prevent overflow issues." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:47 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:54 #: of msgid "" -"The timeout duration in seconds. If specified, the workflow will wait for" -" replies for this duration each time. If `None`, there is no time limit " -"and the workflow will wait until replies for all messages are received." +"The timeout duration in seconds. If specified, the workflow will wait for " +"replies for this duration each time. If `None`, there is no time limit and " +"the workflow will wait until replies for all messages are received." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:61 #: of msgid "" "Generally, higher `num_shares` means more robust to dropouts while " -"increasing the computational costs; higher `reconstruction_threshold` " -"means better privacy guarantees but less tolerance to dropouts." +"increasing the computational costs; higher `reconstruction_threshold` means " +"better privacy guarantees but less tolerance to dropouts." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:58 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:64 #: of -msgid "Too large `max_weight` may compromise the precision of the quantization." +msgid "" +"Too large `max_weight` may compromise the precision of the quantization." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:59 @@ -13136,35 +12925,34 @@ msgstr "" #: of msgid "" "When `num_shares` is a float, it is interpreted as the proportion of all " -"selected clients, and hence the number of shares will be determined in " -"the runtime. This allows for dynamic adjustment based on the total number" -" of participating clients." +"selected clients, and hence the number of shares will be determined in the " +"runtime. This allows for dynamic adjustment based on the total number of " +"participating clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:69 #: of msgid "" -"Similarly, when `reconstruction_threshold` is a float, it is interpreted " -"as the proportion of the number of shares needed for the reconstruction " -"of a private key. This feature enables flexibility in setting the " -"security threshold relative to the number of distributed shares." +"Similarly, when `reconstruction_threshold` is a float, it is interpreted as " +"the proportion of the number of shares needed for the reconstruction of a " +"private key. This feature enables flexibility in setting the security " +"threshold relative to the number of distributed shares." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:73 #: of msgid "" -"`num_shares`, `reconstruction_threshold`, and the quantization parameters" -" (`clipping_range`, `quantization_range`, `modulus_range`) play critical " -"roles in balancing privacy, robustness, and efficiency within the SecAgg+" -" protocol." +"`num_shares`, `reconstruction_threshold`, and the quantization parameters " +"(`clipping_range`, `quantization_range`, `modulus_range`) play critical " +"roles in balancing privacy, robustness, and efficiency within the SecAgg+ " +"protocol." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`collect_masked_vectors_stage " -"`\\" -" \\(driver\\, ...\\)" +":py:obj:`collect_masked_vectors_stage `\\ \\(driver\\, ...\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1 @@ -13176,9 +12964,8 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`setup_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`setup_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -13190,9 +12977,8 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`share_keys_stage " -"`\\ " -"\\(driver\\, context\\, state\\)" +":py:obj:`share_keys_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -13204,9 +12990,8 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`unmask_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`unmask_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -13221,51 +13006,50 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:1 of msgid "" -"Bases: " -":py:class:`~flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow`" +"Bases: :py:class:`~flwr.server.workflow.secure_aggregation." +"secaggplus_workflow.SecAggPlusWorkflow`" msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:3 of msgid "" -"The SecAgg protocol ensures the secure summation of integer vectors owned" -" by multiple parties, without accessing any individual integer vector. " -"This workflow allows the server to compute the weighted average of model " +"The SecAgg protocol ensures the secure summation of integer vectors owned by " +"multiple parties, without accessing any individual integer vector. This " +"workflow allows the server to compute the weighted average of model " "parameters across all clients, ensuring individual contributions remain " -"private. This is achieved by clients sending both, a weighting factor and" -" a weighted version of the locally updated parameters, both of which are " -"masked for privacy. Specifically, each client uploads \"[w, w * params]\"" -" with masks, where weighting factor 'w' is the number of examples " -"('num_examples') and 'params' represents the model parameters " -"('parameters') from the client's `FitRes`. The server then aggregates " -"these contributions to compute the weighted average of model parameters." +"private. This is achieved by clients sending both, a weighting factor and a " +"weighted version of the locally updated parameters, both of which are masked " +"for privacy. Specifically, each client uploads \"[w, w * params]\" with " +"masks, where weighting factor 'w' is the number of examples ('num_examples') " +"and 'params' represents the model parameters ('parameters') from the " +"client's `FitRes`. The server then aggregates these contributions to compute " +"the weighted average of model parameters." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:14 of msgid "" -"The protocol involves four main stages: - 'setup': Send SecAgg " -"configuration to clients and collect their public keys. - 'share keys': " -"Broadcast public keys among clients and collect encrypted secret" +"The protocol involves four main stages: - 'setup': Send SecAgg configuration " +"to clients and collect their public keys. - 'share keys': Broadcast public " +"keys among clients and collect encrypted secret" msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:54 of msgid "" -"Each client's private key is split into N shares under the SecAgg " -"protocol, where N is the number of selected clients." +"Each client's private key is split into N shares under the SecAgg protocol, " +"where N is the number of selected clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:56 of msgid "" -"Generally, higher `reconstruction_threshold` means better privacy " -"guarantees but less tolerance to dropouts." +"Generally, higher `reconstruction_threshold` means better privacy guarantees " +"but less tolerance to dropouts." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:60 of msgid "" "When `reconstruction_threshold` is a float, it is interpreted as the " "proportion of the number of all selected clients needed for the " -"reconstruction of a private key. This feature enables flexibility in " -"setting the security threshold relative to the number of selected " -"clients." +"reconstruction of a private key. This feature enables flexibility in setting " +"the security threshold relative to the number of selected clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:64 of @@ -13279,32 +13063,29 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`collect_masked_vectors_stage " -"`\\ " -"\\(driver\\, ...\\)" +":py:obj:`collect_masked_vectors_stage `\\ \\(driver\\, ...\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`setup_stage `\\" -" \\(driver\\, context\\, state\\)" +":py:obj:`setup_stage `\\ " +"\\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`share_keys_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`share_keys_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`unmask_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`unmask_stage `\\ " +"\\(driver\\, context\\, state\\)" msgstr "" #: ../../source/ref-api/flwr.simulation.rst:2 @@ -13313,8 +13094,8 @@ msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 msgid "" -":py:obj:`start_simulation `\\ \\(\\*\\," -" client\\_fn\\[\\, ...\\]\\)" +":py:obj:`start_simulation `\\ \\(\\*\\, " +"client\\_fn\\[\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 @@ -13345,15 +13126,14 @@ msgstr "" #: flwr.simulation.run_simulation.run_simulation:6 of msgid "" -"The `ClientApp` to be executed by each of the SuperNodes. It will receive" -" messages sent by the `ServerApp`." +"The `ClientApp` to be executed by each of the SuperNodes. It will receive " +"messages sent by the `ServerApp`." msgstr "" #: flwr.simulation.run_simulation.run_simulation:9 of msgid "" -"Number of nodes that run a ClientApp. They can be sampled by a Driver in " -"the ServerApp and receive a Message describing what the ClientApp should " -"perform." +"Number of nodes that run a ClientApp. They can be sampled by a Driver in the " +"ServerApp and receive a Message describing what the ClientApp should perform." msgstr "" #: flwr.simulation.run_simulation.run_simulation:13 of @@ -13362,26 +13142,26 @@ msgstr "" #: flwr.simulation.run_simulation.run_simulation:15 of msgid "" -"'A dictionary, e.g {\"\": , \"\": } to " -"configure a backend. Values supported in are those included by " -"`flwr.common.typing.ConfigsRecordValues`." +"'A dictionary, e.g {\"\": , \"\": } to configure a " +"backend. Values supported in are those included by `flwr.common." +"typing.ConfigsRecordValues`." msgstr "" #: flwr.simulation.run_simulation.run_simulation:19 of msgid "" -"A boolean to indicate whether to enable GPU growth on the main thread. " -"This is desirable if you make use of a TensorFlow model on your " -"`ServerApp` while having your `ClientApp` running on the same GPU. " -"Without enabling this, you might encounter an out-of-memory error because" -" TensorFlow, by default, allocates all GPU memory. Read more about how " -"`tf.config.experimental.set_memory_growth()` works in the TensorFlow " -"documentation: https://www.tensorflow.org/api/stable." +"A boolean to indicate whether to enable GPU growth on the main thread. This " +"is desirable if you make use of a TensorFlow model on your `ServerApp` while " +"having your `ClientApp` running on the same GPU. Without enabling this, you " +"might encounter an out-of-memory error because TensorFlow, by default, " +"allocates all GPU memory. Read more about how `tf.config.experimental." +"set_memory_growth()` works in the TensorFlow documentation: https://www." +"tensorflow.org/api/stable." msgstr "" #: flwr.simulation.run_simulation.run_simulation:26 of msgid "" -"When diabled, only INFO, WARNING and ERROR log messages will be shown. If" -" enabled, DEBUG-level logs will be displayed." +"When diabled, only INFO, WARNING and ERROR log messages will be shown. If " +"enabled, DEBUG-level logs will be displayed." msgstr "" #: ../../source/ref-api/flwr.simulation.start_simulation.rst:2 @@ -13390,15 +13170,15 @@ msgstr "" #: flwr.simulation.app.start_simulation:3 of msgid "" -"A function creating client instances. The function must take a single " -"`str` argument called `cid`. It should return a single client instance of" -" type Client. Note that the created client instances are ephemeral and " -"will often be destroyed after a single method invocation. Since client " -"instances are not long-lived, they should not attempt to carry state over" -" method invocations. Any state required by the instance (model, dataset, " +"A function creating client instances. The function must take a single `str` " +"argument called `cid`. It should return a single client instance of type " +"Client. Note that the created client instances are ephemeral and will often " +"be destroyed after a single method invocation. Since client instances are " +"not long-lived, they should not attempt to carry state over method " +"invocations. Any state required by the instance (model, dataset, " "hyperparameters, ...) should be (re-)created in either the call to " -"`client_fn` or the call to any of the client methods (e.g., load " -"evaluation data in the `evaluate` method itself)." +"`client_fn` or the call to any of the client methods (e.g., load evaluation " +"data in the `evaluate` method itself)." msgstr "" #: flwr.simulation.app.start_simulation:13 of @@ -13409,16 +13189,16 @@ msgstr "" #: flwr.simulation.app.start_simulation:16 of msgid "" -"List `client_id`s for each client. This is only required if `num_clients`" -" is not set. Setting both `num_clients` and `clients_ids` with " +"List `client_id`s for each client. This is only required if `num_clients` is " +"not set. Setting both `num_clients` and `clients_ids` with " "`len(clients_ids)` not equal to `num_clients` generates an error." msgstr "" #: flwr.simulation.app.start_simulation:20 of msgid "" -"CPU and GPU resources for a single client. Supported keys are `num_cpus` " -"and `num_gpus`. To understand the GPU utilization caused by `num_gpus`, " -"as well as using custom resources, please consult the Ray documentation." +"CPU and GPU resources for a single client. Supported keys are `num_cpus` and " +"`num_gpus`. To understand the GPU utilization caused by `num_gpus`, as well " +"as using custom resources, please consult the Ray documentation." msgstr "" #: flwr.simulation.app.start_simulation:25 of @@ -13429,16 +13209,16 @@ msgstr "" #: flwr.simulation.app.start_simulation:31 of msgid "" -"An implementation of the abstract base class `flwr.server.Strategy`. If " -"no strategy is provided, then `start_server` will use " -"`flwr.server.strategy.FedAvg`." +"An implementation of the abstract base class `flwr.server.Strategy`. If no " +"strategy is provided, then `start_server` will use `flwr.server.strategy." +"FedAvg`." msgstr "" #: flwr.simulation.app.start_simulation:35 of msgid "" -"An implementation of the abstract base class `flwr.server.ClientManager`." -" If no implementation is provided, then `start_simulation` will use " -"`flwr.server.client_manager.SimpleClientManager`." +"An implementation of the abstract base class `flwr.server.ClientManager`. If " +"no implementation is provided, then `start_simulation` will use `flwr.server." +"client_manager.SimpleClientManager`." msgstr "" #: flwr.simulation.app.start_simulation:39 of @@ -13447,8 +13227,7 @@ msgid "" "ray_init_args is None (the default), Ray will be initialized with the " "following default args: { \"ignore_reinit_error\": True, " "\"include_dashboard\": False } An empty dictionary can be used " -"(ray_init_args={}) to prevent any arguments from being passed to " -"ray.init." +"(ray_init_args={}) to prevent any arguments from being passed to ray.init." msgstr "" #: flwr.simulation.app.start_simulation:39 of @@ -13464,14 +13243,13 @@ msgstr "" #: flwr.simulation.app.start_simulation:45 of msgid "" -"An empty dictionary can be used (ray_init_args={}) to prevent any " -"arguments from being passed to ray.init." +"An empty dictionary can be used (ray_init_args={}) to prevent any arguments " +"from being passed to ray.init." msgstr "" #: flwr.simulation.app.start_simulation:48 of msgid "" -"Set to True to prevent `ray.shutdown()` in case " -"`ray.is_initialized()=True`." +"Set to True to prevent `ray.shutdown()` in case `ray.is_initialized()=True`." msgstr "" #: flwr.simulation.app.start_simulation:50 of @@ -13483,19 +13261,19 @@ msgstr "" #: flwr.simulation.app.start_simulation:54 of msgid "" -"If you want to create your own Actor classes, you might need to pass some" -" input argument. You can use this dictionary for such purpose." +"If you want to create your own Actor classes, you might need to pass some " +"input argument. You can use this dictionary for such purpose." msgstr "" #: flwr.simulation.app.start_simulation:57 of msgid "" -"(default: \"DEFAULT\") Optional string (\"DEFAULT\" or \"SPREAD\") for " -"the VCE to choose in which node the actor is placed. If you are an " -"advanced user needed more control you can use lower-level scheduling " -"strategies to pin actors to specific compute nodes (e.g. via " -"NodeAffinitySchedulingStrategy). Please note this is an advanced feature." -" For all details, please refer to the Ray documentation: " -"https://docs.ray.io/en/latest/ray-core/scheduling/index.html" +"(default: \"DEFAULT\") Optional string (\"DEFAULT\" or \"SPREAD\") for the " +"VCE to choose in which node the actor is placed. If you are an advanced user " +"needed more control you can use lower-level scheduling strategies to pin " +"actors to specific compute nodes (e.g. via NodeAffinitySchedulingStrategy). " +"Please note this is an advanced feature. For all details, please refer to " +"the Ray documentation: https://docs.ray.io/en/latest/ray-core/scheduling/" +"index.html" msgstr "" #: flwr.simulation.app.start_simulation:66 of @@ -13556,244 +13334,219 @@ msgstr "" #: ../../source/ref-changelog.md:356 ../../source/ref-changelog.md:420 #: ../../source/ref-changelog.md:478 msgid "" -"We would like to give our special thanks to all the contributors who made" -" the new version of Flower possible (in `git shortlog` order):" +"We would like to give our special thanks to all the contributors who made " +"the new version of Flower possible (in `git shortlog` order):" msgstr "" #: ../../source/ref-changelog.md:17 msgid "" "`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " -"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear " -"Ashimine`, `Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, " -"`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " -"`tabdar-khan` " +"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear Ashimine`, " +"`Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, `Sebastian van der " +"Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, `tabdar-khan` " msgstr "" #: ../../source/ref-changelog.md:21 msgid "" -"**Introduce Flower Next high-level API (stable)** " -"([#3002](https://github.com/adap/flower/pull/3002), " -"[#2934](https://github.com/adap/flower/pull/2934), " -"[#2958](https://github.com/adap/flower/pull/2958), " -"[#3173](https://github.com/adap/flower/pull/3173), " -"[#3174](https://github.com/adap/flower/pull/3174), " -"[#2923](https://github.com/adap/flower/pull/2923), " -"[#2691](https://github.com/adap/flower/pull/2691), " -"[#3079](https://github.com/adap/flower/pull/3079), " -"[#2961](https://github.com/adap/flower/pull/2961), " -"[#2924](https://github.com/adap/flower/pull/2924), " -"[#3166](https://github.com/adap/flower/pull/3166), " -"[#3031](https://github.com/adap/flower/pull/3031), " -"[#3057](https://github.com/adap/flower/pull/3057), " -"[#3000](https://github.com/adap/flower/pull/3000), " -"[#3113](https://github.com/adap/flower/pull/3113), " -"[#2957](https://github.com/adap/flower/pull/2957), " -"[#3183](https://github.com/adap/flower/pull/3183), " -"[#3180](https://github.com/adap/flower/pull/3180), " -"[#3035](https://github.com/adap/flower/pull/3035), " -"[#3189](https://github.com/adap/flower/pull/3189), " -"[#3185](https://github.com/adap/flower/pull/3185), " -"[#3190](https://github.com/adap/flower/pull/3190), " -"[#3191](https://github.com/adap/flower/pull/3191), " -"[#3195](https://github.com/adap/flower/pull/3195), " -"[#3197](https://github.com/adap/flower/pull/3197))" +"**Introduce Flower Next high-level API (stable)** ([#3002](https://github." +"com/adap/flower/pull/3002), [#2934](https://github.com/adap/flower/" +"pull/2934), [#2958](https://github.com/adap/flower/pull/2958), [#3173]" +"(https://github.com/adap/flower/pull/3173), [#3174](https://github.com/adap/" +"flower/pull/3174), [#2923](https://github.com/adap/flower/pull/2923), [#2691]" +"(https://github.com/adap/flower/pull/2691), [#3079](https://github.com/adap/" +"flower/pull/3079), [#2961](https://github.com/adap/flower/pull/2961), [#2924]" +"(https://github.com/adap/flower/pull/2924), [#3166](https://github.com/adap/" +"flower/pull/3166), [#3031](https://github.com/adap/flower/pull/3031), [#3057]" +"(https://github.com/adap/flower/pull/3057), [#3000](https://github.com/adap/" +"flower/pull/3000), [#3113](https://github.com/adap/flower/pull/3113), [#2957]" +"(https://github.com/adap/flower/pull/2957), [#3183](https://github.com/adap/" +"flower/pull/3183), [#3180](https://github.com/adap/flower/pull/3180), [#3035]" +"(https://github.com/adap/flower/pull/3035), [#3189](https://github.com/adap/" +"flower/pull/3189), [#3185](https://github.com/adap/flower/pull/3185), [#3190]" +"(https://github.com/adap/flower/pull/3190), [#3191](https://github.com/adap/" +"flower/pull/3191), [#3195](https://github.com/adap/flower/pull/3195), [#3197]" +"(https://github.com/adap/flower/pull/3197))" msgstr "" #: ../../source/ref-changelog.md:23 msgid "" "The Flower Next high-level API is stable! Flower Next is the future of " -"Flower - all new features (like Flower Mods) will be built on top of it. " -"You can start to migrate your existing projects to Flower Next by using " -"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or " -"`quickstart-tensorflow`, a detailed migration guide will follow shortly)." -" Flower Next allows you to run multiple projects concurrently (we call " -"this multi-run) and execute the same project in either simulation " -"environments or deployment environments without having to change a single" -" line of code. The best part? It's fully compatible with existing Flower " -"projects that use `Strategy`, `NumPyClient` & co." +"Flower - all new features (like Flower Mods) will be built on top of it. You " +"can start to migrate your existing projects to Flower Next by using " +"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or `quickstart-" +"tensorflow`, a detailed migration guide will follow shortly). Flower Next " +"allows you to run multiple projects concurrently (we call this multi-run) " +"and execute the same project in either simulation environments or deployment " +"environments without having to change a single line of code. The best part? " +"It's fully compatible with existing Flower projects that use `Strategy`, " +"`NumPyClient` & co." msgstr "" #: ../../source/ref-changelog.md:25 msgid "" -"**Introduce Flower Next low-level API (preview)** " -"([#3062](https://github.com/adap/flower/pull/3062), " -"[#3034](https://github.com/adap/flower/pull/3034), " -"[#3069](https://github.com/adap/flower/pull/3069))" +"**Introduce Flower Next low-level API (preview)** ([#3062](https://github." +"com/adap/flower/pull/3062), [#3034](https://github.com/adap/flower/" +"pull/3034), [#3069](https://github.com/adap/flower/pull/3069))" msgstr "" #: ../../source/ref-changelog.md:27 msgid "" "In addition to the Flower Next *high-level* API that uses `Strategy`, " -"`NumPyClient` & co, Flower 1.8 also comes with a preview version of the " -"new Flower Next *low-level* API. The low-level API allows for granular " -"control of every aspect of the learning process by sending/receiving " -"individual messages to/from client nodes. The new `ServerApp` supports " -"registering a custom `main` function that allows writing custom training " -"loops for methods like async FL, cyclic training, or federated analytics." -" The new `ClientApp` supports registering `train`, `evaluate` and `query`" -" functions that can access the raw message received from the `ServerApp`." -" New abstractions like `RecordSet`, `Message` and `Context` further " -"enable sending multiple models, multiple sets of config values and " -"metrics, stateful computations on the client node and implementations of " -"custom SMPC protocols, to name just a few." +"`NumPyClient` & co, Flower 1.8 also comes with a preview version of the new " +"Flower Next *low-level* API. The low-level API allows for granular control " +"of every aspect of the learning process by sending/receiving individual " +"messages to/from client nodes. The new `ServerApp` supports registering a " +"custom `main` function that allows writing custom training loops for methods " +"like async FL, cyclic training, or federated analytics. The new `ClientApp` " +"supports registering `train`, `evaluate` and `query` functions that can " +"access the raw message received from the `ServerApp`. New abstractions like " +"`RecordSet`, `Message` and `Context` further enable sending multiple models, " +"multiple sets of config values and metrics, stateful computations on the " +"client node and implementations of custom SMPC protocols, to name just a few." msgstr "" #: ../../source/ref-changelog.md:29 msgid "" -"**Introduce Flower Mods (preview)** " -"([#3054](https://github.com/adap/flower/pull/3054), " -"[#2911](https://github.com/adap/flower/pull/2911), " -"[#3083](https://github.com/adap/flower/pull/3083))" +"**Introduce Flower Mods (preview)** ([#3054](https://github.com/adap/flower/" +"pull/3054), [#2911](https://github.com/adap/flower/pull/2911), [#3083]" +"(https://github.com/adap/flower/pull/3083))" msgstr "" #: ../../source/ref-changelog.md:31 msgid "" "Flower Modifiers (we call them Mods) can intercept messages and analyze, " -"edit or handle them directly. Mods can be used to develop pluggable " -"modules that work across different projects. Flower 1.8 already includes " -"mods to log the size of a message, the number of parameters sent over the" -" network, differential privacy with fixed clipping and adaptive clipping," -" local differential privacy and secure aggregation protocols SecAgg and " -"SecAgg+. The Flower Mods API is released as a preview, but researchers " -"can already use it to experiment with arbirtrary SMPC protocols." +"edit or handle them directly. Mods can be used to develop pluggable modules " +"that work across different projects. Flower 1.8 already includes mods to log " +"the size of a message, the number of parameters sent over the network, " +"differential privacy with fixed clipping and adaptive clipping, local " +"differential privacy and secure aggregation protocols SecAgg and SecAgg+. " +"The Flower Mods API is released as a preview, but researchers can already " +"use it to experiment with arbirtrary SMPC protocols." msgstr "" #: ../../source/ref-changelog.md:33 msgid "" -"**Fine-tune LLMs with LLM FlowerTune** " -"([#3029](https://github.com/adap/flower/pull/3029), " -"[#3089](https://github.com/adap/flower/pull/3089), " -"[#3092](https://github.com/adap/flower/pull/3092), " -"[#3100](https://github.com/adap/flower/pull/3100), " -"[#3114](https://github.com/adap/flower/pull/3114), " -"[#3162](https://github.com/adap/flower/pull/3162), " -"[#3172](https://github.com/adap/flower/pull/3172))" +"**Fine-tune LLMs with LLM FlowerTune** ([#3029](https://github.com/adap/" +"flower/pull/3029), [#3089](https://github.com/adap/flower/pull/3089), [#3092]" +"(https://github.com/adap/flower/pull/3092), [#3100](https://github.com/adap/" +"flower/pull/3100), [#3114](https://github.com/adap/flower/pull/3114), [#3162]" +"(https://github.com/adap/flower/pull/3162), [#3172](https://github.com/adap/" +"flower/pull/3172))" msgstr "" #: ../../source/ref-changelog.md:35 msgid "" -"We are introducing LLM FlowerTune, an introductory example that " -"demonstrates federated LLM fine-tuning of pre-trained Llama2 models on " -"the Alpaca-GPT4 dataset. The example is built to be easily adapted to use" -" different models and/or datasets. Read our blog post [LLM FlowerTune: " -"Federated LLM Fine-tuning with Flower](https://flower.ai/blog/2024-03-14" -"-llm-flowertune-federated-llm-finetuning-with-flower/) for more details." +"We are introducing LLM FlowerTune, an introductory example that demonstrates " +"federated LLM fine-tuning of pre-trained Llama2 models on the Alpaca-GPT4 " +"dataset. The example is built to be easily adapted to use different models " +"and/or datasets. Read our blog post [LLM FlowerTune: Federated LLM Fine-" +"tuning with Flower](https://flower.ai/blog/2024-03-14-llm-flowertune-" +"federated-llm-finetuning-with-flower/) for more details." msgstr "" #: ../../source/ref-changelog.md:37 msgid "" -"**Introduce built-in Differential Privacy (preview)** " -"([#2798](https://github.com/adap/flower/pull/2798), " -"[#2959](https://github.com/adap/flower/pull/2959), " -"[#3038](https://github.com/adap/flower/pull/3038), " -"[#3147](https://github.com/adap/flower/pull/3147), " -"[#2909](https://github.com/adap/flower/pull/2909), " -"[#2893](https://github.com/adap/flower/pull/2893), " -"[#2892](https://github.com/adap/flower/pull/2892), " -"[#3039](https://github.com/adap/flower/pull/3039), " -"[#3074](https://github.com/adap/flower/pull/3074))" +"**Introduce built-in Differential Privacy (preview)** ([#2798](https://" +"github.com/adap/flower/pull/2798), [#2959](https://github.com/adap/flower/" +"pull/2959), [#3038](https://github.com/adap/flower/pull/3038), [#3147]" +"(https://github.com/adap/flower/pull/3147), [#2909](https://github.com/adap/" +"flower/pull/2909), [#2893](https://github.com/adap/flower/pull/2893), [#2892]" +"(https://github.com/adap/flower/pull/2892), [#3039](https://github.com/adap/" +"flower/pull/3039), [#3074](https://github.com/adap/flower/pull/3074))" msgstr "" #: ../../source/ref-changelog.md:39 msgid "" "Built-in Differential Privacy is here! Flower supports both central and " -"local differential privacy (DP). Central DP can be configured with either" -" fixed or adaptive clipping. The clipping can happen either on the " -"server-side or the client-side. Local DP does both clipping and noising " -"on the client-side. A new documentation page [explains Differential " -"Privacy approaches](https://flower.ai/docs/framework/explanation-" -"differential-privacy.html) and a new how-to guide describes [how to use " -"the new Differential Privacy components](https://flower.ai/docs/framework" -"/how-to-use-differential-privacy.html) in Flower." +"local differential privacy (DP). Central DP can be configured with either " +"fixed or adaptive clipping. The clipping can happen either on the server-" +"side or the client-side. Local DP does both clipping and noising on the " +"client-side. A new documentation page [explains Differential Privacy " +"approaches](https://flower.ai/docs/framework/explanation-differential-" +"privacy.html) and a new how-to guide describes [how to use the new " +"Differential Privacy components](https://flower.ai/docs/framework/how-to-use-" +"differential-privacy.html) in Flower." msgstr "" #: ../../source/ref-changelog.md:41 msgid "" -"**Introduce built-in Secure Aggregation (preview)** " -"([#3120](https://github.com/adap/flower/pull/3120), " -"[#3110](https://github.com/adap/flower/pull/3110), " -"[#3108](https://github.com/adap/flower/pull/3108))" +"**Introduce built-in Secure Aggregation (preview)** ([#3120](https://github." +"com/adap/flower/pull/3120), [#3110](https://github.com/adap/flower/" +"pull/3110), [#3108](https://github.com/adap/flower/pull/3108))" msgstr "" #: ../../source/ref-changelog.md:43 msgid "" -"Built-in Secure Aggregation is here! Flower now supports different secure" -" aggregation protocols out-of-the-box. The best part? You can add secure " -"aggregation to your Flower projects with only a few lines of code. In " -"this initial release, we inlcude support for SecAgg and SecAgg+, but more" -" protocols will be implemented shortly. We'll also add detailed docs that" -" explain secure aggregation and how to use it in Flower. You can already " +"Built-in Secure Aggregation is here! Flower now supports different secure " +"aggregation protocols out-of-the-box. The best part? You can add secure " +"aggregation to your Flower projects with only a few lines of code. In this " +"initial release, we inlcude support for SecAgg and SecAgg+, but more " +"protocols will be implemented shortly. We'll also add detailed docs that " +"explain secure aggregation and how to use it in Flower. You can already " "check out the new code example that shows how to use Flower to easily " -"combine Federated Learning, Differential Privacy and Secure Aggregation " -"in the same project." +"combine Federated Learning, Differential Privacy and Secure Aggregation in " +"the same project." msgstr "" #: ../../source/ref-changelog.md:45 msgid "" -"**Introduce** `flwr` **CLI (preview)** " -"([#2942](https://github.com/adap/flower/pull/2942), " -"[#3055](https://github.com/adap/flower/pull/3055), " -"[#3111](https://github.com/adap/flower/pull/3111), " -"[#3130](https://github.com/adap/flower/pull/3130), " -"[#3136](https://github.com/adap/flower/pull/3136), " -"[#3094](https://github.com/adap/flower/pull/3094), " -"[#3059](https://github.com/adap/flower/pull/3059), " -"[#3049](https://github.com/adap/flower/pull/3049), " -"[#3142](https://github.com/adap/flower/pull/3142))" +"**Introduce** `flwr` **CLI (preview)** ([#2942](https://github.com/adap/" +"flower/pull/2942), [#3055](https://github.com/adap/flower/pull/3055), [#3111]" +"(https://github.com/adap/flower/pull/3111), [#3130](https://github.com/adap/" +"flower/pull/3130), [#3136](https://github.com/adap/flower/pull/3136), [#3094]" +"(https://github.com/adap/flower/pull/3094), [#3059](https://github.com/adap/" +"flower/pull/3059), [#3049](https://github.com/adap/flower/pull/3049), [#3142]" +"(https://github.com/adap/flower/pull/3142))" msgstr "" #: ../../source/ref-changelog.md:47 msgid "" -"A new `flwr` CLI command allows creating new Flower projects (`flwr new`)" -" and then running them using the Simulation Engine (`flwr run`)." +"A new `flwr` CLI command allows creating new Flower projects (`flwr new`) " +"and then running them using the Simulation Engine (`flwr run`)." msgstr "" #: ../../source/ref-changelog.md:49 msgid "" -"**Introduce Flower Next Simulation Engine** " -"([#3024](https://github.com/adap/flower/pull/3024), " -"[#3061](https://github.com/adap/flower/pull/3061), " -"[#2997](https://github.com/adap/flower/pull/2997), " -"[#2783](https://github.com/adap/flower/pull/2783), " -"[#3184](https://github.com/adap/flower/pull/3184), " -"[#3075](https://github.com/adap/flower/pull/3075), " -"[#3047](https://github.com/adap/flower/pull/3047), " -"[#2998](https://github.com/adap/flower/pull/2998), " -"[#3009](https://github.com/adap/flower/pull/3009), " -"[#3008](https://github.com/adap/flower/pull/3008))" +"**Introduce Flower Next Simulation Engine** ([#3024](https://github.com/adap/" +"flower/pull/3024), [#3061](https://github.com/adap/flower/pull/3061), [#2997]" +"(https://github.com/adap/flower/pull/2997), [#2783](https://github.com/adap/" +"flower/pull/2783), [#3184](https://github.com/adap/flower/pull/3184), [#3075]" +"(https://github.com/adap/flower/pull/3075), [#3047](https://github.com/adap/" +"flower/pull/3047), [#2998](https://github.com/adap/flower/pull/2998), [#3009]" +"(https://github.com/adap/flower/pull/3009), [#3008](https://github.com/adap/" +"flower/pull/3008))" msgstr "" #: ../../source/ref-changelog.md:51 msgid "" -"The Flower Simulation Engine can now run Flower Next projects. For " -"notebook environments, there's also a new `run_simulation` function that " -"can run `ServerApp` and `ClientApp`." +"The Flower Simulation Engine can now run Flower Next projects. For notebook " +"environments, there's also a new `run_simulation` function that can run " +"`ServerApp` and `ClientApp`." msgstr "" #: ../../source/ref-changelog.md:53 msgid "" -"**Handle SuperNode connection errors** " -"([#2969](https://github.com/adap/flower/pull/2969))" +"**Handle SuperNode connection errors** ([#2969](https://github.com/adap/" +"flower/pull/2969))" msgstr "" #: ../../source/ref-changelog.md:55 msgid "" -"A SuperNode will now try to reconnect indefinitely to the SuperLink in " -"case of connection errors. The arguments `--max-retries` and `--max-wait-" -"time` can now be passed to the `flower-client-app` command. `--max-" -"retries` will define the number of tentatives the client should make " -"before it gives up trying to reconnect to the SuperLink, and, `--max-" -"wait-time` defines the time before the SuperNode gives up trying to " -"reconnect to the SuperLink." +"A SuperNode will now try to reconnect indefinitely to the SuperLink in case " +"of connection errors. The arguments `--max-retries` and `--max-wait-time` " +"can now be passed to the `flower-client-app` command. `--max-retries` will " +"define the number of tentatives the client should make before it gives up " +"trying to reconnect to the SuperLink, and, `--max-wait-time` defines the " +"time before the SuperNode gives up trying to reconnect to the SuperLink." msgstr "" #: ../../source/ref-changelog.md:57 msgid "" -"**General updates to Flower Baselines** " -"([#2904](https://github.com/adap/flower/pull/2904), " -"[#2482](https://github.com/adap/flower/pull/2482), " -"[#2985](https://github.com/adap/flower/pull/2985), " -"[#2968](https://github.com/adap/flower/pull/2968))" +"**General updates to Flower Baselines** ([#2904](https://github.com/adap/" +"flower/pull/2904), [#2482](https://github.com/adap/flower/pull/2482), [#2985]" +"(https://github.com/adap/flower/pull/2985), [#2968](https://github.com/adap/" +"flower/pull/2968))" msgstr "" #: ../../source/ref-changelog.md:59 @@ -13804,133 +13557,100 @@ msgstr "" #: ../../source/ref-changelog.md:61 msgid "" -"**Improve documentation and translations** " -"([#3050](https://github.com/adap/flower/pull/3050), " -"[#3044](https://github.com/adap/flower/pull/3044), " -"[#3043](https://github.com/adap/flower/pull/3043), " -"[#2986](https://github.com/adap/flower/pull/2986), " -"[#3041](https://github.com/adap/flower/pull/3041), " -"[#3046](https://github.com/adap/flower/pull/3046), " -"[#3042](https://github.com/adap/flower/pull/3042), " -"[#2978](https://github.com/adap/flower/pull/2978), " -"[#2952](https://github.com/adap/flower/pull/2952), " -"[#3167](https://github.com/adap/flower/pull/3167), " -"[#2953](https://github.com/adap/flower/pull/2953), " -"[#3045](https://github.com/adap/flower/pull/3045), " -"[#2654](https://github.com/adap/flower/pull/2654), " -"[#3082](https://github.com/adap/flower/pull/3082), " -"[#2990](https://github.com/adap/flower/pull/2990), " -"[#2989](https://github.com/adap/flower/pull/2989))" +"**Improve documentation and translations** ([#3050](https://github.com/adap/" +"flower/pull/3050), [#3044](https://github.com/adap/flower/pull/3044), [#3043]" +"(https://github.com/adap/flower/pull/3043), [#2986](https://github.com/adap/" +"flower/pull/2986), [#3041](https://github.com/adap/flower/pull/3041), [#3046]" +"(https://github.com/adap/flower/pull/3046), [#3042](https://github.com/adap/" +"flower/pull/3042), [#2978](https://github.com/adap/flower/pull/2978), [#2952]" +"(https://github.com/adap/flower/pull/2952), [#3167](https://github.com/adap/" +"flower/pull/3167), [#2953](https://github.com/adap/flower/pull/2953), [#3045]" +"(https://github.com/adap/flower/pull/3045), [#2654](https://github.com/adap/" +"flower/pull/2654), [#3082](https://github.com/adap/flower/pull/3082), [#2990]" +"(https://github.com/adap/flower/pull/2990), [#2989](https://github.com/adap/" +"flower/pull/2989))" msgstr "" #: ../../source/ref-changelog.md:63 msgid "" "As usual, we merged many smaller and larger improvements to the " -"documentation. A special thank you goes to [Sebastian van der " -"Voort](https://github.com/svdvoort) for landing a big documentation PR!" +"documentation. A special thank you goes to [Sebastian van der Voort](https://" +"github.com/svdvoort) for landing a big documentation PR!" msgstr "" #: ../../source/ref-changelog.md:65 msgid "" -"**General updates to Flower Examples** " -"([3134](https://github.com/adap/flower/pull/3134), " -"[2996](https://github.com/adap/flower/pull/2996), " -"[2930](https://github.com/adap/flower/pull/2930), " -"[2967](https://github.com/adap/flower/pull/2967), " -"[2467](https://github.com/adap/flower/pull/2467), " -"[2910](https://github.com/adap/flower/pull/2910), " -"[#2918](https://github.com/adap/flower/pull/2918), " -"[#2773](https://github.com/adap/flower/pull/2773), " -"[#3063](https://github.com/adap/flower/pull/3063), " -"[#3116](https://github.com/adap/flower/pull/3116), " -"[#3117](https://github.com/adap/flower/pull/3117))" +"**General updates to Flower Examples** ([3134](https://github.com/adap/" +"flower/pull/3134), [2996](https://github.com/adap/flower/pull/2996), [2930]" +"(https://github.com/adap/flower/pull/2930), [2967](https://github.com/adap/" +"flower/pull/2967), [2467](https://github.com/adap/flower/pull/2467), [2910]" +"(https://github.com/adap/flower/pull/2910), [#2918](https://github.com/adap/" +"flower/pull/2918), [#2773](https://github.com/adap/flower/pull/2773), [#3063]" +"(https://github.com/adap/flower/pull/3063), [#3116](https://github.com/adap/" +"flower/pull/3116), [#3117](https://github.com/adap/flower/pull/3117))" msgstr "" #: ../../source/ref-changelog.md:67 msgid "" -"Two new examples show federated training of a Vision Transformer (ViT) " -"and federated learning in a medical context using the popular MONAI " -"library. `quickstart-pytorch` and `quickstart-tensorflow` demonstrate the" -" new Flower Next `ServerApp` and `ClientApp`. Many other examples " -"received considerable updates as well." +"Two new examples show federated training of a Vision Transformer (ViT) and " +"federated learning in a medical context using the popular MONAI library. " +"`quickstart-pytorch` and `quickstart-tensorflow` demonstrate the new Flower " +"Next `ServerApp` and `ClientApp`. Many other examples received considerable " +"updates as well." msgstr "" #: ../../source/ref-changelog.md:69 msgid "" -"**General improvements** " -"([#3171](https://github.com/adap/flower/pull/3171), " -"[3099](https://github.com/adap/flower/pull/3099), " -"[3003](https://github.com/adap/flower/pull/3003), " -"[3145](https://github.com/adap/flower/pull/3145), " -"[3017](https://github.com/adap/flower/pull/3017), " -"[3085](https://github.com/adap/flower/pull/3085), " -"[3012](https://github.com/adap/flower/pull/3012), " -"[3119](https://github.com/adap/flower/pull/3119), " -"[2991](https://github.com/adap/flower/pull/2991), " -"[2970](https://github.com/adap/flower/pull/2970), " -"[2980](https://github.com/adap/flower/pull/2980), " -"[3086](https://github.com/adap/flower/pull/3086), " -"[2932](https://github.com/adap/flower/pull/2932), " -"[2928](https://github.com/adap/flower/pull/2928), " -"[2941](https://github.com/adap/flower/pull/2941), " -"[2933](https://github.com/adap/flower/pull/2933), " -"[3181](https://github.com/adap/flower/pull/3181), " -"[2973](https://github.com/adap/flower/pull/2973), " -"[2992](https://github.com/adap/flower/pull/2992), " -"[2915](https://github.com/adap/flower/pull/2915), " -"[3040](https://github.com/adap/flower/pull/3040), " -"[3022](https://github.com/adap/flower/pull/3022), " -"[3032](https://github.com/adap/flower/pull/3032), " -"[2902](https://github.com/adap/flower/pull/2902), " -"[2931](https://github.com/adap/flower/pull/2931), " -"[3005](https://github.com/adap/flower/pull/3005), " -"[3132](https://github.com/adap/flower/pull/3132), " -"[3115](https://github.com/adap/flower/pull/3115), " -"[2944](https://github.com/adap/flower/pull/2944), " -"[3064](https://github.com/adap/flower/pull/3064), " -"[3106](https://github.com/adap/flower/pull/3106), " -"[2974](https://github.com/adap/flower/pull/2974), " -"[3178](https://github.com/adap/flower/pull/3178), " -"[2993](https://github.com/adap/flower/pull/2993), " -"[3186](https://github.com/adap/flower/pull/3186), " -"[3091](https://github.com/adap/flower/pull/3091), " -"[3125](https://github.com/adap/flower/pull/3125), " -"[3093](https://github.com/adap/flower/pull/3093), " -"[3013](https://github.com/adap/flower/pull/3013), " -"[3033](https://github.com/adap/flower/pull/3033), " -"[3133](https://github.com/adap/flower/pull/3133), " -"[3068](https://github.com/adap/flower/pull/3068), " -"[2916](https://github.com/adap/flower/pull/2916), " -"[2975](https://github.com/adap/flower/pull/2975), " -"[2984](https://github.com/adap/flower/pull/2984), " -"[2846](https://github.com/adap/flower/pull/2846), " -"[3077](https://github.com/adap/flower/pull/3077), " -"[3143](https://github.com/adap/flower/pull/3143), " -"[2921](https://github.com/adap/flower/pull/2921), " -"[3101](https://github.com/adap/flower/pull/3101), " -"[2927](https://github.com/adap/flower/pull/2927), " -"[2995](https://github.com/adap/flower/pull/2995), " -"[2972](https://github.com/adap/flower/pull/2972), " -"[2912](https://github.com/adap/flower/pull/2912), " -"[3065](https://github.com/adap/flower/pull/3065), " -"[3028](https://github.com/adap/flower/pull/3028), " -"[2922](https://github.com/adap/flower/pull/2922), " -"[2982](https://github.com/adap/flower/pull/2982), " -"[2914](https://github.com/adap/flower/pull/2914), " -"[3179](https://github.com/adap/flower/pull/3179), " -"[3080](https://github.com/adap/flower/pull/3080), " -"[2994](https://github.com/adap/flower/pull/2994), " -"[3187](https://github.com/adap/flower/pull/3187), " -"[2926](https://github.com/adap/flower/pull/2926), " -"[3018](https://github.com/adap/flower/pull/3018), " -"[3144](https://github.com/adap/flower/pull/3144), " -"[3011](https://github.com/adap/flower/pull/3011), " -"[#3152](https://github.com/adap/flower/pull/3152), " -"[#2836](https://github.com/adap/flower/pull/2836), " -"[#2929](https://github.com/adap/flower/pull/2929), " -"[#2943](https://github.com/adap/flower/pull/2943), " -"[#2955](https://github.com/adap/flower/pull/2955), " -"[#2954](https://github.com/adap/flower/pull/2954))" +"**General improvements** ([#3171](https://github.com/adap/flower/pull/3171), " +"[3099](https://github.com/adap/flower/pull/3099), [3003](https://github.com/" +"adap/flower/pull/3003), [3145](https://github.com/adap/flower/pull/3145), " +"[3017](https://github.com/adap/flower/pull/3017), [3085](https://github.com/" +"adap/flower/pull/3085), [3012](https://github.com/adap/flower/pull/3012), " +"[3119](https://github.com/adap/flower/pull/3119), [2991](https://github.com/" +"adap/flower/pull/2991), [2970](https://github.com/adap/flower/pull/2970), " +"[2980](https://github.com/adap/flower/pull/2980), [3086](https://github.com/" +"adap/flower/pull/3086), [2932](https://github.com/adap/flower/pull/2932), " +"[2928](https://github.com/adap/flower/pull/2928), [2941](https://github.com/" +"adap/flower/pull/2941), [2933](https://github.com/adap/flower/pull/2933), " +"[3181](https://github.com/adap/flower/pull/3181), [2973](https://github.com/" +"adap/flower/pull/2973), [2992](https://github.com/adap/flower/pull/2992), " +"[2915](https://github.com/adap/flower/pull/2915), [3040](https://github.com/" +"adap/flower/pull/3040), [3022](https://github.com/adap/flower/pull/3022), " +"[3032](https://github.com/adap/flower/pull/3032), [2902](https://github.com/" +"adap/flower/pull/2902), [2931](https://github.com/adap/flower/pull/2931), " +"[3005](https://github.com/adap/flower/pull/3005), [3132](https://github.com/" +"adap/flower/pull/3132), [3115](https://github.com/adap/flower/pull/3115), " +"[2944](https://github.com/adap/flower/pull/2944), [3064](https://github.com/" +"adap/flower/pull/3064), [3106](https://github.com/adap/flower/pull/3106), " +"[2974](https://github.com/adap/flower/pull/2974), [3178](https://github.com/" +"adap/flower/pull/3178), [2993](https://github.com/adap/flower/pull/2993), " +"[3186](https://github.com/adap/flower/pull/3186), [3091](https://github.com/" +"adap/flower/pull/3091), [3125](https://github.com/adap/flower/pull/3125), " +"[3093](https://github.com/adap/flower/pull/3093), [3013](https://github.com/" +"adap/flower/pull/3013), [3033](https://github.com/adap/flower/pull/3033), " +"[3133](https://github.com/adap/flower/pull/3133), [3068](https://github.com/" +"adap/flower/pull/3068), [2916](https://github.com/adap/flower/pull/2916), " +"[2975](https://github.com/adap/flower/pull/2975), [2984](https://github.com/" +"adap/flower/pull/2984), [2846](https://github.com/adap/flower/pull/2846), " +"[3077](https://github.com/adap/flower/pull/3077), [3143](https://github.com/" +"adap/flower/pull/3143), [2921](https://github.com/adap/flower/pull/2921), " +"[3101](https://github.com/adap/flower/pull/3101), [2927](https://github.com/" +"adap/flower/pull/2927), [2995](https://github.com/adap/flower/pull/2995), " +"[2972](https://github.com/adap/flower/pull/2972), [2912](https://github.com/" +"adap/flower/pull/2912), [3065](https://github.com/adap/flower/pull/3065), " +"[3028](https://github.com/adap/flower/pull/3028), [2922](https://github.com/" +"adap/flower/pull/2922), [2982](https://github.com/adap/flower/pull/2982), " +"[2914](https://github.com/adap/flower/pull/2914), [3179](https://github.com/" +"adap/flower/pull/3179), [3080](https://github.com/adap/flower/pull/3080), " +"[2994](https://github.com/adap/flower/pull/2994), [3187](https://github.com/" +"adap/flower/pull/3187), [2926](https://github.com/adap/flower/pull/2926), " +"[3018](https://github.com/adap/flower/pull/3018), [3144](https://github.com/" +"adap/flower/pull/3144), [3011](https://github.com/adap/flower/pull/3011), " +"[#3152](https://github.com/adap/flower/pull/3152), [#2836](https://github." +"com/adap/flower/pull/2836), [#2929](https://github.com/adap/flower/" +"pull/2929), [#2943](https://github.com/adap/flower/pull/2943), [#2955]" +"(https://github.com/adap/flower/pull/2955), [#2954](https://github.com/adap/" +"flower/pull/2954))" msgstr "" #: ../../source/ref-changelog.md:75 @@ -13939,97 +13659,91 @@ msgstr "" #: ../../source/ref-changelog.md:81 msgid "" -"`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles " -"Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo " -"Gabrielli`, `Gustavo Bertoli`, `HelinLin`, `Heng Pan`, `Javier`, `M S " -"Chaitanya Kumar`, `Mohammad Naseri`, `Nikos Vlachakis`, `Pritam Neog`, " -"`Robert Kuska`, `Robert Steiner`, `Taner Topal`, `Yahia Salaheldin " -"Shaaban`, `Yan Gao`, `Yasar Abbas` " +"`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles Beauville`, " +"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " +"Bertoli`, `HelinLin`, `Heng Pan`, `Javier`, `M S Chaitanya Kumar`, `Mohammad " +"Naseri`, `Nikos Vlachakis`, `Pritam Neog`, `Robert Kuska`, `Robert Steiner`, " +"`Taner Topal`, `Yahia Salaheldin Shaaban`, `Yan Gao`, `Yasar Abbas` " msgstr "" #: ../../source/ref-changelog.md:85 msgid "" -"**Introduce stateful clients (experimental)** " -"([#2770](https://github.com/adap/flower/pull/2770), " -"[#2686](https://github.com/adap/flower/pull/2686), " -"[#2696](https://github.com/adap/flower/pull/2696), " -"[#2643](https://github.com/adap/flower/pull/2643), " -"[#2769](https://github.com/adap/flower/pull/2769))" +"**Introduce stateful clients (experimental)** ([#2770](https://github.com/" +"adap/flower/pull/2770), [#2686](https://github.com/adap/flower/pull/2686), " +"[#2696](https://github.com/adap/flower/pull/2696), [#2643](https://github." +"com/adap/flower/pull/2643), [#2769](https://github.com/adap/flower/" +"pull/2769))" msgstr "" #: ../../source/ref-changelog.md:87 msgid "" "Subclasses of `Client` and `NumPyClient` can now store local state that " "remains on the client. Let's start with the highlight first: this new " -"feature is compatible with both simulated clients (via " -"`start_simulation`) and networked clients (via `start_client`). It's also" -" the first preview of new abstractions like `Context` and `RecordSet`. " -"Clients can access state of type `RecordSet` via `state: RecordSet = " -"self.context.state`. Changes to this `RecordSet` are preserved across " -"different rounds of execution to enable stateful computations in a " -"unified way across simulation and deployment." +"feature is compatible with both simulated clients (via `start_simulation`) " +"and networked clients (via `start_client`). It's also the first preview of " +"new abstractions like `Context` and `RecordSet`. Clients can access state of " +"type `RecordSet` via `state: RecordSet = self.context.state`. Changes to " +"this `RecordSet` are preserved across different rounds of execution to " +"enable stateful computations in a unified way across simulation and " +"deployment." msgstr "" #: ../../source/ref-changelog.md:89 msgid "" -"**Improve performance** " -"([#2293](https://github.com/adap/flower/pull/2293))" +"**Improve performance** ([#2293](https://github.com/adap/flower/pull/2293))" msgstr "" #: ../../source/ref-changelog.md:91 msgid "" -"Flower is faster than ever. All `FedAvg`-derived strategies now use in-" -"place aggregation to reduce memory consumption. The Flower client " -"serialization/deserialization has been rewritten from the ground up, " -"which results in significant speedups, especially when the client-side " -"training time is short." +"Flower is faster than ever. All `FedAvg`-derived strategies now use in-place " +"aggregation to reduce memory consumption. The Flower client serialization/" +"deserialization has been rewritten from the ground up, which results in " +"significant speedups, especially when the client-side training time is short." msgstr "" #: ../../source/ref-changelog.md:93 msgid "" -"**Support Federated Learning with Apple MLX and Flower** " -"([#2693](https://github.com/adap/flower/pull/2693))" +"**Support Federated Learning with Apple MLX and Flower** ([#2693](https://" +"github.com/adap/flower/pull/2693))" msgstr "" #: ../../source/ref-changelog.md:95 msgid "" -"Flower has official support for federated learning using [Apple " -"MLX](https://ml-explore.github.io/mlx) via the new `quickstart-mlx` code " -"example." +"Flower has official support for federated learning using [Apple MLX](https://" +"ml-explore.github.io/mlx) via the new `quickstart-mlx` code example." msgstr "" #: ../../source/ref-changelog.md:97 msgid "" -"**Introduce new XGBoost cyclic strategy** " -"([#2666](https://github.com/adap/flower/pull/2666), " -"[#2668](https://github.com/adap/flower/pull/2668))" +"**Introduce new XGBoost cyclic strategy** ([#2666](https://github.com/adap/" +"flower/pull/2666), [#2668](https://github.com/adap/flower/pull/2668))" msgstr "" #: ../../source/ref-changelog.md:99 msgid "" -"A new strategy called `FedXgbCyclic` supports a client-by-client style of" -" training (often called cyclic). The `xgboost-comprehensive` code example" -" shows how to use it in a full project. In addition to that, `xgboost-" -"comprehensive` now also supports simulation mode. With this, Flower " -"offers best-in-class XGBoost support." +"A new strategy called `FedXgbCyclic` supports a client-by-client style of " +"training (often called cyclic). The `xgboost-comprehensive` code example " +"shows how to use it in a full project. In addition to that, `xgboost-" +"comprehensive` now also supports simulation mode. With this, Flower offers " +"best-in-class XGBoost support." msgstr "" #: ../../source/ref-changelog.md:101 msgid "" -"**Support Python 3.11** " -"([#2394](https://github.com/adap/flower/pull/2394))" +"**Support Python 3.11** ([#2394](https://github.com/adap/flower/pull/2394))" msgstr "" #: ../../source/ref-changelog.md:103 msgid "" -"Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will " -"ensure better support for users using more recent Python versions." +"Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will ensure " +"better support for users using more recent Python versions." msgstr "" #: ../../source/ref-changelog.md:105 msgid "" -"**Update gRPC and ProtoBuf dependencies** " -"([#2814](https://github.com/adap/flower/pull/2814))" +"**Update gRPC and ProtoBuf dependencies** ([#2814](https://github.com/adap/" +"flower/pull/2814))" msgstr "" #: ../../source/ref-changelog.md:107 @@ -14040,72 +13754,65 @@ msgstr "" #: ../../source/ref-changelog.md:109 msgid "" -"**Introduce Docker image for Flower server** " -"([#2700](https://github.com/adap/flower/pull/2700), " -"[#2688](https://github.com/adap/flower/pull/2688), " -"[#2705](https://github.com/adap/flower/pull/2705), " -"[#2695](https://github.com/adap/flower/pull/2695), " -"[#2747](https://github.com/adap/flower/pull/2747), " -"[#2746](https://github.com/adap/flower/pull/2746), " -"[#2680](https://github.com/adap/flower/pull/2680), " -"[#2682](https://github.com/adap/flower/pull/2682), " -"[#2701](https://github.com/adap/flower/pull/2701))" +"**Introduce Docker image for Flower server** ([#2700](https://github.com/" +"adap/flower/pull/2700), [#2688](https://github.com/adap/flower/pull/2688), " +"[#2705](https://github.com/adap/flower/pull/2705), [#2695](https://github." +"com/adap/flower/pull/2695), [#2747](https://github.com/adap/flower/" +"pull/2747), [#2746](https://github.com/adap/flower/pull/2746), [#2680]" +"(https://github.com/adap/flower/pull/2680), [#2682](https://github.com/adap/" +"flower/pull/2682), [#2701](https://github.com/adap/flower/pull/2701))" msgstr "" #: ../../source/ref-changelog.md:111 msgid "" -"The Flower server can now be run using an official Docker image. A new " -"how-to guide explains [how to run Flower using " -"Docker](https://flower.ai/docs/framework/how-to-run-flower-using-" -"docker.html). An official Flower client Docker image will follow." +"The Flower server can now be run using an official Docker image. A new how-" +"to guide explains [how to run Flower using Docker](https://flower.ai/docs/" +"framework/how-to-run-flower-using-docker.html). An official Flower client " +"Docker image will follow." msgstr "" #: ../../source/ref-changelog.md:113 msgid "" -"**Introduce** `flower-via-docker-compose` **example** " -"([#2626](https://github.com/adap/flower/pull/2626))" +"**Introduce** `flower-via-docker-compose` **example** ([#2626](https://" +"github.com/adap/flower/pull/2626))" msgstr "" #: ../../source/ref-changelog.md:115 msgid "" -"**Introduce** `quickstart-sklearn-tabular` **example** " -"([#2719](https://github.com/adap/flower/pull/2719))" +"**Introduce** `quickstart-sklearn-tabular` **example** ([#2719](https://" +"github.com/adap/flower/pull/2719))" msgstr "" #: ../../source/ref-changelog.md:117 msgid "" -"**Introduce** `custom-metrics` **example** " -"([#1958](https://github.com/adap/flower/pull/1958))" +"**Introduce** `custom-metrics` **example** ([#1958](https://github.com/adap/" +"flower/pull/1958))" msgstr "" #: ../../source/ref-changelog.md:119 msgid "" -"**Update code examples to use Flower Datasets** " -"([#2450](https://github.com/adap/flower/pull/2450), " -"[#2456](https://github.com/adap/flower/pull/2456), " -"[#2318](https://github.com/adap/flower/pull/2318), " -"[#2712](https://github.com/adap/flower/pull/2712))" +"**Update code examples to use Flower Datasets** ([#2450](https://github.com/" +"adap/flower/pull/2450), [#2456](https://github.com/adap/flower/pull/2456), " +"[#2318](https://github.com/adap/flower/pull/2318), [#2712](https://github." +"com/adap/flower/pull/2712))" msgstr "" #: ../../source/ref-changelog.md:121 msgid "" -"Several code examples were updated to use [Flower " -"Datasets](https://flower.ai/docs/datasets/)." +"Several code examples were updated to use [Flower Datasets](https://flower." +"ai/docs/datasets/)." msgstr "" #: ../../source/ref-changelog.md:123 msgid "" -"**General updates to Flower Examples** " -"([#2381](https://github.com/adap/flower/pull/2381), " -"[#2805](https://github.com/adap/flower/pull/2805), " -"[#2782](https://github.com/adap/flower/pull/2782), " -"[#2806](https://github.com/adap/flower/pull/2806), " -"[#2829](https://github.com/adap/flower/pull/2829), " -"[#2825](https://github.com/adap/flower/pull/2825), " -"[#2816](https://github.com/adap/flower/pull/2816), " -"[#2726](https://github.com/adap/flower/pull/2726), " -"[#2659](https://github.com/adap/flower/pull/2659), " -"[#2655](https://github.com/adap/flower/pull/2655))" +"**General updates to Flower Examples** ([#2381](https://github.com/adap/" +"flower/pull/2381), [#2805](https://github.com/adap/flower/pull/2805), [#2782]" +"(https://github.com/adap/flower/pull/2782), [#2806](https://github.com/adap/" +"flower/pull/2806), [#2829](https://github.com/adap/flower/pull/2829), [#2825]" +"(https://github.com/adap/flower/pull/2825), [#2816](https://github.com/adap/" +"flower/pull/2816), [#2726](https://github.com/adap/flower/pull/2726), [#2659]" +"(https://github.com/adap/flower/pull/2659), [#2655](https://github.com/adap/" +"flower/pull/2655))" msgstr "" #: ../../source/ref-changelog.md:125 @@ -14118,8 +13825,8 @@ msgstr "" #: ../../source/ref-changelog.md:129 msgid "" -"HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), " -"[#2771](https://github.com/adap/flower/pull/2771))" +"HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), [#2771]" +"(https://github.com/adap/flower/pull/2771))" msgstr "" #: ../../source/ref-changelog.md:130 @@ -14144,149 +13851,119 @@ msgstr "" #: ../../source/ref-changelog.md:136 msgid "" -"**Improve documentation** " -"([#2674](https://github.com/adap/flower/pull/2674), " -"[#2480](https://github.com/adap/flower/pull/2480), " -"[#2826](https://github.com/adap/flower/pull/2826), " -"[#2727](https://github.com/adap/flower/pull/2727), " -"[#2761](https://github.com/adap/flower/pull/2761), " -"[#2900](https://github.com/adap/flower/pull/2900))" +"**Improve documentation** ([#2674](https://github.com/adap/flower/" +"pull/2674), [#2480](https://github.com/adap/flower/pull/2480), [#2826]" +"(https://github.com/adap/flower/pull/2826), [#2727](https://github.com/adap/" +"flower/pull/2727), [#2761](https://github.com/adap/flower/pull/2761), [#2900]" +"(https://github.com/adap/flower/pull/2900))" msgstr "" #: ../../source/ref-changelog.md:138 msgid "" -"**Improved testing and development infrastructure** " -"([#2797](https://github.com/adap/flower/pull/2797), " -"[#2676](https://github.com/adap/flower/pull/2676), " -"[#2644](https://github.com/adap/flower/pull/2644), " -"[#2656](https://github.com/adap/flower/pull/2656), " -"[#2848](https://github.com/adap/flower/pull/2848), " -"[#2675](https://github.com/adap/flower/pull/2675), " -"[#2735](https://github.com/adap/flower/pull/2735), " -"[#2767](https://github.com/adap/flower/pull/2767), " -"[#2732](https://github.com/adap/flower/pull/2732), " -"[#2744](https://github.com/adap/flower/pull/2744), " -"[#2681](https://github.com/adap/flower/pull/2681), " -"[#2699](https://github.com/adap/flower/pull/2699), " -"[#2745](https://github.com/adap/flower/pull/2745), " -"[#2734](https://github.com/adap/flower/pull/2734), " -"[#2731](https://github.com/adap/flower/pull/2731), " -"[#2652](https://github.com/adap/flower/pull/2652), " -"[#2720](https://github.com/adap/flower/pull/2720), " -"[#2721](https://github.com/adap/flower/pull/2721), " -"[#2717](https://github.com/adap/flower/pull/2717), " -"[#2864](https://github.com/adap/flower/pull/2864), " -"[#2694](https://github.com/adap/flower/pull/2694), " -"[#2709](https://github.com/adap/flower/pull/2709), " -"[#2658](https://github.com/adap/flower/pull/2658), " -"[#2796](https://github.com/adap/flower/pull/2796), " -"[#2692](https://github.com/adap/flower/pull/2692), " -"[#2657](https://github.com/adap/flower/pull/2657), " -"[#2813](https://github.com/adap/flower/pull/2813), " -"[#2661](https://github.com/adap/flower/pull/2661), " -"[#2398](https://github.com/adap/flower/pull/2398))" +"**Improved testing and development infrastructure** ([#2797](https://github." +"com/adap/flower/pull/2797), [#2676](https://github.com/adap/flower/" +"pull/2676), [#2644](https://github.com/adap/flower/pull/2644), [#2656]" +"(https://github.com/adap/flower/pull/2656), [#2848](https://github.com/adap/" +"flower/pull/2848), [#2675](https://github.com/adap/flower/pull/2675), [#2735]" +"(https://github.com/adap/flower/pull/2735), [#2767](https://github.com/adap/" +"flower/pull/2767), [#2732](https://github.com/adap/flower/pull/2732), [#2744]" +"(https://github.com/adap/flower/pull/2744), [#2681](https://github.com/adap/" +"flower/pull/2681), [#2699](https://github.com/adap/flower/pull/2699), [#2745]" +"(https://github.com/adap/flower/pull/2745), [#2734](https://github.com/adap/" +"flower/pull/2734), [#2731](https://github.com/adap/flower/pull/2731), [#2652]" +"(https://github.com/adap/flower/pull/2652), [#2720](https://github.com/adap/" +"flower/pull/2720), [#2721](https://github.com/adap/flower/pull/2721), [#2717]" +"(https://github.com/adap/flower/pull/2717), [#2864](https://github.com/adap/" +"flower/pull/2864), [#2694](https://github.com/adap/flower/pull/2694), [#2709]" +"(https://github.com/adap/flower/pull/2709), [#2658](https://github.com/adap/" +"flower/pull/2658), [#2796](https://github.com/adap/flower/pull/2796), [#2692]" +"(https://github.com/adap/flower/pull/2692), [#2657](https://github.com/adap/" +"flower/pull/2657), [#2813](https://github.com/adap/flower/pull/2813), [#2661]" +"(https://github.com/adap/flower/pull/2661), [#2398](https://github.com/adap/" +"flower/pull/2398))" msgstr "" #: ../../source/ref-changelog.md:140 msgid "" -"The Flower testing and development infrastructure has received " -"substantial updates. This makes Flower 1.7 the most tested release ever." +"The Flower testing and development infrastructure has received substantial " +"updates. This makes Flower 1.7 the most tested release ever." msgstr "" #: ../../source/ref-changelog.md:142 msgid "" -"**Update dependencies** " -"([#2753](https://github.com/adap/flower/pull/2753), " -"[#2651](https://github.com/adap/flower/pull/2651), " -"[#2739](https://github.com/adap/flower/pull/2739), " -"[#2837](https://github.com/adap/flower/pull/2837), " -"[#2788](https://github.com/adap/flower/pull/2788), " -"[#2811](https://github.com/adap/flower/pull/2811), " -"[#2774](https://github.com/adap/flower/pull/2774), " -"[#2790](https://github.com/adap/flower/pull/2790), " -"[#2751](https://github.com/adap/flower/pull/2751), " -"[#2850](https://github.com/adap/flower/pull/2850), " -"[#2812](https://github.com/adap/flower/pull/2812), " -"[#2872](https://github.com/adap/flower/pull/2872), " -"[#2736](https://github.com/adap/flower/pull/2736), " -"[#2756](https://github.com/adap/flower/pull/2756), " -"[#2857](https://github.com/adap/flower/pull/2857), " -"[#2757](https://github.com/adap/flower/pull/2757), " -"[#2810](https://github.com/adap/flower/pull/2810), " -"[#2740](https://github.com/adap/flower/pull/2740), " -"[#2789](https://github.com/adap/flower/pull/2789))" +"**Update dependencies** ([#2753](https://github.com/adap/flower/pull/2753), " +"[#2651](https://github.com/adap/flower/pull/2651), [#2739](https://github." +"com/adap/flower/pull/2739), [#2837](https://github.com/adap/flower/" +"pull/2837), [#2788](https://github.com/adap/flower/pull/2788), [#2811]" +"(https://github.com/adap/flower/pull/2811), [#2774](https://github.com/adap/" +"flower/pull/2774), [#2790](https://github.com/adap/flower/pull/2790), [#2751]" +"(https://github.com/adap/flower/pull/2751), [#2850](https://github.com/adap/" +"flower/pull/2850), [#2812](https://github.com/adap/flower/pull/2812), [#2872]" +"(https://github.com/adap/flower/pull/2872), [#2736](https://github.com/adap/" +"flower/pull/2736), [#2756](https://github.com/adap/flower/pull/2756), [#2857]" +"(https://github.com/adap/flower/pull/2857), [#2757](https://github.com/adap/" +"flower/pull/2757), [#2810](https://github.com/adap/flower/pull/2810), [#2740]" +"(https://github.com/adap/flower/pull/2740), [#2789](https://github.com/adap/" +"flower/pull/2789))" msgstr "" #: ../../source/ref-changelog.md:144 msgid "" -"**General improvements** " -"([#2803](https://github.com/adap/flower/pull/2803), " -"[#2847](https://github.com/adap/flower/pull/2847), " -"[#2877](https://github.com/adap/flower/pull/2877), " -"[#2690](https://github.com/adap/flower/pull/2690), " -"[#2889](https://github.com/adap/flower/pull/2889), " -"[#2874](https://github.com/adap/flower/pull/2874), " -"[#2819](https://github.com/adap/flower/pull/2819), " -"[#2689](https://github.com/adap/flower/pull/2689), " -"[#2457](https://github.com/adap/flower/pull/2457), " -"[#2870](https://github.com/adap/flower/pull/2870), " -"[#2669](https://github.com/adap/flower/pull/2669), " -"[#2876](https://github.com/adap/flower/pull/2876), " -"[#2885](https://github.com/adap/flower/pull/2885), " -"[#2858](https://github.com/adap/flower/pull/2858), " -"[#2867](https://github.com/adap/flower/pull/2867), " -"[#2351](https://github.com/adap/flower/pull/2351), " -"[#2886](https://github.com/adap/flower/pull/2886), " -"[#2860](https://github.com/adap/flower/pull/2860), " -"[#2828](https://github.com/adap/flower/pull/2828), " -"[#2869](https://github.com/adap/flower/pull/2869), " -"[#2875](https://github.com/adap/flower/pull/2875), " -"[#2733](https://github.com/adap/flower/pull/2733), " -"[#2488](https://github.com/adap/flower/pull/2488), " -"[#2646](https://github.com/adap/flower/pull/2646), " -"[#2879](https://github.com/adap/flower/pull/2879), " -"[#2821](https://github.com/adap/flower/pull/2821), " -"[#2855](https://github.com/adap/flower/pull/2855), " -"[#2800](https://github.com/adap/flower/pull/2800), " -"[#2807](https://github.com/adap/flower/pull/2807), " -"[#2801](https://github.com/adap/flower/pull/2801), " -"[#2804](https://github.com/adap/flower/pull/2804), " -"[#2851](https://github.com/adap/flower/pull/2851), " -"[#2787](https://github.com/adap/flower/pull/2787), " -"[#2852](https://github.com/adap/flower/pull/2852), " -"[#2672](https://github.com/adap/flower/pull/2672), " -"[#2759](https://github.com/adap/flower/pull/2759))" +"**General improvements** ([#2803](https://github.com/adap/flower/pull/2803), " +"[#2847](https://github.com/adap/flower/pull/2847), [#2877](https://github." +"com/adap/flower/pull/2877), [#2690](https://github.com/adap/flower/" +"pull/2690), [#2889](https://github.com/adap/flower/pull/2889), [#2874]" +"(https://github.com/adap/flower/pull/2874), [#2819](https://github.com/adap/" +"flower/pull/2819), [#2689](https://github.com/adap/flower/pull/2689), [#2457]" +"(https://github.com/adap/flower/pull/2457), [#2870](https://github.com/adap/" +"flower/pull/2870), [#2669](https://github.com/adap/flower/pull/2669), [#2876]" +"(https://github.com/adap/flower/pull/2876), [#2885](https://github.com/adap/" +"flower/pull/2885), [#2858](https://github.com/adap/flower/pull/2858), [#2867]" +"(https://github.com/adap/flower/pull/2867), [#2351](https://github.com/adap/" +"flower/pull/2351), [#2886](https://github.com/adap/flower/pull/2886), [#2860]" +"(https://github.com/adap/flower/pull/2860), [#2828](https://github.com/adap/" +"flower/pull/2828), [#2869](https://github.com/adap/flower/pull/2869), [#2875]" +"(https://github.com/adap/flower/pull/2875), [#2733](https://github.com/adap/" +"flower/pull/2733), [#2488](https://github.com/adap/flower/pull/2488), [#2646]" +"(https://github.com/adap/flower/pull/2646), [#2879](https://github.com/adap/" +"flower/pull/2879), [#2821](https://github.com/adap/flower/pull/2821), [#2855]" +"(https://github.com/adap/flower/pull/2855), [#2800](https://github.com/adap/" +"flower/pull/2800), [#2807](https://github.com/adap/flower/pull/2807), [#2801]" +"(https://github.com/adap/flower/pull/2801), [#2804](https://github.com/adap/" +"flower/pull/2804), [#2851](https://github.com/adap/flower/pull/2851), [#2787]" +"(https://github.com/adap/flower/pull/2787), [#2852](https://github.com/adap/" +"flower/pull/2852), [#2672](https://github.com/adap/flower/pull/2672), [#2759]" +"(https://github.com/adap/flower/pull/2759))" msgstr "" #: ../../source/ref-changelog.md:148 msgid "" -"**Deprecate** `start_numpy_client` " -"([#2563](https://github.com/adap/flower/pull/2563), " -"[#2718](https://github.com/adap/flower/pull/2718))" +"**Deprecate** `start_numpy_client` ([#2563](https://github.com/adap/flower/" +"pull/2563), [#2718](https://github.com/adap/flower/pull/2718))" msgstr "" #: ../../source/ref-changelog.md:150 msgid "" "Until now, clients of type `NumPyClient` needed to be started via " -"`start_numpy_client`. In our efforts to consolidate framework APIs, we " -"have introduced changes, and now all client types should start via " -"`start_client`. To continue using `NumPyClient` clients, you simply need " -"to first call the `.to_client()` method and then pass returned `Client` " -"object to `start_client`. The examples and the documentation have been " -"updated accordingly." +"`start_numpy_client`. In our efforts to consolidate framework APIs, we have " +"introduced changes, and now all client types should start via " +"`start_client`. To continue using `NumPyClient` clients, you simply need to " +"first call the `.to_client()` method and then pass returned `Client` object " +"to `start_client`. The examples and the documentation have been updated " +"accordingly." msgstr "" #: ../../source/ref-changelog.md:152 msgid "" -"**Deprecate legacy DP wrappers** " -"([#2749](https://github.com/adap/flower/pull/2749))" +"**Deprecate legacy DP wrappers** ([#2749](https://github.com/adap/flower/" +"pull/2749))" msgstr "" #: ../../source/ref-changelog.md:154 msgid "" -"Legacy DP wrapper classes are deprecated, but still functional. This is " -"in preparation for an all-new pluggable version of differential privacy " -"support in Flower." +"Legacy DP wrapper classes are deprecated, but still functional. This is in " +"preparation for an all-new pluggable version of differential privacy support " +"in Flower." msgstr "" #: ../../source/ref-changelog.md:156 @@ -14297,28 +13974,26 @@ msgstr "" #: ../../source/ref-changelog.md:158 msgid "" -"**Rename** `certificates` **to** `root_certificates` **in** `Driver` " -"([#2890](https://github.com/adap/flower/pull/2890))" +"**Rename** `certificates` **to** `root_certificates` **in** `Driver` ([#2890]" +"(https://github.com/adap/flower/pull/2890))" msgstr "" #: ../../source/ref-changelog.md:160 msgid "" -"**Drop experimental** `Task` **fields** " -"([#2866](https://github.com/adap/flower/pull/2866), " -"[#2865](https://github.com/adap/flower/pull/2865))" +"**Drop experimental** `Task` **fields** ([#2866](https://github.com/adap/" +"flower/pull/2866), [#2865](https://github.com/adap/flower/pull/2865))" msgstr "" #: ../../source/ref-changelog.md:162 msgid "" "Experimental fields `sa`, `legacy_server_message` and " -"`legacy_client_message` were removed from `Task` message. The removed " -"fields are superseded by the new `RecordSet` abstraction." +"`legacy_client_message` were removed from `Task` message. The removed fields " +"are superseded by the new `RecordSet` abstraction." msgstr "" #: ../../source/ref-changelog.md:164 msgid "" -"**Retire MXNet examples** " -"([#2724](https://github.com/adap/flower/pull/2724))" +"**Retire MXNet examples** ([#2724](https://github.com/adap/flower/pull/2724))" msgstr "" #: ../../source/ref-changelog.md:166 @@ -14336,65 +14011,62 @@ msgstr "" msgid "" "`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " "`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " -"Mota`, `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`," -" `Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, " -"`Steve Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, " -"`cnxdeveloper`, `k3nfalt` " +"Mota`, `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`, " +"`Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, `Steve " +"Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, `cnxdeveloper`, " +"`k3nfalt` " msgstr "" #: ../../source/ref-changelog.md:178 msgid "" -"**Add experimental support for Python 3.12** " -"([#2565](https://github.com/adap/flower/pull/2565))" +"**Add experimental support for Python 3.12** ([#2565](https://github.com/" +"adap/flower/pull/2565))" msgstr "" #: ../../source/ref-changelog.md:180 msgid "" -"**Add new XGBoost examples** " -"([#2612](https://github.com/adap/flower/pull/2612), " -"[#2554](https://github.com/adap/flower/pull/2554), " -"[#2617](https://github.com/adap/flower/pull/2617), " -"[#2618](https://github.com/adap/flower/pull/2618), " -"[#2619](https://github.com/adap/flower/pull/2619), " -"[#2567](https://github.com/adap/flower/pull/2567))" +"**Add new XGBoost examples** ([#2612](https://github.com/adap/flower/" +"pull/2612), [#2554](https://github.com/adap/flower/pull/2554), [#2617]" +"(https://github.com/adap/flower/pull/2617), [#2618](https://github.com/adap/" +"flower/pull/2618), [#2619](https://github.com/adap/flower/pull/2619), [#2567]" +"(https://github.com/adap/flower/pull/2567))" msgstr "" #: ../../source/ref-changelog.md:182 msgid "" -"We have added a new `xgboost-quickstart` example alongside a new " -"`xgboost-comprehensive` example that goes more in-depth." +"We have added a new `xgboost-quickstart` example alongside a new `xgboost-" +"comprehensive` example that goes more in-depth." msgstr "" #: ../../source/ref-changelog.md:184 msgid "" -"**Add Vertical FL example** " -"([#2598](https://github.com/adap/flower/pull/2598))" +"**Add Vertical FL example** ([#2598](https://github.com/adap/flower/" +"pull/2598))" msgstr "" #: ../../source/ref-changelog.md:186 msgid "" -"We had many questions about Vertical Federated Learning using Flower, so " -"we decided to add an simple example for it on the [Titanic " -"dataset](https://www.kaggle.com/competitions/titanic/data) alongside a " -"tutorial (in the README)." +"We had many questions about Vertical Federated Learning using Flower, so we " +"decided to add an simple example for it on the [Titanic dataset](https://www." +"kaggle.com/competitions/titanic/data) alongside a tutorial (in the README)." msgstr "" #: ../../source/ref-changelog.md:188 msgid "" -"**Support custom** `ClientManager` **in** `start_driver()` " -"([#2292](https://github.com/adap/flower/pull/2292))" +"**Support custom** `ClientManager` **in** `start_driver()` ([#2292](https://" +"github.com/adap/flower/pull/2292))" msgstr "" #: ../../source/ref-changelog.md:190 msgid "" -"**Update REST API to support create and delete nodes** " -"([#2283](https://github.com/adap/flower/pull/2283))" +"**Update REST API to support create and delete nodes** ([#2283](https://" +"github.com/adap/flower/pull/2283))" msgstr "" #: ../../source/ref-changelog.md:192 msgid "" -"**Update the Android SDK** " -"([#2187](https://github.com/adap/flower/pull/2187))" +"**Update the Android SDK** ([#2187](https://github.com/adap/flower/" +"pull/2187))" msgstr "" #: ../../source/ref-changelog.md:194 @@ -14403,11 +14075,10 @@ msgstr "" #: ../../source/ref-changelog.md:196 msgid "" -"**Update the C++ SDK** " -"([#2537](https://github.com/adap/flower/pull/2537), " -"[#2528](https://github.com/adap/flower/pull/2528), " -"[#2523](https://github.com/adap/flower/pull/2523), " -"[#2522](https://github.com/adap/flower/pull/2522))" +"**Update the C++ SDK** ([#2537](https://github.com/adap/flower/pull/2537), " +"[#2528](https://github.com/adap/flower/pull/2528), [#2523](https://github." +"com/adap/flower/pull/2523), [#2522](https://github.com/adap/flower/" +"pull/2522))" msgstr "" #: ../../source/ref-changelog.md:198 @@ -14416,93 +14087,90 @@ msgstr "" #: ../../source/ref-changelog.md:200 msgid "" -"**Make HTTPS the new default** " -"([#2591](https://github.com/adap/flower/pull/2591), " -"[#2636](https://github.com/adap/flower/pull/2636))" +"**Make HTTPS the new default** ([#2591](https://github.com/adap/flower/" +"pull/2591), [#2636](https://github.com/adap/flower/pull/2636))" msgstr "" #: ../../source/ref-changelog.md:202 msgid "" "Flower is moving to HTTPS by default. The new `flower-server` requires " -"passing `--certificates`, but users can enable `--insecure` to use HTTP " -"for prototyping. The same applies to `flower-client`, which can either " -"use user-provided credentials or gRPC-bundled certificates to connect to " -"an HTTPS-enabled server or requires opt-out via passing `--insecure` to " -"enable insecure HTTP connections." +"passing `--certificates`, but users can enable `--insecure` to use HTTP for " +"prototyping. The same applies to `flower-client`, which can either use user-" +"provided credentials or gRPC-bundled certificates to connect to an HTTPS-" +"enabled server or requires opt-out via passing `--insecure` to enable " +"insecure HTTP connections." msgstr "" #: ../../source/ref-changelog.md:204 msgid "" -"For backward compatibility, `start_client()` and `start_numpy_client()` " -"will still start in insecure mode by default. In a future release, " -"insecure connections will require user opt-in by passing `insecure=True`." +"For backward compatibility, `start_client()` and `start_numpy_client()` will " +"still start in insecure mode by default. In a future release, insecure " +"connections will require user opt-in by passing `insecure=True`." msgstr "" #: ../../source/ref-changelog.md:206 msgid "" "**Unify client API** ([#2303](https://github.com/adap/flower/pull/2303), " -"[#2390](https://github.com/adap/flower/pull/2390), " -"[#2493](https://github.com/adap/flower/pull/2493))" +"[#2390](https://github.com/adap/flower/pull/2390), [#2493](https://github." +"com/adap/flower/pull/2493))" msgstr "" #: ../../source/ref-changelog.md:208 msgid "" -"Using the `client_fn`, Flower clients can interchangeably run as " -"standalone processes (i.e. via `start_client`) or in simulation (i.e. via" -" `start_simulation`) without requiring changes to how the client class is" -" defined and instantiated. The `to_client()` function is introduced to " +"Using the `client_fn`, Flower clients can interchangeably run as standalone " +"processes (i.e. via `start_client`) or in simulation (i.e. via " +"`start_simulation`) without requiring changes to how the client class is " +"defined and instantiated. The `to_client()` function is introduced to " "convert a `NumPyClient` to a `Client`." msgstr "" #: ../../source/ref-changelog.md:210 msgid "" -"**Add new** `Bulyan` **strategy** " -"([#1817](https://github.com/adap/flower/pull/1817), " -"[#1891](https://github.com/adap/flower/pull/1891))" +"**Add new** `Bulyan` **strategy** ([#1817](https://github.com/adap/flower/" +"pull/1817), [#1891](https://github.com/adap/flower/pull/1891))" msgstr "" #: ../../source/ref-changelog.md:212 msgid "" -"The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., " -"2018](https://arxiv.org/abs/1802.07927)" +"The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., 2018]" +"(https://arxiv.org/abs/1802.07927)" msgstr "" #: ../../source/ref-changelog.md:214 msgid "" -"**Add new** `XGB Bagging` **strategy** " -"([#2611](https://github.com/adap/flower/pull/2611))" +"**Add new** `XGB Bagging` **strategy** ([#2611](https://github.com/adap/" +"flower/pull/2611))" msgstr "" #: ../../source/ref-changelog.md:216 ../../source/ref-changelog.md:218 msgid "" -"**Introduce `WorkloadState`** " -"([#2564](https://github.com/adap/flower/pull/2564), " -"[#2632](https://github.com/adap/flower/pull/2632))" +"**Introduce `WorkloadState`** ([#2564](https://github.com/adap/flower/" +"pull/2564), [#2632](https://github.com/adap/flower/pull/2632))" msgstr "" #: ../../source/ref-changelog.md:222 msgid "" -"FedProx ([#2210](https://github.com/adap/flower/pull/2210), " -"[#2286](https://github.com/adap/flower/pull/2286), " -"[#2509](https://github.com/adap/flower/pull/2509))" +"FedProx ([#2210](https://github.com/adap/flower/pull/2210), [#2286](https://" +"github.com/adap/flower/pull/2286), [#2509](https://github.com/adap/flower/" +"pull/2509))" msgstr "" #: ../../source/ref-changelog.md:224 msgid "" -"Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), " -"[#2400](https://github.com/adap/flower/pull/2400))" +"Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), [#2400]" +"(https://github.com/adap/flower/pull/2400))" msgstr "" #: ../../source/ref-changelog.md:226 msgid "" -"FedMLB ([#2340](https://github.com/adap/flower/pull/2340), " -"[#2507](https://github.com/adap/flower/pull/2507))" +"FedMLB ([#2340](https://github.com/adap/flower/pull/2340), [#2507](https://" +"github.com/adap/flower/pull/2507))" msgstr "" #: ../../source/ref-changelog.md:228 msgid "" -"TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " -"[#2508](https://github.com/adap/flower/pull/2508))" +"TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), [#2508](https://" +"github.com/adap/flower/pull/2508))" msgstr "" #: ../../source/ref-changelog.md:230 @@ -14535,125 +14203,106 @@ msgstr "" #: ../../source/ref-changelog.md:244 msgid "" -"FedBN ([#2608](https://github.com/adap/flower/pull/2608), " -"[#2615](https://github.com/adap/flower/pull/2615))" +"FedBN ([#2608](https://github.com/adap/flower/pull/2608), [#2615](https://" +"github.com/adap/flower/pull/2615))" msgstr "" #: ../../source/ref-changelog.md:246 msgid "" -"**General updates to Flower Examples** " -"([#2384](https://github.com/adap/flower/pull/2384), " -"[#2425](https://github.com/adap/flower/pull/2425), " -"[#2526](https://github.com/adap/flower/pull/2526), " -"[#2302](https://github.com/adap/flower/pull/2302), " -"[#2545](https://github.com/adap/flower/pull/2545))" +"**General updates to Flower Examples** ([#2384](https://github.com/adap/" +"flower/pull/2384), [#2425](https://github.com/adap/flower/pull/2425), [#2526]" +"(https://github.com/adap/flower/pull/2526), [#2302](https://github.com/adap/" +"flower/pull/2302), [#2545](https://github.com/adap/flower/pull/2545))" msgstr "" #: ../../source/ref-changelog.md:248 msgid "" -"**General updates to Flower Baselines** " -"([#2301](https://github.com/adap/flower/pull/2301), " -"[#2305](https://github.com/adap/flower/pull/2305), " -"[#2307](https://github.com/adap/flower/pull/2307), " -"[#2327](https://github.com/adap/flower/pull/2327), " -"[#2435](https://github.com/adap/flower/pull/2435), " -"[#2462](https://github.com/adap/flower/pull/2462), " -"[#2463](https://github.com/adap/flower/pull/2463), " -"[#2461](https://github.com/adap/flower/pull/2461), " -"[#2469](https://github.com/adap/flower/pull/2469), " -"[#2466](https://github.com/adap/flower/pull/2466), " -"[#2471](https://github.com/adap/flower/pull/2471), " -"[#2472](https://github.com/adap/flower/pull/2472), " -"[#2470](https://github.com/adap/flower/pull/2470))" +"**General updates to Flower Baselines** ([#2301](https://github.com/adap/" +"flower/pull/2301), [#2305](https://github.com/adap/flower/pull/2305), [#2307]" +"(https://github.com/adap/flower/pull/2307), [#2327](https://github.com/adap/" +"flower/pull/2327), [#2435](https://github.com/adap/flower/pull/2435), [#2462]" +"(https://github.com/adap/flower/pull/2462), [#2463](https://github.com/adap/" +"flower/pull/2463), [#2461](https://github.com/adap/flower/pull/2461), [#2469]" +"(https://github.com/adap/flower/pull/2469), [#2466](https://github.com/adap/" +"flower/pull/2466), [#2471](https://github.com/adap/flower/pull/2471), [#2472]" +"(https://github.com/adap/flower/pull/2472), [#2470](https://github.com/adap/" +"flower/pull/2470))" msgstr "" #: ../../source/ref-changelog.md:250 msgid "" -"**General updates to the simulation engine** " -"([#2331](https://github.com/adap/flower/pull/2331), " -"[#2447](https://github.com/adap/flower/pull/2447), " -"[#2448](https://github.com/adap/flower/pull/2448), " -"[#2294](https://github.com/adap/flower/pull/2294))" +"**General updates to the simulation engine** ([#2331](https://github.com/" +"adap/flower/pull/2331), [#2447](https://github.com/adap/flower/pull/2447), " +"[#2448](https://github.com/adap/flower/pull/2448), [#2294](https://github." +"com/adap/flower/pull/2294))" msgstr "" #: ../../source/ref-changelog.md:252 msgid "" -"**General updates to Flower SDKs** " -"([#2288](https://github.com/adap/flower/pull/2288), " -"[#2429](https://github.com/adap/flower/pull/2429), " -"[#2555](https://github.com/adap/flower/pull/2555), " -"[#2543](https://github.com/adap/flower/pull/2543), " -"[#2544](https://github.com/adap/flower/pull/2544), " -"[#2597](https://github.com/adap/flower/pull/2597), " -"[#2623](https://github.com/adap/flower/pull/2623))" +"**General updates to Flower SDKs** ([#2288](https://github.com/adap/flower/" +"pull/2288), [#2429](https://github.com/adap/flower/pull/2429), [#2555]" +"(https://github.com/adap/flower/pull/2555), [#2543](https://github.com/adap/" +"flower/pull/2543), [#2544](https://github.com/adap/flower/pull/2544), [#2597]" +"(https://github.com/adap/flower/pull/2597), [#2623](https://github.com/adap/" +"flower/pull/2623))" msgstr "" #: ../../source/ref-changelog.md:254 msgid "" -"**General improvements** " -"([#2309](https://github.com/adap/flower/pull/2309), " -"[#2310](https://github.com/adap/flower/pull/2310), " -"[#2313](https://github.com/adap/flower/pull/2313), " -"[#2316](https://github.com/adap/flower/pull/2316), " -"[#2317](https://github.com/adap/flower/pull/2317), " -"[#2349](https://github.com/adap/flower/pull/2349), " -"[#2360](https://github.com/adap/flower/pull/2360), " -"[#2402](https://github.com/adap/flower/pull/2402), " -"[#2446](https://github.com/adap/flower/pull/2446), " -"[#2561](https://github.com/adap/flower/pull/2561), " -"[#2273](https://github.com/adap/flower/pull/2273), " -"[#2267](https://github.com/adap/flower/pull/2267), " -"[#2274](https://github.com/adap/flower/pull/2274), " -"[#2275](https://github.com/adap/flower/pull/2275), " -"[#2432](https://github.com/adap/flower/pull/2432), " -"[#2251](https://github.com/adap/flower/pull/2251), " -"[#2321](https://github.com/adap/flower/pull/2321), " -"[#1936](https://github.com/adap/flower/pull/1936), " -"[#2408](https://github.com/adap/flower/pull/2408), " -"[#2413](https://github.com/adap/flower/pull/2413), " -"[#2401](https://github.com/adap/flower/pull/2401), " -"[#2531](https://github.com/adap/flower/pull/2531), " -"[#2534](https://github.com/adap/flower/pull/2534), " -"[#2535](https://github.com/adap/flower/pull/2535), " -"[#2521](https://github.com/adap/flower/pull/2521), " -"[#2553](https://github.com/adap/flower/pull/2553), " -"[#2596](https://github.com/adap/flower/pull/2596))" +"**General improvements** ([#2309](https://github.com/adap/flower/pull/2309), " +"[#2310](https://github.com/adap/flower/pull/2310), [#2313](https://github." +"com/adap/flower/pull/2313), [#2316](https://github.com/adap/flower/" +"pull/2316), [#2317](https://github.com/adap/flower/pull/2317), [#2349]" +"(https://github.com/adap/flower/pull/2349), [#2360](https://github.com/adap/" +"flower/pull/2360), [#2402](https://github.com/adap/flower/pull/2402), [#2446]" +"(https://github.com/adap/flower/pull/2446), [#2561](https://github.com/adap/" +"flower/pull/2561), [#2273](https://github.com/adap/flower/pull/2273), [#2267]" +"(https://github.com/adap/flower/pull/2267), [#2274](https://github.com/adap/" +"flower/pull/2274), [#2275](https://github.com/adap/flower/pull/2275), [#2432]" +"(https://github.com/adap/flower/pull/2432), [#2251](https://github.com/adap/" +"flower/pull/2251), [#2321](https://github.com/adap/flower/pull/2321), [#1936]" +"(https://github.com/adap/flower/pull/1936), [#2408](https://github.com/adap/" +"flower/pull/2408), [#2413](https://github.com/adap/flower/pull/2413), [#2401]" +"(https://github.com/adap/flower/pull/2401), [#2531](https://github.com/adap/" +"flower/pull/2531), [#2534](https://github.com/adap/flower/pull/2534), [#2535]" +"(https://github.com/adap/flower/pull/2535), [#2521](https://github.com/adap/" +"flower/pull/2521), [#2553](https://github.com/adap/flower/pull/2553), [#2596]" +"(https://github.com/adap/flower/pull/2596))" msgstr "" #: ../../source/ref-changelog.md:256 ../../source/ref-changelog.md:346 #: ../../source/ref-changelog.md:410 ../../source/ref-changelog.md:464 #: ../../source/ref-changelog.md:531 -msgid "Flower received many improvements under the hood, too many to list here." +msgid "" +"Flower received many improvements under the hood, too many to list here." msgstr "" #: ../../source/ref-changelog.md:260 msgid "" -"**Remove support for Python 3.7** " -"([#2280](https://github.com/adap/flower/pull/2280), " -"[#2299](https://github.com/adap/flower/pull/2299), " -"[#2304](https://github.com/adap/flower/pull/2304), " -"[#2306](https://github.com/adap/flower/pull/2306), " -"[#2355](https://github.com/adap/flower/pull/2355), " -"[#2356](https://github.com/adap/flower/pull/2356))" +"**Remove support for Python 3.7** ([#2280](https://github.com/adap/flower/" +"pull/2280), [#2299](https://github.com/adap/flower/pull/2299), [#2304]" +"(https://github.com/adap/flower/pull/2304), [#2306](https://github.com/adap/" +"flower/pull/2306), [#2355](https://github.com/adap/flower/pull/2355), [#2356]" +"(https://github.com/adap/flower/pull/2356))" msgstr "" #: ../../source/ref-changelog.md:262 msgid "" -"Python 3.7 support was deprecated in Flower 1.5, and this release removes" -" support. Flower now requires Python 3.8." +"Python 3.7 support was deprecated in Flower 1.5, and this release removes " +"support. Flower now requires Python 3.8." msgstr "" #: ../../source/ref-changelog.md:264 msgid "" -"**Remove experimental argument** `rest` **from** `start_client` " -"([#2324](https://github.com/adap/flower/pull/2324))" +"**Remove experimental argument** `rest` **from** `start_client` ([#2324]" +"(https://github.com/adap/flower/pull/2324))" msgstr "" #: ../../source/ref-changelog.md:266 msgid "" -"The (still experimental) argument `rest` was removed from `start_client` " -"and `start_numpy_client`. Use `transport=\"rest\"` to opt into the " -"experimental REST API instead." +"The (still experimental) argument `rest` was removed from `start_client` and " +"`start_numpy_client`. Use `transport=\"rest\"` to opt into the experimental " +"REST API instead." msgstr "" #: ../../source/ref-changelog.md:268 @@ -14671,125 +14320,108 @@ msgstr "" #: ../../source/ref-changelog.md:278 msgid "" -"**Introduce new simulation engine** " -"([#1969](https://github.com/adap/flower/pull/1969), " -"[#2221](https://github.com/adap/flower/pull/2221), " -"[#2248](https://github.com/adap/flower/pull/2248))" +"**Introduce new simulation engine** ([#1969](https://github.com/adap/flower/" +"pull/1969), [#2221](https://github.com/adap/flower/pull/2221), [#2248]" +"(https://github.com/adap/flower/pull/2248))" msgstr "" #: ../../source/ref-changelog.md:280 msgid "" "The new simulation engine has been rewritten from the ground up, yet it " -"remains fully backwards compatible. It offers much improved stability and" -" memory handling, especially when working with GPUs. Simulations " -"transparently adapt to different settings to scale simulation in CPU-" -"only, CPU+GPU, multi-GPU, or multi-node multi-GPU environments." +"remains fully backwards compatible. It offers much improved stability and " +"memory handling, especially when working with GPUs. Simulations " +"transparently adapt to different settings to scale simulation in CPU-only, " +"CPU+GPU, multi-GPU, or multi-node multi-GPU environments." msgstr "" #: ../../source/ref-changelog.md:282 msgid "" -"Comprehensive documentation includes a new [how-to run " -"simulations](https://flower.ai/docs/framework/how-to-run-" -"simulations.html) guide, new [simulation-" +"Comprehensive documentation includes a new [how-to run simulations](https://" +"flower.ai/docs/framework/how-to-run-simulations.html) guide, new [simulation-" "pytorch](https://flower.ai/docs/examples/simulation-pytorch.html) and " "[simulation-tensorflow](https://flower.ai/docs/examples/simulation-" -"tensorflow.html) notebooks, and a new [YouTube tutorial " -"series](https://www.youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." +"tensorflow.html) notebooks, and a new [YouTube tutorial series](https://www." +"youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." msgstr "" #: ../../source/ref-changelog.md:284 msgid "" -"**Restructure Flower Docs** " -"([#1824](https://github.com/adap/flower/pull/1824), " -"[#1865](https://github.com/adap/flower/pull/1865), " -"[#1884](https://github.com/adap/flower/pull/1884), " -"[#1887](https://github.com/adap/flower/pull/1887), " -"[#1919](https://github.com/adap/flower/pull/1919), " -"[#1922](https://github.com/adap/flower/pull/1922), " -"[#1920](https://github.com/adap/flower/pull/1920), " -"[#1923](https://github.com/adap/flower/pull/1923), " -"[#1924](https://github.com/adap/flower/pull/1924), " -"[#1962](https://github.com/adap/flower/pull/1962), " -"[#2006](https://github.com/adap/flower/pull/2006), " -"[#2133](https://github.com/adap/flower/pull/2133), " -"[#2203](https://github.com/adap/flower/pull/2203), " -"[#2215](https://github.com/adap/flower/pull/2215), " -"[#2122](https://github.com/adap/flower/pull/2122), " -"[#2223](https://github.com/adap/flower/pull/2223), " -"[#2219](https://github.com/adap/flower/pull/2219), " -"[#2232](https://github.com/adap/flower/pull/2232), " -"[#2233](https://github.com/adap/flower/pull/2233), " -"[#2234](https://github.com/adap/flower/pull/2234), " -"[#2235](https://github.com/adap/flower/pull/2235), " -"[#2237](https://github.com/adap/flower/pull/2237), " -"[#2238](https://github.com/adap/flower/pull/2238), " -"[#2242](https://github.com/adap/flower/pull/2242), " -"[#2231](https://github.com/adap/flower/pull/2231), " -"[#2243](https://github.com/adap/flower/pull/2243), " -"[#2227](https://github.com/adap/flower/pull/2227))" +"**Restructure Flower Docs** ([#1824](https://github.com/adap/flower/" +"pull/1824), [#1865](https://github.com/adap/flower/pull/1865), [#1884]" +"(https://github.com/adap/flower/pull/1884), [#1887](https://github.com/adap/" +"flower/pull/1887), [#1919](https://github.com/adap/flower/pull/1919), [#1922]" +"(https://github.com/adap/flower/pull/1922), [#1920](https://github.com/adap/" +"flower/pull/1920), [#1923](https://github.com/adap/flower/pull/1923), [#1924]" +"(https://github.com/adap/flower/pull/1924), [#1962](https://github.com/adap/" +"flower/pull/1962), [#2006](https://github.com/adap/flower/pull/2006), [#2133]" +"(https://github.com/adap/flower/pull/2133), [#2203](https://github.com/adap/" +"flower/pull/2203), [#2215](https://github.com/adap/flower/pull/2215), [#2122]" +"(https://github.com/adap/flower/pull/2122), [#2223](https://github.com/adap/" +"flower/pull/2223), [#2219](https://github.com/adap/flower/pull/2219), [#2232]" +"(https://github.com/adap/flower/pull/2232), [#2233](https://github.com/adap/" +"flower/pull/2233), [#2234](https://github.com/adap/flower/pull/2234), [#2235]" +"(https://github.com/adap/flower/pull/2235), [#2237](https://github.com/adap/" +"flower/pull/2237), [#2238](https://github.com/adap/flower/pull/2238), [#2242]" +"(https://github.com/adap/flower/pull/2242), [#2231](https://github.com/adap/" +"flower/pull/2231), [#2243](https://github.com/adap/flower/pull/2243), [#2227]" +"(https://github.com/adap/flower/pull/2227))" msgstr "" #: ../../source/ref-changelog.md:286 msgid "" -"Much effort went into a completely restructured Flower docs experience. " -"The documentation on [flower.ai/docs](https://flower.ai/docs) is now " -"divided into Flower Framework, Flower Baselines, Flower Android SDK, " -"Flower iOS SDK, and code example projects." +"Much effort went into a completely restructured Flower docs experience. The " +"documentation on [flower.ai/docs](https://flower.ai/docs) is now divided " +"into Flower Framework, Flower Baselines, Flower Android SDK, Flower iOS SDK, " +"and code example projects." msgstr "" #: ../../source/ref-changelog.md:288 msgid "" -"**Introduce Flower Swift SDK** " -"([#1858](https://github.com/adap/flower/pull/1858), " -"[#1897](https://github.com/adap/flower/pull/1897))" +"**Introduce Flower Swift SDK** ([#1858](https://github.com/adap/flower/" +"pull/1858), [#1897](https://github.com/adap/flower/pull/1897))" msgstr "" #: ../../source/ref-changelog.md:290 msgid "" -"This is the first preview release of the Flower Swift SDK. Flower support" -" on iOS is improving, and alongside the Swift SDK and code example, there" -" is now also an iOS quickstart tutorial." +"This is the first preview release of the Flower Swift SDK. Flower support on " +"iOS is improving, and alongside the Swift SDK and code example, there is now " +"also an iOS quickstart tutorial." msgstr "" #: ../../source/ref-changelog.md:292 msgid "" -"**Introduce Flower Android SDK** " -"([#2131](https://github.com/adap/flower/pull/2131))" +"**Introduce Flower Android SDK** ([#2131](https://github.com/adap/flower/" +"pull/2131))" msgstr "" #: ../../source/ref-changelog.md:294 msgid "" -"This is the first preview release of the Flower Kotlin SDK. Flower " -"support on Android is improving, and alongside the Kotlin SDK and code " -"example, there is now also an Android quickstart tutorial." +"This is the first preview release of the Flower Kotlin SDK. Flower support " +"on Android is improving, and alongside the Kotlin SDK and code example, " +"there is now also an Android quickstart tutorial." msgstr "" #: ../../source/ref-changelog.md:296 msgid "" -"**Introduce new end-to-end testing infrastructure** " -"([#1842](https://github.com/adap/flower/pull/1842), " -"[#2071](https://github.com/adap/flower/pull/2071), " -"[#2072](https://github.com/adap/flower/pull/2072), " -"[#2068](https://github.com/adap/flower/pull/2068), " -"[#2067](https://github.com/adap/flower/pull/2067), " -"[#2069](https://github.com/adap/flower/pull/2069), " -"[#2073](https://github.com/adap/flower/pull/2073), " -"[#2070](https://github.com/adap/flower/pull/2070), " -"[#2074](https://github.com/adap/flower/pull/2074), " -"[#2082](https://github.com/adap/flower/pull/2082), " -"[#2084](https://github.com/adap/flower/pull/2084), " -"[#2093](https://github.com/adap/flower/pull/2093), " -"[#2109](https://github.com/adap/flower/pull/2109), " -"[#2095](https://github.com/adap/flower/pull/2095), " -"[#2140](https://github.com/adap/flower/pull/2140), " -"[#2137](https://github.com/adap/flower/pull/2137), " -"[#2165](https://github.com/adap/flower/pull/2165))" +"**Introduce new end-to-end testing infrastructure** ([#1842](https://github." +"com/adap/flower/pull/1842), [#2071](https://github.com/adap/flower/" +"pull/2071), [#2072](https://github.com/adap/flower/pull/2072), [#2068]" +"(https://github.com/adap/flower/pull/2068), [#2067](https://github.com/adap/" +"flower/pull/2067), [#2069](https://github.com/adap/flower/pull/2069), [#2073]" +"(https://github.com/adap/flower/pull/2073), [#2070](https://github.com/adap/" +"flower/pull/2070), [#2074](https://github.com/adap/flower/pull/2074), [#2082]" +"(https://github.com/adap/flower/pull/2082), [#2084](https://github.com/adap/" +"flower/pull/2084), [#2093](https://github.com/adap/flower/pull/2093), [#2109]" +"(https://github.com/adap/flower/pull/2109), [#2095](https://github.com/adap/" +"flower/pull/2095), [#2140](https://github.com/adap/flower/pull/2140), [#2137]" +"(https://github.com/adap/flower/pull/2137), [#2165](https://github.com/adap/" +"flower/pull/2165))" msgstr "" #: ../../source/ref-changelog.md:298 msgid "" -"A new testing infrastructure ensures that new changes stay compatible " -"with existing framework integrations or strategies." +"A new testing infrastructure ensures that new changes stay compatible with " +"existing framework integrations or strategies." msgstr "" #: ../../source/ref-changelog.md:300 @@ -14798,124 +14430,121 @@ msgstr "" #: ../../source/ref-changelog.md:302 msgid "" -"Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for" -" Python 3.7 is now deprecated and will be removed in an upcoming release." +"Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for " +"Python 3.7 is now deprecated and will be removed in an upcoming release." msgstr "" #: ../../source/ref-changelog.md:304 msgid "" -"**Add new** `FedTrimmedAvg` **strategy** " -"([#1769](https://github.com/adap/flower/pull/1769), " -"[#1853](https://github.com/adap/flower/pull/1853))" +"**Add new** `FedTrimmedAvg` **strategy** ([#1769](https://github.com/adap/" +"flower/pull/1769), [#1853](https://github.com/adap/flower/pull/1853))" msgstr "" #: ../../source/ref-changelog.md:306 msgid "" -"The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, " -"2018](https://arxiv.org/abs/1803.01498)." +"The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, 2018]" +"(https://arxiv.org/abs/1803.01498)." msgstr "" #: ../../source/ref-changelog.md:308 msgid "" -"**Introduce start_driver** " -"([#1697](https://github.com/adap/flower/pull/1697))" +"**Introduce start_driver** ([#1697](https://github.com/adap/flower/" +"pull/1697))" msgstr "" #: ../../source/ref-changelog.md:310 msgid "" -"In addition to `start_server` and using the raw Driver API, there is a " -"new `start_driver` function that allows for running `start_server` " -"scripts as a Flower driver with only a single-line code change. Check out" -" the `mt-pytorch` code example to see a working example using " -"`start_driver`." +"In addition to `start_server` and using the raw Driver API, there is a new " +"`start_driver` function that allows for running `start_server` scripts as a " +"Flower driver with only a single-line code change. Check out the `mt-" +"pytorch` code example to see a working example using `start_driver`." msgstr "" #: ../../source/ref-changelog.md:312 msgid "" -"**Add parameter aggregation to** `mt-pytorch` **code example** " -"([#1785](https://github.com/adap/flower/pull/1785))" +"**Add parameter aggregation to** `mt-pytorch` **code example** ([#1785]" +"(https://github.com/adap/flower/pull/1785))" msgstr "" #: ../../source/ref-changelog.md:314 msgid "" -"The `mt-pytorch` example shows how to aggregate parameters when writing a" -" driver script. The included `driver.py` and `server.py` have been " -"aligned to demonstrate both the low-level way and the high-level way of " -"building server-side logic." +"The `mt-pytorch` example shows how to aggregate parameters when writing a " +"driver script. The included `driver.py` and `server.py` have been aligned to " +"demonstrate both the low-level way and the high-level way of building server-" +"side logic." msgstr "" #: ../../source/ref-changelog.md:316 msgid "" -"**Migrate experimental REST API to Starlette** " -"([2171](https://github.com/adap/flower/pull/2171))" +"**Migrate experimental REST API to Starlette** ([2171](https://github.com/" +"adap/flower/pull/2171))" msgstr "" #: ../../source/ref-changelog.md:318 msgid "" -"The (experimental) REST API used to be implemented in " -"[FastAPI](https://fastapi.tiangolo.com/), but it has now been migrated to" -" use [Starlette](https://www.starlette.io/) directly." +"The (experimental) REST API used to be implemented in [FastAPI](https://" +"fastapi.tiangolo.com/), but it has now been migrated to use [Starlette]" +"(https://www.starlette.io/) directly." msgstr "" #: ../../source/ref-changelog.md:320 msgid "" -"Please note: The REST request-response API is still experimental and will" -" likely change significantly over time." +"Please note: The REST request-response API is still experimental and will " +"likely change significantly over time." msgstr "" #: ../../source/ref-changelog.md:322 msgid "" -"**Introduce experimental gRPC request-response API** " -"([#1867](https://github.com/adap/flower/pull/1867), " -"[#1901](https://github.com/adap/flower/pull/1901))" +"**Introduce experimental gRPC request-response API** ([#1867](https://github." +"com/adap/flower/pull/1867), [#1901](https://github.com/adap/flower/" +"pull/1901))" msgstr "" #: ../../source/ref-changelog.md:324 msgid "" -"In addition to the existing gRPC API (based on bidirectional streaming) " -"and the experimental REST API, there is now a new gRPC API that uses a " -"request-response model to communicate with client nodes." +"In addition to the existing gRPC API (based on bidirectional streaming) and " +"the experimental REST API, there is now a new gRPC API that uses a request-" +"response model to communicate with client nodes." msgstr "" #: ../../source/ref-changelog.md:326 msgid "" -"Please note: The gRPC request-response API is still experimental and will" -" likely change significantly over time." +"Please note: The gRPC request-response API is still experimental and will " +"likely change significantly over time." msgstr "" #: ../../source/ref-changelog.md:328 msgid "" "**Replace the experimental** `start_client(rest=True)` **with the new** " -"`start_client(transport=\"rest\")` " -"([#1880](https://github.com/adap/flower/pull/1880))" +"`start_client(transport=\"rest\")` ([#1880](https://github.com/adap/flower/" +"pull/1880))" msgstr "" #: ../../source/ref-changelog.md:330 msgid "" -"The (experimental) `start_client` argument `rest` was deprecated in " -"favour of a new argument `transport`. `start_client(transport=\"rest\")` " -"will yield the same behaviour as `start_client(rest=True)` did before. " -"All code should migrate to the new argument `transport`. The deprecated " -"argument `rest` will be removed in a future release." +"The (experimental) `start_client` argument `rest` was deprecated in favour " +"of a new argument `transport`. `start_client(transport=\"rest\")` will yield " +"the same behaviour as `start_client(rest=True)` did before. All code should " +"migrate to the new argument `transport`. The deprecated argument `rest` will " +"be removed in a future release." msgstr "" #: ../../source/ref-changelog.md:332 msgid "" -"**Add a new gRPC option** " -"([#2197](https://github.com/adap/flower/pull/2197))" +"**Add a new gRPC option** ([#2197](https://github.com/adap/flower/pull/2197))" msgstr "" #: ../../source/ref-changelog.md:334 msgid "" -"We now start a gRPC server with the `grpc.keepalive_permit_without_calls`" -" option set to 0 by default. This prevents the clients from sending " -"keepalive pings when there is no outstanding stream." +"We now start a gRPC server with the `grpc.keepalive_permit_without_calls` " +"option set to 0 by default. This prevents the clients from sending keepalive " +"pings when there is no outstanding stream." msgstr "" #: ../../source/ref-changelog.md:336 msgid "" -"**Improve example notebooks** " -"([#2005](https://github.com/adap/flower/pull/2005))" +"**Improve example notebooks** ([#2005](https://github.com/adap/flower/" +"pull/2005))" msgstr "" #: ../../source/ref-changelog.md:338 @@ -14925,36 +14554,31 @@ msgstr "" #: ../../source/ref-changelog.md:340 msgid "" "**Example updates** ([#1772](https://github.com/adap/flower/pull/1772), " -"[#1873](https://github.com/adap/flower/pull/1873), " -"[#1981](https://github.com/adap/flower/pull/1981), " -"[#1988](https://github.com/adap/flower/pull/1988), " -"[#1984](https://github.com/adap/flower/pull/1984), " -"[#1982](https://github.com/adap/flower/pull/1982), " -"[#2112](https://github.com/adap/flower/pull/2112), " -"[#2144](https://github.com/adap/flower/pull/2144), " -"[#2174](https://github.com/adap/flower/pull/2174), " -"[#2225](https://github.com/adap/flower/pull/2225), " -"[#2183](https://github.com/adap/flower/pull/2183))" +"[#1873](https://github.com/adap/flower/pull/1873), [#1981](https://github." +"com/adap/flower/pull/1981), [#1988](https://github.com/adap/flower/" +"pull/1988), [#1984](https://github.com/adap/flower/pull/1984), [#1982]" +"(https://github.com/adap/flower/pull/1982), [#2112](https://github.com/adap/" +"flower/pull/2112), [#2144](https://github.com/adap/flower/pull/2144), [#2174]" +"(https://github.com/adap/flower/pull/2174), [#2225](https://github.com/adap/" +"flower/pull/2225), [#2183](https://github.com/adap/flower/pull/2183))" msgstr "" #: ../../source/ref-changelog.md:342 msgid "" "Many examples have received significant updates, including simplified " "advanced-tensorflow and advanced-pytorch examples, improved macOS " -"compatibility of TensorFlow examples, and code examples for simulation. A" -" major upgrade is that all code examples now have a `requirements.txt` " -"(in addition to `pyproject.toml`)." +"compatibility of TensorFlow examples, and code examples for simulation. A " +"major upgrade is that all code examples now have a `requirements.txt` (in " +"addition to `pyproject.toml`)." msgstr "" #: ../../source/ref-changelog.md:344 msgid "" -"**General improvements** " -"([#1872](https://github.com/adap/flower/pull/1872), " -"[#1866](https://github.com/adap/flower/pull/1866), " -"[#1884](https://github.com/adap/flower/pull/1884), " -"[#1837](https://github.com/adap/flower/pull/1837), " -"[#1477](https://github.com/adap/flower/pull/1477), " -"[#2171](https://github.com/adap/flower/pull/2171))" +"**General improvements** ([#1872](https://github.com/adap/flower/pull/1872), " +"[#1866](https://github.com/adap/flower/pull/1866), [#1884](https://github." +"com/adap/flower/pull/1884), [#1837](https://github.com/adap/flower/" +"pull/1837), [#1477](https://github.com/adap/flower/pull/1477), [#2171]" +"(https://github.com/adap/flower/pull/2171))" msgstr "" #: ../../source/ref-changelog.md:352 @@ -14964,110 +14588,103 @@ msgstr "" #: ../../source/ref-changelog.md:358 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " -"`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, " -"`Iacob-Alexandru-Andrei`, `JDRanpariya`, `Jean Charle Yaacoub`, `Kunal " -"Sarkhel`, `L. Jiang`, `Lennart Behme`, `Max Kapsecker`, `Michał`, `Nic " -"Lane`, `Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, " -"`Steve Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" +"`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, `Iacob-" +"Alexandru-Andrei`, `JDRanpariya`, `Jean Charle Yaacoub`, `Kunal Sarkhel`, " +"`L. Jiang`, `Lennart Behme`, `Max Kapsecker`, `Michał`, `Nic Lane`, " +"`Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, `Steve " +"Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" msgstr "" #: ../../source/ref-changelog.md:362 msgid "" -"**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and " -"example)** ([#1694](https://github.com/adap/flower/pull/1694), " -"[#1709](https://github.com/adap/flower/pull/1709), " -"[#1715](https://github.com/adap/flower/pull/1715), " -"[#1717](https://github.com/adap/flower/pull/1717), " -"[#1763](https://github.com/adap/flower/pull/1763), " -"[#1795](https://github.com/adap/flower/pull/1795))" +"**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and example)** " +"([#1694](https://github.com/adap/flower/pull/1694), [#1709](https://github." +"com/adap/flower/pull/1709), [#1715](https://github.com/adap/flower/" +"pull/1715), [#1717](https://github.com/adap/flower/pull/1717), [#1763]" +"(https://github.com/adap/flower/pull/1763), [#1795](https://github.com/adap/" +"flower/pull/1795))" msgstr "" #: ../../source/ref-changelog.md:364 msgid "" "XGBoost is a tree-based ensemble machine learning algorithm that uses " -"gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg`" -" " -"[strategy](https://github.com/adap/flower/tree/main/src/py/flwr/server/strategy/fedxgb_nn_avg.py)," -" and a [code example](https://github.com/adap/flower/tree/main/examples" -"/xgboost-quickstart) that demonstrates the usage of this new strategy in " -"an XGBoost project." +"gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg` " +"[strategy](https://github.com/adap/flower/tree/main/src/py/flwr/server/" +"strategy/fedxgb_nn_avg.py), and a [code example](https://github.com/adap/" +"flower/tree/main/examples/xgboost-quickstart) that demonstrates the usage of " +"this new strategy in an XGBoost project." msgstr "" #: ../../source/ref-changelog.md:366 msgid "" -"**Introduce iOS SDK (preview)** " -"([#1621](https://github.com/adap/flower/pull/1621), " -"[#1764](https://github.com/adap/flower/pull/1764))" +"**Introduce iOS SDK (preview)** ([#1621](https://github.com/adap/flower/" +"pull/1621), [#1764](https://github.com/adap/flower/pull/1764))" msgstr "" #: ../../source/ref-changelog.md:368 msgid "" -"This is a major update for anyone wanting to implement Federated Learning" -" on iOS mobile devices. We now have a swift iOS SDK present under " -"[src/swift/flwr](https://github.com/adap/flower/tree/main/src/swift/flwr)" -" that will facilitate greatly the app creating process. To showcase its " -"use, the [iOS " +"This is a major update for anyone wanting to implement Federated Learning on " +"iOS mobile devices. We now have a swift iOS SDK present under [src/swift/" +"flwr](https://github.com/adap/flower/tree/main/src/swift/flwr) that will " +"facilitate greatly the app creating process. To showcase its use, the [iOS " "example](https://github.com/adap/flower/tree/main/examples/ios) has also " "been updated!" msgstr "" #: ../../source/ref-changelog.md:370 msgid "" -"**Introduce new \"What is Federated Learning?\" tutorial** " -"([#1657](https://github.com/adap/flower/pull/1657), " -"[#1721](https://github.com/adap/flower/pull/1721))" +"**Introduce new \"What is Federated Learning?\" tutorial** ([#1657](https://" +"github.com/adap/flower/pull/1657), [#1721](https://github.com/adap/flower/" +"pull/1721))" msgstr "" #: ../../source/ref-changelog.md:372 msgid "" -"A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-" -"what-is-federated-learning.html) in our documentation explains the basics" -" of Fedetated Learning. It enables anyone who's unfamiliar with Federated" -" Learning to start their journey with Flower. Forward it to anyone who's " +"A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-what-" +"is-federated-learning.html) in our documentation explains the basics of " +"Fedetated Learning. It enables anyone who's unfamiliar with Federated " +"Learning to start their journey with Flower. Forward it to anyone who's " "interested in Federated Learning!" msgstr "" #: ../../source/ref-changelog.md:374 msgid "" -"**Introduce new Flower Baseline: FedProx MNIST** " -"([#1513](https://github.com/adap/flower/pull/1513), " -"[#1680](https://github.com/adap/flower/pull/1680), " -"[#1681](https://github.com/adap/flower/pull/1681), " -"[#1679](https://github.com/adap/flower/pull/1679))" +"**Introduce new Flower Baseline: FedProx MNIST** ([#1513](https://github.com/" +"adap/flower/pull/1513), [#1680](https://github.com/adap/flower/pull/1680), " +"[#1681](https://github.com/adap/flower/pull/1681), [#1679](https://github." +"com/adap/flower/pull/1679))" msgstr "" #: ../../source/ref-changelog.md:376 msgid "" -"This new baseline replicates the MNIST+CNN task from the paper [Federated" -" Optimization in Heterogeneous Networks (Li et al., " -"2018)](https://arxiv.org/abs/1812.06127). It uses the `FedProx` strategy," -" which aims at making convergence more robust in heterogeneous settings." +"This new baseline replicates the MNIST+CNN task from the paper [Federated " +"Optimization in Heterogeneous Networks (Li et al., 2018)](https://arxiv.org/" +"abs/1812.06127). It uses the `FedProx` strategy, which aims at making " +"convergence more robust in heterogeneous settings." msgstr "" #: ../../source/ref-changelog.md:378 msgid "" -"**Introduce new Flower Baseline: FedAvg FEMNIST** " -"([#1655](https://github.com/adap/flower/pull/1655))" +"**Introduce new Flower Baseline: FedAvg FEMNIST** ([#1655](https://github." +"com/adap/flower/pull/1655))" msgstr "" #: ../../source/ref-changelog.md:380 msgid "" -"This new baseline replicates an experiment evaluating the performance of " -"the FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A " -"Benchmark for Federated Settings (Caldas et al., " -"2018)](https://arxiv.org/abs/1812.01097)." +"This new baseline replicates an experiment evaluating the performance of the " +"FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A Benchmark " +"for Federated Settings (Caldas et al., 2018)](https://arxiv.org/" +"abs/1812.01097)." msgstr "" #: ../../source/ref-changelog.md:382 msgid "" -"**Introduce (experimental) REST API** " -"([#1594](https://github.com/adap/flower/pull/1594), " -"[#1690](https://github.com/adap/flower/pull/1690), " -"[#1695](https://github.com/adap/flower/pull/1695), " -"[#1712](https://github.com/adap/flower/pull/1712), " -"[#1802](https://github.com/adap/flower/pull/1802), " -"[#1770](https://github.com/adap/flower/pull/1770), " -"[#1733](https://github.com/adap/flower/pull/1733))" +"**Introduce (experimental) REST API** ([#1594](https://github.com/adap/" +"flower/pull/1594), [#1690](https://github.com/adap/flower/pull/1690), [#1695]" +"(https://github.com/adap/flower/pull/1695), [#1712](https://github.com/adap/" +"flower/pull/1712), [#1802](https://github.com/adap/flower/pull/1802), [#1770]" +"(https://github.com/adap/flower/pull/1770), [#1733](https://github.com/adap/" +"flower/pull/1733))" msgstr "" #: ../../source/ref-changelog.md:384 @@ -15085,132 +14702,112 @@ msgstr "" #: ../../source/ref-changelog.md:388 msgid "" -"**Improve the (experimental) Driver API** " -"([#1663](https://github.com/adap/flower/pull/1663), " -"[#1666](https://github.com/adap/flower/pull/1666), " -"[#1667](https://github.com/adap/flower/pull/1667), " -"[#1664](https://github.com/adap/flower/pull/1664), " -"[#1675](https://github.com/adap/flower/pull/1675), " -"[#1676](https://github.com/adap/flower/pull/1676), " -"[#1693](https://github.com/adap/flower/pull/1693), " -"[#1662](https://github.com/adap/flower/pull/1662), " -"[#1794](https://github.com/adap/flower/pull/1794))" +"**Improve the (experimental) Driver API** ([#1663](https://github.com/adap/" +"flower/pull/1663), [#1666](https://github.com/adap/flower/pull/1666), [#1667]" +"(https://github.com/adap/flower/pull/1667), [#1664](https://github.com/adap/" +"flower/pull/1664), [#1675](https://github.com/adap/flower/pull/1675), [#1676]" +"(https://github.com/adap/flower/pull/1676), [#1693](https://github.com/adap/" +"flower/pull/1693), [#1662](https://github.com/adap/flower/pull/1662), [#1794]" +"(https://github.com/adap/flower/pull/1794))" msgstr "" #: ../../source/ref-changelog.md:390 msgid "" -"The Driver API is still an experimental feature, but this release " -"introduces some major upgrades. One of the main improvements is the " -"introduction of an SQLite database to store server state on disk (instead" -" of in-memory). Another improvement is that tasks (instructions or " -"results) that have been delivered will now be deleted. This greatly " -"improves the memory efficiency of a long-running Flower server." +"The Driver API is still an experimental feature, but this release introduces " +"some major upgrades. One of the main improvements is the introduction of an " +"SQLite database to store server state on disk (instead of in-memory). " +"Another improvement is that tasks (instructions or results) that have been " +"delivered will now be deleted. This greatly improves the memory efficiency " +"of a long-running Flower server." msgstr "" #: ../../source/ref-changelog.md:392 msgid "" -"**Fix spilling issues related to Ray during simulations** " -"([#1698](https://github.com/adap/flower/pull/1698))" +"**Fix spilling issues related to Ray during simulations** ([#1698](https://" +"github.com/adap/flower/pull/1698))" msgstr "" #: ../../source/ref-changelog.md:394 msgid "" -"While running long simulations, `ray` was sometimes spilling huge amounts" -" of data that would make the training unable to continue. This is now " -"fixed! 🎉" +"While running long simulations, `ray` was sometimes spilling huge amounts of " +"data that would make the training unable to continue. This is now fixed! 🎉" msgstr "" #: ../../source/ref-changelog.md:396 msgid "" -"**Add new example using** `TabNet` **and Flower** " -"([#1725](https://github.com/adap/flower/pull/1725))" +"**Add new example using** `TabNet` **and Flower** ([#1725](https://github." +"com/adap/flower/pull/1725))" msgstr "" #: ../../source/ref-changelog.md:398 msgid "" -"TabNet is a powerful and flexible framework for training machine learning" -" models on tabular data. We now have a federated example using Flower: " -"[quickstart-tabnet](https://github.com/adap/flower/tree/main/examples" -"/quickstart-tabnet)." +"TabNet is a powerful and flexible framework for training machine learning " +"models on tabular data. We now have a federated example using Flower: " +"[quickstart-tabnet](https://github.com/adap/flower/tree/main/examples/" +"quickstart-tabnet)." msgstr "" #: ../../source/ref-changelog.md:400 msgid "" -"**Add new how-to guide for monitoring simulations** " -"([#1649](https://github.com/adap/flower/pull/1649))" +"**Add new how-to guide for monitoring simulations** ([#1649](https://github." +"com/adap/flower/pull/1649))" msgstr "" #: ../../source/ref-changelog.md:402 msgid "" -"We now have a documentation guide to help users monitor their performance" -" during simulations." +"We now have a documentation guide to help users monitor their performance " +"during simulations." msgstr "" #: ../../source/ref-changelog.md:404 msgid "" -"**Add training metrics to** `History` **object during simulations** " -"([#1696](https://github.com/adap/flower/pull/1696))" +"**Add training metrics to** `History` **object during simulations** ([#1696]" +"(https://github.com/adap/flower/pull/1696))" msgstr "" #: ../../source/ref-changelog.md:406 msgid "" -"The `fit_metrics_aggregation_fn` can be used to aggregate training " -"metrics, but previous releases did not save the results in the `History` " -"object. This is now the case!" +"The `fit_metrics_aggregation_fn` can be used to aggregate training metrics, " +"but previous releases did not save the results in the `History` object. This " +"is now the case!" msgstr "" #: ../../source/ref-changelog.md:408 msgid "" -"**General improvements** " -"([#1659](https://github.com/adap/flower/pull/1659), " -"[#1646](https://github.com/adap/flower/pull/1646), " -"[#1647](https://github.com/adap/flower/pull/1647), " -"[#1471](https://github.com/adap/flower/pull/1471), " -"[#1648](https://github.com/adap/flower/pull/1648), " -"[#1651](https://github.com/adap/flower/pull/1651), " -"[#1652](https://github.com/adap/flower/pull/1652), " -"[#1653](https://github.com/adap/flower/pull/1653), " -"[#1659](https://github.com/adap/flower/pull/1659), " -"[#1665](https://github.com/adap/flower/pull/1665), " -"[#1670](https://github.com/adap/flower/pull/1670), " -"[#1672](https://github.com/adap/flower/pull/1672), " -"[#1677](https://github.com/adap/flower/pull/1677), " -"[#1684](https://github.com/adap/flower/pull/1684), " -"[#1683](https://github.com/adap/flower/pull/1683), " -"[#1686](https://github.com/adap/flower/pull/1686), " -"[#1682](https://github.com/adap/flower/pull/1682), " -"[#1685](https://github.com/adap/flower/pull/1685), " -"[#1692](https://github.com/adap/flower/pull/1692), " -"[#1705](https://github.com/adap/flower/pull/1705), " -"[#1708](https://github.com/adap/flower/pull/1708), " -"[#1711](https://github.com/adap/flower/pull/1711), " -"[#1713](https://github.com/adap/flower/pull/1713), " -"[#1714](https://github.com/adap/flower/pull/1714), " -"[#1718](https://github.com/adap/flower/pull/1718), " -"[#1716](https://github.com/adap/flower/pull/1716), " -"[#1723](https://github.com/adap/flower/pull/1723), " -"[#1735](https://github.com/adap/flower/pull/1735), " -"[#1678](https://github.com/adap/flower/pull/1678), " -"[#1750](https://github.com/adap/flower/pull/1750), " -"[#1753](https://github.com/adap/flower/pull/1753), " -"[#1736](https://github.com/adap/flower/pull/1736), " -"[#1766](https://github.com/adap/flower/pull/1766), " -"[#1760](https://github.com/adap/flower/pull/1760), " -"[#1775](https://github.com/adap/flower/pull/1775), " -"[#1776](https://github.com/adap/flower/pull/1776), " -"[#1777](https://github.com/adap/flower/pull/1777), " -"[#1779](https://github.com/adap/flower/pull/1779), " -"[#1784](https://github.com/adap/flower/pull/1784), " -"[#1773](https://github.com/adap/flower/pull/1773), " -"[#1755](https://github.com/adap/flower/pull/1755), " -"[#1789](https://github.com/adap/flower/pull/1789), " -"[#1788](https://github.com/adap/flower/pull/1788), " -"[#1798](https://github.com/adap/flower/pull/1798), " -"[#1799](https://github.com/adap/flower/pull/1799), " -"[#1739](https://github.com/adap/flower/pull/1739), " -"[#1800](https://github.com/adap/flower/pull/1800), " -"[#1804](https://github.com/adap/flower/pull/1804), " -"[#1805](https://github.com/adap/flower/pull/1805))" +"**General improvements** ([#1659](https://github.com/adap/flower/pull/1659), " +"[#1646](https://github.com/adap/flower/pull/1646), [#1647](https://github." +"com/adap/flower/pull/1647), [#1471](https://github.com/adap/flower/" +"pull/1471), [#1648](https://github.com/adap/flower/pull/1648), [#1651]" +"(https://github.com/adap/flower/pull/1651), [#1652](https://github.com/adap/" +"flower/pull/1652), [#1653](https://github.com/adap/flower/pull/1653), [#1659]" +"(https://github.com/adap/flower/pull/1659), [#1665](https://github.com/adap/" +"flower/pull/1665), [#1670](https://github.com/adap/flower/pull/1670), [#1672]" +"(https://github.com/adap/flower/pull/1672), [#1677](https://github.com/adap/" +"flower/pull/1677), [#1684](https://github.com/adap/flower/pull/1684), [#1683]" +"(https://github.com/adap/flower/pull/1683), [#1686](https://github.com/adap/" +"flower/pull/1686), [#1682](https://github.com/adap/flower/pull/1682), [#1685]" +"(https://github.com/adap/flower/pull/1685), [#1692](https://github.com/adap/" +"flower/pull/1692), [#1705](https://github.com/adap/flower/pull/1705), [#1708]" +"(https://github.com/adap/flower/pull/1708), [#1711](https://github.com/adap/" +"flower/pull/1711), [#1713](https://github.com/adap/flower/pull/1713), [#1714]" +"(https://github.com/adap/flower/pull/1714), [#1718](https://github.com/adap/" +"flower/pull/1718), [#1716](https://github.com/adap/flower/pull/1716), [#1723]" +"(https://github.com/adap/flower/pull/1723), [#1735](https://github.com/adap/" +"flower/pull/1735), [#1678](https://github.com/adap/flower/pull/1678), [#1750]" +"(https://github.com/adap/flower/pull/1750), [#1753](https://github.com/adap/" +"flower/pull/1753), [#1736](https://github.com/adap/flower/pull/1736), [#1766]" +"(https://github.com/adap/flower/pull/1766), [#1760](https://github.com/adap/" +"flower/pull/1760), [#1775](https://github.com/adap/flower/pull/1775), [#1776]" +"(https://github.com/adap/flower/pull/1776), [#1777](https://github.com/adap/" +"flower/pull/1777), [#1779](https://github.com/adap/flower/pull/1779), [#1784]" +"(https://github.com/adap/flower/pull/1784), [#1773](https://github.com/adap/" +"flower/pull/1773), [#1755](https://github.com/adap/flower/pull/1755), [#1789]" +"(https://github.com/adap/flower/pull/1789), [#1788](https://github.com/adap/" +"flower/pull/1788), [#1798](https://github.com/adap/flower/pull/1798), [#1799]" +"(https://github.com/adap/flower/pull/1799), [#1739](https://github.com/adap/" +"flower/pull/1739), [#1800](https://github.com/adap/flower/pull/1800), [#1804]" +"(https://github.com/adap/flower/pull/1804), [#1805](https://github.com/adap/" +"flower/pull/1805))" msgstr "" #: ../../source/ref-changelog.md:416 @@ -15219,8 +14816,8 @@ msgstr "" #: ../../source/ref-changelog.md:422 msgid "" -"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " -"`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" +"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, `Daniel " +"J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" msgstr "" #: ../../source/ref-changelog.md:426 @@ -15231,24 +14828,24 @@ msgstr "" #: ../../source/ref-changelog.md:428 msgid "" -"The (experimental) Driver API now supports a `workload_id` that can be " -"used to identify which workload a task belongs to. It also supports a new" -" `group_id` that can be used, for example, to indicate the current " -"training round. Both the `workload_id` and `group_id` enable client nodes" -" to decide whether they want to handle a task or not." +"The (experimental) Driver API now supports a `workload_id` that can be used " +"to identify which workload a task belongs to. It also supports a new " +"`group_id` that can be used, for example, to indicate the current training " +"round. Both the `workload_id` and `group_id` enable client nodes to decide " +"whether they want to handle a task or not." msgstr "" #: ../../source/ref-changelog.md:430 msgid "" -"**Make Driver API and Fleet API address configurable** " -"([#1637](https://github.com/adap/flower/pull/1637))" +"**Make Driver API and Fleet API address configurable** ([#1637](https://" +"github.com/adap/flower/pull/1637))" msgstr "" #: ../../source/ref-changelog.md:432 msgid "" -"The (experimental) long-running Flower server (Driver API and Fleet API) " -"can now configure the server address of both Driver API (via `--driver-" -"api-address`) and Fleet API (via `--fleet-api-address`) when starting:" +"The (experimental) long-running Flower server (Driver API and Fleet API) can " +"now configure the server address of both Driver API (via `--driver-api-" +"address`) and Fleet API (via `--fleet-api-address`) when starting:" msgstr "" #: ../../source/ref-changelog.md:434 @@ -15263,55 +14860,51 @@ msgstr "" #: ../../source/ref-changelog.md:438 msgid "" -"**Add new example of Federated Learning using fastai and Flower** " -"([#1598](https://github.com/adap/flower/pull/1598))" +"**Add new example of Federated Learning using fastai and Flower** ([#1598]" +"(https://github.com/adap/flower/pull/1598))" msgstr "" #: ../../source/ref-changelog.md:440 msgid "" "A new code example (`quickstart-fastai`) demonstrates federated learning " "with [fastai](https://www.fast.ai/) and Flower. You can find it here: " -"[quickstart-fastai](https://github.com/adap/flower/tree/main/examples" -"/quickstart-fastai)." +"[quickstart-fastai](https://github.com/adap/flower/tree/main/examples/" +"quickstart-fastai)." msgstr "" #: ../../source/ref-changelog.md:442 msgid "" -"**Make Android example compatible with** `flwr >= 1.0.0` **and the latest" -" versions of Android** " -"([#1603](https://github.com/adap/flower/pull/1603))" +"**Make Android example compatible with** `flwr >= 1.0.0` **and the latest " +"versions of Android** ([#1603](https://github.com/adap/flower/pull/1603))" msgstr "" #: ../../source/ref-changelog.md:444 msgid "" -"The Android code example has received a substantial update: the project " -"is compatible with Flower 1.0 (and later), the UI received a full " -"refresh, and the project is updated to be compatible with newer Android " -"tooling." +"The Android code example has received a substantial update: the project is " +"compatible with Flower 1.0 (and later), the UI received a full refresh, and " +"the project is updated to be compatible with newer Android tooling." msgstr "" #: ../../source/ref-changelog.md:446 msgid "" -"**Add new `FedProx` strategy** " -"([#1619](https://github.com/adap/flower/pull/1619))" +"**Add new `FedProx` strategy** ([#1619](https://github.com/adap/flower/" +"pull/1619))" msgstr "" #: ../../source/ref-changelog.md:448 msgid "" -"This " -"[strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedprox.py)" -" is almost identical to " -"[`FedAvg`](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedavg.py)," -" but helps users replicate what is described in this " -"[paper](https://arxiv.org/abs/1812.06127). It essentially adds a " -"parameter called `proximal_mu` to regularize the local models with " -"respect to the global models." +"This [strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/" +"strategy/fedprox.py) is almost identical to [`FedAvg`](https://github.com/" +"adap/flower/blob/main/src/py/flwr/server/strategy/fedavg.py), but helps " +"users replicate what is described in this [paper](https://arxiv.org/" +"abs/1812.06127). It essentially adds a parameter called `proximal_mu` to " +"regularize the local models with respect to the global models." msgstr "" #: ../../source/ref-changelog.md:450 msgid "" -"**Add new metrics to telemetry events** " -"([#1640](https://github.com/adap/flower/pull/1640))" +"**Add new metrics to telemetry events** ([#1640](https://github.com/adap/" +"flower/pull/1640))" msgstr "" #: ../../source/ref-changelog.md:452 @@ -15322,87 +14915,73 @@ msgstr "" #: ../../source/ref-changelog.md:454 msgid "" -"**Add new custom strategy tutorial section** " -"[#1623](https://github.com/adap/flower/pull/1623)" +"**Add new custom strategy tutorial section** [#1623](https://github.com/adap/" +"flower/pull/1623)" msgstr "" #: ../../source/ref-changelog.md:456 msgid "" -"The Flower tutorial now has a new section that covers implementing a " -"custom strategy from scratch: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" -"/tutorial-build-a-strategy-from-scratch-pytorch.ipynb)" +"The Flower tutorial now has a new section that covers implementing a custom " +"strategy from scratch: [Open in Colab](https://colab.research.google.com/" +"github/adap/flower/blob/main/doc/source/tutorial-build-a-strategy-from-" +"scratch-pytorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:458 msgid "" -"**Add new custom serialization tutorial section** " -"([#1622](https://github.com/adap/flower/pull/1622))" +"**Add new custom serialization tutorial section** ([#1622](https://github." +"com/adap/flower/pull/1622))" msgstr "" #: ../../source/ref-changelog.md:460 msgid "" -"The Flower tutorial now has a new section that covers custom " -"serialization: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" -"/tutorial-customize-the-client-pytorch.ipynb)" +"The Flower tutorial now has a new section that covers custom serialization: " +"[Open in Colab](https://colab.research.google.com/github/adap/flower/blob/" +"main/doc/source/tutorial-customize-the-client-pytorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:462 msgid "" -"**General improvements** " -"([#1638](https://github.com/adap/flower/pull/1638), " -"[#1634](https://github.com/adap/flower/pull/1634), " -"[#1636](https://github.com/adap/flower/pull/1636), " -"[#1635](https://github.com/adap/flower/pull/1635), " -"[#1633](https://github.com/adap/flower/pull/1633), " -"[#1632](https://github.com/adap/flower/pull/1632), " -"[#1631](https://github.com/adap/flower/pull/1631), " -"[#1630](https://github.com/adap/flower/pull/1630), " -"[#1627](https://github.com/adap/flower/pull/1627), " -"[#1593](https://github.com/adap/flower/pull/1593), " -"[#1616](https://github.com/adap/flower/pull/1616), " -"[#1615](https://github.com/adap/flower/pull/1615), " -"[#1607](https://github.com/adap/flower/pull/1607), " -"[#1609](https://github.com/adap/flower/pull/1609), " -"[#1608](https://github.com/adap/flower/pull/1608), " -"[#1603](https://github.com/adap/flower/pull/1603), " -"[#1590](https://github.com/adap/flower/pull/1590), " -"[#1580](https://github.com/adap/flower/pull/1580), " -"[#1599](https://github.com/adap/flower/pull/1599), " -"[#1600](https://github.com/adap/flower/pull/1600), " -"[#1601](https://github.com/adap/flower/pull/1601), " -"[#1597](https://github.com/adap/flower/pull/1597), " -"[#1595](https://github.com/adap/flower/pull/1595), " -"[#1591](https://github.com/adap/flower/pull/1591), " -"[#1588](https://github.com/adap/flower/pull/1588), " -"[#1589](https://github.com/adap/flower/pull/1589), " -"[#1587](https://github.com/adap/flower/pull/1587), " -"[#1573](https://github.com/adap/flower/pull/1573), " -"[#1581](https://github.com/adap/flower/pull/1581), " -"[#1578](https://github.com/adap/flower/pull/1578), " -"[#1574](https://github.com/adap/flower/pull/1574), " -"[#1572](https://github.com/adap/flower/pull/1572), " -"[#1586](https://github.com/adap/flower/pull/1586))" +"**General improvements** ([#1638](https://github.com/adap/flower/pull/1638), " +"[#1634](https://github.com/adap/flower/pull/1634), [#1636](https://github." +"com/adap/flower/pull/1636), [#1635](https://github.com/adap/flower/" +"pull/1635), [#1633](https://github.com/adap/flower/pull/1633), [#1632]" +"(https://github.com/adap/flower/pull/1632), [#1631](https://github.com/adap/" +"flower/pull/1631), [#1630](https://github.com/adap/flower/pull/1630), [#1627]" +"(https://github.com/adap/flower/pull/1627), [#1593](https://github.com/adap/" +"flower/pull/1593), [#1616](https://github.com/adap/flower/pull/1616), [#1615]" +"(https://github.com/adap/flower/pull/1615), [#1607](https://github.com/adap/" +"flower/pull/1607), [#1609](https://github.com/adap/flower/pull/1609), [#1608]" +"(https://github.com/adap/flower/pull/1608), [#1603](https://github.com/adap/" +"flower/pull/1603), [#1590](https://github.com/adap/flower/pull/1590), [#1580]" +"(https://github.com/adap/flower/pull/1580), [#1599](https://github.com/adap/" +"flower/pull/1599), [#1600](https://github.com/adap/flower/pull/1600), [#1601]" +"(https://github.com/adap/flower/pull/1601), [#1597](https://github.com/adap/" +"flower/pull/1597), [#1595](https://github.com/adap/flower/pull/1595), [#1591]" +"(https://github.com/adap/flower/pull/1591), [#1588](https://github.com/adap/" +"flower/pull/1588), [#1589](https://github.com/adap/flower/pull/1589), [#1587]" +"(https://github.com/adap/flower/pull/1587), [#1573](https://github.com/adap/" +"flower/pull/1573), [#1581](https://github.com/adap/flower/pull/1581), [#1578]" +"(https://github.com/adap/flower/pull/1578), [#1574](https://github.com/adap/" +"flower/pull/1574), [#1572](https://github.com/adap/flower/pull/1572), [#1586]" +"(https://github.com/adap/flower/pull/1586))" msgstr "" #: ../../source/ref-changelog.md:466 msgid "" -"**Updated documentation** " -"([#1629](https://github.com/adap/flower/pull/1629), " -"[#1628](https://github.com/adap/flower/pull/1628), " -"[#1620](https://github.com/adap/flower/pull/1620), " -"[#1618](https://github.com/adap/flower/pull/1618), " -"[#1617](https://github.com/adap/flower/pull/1617), " -"[#1613](https://github.com/adap/flower/pull/1613), " -"[#1614](https://github.com/adap/flower/pull/1614))" +"**Updated documentation** ([#1629](https://github.com/adap/flower/" +"pull/1629), [#1628](https://github.com/adap/flower/pull/1628), [#1620]" +"(https://github.com/adap/flower/pull/1620), [#1618](https://github.com/adap/" +"flower/pull/1618), [#1617](https://github.com/adap/flower/pull/1617), [#1613]" +"(https://github.com/adap/flower/pull/1613), [#1614](https://github.com/adap/" +"flower/pull/1614))" msgstr "" #: ../../source/ref-changelog.md:468 ../../source/ref-changelog.md:535 msgid "" -"As usual, the documentation has improved quite a bit. It is another step " -"in our effort to make the Flower documentation the best documentation of " -"any project. Stay tuned and as always, feel free to provide feedback!" +"As usual, the documentation has improved quite a bit. It is another step in " +"our effort to make the Flower documentation the best documentation of any " +"project. Stay tuned and as always, feel free to provide feedback!" msgstr "" #: ../../source/ref-changelog.md:474 @@ -15411,15 +14990,14 @@ msgstr "" #: ../../source/ref-changelog.md:480 msgid "" -"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L." -" Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" +"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L. " +"Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" msgstr "" #: ../../source/ref-changelog.md:484 msgid "" -"**Introduce new Flower Baseline: FedAvg MNIST** " -"([#1497](https://github.com/adap/flower/pull/1497), " -"[#1552](https://github.com/adap/flower/pull/1552))" +"**Introduce new Flower Baseline: FedAvg MNIST** ([#1497](https://github.com/" +"adap/flower/pull/1497), [#1552](https://github.com/adap/flower/pull/1552))" msgstr "" #: ../../source/ref-changelog.md:486 @@ -15428,207 +15006,195 @@ msgid "" "implementations useful especially to FL newcomers. They will typically " "revisit well known papers from the literature, and be suitable for " "integration in your own application or for experimentation, in order to " -"deepen your knowledge of FL in general. Today's release is the first in " -"this series. [Read more.](https://flower.ai/blog/2023-01-12-fl-starter-" -"pack-fedavg-mnist-cnn/)" +"deepen your knowledge of FL in general. Today's release is the first in this " +"series. [Read more.](https://flower.ai/blog/2023-01-12-fl-starter-pack-" +"fedavg-mnist-cnn/)" msgstr "" #: ../../source/ref-changelog.md:488 msgid "" -"**Improve GPU support in simulations** " -"([#1555](https://github.com/adap/flower/pull/1555))" +"**Improve GPU support in simulations** ([#1555](https://github.com/adap/" +"flower/pull/1555))" msgstr "" #: ../../source/ref-changelog.md:490 msgid "" -"The Ray-based Virtual Client Engine (`start_simulation`) has been updated" -" to improve GPU support. The update includes some of the hard-earned " -"lessons from scaling simulations in GPU cluster environments. New " -"defaults make running GPU-based simulations substantially more robust." +"The Ray-based Virtual Client Engine (`start_simulation`) has been updated to " +"improve GPU support. The update includes some of the hard-earned lessons " +"from scaling simulations in GPU cluster environments. New defaults make " +"running GPU-based simulations substantially more robust." msgstr "" #: ../../source/ref-changelog.md:492 msgid "" -"**Improve GPU support in Jupyter Notebook tutorials** " -"([#1527](https://github.com/adap/flower/pull/1527), " -"[#1558](https://github.com/adap/flower/pull/1558))" +"**Improve GPU support in Jupyter Notebook tutorials** ([#1527](https://" +"github.com/adap/flower/pull/1527), [#1558](https://github.com/adap/flower/" +"pull/1558))" msgstr "" #: ../../source/ref-changelog.md:494 msgid "" -"Some users reported that Jupyter Notebooks have not always been easy to " -"use on GPU instances. We listened and made improvements to all of our " -"Jupyter notebooks! Check out the updated notebooks here:" +"Some users reported that Jupyter Notebooks have not always been easy to use " +"on GPU instances. We listened and made improvements to all of our Jupyter " +"notebooks! Check out the updated notebooks here:" msgstr "" #: ../../source/ref-changelog.md:496 msgid "" -"[An Introduction to Federated Learning](https://flower.ai/docs/framework" -"/tutorial-get-started-with-flower-pytorch.html)" +"[An Introduction to Federated Learning](https://flower.ai/docs/framework/" +"tutorial-get-started-with-flower-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:497 msgid "" -"[Strategies in Federated Learning](https://flower.ai/docs/framework" -"/tutorial-use-a-federated-learning-strategy-pytorch.html)" +"[Strategies in Federated Learning](https://flower.ai/docs/framework/tutorial-" +"use-a-federated-learning-strategy-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:498 msgid "" -"[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a" -"-strategy-from-scratch-pytorch.html)" +"[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a-" +"strategy-from-scratch-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:499 msgid "" -"[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-" -"customize-the-client-pytorch.html)" +"[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-customize-" +"the-client-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:501 msgid "" -"**Introduce optional telemetry** " -"([#1533](https://github.com/adap/flower/pull/1533), " -"[#1544](https://github.com/adap/flower/pull/1544), " -"[#1584](https://github.com/adap/flower/pull/1584))" +"**Introduce optional telemetry** ([#1533](https://github.com/adap/flower/" +"pull/1533), [#1544](https://github.com/adap/flower/pull/1544), [#1584]" +"(https://github.com/adap/flower/pull/1584))" msgstr "" #: ../../source/ref-changelog.md:503 msgid "" -"After a [request for " -"feedback](https://github.com/adap/flower/issues/1534) from the community," -" the Flower open-source project introduces optional collection of " -"*anonymous* usage metrics to make well-informed decisions to improve " -"Flower. Doing this enables the Flower team to understand how Flower is " -"used and what challenges users might face." +"After a [request for feedback](https://github.com/adap/flower/issues/1534) " +"from the community, the Flower open-source project introduces optional " +"collection of *anonymous* usage metrics to make well-informed decisions to " +"improve Flower. Doing this enables the Flower team to understand how Flower " +"is used and what challenges users might face." msgstr "" #: ../../source/ref-changelog.md:505 msgid "" -"**Flower is a friendly framework for collaborative AI and data science.**" -" Staying true to this statement, Flower makes it easy to disable " -"telemetry for users who do not want to share anonymous usage metrics. " -"[Read more.](https://flower.ai/docs/telemetry.html)." +"**Flower is a friendly framework for collaborative AI and data science.** " +"Staying true to this statement, Flower makes it easy to disable telemetry " +"for users who do not want to share anonymous usage metrics. [Read more.]" +"(https://flower.ai/docs/telemetry.html)." msgstr "" #: ../../source/ref-changelog.md:507 msgid "" -"**Introduce (experimental) Driver API** " -"([#1520](https://github.com/adap/flower/pull/1520), " -"[#1525](https://github.com/adap/flower/pull/1525), " -"[#1545](https://github.com/adap/flower/pull/1545), " -"[#1546](https://github.com/adap/flower/pull/1546), " -"[#1550](https://github.com/adap/flower/pull/1550), " -"[#1551](https://github.com/adap/flower/pull/1551), " -"[#1567](https://github.com/adap/flower/pull/1567))" +"**Introduce (experimental) Driver API** ([#1520](https://github.com/adap/" +"flower/pull/1520), [#1525](https://github.com/adap/flower/pull/1525), [#1545]" +"(https://github.com/adap/flower/pull/1545), [#1546](https://github.com/adap/" +"flower/pull/1546), [#1550](https://github.com/adap/flower/pull/1550), [#1551]" +"(https://github.com/adap/flower/pull/1551), [#1567](https://github.com/adap/" +"flower/pull/1567))" msgstr "" #: ../../source/ref-changelog.md:509 msgid "" "Flower now has a new (experimental) Driver API which will enable fully " "programmable, async, and multi-tenant Federated Learning and Federated " -"Analytics applications. Phew, that's a lot! Going forward, the Driver API" -" will be the abstraction that many upcoming features will be built on - " -"and you can start building those things now, too." +"Analytics applications. Phew, that's a lot! Going forward, the Driver API " +"will be the abstraction that many upcoming features will be built on - and " +"you can start building those things now, too." msgstr "" #: ../../source/ref-changelog.md:511 msgid "" -"The Driver API also enables a new execution mode in which the server runs" -" indefinitely. Multiple individual workloads can run concurrently and " -"start and stop their execution independent of the server. This is " -"especially useful for users who want to deploy Flower in production." +"The Driver API also enables a new execution mode in which the server runs " +"indefinitely. Multiple individual workloads can run concurrently and start " +"and stop their execution independent of the server. This is especially " +"useful for users who want to deploy Flower in production." msgstr "" #: ../../source/ref-changelog.md:513 msgid "" -"To learn more, check out the `mt-pytorch` code example. We look forward " -"to you feedback!" +"To learn more, check out the `mt-pytorch` code example. We look forward to " +"you feedback!" msgstr "" #: ../../source/ref-changelog.md:515 msgid "" -"Please note: *The Driver API is still experimental and will likely change" -" significantly over time.*" +"Please note: *The Driver API is still experimental and will likely change " +"significantly over time.*" msgstr "" #: ../../source/ref-changelog.md:517 msgid "" -"**Add new Federated Analytics with Pandas example** " -"([#1469](https://github.com/adap/flower/pull/1469), " -"[#1535](https://github.com/adap/flower/pull/1535))" +"**Add new Federated Analytics with Pandas example** ([#1469](https://github." +"com/adap/flower/pull/1469), [#1535](https://github.com/adap/flower/" +"pull/1535))" msgstr "" #: ../../source/ref-changelog.md:519 msgid "" -"A new code example (`quickstart-pandas`) demonstrates federated analytics" -" with Pandas and Flower. You can find it here: [quickstart-" -"pandas](https://github.com/adap/flower/tree/main/examples/quickstart-" -"pandas)." +"A new code example (`quickstart-pandas`) demonstrates federated analytics " +"with Pandas and Flower. You can find it here: [quickstart-pandas](https://" +"github.com/adap/flower/tree/main/examples/quickstart-pandas)." msgstr "" #: ../../source/ref-changelog.md:521 msgid "" -"**Add new strategies: Krum and MultiKrum** " -"([#1481](https://github.com/adap/flower/pull/1481))" +"**Add new strategies: Krum and MultiKrum** ([#1481](https://github.com/adap/" +"flower/pull/1481))" msgstr "" #: ../../source/ref-changelog.md:523 msgid "" "Edoardo, a computer science student at the Sapienza University of Rome, " -"contributed a new `Krum` strategy that enables users to easily use Krum " -"and MultiKrum in their workloads." +"contributed a new `Krum` strategy that enables users to easily use Krum and " +"MultiKrum in their workloads." msgstr "" #: ../../source/ref-changelog.md:525 msgid "" -"**Update C++ example to be compatible with Flower v1.2.0** " -"([#1495](https://github.com/adap/flower/pull/1495))" +"**Update C++ example to be compatible with Flower v1.2.0** ([#1495](https://" +"github.com/adap/flower/pull/1495))" msgstr "" #: ../../source/ref-changelog.md:527 msgid "" -"The C++ code example has received a substantial update to make it " -"compatible with the latest version of Flower." +"The C++ code example has received a substantial update to make it compatible " +"with the latest version of Flower." msgstr "" #: ../../source/ref-changelog.md:529 msgid "" -"**General improvements** " -"([#1491](https://github.com/adap/flower/pull/1491), " -"[#1504](https://github.com/adap/flower/pull/1504), " -"[#1506](https://github.com/adap/flower/pull/1506), " -"[#1514](https://github.com/adap/flower/pull/1514), " -"[#1522](https://github.com/adap/flower/pull/1522), " -"[#1523](https://github.com/adap/flower/pull/1523), " -"[#1526](https://github.com/adap/flower/pull/1526), " -"[#1528](https://github.com/adap/flower/pull/1528), " -"[#1547](https://github.com/adap/flower/pull/1547), " -"[#1549](https://github.com/adap/flower/pull/1549), " -"[#1560](https://github.com/adap/flower/pull/1560), " -"[#1564](https://github.com/adap/flower/pull/1564), " -"[#1566](https://github.com/adap/flower/pull/1566))" +"**General improvements** ([#1491](https://github.com/adap/flower/pull/1491), " +"[#1504](https://github.com/adap/flower/pull/1504), [#1506](https://github." +"com/adap/flower/pull/1506), [#1514](https://github.com/adap/flower/" +"pull/1514), [#1522](https://github.com/adap/flower/pull/1522), [#1523]" +"(https://github.com/adap/flower/pull/1523), [#1526](https://github.com/adap/" +"flower/pull/1526), [#1528](https://github.com/adap/flower/pull/1528), [#1547]" +"(https://github.com/adap/flower/pull/1547), [#1549](https://github.com/adap/" +"flower/pull/1549), [#1560](https://github.com/adap/flower/pull/1560), [#1564]" +"(https://github.com/adap/flower/pull/1564), [#1566](https://github.com/adap/" +"flower/pull/1566))" msgstr "" #: ../../source/ref-changelog.md:533 msgid "" -"**Updated documentation** " -"([#1494](https://github.com/adap/flower/pull/1494), " -"[#1496](https://github.com/adap/flower/pull/1496), " -"[#1500](https://github.com/adap/flower/pull/1500), " -"[#1503](https://github.com/adap/flower/pull/1503), " -"[#1505](https://github.com/adap/flower/pull/1505), " -"[#1524](https://github.com/adap/flower/pull/1524), " -"[#1518](https://github.com/adap/flower/pull/1518), " -"[#1519](https://github.com/adap/flower/pull/1519), " -"[#1515](https://github.com/adap/flower/pull/1515))" +"**Updated documentation** ([#1494](https://github.com/adap/flower/" +"pull/1494), [#1496](https://github.com/adap/flower/pull/1496), [#1500]" +"(https://github.com/adap/flower/pull/1500), [#1503](https://github.com/adap/" +"flower/pull/1503), [#1505](https://github.com/adap/flower/pull/1505), [#1524]" +"(https://github.com/adap/flower/pull/1524), [#1518](https://github.com/adap/" +"flower/pull/1518), [#1519](https://github.com/adap/flower/pull/1519), [#1515]" +"(https://github.com/adap/flower/pull/1515))" msgstr "" #: ../../source/ref-changelog.md:537 msgid "" -"One highlight is the new [first time contributor " -"guide](https://flower.ai/docs/first-time-contributors.html): if you've " -"never contributed on GitHub before, this is the perfect place to start!" +"One highlight is the new [first time contributor guide](https://flower.ai/" +"docs/first-time-contributors.html): if you've never contributed on GitHub " +"before, this is the perfect place to start!" msgstr "" #: ../../source/ref-changelog.md:543 @@ -15645,110 +15211,106 @@ msgstr "" msgid "" "`Akis Linardos`, `Christopher S`, `Daniel J. Beutel`, `George`, `Jan " "Schlicht`, `Mohammad Fares`, `Pedro Porto Buarque de Gusmão`, `Philipp " -"Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, " -"`danielnugraha`, `edogab33`" +"Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, `danielnugraha`, " +"`edogab33`" msgstr "" #: ../../source/ref-changelog.md:553 msgid "" -"**Introduce Differential Privacy wrappers (preview)** " -"([#1357](https://github.com/adap/flower/pull/1357), " -"[#1460](https://github.com/adap/flower/pull/1460))" +"**Introduce Differential Privacy wrappers (preview)** ([#1357](https://" +"github.com/adap/flower/pull/1357), [#1460](https://github.com/adap/flower/" +"pull/1460))" msgstr "" #: ../../source/ref-changelog.md:555 msgid "" -"The first (experimental) preview of pluggable Differential Privacy " -"wrappers enables easy configuration and usage of differential privacy " -"(DP). The pluggable DP wrappers enable framework-agnostic **and** " -"strategy-agnostic usage of both client-side DP and server-side DP. Head " -"over to the Flower docs, a new explainer goes into more detail." +"The first (experimental) preview of pluggable Differential Privacy wrappers " +"enables easy configuration and usage of differential privacy (DP). The " +"pluggable DP wrappers enable framework-agnostic **and** strategy-agnostic " +"usage of both client-side DP and server-side DP. Head over to the Flower " +"docs, a new explainer goes into more detail." msgstr "" #: ../../source/ref-changelog.md:557 msgid "" -"**New iOS CoreML code example** " -"([#1289](https://github.com/adap/flower/pull/1289))" +"**New iOS CoreML code example** ([#1289](https://github.com/adap/flower/" +"pull/1289))" msgstr "" #: ../../source/ref-changelog.md:559 msgid "" -"Flower goes iOS! A massive new code example shows how Flower clients can " -"be built for iOS. The code example contains both Flower iOS SDK " -"components that can be used for many tasks, and one task example running " -"on CoreML." +"Flower goes iOS! A massive new code example shows how Flower clients can be " +"built for iOS. The code example contains both Flower iOS SDK components that " +"can be used for many tasks, and one task example running on CoreML." msgstr "" #: ../../source/ref-changelog.md:561 msgid "" -"**New FedMedian strategy** " -"([#1461](https://github.com/adap/flower/pull/1461))" +"**New FedMedian strategy** ([#1461](https://github.com/adap/flower/" +"pull/1461))" msgstr "" #: ../../source/ref-changelog.md:563 msgid "" -"The new `FedMedian` strategy implements Federated Median (FedMedian) by " -"[Yin et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." +"The new `FedMedian` strategy implements Federated Median (FedMedian) by [Yin " +"et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." msgstr "" #: ../../source/ref-changelog.md:565 msgid "" -"**Log** `Client` **exceptions in Virtual Client Engine** " -"([#1493](https://github.com/adap/flower/pull/1493))" +"**Log** `Client` **exceptions in Virtual Client Engine** ([#1493](https://" +"github.com/adap/flower/pull/1493))" msgstr "" #: ../../source/ref-changelog.md:567 msgid "" -"All `Client` exceptions happening in the VCE are now logged by default " -"and not just exposed to the configured `Strategy` (via the `failures` " -"argument)." +"All `Client` exceptions happening in the VCE are now logged by default and " +"not just exposed to the configured `Strategy` (via the `failures` argument)." msgstr "" #: ../../source/ref-changelog.md:569 msgid "" -"**Improve Virtual Client Engine internals** " -"([#1401](https://github.com/adap/flower/pull/1401), " -"[#1453](https://github.com/adap/flower/pull/1453))" +"**Improve Virtual Client Engine internals** ([#1401](https://github.com/adap/" +"flower/pull/1401), [#1453](https://github.com/adap/flower/pull/1453))" msgstr "" #: ../../source/ref-changelog.md:571 msgid "" -"Some internals of the Virtual Client Engine have been revamped. The VCE " -"now uses Ray 2.0 under the hood, the value type of the `client_resources`" -" dictionary changed to `float` to allow fractions of resources to be " +"Some internals of the Virtual Client Engine have been revamped. The VCE now " +"uses Ray 2.0 under the hood, the value type of the `client_resources` " +"dictionary changed to `float` to allow fractions of resources to be " "allocated." msgstr "" #: ../../source/ref-changelog.md:573 msgid "" -"**Support optional** `Client`**/**`NumPyClient` **methods in Virtual " -"Client Engine**" +"**Support optional** `Client`**/**`NumPyClient` **methods in Virtual Client " +"Engine**" msgstr "" #: ../../source/ref-changelog.md:575 msgid "" -"The Virtual Client Engine now has full support for optional `Client` (and" -" `NumPyClient`) methods." +"The Virtual Client Engine now has full support for optional `Client` (and " +"`NumPyClient`) methods." msgstr "" #: ../../source/ref-changelog.md:577 msgid "" -"**Provide type information to packages using** `flwr` " -"([#1377](https://github.com/adap/flower/pull/1377))" +"**Provide type information to packages using** `flwr` ([#1377](https://" +"github.com/adap/flower/pull/1377))" msgstr "" #: ../../source/ref-changelog.md:579 msgid "" -"The package `flwr` is now bundled with a `py.typed` file indicating that " -"the package is typed. This enables typing support for projects or " -"packages that use `flwr` by enabling them to improve their code using " -"static type checkers like `mypy`." +"The package `flwr` is now bundled with a `py.typed` file indicating that the " +"package is typed. This enables typing support for projects or packages that " +"use `flwr` by enabling them to improve their code using static type checkers " +"like `mypy`." msgstr "" #: ../../source/ref-changelog.md:581 msgid "" -"**Updated code example** " -"([#1344](https://github.com/adap/flower/pull/1344), " +"**Updated code example** ([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" msgstr "" @@ -15760,24 +15322,18 @@ msgstr "" #: ../../source/ref-changelog.md:585 msgid "" -"**Updated documentation** " -"([#1355](https://github.com/adap/flower/pull/1355), " -"[#1558](https://github.com/adap/flower/pull/1558), " -"[#1379](https://github.com/adap/flower/pull/1379), " -"[#1380](https://github.com/adap/flower/pull/1380), " -"[#1381](https://github.com/adap/flower/pull/1381), " -"[#1332](https://github.com/adap/flower/pull/1332), " -"[#1391](https://github.com/adap/flower/pull/1391), " -"[#1403](https://github.com/adap/flower/pull/1403), " -"[#1364](https://github.com/adap/flower/pull/1364), " -"[#1409](https://github.com/adap/flower/pull/1409), " -"[#1419](https://github.com/adap/flower/pull/1419), " -"[#1444](https://github.com/adap/flower/pull/1444), " -"[#1448](https://github.com/adap/flower/pull/1448), " -"[#1417](https://github.com/adap/flower/pull/1417), " -"[#1449](https://github.com/adap/flower/pull/1449), " -"[#1465](https://github.com/adap/flower/pull/1465), " -"[#1467](https://github.com/adap/flower/pull/1467))" +"**Updated documentation** ([#1355](https://github.com/adap/flower/" +"pull/1355), [#1558](https://github.com/adap/flower/pull/1558), [#1379]" +"(https://github.com/adap/flower/pull/1379), [#1380](https://github.com/adap/" +"flower/pull/1380), [#1381](https://github.com/adap/flower/pull/1381), [#1332]" +"(https://github.com/adap/flower/pull/1332), [#1391](https://github.com/adap/" +"flower/pull/1391), [#1403](https://github.com/adap/flower/pull/1403), [#1364]" +"(https://github.com/adap/flower/pull/1364), [#1409](https://github.com/adap/" +"flower/pull/1409), [#1419](https://github.com/adap/flower/pull/1419), [#1444]" +"(https://github.com/adap/flower/pull/1444), [#1448](https://github.com/adap/" +"flower/pull/1448), [#1417](https://github.com/adap/flower/pull/1417), [#1449]" +"(https://github.com/adap/flower/pull/1449), [#1465](https://github.com/adap/" +"flower/pull/1465), [#1467](https://github.com/adap/flower/pull/1467))" msgstr "" #: ../../source/ref-changelog.md:587 @@ -15788,47 +15344,45 @@ msgstr "" #: ../../source/ref-changelog.md:589 msgid "" -"**Restructured documentation** " -"([#1387](https://github.com/adap/flower/pull/1387))" +"**Restructured documentation** ([#1387](https://github.com/adap/flower/" +"pull/1387))" msgstr "" #: ../../source/ref-changelog.md:591 msgid "" -"The documentation has been restructured to make it easier to navigate. " -"This is just the first step in a larger effort to make the Flower " -"documentation the best documentation of any project ever. Stay tuned!" +"The documentation has been restructured to make it easier to navigate. This " +"is just the first step in a larger effort to make the Flower documentation " +"the best documentation of any project ever. Stay tuned!" msgstr "" #: ../../source/ref-changelog.md:593 msgid "" -"**Open in Colab button** " -"([#1389](https://github.com/adap/flower/pull/1389))" +"**Open in Colab button** ([#1389](https://github.com/adap/flower/pull/1389))" msgstr "" #: ../../source/ref-changelog.md:595 msgid "" -"The four parts of the Flower Federated Learning Tutorial now come with a " -"new `Open in Colab` button. No need to install anything on your local " -"machine, you can now use and learn about Flower in your browser, it's " -"only a single click away." +"The four parts of the Flower Federated Learning Tutorial now come with a new " +"`Open in Colab` button. No need to install anything on your local machine, " +"you can now use and learn about Flower in your browser, it's only a single " +"click away." msgstr "" #: ../../source/ref-changelog.md:597 msgid "" -"**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468)," -" [#1470](https://github.com/adap/flower/pull/1470), " -"[#1472](https://github.com/adap/flower/pull/1472), " -"[#1473](https://github.com/adap/flower/pull/1473), " -"[#1474](https://github.com/adap/flower/pull/1474), " -"[#1475](https://github.com/adap/flower/pull/1475))" +"**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468), " +"[#1470](https://github.com/adap/flower/pull/1470), [#1472](https://github." +"com/adap/flower/pull/1472), [#1473](https://github.com/adap/flower/" +"pull/1473), [#1474](https://github.com/adap/flower/pull/1474), [#1475]" +"(https://github.com/adap/flower/pull/1475))" msgstr "" #: ../../source/ref-changelog.md:599 msgid "" "The Flower Federated Learning Tutorial has two brand-new parts covering " "custom strategies (still WIP) and the distinction between `Client` and " -"`NumPyClient`. The existing parts one and two have also been improved " -"(many small changes and fixes)." +"`NumPyClient`. The existing parts one and two have also been improved (many " +"small changes and fixes)." msgstr "" #: ../../source/ref-changelog.md:605 @@ -15853,93 +15407,79 @@ msgstr "" #: ../../source/ref-changelog.md:612 msgid "" -"Tons of small API cleanups resulting in a more coherent developer " -"experience" +"Tons of small API cleanups resulting in a more coherent developer experience" msgstr "" #: ../../source/ref-changelog.md:616 msgid "" "We would like to give our **special thanks** to all the contributors who " -"made Flower 1.0 possible (in reverse [GitHub " -"Contributors](https://github.com/adap/flower/graphs/contributors) order):" +"made Flower 1.0 possible (in reverse [GitHub Contributors](https://github." +"com/adap/flower/graphs/contributors) order):" msgstr "" #: ../../source/ref-changelog.md:618 msgid "" -"[@rtaiello](https://github.com/rtaiello), " -"[@g-pichler](https://github.com/g-pichler), [@rob-" -"luke](https://github.com/rob-luke), [@andreea-zaharia](https://github.com" -"/andreea-zaharia), [@kinshukdua](https://github.com/kinshukdua), " -"[@nfnt](https://github.com/nfnt), " -"[@tatiana-s](https://github.com/tatiana-s), " -"[@TParcollet](https://github.com/TParcollet), " -"[@vballoli](https://github.com/vballoli), " -"[@negedng](https://github.com/negedng), " -"[@RISHIKESHAVAN](https://github.com/RISHIKESHAVAN), " -"[@hei411](https://github.com/hei411), " -"[@SebastianSpeitel](https://github.com/SebastianSpeitel), " -"[@AmitChaulwar](https://github.com/AmitChaulwar), " -"[@Rubiel1](https://github.com/Rubiel1), [@FANTOME-PAN](https://github.com" -"/FANTOME-PAN), [@Rono-BC](https://github.com/Rono-BC), " -"[@lbhm](https://github.com/lbhm), " -"[@sishtiaq](https://github.com/sishtiaq), " -"[@remde](https://github.com/remde), [@Jueun-Park](https://github.com" -"/Jueun-Park), [@architjen](https://github.com/architjen), " -"[@PratikGarai](https://github.com/PratikGarai), " -"[@mrinaald](https://github.com/mrinaald), " -"[@zliel](https://github.com/zliel), " -"[@MeiruiJiang](https://github.com/MeiruiJiang), " -"[@sancarlim](https://github.com/sancarlim), " -"[@gubertoli](https://github.com/gubertoli), " -"[@Vingt100](https://github.com/Vingt100), " -"[@MakGulati](https://github.com/MakGulati), " -"[@cozek](https://github.com/cozek), " -"[@jafermarq](https://github.com/jafermarq), " -"[@sisco0](https://github.com/sisco0), " -"[@akhilmathurs](https://github.com/akhilmathurs), " -"[@CanTuerk](https://github.com/CanTuerk), " -"[@mariaboerner1987](https://github.com/mariaboerner1987), " -"[@pedropgusmao](https://github.com/pedropgusmao), " -"[@tanertopal](https://github.com/tanertopal), " -"[@danieljanes](https://github.com/danieljanes)." +"[@rtaiello](https://github.com/rtaiello), [@g-pichler](https://github.com/g-" +"pichler), [@rob-luke](https://github.com/rob-luke), [@andreea-zaharia]" +"(https://github.com/andreea-zaharia), [@kinshukdua](https://github.com/" +"kinshukdua), [@nfnt](https://github.com/nfnt), [@tatiana-s](https://github." +"com/tatiana-s), [@TParcollet](https://github.com/TParcollet), [@vballoli]" +"(https://github.com/vballoli), [@negedng](https://github.com/negedng), " +"[@RISHIKESHAVAN](https://github.com/RISHIKESHAVAN), [@hei411](https://github." +"com/hei411), [@SebastianSpeitel](https://github.com/SebastianSpeitel), " +"[@AmitChaulwar](https://github.com/AmitChaulwar), [@Rubiel1](https://github." +"com/Rubiel1), [@FANTOME-PAN](https://github.com/FANTOME-PAN), [@Rono-BC]" +"(https://github.com/Rono-BC), [@lbhm](https://github.com/lbhm), [@sishtiaq]" +"(https://github.com/sishtiaq), [@remde](https://github.com/remde), [@Jueun-" +"Park](https://github.com/Jueun-Park), [@architjen](https://github.com/" +"architjen), [@PratikGarai](https://github.com/PratikGarai), [@mrinaald]" +"(https://github.com/mrinaald), [@zliel](https://github.com/zliel), " +"[@MeiruiJiang](https://github.com/MeiruiJiang), [@sancarlim](https://github." +"com/sancarlim), [@gubertoli](https://github.com/gubertoli), [@Vingt100]" +"(https://github.com/Vingt100), [@MakGulati](https://github.com/MakGulati), " +"[@cozek](https://github.com/cozek), [@jafermarq](https://github.com/" +"jafermarq), [@sisco0](https://github.com/sisco0), [@akhilmathurs](https://" +"github.com/akhilmathurs), [@CanTuerk](https://github.com/CanTuerk), " +"[@mariaboerner1987](https://github.com/mariaboerner1987), [@pedropgusmao]" +"(https://github.com/pedropgusmao), [@tanertopal](https://github.com/" +"tanertopal), [@danieljanes](https://github.com/danieljanes)." msgstr "" #: ../../source/ref-changelog.md:622 msgid "" -"**All arguments must be passed as keyword arguments** " -"([#1338](https://github.com/adap/flower/pull/1338))" +"**All arguments must be passed as keyword arguments** ([#1338](https://" +"github.com/adap/flower/pull/1338))" msgstr "" #: ../../source/ref-changelog.md:624 msgid "" -"Pass all arguments as keyword arguments, positional arguments are not " -"longer supported. Code that uses positional arguments (e.g., " -"`start_client(\"127.0.0.1:8080\", FlowerClient())`) must add the keyword " -"for each positional argument (e.g., " -"`start_client(server_address=\"127.0.0.1:8080\", " -"client=FlowerClient())`)." +"Pass all arguments as keyword arguments, positional arguments are not longer " +"supported. Code that uses positional arguments (e.g., " +"`start_client(\"127.0.0.1:8080\", FlowerClient())`) must add the keyword for " +"each positional argument (e.g., " +"`start_client(server_address=\"127.0.0.1:8080\", client=FlowerClient())`)." msgstr "" #: ../../source/ref-changelog.md:626 msgid "" "**Introduce configuration object** `ServerConfig` **in** `start_server` " -"**and** `start_simulation` " -"([#1317](https://github.com/adap/flower/pull/1317))" +"**and** `start_simulation` ([#1317](https://github.com/adap/flower/" +"pull/1317))" msgstr "" #: ../../source/ref-changelog.md:628 msgid "" -"Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": " -"600.0}`, `start_server` and `start_simulation` now expect a configuration" -" object of type `flwr.server.ServerConfig`. `ServerConfig` takes the same" -" arguments that as the previous config dict, but it makes writing type-" -"safe code easier and the default parameters values more transparent." +"Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": 600.0}" +"`, `start_server` and `start_simulation` now expect a configuration object " +"of type `flwr.server.ServerConfig`. `ServerConfig` takes the same arguments " +"that as the previous config dict, but it makes writing type-safe code easier " +"and the default parameters values more transparent." msgstr "" #: ../../source/ref-changelog.md:630 msgid "" -"**Rename built-in strategy parameters for clarity** " -"([#1334](https://github.com/adap/flower/pull/1334))" +"**Rename built-in strategy parameters for clarity** ([#1334](https://github." +"com/adap/flower/pull/1334))" msgstr "" #: ../../source/ref-changelog.md:632 @@ -15962,17 +15502,17 @@ msgstr "" #: ../../source/ref-changelog.md:638 msgid "" -"**Update default arguments of built-in strategies** " -"([#1278](https://github.com/adap/flower/pull/1278))" +"**Update default arguments of built-in strategies** ([#1278](https://github." +"com/adap/flower/pull/1278))" msgstr "" #: ../../source/ref-changelog.md:640 msgid "" "All built-in strategies now use `fraction_fit=1.0` and " -"`fraction_evaluate=1.0`, which means they select *all* currently " -"available clients for training and evaluation. Projects that relied on " -"the previous default values can get the previous behaviour by " -"initializing the strategy in the following way:" +"`fraction_evaluate=1.0`, which means they select *all* currently available " +"clients for training and evaluation. Projects that relied on the previous " +"default values can get the previous behaviour by initializing the strategy " +"in the following way:" msgstr "" #: ../../source/ref-changelog.md:642 @@ -15981,14 +15521,14 @@ msgstr "" #: ../../source/ref-changelog.md:644 msgid "" -"**Add** `server_round` **to** `Strategy.evaluate` " -"([#1334](https://github.com/adap/flower/pull/1334))" +"**Add** `server_round` **to** `Strategy.evaluate` ([#1334](https://github." +"com/adap/flower/pull/1334))" msgstr "" #: ../../source/ref-changelog.md:646 msgid "" -"The `Strategy` method `evaluate` now receives the current round of " -"federated learning/evaluation as the first parameter." +"The `Strategy` method `evaluate` now receives the current round of federated " +"learning/evaluation as the first parameter." msgstr "" #: ../../source/ref-changelog.md:648 @@ -16001,39 +15541,40 @@ msgstr "" msgid "" "The `evaluate_fn` passed to built-in strategies like `FedAvg` now takes " "three parameters: (1) The current round of federated learning/evaluation " -"(`server_round`), (2) the model parameters to evaluate (`parameters`), " -"and (3) a config dictionary (`config`)." +"(`server_round`), (2) the model parameters to evaluate (`parameters`), and " +"(3) a config dictionary (`config`)." msgstr "" #: ../../source/ref-changelog.md:652 msgid "" -"**Rename** `rnd` **to** `server_round` " -"([#1321](https://github.com/adap/flower/pull/1321))" +"**Rename** `rnd` **to** `server_round` ([#1321](https://github.com/adap/" +"flower/pull/1321))" msgstr "" #: ../../source/ref-changelog.md:654 msgid "" "Several Flower methods and functions (`evaluate_fn`, `configure_fit`, " "`aggregate_fit`, `configure_evaluate`, `aggregate_evaluate`) receive the " -"current round of federated learning/evaluation as their first parameter. " -"To improve reaability and avoid confusion with *random*, this parameter " -"has been renamed from `rnd` to `server_round`." +"current round of federated learning/evaluation as their first parameter. To " +"improve reaability and avoid confusion with *random*, this parameter has " +"been renamed from `rnd` to `server_round`." msgstr "" #: ../../source/ref-changelog.md:656 msgid "" -"**Move** `flwr.dataset` **to** `flwr_baselines` " -"([#1273](https://github.com/adap/flower/pull/1273))" +"**Move** `flwr.dataset` **to** `flwr_baselines` ([#1273](https://github.com/" +"adap/flower/pull/1273))" msgstr "" #: ../../source/ref-changelog.md:658 -msgid "The experimental package `flwr.dataset` was migrated to Flower Baselines." +msgid "" +"The experimental package `flwr.dataset` was migrated to Flower Baselines." msgstr "" #: ../../source/ref-changelog.md:660 msgid "" -"**Remove experimental strategies** " -"([#1280](https://github.com/adap/flower/pull/1280))" +"**Remove experimental strategies** ([#1280](https://github.com/adap/flower/" +"pull/1280))" msgstr "" #: ../../source/ref-changelog.md:662 @@ -16044,9 +15585,8 @@ msgstr "" #: ../../source/ref-changelog.md:664 msgid "" -"**Rename** `Weights` **to** `NDArrays` " -"([#1258](https://github.com/adap/flower/pull/1258), " -"[#1259](https://github.com/adap/flower/pull/1259))" +"**Rename** `Weights` **to** `NDArrays` ([#1258](https://github.com/adap/" +"flower/pull/1258), [#1259](https://github.com/adap/flower/pull/1259))" msgstr "" #: ../../source/ref-changelog.md:666 @@ -16057,21 +15597,21 @@ msgstr "" #: ../../source/ref-changelog.md:668 msgid "" -"**Remove antiquated** `force_final_distributed_eval` **from** " -"`start_server` ([#1258](https://github.com/adap/flower/pull/1258), " -"[#1259](https://github.com/adap/flower/pull/1259))" +"**Remove antiquated** `force_final_distributed_eval` **from** `start_server` " +"([#1258](https://github.com/adap/flower/pull/1258), [#1259](https://github." +"com/adap/flower/pull/1259))" msgstr "" #: ../../source/ref-changelog.md:670 msgid "" -"The `start_server` parameter `force_final_distributed_eval` has long been" -" a historic artefact, in this release it is finally gone for good." +"The `start_server` parameter `force_final_distributed_eval` has long been a " +"historic artefact, in this release it is finally gone for good." msgstr "" #: ../../source/ref-changelog.md:672 msgid "" -"**Make** `get_parameters` **configurable** " -"([#1242](https://github.com/adap/flower/pull/1242))" +"**Make** `get_parameters` **configurable** ([#1242](https://github.com/adap/" +"flower/pull/1242))" msgstr "" #: ../../source/ref-changelog.md:674 @@ -16089,64 +15629,62 @@ msgstr "" #: ../../source/ref-changelog.md:678 msgid "" "The `start_simulation` function now accepts a configuration dictionary " -"`config` instead of the `num_rounds` integer. This improves the " -"consistency between `start_simulation` and `start_server` and makes " -"transitioning between the two easier." +"`config` instead of the `num_rounds` integer. This improves the consistency " +"between `start_simulation` and `start_server` and makes transitioning " +"between the two easier." msgstr "" #: ../../source/ref-changelog.md:682 msgid "" -"**Support Python 3.10** " -"([#1320](https://github.com/adap/flower/pull/1320))" +"**Support Python 3.10** ([#1320](https://github.com/adap/flower/pull/1320))" msgstr "" #: ../../source/ref-changelog.md:684 msgid "" -"The previous Flower release introduced experimental support for Python " -"3.10, this release declares Python 3.10 support as stable." +"The previous Flower release introduced experimental support for Python 3.10, " +"this release declares Python 3.10 support as stable." msgstr "" #: ../../source/ref-changelog.md:686 msgid "" -"**Make all** `Client` **and** `NumPyClient` **methods optional** " -"([#1260](https://github.com/adap/flower/pull/1260), " -"[#1277](https://github.com/adap/flower/pull/1277))" +"**Make all** `Client` **and** `NumPyClient` **methods optional** ([#1260]" +"(https://github.com/adap/flower/pull/1260), [#1277](https://github.com/adap/" +"flower/pull/1277))" msgstr "" #: ../../source/ref-changelog.md:688 msgid "" "The `Client`/`NumPyClient` methods `get_properties`, `get_parameters`, " -"`fit`, and `evaluate` are all optional. This enables writing clients that" -" implement, for example, only `fit`, but no other method. No need to " +"`fit`, and `evaluate` are all optional. This enables writing clients that " +"implement, for example, only `fit`, but no other method. No need to " "implement `evaluate` when using centralized evaluation!" msgstr "" #: ../../source/ref-changelog.md:690 msgid "" -"**Enable passing a** `Server` **instance to** `start_simulation` " -"([#1281](https://github.com/adap/flower/pull/1281))" +"**Enable passing a** `Server` **instance to** `start_simulation` ([#1281]" +"(https://github.com/adap/flower/pull/1281))" msgstr "" #: ../../source/ref-changelog.md:692 msgid "" -"Similar to `start_server`, `start_simulation` now accepts a full `Server`" -" instance. This enables users to heavily customize the execution of " -"eperiments and opens the door to running, for example, async FL using the" -" Virtual Client Engine." +"Similar to `start_server`, `start_simulation` now accepts a full `Server` " +"instance. This enables users to heavily customize the execution of " +"eperiments and opens the door to running, for example, async FL using the " +"Virtual Client Engine." msgstr "" #: ../../source/ref-changelog.md:694 msgid "" -"**Update code examples** " -"([#1291](https://github.com/adap/flower/pull/1291), " -"[#1286](https://github.com/adap/flower/pull/1286), " -"[#1282](https://github.com/adap/flower/pull/1282))" +"**Update code examples** ([#1291](https://github.com/adap/flower/pull/1291), " +"[#1286](https://github.com/adap/flower/pull/1286), [#1282](https://github." +"com/adap/flower/pull/1282))" msgstr "" #: ../../source/ref-changelog.md:696 msgid "" -"Many code examples received small or even large maintenance updates, " -"among them are" +"Many code examples received small or even large maintenance updates, among " +"them are" msgstr "" #: ../../source/ref-changelog.md:698 @@ -16175,8 +15713,8 @@ msgstr "" #: ../../source/ref-changelog.md:705 msgid "" -"**Remove the obsolete simulation example** " -"([#1328](https://github.com/adap/flower/pull/1328))" +"**Remove the obsolete simulation example** ([#1328](https://github.com/adap/" +"flower/pull/1328))" msgstr "" #: ../../source/ref-changelog.md:707 @@ -16188,27 +15726,24 @@ msgstr "" #: ../../source/ref-changelog.md:709 msgid "" -"**Update documentation** " -"([#1223](https://github.com/adap/flower/pull/1223), " -"[#1209](https://github.com/adap/flower/pull/1209), " -"[#1251](https://github.com/adap/flower/pull/1251), " -"[#1257](https://github.com/adap/flower/pull/1257), " -"[#1267](https://github.com/adap/flower/pull/1267), " -"[#1268](https://github.com/adap/flower/pull/1268), " -"[#1300](https://github.com/adap/flower/pull/1300), " -"[#1304](https://github.com/adap/flower/pull/1304), " -"[#1305](https://github.com/adap/flower/pull/1305), " -"[#1307](https://github.com/adap/flower/pull/1307))" +"**Update documentation** ([#1223](https://github.com/adap/flower/pull/1223), " +"[#1209](https://github.com/adap/flower/pull/1209), [#1251](https://github." +"com/adap/flower/pull/1251), [#1257](https://github.com/adap/flower/" +"pull/1257), [#1267](https://github.com/adap/flower/pull/1267), [#1268]" +"(https://github.com/adap/flower/pull/1268), [#1300](https://github.com/adap/" +"flower/pull/1300), [#1304](https://github.com/adap/flower/pull/1304), [#1305]" +"(https://github.com/adap/flower/pull/1305), [#1307](https://github.com/adap/" +"flower/pull/1307))" msgstr "" #: ../../source/ref-changelog.md:711 msgid "" "One substantial documentation update fixes multiple smaller rendering " "issues, makes titles more succinct to improve navigation, removes a " -"deprecated library, updates documentation dependencies, includes the " -"`flwr.common` module in the API reference, includes support for markdown-" -"based documentation, migrates the changelog from `.rst` to `.md`, and " -"fixes a number of smaller details!" +"deprecated library, updates documentation dependencies, includes the `flwr." +"common` module in the API reference, includes support for markdown-based " +"documentation, migrates the changelog from `.rst` to `.md`, and fixes a " +"number of smaller details!" msgstr "" #: ../../source/ref-changelog.md:713 ../../source/ref-changelog.md:768 @@ -16218,30 +15753,28 @@ msgstr "" #: ../../source/ref-changelog.md:715 msgid "" -"Add round number to fit and evaluate log messages " -"([#1266](https://github.com/adap/flower/pull/1266))" +"Add round number to fit and evaluate log messages ([#1266](https://github." +"com/adap/flower/pull/1266))" msgstr "" #: ../../source/ref-changelog.md:716 msgid "" -"Add secure gRPC connection to the `advanced_tensorflow` code example " -"([#847](https://github.com/adap/flower/pull/847))" +"Add secure gRPC connection to the `advanced_tensorflow` code example ([#847]" +"(https://github.com/adap/flower/pull/847))" msgstr "" #: ../../source/ref-changelog.md:717 msgid "" -"Update developer tooling " -"([#1231](https://github.com/adap/flower/pull/1231), " -"[#1276](https://github.com/adap/flower/pull/1276), " -"[#1301](https://github.com/adap/flower/pull/1301), " -"[#1310](https://github.com/adap/flower/pull/1310))" +"Update developer tooling ([#1231](https://github.com/adap/flower/pull/1231), " +"[#1276](https://github.com/adap/flower/pull/1276), [#1301](https://github." +"com/adap/flower/pull/1301), [#1310](https://github.com/adap/flower/" +"pull/1310))" msgstr "" #: ../../source/ref-changelog.md:718 msgid "" -"Rename ProtoBuf messages to improve consistency " -"([#1214](https://github.com/adap/flower/pull/1214), " -"[#1258](https://github.com/adap/flower/pull/1258), " +"Rename ProtoBuf messages to improve consistency ([#1214](https://github.com/" +"adap/flower/pull/1214), [#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" @@ -16251,123 +15784,120 @@ msgstr "" #: ../../source/ref-changelog.md:724 msgid "" -"**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** " -"([#919](https://github.com/adap/flower/pull/919), " -"[#1127](https://github.com/adap/flower/pull/1127), " -"[#914](https://github.com/adap/flower/pull/914))" +"**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** ([#919](https://" +"github.com/adap/flower/pull/919), [#1127](https://github.com/adap/flower/" +"pull/1127), [#914](https://github.com/adap/flower/pull/914))" msgstr "" #: ../../source/ref-changelog.md:726 msgid "" "The first preview release of Flower Baselines has arrived! We're " "kickstarting Flower Baselines with implementations of FedOpt (FedYogi, " -"FedAdam, FedAdagrad), FedBN, and FedAvgM. Check the documentation on how " -"to use [Flower Baselines](https://flower.ai/docs/using-baselines.html). " -"With this first preview release we're also inviting the community to " -"[contribute their own baselines](https://flower.ai/docs/baselines/how-to-" -"contribute-baselines.html)." +"FedAdam, FedAdagrad), FedBN, and FedAvgM. Check the documentation on how to " +"use [Flower Baselines](https://flower.ai/docs/using-baselines.html). With " +"this first preview release we're also inviting the community to [contribute " +"their own baselines](https://flower.ai/docs/baselines/how-to-contribute-" +"baselines.html)." msgstr "" #: ../../source/ref-changelog.md:728 msgid "" -"**C++ client SDK (preview) and code example** " -"([#1111](https://github.com/adap/flower/pull/1111))" +"**C++ client SDK (preview) and code example** ([#1111](https://github.com/" +"adap/flower/pull/1111))" msgstr "" #: ../../source/ref-changelog.md:730 msgid "" -"Preview support for Flower clients written in C++. The C++ preview " -"includes a Flower client SDK and a quickstart code example that " -"demonstrates a simple C++ client using the SDK." +"Preview support for Flower clients written in C++. The C++ preview includes " +"a Flower client SDK and a quickstart code example that demonstrates a simple " +"C++ client using the SDK." msgstr "" #: ../../source/ref-changelog.md:732 msgid "" -"**Add experimental support for Python 3.10 and Python 3.11** " -"([#1135](https://github.com/adap/flower/pull/1135))" +"**Add experimental support for Python 3.10 and Python 3.11** ([#1135]" +"(https://github.com/adap/flower/pull/1135))" msgstr "" #: ../../source/ref-changelog.md:734 msgid "" -"Python 3.10 is the latest stable release of Python and Python 3.11 is due" -" to be released in October. This Flower release adds experimental support" -" for both Python versions." +"Python 3.10 is the latest stable release of Python and Python 3.11 is due to " +"be released in October. This Flower release adds experimental support for " +"both Python versions." msgstr "" #: ../../source/ref-changelog.md:736 msgid "" -"**Aggregate custom metrics through user-provided functions** " -"([#1144](https://github.com/adap/flower/pull/1144))" +"**Aggregate custom metrics through user-provided functions** ([#1144]" +"(https://github.com/adap/flower/pull/1144))" msgstr "" #: ../../source/ref-changelog.md:738 msgid "" -"Custom metrics (e.g., `accuracy`) can now be aggregated without having to" -" customize the strategy. Built-in strategies support two new arguments, " +"Custom metrics (e.g., `accuracy`) can now be aggregated without having to " +"customize the strategy. Built-in strategies support two new arguments, " "`fit_metrics_aggregation_fn` and `evaluate_metrics_aggregation_fn`, that " "allow passing custom metric aggregation functions." msgstr "" #: ../../source/ref-changelog.md:740 msgid "" -"**User-configurable round timeout** " -"([#1162](https://github.com/adap/flower/pull/1162))" +"**User-configurable round timeout** ([#1162](https://github.com/adap/flower/" +"pull/1162))" msgstr "" #: ../../source/ref-changelog.md:742 msgid "" "A new configuration value allows the round timeout to be set for " -"`start_server` and `start_simulation`. If the `config` dictionary " -"contains a `round_timeout` key (with a `float` value in seconds), the " -"server will wait *at least* `round_timeout` seconds before it closes the " -"connection." +"`start_server` and `start_simulation`. If the `config` dictionary contains a " +"`round_timeout` key (with a `float` value in seconds), the server will wait " +"*at least* `round_timeout` seconds before it closes the connection." msgstr "" #: ../../source/ref-changelog.md:744 msgid "" -"**Enable both federated evaluation and centralized evaluation to be used " -"at the same time in all built-in strategies** " -"([#1091](https://github.com/adap/flower/pull/1091))" +"**Enable both federated evaluation and centralized evaluation to be used at " +"the same time in all built-in strategies** ([#1091](https://github.com/adap/" +"flower/pull/1091))" msgstr "" #: ../../source/ref-changelog.md:746 msgid "" -"Built-in strategies can now perform both federated evaluation (i.e., " -"client-side) and centralized evaluation (i.e., server-side) in the same " -"round. Federated evaluation can be disabled by setting `fraction_eval` to" -" `0.0`." +"Built-in strategies can now perform both federated evaluation (i.e., client-" +"side) and centralized evaluation (i.e., server-side) in the same round. " +"Federated evaluation can be disabled by setting `fraction_eval` to `0.0`." msgstr "" #: ../../source/ref-changelog.md:748 msgid "" -"**Two new Jupyter Notebook tutorials** " -"([#1141](https://github.com/adap/flower/pull/1141))" +"**Two new Jupyter Notebook tutorials** ([#1141](https://github.com/adap/" +"flower/pull/1141))" msgstr "" #: ../../source/ref-changelog.md:750 msgid "" -"Two Jupyter Notebook tutorials (compatible with Google Colab) explain " -"basic and intermediate Flower features:" +"Two Jupyter Notebook tutorials (compatible with Google Colab) explain basic " +"and intermediate Flower features:" msgstr "" #: ../../source/ref-changelog.md:752 msgid "" -"*An Introduction to Federated Learning*: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" -"-Intro-to-FL-PyTorch.ipynb)" +"*An Introduction to Federated Learning*: [Open in Colab](https://colab." +"research.google.com/github/adap/flower/blob/main/tutorials/Flower-1-Intro-to-" +"FL-PyTorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:754 msgid "" -"*Using Strategies in Federated Learning*: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" -"-Strategies-in-FL-PyTorch.ipynb)" +"*Using Strategies in Federated Learning*: [Open in Colab](https://colab." +"research.google.com/github/adap/flower/blob/main/tutorials/Flower-2-" +"Strategies-in-FL-PyTorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:756 msgid "" -"**New FedAvgM strategy (Federated Averaging with Server Momentum)** " -"([#1076](https://github.com/adap/flower/pull/1076))" +"**New FedAvgM strategy (Federated Averaging with Server Momentum)** ([#1076]" +"(https://github.com/adap/flower/pull/1076))" msgstr "" #: ../../source/ref-changelog.md:758 @@ -16378,8 +15908,8 @@ msgstr "" #: ../../source/ref-changelog.md:760 msgid "" -"**New advanced PyTorch code example** " -"([#1007](https://github.com/adap/flower/pull/1007))" +"**New advanced PyTorch code example** ([#1007](https://github.com/adap/" +"flower/pull/1007))" msgstr "" #: ../../source/ref-changelog.md:762 @@ -16390,8 +15920,7 @@ msgstr "" #: ../../source/ref-changelog.md:764 msgid "" -"**New JAX code example** " -"([#906](https://github.com/adap/flower/pull/906), " +"**New JAX code example** ([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143))" msgstr "" @@ -16415,41 +15944,40 @@ msgstr "" #: ../../source/ref-changelog.md:772 msgid "" -"New documentation for [implementing " -"strategies](https://flower.ai/docs/framework/how-to-implement-" -"strategies.html) ([#1097](https://github.com/adap/flower/pull/1097), " -"[#1175](https://github.com/adap/flower/pull/1175))" +"New documentation for [implementing strategies](https://flower.ai/docs/" +"framework/how-to-implement-strategies.html) ([#1097](https://github.com/adap/" +"flower/pull/1097), [#1175](https://github.com/adap/flower/pull/1175))" msgstr "" #: ../../source/ref-changelog.md:773 msgid "" -"New mobile-friendly documentation theme " -"([#1174](https://github.com/adap/flower/pull/1174))" +"New mobile-friendly documentation theme ([#1174](https://github.com/adap/" +"flower/pull/1174))" msgstr "" #: ../../source/ref-changelog.md:774 msgid "" "Limit version range for (optional) `ray` dependency to include only " -"compatible releases (`>=1.9.2,<1.12.0`) " -"([#1205](https://github.com/adap/flower/pull/1205))" +"compatible releases (`>=1.9.2,<1.12.0`) ([#1205](https://github.com/adap/" +"flower/pull/1205))" msgstr "" #: ../../source/ref-changelog.md:778 msgid "" -"**Remove deprecated support for Python 3.6** " -"([#871](https://github.com/adap/flower/pull/871))" +"**Remove deprecated support for Python 3.6** ([#871](https://github.com/adap/" +"flower/pull/871))" msgstr "" #: ../../source/ref-changelog.md:779 msgid "" -"**Remove deprecated KerasClient** " -"([#857](https://github.com/adap/flower/pull/857))" +"**Remove deprecated KerasClient** ([#857](https://github.com/adap/flower/" +"pull/857))" msgstr "" #: ../../source/ref-changelog.md:780 msgid "" -"**Remove deprecated no-op extra installs** " -"([#973](https://github.com/adap/flower/pull/973))" +"**Remove deprecated no-op extra installs** ([#973](https://github.com/adap/" +"flower/pull/973))" msgstr "" #: ../../source/ref-changelog.md:781 @@ -16460,20 +15988,20 @@ msgstr "" #: ../../source/ref-changelog.md:782 msgid "" -"**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** " -"([#1107](https://github.com/adap/flower/pull/1107))" +"**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** ([#1107]" +"(https://github.com/adap/flower/pull/1107))" msgstr "" #: ../../source/ref-changelog.md:783 msgid "" -"**Remove deprecated DefaultStrategy strategy** " -"([#1142](https://github.com/adap/flower/pull/1142))" +"**Remove deprecated DefaultStrategy strategy** ([#1142](https://github.com/" +"adap/flower/pull/1142))" msgstr "" #: ../../source/ref-changelog.md:784 msgid "" -"**Remove deprecated support for eval_fn accuracy return value** " -"([#1142](https://github.com/adap/flower/pull/1142))" +"**Remove deprecated support for eval_fn accuracy return value** ([#1142]" +"(https://github.com/adap/flower/pull/1142))" msgstr "" #: ../../source/ref-changelog.md:785 @@ -16489,156 +16017,152 @@ msgstr "" #: ../../source/ref-changelog.md:791 msgid "" "**Improved Virtual Client Engine compatibility with Jupyter Notebook / " -"Google Colab** ([#866](https://github.com/adap/flower/pull/866), " -"[#872](https://github.com/adap/flower/pull/872), " -"[#833](https://github.com/adap/flower/pull/833), " -"[#1036](https://github.com/adap/flower/pull/1036))" +"Google Colab** ([#866](https://github.com/adap/flower/pull/866), [#872]" +"(https://github.com/adap/flower/pull/872), [#833](https://github.com/adap/" +"flower/pull/833), [#1036](https://github.com/adap/flower/pull/1036))" msgstr "" #: ../../source/ref-changelog.md:793 msgid "" -"Simulations (using the Virtual Client Engine through `start_simulation`) " -"now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " +"Simulations (using the Virtual Client Engine through `start_simulation`) now " +"work more smoothly on Jupyter Notebooks (incl. Google Colab) after " "installing Flower with the `simulation` extra (`pip install " "flwr[simulation]`)." msgstr "" #: ../../source/ref-changelog.md:795 msgid "" -"**New Jupyter Notebook code example** " -"([#833](https://github.com/adap/flower/pull/833))" +"**New Jupyter Notebook code example** ([#833](https://github.com/adap/flower/" +"pull/833))" msgstr "" #: ../../source/ref-changelog.md:797 msgid "" -"A new code example (`quickstart_simulation`) demonstrates Flower " -"simulations using the Virtual Client Engine through Jupyter Notebook " -"(incl. Google Colab)." +"A new code example (`quickstart_simulation`) demonstrates Flower simulations " +"using the Virtual Client Engine through Jupyter Notebook (incl. Google " +"Colab)." msgstr "" #: ../../source/ref-changelog.md:799 msgid "" -"**Client properties (feature preview)** " -"([#795](https://github.com/adap/flower/pull/795))" +"**Client properties (feature preview)** ([#795](https://github.com/adap/" +"flower/pull/795))" msgstr "" #: ../../source/ref-changelog.md:801 msgid "" -"Clients can implement a new method `get_properties` to enable server-side" -" strategies to query client properties." +"Clients can implement a new method `get_properties` to enable server-side " +"strategies to query client properties." msgstr "" #: ../../source/ref-changelog.md:803 msgid "" -"**Experimental Android support with TFLite** " -"([#865](https://github.com/adap/flower/pull/865))" +"**Experimental Android support with TFLite** ([#865](https://github.com/adap/" +"flower/pull/865))" msgstr "" #: ../../source/ref-changelog.md:805 msgid "" "Android support has finally arrived in `main`! Flower is both client-" "agnostic and framework-agnostic by design. One can integrate arbitrary " -"client platforms and with this release, using Flower on Android has " -"become a lot easier." +"client platforms and with this release, using Flower on Android has become a " +"lot easier." msgstr "" #: ../../source/ref-changelog.md:807 msgid "" -"The example uses TFLite on the client side, along with a new " -"`FedAvgAndroid` strategy. The Android client and `FedAvgAndroid` are " -"still experimental, but they are a first step towards a fully-fledged " -"Android SDK and a unified `FedAvg` implementation that integrated the new" -" functionality from `FedAvgAndroid`." +"The example uses TFLite on the client side, along with a new `FedAvgAndroid` " +"strategy. The Android client and `FedAvgAndroid` are still experimental, but " +"they are a first step towards a fully-fledged Android SDK and a unified " +"`FedAvg` implementation that integrated the new functionality from " +"`FedAvgAndroid`." msgstr "" #: ../../source/ref-changelog.md:809 msgid "" -"**Make gRPC keepalive time user-configurable and decrease default " -"keepalive time** ([#1069](https://github.com/adap/flower/pull/1069))" +"**Make gRPC keepalive time user-configurable and decrease default keepalive " +"time** ([#1069](https://github.com/adap/flower/pull/1069))" msgstr "" #: ../../source/ref-changelog.md:811 msgid "" "The default gRPC keepalive time has been reduced to increase the " -"compatibility of Flower with more cloud environments (for example, " -"Microsoft Azure). Users can configure the keepalive time to customize the" -" gRPC stack based on specific requirements." +"compatibility of Flower with more cloud environments (for example, Microsoft " +"Azure). Users can configure the keepalive time to customize the gRPC stack " +"based on specific requirements." msgstr "" #: ../../source/ref-changelog.md:813 msgid "" -"**New differential privacy example using Opacus and PyTorch** " -"([#805](https://github.com/adap/flower/pull/805))" +"**New differential privacy example using Opacus and PyTorch** ([#805]" +"(https://github.com/adap/flower/pull/805))" msgstr "" #: ../../source/ref-changelog.md:815 msgid "" -"A new code example (`opacus`) demonstrates differentially-private " -"federated learning with Opacus, PyTorch, and Flower." +"A new code example (`opacus`) demonstrates differentially-private federated " +"learning with Opacus, PyTorch, and Flower." msgstr "" #: ../../source/ref-changelog.md:817 msgid "" -"**New Hugging Face Transformers code example** " -"([#863](https://github.com/adap/flower/pull/863))" +"**New Hugging Face Transformers code example** ([#863](https://github.com/" +"adap/flower/pull/863))" msgstr "" #: ../../source/ref-changelog.md:819 msgid "" -"A new code example (`quickstart_huggingface`) demonstrates usage of " -"Hugging Face Transformers with Flower." +"A new code example (`quickstart_huggingface`) demonstrates usage of Hugging " +"Face Transformers with Flower." msgstr "" #: ../../source/ref-changelog.md:821 msgid "" -"**New MLCube code example** " -"([#779](https://github.com/adap/flower/pull/779), " -"[#1034](https://github.com/adap/flower/pull/1034), " -"[#1065](https://github.com/adap/flower/pull/1065), " -"[#1090](https://github.com/adap/flower/pull/1090))" +"**New MLCube code example** ([#779](https://github.com/adap/flower/" +"pull/779), [#1034](https://github.com/adap/flower/pull/1034), [#1065]" +"(https://github.com/adap/flower/pull/1065), [#1090](https://github.com/adap/" +"flower/pull/1090))" msgstr "" #: ../../source/ref-changelog.md:823 msgid "" -"A new code example (`quickstart_mlcube`) demonstrates usage of MLCube " -"with Flower." +"A new code example (`quickstart_mlcube`) demonstrates usage of MLCube with " +"Flower." msgstr "" #: ../../source/ref-changelog.md:825 msgid "" -"**SSL-enabled server and client** " -"([#842](https://github.com/adap/flower/pull/842), " -"[#844](https://github.com/adap/flower/pull/844), " -"[#845](https://github.com/adap/flower/pull/845), " -"[#847](https://github.com/adap/flower/pull/847), " -"[#993](https://github.com/adap/flower/pull/993), " -"[#994](https://github.com/adap/flower/pull/994))" +"**SSL-enabled server and client** ([#842](https://github.com/adap/flower/" +"pull/842), [#844](https://github.com/adap/flower/pull/844), [#845](https://" +"github.com/adap/flower/pull/845), [#847](https://github.com/adap/flower/" +"pull/847), [#993](https://github.com/adap/flower/pull/993), [#994](https://" +"github.com/adap/flower/pull/994))" msgstr "" #: ../../source/ref-changelog.md:827 msgid "" -"SSL enables secure encrypted connections between clients and servers. " -"This release open-sources the Flower secure gRPC implementation to make " -"encrypted communication channels accessible to all Flower users." +"SSL enables secure encrypted connections between clients and servers. This " +"release open-sources the Flower secure gRPC implementation to make encrypted " +"communication channels accessible to all Flower users." msgstr "" #: ../../source/ref-changelog.md:829 msgid "" -"**Updated** `FedAdam` **and** `FedYogi` **strategies** " -"([#885](https://github.com/adap/flower/pull/885), " -"[#895](https://github.com/adap/flower/pull/895))" +"**Updated** `FedAdam` **and** `FedYogi` **strategies** ([#885](https://" +"github.com/adap/flower/pull/885), [#895](https://github.com/adap/flower/" +"pull/895))" msgstr "" #: ../../source/ref-changelog.md:831 msgid "" -"`FedAdam` and `FedAdam` match the latest version of the Adaptive " -"Federated Optimization paper." +"`FedAdam` and `FedAdam` match the latest version of the Adaptive Federated " +"Optimization paper." msgstr "" #: ../../source/ref-changelog.md:833 msgid "" -"**Initialize** `start_simulation` **with a list of client IDs** " -"([#860](https://github.com/adap/flower/pull/860))" +"**Initialize** `start_simulation` **with a list of client IDs** ([#860]" +"(https://github.com/adap/flower/pull/860))" msgstr "" #: ../../source/ref-changelog.md:835 @@ -16652,38 +16176,38 @@ msgstr "" #: ../../source/ref-changelog.md:839 msgid "" -"Update `num_examples` calculation in PyTorch code examples in " -"([#909](https://github.com/adap/flower/pull/909))" +"Update `num_examples` calculation in PyTorch code examples in ([#909]" +"(https://github.com/adap/flower/pull/909))" msgstr "" #: ../../source/ref-changelog.md:840 msgid "" -"Expose Flower version through `flwr.__version__` " -"([#952](https://github.com/adap/flower/pull/952))" +"Expose Flower version through `flwr.__version__` ([#952](https://github.com/" +"adap/flower/pull/952))" msgstr "" #: ../../source/ref-changelog.md:841 msgid "" -"`start_server` in `app.py` now returns a `History` object containing " -"metrics from training ([#974](https://github.com/adap/flower/pull/974))" +"`start_server` in `app.py` now returns a `History` object containing metrics " +"from training ([#974](https://github.com/adap/flower/pull/974))" msgstr "" #: ../../source/ref-changelog.md:842 msgid "" -"Make `max_workers` (used by `ThreadPoolExecutor`) configurable " -"([#978](https://github.com/adap/flower/pull/978))" +"Make `max_workers` (used by `ThreadPoolExecutor`) configurable ([#978]" +"(https://github.com/adap/flower/pull/978))" msgstr "" #: ../../source/ref-changelog.md:843 msgid "" -"Increase sleep time after server start to three seconds in all code " -"examples ([#1086](https://github.com/adap/flower/pull/1086))" +"Increase sleep time after server start to three seconds in all code examples " +"([#1086](https://github.com/adap/flower/pull/1086))" msgstr "" #: ../../source/ref-changelog.md:844 msgid "" -"Added a new FAQ section to the documentation " -"([#948](https://github.com/adap/flower/pull/948))" +"Added a new FAQ section to the documentation ([#948](https://github.com/adap/" +"flower/pull/948))" msgstr "" #: ../../source/ref-changelog.md:845 @@ -16703,8 +16227,8 @@ msgid "" "The packages `flwr_example` and `flwr_experimental` have been deprecated " "since Flower 0.12.0 and they are not longer included in Flower release " "builds. The associated extras (`baseline`, `examples-pytorch`, `examples-" -"tensorflow`, `http-logger`, `ops`) are now no-op and will be removed in " -"an upcoming release." +"tensorflow`, `http-logger`, `ops`) are now no-op and will be removed in an " +"upcoming release." msgstr "" #: ../../source/ref-changelog.md:853 @@ -16713,34 +16237,32 @@ msgstr "" #: ../../source/ref-changelog.md:857 msgid "" -"**Experimental virtual client engine** " -"([#781](https://github.com/adap/flower/pull/781) " -"[#790](https://github.com/adap/flower/pull/790) " -"[#791](https://github.com/adap/flower/pull/791))" +"**Experimental virtual client engine** ([#781](https://github.com/adap/" +"flower/pull/781) [#790](https://github.com/adap/flower/pull/790) [#791]" +"(https://github.com/adap/flower/pull/791))" msgstr "" #: ../../source/ref-changelog.md:859 msgid "" -"One of Flower's goals is to enable research at scale. This release " -"enables a first (experimental) peek at a major new feature, codenamed the" -" virtual client engine. Virtual clients enable simulations that scale to " -"a (very) large number of clients on a single machine or compute cluster. " -"The easiest way to test the new functionality is to look at the two new " -"code examples called `quickstart_simulation` and `simulation_pytorch`." +"One of Flower's goals is to enable research at scale. This release enables a " +"first (experimental) peek at a major new feature, codenamed the virtual " +"client engine. Virtual clients enable simulations that scale to a (very) " +"large number of clients on a single machine or compute cluster. The easiest " +"way to test the new functionality is to look at the two new code examples " +"called `quickstart_simulation` and `simulation_pytorch`." msgstr "" #: ../../source/ref-changelog.md:861 msgid "" -"The feature is still experimental, so there's no stability guarantee for " -"the API. It's also not quite ready for prime time and comes with a few " -"known caveats. However, those who are curious are encouraged to try it " -"out and share their thoughts." +"The feature is still experimental, so there's no stability guarantee for the " +"API. It's also not quite ready for prime time and comes with a few known " +"caveats. However, those who are curious are encouraged to try it out and " +"share their thoughts." msgstr "" #: ../../source/ref-changelog.md:863 msgid "" -"**New built-in strategies** " -"([#828](https://github.com/adap/flower/pull/828) " +"**New built-in strategies** ([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" msgstr "" @@ -16758,101 +16280,99 @@ msgstr "" #: ../../source/ref-changelog.md:868 msgid "" -"**New PyTorch Lightning code example** " -"([#617](https://github.com/adap/flower/pull/617))" +"**New PyTorch Lightning code example** ([#617](https://github.com/adap/" +"flower/pull/617))" msgstr "" #: ../../source/ref-changelog.md:870 msgid "" -"**New Variational Auto-Encoder code example** " -"([#752](https://github.com/adap/flower/pull/752))" +"**New Variational Auto-Encoder code example** ([#752](https://github.com/" +"adap/flower/pull/752))" msgstr "" #: ../../source/ref-changelog.md:872 msgid "" -"**New scikit-learn code example** " -"([#748](https://github.com/adap/flower/pull/748))" +"**New scikit-learn code example** ([#748](https://github.com/adap/flower/" +"pull/748))" msgstr "" #: ../../source/ref-changelog.md:874 msgid "" -"**New experimental TensorBoard strategy** " -"([#789](https://github.com/adap/flower/pull/789))" +"**New experimental TensorBoard strategy** ([#789](https://github.com/adap/" +"flower/pull/789))" msgstr "" #: ../../source/ref-changelog.md:878 msgid "" -"Improved advanced TensorFlow code example " -"([#769](https://github.com/adap/flower/pull/769))" +"Improved advanced TensorFlow code example ([#769](https://github.com/adap/" +"flower/pull/769))" msgstr "" #: ../../source/ref-changelog.md:879 msgid "" -"Warning when `min_available_clients` is misconfigured " -"([#830](https://github.com/adap/flower/pull/830))" +"Warning when `min_available_clients` is misconfigured ([#830](https://github." +"com/adap/flower/pull/830))" msgstr "" #: ../../source/ref-changelog.md:880 msgid "" -"Improved gRPC server docs " -"([#841](https://github.com/adap/flower/pull/841))" +"Improved gRPC server docs ([#841](https://github.com/adap/flower/pull/841))" msgstr "" #: ../../source/ref-changelog.md:881 msgid "" -"Improved error message in `NumPyClient` " -"([#851](https://github.com/adap/flower/pull/851))" +"Improved error message in `NumPyClient` ([#851](https://github.com/adap/" +"flower/pull/851))" msgstr "" #: ../../source/ref-changelog.md:882 msgid "" -"Improved PyTorch quickstart code example " -"([#852](https://github.com/adap/flower/pull/852))" +"Improved PyTorch quickstart code example ([#852](https://github.com/adap/" +"flower/pull/852))" msgstr "" #: ../../source/ref-changelog.md:886 msgid "" -"**Disabled final distributed evaluation** " -"([#800](https://github.com/adap/flower/pull/800))" +"**Disabled final distributed evaluation** ([#800](https://github.com/adap/" +"flower/pull/800))" msgstr "" #: ../../source/ref-changelog.md:888 msgid "" -"Prior behaviour was to perform a final round of distributed evaluation on" -" all connected clients, which is often not required (e.g., when using " -"server-side evaluation). The prior behaviour can be enabled by passing " +"Prior behaviour was to perform a final round of distributed evaluation on " +"all connected clients, which is often not required (e.g., when using server-" +"side evaluation). The prior behaviour can be enabled by passing " "`force_final_distributed_eval=True` to `start_server`." msgstr "" #: ../../source/ref-changelog.md:890 msgid "" -"**Renamed q-FedAvg strategy** " -"([#802](https://github.com/adap/flower/pull/802))" +"**Renamed q-FedAvg strategy** ([#802](https://github.com/adap/flower/" +"pull/802))" msgstr "" #: ../../source/ref-changelog.md:892 msgid "" -"The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect " -"the notation given in the original paper (q-FFL is the optimization " -"objective, q-FedAvg is the proposed solver). Note the original (now " -"deprecated) `QffedAvg` class is still available for compatibility reasons" -" (it will be removed in a future release)." +"The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect the " +"notation given in the original paper (q-FFL is the optimization objective, q-" +"FedAvg is the proposed solver). Note the original (now deprecated) " +"`QffedAvg` class is still available for compatibility reasons (it will be " +"removed in a future release)." msgstr "" #: ../../source/ref-changelog.md:894 msgid "" "**Deprecated and renamed code example** `simulation_pytorch` **to** " -"`simulation_pytorch_legacy` " -"([#791](https://github.com/adap/flower/pull/791))" +"`simulation_pytorch_legacy` ([#791](https://github.com/adap/flower/pull/791))" msgstr "" #: ../../source/ref-changelog.md:896 msgid "" -"This example has been replaced by a new example. The new example is based" -" on the experimental virtual client engine, which will become the new " -"default way of doing most types of large-scale simulations in Flower. The" -" existing example was kept for reference purposes, but it might be " -"removed in the future." +"This example has been replaced by a new example. The new example is based on " +"the experimental virtual client engine, which will become the new default " +"way of doing most types of large-scale simulations in Flower. The existing " +"example was kept for reference purposes, but it might be removed in the " +"future." msgstr "" #: ../../source/ref-changelog.md:898 @@ -16861,8 +16381,7 @@ msgstr "" #: ../../source/ref-changelog.md:902 msgid "" -"**New built-in strategies** " -"([#549](https://github.com/adap/flower/pull/549))" +"**New built-in strategies** ([#549](https://github.com/adap/flower/pull/549))" msgstr "" #: ../../source/ref-changelog.md:904 @@ -16871,8 +16390,8 @@ msgstr "" #: ../../source/ref-changelog.md:907 msgid "" -"**Custom metrics for server and strategies** " -"([#717](https://github.com/adap/flower/pull/717))" +"**Custom metrics for server and strategies** ([#717](https://github.com/adap/" +"flower/pull/717))" msgstr "" #: ../../source/ref-changelog.md:909 @@ -16886,20 +16405,19 @@ msgstr "" #: ../../source/ref-changelog.md:911 msgid "" -"Custom metric dictionaries are now used in two user-facing APIs: they are" -" returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and " -"they enable evaluation functions passed to built-in strategies (via " -"`eval_fn`) to return more than two evaluation metrics. Strategies can " -"even return *aggregated* metrics dictionaries for the server to keep " -"track of." +"Custom metric dictionaries are now used in two user-facing APIs: they are " +"returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and they " +"enable evaluation functions passed to built-in strategies (via `eval_fn`) to " +"return more than two evaluation metrics. Strategies can even return " +"*aggregated* metrics dictionaries for the server to keep track of." msgstr "" #: ../../source/ref-changelog.md:913 msgid "" "Strategy implementations should migrate their `aggregate_fit` and " "`aggregate_evaluate` methods to the new return type (e.g., by simply " -"returning an empty `{}`), server-side evaluation functions should migrate" -" from `return loss, accuracy` to `return loss, {\"accuracy\": accuracy}`." +"returning an empty `{}`), server-side evaluation functions should migrate " +"from `return loss, accuracy` to `return loss, {\"accuracy\": accuracy}`." msgstr "" #: ../../source/ref-changelog.md:915 @@ -16910,25 +16428,24 @@ msgstr "" #: ../../source/ref-changelog.md:917 msgid "" -"**Migration warnings for deprecated functionality** " -"([#690](https://github.com/adap/flower/pull/690))" +"**Migration warnings for deprecated functionality** ([#690](https://github." +"com/adap/flower/pull/690))" msgstr "" #: ../../source/ref-changelog.md:919 msgid "" "Earlier versions of Flower were often migrated to new APIs, while " -"maintaining compatibility with legacy APIs. This release introduces " -"detailed warning messages if usage of deprecated APIs is detected. The " -"new warning messages often provide details on how to migrate to more " -"recent APIs, thus easing the transition from one release to another." +"maintaining compatibility with legacy APIs. This release introduces detailed " +"warning messages if usage of deprecated APIs is detected. The new warning " +"messages often provide details on how to migrate to more recent APIs, thus " +"easing the transition from one release to another." msgstr "" #: ../../source/ref-changelog.md:921 msgid "" -"Improved docs and docstrings " -"([#691](https://github.com/adap/flower/pull/691) " -"[#692](https://github.com/adap/flower/pull/692) " -"[#713](https://github.com/adap/flower/pull/713))" +"Improved docs and docstrings ([#691](https://github.com/adap/flower/" +"pull/691) [#692](https://github.com/adap/flower/pull/692) [#713](https://" +"github.com/adap/flower/pull/713))" msgstr "" #: ../../source/ref-changelog.md:923 @@ -16938,43 +16455,39 @@ msgstr "" #: ../../source/ref-changelog.md:925 msgid "" "FedBN implementation in example PyTorch: From Centralized To Federated " -"([#696](https://github.com/adap/flower/pull/696) " -"[#702](https://github.com/adap/flower/pull/702) " -"[#705](https://github.com/adap/flower/pull/705))" +"([#696](https://github.com/adap/flower/pull/696) [#702](https://github.com/" +"adap/flower/pull/702) [#705](https://github.com/adap/flower/pull/705))" msgstr "" #: ../../source/ref-changelog.md:929 msgid "" -"**Serialization-agnostic server** " -"([#721](https://github.com/adap/flower/pull/721))" +"**Serialization-agnostic server** ([#721](https://github.com/adap/flower/" +"pull/721))" msgstr "" #: ../../source/ref-changelog.md:931 msgid "" -"The Flower server is now fully serialization-agnostic. Prior usage of " -"class `Weights` (which represents parameters as deserialized NumPy " -"ndarrays) was replaced by class `Parameters` (e.g., in `Strategy`). " -"`Parameters` objects are fully serialization-agnostic and represents " -"parameters as byte arrays, the `tensor_type` attributes indicates how " -"these byte arrays should be interpreted (e.g., for " -"serialization/deserialization)." +"The Flower server is now fully serialization-agnostic. Prior usage of class " +"`Weights` (which represents parameters as deserialized NumPy ndarrays) was " +"replaced by class `Parameters` (e.g., in `Strategy`). `Parameters` objects " +"are fully serialization-agnostic and represents parameters as byte arrays, " +"the `tensor_type` attributes indicates how these byte arrays should be " +"interpreted (e.g., for serialization/deserialization)." msgstr "" #: ../../source/ref-changelog.md:933 msgid "" -"Built-in strategies implement this approach by handling serialization and" -" deserialization to/from `Weights` internally. Custom/3rd-party Strategy " +"Built-in strategies implement this approach by handling serialization and " +"deserialization to/from `Weights` internally. Custom/3rd-party Strategy " "implementations should update to the slightly changed Strategy method " -"definitions. Strategy authors can consult PR " -"[#721](https://github.com/adap/flower/pull/721) to see how strategies can" -" easily migrate to the new format." +"definitions. Strategy authors can consult PR [#721](https://github.com/adap/" +"flower/pull/721) to see how strategies can easily migrate to the new format." msgstr "" #: ../../source/ref-changelog.md:935 msgid "" -"Deprecated `flwr.server.Server.evaluate`, use " -"`flwr.server.Server.evaluate_round` instead " -"([#717](https://github.com/adap/flower/pull/717))" +"Deprecated `flwr.server.Server.evaluate`, use `flwr.server.Server." +"evaluate_round` instead ([#717](https://github.com/adap/flower/pull/717))" msgstr "" #: ../../source/ref-changelog.md:937 @@ -16983,8 +16496,8 @@ msgstr "" #: ../../source/ref-changelog.md:941 msgid "" -"**Server-side parameter initialization** " -"([#658](https://github.com/adap/flower/pull/658))" +"**Server-side parameter initialization** ([#658](https://github.com/adap/" +"flower/pull/658))" msgstr "" #: ../../source/ref-changelog.md:943 @@ -16997,9 +16510,9 @@ msgstr "" #: ../../source/ref-changelog.md:945 msgid "" "Built-in strategies support a new constructor argument called " -"`initial_parameters` to set the initial parameters. Built-in strategies " -"will provide these initial parameters to the server on startup and then " -"delete them to free the memory afterwards." +"`initial_parameters` to set the initial parameters. Built-in strategies will " +"provide these initial parameters to the server on startup and then delete " +"them to free the memory afterwards." msgstr "" #: ../../source/ref-changelog.md:964 @@ -17016,8 +16529,8 @@ msgstr "" #: ../../source/ref-changelog.md:968 msgid "" -"Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to " -"`flwr.server.strategy.FedAvg`, which is equivalent)" +"Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to `flwr.server." +"strategy.FedAvg`, which is equivalent)" msgstr "" #: ../../source/ref-changelog.md:970 @@ -17027,35 +16540,33 @@ msgstr "" #: ../../source/ref-changelog.md:974 msgid "" "**Generalized** `Client.fit` **and** `Client.evaluate` **return values** " -"([#610](https://github.com/adap/flower/pull/610) " -"[#572](https://github.com/adap/flower/pull/572) " -"[#633](https://github.com/adap/flower/pull/633))" +"([#610](https://github.com/adap/flower/pull/610) [#572](https://github.com/" +"adap/flower/pull/572) [#633](https://github.com/adap/flower/pull/633))" msgstr "" #: ../../source/ref-changelog.md:976 msgid "" -"Clients can now return an additional dictionary mapping `str` keys to " -"values of the following types: `bool`, `bytes`, `float`, `int`, `str`. " -"This means one can return almost arbitrary values from `fit`/`evaluate` " -"and make use of them on the server side!" +"Clients can now return an additional dictionary mapping `str` keys to values " +"of the following types: `bool`, `bytes`, `float`, `int`, `str`. This means " +"one can return almost arbitrary values from `fit`/`evaluate` and make use of " +"them on the server side!" msgstr "" #: ../../source/ref-changelog.md:978 msgid "" -"This improvement also allowed for more consistent return types between " -"`fit` and `evaluate`: `evaluate` should now return a tuple `(float, int, " -"dict)` representing the loss, number of examples, and a dictionary " -"holding arbitrary problem-specific values like accuracy." +"This improvement also allowed for more consistent return types between `fit` " +"and `evaluate`: `evaluate` should now return a tuple `(float, int, dict)` " +"representing the loss, number of examples, and a dictionary holding " +"arbitrary problem-specific values like accuracy." msgstr "" #: ../../source/ref-changelog.md:980 msgid "" -"In case you wondered: this feature is compatible with existing projects, " -"the additional dictionary return value is optional. New code should " -"however migrate to the new return types to be compatible with upcoming " -"Flower releases (`fit`: `List[np.ndarray], int, Dict[str, Scalar]`, " -"`evaluate`: `float, int, Dict[str, Scalar]`). See the example below for " -"details." +"In case you wondered: this feature is compatible with existing projects, the " +"additional dictionary return value is optional. New code should however " +"migrate to the new return types to be compatible with upcoming Flower " +"releases (`fit`: `List[np.ndarray], int, Dict[str, Scalar]`, `evaluate`: " +"`float, int, Dict[str, Scalar]`). See the example below for details." msgstr "" #: ../../source/ref-changelog.md:982 @@ -17066,23 +16577,23 @@ msgstr "" #: ../../source/ref-changelog.md:997 msgid "" -"**Generalized** `config` **argument in** `Client.fit` **and** " -"`Client.evaluate` ([#595](https://github.com/adap/flower/pull/595))" +"**Generalized** `config` **argument in** `Client.fit` **and** `Client." +"evaluate` ([#595](https://github.com/adap/flower/pull/595))" msgstr "" #: ../../source/ref-changelog.md:999 msgid "" -"The `config` argument used to be of type `Dict[str, str]`, which means " -"that dictionary values were expected to be strings. The new release " -"generalizes this to enable values of the following types: `bool`, " -"`bytes`, `float`, `int`, `str`." +"The `config` argument used to be of type `Dict[str, str]`, which means that " +"dictionary values were expected to be strings. The new release generalizes " +"this to enable values of the following types: `bool`, `bytes`, `float`, " +"`int`, `str`." msgstr "" #: ../../source/ref-changelog.md:1001 msgid "" "This means one can now pass almost arbitrary values to `fit`/`evaluate` " -"using the `config` dictionary. Yay, no more `str(epochs)` on the server-" -"side and `int(config[\"epochs\"])` on the client side!" +"using the `config` dictionary. Yay, no more `str(epochs)` on the server-side " +"and `int(config[\"epochs\"])` on the client side!" msgstr "" #: ../../source/ref-changelog.md:1003 @@ -17097,8 +16608,8 @@ msgstr "" #: ../../source/ref-changelog.md:1024 msgid "" -"New example: PyTorch From Centralized To Federated " -"([#549](https://github.com/adap/flower/pull/549))" +"New example: PyTorch From Centralized To Federated ([#549](https://github." +"com/adap/flower/pull/549))" msgstr "" #: ../../source/ref-changelog.md:1025 @@ -17106,7 +16617,8 @@ msgid "Improved documentation" msgstr "" #: ../../source/ref-changelog.md:1026 -msgid "New documentation theme ([#551](https://github.com/adap/flower/pull/551))" +msgid "" +"New documentation theme ([#551](https://github.com/adap/flower/pull/551))" msgstr "" #: ../../source/ref-changelog.md:1027 @@ -17115,14 +16627,14 @@ msgstr "" #: ../../source/ref-changelog.md:1028 msgid "" -"Updated examples documentation " -"([#549](https://github.com/adap/flower/pull/549))" +"Updated examples documentation ([#549](https://github.com/adap/flower/" +"pull/549))" msgstr "" #: ../../source/ref-changelog.md:1029 msgid "" -"Removed obsolete documentation " -"([#548](https://github.com/adap/flower/pull/548))" +"Removed obsolete documentation ([#548](https://github.com/adap/flower/" +"pull/548))" msgstr "" #: ../../source/ref-changelog.md:1031 @@ -17131,10 +16643,9 @@ msgstr "" #: ../../source/ref-changelog.md:1033 msgid "" -"`Server.fit` does not disconnect clients when finished, disconnecting the" -" clients is now handled in `flwr.server.start_server` " -"([#553](https://github.com/adap/flower/pull/553) " -"[#540](https://github.com/adap/flower/issues/540))." +"`Server.fit` does not disconnect clients when finished, disconnecting the " +"clients is now handled in `flwr.server.start_server` ([#553](https://github." +"com/adap/flower/pull/553) [#540](https://github.com/adap/flower/issues/540))." msgstr "" #: ../../source/ref-changelog.md:1035 @@ -17147,23 +16658,22 @@ msgstr "" #: ../../source/ref-changelog.md:1039 msgid "" -"Added an example for embedded devices " -"([#507](https://github.com/adap/flower/pull/507))" +"Added an example for embedded devices ([#507](https://github.com/adap/flower/" +"pull/507))" msgstr "" #: ../../source/ref-changelog.md:1040 msgid "" -"Added a new NumPyClient (in addition to the existing KerasClient) " -"([#504](https://github.com/adap/flower/pull/504) " -"[#508](https://github.com/adap/flower/pull/508))" +"Added a new NumPyClient (in addition to the existing KerasClient) ([#504]" +"(https://github.com/adap/flower/pull/504) [#508](https://github.com/adap/" +"flower/pull/508))" msgstr "" #: ../../source/ref-changelog.md:1041 msgid "" -"Deprecated `flwr_example` package and started to migrate examples into " -"the top-level `examples` directory " -"([#494](https://github.com/adap/flower/pull/494) " -"[#512](https://github.com/adap/flower/pull/512))" +"Deprecated `flwr_example` package and started to migrate examples into the " +"top-level `examples` directory ([#494](https://github.com/adap/flower/" +"pull/494) [#512](https://github.com/adap/flower/pull/512))" msgstr "" #: ../../source/ref-changelog.md:1043 @@ -17176,12 +16686,11 @@ msgstr "" #: ../../source/ref-changelog.md:1047 msgid "" -"Renamed strategy methods " -"([#486](https://github.com/adap/flower/pull/486)) to unify the naming of " -"Flower's public APIs. Other public methods/functions (e.g., every method " -"in `Client`, but also `Strategy.evaluate`) do not use the `on_` prefix, " -"which is why we're removing it from the four methods in Strategy. To " -"migrate rename the following `Strategy` methods accordingly:" +"Renamed strategy methods ([#486](https://github.com/adap/flower/pull/486)) " +"to unify the naming of Flower's public APIs. Other public methods/functions " +"(e.g., every method in `Client`, but also `Strategy.evaluate`) do not use " +"the `on_` prefix, which is why we're removing it from the four methods in " +"Strategy. To migrate rename the following `Strategy` methods accordingly:" msgstr "" #: ../../source/ref-changelog.md:1048 @@ -17202,33 +16711,32 @@ msgstr "" #: ../../source/ref-changelog.md:1055 msgid "" -"Deprecated `DefaultStrategy` " -"([#479](https://github.com/adap/flower/pull/479)). To migrate use " -"`FedAvg` instead." +"Deprecated `DefaultStrategy` ([#479](https://github.com/adap/flower/" +"pull/479)). To migrate use `FedAvg` instead." msgstr "" #: ../../source/ref-changelog.md:1056 msgid "" -"Simplified examples and baselines " -"([#484](https://github.com/adap/flower/pull/484))." +"Simplified examples and baselines ([#484](https://github.com/adap/flower/" +"pull/484))." msgstr "" #: ../../source/ref-changelog.md:1057 msgid "" -"Removed presently unused `on_conclude_round` from strategy interface " -"([#483](https://github.com/adap/flower/pull/483))." +"Removed presently unused `on_conclude_round` from strategy interface ([#483]" +"(https://github.com/adap/flower/pull/483))." msgstr "" #: ../../source/ref-changelog.md:1058 msgid "" -"Set minimal Python version to 3.6.1 instead of 3.6.9 " -"([#471](https://github.com/adap/flower/pull/471))." +"Set minimal Python version to 3.6.1 instead of 3.6.9 ([#471](https://github." +"com/adap/flower/pull/471))." msgstr "" #: ../../source/ref-changelog.md:1059 msgid "" -"Improved `Strategy` docstrings " -"([#470](https://github.com/adap/flower/pull/470))." +"Improved `Strategy` docstrings ([#470](https://github.com/adap/flower/" +"pull/470))." msgstr "" #: ../../source/ref-example-projects.rst:2 @@ -17237,11 +16745,11 @@ msgstr "" #: ../../source/ref-example-projects.rst:4 msgid "" -"Flower comes with a number of usage examples. The examples demonstrate " -"how Flower can be used to federate different kinds of existing machine " -"learning pipelines, usually leveraging popular machine learning " -"frameworks such as `PyTorch `_ or `TensorFlow " -"`_." +"Flower comes with a number of usage examples. The examples demonstrate how " +"Flower can be used to federate different kinds of existing machine learning " +"pipelines, usually leveraging popular machine learning frameworks such as " +"`PyTorch `_ or `TensorFlow `_." msgstr "" #: ../../source/ref-example-projects.rst:10 @@ -17252,25 +16760,25 @@ msgstr "" #: ../../source/ref-example-projects.rst:14 msgid "" -"The TensorFlow/Keras quickstart example shows CIFAR-10 image " -"classification with MobileNetV2:" +"The TensorFlow/Keras quickstart example shows CIFAR-10 image classification " +"with MobileNetV2:" msgstr "" #: ../../source/ref-example-projects.rst:17 msgid "" -"`Quickstart TensorFlow (Code) " -"`_" +"`Quickstart TensorFlow (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:18 -msgid ":doc:`Quickstart TensorFlow (Tutorial) `" +msgid "" +":doc:`Quickstart TensorFlow (Tutorial) `" msgstr "" #: ../../source/ref-example-projects.rst:19 msgid "" -"`Quickstart TensorFlow (Blog Post) `_" +"`Quickstart TensorFlow (Blog Post) `_" msgstr "" #: ../../source/ref-example-projects.rst:23 @@ -17280,14 +16788,14 @@ msgstr "" #: ../../source/ref-example-projects.rst:25 msgid "" -"The PyTorch quickstart example shows CIFAR-10 image classification with a" -" simple Convolutional Neural Network:" +"The PyTorch quickstart example shows CIFAR-10 image classification with a " +"simple Convolutional Neural Network:" msgstr "" #: ../../source/ref-example-projects.rst:28 msgid "" -"`Quickstart PyTorch (Code) " -"`_" +"`Quickstart PyTorch (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:29 @@ -17306,9 +16814,8 @@ msgstr "" #: ../../source/ref-example-projects.rst:37 msgid "" -"`PyTorch: From Centralized To Federated (Code) " -"`_" +"`PyTorch: From Centralized To Federated (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:38 @@ -17329,14 +16836,15 @@ msgstr "" #: ../../source/ref-example-projects.rst:46 msgid "" -"`Federated Learning on Raspberry Pi and Nvidia Jetson (Code) " -"`_" +"`Federated Learning on Raspberry Pi and Nvidia Jetson (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:47 msgid "" -"`Federated Learning on Raspberry Pi and Nvidia Jetson (Blog Post) " -"`_" +"`Federated Learning on Raspberry Pi and Nvidia Jetson (Blog Post) `_" msgstr "" #: ../../source/ref-faq.rst:4 @@ -17351,22 +16859,20 @@ msgstr "" #: ../../source/ref-faq.rst:8 msgid "" -"Yes, it can! Flower even comes with a few under-the-hood optimizations to" -" make it work even better on Colab. Here's a quickstart example:" +"Yes, it can! Flower even comes with a few under-the-hood optimizations to " +"make it work even better on Colab. Here's a quickstart example:" msgstr "" #: ../../source/ref-faq.rst:10 msgid "" -"`Flower simulation PyTorch " -"`_" +"`Flower simulation PyTorch `_" msgstr "" #: ../../source/ref-faq.rst:11 msgid "" -"`Flower simulation TensorFlow/Keras " -"`_" +"`Flower simulation TensorFlow/Keras `_" msgstr "" #: ../../source/ref-faq.rst @@ -17376,26 +16882,28 @@ msgstr "" #: ../../source/ref-faq.rst:15 msgid "" "Find the `blog post about federated learning on embedded device here " -"`_" -" and the corresponding `GitHub code example " -"`_." +"`_ " +"and the corresponding `GitHub code example `_." msgstr "" #: ../../source/ref-faq.rst -msgid ":fa:`eye,mr-1` Does Flower support federated learning on Android devices?" +msgid "" +":fa:`eye,mr-1` Does Flower support federated learning on Android devices?" msgstr "" #: ../../source/ref-faq.rst:19 msgid "" -"Yes, it does. Please take a look at our `blog post " -"`_ or check out the code examples:" +"Yes, it does. Please take a look at our `blog post `_ or " +"check out the code examples:" msgstr "" #: ../../source/ref-faq.rst:21 msgid "" -"`Android Kotlin example `_" +"`Android Kotlin example `_" msgstr "" #: ../../source/ref-faq.rst:22 @@ -17414,33 +16922,33 @@ msgstr "" #: ../../source/ref-faq.rst:28 msgid "" -"`Flower meets Nevermined GitHub Repository `_." +"`Flower meets Nevermined GitHub Repository `_." msgstr "" #: ../../source/ref-faq.rst:29 msgid "" -"`Flower meets Nevermined YouTube video " -"`_." +"`Flower meets Nevermined YouTube video `_." msgstr "" #: ../../source/ref-faq.rst:30 msgid "" -"`Flower meets KOSMoS `_." +"`Flower meets KOSMoS `_." msgstr "" #: ../../source/ref-faq.rst:31 msgid "" "`Flower meets Talan blog post `_ ." +"learning-same-mask-different-faces-imen-ayari/?" +"trackingId=971oIlxLQ9%2BA9RB0IQ73XQ%3D%3D>`_ ." msgstr "" #: ../../source/ref-faq.rst:32 msgid "" -"`Flower meets Talan GitHub Repository " -"`_ ." +"`Flower meets Talan GitHub Repository `_ ." msgstr "" #: ../../source/ref-telemetry.md:1 @@ -17449,17 +16957,16 @@ msgstr "" #: ../../source/ref-telemetry.md:3 msgid "" -"The Flower open-source project collects **anonymous** usage metrics to " -"make well-informed decisions to improve Flower. Doing this enables the " -"Flower team to understand how Flower is used and what challenges users " -"might face." +"The Flower open-source project collects **anonymous** usage metrics to make " +"well-informed decisions to improve Flower. Doing this enables the Flower " +"team to understand how Flower is used and what challenges users might face." msgstr "" #: ../../source/ref-telemetry.md:5 msgid "" -"**Flower is a friendly framework for collaborative AI and data science.**" -" Staying true to this statement, Flower makes it easy to disable " -"telemetry for users that do not want to share anonymous usage metrics." +"**Flower is a friendly framework for collaborative AI and data science.** " +"Staying true to this statement, Flower makes it easy to disable telemetry " +"for users that do not want to share anonymous usage metrics." msgstr "" #: ../../source/ref-telemetry.md:7 @@ -17467,35 +16974,34 @@ msgid "Principles" msgstr "" #: ../../source/ref-telemetry.md:9 -msgid "We follow strong principles guarding anonymous usage metrics collection:" +msgid "" +"We follow strong principles guarding anonymous usage metrics collection:" msgstr "" #: ../../source/ref-telemetry.md:11 msgid "" -"**Optional:** You will always be able to disable telemetry; read on to " -"learn “[How to opt-out](#how-to-opt-out)”." +"**Optional:** You will always be able to disable telemetry; read on to learn " +"“[How to opt-out](#how-to-opt-out)”." msgstr "" #: ../../source/ref-telemetry.md:12 msgid "" -"**Anonymous:** The reported usage metrics are anonymous and do not " -"contain any personally identifiable information (PII). See “[Collected " -"metrics](#collected-metrics)” to understand what metrics are being " -"reported." +"**Anonymous:** The reported usage metrics are anonymous and do not contain " +"any personally identifiable information (PII). See “[Collected metrics]" +"(#collected-metrics)” to understand what metrics are being reported." msgstr "" #: ../../source/ref-telemetry.md:13 msgid "" "**Transparent:** You can easily inspect what anonymous metrics are being " -"reported; see the section “[How to inspect what is being reported](#how-" -"to-inspect-what-is-being-reported)”" +"reported; see the section “[How to inspect what is being reported](#how-to-" +"inspect-what-is-being-reported)”" msgstr "" #: ../../source/ref-telemetry.md:14 msgid "" -"**Open for feedback:** You can always reach out to us if you have " -"feedback; see the section “[How to contact us](#how-to-contact-us)” for " -"details." +"**Open for feedback:** You can always reach out to us if you have feedback; " +"see the section “[How to contact us](#how-to-contact-us)” for details." msgstr "" #: ../../source/ref-telemetry.md:16 @@ -17512,9 +17018,9 @@ msgstr "" #: ../../source/ref-telemetry.md:24 msgid "" -"Alternatively, you can export `FLWR_TELEMETRY_ENABLED=0` in, for example," -" `.bashrc` (or whatever configuration file applies to your environment) " -"to disable Flower telemetry permanently." +"Alternatively, you can export `FLWR_TELEMETRY_ENABLED=0` in, for example, `." +"bashrc` (or whatever configuration file applies to your environment) to " +"disable Flower telemetry permanently." msgstr "" #: ../../source/ref-telemetry.md:26 @@ -17527,10 +17033,10 @@ msgstr "" #: ../../source/ref-telemetry.md:30 msgid "" -"**Flower version.** Understand which versions of Flower are currently " -"being used. This helps us to decide whether we should invest effort into " -"releasing a patch version for an older version of Flower or instead use " -"the bandwidth to build new features." +"**Flower version.** Understand which versions of Flower are currently being " +"used. This helps us to decide whether we should invest effort into releasing " +"a patch version for an older version of Flower or instead use the bandwidth " +"to build new features." msgstr "" #: ../../source/ref-telemetry.md:32 @@ -17549,15 +17055,15 @@ msgstr "" #: ../../source/ref-telemetry.md:36 msgid "" -"**Hardware properties.** Understanding the hardware environment that " -"Flower is being used in helps to decide whether we should, for example, " -"put more effort into supporting low-resource environments." +"**Hardware properties.** Understanding the hardware environment that Flower " +"is being used in helps to decide whether we should, for example, put more " +"effort into supporting low-resource environments." msgstr "" #: ../../source/ref-telemetry.md:38 msgid "" -"**Execution mode.** Knowing what execution mode Flower starts in enables " -"us to understand how heavily certain features are being used and better " +"**Execution mode.** Knowing what execution mode Flower starts in enables us " +"to understand how heavily certain features are being used and better " "prioritize based on that." msgstr "" @@ -17565,37 +17071,34 @@ msgstr "" msgid "" "**Cluster.** Flower telemetry assigns a random in-memory cluster ID each " "time a Flower workload starts. This allows us to understand which device " -"types not only start Flower workloads but also successfully complete " -"them." +"types not only start Flower workloads but also successfully complete them." msgstr "" #: ../../source/ref-telemetry.md:42 msgid "" -"**Source.** Flower telemetry tries to store a random source ID in " -"`~/.flwr/source` the first time a telemetry event is generated. The " -"source ID is important to identify whether an issue is recurring or " -"whether an issue is triggered by multiple clusters running concurrently " -"(which often happens in simulation). For example, if a device runs " -"multiple workloads at the same time, and this results in an issue, then, " -"in order to reproduce the issue, multiple workloads must be started at " -"the same time." +"**Source.** Flower telemetry tries to store a random source ID in `~/.flwr/" +"source` the first time a telemetry event is generated. The source ID is " +"important to identify whether an issue is recurring or whether an issue is " +"triggered by multiple clusters running concurrently (which often happens in " +"simulation). For example, if a device runs multiple workloads at the same " +"time, and this results in an issue, then, in order to reproduce the issue, " +"multiple workloads must be started at the same time." msgstr "" #: ../../source/ref-telemetry.md:44 msgid "" -"You may delete the source ID at any time. If you wish for all events " -"logged under a specific source ID to be deleted, you can send a deletion " -"request mentioning the source ID to `telemetry@flower.ai`. All events " -"related to that source ID will then be permanently deleted." +"You may delete the source ID at any time. If you wish for all events logged " +"under a specific source ID to be deleted, you can send a deletion request " +"mentioning the source ID to `telemetry@flower.ai`. All events related to " +"that source ID will then be permanently deleted." msgstr "" #: ../../source/ref-telemetry.md:46 msgid "" -"We will not collect any personally identifiable information. If you think" -" any of the metrics collected could be misused in any way, please [get in" -" touch with us](#how-to-contact-us). We will update this page to reflect " -"any changes to the metrics collected and publish changes in the " -"changelog." +"We will not collect any personally identifiable information. If you think " +"any of the metrics collected could be misused in any way, please [get in " +"touch with us](#how-to-contact-us). We will update this page to reflect any " +"changes to the metrics collected and publish changes in the changelog." msgstr "" #: ../../source/ref-telemetry.md:48 @@ -17612,17 +17115,17 @@ msgstr "" #: ../../source/ref-telemetry.md:52 msgid "" "We wanted to make it very easy for you to inspect what anonymous usage " -"metrics are reported. You can view all the reported telemetry information" -" by setting the environment variable `FLWR_TELEMETRY_LOGGING=1`. Logging " -"is disabled by default. You may use logging independently from " +"metrics are reported. You can view all the reported telemetry information by " +"setting the environment variable `FLWR_TELEMETRY_LOGGING=1`. Logging is " +"disabled by default. You may use logging independently from " "`FLWR_TELEMETRY_ENABLED` so that you can inspect the telemetry feature " "without sending any metrics." msgstr "" #: ../../source/ref-telemetry.md:58 msgid "" -"The inspect Flower telemetry without sending any anonymous usage metrics," -" use both environment variables:" +"The inspect Flower telemetry without sending any anonymous usage metrics, " +"use both environment variables:" msgstr "" #: ../../source/ref-telemetry.md:64 @@ -17639,8 +17142,8 @@ msgstr "" #: ../../source/tutorial-quickstart-android.rst:-1 msgid "" -"Read this Federated Learning quickstart tutorial for creating an Android " -"app using Flower." +"Read this Federated Learning quickstart tutorial for creating an Android app " +"using Flower." msgstr "" #: ../../source/tutorial-quickstart-android.rst:5 @@ -17649,21 +17152,19 @@ msgstr "" #: ../../source/tutorial-quickstart-android.rst:10 msgid "" -"Let's build a federated learning system using TFLite and Flower on " -"Android!" +"Let's build a federated learning system using TFLite and Flower on Android!" msgstr "" #: ../../source/tutorial-quickstart-android.rst:12 msgid "" -"Please refer to the `full code example " -"`_ to learn " -"more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with FastAI to train a vision model on CIFAR-10." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"FastAI to train a vision model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:5 @@ -17676,15 +17177,14 @@ msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:12 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:-1 msgid "" -"Check out this Federating Learning quickstart tutorial for using Flower " -"with HuggingFace Transformers in order to fine-tune an LLM." +"Check out this Federating Learning quickstart tutorial for using Flower with " +"HuggingFace Transformers in order to fine-tune an LLM." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:5 @@ -17693,17 +17193,17 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:10 msgid "" -"Let's build a federated learning system using Hugging Face Transformers " -"and Flower!" +"Let's build a federated learning system using Hugging Face Transformers and " +"Flower!" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:12 msgid "" -"We will leverage Hugging Face to federate the training of language models" -" over multiple clients using Flower. More specifically, we will fine-tune" -" a pre-trained Transformer model (distilBERT) for sequence classification" -" over a dataset of IMDB ratings. The end goal is to detect if a movie " -"rating is positive or negative." +"We will leverage Hugging Face to federate the training of language models " +"over multiple clients using Flower. More specifically, we will fine-tune a " +"pre-trained Transformer model (distilBERT) for sequence classification over " +"a dataset of IMDB ratings. The end goal is to detect if a movie rating is " +"positive or negative." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:18 @@ -17713,9 +17213,8 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:20 msgid "" "To follow along this tutorial you will need to install the following " -"packages: :code:`datasets`, :code:`evaluate`, :code:`flwr`, " -":code:`torch`, and :code:`transformers`. This can be done using " -":code:`pip`:" +"packages: :code:`datasets`, :code:`evaluate`, :code:`flwr`, :code:`torch`, " +"and :code:`transformers`. This can be done using :code:`pip`:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:30 @@ -17739,9 +17238,9 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:83 msgid "" -"Once we have a way of creating our trainloader and testloader, we can " -"take care of the training and testing. This is very similar to any " -":code:`PyTorch` training or testing loop:" +"Once we have a way of creating our trainloader and testloader, we can take " +"care of the training and testing. This is very similar to any :code:" +"`PyTorch` training or testing loop:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:121 @@ -17750,8 +17249,8 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:123 msgid "" -"To create the model itself, we will just load the pre-trained distillBERT" -" model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" +"To create the model itself, we will just load the pre-trained distillBERT " +"model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:136 @@ -17765,18 +17264,17 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:141 msgid "" "To federate our example to multiple clients, we first need to write our " -"Flower client class (inheriting from :code:`flwr.client.NumPyClient`). " -"This is very easy, as our model is a standard :code:`PyTorch` model:" +"Flower client class (inheriting from :code:`flwr.client.NumPyClient`). This " +"is very easy, as our model is a standard :code:`PyTorch` model:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:169 msgid "" "The :code:`get_parameters` function lets the server get the client's " -"parameters. Inversely, the :code:`set_parameters` function allows the " -"server to send its parameters to the client. Finally, the :code:`fit` " -"function trains the model locally for the client, and the " -":code:`evaluate` function tests the model locally and returns the " -"relevant metrics." +"parameters. Inversely, the :code:`set_parameters` function allows the server " +"to send its parameters to the client. Finally, the :code:`fit` function " +"trains the model locally for the client, and the :code:`evaluate` function " +"tests the model locally and returns the relevant metrics." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:175 @@ -17785,19 +17283,19 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:177 msgid "" -"Now that we have a way to instantiate clients, we need to create our " -"server in order to aggregate the results. Using Flower, this can be done " -"very easily by first choosing a strategy (here, we are using " -":code:`FedAvg`, which will define the global weights as the average of " -"all the clients' weights at each round) and then using the " -":code:`flwr.server.start_server` function:" +"Now that we have a way to instantiate clients, we need to create our server " +"in order to aggregate the results. Using Flower, this can be done very " +"easily by first choosing a strategy (here, we are using :code:`FedAvg`, " +"which will define the global weights as the average of all the clients' " +"weights at each round) and then using the :code:`flwr.server.start_server` " +"function:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:205 msgid "" -"The :code:`weighted_average` function is there to provide a way to " -"aggregate the metrics distributed amongst the clients (basically this " -"allows us to display a nice average accuracy and loss for every round)." +"The :code:`weighted_average` function is there to provide a way to aggregate " +"the metrics distributed amongst the clients (basically this allows us to " +"display a nice average accuracy and loss for every round)." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:209 @@ -17816,22 +17314,22 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:223 msgid "" -"If you want to check out everything put together, you should check out " -"the `full code example `_ ." +"If you want to check out everything put together, you should check out the " +"`full code example `_ ." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:226 msgid "" -"Of course, this is a very basic example, and a lot can be added or " -"modified, it was just to showcase how simply we could federate a Hugging " -"Face workflow using Flower." +"Of course, this is a very basic example, and a lot can be added or modified, " +"it was just to showcase how simply we could federate a Hugging Face workflow " +"using Flower." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:229 msgid "" -"Note that in this example we used :code:`PyTorch`, but we could have very" -" well used :code:`TensorFlow`." +"Note that in this example we used :code:`PyTorch`, but we could have very " +"well used :code:`TensorFlow`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:-1 @@ -17846,38 +17344,38 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:10 msgid "" -"In this tutorial we will learn how to train a Neural Network on MNIST " -"using Flower and CoreML on iOS devices." +"In this tutorial we will learn how to train a Neural Network on MNIST using " +"Flower and CoreML on iOS devices." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:12 msgid "" "First of all, for running the Flower Python server, it is recommended to " -"create a virtual environment and run everything within a :doc:`virtualenv" -" `. For the Flower client " +"create a virtual environment and run everything within a :doc:`virtualenv " +"`. For the Flower client " "implementation in iOS, it is recommended to use Xcode as our IDE." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:15 msgid "" -"Our example consists of one Python *server* and two iPhone *clients* that" -" all have the same model." +"Our example consists of one Python *server* and two iPhone *clients* that " +"all have the same model." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:17 msgid "" -"*Clients* are responsible for generating individual weight updates for " -"the model based on their local datasets. These updates are then sent to " -"the *server* which will aggregate them to produce a better model. " -"Finally, the *server* sends this improved version of the model back to " -"each *client*. A complete cycle of weight updates is called a *round*." +"*Clients* are responsible for generating individual weight updates for the " +"model based on their local datasets. These updates are then sent to the " +"*server* which will aggregate them to produce a better model. Finally, the " +"*server* sends this improved version of the model back to each *client*. A " +"complete cycle of weight updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:21 msgid "" "Now that we have a rough idea of what is going on, let's get started to " -"setup our Flower server environment. We first need to install Flower. You" -" can do this by using pip:" +"setup our Flower server environment. We first need to install Flower. You " +"can do this by using pip:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:27 @@ -17895,21 +17393,20 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:36 msgid "" "Now that we have all our dependencies installed, let's run a simple " -"distributed training using CoreML as our local training pipeline and " -"MNIST as our dataset. For simplicity reasons we will use the complete " -"Flower client with CoreML, that has been implemented and stored inside " -"the Swift SDK. The client implementation can be seen below:" +"distributed training using CoreML as our local training pipeline and MNIST " +"as our dataset. For simplicity reasons we will use the complete Flower " +"client with CoreML, that has been implemented and stored inside the Swift " +"SDK. The client implementation can be seen below:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:72 msgid "" -"Let's create a new application project in Xcode and add :code:`flwr` as a" -" dependency in your project. For our application, we will store the logic" -" of our app in :code:`FLiOSModel.swift` and the UI elements in " -":code:`ContentView.swift`. We will focus more on :code:`FLiOSModel.swift`" -" in this quickstart. Please refer to the `full code example " -"`_ to learn more " -"about the app." +"Let's create a new application project in Xcode and add :code:`flwr` as a " +"dependency in your project. For our application, we will store the logic of " +"our app in :code:`FLiOSModel.swift` and the UI elements in :code:" +"`ContentView.swift`. We will focus more on :code:`FLiOSModel.swift` in this " +"quickstart. Please refer to the `full code example `_ to learn more about the app." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:75 @@ -17919,22 +17416,21 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:83 msgid "" "Then add the mlmodel to the project simply by drag-and-drop, the mlmodel " -"will be bundled inside the application during deployment to your iOS " -"device. We need to pass the url to access mlmodel and run CoreML machine " -"learning processes, it can be retrieved by calling the function " -":code:`Bundle.main.url`. For the MNIST dataset, we need to preprocess it " -"into :code:`MLBatchProvider` object. The preprocessing is done inside " -":code:`DataLoader.swift`." +"will be bundled inside the application during deployment to your iOS device. " +"We need to pass the url to access mlmodel and run CoreML machine learning " +"processes, it can be retrieved by calling the function :code:`Bundle.main." +"url`. For the MNIST dataset, we need to preprocess it into :code:" +"`MLBatchProvider` object. The preprocessing is done inside :code:`DataLoader." +"swift`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:99 msgid "" -"Since CoreML does not allow the model parameters to be seen before " -"training, and accessing the model parameters during or after the training" -" can only be done by specifying the layer name, we need to know this " -"information beforehand, through looking at the model specification, which" -" are written as proto files. The implementation can be seen in " -":code:`MLModelInspect`." +"Since CoreML does not allow the model parameters to be seen before training, " +"and accessing the model parameters during or after the training can only be " +"done by specifying the layer name, we need to know this information " +"beforehand, through looking at the model specification, which are written as " +"proto files. The implementation can be seen in :code:`MLModelInspect`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:102 @@ -17945,18 +17441,18 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:117 msgid "" -"Then start the Flower gRPC client and start communicating to the server " -"by passing our Flower client to the function :code:`startFlwrGRPC`." +"Then start the Flower gRPC client and start communicating to the server by " +"passing our Flower client to the function :code:`startFlwrGRPC`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:124 msgid "" -"That's it for the client. We only have to implement :code:`Client` or " -"call the provided :code:`MLFlwrClient` and call :code:`startFlwrGRPC()`. " -"The attribute :code:`hostname` and :code:`port` tells the client which " -"server to connect to. This can be done by entering the hostname and port " -"in the application before clicking the start button to start the " -"federated learning process." +"That's it for the client. We only have to implement :code:`Client` or call " +"the provided :code:`MLFlwrClient` and call :code:`startFlwrGRPC()`. The " +"attribute :code:`hostname` and :code:`port` tells the client which server to " +"connect to. This can be done by entering the hostname and port in the " +"application before clicking the start button to start the federated learning " +"process." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:129 @@ -17972,8 +17468,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:100 msgid "" "For simple workloads we can start a Flower server and leave all the " -"configuration possibilities at their default values. In a file named " -":code:`server.py`, import Flower and start the server:" +"configuration possibilities at their default values. In a file named :code:" +"`server.py`, import Flower and start the server:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:142 @@ -17989,32 +17485,31 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:525 msgid "" "With both client and server ready, we can now run everything and see " -"federated learning in action. FL systems usually have a server and " -"multiple clients. We therefore have to start the server first:" +"federated learning in action. FL systems usually have a server and multiple " +"clients. We therefore have to start the server first:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:152 msgid "" -"Once the server is running we can start the clients in different " -"terminals. Build and run the client through your Xcode, one through Xcode" -" Simulator and the other by deploying it to your iPhone. To see more " -"about how to deploy your app to iPhone or Simulator visit `here " -"`_." +"Once the server is running we can start the clients in different terminals. " +"Build and run the client through your Xcode, one through Xcode Simulator and " +"the other by deploying it to your iPhone. To see more about how to deploy " +"your app to iPhone or Simulator visit `here `_." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:156 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system in your ios device. The full `source code " -"`_ for this " -"example can be found in :code:`examples/ios`." +"learning system in your ios device. The full `source code `_ for this example can be found in :" +"code:`examples/ios`." msgstr "" #: ../../source/tutorial-quickstart-jax.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with Jax to train a linear regression model on a scikit-learn dataset." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"Jax to train a linear regression model on a scikit-learn dataset." msgstr "" #: ../../source/tutorial-quickstart-jax.rst:5 @@ -18023,8 +17518,8 @@ msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with Pandas to perform Federated Analytics." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"Pandas to perform Federated Analytics." msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:5 @@ -18037,45 +17532,44 @@ msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:12 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with PyTorch to train a CNN model on MNIST." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"PyTorch to train a CNN model on MNIST." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:13 msgid "" -"In this tutorial we will learn how to train a Convolutional Neural " -"Network on CIFAR10 using Flower and PyTorch." +"In this tutorial we will learn how to train a Convolutional Neural Network " +"on CIFAR10 using Flower and PyTorch." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:15 #: ../../source/tutorial-quickstart-xgboost.rst:39 msgid "" "First of all, it is recommended to create a virtual environment and run " -"everything within a :doc:`virtualenv `." +"everything within a :doc:`virtualenv `." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:17 #: ../../source/tutorial-quickstart-scikitlearn.rst:14 msgid "" -"Our example consists of one *server* and two *clients* all having the " -"same model." +"Our example consists of one *server* and two *clients* all having the same " +"model." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:19 msgid "" -"*Clients* are responsible for generating individual weight-updates for " -"the model based on their local datasets. These updates are then sent to " -"the *server* which will aggregate them to produce a better model. " -"Finally, the *server* sends this improved version of the model back to " -"each *client*. A complete cycle of weight updates is called a *round*." +"*Clients* are responsible for generating individual weight-updates for the " +"model based on their local datasets. These updates are then sent to the " +"*server* which will aggregate them to produce a better model. Finally, the " +"*server* sends this improved version of the model back to each *client*. A " +"complete cycle of weight updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:23 @@ -18086,16 +17580,15 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:29 msgid "" -"Since we want to use PyTorch to solve a computer vision task, let's go " -"ahead and install PyTorch and the **torchvision** library:" +"Since we want to use PyTorch to solve a computer vision task, let's go ahead " +"and install PyTorch and the **torchvision** library:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:39 msgid "" "Now that we have all our dependencies installed, let's run a simple " -"distributed training with two clients and one server. Our training " -"procedure and network architecture are based on PyTorch's `Deep Learning " -"with PyTorch " +"distributed training with two clients and one server. Our training procedure " +"and network architecture are based on PyTorch's `Deep Learning with PyTorch " "`_." msgstr "" @@ -18112,33 +17605,33 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:62 msgid "" "We use PyTorch to load CIFAR10, a popular colored image classification " -"dataset for machine learning. The PyTorch :code:`DataLoader()` downloads " -"the training and test data that are then normalized." +"dataset for machine learning. The PyTorch :code:`DataLoader()` downloads the " +"training and test data that are then normalized." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:78 msgid "" -"Define the loss and optimizer with PyTorch. The training of the dataset " -"is done by looping over the dataset, measure the corresponding loss and " +"Define the loss and optimizer with PyTorch. The training of the dataset is " +"done by looping over the dataset, measure the corresponding loss and " "optimize it." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:94 msgid "" -"Define then the validation of the machine learning network. We loop over" -" the test set and measure the loss and accuracy of the test set." +"Define then the validation of the machine learning network. We loop over " +"the test set and measure the loss and accuracy of the test set." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:113 msgid "" -"After defining the training and testing of a PyTorch machine learning " -"model, we use the functions for the Flower clients." +"After defining the training and testing of a PyTorch machine learning model, " +"we use the functions for the Flower clients." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:115 msgid "" -"The Flower clients will use a simple CNN adapted from 'PyTorch: A 60 " -"Minute Blitz':" +"The Flower clients will use a simple CNN adapted from 'PyTorch: A 60 Minute " +"Blitz':" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:142 @@ -18150,20 +17643,19 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:144 #: ../../source/tutorial-quickstart-tensorflow.rst:54 msgid "" -"The Flower server interacts with clients through an interface called " -":code:`Client`. When the server selects a particular client for training," -" it sends training instructions over the network. The client receives " -"those instructions and calls one of the :code:`Client` methods to run " -"your code (i.e., to train the neural network we defined earlier)." +"The Flower server interacts with clients through an interface called :code:" +"`Client`. When the server selects a particular client for training, it sends " +"training instructions over the network. The client receives those " +"instructions and calls one of the :code:`Client` methods to run your code (i." +"e., to train the neural network we defined earlier)." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:150 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which " -"makes it easier to implement the :code:`Client` interface when your " -"workload uses PyTorch. Implementing :code:`NumPyClient` usually means " -"defining the following methods (:code:`set_parameters` is optional " -"though):" +"Flower provides a convenience class called :code:`NumPyClient` which makes " +"it easier to implement the :code:`Client` interface when your workload uses " +"PyTorch. Implementing :code:`NumPyClient` usually means defining the " +"following methods (:code:`set_parameters` is optional though):" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:156 @@ -18179,8 +17671,7 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:158 #: ../../source/tutorial-quickstart-scikitlearn.rst:121 msgid "" -"update the local model weights with the parameters received from the " -"server" +"update the local model weights with the parameters received from the server" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:160 @@ -18210,22 +17701,22 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:189 #: ../../source/tutorial-quickstart-tensorflow.rst:82 msgid "" -"We can now create an instance of our class :code:`CifarClient` and add " -"one line to actually run this client:" +"We can now create an instance of our class :code:`CifarClient` and add one " +"line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:196 #: ../../source/tutorial-quickstart-tensorflow.rst:90 msgid "" -"That's it for the client. We only have to implement :code:`Client` or " -":code:`NumPyClient` and call :code:`fl.client.start_client()`. If you " -"implement a client of type :code:`NumPyClient` you'll need to first call " -"its :code:`to_client()` method. The string :code:`\"[::]:8080\"` tells " -"the client which server to connect to. In our case we can run the server " -"and the client on the same machine, therefore we use " -":code:`\"[::]:8080\"`. If we run a truly federated workload with the " -"server and clients running on different machines, all that needs to " -"change is the :code:`server_address` we point the client at." +"That's it for the client. We only have to implement :code:`Client` or :code:" +"`NumPyClient` and call :code:`fl.client.start_client()`. If you implement a " +"client of type :code:`NumPyClient` you'll need to first call its :code:" +"`to_client()` method. The string :code:`\"[::]:8080\"` tells the client " +"which server to connect to. In our case we can run the server and the client " +"on the same machine, therefore we use :code:`\"[::]:8080\"`. If we run a " +"truly federated workload with the server and clients running on different " +"machines, all that needs to change is the :code:`server_address` we point " +"the client at." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:226 @@ -18233,8 +17724,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:122 #: ../../source/tutorial-quickstart-xgboost.rst:533 msgid "" -"Once the server is running we can start the clients in different " -"terminals. Open a new terminal and start the first client:" +"Once the server is running we can start the clients in different terminals. " +"Open a new terminal and start the first client:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:233 @@ -18248,24 +17739,22 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:252 #: ../../source/tutorial-quickstart-xgboost.rst:546 msgid "" -"Each client will have its own dataset. You should now see how the " -"training does in the very first terminal (the one that started the " -"server):" +"Each client will have its own dataset. You should now see how the training " +"does in the very first terminal (the one that started the server):" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:271 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code " -"`_ for this example can be found in :code:`examples" -"/quickstart-pytorch`." +"learning system. The full `source code `_ for this example can be found " +"in :code:`examples/quickstart-pytorch`." msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with PyTorch Lightning to train an Auto Encoder model on MNIST." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"PyTorch Lightning to train an Auto Encoder model on MNIST." msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 @@ -18274,21 +17763,20 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:10 msgid "" -"Let's build a horizontal federated learning system using PyTorch " -"Lightning and Flower!" +"Let's build a horizontal federated learning system using PyTorch Lightning " +"and Flower!" msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 msgid "" -"Please refer to the `full code example " -"`_ to learn more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with scikit-learn to train a linear regression model." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"scikit-learn to train a linear regression model." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:5 @@ -18297,24 +17785,23 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:10 msgid "" -"In this tutorial, we will learn how to train a :code:`Logistic " -"Regression` model on MNIST using Flower and scikit-learn." +"In this tutorial, we will learn how to train a :code:`Logistic Regression` " +"model on MNIST using Flower and scikit-learn." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:12 msgid "" -"It is recommended to create a virtual environment and run everything " -"within this :doc:`virtualenv `." +"It is recommended to create a virtual environment and run everything within " +"this :doc:`virtualenv `." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:16 msgid "" -"*Clients* are responsible for generating individual model parameter " -"updates for the model based on their local datasets. These updates are " -"then sent to the *server* which will aggregate them to produce an updated" -" global model. Finally, the *server* sends this improved version of the " -"model back to each *client*. A complete cycle of parameters updates is " -"called a *round*." +"*Clients* are responsible for generating individual model parameter updates " +"for the model based on their local datasets. These updates are then sent to " +"the *server* which will aggregate them to produce an updated global model. " +"Finally, the *server* sends this improved version of the model back to each " +"*client*. A complete cycle of parameters updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:20 @@ -18335,10 +17822,10 @@ msgstr "" msgid "" "Now that we have all our dependencies installed, let's run a simple " "distributed training with two clients and one server. However, before " -"setting up the client and server, we will define all functionalities that" -" we need for our federated learning setup within :code:`utils.py`. The " -":code:`utils.py` contains different functions defining all the machine " -"learning basics:" +"setting up the client and server, we will define all functionalities that we " +"need for our federated learning setup within :code:`utils.py`. The :code:" +"`utils.py` contains different functions defining all the machine learning " +"basics:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:45 @@ -18367,46 +17854,44 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:52 msgid "" -"Please check out :code:`utils.py` `here " -"`_ for more details. The pre-defined functions are used in" -" the :code:`client.py` and imported. The :code:`client.py` also requires " -"to import several packages such as Flower and scikit-learn:" +"Please check out :code:`utils.py` `here `_ for more details. The pre-" +"defined functions are used in the :code:`client.py` and imported. The :code:" +"`client.py` also requires to import several packages such as Flower and " +"scikit-learn:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:67 msgid "" -"Prior to local training, we need to load the MNIST dataset, a popular " -"image classification dataset of handwritten digits for machine learning, " -"and partition the dataset for FL. This can be conveniently achieved using" -" `Flower Datasets `_. The " -":code:`FederatedDataset.load_partition()` method loads the partitioned " -"training set for each partition ID defined in the :code:`--partition-id` " -"argument." +"Prior to local training, we need to load the MNIST dataset, a popular image " +"classification dataset of handwritten digits for machine learning, and " +"partition the dataset for FL. This can be conveniently achieved using " +"`Flower Datasets `_. The :code:" +"`FederatedDataset.load_partition()` method loads the partitioned training " +"set for each partition ID defined in the :code:`--partition-id` argument." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:95 msgid "" -"Next, the logistic regression model is defined and initialized with " -":code:`utils.set_initial_params()`." +"Next, the logistic regression model is defined and initialized with :code:" +"`utils.set_initial_params()`." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:107 msgid "" -"The Flower server interacts with clients through an interface called " -":code:`Client`. When the server selects a particular client for training," -" it sends training instructions over the network. The client receives " -"those instructions and calls one of the :code:`Client` methods to run " -"your code (i.e., to fit the logistic regression we defined earlier)." +"The Flower server interacts with clients through an interface called :code:" +"`Client`. When the server selects a particular client for training, it sends " +"training instructions over the network. The client receives those " +"instructions and calls one of the :code:`Client` methods to run your code (i." +"e., to fit the logistic regression we defined earlier)." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:113 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which " -"makes it easier to implement the :code:`Client` interface when your " -"workload uses scikit-learn. Implementing :code:`NumPyClient` usually " -"means defining the following methods (:code:`set_parameters` is optional " -"though):" +"Flower provides a convenience class called :code:`NumPyClient` which makes " +"it easier to implement the :code:`Client` interface when your workload uses " +"scikit-learn. Implementing :code:`NumPyClient` usually means defining the " +"following methods (:code:`set_parameters` is optional though):" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:122 @@ -18419,28 +17904,28 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:153 msgid "" -"We can now create an instance of our class :code:`MnistClient` and add " -"one line to actually run this client:" +"We can now create an instance of our class :code:`MnistClient` and add one " +"line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:160 msgid "" -"That's it for the client. We only have to implement :code:`Client` or " -":code:`NumPyClient` and call :code:`fl.client.start_client()`. If you " -"implement a client of type :code:`NumPyClient` you'll need to first call " -"its :code:`to_client()` method. The string :code:`\"0.0.0.0:8080\"` tells" -" the client which server to connect to. In our case we can run the server" -" and the client on the same machine, therefore we use " -":code:`\"0.0.0.0:8080\"`. If we run a truly federated workload with the " -"server and clients running on different machines, all that needs to " -"change is the :code:`server_address` we pass to the client." +"That's it for the client. We only have to implement :code:`Client` or :code:" +"`NumPyClient` and call :code:`fl.client.start_client()`. If you implement a " +"client of type :code:`NumPyClient` you'll need to first call its :code:" +"`to_client()` method. The string :code:`\"0.0.0.0:8080\"` tells the client " +"which server to connect to. In our case we can run the server and the client " +"on the same machine, therefore we use :code:`\"0.0.0.0:8080\"`. If we run a " +"truly federated workload with the server and clients running on different " +"machines, all that needs to change is the :code:`server_address` we pass to " +"the client." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:169 msgid "" "The following Flower server is a little bit more advanced and returns an " -"evaluation function for the server-side evaluation. First, we import " -"again all required libraries such as Flower and scikit-learn." +"evaluation function for the server-side evaluation. First, we import again " +"all required libraries such as Flower and scikit-learn." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:172 @@ -18449,46 +17934,44 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:185 msgid "" -"The number of federated learning rounds is set in :code:`fit_round()` and" -" the evaluation is defined in :code:`get_evaluate_fn()`. The evaluation " +"The number of federated learning rounds is set in :code:`fit_round()` and " +"the evaluation is defined in :code:`get_evaluate_fn()`. The evaluation " "function is called after each federated learning round and gives you " -"information about loss and accuracy. Note that we also make use of Flower" -" Datasets here to load the test split of the MNIST dataset for server-" -"side evaluation." +"information about loss and accuracy. Note that we also make use of Flower " +"Datasets here to load the test split of the MNIST dataset for server-side " +"evaluation." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:213 msgid "" -"The :code:`main` contains the server-side parameter initialization " -":code:`utils.set_initial_params()` as well as the aggregation strategy " -":code:`fl.server.strategy:FedAvg()`. The strategy is the default one, " -"federated averaging (or FedAvg), with two clients and evaluation after " -"each federated learning round. The server can be started with the command" -" :code:`fl.server.start_server(server_address=\"0.0.0.0:8080\", " -"strategy=strategy, config=fl.server.ServerConfig(num_rounds=3))`." +"The :code:`main` contains the server-side parameter initialization :code:" +"`utils.set_initial_params()` as well as the aggregation strategy :code:`fl." +"server.strategy:FedAvg()`. The strategy is the default one, federated " +"averaging (or FedAvg), with two clients and evaluation after each federated " +"learning round. The server can be started with the command :code:`fl.server." +"start_server(server_address=\"0.0.0.0:8080\", strategy=strategy, config=fl." +"server.ServerConfig(num_rounds=3))`." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:232 msgid "" "With both client and server ready, we can now run everything and see " "federated learning in action. Federated learning systems usually have a " -"server and multiple clients. We, therefore, have to start the server " -"first:" +"server and multiple clients. We, therefore, have to start the server first:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:286 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code " -"`_ for this example can be found in :code:`examples/sklearn-logreg-" -"mnist`." +"learning system. The full `source code `_ for this example can be found in :code:" +"`examples/sklearn-logreg-mnist`." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with TensorFlow to train a MobilNetV2 model on CIFAR-10." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"TensorFlow to train a MobilNetV2 model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:5 @@ -18505,8 +17988,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:21 msgid "" -"Since we want to use the Keras API of TensorFlow (TF), we have to install" -" TF as well:" +"Since we want to use the Keras API of TensorFlow (TF), we have to install TF " +"as well:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:31 @@ -18515,25 +17998,24 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:38 msgid "" -"We use the Keras utilities of TF to load CIFAR10, a popular colored image" -" classification dataset for machine learning. The call to " -":code:`tf.keras.datasets.cifar10.load_data()` downloads CIFAR10, caches " -"it locally, and then returns the entire training and test set as NumPy " -"ndarrays." +"We use the Keras utilities of TF to load CIFAR10, a popular colored image " +"classification dataset for machine learning. The call to :code:`tf.keras." +"datasets.cifar10.load_data()` downloads CIFAR10, caches it locally, and then " +"returns the entire training and test set as NumPy ndarrays." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:47 msgid "" -"Next, we need a model. For the purpose of this tutorial, we use " -"MobilNetV2 with 10 output classes:" +"Next, we need a model. For the purpose of this tutorial, we use MobilNetV2 " +"with 10 output classes:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:60 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which " -"makes it easier to implement the :code:`Client` interface when your " -"workload uses Keras. The :code:`NumPyClient` interface defines three " -"methods which can be implemented in the following way:" +"Flower provides a convenience class called :code:`NumPyClient` which makes " +"it easier to implement the :code:`Client` interface when your workload uses " +"Keras. The :code:`NumPyClient` interface defines three methods which can be " +"implemented in the following way:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:135 @@ -18542,23 +18024,22 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:137 msgid "" -"You should now see how the training does in the very first terminal (the " -"one that started the server):" +"You should now see how the training does in the very first terminal (the one " +"that started the server):" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:169 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code " -"`_ for this can be found in :code:`examples" -"/quickstart-tensorflow/client.py`." +"learning system. The full `source code `_ for this can be found in :" +"code:`examples/quickstart-tensorflow/client.py`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with XGBoost to train classification models on trees." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"XGBoost to train classification models on trees." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:5 @@ -18572,18 +18053,17 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:16 msgid "" "EXtreme Gradient Boosting (**XGBoost**) is a robust and efficient " -"implementation of gradient-boosted decision tree (**GBDT**), that " -"maximises the computational boundaries for boosted tree methods. It's " -"primarily designed to enhance both the performance and computational " -"speed of machine learning models. In XGBoost, trees are constructed " -"concurrently, unlike the sequential approach taken by GBDT." +"implementation of gradient-boosted decision tree (**GBDT**), that maximises " +"the computational boundaries for boosted tree methods. It's primarily " +"designed to enhance both the performance and computational speed of machine " +"learning models. In XGBoost, trees are constructed concurrently, unlike the " +"sequential approach taken by GBDT." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:20 msgid "" "Often, for tabular data on medium-sized datasets with fewer than 10k " -"training examples, XGBoost surpasses the results of deep learning " -"techniques." +"training examples, XGBoost surpasses the results of deep learning techniques." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:23 @@ -18593,30 +18073,30 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:25 msgid "" "Indeed, as the demand for data privacy and decentralized learning grows, " -"there's an increasing requirement to implement federated XGBoost systems " -"for specialised applications, like survival analysis and financial fraud " +"there's an increasing requirement to implement federated XGBoost systems for " +"specialised applications, like survival analysis and financial fraud " "detection." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:27 msgid "" -"Federated learning ensures that raw data remains on the local device, " -"making it an attractive approach for sensitive domains where data " -"security and privacy are paramount. Given the robustness and efficiency " -"of XGBoost, combining it with federated learning offers a promising " -"solution for these specific challenges." +"Federated learning ensures that raw data remains on the local device, making " +"it an attractive approach for sensitive domains where data security and " +"privacy are paramount. Given the robustness and efficiency of XGBoost, " +"combining it with federated learning offers a promising solution for these " +"specific challenges." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:30 msgid "" "In this tutorial we will learn how to train a federated XGBoost model on " "HIGGS dataset using Flower and :code:`xgboost` package. We use a simple " -"example (`full code xgboost-quickstart " -"`_)" -" with two *clients* and one *server* to demonstrate how federated XGBoost" -" works, and then we dive into a more complex example (`full code xgboost-" -"comprehensive `_) to run various experiments." +"example (`full code xgboost-quickstart `_) with two *clients* and one *server* to " +"demonstrate how federated XGBoost works, and then we dive into a more " +"complex example (`full code xgboost-comprehensive `_) to run various " +"experiments." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:37 @@ -18637,16 +18117,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:57 msgid "" -"*Clients* are responsible for generating individual weight-updates for " -"the model based on their local datasets. Now that we have all our " -"dependencies installed, let's run a simple distributed training with two " -"clients and one server." +"*Clients* are responsible for generating individual weight-updates for the " +"model based on their local datasets. Now that we have all our dependencies " +"installed, let's run a simple distributed training with two clients and one " +"server." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:60 msgid "" -"In a file called :code:`client.py`, import xgboost, Flower, Flower " -"Datasets and other related functions:" +"In a file called :code:`client.py`, import xgboost, Flower, Flower Datasets " +"and other related functions:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:87 @@ -18655,15 +18135,15 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:89 msgid "" -"Prior to local training, we require loading the HIGGS dataset from Flower" -" Datasets and conduct data partitioning for FL:" +"Prior to local training, we require loading the HIGGS dataset from Flower " +"Datasets and conduct data partitioning for FL:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:102 msgid "" "In this example, we split the dataset into two partitions with uniform " -"distribution (:code:`IidPartitioner(num_partitions=2)`). Then, we load " -"the partition for the given client based on :code:`node_id`:" +"distribution (:code:`IidPartitioner(num_partitions=2)`). Then, we load the " +"partition for the given client based on :code:`node_id`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:121 @@ -18674,8 +18154,8 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:134 msgid "" -"The functions of :code:`train_test_split` and " -":code:`transform_dataset_to_dmatrix` are defined as below:" +"The functions of :code:`train_test_split` and :code:" +"`transform_dataset_to_dmatrix` are defined as below:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:158 @@ -18684,10 +18164,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:174 msgid "" -"The :code:`num_local_round` represents the number of iterations for local" -" tree boost. We use CPU for the training in default. One can shift it to " -"GPU by setting :code:`tree_method` to :code:`gpu_hist`. We use AUC as " -"evaluation metric." +"The :code:`num_local_round` represents the number of iterations for local " +"tree boost. We use CPU for the training in default. One can shift it to GPU " +"by setting :code:`tree_method` to :code:`gpu_hist`. We use AUC as evaluation " +"metric." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:181 @@ -18696,86 +18176,85 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:183 msgid "" -"After loading the dataset we define the Flower client. We follow the " -"general rule to define :code:`XgbClient` class inherited from " -":code:`fl.client.Client`." +"After loading the dataset we define the Flower client. We follow the general " +"rule to define :code:`XgbClient` class inherited from :code:`fl.client." +"Client`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:193 msgid "" "The :code:`self.bst` is used to keep the Booster objects that remain " "consistent across rounds, allowing them to store predictions from trees " -"integrated in earlier rounds and maintain other essential data structures" -" for training." +"integrated in earlier rounds and maintain other essential data structures " +"for training." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:196 msgid "" -"Then, we override :code:`get_parameters`, :code:`fit` and " -":code:`evaluate` methods insides :code:`XgbClient` class as follows." +"Then, we override :code:`get_parameters`, :code:`fit` and :code:`evaluate` " +"methods insides :code:`XgbClient` class as follows." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:210 msgid "" "Unlike neural network training, XGBoost trees are not started from a " -"specified random weights. In this case, we do not use " -":code:`get_parameters` and :code:`set_parameters` to initialise model " -"parameters for XGBoost. As a result, let's return an empty tensor in " -":code:`get_parameters` when it is called by the server at the first " -"round." +"specified random weights. In this case, we do not use :code:`get_parameters` " +"and :code:`set_parameters` to initialise model parameters for XGBoost. As a " +"result, let's return an empty tensor in :code:`get_parameters` when it is " +"called by the server at the first round." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:251 msgid "" -"In :code:`fit`, at the first round, we call :code:`xgb.train()` to build " -"up the first set of trees. the returned Booster object and config are " -"stored in :code:`self.bst` and :code:`self.config`, respectively. From " -"the second round, we load the global model sent from server to " -":code:`self.bst`, and then update model weights on local training data " -"with function :code:`local_boost` as follows:" +"In :code:`fit`, at the first round, we call :code:`xgb.train()` to build up " +"the first set of trees. the returned Booster object and config are stored " +"in :code:`self.bst` and :code:`self.config`, respectively. From the second " +"round, we load the global model sent from server to :code:`self.bst`, and " +"then update model weights on local training data with function :code:" +"`local_boost` as follows:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:269 msgid "" -"Given :code:`num_local_round`, we update trees by calling " -":code:`self.bst.update` method. After training, the last " -":code:`N=num_local_round` trees will be extracted to send to the server." +"Given :code:`num_local_round`, we update trees by calling :code:`self.bst." +"update` method. After training, the last :code:`N=num_local_round` trees " +"will be extracted to send to the server." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:291 msgid "" -"In :code:`evaluate`, we call :code:`self.bst.eval_set` function to " -"conduct evaluation on valid set. The AUC value will be returned." +"In :code:`evaluate`, we call :code:`self.bst.eval_set` function to conduct " +"evaluation on valid set. The AUC value will be returned." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:294 msgid "" -"Now, we can create an instance of our class :code:`XgbClient` and add one" -" line to actually run this client:" +"Now, we can create an instance of our class :code:`XgbClient` and add one " +"line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:300 msgid "" -"That's it for the client. We only have to implement :code:`Client`and " -"call :code:`fl.client.start_client()`. The string :code:`\"[::]:8080\"` " -"tells the client which server to connect to. In our case we can run the " -"server and the client on the same machine, therefore we use " -":code:`\"[::]:8080\"`. If we run a truly federated workload with the " -"server and clients running on different machines, all that needs to " -"change is the :code:`server_address` we point the client at." +"That's it for the client. We only have to implement :code:`Client`and call :" +"code:`fl.client.start_client()`. The string :code:`\"[::]:8080\"` tells the " +"client which server to connect to. In our case we can run the server and the " +"client on the same machine, therefore we use :code:`\"[::]:8080\"`. If we " +"run a truly federated workload with the server and clients running on " +"different machines, all that needs to change is the :code:`server_address` " +"we point the client at." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:311 msgid "" "These updates are then sent to the *server* which will aggregate them to " -"produce a better model. Finally, the *server* sends this improved version" -" of the model back to each *client* to finish a complete FL round." +"produce a better model. Finally, the *server* sends this improved version of " +"the model back to each *client* to finish a complete FL round." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:314 msgid "" -"In a file named :code:`server.py`, import Flower and FedXgbBagging from " -":code:`flwr.server.strategy`." +"In a file named :code:`server.py`, import Flower and FedXgbBagging from :" +"code:`flwr.server.strategy`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:316 @@ -18784,9 +18263,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:339 msgid "" -"We use two clients for this example. An " -":code:`evaluate_metrics_aggregation` function is defined to collect and " -"wighted average the AUC values from clients." +"We use two clients for this example. An :code:`evaluate_metrics_aggregation` " +"function is defined to collect and wighted average the AUC values from " +"clients." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:342 @@ -18799,16 +18278,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:356 msgid "" -"You must be curious about how bagging aggregation works. Let's look into " -"the details." +"You must be curious about how bagging aggregation works. Let's look into the " +"details." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:358 msgid "" -"In file :code:`flwr.server.strategy.fedxgb_bagging.py`, we define " -":code:`FedXgbBagging` inherited from :code:`flwr.server.strategy.FedAvg`." -" Then, we override the :code:`aggregate_fit`, :code:`aggregate_evaluate` " -"and :code:`evaluate` methods as follows:" +"In file :code:`flwr.server.strategy.fedxgb_bagging.py`, we define :code:" +"`FedXgbBagging` inherited from :code:`flwr.server.strategy.FedAvg`. Then, we " +"override the :code:`aggregate_fit`, :code:`aggregate_evaluate` and :code:" +"`evaluate` methods as follows:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:454 @@ -18820,10 +18299,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:513 msgid "" "In this function, we first fetch the number of trees and the number of " -"parallel trees for the current and previous model by calling " -":code:`_get_tree_nums`. Then, the fetched information will be aggregated." -" After that, the trees (containing model weights) are aggregated to " -"generate a new tree model." +"parallel trees for the current and previous model by calling :code:" +"`_get_tree_nums`. Then, the fetched information will be aggregated. After " +"that, the trees (containing model weights) are aggregated to generate a new " +"tree model." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:518 @@ -18839,16 +18318,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:585 msgid "" "Congratulations! You've successfully built and run your first federated " -"XGBoost system. The AUC values can be checked in " -":code:`metrics_distributed`. One can see that the average AUC increases " -"over FL rounds." +"XGBoost system. The AUC values can be checked in :code:" +"`metrics_distributed`. One can see that the average AUC increases over FL " +"rounds." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:590 msgid "" -"The full `source code `_ for this example can be found in :code:`examples" -"/xgboost-quickstart`." +"The full `source code `_ for this example can be found in :code:`examples/" +"xgboost-quickstart`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:594 @@ -18857,15 +18336,15 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:596 msgid "" -"Now that you have known how federated XGBoost work with Flower, it's time" -" to run some more comprehensive experiments by customising the " -"experimental settings. In the xgboost-comprehensive example (`full code " -"`_), we provide more options to define various experimental" -" setups, including aggregation strategies, data partitioning and " -"centralised/distributed evaluation. We also support :doc:`Flower " -"simulation ` making it easy to simulate large " -"client cohorts in a resource-aware manner. Let's take a look!" +"Now that you have known how federated XGBoost work with Flower, it's time to " +"run some more comprehensive experiments by customising the experimental " +"settings. In the xgboost-comprehensive example (`full code `_), we provide " +"more options to define various experimental setups, including aggregation " +"strategies, data partitioning and centralised/distributed evaluation. We " +"also support :doc:`Flower simulation ` making it " +"easy to simulate large client cohorts in a resource-aware manner. Let's take " +"a look!" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:603 @@ -18874,41 +18353,40 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:605 msgid "" -"In addition to bagging aggregation, we offer a cyclic training scheme, " -"which performs FL in a client-by-client fashion. Instead of aggregating " -"multiple clients, there is only one single client participating in the " -"training per round in the cyclic training scenario. The trained local " -"XGBoost trees will be passed to the next client as an initialised model " -"for next round's boosting." +"In addition to bagging aggregation, we offer a cyclic training scheme, which " +"performs FL in a client-by-client fashion. Instead of aggregating multiple " +"clients, there is only one single client participating in the training per " +"round in the cyclic training scenario. The trained local XGBoost trees will " +"be passed to the next client as an initialised model for next round's " +"boosting." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:609 msgid "" -"To do this, we first customise a :code:`ClientManager` in " -":code:`server_utils.py`:" +"To do this, we first customise a :code:`ClientManager` in :code:" +"`server_utils.py`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:649 msgid "" -"The customised :code:`ClientManager` samples all available clients in " -"each FL round based on the order of connection to the server. Then, we " -"define a new strategy :code:`FedXgbCyclic` in " -":code:`flwr.server.strategy.fedxgb_cyclic.py`, in order to sequentially " -"select only one client in given round and pass the received model to next" -" client." +"The customised :code:`ClientManager` samples all available clients in each " +"FL round based on the order of connection to the server. Then, we define a " +"new strategy :code:`FedXgbCyclic` in :code:`flwr.server.strategy." +"fedxgb_cyclic.py`, in order to sequentially select only one client in given " +"round and pass the received model to next client." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:690 msgid "" "Unlike the original :code:`FedAvg`, we don't perform aggregation here. " -"Instead, we just make a copy of the received client model as global model" -" by overriding :code:`aggregate_fit`." +"Instead, we just make a copy of the received client model as global model by " +"overriding :code:`aggregate_fit`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:693 msgid "" -"Also, the customised :code:`configure_fit` and :code:`configure_evaluate`" -" methods ensure the clients to be sequentially selected given FL round:" +"Also, the customised :code:`configure_fit` and :code:`configure_evaluate` " +"methods ensure the clients to be sequentially selected given FL round:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:757 @@ -18917,11 +18395,11 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:759 msgid "" -"In :code:`dataset.py`, we have a function :code:`instantiate_partitioner`" -" to instantiate the data partitioner based on the given " -":code:`num_partitions` and :code:`partitioner_type`. Currently, we " -"provide four supported partitioner type to simulate the uniformity/non-" -"uniformity in data quantity (uniform, linear, square, exponential)." +"In :code:`dataset.py`, we have a function :code:`instantiate_partitioner` to " +"instantiate the data partitioner based on the given :code:`num_partitions` " +"and :code:`partitioner_type`. Currently, we provide four supported " +"partitioner type to simulate the uniformity/non-uniformity in data quantity " +"(uniform, linear, square, exponential)." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:790 @@ -18930,23 +18408,23 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:792 msgid "" -"To facilitate centralised evaluation, we define a function in " -":code:`server_utils.py`:" +"To facilitate centralised evaluation, we define a function in :code:" +"`server_utils.py`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:824 msgid "" -"This function returns a evaluation function which instantiates a " -":code:`Booster` object and loads the global model weights to it. The " -"evaluation is conducted by calling :code:`eval_set()` method, and the " -"tested AUC value is reported." +"This function returns a evaluation function which instantiates a :code:" +"`Booster` object and loads the global model weights to it. The evaluation is " +"conducted by calling :code:`eval_set()` method, and the tested AUC value is " +"reported." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:827 msgid "" -"As for distributed evaluation on the clients, it's same as the quick-" -"start example by overriding the :code:`evaluate()` method insides the " -":code:`XgbClient` class in :code:`client_utils.py`." +"As for distributed evaluation on the clients, it's same as the quick-start " +"example by overriding the :code:`evaluate()` method insides the :code:" +"`XgbClient` class in :code:`client_utils.py`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:831 @@ -18956,21 +18434,21 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:832 msgid "" "We also provide an example code (:code:`sim.py`) to use the simulation " -"capabilities of Flower to simulate federated XGBoost training on either a" -" single machine or a cluster of machines." +"capabilities of Flower to simulate federated XGBoost training on either a " +"single machine or a cluster of machines." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:866 msgid "" -"After importing all required packages, we define a :code:`main()` " -"function to perform the simulation process:" +"After importing all required packages, we define a :code:`main()` function " +"to perform the simulation process:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:921 msgid "" "We first load the dataset and perform data partitioning, and the pre-" -"processed data is stored in a :code:`list`. After the simulation begins, " -"the clients won't need to pre-process their partitions again." +"processed data is stored in a :code:`list`. After the simulation begins, the " +"clients won't need to pre-process their partitions again." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:924 @@ -18979,8 +18457,8 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:975 msgid "" -"After that, we start the simulation by calling " -":code:`fl.simulation.start_simulation`:" +"After that, we start the simulation by calling :code:`fl.simulation." +"start_simulation`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:995 @@ -18995,18 +18473,18 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1040 msgid "" -"In :code:`utils.py`, we define the arguments parsers for clients, server " -"and simulation, allowing users to specify different experimental " -"settings. Let's first see the sever side:" +"In :code:`utils.py`, we define the arguments parsers for clients, server and " +"simulation, allowing users to specify different experimental settings. Let's " +"first see the sever side:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1086 msgid "" "This allows user to specify training strategies / the number of total " -"clients / FL rounds / participating clients / clients for evaluation, and" -" evaluation fashion. Note that with :code:`--centralised-eval`, the sever" -" will do centralised evaluation and all functionalities for client " -"evaluation will be disabled." +"clients / FL rounds / participating clients / clients for evaluation, and " +"evaluation fashion. Note that with :code:`--centralised-eval`, the sever " +"will do centralised evaluation and all functionalities for client evaluation " +"will be disabled." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1090 @@ -19015,11 +18493,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1144 msgid "" -"This defines various options for client data partitioning. Besides, " -"clients also have an option to conduct evaluation on centralised test set" -" by setting :code:`--centralised-eval`, as well as an option to perform " -"scaled learning rate based on the number of clients by setting :code" -":`--scaled-lr`." +"This defines various options for client data partitioning. Besides, clients " +"also have an option to conduct evaluation on centralised test set by " +"setting :code:`--centralised-eval`, as well as an option to perform scaled " +"learning rate based on the number of clients by setting :code:`--scaled-lr`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1148 @@ -19036,9 +18513,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1231 msgid "" -"To run a centralised evaluated experiment with bagging strategy on 5 " -"clients with exponential distribution for 50 rounds, we first start the " -"server as below:" +"To run a centralised evaluated experiment with bagging strategy on 5 clients " +"with exponential distribution for 50 rounds, we first start the server as " +"below:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1238 @@ -19051,9 +18528,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1250 msgid "" -"The full `code `_ for this comprehensive example can be found in" -" :code:`examples/xgboost-comprehensive`." +"The full `code `_ for this comprehensive example can be found in :code:" +"`examples/xgboost-comprehensive`." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:9 @@ -19064,19 +18541,18 @@ msgstr "" msgid "" "Welcome to the third part of the Flower federated learning tutorial. In " "previous parts of this tutorial, we introduced federated learning with " -"PyTorch and Flower (`part 1 `__) and we learned how strategies " -"can be used to customize the execution on both the server and the clients" -" (`part 2 `__)." +"PyTorch and Flower (`part 1 `__) and we learned how strategies can be " +"used to customize the execution on both the server and the clients (`part 2 " +"`__)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:13 msgid "" -"In this notebook, we'll continue to customize the federated learning " -"system we built previously by creating a custom version of FedAvg (again," -" using `Flower `__ and `PyTorch " -"`__)." +"In this notebook, we'll continue to customize the federated learning system " +"we built previously by creating a custom version of FedAvg (again, using " +"`Flower `__ and `PyTorch `__)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:15 @@ -19084,11 +18560,11 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:15 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:15 msgid "" -"`Star Flower on GitHub `__ ⭐️ and join " -"the Flower community on Slack to connect, ask questions, and get help: " -"`Join Slack `__ 🌼 We'd love to hear from " -"you in the ``#introductions`` channel! And if anything is unclear, head " -"over to the ``#questions`` channel." +"`Star Flower on GitHub `__ ⭐️ and join the " +"Flower community on Slack to connect, ask questions, and get help: `Join " +"Slack `__ 🌼 We'd love to hear from you in the " +"``#introductions`` channel! And if anything is unclear, head over to the " +"``#questions`` channel." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:17 @@ -19134,14 +18610,14 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:102 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:101 msgid "" -"It is possible to switch to a runtime that has GPU acceleration enabled " -"(on Google Colab: ``Runtime > Change runtime type > Hardware acclerator: " -"GPU > Save``). Note, however, that Google Colab is not always able to " -"offer GPU acceleration. If you see an error related to GPU availability " -"in one of the following sections, consider switching back to CPU-based " -"execution by setting ``DEVICE = torch.device(\"cpu\")``. If the runtime " -"has GPU acceleration enabled, you should see the output ``Training on " -"cuda``, otherwise it'll say ``Training on cpu``." +"It is possible to switch to a runtime that has GPU acceleration enabled (on " +"Google Colab: ``Runtime > Change runtime type > Hardware acclerator: GPU > " +"Save``). Note, however, that Google Colab is not always able to offer GPU " +"acceleration. If you see an error related to GPU availability in one of the " +"following sections, consider switching back to CPU-based execution by " +"setting ``DEVICE = torch.device(\"cpu\")``. If the runtime has GPU " +"acceleration enabled, you should see the output ``Training on cuda``, " +"otherwise it'll say ``Training on cpu``." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:114 @@ -19153,11 +18629,11 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:116 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:116 msgid "" -"Let's now load the CIFAR-10 training and test set, partition them into " -"ten smaller datasets (each split into training and validation set), and " -"wrap everything in their own ``DataLoader``. We introduce a new parameter" -" ``num_clients`` which allows us to call ``load_datasets`` with different" -" numbers of clients." +"Let's now load the CIFAR-10 training and test set, partition them into ten " +"smaller datasets (each split into training and validation set), and wrap " +"everything in their own ``DataLoader``. We introduce a new parameter " +"``num_clients`` which allows us to call ``load_datasets`` with different " +"numbers of clients." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:167 @@ -19170,8 +18646,8 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:170 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:169 msgid "" -"Let's continue with the usual model definition (including " -"``set_parameters`` and ``get_parameters``), training and test functions:" +"Let's continue with the usual model definition (including ``set_parameters`` " +"and ``get_parameters``), training and test functions:" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:258 @@ -19182,10 +18658,10 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:260 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:260 msgid "" -"To implement the Flower client, we (again) create a subclass of " -"``flwr.client.NumPyClient`` and implement the three methods " -"``get_parameters``, ``fit``, and ``evaluate``. Here, we also pass the " -"``cid`` to the client and use it log additional details:" +"To implement the Flower client, we (again) create a subclass of ``flwr." +"client.NumPyClient`` and implement the three methods ``get_parameters``, " +"``fit``, and ``evaluate``. Here, we also pass the ``cid`` to the client and " +"use it log additional details:" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:308 @@ -19198,11 +18674,11 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:341 msgid "" -"Let’s overwrite the ``configure_fit`` method such that it passes a higher" -" learning rate (potentially also other hyperparameters) to the optimizer " -"of a fraction of the clients. We will keep the sampling of the clients as" -" it is in ``FedAvg`` and then change the configuration dictionary (one of" -" the ``FitIns`` attributes)." +"Let’s overwrite the ``configure_fit`` method such that it passes a higher " +"learning rate (potentially also other hyperparameters) to the optimizer of a " +"fraction of the clients. We will keep the sampling of the clients as it is " +"in ``FedAvg`` and then change the configuration dictionary (one of the " +"``FitIns`` attributes)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:507 @@ -19219,13 +18695,13 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:536 msgid "" -"In this notebook, we’ve seen how to implement a custom strategy. A custom" -" strategy enables granular control over client node configuration, result" -" aggregation, and more. To define a custom strategy, you only have to " -"overwrite the abstract methods of the (abstract) base class ``Strategy``." -" To make custom strategies even more powerful, you can pass custom " -"functions to the constructor of your new class (``__init__``) and then " -"call these functions whenever needed." +"In this notebook, we’ve seen how to implement a custom strategy. A custom " +"strategy enables granular control over client node configuration, result " +"aggregation, and more. To define a custom strategy, you only have to " +"overwrite the abstract methods of the (abstract) base class ``Strategy``. To " +"make custom strategies even more powerful, you can pass custom functions to " +"the constructor of your new class (``__init__``) and then call these " +"functions whenever needed." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:550 @@ -19234,8 +18710,8 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:715 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:369 msgid "" -"Before you continue, make sure to join the Flower community on Slack: " -"`Join Slack `__" +"Before you continue, make sure to join the Flower community on Slack: `Join " +"Slack `__" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:552 @@ -19244,16 +18720,15 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:717 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:371 msgid "" -"There's a dedicated ``#questions`` channel if you need help, but we'd " -"also love to hear who you are in ``#introductions``!" +"There's a dedicated ``#questions`` channel if you need help, but we'd also " +"love to hear who you are in ``#introductions``!" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:554 msgid "" -"The `Flower Federated Learning Tutorial - Part 4 " -"`__ introduces ``Client``, the flexible API underlying " -"``NumPyClient``." +"The `Flower Federated Learning Tutorial - Part 4 `__ introduces " +"``Client``, the flexible API underlying ``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:9 @@ -19262,26 +18737,26 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:11 msgid "" -"Welcome to the fourth part of the Flower federated learning tutorial. In " -"the previous parts of this tutorial, we introduced federated learning " -"with PyTorch and Flower (`part 1 `__), we learned how " -"strategies can be used to customize the execution on both the server and " -"the clients (`part 2 `__), and we built our own " -"custom strategy from scratch (`part 3 `__)." +"Welcome to the fourth part of the Flower federated learning tutorial. In the " +"previous parts of this tutorial, we introduced federated learning with " +"PyTorch and Flower (`part 1 `__), we learned how strategies can be used " +"to customize the execution on both the server and the clients (`part 2 " +"`__), and we built our own custom strategy from scratch (`part " +"3 `__)." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:14 msgid "" -"In this notebook, we revisit ``NumPyClient`` and introduce a new " -"baseclass for building clients, simply named ``Client``. In previous " -"parts of this tutorial, we've based our client on ``NumPyClient``, a " -"convenience class which makes it easy to work with machine learning " -"libraries that have good NumPy interoperability. With ``Client``, we gain" -" a lot of flexibility that we didn't have before, but we'll also have to " -"do a few things the we didn't have to do before." +"In this notebook, we revisit ``NumPyClient`` and introduce a new baseclass " +"for building clients, simply named ``Client``. In previous parts of this " +"tutorial, we've based our client on ``NumPyClient``, a convenience class " +"which makes it easy to work with machine learning libraries that have good " +"NumPy interoperability. With ``Client``, we gain a lot of flexibility that " +"we didn't have before, but we'll also have to do a few things the we didn't " +"have to do before." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:18 @@ -19297,9 +18772,9 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:117 msgid "" -"Let's now load the CIFAR-10 training and test set, partition them into " -"ten smaller datasets (each split into training and validation set), and " -"wrap everything in their own ``DataLoader``." +"Let's now load the CIFAR-10 training and test set, partition them into ten " +"smaller datasets (each split into training and validation set), and wrap " +"everything in their own ``DataLoader``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:259 @@ -19308,10 +18783,10 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:261 msgid "" -"So far, we've implemented our client by subclassing " -"``flwr.client.NumPyClient``. The three methods we implemented are " -"``get_parameters``, ``fit``, and ``evaluate``. Finally, we wrap the " -"creation of instances of this class in a function called ``client_fn``:" +"So far, we've implemented our client by subclassing ``flwr.client." +"NumPyClient``. The three methods we implemented are ``get_parameters``, " +"``fit``, and ``evaluate``. Finally, we wrap the creation of instances of " +"this class in a function called ``client_fn``:" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:309 @@ -19333,25 +18808,24 @@ msgid "" "Let's dive a little bit deeper and discuss how Flower executes this " "simulation. Whenever a client is selected to do some work, " "``start_simulation`` calls the function ``numpyclient_fn`` to create an " -"instance of our ``FlowerNumPyClient`` (along with loading the model and " -"the data)." +"instance of our ``FlowerNumPyClient`` (along with loading the model and the " +"data)." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:343 msgid "" "But here's the perhaps surprising part: Flower doesn't actually use the " -"``FlowerNumPyClient`` object directly. Instead, it wraps the object to " -"makes it look like a subclass of ``flwr.client.Client``, not " -"``flwr.client.NumPyClient``. In fact, the Flower core framework doesn't " -"know how to handle ``NumPyClient``'s, it only knows how to handle " -"``Client``'s. ``NumPyClient`` is just a convenience abstraction built on " -"top of ``Client``." +"``FlowerNumPyClient`` object directly. Instead, it wraps the object to makes " +"it look like a subclass of ``flwr.client.Client``, not ``flwr.client." +"NumPyClient``. In fact, the Flower core framework doesn't know how to handle " +"``NumPyClient``'s, it only knows how to handle ``Client``'s. ``NumPyClient`` " +"is just a convenience abstraction built on top of ``Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:345 msgid "" -"Instead of building on top of ``NumPyClient``, we can directly build on " -"top of ``Client``." +"Instead of building on top of ``NumPyClient``, we can directly build on top " +"of ``Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:357 @@ -19360,14 +18834,13 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:359 msgid "" -"Let's try to do the same thing using ``Client`` instead of " -"``NumPyClient``." +"Let's try to do the same thing using ``Client`` instead of ``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:465 msgid "" -"Before we discuss the code in more detail, let's try to run it! Gotta " -"make sure our new ``Client``-based client works, right?" +"Before we discuss the code in more detail, let's try to run it! Gotta make " +"sure our new ``Client``-based client works, right?" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:490 @@ -19378,40 +18851,40 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:492 msgid "" -"First of all, it's more code. But why? The difference comes from the fact" -" that ``Client`` expects us to take care of parameter serialization and " -"deserialization. For Flower to be able to send parameters over the " -"network, it eventually needs to turn these parameters into ``bytes``. " -"Turning parameters (e.g., NumPy ``ndarray``'s) into raw bytes is called " +"First of all, it's more code. But why? The difference comes from the fact " +"that ``Client`` expects us to take care of parameter serialization and " +"deserialization. For Flower to be able to send parameters over the network, " +"it eventually needs to turn these parameters into ``bytes``. Turning " +"parameters (e.g., NumPy ``ndarray``'s) into raw bytes is called " "serialization. Turning raw bytes into something more useful (like NumPy " -"``ndarray``'s) is called deserialization. Flower needs to do both: it " -"needs to serialize parameters on the server-side and send them to the " -"client, the client needs to deserialize them to use them for local " -"training, and then serialize the updated parameters again to send them " -"back to the server, which (finally!) deserializes them again in order to " -"aggregate them with the updates received from other clients." +"``ndarray``'s) is called deserialization. Flower needs to do both: it needs " +"to serialize parameters on the server-side and send them to the client, the " +"client needs to deserialize them to use them for local training, and then " +"serialize the updated parameters again to send them back to the server, " +"which (finally!) deserializes them again in order to aggregate them with the " +"updates received from other clients." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:495 msgid "" "The only *real* difference between Client and NumPyClient is that " -"NumPyClient takes care of serialization and deserialization for you. It " -"can do so because it expects you to return parameters as NumPy ndarray's," -" and it knows how to handle these. This makes working with machine " -"learning libraries that have good NumPy support (most of them) a breeze." +"NumPyClient takes care of serialization and deserialization for you. It can " +"do so because it expects you to return parameters as NumPy ndarray's, and it " +"knows how to handle these. This makes working with machine learning " +"libraries that have good NumPy support (most of them) a breeze." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:497 msgid "" -"In terms of API, there's one major difference: all methods in Client take" -" exactly one argument (e.g., ``FitIns`` in ``Client.fit``) and return " -"exactly one value (e.g., ``FitRes`` in ``Client.fit``). The methods in " +"In terms of API, there's one major difference: all methods in Client take " +"exactly one argument (e.g., ``FitIns`` in ``Client.fit``) and return exactly " +"one value (e.g., ``FitRes`` in ``Client.fit``). The methods in " "``NumPyClient`` on the other hand have multiple arguments (e.g., " -"``parameters`` and ``config`` in ``NumPyClient.fit``) and multiple return" -" values (e.g., ``parameters``, ``num_example``, and ``metrics`` in " -"``NumPyClient.fit``) if there are multiple things to handle. These " -"``*Ins`` and ``*Res`` objects in ``Client`` wrap all the individual " -"values you're used to from ``NumPyClient``." +"``parameters`` and ``config`` in ``NumPyClient.fit``) and multiple return " +"values (e.g., ``parameters``, ``num_example``, and ``metrics`` in " +"``NumPyClient.fit``) if there are multiple things to handle. These ``*Ins`` " +"and ``*Res`` objects in ``Client`` wrap all the individual values you're " +"used to from ``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:510 @@ -19428,17 +18901,16 @@ msgstr "" msgid "" "But first what is serialization? Serialization is just the process of " "converting an object into raw bytes, and equally as important, " -"deserialization is the process of converting raw bytes back into an " -"object. This is very useful for network communication. Indeed, without " +"deserialization is the process of converting raw bytes back into an object. " +"This is very useful for network communication. Indeed, without " "serialization, you could not just a Python object through the internet." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:516 msgid "" -"Federated Learning relies heavily on internet communication for training " -"by sending Python objects back and forth between the clients and the " -"server. This means that serialization is an essential part of Federated " -"Learning." +"Federated Learning relies heavily on internet communication for training by " +"sending Python objects back and forth between the clients and the server. " +"This means that serialization is an essential part of Federated Learning." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:518 @@ -19458,15 +18930,15 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:523 msgid "" -"This is where the real serialization/deserialization will happen, " -"especially in ``ndarray_to_sparse_bytes`` for serialization and " +"This is where the real serialization/deserialization will happen, especially " +"in ``ndarray_to_sparse_bytes`` for serialization and " "``sparse_bytes_to_ndarray`` for deserialization." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:525 msgid "" -"Note that we imported the ``scipy.sparse`` library in order to convert " -"our arrays." +"Note that we imported the ``scipy.sparse`` library in order to convert our " +"arrays." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:613 @@ -19475,30 +18947,28 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:615 msgid "" -"To be able to serialize our ``ndarray``\\ s into sparse parameters, we " -"will just have to call our custom functions in our " -"``flwr.client.Client``." +"To be able to serialize our ``ndarray``\\ s into sparse parameters, we will " +"just have to call our custom functions in our ``flwr.client.Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:617 msgid "" "Indeed, in ``get_parameters`` we need to serialize the parameters we got " -"from our network using our custom ``ndarrays_to_sparse_parameters`` " -"defined above." +"from our network using our custom ``ndarrays_to_sparse_parameters`` defined " +"above." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:619 msgid "" "In ``fit``, we first need to deserialize the parameters coming from the " -"server using our custom ``sparse_parameters_to_ndarrays`` and then we " -"need to serialize our local results with " -"``ndarrays_to_sparse_parameters``." +"server using our custom ``sparse_parameters_to_ndarrays`` and then we need " +"to serialize our local results with ``ndarrays_to_sparse_parameters``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:621 msgid "" -"In ``evaluate``, we will only need to deserialize the global parameters " -"with our custom function." +"In ``evaluate``, we will only need to deserialize the global parameters with " +"our custom function." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:725 @@ -19507,11 +18977,10 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:727 msgid "" -"For this example, we will just use ``FedAvg`` as a strategy. To change " -"the serialization and deserialization here, we only need to reimplement " -"the ``evaluate`` and ``aggregate_fit`` functions of ``FedAvg``. The other" -" functions of the strategy will be inherited from the super class " -"``FedAvg``." +"For this example, we will just use ``FedAvg`` as a strategy. To change the " +"serialization and deserialization here, we only need to reimplement the " +"``evaluate`` and ``aggregate_fit`` functions of ``FedAvg``. The other " +"functions of the strategy will be inherited from the super class ``FedAvg``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:729 @@ -19536,19 +19005,19 @@ msgstr "" msgid "" "In this part of the tutorial, we've seen how we can build clients by " "subclassing either ``NumPyClient`` or ``Client``. ``NumPyClient`` is a " -"convenience abstraction that makes it easier to work with machine " -"learning libraries that have good NumPy interoperability. ``Client`` is a" -" more flexible abstraction that allows us to do things that are not " -"possible in ``NumPyClient``. In order to do so, it requires us to handle " -"parameter serialization and deserialization ourselves." +"convenience abstraction that makes it easier to work with machine learning " +"libraries that have good NumPy interoperability. ``Client`` is a more " +"flexible abstraction that allows us to do things that are not possible in " +"``NumPyClient``. In order to do so, it requires us to handle parameter " +"serialization and deserialization ourselves." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:952 msgid "" -"This is the final part of the Flower tutorial (for now!), " -"congratulations! You're now well equipped to understand the rest of the " -"documentation. There are many topics we didn't cover in the tutorial, we " -"recommend the following resources:" +"This is the final part of the Flower tutorial (for now!), congratulations! " +"You're now well equipped to understand the rest of the documentation. There " +"are many topics we didn't cover in the tutorial, we recommend the following " +"resources:" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:954 @@ -19557,20 +19026,20 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:955 msgid "" -"`Check out Flower Code Examples " -"`__" +"`Check out Flower Code Examples `__" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:956 msgid "" -"`Use Flower Baselines for your research " -"`__" +"`Use Flower Baselines for your research `__" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:957 msgid "" -"`Watch Flower Summit 2023 videos `__" +"`Watch Flower Summit 2023 videos `__" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:9 @@ -19585,10 +19054,9 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:13 msgid "" "In this notebook, we'll build a federated learning system using Flower, " -"`Flower Datasets `__ and PyTorch. In " -"part 1, we use PyTorch for the model training pipeline and data loading. " -"In part 2, we continue to federate the PyTorch-based pipeline using " -"Flower." +"`Flower Datasets `__ and PyTorch. In part " +"1, we use PyTorch for the model training pipeline and data loading. In part " +"2, we continue to federate the PyTorch-based pipeline using Flower." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:17 @@ -19605,20 +19073,19 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:45 msgid "" "Next, we install the necessary packages for PyTorch (``torch`` and " -"``torchvision``), Flower Datasets (``flwr-datasets``) and Flower " -"(``flwr``):" +"``torchvision``), Flower Datasets (``flwr-datasets``) and Flower (``flwr``):" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:105 msgid "" -"It is possible to switch to a runtime that has GPU acceleration enabled " -"(on Google Colab: ``Runtime > Change runtime type > Hardware accelerator:" -" GPU > Save``). Note, however, that Google Colab is not always able to " -"offer GPU acceleration. If you see an error related to GPU availability " -"in one of the following sections, consider switching back to CPU-based " -"execution by setting ``DEVICE = torch.device(\"cpu\")``. If the runtime " -"has GPU acceleration enabled, you should see the output ``Training on " -"cuda``, otherwise it'll say ``Training on cpu``." +"It is possible to switch to a runtime that has GPU acceleration enabled (on " +"Google Colab: ``Runtime > Change runtime type > Hardware accelerator: GPU > " +"Save``). Note, however, that Google Colab is not always able to offer GPU " +"acceleration. If you see an error related to GPU availability in one of the " +"following sections, consider switching back to CPU-based execution by " +"setting ``DEVICE = torch.device(\"cpu\")``. If the runtime has GPU " +"acceleration enabled, you should see the output ``Training on cuda``, " +"otherwise it'll say ``Training on cpu``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:118 @@ -19627,51 +19094,50 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:120 msgid "" -"Federated learning can be applied to many different types of tasks across" -" different domains. In this tutorial, we introduce federated learning by " -"training a simple convolutional neural network (CNN) on the popular " -"CIFAR-10 dataset. CIFAR-10 can be used to train image classifiers that " -"distinguish between images from ten different classes: 'airplane', " -"'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', and " -"'truck'." +"Federated learning can be applied to many different types of tasks across " +"different domains. In this tutorial, we introduce federated learning by " +"training a simple convolutional neural network (CNN) on the popular CIFAR-10 " +"dataset. CIFAR-10 can be used to train image classifiers that distinguish " +"between images from ten different classes: 'airplane', 'automobile', 'bird', " +"'cat', 'deer', 'dog', 'frog', 'horse', 'ship', and 'truck'." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:131 msgid "" "We simulate having multiple datasets from multiple organizations (also " -"called the \"cross-silo\" setting in federated learning) by splitting the" -" original CIFAR-10 dataset into multiple partitions. Each partition will " -"represent the data from a single organization. We're doing this purely " -"for experimentation purposes, in the real world there's no need for data " -"splitting because each organization already has their own data (so the " -"data is naturally partitioned)." +"called the \"cross-silo\" setting in federated learning) by splitting the " +"original CIFAR-10 dataset into multiple partitions. Each partition will " +"represent the data from a single organization. We're doing this purely for " +"experimentation purposes, in the real world there's no need for data " +"splitting because each organization already has their own data (so the data " +"is naturally partitioned)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:133 msgid "" -"Each organization will act as a client in the federated learning system. " -"So having ten organizations participate in a federation means having ten " +"Each organization will act as a client in the federated learning system. So " +"having ten organizations participate in a federation means having ten " "clients connected to the federated learning server." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:144 msgid "" "Let's now create the Federated Dataset abstraction that from ``flwr-" -"datasets`` that partitions the CIFAR-10. We will create small training " -"and test set for each edge device and wrap each of them into a PyTorch " +"datasets`` that partitions the CIFAR-10. We will create small training and " +"test set for each edge device and wrap each of them into a PyTorch " "``DataLoader``:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:198 msgid "" "We now have a list of ten training sets and ten validation sets " -"(``trainloaders`` and ``valloaders``) representing the data of ten " -"different organizations. Each ``trainloader``/``valloader`` pair contains" -" 4000 training examples and 1000 validation examples. There's also a " -"single ``testloader`` (we did not split the test set). Again, this is " -"only necessary for building research or educational systems, actual " -"federated learning systems have their data naturally distributed across " -"multiple partitions." +"(``trainloaders`` and ``valloaders``) representing the data of ten different " +"organizations. Each ``trainloader``/``valloader`` pair contains 4000 " +"training examples and 1000 validation examples. There's also a single " +"``testloader`` (we did not split the test set). Again, this is only " +"necessary for building research or educational systems, actual federated " +"learning systems have their data naturally distributed across multiple " +"partitions." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:201 @@ -19685,8 +19151,8 @@ msgid "" "The output above shows a random batch of images from the first " "``trainloader`` in our list of ten ``trainloaders``. It also prints the " "labels associated with each image (i.e., one of the ten possible labels " -"we've seen above). If you run the cell again, you should see another " -"batch of images." +"we've seen above). If you run the cell again, you should see another batch " +"of images." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:252 @@ -19699,8 +19165,8 @@ msgid "" "network. This introduction assumes basic familiarity with PyTorch, so it " "doesn't cover the PyTorch-related aspects in full detail. If you want to " "dive deeper into PyTorch, we recommend `DEEP LEARNING WITH PYTORCH: A 60 " -"MINUTE BLITZ " -"`__." +"MINUTE BLITZ `__." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:275 @@ -19709,9 +19175,9 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:277 msgid "" -"We use the simple CNN described in the `PyTorch tutorial " -"`__:" +"We use the simple CNN described in the `PyTorch tutorial `__:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:314 @@ -19725,20 +19191,19 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:376 msgid "" "We now have all the basic building blocks we need: a dataset, a model, a " -"training function, and a test function. Let's put them together to train " -"the model on the dataset of one of our organizations " -"(``trainloaders[0]``). This simulates the reality of most machine " -"learning projects today: each organization has their own data and trains " -"models only on this internal data:" +"training function, and a test function. Let's put them together to train the " +"model on the dataset of one of our organizations (``trainloaders[0]``). This " +"simulates the reality of most machine learning projects today: each " +"organization has their own data and trains models only on this internal data:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:406 msgid "" -"Training the simple CNN on our CIFAR-10 split for 5 epochs should result " -"in a test set accuracy of about 41%, which is not good, but at the same " -"time, it doesn't really matter for the purposes of this tutorial. The " -"intent was just to show a simplistic centralized training pipeline that " -"sets the stage for what comes next - federated learning!" +"Training the simple CNN on our CIFAR-10 split for 5 epochs should result in " +"a test set accuracy of about 41%, which is not good, but at the same time, " +"it doesn't really matter for the purposes of this tutorial. The intent was " +"just to show a simplistic centralized training pipeline that sets the stage " +"for what comes next - federated learning!" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:418 @@ -19747,11 +19212,11 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:420 msgid "" -"Step 1 demonstrated a simple centralized training pipeline. All data was " -"in one place (i.e., a single ``trainloader`` and a single ``valloader``)." -" Next, we'll simulate a situation where we have multiple datasets in " -"multiple organizations and where we train a model over these " -"organizations using federated learning." +"Step 1 demonstrated a simple centralized training pipeline. All data was in " +"one place (i.e., a single ``trainloader`` and a single ``valloader``). Next, " +"we'll simulate a situation where we have multiple datasets in multiple " +"organizations and where we train a model over these organizations using " +"federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:432 @@ -19760,30 +19225,29 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:434 msgid "" -"In federated learning, the server sends the global model parameters to " -"the client, and the client updates the local model with the parameters " -"received from the server. It then trains the model on the local data " -"(which changes the model parameters locally) and sends the " -"updated/changed model parameters back to the server (or, alternatively, " -"it sends just the gradients back to the server, not the full model " -"parameters)." +"In federated learning, the server sends the global model parameters to the " +"client, and the client updates the local model with the parameters received " +"from the server. It then trains the model on the local data (which changes " +"the model parameters locally) and sends the updated/changed model parameters " +"back to the server (or, alternatively, it sends just the gradients back to " +"the server, not the full model parameters)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:436 msgid "" "We need two helper functions to update the local model with parameters " -"received from the server and to get the updated model parameters from the" -" local model: ``set_parameters`` and ``get_parameters``. The following " -"two functions do just that for the PyTorch model above." +"received from the server and to get the updated model parameters from the " +"local model: ``set_parameters`` and ``get_parameters``. The following two " +"functions do just that for the PyTorch model above." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:438 msgid "" -"The details of how this works are not really important here (feel free to" -" consult the PyTorch documentation if you want to learn more). In " -"essence, we use ``state_dict`` to access PyTorch model parameter tensors." -" The parameter tensors are then converted to/from a list of NumPy " -"ndarray's (which Flower knows how to serialize/deserialize):" +"The details of how this works are not really important here (feel free to " +"consult the PyTorch documentation if you want to learn more). In essence, we " +"use ``state_dict`` to access PyTorch model parameter tensors. The parameter " +"tensors are then converted to/from a list of NumPy ndarray's (which Flower " +"knows how to serialize/deserialize):" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:466 @@ -19792,19 +19256,18 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:468 msgid "" -"With that out of the way, let's move on to the interesting part. " -"Federated learning systems consist of a server and multiple clients. In " -"Flower, we create clients by implementing subclasses of " -"``flwr.client.Client`` or ``flwr.client.NumPyClient``. We use " -"``NumPyClient`` in this tutorial because it is easier to implement and " -"requires us to write less boilerplate." +"With that out of the way, let's move on to the interesting part. Federated " +"learning systems consist of a server and multiple clients. In Flower, we " +"create clients by implementing subclasses of ``flwr.client.Client`` or " +"``flwr.client.NumPyClient``. We use ``NumPyClient`` in this tutorial because " +"it is easier to implement and requires us to write less boilerplate." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:470 msgid "" -"To implement the Flower client, we create a subclass of " -"``flwr.client.NumPyClient`` and implement the three methods " -"``get_parameters``, ``fit``, and ``evaluate``:" +"To implement the Flower client, we create a subclass of ``flwr.client." +"NumPyClient`` and implement the three methods ``get_parameters``, ``fit``, " +"and ``evaluate``:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:472 @@ -19814,15 +19277,14 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:473 msgid "" "``fit``: Receive model parameters from the server, train the model " -"parameters on the local data, and return the (updated) model parameters " -"to the server" +"parameters on the local data, and return the (updated) model parameters to " +"the server" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:474 msgid "" -"``evaluate``: Receive model parameters from the server, evaluate the " -"model parameters on the local data, and return the evaluation result to " -"the server" +"``evaluate``: Receive model parameters from the server, evaluate the model " +"parameters on the local data, and return the evaluation result to the server" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:476 @@ -19835,16 +19297,15 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:513 msgid "" "Our class ``FlowerClient`` defines how local training/evaluation will be " -"performed and allows Flower to call the local training/evaluation through" -" ``fit`` and ``evaluate``. Each instance of ``FlowerClient`` represents a" -" *single client* in our federated learning system. Federated learning " -"systems have multiple clients (otherwise, there's not much to federate), " -"so each client will be represented by its own instance of " -"``FlowerClient``. If we have, for example, three clients in our workload," -" then we'd have three instances of ``FlowerClient``. Flower calls " -"``FlowerClient.fit`` on the respective instance when the server selects a" -" particular client for training (and ``FlowerClient.evaluate`` for " -"evaluation)." +"performed and allows Flower to call the local training/evaluation through " +"``fit`` and ``evaluate``. Each instance of ``FlowerClient`` represents a " +"*single client* in our federated learning system. Federated learning systems " +"have multiple clients (otherwise, there's not much to federate), so each " +"client will be represented by its own instance of ``FlowerClient``. If we " +"have, for example, three clients in our workload, then we'd have three " +"instances of ``FlowerClient``. Flower calls ``FlowerClient.fit`` on the " +"respective instance when the server selects a particular client for training " +"(and ``FlowerClient.evaluate`` for evaluation)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:517 @@ -19853,13 +19314,13 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:519 msgid "" -"In this notebook, we want to simulate a federated learning system with 10" -" clients on a single machine. This means that the server and all 10 " -"clients will live on a single machine and share resources such as CPU, " -"GPU, and memory. Having 10 clients would mean having 10 instances of " -"``FlowerClient`` in memory. Doing this on a single machine can quickly " -"exhaust the available memory resources, even if only a subset of these " -"clients participates in a single round of federated learning." +"In this notebook, we want to simulate a federated learning system with 10 " +"clients on a single machine. This means that the server and all 10 clients " +"will live on a single machine and share resources such as CPU, GPU, and " +"memory. Having 10 clients would mean having 10 instances of ``FlowerClient`` " +"in memory. Doing this on a single machine can quickly exhaust the available " +"memory resources, even if only a subset of these clients participates in a " +"single round of federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:521 @@ -19868,14 +19329,14 @@ msgid "" "multiple machines, Flower, therefore, provides special simulation " "capabilities that create ``FlowerClient`` instances only when they are " "actually necessary for training or evaluation. To enable the Flower " -"framework to create clients when necessary, we need to implement a " -"function called ``client_fn`` that creates a ``FlowerClient`` instance on" -" demand. Flower calls ``client_fn`` whenever it needs an instance of one " -"particular client to call ``fit`` or ``evaluate`` (those instances are " -"usually discarded after use, so they should not keep any local state). " -"Clients are identified by a client ID, or short ``cid``. The ``cid`` can " -"be used, for example, to load different local data partitions for " -"different clients, as can be seen below:" +"framework to create clients when necessary, we need to implement a function " +"called ``client_fn`` that creates a ``FlowerClient`` instance on demand. " +"Flower calls ``client_fn`` whenever it needs an instance of one particular " +"client to call ``fit`` or ``evaluate`` (those instances are usually " +"discarded after use, so they should not keep any local state). Clients are " +"identified by a client ID, or short ``cid``. The ``cid`` can be used, for " +"example, to load different local data partitions for different clients, as " +"can be seen below:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:556 @@ -19884,31 +19345,31 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:558 msgid "" -"We now have the class ``FlowerClient`` which defines client-side " -"training/evaluation and ``client_fn`` which allows Flower to create " -"``FlowerClient`` instances whenever it needs to call ``fit`` or " -"``evaluate`` on one particular client. The last step is to start the " -"actual simulation using ``flwr.simulation.start_simulation``." +"We now have the class ``FlowerClient`` which defines client-side training/" +"evaluation and ``client_fn`` which allows Flower to create ``FlowerClient`` " +"instances whenever it needs to call ``fit`` or ``evaluate`` on one " +"particular client. The last step is to start the actual simulation using " +"``flwr.simulation.start_simulation``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:560 msgid "" "The function ``start_simulation`` accepts a number of arguments, amongst " -"them the ``client_fn`` used to create ``FlowerClient`` instances, the " -"number of clients to simulate (``num_clients``), the number of federated " -"learning rounds (``num_rounds``), and the strategy. The strategy " -"encapsulates the federated learning approach/algorithm, for example, " -"*Federated Averaging* (FedAvg)." +"them the ``client_fn`` used to create ``FlowerClient`` instances, the number " +"of clients to simulate (``num_clients``), the number of federated learning " +"rounds (``num_rounds``), and the strategy. The strategy encapsulates the " +"federated learning approach/algorithm, for example, *Federated Averaging* " +"(FedAvg)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:562 msgid "" "Flower has a number of built-in strategies, but we can also use our own " -"strategy implementations to customize nearly all aspects of the federated" -" learning approach. For this example, we use the built-in ``FedAvg`` " -"implementation and customize it using a few basic parameters. The last " -"step is the actual call to ``start_simulation`` which - you guessed it - " -"starts the simulation:" +"strategy implementations to customize nearly all aspects of the federated " +"learning approach. For this example, we use the built-in ``FedAvg`` " +"implementation and customize it using a few basic parameters. The last step " +"is the actual call to ``start_simulation`` which - you guessed it - starts " +"the simulation:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:608 @@ -19922,20 +19383,20 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:612 #, python-format msgid "" -"When we call ``start_simulation``, we tell Flower that there are 10 " -"clients (``num_clients=10``). Flower then goes ahead an asks the " -"``FedAvg`` strategy to select clients. ``FedAvg`` knows that it should " -"select 100% of the available clients (``fraction_fit=1.0``), so it goes " -"ahead and selects 10 random clients (i.e., 100% of 10)." +"When we call ``start_simulation``, we tell Flower that there are 10 clients " +"(``num_clients=10``). Flower then goes ahead an asks the ``FedAvg`` strategy " +"to select clients. ``FedAvg`` knows that it should select 100% of the " +"available clients (``fraction_fit=1.0``), so it goes ahead and selects 10 " +"random clients (i.e., 100% of 10)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:614 msgid "" -"Flower then asks the selected 10 clients to train the model. When the " -"server receives the model parameter updates from the clients, it hands " -"those updates over to the strategy (*FedAvg*) for aggregation. The " -"strategy aggregates those updates and returns the new global model, which" -" then gets used in the next round of federated learning." +"Flower then asks the selected 10 clients to train the model. When the server " +"receives the model parameter updates from the clients, it hands those " +"updates over to the strategy (*FedAvg*) for aggregation. The strategy " +"aggregates those updates and returns the new global model, which then gets " +"used in the next round of federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:626 @@ -19944,28 +19405,27 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:628 msgid "" -"You may have noticed that all metrics except for ``losses_distributed`` " -"are empty. Where did the ``{\"accuracy\": float(accuracy)}`` go?" +"You may have noticed that all metrics except for ``losses_distributed`` are " +"empty. Where did the ``{\"accuracy\": float(accuracy)}`` go?" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:630 msgid "" -"Flower can automatically aggregate losses returned by individual clients," -" but it cannot do the same for metrics in the generic metrics dictionary " -"(the one with the ``accuracy`` key). Metrics dictionaries can contain " -"very different kinds of metrics and even key/value pairs that are not " -"metrics at all, so the framework does not (and can not) know how to " -"handle these automatically." +"Flower can automatically aggregate losses returned by individual clients, " +"but it cannot do the same for metrics in the generic metrics dictionary (the " +"one with the ``accuracy`` key). Metrics dictionaries can contain very " +"different kinds of metrics and even key/value pairs that are not metrics at " +"all, so the framework does not (and can not) know how to handle these " +"automatically." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:632 msgid "" -"As users, we need to tell the framework how to handle/aggregate these " -"custom metrics, and we do so by passing metric aggregation functions to " -"the strategy. The strategy will then call these functions whenever it " -"receives fit or evaluate metrics from clients. The two possible functions" -" are ``fit_metrics_aggregation_fn`` and " -"``evaluate_metrics_aggregation_fn``." +"As users, we need to tell the framework how to handle/aggregate these custom " +"metrics, and we do so by passing metric aggregation functions to the " +"strategy. The strategy will then call these functions whenever it receives " +"fit or evaluate metrics from clients. The two possible functions are " +"``fit_metrics_aggregation_fn`` and ``evaluate_metrics_aggregation_fn``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:634 @@ -19983,17 +19443,17 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:697 msgid "" "We now have a full system that performs federated training and federated " -"evaluation. It uses the ``weighted_average`` function to aggregate custom" -" evaluation metrics and calculates a single ``accuracy`` metric across " -"all clients on the server side." +"evaluation. It uses the ``weighted_average`` function to aggregate custom " +"evaluation metrics and calculates a single ``accuracy`` metric across all " +"clients on the server side." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:699 msgid "" "The other two categories of metrics (``losses_centralized`` and " "``metrics_centralized``) are still empty because they only apply when " -"centralized evaluation is being used. Part two of the Flower tutorial " -"will cover centralized evaluation." +"centralized evaluation is being used. Part two of the Flower tutorial will " +"cover centralized evaluation." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:711 @@ -20003,28 +19463,28 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:713 msgid "" -"Congratulations, you just trained a convolutional neural network, " -"federated over 10 clients! With that, you understand the basics of " -"federated learning with Flower. The same approach you've seen can be used" -" with other machine learning frameworks (not just PyTorch) and tasks (not" -" just CIFAR-10 images classification), for example NLP with Hugging Face " -"Transformers or speech with SpeechBrain." +"Congratulations, you just trained a convolutional neural network, federated " +"over 10 clients! With that, you understand the basics of federated learning " +"with Flower. The same approach you've seen can be used with other machine " +"learning frameworks (not just PyTorch) and tasks (not just CIFAR-10 images " +"classification), for example NLP with Hugging Face Transformers or speech " +"with SpeechBrain." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:715 msgid "" -"In the next notebook, we're going to cover some more advanced concepts. " -"Want to customize your strategy? Initialize parameters on the server " -"side? Or evaluate the aggregated model on the server side? We'll cover " -"all this and more in the next tutorial." +"In the next notebook, we're going to cover some more advanced concepts. Want " +"to customize your strategy? Initialize parameters on the server side? Or " +"evaluate the aggregated model on the server side? We'll cover all this and " +"more in the next tutorial." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:733 msgid "" -"The `Flower Federated Learning Tutorial - Part 2 " -"`__ goes into more depth about strategies and all " -"the advanced things you can build with them." +"The `Flower Federated Learning Tutorial - Part 2 `__ goes " +"into more depth about strategies and all the advanced things you can build " +"with them." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:9 @@ -20034,16 +19494,16 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:11 msgid "" "Welcome to the next part of the federated learning tutorial. In previous " -"parts of this tutorial, we introduced federated learning with PyTorch and" -" Flower (`part 1 `__)." +"parts of this tutorial, we introduced federated learning with PyTorch and " +"Flower (`part 1 `__)." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:13 msgid "" -"In this notebook, we'll begin to customize the federated learning system " -"we built in the introductory notebook (again, using `Flower " -"`__ and `PyTorch `__)." +"In this notebook, we'll begin to customize the federated learning system we " +"built in the introductory notebook (again, using `Flower `__ and `PyTorch `__)." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:17 @@ -20057,8 +19517,8 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:311 msgid "" "So far, everything should look familiar if you've worked through the " -"introductory notebook. With that, we're ready to introduce a number of " -"new features." +"introductory notebook. With that, we're ready to introduce a number of new " +"features." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:323 @@ -20067,16 +19527,16 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:325 msgid "" -"Flower, by default, initializes the global model by asking one random " -"client for the initial parameters. In many cases, we want more control " -"over parameter initialization though. Flower therefore allows you to " -"directly pass the initial parameters to the Strategy:" +"Flower, by default, initializes the global model by asking one random client " +"for the initial parameters. In many cases, we want more control over " +"parameter initialization though. Flower therefore allows you to directly " +"pass the initial parameters to the Strategy:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:370 msgid "" -"Passing ``initial_parameters`` to the ``FedAvg`` strategy prevents Flower" -" from asking one of the clients for the initial parameters. If we look " +"Passing ``initial_parameters`` to the ``FedAvg`` strategy prevents Flower " +"from asking one of the clients for the initial parameters. If we look " "closely, we can see that the logs do not show any calls to the " "``FlowerClient.get_parameters`` method." msgstr "" @@ -20087,17 +19547,17 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:384 msgid "" -"We've seen the function ``start_simulation`` before. It accepts a number " -"of arguments, amongst them the ``client_fn`` used to create " -"``FlowerClient`` instances, the number of clients to simulate " -"``num_clients``, the number of rounds ``num_rounds``, and the strategy." +"We've seen the function ``start_simulation`` before. It accepts a number of " +"arguments, amongst them the ``client_fn`` used to create ``FlowerClient`` " +"instances, the number of clients to simulate ``num_clients``, the number of " +"rounds ``num_rounds``, and the strategy." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:386 msgid "" "The strategy encapsulates the federated learning approach/algorithm, for " -"example, ``FedAvg`` or ``FedAdagrad``. Let's try to use a different " -"strategy this time:" +"example, ``FedAvg`` or ``FedAdagrad``. Let's try to use a different strategy " +"this time:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:424 @@ -20106,9 +19566,9 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:426 msgid "" -"Flower can evaluate the aggregated model on the server-side or on the " -"client-side. Client-side and server-side evaluation are similar in some " -"ways, but different in others." +"Flower can evaluate the aggregated model on the server-side or on the client-" +"side. Client-side and server-side evaluation are similar in some ways, but " +"different in others." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:428 @@ -20116,33 +19576,33 @@ msgid "" "**Centralized Evaluation** (or *server-side evaluation*) is conceptually " "simple: it works the same way that evaluation in centralized machine " "learning does. If there is a server-side dataset that can be used for " -"evaluation purposes, then that's great. We can evaluate the newly " -"aggregated model after each round of training without having to send the " -"model to clients. We're also fortunate in the sense that our entire " -"evaluation dataset is available at all times." +"evaluation purposes, then that's great. We can evaluate the newly aggregated " +"model after each round of training without having to send the model to " +"clients. We're also fortunate in the sense that our entire evaluation " +"dataset is available at all times." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:430 msgid "" -"**Federated Evaluation** (or *client-side evaluation*) is more complex, " -"but also more powerful: it doesn't require a centralized dataset and " -"allows us to evaluate models over a larger set of data, which often " -"yields more realistic evaluation results. In fact, many scenarios require" -" us to use **Federated Evaluation** if we want to get representative " -"evaluation results at all. But this power comes at a cost: once we start " -"to evaluate on the client side, we should be aware that our evaluation " -"dataset can change over consecutive rounds of learning if those clients " -"are not always available. Moreover, the dataset held by each client can " -"also change over consecutive rounds. This can lead to evaluation results " -"that are not stable, so even if we would not change the model, we'd see " -"our evaluation results fluctuate over consecutive rounds." +"**Federated Evaluation** (or *client-side evaluation*) is more complex, but " +"also more powerful: it doesn't require a centralized dataset and allows us " +"to evaluate models over a larger set of data, which often yields more " +"realistic evaluation results. In fact, many scenarios require us to use " +"**Federated Evaluation** if we want to get representative evaluation results " +"at all. But this power comes at a cost: once we start to evaluate on the " +"client side, we should be aware that our evaluation dataset can change over " +"consecutive rounds of learning if those clients are not always available. " +"Moreover, the dataset held by each client can also change over consecutive " +"rounds. This can lead to evaluation results that are not stable, so even if " +"we would not change the model, we'd see our evaluation results fluctuate " +"over consecutive rounds." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:433 msgid "" "We've seen how federated evaluation works on the client side (i.e., by " -"implementing the ``evaluate`` method in ``FlowerClient``). Now let's see " -"how we can evaluate aggregated model parameters on the server-side:" +"implementing the ``evaluate`` method in ``FlowerClient``). Now let's see how " +"we can evaluate aggregated model parameters on the server-side:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:490 @@ -20151,50 +19611,48 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:492 msgid "" -"In some situations, we want to configure client-side execution (training," -" evaluation) from the server-side. One example for that is the server " -"asking the clients to train for a certain number of local epochs. Flower " -"provides a way to send configuration values from the server to the " -"clients using a dictionary. Let's look at an example where the clients " -"receive values from the server through the ``config`` parameter in " -"``fit`` (``config`` is also available in ``evaluate``). The ``fit`` " -"method receives the configuration dictionary through the ``config`` " -"parameter and can then read values from this dictionary. In this example," -" it reads ``server_round`` and ``local_epochs`` and uses those values to " -"improve the logging and configure the number of local training epochs:" +"In some situations, we want to configure client-side execution (training, " +"evaluation) from the server-side. One example for that is the server asking " +"the clients to train for a certain number of local epochs. Flower provides a " +"way to send configuration values from the server to the clients using a " +"dictionary. Let's look at an example where the clients receive values from " +"the server through the ``config`` parameter in ``fit`` (``config`` is also " +"available in ``evaluate``). The ``fit`` method receives the configuration " +"dictionary through the ``config`` parameter and can then read values from " +"this dictionary. In this example, it reads ``server_round`` and " +"``local_epochs`` and uses those values to improve the logging and configure " +"the number of local training epochs:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:546 msgid "" -"So how can we send this config dictionary from server to clients? The " -"built-in Flower Strategies provide way to do this, and it works similarly" -" to the way server-side evaluation works. We provide a function to the " -"strategy, and the strategy calls this function for every round of " -"federated learning:" +"So how can we send this config dictionary from server to clients? The built-" +"in Flower Strategies provide way to do this, and it works similarly to the " +"way server-side evaluation works. We provide a function to the strategy, and " +"the strategy calls this function for every round of federated learning:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:576 msgid "" -"Next, we'll just pass this function to the FedAvg strategy before " -"starting the simulation:" +"Next, we'll just pass this function to the FedAvg strategy before starting " +"the simulation:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:613 msgid "" -"As we can see, the client logs now include the current round of federated" -" learning (which they read from the ``config`` dictionary). We can also " -"configure local training to run for one epoch during the first and second" -" round of federated learning, and then for two epochs during the third " -"round." +"As we can see, the client logs now include the current round of federated " +"learning (which they read from the ``config`` dictionary). We can also " +"configure local training to run for one epoch during the first and second " +"round of federated learning, and then for two epochs during the third round." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:615 msgid "" "Clients can also return arbitrary values to the server. To do so, they " -"return a dictionary from ``fit`` and/or ``evaluate``. We have seen and " -"used this concept throughout this notebook without mentioning it " -"explicitly: our ``FlowerClient`` returns a dictionary containing a custom" -" key/value pair as the third return value in ``evaluate``." +"return a dictionary from ``fit`` and/or ``evaluate``. We have seen and used " +"this concept throughout this notebook without mentioning it explicitly: our " +"``FlowerClient`` returns a dictionary containing a custom key/value pair as " +"the third return value in ``evaluate``." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:627 @@ -20211,14 +19669,13 @@ msgstr "" #, python-format msgid "" "We now have 1000 partitions, each holding 45 training and 5 validation " -"examples. Given that the number of training examples on each client is " -"quite small, we should probably train the model a bit longer, so we " -"configure the clients to perform 3 local training epochs. We should also " -"adjust the fraction of clients selected for training during each round " -"(we don't want all 1000 clients participating in every round), so we " -"adjust ``fraction_fit`` to ``0.05``, which means that only 5% of " -"available clients (so 50 clients) will be selected for training each " -"round:" +"examples. Given that the number of training examples on each client is quite " +"small, we should probably train the model a bit longer, so we configure the " +"clients to perform 3 local training epochs. We should also adjust the " +"fraction of clients selected for training during each round (we don't want " +"all 1000 clients participating in every round), so we adjust " +"``fraction_fit`` to ``0.05``, which means that only 5% of available clients " +"(so 50 clients) will be selected for training each round:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:699 @@ -20231,19 +19688,18 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:701 msgid "" -"In the later sections, we've seen how we can communicate arbitrary values" -" between server and clients to fully customize client-side execution. " -"With that capability, we built a large-scale Federated Learning " -"simulation using the Flower Virtual Client Engine and ran an experiment " -"involving 1000 clients in the same workload - all in a Jupyter Notebook!" +"In the later sections, we've seen how we can communicate arbitrary values " +"between server and clients to fully customize client-side execution. With " +"that capability, we built a large-scale Federated Learning simulation using " +"the Flower Virtual Client Engine and ran an experiment involving 1000 " +"clients in the same workload - all in a Jupyter Notebook!" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:719 msgid "" -"The `Flower Federated Learning Tutorial - Part 3 " -"`__ shows how to build a fully custom ``Strategy`` from " -"scratch." +"The `Flower Federated Learning Tutorial - Part 3 `__ shows how " +"to build a fully custom ``Strategy`` from scratch." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:9 @@ -20254,24 +19710,24 @@ msgstr "" msgid "" "In this tutorial, you will learn what federated learning is, build your " "first system in Flower, and gradually extend it. If you work through all " -"parts of the tutorial, you will be able to build advanced federated " -"learning systems that approach the current state of the art in the field." +"parts of the tutorial, you will be able to build advanced federated learning " +"systems that approach the current state of the art in the field." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:15 msgid "" -"🧑‍🏫 This tutorial starts at zero and expects no familiarity with " -"federated learning. Only a basic understanding of data science and Python" -" programming is assumed." +"🧑‍🏫 This tutorial starts at zero and expects no familiarity with federated " +"learning. Only a basic understanding of data science and Python programming " +"is assumed." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:17 msgid "" -"`Star Flower on GitHub `__ ⭐️ and join " -"the open-source Flower community on Slack to connect, ask questions, and " -"get help: `Join Slack `__ 🌼 We'd love to " -"hear from you in the ``#introductions`` channel! And if anything is " -"unclear, head over to the ``#questions`` channel." +"`Star Flower on GitHub `__ ⭐️ and join the " +"open-source Flower community on Slack to connect, ask questions, and get " +"help: `Join Slack `__ 🌼 We'd love to hear " +"from you in the ``#introductions`` channel! And if anything is unclear, head " +"over to the ``#questions`` channel." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:31 @@ -20280,15 +19736,15 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:33 msgid "" -"Before we begin to discuss federated learning, let us quickly recap how " -"most machine learning works today." +"Before we begin to discuss federated learning, let us quickly recap how most " +"machine learning works today." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:35 msgid "" -"In machine learning, we have a model, and we have data. The model could " -"be a neural network (as depicted here), or something else, like classical" -" linear regression." +"In machine learning, we have a model, and we have data. The model could be a " +"neural network (as depicted here), or something else, like classical linear " +"regression." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 @@ -20301,9 +19757,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:47 msgid "" -"We train the model using the data to perform a useful task. A task could " -"be to detect objects in images, transcribe an audio recording, or play a " -"game like Go." +"We train the model using the data to perform a useful task. A task could be " +"to detect objects in images, transcribe an audio recording, or play a game " +"like Go." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 @@ -20316,8 +19772,8 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:59 msgid "" -"Now, in practice, the training data we work with doesn't originate on the" -" machine we train the model on. It gets created somewhere else." +"Now, in practice, the training data we work with doesn't originate on the " +"machine we train the model on. It gets created somewhere else." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:61 @@ -20338,9 +19794,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:73 msgid "" "What's also important to mention, this \"somewhere else\" is usually not " -"just one place, it's many places. It could be several devices all running" -" the same app. But it could also be several organizations, all generating" -" data for the same task." +"just one place, it's many places. It could be several devices all running " +"the same app. But it could also be several organizations, all generating " +"data for the same task." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 @@ -20353,10 +19809,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:85 msgid "" -"So to use machine learning, or any kind of data analysis, the approach " -"that has been used in the past was to collect all data on a central " -"server. This server can be somewhere in a data center, or somewhere in " -"the cloud." +"So to use machine learning, or any kind of data analysis, the approach that " +"has been used in the past was to collect all data on a central server. This " +"server can be somewhere in a data center, or somewhere in the cloud." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 @@ -20388,10 +19843,10 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:132 msgid "" -"The classic machine learning approach we've just seen can be used in some" -" cases. Great examples include categorizing holiday photos, or analyzing " -"web traffic. Cases, where all the data is naturally available on a " -"centralized server." +"The classic machine learning approach we've just seen can be used in some " +"cases. Great examples include categorizing holiday photos, or analyzing web " +"traffic. Cases, where all the data is naturally available on a centralized " +"server." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 @@ -20404,9 +19859,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:144 msgid "" -"But the approach can not be used in many other cases. Cases, where the " -"data is not available on a centralized server, or cases where the data " -"available on one server is not enough to train a good model." +"But the approach can not be used in many other cases. Cases, where the data " +"is not available on a centralized server, or cases where the data available " +"on one server is not enough to train a good model." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 @@ -20419,9 +19874,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:156 msgid "" -"There are many reasons why the classic centralized machine learning " -"approach does not work for a large number of highly important real-world " -"use cases. Those reasons include:" +"There are many reasons why the classic centralized machine learning approach " +"does not work for a large number of highly important real-world use cases. " +"Those reasons include:" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:158 @@ -20431,32 +19886,32 @@ msgid "" "(Russia), CDPR (China), PDPB (India), PIPA (Korea), APPI (Japan), PDP " "(Indonesia), PDPA (Singapore), APP (Australia), and other regulations " "protect sensitive data from being moved. In fact, those regulations " -"sometimes even prevent single organizations from combining their own " -"users' data for artificial intelligence training because those users live" -" in different parts of the world, and their data is governed by different" -" data protection regulations." +"sometimes even prevent single organizations from combining their own users' " +"data for artificial intelligence training because those users live in " +"different parts of the world, and their data is governed by different data " +"protection regulations." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:160 msgid "" -"**User preference**: In addition to regulation, there are use cases where" -" users just expect that no data leaves their device, ever. If you type " -"your passwords and credit card info into the digital keyboard of your " -"phone, you don't expect those passwords to end up on the server of the " -"company that developed that keyboard, do you? In fact, that use case was " -"the reason federated learning was invented in the first place." +"**User preference**: In addition to regulation, there are use cases where " +"users just expect that no data leaves their device, ever. If you type your " +"passwords and credit card info into the digital keyboard of your phone, you " +"don't expect those passwords to end up on the server of the company that " +"developed that keyboard, do you? In fact, that use case was the reason " +"federated learning was invented in the first place." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:161 msgid "" -"**Data volume**: Some sensors, like cameras, produce such a high data " -"volume that it is neither feasible nor economic to collect all the data " -"(due to, for example, bandwidth or communication efficiency). Think about" -" a national rail service with hundreds of train stations across the " -"country. If each of these train stations is outfitted with a number of " -"security cameras, the volume of raw on-device data they produce requires " -"incredibly powerful and exceedingly expensive infrastructure to process " -"and store. And most of the data isn't even useful." +"**Data volume**: Some sensors, like cameras, produce such a high data volume " +"that it is neither feasible nor economic to collect all the data (due to, " +"for example, bandwidth or communication efficiency). Think about a national " +"rail service with hundreds of train stations across the country. If each of " +"these train stations is outfitted with a number of security cameras, the " +"volume of raw on-device data they produce requires incredibly powerful and " +"exceedingly expensive infrastructure to process and store. And most of the " +"data isn't even useful." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:164 @@ -20471,8 +19926,7 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:167 msgid "" -"Financial information from different organizations to detect financial " -"fraud" +"Financial information from different organizations to detect financial fraud" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:168 @@ -20485,13 +19939,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:171 msgid "" -"The popularity of privacy-enhancing systems like the `Brave " -"`__ browser or the `Signal `__ " -"messenger shows that users care about privacy. In fact, they choose the " -"privacy-enhancing version over other alternatives, if such an alternative" -" exists. But what can we do to apply machine learning and data science to" -" these cases to utilize private data? After all, these are all areas that" -" would benefit significantly from recent advances in AI." +"The popularity of privacy-enhancing systems like the `Brave `__ browser or the `Signal `__ messenger shows " +"that users care about privacy. In fact, they choose the privacy-enhancing " +"version over other alternatives, if such an alternative exists. But what can " +"we do to apply machine learning and data science to these cases to utilize " +"private data? After all, these are all areas that would benefit " +"significantly from recent advances in AI." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:186 @@ -20501,9 +19955,8 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:188 msgid "" "Federated learning simply reverses this approach. It enables machine " -"learning on distributed data by moving the training to the data, instead " -"of moving the data to the training. Here's the single-sentence " -"explanation:" +"learning on distributed data by moving the training to the data, instead of " +"moving the data to the training. Here's the single-sentence explanation:" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:190 @@ -20516,22 +19969,22 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:193 msgid "" -"By doing so, it enables us to use machine learning (and other data " -"science approaches) in areas where it wasn't possible before. We can now " -"train excellent medical AI models by enabling different hospitals to work" -" together. We can solve financial fraud by training AI models on the data" -" of different financial institutions. We can build novel privacy-" -"enhancing applications (such as secure messaging) that have better built-" -"in AI than their non-privacy-enhancing alternatives. And those are just a" -" few of the examples that come to mind. As we deploy federated learning, " -"we discover more and more areas that can suddenly be reinvented because " -"they now have access to vast amounts of previously inaccessible data." +"By doing so, it enables us to use machine learning (and other data science " +"approaches) in areas where it wasn't possible before. We can now train " +"excellent medical AI models by enabling different hospitals to work " +"together. We can solve financial fraud by training AI models on the data of " +"different financial institutions. We can build novel privacy-enhancing " +"applications (such as secure messaging) that have better built-in AI than " +"their non-privacy-enhancing alternatives. And those are just a few of the " +"examples that come to mind. As we deploy federated learning, we discover " +"more and more areas that can suddenly be reinvented because they now have " +"access to vast amounts of previously inaccessible data." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:196 msgid "" -"So how does federated learning work, exactly? Let's start with an " -"intuitive explanation." +"So how does federated learning work, exactly? Let's start with an intuitive " +"explanation." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:199 @@ -20544,9 +19997,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:204 msgid "" -"We start by initializing the model on the server. This is exactly the " -"same in classic centralized learning: we initialize the model parameters," -" either randomly or from a previously saved checkpoint." +"We start by initializing the model on the server. This is exactly the same " +"in classic centralized learning: we initialize the model parameters, either " +"randomly or from a previously saved checkpoint." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 @@ -20559,18 +20012,18 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:217 msgid "" -"Step 1: Send model to a number of connected organizations/devices (client" -" nodes)" +"Step 1: Send model to a number of connected organizations/devices (client " +"nodes)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:219 msgid "" "Next, we send the parameters of the global model to the connected client " "nodes (think: edge devices like smartphones or servers belonging to " -"organizations). This is to ensure that each participating node starts " -"their local training using the same model parameters. We often use only a" -" few of the connected nodes instead of all nodes. The reason for this is " -"that selecting more and more client nodes has diminishing returns." +"organizations). This is to ensure that each participating node starts their " +"local training using the same model parameters. We often use only a few of " +"the connected nodes instead of all nodes. The reason for this is that " +"selecting more and more client nodes has diminishing returns." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 @@ -20583,18 +20036,18 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:232 msgid "" -"Step 2: Train model locally on the data of each organization/device " -"(client node)" +"Step 2: Train model locally on the data of each organization/device (client " +"node)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:234 msgid "" -"Now that all (selected) client nodes have the latest version of the " -"global model parameters, they start the local training. They use their " -"own local dataset to train their own local model. They don't train the " -"model until full convergence, but they only train for a little while. " -"This could be as little as one epoch on the local data, or even just a " -"few steps (mini-batches)." +"Now that all (selected) client nodes have the latest version of the global " +"model parameters, they start the local training. They use their own local " +"dataset to train their own local model. They don't train the model until " +"full convergence, but they only train for a little while. This could be as " +"little as one epoch on the local data, or even just a few steps (mini-" +"batches)." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 @@ -20611,13 +20064,12 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:249 msgid "" -"After local training, each client node has a slightly different version " -"of the model parameters they originally received. The parameters are all " +"After local training, each client node has a slightly different version of " +"the model parameters they originally received. The parameters are all " "different because each client node has different examples in its local " -"dataset. The client nodes then send those model updates back to the " -"server. The model updates they send can either be the full model " -"parameters or just the gradients that were accumulated during local " -"training." +"dataset. The client nodes then send those model updates back to the server. " +"The model updates they send can either be the full model parameters or just " +"the gradients that were accumulated during local training." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 @@ -20635,27 +20087,26 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:264 msgid "" "The server receives model updates from the selected client nodes. If it " -"selected 100 client nodes, it now has 100 slightly different versions of " -"the original global model, each trained on the local data of one client. " -"But didn't we want to have one model that contains the learnings from the" -" data of all 100 client nodes?" +"selected 100 client nodes, it now has 100 slightly different versions of the " +"original global model, each trained on the local data of one client. But " +"didn't we want to have one model that contains the learnings from the data " +"of all 100 client nodes?" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:266 msgid "" -"In order to get one single model, we have to combine all the model " -"updates we received from the client nodes. This process is called " -"*aggregation*, and there are many different ways to do it. The most basic" -" way to do it is called *Federated Averaging* (`McMahan et al., 2016 " -"`__), often abbreviated as *FedAvg*. " -"*FedAvg* takes the 100 model updates and, as the name suggests, averages " -"them. To be more precise, it takes the *weighted average* of the model " -"updates, weighted by the number of examples each client used for " -"training. The weighting is important to make sure that each data example " -"has the same \"influence\" on the resulting global model. If one client " -"has 10 examples, and another client has 100 examples, then - without " -"weighting - each of the 10 examples would influence the global model ten " -"times as much as each of the 100 examples." +"In order to get one single model, we have to combine all the model updates " +"we received from the client nodes. This process is called *aggregation*, and " +"there are many different ways to do it. The most basic way to do it is " +"called *Federated Averaging* (`McMahan et al., 2016 `__), often abbreviated as *FedAvg*. *FedAvg* takes the 100 " +"model updates and, as the name suggests, averages them. To be more precise, " +"it takes the *weighted average* of the model updates, weighted by the number " +"of examples each client used for training. The weighting is important to " +"make sure that each data example has the same \"influence\" on the resulting " +"global model. If one client has 10 examples, and another client has 100 " +"examples, then - without weighting - each of the 10 examples would influence " +"the global model ten times as much as each of the 100 examples." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 @@ -20673,41 +20124,39 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:282 msgid "" "Steps 1 to 4 are what we call a single round of federated learning. The " -"global model parameters get sent to the participating client nodes (step " -"1), the client nodes train on their local data (step 2), they send their " -"updated models to the server (step 3), and the server then aggregates the" -" model updates to get a new version of the global model (step 4)." +"global model parameters get sent to the participating client nodes (step 1), " +"the client nodes train on their local data (step 2), they send their updated " +"models to the server (step 3), and the server then aggregates the model " +"updates to get a new version of the global model (step 4)." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:284 msgid "" -"During a single round, each client node that participates in that " -"iteration only trains for a little while. This means that after the " -"aggregation step (step 4), we have a model that has been trained on all " -"the data of all participating client nodes, but only for a little while. " -"We then have to repeat this training process over and over again to " -"eventually arrive at a fully trained model that performs well across the " -"data of all client nodes." +"During a single round, each client node that participates in that iteration " +"only trains for a little while. This means that after the aggregation step " +"(step 4), we have a model that has been trained on all the data of all " +"participating client nodes, but only for a little while. We then have to " +"repeat this training process over and over again to eventually arrive at a " +"fully trained model that performs well across the data of all client nodes." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:289 msgid "" "Congratulations, you now understand the basics of federated learning. " -"There's a lot more to discuss, of course, but that was federated learning" -" in a nutshell. In later parts of this tutorial, we will go into more " -"detail. Interesting questions include: How can we select the best client " -"nodes that should participate in the next round? What's the best way to " -"aggregate model updates? How can we handle failing client nodes " -"(stragglers)?" +"There's a lot more to discuss, of course, but that was federated learning in " +"a nutshell. In later parts of this tutorial, we will go into more detail. " +"Interesting questions include: How can we select the best client nodes that " +"should participate in the next round? What's the best way to aggregate model " +"updates? How can we handle failing client nodes (stragglers)?" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:294 msgid "" -"Just like we can train a model on the decentralized data of different " -"client nodes, we can also evaluate the model on that data to receive " -"valuable metrics. This is called federated evaluation, sometimes " -"abbreviated as FE. In fact, federated evaluation is an integral part of " -"most federated learning systems." +"Just like we can train a model on the decentralized data of different client " +"nodes, we can also evaluate the model on that data to receive valuable " +"metrics. This is called federated evaluation, sometimes abbreviated as FE. " +"In fact, federated evaluation is an integral part of most federated learning " +"systems." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:297 @@ -20716,25 +20165,24 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:299 msgid "" -"In many cases, machine learning isn't necessary to derive value from " -"data. Data analysis can yield valuable insights, but again, there's often" -" not enough data to get a clear answer. What's the average age at which " -"people develop a certain type of health condition? Federated analytics " -"enables such queries over multiple client nodes. It is usually used in " -"conjunction with other privacy-enhancing technologies like secure " -"aggregation to prevent the server from seeing the results submitted by " -"individual client nodes." +"In many cases, machine learning isn't necessary to derive value from data. " +"Data analysis can yield valuable insights, but again, there's often not " +"enough data to get a clear answer. What's the average age at which people " +"develop a certain type of health condition? Federated analytics enables such " +"queries over multiple client nodes. It is usually used in conjunction with " +"other privacy-enhancing technologies like secure aggregation to prevent the " +"server from seeing the results submitted by individual client nodes." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:305 msgid "" "Differential privacy (DP) is often mentioned in the context of Federated " -"Learning. It is a privacy-preserving method used when analyzing and " -"sharing statistical data, ensuring the privacy of individual " -"participants. DP achieves this by adding statistical noise to the model " -"updates, ensuring any individual participants’ information cannot be " -"distinguished or re-identified. This technique can be considered an " -"optimization that provides a quantifiable privacy protection measure." +"Learning. It is a privacy-preserving method used when analyzing and sharing " +"statistical data, ensuring the privacy of individual participants. DP " +"achieves this by adding statistical noise to the model updates, ensuring any " +"individual participants’ information cannot be distinguished or re-" +"identified. This technique can be considered an optimization that provides a " +"quantifiable privacy protection measure." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:326 @@ -20743,13 +20191,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:328 msgid "" -"Federated learning, federated evaluation, and federated analytics require" -" infrastructure to move machine learning models back and forth, train and" -" evaluate them on local data, and then aggregate the updated models. " -"Flower provides the infrastructure to do exactly that in an easy, " -"scalable, and secure way. In short, Flower presents a unified approach to" -" federated learning, analytics, and evaluation. It allows the user to " -"federate any workload, any ML framework, and any programming language." +"Federated learning, federated evaluation, and federated analytics require " +"infrastructure to move machine learning models back and forth, train and " +"evaluate them on local data, and then aggregate the updated models. Flower " +"provides the infrastructure to do exactly that in an easy, scalable, and " +"secure way. In short, Flower presents a unified approach to federated " +"learning, analytics, and evaluation. It allows the user to federate any " +"workload, any ML framework, and any programming language." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 @@ -20758,548 +20206,25 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 msgid "" -"Flower federated learning server and client nodes (car, scooter, personal" -" computer, roomba, and phone)" +"Flower federated learning server and client nodes (car, scooter, personal " +"computer, roomba, and phone)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:353 msgid "" -"Congratulations, you just learned the basics of federated learning and " -"how it relates to the classic (centralized) machine learning!" +"Congratulations, you just learned the basics of federated learning and how " +"it relates to the classic (centralized) machine learning!" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:355 msgid "" -"In the next part of this tutorial, we are going to build a first " -"federated learning system with Flower." +"In the next part of this tutorial, we are going to build a first federated " +"learning system with Flower." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:373 msgid "" -"The `Flower Federated Learning Tutorial - Part 1 " -"`__ shows how to build a simple federated learning system " -"with PyTorch and Flower." -msgstr "" - -#~ msgid "" -#~ "If you want to use your own " -#~ "base image instead of the official " -#~ "Flower base image, all you need to" -#~ " do is set the ``BASE_REPOSITORY``, " -#~ "``PYTHON_VERSION`` and ``UBUNTU_VERSION`` build " -#~ "arguments. .. code-block:: bash" -#~ msgstr "" - -#~ msgid "$ cd src/docker/superlink/ $ docker build \\" -#~ msgstr "" - -#~ msgid "" -#~ "--build-arg BASE_REPOSITORY=flwr_base \\ " -#~ "--build-arg PYTHON_VERSION=3.11 \\ --build-" -#~ "arg UBUNTU_VERSION=ubuntu22.04 \\ --build-arg" -#~ " FLWR_VERSION=1.8.0 \\ -t flwr_superlink:0.1.0" -#~ " ." -#~ msgstr "" - -#~ msgid "" -#~ "It is important to follow the " -#~ "instructions described in comments. For " -#~ "instance, in order to not break " -#~ "how our changelog system works, you " -#~ "should read the information above the" -#~ " ``Changelog entry`` section carefully. You" -#~ " can also checkout some examples and" -#~ " details in the :ref:`changelogentry` " -#~ "appendix." -#~ msgstr "" - -#~ msgid "Open a PR (as shown above)" -#~ msgstr "" - -#~ msgid "How to write a good PR title" -#~ msgstr "" - -#~ msgid "" -#~ "A well-crafted PR title helps team" -#~ " members quickly understand the purpose " -#~ "and scope of the changes being " -#~ "proposed. Here's a guide to help " -#~ "you write a good GitHub PR title:" -#~ msgstr "" - -#~ msgid "" -#~ "1. Be Clear and Concise: Provide a" -#~ " clear summary of the changes in " -#~ "a concise manner. 1. Use Actionable " -#~ "Verbs: Start with verbs like \"Add,\"" -#~ " \"Update,\" or \"Fix\" to indicate " -#~ "the purpose. 1. Include Relevant " -#~ "Information: Mention the affected feature " -#~ "or module for context. 1. Keep it" -#~ " Short: Avoid lengthy titles for easy" -#~ " readability. 1. Use Proper Capitalization" -#~ " and Punctuation: Follow grammar rules " -#~ "for clarity." -#~ msgstr "" - -#~ msgid "" -#~ "Let's start with a few examples " -#~ "for titles that should be avoided " -#~ "because they do not provide meaningful" -#~ " information:" -#~ msgstr "" - -#~ msgid "Implement Algorithm" -#~ msgstr "" - -#~ msgid "Database" -#~ msgstr "" - -#~ msgid "Add my_new_file.py to codebase" -#~ msgstr "" - -#~ msgid "Improve code in module" -#~ msgstr "" - -#~ msgid "Change SomeModule" -#~ msgstr "" - -#~ msgid "" -#~ "Here are a few positive examples " -#~ "which provide helpful information without " -#~ "repeating how they do it, as that" -#~ " is already visible in the \"Files" -#~ " changed\" section of the PR:" -#~ msgstr "" - -#~ msgid "Update docs banner to mention Flower Summit 2023" -#~ msgstr "" - -#~ msgid "Remove unnecessary XGBoost dependency" -#~ msgstr "" - -#~ msgid "Remove redundant attributes in strategies subclassing FedAvg" -#~ msgstr "" - -#~ msgid "" -#~ "Add CI job to deploy the staging" -#~ " system when the ``main`` branch " -#~ "changes" -#~ msgstr "" - -#~ msgid "" -#~ "Add new amazing library which will " -#~ "be used to improve the simulation " -#~ "engine" -#~ msgstr "" - -#~ msgid "Changelog entry" -#~ msgstr "" - -#~ msgid "" -#~ "When opening a new PR, inside its" -#~ " description, there should be a " -#~ "``Changelog entry`` header." -#~ msgstr "" - -#~ msgid "" -#~ "Above this header you should see " -#~ "the following comment that explains how" -#~ " to write your changelog entry:" -#~ msgstr "" - -#~ msgid "" -#~ "Inside the following 'Changelog entry' " -#~ "section, you should put the description" -#~ " of your changes that will be " -#~ "added to the changelog alongside your" -#~ " PR title." -#~ msgstr "" - -#~ msgid "" -#~ "If the section is completely empty " -#~ "(without any token) or non-existent, " -#~ "the changelog will just contain the " -#~ "title of the PR for the changelog" -#~ " entry, without any description." -#~ msgstr "" - -#~ msgid "" -#~ "If the section contains some text " -#~ "other than tokens, it will use it" -#~ " to add a description to the " -#~ "change." -#~ msgstr "" - -#~ msgid "" -#~ "If the section contains one of the" -#~ " following tokens it will ignore any" -#~ " other text and put the PR " -#~ "under the corresponding section of the" -#~ " changelog:" -#~ msgstr "" - -#~ msgid " is for classifying a PR as a general improvement." -#~ msgstr "" - -#~ msgid " is to not add the PR to the changelog" -#~ msgstr "" - -#~ msgid " is to add a general baselines change to the PR" -#~ msgstr "" - -#~ msgid " is to add a general examples change to the PR" -#~ msgstr "" - -#~ msgid " is to add a general sdk change to the PR" -#~ msgstr "" - -#~ msgid " is to add a general simulations change to the PR" -#~ msgstr "" - -#~ msgid "Note that only one token should be used." -#~ msgstr "" - -#~ msgid "" -#~ "Its content must have a specific " -#~ "format. We will break down what " -#~ "each possibility does:" -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains nothing or doesn't exist, " -#~ "the following text will be added " -#~ "to the changelog::" -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains a description (and no " -#~ "token), the following text will be " -#~ "added to the changelog::" -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains ````, nothing will change" -#~ " in the changelog." -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains ````, the following text" -#~ " will be added to the changelog::" -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains ````, the following " -#~ "text will be added to the " -#~ "changelog::" -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains ````, the following " -#~ "text will be added to the " -#~ "changelog::" -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains ````, the following text " -#~ "will be added to the changelog::" -#~ msgstr "" - -#~ msgid "" -#~ "If the ``### Changelog entry`` section" -#~ " contains ````, the following " -#~ "text will be added to the " -#~ "changelog::" -#~ msgstr "" - -#~ msgid "" -#~ "Note that only one token must be" -#~ " provided, otherwise, only the first " -#~ "action (in the order listed above), " -#~ "will be performed." -#~ msgstr "" - -#~ msgid "" -#~ "We recommend you to check out the" -#~ " complete `code example " -#~ "`_ demonstrating federated " -#~ "learning with Flower in an authenticated" -#~ " setting." -#~ msgstr "" - -#~ msgid "Let's break down the :code:`--require-client-authentication` flag:" -#~ msgstr "" - -#~ msgid "" -#~ "The first argument is a path to" -#~ " a CSV file storing all known " -#~ "node public keys. You need to " -#~ "store all known node public keys " -#~ "that are allowed to participate in " -#~ "a federation in one CSV file " -#~ "(:code:`.csv`)." -#~ msgstr "" - -#~ msgid "" -#~ "The second and third arguments are " -#~ "paths to the server's private and " -#~ "public keys. For development purposes, " -#~ "you can generate a private and " -#~ "public key pair using :code:`ssh-keygen" -#~ " -t ecdsa -b 384`." -#~ msgstr "" - -#~ msgid "" -#~ "The :code:`--authentication-keys` flag expects" -#~ " two arguments: a path to the " -#~ "node's private key file and a path" -#~ " to the node's public key file. " -#~ "For development purposes, you can " -#~ "generate a private and public key " -#~ "pair using :code:`ssh-keygen -t ecdsa" -#~ " -b 384`." -#~ msgstr "" - -#~ msgid "" -#~ "The simplest way to get started " -#~ "with Flower is by using the " -#~ "pre-made Docker images, which you can" -#~ " find on `Docker Hub " -#~ "`_." -#~ msgstr "" - -#~ msgid "" -#~ "The ``--insecure`` flag enables insecure " -#~ "communication (using HTTP, not HTTPS) " -#~ "and should only be used for " -#~ "testing purposes. We strongly recommend " -#~ "enabling `SSL `_ when " -#~ "deploying to a production environment." -#~ msgstr "" - -#~ msgid "" -#~ "If you want to persist the state" -#~ " of the SuperLink on your host " -#~ "system, all you need to do is " -#~ "specify a path where you want to" -#~ " save the file on your host " -#~ "system and a name for the database" -#~ " file. In the example below, we " -#~ "tell Docker via the flag ``-v`` to" -#~ " mount the user's home directory " -#~ "(``~/`` on your host) into the " -#~ "``/app/`` directory of the container. " -#~ "Furthermore, we use the flag " -#~ "``--database`` to specify the name of" -#~ " the database file." -#~ msgstr "" - -#~ msgid "" -#~ "For testing purposes, you can generate" -#~ " your own self-signed certificates. " -#~ "The `Enable SSL connections " -#~ "`_ page contains " -#~ "a section that will guide you " -#~ "through the process." -#~ msgstr "" - -#~ msgid "" -#~ "Assuming all files we need are in" -#~ " the local ``certificates`` directory, we" -#~ " can use the flag ``-v`` to " -#~ "mount the local directory into the " -#~ "``/app/`` directory of the container. " -#~ "This allows the SuperLink to access " -#~ "the files within the container. Finally," -#~ " we pass the names of the " -#~ "certificates to the SuperLink with the" -#~ " ``--certificates`` flag." -#~ msgstr "" - -#~ msgid "" -#~ "The SuperNode Docker image currently " -#~ "works only with the 1.9.0-nightly " -#~ "release. A stable version will be " -#~ "available when Flower 1.9.0 (stable) " -#~ "gets released (ETA: May). A SuperNode" -#~ " nightly image must be paired with" -#~ " the corresponding SuperLink nightly image" -#~ " released on the same day. To " -#~ "ensure the versions are in sync, " -#~ "using the concrete tag, e.g., " -#~ "``1.9.0.dev20240501`` instead of ``nightly`` " -#~ "is recommended." -#~ msgstr "" - -#~ msgid "" -#~ "We will use the ``app-pytorch`` " -#~ "example, which you can find in the" -#~ " Flower repository, to illustrate how " -#~ "you can dockerize your client-app." -#~ msgstr "" - -#~ msgid "" -#~ "Before we can start, we need to" -#~ " meet a few prerequisites in our " -#~ "local development environment. You can " -#~ "skip the first part if you want" -#~ " to run your client-app instead " -#~ "of the ``app-pytorch`` example." -#~ msgstr "" - -#~ msgid "" -#~ "First, we need to create a " -#~ "Dockerfile in the directory where the" -#~ " ``ClientApp`` code is located. If " -#~ "you use the ``app-pytorch`` example, " -#~ "create a new file called ``Dockerfile``" -#~ " in ``examples/app-pytorch``." -#~ msgstr "" - -#~ msgid "" -#~ "The ``Dockerfile`` contains the instructions" -#~ " that assemble the SuperNode image." -#~ msgstr "" - -#~ msgid "" -#~ "In the first two lines, we " -#~ "instruct Docker to use the SuperNode " -#~ "image tagged ``nightly`` as a base " -#~ "image and set our working directory " -#~ "to ``/app``. The following instructions " -#~ "will now be executed in the " -#~ "``/app`` directory. Next, we install the" -#~ " ``ClientApp`` dependencies by copying the" -#~ " ``requirements.txt`` file into the image" -#~ " and run ``pip install``. In the " -#~ "last two lines, we copy the " -#~ "``ClientApp`` code (``client.py`` and " -#~ "``task.py``) into the image and set " -#~ "the entry point to ``flower-client-" -#~ "app``." -#~ msgstr "" - -#~ msgid "" -#~ "Next, we build the SuperNode Docker " -#~ "image by running the following command" -#~ " in the directory where Dockerfile " -#~ "and client-app code are located." -#~ msgstr "" - -#~ msgid "" -#~ "``client:app``: The object reference of " -#~ "the ``ClientApp`` (``:``)." -#~ msgstr "" - -#~ msgid "" -#~ "It points to the ``ClientApp`` that " -#~ "will be run inside the SuperNode " -#~ "container." -#~ msgstr "" - -#~ msgid "" -#~ "Assuming the certificate already exists " -#~ "locally, we can use the flag " -#~ "``-v`` to mount the local certificate" -#~ " into the container's ``/app/`` directory." -#~ " This allows the SuperNode to access" -#~ " the certificate within the container. " -#~ "Use the ``--certificates`` flag when " -#~ "starting the container." -#~ msgstr "" - -#~ msgid "" -#~ "If you want to use a different " -#~ "version of Flower, for example Flower" -#~ " nightly, you can do so by " -#~ "changing the tag. All available versions" -#~ " are on `Docker Hub " -#~ "`_." -#~ msgstr "" - -#~ msgid "" -#~ "Run |runsimcli_link|_ in CLI and point" -#~ " to the ``server_app`` / ``client_app`` " -#~ "object in the code instead of " -#~ "executing the Python script. Here's an" -#~ " example (assuming the ``server_app`` and" -#~ " ``client_app`` objects are in a " -#~ "``sim.py`` module):" -#~ msgstr "" - -#~ msgid "start\\_driver" -#~ msgstr "" - -#~ msgid "run\\_simulation\\_from\\_cli" -#~ msgstr "" - -#~ msgid "" -#~ "We now have a list of ten " -#~ "training sets and ten validation sets" -#~ " (``trainloaders`` and ``valloaders``) " -#~ "representing the data of ten different" -#~ " organizations. Each ``trainloader``/``valloader`` " -#~ "pair contains 4500 training examples and" -#~ " 500 validation examples. There's also " -#~ "a single ``testloader`` (we did not " -#~ "split the test set). Again, this " -#~ "is only necessary for building research" -#~ " or educational systems, actual federated" -#~ " learning systems have their data " -#~ "naturally distributed across multiple " -#~ "partitions." -#~ msgstr "" - -#~ msgid "|191c6b8b5e1d46f99de4872746afa8af|" -#~ msgstr "" - -#~ msgid "|21b83f3feb024a049617190555a13549|" -#~ msgstr "" - -#~ msgid "|0dd15b4df7e3422f88aaf74cb401bfa7|" -#~ msgstr "" - -#~ msgid "|60e16f6be7354ca793444e01aa7adf25|" -#~ msgstr "" - -#~ msgid "|a7032acbd65948a8beef8bccbbb9b83a|" -#~ msgstr "" - -#~ msgid "|dd0e05706e584ee29e07cd39e6af5498|" -#~ msgstr "" - -#~ msgid "|2a2031018a1c4f81a69ea16df4947bd0|" -#~ msgstr "" - -#~ msgid "|5e841497933340d3b5c2efbf37e3e6a6|" -#~ msgstr "" - -#~ msgid "|19687aecbc3a485da999b66fe2051005|" -#~ msgstr "" - -#~ msgid "|32ef0bbade4d4500b7be97cf62405661|" -#~ msgstr "" - -#~ msgid "|9d57ed324b304a698263f5a983a56a6b|" -#~ msgstr "" - -#~ msgid "|d41510e6781c4bf18c234c6bfb8d4937|" -#~ msgstr "" - -#~ msgid "|a0198a7ebbfb4b9289e7312711cbc967|" -#~ msgstr "" - -#~ msgid "|2c13f726c8c843fc8aae997bf906125b|" -#~ msgstr "" +"The `Flower Federated Learning Tutorial - Part 1 `__ shows how to " +"build a simple federated learning system with PyTorch and Flower." +msgstr "" From c21c913bb248325dbbb91c47c1094a6bf9f8343b Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:53:23 +0200 Subject: [PATCH 017/595] feat(datasets) Add label count utils (#3551) --- datasets/flwr_datasets/metrics/__init__.py | 4 +- datasets/flwr_datasets/metrics/utils.py | 199 +++++++++++++++++- datasets/flwr_datasets/metrics/utils_test.py | 130 +++++++++++- .../visualization/label_distribution.py | 73 ++----- 4 files changed, 333 insertions(+), 73 deletions(-) diff --git a/datasets/flwr_datasets/metrics/__init__.py b/datasets/flwr_datasets/metrics/__init__.py index 807860b5b7b8..e82cb3088822 100644 --- a/datasets/flwr_datasets/metrics/__init__.py +++ b/datasets/flwr_datasets/metrics/__init__.py @@ -15,9 +15,9 @@ """Metrics package.""" -from flwr_datasets.metrics.utils import compute_counts, compute_frequency +from flwr_datasets.metrics.utils import compute_counts, compute_frequencies __all__ = [ "compute_counts", - "compute_frequency", + "compute_frequencies", ] diff --git a/datasets/flwr_datasets/metrics/utils.py b/datasets/flwr_datasets/metrics/utils.py index a11e7193fbde..8f78b2fd4c32 100644 --- a/datasets/flwr_datasets/metrics/utils.py +++ b/datasets/flwr_datasets/metrics/utils.py @@ -15,12 +15,197 @@ """Utils for metrics computation.""" -from typing import List, Union +import warnings +from typing import List, Optional, Union import pandas as pd +from flwr_datasets.partitioner import Partitioner + def compute_counts( + partitioner: Partitioner, + column_name: str, + verbose_names: bool = False, + max_num_partitions: Optional[int] = None, +) -> pd.DataFrame: + """Compute the counts of unique values in a given column in the partitions. + + Take into account all possible labels in dataset when computing count for each + partition (assign 0 as the size when there are no values for a label in the + partition). + + Parameters + ---------- + partitioner : Partitioner + Partitioner with an assigned dataset. + column_name : str + Column name identifying label based on which the count will be calculated. + verbose_names : bool + Whether to use verbose versions of the values in the column specified by + `column_name`. The verbose values are possible to extract if the column is a + feature of type `ClassLabel`. + max_num_partitions : Optional[int] + The maximum number of partitions that will be used. If greater than the + total number of partitions in a partitioner, it won't have an effect. If left + as None, then all partitions will be used. + + Returns + ------- + dataframe: pd.DataFrame + DataFrame where the row index represent the partition id and the column index + represent the unique values found in column specified by `column_name` + (e.g. represeting the labels). The value of the dataframe.loc[i, j] represents + the count of the label j, in the partition of index i. + + Examples + -------- + Generate DataFrame with label counts resulting from DirichletPartitioner on cifar10 + + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import DirichletPartitioner + >>> from flwr_datasets.metrics import compute_counts + >>> + >>> fds = FederatedDataset( + >>> dataset="cifar10", + >>> partitioners={ + >>> "train": DirichletPartitioner( + >>> num_partitions=20, + >>> partition_by="label", + >>> alpha=0.3, + >>> min_partition_size=0, + >>> ), + >>> }, + >>> ) + >>> partitioner = fds.partitioners["train"] + >>> counts_dataframe = compute_counts( + >>> partitioner=partitioner, + >>> column_name="label" + >>> ) + """ + if column_name not in partitioner.dataset.column_names: + raise ValueError( + f"The specified 'column_name': '{column_name}' is not present in the " + f"dataset. The dataset contains columns {partitioner.dataset.column_names}." + ) + + if max_num_partitions is None: + max_num_partitions = partitioner.num_partitions + else: + max_num_partitions = min(max_num_partitions, partitioner.num_partitions) + assert isinstance(max_num_partitions, int) + partition = partitioner.load_partition(0) + + try: + # Unique labels are needed to represent the correct count of each class + # (some of the classes can have zero samples that's why this + # adjustment is needed) + unique_labels = partition.features[column_name].str2int( + partition.features[column_name].names + ) + except AttributeError: # If the column_name is not formally a Label + unique_labels = partitioner.dataset.unique(column_name) + + partition_id_to_label_absolute_size = {} + for partition_id in range(max_num_partitions): + partition = partitioner.load_partition(partition_id) + partition_id_to_label_absolute_size[partition_id] = _compute_counts( + partition[column_name], unique_labels + ) + + dataframe = pd.DataFrame.from_dict( + partition_id_to_label_absolute_size, orient="index" + ) + dataframe.index.name = "Partition ID" + + if verbose_names: + # Adjust the column name values of the dataframe + current_labels = dataframe.columns + try: + legend_names = partitioner.dataset.features[column_name].int2str( + [int(v) for v in current_labels] + ) + dataframe.columns = legend_names + except AttributeError: + warnings.warn( + "The verbose names can not be established. " + "The column specified by 'column_name' needs to be of type " + "'ClassLabel' to create a verbose names. " + "The available names will used.", + stacklevel=1, + ) + return dataframe + + +def compute_frequencies( + partitioner: Partitioner, + column_name: str, + verbose_names: bool = False, + max_num_partitions: Optional[int] = None, +) -> pd.DataFrame: + """Compute the frequencies of unique values in a given column in the partitions. + + The frequencies sum up to 1 for a given partition id. This function takes into + account all possible labels in the dataset when computing the count for each + partition (assign 0 as the size when there are no values for a label in the + partition). + + Parameters + ---------- + partitioner : Partitioner + Partitioner with an assigned dataset. + column_name : str + Column name identifying label based on which the count will be calculated. + verbose_names : bool + Whether to use verbose versions of the values in the column specified by + `column_name`. The verbose value are possible to extract if the column is a + feature of type `ClassLabel`. + max_num_partitions : Optional[int] + The maximum number of partitions that will be used. If greater than the + total number of partitions in a partitioner, it won't have an effect. If left + as None, then all partitions will be used. + + Returns + ------- + dataframe: pd.DataFrame + DataFrame where the row index represent the partition id and the column index + represent the unique values found in column specified by `column_name` + (e.g. represeting the labels). The value of the dataframe.loc[i, j] represnt + the ratio of the label j to the total number of sample of in partition i. + + Examples + -------- + Generate DataFrame with label counts resulting from DirichletPartitioner on cifar10 + + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import DirichletPartitioner + >>> from flwr_datasets.metrics import compute_frequencies + >>> + >>> fds = FederatedDataset( + >>> dataset="cifar10", + >>> partitioners={ + >>> "train": DirichletPartitioner( + >>> num_partitions=20, + >>> partition_by="label", + >>> alpha=0.3, + >>> min_partition_size=0, + >>> ), + >>> }, + >>> ) + >>> partitioner = fds.partitioners["train"] + >>> counts_dataframe = compute_frequencies( + >>> partitioner=partitioner, + >>> column_name="label" + >>> ) + """ + dataframe = compute_counts( + partitioner, column_name, verbose_names, max_num_partitions + ) + dataframe = dataframe.div(dataframe.sum(axis=1), axis=0) + return dataframe + + +def _compute_counts( labels: Union[List[int], List[str]], unique_labels: Union[List[int], List[str]] ) -> pd.Series: """Compute the count of labels when taking into account all possible labels. @@ -51,7 +236,7 @@ def compute_counts( return label_counts_with_zeros -def compute_frequency( +def _compute_frequencies( labels: Union[List[int], List[str]], unique_labels: Union[List[int], List[str]] ) -> pd.Series: """Compute the distribution of labels when taking into account all possible labels. @@ -70,9 +255,9 @@ def compute_frequency( ------- The pd.Series with label as indices and probabilities as values. """ - counts = compute_counts(labels, unique_labels) + counts = _compute_counts(labels, unique_labels) if len(labels) == 0: - counts = counts.astype(float) - return counts - counts = counts.divide(len(labels)) - return counts + frequencies = counts.astype(float) + return frequencies + frequencies = counts.divide(len(labels)) + return frequencies diff --git a/datasets/flwr_datasets/metrics/utils_test.py b/datasets/flwr_datasets/metrics/utils_test.py index 687aa67dbde6..9e0f3acdf805 100644 --- a/datasets/flwr_datasets/metrics/utils_test.py +++ b/datasets/flwr_datasets/metrics/utils_test.py @@ -19,12 +19,124 @@ import unittest import pandas as pd -from parameterized import parameterized +from parameterized import parameterized, parameterized_class -from flwr_datasets.metrics.utils import compute_counts, compute_frequency +import datasets +from datasets import ClassLabel +from flwr_datasets.metrics.utils import ( + _compute_counts, + _compute_frequencies, + compute_counts, + compute_frequencies, +) +from flwr_datasets.partitioner import IidPartitioner -class TestMetricsUtils(unittest.TestCase): +@parameterized_class( + ("dataset", "result"), + [ + ( + datasets.Dataset.from_dict({"feature": list(range(10)), "label": [0] * 10}), + pd.DataFrame([[5], [5]], index=pd.Index([0, 1], name="Partition ID")), + ), + ( + datasets.Dataset.from_dict( + {"feature": list(range(10)), "label": [0] * 5 + [1] * 5} + ), + pd.DataFrame([[5, 0], [0, 5]], index=pd.Index([0, 1], name="Partition ID")), + ), + ( + datasets.Dataset.from_dict( + {"feature": list(range(10)), "label": [0, 0, 0, 1, 1] + [1, 1, 1, 1, 2]} + ), + pd.DataFrame( + [[3, 2, 0], [0, 4, 1]], index=pd.Index([0, 1], name="Partition ID") + ), + ), + ], +) +class TestPublicMetricsUtils(unittest.TestCase): + """Test metrics utils.""" + + dataset: datasets.Dataset + result: pd.DataFrame + + def test_compute_counts(self) -> None: + """Test if the counts are computed correctly.""" + iid_partitioner = IidPartitioner(num_partitions=2) + iid_partitioner.dataset = self.dataset + count = compute_counts(iid_partitioner, column_name="label") + pd.testing.assert_frame_equal(count, self.result) + + def test_compute_frequencies(self) -> None: + """Test if the frequencies are computed correctly.""" + iid_partitioner = IidPartitioner(num_partitions=2) + iid_partitioner.dataset = self.dataset + frequencies = compute_frequencies(iid_partitioner, column_name="label") + result = self.result.div(self.result.sum(axis=1), axis=0) + pd.testing.assert_frame_equal(frequencies, result) + + def test_compute_counts_with_verbose_label(self) -> None: + """Test if the counts are computed correctly.""" + iid_partitioner = IidPartitioner(num_partitions=2) + dataset = self.dataset + new_col_names = [ + str(col_id) for col_id in range(len(self.dataset.unique("label"))) + ] + dataset = dataset.cast_column( + "label", + ClassLabel( + num_classes=len(self.dataset.unique("label")), names=new_col_names + ), + ) + iid_partitioner.dataset = dataset + result = self.result.copy() + result.columns = new_col_names + count = compute_counts(iid_partitioner, column_name="label", verbose_names=True) + pd.testing.assert_frame_equal(count, result) + + def test_compute_frequencies_with_verbose_label(self) -> None: + """Test if the frequencies are computed correctly.""" + iid_partitioner = IidPartitioner(num_partitions=2) + dataset = self.dataset + new_col_names = [ + str(col_id) for col_id in range(len(self.dataset.unique("label"))) + ] + dataset = dataset.cast_column( + "label", + ClassLabel( + num_classes=len(self.dataset.unique("label")), names=new_col_names + ), + ) + iid_partitioner.dataset = dataset + result = self.result.copy() + result.columns = new_col_names + result = result.div(result.sum(axis=1), axis=0) + frequencies = compute_frequencies( + iid_partitioner, column_name="label", verbose_names=True + ) + pd.testing.assert_frame_equal(frequencies, result) + + def test_compute_count_with_smaller_max_partitions(self) -> None: + """Test is compute_count works when the max_partitions None: + """Test is compute_count works when the max_partitions>total partitions.""" + iid_partitioner = IidPartitioner(num_partitions=2) + iid_partitioner.dataset = self.dataset + count = compute_counts( + iid_partitioner, column_name="label", max_num_partitions=3 + ) + pd.testing.assert_frame_equal(count, self.result) + + +class TestPrivateMetricsUtils(unittest.TestCase): """Test metrics utils.""" @parameterized.expand( # type: ignore @@ -34,9 +146,9 @@ class TestMetricsUtils(unittest.TestCase): ([1, 1, 2], [1, 2, 3, 4], pd.Series([2, 1, 0, 0], index=[1, 2, 3, 4])), ] ) - def test_compute_counts(self, labels, unique_labels, expected) -> None: + def test__compute_counts(self, labels, unique_labels, expected) -> None: """Test if the counts are computed correctly.""" - result = compute_counts(labels, unique_labels) + result = _compute_counts(labels, unique_labels) pd.testing.assert_series_equal(result, expected) @parameterized.expand( # type: ignore @@ -56,7 +168,7 @@ def test_compute_counts(self, labels, unique_labels, expected) -> None: ) def test_compute_distribution(self, labels, unique_labels, expected) -> None: """Test if the distributions are computed correctly.""" - result = compute_frequency(labels, unique_labels) + result = _compute_frequencies(labels, unique_labels) pd.testing.assert_series_equal(result, expected, atol=0.001) @parameterized.expand( # type: ignore @@ -67,7 +179,7 @@ def test_compute_distribution(self, labels, unique_labels, expected) -> None: ) def test_distribution_sum_to_one(self, labels, unique_labels) -> None: """Test if distributions sum up to one.""" - result = compute_frequency(labels, unique_labels) + result = _compute_frequencies(labels, unique_labels) self.assertAlmostEqual(result.sum(), 1.0) def test_compute_counts_non_unique_labels(self) -> None: @@ -75,14 +187,14 @@ def test_compute_counts_non_unique_labels(self) -> None: labels = [1, 2, 3] unique_labels = [1, 2, 2, 3] with self.assertRaises(ValueError): - compute_counts(labels, unique_labels) + _compute_counts(labels, unique_labels) def test_compute_distribution_non_unique_labels(self) -> None: """Test if not having the unique labels raises ValueError.""" labels = [1, 1, 2, 3] unique_labels = [1, 1, 2, 3] with self.assertRaises(ValueError): - compute_frequency(labels, unique_labels) + _compute_frequencies(labels, unique_labels) if __name__ == "__main__": diff --git a/datasets/flwr_datasets/visualization/label_distribution.py b/datasets/flwr_datasets/visualization/label_distribution.py index f959fbd856ee..940b0e8f91bd 100644 --- a/datasets/flwr_datasets/visualization/label_distribution.py +++ b/datasets/flwr_datasets/visualization/label_distribution.py @@ -15,7 +15,6 @@ """Label distribution plotting.""" -import warnings from typing import Any, Dict, Optional, Tuple, Union import matplotlib.colors as mcolors @@ -23,7 +22,7 @@ from matplotlib.axes import Axes from matplotlib.figure import Figure -from flwr_datasets.metrics import compute_counts +from flwr_datasets.metrics.utils import compute_counts, compute_frequencies from flwr_datasets.partitioner import Partitioner from flwr_datasets.visualization.bar_plot import _plot_bar from flwr_datasets.visualization.heatmap_plot import _plot_heatmap @@ -81,7 +80,9 @@ def plot_label_distributions( Title for the legend. If None, the defaults will be takes based on the type of plot. verbose_labels : bool - Whether to use verbose versions of the labels. + Whether to use verbose versions of the labels. These values are used as columns + of the returned dataframe and as labels on the legend in a bar plot and columns/ + rows ticks in a heatmap plot. plot_kwargs: Optional[Dict[str, Any]] Any key value pair that can be passed to a plot function that are not supported directly. In case of the parameter doubling (e.g. specifying cmap here too) the @@ -192,60 +193,22 @@ def plot_label_distributions( """ _validate_parameters(plot_type, size_unit, partition_id_axis) - if label_name not in partitioner.dataset.column_names: - raise ValueError( - f"The specified 'label_name': '{label_name}' is not present in the " - f"dataset. The dataset contains columns {partitioner.dataset.column_names}." + dataframe = pd.DataFrame() + if size_unit == "absolute": + dataframe = compute_counts( + partitioner=partitioner, + column_name=label_name, + verbose_names=verbose_labels, + max_num_partitions=max_num_partitions, ) - - if max_num_partitions is None: - max_num_partitions = partitioner.num_partitions - else: - max_num_partitions = min(max_num_partitions, partitioner.num_partitions) - assert isinstance(max_num_partitions, int) - partitions = [partitioner.load_partition(i) for i in range(max_num_partitions)] - - partition = partitions[0] - try: - # Unique labels are needed to represent the correct count of each class - # (some of the classes can have zero samples that's why this - # adjustment is needed) - unique_labels = partition.features[label_name].str2int( - partition.features[label_name].names + elif size_unit == "percent": + dataframe = compute_frequencies( + partitioner=partitioner, + column_name=label_name, + verbose_names=verbose_labels, + max_num_partitions=max_num_partitions, ) - except AttributeError: # If the label_name is not formally a Label - unique_labels = partitioner.dataset.unique(label_name) - - partition_id_to_label_absolute_size = { - pid: compute_counts(partition[label_name], unique_labels) - for pid, partition in enumerate(partitions) - } - - dataframe = pd.DataFrame.from_dict( - partition_id_to_label_absolute_size, orient="index" - ) - dataframe.index.name = "Partition ID" - - if size_unit == "percent": - dataframe = dataframe.div(dataframe.sum(axis=1), axis=0) * 100.0 - - if verbose_labels: - # Adjust the column name values of the dataframe - # (these values are used for as labels in bar plot and columns/rows ticks - # in heatmap) - current_labels = dataframe.columns - try: - legend_names = partition.features[label_name].int2str( - [int(v) for v in current_labels] - ) - dataframe.columns = legend_names - except AttributeError: - warnings.warn( - "The verbose label names can not be established. " - "The column specified by 'label_name' needs to be of type " - "'ClassLabel'", - stacklevel=1, - ) + dataframe = dataframe * 100.0 if plot_type == "bar": axis = _plot_bar( From 408d8820ba89b02fe938eafd1f6c50bacd0b41cf Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 12 Jun 2024 12:56:00 +0200 Subject: [PATCH 018/595] feat(framework) Add `flwr install` command (#3258) Co-authored-by: Heng Pan Co-authored-by: Taner Topal Co-authored-by: Javier --- src/py/flwr/cli/app.py | 2 + src/py/flwr/cli/build.py | 17 +-- src/py/flwr/cli/config_utils.py | 15 ++- src/py/flwr/cli/install.py | 196 +++++++++++++++++++++++++++++++ src/py/flwr/cli/utils.py | 14 +++ src/py/flwr/common/object_ref.py | 22 ++-- 6 files changed, 238 insertions(+), 28 deletions(-) create mode 100644 src/py/flwr/cli/install.py diff --git a/src/py/flwr/cli/app.py b/src/py/flwr/cli/app.py index e1417f1267ac..477b990bf1da 100644 --- a/src/py/flwr/cli/app.py +++ b/src/py/flwr/cli/app.py @@ -18,6 +18,7 @@ from .build import build from .example import example +from .install import install from .new import new from .run import run @@ -34,6 +35,7 @@ app.command()(example) app.command()(run) app.command()(build) +app.command()(install) if __name__ == "__main__": app() diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index ca7ab8686c5c..d279a8d11bc2 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -14,7 +14,6 @@ # ============================================================================== """Flower command line interface `build` command.""" -import hashlib import os import zipfile from pathlib import Path @@ -25,7 +24,7 @@ from typing_extensions import Annotated from .config_utils import load_and_validate -from .utils import is_valid_project_name +from .utils import get_sha256_hash, is_valid_project_name # pylint: disable=too-many-locals @@ -115,7 +114,7 @@ def build( fab_file.write(file_path, archive_path) # Calculate file info - sha256_hash = _get_sha256_hash(file_path) + sha256_hash = get_sha256_hash(file_path) file_size_bits = os.path.getsize(file_path) * 8 # size in bits list_file_content += f"{archive_path},{sha256_hash},{file_size_bits}\n" @@ -127,18 +126,6 @@ def build( ) -def _get_sha256_hash(file_path: Path) -> str: - """Calculate the SHA-256 hash of a file.""" - sha256 = hashlib.sha256() - with open(file_path, "rb") as f: - while True: - data = f.read(65536) # Read in 64kB blocks - if not data: - break - sha256.update(data) - return sha256.hexdigest() - - def _load_gitignore(directory: Path) -> pathspec.PathSpec: """Load and parse .gitignore file, returning a pathspec.""" gitignore_path = directory / ".gitignore" diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index d943d87e3812..ec67fefda0d2 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -24,6 +24,7 @@ def load_and_validate( path: Optional[Path] = None, + check_module: bool = True, ) -> Tuple[Optional[Dict[str, Any]], List[str], List[str]]: """Load and validate pyproject.toml as dict. @@ -42,7 +43,7 @@ def load_and_validate( ] return (None, errors, []) - is_valid, errors, warnings = validate(config) + is_valid, errors, warnings = validate(config, check_module) if not is_valid: return (None, errors, warnings) @@ -102,7 +103,9 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]] return len(errors) == 0, errors, warnings -def validate(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]]: +def validate( + config: Dict[str, Any], check_module: bool = True +) -> Tuple[bool, List[str], List[str]]: """Validate pyproject.toml.""" is_valid, errors, warnings = validate_fields(config) @@ -110,12 +113,16 @@ def validate(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]]: return False, errors, warnings # Validate serverapp - is_valid, reason = object_ref.validate(config["flower"]["components"]["serverapp"]) + is_valid, reason = object_ref.validate( + config["flower"]["components"]["serverapp"], check_module + ) if not is_valid and isinstance(reason, str): return False, [reason], [] # Validate clientapp - is_valid, reason = object_ref.validate(config["flower"]["components"]["clientapp"]) + is_valid, reason = object_ref.validate( + config["flower"]["components"]["clientapp"], check_module + ) if not is_valid and isinstance(reason, str): return False, [reason], [] diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py new file mode 100644 index 000000000000..e6ce9fe1a69a --- /dev/null +++ b/src/py/flwr/cli/install.py @@ -0,0 +1,196 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Flower command line interface `install` command.""" + + +import os +import shutil +import tempfile +import zipfile +from pathlib import Path +from typing import Optional + +import typer +from typing_extensions import Annotated + +from .config_utils import load_and_validate +from .utils import get_sha256_hash + + +def install( + source: Annotated[ + Optional[Path], + typer.Argument(metavar="source", help="The source FAB file to install."), + ] = None, + flwr_dir: Annotated[ + Optional[Path], + typer.Option(help="The desired install path."), + ] = None, +) -> None: + """Install a Flower App Bundle. + + It can be ran with a single FAB file argument: + + ``flwr install ./target_project.fab`` + + The target install directory can be specified with ``--flwr-dir``: + + ``flwr install ./target_project.fab --flwr-dir ./docs/flwr`` + + This will install ``target_project`` to ``./docs/flwr/``. By default, + ``flwr-dir`` is equal to: + + - ``$FLWR_HOME/`` if ``$FLWR_HOME`` is defined + - ``$XDG_DATA_HOME/.flwr/`` if ``$XDG_DATA_HOME`` is defined + - ``$HOME/.flwr/`` in all other cases + """ + if source is None: + source = Path(typer.prompt("Enter the source FAB file")) + + source = source.resolve() + if not source.exists() or not source.is_file(): + typer.secho( + f"❌ The source {source} does not exist or is not a file.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + if source.suffix != ".fab": + typer.secho( + f"❌ The source {source} is not a `.fab` file.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + install_from_fab(source, flwr_dir) + + +def install_from_fab( + fab_file: Path, flwr_dir: Optional[Path], skip_prompt: bool = False +) -> None: + """Install from a FAB file after extracting and validating.""" + with tempfile.TemporaryDirectory() as tmpdir: + with zipfile.ZipFile(fab_file, "r") as zipf: + zipf.extractall(tmpdir) + tmpdir_path = Path(tmpdir) + info_dir = tmpdir_path / ".info" + if not info_dir.exists(): + typer.secho( + "❌ FAB file has incorrect format.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + content_file = info_dir / "CONTENT" + + if not content_file.exists() or not _verify_hashes( + content_file.read_text(), tmpdir_path + ): + typer.secho( + "❌ File hashes couldn't be verified.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + shutil.rmtree(info_dir) + + validate_and_install(tmpdir_path, fab_file.stem, flwr_dir, skip_prompt) + + +def validate_and_install( + project_dir: Path, + fab_name: str, + flwr_dir: Optional[Path], + skip_prompt: bool = False, +) -> None: + """Validate TOML files and install the project to the desired directory.""" + config, _, _ = load_and_validate(project_dir / "pyproject.toml", check_module=False) + + if config is None: + typer.secho( + "❌ Invalid config inside FAB file.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + publisher = config["flower"]["publisher"] + project_name = config["project"]["name"] + version = config["project"]["version"] + + if fab_name != f"{publisher}.{project_name}.{version.replace('.', '-')}": + typer.secho( + "❌ FAB file has incorrect name. The file name must follow the format " + "`...fab`.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + install_dir: Path = ( + ( + Path( + os.getenv( + "FLWR_HOME", + f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", + ) + ) + if not flwr_dir + else flwr_dir + ) + / "apps" + / publisher + / project_name + / version + ) + if install_dir.exists() and not skip_prompt: + if not typer.confirm( + typer.style( + f"\n💬 {project_name} version {version} is already installed, " + "do you want to reinstall it?", + fg=typer.colors.MAGENTA, + bold=True, + ) + ): + return + + install_dir.mkdir(parents=True, exist_ok=True) + + # Move contents from source directory + for item in project_dir.iterdir(): + if item.is_dir(): + shutil.copytree(item, install_dir / item.name, dirs_exist_ok=True) + else: + shutil.copy2(item, install_dir / item.name) + + typer.secho( + f"🎊 Successfully installed {project_name} to {install_dir}.", + fg=typer.colors.GREEN, + bold=True, + ) + + +def _verify_hashes(list_content: str, tmpdir: Path) -> bool: + """Verify file hashes based on the LIST content.""" + for line in list_content.strip().split("\n"): + rel_path, hash_expected, _ = line.split(",") + file_path = tmpdir / rel_path + if not file_path.exists() or get_sha256_hash(file_path) != hash_expected: + return False + return True diff --git a/src/py/flwr/cli/utils.py b/src/py/flwr/cli/utils.py index 6460b770b184..2f5a8831fa7c 100644 --- a/src/py/flwr/cli/utils.py +++ b/src/py/flwr/cli/utils.py @@ -14,7 +14,9 @@ # ============================================================================== """Flower command line interface utils.""" +import hashlib import re +from pathlib import Path from typing import Callable, List, Optional, cast import typer @@ -122,3 +124,15 @@ def sanitize_project_name(name: str) -> str: sanitized_name = sanitized_name[1:] return sanitized_name + + +def get_sha256_hash(file_path: Path) -> str: + """Calculate the SHA-256 hash of a file.""" + sha256 = hashlib.sha256() + with open(file_path, "rb") as f: + while True: + data = f.read(65536) # Read in 64kB blocks + if not data: + break + sha256.update(data) + return sha256.hexdigest() diff --git a/src/py/flwr/common/object_ref.py b/src/py/flwr/common/object_ref.py index 4660f07e24a4..b56c69a4f36b 100644 --- a/src/py/flwr/common/object_ref.py +++ b/src/py/flwr/common/object_ref.py @@ -30,6 +30,7 @@ def validate( module_attribute_str: str, + check_module: bool = True, ) -> Tuple[bool, Optional[str]]: """Validate object reference. @@ -56,15 +57,18 @@ def validate( f"Missing attribute in {module_attribute_str}{OBJECT_REF_HELP_STR}", ) - # Load module - module = find_spec(module_str) - if module and module.origin: - if not _find_attribute_in_module(module.origin, attributes_str): - return ( - False, - f"Unable to find attribute {attributes_str} in module {module_str}" - f"{OBJECT_REF_HELP_STR}", - ) + if check_module: + # Load module + module = find_spec(module_str) + if module and module.origin: + if not _find_attribute_in_module(module.origin, attributes_str): + return ( + False, + f"Unable to find attribute {attributes_str} in module {module_str}" + f"{OBJECT_REF_HELP_STR}", + ) + return (True, None) + else: return (True, None) return ( From e54a33bdbb96dfbec7abcf844aa3157e5480ee9c Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 12 Jun 2024 14:54:02 +0100 Subject: [PATCH 019/595] feat(framework) Add `GetRun` rpc to the Driver servicer (#3578) --- src/proto/flwr/proto/driver.proto | 4 ++ src/proto/flwr/proto/fleet.proto | 10 +-- src/proto/flwr/proto/run.proto | 26 ++++++++ .../grpc_rere_client/client_interceptor.py | 2 +- .../client_interceptor_test.py | 3 +- .../client/grpc_rere_client/connection.py | 3 +- src/py/flwr/client/rest_client/connection.py | 3 +- src/py/flwr/proto/driver_pb2.py | 39 ++++++------ src/py/flwr/proto/driver_pb2_grpc.py | 35 +++++++++++ src/py/flwr/proto/driver_pb2_grpc.pyi | 14 +++++ src/py/flwr/proto/fleet_pb2.py | 61 +++++++++---------- src/py/flwr/proto/fleet_pb2.pyi | 42 ------------- src/py/flwr/proto/fleet_pb2_grpc.py | 13 ++-- src/py/flwr/proto/fleet_pb2_grpc.pyi | 9 +-- src/py/flwr/proto/run_pb2.py | 30 +++++++++ src/py/flwr/proto/run_pb2.pyi | 52 ++++++++++++++++ src/py/flwr/proto/run_pb2_grpc.py | 4 ++ src/py/flwr/proto/run_pb2_grpc.pyi | 4 ++ .../superlink/driver/driver_servicer.py | 7 +++ .../fleet/grpc_rere/fleet_servicer.py | 3 +- .../fleet/grpc_rere/server_interceptor.py | 3 +- .../grpc_rere/server_interceptor_test.py | 3 +- .../fleet/message_handler/message_handler.py | 8 ++- .../superlink/fleet/rest_rere/rest_api.py | 2 +- src/py/flwr_tool/protoc_test.py | 2 +- 25 files changed, 251 insertions(+), 131 deletions(-) create mode 100644 src/proto/flwr/proto/run.proto create mode 100644 src/py/flwr/proto/run_pb2.py create mode 100644 src/py/flwr/proto/run_pb2.pyi create mode 100644 src/py/flwr/proto/run_pb2_grpc.py create mode 100644 src/py/flwr/proto/run_pb2_grpc.pyi diff --git a/src/proto/flwr/proto/driver.proto b/src/proto/flwr/proto/driver.proto index 54e6b6b41b68..edbd5d91bb5b 100644 --- a/src/proto/flwr/proto/driver.proto +++ b/src/proto/flwr/proto/driver.proto @@ -19,6 +19,7 @@ package flwr.proto; import "flwr/proto/node.proto"; import "flwr/proto/task.proto"; +import "flwr/proto/run.proto"; service Driver { // Request run_id @@ -32,6 +33,9 @@ service Driver { // Get task results rpc PullTaskRes(PullTaskResRequest) returns (PullTaskResResponse) {} + + // Get run details + rpc GetRun(GetRunRequest) returns (GetRunResponse) {} } // CreateRun diff --git a/src/proto/flwr/proto/fleet.proto b/src/proto/flwr/proto/fleet.proto index df6b5843023d..24f60bb3d825 100644 --- a/src/proto/flwr/proto/fleet.proto +++ b/src/proto/flwr/proto/fleet.proto @@ -19,6 +19,7 @@ package flwr.proto; import "flwr/proto/node.proto"; import "flwr/proto/task.proto"; +import "flwr/proto/run.proto"; service Fleet { rpc CreateNode(CreateNodeRequest) returns (CreateNodeResponse) {} @@ -70,13 +71,4 @@ message PushTaskResResponse { map results = 2; } -// GetRun messages -message Run { - sint64 run_id = 1; - string fab_id = 2; - string fab_version = 3; -} -message GetRunRequest { sint64 run_id = 1; } -message GetRunResponse { Run run = 1; } - message Reconnect { uint64 reconnect = 1; } diff --git a/src/proto/flwr/proto/run.proto b/src/proto/flwr/proto/run.proto new file mode 100644 index 000000000000..76a7fd91532f --- /dev/null +++ b/src/proto/flwr/proto/run.proto @@ -0,0 +1,26 @@ +// Copyright 2024 Flower Labs GmbH. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================== + +syntax = "proto3"; + +package flwr.proto; + +message Run { + sint64 run_id = 1; + string fab_id = 2; + string fab_version = 3; +} +message GetRunRequest { sint64 run_id = 1; } +message GetRunResponse { Run run = 1; } diff --git a/src/py/flwr/client/grpc_rere_client/client_interceptor.py b/src/py/flwr/client/grpc_rere_client/client_interceptor.py index 8bc55878971d..d2dded8a73d9 100644 --- a/src/py/flwr/client/grpc_rere_client/client_interceptor.py +++ b/src/py/flwr/client/grpc_rere_client/client_interceptor.py @@ -31,11 +31,11 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, DeleteNodeRequest, - GetRunRequest, PingRequest, PullTaskInsRequest, PushTaskResRequest, ) +from flwr.proto.run_pb2 import GetRunRequest # pylint: disable=E0611 _PUBLIC_KEY_HEADER = "public-key" _AUTH_TOKEN_HEADER = "auth-token" diff --git a/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py b/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py index 487361a06026..cc35ffef46db 100644 --- a/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py +++ b/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py @@ -41,13 +41,12 @@ CreateNodeResponse, DeleteNodeRequest, DeleteNodeResponse, - GetRunRequest, - GetRunResponse, PullTaskInsRequest, PullTaskInsResponse, PushTaskResRequest, PushTaskResResponse, ) +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from .client_interceptor import _AUTH_TOKEN_HEADER, _PUBLIC_KEY_HEADER, Request diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index 9579d5830165..52d0cc58b2bb 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -44,8 +44,6 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, DeleteNodeRequest, - GetRunRequest, - GetRunResponse, PingRequest, PingResponse, PullTaskInsRequest, @@ -53,6 +51,7 @@ ) from flwr.proto.fleet_pb2_grpc import FleetStub # pylint: disable=E0611 from flwr.proto.node_pb2 import Node # pylint: disable=E0611 +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611 from .client_interceptor import AuthenticateClientInterceptor diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index da8fbd351ab1..7383eae3d22b 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -46,8 +46,6 @@ CreateNodeResponse, DeleteNodeRequest, DeleteNodeResponse, - GetRunRequest, - GetRunResponse, PingRequest, PingResponse, PullTaskInsRequest, @@ -56,6 +54,7 @@ PushTaskResResponse, ) from flwr.proto.node_pb2 import Node # pylint: disable=E0611 +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611 try: diff --git a/src/py/flwr/proto/driver_pb2.py b/src/py/flwr/proto/driver_pb2.py index b0caae58ff6f..a2458b445563 100644 --- a/src/py/flwr/proto/driver_pb2.py +++ b/src/py/flwr/proto/driver_pb2.py @@ -14,31 +14,32 @@ from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2 from flwr.proto import task_pb2 as flwr_dot_proto_dot_task__pb2 +from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\"7\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\xc1\x02\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\"7\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\x84\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.driver_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_CREATERUNREQUEST']._serialized_start=85 - _globals['_CREATERUNREQUEST']._serialized_end=140 - _globals['_CREATERUNRESPONSE']._serialized_start=142 - _globals['_CREATERUNRESPONSE']._serialized_end=177 - _globals['_GETNODESREQUEST']._serialized_start=179 - _globals['_GETNODESREQUEST']._serialized_end=212 - _globals['_GETNODESRESPONSE']._serialized_start=214 - _globals['_GETNODESRESPONSE']._serialized_end=265 - _globals['_PUSHTASKINSREQUEST']._serialized_start=267 - _globals['_PUSHTASKINSREQUEST']._serialized_end=331 - _globals['_PUSHTASKINSRESPONSE']._serialized_start=333 - _globals['_PUSHTASKINSRESPONSE']._serialized_end=372 - _globals['_PULLTASKRESREQUEST']._serialized_start=374 - _globals['_PULLTASKRESREQUEST']._serialized_end=444 - _globals['_PULLTASKRESRESPONSE']._serialized_start=446 - _globals['_PULLTASKRESRESPONSE']._serialized_end=511 - _globals['_DRIVER']._serialized_start=514 - _globals['_DRIVER']._serialized_end=835 + _globals['_CREATERUNREQUEST']._serialized_start=107 + _globals['_CREATERUNREQUEST']._serialized_end=162 + _globals['_CREATERUNRESPONSE']._serialized_start=164 + _globals['_CREATERUNRESPONSE']._serialized_end=199 + _globals['_GETNODESREQUEST']._serialized_start=201 + _globals['_GETNODESREQUEST']._serialized_end=234 + _globals['_GETNODESRESPONSE']._serialized_start=236 + _globals['_GETNODESRESPONSE']._serialized_end=287 + _globals['_PUSHTASKINSREQUEST']._serialized_start=289 + _globals['_PUSHTASKINSREQUEST']._serialized_end=353 + _globals['_PUSHTASKINSRESPONSE']._serialized_start=355 + _globals['_PUSHTASKINSRESPONSE']._serialized_end=394 + _globals['_PULLTASKRESREQUEST']._serialized_start=396 + _globals['_PULLTASKRESREQUEST']._serialized_end=466 + _globals['_PULLTASKRESRESPONSE']._serialized_start=468 + _globals['_PULLTASKRESRESPONSE']._serialized_end=533 + _globals['_DRIVER']._serialized_start=536 + _globals['_DRIVER']._serialized_end=924 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/driver_pb2_grpc.py b/src/py/flwr/proto/driver_pb2_grpc.py index ac6815023ebd..2cd3ebe62a63 100644 --- a/src/py/flwr/proto/driver_pb2_grpc.py +++ b/src/py/flwr/proto/driver_pb2_grpc.py @@ -3,6 +3,7 @@ import grpc from flwr.proto import driver_pb2 as flwr_dot_proto_dot_driver__pb2 +from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 class DriverStub(object): @@ -34,6 +35,11 @@ def __init__(self, channel): request_serializer=flwr_dot_proto_dot_driver__pb2.PullTaskResRequest.SerializeToString, response_deserializer=flwr_dot_proto_dot_driver__pb2.PullTaskResResponse.FromString, ) + self.GetRun = channel.unary_unary( + '/flwr.proto.Driver/GetRun', + request_serializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString, + ) class DriverServicer(object): @@ -67,6 +73,13 @@ def PullTaskRes(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def GetRun(self, request, context): + """Get run details + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_DriverServicer_to_server(servicer, server): rpc_method_handlers = { @@ -90,6 +103,11 @@ def add_DriverServicer_to_server(servicer, server): request_deserializer=flwr_dot_proto_dot_driver__pb2.PullTaskResRequest.FromString, response_serializer=flwr_dot_proto_dot_driver__pb2.PullTaskResResponse.SerializeToString, ), + 'GetRun': grpc.unary_unary_rpc_method_handler( + servicer.GetRun, + request_deserializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'flwr.proto.Driver', rpc_method_handlers) @@ -167,3 +185,20 @@ def PullTaskRes(request, flwr_dot_proto_dot_driver__pb2.PullTaskResResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetRun(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/flwr.proto.Driver/GetRun', + flwr_dot_proto_dot_run__pb2.GetRunRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/driver_pb2_grpc.pyi b/src/py/flwr/proto/driver_pb2_grpc.pyi index 43cf45f39b25..4ff09db588ca 100644 --- a/src/py/flwr/proto/driver_pb2_grpc.pyi +++ b/src/py/flwr/proto/driver_pb2_grpc.pyi @@ -4,6 +4,7 @@ isort:skip_file """ import abc import flwr.proto.driver_pb2 +import flwr.proto.run_pb2 import grpc class DriverStub: @@ -28,6 +29,11 @@ class DriverStub: flwr.proto.driver_pb2.PullTaskResResponse] """Get task results""" + GetRun: grpc.UnaryUnaryMultiCallable[ + flwr.proto.run_pb2.GetRunRequest, + flwr.proto.run_pb2.GetRunResponse] + """Get run details""" + class DriverServicer(metaclass=abc.ABCMeta): @abc.abstractmethod @@ -62,5 +68,13 @@ class DriverServicer(metaclass=abc.ABCMeta): """Get task results""" pass + @abc.abstractmethod + def GetRun(self, + request: flwr.proto.run_pb2.GetRunRequest, + context: grpc.ServicerContext, + ) -> flwr.proto.run_pb2.GetRunResponse: + """Get run details""" + pass + def add_DriverServicer_to_server(servicer: DriverServicer, server: grpc.Server) -> None: ... diff --git a/src/py/flwr/proto/fleet_pb2.py b/src/py/flwr/proto/fleet_pb2.py index 42f3292d910d..9763b71fed2f 100644 --- a/src/py/flwr/proto/fleet_pb2.py +++ b/src/py/flwr/proto/fleet_pb2.py @@ -14,9 +14,10 @@ from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2 from flwr.proto import task_pb2 as flwr_dot_proto_dot_task__pb2 +from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x66lwr/proto/fleet.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\"*\n\x11\x43reateNodeRequest\x12\x15\n\rping_interval\x18\x01 \x01(\x01\"4\n\x12\x43reateNodeResponse\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"3\n\x11\x44\x65leteNodeRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"\x14\n\x12\x44\x65leteNodeResponse\"D\n\x0bPingRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x15\n\rping_interval\x18\x02 \x01(\x01\"\x1f\n\x0cPingResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"F\n\x12PullTaskInsRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"k\n\x13PullTaskInsResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12*\n\rtask_ins_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"@\n\x12PushTaskResRequest\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes\"\xae\x01\n\x13PushTaskResResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12=\n\x07results\x18\x02 \x03(\x0b\x32,.flwr.proto.PushTaskResResponse.ResultsEntry\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\r:\x02\x38\x01\":\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Run\"\x1e\n\tReconnect\x12\x11\n\treconnect\x18\x01 \x01(\x04\x32\xc9\x03\n\x05\x46leet\x12M\n\nCreateNode\x12\x1d.flwr.proto.CreateNodeRequest\x1a\x1e.flwr.proto.CreateNodeResponse\"\x00\x12M\n\nDeleteNode\x12\x1d.flwr.proto.DeleteNodeRequest\x1a\x1e.flwr.proto.DeleteNodeResponse\"\x00\x12;\n\x04Ping\x12\x17.flwr.proto.PingRequest\x1a\x18.flwr.proto.PingResponse\"\x00\x12P\n\x0bPullTaskIns\x12\x1e.flwr.proto.PullTaskInsRequest\x1a\x1f.flwr.proto.PullTaskInsResponse\"\x00\x12P\n\x0bPushTaskRes\x12\x1e.flwr.proto.PushTaskResRequest\x1a\x1f.flwr.proto.PushTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x16\x66lwr/proto/fleet.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\"*\n\x11\x43reateNodeRequest\x12\x15\n\rping_interval\x18\x01 \x01(\x01\"4\n\x12\x43reateNodeResponse\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"3\n\x11\x44\x65leteNodeRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\"\x14\n\x12\x44\x65leteNodeResponse\"D\n\x0bPingRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x15\n\rping_interval\x18\x02 \x01(\x01\"\x1f\n\x0cPingResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\"F\n\x12PullTaskInsRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"k\n\x13PullTaskInsResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12*\n\rtask_ins_list\x18\x02 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"@\n\x12PushTaskResRequest\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes\"\xae\x01\n\x13PushTaskResResponse\x12(\n\treconnect\x18\x01 \x01(\x0b\x32\x15.flwr.proto.Reconnect\x12=\n\x07results\x18\x02 \x03(\x0b\x32,.flwr.proto.PushTaskResResponse.ResultsEntry\x1a.\n\x0cResultsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\r:\x02\x38\x01\"\x1e\n\tReconnect\x12\x11\n\treconnect\x18\x01 \x01(\x04\x32\xc9\x03\n\x05\x46leet\x12M\n\nCreateNode\x12\x1d.flwr.proto.CreateNodeRequest\x1a\x1e.flwr.proto.CreateNodeResponse\"\x00\x12M\n\nDeleteNode\x12\x1d.flwr.proto.DeleteNodeRequest\x1a\x1e.flwr.proto.DeleteNodeResponse\"\x00\x12;\n\x04Ping\x12\x17.flwr.proto.PingRequest\x1a\x18.flwr.proto.PingResponse\"\x00\x12P\n\x0bPullTaskIns\x12\x1e.flwr.proto.PullTaskInsRequest\x1a\x1f.flwr.proto.PullTaskInsResponse\"\x00\x12P\n\x0bPushTaskRes\x12\x1e.flwr.proto.PushTaskResRequest\x1a\x1f.flwr.proto.PushTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -25,36 +26,30 @@ DESCRIPTOR._options = None _globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._options = None _globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_options = b'8\001' - _globals['_CREATENODEREQUEST']._serialized_start=84 - _globals['_CREATENODEREQUEST']._serialized_end=126 - _globals['_CREATENODERESPONSE']._serialized_start=128 - _globals['_CREATENODERESPONSE']._serialized_end=180 - _globals['_DELETENODEREQUEST']._serialized_start=182 - _globals['_DELETENODEREQUEST']._serialized_end=233 - _globals['_DELETENODERESPONSE']._serialized_start=235 - _globals['_DELETENODERESPONSE']._serialized_end=255 - _globals['_PINGREQUEST']._serialized_start=257 - _globals['_PINGREQUEST']._serialized_end=325 - _globals['_PINGRESPONSE']._serialized_start=327 - _globals['_PINGRESPONSE']._serialized_end=358 - _globals['_PULLTASKINSREQUEST']._serialized_start=360 - _globals['_PULLTASKINSREQUEST']._serialized_end=430 - _globals['_PULLTASKINSRESPONSE']._serialized_start=432 - _globals['_PULLTASKINSRESPONSE']._serialized_end=539 - _globals['_PUSHTASKRESREQUEST']._serialized_start=541 - _globals['_PUSHTASKRESREQUEST']._serialized_end=605 - _globals['_PUSHTASKRESRESPONSE']._serialized_start=608 - _globals['_PUSHTASKRESRESPONSE']._serialized_end=782 - _globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_start=736 - _globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_end=782 - _globals['_RUN']._serialized_start=784 - _globals['_RUN']._serialized_end=842 - _globals['_GETRUNREQUEST']._serialized_start=844 - _globals['_GETRUNREQUEST']._serialized_end=875 - _globals['_GETRUNRESPONSE']._serialized_start=877 - _globals['_GETRUNRESPONSE']._serialized_end=923 - _globals['_RECONNECT']._serialized_start=925 - _globals['_RECONNECT']._serialized_end=955 - _globals['_FLEET']._serialized_start=958 - _globals['_FLEET']._serialized_end=1415 + _globals['_CREATENODEREQUEST']._serialized_start=106 + _globals['_CREATENODEREQUEST']._serialized_end=148 + _globals['_CREATENODERESPONSE']._serialized_start=150 + _globals['_CREATENODERESPONSE']._serialized_end=202 + _globals['_DELETENODEREQUEST']._serialized_start=204 + _globals['_DELETENODEREQUEST']._serialized_end=255 + _globals['_DELETENODERESPONSE']._serialized_start=257 + _globals['_DELETENODERESPONSE']._serialized_end=277 + _globals['_PINGREQUEST']._serialized_start=279 + _globals['_PINGREQUEST']._serialized_end=347 + _globals['_PINGRESPONSE']._serialized_start=349 + _globals['_PINGRESPONSE']._serialized_end=380 + _globals['_PULLTASKINSREQUEST']._serialized_start=382 + _globals['_PULLTASKINSREQUEST']._serialized_end=452 + _globals['_PULLTASKINSRESPONSE']._serialized_start=454 + _globals['_PULLTASKINSRESPONSE']._serialized_end=561 + _globals['_PUSHTASKRESREQUEST']._serialized_start=563 + _globals['_PUSHTASKRESREQUEST']._serialized_end=627 + _globals['_PUSHTASKRESRESPONSE']._serialized_start=630 + _globals['_PUSHTASKRESRESPONSE']._serialized_end=804 + _globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_start=758 + _globals['_PUSHTASKRESRESPONSE_RESULTSENTRY']._serialized_end=804 + _globals['_RECONNECT']._serialized_start=806 + _globals['_RECONNECT']._serialized_end=836 + _globals['_FLEET']._serialized_start=839 + _globals['_FLEET']._serialized_end=1296 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/fleet_pb2.pyi b/src/py/flwr/proto/fleet_pb2.pyi index a6f38b703e76..5989f45c5c60 100644 --- a/src/py/flwr/proto/fleet_pb2.pyi +++ b/src/py/flwr/proto/fleet_pb2.pyi @@ -164,48 +164,6 @@ class PushTaskResResponse(google.protobuf.message.Message): def ClearField(self, field_name: typing_extensions.Literal["reconnect",b"reconnect","results",b"results"]) -> None: ... global___PushTaskResResponse = PushTaskResResponse -class Run(google.protobuf.message.Message): - """GetRun messages""" - DESCRIPTOR: google.protobuf.descriptor.Descriptor - RUN_ID_FIELD_NUMBER: builtins.int - FAB_ID_FIELD_NUMBER: builtins.int - FAB_VERSION_FIELD_NUMBER: builtins.int - run_id: builtins.int - fab_id: typing.Text - fab_version: typing.Text - def __init__(self, - *, - run_id: builtins.int = ..., - fab_id: typing.Text = ..., - fab_version: typing.Text = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","run_id",b"run_id"]) -> None: ... -global___Run = Run - -class GetRunRequest(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - RUN_ID_FIELD_NUMBER: builtins.int - run_id: builtins.int - def __init__(self, - *, - run_id: builtins.int = ..., - ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ... -global___GetRunRequest = GetRunRequest - -class GetRunResponse(google.protobuf.message.Message): - DESCRIPTOR: google.protobuf.descriptor.Descriptor - RUN_FIELD_NUMBER: builtins.int - @property - def run(self) -> global___Run: ... - def __init__(self, - *, - run: typing.Optional[global___Run] = ..., - ) -> None: ... - def HasField(self, field_name: typing_extensions.Literal["run",b"run"]) -> builtins.bool: ... - def ClearField(self, field_name: typing_extensions.Literal["run",b"run"]) -> None: ... -global___GetRunResponse = GetRunResponse - class Reconnect(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor RECONNECT_FIELD_NUMBER: builtins.int diff --git a/src/py/flwr/proto/fleet_pb2_grpc.py b/src/py/flwr/proto/fleet_pb2_grpc.py index 16757eaed381..e0b0fbc50460 100644 --- a/src/py/flwr/proto/fleet_pb2_grpc.py +++ b/src/py/flwr/proto/fleet_pb2_grpc.py @@ -3,6 +3,7 @@ import grpc from flwr.proto import fleet_pb2 as flwr_dot_proto_dot_fleet__pb2 +from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 class FleetStub(object): @@ -41,8 +42,8 @@ def __init__(self, channel): ) self.GetRun = channel.unary_unary( '/flwr.proto.Fleet/GetRun', - request_serializer=flwr_dot_proto_dot_fleet__pb2.GetRunRequest.SerializeToString, - response_deserializer=flwr_dot_proto_dot_fleet__pb2.GetRunResponse.FromString, + request_serializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString, ) @@ -121,8 +122,8 @@ def add_FleetServicer_to_server(servicer, server): ), 'GetRun': grpc.unary_unary_rpc_method_handler( servicer.GetRun, - request_deserializer=flwr_dot_proto_dot_fleet__pb2.GetRunRequest.FromString, - response_serializer=flwr_dot_proto_dot_fleet__pb2.GetRunResponse.SerializeToString, + request_deserializer=flwr_dot_proto_dot_run__pb2.GetRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.GetRunResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -231,7 +232,7 @@ def GetRun(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/flwr.proto.Fleet/GetRun', - flwr_dot_proto_dot_fleet__pb2.GetRunRequest.SerializeToString, - flwr_dot_proto_dot_fleet__pb2.GetRunResponse.FromString, + flwr_dot_proto_dot_run__pb2.GetRunRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.GetRunResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/fleet_pb2_grpc.pyi b/src/py/flwr/proto/fleet_pb2_grpc.pyi index f275cd149d69..1c0ab862d45c 100644 --- a/src/py/flwr/proto/fleet_pb2_grpc.pyi +++ b/src/py/flwr/proto/fleet_pb2_grpc.pyi @@ -4,6 +4,7 @@ isort:skip_file """ import abc import flwr.proto.fleet_pb2 +import flwr.proto.run_pb2 import grpc class FleetStub: @@ -37,8 +38,8 @@ class FleetStub: """ GetRun: grpc.UnaryUnaryMultiCallable[ - flwr.proto.fleet_pb2.GetRunRequest, - flwr.proto.fleet_pb2.GetRunResponse] + flwr.proto.run_pb2.GetRunRequest, + flwr.proto.run_pb2.GetRunResponse] class FleetServicer(metaclass=abc.ABCMeta): @@ -84,9 +85,9 @@ class FleetServicer(metaclass=abc.ABCMeta): @abc.abstractmethod def GetRun(self, - request: flwr.proto.fleet_pb2.GetRunRequest, + request: flwr.proto.run_pb2.GetRunRequest, context: grpc.ServicerContext, - ) -> flwr.proto.fleet_pb2.GetRunResponse: ... + ) -> flwr.proto.run_pb2.GetRunResponse: ... def add_FleetServicer_to_server(servicer: FleetServicer, server: grpc.Server) -> None: ... diff --git a/src/py/flwr/proto/run_pb2.py b/src/py/flwr/proto/run_pb2.py new file mode 100644 index 000000000000..13f06e7169aa --- /dev/null +++ b/src/py/flwr/proto/run_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: flwr/proto/run.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\":\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.run_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_RUN']._serialized_start=36 + _globals['_RUN']._serialized_end=94 + _globals['_GETRUNREQUEST']._serialized_start=96 + _globals['_GETRUNREQUEST']._serialized_end=127 + _globals['_GETRUNRESPONSE']._serialized_start=129 + _globals['_GETRUNRESPONSE']._serialized_end=175 +# @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/run_pb2.pyi b/src/py/flwr/proto/run_pb2.pyi new file mode 100644 index 000000000000..401d27855a41 --- /dev/null +++ b/src/py/flwr/proto/run_pb2.pyi @@ -0,0 +1,52 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import builtins +import google.protobuf.descriptor +import google.protobuf.message +import typing +import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class Run(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_ID_FIELD_NUMBER: builtins.int + FAB_ID_FIELD_NUMBER: builtins.int + FAB_VERSION_FIELD_NUMBER: builtins.int + run_id: builtins.int + fab_id: typing.Text + fab_version: typing.Text + def __init__(self, + *, + run_id: builtins.int = ..., + fab_id: typing.Text = ..., + fab_version: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","run_id",b"run_id"]) -> None: ... +global___Run = Run + +class GetRunRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_ID_FIELD_NUMBER: builtins.int + run_id: builtins.int + def __init__(self, + *, + run_id: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ... +global___GetRunRequest = GetRunRequest + +class GetRunResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_FIELD_NUMBER: builtins.int + @property + def run(self) -> global___Run: ... + def __init__(self, + *, + run: typing.Optional[global___Run] = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["run",b"run"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["run",b"run"]) -> None: ... +global___GetRunResponse = GetRunResponse diff --git a/src/py/flwr/proto/run_pb2_grpc.py b/src/py/flwr/proto/run_pb2_grpc.py new file mode 100644 index 000000000000..2daafffebfc8 --- /dev/null +++ b/src/py/flwr/proto/run_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/py/flwr/proto/run_pb2_grpc.pyi b/src/py/flwr/proto/run_pb2_grpc.pyi new file mode 100644 index 000000000000..f3a5a087ef5d --- /dev/null +++ b/src/py/flwr/proto/run_pb2_grpc.pyi @@ -0,0 +1,4 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" diff --git a/src/py/flwr/server/superlink/driver/driver_servicer.py b/src/py/flwr/server/superlink/driver/driver_servicer.py index ce2d9d68d8ca..e808616af778 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer.py @@ -35,6 +35,7 @@ PushTaskInsResponse, ) from flwr.proto.node_pb2 import Node # pylint: disable=E0611 +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from flwr.proto.task_pb2 import TaskRes # pylint: disable=E0611 from flwr.server.superlink.state import State, StateFactory from flwr.server.utils.validator import validate_task_ins_or_res @@ -129,6 +130,12 @@ def on_rpc_done() -> None: context.set_code(grpc.StatusCode.OK) return PullTaskResResponse(task_res_list=task_res_list) + def GetRun( + self, request: GetRunRequest, context: grpc.ServicerContext + ) -> GetRunResponse: + """Get run information.""" + raise NotImplementedError + def _raise_if(validation_error: bool, detail: str) -> None: if validation_error: diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py b/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py index 03a2ec064213..13e024eb31e4 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py @@ -26,8 +26,6 @@ CreateNodeResponse, DeleteNodeRequest, DeleteNodeResponse, - GetRunRequest, - GetRunResponse, PingRequest, PingResponse, PullTaskInsRequest, @@ -35,6 +33,7 @@ PushTaskResRequest, PushTaskResResponse, ) +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from flwr.server.superlink.fleet.message_handler import message_handler from flwr.server.superlink.state import StateFactory diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py index 6a302679a235..21e9c44907cd 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor.py @@ -34,8 +34,6 @@ CreateNodeResponse, DeleteNodeRequest, DeleteNodeResponse, - GetRunRequest, - GetRunResponse, PingRequest, PingResponse, PullTaskInsRequest, @@ -44,6 +42,7 @@ PushTaskResResponse, ) from flwr.proto.node_pb2 import Node # pylint: disable=E0611 +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from flwr.server.superlink.state import State _PUBLIC_KEY_HEADER = "public-key" diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py index c4c71e5a8188..01499102b7d8 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py @@ -32,8 +32,6 @@ CreateNodeResponse, DeleteNodeRequest, DeleteNodeResponse, - GetRunRequest, - GetRunResponse, PingRequest, PingResponse, PullTaskInsRequest, @@ -42,6 +40,7 @@ PushTaskResResponse, ) from flwr.proto.node_pb2 import Node # pylint: disable=E0611 +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from flwr.proto.task_pb2 import Task, TaskRes # pylint: disable=E0611 from flwr.server.app import ADDRESS_FLEET_API_GRPC_RERE, _run_fleet_api_grpc_rere from flwr.server.superlink.state.state_factory import StateFactory diff --git a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py index 83b005a4cb8e..4c796502436b 100644 --- a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py +++ b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py @@ -24,8 +24,6 @@ CreateNodeResponse, DeleteNodeRequest, DeleteNodeResponse, - GetRunRequest, - GetRunResponse, PingRequest, PingResponse, PullTaskInsRequest, @@ -33,9 +31,13 @@ PushTaskResRequest, PushTaskResResponse, Reconnect, - Run, ) from flwr.proto.node_pb2 import Node # pylint: disable=E0611 +from flwr.proto.run_pb2 import ( # pylint: disable=E0611 + GetRunRequest, + GetRunResponse, + Run, +) from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 from flwr.server.superlink.state import State diff --git a/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py b/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py index 8ac7c6cfc613..c7ff496d39bf 100644 --- a/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py +++ b/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py @@ -21,11 +21,11 @@ from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, DeleteNodeRequest, - GetRunRequest, PingRequest, PullTaskInsRequest, PushTaskResRequest, ) +from flwr.proto.run_pb2 import GetRunRequest # pylint: disable=E0611 from flwr.server.superlink.fleet.message_handler import message_handler from flwr.server.superlink.state import State diff --git a/src/py/flwr_tool/protoc_test.py b/src/py/flwr_tool/protoc_test.py index 8dcf4c6474d6..6aec4251c384 100644 --- a/src/py/flwr_tool/protoc_test.py +++ b/src/py/flwr_tool/protoc_test.py @@ -28,4 +28,4 @@ def test_directories() -> None: def test_proto_file_count() -> None: """Test if the correct number of proto files were captured by the glob.""" - assert len(PROTO_FILES) == 8 + assert len(PROTO_FILES) == 9 From 742e49886753b613c577b2117e4acab7be9da9f3 Mon Sep 17 00:00:00 2001 From: Daniel Nata Nugraha Date: Wed, 12 Jun 2024 16:04:47 +0200 Subject: [PATCH 020/595] break(framework) Remove `flower-driver-api` and `flower-fleet-api` (#3418) Co-authored-by: jafermarq Co-authored-by: Daniel J. Beutel --- pyproject.toml | 2 - src/py/flwr/server/__init__.py | 4 - src/py/flwr/server/app.py | 169 +-------------------------------- 3 files changed, 1 insertion(+), 174 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a1a21a6b94cf..62e17aeb5281 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,8 +53,6 @@ exclude = [ [tool.poetry.scripts] flwr = "flwr.cli.app:app" -flower-driver-api = "flwr.server:run_driver_api" -flower-fleet-api = "flwr.server:run_fleet_api" flower-superlink = "flwr.server:run_superlink" flower-supernode = "flwr.client:run_supernode" flower-client-app = "flwr.client:run_client_app" diff --git a/src/py/flwr/server/__init__.py b/src/py/flwr/server/__init__.py index 875f66c43d03..19c6034bcaa1 100644 --- a/src/py/flwr/server/__init__.py +++ b/src/py/flwr/server/__init__.py @@ -17,8 +17,6 @@ from . import strategy from . import workflow as workflow -from .app import run_driver_api as run_driver_api -from .app import run_fleet_api as run_fleet_api from .app import run_superlink as run_superlink from .app import start_server as start_server from .client_manager import ClientManager as ClientManager @@ -36,8 +34,6 @@ "Driver", "History", "LegacyContext", - "run_driver_api", - "run_fleet_api", "run_server_app", "run_superlink", "Server", diff --git a/src/py/flwr/server/app.py b/src/py/flwr/server/app.py index c050c983134b..cbb18b602fcd 100644 --- a/src/py/flwr/server/app.py +++ b/src/py/flwr/server/app.py @@ -40,7 +40,7 @@ TRANSPORT_TYPE_REST, ) from flwr.common.exit_handlers import register_exit_handlers -from flwr.common.logger import log, warn_deprecated_feature +from flwr.common.logger import log from flwr.common.secure_aggregation.crypto.symmetric_encryption import ( private_key_to_bytes, public_key_to_bytes, @@ -190,139 +190,6 @@ def start_server( # pylint: disable=too-many-arguments,too-many-locals return hist -def run_driver_api() -> None: - """Run Flower server (Driver API).""" - log(INFO, "Starting Flower server (Driver API)") - # Running `flower-driver-api` is deprecated - warn_deprecated_feature("flower-driver-api") - log(WARN, "Use `flower-superlink` instead") - event(EventType.RUN_DRIVER_API_ENTER) - args = _parse_args_run_driver_api().parse_args() - - # Parse IP address - parsed_address = parse_address(args.driver_api_address) - if not parsed_address: - sys.exit(f"Driver IP address ({args.driver_api_address}) cannot be parsed.") - host, port, is_v6 = parsed_address - address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}" - - # Obtain certificates - certificates = _try_obtain_certificates(args) - - # Initialize StateFactory - state_factory = StateFactory(args.database) - - # Start server - grpc_server: grpc.Server = run_driver_api_grpc( - address=address, - state_factory=state_factory, - certificates=certificates, - ) - - # Graceful shutdown - register_exit_handlers( - event_type=EventType.RUN_DRIVER_API_LEAVE, - grpc_servers=[grpc_server], - bckg_threads=[], - ) - - # Block - grpc_server.wait_for_termination() - - -# pylint: disable=too-many-locals -def run_fleet_api() -> None: - """Run Flower server (Fleet API).""" - log(INFO, "Starting Flower server (Fleet API)") - # Running `flower-fleet-api` is deprecated - warn_deprecated_feature("flower-fleet-api") - log(WARN, "Use `flower-superlink` instead") - event(EventType.RUN_FLEET_API_ENTER) - args = _parse_args_run_fleet_api().parse_args() - - # Obtain certificates - certificates = _try_obtain_certificates(args) - - # Initialize StateFactory - state_factory = StateFactory(args.database) - - grpc_servers = [] - bckg_threads = [] - - address_arg = args.fleet_api_address - parsed_address = parse_address(address_arg) - if not parsed_address: - sys.exit(f"Fleet IP address ({address_arg}) cannot be parsed.") - host, port, is_v6 = parsed_address - address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}" - - num_workers = args.fleet_api_num_workers - if num_workers != 1: - log( - WARN, - "The Fleet API currently supports only 1 worker. " - "You have specified %d workers. " - "Support for multiple workers will be added in future releases. " - "Proceeding with a single worker.", - args.fleet_api_num_workers, - ) - num_workers = 1 - - # Start Fleet API - if args.fleet_api_type == TRANSPORT_TYPE_REST: - if ( - importlib.util.find_spec("requests") - and importlib.util.find_spec("starlette") - and importlib.util.find_spec("uvicorn") - ) is None: - sys.exit(MISSING_EXTRA_REST) - - _, ssl_certfile, ssl_keyfile = ( - certificates if certificates is not None else (None, None, None) - ) - fleet_thread = threading.Thread( - target=_run_fleet_api_rest, - args=( - host, - port, - ssl_keyfile, - ssl_certfile, - state_factory, - num_workers, - ), - ) - fleet_thread.start() - bckg_threads.append(fleet_thread) - elif args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE: - address_arg = args.grpc_rere_fleet_api_address - parsed_address = parse_address(address_arg) - if not parsed_address: - sys.exit(f"Fleet IP address ({address_arg}) cannot be parsed.") - host, port, is_v6 = parsed_address - address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}" - fleet_server = _run_fleet_api_grpc_rere( - address=address, - state_factory=state_factory, - certificates=certificates, - ) - grpc_servers.append(fleet_server) - else: - raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}") - - # Graceful shutdown - register_exit_handlers( - event_type=EventType.RUN_FLEET_API_LEAVE, - grpc_servers=grpc_servers, - bckg_threads=bckg_threads, - ) - - # Block - if len(grpc_servers) > 0: - grpc_servers[0].wait_for_termination() - elif len(bckg_threads) > 0: - bckg_threads[0].join() - - # pylint: disable=too-many-branches, too-many-locals, too-many-statements def run_superlink() -> None: """Run Flower SuperLink (Driver API and Fleet API).""" @@ -661,40 +528,6 @@ def _run_fleet_api_rest( ) -def _parse_args_run_driver_api() -> argparse.ArgumentParser: - """Parse command line arguments for Driver API.""" - parser = argparse.ArgumentParser( - description="Start a Flower Driver API server. " - "This server will be responsible for " - "receiving TaskIns from the Driver script and " - "sending them to the Fleet API. Once the client nodes " - "are done, they will send the TaskRes back to this Driver API server (through" - " the Fleet API) which will then send them back to the Driver script.", - ) - - _add_args_common(parser=parser) - _add_args_driver_api(parser=parser) - - return parser - - -def _parse_args_run_fleet_api() -> argparse.ArgumentParser: - """Parse command line arguments for Fleet API.""" - parser = argparse.ArgumentParser( - description="Start a Flower Fleet API server." - "This server will be responsible for " - "sending TaskIns (received from the Driver API) to the client nodes " - "and of receiving TaskRes sent back from those same client nodes once " - "they are done. Then, this Fleet API server can send those " - "TaskRes back to the Driver API.", - ) - - _add_args_common(parser=parser) - _add_args_fleet_api(parser=parser) - - return parser - - def _parse_args_run_superlink() -> argparse.ArgumentParser: """Parse command line arguments for both Driver API and Fleet API.""" parser = argparse.ArgumentParser( From 910c1f1784fac4497d055ee86c786bc6b2af7efb Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 12:52:43 +0200 Subject: [PATCH 021/595] feat(framework:skip) Allow `flwr install` to work with `bytes` (#3546) --- src/py/flwr/cli/install.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index e6ce9fe1a69a..d6d2ee55a47a 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -19,8 +19,9 @@ import shutil import tempfile import zipfile +from io import BytesIO from pathlib import Path -from typing import Optional +from typing import IO, Optional, Union import typer from typing_extensions import Annotated @@ -80,11 +81,24 @@ def install( def install_from_fab( - fab_file: Path, flwr_dir: Optional[Path], skip_prompt: bool = False + fab_file: Union[Path, bytes], + flwr_dir: Optional[Path], + skip_prompt: bool = False, ) -> None: """Install from a FAB file after extracting and validating.""" + fab_file_archive: Union[Path, IO[bytes]] + fab_name: Optional[str] + if isinstance(fab_file, bytes): + fab_file_archive = BytesIO(fab_file) + fab_name = None + elif isinstance(fab_file, Path): + fab_file_archive = fab_file + fab_name = fab_file.stem + else: + raise ValueError("fab_file must be either a Path or bytes") + with tempfile.TemporaryDirectory() as tmpdir: - with zipfile.ZipFile(fab_file, "r") as zipf: + with zipfile.ZipFile(fab_file_archive, "r") as zipf: zipf.extractall(tmpdir) tmpdir_path = Path(tmpdir) info_dir = tmpdir_path / ".info" @@ -110,12 +124,12 @@ def install_from_fab( shutil.rmtree(info_dir) - validate_and_install(tmpdir_path, fab_file.stem, flwr_dir, skip_prompt) + validate_and_install(tmpdir_path, fab_name, flwr_dir, skip_prompt) def validate_and_install( project_dir: Path, - fab_name: str, + fab_name: Optional[str], flwr_dir: Optional[Path], skip_prompt: bool = False, ) -> None: @@ -134,7 +148,10 @@ def validate_and_install( project_name = config["project"]["name"] version = config["project"]["version"] - if fab_name != f"{publisher}.{project_name}.{version.replace('.', '-')}": + if ( + fab_name + and fab_name != f"{publisher}.{project_name}.{version.replace('.', '-')}" + ): typer.secho( "❌ FAB file has incorrect name. The file name must follow the format " "`...fab`.", From 80191fe736b85ba6175edd1382b60bd2d17a1ec8 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Thu, 13 Jun 2024 13:54:54 +0200 Subject: [PATCH 022/595] docs(*:skip) Fix chown command (#3595) --- doc/source/how-to-run-flower-using-docker.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/how-to-run-flower-using-docker.rst b/doc/source/how-to-run-flower-using-docker.rst index cffcd18129b5..54079968d417 100644 --- a/doc/source/how-to-run-flower-using-docker.rst +++ b/doc/source/how-to-run-flower-using-docker.rst @@ -86,7 +86,7 @@ container. Furthermore, we use the flag ``--database`` to specify the name of th .. code-block:: bash $ mkdir state - $ sudo chmod -R 49999:49999 state + $ sudo chown -R 49999:49999 state $ docker run --rm \ -p 9091:9091 -p 9092:9092 --volume ./state/:/app/state flwr/superlink:1.8.0 \ --insecure \ From 8fc4287c27be3435e02e797617c857b46c00379d Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 13 Jun 2024 16:32:23 +0100 Subject: [PATCH 023/595] feat(framework) Implement `run_supernode` (#3353) --- src/py/flwr/client/app.py | 28 +++-- src/py/flwr/client/supernode/app.py | 184 ++++++++++++++++++++++------ 2 files changed, 164 insertions(+), 48 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 7294600288aa..cdb7b25cbf6b 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -19,7 +19,7 @@ import time from dataclasses import dataclass from logging import DEBUG, ERROR, INFO, WARN -from typing import Callable, ContextManager, Optional, Tuple, Type, Union +from typing import Callable, ContextManager, Dict, Optional, Tuple, Type, Union from cryptography.hazmat.primitives.asymmetric import ec from grpc import RpcError @@ -177,7 +177,7 @@ class `flwr.client.Client` (default: None) def _start_client_internal( *, server_address: str, - load_client_app_fn: Optional[Callable[[], ClientApp]] = None, + load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None, client_fn: Optional[ClientFn] = None, client: Optional[Client] = None, grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, @@ -252,7 +252,7 @@ def single_client_factory( client_fn = single_client_factory - def _load_client_app() -> ClientApp: + def _load_client_app(_1: str, _2: str) -> ClientApp: return ClientApp(client_fn=client_fn) load_client_app_fn = _load_client_app @@ -308,6 +308,8 @@ def _on_backoff(retry_state: RetryState) -> None: ) node_state = NodeState() + # run_id -> (fab_id, fab_version) + run_info: Dict[int, Tuple[str, str]] = {} while not app_state_tracker.interrupt: sleep_duration: int = 0 @@ -319,7 +321,6 @@ def _on_backoff(retry_state: RetryState) -> None: root_certificates, authentication_keys, ) as conn: - # pylint: disable-next=W0612 receive, send, create_node, delete_node, get_run = conn # Register node @@ -356,13 +357,20 @@ def _on_backoff(retry_state: RetryState) -> None: send(out_message) break + # Get run info + run_id = message.metadata.run_id + if run_id not in run_info: + if get_run is not None: + run_info[run_id] = get_run(run_id) + # If get_run is None, i.e., in grpc-bidi mode + else: + run_info[run_id] = ("", "") + # Register context for this run - node_state.register_context(run_id=message.metadata.run_id) + node_state.register_context(run_id=run_id) # Retrieve context for this run - context = node_state.retrieve_context( - run_id=message.metadata.run_id - ) + context = node_state.retrieve_context(run_id=run_id) # Create an error reply message that will never be used to prevent # the used-before-assignment linting error @@ -373,7 +381,7 @@ def _on_backoff(retry_state: RetryState) -> None: # Handle app loading and task message try: # Load ClientApp instance - client_app: ClientApp = load_client_app_fn() + client_app: ClientApp = load_client_app_fn(*run_info[run_id]) # Execute ClientApp reply_message = client_app(message=message, context=context) @@ -411,7 +419,7 @@ def _on_backoff(retry_state: RetryState) -> None: else: # No exception, update node state node_state.update_context( - run_id=message.metadata.run_id, + run_id=run_id, context=context, ) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 336cd818d718..9ec9695fb51e 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -15,11 +15,13 @@ """Flower SuperNode.""" import argparse +import os import sys from logging import DEBUG, INFO, WARN from pathlib import Path from typing import Callable, Optional, Tuple +import tomli from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import ( @@ -27,6 +29,7 @@ load_ssh_public_key, ) +from flwr.cli.config_utils import validate_fields from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event from flwr.common.exit_handlers import register_exit_handlers @@ -44,11 +47,23 @@ def run_supernode() -> None: event(EventType.RUN_SUPERNODE_ENTER) - _ = _parse_args_run_supernode().parse_args() + args = _parse_args_run_supernode().parse_args() - log( - DEBUG, - "Flower SuperNode starting...", + _warn_deprecated_server_arg(args) + + root_certificates = _get_certificates(args) + load_fn = _get_load_client_app_fn(args, multi_app=True) + authentication_keys = _try_setup_client_authentication(args) + + _start_client_internal( + server_address=args.server, + load_client_app_fn=load_fn, + transport="rest" if args.rest else "grpc-rere", + root_certificates=root_certificates, + insecure=args.insecure, + authentication_keys=authentication_keys, + max_retries=args.max_retries, + max_wait_time=args.max_wait_time, ) # Graceful shutdown @@ -65,6 +80,27 @@ def run_client_app() -> None: args = _parse_args_run_client_app().parse_args() + _warn_deprecated_server_arg(args) + + root_certificates = _get_certificates(args) + load_fn = _get_load_client_app_fn(args, multi_app=False) + authentication_keys = _try_setup_client_authentication(args) + + _start_client_internal( + server_address=args.superlink, + load_client_app_fn=load_fn, + transport="rest" if args.rest else "grpc-rere", + root_certificates=root_certificates, + insecure=args.insecure, + authentication_keys=authentication_keys, + max_retries=args.max_retries, + max_wait_time=args.max_wait_time, + ) + register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE) + + +def _warn_deprecated_server_arg(args: argparse.Namespace) -> None: + """Warn about the deprecated argument `--server`.""" if args.server != ADDRESS_FLEET_API_GRPC_RERE: warn = "Passing flag --server is deprecated. Use --superlink instead." warn_deprecated_feature(warn) @@ -82,27 +118,6 @@ def run_client_app() -> None: else: args.superlink = args.server - root_certificates = _get_certificates(args) - log( - DEBUG, - "Flower will load ClientApp `%s`", - getattr(args, "client-app"), - ) - load_fn = _get_load_client_app_fn(args) - authentication_keys = _try_setup_client_authentication(args) - - _start_client_internal( - server_address=args.superlink, - load_client_app_fn=load_fn, - transport="rest" if args.rest else "grpc-rere", - root_certificates=root_certificates, - insecure=args.insecure, - authentication_keys=authentication_keys, - max_retries=args.max_retries, - max_wait_time=args.max_wait_time, - ) - register_exit_handlers(event_type=EventType.RUN_CLIENT_APP_LEAVE) - def _get_certificates(args: argparse.Namespace) -> Optional[bytes]: """Load certificates if specified in args.""" @@ -140,24 +155,117 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]: def _get_load_client_app_fn( - args: argparse.Namespace, -) -> Callable[[], ClientApp]: - """Get the load_client_app_fn function.""" - client_app_dir = args.dir - if client_app_dir is not None: - sys.path.insert(0, client_app_dir) + args: argparse.Namespace, multi_app: bool +) -> Callable[[str, str], ClientApp]: + """Get the load_client_app_fn function. + + If `multi_app` is True, this function loads the specified ClientApp + based on `fab_id` and `fab_version`. If `fab_id` is empty, a default + ClientApp will be loaded. + + If `multi_app` is False, it ignores `fab_id` and `fab_version` and + loads a default ClientApp. + """ + # Find the Flower directory containing Flower Apps (only for multi-app) + flwr_dir = Path("") + if "flwr_dir" in args: + if args.flwr_dir is None: + flwr_dir = Path( + os.getenv( + "FLWR_HOME", + f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", + ) + ) + else: + flwr_dir = Path(args.flwr_dir) + + sys.path.insert(0, str(flwr_dir)) - app_ref: str = getattr(args, "client-app") - valid, error_msg = validate(app_ref) - if not valid and error_msg: - raise LoadClientAppError(error_msg) from None + default_app_ref: str = getattr(args, "client-app") - def _load() -> ClientApp: - client_app = load_app(app_ref, LoadClientAppError) + if not multi_app: + log( + DEBUG, + "Flower SuperNode will load and validate ClientApp `%s`", + getattr(args, "client-app"), + ) + valid, error_msg = validate(default_app_ref) + if not valid and error_msg: + raise LoadClientAppError(error_msg) from None + + def _load(fab_id: str, fab_version: str) -> ClientApp: + # If multi-app feature is disabled + if not multi_app: + # Set sys.path + sys.path[0] = args.dir + + # Set app reference + client_app_ref = default_app_ref + # If multi-app feature is enabled but the fab id is not specified + elif fab_id == "": + if default_app_ref == "": + raise LoadClientAppError( + "Invalid FAB ID: The FAB ID is empty.", + ) from None + + log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.") + # Set sys.path + sys.path[0] = args.dir + + # Set app reference + client_app_ref = default_app_ref + # If multi-app feature is enabled + else: + # Check the fab_id + if fab_id.count("/") != 1: + raise LoadClientAppError( + f"Invalid FAB ID: {fab_id}", + ) from None + username, project_name = fab_id.split("/") + + # Locate the directory + project_dir = flwr_dir / "apps" / username / project_name / fab_version + + # Check if the directory exists + if not project_dir.exists(): + raise LoadClientAppError( + f"Invalid Flower App directory: {project_dir}", + ) from None + + # Load pyproject.toml file + toml_path = project_dir / "pyproject.toml" + if not os.path.isfile(toml_path): + raise LoadClientAppError( + f"Cannot find pyproject.toml in {project_dir}", + ) from None + with open(toml_path, encoding="utf-8") as toml_file: + config = tomli.loads(toml_file.read()) + + # Validate pyproject.toml fields + is_valid, errors, _ = validate_fields(config) + if not is_valid: + error_msg = "\n".join([f" - {error}" for error in errors]) + raise LoadClientAppError( + f"Invalid pyproject.toml:\n{error_msg}", + ) from None + + # Set sys.path + sys.path[0] = str(project_dir) + + # Set app reference + client_app_ref = config["flower"]["components"]["clientapp"] + + # Load ClientApp + log( + DEBUG, + "Loading ClientApp `%s`", + client_app_ref, + ) + client_app = load_app(client_app_ref, LoadClientAppError) if not isinstance(client_app, ClientApp): raise LoadClientAppError( - f"Attribute {app_ref} is not of type {ClientApp}", + f"Attribute {client_app_ref} is not of type {ClientApp}", ) from None return client_app From 18658f52d3a3504286f4f06b12fa8a3d94bd26d4 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Thu, 13 Jun 2024 18:12:39 +0200 Subject: [PATCH 024/595] docs(framework) Add latest Hosted Weblate translation updates (#3586) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yan Gao Co-authored-by: 박태현 --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 113 +- .../zh_Hans/LC_MESSAGES/framework-docs.po | 1168 ++++++++++++++--- 2 files changed, 1059 insertions(+), 222 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 68440f928f08..2d106db01fd5 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-28 11:47+0200\n" -"PO-Revision-Date: 2024-06-11 06:22+0000\n" +"PO-Revision-Date: 2024-06-13 16:07+0000\n" "Last-Translator: 박태현 \n" "Language-Team: Korean \n" @@ -621,16 +621,20 @@ msgid "" "means that you can seamlessly switch your entire development environment " "just by connecting to a different container." msgstr "" +"작업 공간 파일은 로컬 파일 시스템에서 마운트되거나 컨테이너에 복사 또는 " +"클론됩니다. 확장 프로그램은 컨테이너 내부에 설치되고 실행되며, 도구, 플랫폼 " +"및 파일 시스템에 완전한 접근 권한을 갖습니다. 이는 다른 컨테이너에 연결하는 " +"것만으로 전체 개발 환경을 원활하게 전환할 수 있음을 의미합니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:11 msgid "" "Source: `Official VSCode documentation `_" -msgstr "" +msgstr "출처 : 공식 VSCode 문서" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:15 msgid "Getting started" -msgstr "" +msgstr "시작하기" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:17 msgid "" @@ -641,6 +645,12 @@ msgid "" "your command line. Additionally, install the `VSCode Containers Extension " "`_." msgstr "" +"`Dockerfile`을 설정하고 구성하는 것과 개발 컨테이너 구성은 약간 복잡할 수 " +"있습니다. 다행히도, 이를 직접 할 필요는 없습니다. 일반적으로 시스템에 `" +"Docker `_를 설치하고 커맨드 " +"라인에서 사용할 수 있는지 확인하는 것으로 충분합니다. 추가로 `VSCode " +"Containers Extension `" +"_을 설치하세요." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:19 msgid "" @@ -651,36 +661,46 @@ msgid "" "left corner of your VSCode window and select the option *(Re)Open Folder in " "Container*." msgstr "" +"이제 준비가 완료되었습니다. VSCode를 시작하면 컨테이너 환경에서 실행할지를 " +"묻고, 확인하면 자동으로 컨테이너를 빌드하고 사용할 것입니다. VSCode에 " +"수동으로 개발 컨테이너를 사용하도록 지시하려면, 확장을 설치한 후, VSCode " +"창의 왼쪽 하단에 있는 초록색 부을 클릭하고 *(Re)Open Folder in Container* " +"옵션을 선택하세요." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:21 msgid "" "In some cases your setup might be more involved. For those cases consult the " "following sources:" -msgstr "" +msgstr "경우에 따라 설정이 더 복잡할 수도 있습니다. 이러한 경우에는 다음 소스를 " +"참조하세요:" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:23 msgid "" "`Developing inside a Container `_" msgstr "" +"`컨테이너 내부 개발`_" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:24 msgid "" "`Remote development in Containers `_" msgstr "" +"`컨테이너 원격 개발`_" #: ../../source/contributor-how-to-install-development-versions.rst:2 msgid "Install development versions" -msgstr "" +msgstr "개발 버전 설치하기" #: ../../source/contributor-how-to-install-development-versions.rst:5 msgid "Install development versions of Flower" -msgstr "" +msgstr "Flower 개발 버전 설치하기" #: ../../source/contributor-how-to-install-development-versions.rst:8 msgid "Using Poetry (recommended)" -msgstr "" +msgstr "Poetry 사용하기(권장)" #: ../../source/contributor-how-to-install-development-versions.rst:10 msgid "" @@ -688,50 +708,62 @@ msgid "" "``pyproject.toml`` and then reinstall (don't forget to delete ``poetry." "lock`` (``rm poetry.lock``) before running ``poetry install``)." msgstr "" +"PyPI에서 ``flwr`` 사전 릴리스 설치하기: ``pyproject.toml``에서 ``flwr``의 " +"dependency를 업데이트한 다음, 재설치하세요(``poetry 설치``이전에 ``poetry." +"lock`` (``rm poetry.lock``)를 제거하는 것을 잊지 마세요)." #: ../../source/contributor-how-to-install-development-versions.rst:12 msgid "" "``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (without " "extras)" msgstr "" +"``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:13 msgid "" "``flwr = { version = \"1.0.0a0\", allow-prereleases = true, extras = " "[\"simulation\"] }`` (with extras)" msgstr "" +"``flwr = { version = \"1.0.0a0\", allow-prereleases = true, extras = [" +"\"simulation\"] }`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:15 msgid "" "Install ``flwr`` from a local copy of the Flower source code via ``pyproject." "toml``:" -msgstr "" +msgstr "``pyproject.toml``을 통해 Flower 소스 코드의 로컬 복사본에서 ``flwr``을 설치:" #: ../../source/contributor-how-to-install-development-versions.rst:17 msgid "``flwr = { path = \"../../\", develop = true }`` (without extras)" -msgstr "" +msgstr "``flwr = { path = \"../../\", develop = true }`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:18 msgid "" "``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] }`` " "(with extras)" msgstr "" +"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] }`` (" +"extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:20 msgid "Install ``flwr`` from a local wheel file via ``pyproject.toml``:" -msgstr "" +msgstr "``pyproject.toml``을 통해 로컬 wheel file에서 ``flwr``을 설치하세요:" #: ../../source/contributor-how-to-install-development-versions.rst:22 msgid "" "``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (without " "extras)" msgstr "" +"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (extras " +"제외)" #: ../../source/contributor-how-to-install-development-versions.rst:23 msgid "" "``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\", extras = " "[\"simulation\"] }`` (with extras)" msgstr "" +"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\", extras = [" +"\"simulation\"] }`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:25 msgid "" @@ -739,101 +771,120 @@ msgid "" "Dependency Specification `_" msgstr "" +"자세한 내용은 Poetry 문서를 참고하세요: `Poetry Dependency Specification " +"`_" #: ../../source/contributor-how-to-install-development-versions.rst:28 msgid "Using pip (recommended on Colab)" -msgstr "" +msgstr "pip 사용하기(Colab에서 권장)" #: ../../source/contributor-how-to-install-development-versions.rst:30 msgid "Install a ``flwr`` pre-release from PyPI:" -msgstr "" +msgstr "PyPI에서 ``flwr`` 사전 릴리스를 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:32 msgid "``pip install -U --pre flwr`` (without extras)" -msgstr "" +msgstr "``pip install -U --pre flwr`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:33 msgid "``pip install -U --pre flwr[simulation]`` (with extras)" -msgstr "" +msgstr "``pip install -U --pre flwr[simulation]`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:35 msgid "" "Python packages can be installed from git repositories. Use one of the " "following commands to install the Flower directly from GitHub." msgstr "" +"Python 패키지는 git 저장소에서 설치할 수 있습니다. 다음 명령어 중 하나를 " +"사용하여 GitHub에서 직접 Flower를 설치하세요." #: ../../source/contributor-how-to-install-development-versions.rst:37 msgid "Install ``flwr`` from the default GitHub branch (``main``):" -msgstr "" +msgstr "기본 GitHub branch (``main``)에서 ``flwr`` 를 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:39 msgid "" "``pip install flwr@git+https://github.com/adap/flower.git`` (without extras)" -msgstr "" +msgstr "``pip install flwr@git+https://github.com/adap/flower.git`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:40 msgid "" "``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " "(with extras)" msgstr "" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` (" +"extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:42 msgid "Install ``flwr`` from a specific GitHub branch (``branch-name``):" -msgstr "" +msgstr "특정 GitHub branch (``branch-name``)에서 ``flwr``설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:44 msgid "" "``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` " "(without extras)" msgstr "" +"``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` (" +"extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" "``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" "name`` (with extras)" msgstr "" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" +"name`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:49 msgid "Open Jupyter Notebooks on Google Colab" -msgstr "" +msgstr "Google Colab에서 Jupyter Notebooks 열기" #: ../../source/contributor-how-to-install-development-versions.rst:51 msgid "" "Open the notebook ``doc/source/tutorial-series-get-started-with-flower-" "pytorch.ipynb``:" msgstr "" +"``doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb``" +"notebook을 엽니다:" #: ../../source/contributor-how-to-install-development-versions.rst:53 msgid "" "https://colab.research.google.com/github/adap/flower/blob/main/doc/source/" "tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" +"https://colab.research.google.com/github/adap/flower/blob/main/doc/source/" +"tutorial-series-get-started-with-flower-pytorch.ipynb" #: ../../source/contributor-how-to-install-development-versions.rst:55 msgid "" "Open a development version of the same notebook from branch `branch-name` by " "changing ``main`` to ``branch-name`` (right after ``blob``):" msgstr "" +"``main``을 ``branch-name``(``blob`` 바로 뒤)으로 변경하여 동일한 notebook의 " +"개발 버전을 브랜치 `branch-name`에서 엽니다 :" #: ../../source/contributor-how-to-install-development-versions.rst:57 msgid "" "https://colab.research.google.com/github/adap/flower/blob/branch-name/doc/" "source/tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" +"https://colab.research.google.com/github/adap/flower/blob/branch-name/doc/" +"source/tutorial-series-get-started-with-flower-pytorch.ipynb" #: ../../source/contributor-how-to-install-development-versions.rst:59 msgid "Install a `whl` on Google Colab:" -msgstr "" +msgstr "Google Colab에서 `whl` 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:61 msgid "" "In the vertical icon grid on the left hand side, select ``Files`` > ``Upload " "to session storage``" -msgstr "" +msgstr "왼쪽의 수직 아이콘 그리드에서 ``Files`` > ``Upload to session storage``를 " +"선택하세요" #: ../../source/contributor-how-to-install-development-versions.rst:62 msgid "Upload the whl (e.g., ``flwr-1.8.0-py3-none-any.whl``)" -msgstr "" +msgstr "whl (예:``flwr-1.8.0-py3-none-any.whl``)을 업로드하세요" #: ../../source/contributor-how-to-install-development-versions.rst:63 msgid "" @@ -841,26 +892,31 @@ msgid "" "to ``!pip install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch " "torchvision matplotlib``" msgstr "" +"``!pip install -q 'flwr[simulation]' torch torchvision matplotlib``를 ``!pip " +"install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch torchvision " +"matplotlib``로 바꾸세요" #: ../../source/contributor-how-to-release-flower.rst:2 msgid "Release Flower" -msgstr "" +msgstr "Flower 릴리즈 하기" #: ../../source/contributor-how-to-release-flower.rst:4 msgid "" "This document describes the current release process. It may or may not " "change in the future." -msgstr "" +msgstr "이 문서는 현재 릴리즈 과정을 설명합니다. 이는 앞으로 변경될 수도 있습니다." #: ../../source/contributor-how-to-release-flower.rst:7 msgid "During the release" -msgstr "" +msgstr "릴리즈 동안에" #: ../../source/contributor-how-to-release-flower.rst:9 msgid "" "The version number of a release is stated in ``pyproject.toml``. To release " "a new version of Flower, the following things need to happen (in that order):" msgstr "" +"릴리즈의 버전 번호는 ``pyproject.toml``에 명시되어 있습니다. Flower의 새 " +"버전을 릴리즈하려면 다음 작업이 순서대로 수행되어야 합니다:" #: ../../source/contributor-how-to-release-flower.rst:11 msgid "" @@ -868,6 +924,9 @@ msgid "" "order to add every new change to the changelog (feel free to make manual " "changes to the changelog afterwards until it looks good)." msgstr "" +"모든 새로운 변경 사항을 변경 로그에 추가하기 위해``python3 src/py/flwr_tool/" +"update_changelog.py ``을 실행합니다 (변경 로그가 만족스러워질 " +"때까지 수동으로 변경해도 됩니다)." #: ../../source/contributor-how-to-release-flower.rst:12 msgid "" @@ -878,6 +937,12 @@ msgid "" "and current date, and it will add a thanking message for the contributors. " "Open a pull request with those changes." msgstr "" +"모든 변경 사항으로 변경 로그가 업데이트되면,``./dev/prepare-release-" +"changelog.sh v``을 실행합니다. 여기서 ````은 " +"``pyproject.toml``에 명시된 버전 번호입니다 (앞에 ``v``가 추가된 것을 " +"주의하세요). 이 명령어는 변경 로그의 ``Unreleased``헤더를 해당 버전과 현재 " +"날짜로 교체하고, 기여자들에게 감사 메시지가 추가됩니다. 이러한 변경 사항으로 " +"pull request합니다." #: ../../source/contributor-how-to-release-flower.rst:13 msgid "" diff --git a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po b/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po index 47be2dfda762..7ca5b00176fb 100644 --- a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po +++ b/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po @@ -8,15 +8,16 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-28 11:47+0200\n" -"PO-Revision-Date: 2024-05-10 06:59+0000\n" +"PO-Revision-Date: 2024-06-12 10:09+0000\n" "Last-Translator: Yan Gao \n" +"Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" -"Language-Team: Chinese (Simplified) \n" -"Plural-Forms: nplurals=1; plural=0;\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" +"X-Generator: Weblate 5.6-dev\n" "Generated-By: Babel 2.15.0\n" #: ../../source/contributor-explanation-architecture.rst:2 @@ -295,8 +296,9 @@ msgid "``FLWR_PACKAGE``" msgstr "``FLWR_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:103 +#, fuzzy msgid "The PyPI package to install." -msgstr "" +msgstr "要安装的 PyPI 软件包。" #: ../../source/contributor-how-to-build-docker-images.rst:104 #, fuzzy @@ -1118,12 +1120,16 @@ msgstr "" "3.10 `_或更高版本。" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:14 +#, fuzzy msgid "" "Due to a known incompatibility with `ray " "`_, we currently recommend utilizing at " "most `Python 3.11 `_ for running Flower " "simulations." msgstr "" +"由于已知与 `ray `_ 不兼容," +"我们目前建议最多使用 `Python 3.11 `_ 运行 " +"Flower 仿真。" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:19 #, fuzzy @@ -1738,11 +1744,14 @@ msgid "" msgstr "在这个例子中,你可以看到请求将我分叉的版本库中的分支 ``doc-fixes`` 合并到 Flower 版本库中的分支 ``main``。" #: ../../source/contributor-tutorial-contribute-on-github.rst:193 +#, fuzzy msgid "" "The title should be changed to adhere to the :ref:`pr_title_format` " "guidelines, otherwise it won't be possible to merge the PR. So in this " "case, a correct title might be ``docs(framework:skip) Fix typos``." msgstr "" +"应该修改标题以符合 :ref:`pr_title_format` 准则,否则将无法合并 " +"PR。因此,在这种情况下,正确的标题可能是 ``docs(framework:skip)修复错字``。" #: ../../source/contributor-tutorial-contribute-on-github.rst:196 msgid "" @@ -1753,8 +1762,9 @@ msgid "" msgstr "中间的输入框供您描述 PR 的作用,并将其与现有问题联系起来。我们在此放置了注释(一旦 PR 打开,注释将不会显示),以指导您完成整个过程。" #: ../../source/contributor-tutorial-contribute-on-github.rst:199 +#, fuzzy msgid "It is important to follow the instructions described in comments." -msgstr "" +msgstr "请务必遵守注释中的说明。" #: ../../source/contributor-tutorial-contribute-on-github.rst:201 msgid "" @@ -1989,10 +1999,12 @@ msgid "Push the changes to your fork" msgstr "将更改推送到分叉" #: ../../source/contributor-tutorial-contribute-on-github.rst:308 +#, fuzzy msgid "" "Open a PR (as shown above) with title ``docs(framework) Update how-to " "guide title``" -msgstr "" +msgstr "打开一个 PR(如上图所示),标题为\"`docs(framework) Update how-to guide " +"title```\"。" #: ../../source/contributor-tutorial-contribute-on-github.rst:309 msgid "Wait for it to be approved!" @@ -2033,20 +2045,24 @@ msgid "Appendix" msgstr "附录" #: ../../source/contributor-tutorial-contribute-on-github.rst:327 +#, fuzzy msgid "PR title format" -msgstr "" +msgstr "PR 标题格式" #: ../../source/contributor-tutorial-contribute-on-github.rst:329 +#, fuzzy msgid "We enforce the following PR title format:" -msgstr "" +msgstr "我们执行以下 PR 标题格式:" #: ../../source/contributor-tutorial-contribute-on-github.rst:335 +#, fuzzy msgid "" "(or ``(:skip) `` to ignore the PR in the " "changelog)" -msgstr "" +msgstr "(或 ``(:skip) `` 忽略更新日志中的 PR)。" #: ../../source/contributor-tutorial-contribute-on-github.rst:337 +#, fuzzy msgid "" "Where ```` needs to be in ``{ci, fix, feat, docs, refactor, " "break}``, ```` should be in ``{framework, baselines, datasets, " @@ -2054,6 +2070,10 @@ msgid "" "':skip' flag to be used}``, and ```` starts with a capitalised " "verb in the imperative mood." msgstr "" +"其中 ```` 需要使用 ``{ci, fix, feat, docs, refactor, break}``, " +"```` 应该使用 ``{framework, baselines, datasets, examples, 或者 '*' " +"当修改多个项目时需要使用 ':skip'标记}``, 并且 ```` " +"应该以一个大写的动词开始。" #: ../../source/contributor-tutorial-contribute-on-github.rst:341 #, fuzzy @@ -2061,16 +2081,19 @@ msgid "Valid examples:" msgstr "实例" #: ../../source/contributor-tutorial-contribute-on-github.rst:343 +#, fuzzy msgid "``feat(framework) Add flwr build CLI command``" -msgstr "" +msgstr "`feat(框架) 添加 flwr build CLI 命令```" #: ../../source/contributor-tutorial-contribute-on-github.rst:344 +#, fuzzy msgid "``refactor(examples:skip) Improve quickstart-pytorch logging``" -msgstr "" +msgstr "``refactor(examples:skip) Improve quickstart-pytorch logging``." #: ../../source/contributor-tutorial-contribute-on-github.rst:345 +#, fuzzy msgid "``ci(*:skip) Enforce PR title format``" -msgstr "" +msgstr "`ci(*:skip)执行 PR 标题格式``。" #: ../../source/contributor-tutorial-contribute-on-github.rst:347 #, fuzzy @@ -2078,30 +2101,38 @@ msgid "Invalid examples:" msgstr "模拟示例" #: ../../source/contributor-tutorial-contribute-on-github.rst:349 +#, fuzzy msgid "``feat(framework): Add flwr build CLI command`` (extra ``:``)" -msgstr "" +msgstr "`feat(框架): 添加 flwr build CLI 命令``(额外的``:``)" #: ../../source/contributor-tutorial-contribute-on-github.rst:350 +#, fuzzy msgid "" "``feat(*) Add flwr build CLI command`` (missing ``skip`` flag along with " "``*``)" -msgstr "" +msgstr "`feat(*)添加flwr构建CLI命令``(缺少``skip``标志和``*``)。" #: ../../source/contributor-tutorial-contribute-on-github.rst:351 +#, fuzzy msgid "``feat(skip) Add flwr build CLI command`` (missing ````)" -msgstr "" +msgstr "`feat(skip)添加flwr构建CLI命令``(缺少```)。" #: ../../source/contributor-tutorial-contribute-on-github.rst:352 +#, fuzzy msgid "``feat(framework) add flwr build CLI command`` (non capitalised verb)" -msgstr "" +msgstr "`feat(framework)添加 flwr 构建 CLI 命令``(非大写动词)" #: ../../source/contributor-tutorial-contribute-on-github.rst:353 +#, fuzzy msgid "``feat(framework) Add flwr build CLI command.`` (dot at the end)" -msgstr "" +msgstr "feat(框架) 添加 flwr 构建 CLI 命令。" #: ../../source/contributor-tutorial-contribute-on-github.rst:354 +#, fuzzy msgid "``Add flwr build CLI command.`` (missing ``()``)" msgstr "" +"``添加 flwr build CLI 命令.``(缺少``()``) ``Add flwr build " +"CLI command.`` (missing ``()``)" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:2 msgid "Get started as a contributor" @@ -2264,49 +2295,62 @@ msgid "Run Linters and Tests" msgstr "运行分类器和测试" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:106 +#, fuzzy msgid "Add a pre-commit hook" -msgstr "" +msgstr "添加预先提交钩子" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:108 +#, fuzzy msgid "" "Developers may integrate a pre-commit hook into their workflow utilizing " "the `pre-commit `_ library. The pre-" "commit hook is configured to execute two primary operations: " "``./dev/format.sh`` and ``./dev/test.sh`` scripts." msgstr "" +"开发人员可利用 `pre-commit `_ " +"库将预提交钩子集成到工作流程中。预提交钩子被配置为执行两个主要操作: `./dev/" +"format.sh`` 和 ``./dev/test.sh`` 脚本。" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:110 +#, fuzzy msgid "There are multiple ways developers can use this:" -msgstr "" +msgstr "开发人员可以通过多种方式使用它:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:112 +#, fuzzy msgid "Install the pre-commit hook to your local git directory by simply running:" -msgstr "" +msgstr "在本地 git 目录中安装预提交钩子,只需运行" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:118 +#, fuzzy msgid "" "Each ``git commit`` will trigger the execution of formatting and " "linting/test scripts." -msgstr "" +msgstr "每次 \"git 提交 \"都会触发格式化和内核/测试脚本的执行。" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:119 +#, fuzzy msgid "" "If in a hurry, bypass the hook using ``--no-verify`` with the ``git " "commit`` command. ::" -msgstr "" +msgstr "如果赶时间,可使用 ``--no-verify`` 和 ``git commit` 命令绕过钩子:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:124 +#, fuzzy msgid "" "For developers who prefer not to install the hook permanently, it is " "possible to execute a one-time check prior to committing changes by using" " the following command:" -msgstr "" +msgstr "对于不想永久安装钩子的开发人员,可以使用以下命令在提交更改之前执行一次性检查" +":" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:130 +#, fuzzy msgid "" "This executes the formatting and linting checks/tests on all the files " "without modifying the default behavior of ``git commit``." -msgstr "" +msgstr "这将在不修改 ``git commit`` " +"默认行为的情况下对所有文件执行格式化和词排检查/测试。" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:133 msgid "Run Github Actions (CI) locally" @@ -4068,36 +4112,45 @@ msgid "" msgstr "然后,服务器可以使用定制的策略来汇总这些字典中提供的指标:" #: ../../source/how-to-authenticate-supernodes.rst:2 +#, fuzzy msgid "Authenticate SuperNodes" -msgstr "" +msgstr "验证超级节点" #: ../../source/how-to-authenticate-supernodes.rst:4 +#, fuzzy msgid "" "Flower has built-in support for authenticated SuperNodes that you can use" " to verify the identities of each SuperNode connecting to a SuperLink. " "Flower node authentication works similar to how GitHub SSH authentication" " works:" msgstr "" +"Flower 内置了对经过身份验证的超级节点的支持,您可以用它来验证连接到超级链接的" +"每个超级节点的身份。Flower 节点身份验证的工作方式与 GitHub SSH " +"身份验证的工作方式类似:" #: ../../source/how-to-authenticate-supernodes.rst:7 +#, fuzzy msgid "SuperLink (server) stores a list of known (client) node public keys" -msgstr "" +msgstr "超级链接(服务器)存储已知(客户端)节点公钥列表" #: ../../source/how-to-authenticate-supernodes.rst:8 +#, fuzzy msgid "" "Using ECDH, both SuperNode and SuperLink independently derive a shared " "secret" -msgstr "" +msgstr "使用 ECDH,超级节点和超级链路可独立生成共享秘密" #: ../../source/how-to-authenticate-supernodes.rst:9 +#, fuzzy msgid "" "Shared secret is used to compute the HMAC value of the message sent from " "SuperNode to SuperLink as a token" -msgstr "" +msgstr "共享秘密用于计算作为令牌从超级节点发送到超级链接的信息的 HMAC 值" #: ../../source/how-to-authenticate-supernodes.rst:10 +#, fuzzy msgid "SuperLink verifies the token" -msgstr "" +msgstr "超级链接验证令牌" #: ../../source/how-to-authenticate-supernodes.rst:12 #, fuzzy @@ -4111,22 +4164,26 @@ msgstr "" "`_了解更多信息。" #: ../../source/how-to-authenticate-supernodes.rst:15 +#, fuzzy msgid "" "This guide covers a preview feature that might change in future versions " "of Flower." -msgstr "" +msgstr "本指南涵盖的预览功能可能会在 Flower 的未来版本中有所改变。" #: ../../source/how-to-authenticate-supernodes.rst:18 +#, fuzzy msgid "" "For increased security, node authentication can only be used when " "encrypted connections (SSL/TLS) are enabled." -msgstr "" +msgstr "为提高安全性,只有启用加密连接(SSL/TLS)时才能使用节点验证。" #: ../../source/how-to-authenticate-supernodes.rst:21 +#, fuzzy msgid "Enable node authentication in :code:`SuperLink`" -msgstr "" +msgstr "在 :code:`SuperLink` 中启用节点验证" #: ../../source/how-to-authenticate-supernodes.rst:23 +#, fuzzy msgid "" "To enable node authentication, first you need to configure SSL/TLS " "connections to secure the SuperLink<>SuperNode communication. You can " @@ -4137,36 +4194,56 @@ msgid "" ":code:`SuperNode` that has both secure connections and node " "authentication enabled:" msgstr "" +"要启用节点验证,首先需要配置 SSL/TLS 连接,以确保 SuperLink<>SuperNode " +"通信的安全。您可以在 `_ 找到完整的指南。配置安全连接后,您就可以在长期运行的 " +"Flower :code:`SuperLink`中启用客户端身份验证。" +"使用以下终端命令启动一个同时启用了安全连接和节点验证的 Flower " +":code:`SuperNode`:" #: ../../source/how-to-authenticate-supernodes.rst:36 +#, fuzzy msgid "Let's break down the authentication flags:" -msgstr "" +msgstr "让我们来分析一下身份验证标志:" #: ../../source/how-to-authenticate-supernodes.rst:38 +#, fuzzy msgid "" "The first flag :code:`--auth-list-public-keys` expects a path to a CSV " "file storing all known node public keys. You need to store all known node" " public keys that are allowed to participate in a federation in one CSV " "file (:code:`.csv`)." msgstr "" +"第一个标志 :code:`--auth-list-public-keys`(密码:`--auth-list-public-keys`)" +"需要一个 CSV 文件路径,该文件存储了所有已知节点的公钥。您需要在一个 CSV " +"文件(:code:`.csv`)中存储所有允许参与联盟的已知节点公钥。" #: ../../source/how-to-authenticate-supernodes.rst:40 +#, fuzzy msgid "" "A valid CSV file storing known node public keys should list the keys in " "OpenSSH format, separated by commas and without any comments. For an " "example, refer to our code sample, which contains a CSV file with two " "known node public keys." msgstr "" +"存储已知节点公开密钥的有效 CSV 文件应以 OpenSSH " +"格式列出密钥,以逗号分隔,不含任何注释。有关示例,请参阅我们的代码示例," +"其中包含一个包含两个已知节点公钥的 CSV 文件。" #: ../../source/how-to-authenticate-supernodes.rst:42 +#, fuzzy msgid "" "The second and third flags :code:`--auth-superlink-private-key` and :code" ":`--auth-superlink-public-key` expect paths to the server's private and " "public keys. For development purposes, you can generate a private and " "public key pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" +"第二和第三个标记 :code:`--auth-superlink-private-key` 和 :code:`--auth-" +"superlink-public-key` 希望指向服务器私钥和公钥的路径。出于开发目的," +"您可以使用 :code:`ssh-keygen -t ecdsa -b 384` 生成一对私钥和公钥。" #: ../../source/how-to-authenticate-supernodes.rst:45 +#, fuzzy msgid "" "In Flower 1.9, there is no support for dynamically removing, editing, or " "adding known node public keys to the SuperLink. To change the set of " @@ -4174,20 +4251,29 @@ msgid "" "start the server again. Support for dynamically changing the set of known" " nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" +"在 Flower 1.9 中,超级链接不支持动态删除、编辑或添加已知节点公钥。要更改已知" +"节点集,您需要关闭服务器,编辑 CSV 文件,然后重新启动服务器。" +"动态更改已知节点集的支持已列入 Flower 1.10(预计发布时间:6 月)的路线图。" #: ../../source/how-to-authenticate-supernodes.rst:51 +#, fuzzy msgid "Enable node authentication in :code:`SuperNode`" -msgstr "" +msgstr "在 :code:`SuperNode` 中启用节点验证" #: ../../source/how-to-authenticate-supernodes.rst:53 +#, fuzzy msgid "" "Similar to the long-running Flower server (:code:`SuperLink`), you can " "easily enable node authentication in the long-running Flower client " "(:code:`SuperNode`). Use the following terminal command to start an " "authenticated :code:`SuperNode`:" msgstr "" +"与长期运行的 Flower 服务器(:code:`SuperLink`)类似,您也可以在长期运行的 " +"Flower 客户端(:code:`SuperNode`)中轻松启用节点身份验证。" +"使用以下终端命令启动已验证的 :code:`SuperNode`:" #: ../../source/how-to-authenticate-supernodes.rst:64 +#, fuzzy msgid "" "The :code:`--auth-supernode-private-key` flag expects a path to the " "node's private key file and the :code:`--auth-supernode-public-key` flag " @@ -4195,12 +4281,17 @@ msgid "" "you can generate a private and public key pair using :code:`ssh-keygen -t" " ecdsa -b 384`." msgstr "" +":code:`--auth-supernode-private-key`标志需要节点私钥文件的路径,:code:`-auth-" +"supernode-public-key`标志需要节点公钥文件的路径。出于开发目的,可以使用 :code" +":`ssh-keygen -t ecdsa -b 384` 生成一对私钥和公钥。" #: ../../source/how-to-authenticate-supernodes.rst:68 +#, fuzzy msgid "Security notice" -msgstr "" +msgstr "安全通知" #: ../../source/how-to-authenticate-supernodes.rst:70 +#, fuzzy msgid "" "The system's security relies on the credentials of the SuperLink and each" " SuperNode. Therefore, it is imperative to safeguard and safely store the" @@ -4210,6 +4301,9 @@ msgid "" "communication is done in a secure manner, using trusted communication " "methods." msgstr "" +"系统的安全性依赖于超级链接和每个超级节点的凭证。因此,必须保护和安全存储凭证" +",以避免公钥基础设施 (PKI) 假冒攻击等安全风险。节点验证机制还涉及人机交互,因" +"此请确保使用可信的通信方法,以安全的方式进行所有通信。" #: ../../source/how-to-authenticate-supernodes.rst:75 #: ../../source/how-to-enable-ssl-connections.rst:65 @@ -4219,12 +4313,16 @@ msgid "Conclusion" msgstr "总结" #: ../../source/how-to-authenticate-supernodes.rst:77 +#, fuzzy msgid "" "You should now have learned how to start a long-running Flower server " "(:code:`SuperLink`) and client (:code:`SuperNode`) with node " "authentication enabled. You should also know the significance of the " "private key and store it safely to minimize security risks." msgstr "" +"现在,您应该已经学会了如何启动长期运行的 Flower 服务器(:code:`SuperLink`)和" +"客户端(:code:`SuperNode`)并启用节点身份验证。您还应该知道私钥的重要性,并将" +"其安全存储,以尽量减少安全风险。" #: ../../source/how-to-configure-clients.rst:2 msgid "Configure clients" @@ -5312,12 +5410,16 @@ msgstr "" "`安装后步骤 `_进行操作。" #: ../../source/how-to-run-flower-using-docker.rst:26 +#, fuzzy msgid "" "To ensure optimal performance and compatibility, the SuperLink, SuperNode" " and ServerApp image must have the same version when running together. " "This guarantees seamless integration and avoids potential conflicts or " "issues that may arise from using different versions." msgstr "" +"为确保最佳性能和兼容性,SuperLink、SuperNode 和 ServerApp 映像在一起运行时必" +"须具有相同的版本。这可确保无缝集成,并避免因使用不同版本而可能产生的潜在冲突" +"或问题。" #: ../../source/how-to-run-flower-using-docker.rst:31 #, fuzzy @@ -5467,12 +5569,14 @@ msgid "Flower SuperNode" msgstr "Flower 服务器" #: ../../source/how-to-run-flower-using-docker.rst:115 +#, fuzzy msgid "" "The SuperNode Docker image comes with a pre-installed version of Flower " "and serves as a base for building your own SuperNode image." -msgstr "" +msgstr "超级节点 Docker 镜像预装了 Flower 版本,可作为构建自己的超级节点镜像的基础。" #: ../../source/how-to-run-flower-using-docker.rst:120 +#, fuzzy msgid "" "The SuperNode Docker image currently works only with the 1.9.0-nightly " "release. A stable version will be available when Flower 1.9.0 (stable) " @@ -5481,12 +5585,19 @@ msgid "" "same day. To ensure the versions are in sync, using the concrete tag, " "e.g., ``1.9.0.dev20240501`` instead of ``nightly`` is recommended." msgstr "" +"超级节点 Docker 映像目前仅适用于 1.9.0-nightly 版本。稳定版将在 Flower 1.9." +"0(稳定版)发布时推出(预计发布时间:5 月)。超级节点夜间镜像必须与同一天发布" +"的相应超级链接和服务器应用程序夜间镜像配对。为确保版本同步,建议使用具体标签" +",例如``1.9.0.dev20240501``,而不是``nightly``。" #: ../../source/how-to-run-flower-using-docker.rst:126 +#, fuzzy msgid "" "We will use the ``quickstart-pytorch`` example, which you can find in the" " Flower repository, to illustrate how you can dockerize your ClientApp." msgstr "" +"我们将使用 \"quickstart-pytorch\"(快速启动-pytorch)示例来说明如何对 " +"ClientApp 进行 docker 化。" #: ../../source/how-to-run-flower-using-docker.rst:134 #, fuzzy @@ -5502,43 +5613,58 @@ msgid "Clone the Flower repository." msgstr "**叉花仓库**" #: ../../source/how-to-run-flower-using-docker.rst:152 +#, fuzzy msgid "Creating a SuperNode Dockerfile" -msgstr "" +msgstr "创建超级节点 Dockerfile" #: ../../source/how-to-run-flower-using-docker.rst:154 #: ../../source/how-to-run-flower-using-docker.rst:289 +#, fuzzy msgid "Let's assume the following project layout:" -msgstr "" +msgstr "假设项目布局如下" #: ../../source/how-to-run-flower-using-docker.rst:163 +#, fuzzy msgid "" "First, we need to create a ``requirements.txt`` file in the directory " "where the ``ClientApp`` code is located. In the file, we list all the " "dependencies that the ClientApp requires." msgstr "" +"首先,我们需要在 ``ClientApp`` 代码所在的目录中创建一个 ``requirements.txt`` " +"文件。在该文件中,我们列出了 ClientApp 需要的所有依赖项。" #: ../../source/how-to-run-flower-using-docker.rst:175 +#, fuzzy msgid "" "Note that `flwr `__ is already installed " "in the ``flwr/supernode`` base image, so you only need to include other " "package dependencies in your ``requirements.txt``, such as ``torch``, " "``tensorflow``, etc." msgstr "" +"请注意,`flwr `__ 已经安装在`flwr/" +"supernode``基础镜像中,因此只需在`requirements." +"txt``中包含其他依赖包,如`torch``、`tensorflow`等。" #: ../../source/how-to-run-flower-using-docker.rst:179 +#, fuzzy msgid "" "Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` " "example, create a new file called ``Dockerfile.supernode`` in ``examples" "/quickstart-pytorch``." msgstr "" +"接下来,我们创建一个 Dockerfile。如果使用 ``quickstart-pytorch`` 示例,请在 " +"``examples/quickstart-pytorch`` 中创建一个名为 ``Dockerfile.supernode`` " +"的新文件。" #: ../../source/how-to-run-flower-using-docker.rst:182 +#, fuzzy msgid "" "The ``Dockerfile.supernode`` contains the instructions that assemble the " "SuperNode image." -msgstr "" +msgstr "Dockerfile.supernode \"包含组装超级节点映像的指令。" #: ../../source/how-to-run-flower-using-docker.rst:196 +#, fuzzy msgid "" "In the first two lines, we instruct Docker to use the SuperNode image " "tagged ``nightly`` as a base image and set our working directory to " @@ -5550,6 +5676,13 @@ msgid "" "``client:app``. The argument is the object reference of the ClientApp " "(``:``) that will be run inside the ClientApp." msgstr "" +"在前两行中,我们指示 Docker 使用标记为 ``nightly`` 的 SuperNode " +"镜像作为基础镜像,并将工作目录设置为 ``/app``。下面的指令将在 ``/app`` " +"目录中执行。接下来,我们通过将 ``requirements.txt`` 文件复制到映像中并运行 ``" +"pip install`` 来安装 ClientApp 依赖项。最后两行,我们将 ``client.py`` " +"模块复制到映像中,并将入口点设置为 ``flower-client-app``,参数为 " +"``client:app``。参数是将在 ClientApp 内运行的 ClientApp " +"的对象引用(``<模块>:<属性>``)。" #: ../../source/how-to-run-flower-using-docker.rst:205 #, fuzzy @@ -5557,17 +5690,22 @@ msgid "Building the SuperNode Docker image" msgstr "启动服务器" #: ../../source/how-to-run-flower-using-docker.rst:207 +#, fuzzy msgid "" "Next, we build the SuperNode Docker image by running the following " "command in the directory where Dockerfile and ClientApp code are located." -msgstr "" +msgstr "接下来,我们在 Dockerfile 和 ClientApp 代码所在的目录下运行以下命令,构建 " +"SuperNode Docker 映像。" #: ../../source/how-to-run-flower-using-docker.rst:214 +#, fuzzy msgid "" "We gave the image the name ``flwr_supernode``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" +"我们将图像命名为 ``flwr_supernode``,标签为 ``0.0." +"1``。请记住,这里选择的值只是一个示例。您可以根据自己的需要进行更改。" #: ../../source/how-to-run-flower-using-docker.rst:219 #, fuzzy @@ -5575,64 +5713,79 @@ msgid "Running the SuperNode Docker image" msgstr "启动服务器" #: ../../source/how-to-run-flower-using-docker.rst:221 +#, fuzzy msgid "Now that we have built the SuperNode image, we can finally run it." -msgstr "" +msgstr "现在,我们已经构建了超级节点镜像,终于可以运行它了。" #: ../../source/how-to-run-flower-using-docker.rst:229 #: ../../source/how-to-run-flower-using-docker.rst:345 +#, fuzzy msgid "Let's break down each part of this command:" -msgstr "" +msgstr "让我们来分析一下这条命令的各个部分:" #: ../../source/how-to-run-flower-using-docker.rst:231 #: ../../source/how-to-run-flower-using-docker.rst:347 +#, fuzzy msgid "``docker run``: This is the command to run a new Docker container." -msgstr "" +msgstr "`docker run``: 这是运行新 Docker 容器的命令。" #: ../../source/how-to-run-flower-using-docker.rst:232 #: ../../source/how-to-run-flower-using-docker.rst:348 +#, fuzzy msgid "" "``--rm``: This option specifies that the container should be " "automatically removed when it stops." -msgstr "" +msgstr "`-rm``: 该选项指定容器停止时应自动移除。" #: ../../source/how-to-run-flower-using-docker.rst:233 +#, fuzzy msgid "``flwr_supernode:0.0.1``: The name the tag of the Docker image to use." -msgstr "" +msgstr "flwr_supernode:0.0.1``: 要使用的 Docker 映像的名称和标记。" #: ../../source/how-to-run-flower-using-docker.rst:234 #: ../../source/how-to-run-flower-using-docker.rst:350 +#, fuzzy msgid "``--insecure``: This option enables insecure communication." -msgstr "" +msgstr "不安全\": 该选项启用不安全通信。" #: ../../source/how-to-run-flower-using-docker.rst +#, fuzzy msgid "" "``--server 192.168.1.100:9092``: This option specifies the address of the" " SuperLinks Fleet" -msgstr "" +msgstr "``--server 192.168.1.100:9092``: 该选项指定超级链接舰队的地址" #: ../../source/how-to-run-flower-using-docker.rst +#, fuzzy msgid "API to connect to. Remember to update it with your SuperLink IP." -msgstr "" +msgstr "要连接的 API。记住用您的超级链接 IP 更新它。" #: ../../source/how-to-run-flower-using-docker.rst:248 +#, fuzzy msgid "" "To test running Flower locally, you can create a `bridge network " "`__, use the ``--network`` argument and pass the " "name of the Docker network to run your SuperNodes." msgstr "" +"要测试在本地运行 Flower,可以创建一个 \"桥接网络 `__\"," +"使用\"--网络 \"参数并传递 Docker 网络的名称,以运行超级节点。" #: ../../source/how-to-run-flower-using-docker.rst:252 +#, fuzzy msgid "" "Any argument that comes after the tag is passed to the Flower SuperNode " "binary. To see all available flags that the SuperNode supports, run:" -msgstr "" +msgstr "标记后的任何参数都将传递给 Flower " +"超级节点二进制文件。要查看超级节点支持的所有可用标记,请运行" #: ../../source/how-to-run-flower-using-docker.rst:262 +#, fuzzy msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your SuperNode container." -msgstr "" +msgstr "要启用 SSL,我们需要将 PEM 编码的根证书挂载到 SuperNode 容器中。" #: ../../source/how-to-run-flower-using-docker.rst:264 #, fuzzy @@ -5652,44 +5805,58 @@ msgid "Flower ServerApp" msgstr "Flower 服务器。" #: ../../source/how-to-run-flower-using-docker.rst:277 +#, fuzzy msgid "" "The procedure for building and running a ServerApp image is almost " "identical to the SuperNode image." -msgstr "" +msgstr "构建和运行 ServerApp 映像的程序与 SuperNode 映像几乎完全相同。" #: ../../source/how-to-run-flower-using-docker.rst:279 +#, fuzzy msgid "" "Similar to the SuperNode image, the ServerApp Docker image comes with a " "pre-installed version of Flower and serves as a base for building your " "own ServerApp image." msgstr "" +"与 SuperNode 映像类似,ServerApp Docker 映像也预装了 Flower 版本," +"可作为构建自己的 ServerApp 映像的基础。" #: ../../source/how-to-run-flower-using-docker.rst:282 +#, fuzzy msgid "" "We will use the same ``quickstart-pytorch`` example as we do in the " "Flower SuperNode section. If you have not already done so, please follow " "the `SuperNode Prerequisites`_ before proceeding." msgstr "" +"我们将使用与 \"Flower SuperNode \"部分相同的 \"quickstart-pytorch \"示例" +"。如果您还没有这样做,请在继续之前遵循 \"SuperNode 先决条件\"。" #: ../../source/how-to-run-flower-using-docker.rst:287 +#, fuzzy msgid "Creating a ServerApp Dockerfile" -msgstr "" +msgstr "创建 ServerApp Dockerfile" #: ../../source/how-to-run-flower-using-docker.rst:298 +#, fuzzy msgid "" "First, we need to create a Dockerfile in the directory where the " "``ServerApp`` code is located. If you use the ``quickstart-pytorch`` " "example, create a new file called ``Dockerfile.serverapp`` in ``examples" "/quickstart-pytorch``." msgstr "" +"首先,我们需要在 ``ServerApp`` 代码所在的目录中创建一个 Dockerfile。如果使用 " +"``quickstart-pytorch`` 示例,请在 ``examples/quickstart-pytorch`` " +"中创建一个名为 ``Dockerfile.serverapp`` 的新文件。" #: ../../source/how-to-run-flower-using-docker.rst:302 +#, fuzzy msgid "" "The ``Dockerfile.serverapp`` contains the instructions that assemble the " "ServerApp image." -msgstr "" +msgstr "Dockerfile.serverapp \"包含组装 ServerApp 镜像的说明。" #: ../../source/how-to-run-flower-using-docker.rst:313 +#, fuzzy msgid "" "In the first two lines, we instruct Docker to use the ServerApp image " "tagged ``1.8.0`` as a base image and set our working directory to " @@ -5700,6 +5867,11 @@ msgid "" "ServerApp (``:``) that will be run inside the " "ServerApp container." msgstr "" +"在前两行中,我们指示 Docker 使用标记为 ``1.8.0`` 的 ServerApp " +"镜像作为基础镜像,并将工作目录设置为 ``/app``。下面的指令将在 ``/app`` " +"目录中执行。在最后两行中,我们将 ``server.py`` 模块复制到映像中," +"并将入口点设置为 ``flower-server-app``,参数为 ``server:app``。参数是将在 " +"ServerApp 容器内运行的 ServerApp 的对象引用(``<模块>:<属性>``)。" #: ../../source/how-to-run-flower-using-docker.rst:321 #, fuzzy @@ -5707,17 +5879,22 @@ msgid "Building the ServerApp Docker image" msgstr "启动服务器" #: ../../source/how-to-run-flower-using-docker.rst:323 +#, fuzzy msgid "" "Next, we build the ServerApp Docker image by running the following " "command in the directory where Dockerfile and ServerApp code are located." -msgstr "" +msgstr "接下来,我们在 Dockerfile 和 ServerApp 代码所在的目录下运行以下命令,构建 " +"ServerApp Docker 镜像。" #: ../../source/how-to-run-flower-using-docker.rst:330 +#, fuzzy msgid "" "We gave the image the name ``flwr_serverapp``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" +"我们给图片命名为 ``flwr_serverapp``,标签为 ``0.0." +"1``。请记住,这里选择的值只是一个示例。您可以根据自己的需要进行更改。" #: ../../source/how-to-run-flower-using-docker.rst:335 #, fuzzy @@ -5725,32 +5902,42 @@ msgid "Running the ServerApp Docker image" msgstr "启动服务器" #: ../../source/how-to-run-flower-using-docker.rst:337 +#, fuzzy msgid "Now that we have built the ServerApp image, we can finally run it." -msgstr "" +msgstr "现在我们已经构建了 ServerApp 镜像,终于可以运行它了。" #: ../../source/how-to-run-flower-using-docker.rst:349 +#, fuzzy msgid "``flwr_serverapp:0.0.1``: The name the tag of the Docker image to use." -msgstr "" +msgstr "flwr_serverapp:0.0.1``: 要使用的 Docker 映像的名称和标记。" #: ../../source/how-to-run-flower-using-docker.rst +#, fuzzy msgid "" "``--server 192.168.1.100:9091``: This option specifies the address of the" " SuperLinks Driver" -msgstr "" +msgstr "``--server 192.168.1.100:9091``: 此选项指定超级链接驱动程序的地址" #: ../../source/how-to-run-flower-using-docker.rst:363 +#, fuzzy msgid "" "To test running Flower locally, you can create a `bridge network " "`__, use the ``--network`` argument and pass the " "name of the Docker network to run your ServerApps." msgstr "" +"要测试在本地运行 Flower,可以创建一个 ``bridge network `___,使用 ``--network`` 参数并传递 Docker 网络的名称,以运行 " +"ServerApps。" #: ../../source/how-to-run-flower-using-docker.rst:367 +#, fuzzy msgid "" "Any argument that comes after the tag is passed to the Flower ServerApp " "binary. To see all available flags that the ServerApp supports, run:" -msgstr "" +msgstr "标记后的任何参数都将传递给 Flower ServerApp 二进制文件。要查看 ServerApp " +"支持的所有可用标记,请运行" #: ../../source/how-to-run-flower-using-docker.rst:377 #, fuzzy @@ -6715,6 +6902,7 @@ msgid "Upgrade to Flower Next" msgstr "升级至 Flower 1.0" #: ../../source/how-to-upgrade-to-flower-next.rst:4 +#, fuzzy msgid "" "Welcome to the migration guide for updating Flower to Flower Next! " "Whether you're a seasoned user or just getting started, this guide will " @@ -6722,18 +6910,27 @@ msgid "" " latest features and improvements in Flower Next, starting from version " "1.8." msgstr "" +"欢迎阅读从 Flower 升级到 Flower Next 的迁移指南!" +"无论您是经验丰富的用户还是刚刚开始使用 " +"Flower,本指南都将帮助您顺利过渡现有设置,以利用 Flower Next 从 1.8 " +"版开始的最新功能和改进。" #: ../../source/how-to-upgrade-to-flower-next.rst:9 +#, fuzzy msgid "" "This guide shows how to reuse pre-``1.8`` Flower code with minimum code " "changes by using the *compatibility layer* in Flower Next. In another " "guide, we will show how to run Flower Next end-to-end with pure Flower " "Next APIs." msgstr "" +"本指南展示了如何通过使用 Flower Next 中的*可兼容层*,以最小的代码改动重用```1" +".8```前的 Flower 代码。在另一个指南中,我们将介绍如何使用纯 Flower Next API " +"端到端运行 Flower Next。" #: ../../source/how-to-upgrade-to-flower-next.rst:13 +#, fuzzy msgid "Let's dive in!" -msgstr "" +msgstr "让我们深入了解一下!" #: ../../source/how-to-upgrade-to-flower-next.rst:48 #, fuzzy @@ -6748,14 +6945,16 @@ msgid "or if you need Flower Next with simulation:" msgstr "启动 Flower 模拟" #: ../../source/how-to-upgrade-to-flower-next.rst:61 +#, fuzzy msgid "" "Ensure you set the following version constraint in your " "``requirements.txt``" -msgstr "" +msgstr "确保在 ``requirements.txt`` 中设置了以下版本限制" #: ../../source/how-to-upgrade-to-flower-next.rst:71 +#, fuzzy msgid "or ``pyproject.toml``:" -msgstr "" +msgstr "或 ``pyproject.toml```:" #: ../../source/how-to-upgrade-to-flower-next.rst:82 #, fuzzy @@ -6780,6 +6979,7 @@ msgid "" msgstr "将 ``pyproject.toml`` 中的次要版本增加一个。" #: ../../source/how-to-upgrade-to-flower-next.rst:102 +#, fuzzy msgid "" "In Flower Next, the *infrastructure* and *application layers* have been " "decoupled. Instead of starting a client in code via ``start_client()``, " @@ -6791,6 +6991,12 @@ msgid "" "to run your project both in the traditional way and in the Flower Next " "way:" msgstr "" +"在 Flower Next 中,*基础架构层*和*应用层*已经解耦。你不再需要在代码中通过``st" +"art_client()``启动客户端,而是创建一个|clientapp_link|_,然后通过命令行启动它" +"。无需通过``start_server()``在代码中启动服务器,而是创建一个 |serverapp_link|" +"_ 并通过命令行启动它。服务器和客户端的长期运行组件被称为超级链接(SuperLink)" +"和超级节点(SuperNode)。以下是无需手动更新的非破坏性更改," +"可让您以传统方式和 Flower Next 方式运行项目:" #: ../../source/how-to-upgrade-to-flower-next.rst:109 #, fuzzy @@ -6798,10 +7004,12 @@ msgid "|clientapp_link|_" msgstr "客户端" #: ../../source/how-to-upgrade-to-flower-next.rst:110 +#, fuzzy msgid "" "Wrap your existing client with |clientapp_link|_ instead of launching it " "via |startclient_link|_. Here's an example:" -msgstr "" +msgstr "用 |clientapp_link|_ 封装现有客户端,而不是通过 |startclient_link|_ " +"启动。下面是一个例子:" #: ../../source/how-to-upgrade-to-flower-next.rst:132 #, fuzzy @@ -6809,35 +7017,45 @@ msgid "|serverapp_link|_" msgstr "服务器" #: ../../source/how-to-upgrade-to-flower-next.rst:133 +#, fuzzy msgid "" "Wrap your existing strategy with |serverapp_link|_ instead of starting " "the server via |startserver_link|_. Here's an example:" -msgstr "" +msgstr "用 |serverapp_link|_ 包住现有策略,而不是通过 |startserver_link|_ " +"启动服务器。下面是一个例子:" #: ../../source/how-to-upgrade-to-flower-next.rst:154 +#, fuzzy msgid "Deployment" -msgstr "" +msgstr "调配" #: ../../source/how-to-upgrade-to-flower-next.rst:155 +#, fuzzy msgid "" "Run the ``SuperLink`` using |flowernext_superlink_link|_ before running, " "in sequence, |flowernext_clientapp_link|_ (2x) and " "|flowernext_serverapp_link|_. There is no need to execute `client.py` and" " `server.py` as Python scripts." msgstr "" +"在依次运行 |flowernext_clientapp_link|_ (2x) 和 |flowernext_serverapp_link|_ " +"之前,使用 |flowernext_superlink_link|_ 运行 ``SuperLink`` 。无需将 |client." +"py` 和 `server.py` 作为 Python 脚本执行。" #: ../../source/how-to-upgrade-to-flower-next.rst:158 +#, fuzzy msgid "" "Here's an example to start the server without HTTPS (only for " "prototyping):" -msgstr "" +msgstr "下面是一个在不使用 HTTPS 的情况下启动服务器的示例(仅用于原型开发):" #: ../../source/how-to-upgrade-to-flower-next.rst:174 +#, fuzzy msgid "" "Here's another example to start with HTTPS. Use the ``--certificates`` " "command line argument to pass paths to (CA certificate, server " "certificate, and server private key)." -msgstr "" +msgstr "下面是另一个使用 HTTPS 的示例。使用 ``--certificates`` 命令行参数传递路径(" +"CA 证书、服务器证书和服务器私钥)。" #: ../../source/how-to-upgrade-to-flower-next.rst:201 #, fuzzy @@ -6845,36 +7063,48 @@ msgid "Simulation in CLI" msgstr "运行模拟" #: ../../source/how-to-upgrade-to-flower-next.rst:202 +#, fuzzy msgid "" "Wrap your existing client and strategy with |clientapp_link|_ and " "|serverapp_link|_, respectively. There is no need to use |startsim_link|_" " anymore. Here's an example:" msgstr "" +"分别用 |clientapp_link|_ 和 |serverapp_link|_ 封装现有的客户端和策略。" +"无需再使用 |startsim_link|_。下面是一个示例:" #: ../../source/how-to-upgrade-to-flower-next.rst:232 +#, fuzzy msgid "" "Run |flower_simulation_link|_ in CLI and point to the ``server_app`` / " "``client_app`` object in the code instead of executing the Python script." " Here's an example (assuming the ``server_app`` and ``client_app`` " "objects are in a ``sim.py`` module):" msgstr "" +"在 CLI 中运行 |flower_simulation_link|_ 并指向代码中的 ``server_app`` " +"/``client_app`` 对象,而不是执行 Python 脚本。下面是一个示例(假定 " +"`server_app`` 和 `client_app`` 对象位于 `sim.py`` 模块中):" #: ../../source/how-to-upgrade-to-flower-next.rst:249 +#, fuzzy msgid "" "Set default resources for each |clientapp_link|_ using the ``--backend-" "config`` command line argument instead of setting the " "``client_resources`` argument in |startsim_link|_. Here's an example:" msgstr "" +"使用 ``--backend-config`` 命令行参数为每个 |clientapp_link|_ 设置默认资源," +"而不是在 |startsim_link|_ 中设置 ``client_resources`` 参数。下面是一个例子:" #: ../../source/how-to-upgrade-to-flower-next.rst:275 +#, fuzzy msgid "Simulation in a Notebook" -msgstr "" +msgstr "笔记本中的模拟" #: ../../source/how-to-upgrade-to-flower-next.rst:276 +#, fuzzy msgid "" "Run |runsim_link|_ in your notebook instead of |startsim_link|_. Here's " "an example:" -msgstr "" +msgstr "在笔记本中运行 |runsim_link|_,而不是 |startsim_link|_。下面是一个例子:" #: ../../source/how-to-upgrade-to-flower-next.rst:319 #, fuzzy @@ -6897,15 +7127,18 @@ msgid "Important" msgstr "重要变更:" #: ../../source/how-to-upgrade-to-flower-next.rst:328 +#, fuzzy msgid "" "As we continuously enhance Flower Next at a rapid pace, we'll be " "periodically updating this guide. Please feel free to share any feedback " "with us!" -msgstr "" +msgstr "随着 Flower Next " +"的不断快速改进,我们将定期更新本指南。如有任何反馈,请随时与我们分享!" #: ../../source/how-to-upgrade-to-flower-next.rst:334 +#, fuzzy msgid "Happy migrating! 🚀" -msgstr "" +msgstr "移民愉快!🚀" #: ../../source/how-to-use-built-in-mods.rst:2 #, fuzzy @@ -9316,8 +9549,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.encode:1 of +#, fuzzy msgid "Encode the string using the codec registered for encoding." -msgstr "" +msgstr "使用注册的编码解码器对字符串进行编码。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9328,8 +9562,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.replace:1 of +#, fuzzy msgid "Return a copy with all occurrences of substring old replaced by new." -msgstr "" +msgstr "返回用 new 替换子串 old 的所有出现次数的副本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9340,10 +9575,11 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rsplit:1 flwr.common.EventType.split:1 of +#, fuzzy msgid "" "Return a list of the substrings in the string, using sep as the separator" " string." -msgstr "" +msgstr "使用 sep 作为分隔符,返回字符串中的子字符串列表。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9359,8 +9595,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.join:1 of +#, fuzzy msgid "Concatenate any number of strings." -msgstr "" +msgstr "连接任意数量的字符串。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9369,8 +9606,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.capitalize:1 of +#, fuzzy msgid "Return a capitalized version of the string." -msgstr "" +msgstr "返回字符串的大写版本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9379,8 +9617,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.casefold:1 of +#, fuzzy msgid "Return a version of the string suitable for caseless comparisons." -msgstr "" +msgstr "返回适合无例比较的字符串版本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9389,8 +9628,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.title:1 of +#, fuzzy msgid "Return a version of the string where each word is titlecased." -msgstr "" +msgstr "返回字符串的版本,其中每个单词都使用了标题大小写。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9413,10 +9653,11 @@ msgid "" msgstr ":py:obj:`Context `\\ \\(state\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 +#, fuzzy msgid "" "Return the number of non-overlapping occurrences of substring sub in " "string S[start:end]." -msgstr "" +msgstr "返回子字符串 sub 在字符串 S[start:end] 中非重叠出现的次数。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9427,8 +9668,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.expandtabs:1 of +#, fuzzy msgid "Return a copy where all tab characters are expanded using spaces." -msgstr "" +msgstr "返回使用空格扩展所有制表符的副本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9438,10 +9680,11 @@ msgid "" msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 +#, fuzzy msgid "" "Return the lowest index in S where substring sub is found, such that sub " "is contained within S[start:end]." -msgstr "" +msgstr "返回在 S 中找到子串 sub 的最低索引,且 sub 包含在 S[start:end] 中。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9450,8 +9693,9 @@ msgstr ":py:obj:`partition_id `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.partition:1 flwr.common.EventType.rpartition:1 of +#, fuzzy msgid "Partition the string into three parts using the given separator." -msgstr "" +msgstr "使用给定的分隔符将字符串分为三部分。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9469,8 +9713,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.ljust:1 of +#, fuzzy msgid "Return a left-justified string of length width." -msgstr "" +msgstr "返回长度为 width 的左对齐字符串。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9479,8 +9724,9 @@ msgstr ":py:obj:`now `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.lower:1 of +#, fuzzy msgid "Return a copy of the string converted to lowercase." -msgstr "" +msgstr "返回转换为小写的字符串副本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9489,8 +9735,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.lstrip:1 of +#, fuzzy msgid "Return a copy of the string with leading whitespace removed." -msgstr "" +msgstr "返回去掉前导空白的字符串副本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9500,10 +9747,11 @@ msgid "" msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 +#, fuzzy msgid "" "Return the highest index in S where substring sub is found, such that sub" " is contained within S[start:end]." -msgstr "" +msgstr "返回在 S 中找到子串 sub 的最高索引,且 sub 包含在 S[start:end] 中。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9521,8 +9769,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rjust:1 of +#, fuzzy msgid "Return a right-justified string of length width." -msgstr "" +msgstr "返回长度为 width 的右对齐字符串。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9531,8 +9780,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rstrip:1 of +#, fuzzy msgid "Return a copy of the string with trailing whitespace removed." -msgstr "" +msgstr "返回去掉尾部空白的字符串副本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9548,8 +9798,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.splitlines:1 of +#, fuzzy msgid "Return a list of the lines in the string, breaking at line boundaries." -msgstr "" +msgstr "返回字符串中的行列表,以行为分界线。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9558,8 +9809,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.strip:1 of +#, fuzzy msgid "Return a copy of the string with leading and trailing whitespace removed." -msgstr "" +msgstr "返回去掉前导和尾部空白的字符串副本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9568,10 +9820,11 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.swapcase:1 of +#, fuzzy msgid "" "Convert uppercase characters to lowercase and lowercase characters to " "uppercase." -msgstr "" +msgstr "将大写字母转换为小写字母,将小写字母转换为大写字母。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9580,8 +9833,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.translate:1 of +#, fuzzy msgid "Replace each character in the string using the given translation table." -msgstr "" +msgstr "使用给定的翻译表替换字符串中的每个字符。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9590,8 +9844,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.upper:1 of +#, fuzzy msgid "Return a copy of the string converted to uppercase." -msgstr "" +msgstr "返回转换为大写字符串的副本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9601,8 +9856,9 @@ msgid "" msgstr ":py:obj:`Status `\\ \\(code\\, message\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 +#, fuzzy msgid "Return True if S starts with the specified prefix, False otherwise." -msgstr "" +msgstr "如果 S 以指定前缀开头,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9612,8 +9868,9 @@ msgid "" msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 +#, fuzzy msgid "Return True if S ends with the specified suffix, False otherwise." -msgstr "" +msgstr "如果 S 以指定后缀结束,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9624,8 +9881,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.removeprefix:1 of +#, fuzzy msgid "Return a str with the given prefix string removed if present." -msgstr "" +msgstr "返回一个字符串,如果存在,则去掉给定的前缀字符串。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9636,8 +9894,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.removesuffix:1 of +#, fuzzy msgid "Return a str with the given suffix string removed if present." -msgstr "" +msgstr "返回一个字符串,如果存在给定的后缀字符串,则将其删除。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9646,8 +9905,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isascii:1 of +#, fuzzy msgid "Return True if all characters in the string are ASCII, False otherwise." -msgstr "" +msgstr "如果字符串中的所有字符都是 ASCII 码,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9656,8 +9916,9 @@ msgstr ":py:obj:`now `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.islower:1 of +#, fuzzy msgid "Return True if the string is a lowercase string, False otherwise." -msgstr "" +msgstr "如果字符串是小写字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9666,8 +9927,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isupper:1 of +#, fuzzy msgid "Return True if the string is an uppercase string, False otherwise." -msgstr "" +msgstr "如果字符串是大写字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9676,8 +9938,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.istitle:1 of +#, fuzzy msgid "Return True if the string is a title-cased string, False otherwise." -msgstr "" +msgstr "如果字符串是带标题的字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9686,8 +9949,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isspace:1 of +#, fuzzy msgid "Return True if the string is a whitespace string, False otherwise." -msgstr "" +msgstr "如果字符串是空白字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9696,8 +9960,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isdecimal:1 of +#, fuzzy msgid "Return True if the string is a decimal string, False otherwise." -msgstr "" +msgstr "如果字符串是十进制字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9706,8 +9971,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isdigit:1 of +#, fuzzy msgid "Return True if the string is a digit string, False otherwise." -msgstr "" +msgstr "如果字符串是数字字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9716,8 +9982,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isnumeric:1 of +#, fuzzy msgid "Return True if the string is a numeric string, False otherwise." -msgstr "" +msgstr "如果字符串是数字字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9726,8 +9993,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isalpha:1 of +#, fuzzy msgid "Return True if the string is an alphabetic string, False otherwise." -msgstr "" +msgstr "如果字符串是字母字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9736,8 +10004,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isalnum:1 of +#, fuzzy msgid "Return True if the string is an alpha-numeric string, False otherwise." -msgstr "" +msgstr "如果字符串是字母数字字符串,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9746,8 +10015,9 @@ msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isidentifier:1 of +#, fuzzy msgid "Return True if the string is a valid Python identifier, False otherwise." -msgstr "" +msgstr "如果字符串是有效的 Python 标识符,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9756,8 +10026,9 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isprintable:1 of +#, fuzzy msgid "Return True if the string is printable, False otherwise." -msgstr "" +msgstr "如果字符串可打印,则返回 True,否则返回 False。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9766,10 +10037,11 @@ msgstr ":py:obj:`PING `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.zfill:1 of +#, fuzzy msgid "" "Pad a numeric string with zeros on the left, to fill a field of the given" " width." -msgstr "" +msgstr "在数字字符串左侧填充零,以填满给定宽度的字段。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9781,8 +10053,9 @@ msgstr "" "\\*\\*kwargs\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 +#, fuzzy msgid "Return a formatted version of S, using substitutions from args and kwargs." -msgstr "" +msgstr "使用 args 和 kwargs 的替换,返回 S 的格式化版本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9790,8 +10063,9 @@ msgid ":py:obj:`format_map `\\ \\(mapping\\)" msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 +#, fuzzy msgid "Return a formatted version of S, using substitutions from mapping." -msgstr "" +msgstr "使用映射中的替换,返回 S 的格式化版本。" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #, fuzzy @@ -9800,8 +10074,9 @@ msgstr ":py:obj:`TRAIN `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.maketrans:1 of +#, fuzzy msgid "Return a translation table usable for str.translate()." -msgstr "" +msgstr "返回可用于 str.translate() 的翻译表。" #: flwr.common.EventType.capitalize:1::1 of #, fuzzy @@ -9975,30 +10250,36 @@ msgstr "" "`\\" #: flwr.common.EventType.capitalize:3 of +#, fuzzy msgid "" "More specifically, make the first character have upper case and the rest " "lower case." -msgstr "" +msgstr "更具体地说,让第一个字符大写,其余字符小写。" #: flwr.common.EventType.center:3 flwr.common.EventType.ljust:3 #: flwr.common.EventType.rjust:3 of +#, fuzzy msgid "Padding is done using the specified fill character (default is a space)." -msgstr "" +msgstr "使用指定的填充字符(默认为空格)进行填充。" #: flwr.common.EventType.count:1 of +#, fuzzy msgid "" "Return the number of non-overlapping occurrences of substring sub in " "string S[start:end]. Optional arguments start and end are interpreted as" " in slice notation." -msgstr "" +msgstr "返回子串 sub 在字符串 S[start:end] 中非重叠出现的次数。 可选参数 start 和 " +"end 按切分符号解释。" #: flwr.common.EventType.encode:3 of +#, fuzzy msgid "encoding" -msgstr "" +msgstr "编码" #: flwr.common.EventType.encode:4 of +#, fuzzy msgid "The encoding in which to encode the string." -msgstr "" +msgstr "字符串的编码。" #: flwr.common.EventType.encode:9 of #, fuzzy @@ -10006,6 +10287,7 @@ msgid "errors" msgstr "错误" #: flwr.common.EventType.encode:6 of +#, fuzzy msgid "" "The error handling scheme to use for encoding errors. The default is " "'strict' meaning that encoding errors raise a UnicodeEncodeError. Other " @@ -10013,134 +10295,177 @@ msgid "" "as any other name registered with codecs.register_error that can handle " "UnicodeEncodeErrors." msgstr "" +"编码错误的错误处理方案。默认值为 \"strict\",即编码错误会引发 " +"UnicodeEncodeError。 其他可能的值包括 \"ignore\"、\"replace \"和 " +"\"xmlcharrefreplace\",以及通过 codecs.register_error 注册的、可处理 " +"UnicodeEncodeErrror 的其他名称。" #: flwr.common.EventType.endswith:1 of +#, fuzzy msgid "" "Return True if S ends with the specified suffix, False otherwise. With " "optional start, test S beginning at that position. With optional end, " "stop comparing S at that position. suffix can also be a tuple of strings " "to try." msgstr "" +"如果 S 以指定后缀结束,则返回 True,否则返回 False。如果起始位置可选," +"则从该位置开始测试 S。如果使用可选的 end,则在该位置停止比较 " +"S。后缀也可以是要尝试的字符串元组。" #: flwr.common.EventType.expandtabs:3 of +#, fuzzy msgid "If tabsize is not given, a tab size of 8 characters is assumed." -msgstr "" +msgstr "如果未给出制表符大小,则假定制表符大小为 8 个字符。" #: flwr.common.EventType.find:1 flwr.common.EventType.index:1 of +#, fuzzy msgid "" "Return the lowest index in S where substring sub is found, such that sub " "is contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" +"返回在 S 中找到子串 sub 的最低索引,即 sub 包含在 S[start:end] 中。 可选参数 " +"start 和 end 按切分符号解释。" #: flwr.common.EventType.find:5 flwr.common.EventType.rfind:5 of +#, fuzzy msgid "Return -1 on failure." -msgstr "" +msgstr "失败时返回-1。" #: flwr.common.EventType.format:1 of +#, fuzzy msgid "" "Return a formatted version of S, using substitutions from args and " "kwargs. The substitutions are identified by braces ('{' and '}')." -msgstr "" +msgstr "使用来自 args 和 kwargs 的替换,返回 S " +"的格式化版本。替换用大括号('{'和'}')标识。" #: flwr.common.EventType.format_map:1 of +#, fuzzy msgid "" "Return a formatted version of S, using substitutions from mapping. The " "substitutions are identified by braces ('{' and '}')." -msgstr "" +msgstr "使用映射中的替换,返回 S 的格式化版本。替换用大括号('{'和'}')标识。" #: flwr.common.EventType.index:5 flwr.common.EventType.rindex:5 of +#, fuzzy msgid "Raises ValueError when the substring is not found." -msgstr "" +msgstr "如果未找到子串,则引发 ValueError。" #: flwr.common.EventType.isalnum:3 of +#, fuzzy msgid "" "A string is alpha-numeric if all characters in the string are alpha-" "numeric and there is at least one character in the string." -msgstr "" +msgstr "如果字符串中的所有字符都是字母数字,且字符串中至少有一个字符,则该字符串为字" +"母数字字符串。" #: flwr.common.EventType.isalpha:3 of +#, fuzzy msgid "" "A string is alphabetic if all characters in the string are alphabetic and" " there is at least one character in the string." -msgstr "" +msgstr "如果字符串中的所有字符都是字母,并且字符串中至少有一个字符,那么该字符串就是" +"字母字符串。" #: flwr.common.EventType.isascii:3 of +#, fuzzy msgid "" "ASCII characters have code points in the range U+0000-U+007F. Empty " "string is ASCII too." -msgstr "" +msgstr "ASCII 字符的码位范围为 U+0000-U+007F。空字符串也是 ASCII 字符。" #: flwr.common.EventType.isdecimal:3 of +#, fuzzy msgid "" "A string is a decimal string if all characters in the string are decimal " "and there is at least one character in the string." -msgstr "" +msgstr "如果字符串中的所有字符都是十进制,并且字符串中至少有一个字符是十进制,那么该" +"字符串就是十进制字符串。" #: flwr.common.EventType.isdigit:3 of +#, fuzzy msgid "" "A string is a digit string if all characters in the string are digits and" " there is at least one character in the string." -msgstr "" +msgstr "如果字符串中的所有字符都是数字,并且字符串中至少有一个字符,那么该字符串就是" +"数字字符串。" #: flwr.common.EventType.isidentifier:3 of +#, fuzzy msgid "" "Call keyword.iskeyword(s) to test whether string s is a reserved " "identifier, such as \"def\" or \"class\"." -msgstr "" +msgstr "调用 keyword.iskeyword(s) 测试字符串 s 是否为保留标识符,如 \"def \"或 " +"\"class\"。" #: flwr.common.EventType.islower:3 of +#, fuzzy msgid "" "A string is lowercase if all cased characters in the string are lowercase" " and there is at least one cased character in the string." -msgstr "" +msgstr "如果字符串中的所有大小写字符都是小写,且字符串中至少有一个大小写字符,则该字" +"符串为小写字符串。" #: flwr.common.EventType.isnumeric:3 of +#, fuzzy msgid "" "A string is numeric if all characters in the string are numeric and there" " is at least one character in the string." -msgstr "" +msgstr "如果字符串中的所有字符都是数字,且字符串中至少有一个字符,则该字符串为数字字" +"符串。" #: flwr.common.EventType.isprintable:3 of +#, fuzzy msgid "" "A string is printable if all of its characters are considered printable " "in repr() or if it is empty." -msgstr "" +msgstr "如果字符串的所有字符在 repr() " +"中都被认为是可打印的,或者字符串为空,那么该字符串就是可打印的。" #: flwr.common.EventType.isspace:3 of +#, fuzzy msgid "" "A string is whitespace if all characters in the string are whitespace and" " there is at least one character in the string." -msgstr "" +msgstr "如果字符串中的所有字符都是空格,且字符串中至少有一个字符,则该字符串为空格。" #: flwr.common.EventType.istitle:3 of +#, fuzzy msgid "" "In a title-cased string, upper- and title-case characters may only follow" " uncased characters and lowercase characters only cased ones." -msgstr "" +msgstr "在标题大小写字符串中,大写和标题大小写字符只能跟在无大小写字符之后,小写字符" +"只能跟在有大小写字符之后。" #: flwr.common.EventType.isupper:3 of +#, fuzzy msgid "" "A string is uppercase if all cased characters in the string are uppercase" " and there is at least one cased character in the string." -msgstr "" +msgstr "如果字符串中所有带大小写的字符都是大写,并且字符串中至少有一个带大小写的字符" +",则该字符串为大写字符串。" #: flwr.common.EventType.join:3 of +#, fuzzy msgid "" "The string whose method is called is inserted in between each given " "string. The result is returned as a new string." -msgstr "" +msgstr "方法被调用的字符串会被插入每个给定的字符串之间。结果将以新字符串的形式返回。" #: flwr.common.EventType.join:6 of +#, fuzzy msgid "Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'" -msgstr "" +msgstr "示例:'.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'" #: flwr.common.EventType.lstrip:3 flwr.common.EventType.rstrip:3 #: flwr.common.EventType.strip:3 of +#, fuzzy msgid "If chars is given and not None, remove characters in chars instead." -msgstr "" +msgstr "如果给定的是 chars 而不是 None,则删除 chars 中的字符。" #: flwr.common.EventType.maketrans:3 of +#, fuzzy msgid "" "If there is only one argument, it must be a dictionary mapping Unicode " "ordinals (integers) or characters to Unicode ordinals, strings or None. " @@ -10150,32 +10475,43 @@ msgid "" "same position in y. If there is a third argument, it must be a string, " "whose characters will be mapped to None in the result." msgstr "" +"如果只有一个参数,则必须是一个将 Unicode 序号(整数)或字符映射到 Unicode " +"序号、字符串或 None 的字典。字符键将被转换为序号。如果有两个参数,它们必须是" +"长度相等的字符串,在生成的字典中,x 中的每个字符将被映射到 y " +"中相同位置的字符。" #: flwr.common.EventType.partition:3 of +#, fuzzy msgid "" "This will search for the separator in the string. If the separator is " "found, returns a 3-tuple containing the part before the separator, the " "separator itself, and the part after it." -msgstr "" +msgstr "它会在字符串中搜索分隔符。 如果找到分隔符,则返回一个包含分隔符前部分、" +"分隔符本身和分隔符后部分的 3 元组。" #: flwr.common.EventType.partition:7 of +#, fuzzy msgid "" "If the separator is not found, returns a 3-tuple containing the original " "string and two empty strings." -msgstr "" +msgstr "如果找不到分隔符,则返回一个包含原始字符串和两个空字符串的 3 元组。" #: flwr.common.EventType.removeprefix:3 of +#, fuzzy msgid "" "If the string starts with the prefix string, return string[len(prefix):]." " Otherwise, return a copy of the original string." -msgstr "" +msgstr "如果字符串以前缀字符串开始,则返回 " +"string[len(prefix):]。否则,返回原始字符串的副本。" #: flwr.common.EventType.removesuffix:3 of +#, fuzzy msgid "" "If the string ends with the suffix string and that suffix is not empty, " "return string[:-len(suffix)]. Otherwise, return a copy of the original " "string." -msgstr "" +msgstr "如果字符串以后缀字符串结尾,且后缀不为空,则返回 " +"string[:-len(suffix)]。否则,返回原始字符串的副本。" #: flwr.common.EventType.replace:5 of #, fuzzy @@ -10183,92 +10519,115 @@ msgid "count" msgstr "背景" #: flwr.common.EventType.replace:4 of +#, fuzzy msgid "" "Maximum number of occurrences to replace. -1 (the default value) means " "replace all occurrences." -msgstr "" +msgstr "要替换的最大出现次数。-1(默认值)表示替换所有出现次数。" #: flwr.common.EventType.replace:7 of +#, fuzzy msgid "" "If the optional argument count is given, only the first count occurrences" " are replaced." -msgstr "" +msgstr "如果给出可选参数 count,则只替换第一个计数出现的次数。" #: flwr.common.EventType.rfind:1 flwr.common.EventType.rindex:1 of +#, fuzzy msgid "" "Return the highest index in S where substring sub is found, such that sub" " is contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" +"返回在 S 中找到子串 sub 且 sub 包含在 S[start:end] 中的最高索引。 可选参数 " +"start 和 end 按切分符号解释。" #: flwr.common.EventType.rpartition:3 of +#, fuzzy msgid "" "This will search for the separator in the string, starting at the end. If" " the separator is found, returns a 3-tuple containing the part before the" " separator, the separator itself, and the part after it." -msgstr "" +msgstr "它会从字符串的末尾开始搜索分隔符。如果找到分隔符,则返回一个包含分隔符前部分" +"、分隔符本身和分隔符后部分的 3 元组。" #: flwr.common.EventType.rpartition:7 of +#, fuzzy msgid "" "If the separator is not found, returns a 3-tuple containing two empty " "strings and the original string." -msgstr "" +msgstr "如果找不到分隔符,则返回一个包含两个空字符串和原始字符串的 3 元组。" #: flwr.common.EventType.rsplit:7 flwr.common.EventType.split:7 of +#, fuzzy msgid "sep" -msgstr "" +msgstr "sep" #: flwr.common.EventType.rsplit:4 flwr.common.EventType.split:4 of +#, fuzzy msgid "The separator used to split the string." -msgstr "" +msgstr "用于分割字符串的分隔符。" #: flwr.common.EventType.rsplit:6 flwr.common.EventType.split:6 of +#, fuzzy msgid "" "When set to None (the default value), will split on any whitespace " "character (including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard" " empty strings from the result." msgstr "" +"当设置为 \"无\"(默认值)时,将对任何空白字符(包括 \\n" +" \\r \\t \\f 和空格)进行分割,并从结果中剔除空字符串。" #: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of +#, fuzzy msgid "maxsplit" -msgstr "" +msgstr "最大分割" #: flwr.common.EventType.rsplit:10 flwr.common.EventType.split:10 of +#, fuzzy msgid "" "Maximum number of splits (starting from the left). -1 (the default value)" " means no limit." -msgstr "" +msgstr "最大分割次数(从左边开始)。-1(默认值)表示没有限制。" #: flwr.common.EventType.rsplit:13 of +#, fuzzy msgid "Splitting starts at the end of the string and works to the front." -msgstr "" +msgstr "从琴弦末端开始分弦,一直到琴弦前端。" #: flwr.common.EventType.split:13 of +#, fuzzy msgid "" "Note, str.split() is mainly useful for data that has been intentionally " "delimited. With natural text that includes punctuation, consider using " "the regular expression module." -msgstr "" +msgstr "注意,str.split() 主要适用于有意分隔的数据。 " +"对于包含标点符号的自然文本,可以考虑使用正则表达式模块。" #: flwr.common.EventType.splitlines:3 of +#, fuzzy msgid "" "Line breaks are not included in the resulting list unless keepends is " "given and true." -msgstr "" +msgstr "除非指定 keepends 为 true,否则换行符不会包含在生成的列表中。" #: flwr.common.EventType.startswith:1 of +#, fuzzy msgid "" "Return True if S starts with the specified prefix, False otherwise. With " "optional start, test S beginning at that position. With optional end, " "stop comparing S at that position. prefix can also be a tuple of strings " "to try." msgstr "" +"如果 S 以指定的前缀开始,则返回 True,否则返回 False。如果选择 start," +"则从该位置开始测试 S。如果使用可选的 end,则在该位置停止比较 S。" #: flwr.common.EventType.title:3 of +#, fuzzy msgid "" "More specifically, words start with uppercased characters and all " "remaining cased characters have lower case." -msgstr "" +msgstr "更具体地说,单词以大写字母开头,其余所有大小写字符均为小写。" #: flwr.common.EventType.translate:5 of #, fuzzy @@ -10276,21 +10635,26 @@ msgid "table" msgstr "数据库" #: flwr.common.EventType.translate:4 of +#, fuzzy msgid "" "Translation table, which must be a mapping of Unicode ordinals to Unicode" " ordinals, strings, or None." -msgstr "" +msgstr "翻译表,必须是 Unicode 序号到 Unicode 序号、字符串或无的映射。" #: flwr.common.EventType.translate:7 of +#, fuzzy msgid "" "The table must implement lookup/indexing via __getitem__, for instance a " "dictionary or list. If this operation raises LookupError, the character " "is left untouched. Characters mapped to None are deleted." msgstr "" +"表必须通过 __getitem__ 实现查找/索引,例如字典或列表。 如果该操作引发 " +"LookupError,该字符将保持不变。 映射为 None 的字符将被删除。" #: flwr.common.EventType.zfill:3 of +#, fuzzy msgid "The string is never truncated." -msgstr "" +msgstr "字符串不会被截断。" #: ../../source/ref-api/flwr.common.FitIns.rst:2 #, fuzzy @@ -10492,25 +10856,32 @@ msgstr "遇到的错误。" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of +#, fuzzy msgid "" "Time-to-live for this message in seconds. If unset, it will be set based " "on the remaining time for the received message before it expires. This " "follows the equation: ttl = msg.meta.ttl - (reply.meta.created_at - " "msg.meta.created_at)" msgstr "" +"该信息的有效时间(秒)。如果未设置,则将根据收到的信息过期前的剩余时间来设置" +"。其计算公式为:ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta." +"created_at)" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of +#, fuzzy msgid "" "Time-to-live for this message in seconds. If unset, it will be set based " "on the remaining time for the received message before it expires. This " "follows the equation:" -msgstr "" +msgstr "该信息的有效时间(秒)。如果未设置,则将根据接收到的信息过期前的剩余时间来设" +"置。其计算公式如下" #: flwr.common.message.Message.create_error_reply:9 #: flwr.common.message.Message.create_reply:13 of +#, fuzzy msgid "ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)" -msgstr "" +msgstr "ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)" #: flwr.common.message.Message.create_reply:3 of #, fuzzy @@ -10631,8 +11002,9 @@ msgstr ":py:obj:`ttl `\\" #: flwr.common.Metadata.created_at:1 #: flwr.common.Metadata.created_at:1::1 of +#, fuzzy msgid "Unix timestamp when the message was created." -msgstr "" +msgstr "创建信息时的 Unix 时间戳。" #: flwr.common.Metadata.created_at:1::1 of #, fuzzy @@ -15720,8 +16092,9 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.unmask_stage:1 #: of +#, fuzzy msgid "Execute the 'unmask' stage." -msgstr "" +msgstr "执行 \"解除屏蔽 \"阶段。" #: ../../source/ref-api/flwr.server.workflow.SecAggWorkflow.rst:2 #, fuzzy @@ -15729,12 +16102,16 @@ msgid "SecAggWorkflow" msgstr "工作流程" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:1 of +#, fuzzy msgid "" "Bases: " ":py:class:`~flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow`" msgstr "" +"基础: :py:class:`~flwr.server.workflow.secure_aggregation." +"secaggplus_workflow.SecAggPlusWorkflow`." #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:3 of +#, fuzzy msgid "" "The SecAgg protocol ensures the secure summation of integer vectors owned" " by multiple parties, without accessing any individual integer vector. " @@ -15748,27 +16125,40 @@ msgid "" "('parameters') from the client's `FitRes`. The server then aggregates " "these contributions to compute the weighted average of model parameters." msgstr "" +"SecAgg 协议可确保对多方拥有的整数向量进行安全求和,而不会访问任何单个整数向量" +"。该工作流程允许服务器计算所有客户端模型参数的加权平均值,确保个人贡献保持私" +"密。这可以通过客户端同时发送加权因子和本地更新参数的加权版本来实现,为了保护" +"隐私,两者都会被屏蔽。具体来说,每个客户端都会上传带掩码的\"[w, w * params]\"" +",其中加权因子 \"w \"是示例数(\"num_examples\"),\"params \"代表客户端 " +"\"FitRes \"中的模型参数(\"parameters\"" +")。然后,服务器会汇总这些贡献,计算模型参数的加权平均值。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:14 of +#, fuzzy msgid "" "The protocol involves four main stages: - 'setup': Send SecAgg " "configuration to clients and collect their public keys. - 'share keys': " "Broadcast public keys among clients and collect encrypted secret" msgstr "" +"协议包括四个主要阶段: - 设置\": 向客户端发送 SecAgg 配置并收集它们的公钥。-" +" 共享密钥\": 在客户端之间广播公钥并收集加密密钥。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:54 of +#, fuzzy msgid "" "Each client's private key is split into N shares under the SecAgg " "protocol, where N is the number of selected clients." -msgstr "" +msgstr "根据 SecAgg 协议,每个客户的私人密钥被分成 N 份,其中 N 是所选客户的数量。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:56 of +#, fuzzy msgid "" "Generally, higher `reconstruction_threshold` means better privacy " "guarantees but less tolerance to dropouts." -msgstr "" +msgstr "一般来说,\"重建阈值 \"越高,隐私保证就越好,但对丢包的容忍度就越低。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:60 of +#, fuzzy msgid "" "When `reconstruction_threshold` is a float, it is interpreted as the " "proportion of the number of all selected clients needed for the " @@ -15776,45 +16166,62 @@ msgid "" "setting the security threshold relative to the number of selected " "clients." msgstr "" +"当 `reconstruction_threshold` 为浮点数时,它被解释为重建私钥所需的所有选定客" +"户端数量的比例。此功能可根据所选客户端的数量灵活设置安全阈值。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:64 of +#, fuzzy msgid "" "`reconstruction_threshold`, and the quantization parameters " "(`clipping_range`, `quantization_range`, `modulus_range`) play critical " "roles in balancing privacy, robustness, and efficiency within the SecAgg " "protocol." msgstr "" +"重构阈值 \"和量化参数(\"裁剪范围\"、\"量化范围\"、\"模量范围\")在 SecAgg " +"协议中平衡隐私性、鲁棒性和效率方面起着至关重要的作用。" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of +#, fuzzy msgid "" ":py:obj:`collect_masked_vectors_stage " "`\\ " "\\(driver\\, ...\\)" msgstr "" +":py:obj:`collect_masked_vectors_stage `\\(driver\\, ...\\)" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of +#, fuzzy msgid "" ":py:obj:`setup_stage `\\" " \\(driver\\, context\\, state\\)" msgstr "" +":py:obj:`setup_stage `\\(" +"driver\\, context\\, state\\)" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of +#, fuzzy msgid "" ":py:obj:`share_keys_stage " "`\\ \\(driver\\, " "context\\, state\\)" msgstr "" +"py:obj:`share_keys_stage `\\(driver\\, context\\, state\\)" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of +#, fuzzy msgid "" ":py:obj:`unmask_stage " "`\\ \\(driver\\, " "context\\, state\\)" msgstr "" +":py:obj:`unmask_stage `\\ " +"\\(driver\\, context\\, state\\)" #: ../../source/ref-api/flwr.simulation.rst:2 #, fuzzy @@ -15822,10 +16229,13 @@ msgid "simulation" msgstr "运行模拟" #: ../../source/ref-api/flwr.simulation.rst:18::1 +#, fuzzy msgid "" ":py:obj:`start_simulation `\\ \\(\\*\\," " client\\_fn\\[\\, ...\\]\\)" msgstr "" +":py:obj:`start_simulation `\\ \\(\\*\\, " +"client\\_fn\\[\\, ...\\]\\)" #: ../../source/ref-api/flwr.simulation.rst:18::1 #: flwr.simulation.app.start_simulation:1 of @@ -15833,15 +16243,19 @@ msgid "Start a Ray-based Flower simulation server." msgstr "启动基于 Ray 的Flower模拟服务器。" #: ../../source/ref-api/flwr.simulation.rst:18::1 +#, fuzzy msgid "" ":py:obj:`run_simulation `\\ " "\\(server\\_app\\, client\\_app\\, ...\\)" msgstr "" +":py:obj:`run_simulation `\\ \\(server\\_app\\" +", client\\_app\\, ...\\)" #: ../../source/ref-api/flwr.simulation.rst:18::1 #: flwr.simulation.run_simulation.run_simulation:1 of +#, fuzzy msgid "Run a Flower App using the Simulation Engine." -msgstr "" +msgstr "使用模拟引擎运行花朵应用程序。" #: ../../source/ref-api/flwr.simulation.run_simulation.rst:2 #, fuzzy @@ -15849,36 +16263,46 @@ msgid "run\\_simulation" msgstr "运行模拟" #: flwr.simulation.run_simulation.run_simulation:3 of +#, fuzzy msgid "" "The `ServerApp` to be executed. It will send messages to different " "`ClientApp` instances running on different (virtual) SuperNodes." -msgstr "" +msgstr "要执行的 `ServerApp`。它将向运行在不同(虚拟)超级节点上的不同 " +"`ClientApp`实例发送消息。" #: flwr.simulation.run_simulation.run_simulation:6 of +#, fuzzy msgid "" "The `ClientApp` to be executed by each of the SuperNodes. It will receive" " messages sent by the `ServerApp`." -msgstr "" +msgstr "由每个超级节点执行的 `ClientApp`。它将接收由 `ServerApp` 发送的信息。" #: flwr.simulation.run_simulation.run_simulation:9 of +#, fuzzy msgid "" "Number of nodes that run a ClientApp. They can be sampled by a Driver in " "the ServerApp and receive a Message describing what the ClientApp should " "perform." -msgstr "" +msgstr "运行 ClientApp 的节点数。它们可被 ServerApp 中的驱动程序采样,并接收描述 " +"ClientApp 应执行的操作的信息。" #: flwr.simulation.run_simulation.run_simulation:13 of +#, fuzzy msgid "A simulation backend that runs `ClientApp`s." -msgstr "" +msgstr "运行 \"客户端应用程序 \"的模拟后台。" #: flwr.simulation.run_simulation.run_simulation:15 of +#, fuzzy msgid "" "'A dictionary, e.g {\"\": , \"\": } to " "configure a backend. Values supported in are those included by " "`flwr.common.typing.ConfigsRecordValues`." msgstr "" +"字典,例如 {\"\": , \"\": } 来配置后端。 " +"中支持的值是 `flwr.common.typing.ConfigsRecordValues`中包含的值。" #: flwr.simulation.run_simulation.run_simulation:19 of +#, fuzzy msgid "" "A boolean to indicate whether to enable GPU growth on the main thread. " "This is desirable if you make use of a TensorFlow model on your " @@ -15888,12 +16312,20 @@ msgid "" "`tf.config.experimental.set_memory_growth()` works in the TensorFlow " "documentation: https://www.tensorflow.org/api/stable." msgstr "" +"布尔值,用于指示是否在主线程上启用 GPU 增长。如果您在 \"ServerApp \"上使用 " +"TensorFlow 模型,同时让 \"ClientApp \"在同一 GPU " +"上运行,则最好启用此选项。如果不启用此功能,您可能会遇到内存不足的错误,因为 " +"TensorFlow 默认会分配所有 GPU 内存。有关 `tf.config.experimental." +"set_memory_growth()` 如何工作的更多信息,请参阅 TensorFlow 文档:https://www." +"tensorflow.org/api/stable。" #: flwr.simulation.run_simulation.run_simulation:26 of +#, fuzzy msgid "" "When diabled, only INFO, WARNING and ERROR log messages will be shown. If" " enabled, DEBUG-level logs will be displayed." -msgstr "" +msgstr "启用后,将只显示 INFO、WARNING 和 ERROR 日志信息。启用后,将显示 DEBUG " +"级日志。" #: ../../source/ref-api/flwr.simulation.start_simulation.rst:2 #, fuzzy @@ -16109,6 +16541,7 @@ msgstr "" "TOKEN_v1.5.0-->" #: ../../source/ref-changelog.md:21 +#, fuzzy msgid "" "**Introduce Flower Next high-level API (stable)** " "([#3002](https://github.com/adap/flower/pull/3002), " @@ -16137,8 +16570,27 @@ msgid "" "[#3195](https://github.com/adap/flower/pull/3195), " "[#3197](https://github.com/adap/flower/pull/3197))" msgstr "" +"**介绍 Flower Next 高级应用程序接口(稳定版)** ([#3002](https://github.com/" +"adap/flower/pull/3002), [#2934](https://github.com/adap/flower/pull/2934), " +"[#2958](https://github.com/adap/flower/pull/2958), [#3173](https://github." +"com/adap/flower/pull/3173), [#3174](https://github.com/adap/flower/pull/3174)" +", [#2923](https://github.com/adap/flower/pull/2923), [#2691](https://github." +"com/adap/flower/pull/2691), [#3079](https://github.com/adap/flower/pull/3079)" +", [#2961](https://github.com/adap/flower/pull/2961), [#2924](https://github." +"com/adap/flower/pull/2924), [#3166](https://github.com/adap/flower/pull/3166)" +", [#3031](https://github.com/adap/flower/pull/3031), [#3057](https://github." +"com/adap/flower/pull/3057), [#3000](https://github.com/adap/flower/pull/3000)" +", [#3113](https://github.com/adap/flower/pull/3113), [#2957](https://github." +"com/adap/flower/pull/2957), [#3183](https://github.com/adap/flower/pull/3183)" +", [#3180](https://github.com/adap/flower/pull/3180), [#3035](https://github." +"com/adap/flower/pull/3035), [#3189](https://github.com/adap/flower/pull/3189)" +", [#3185](https://github.com/adap/flower/pull/3185), [#3190](https://github." +"com/adap/flower/pull/3190), [#3191](https://github.com/adap/flower/pull/3191)" +", [#3195](https://github.com/adap/flower/pull/3195), [#3197](https://github." +"com/adap/flower/pull/3197))" #: ../../source/ref-changelog.md:23 +#, fuzzy msgid "" "The Flower Next high-level API is stable! Flower Next is the future of " "Flower - all new features (like Flower Mods) will be built on top of it. " @@ -16151,6 +16603,13 @@ msgid "" " line of code. The best part? It's fully compatible with existing Flower " "projects that use `Strategy`, `NumPyClient` & co." msgstr "" +"Flower Next 高级应用程序接口已经稳定!Flower Next 是 Flower 的未来 - " +"所有新功能(如 Flower Mods)都将构建在它之上。您可以使用 `ServerApp` 和 " +"`ClientApp` 开始将现有项目迁移到 Flower Next(请查看 `quickstart-pytorch` 或 " +"`quickstart-tensorflow` ,详细的迁移指南将在不久后发布)。Flower Next 允许您" +"同时运行多个项目(我们称之为多重运行),并在模拟环境或部署环境中执行同一项目" +",而无需更改任何代码。最棒的是什么?它与使用 `Strategy`、`NumPyClient` " +"等的现有 Flower 项目完全兼容。" #: ../../source/ref-changelog.md:25 #, fuzzy @@ -16165,6 +16624,7 @@ msgstr "" "[#2493](https://github.com/adap/flower/pull/2493))" #: ../../source/ref-changelog.md:27 +#, fuzzy msgid "" "In addition to the Flower Next *high-level* API that uses `Strategy`, " "`NumPyClient` & co, Flower 1.8 also comes with a preview version of the " @@ -16180,6 +16640,15 @@ msgid "" "metrics, stateful computations on the client node and implementations of " "custom SMPC protocols, to name just a few." msgstr "" +"除了使用 \"Strategy\"、\"NumPyClient \"等的 Flower Next 高级应用程序接口外," +"Flower 1.8 还提供了新的 Flower Next 低级应用程序接口的预览版。低级应用程序接" +"口允许通过向/从客户端节点发送/接收单个消息,对学习过程的各个方面进行细粒度控" +"制。新的 \"ServerApp \"支持注册一个自定义的 \"main \"函数" +",允许为异步FL、循环训练或联合分析等方法编写自定义训练循环。新的 \"ClientApp " +"\"支持注册 \"训练\"、\"评估 \"和 \"查询 \"函数,这些函数可以访问从 " +"\"ServerApp \"接收到的原始信息。新的抽象(如 \"RecordSet\"、\"Message \"和 " +"\"Context\")进一步支持发送多个模型、多套配置值和指标、" +"客户端节点上的有状态计算以及自定义 SMPC 协议的实现等。" #: ../../source/ref-changelog.md:29 #, fuzzy @@ -16194,6 +16663,7 @@ msgstr "" "[#2248](https://github.com/adap/flower/pull/2248))" #: ../../source/ref-changelog.md:31 +#, fuzzy msgid "" "Flower Modifiers (we call them Mods) can intercept messages and analyze, " "edit or handle them directly. Mods can be used to develop pluggable " @@ -16204,6 +16674,11 @@ msgid "" "SecAgg+. The Flower Mods API is released as a preview, but researchers " "can already use it to experiment with arbirtrary SMPC protocols." msgstr "" +"Flower Modifiers(我们称之为 Mods)可以拦截信息,并直接对其进行分析、编辑或处" +"理。修改器可用于开发可在不同项目中使用的可插拔模块。Flower 1.8 已经包含了记录" +"信息大小、通过网络发送的参数数量、固定剪切和自适应剪切的差分隐私、" +"本地差分隐私以及安全聚合协议 SecAgg 和 SecAgg+ 的 Mods。Flower Mods API " +"作为预览版发布,但研究人员已经可以用它来试验任意的 SMPC 协议。" #: ../../source/ref-changelog.md:33 #, fuzzy @@ -16225,6 +16700,7 @@ msgstr "" "[#1475](https://github.com/adap/flower/pull/1475)))" #: ../../source/ref-changelog.md:35 +#, fuzzy msgid "" "We are introducing LLM FlowerTune, an introductory example that " "demonstrates federated LLM fine-tuning of pre-trained Llama2 models on " @@ -16233,6 +16709,12 @@ msgid "" "Federated LLM Fine-tuning with Flower](https://flower.ai/blog/2024-03-14" "-llm-flowertune-federated-llm-finetuning-with-flower/) for more details." msgstr "" +"我们将介绍 LLM FlowerTune,这是一个介绍性示例,演示了在 Alpaca-GPT4 " +"数据集上对预先训练好的 Llama2 模型进行联合 LLM " +"微调。该示例可轻松调整以使用不同的模型和/或数据集。请阅读我们的博文 [LLM " +"FlowerTune: Federated LLM Fine-tuning with Flower](https://flower.ai/blog/" +"2024-03-14-llm-flowertune-federated-llm-finetuning-with-flower/) " +"了解更多详情。" #: ../../source/ref-changelog.md:37 #, fuzzy @@ -16256,6 +16738,7 @@ msgstr "" "[#994](https://github.com/adap/flower/pull/994))" #: ../../source/ref-changelog.md:39 +#, fuzzy msgid "" "Built-in Differential Privacy is here! Flower supports both central and " "local differential privacy (DP). Central DP can be configured with either" @@ -16267,6 +16750,13 @@ msgid "" "the new Differential Privacy components](https://flower.ai/docs/framework" "/how-to-use-differential-privacy.html) in Flower." msgstr "" +"内置差分保密功能!Flower 支持中央和本地差分保密 " +"(DP)。中央差分隐私可配置为固定或自适应剪切。剪切可以发生在服务器端或客户端。" +"本地 DP " +"在客户端进行剪切和噪声处理。新的文档页面[解释差分隐私方法](https://flower.ai/" +"docs/framework/explanation-differential-privacy.html) " +"和新的操作指南[如何使用新的差分隐私组件](https://flower.ai/docs/framework/" +"how-to-use-differential-privacy.html) 介绍了 Flower 的使用方法。" #: ../../source/ref-changelog.md:41 #, fuzzy @@ -16281,6 +16771,7 @@ msgstr "" "[#2248](https://github.com/adap/flower/pull/2248))" #: ../../source/ref-changelog.md:43 +#, fuzzy msgid "" "Built-in Secure Aggregation is here! Flower now supports different secure" " aggregation protocols out-of-the-box. The best part? You can add secure " @@ -16292,6 +16783,12 @@ msgid "" "combine Federated Learning, Differential Privacy and Secure Aggregation " "in the same project." msgstr "" +"内置安全聚合功能!Flower " +"现在支持不同的安全聚合协议。最棒的是什么?只需几行代码," +"您就可以将安全聚合添加到 Flower 项目中。在这个初始版本中,我们包含了对 " +"SecAgg 和 SecAgg+ 的支持,但更多协议将很快实现。我们还将添加详细的文档," +"解释安全聚合以及如何在 Flower 中使用它。您可以查看新的代码示例,了解如何使用 " +"Flower 在同一项目中轻松结合联合学习、差分隐私和安全聚合。" #: ../../source/ref-changelog.md:45 #, fuzzy @@ -16315,10 +16812,12 @@ msgstr "" "[#2171](https://github.com/adap/flower/pull/2171))" #: ../../source/ref-changelog.md:47 +#, fuzzy msgid "" "A new `flwr` CLI command allows creating new Flower projects (`flwr new`)" " and then running them using the Simulation Engine (`flwr run`)." -msgstr "" +msgstr "新的 `flwr` CLI 命令允许创建新的 Flower 项目(`flwr " +"new`),然后使用仿真引擎运行它们(`flwr run`)。" #: ../../source/ref-changelog.md:49 #, fuzzy @@ -16344,11 +16843,14 @@ msgstr "" "[#1733](https://github.com/adap/flower/pull/1733))" #: ../../source/ref-changelog.md:51 +#, fuzzy msgid "" "The Flower Simulation Engine can now run Flower Next projects. For " "notebook environments, there's also a new `run_simulation` function that " "can run `ServerApp` and `ClientApp`." msgstr "" +"Flower 模拟引擎现在可以运行 Flower Next 项目。对于笔记本环境,还有一个新的 " +"`run_simulation` 函数,可以运行 `ServerApp` 和 `ClientApp`。" #: ../../source/ref-changelog.md:53 #, fuzzy @@ -16358,6 +16860,7 @@ msgid "" msgstr "** 添加一个新的 gRPC 选项**([#2197](https://github.com/adap/flower/pull/2197))" #: ../../source/ref-changelog.md:55 +#, fuzzy msgid "" "A SuperNode will now try to reconnect indefinitely to the SuperLink in " "case of connection errors. The arguments `--max-retries` and `--max-wait-" @@ -16367,6 +16870,10 @@ msgid "" "wait-time` defines the time before the SuperNode gives up trying to " "reconnect to the SuperLink." msgstr "" +"如果出现连接错误,超级节点现在会尝试无限期地重新连接超级链接。现在可以向 " +"`flower-client-app` 命令传递参数 `-ax-retries` 和 `-max-wait-time`。" +"最大重试次数 \"将定义客户端在放弃重新连接超级链接之前的重试次数,而 " +"\"最大等待时间 \"则定义超级节点放弃重新连接超级链接之前的等待时间。" #: ../../source/ref-changelog.md:57 #, fuzzy @@ -16384,12 +16891,16 @@ msgstr "" "[#1679](https://github.com/adap/flower/pull/1679)" #: ../../source/ref-changelog.md:59 +#, fuzzy msgid "" "There's a new [FedStar](https://flower.ai/docs/baselines/fedstar.html) " "baseline. Several other baselined have been updated as well." msgstr "" +"有一条新的 [FedStar](https://flower.ai/docs/baselines/fedstar.html) " +"基准线。其他几条基准线也已更新。" #: ../../source/ref-changelog.md:61 +#, fuzzy msgid "" "**Improve documentation and translations** " "([#3050](https://github.com/adap/flower/pull/3050), " @@ -16409,13 +16920,29 @@ msgid "" "[#2990](https://github.com/adap/flower/pull/2990), " "[#2989](https://github.com/adap/flower/pull/2989))" msgstr "" +"**改进文件和翻译** ([#3050](https://github.com/adap/flower/pull/3050), " +"[#3044](https://github.com/adap/flower/pull/3044), [#3043](https://github." +"com/adap/flower/pull/3043), [#2986](https://github.com/adap/flower/pull/2986)" +", [#3041](https://github.com/adap/flower/pull/3041), [#3046](https://github." +"com/adap/flower/pull/3046), [#3042](https://github.com/adap/flower/pull/3042)" +", [#2978](https://github.com/adap/flower/pull/2978), [#2952](https://github." +"com/adap/flower/pull/2952), [#3167](https://github.com/adap/flower/pull/3167)" +", [#2953](https://github.com/adap/flower/pull/2953), [#3045](https://github." +"com/adap/flower/pull/3045), [#2654](https://github.com/adap/flower/pull/2654)" +", [#3082](https://github.com/adap/flower/pull/3082), [#2990](https://github." +"com/adap/flower/pull/2990), [#2989](https://github.com/adap/flower/pull/" +"2989))" #: ../../source/ref-changelog.md:63 +#, fuzzy msgid "" "As usual, we merged many smaller and larger improvements to the " "documentation. A special thank you goes to [Sebastian van der " "Voort](https://github.com/svdvoort) for landing a big documentation PR!" msgstr "" +"像往常一样,我们合并了许多对文档的较大和较小的改进。特别要感谢 [Sebastian " +"van der Voort](https://github.com/svdvoort),他为我们带来了一份重要的文档 " +"PR!" #: ../../source/ref-changelog.md:65 #, fuzzy @@ -16444,6 +16971,7 @@ msgstr "" "[#1515](https://github.com/adap/flower/pull/1515))" #: ../../source/ref-changelog.md:67 +#, fuzzy msgid "" "Two new examples show federated training of a Vision Transformer (ViT) " "and federated learning in a medical context using the popular MONAI " @@ -16451,8 +16979,13 @@ msgid "" " new Flower Next `ServerApp` and `ClientApp`. Many other examples " "received considerable updates as well." msgstr "" +"两个新示例展示了视觉转换器(ViT)的联合训练,以及使用流行的 MONAI " +"库在医疗环境中进行的联合学习。quickstart-pytorch \"和 \"quickstart-" +"tensorflow \"展示了新的 Flower Next \"ServerApp \"和 \"ClientApp\"" +"。许多其他示例也得到了大量更新。" #: ../../source/ref-changelog.md:69 +#, fuzzy msgid "" "**General improvements** " "([#3171](https://github.com/adap/flower/pull/3171), " @@ -16529,6 +17062,56 @@ msgid "" "[#2955](https://github.com/adap/flower/pull/2955), " "[#2954](https://github.com/adap/flower/pull/2954))" msgstr "" +"**一般改进**([#3171](https://github.com/adap/flower/pull/3171), " +"[3099](https://github.com/adap/flower/pull/3099), [3003](https://github.com/" +"adap/flower/pull/3003), [3145](https://github.com/adap/flower/pull/3145), " +"[3017](https://github.com/adap/flower/pull/3017), [3085](https://github.com/" +"adap/flower/pull/3085), [3012](https://github.com/adap/flower/pull/3012), " +"[3119](https://github.com/adap/flower/pull/3119), [2991](https://github.com/" +"adap/flower/pull/2991), [2970](https://github.com/adap/flower/pull/2970), " +"[2980](https://github.com/adap/flower/pull/2980), [3086](https://github.com/" +"adap/flower/pull/3086), [2932](https://github.com/adap/flower/pull/2932), " +"[2928](https://github.com/adap/flower/pull/2928), [2941](https://github.com/" +"adap/flower/pull/2941), [2933](https://github.com/adap/flower/pull/2933), " +"[3181](https://github.com/adap/flower/pull/3181), [2973](https://github.com/" +"adap/flower/pull/2973), [2992](https://github.com/adap/flower/pull/2992), " +"[2915](https://github.com/adap/flower/pull/2915), [3040](https://github.com/" +"adap/flower/pull/3040), [3022](https://github.com/adap/flower/pull/3022), " +"[3032](https://github.com/adap/flower/pull/3032), [2902](https://github.com/" +"adap/flower/pull/2902), [2931](https://github.com/adap/flower/pull/2931), " +"[3005](https://github.com/adap/flower/pull/3005), [3132](https://github.com/" +"adap/flower/pull/3132), [3115](https://github.com/adap/flower/pull/3115), " +"[2944](https://github.com/adap/flower/pull/2944), [3064](https://github.com/" +"adap/flower/pull/3064), [3106](https://github.com/adap/flower/pull/3106), " +"[2974](https://github.com/adap/flower/pull/2974), [3178](https://github.com/" +"adap/flower/pull/3178), [2993](https://github.com/adap/flower/pull/2993), " +"[3186](https://github.com/adap/flower/pull/3186), [3091](https://github.com/" +"adap/flower/pull/3091), [3125](https://github.com/adap/flower/pull/3125), " +"[3093](https://github.com/adap/flower/pull/3093), [3013](https://github.com/" +"adap/flower/pull/3013), [3033](https://github.com/adap/flower/pull/3033), " +"[3133](https://github.com/adap/flower/pull/3133), [3068](https://github.com/" +"adap/flower/pull/3068), [2916](https://github.com/adap/flower/pull/2916), " +"[2975](https://github.com/adap/flower/pull/2975), [2984](https://github.com/" +"adap/flower/pull/2984), [2846](https://github.com/adap/flower/pull/2846), " +"[3077](https://github.com/adap/flower/pull/3077), [3143](https://github.com/" +"adap/flower/pull/3143), [2921](https://github.com/adap/flower/pull/2921), " +"[3101](https://github.com/adap/flower/pull/3101), [2927](https://github.com/" +"adap/flower/pull/2927), [2995](https://github.com/adap/flower/pull/2995), " +"[2972](https://github.com/adap/flower/pull/2972), [2912](https://github.com/" +"adap/flower/pull/2912), [3065](https://github.com/adap/flower/pull/3065), " +"[3028](https://github.com/adap/flower/pull/3028), [2922](https://github.com/" +"adap/flower/pull/2922), [2982](https://github.com/adap/flower/pull/2982), " +"[2914](https://github.com/adap/flower/pull/2914), [3179](https://github.com/" +"adap/flower/pull/3179), [3080](https://github.com/adap/flower/pull/3080), " +"[2994](https://github.com/adap/flower/pull/2994), [3187](https://github.com/" +"adap/flower/pull/3187), [2926](https://github.com/adap/flower/pull/2926), " +"[3018](https://github.com/adap/flower/pull/3018), [3144](https://github.com/" +"adap/flower/pull/3144), [3011](https://github.com/adap/flower/pull/3011), " +"[#3152](https://github.com/adap/flower/pull/3152), [#2836](https://github." +"com/adap/flower/pull/2836), [#2929](https://github.com/adap/flower/pull/2929)" +", [#2943](https://github.com/adap/flower/pull/2943), [#2955](https://github." +"com/adap/flower/pull/2955), [#2954](https://github.com/adap/flower/pull/" +"2954))" #: ../../source/ref-changelog.md:75 #, fuzzy @@ -16568,6 +17151,7 @@ msgstr "" "[#2435](https://github.com/adap/flower/pull/2435))" #: ../../source/ref-changelog.md:87 +#, fuzzy msgid "" "Subclasses of `Client` and `NumPyClient` can now store local state that " "remains on the client. Let's start with the highlight first: this new " @@ -16579,6 +17163,12 @@ msgid "" "different rounds of execution to enable stateful computations in a " "unified way across simulation and deployment." msgstr "" +"客户端 \"和 \"NumPyClient \"的子类现在可以存储保留在客户端上的本地状态" +"。让我们先从亮点开始:这一新功能与模拟客户端(通过 " +"`start_simulation`)和网络客户端(通过 `start_client`)兼容。这也是 `Context`" +" 和 `RecordSet` 等新抽象的首次预览。客户端可以通过 `state.RecordSet` 访问 " +"`RecordSet` 类型的状态: RecordSet = self.context.state`。对该 `RecordSet` " +"的更改会在不同轮执行中保留,以便在模拟和部署中以统一的方式进行有状态计算。" #: ../../source/ref-changelog.md:89 #, fuzzy @@ -16588,6 +17178,7 @@ msgid "" msgstr "**改进示例笔记** ([#2005](https://github.com/adap/flower/pull/2005))" #: ../../source/ref-changelog.md:91 +#, fuzzy msgid "" "Flower is faster than ever. All `FedAvg`-derived strategies now use in-" "place aggregation to reduce memory consumption. The Flower client " @@ -16595,6 +17186,9 @@ msgid "" "which results in significant speedups, especially when the client-side " "training time is short." msgstr "" +"Flower 的速度比以往更快。所有源于 `FedAvg` " +"的策略现在都使用就地聚合,以减少内存消耗。Flower 客户端序列化/解序列化已从头" +"开始重写,从而显著提高了速度,尤其是在客户端训练时间较短的情况下。" #: ../../source/ref-changelog.md:93 #, fuzzy @@ -16606,11 +17200,14 @@ msgstr "" "([#1598](https://github.com/adap/flower/pull/1598))" #: ../../source/ref-changelog.md:95 +#, fuzzy msgid "" "Flower has official support for federated learning using [Apple " "MLX](https://ml-explore.github.io/mlx) via the new `quickstart-mlx` code " "example." msgstr "" +"通过新的 `quickstart-mlx` 代码示例,Flower 正式支持使用 [Apple MLX](https" +"://ml-explore.github.io/mlx)的联合学习。" #: ../../source/ref-changelog.md:97 #, fuzzy @@ -16623,6 +17220,7 @@ msgstr "" "[#1764](https://github.com/adap/flower/pull/1764))" #: ../../source/ref-changelog.md:99 +#, fuzzy msgid "" "A new strategy called `FedXgbCyclic` supports a client-by-client style of" " training (often called cyclic). The `xgboost-comprehensive` code example" @@ -16630,6 +17228,10 @@ msgid "" "comprehensive` now also supports simulation mode. With this, Flower " "offers best-in-class XGBoost support." msgstr "" +"名为 `FedXgbCyclic` 的新策略支持逐个客户端的训练风格(通常称为循环" +")。xgboost-comprehensive \"代码示例展示了如何在一个完整的项目中使用它" +"。除此之外,`xgboost-comprehensive` 现在还支持模拟模式。由此,Flower " +"提供了同类最佳的 XGBoost 支持。" #: ../../source/ref-changelog.md:101 #, fuzzy @@ -16639,10 +17241,12 @@ msgid "" msgstr "** 支持 Python 3.10** ([#1320](https://github.com/adap/flower/pull/1320))" #: ../../source/ref-changelog.md:103 +#, fuzzy msgid "" "Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will " "ensure better support for users using more recent Python versions." -msgstr "" +msgstr "框架测试现在可在 Python 3.8、3.9、3.10 和 3.11 上运行。这将确保为使用最新 " +"Python 版本的用户提供更好的支持。" #: ../../source/ref-changelog.md:105 #, fuzzy @@ -16654,10 +17258,11 @@ msgstr "" "([#2283](https://github.com/adap/flower/pull/2283))" #: ../../source/ref-changelog.md:107 +#, fuzzy msgid "" "The `grpcio` and `protobuf` dependencies were updated to their latest " "versions for improved security and performance." -msgstr "" +msgstr "为提高安全性和性能,\"grpcio \"和 \"protobuf \"依赖项已更新至最新版本。" #: ../../source/ref-changelog.md:109 #, fuzzy @@ -16681,12 +17286,16 @@ msgstr "" "[#994](https://github.com/adap/flower/pull/994))" #: ../../source/ref-changelog.md:111 +#, fuzzy msgid "" "The Flower server can now be run using an official Docker image. A new " "how-to guide explains [how to run Flower using " "Docker](https://flower.ai/docs/framework/how-to-run-flower-using-" "docker.html). An official Flower client Docker image will follow." msgstr "" +"现在可以使用官方 Docker 映像运行 Flower 服务器了。新的操作指南介绍了 [" +"如何使用 Docker 运行 Flower](https://flower.ai/docs/framework/how-to-run-" +"flower-using-docker.html)。Flower 客户端 Docker 官方镜像将随后发布。" #: ../../source/ref-changelog.md:113 #, fuzzy @@ -16726,10 +17335,12 @@ msgstr "" "[#1310](https://github.com/adap/flower/pull/1310)" #: ../../source/ref-changelog.md:121 +#, fuzzy msgid "" "Several code examples were updated to use [Flower " "Datasets](https://flower.ai/docs/datasets/)." -msgstr "" +msgstr "更新了多个代码示例,以使用 [Flower Datasets](https://flower.ai/docs/datasets/" +") 。" #: ../../source/ref-changelog.md:123 #, fuzzy @@ -16757,8 +17368,9 @@ msgstr "" "[#1794](https://github.com/adap/flower/pull/1794))" #: ../../source/ref-changelog.md:125 +#, fuzzy msgid "Many Flower code examples received substantial updates." -msgstr "" +msgstr "许多 \"Flower \"代码示例得到了大幅更新。" #: ../../source/ref-changelog.md:127 ../../source/ref-changelog.md:220 msgid "**Update Flower Baselines**" @@ -16818,6 +17430,7 @@ msgstr "" "[#1614](https://github.com/adap/flower/pull/1614)))" #: ../../source/ref-changelog.md:138 +#, fuzzy msgid "" "**Improved testing and development infrastructure** " "([#2797](https://github.com/adap/flower/pull/2797), " @@ -16850,12 +17463,34 @@ msgid "" "[#2661](https://github.com/adap/flower/pull/2661), " "[#2398](https://github.com/adap/flower/pull/2398))" msgstr "" +"**改进测试和开发基础设施** ([#2797](https://github.com/adap/flower/pull/2797)" +", [#2676](https://github.com/adap/flower/pull/2676), [#2644](https://github." +"com/adap/flower/pull/2644), [#2656](https://github.com/adap/flower/pull/2656)" +", [#2848](https://github.com/adap/flower/pull/2848), [#2675](https://github." +"com/adap/flower/pull/2675), [#2735](https://github.com/adap/flower/pull/2735)" +", [#2767](https://github.com/adap/flower/pull/2767), [#2732](https://github." +"com/adap/flower/pull/2732), [#2744](https://github.com/adap/flower/pull/2744)" +", [#2681](https://github.com/adap/flower/pull/2681), [#2699](https://github." +"com/adap/flower/pull/2699), [#2745](https://github.com/adap/flower/pull/2745)" +", [#2734](https://github.com/adap/flower/pull/2734), [#2731](https://github." +"com/adap/flower/pull/2731), [#2652](https://github.com/adap/flower/pull/2652)" +", [#2720](https://github.com/adap/flower/pull/2720), [#2721](https://github." +"com/adap/flower/pull/2721), [#2717](https://github.com/adap/flower/pull/2717)" +", [#2864](https://github.com/adap/flower/pull/2864), [#2694](https://github." +"com/adap/flower/pull/2694), [#2709](https://github.com/adap/flower/pull/2709)" +", [#2658](https://github.com/adap/flower/pull/2658), [#2796](https://github." +"com/adap/flower/pull/2796), [#2692](https://github.com/adap/flower/pull/2692)" +", [#2657](https://github.com/adap/flower/pull/2657), [#2813](https://github." +"com/adap/flower/pull/2813), [#2661](https://github.com/adap/flower/pull/2661)" +", [#2398](https://github.com/adap/flower/pull/2398))" #: ../../source/ref-changelog.md:140 +#, fuzzy msgid "" "The Flower testing and development infrastructure has received " "substantial updates. This makes Flower 1.7 the most tested release ever." -msgstr "" +msgstr "Flower 测试和开发基础架构已得到大幅更新。这使得 Flower 1.7 " +"成为有史以来经过最多测试的版本。" #: ../../source/ref-changelog.md:142 #, fuzzy @@ -16894,6 +17529,7 @@ msgstr "" "[#2183](https://github.com/adap/flower/pull/2183))" #: ../../source/ref-changelog.md:144 +#, fuzzy msgid "" "**General improvements** " "([#2803](https://github.com/adap/flower/pull/2803), " @@ -16933,6 +17569,31 @@ msgid "" "[#2672](https://github.com/adap/flower/pull/2672), " "[#2759](https://github.com/adap/flower/pull/2759))" msgstr "" +"**一般改进** ([#2803](https://github.com/adap/flower/pull/2803), " +"[#2847](https://github.com/adap/flower/pull/2847), [#2877](https://github." +"com/adap/flower/pull/2877), [#2690](https://github.com/adap/flower/pull/2690)" +", [#2889](https://github.com/adap/flower/pull/2889), [#2874](https://github." +"com/adap/flower/pull/2874), [#2819](https://github.com/adap/flower/pull/2819)" +", [#2689](https://github.com/adap/flower/pull/2689), [#2457](https://github." +"com/adap/flower/pull/2457), [#2870](https://github.com/adap/flower/pull/2870)" +", [#2669](https://github.com/adap/flower/pull/2669), [#2876](https://github." +"com/adap/flower/pull/2876), [#2885](https://github.com/adap/flower/pull/2885)" +", [#2858](https://github.com/adap/flower/pull/2858), [#2867](https://github." +"com/adap/flower/pull/2867), [#2351](https://github.com/adap/flower/pull/2351)" +", [#2886](https://github.com/adap/flower/pull/2886), [#2860](https://github." +"com/adap/flower/pull/2860), [#2828](https://github.com/adap/flower/pull/2828)" +", [#2869](https://github.com/adap/flower/pull/2869), [#2875](https://github." +"com/adap/flower/pull/2875), [#2733](https://github.com/adap/flower/pull/2733)" +", [#2488](https://github.com/adap/flower/pull/2488), [#2646](https://github." +"com/adap/flower/pull/2646), [#2879](https://github.com/adap/flower/pull/2879)" +", [#2821](https://github.com/adap/flower/pull/2821), [#2855](https://github." +"com/adap/flower/pull/2855), [#2800](https://github.com/adap/flower/pull/2800)" +", [#2807](https://github.com/adap/flower/pull/2807), [#2801](https://github." +"com/adap/flower/pull/2801), [#2804](https://github.com/adap/flower/pull/2804)" +", [#2851](https://github.com/adap/flower/pull/2851), [#2787](https://github." +"com/adap/flower/pull/2787), [#2852](https://github.com/adap/flower/pull/2852)" +", [#2672](https://github.com/adap/flower/pull/2672), [#2759](https://github." +"com/adap/flower/pull/2759))" #: ../../source/ref-changelog.md:148 #, fuzzy @@ -16945,6 +17606,7 @@ msgstr "" "[#2508](https://github.com/adap/flower/pull/2508))" #: ../../source/ref-changelog.md:150 +#, fuzzy msgid "" "Until now, clients of type `NumPyClient` needed to be started via " "`start_numpy_client`. In our efforts to consolidate framework APIs, we " @@ -16954,6 +17616,11 @@ msgid "" "object to `start_client`. The examples and the documentation have been " "updated accordingly." msgstr "" +"到目前为止,\"NumPyClient \"类型的客户端需要通过 \"start_numpy_client \"启动" +"。为了整合框架 API,我们引入了一些变化,现在所有客户端类型都应通过 " +"`start_client` 启动。要继续使用 `NumPyClient` 客户端,只需首先调用 `." +"to_client()` 方法,然后将返回的 `Client` 对象传递给 " +"`start_client`。示例和文档已相应更新。" #: ../../source/ref-changelog.md:152 #, fuzzy @@ -16963,11 +17630,13 @@ msgid "" msgstr "**移除过时的 KerasClient**([#857](https://github.com/adap/flower/pull/857))" #: ../../source/ref-changelog.md:154 +#, fuzzy msgid "" "Legacy DP wrapper classes are deprecated, but still functional. This is " "in preparation for an all-new pluggable version of differential privacy " "support in Flower." -msgstr "" +msgstr "传统的 DP 封装类已废弃,但仍可正常使用。这是为 Flower " +"中的全新可插拔差分隐私支持版本做准备。" #: ../../source/ref-changelog.md:156 #, fuzzy @@ -16998,11 +17667,14 @@ msgstr "" "[#2615](https://github.com/adap/flower/pull/2615))" #: ../../source/ref-changelog.md:162 +#, fuzzy msgid "" "Experimental fields `sa`, `legacy_server_message` and " "`legacy_client_message` were removed from `Task` message. The removed " "fields are superseded by the new `RecordSet` abstraction." msgstr "" +"从 `Task` 消息中删除了试验性字段 `sa`、 `legacy_server_message` 和 " +"`legacy_client_message`。删除的字段已被新的 `RecordSet` 抽象所取代。" #: ../../source/ref-changelog.md:164 #, fuzzy @@ -17012,11 +17684,14 @@ msgid "" msgstr "**新的 scikit-learn 代码示例** ([#748](https://github.com/adap/flower/pull/748))" #: ../../source/ref-changelog.md:166 +#, fuzzy msgid "" "The development of the MXNet fremework has ended and the project is now " "[archived on GitHub](https://github.com/apache/mxnet). Existing MXNet " "examples won't receive updates." msgstr "" +"MXNet fremework 的开发工作已经结束,该项目现已[归档于 GitHub](https://github." +"com/apache/mxnet)。现有的 MXNet 示例不会收到更新。" #: ../../source/ref-changelog.md:168 #, fuzzy @@ -17024,6 +17699,7 @@ msgid "v1.6.0 (2023-11-28)" msgstr "v1.4.0 (2023-04-21)" #: ../../source/ref-changelog.md:174 +#, fuzzy msgid "" "`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " "`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " @@ -17032,6 +17708,12 @@ msgid "" "`Steve Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, " "`cnxdeveloper`, `k3nfalt` " msgstr "" +"`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, `" +"Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel Mota`" +", `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`, `Navin " +"Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, `Steve " +"Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, `cnxdeveloper`, " +"`k3nfalt` " #: ../../source/ref-changelog.md:178 msgid "" @@ -17061,10 +17743,13 @@ msgstr "" "[#1567](https://github.com/adap/flower/pull/1567))" #: ../../source/ref-changelog.md:182 +#, fuzzy msgid "" "We have added a new `xgboost-quickstart` example alongside a new " "`xgboost-comprehensive` example that goes more in-depth." msgstr "" +"我们添加了一个新的 \"xgboost-quickstart \"示例和一个新的 \"xgboost-" +"comprehensive \"示例,后者更加深入。" #: ../../source/ref-changelog.md:184 #, fuzzy @@ -17074,12 +17759,16 @@ msgid "" msgstr "**新的 iOS CoreML 代码示例**([#1289](https://github.com/adap/flower/pull/1289))" #: ../../source/ref-changelog.md:186 +#, fuzzy msgid "" "We had many questions about Vertical Federated Learning using Flower, so " "we decided to add an simple example for it on the [Titanic " "dataset](https://www.kaggle.com/competitions/titanic/data) alongside a " "tutorial (in the README)." msgstr "" +"我们收到了许多关于使用 Flower 进行垂直联合学习的问题,因此我们决定在 [" +"Titanic 数据集](https://www.kaggle.com/competitions/titanic/data) " +"上添加一个简单的示例,并附上教程(在 README 中)。" #: ../../source/ref-changelog.md:188 msgid "" @@ -17138,6 +17827,7 @@ msgstr "" "[#2400](https://github.com/adap/flower/pull/2400)" #: ../../source/ref-changelog.md:202 +#, fuzzy msgid "" "Flower is moving to HTTPS by default. The new `flower-server` requires " "passing `--certificates`, but users can enable `--insecure` to use HTTP " @@ -17146,13 +17836,21 @@ msgid "" "an HTTPS-enabled server or requires opt-out via passing `--insecure` to " "enable insecure HTTP connections." msgstr "" +"Flower 默认使用 HTTPS。新的 \"flower-server \"需要通过\"--证书\"," +"但用户可以启用\"--不安全 \"来使用 HTTP 进行原型开发。这同样适用于 `flower-" +"client`,它可以使用用户提供的凭证或 gRPC 绑定证书连接到支持 HTTPS 的服务器," +"也可以通过传递 `--insecure`来启用不安全的 HTTP 连接。" #: ../../source/ref-changelog.md:204 +#, fuzzy msgid "" "For backward compatibility, `start_client()` and `start_numpy_client()` " "will still start in insecure mode by default. In a future release, " "insecure connections will require user opt-in by passing `insecure=True`." msgstr "" +"为了向后兼容,`start_client()` 和 `start_numpy_client()` " +"默认仍以不安全模式启动。在未来的版本中,不安全连接将需要用户通过传递 " +"`insecure=True` 进行选择。" #: ../../source/ref-changelog.md:206 msgid "" @@ -17355,6 +18053,7 @@ msgstr "" "[#1475](https://github.com/adap/flower/pull/1475)))" #: ../../source/ref-changelog.md:254 +#, fuzzy msgid "" "**General improvements** " "([#2309](https://github.com/adap/flower/pull/2309), " @@ -17385,6 +18084,25 @@ msgid "" "[#2553](https://github.com/adap/flower/pull/2553), " "[#2596](https://github.com/adap/flower/pull/2596))" msgstr "" +"**一般改进** ([#2309](https://github.com/adap/flower/pull/2309), " +"[#2310](https://github.com/adap/flower/pull/2310), [#2313](https://github." +"com/adap/flower/pull/2313), [#2316](https://github.com/adap/flower/pull/2316)" +", [#2317](https://github.com/adap/flower/pull/2317), [#2349](https://github." +"com/adap/flower/pull/2349), [#2360](https://github.com/adap/flower/pull/2360)" +", [#2402](https://github.com/adap/flower/pull/2402), [#2446](https://github." +"com/adap/flower/pull/2446), [#2561](https://github.com/adap/flower/pull/2561)" +", [#2273](https://github.com/adap/flower/pull/2273), [#2267](https://github." +"com/adap/flower/pull/2267), [#2274](https://github.com/adap/flower/pull/2274)" +", [#2275](https://github.com/adap/flower/pull/2275), [#2432](https://github." +"com/adap/flower/pull/2432), [#2251](https://github.com/adap/flower/pull/2251)" +", [#2321](https://github.com/adap/flower/pull/2321), [#1936](https://github." +"com/adap/flower/pull/1936), [#2408](https://github.com/adap/flower/pull/2408)" +", [#2413](https://github.com/adap/flower/pull/2413), [#2401](https://github." +"com/adap/flower/pull/2401), [#2531](https://github.com/adap/flower/pull/2531)" +", [#2534](https://github.com/adap/flower/pull/2534), [#2535](https://github." +"com/adap/flower/pull/2535), [#2521](https://github.com/adap/flower/pull/2521)" +", [#2553](https://github.com/adap/flower/pull/2553), [#2596](https://github." +"com/adap/flower/pull/2596))" #: ../../source/ref-changelog.md:256 ../../source/ref-changelog.md:346 #: ../../source/ref-changelog.md:410 ../../source/ref-changelog.md:464 @@ -22077,6 +22795,7 @@ msgstr "" "还需要导入几个软件包,如 Flower 和 scikit-learn:" #: ../../source/tutorial-quickstart-scikitlearn.rst:67 +#, fuzzy msgid "" "Prior to local training, we need to load the MNIST dataset, a popular " "image classification dataset of handwritten digits for machine learning, " @@ -22086,6 +22805,11 @@ msgid "" "training set for each partition ID defined in the :code:`--partition-id` " "argument." msgstr "" +"在本地训练之前,我们需要加载 MNIST " +"数据集(一个用于机器学习的流行手写数字图像分类数据集),并对数据集进行 FL " +"分区。使用 \"Flower Datasets `" +"_\"可以方便地实现这一点。:code:`FederatedDataset.load_partition()` 方法为 " +":code:`--partition-id` 参数中定义的每个分区 ID 加载分区训练集。" #: ../../source/tutorial-quickstart-scikitlearn.rst:95 msgid "" @@ -22667,6 +23391,7 @@ msgid "Cyclic training" msgstr "集中式训练" #: ../../source/tutorial-quickstart-xgboost.rst:605 +#, fuzzy msgid "" "In addition to bagging aggregation, we offer a cyclic training scheme, " "which performs FL in a client-by-client fashion. Instead of aggregating " @@ -22675,14 +23400,20 @@ msgid "" "XGBoost trees will be passed to the next client as an initialised model " "for next round's boosting." msgstr "" +"除了袋式聚合,我们还提供了一种循环训练方案,它以逐个客户端的方式执行 FL。在循" +"环训练方案中,每轮只有一个客户端参与训练,而不是多个客户端聚合在一起。" +"训练好的本地 XGBoost 树将传递给下一个客户端,作为下一轮提升的初始化模型。" #: ../../source/tutorial-quickstart-xgboost.rst:609 +#, fuzzy msgid "" "To do this, we first customise a :code:`ClientManager` in " ":code:`server_utils.py`:" -msgstr "" +msgstr "为此,我们首先要在 :code:`server_utils.py` 中自定义一个 " +":code:`ClientManager`:" #: ../../source/tutorial-quickstart-xgboost.rst:649 +#, fuzzy msgid "" "The customised :code:`ClientManager` samples all available clients in " "each FL round based on the order of connection to the server. Then, we " @@ -22691,19 +23422,29 @@ msgid "" "select only one client in given round and pass the received model to next" " client." msgstr "" +"定制的 :code:`ClientManager` 会根据连接服务器的顺序,在每轮 FL " +"中对所有可用客户端进行采样。然后,我们在 :code:`flwr.server.strategy." +"fedxgb_cyclic.py`\"中定义了一个新策略 :code:`FedXgbCyclic`,以便在给定回合中" +"按顺序只选择一个客户端,并将接收到的模型传递给下一个客户端。" #: ../../source/tutorial-quickstart-xgboost.rst:690 +#, fuzzy msgid "" "Unlike the original :code:`FedAvg`, we don't perform aggregation here. " "Instead, we just make a copy of the received client model as global model" " by overriding :code:`aggregate_fit`." msgstr "" +"与最初的 :code:`FedAvg` 不同,我们在这里不执行聚合。相反,我们只是通过覆盖 " +":code:`aggregate_fit` 将接收到的客户端模型复制为全局模型。" #: ../../source/tutorial-quickstart-xgboost.rst:693 +#, fuzzy msgid "" "Also, the customised :code:`configure_fit` and :code:`configure_evaluate`" " methods ensure the clients to be sequentially selected given FL round:" msgstr "" +"此外,定制的 :code:`configure_fit` 和 :code:`configure_evaluate` " +"方法可确保在 FL 轮中按顺序选择客户:" #: ../../source/tutorial-quickstart-xgboost.rst:757 msgid "Customised data partitioning" @@ -22758,40 +23499,51 @@ msgid "Flower simulation" msgstr "运行模拟" #: ../../source/tutorial-quickstart-xgboost.rst:832 +#, fuzzy msgid "" "We also provide an example code (:code:`sim.py`) to use the simulation " "capabilities of Flower to simulate federated XGBoost training on either a" " single machine or a cluster of machines." msgstr "" +"我们还提供了一个示例代码(:code:`sim.py`),用于使用 Flower " +"的模拟功能在单台机器或机器集群上模拟联合 XGBoost 训练。" #: ../../source/tutorial-quickstart-xgboost.rst:866 +#, fuzzy msgid "" "After importing all required packages, we define a :code:`main()` " "function to perform the simulation process:" -msgstr "" +msgstr "导入所有需要的软件包后,我们定义了一个 :code:`main()` 函数来执行模拟程序:" #: ../../source/tutorial-quickstart-xgboost.rst:921 +#, fuzzy msgid "" "We first load the dataset and perform data partitioning, and the pre-" "processed data is stored in a :code:`list`. After the simulation begins, " "the clients won't need to pre-process their partitions again." -msgstr "" +msgstr "我们首先加载数据集并执行数据分区,预处理后的数据存储在 :code:`list` " +"中。模拟开始后,客户端就不需要再预处理分区了。" #: ../../source/tutorial-quickstart-xgboost.rst:924 +#, fuzzy msgid "Then, we define the strategies and other hyper-parameters:" -msgstr "" +msgstr "然后,我们定义策略和其他超参数:" #: ../../source/tutorial-quickstart-xgboost.rst:975 +#, fuzzy msgid "" "After that, we start the simulation by calling " ":code:`fl.simulation.start_simulation`:" -msgstr "" +msgstr "然后,我们调用 :code:`fl.simulation.start_simulation` 开始模拟:" #: ../../source/tutorial-quickstart-xgboost.rst:995 +#, fuzzy msgid "" "One of key parameters for :code:`start_simulation` is :code:`client_fn` " "which returns a function to construct a client. We define it as follows:" msgstr "" +":code:`start_simulation` 的一个关键参数是 " +":code:`client_fn`,它返回一个用于构建客户端的函数。我们将其定义如下:" #: ../../source/tutorial-quickstart-xgboost.rst:1038 msgid "Arguments parser" @@ -22832,12 +23584,14 @@ msgid "" msgstr "这定义了客户端数据分区的各种选项。此外,通过设置 :code:`-centralised-eval`,客户端还可以选择在集中测试集上进行评估。" #: ../../source/tutorial-quickstart-xgboost.rst:1148 +#, fuzzy msgid "We also have an argument parser for simulation:" -msgstr "" +msgstr "我们还有一个用于模拟的参数解析器:" #: ../../source/tutorial-quickstart-xgboost.rst:1226 +#, fuzzy msgid "This integrates all arguments for both client and server sides." -msgstr "" +msgstr "这整合了客户端和服务器端的所有参数。" #: ../../source/tutorial-quickstart-xgboost.rst:1229 msgid "Example commands" @@ -22856,8 +23610,9 @@ msgid "Then, on each client terminal, we start the clients:" msgstr "然后,我们在每个客户终端上启动客户机:" #: ../../source/tutorial-quickstart-xgboost.rst:1244 +#, fuzzy msgid "To run the same experiment with Flower simulation:" -msgstr "" +msgstr "运行与 Flower 模拟相同的实验:" #: ../../source/tutorial-quickstart-xgboost.rst:1250 #, fuzzy @@ -23562,12 +24317,16 @@ msgid "" msgstr "每个组织都将充当联邦学习系统中的客户端。因此,有十个组织参与联邦学习,就意味着有十个客户端连接到联邦学习服务器:" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:144 +#, fuzzy msgid "" "Let's now create the Federated Dataset abstraction that from ``flwr-" "datasets`` that partitions the CIFAR-10. We will create small training " "and test set for each edge device and wrap each of them into a PyTorch " "``DataLoader``:" msgstr "" +"现在,让我们从 ``flwr-datasets`` 中创建 Federated Dataset 抽象,以分割 " +"CIFAR-10。我们将为每个边缘设备创建小型训练集和测试集,并将它们分别封装到 " +"PyTorch ``DataLoader`` 中:" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:198 #, fuzzy @@ -24310,8 +25069,9 @@ msgid "" msgstr "在机器学习中,我们有一个模型和数据。模型可以是一个神经网络(如图所示),也可以是其他东西,比如经典的线性回归。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 +#, fuzzy msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" -msgstr "" +msgstr "|d8bf04f23d9b46d8a23cc6f4887d7873|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 msgid "Model and data" @@ -24325,8 +25085,9 @@ msgid "" msgstr "我们使用数据来训练模型,以完成一项有用的任务。任务可以是检测图像中的物体、转录音频或玩围棋等游戏。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 +#, fuzzy msgid "|5aa1711387d74d0f8b9c499e1a51627e|" -msgstr "" +msgstr "|5aa1711387d74d0f8b9c499e1a51627e|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 msgid "Train model using data" @@ -24346,8 +25107,9 @@ msgid "" msgstr "它源于智能手机上用户与应用程序的交互、汽车上传感器数据的收集、笔记本电脑上键盘输入的接收,或者智能扬声器上某人试着唱的歌。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 +#, fuzzy msgid "|2bc8e069228d4873804061ff4a95048c|" -msgstr "" +msgstr "|2bc8e069228d4873804061ff4a95048c|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 msgid "Data on a phone" @@ -24364,8 +25126,9 @@ msgstr "" "\"通常不只是一个地方,而是很多地方。它可能是多个运行同一应用程序的设备。但也可能是多个组织,都在为同一任务生成数据。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 +#, fuzzy msgid "|c258488766324dc9a6807f0e7c4fd5f4|" -msgstr "" +msgstr "|c258488766324dc9a6807f0e7c4fd5f4|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 msgid "Data is on many devices" @@ -24380,8 +25143,9 @@ msgid "" msgstr "因此,要使用机器学习或任何类型的数据分析,过去使用的方法是在中央服务器上收集所有数据。这个服务器可以在数据中心的某个地方,也可以在云端的某个地方。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 +#, fuzzy msgid "|d5f962c3f4ec48529efda980868c14b0|" -msgstr "" +msgstr "|d5f962c3f4ec48529efda980868c14b0|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 msgid "Central data collection" @@ -24395,8 +25159,9 @@ msgid "" msgstr "一旦所有数据都收集到一处,我们最终就可以使用机器学习算法在数据上训练我们的模型。这就是我们基本上一直依赖的机器学习方法。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 +#, fuzzy msgid "|a5eccea18d4c43a68b54b65043cabef8|" -msgstr "" +msgstr "|a5eccea18d4c43a68b54b65043cabef8|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 msgid "Central model training" @@ -24415,8 +25180,9 @@ msgid "" msgstr "我们刚刚看到的经典机器学习方法可以在某些情况下使用。很好的例子包括对假日照片进行分类或分析网络流量。在这些案例中,所有数据自然都可以在中央服务器上获得。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 +#, fuzzy msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" -msgstr "" +msgstr "|f17662f7df2d42f68cac70a1fdeda8a7|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 msgid "Centralized possible" @@ -24430,8 +25196,9 @@ msgid "" msgstr "但这种方法并不适用于许多其他情况。例如,集中服务器上没有数据,或者一台服务器上的数据不足以训练出一个好的模型。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 +#, fuzzy msgid "|241fc906441a4f038c625a19d30d01b2|" -msgstr "" +msgstr "|241fc906441a4f038c625a19d30d01b2|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 msgid "Centralized impossible" @@ -24578,8 +25345,9 @@ msgid "" msgstr "我们首先在服务器上初始化模型。这与经典的集中式学习完全相同:我们随机或从先前保存的检查点初始化模型参数。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 +#, fuzzy msgid "|0aa5aa05810b44b6a835cecce28f3137|" -msgstr "" +msgstr "|0aa5aa05810b44b6a835cecce28f3137|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 msgid "Initialize global model" @@ -24602,8 +25370,9 @@ msgid "" msgstr "接下来,我们会将全局模型的参数发送到连接的客户端节点(如智能手机等边缘设备或企业的服务器)。这是为了确保每个参与节点都使用相同的模型参数开始本地训练。我们通常只使用几个连接节点,而不是所有节点。这样做的原因是,选择越来越多的客户端节点会导致收益递减。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 +#, fuzzy msgid "|c742940dd4bf4de09d8d0d5e8d179638|" -msgstr "" +msgstr "|c742940dd4bf4de09d8d0d5e8d179638|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 msgid "Send global model" @@ -24628,8 +25397,9 @@ msgstr "" "(mini-batches)。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 +#, fuzzy msgid "|1f169ab4601a47e1a226f1628f4ebddb|" -msgstr "" +msgstr "|1f169ab4601a47e1a226f1628f4ebddb|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 msgid "Train on local data" @@ -24651,8 +25421,9 @@ msgid "" msgstr "经过本地训练后,每个客户节点最初收到的模型参数都会略有不同。参数之所以不同,是因为每个客户端节点的本地数据集中都有不同的数据。然后,客户端节点将这些模型更新发回服务器。它们发送的模型更新既可以是完整的模型参数,也可以只是本地训练过程中积累的梯度。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 +#, fuzzy msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" -msgstr "" +msgstr "|12cfa9cde14440ecb8c8f6c1d7185bec|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 msgid "Send model updates" @@ -24697,8 +25468,9 @@ msgstr "" " 100 个示例的 10 倍。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 +#, fuzzy msgid "|72939caf6e294b0986fee6dde96614d7|" -msgstr "" +msgstr "|72939caf6e294b0986fee6dde96614d7|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 msgid "Aggregate model updates" @@ -24804,8 +25576,9 @@ msgstr "" "为联邦学习、分析和评估提供了一种统一的方法。它允许用户联邦化任何工作负载、任何 ML 框架和任何编程语言。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 +#, fuzzy msgid "|83a8daee45da4a98b8d6f24ae098fc50|" -msgstr "" +msgstr "|83a8daee45da4a98b8d6f24ae098fc50|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 msgid "" @@ -26925,4 +27698,3 @@ msgstr "" #~ msgid "|ff726bc5505e432388ee2fdd6ef420b9|" #~ msgstr "" - From 3ecf7518229d36c14da1057277189ba27d637b25 Mon Sep 17 00:00:00 2001 From: Daniel Nata Nugraha Date: Thu, 13 Jun 2024 18:42:46 +0200 Subject: [PATCH 025/595] refactor(framework) Refactor IP address format in SuperLink (#3583) --- src/py/flwr/server/app.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/py/flwr/server/app.py b/src/py/flwr/server/app.py index cbb18b602fcd..1574ec46f968 100644 --- a/src/py/flwr/server/app.py +++ b/src/py/flwr/server/app.py @@ -200,15 +200,7 @@ def run_superlink() -> None: args = _parse_args_run_superlink().parse_args() # Parse IP address - parsed_driver_address = parse_address(args.driver_api_address) - if not parsed_driver_address: - sys.exit(f"Driver IP address ({args.driver_api_address}) cannot be parsed.") - driver_host, driver_port, driver_is_v6 = parsed_driver_address - driver_address = ( - f"[{driver_host}]:{driver_port}" - if driver_is_v6 - else f"{driver_host}:{driver_port}" - ) + driver_address, _, _ = _format_address(args.driver_api_address) # Obtain certificates certificates = _try_obtain_certificates(args) @@ -231,13 +223,8 @@ def run_superlink() -> None: if args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE else ADDRESS_FLEET_API_REST ) - parsed_fleet_address = parse_address(args.fleet_api_address) - if not parsed_fleet_address: - sys.exit(f"Fleet IP address ({args.fleet_api_address}) cannot be parsed.") - fleet_host, fleet_port, fleet_is_v6 = parsed_fleet_address - fleet_address = ( - f"[{fleet_host}]:{fleet_port}" if fleet_is_v6 else f"{fleet_host}:{fleet_port}" - ) + + fleet_address, host, port = _format_address(args.fleet_api_address) num_workers = args.fleet_api_num_workers if num_workers != 1: @@ -267,8 +254,8 @@ def run_superlink() -> None: fleet_thread = threading.Thread( target=_run_fleet_api_rest, args=( - fleet_host, - fleet_port, + host, + port, ssl_keyfile, ssl_certfile, state_factory, @@ -325,6 +312,16 @@ def run_superlink() -> None: driver_server.wait_for_termination(timeout=1) +def _format_address(address: str) -> Tuple[str, str, int]: + parsed_address = parse_address(address) + if not parsed_address: + sys.exit( + f"Address ({address}) cannot be parsed (expected: URL or IPv4 or IPv6)." + ) + host, port, is_v6 = parsed_address + return (f"[{host}]:{port}" if is_v6 else f"{host}:{port}", host, port) + + def _try_setup_client_authentication( args: argparse.Namespace, certificates: Optional[Tuple[bytes, bytes, bytes]], From a44f92e80a0ebf9494394e6c93de2c1743b272c6 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 19:34:53 +0200 Subject: [PATCH 026/595] feat(framework:skip) Add get_fab_metadata function (#3553) --- src/py/flwr/cli/config_utils.py | 56 +++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index ec67fefda0d2..2f1acbca03d6 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -14,14 +14,56 @@ # ============================================================================== """Utility to validate the `pyproject.toml` file.""" +import zipfile +from io import BytesIO from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import IO, Any, Dict, List, Optional, Tuple, Union import tomli from flwr.common import object_ref +def get_fab_metadata(fab_file: Union[Path, bytes]) -> Tuple[str, str]: + """Extract the fab_id and the fab_version from a FAB file or path. + + Parameters + ---------- + fab_file : Union[Path, bytes] + The Flower App Bundle file to validate and extract the metadata from. + It can either be a path to the file or the file itself as bytes. + + Returns + ------- + Tuple[str, str] + The `fab_version` and `fab_id` of the given Flower App Bundle. + """ + fab_file_archive: Union[Path, IO[bytes]] + if isinstance(fab_file, bytes): + fab_file_archive = BytesIO(fab_file) + elif isinstance(fab_file, Path): + fab_file_archive = fab_file + else: + raise ValueError("fab_file must be either a Path or bytes") + + with zipfile.ZipFile(fab_file_archive, "r") as zipf: + with zipf.open("pyproject.toml") as file: + toml_content = file.read().decode("utf-8") + + conf = load_from_string(toml_content) + if conf is None: + raise ValueError("Invalid TOML content in pyproject.toml") + + is_valid, errors, _ = validate(conf) + if not is_valid: + raise ValueError(errors) + + return ( + conf["project"]["version"], + f"{conf['flower']['publisher']}/{conf['project']['name']}", + ) + + def load_and_validate( path: Optional[Path] = None, check_module: bool = True, @@ -63,8 +105,7 @@ def load(path: Optional[Path] = None) -> Optional[Dict[str, Any]]: return None with toml_path.open(encoding="utf-8") as toml_file: - data = tomli.loads(toml_file.read()) - return data + return load_from_string(toml_file.read()) # pylint: disable=too-many-branches @@ -128,3 +169,12 @@ def validate( return False, [reason], [] return True, [], [] + + +def load_from_string(toml_content: str) -> Optional[Dict[str, Any]]: + """Load TOML content from a string and return as dict.""" + try: + data = tomli.loads(toml_content) + return data + except tomli.TOMLDecodeError: + return None From be73dc50d1c22870dfa6eb722be33d27d6a1f613 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 21:13:54 +0200 Subject: [PATCH 027/595] refactor(framework:skip) Add return types to certain CLI functions (#3601) --- src/py/flwr/cli/build.py | 4 +++- src/py/flwr/cli/install.py | 14 ++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index d279a8d11bc2..2981eacf925d 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -33,7 +33,7 @@ def build( Optional[Path], typer.Option(help="The Flower project directory to bundle into a FAB"), ] = None, -) -> None: +) -> str: """Build a Flower project into a Flower App Bundle (FAB). You can run `flwr build` without any argument to bundle the current directory: @@ -125,6 +125,8 @@ def build( f"🎊 Successfully built {fab_filename}.", fg=typer.colors.GREEN, bold=True ) + return fab_filename + def _load_gitignore(directory: Path) -> pathspec.PathSpec: """Load and parse .gitignore file, returning a pathspec.""" diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index d6d2ee55a47a..d953c650f3ac 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -84,7 +84,7 @@ def install_from_fab( fab_file: Union[Path, bytes], flwr_dir: Optional[Path], skip_prompt: bool = False, -) -> None: +) -> Path: """Install from a FAB file after extracting and validating.""" fab_file_archive: Union[Path, IO[bytes]] fab_name: Optional[str] @@ -124,7 +124,11 @@ def install_from_fab( shutil.rmtree(info_dir) - validate_and_install(tmpdir_path, fab_name, flwr_dir, skip_prompt) + installed_path = validate_and_install( + tmpdir_path, fab_name, flwr_dir, skip_prompt + ) + + return installed_path def validate_and_install( @@ -132,7 +136,7 @@ def validate_and_install( fab_name: Optional[str], flwr_dir: Optional[Path], skip_prompt: bool = False, -) -> None: +) -> Path: """Validate TOML files and install the project to the desired directory.""" config, _, _ = load_and_validate(project_dir / "pyproject.toml", check_module=False) @@ -185,7 +189,7 @@ def validate_and_install( bold=True, ) ): - return + return install_dir install_dir.mkdir(parents=True, exist_ok=True) @@ -202,6 +206,8 @@ def validate_and_install( bold=True, ) + return install_dir + def _verify_hashes(list_content: str, tmpdir: Path) -> bool: """Verify file hashes based on the LIST content.""" From c16cd7503465f9d13e98173a160bbef03bc1e666 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 21:32:30 +0200 Subject: [PATCH 028/595] refactor(framework:skip) Add function to get flwr dir (#3600) --- src/py/flwr/cli/install.py | 14 +++----------- src/py/flwr/client/supernode/app.py | 11 +++-------- src/py/flwr/common/config.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 19 deletions(-) create mode 100644 src/py/flwr/common/config.py diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index d953c650f3ac..de9227bee450 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -15,7 +15,6 @@ """Flower command line interface `install` command.""" -import os import shutil import tempfile import zipfile @@ -26,6 +25,8 @@ import typer from typing_extensions import Annotated +from flwr.common.config import get_flwr_dir + from .config_utils import load_and_validate from .utils import get_sha256_hash @@ -165,16 +166,7 @@ def validate_and_install( raise typer.Exit(code=1) install_dir: Path = ( - ( - Path( - os.getenv( - "FLWR_HOME", - f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", - ) - ) - if not flwr_dir - else flwr_dir - ) + (get_flwr_dir() if not flwr_dir else flwr_dir) / "apps" / publisher / project_name diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 9ec9695fb51e..ddc547ad371b 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -15,7 +15,6 @@ """Flower SuperNode.""" import argparse -import os import sys from logging import DEBUG, INFO, WARN from pathlib import Path @@ -32,6 +31,7 @@ from flwr.cli.config_utils import validate_fields from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event +from flwr.common.config import get_flwr_dir from flwr.common.exit_handlers import register_exit_handlers from flwr.common.logger import log, warn_deprecated_feature from flwr.common.object_ref import load_app, validate @@ -170,12 +170,7 @@ def _get_load_client_app_fn( flwr_dir = Path("") if "flwr_dir" in args: if args.flwr_dir is None: - flwr_dir = Path( - os.getenv( - "FLWR_HOME", - f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", - ) - ) + flwr_dir = get_flwr_dir() else: flwr_dir = Path(args.flwr_dir) @@ -234,7 +229,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: # Load pyproject.toml file toml_path = project_dir / "pyproject.toml" - if not os.path.isfile(toml_path): + if not toml_path.is_file(): raise LoadClientAppError( f"Cannot find pyproject.toml in {project_dir}", ) from None diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py new file mode 100644 index 000000000000..2c5b5962e7bd --- /dev/null +++ b/src/py/flwr/common/config.py @@ -0,0 +1,28 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Provide functions for managing global Flower config.""" + +import os +from pathlib import Path + + +def get_flwr_dir() -> Path: + """Return the Flower home directory based on env variables.""" + return Path( + os.getenv( + "FLWR_HOME", + f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", + ) + ) From bdb9269d68d452e96339324bf3e5dfffdc4a0270 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 22:28:51 +0200 Subject: [PATCH 029/595] feat(framework) Add proto files for SuperExec service (#3602) --- src/proto/flwr/proto/exec.proto | 26 +++++++++++ src/py/flwr/proto/exec_pb2.py | 30 +++++++++++++ src/py/flwr/proto/exec_pb2.pyi | 32 ++++++++++++++ src/py/flwr/proto/exec_pb2_grpc.py | 67 +++++++++++++++++++++++++++++ src/py/flwr/proto/exec_pb2_grpc.pyi | 27 ++++++++++++ src/py/flwr_tool/protoc_test.py | 2 +- 6 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 src/proto/flwr/proto/exec.proto create mode 100644 src/py/flwr/proto/exec_pb2.py create mode 100644 src/py/flwr/proto/exec_pb2.pyi create mode 100644 src/py/flwr/proto/exec_pb2_grpc.py create mode 100644 src/py/flwr/proto/exec_pb2_grpc.pyi diff --git a/src/proto/flwr/proto/exec.proto b/src/proto/flwr/proto/exec.proto new file mode 100644 index 000000000000..05885c9ceed3 --- /dev/null +++ b/src/proto/flwr/proto/exec.proto @@ -0,0 +1,26 @@ +// Copyright 2024 Flower Labs GmbH. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ============================================================================== + +syntax = "proto3"; + +package flwr.proto; + +service Exec { + // Start run upon request + rpc StartRun(StartRunRequest) returns (StartRunResponse) {} +} + +message StartRunRequest { bytes fab_file = 1; } +message StartRunResponse { sint64 run_id = 1; } diff --git a/src/py/flwr/proto/exec_pb2.py b/src/py/flwr/proto/exec_pb2.py new file mode 100644 index 000000000000..a1d1f24af7d0 --- /dev/null +++ b/src/py/flwr/proto/exec_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: flwr/proto/exec.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x32O\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.exec_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_STARTRUNREQUEST']._serialized_start=37 + _globals['_STARTRUNREQUEST']._serialized_end=72 + _globals['_STARTRUNRESPONSE']._serialized_start=74 + _globals['_STARTRUNRESPONSE']._serialized_end=108 + _globals['_EXEC']._serialized_start=110 + _globals['_EXEC']._serialized_end=189 +# @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/exec_pb2.pyi b/src/py/flwr/proto/exec_pb2.pyi new file mode 100644 index 000000000000..8a0122062dcf --- /dev/null +++ b/src/py/flwr/proto/exec_pb2.pyi @@ -0,0 +1,32 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import builtins +import google.protobuf.descriptor +import google.protobuf.message +import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class StartRunRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + FAB_FILE_FIELD_NUMBER: builtins.int + fab_file: builtins.bytes + def __init__(self, + *, + fab_file: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["fab_file",b"fab_file"]) -> None: ... +global___StartRunRequest = StartRunRequest + +class StartRunResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_ID_FIELD_NUMBER: builtins.int + run_id: builtins.int + def __init__(self, + *, + run_id: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ... +global___StartRunResponse = StartRunResponse diff --git a/src/py/flwr/proto/exec_pb2_grpc.py b/src/py/flwr/proto/exec_pb2_grpc.py new file mode 100644 index 000000000000..349148eb9926 --- /dev/null +++ b/src/py/flwr/proto/exec_pb2_grpc.py @@ -0,0 +1,67 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from flwr.proto import exec_pb2 as flwr_dot_proto_dot_exec__pb2 + + +class ExecStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.StartRun = channel.unary_unary( + '/flwr.proto.Exec/StartRun', + request_serializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString, + ) + + +class ExecServicer(object): + """Missing associated documentation comment in .proto file.""" + + def StartRun(self, request, context): + """Start run upon request + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ExecServicer_to_server(servicer, server): + rpc_method_handlers = { + 'StartRun': grpc.unary_unary_rpc_method_handler( + servicer.StartRun, + request_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'flwr.proto.Exec', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class Exec(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def StartRun(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary(request, target, '/flwr.proto.Exec/StartRun', + flwr_dot_proto_dot_exec__pb2.StartRunRequest.SerializeToString, + flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/exec_pb2_grpc.pyi b/src/py/flwr/proto/exec_pb2_grpc.pyi new file mode 100644 index 000000000000..6cab594babd9 --- /dev/null +++ b/src/py/flwr/proto/exec_pb2_grpc.pyi @@ -0,0 +1,27 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import abc +import flwr.proto.exec_pb2 +import grpc + +class ExecStub: + def __init__(self, channel: grpc.Channel) -> None: ... + StartRun: grpc.UnaryUnaryMultiCallable[ + flwr.proto.exec_pb2.StartRunRequest, + flwr.proto.exec_pb2.StartRunResponse] + """Start run upon request""" + + +class ExecServicer(metaclass=abc.ABCMeta): + @abc.abstractmethod + def StartRun(self, + request: flwr.proto.exec_pb2.StartRunRequest, + context: grpc.ServicerContext, + ) -> flwr.proto.exec_pb2.StartRunResponse: + """Start run upon request""" + pass + + +def add_ExecServicer_to_server(servicer: ExecServicer, server: grpc.Server) -> None: ... diff --git a/src/py/flwr_tool/protoc_test.py b/src/py/flwr_tool/protoc_test.py index 6aec4251c384..0c43ed0b0811 100644 --- a/src/py/flwr_tool/protoc_test.py +++ b/src/py/flwr_tool/protoc_test.py @@ -28,4 +28,4 @@ def test_directories() -> None: def test_proto_file_count() -> None: """Test if the correct number of proto files were captured by the glob.""" - assert len(PROTO_FILES) == 9 + assert len(PROTO_FILES) == 10 From bd39b303f8cf35dcdef6c572670ead3c3fe9f881 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 22:35:14 +0200 Subject: [PATCH 030/595] feat(framework) Add SuperExec binary (#3603) --- pyproject.toml | 1 + src/py/flwr/superexec/__init__.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 src/py/flwr/superexec/__init__.py diff --git a/pyproject.toml b/pyproject.toml index 62e17aeb5281..2dd592050468 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ exclude = [ [tool.poetry.scripts] flwr = "flwr.cli.app:app" flower-superlink = "flwr.server:run_superlink" +flower-superexec = "flwr.superexec:run_superexec" flower-supernode = "flwr.client:run_supernode" flower-client-app = "flwr.client:run_client_app" flower-server-app = "flwr.server:run_server_app" diff --git a/src/py/flwr/superexec/__init__.py b/src/py/flwr/superexec/__init__.py new file mode 100644 index 000000000000..9395b3b51efd --- /dev/null +++ b/src/py/flwr/superexec/__init__.py @@ -0,0 +1,19 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Fower SuperExec package.""" + + +def run_superexec() -> None: + """Empty stub.""" From b0fc4b820d0aee89274aff68e74c704b171e0f68 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 13 Jun 2024 22:45:24 +0200 Subject: [PATCH 031/595] feat(framework) Add SuperExec constants (#3604) --- src/py/flwr/common/constant.py | 2 ++ src/py/flwr/common/telemetry.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index b6d39b6e8932..0722ab7d167e 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -36,6 +36,8 @@ TRANSPORT_TYPE_VCE, ] +SUPEREXEC_DEFAULT_ADDRESS = "0.0.0.0:9093" + # Constants for ping PING_DEFAULT_INTERVAL = 30 PING_CALL_TIMEOUT = 5 diff --git a/src/py/flwr/common/telemetry.py b/src/py/flwr/common/telemetry.py index 41fe1508e652..eeb255e8d6eb 100644 --- a/src/py/flwr/common/telemetry.py +++ b/src/py/flwr/common/telemetry.py @@ -164,6 +164,10 @@ def _generate_next_value_(name: str, start: int, count: int, last_values: List[A RUN_SUPERNODE_ENTER = auto() RUN_SUPERNODE_LEAVE = auto() + # SuperExec + RUN_SUPEREXEC_ENTER = auto() + RUN_SUPEREXEC_LEAVE = auto() + # Use the ThreadPoolExecutor with max_workers=1 to have a queue # and also ensure that telemetry calls are not blocking. From abc3789daa502bd5a230943216e646f216c331ef Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Fri, 14 Jun 2024 10:11:28 +0200 Subject: [PATCH 032/595] fix(datasets) Limit the datasets versions (#3607) --- datasets/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datasets/pyproject.toml b/datasets/pyproject.toml index f874d4f0ce51..56cf3038f4ef 100644 --- a/datasets/pyproject.toml +++ b/datasets/pyproject.toml @@ -54,7 +54,7 @@ exclude = [ [tool.poetry.dependencies] python = "^3.8" numpy = "^1.21.0" -datasets = "^2.14.6" +datasets = ">=2.14.6 <2.20.0" pillow = { version = ">=6.2.1", optional = true } soundfile = { version = ">=0.12.1", optional = true } librosa = { version = ">=0.10.0.post2", optional = true } From dc15282107d0191b5f5239aa3166b3743b0d5bc1 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Fri, 14 Jun 2024 10:31:56 +0200 Subject: [PATCH 033/595] feat(*:skip) Add retry loop when installing flwr (#3588) Signed-off-by: Robert Steiner --- .github/workflows/_docker-build.yml | 35 +++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/.github/workflows/_docker-build.yml b/.github/workflows/_docker-build.yml index 3508b1a7b3f6..b8ddd355eb8e 100644 --- a/.github/workflows/_docker-build.yml +++ b/.github/workflows/_docker-build.yml @@ -60,8 +60,24 @@ jobs: hash = hashlib.sha256('''${{ inputs.namespace-repository }} ${{ inputs.file-dir }} ${{ inputs.build-args }}'''.encode()) + # Adds two spaces to the line breaks to ensure proper indentation + # when passing the multi-line string to the wretry.action. + # Without it, the multi-line string is passed like this: + # + # build-args: | + # ARG1= + # ARG2= + # ARG3= + # + # This causes the Docker action to interpret ARG2 and ARG3 as keys instead + # of values ​​of the multi-line string. + build_args = '''${{ inputs.build-args }}'''.replace("\n", "\n ") + with open(os.environ['GITHUB_OUTPUT'], 'a') as fh: print(f"id={hash.hexdigest()}", file=fh) + print("build-args< Date: Fri, 14 Jun 2024 11:02:03 +0200 Subject: [PATCH 034/595] feat(framework) Add SuperExec to flwr run CLI (#3605) --- src/py/flwr/cli/run/run.py | 39 +++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index dbaf7feb3500..3ac9966a1107 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -16,12 +16,18 @@ import sys from enum import Enum +from logging import DEBUG from typing import Optional import typer from typing_extensions import Annotated from flwr.cli import config_utils +from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS +from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel +from flwr.common.logger import log +from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611 +from flwr.proto.exec_pb2_grpc import ExecStub from flwr.simulation.run_simulation import _run_simulation @@ -31,20 +37,32 @@ class Engine(str, Enum): SIMULATION = "simulation" +# pylint: disable-next=too-many-locals def run( engine: Annotated[ Optional[Engine], typer.Option(case_sensitive=False, help="The ML framework to use"), ] = None, + use_superexec: Annotated[ + bool, + typer.Option( + case_sensitive=False, help="Use this flag to use the new SuperExec API" + ), + ] = False, ) -> None: """Run Flower project.""" + if use_superexec: + _start_superexec_run() + return + typer.secho("Loading project configuration... ", fg=typer.colors.BLUE) config, errors, warnings = config_utils.load_and_validate() if config is None: typer.secho( - "Project configuration could not be loaded.\npyproject.toml is invalid:\n" + "Project configuration could not be loaded.\n" + "pyproject.toml is invalid:\n" + "\n".join([f"- {line}" for line in errors]), fg=typer.colors.RED, bold=True, @@ -82,3 +100,22 @@ def run( fg=typer.colors.RED, bold=True, ) + + +def _start_superexec_run() -> None: + def on_channel_state_change(channel_connectivity: str) -> None: + """Log channel connectivity.""" + log(DEBUG, channel_connectivity) + + channel = create_channel( + server_address=SUPEREXEC_DEFAULT_ADDRESS, + insecure=True, + root_certificates=None, + max_message_length=GRPC_MAX_MESSAGE_LENGTH, + interceptors=None, + ) + channel.subscribe(on_channel_state_change) + stub = ExecStub(channel) + + req = StartRunRequest() + stub.StartRun(req) From 32508010af0d678b555209396ee55f56fcbc8c2a Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 14 Jun 2024 13:39:13 +0200 Subject: [PATCH 035/595] feat(framework) Add SuperExec servicer (#3606) --- src/py/flwr/superexec/exec_grpc.py | 51 +++++++++++++++++++ src/py/flwr/superexec/exec_servicer.py | 54 +++++++++++++++++++++ src/py/flwr/superexec/exec_servicer_test.py | 52 ++++++++++++++++++++ src/py/flwr/superexec/executor.py | 54 +++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 src/py/flwr/superexec/exec_grpc.py create mode 100644 src/py/flwr/superexec/exec_servicer.py create mode 100644 src/py/flwr/superexec/exec_servicer_test.py create mode 100644 src/py/flwr/superexec/executor.py diff --git a/src/py/flwr/superexec/exec_grpc.py b/src/py/flwr/superexec/exec_grpc.py new file mode 100644 index 000000000000..127d5615dd84 --- /dev/null +++ b/src/py/flwr/superexec/exec_grpc.py @@ -0,0 +1,51 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""SuperExec gRPC API.""" + +from logging import INFO +from typing import Optional, Tuple + +import grpc + +from flwr.common import GRPC_MAX_MESSAGE_LENGTH +from flwr.common.logger import log +from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server +from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server + +from .exec_servicer import ExecServicer +from .executor import Executor + + +def run_superexec_api_grpc( + address: str, + executor: Executor, + certificates: Optional[Tuple[bytes, bytes, bytes]], +) -> grpc.Server: + """Run SuperExec API (gRPC, request-response).""" + exec_servicer: grpc.Server = ExecServicer( + executor=executor, + ) + superexec_add_servicer_to_server_fn = add_ExecServicer_to_server + superexec_grpc_server = generic_create_grpc_server( + servicer_and_add_fn=(exec_servicer, superexec_add_servicer_to_server_fn), + server_address=address, + max_message_length=GRPC_MAX_MESSAGE_LENGTH, + certificates=certificates, + ) + + log(INFO, "Flower ECE: Starting SuperExec API (gRPC-rere) on %s", address) + superexec_grpc_server.start() + + return superexec_grpc_server diff --git a/src/py/flwr/superexec/exec_servicer.py b/src/py/flwr/superexec/exec_servicer.py new file mode 100644 index 000000000000..aa8172c18704 --- /dev/null +++ b/src/py/flwr/superexec/exec_servicer.py @@ -0,0 +1,54 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""SuperExec API servicer.""" + + +from logging import ERROR, INFO +from typing import Dict + +import grpc + +from flwr.common.logger import log +from flwr.proto import exec_pb2_grpc # pylint: disable=E0611 +from flwr.proto.exec_pb2 import ( # pylint: disable=E0611 + StartRunRequest, + StartRunResponse, +) + +from .executor import Executor, RunTracker + + +class ExecServicer(exec_pb2_grpc.ExecServicer): + """SuperExec API servicer.""" + + def __init__(self, executor: Executor) -> None: + self.executor = executor + self.runs: Dict[int, RunTracker] = {} + + def StartRun( + self, request: StartRunRequest, context: grpc.ServicerContext + ) -> StartRunResponse: + """Create run ID.""" + log(INFO, "ExecServicer.StartRun") + + run = self.executor.start_run(request.fab_file) + + if run is None: + log(ERROR, "Executor failed to start run") + return StartRunResponse() + + self.runs[run.run_id] = run + + return StartRunResponse(run_id=run.run_id) diff --git a/src/py/flwr/superexec/exec_servicer_test.py b/src/py/flwr/superexec/exec_servicer_test.py new file mode 100644 index 000000000000..41f67b74c48b --- /dev/null +++ b/src/py/flwr/superexec/exec_servicer_test.py @@ -0,0 +1,52 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Test the SuperExec API servicer.""" + + +import subprocess +from unittest.mock import MagicMock + +from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611 + +from .exec_servicer import ExecServicer + + +def test_start_run() -> None: + """Test StartRun method of ExecServicer.""" + run_res = MagicMock() + run_res.run_id = 10 + with subprocess.Popen( + ["echo", "success"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) as proc: + run_res.proc = proc + + executor = MagicMock() + executor.start_run = lambda _: run_res + + context_mock = MagicMock() + + request = StartRunRequest() + request.fab_file = b"test" + + # Create a instance of FlowerServiceServicer + servicer = ExecServicer(executor=executor) + + # Execute + response = servicer.StartRun(request, context_mock) + + assert response.run_id == 10 diff --git a/src/py/flwr/superexec/executor.py b/src/py/flwr/superexec/executor.py new file mode 100644 index 000000000000..f85ac4c157fc --- /dev/null +++ b/src/py/flwr/superexec/executor.py @@ -0,0 +1,54 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Execute and monitor a Flower run.""" + +from abc import ABC, abstractmethod +from dataclasses import dataclass +from subprocess import Popen +from typing import Optional + + +@dataclass +class RunTracker: + """Track a Flower run (composed of a run_id and the associated process).""" + + run_id: int + proc: Popen # type: ignore + + +class Executor(ABC): + """Execute and monitor a Flower run.""" + + @abstractmethod + def start_run( + self, + fab_file: bytes, + ) -> Optional[RunTracker]: + """Start a run using the given Flower FAB ID and version. + + This method creates a new run on the SuperLink, returns its run_id + and also starts the run execution. + + Parameters + ---------- + fab_file : bytes + The Flower App Bundle file bytes. + + Returns + ------- + run_id : Optional[RunTracker] + The run_id and the associated process of the run created by the SuperLink, + or `None` if it fails. + """ From 0be59583e2d39f18d552cc039f1f0601099939f4 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 14 Jun 2024 14:13:03 +0200 Subject: [PATCH 036/595] feat(framework) Add initial `SuperExec` service (#3555) Co-authored-by: Daniel J. Beutel --- src/py/flwr/superexec/__init__.py | 8 +- src/py/flwr/superexec/app.py | 178 ++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 src/py/flwr/superexec/app.py diff --git a/src/py/flwr/superexec/__init__.py b/src/py/flwr/superexec/__init__.py index 9395b3b51efd..a510c41f4182 100644 --- a/src/py/flwr/superexec/__init__.py +++ b/src/py/flwr/superexec/__init__.py @@ -12,8 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============================================================================== -"""Fower SuperExec package.""" +"""Flower SuperExec service.""" +from .app import run_superexec as run_superexec -def run_superexec() -> None: - """Empty stub.""" +__all__ = [ + "run_superexec", +] diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py new file mode 100644 index 000000000000..e1cb4f609e9c --- /dev/null +++ b/src/py/flwr/superexec/app.py @@ -0,0 +1,178 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Flower SuperExec app.""" + +import argparse +import sys +from logging import INFO, WARN +from pathlib import Path +from typing import Optional, Tuple + +import grpc + +from flwr.common import EventType, event, log +from flwr.common.address import parse_address +from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS +from flwr.common.exit_handlers import register_exit_handlers +from flwr.common.object_ref import load_app, validate + +from .exec_grpc import run_superexec_api_grpc +from .executor import Executor + + +def run_superexec() -> None: + """Run Flower SuperExec.""" + log(INFO, "Starting Flower SuperExec") + + event(EventType.RUN_SUPEREXEC_ENTER) + + args = _parse_args_run_superexec().parse_args() + + # Parse IP address + parsed_address = parse_address(args.address) + if not parsed_address: + sys.exit(f"SuperExec IP address ({args.address}) cannot be parsed.") + host, port, is_v6 = parsed_address + address = f"[{host}]:{port}" if is_v6 else f"{host}:{port}" + + # Obtain certificates + certificates = _try_obtain_certificates(args) + + # Start SuperExec API + superexec_server: grpc.Server = run_superexec_api_grpc( + address=address, + executor=_load_executor(args), + certificates=certificates, + ) + + grpc_servers = [superexec_server] + + # Graceful shutdown + register_exit_handlers( + event_type=EventType.RUN_SUPEREXEC_LEAVE, + grpc_servers=grpc_servers, + bckg_threads=None, + ) + + superexec_server.wait_for_termination() + + +def _parse_args_run_superexec() -> argparse.ArgumentParser: + """Parse command line arguments for SuperExec.""" + parser = argparse.ArgumentParser( + description="Start a Flower SuperExec", + ) + parser.add_argument( + "executor", + help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.", + ) + parser.add_argument( + "--address", + help="SuperExec (gRPC) server address (IPv4, IPv6, or a domain name)", + default=SUPEREXEC_DEFAULT_ADDRESS, + ) + parser.add_argument( + "--executor-dir", + help="The directory for the executor.", + default=".", + ) + parser.add_argument( + "--insecure", + action="store_true", + help="Run the SuperExec without HTTPS, regardless of whether certificate " + "paths are provided. By default, the server runs with HTTPS enabled. " + "Use this flag only if you understand the risks.", + ) + parser.add_argument( + "--ssl-certfile", + help="SuperExec server SSL certificate file (as a path str) " + "to create a secure connection.", + type=str, + default=None, + ) + parser.add_argument( + "--ssl-keyfile", + help="SuperExec server SSL private key file (as a path str) " + "to create a secure connection.", + type=str, + ) + parser.add_argument( + "--ssl-ca-certfile", + help="SuperExec server SSL CA certificate file (as a path str) " + "to create a secure connection.", + type=str, + ) + return parser + + +def _try_obtain_certificates( + args: argparse.Namespace, +) -> Optional[Tuple[bytes, bytes, bytes]]: + # Obtain certificates + if args.insecure: + log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.") + return None + # Check if certificates are provided + if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile: + if not Path.is_file(args.ssl_ca_certfile): + sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.") + if not Path.is_file(args.ssl_certfile): + sys.exit("Path argument `--ssl-certfile` does not point to a file.") + if not Path.is_file(args.ssl_keyfile): + sys.exit("Path argument `--ssl-keyfile` does not point to a file.") + certificates = ( + Path(args.ssl_ca_certfile).read_bytes(), # CA certificate + Path(args.ssl_certfile).read_bytes(), # server certificate + Path(args.ssl_keyfile).read_bytes(), # server private key + ) + return certificates + if args.ssl_certfile or args.ssl_keyfile or args.ssl_ca_certfile: + sys.exit( + "You need to provide valid file paths to `--ssl-certfile`, " + "`--ssl-keyfile`, and `—-ssl-ca-certfile` to create a secure " + "connection in SuperExec server (gRPC-rere)." + ) + sys.exit( + "Certificates are required unless running in insecure mode. " + "Please provide certificate paths to `--ssl-certfile`, " + "`--ssl-keyfile`, and `—-ssl-ca-certfile` or run the server " + "in insecure mode using '--insecure' if you understand the risks." + ) + + +def _load_executor( + args: argparse.Namespace, +) -> Executor: + """Get the executor plugin.""" + if args.executor_dir is not None: + sys.path.insert(0, args.executor_dir) + + executor_ref: str = args.executor + valid, error_msg = validate(executor_ref) + if not valid and error_msg: + raise LoadExecutorError(error_msg) from None + + executor = load_app(executor_ref, LoadExecutorError) + + if not isinstance(executor, Executor): + raise LoadExecutorError( + f"Attribute {executor_ref} is not of type {Executor}", + ) from None + + return executor + + +class LoadExecutorError(Exception): + """Error when trying to load `Executor`.""" From 6a7fc7d494fabcdd3d7e3f79818afcba2d82b6d4 Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 14 Jun 2024 14:33:19 +0200 Subject: [PATCH 037/595] feat(framework) Enable setting `run_id` when starting simulation (#3576) --- src/py/flwr/simulation/run_simulation.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 2dbeef1a261c..3532c5a4e877 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -53,6 +53,7 @@ def run_simulation_from_cli() -> None: backend_name=args.backend, backend_config=backend_config_dict, app_dir=args.app_dir, + run_id=args.run_id, enable_tf_gpu_growth=args.enable_tf_gpu_growth, verbose_logging=args.verbose, ) @@ -168,6 +169,13 @@ def server_th_with_start_checks( # type: ignore return serverapp_th +def _init_run_id(driver: InMemoryDriver, state: StateFactory, run_id: int) -> None: + """Create a run with a given `run_id`.""" + log(DEBUG, "Pre-registering run with id %s", run_id) + state.state().run_ids[run_id] = ("", "") # type: ignore + driver.run_id = run_id + + # pylint: disable=too-many-locals def _main_loop( num_supernodes: int, @@ -175,6 +183,7 @@ def _main_loop( backend_config_stream: str, app_dir: str, enable_tf_gpu_growth: bool, + run_id: Optional[int] = None, client_app: Optional[ClientApp] = None, client_app_attr: Optional[str] = None, server_app: Optional[ServerApp] = None, @@ -195,6 +204,9 @@ def _main_loop( # Initialize Driver driver = InMemoryDriver(state_factory) + if run_id: + _init_run_id(driver, state_factory, run_id) + # Get and run ServerApp thread serverapp_th = run_serverapp_th( server_app_attr=server_app_attr, @@ -244,6 +256,7 @@ def _run_simulation( client_app_attr: Optional[str] = None, server_app_attr: Optional[str] = None, app_dir: str = "", + run_id: Optional[int] = None, enable_tf_gpu_growth: bool = False, verbose_logging: bool = False, ) -> None: @@ -283,6 +296,9 @@ def _run_simulation( Add specified directory to the PYTHONPATH and load `ClientApp` from there. (Default: current working directory.) + run_id : Optional[int] + An integer specifying the ID of the run started when running this function. + enable_tf_gpu_growth : bool (default: False) A boolean to indicate whether to enable GPU growth on the main thread. This is desirable if you make use of a TensorFlow model on your `ServerApp` while @@ -322,6 +338,7 @@ def _run_simulation( backend_config_stream, app_dir, enable_tf_gpu_growth, + run_id, client_app, client_app_attr, server_app, @@ -413,5 +430,10 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: "ClientApp and ServerApp from there." " Default: current working directory.", ) + parser.add_argument( + "--run-id", + type=int, + help="Sets the ID of the run started by the Simulation Engine.", + ) return parser From 19d063efd6716b9d8708290c0562026ac03083dd Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 14 Jun 2024 14:39:53 +0200 Subject: [PATCH 038/595] fix(framework:skip) Skip module checking for metadata extraction (#3608) --- src/py/flwr/cli/config_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index 2f1acbca03d6..d06a1d6dba96 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -54,7 +54,7 @@ def get_fab_metadata(fab_file: Union[Path, bytes]) -> Tuple[str, str]: if conf is None: raise ValueError("Invalid TOML content in pyproject.toml") - is_valid, errors, _ = validate(conf) + is_valid, errors, _ = validate(conf, check_module=False) if not is_valid: raise ValueError(errors) From 2bfd47a478dd50ee21170873421cd57706261dbd Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 16 Jun 2024 15:50:51 +0200 Subject: [PATCH 039/595] docs(framework:skip) Use correct description for CLI arg (#3614) --- src/py/flwr/cli/run/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 3ac9966a1107..7577d9efbd8c 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -41,7 +41,7 @@ class Engine(str, Enum): def run( engine: Annotated[ Optional[Engine], - typer.Option(case_sensitive=False, help="The ML framework to use"), + typer.Option(case_sensitive=False, help="The execution engine to run the app"), ] = None, use_superexec: Annotated[ bool, From b658ba7e7af034d0267c54191086683f7288822d Mon Sep 17 00:00:00 2001 From: Taner Topal Date: Sun, 16 Jun 2024 21:59:23 +0200 Subject: [PATCH 040/595] feat(framework) Add new proto definitions for FABs (#3618) --- src/proto/flwr/proto/fab.proto | 15 ++++++++ src/py/flwr/proto/fab_pb2.py | 30 ++++++++++++++++ src/py/flwr/proto/fab_pb2.pyi | 56 ++++++++++++++++++++++++++++++ src/py/flwr/proto/fab_pb2_grpc.py | 4 +++ src/py/flwr/proto/fab_pb2_grpc.pyi | 4 +++ src/py/flwr_tool/protoc_test.py | 2 +- 6 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/proto/flwr/proto/fab.proto create mode 100644 src/py/flwr/proto/fab_pb2.py create mode 100644 src/py/flwr/proto/fab_pb2.pyi create mode 100644 src/py/flwr/proto/fab_pb2_grpc.py create mode 100644 src/py/flwr/proto/fab_pb2_grpc.pyi diff --git a/src/proto/flwr/proto/fab.proto b/src/proto/flwr/proto/fab.proto new file mode 100644 index 000000000000..1e796a59c82a --- /dev/null +++ b/src/proto/flwr/proto/fab.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package flwr.proto; + +message Fab { + // This field is the hash of the data field. It is used to identify the data. + // The hash is calculated using the SHA-256 algorithm and is represented as a + // hex string (sha256hex). + string hash = 1; + // This field contains the fab file contents a one bytes blob. + bytes content = 2; +} + +message GetFabRequest { string hash = 1; } +message GetFabResponse { Fab fab = 1; } diff --git a/src/py/flwr/proto/fab_pb2.py b/src/py/flwr/proto/fab_pb2.py new file mode 100644 index 000000000000..c146a1635597 --- /dev/null +++ b/src/py/flwr/proto/fab_pb2.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: flwr/proto/fab.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/fab.proto\x12\nflwr.proto\"$\n\x03\x46\x61\x62\x12\x0c\n\x04hash\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\x0c\"\x1d\n\rGetFabRequest\x12\x0c\n\x04hash\x18\x01 \x01(\t\".\n\x0eGetFabResponse\x12\x1c\n\x03\x66\x61\x62\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Fabb\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.fab_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_FAB']._serialized_start=36 + _globals['_FAB']._serialized_end=72 + _globals['_GETFABREQUEST']._serialized_start=74 + _globals['_GETFABREQUEST']._serialized_end=103 + _globals['_GETFABRESPONSE']._serialized_start=105 + _globals['_GETFABRESPONSE']._serialized_end=151 +# @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/fab_pb2.pyi b/src/py/flwr/proto/fab_pb2.pyi new file mode 100644 index 000000000000..dafc217d0ce2 --- /dev/null +++ b/src/py/flwr/proto/fab_pb2.pyi @@ -0,0 +1,56 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import builtins +import google.protobuf.descriptor +import google.protobuf.message +import typing +import typing_extensions + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class Fab(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + HASH_FIELD_NUMBER: builtins.int + CONTENT_FIELD_NUMBER: builtins.int + hash: typing.Text + """This field is the hash of the data field. It is used to identify the data. + The hash is calculated using the SHA-256 algorithm and is represented as a + hex string (sha256hex). + """ + + content: builtins.bytes + """This field contains the fab file contents a one bytes blob.""" + + def __init__(self, + *, + hash: typing.Text = ..., + content: builtins.bytes = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["content",b"content","hash",b"hash"]) -> None: ... +global___Fab = Fab + +class GetFabRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + HASH_FIELD_NUMBER: builtins.int + hash: typing.Text + def __init__(self, + *, + hash: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["hash",b"hash"]) -> None: ... +global___GetFabRequest = GetFabRequest + +class GetFabResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + FAB_FIELD_NUMBER: builtins.int + @property + def fab(self) -> global___Fab: ... + def __init__(self, + *, + fab: typing.Optional[global___Fab] = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["fab",b"fab"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["fab",b"fab"]) -> None: ... +global___GetFabResponse = GetFabResponse diff --git a/src/py/flwr/proto/fab_pb2_grpc.py b/src/py/flwr/proto/fab_pb2_grpc.py new file mode 100644 index 000000000000..2daafffebfc8 --- /dev/null +++ b/src/py/flwr/proto/fab_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/py/flwr/proto/fab_pb2_grpc.pyi b/src/py/flwr/proto/fab_pb2_grpc.pyi new file mode 100644 index 000000000000..f3a5a087ef5d --- /dev/null +++ b/src/py/flwr/proto/fab_pb2_grpc.pyi @@ -0,0 +1,4 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" diff --git a/src/py/flwr_tool/protoc_test.py b/src/py/flwr_tool/protoc_test.py index 0c43ed0b0811..6ba7daa0efea 100644 --- a/src/py/flwr_tool/protoc_test.py +++ b/src/py/flwr_tool/protoc_test.py @@ -28,4 +28,4 @@ def test_directories() -> None: def test_proto_file_count() -> None: """Test if the correct number of proto files were captured by the glob.""" - assert len(PROTO_FILES) == 10 + assert len(PROTO_FILES) == 11 From 41a673b60a48d94c52cf7d62cdf966dff5eea228 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 16 Jun 2024 23:47:24 +0200 Subject: [PATCH 041/595] fix(framework) Pass superlink address when starting supernode (#3621) --- src/py/flwr/client/supernode/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index ddc547ad371b..4ed3027547ab 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -56,7 +56,7 @@ def run_supernode() -> None: authentication_keys = _try_setup_client_authentication(args) _start_client_internal( - server_address=args.server, + server_address=args.superlink, load_client_app_fn=load_fn, transport="rest" if args.rest else "grpc-rere", root_certificates=root_certificates, From 5d1cb0e6baa5dca4a3a0fd902ac414b1aa74a5e8 Mon Sep 17 00:00:00 2001 From: MarcelRoesberg <66379010+MarcelRoesberg@users.noreply.github.com> Date: Mon, 17 Jun 2024 13:12:18 +0200 Subject: [PATCH 042/595] fix(examples:skip) Fix typo in one example (#3628) --- examples/vertical-fl/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/vertical-fl/README.md b/examples/vertical-fl/README.md index d8c599d617c4..ba8228a059f9 100644 --- a/examples/vertical-fl/README.md +++ b/examples/vertical-fl/README.md @@ -459,7 +459,7 @@ evaluate function are bogus, as they won't be used on the server side. The `client_fn` we will use in our `start_simulation` function to generate our 3 clients will be very basic: -```pyhton3 +```python3 partitions, label = get_partitions_and_label() def client_fn(cid): From dc3d4eea34751cb78dc762c56459e5f7c80fe8fa Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Mon, 17 Jun 2024 16:07:36 +0200 Subject: [PATCH 043/595] docs(framework) Add latest Hosted Weblate translation updates (#3617) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 박태현 --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 2d106db01fd5..9333ce670e93 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-05-28 11:47+0200\n" -"PO-Revision-Date: 2024-06-13 16:07+0000\n" +"PO-Revision-Date: 2024-06-16 09:09+0000\n" "Last-Translator: 박태현 \n" "Language-Team: Korean \n" @@ -301,8 +301,8 @@ msgid "" "The following example creates a SuperLink image with the official Flower " "base image py3.11-ubuntu22.04 and Flower 1.8.0:" msgstr "" -"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base 이미지" -"로 SuperLink 이미지를 만듭니다:" +"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base " +"이미지로 SuperLink 이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:122 msgid "" @@ -731,7 +731,8 @@ msgstr "" msgid "" "Install ``flwr`` from a local copy of the Flower source code via ``pyproject." "toml``:" -msgstr "``pyproject.toml``을 통해 Flower 소스 코드의 로컬 복사본에서 ``flwr``을 설치:" +msgstr "``pyproject.toml``을 통해 Flower 소스 코드의 로컬 복사본에서 ``flwr``을 " +"설치하세요:" #: ../../source/contributor-how-to-install-development-versions.rst:17 msgid "``flwr = { path = \"../../\", develop = true }`` (without extras)" @@ -817,7 +818,7 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:42 msgid "Install ``flwr`` from a specific GitHub branch (``branch-name``):" -msgstr "특정 GitHub branch (``branch-name``)에서 ``flwr``설치하기:" +msgstr "특정 GitHub branch (``branch-name``)에서 ``flwr`` 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:44 msgid "" From b8e5b502016b745feb0cee2fef02a4996372e5d7 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Mon, 17 Jun 2024 21:53:25 +0100 Subject: [PATCH 044/595] refactor(framework:skip) Use absolute paths when setting `sys.path` and replace `os.path.abspath` with `pathlib.Path.absolute` (#3610) --- src/py/flwr/client/supernode/app.py | 17 ++++++++++------- src/py/flwr/server/run_serverapp.py | 2 +- .../flwr/server/superlink/fleet/vce/vce_api.py | 2 ++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 4ed3027547ab..d1d2bc197ce9 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -174,7 +174,7 @@ def _get_load_client_app_fn( else: flwr_dir = Path(args.flwr_dir) - sys.path.insert(0, str(flwr_dir)) + sys.path.insert(0, str(flwr_dir.absolute())) default_app_ref: str = getattr(args, "client-app") @@ -191,8 +191,8 @@ def _get_load_client_app_fn( def _load(fab_id: str, fab_version: str) -> ClientApp: # If multi-app feature is disabled if not multi_app: - # Set sys.path - sys.path[0] = args.dir + # Get sys path to be inserted + sys_path = Path(args.dir).absolute() # Set app reference client_app_ref = default_app_ref @@ -204,8 +204,8 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: ) from None log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.") - # Set sys.path - sys.path[0] = args.dir + # Get sys path to be inserted + sys_path = Path(args.dir).absolute() # Set app reference client_app_ref = default_app_ref @@ -244,12 +244,15 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: f"Invalid pyproject.toml:\n{error_msg}", ) from None - # Set sys.path - sys.path[0] = str(project_dir) + # Get sys path to be inserted + sys_path = Path(project_dir).absolute() # Set app reference client_app_ref = config["flower"]["components"]["clientapp"] + # Set sys.path + sys.path.insert(0, str(sys_path)) + # Load ClientApp log( DEBUG, diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 0879dd6054d0..fd0214a040bc 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -45,7 +45,7 @@ def run( ) if server_app_dir is not None: - sys.path.insert(0, server_app_dir) + sys.path.insert(0, str(Path(server_app_dir).absolute())) # Load ServerApp if needed def _load() -> ServerApp: diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index cc3e85b28097..dcd37aba09c0 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -20,6 +20,7 @@ import time import traceback from logging import DEBUG, ERROR, INFO, WARN +from pathlib import Path from typing import Callable, Dict, List, Optional from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError @@ -274,6 +275,7 @@ def start_vce( # Use mapping constructed externally. This also means nodes # have previously being registered. nodes_mapping = existing_nodes_mapping + app_dir = str(Path(app_dir).absolute()) if not state_factory: log(INFO, "A StateFactory was not supplied to the SimulationEngine.") From 77031b1078c81aebe509ffe891c44abeed34538f Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Mon, 17 Jun 2024 21:58:47 +0100 Subject: [PATCH 045/595] refactor(framework:skip) Add `get_project_dir` and `get_project_config` (#3612) --- src/py/flwr/client/supernode/app.py | 43 +++++---------------------- src/py/flwr/common/config.py | 45 ++++++++++++++++++++++++++++- src/py/flwr/common/constant.py | 5 ++++ 3 files changed, 56 insertions(+), 37 deletions(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index d1d2bc197ce9..97951f2a3279 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -20,7 +20,6 @@ from pathlib import Path from typing import Callable, Optional, Tuple -import tomli from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import ( @@ -28,10 +27,9 @@ load_ssh_public_key, ) -from flwr.cli.config_utils import validate_fields from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event -from flwr.common.config import get_flwr_dir +from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir from flwr.common.exit_handlers import register_exit_handlers from flwr.common.logger import log, warn_deprecated_feature from flwr.common.object_ref import load_app, validate @@ -172,7 +170,7 @@ def _get_load_client_app_fn( if args.flwr_dir is None: flwr_dir = get_flwr_dir() else: - flwr_dir = Path(args.flwr_dir) + flwr_dir = Path(args.flwr_dir).absolute() sys.path.insert(0, str(flwr_dir.absolute())) @@ -211,38 +209,11 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: client_app_ref = default_app_ref # If multi-app feature is enabled else: - # Check the fab_id - if fab_id.count("/") != 1: - raise LoadClientAppError( - f"Invalid FAB ID: {fab_id}", - ) from None - username, project_name = fab_id.split("/") - - # Locate the directory - project_dir = flwr_dir / "apps" / username / project_name / fab_version - - # Check if the directory exists - if not project_dir.exists(): - raise LoadClientAppError( - f"Invalid Flower App directory: {project_dir}", - ) from None - - # Load pyproject.toml file - toml_path = project_dir / "pyproject.toml" - if not toml_path.is_file(): - raise LoadClientAppError( - f"Cannot find pyproject.toml in {project_dir}", - ) from None - with open(toml_path, encoding="utf-8") as toml_file: - config = tomli.loads(toml_file.read()) - - # Validate pyproject.toml fields - is_valid, errors, _ = validate_fields(config) - if not is_valid: - error_msg = "\n".join([f" - {error}" for error in errors]) - raise LoadClientAppError( - f"Invalid pyproject.toml:\n{error_msg}", - ) from None + try: + project_dir = get_project_dir(fab_id, fab_version, flwr_dir) + config = get_project_config(project_dir) + except Exception as e: + raise LoadClientAppError("Failed to load ClientApp") from e # Get sys path to be inserted sys_path = Path(project_dir).absolute() diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 2c5b5962e7bd..95bf8ce31c45 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -16,13 +16,56 @@ import os from pathlib import Path +from typing import Any, Dict, Optional, Union + +import tomli + +from flwr.cli.config_utils import validate_fields +from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME def get_flwr_dir() -> Path: """Return the Flower home directory based on env variables.""" return Path( os.getenv( - "FLWR_HOME", + FLWR_HOME, f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", ) ) + + +def get_project_dir( + fab_id: str, fab_version: str, flwr_dir: Optional[Union[str, Path]] = None +) -> Path: + """Return the project directory based on the given fab_id and fab_version.""" + # Check the fab_id + if fab_id.count("/") != 1: + raise ValueError( + f"Invalid FAB ID: {fab_id}", + ) + publisher, project_name = fab_id.split("/") + if flwr_dir is None: + flwr_dir = get_flwr_dir() + return Path(flwr_dir) / APP_DIR / publisher / project_name / fab_version + + +def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]: + """Return pyproject.toml in the given project directory.""" + # Load pyproject.toml file + toml_path = Path(project_dir) / FAB_CONFIG_FILE + if not toml_path.is_file(): + raise FileNotFoundError( + f"Cannot find {FAB_CONFIG_FILE} in {project_dir}", + ) + with toml_path.open(encoding="utf-8") as toml_file: + config = tomli.loads(toml_file.read()) + + # Validate pyproject.toml fields + is_valid, errors, _ = validate_fields(config) + if not is_valid: + error_msg = "\n".join([f" - {error}" for error in errors]) + raise ValueError( + f"Invalid {FAB_CONFIG_FILE}:\n{error_msg}", + ) + + return config diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index 0722ab7d167e..1548694858fe 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -45,6 +45,11 @@ PING_RANDOM_RANGE = (-0.1, 0.1) PING_MAX_INTERVAL = 1e300 +# Constants for FAB +APP_DIR = "apps" +FAB_CONFIG_FILE = "pyproject.toml" +FLWR_HOME = "FLWR_HOME" + class MessageType: """Message type.""" From 8ea769cbbe7376be2dd54d1ceaa500cee84f0c87 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 17 Jun 2024 23:05:36 +0200 Subject: [PATCH 046/595] docs(framework) Update translation text (#3631) --- doc/locales/fr/LC_MESSAGES/framework-docs.po | 3283 ++-- doc/locales/ko/LC_MESSAGES/framework-docs.po | 13496 +++++++++------- .../pt_BR/LC_MESSAGES/framework-docs.po | 3086 ++-- .../zh_Hans/LC_MESSAGES/framework-docs.po | 4100 +++-- 4 files changed, 14305 insertions(+), 9660 deletions(-) diff --git a/doc/locales/fr/LC_MESSAGES/framework-docs.po b/doc/locales/fr/LC_MESSAGES/framework-docs.po index 67edf687bbbe..6624d91f9e64 100644 --- a/doc/locales/fr/LC_MESSAGES/framework-docs.po +++ b/doc/locales/fr/LC_MESSAGES/framework-docs.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: Flower Docs\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-05-28 11:47+0200\n" +"POT-Creation-Date: 2024-06-17 16:09+0200\n" "PO-Revision-Date: 2023-09-05 17:54+0000\n" "Last-Translator: Charles Beauville \n" "Language: fr\n" @@ -61,248 +61,248 @@ msgstr "" msgid "" "Flower provides pre-made docker images on `Docker Hub " "`_ that include all necessary dependencies" -" for running the SuperLink. You can also build your own custom docker " -"images from scratch with a different version of Python or Ubuntu if that " -"is what you need. In this guide, we will explain what images exist and " -"how to build them locally." +" for running the SuperLink, SuperNode or ServerApp. You can also build " +"your own custom docker images from scratch with a different version of " +"Python or Linux distribution (Ubuntu/Alpine) if that is what you need. In" +" this guide, we will explain what images exist and how to build them " +"locally." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:9 +#: ../../source/contributor-how-to-build-docker-images.rst:10 msgid "" "Before we can start, we need to meet a few prerequisites in our local " "development environment." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:11 +#: ../../source/contributor-how-to-build-docker-images.rst:12 #, fuzzy msgid "Clone the flower repository." msgstr "**Fourche le dépôt de Flower**" -#: ../../source/contributor-how-to-build-docker-images.rst:17 -#: ../../source/how-to-run-flower-using-docker.rst:144 +#: ../../source/contributor-how-to-build-docker-images.rst:18 +#: ../../source/how-to-run-flower-using-docker.rst:165 msgid "Verify the Docker daemon is running." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:19 -#: ../../source/how-to-run-flower-using-docker.rst:146 +#: ../../source/contributor-how-to-build-docker-images.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:167 msgid "" "Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:23 -msgid "" -"Currently, Flower provides two images, a ``base`` image and a " -"``superlink`` image. The base image, as the name suggests, contains basic" -" dependencies that the SuperLink needs. This includes system " -"dependencies, Python and Python tools. The SuperLink image is based on " -"the base image, but it additionally installs the SuperLink using ``pip``." -msgstr "" - -#: ../../source/contributor-how-to-build-docker-images.rst:28 +#: ../../source/contributor-how-to-build-docker-images.rst:25 msgid "" "The build instructions that assemble the images are located in the " "respective Dockerfiles. You can find them in the subdirectories of " "``src/docker``." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:31 +#: ../../source/contributor-how-to-build-docker-images.rst:28 msgid "" -"Both, base and SuperLink image are configured via build arguments. " -"Through build arguments, we can make our build more flexible. For " -"example, in the base image, we can specify the version of Python to " -"install using the ``PYTHON_VERSION`` build argument. Some of the build " -"arguments have default values, others must be specified when building the" -" image. All available build arguments for each image are listed in one of" -" the tables below." +"Flower Docker images are configured via build arguments. Through build " +"arguments, we can make the creation of images more flexible. For example," +" in the base image, we can specify the version of Python to install using" +" the ``PYTHON_VERSION`` build argument. Some of the build arguments have " +"default values, others must be specified when building the image. All " +"available build arguments for each image are listed in one of the tables " +"below." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:38 +#: ../../source/contributor-how-to-build-docker-images.rst:35 #, fuzzy msgid "Building the base image" msgstr "Chargement des données" -#: ../../source/contributor-how-to-build-docker-images.rst:44 -#: ../../source/contributor-how-to-build-docker-images.rst:86 +#: ../../source/contributor-how-to-build-docker-images.rst:41 +#: ../../source/contributor-how-to-build-docker-images.rst:98 #, fuzzy msgid "Build argument" msgstr "Amélioration de la documentation" -#: ../../source/contributor-how-to-build-docker-images.rst:45 -#: ../../source/contributor-how-to-build-docker-images.rst:87 +#: ../../source/contributor-how-to-build-docker-images.rst:42 +#: ../../source/contributor-how-to-build-docker-images.rst:99 #, fuzzy msgid "Description" msgstr "Dépréciations" -#: ../../source/contributor-how-to-build-docker-images.rst:46 -#: ../../source/contributor-how-to-build-docker-images.rst:88 +#: ../../source/contributor-how-to-build-docker-images.rst:43 +#: ../../source/contributor-how-to-build-docker-images.rst:100 #, fuzzy msgid "Required" msgstr "Changements nécessaires" -#: ../../source/contributor-how-to-build-docker-images.rst:47 -#: ../../source/contributor-how-to-build-docker-images.rst:89 +#: ../../source/contributor-how-to-build-docker-images.rst:44 +#: ../../source/contributor-how-to-build-docker-images.rst:101 #, fuzzy msgid "Example" msgstr "Exemples de PyTorch" -#: ../../source/contributor-how-to-build-docker-images.rst:48 -#: ../../source/contributor-how-to-build-docker-images.rst:94 -#, fuzzy -msgid "``PYTHON_VERSION``" -msgstr "Version Python" - -#: ../../source/contributor-how-to-build-docker-images.rst:49 -msgid "Version of ``python`` to be installed." +#: ../../source/contributor-how-to-build-docker-images.rst:45 +msgid "``DISTRO``" msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:50 -#: ../../source/contributor-how-to-build-docker-images.rst:54 -#: ../../source/contributor-how-to-build-docker-images.rst:58 -#: ../../source/contributor-how-to-build-docker-images.rst:108 +#: ../../source/contributor-how-to-build-docker-images.rst:46 #, fuzzy -msgid "Yes" -msgstr "Types" +msgid "The Linux distribution to use as the base image." +msgstr "Chargement des données" +#: ../../source/contributor-how-to-build-docker-images.rst:47 #: ../../source/contributor-how-to-build-docker-images.rst:51 +#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:71 +#: ../../source/contributor-how-to-build-docker-images.rst:104 #, fuzzy -msgid "``3.11``" -msgstr "1.0.0rc1" +msgid "No" +msgstr "Aucun" -#: ../../source/contributor-how-to-build-docker-images.rst:52 -msgid "``PIP_VERSION``" +#: ../../source/contributor-how-to-build-docker-images.rst:48 +msgid "``ubuntu``" msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:53 -msgid "Version of ``pip`` to be installed." +#: ../../source/contributor-how-to-build-docker-images.rst:49 +#, fuzzy +msgid "``DISTRO_VERSION``" +msgstr "Version Python" + +#: ../../source/contributor-how-to-build-docker-images.rst:50 +msgid "Version of the Linux distribution." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:52 #, fuzzy -msgid "``23.0.1``" +msgid "``22.04``" msgstr "1.0.0rc1" +#: ../../source/contributor-how-to-build-docker-images.rst:53 +#, fuzzy +msgid "``PYTHON_VERSION``" +msgstr "Version Python" + +#: ../../source/contributor-how-to-build-docker-images.rst:54 +msgid "Version of ``python`` to be installed." +msgstr "" + #: ../../source/contributor-how-to-build-docker-images.rst:56 -msgid "``SETUPTOOLS_VERSION``" +msgid "``3.11`` or ``3.11.1``" msgstr "" #: ../../source/contributor-how-to-build-docker-images.rst:57 -msgid "Version of ``setuptools`` to be installed." +msgid "``PIP_VERSION``" +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:58 +msgid "Version of ``pip`` to be installed." msgstr "" #: ../../source/contributor-how-to-build-docker-images.rst:59 +#: ../../source/contributor-how-to-build-docker-images.rst:63 +#: ../../source/contributor-how-to-build-docker-images.rst:67 +#: ../../source/contributor-how-to-build-docker-images.rst:108 #, fuzzy -msgid "``69.0.2``" -msgstr "``1.0.0b0``" +msgid "Yes" +msgstr "Types" #: ../../source/contributor-how-to-build-docker-images.rst:60 -#: ../../source/contributor-how-to-build-docker-images.rst:98 -msgid "``UBUNTU_VERSION``" -msgstr "" +#, fuzzy +msgid "``23.0.1``" +msgstr "1.0.0rc1" #: ../../source/contributor-how-to-build-docker-images.rst:61 -msgid "Version of the official Ubuntu Docker image." +msgid "``SETUPTOOLS_VERSION``" msgstr "" #: ../../source/contributor-how-to-build-docker-images.rst:62 -msgid "Defaults to ``22.04``." +msgid "Version of ``setuptools`` to be installed." msgstr "" +#: ../../source/contributor-how-to-build-docker-images.rst:64 +#, fuzzy +msgid "``69.0.2``" +msgstr "``1.0.0b0``" + #: ../../source/contributor-how-to-build-docker-images.rst:65 -msgid "" -"The following example creates a base image with Python 3.11.0, pip 23.0.1" -" and setuptools 69.0.2:" +msgid "``FLWR_VERSION``" msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:76 -msgid "" -"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that " -"the build arguments as well as the name and tag can be adapted to your " -"needs. These values serve as examples only." +#: ../../source/contributor-how-to-build-docker-images.rst:66 +msgid "Version of Flower to be installed." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:80 +#: ../../source/contributor-how-to-build-docker-images.rst:68 #, fuzzy -msgid "Building the SuperLink image" -msgstr "Démarrer le serveur" +msgid "``1.8.0``" +msgstr "``1.0.0b0``" -#: ../../source/contributor-how-to-build-docker-images.rst:90 -msgid "``BASE_REPOSITORY``" +#: ../../source/contributor-how-to-build-docker-images.rst:69 +msgid "``FLWR_PACKAGE``" msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:91 -msgid "The repository name of the base image." +#: ../../source/contributor-how-to-build-docker-images.rst:70 +msgid "The Flower package to be installed." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:92 -msgid "Defaults to ``flwr/base``." +#: ../../source/contributor-how-to-build-docker-images.rst:72 +msgid "``flwr`` or ``flwr-nightly``" msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:95 -#, fuzzy -msgid "The Python version of the base image." -msgstr "Évaluer la réponse d'un client." +#: ../../source/contributor-how-to-build-docker-images.rst:75 +msgid "" +"The following example creates a base Ubuntu/Alpine image with Python " +"3.11.0, pip 23.0.1, setuptools 69.0.2 and Flower 1.8.0:" +msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:96 -msgid "Defaults to ``py3.11``." +#: ../../source/contributor-how-to-build-docker-images.rst:88 +msgid "" +"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that " +"the build arguments as well as the name and tag can be adapted to your " +"needs. These values serve as examples only." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:99 +#: ../../source/contributor-how-to-build-docker-images.rst:92 #, fuzzy -msgid "The Ubuntu version of the base image." -msgstr "Chargement des données" - -#: ../../source/contributor-how-to-build-docker-images.rst:100 -msgid "Defaults to ``ubuntu22.04``." -msgstr "" +msgid "Building the SuperLink/SuperNode or ServerApp image" +msgstr "Démarrer le serveur" #: ../../source/contributor-how-to-build-docker-images.rst:102 -msgid "``FLWR_PACKAGE``" +msgid "``BASE_REPOSITORY``" msgstr "" #: ../../source/contributor-how-to-build-docker-images.rst:103 -msgid "The PyPI package to install." +msgid "The repository name of the base image." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:104 -#, fuzzy -msgid "Defaults to ``flwr``." -msgstr "Flux de travail" +#: ../../source/contributor-how-to-build-docker-images.rst:105 +msgid "``flwr/base``" +msgstr "" #: ../../source/contributor-how-to-build-docker-images.rst:106 -msgid "``FLWR_VERSION``" +msgid "``BASE_IMAGE``" msgstr "" #: ../../source/contributor-how-to-build-docker-images.rst:107 -msgid "Version of Flower to be installed." -msgstr "" - -#: ../../source/contributor-how-to-build-docker-images.rst:109 #, fuzzy -msgid "``1.8.0``" -msgstr "``1.0.0b0``" +msgid "The Tag of the Flower base image." +msgstr "Chargement des données" -#: ../../source/contributor-how-to-build-docker-images.rst:112 -msgid "" -"The following example creates a SuperLink image with the official Flower " -"base image py3.11-ubuntu22.04 and Flower 1.8.0:" +#: ../../source/contributor-how-to-build-docker-images.rst:109 +msgid "``1.8.0-py3.10-ubuntu22.04``" msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:122 +#: ../../source/contributor-how-to-build-docker-images.rst:111 msgid "" -"The name of image is ``flwr_superlink`` and the tag ``0.1.0``. Remember " -"that the build arguments as well as the name and tag can be adapted to " -"your needs. These values serve as examples only." +"The following example creates a SuperLink/SuperNode or ServerApp image " +"with the official Flower base image:" msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:125 +#: ../../source/contributor-how-to-build-docker-images.rst:122 msgid "" "If you want to use your own base image instead of the official Flower " -"base image, all you need to do is set the ``BASE_REPOSITORY``, " -"``PYTHON_VERSION`` and ``UBUNTU_VERSION`` build arguments." +"base image, all you need to do is set the ``BASE_REPOSITORY`` build " +"argument." msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:138 +#: ../../source/contributor-how-to-build-docker-images.rst:133 msgid "After creating the image, we can test whether the image is working:" msgstr "" @@ -414,146 +414,6 @@ msgid "" " on our `GitHub repo `_." msgstr "" -#: ../../source/contributor-how-to-create-new-messages.rst:2 -msgid "Creating New Messages" -msgstr "Création de nouveaux messages" - -#: ../../source/contributor-how-to-create-new-messages.rst:4 -msgid "" -"This is a simple guide for creating a new type of message between the " -"server and clients in Flower." -msgstr "" -"Voici un guide simple pour créer un nouveau type de message entre le " -"serveur et les clients dans Flower." - -#: ../../source/contributor-how-to-create-new-messages.rst:6 -msgid "" -"Let's suppose we have the following example functions in " -":code:`server.py` and :code:`numpy_client.py`..." -msgstr "" -"Supposons que nous ayons les fonctions suivantes dans :code:`server.py` " -"et :code:`numpy_client.py`..." - -#: ../../source/contributor-how-to-create-new-messages.rst:8 -msgid "Server's side:" -msgstr "Côté serveur :" - -#: ../../source/contributor-how-to-create-new-messages.rst:17 -msgid "Client's side:" -msgstr "Côté client :" - -#: ../../source/contributor-how-to-create-new-messages.rst:26 -msgid "" -"Let's now see what we need to implement in order to get this simple " -"function between the server and client to work!" -msgstr "" -"Voyons maintenant ce que nous devons mettre en œuvre pour que cette " -"simple fonction entre le serveur et le client fonctionne !" - -#: ../../source/contributor-how-to-create-new-messages.rst:30 -msgid "Message Types for Protocol Buffers" -msgstr "Types de messages pour les tampons de protocole" - -#: ../../source/contributor-how-to-create-new-messages.rst:32 -#, fuzzy -msgid "" -"The first thing we need to do is to define a message type for the RPC " -"system in :code:`transport.proto`. Note that we have to do it for both " -"the request and response messages. For more details on the syntax of " -"proto3, please see the `official documentation `_." -msgstr "" -"La première chose à faire est de définir un type de message pour le " -"système RPC dans :code:`transport.proto`. Notez que nous devons le faire " -"à la fois pour les messages de demande et de réponse. Pour plus de " -"détails sur la syntaxe de proto3, veuillez consulter la `documentation " -"officielle `_." - -#: ../../source/contributor-how-to-create-new-messages.rst:35 -msgid "Within the :code:`ServerMessage` block:" -msgstr "Dans le bloc :code:`ServerMessage` :" - -#: ../../source/contributor-how-to-create-new-messages.rst:52 -msgid "Within the ClientMessage block:" -msgstr "Dans le bloc ClientMessage :" - -#: ../../source/contributor-how-to-create-new-messages.rst:70 -msgid "" -"Make sure to also add a field of the newly created message type in " -":code:`oneof msg`." -msgstr "" -"Veille à ajouter également un champ du type de message nouvellement créé " -"dans :code:`oneof msg`." - -#: ../../source/contributor-how-to-create-new-messages.rst:72 -msgid "Once that is done, we will compile the file with:" -msgstr "Une fois que c'est fait, nous compilerons le fichier avec :" - -#: ../../source/contributor-how-to-create-new-messages.rst:78 -msgid "If it compiles successfully, you should see the following message:" -msgstr "S'il se compile avec succès, tu devrais voir le message suivant :" - -#: ../../source/contributor-how-to-create-new-messages.rst:87 -msgid "Serialization and Deserialization Functions" -msgstr "Fonctions de sérialisation et de désérialisation" - -#: ../../source/contributor-how-to-create-new-messages.rst:89 -msgid "" -"Our next step is to add functions to serialize and deserialize Python " -"datatypes to or from our defined RPC message types. You should add these " -"functions in :code:`serde.py`." -msgstr "" -"La prochaine étape consiste à ajouter des fonctions pour sérialiser et " -"désérialiser les types de données Python vers ou à partir des types de " -"messages RPC définis. Tu dois ajouter ces fonctions dans " -":code:`serde.py`." - -#: ../../source/contributor-how-to-create-new-messages.rst:91 -msgid "The four functions:" -msgstr "Les quatre fonctions :" - -#: ../../source/contributor-how-to-create-new-messages.rst:112 -msgid "Sending the Message from the Server" -msgstr "Envoi du message à partir du serveur" - -#: ../../source/contributor-how-to-create-new-messages.rst:114 -msgid "" -"Now write the request function in your Client Proxy class (e.g., " -":code:`grpc_client_proxy.py`) using the serde functions you just created:" -msgstr "" -"Écris maintenant la fonction de demande dans ta classe Client Proxy (par " -"exemple, :code:`grpc_client_proxy.py`) en utilisant les fonctions serde " -"que tu viens de créer :" - -#: ../../source/contributor-how-to-create-new-messages.rst:128 -msgid "Receiving the Message by the Client" -msgstr "Réception du message par le client" - -#: ../../source/contributor-how-to-create-new-messages.rst:130 -msgid "" -"Last step! Modify the code in :code:`message_handler.py` to check the " -"field of your message and call the :code:`example_response` function. " -"Remember to use the serde functions!" -msgstr "" -"Dernière étape ! Modifie le code dans :code:`message_handler.py` pour " -"vérifier le champ de ton message et appeler la fonction " -":code:`example_response`. N'oublie pas d'utiliser les fonctions serde !" - -#: ../../source/contributor-how-to-create-new-messages.rst:132 -msgid "Within the handle function:" -msgstr "Dans le cadre de la fonction de poignée :" - -#: ../../source/contributor-how-to-create-new-messages.rst:139 -msgid "And add a new function:" -msgstr "Et ajoute une nouvelle fonction :" - -#: ../../source/contributor-how-to-create-new-messages.rst:149 -msgid "Hopefully, when you run your program you will get the intended result!" -msgstr "" -"Avec un peu de chance, lorsque tu exécuteras ton programme, tu obtiendras" -" le résultat escompté !" - #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:2 msgid "Develop in VSCode Dev Containers" msgstr "Utiliser les conteneurs VS Code Remote" @@ -961,29 +821,79 @@ msgstr "" msgid "Check the draft release on GitHub, and if everything is good, publish it." msgstr "" +#: ../../source/contributor-how-to-release-flower.rst:15 +#, fuzzy +msgid "Trigger the CI for building the Docker images." +msgstr "Démarrer le serveur" + #: ../../source/contributor-how-to-release-flower.rst:17 +msgid "" +"To trigger the workflow, a collaborator must create a " +"``workflow_dispatch`` event in the GitHub CI. This can be done either " +"through the UI or via the GitHub CLI. The event requires only one input, " +"the Flower version, to be released." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:21 +#, fuzzy +msgid "**Via the UI**" +msgstr "**Review the PR**" + +#: ../../source/contributor-how-to-release-flower.rst:23 +msgid "" +"Go to the ``Build docker images`` workflow `page " +"`_." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:24 +msgid "" +"Click on the ``Run workflow`` button and type the new version of Flower " +"in the ``Version of Flower`` input field." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:25 +msgid "Click on the **green** ``Run workflow`` button." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:29 +msgid "**Via the GitHub CI**" +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:31 +msgid "" +"Make sure you are logged in via ``gh auth login`` and that the current " +"working directory is the root of the Flower repository." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:32 +msgid "" +"Trigger the workflow via ``gh workflow run docker-images.yml -f flwr-" +"version=``." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:35 msgid "After the release" msgstr "Après la publication" -#: ../../source/contributor-how-to-release-flower.rst:19 +#: ../../source/contributor-how-to-release-flower.rst:37 msgid "Create a pull request which contains the following changes:" msgstr "Crée une demande de pull qui contient les modifications suivantes :" -#: ../../source/contributor-how-to-release-flower.rst:21 +#: ../../source/contributor-how-to-release-flower.rst:39 msgid "Increase the minor version in ``pyproject.toml`` by one." msgstr "Augmente la version mineure de ``pyproject.toml`` d'une unité." -#: ../../source/contributor-how-to-release-flower.rst:22 +#: ../../source/contributor-how-to-release-flower.rst:40 msgid "Update all files which contain the current version number if necessary." msgstr "" "Mets à jour tous les fichiers qui contiennent le numéro de version actuel" " si nécessaire." -#: ../../source/contributor-how-to-release-flower.rst:23 +#: ../../source/contributor-how-to-release-flower.rst:41 msgid "Add a new ``Unreleased`` section in ``changelog.md``." msgstr "Ajoute une nouvelle section ``Unreleased`` dans ``changelog.md``." -#: ../../source/contributor-how-to-release-flower.rst:25 +#: ../../source/contributor-how-to-release-flower.rst:43 msgid "" "Merge the pull request on the same day (i.e., before a new nightly " "release gets published to PyPI)." @@ -991,15 +901,15 @@ msgstr "" "Fusionne la pull request le jour même (c'est-à-dire avant qu'une nouvelle" " version nightly ne soit publiée sur PyPI)." -#: ../../source/contributor-how-to-release-flower.rst:28 +#: ../../source/contributor-how-to-release-flower.rst:46 msgid "Publishing a pre-release" msgstr "Publier une pré-version" -#: ../../source/contributor-how-to-release-flower.rst:31 +#: ../../source/contributor-how-to-release-flower.rst:49 msgid "Pre-release naming" msgstr "Nom de la pré-version" -#: ../../source/contributor-how-to-release-flower.rst:33 +#: ../../source/contributor-how-to-release-flower.rst:51 msgid "" "PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases" " MUST use one of the following naming patterns:" @@ -1008,39 +918,39 @@ msgstr "" "Les préversions DOIVENT utiliser l'un des modèles de dénomination " "suivants :" -#: ../../source/contributor-how-to-release-flower.rst:35 +#: ../../source/contributor-how-to-release-flower.rst:53 msgid "Alpha: ``MAJOR.MINOR.PATCHaN``" msgstr "Alpha : ``MAJOR.MINOR.PATCHaN``" -#: ../../source/contributor-how-to-release-flower.rst:36 +#: ../../source/contributor-how-to-release-flower.rst:54 msgid "Beta: ``MAJOR.MINOR.PATCHbN``" msgstr "Bêta : ``MAJOR.MINOR.PATCHbN``" -#: ../../source/contributor-how-to-release-flower.rst:37 +#: ../../source/contributor-how-to-release-flower.rst:55 msgid "Release candidate (RC): ``MAJOR.MINOR.PATCHrcN``" msgstr "Candidat à la publication (RC) : ``MAJOR.MINOR.PATCHrcN``" -#: ../../source/contributor-how-to-release-flower.rst:39 +#: ../../source/contributor-how-to-release-flower.rst:57 msgid "Examples include:" msgstr "Voici quelques exemples :" -#: ../../source/contributor-how-to-release-flower.rst:41 +#: ../../source/contributor-how-to-release-flower.rst:59 msgid "``1.0.0a0``" msgstr "``1.0.0a0``" -#: ../../source/contributor-how-to-release-flower.rst:42 +#: ../../source/contributor-how-to-release-flower.rst:60 msgid "``1.0.0b0``" msgstr "``1.0.0b0``" -#: ../../source/contributor-how-to-release-flower.rst:43 +#: ../../source/contributor-how-to-release-flower.rst:61 msgid "``1.0.0rc0``" msgstr "``1.0.0rc0``" -#: ../../source/contributor-how-to-release-flower.rst:44 +#: ../../source/contributor-how-to-release-flower.rst:62 msgid "``1.0.0rc1``" msgstr "1.0.0rc1" -#: ../../source/contributor-how-to-release-flower.rst:46 +#: ../../source/contributor-how-to-release-flower.rst:64 msgid "" "This is in line with PEP-440 and the recommendations from the Python " "Packaging Authority (PyPA):" @@ -1048,11 +958,11 @@ msgstr "" "Ceci est conforme au PEP-440 et aux recommandations de l'Autorité de " "l'emballage Python (PyPA) :" -#: ../../source/contributor-how-to-release-flower.rst:49 +#: ../../source/contributor-how-to-release-flower.rst:67 msgid "`PEP-440 `_" msgstr "`PEP-440 `_" -#: ../../source/contributor-how-to-release-flower.rst:50 +#: ../../source/contributor-how-to-release-flower.rst:68 msgid "" "`PyPA Choosing a versioning scheme " "`_" -#: ../../source/contributor-how-to-release-flower.rst:52 +#: ../../source/contributor-how-to-release-flower.rst:70 msgid "" "Note that the approach defined by PyPA is not compatible with SemVer " "2.0.0 spec, for details consult the `Semantic Versioning Specification " @@ -1074,17 +984,17 @@ msgstr "" "Versioning Specification `_ (en particulier le point 11 sur la préséance)." -#: ../../source/contributor-how-to-release-flower.rst:55 +#: ../../source/contributor-how-to-release-flower.rst:73 msgid "Pre-release classification" msgstr "Classification avant publication" -#: ../../source/contributor-how-to-release-flower.rst:57 +#: ../../source/contributor-how-to-release-flower.rst:75 msgid "Should the next pre-release be called alpha, beta, or release candidate?" msgstr "" "La prochaine préversion doit-elle être appelée alpha, bêta ou release " "candidate ?" -#: ../../source/contributor-how-to-release-flower.rst:59 +#: ../../source/contributor-how-to-release-flower.rst:77 msgid "" "RC: feature complete, no known issues (apart from issues that are " "classified as \"won't fix\" for the next stable release) - if no issues " @@ -1095,11 +1005,11 @@ msgstr "" "version stable) - si aucun problème n'apparaît, cette version deviendra " "la prochaine version stable" -#: ../../source/contributor-how-to-release-flower.rst:60 +#: ../../source/contributor-how-to-release-flower.rst:78 msgid "Beta: feature complete, allowed to have known issues" msgstr "Bêta : fonctionnalité complète, autorisée à avoir des problèmes connus" -#: ../../source/contributor-how-to-release-flower.rst:61 +#: ../../source/contributor-how-to-release-flower.rst:79 msgid "Alpha: not feature complete, allowed to have known issues" msgstr "" "Alpha : les fonctionnalités ne sont pas complètes, les problèmes connus " @@ -2310,7 +2220,7 @@ msgid "Get started as a contributor" msgstr "Devenez un·e contributeur·ice" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 -#: ../../source/how-to-run-flower-using-docker.rst:132 +#: ../../source/how-to-run-flower-using-docker.rst:153 msgid "Prerequisites" msgstr "Prérequis" @@ -4606,11 +4516,11 @@ msgid "" "authentication enabled:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:36 +#: ../../source/how-to-authenticate-supernodes.rst:38 msgid "Let's break down the authentication flags:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:38 +#: ../../source/how-to-authenticate-supernodes.rst:40 msgid "" "The first flag :code:`--auth-list-public-keys` expects a path to a CSV " "file storing all known node public keys. You need to store all known node" @@ -4618,7 +4528,7 @@ msgid "" "file (:code:`.csv`)." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:40 +#: ../../source/how-to-authenticate-supernodes.rst:42 msgid "" "A valid CSV file storing known node public keys should list the keys in " "OpenSSH format, separated by commas and without any comments. For an " @@ -4626,7 +4536,7 @@ msgid "" "known node public keys." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:42 +#: ../../source/how-to-authenticate-supernodes.rst:44 msgid "" "The second and third flags :code:`--auth-superlink-private-key` and :code" ":`--auth-superlink-public-key` expect paths to the server's private and " @@ -4634,7 +4544,7 @@ msgid "" "public key pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:45 +#: ../../source/how-to-authenticate-supernodes.rst:47 msgid "" "In Flower 1.9, there is no support for dynamically removing, editing, or " "adding known node public keys to the SuperLink. To change the set of " @@ -4643,11 +4553,11 @@ msgid "" " nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:51 +#: ../../source/how-to-authenticate-supernodes.rst:53 msgid "Enable node authentication in :code:`SuperNode`" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:53 +#: ../../source/how-to-authenticate-supernodes.rst:55 msgid "" "Similar to the long-running Flower server (:code:`SuperLink`), you can " "easily enable node authentication in the long-running Flower client " @@ -4655,7 +4565,7 @@ msgid "" "authenticated :code:`SuperNode`:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:64 +#: ../../source/how-to-authenticate-supernodes.rst:66 msgid "" "The :code:`--auth-supernode-private-key` flag expects a path to the " "node's private key file and the :code:`--auth-supernode-public-key` flag " @@ -4664,11 +4574,11 @@ msgid "" " ecdsa -b 384`." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:68 +#: ../../source/how-to-authenticate-supernodes.rst:70 msgid "Security notice" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:70 +#: ../../source/how-to-authenticate-supernodes.rst:72 msgid "" "The system's security relies on the credentials of the SuperLink and each" " SuperNode. Therefore, it is imperative to safeguard and safely store the" @@ -4679,14 +4589,14 @@ msgid "" "methods." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:75 -#: ../../source/how-to-enable-ssl-connections.rst:65 +#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:68 #: ../../source/how-to-use-built-in-mods.rst:85 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:287 msgid "Conclusion" msgstr "Conclusion" -#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-authenticate-supernodes.rst:79 msgid "" "You should now have learned how to start a long-running Flower server " "(:code:`SuperLink`) and client (:code:`SuperNode`) with node " @@ -5059,19 +4969,19 @@ msgstr "" "Nous allons maintenant montrer comment écrire un client qui utilise les " "scripts générés précédemment :" -#: ../../source/how-to-enable-ssl-connections.rst:47 +#: ../../source/how-to-enable-ssl-connections.rst:50 msgid "" "When providing certificates, the server expects a tuple of three " "certificates paths: CA certificate, server certificate and server private" " key." msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:51 +#: ../../source/how-to-enable-ssl-connections.rst:54 #, fuzzy msgid "Client (SuperNode)" msgstr "Codes d'état du client." -#: ../../source/how-to-enable-ssl-connections.rst:53 +#: ../../source/how-to-enable-ssl-connections.rst:56 #, fuzzy msgid "" "Use the following terminal command to start a client (SuperNode) that " @@ -5080,7 +4990,7 @@ msgstr "" "Nous allons maintenant montrer comment écrire un client qui utilise les " "scripts générés précédemment :" -#: ../../source/how-to-enable-ssl-connections.rst:61 +#: ../../source/how-to-enable-ssl-connections.rst:64 #, fuzzy msgid "" "When setting :code:`root_certificates`, the client expects a file path to" @@ -5091,7 +5001,7 @@ msgstr "" "utilisons à nouveau :code:`Path` pour simplifier la lecture de ces " "certificats sous forme de chaînes d'octets." -#: ../../source/how-to-enable-ssl-connections.rst:67 +#: ../../source/how-to-enable-ssl-connections.rst:70 #, fuzzy msgid "" "You should now have learned how to generate self-signed certificates " @@ -5102,12 +5012,12 @@ msgstr "" "à l'aide du script donné, à démarrer un serveur compatible SSL et à " "demander à un client d'établir une connexion sécurisée avec lui." -#: ../../source/how-to-enable-ssl-connections.rst:72 +#: ../../source/how-to-enable-ssl-connections.rst:75 #, fuzzy msgid "Additional resources" msgstr "Ressources supplémentaires" -#: ../../source/how-to-enable-ssl-connections.rst:74 +#: ../../source/how-to-enable-ssl-connections.rst:77 msgid "" "These additional sources might be relevant if you would like to dive " "deeper into the topic of certificates:" @@ -5115,11 +5025,11 @@ msgstr "" "Ces sources supplémentaires peuvent être pertinentes si tu souhaites " "approfondir le sujet des certificats :" -#: ../../source/how-to-enable-ssl-connections.rst:76 +#: ../../source/how-to-enable-ssl-connections.rst:79 msgid "`Let's Encrypt `_" msgstr "`Let's Encrypt `_" -#: ../../source/how-to-enable-ssl-connections.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:80 msgid "`certbot `_" msgstr "`certbot `_" @@ -5961,14 +5871,15 @@ msgstr "" msgid "" "The simplest way to get started with Flower is by using the pre-made " "Docker images, which you can find on `Docker Hub " -"`__." +"`__. Supported architectures include " +"``amd64`` and ``arm64v8``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:7 +#: ../../source/how-to-run-flower-using-docker.rst:8 msgid "Before you start, make sure that the Docker daemon is running:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:14 +#: ../../source/how-to-run-flower-using-docker.rst:15 msgid "" "If you do not see the version of Docker but instead get an error saying " "that the command was not found, you will need to install Docker first. " @@ -5976,7 +5887,7 @@ msgid "" "docker/>`_." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:21 msgid "" "On Linux, Docker commands require ``sudo`` privilege. If you want to " "avoid using ``sudo``, you can follow the `Post-installation steps " @@ -5984,7 +5895,7 @@ msgid "" "official Docker website." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:26 +#: ../../source/how-to-run-flower-using-docker.rst:27 msgid "" "To ensure optimal performance and compatibility, the SuperLink, SuperNode" " and ServerApp image must have the same version when running together. " @@ -5992,28 +5903,28 @@ msgid "" "issues that may arise from using different versions." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:31 +#: ../../source/how-to-run-flower-using-docker.rst:32 #, fuzzy msgid "Flower SuperLink" msgstr "flower-superlink" -#: ../../source/how-to-run-flower-using-docker.rst:34 +#: ../../source/how-to-run-flower-using-docker.rst:35 #, fuzzy msgid "Quickstart" msgstr "Démarrage rapide de JAX" -#: ../../source/how-to-run-flower-using-docker.rst:36 +#: ../../source/how-to-run-flower-using-docker.rst:37 msgid "If you're looking to try out Flower, you can use the following command:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:42 +#: ../../source/how-to-run-flower-using-docker.rst:43 msgid "" "The command pulls the Docker image with the tag ``1.8.0`` from Docker " "Hub. The tag specifies the Flower version. In this case, Flower 1.8.0. " "The ``--rm`` flag tells Docker to remove the container after it exits." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:48 +#: ../../source/how-to-run-flower-using-docker.rst:49 msgid "" "By default, the Flower SuperLink keeps state in-memory. When using the " "Docker flag ``--rm``, the state is not persisted between container " @@ -6021,7 +5932,7 @@ msgid "" "system." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:52 +#: ../../source/how-to-run-flower-using-docker.rst:53 msgid "" "The ``-p :`` flag tells Docker to map the ports " "``9091``/``9092`` of the host to ``9091``/``9092`` of the container, " @@ -6031,9 +5942,9 @@ msgid "" " flag ``--insecure``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:59 -#: ../../source/how-to-run-flower-using-docker.rst:238 -#: ../../source/how-to-run-flower-using-docker.rst:354 +#: ../../source/how-to-run-flower-using-docker.rst:60 +#: ../../source/how-to-run-flower-using-docker.rst:259 +#: ../../source/how-to-run-flower-using-docker.rst:376 msgid "" "The ``--insecure`` flag enables insecure communication (using HTTP, not " "HTTPS) and should only be used for testing purposes. We strongly " @@ -6042,49 +5953,60 @@ msgid "" "deploying to a production environment." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:64 +#: ../../source/how-to-run-flower-using-docker.rst:65 msgid "" "You can use ``--help`` to view all available flags that the SuperLink " "supports:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:71 +#: ../../source/how-to-run-flower-using-docker.rst:72 msgid "Mounting a volume to store the state on the host system" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:73 +#: ../../source/how-to-run-flower-using-docker.rst:74 msgid "" "If you want to persist the state of the SuperLink on your host system, " -"all you need to do is specify a path where you want to save the file on " -"your host system and a name for the database file. In the example below, " -"we tell Docker via the flag ``--volume`` to mount the user's home " -"directory (``~/`` on your host) into the ``/app/`` directory of the " -"container. Furthermore, we use the flag ``--database`` to specify the " -"name of the database file." +"all you need to do is specify a directory where you want to save the file" +" on your host system and a name for the database file. By default, the " +"SuperLink container runs with a non-root user called ``app`` with the " +"user ID ``49999``. It is recommended to create new directory and change " +"the user ID of the directory to ``49999`` to ensure the mounted directory" +" has the proper permissions. If you later want to delete the directory, " +"you can change the user ID back to the current user ID by running ``sudo " +"chown -R $USER:$(id -gn) state``." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:82 +msgid "" +"In the example below, we create a new directory, change the user ID and " +"tell Docker via the flag ``--volume`` to mount the local ``state`` " +"directory into the ``/app/state`` directory of the container. " +"Furthermore, we use the flag ``--database`` to specify the name of the " +"database file." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:86 +#: ../../source/how-to-run-flower-using-docker.rst:95 msgid "" "As soon as the SuperLink starts, the file ``state.db`` is created in the " -"user's home directory on your host system. If the file already exists, " -"the SuperLink tries to restore the state from the file. To start the " +"``state`` directory on your host system. If the file already exists, the " +"SuperLink tries to restore the state from the file. To start the " "SuperLink with an empty database, simply remove the ``state.db`` file." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:91 -#: ../../source/how-to-run-flower-using-docker.rst:260 -#: ../../source/how-to-run-flower-using-docker.rst:375 +#: ../../source/how-to-run-flower-using-docker.rst:100 +#: ../../source/how-to-run-flower-using-docker.rst:281 +#: ../../source/how-to-run-flower-using-docker.rst:397 #, fuzzy msgid "Enabling SSL for secure connections" msgstr "Collecte centralisée des données" -#: ../../source/how-to-run-flower-using-docker.rst:93 +#: ../../source/how-to-run-flower-using-docker.rst:102 msgid "" "To enable SSL, you will need a PEM-encoded root certificate, a PEM-" "encoded private key and a PEM-encoded certificate chain." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:97 +#: ../../source/how-to-run-flower-using-docker.rst:106 msgid "" "For testing purposes, you can generate your own self-signed certificates." " The `Enable SSL connections `__ is already installed " "in the ``flwr/supernode`` base image, so you only need to include other " @@ -6164,20 +6098,20 @@ msgid "" "``tensorflow``, etc." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:179 +#: ../../source/how-to-run-flower-using-docker.rst:200 msgid "" "Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` " "example, create a new file called ``Dockerfile.supernode`` in ``examples" "/quickstart-pytorch``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:182 +#: ../../source/how-to-run-flower-using-docker.rst:203 msgid "" "The ``Dockerfile.supernode`` contains the instructions that assemble the " "SuperNode image." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:196 +#: ../../source/how-to-run-flower-using-docker.rst:217 msgid "" "In the first two lines, we instruct Docker to use the SuperNode image " "tagged ``nightly`` as a base image and set our working directory to " @@ -6190,70 +6124,70 @@ msgid "" "(``:``) that will be run inside the ClientApp." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:205 +#: ../../source/how-to-run-flower-using-docker.rst:226 #, fuzzy msgid "Building the SuperNode Docker image" msgstr "Démarrer le serveur" -#: ../../source/how-to-run-flower-using-docker.rst:207 +#: ../../source/how-to-run-flower-using-docker.rst:228 msgid "" "Next, we build the SuperNode Docker image by running the following " "command in the directory where Dockerfile and ClientApp code are located." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:214 +#: ../../source/how-to-run-flower-using-docker.rst:235 msgid "" "We gave the image the name ``flwr_supernode``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:219 +#: ../../source/how-to-run-flower-using-docker.rst:240 #, fuzzy msgid "Running the SuperNode Docker image" msgstr "Démarrer le serveur" -#: ../../source/how-to-run-flower-using-docker.rst:221 +#: ../../source/how-to-run-flower-using-docker.rst:242 msgid "Now that we have built the SuperNode image, we can finally run it." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:229 -#: ../../source/how-to-run-flower-using-docker.rst:345 +#: ../../source/how-to-run-flower-using-docker.rst:250 +#: ../../source/how-to-run-flower-using-docker.rst:367 msgid "Let's break down each part of this command:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:231 -#: ../../source/how-to-run-flower-using-docker.rst:347 +#: ../../source/how-to-run-flower-using-docker.rst:252 +#: ../../source/how-to-run-flower-using-docker.rst:369 msgid "``docker run``: This is the command to run a new Docker container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:232 -#: ../../source/how-to-run-flower-using-docker.rst:348 +#: ../../source/how-to-run-flower-using-docker.rst:253 +#: ../../source/how-to-run-flower-using-docker.rst:370 msgid "" "``--rm``: This option specifies that the container should be " "automatically removed when it stops." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:233 +#: ../../source/how-to-run-flower-using-docker.rst:254 msgid "``flwr_supernode:0.0.1``: The name the tag of the Docker image to use." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:234 -#: ../../source/how-to-run-flower-using-docker.rst:350 +#: ../../source/how-to-run-flower-using-docker.rst:255 +#: ../../source/how-to-run-flower-using-docker.rst:372 msgid "``--insecure``: This option enables insecure communication." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--server 192.168.1.100:9092``: This option specifies the address of the" -" SuperLinks Fleet" +"``--superlink 192.168.1.100:9092``: This option specifies the address of " +"the SuperLinks Fleet" msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "API to connect to. Remember to update it with your SuperLink IP." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:248 +#: ../../source/how-to-run-flower-using-docker.rst:269 msgid "" "To test running Flower locally, you can create a `bridge network " "`__." +" `Docker Hub `__." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:400 +#: ../../source/how-to-run-flower-using-docker.rst:460 msgid "Pinning a Docker image to a specific version" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:402 +#: ../../source/how-to-run-flower-using-docker.rst:462 msgid "" "It may happen that we update the images behind the tags. Such updates " "usually include security updates of system dependencies that should not " @@ -6432,22 +6404,22 @@ msgid "" "instead of the tag." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:407 +#: ../../source/how-to-run-flower-using-docker.rst:467 msgid "" "The following command returns the current image hash referenced by the " "``superlink:1.8.0`` tag:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:414 +#: ../../source/how-to-run-flower-using-docker.rst:474 msgid "Next, we can pin the hash when running a new SuperLink container:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:423 +#: ../../source/how-to-run-flower-using-docker.rst:483 #, fuzzy msgid "Setting environment variables" msgstr "Mise en place de l'environnement de codage" -#: ../../source/how-to-run-flower-using-docker.rst:425 +#: ../../source/how-to-run-flower-using-docker.rst:485 msgid "" "To set a variable inside a Docker container, you can use the ``-e " "=`` flag." @@ -7433,9 +7405,10 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:174 msgid "" -"Here's another example to start with HTTPS. Use the ``--certificates`` " -"command line argument to pass paths to (CA certificate, server " -"certificate, and server private key)." +"Here's another example to start with HTTPS. Use the ``--ssl-ca-" +"certfile``, ``--ssl-certfile``, and ``--ssl-keyfile`` command line " +"options to pass paths to (CA certificate, server certificate, and server " +"private key)." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:201 @@ -7958,12 +7931,12 @@ msgstr "Configuration du contributeur" msgid "Contributor how-to guides" msgstr "Guide pour les contributeurs" -#: ../../source/index.rst:173 +#: ../../source/index.rst:172 #, fuzzy msgid "Contributor explanations" msgstr "Explications" -#: ../../source/index.rst:179 +#: ../../source/index.rst:178 #, fuzzy msgid "Contributor references" msgstr "Configuration du contributeur" @@ -8141,7 +8114,8 @@ msgstr "flower-driver-api" msgid "flwr" msgstr "Fleur" -#: ../../source/ref-api/flwr.rst:25 ../../source/ref-api/flwr.server.rst:51 +#: ../../source/ref-api/flwr.client.rst:45 ../../source/ref-api/flwr.rst:25 +#: ../../source/ref-api/flwr.server.rst:49 msgid "Modules" msgstr "" @@ -8167,7 +8141,7 @@ msgid ":py:obj:`flwr.server `\\" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 -#: ../../source/ref-api/flwr.server.rst:40::1 flwr.server:1 +#: ../../source/ref-api/flwr.server.rst:38::1 flwr.server:1 #: flwr.server.server.Server:1 of #, fuzzy msgid "Flower server." @@ -8186,6 +8160,7 @@ msgstr "Simulation de moniteur" msgid "client" msgstr "client" +#: ../../source/ref-api/flwr.client.mod.rst:13 #: ../../source/ref-api/flwr.client.rst:13 #: ../../source/ref-api/flwr.common.rst:13 #: ../../source/ref-api/flwr.server.rst:13 @@ -8237,9 +8212,10 @@ msgstr "" msgid "Start a Flower NumPyClient which connects to a gRPC server." msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:30 #: ../../source/ref-api/flwr.client.rst:27 #: ../../source/ref-api/flwr.common.rst:32 -#: ../../source/ref-api/flwr.server.rst:28 +#: ../../source/ref-api/flwr.server.rst:26 #: ../../source/ref-api/flwr.server.strategy.rst:17 #: ../../source/ref-api/flwr.server.workflow.rst:17 msgid "Classes" @@ -8275,6 +8251,16 @@ msgstr "" msgid "Abstract base class for Flower clients using NumPy." msgstr "" +#: ../../source/ref-api/flwr.client.rst:52::1 +#, fuzzy +msgid ":py:obj:`flwr.client.mod `\\" +msgstr "serveur.stratégie.Stratégie" + +#: ../../source/ref-api/flwr.client.rst:52::1 flwr.client.mod:1 of +#, fuzzy +msgid "Flower Built-in Mods." +msgstr "Client de Flower" + #: flwr.client.client.Client:1 flwr.client.numpy_client.NumPyClient:1 #: flwr.server.client_manager.ClientManager:1 #: flwr.server.driver.driver.Driver:1 flwr.server.strategy.strategy.Strategy:1 @@ -8285,6 +8271,7 @@ msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:15 #: ../../source/ref-api/flwr.client.ClientApp.rst:15 #: ../../source/ref-api/flwr.client.NumPyClient.rst:15 +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:15 #: ../../source/ref-api/flwr.common.Array.rst:15 #: ../../source/ref-api/flwr.common.ClientMessage.rst:15 #: ../../source/ref-api/flwr.common.ConfigsRecord.rst:15 @@ -8463,6 +8450,7 @@ msgstr "" #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit #: flwr.client.client.Client.get_parameters #: flwr.client.client.Client.get_properties +#: flwr.client.mod.localdp_mod.LocalDpMod #: flwr.client.numpy_client.NumPyClient.evaluate #: flwr.client.numpy_client.NumPyClient.fit #: flwr.client.numpy_client.NumPyClient.get_parameters @@ -8617,10 +8605,11 @@ msgstr "" msgid "ClientApp" msgstr "client" -#: flwr.client.client_app.ClientApp:1 flwr.common.constant.MessageType:1 -#: flwr.common.constant.MessageTypeLegacy:1 flwr.common.context.Context:1 -#: flwr.common.message.Error:1 flwr.common.message.Message:1 -#: flwr.common.message.Metadata:1 flwr.common.record.parametersrecord.Array:1 +#: flwr.client.client_app.ClientApp:1 flwr.client.mod.localdp_mod.LocalDpMod:1 +#: flwr.common.constant.MessageType:1 flwr.common.constant.MessageTypeLegacy:1 +#: flwr.common.context.Context:1 flwr.common.message.Error:1 +#: flwr.common.message.Message:1 flwr.common.message.Metadata:1 +#: flwr.common.record.parametersrecord.Array:1 #: flwr.common.record.recordset.RecordSet:1 flwr.common.typing.ClientMessage:1 #: flwr.common.typing.DisconnectRes:1 flwr.common.typing.EvaluateIns:1 #: flwr.common.typing.EvaluateRes:1 flwr.common.typing.FitIns:1 @@ -8641,7 +8630,8 @@ msgstr "" #: flwr.client.client_app.ClientApp:4 #: flwr.client.client_app.ClientApp.evaluate:4 #: flwr.client.client_app.ClientApp.query:4 -#: flwr.client.client_app.ClientApp.train:4 flwr.server.app.start_server:41 +#: flwr.client.client_app.ClientApp.train:4 +#: flwr.client.mod.localdp_mod.LocalDpMod:22 flwr.server.app.start_server:41 #: flwr.server.server_app.ServerApp:4 flwr.server.server_app.ServerApp.main:4 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:29 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:22 @@ -8865,6 +8855,239 @@ msgid "" "arbitrary property values back to the server." msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:2 +msgid "mod" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`adaptiveclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:1 of +#, fuzzy +msgid "Client-side adaptive clipping modifier." +msgstr "Logique côté client" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`fixedclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:1 of +#, fuzzy +msgid "Client-side fixed clipping modifier." +msgstr "Logique côté client" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#, fuzzy +msgid ":py:obj:`make_ffn `\\ \\(ffn\\, mods\\)" +msgstr "serveur.stratégie.Stratégie" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.utils.make_ffn:1 of +msgid "." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`secagg_mod `\\ \\(msg\\, ctxt\\, " +"call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secagg_mod.secagg_mod:1 of +msgid "Handle incoming message and return results, following the SecAgg protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`secaggplus_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secaggplus_mod.secaggplus_mod:1 of +msgid "" +"Handle incoming message and return results, following the SecAgg+ " +"protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`message_size_mod `\\ \\(msg\\," +" ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.message_size_mod:1 of +msgid "Message size mod." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`parameters_size_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.parameters_size_mod:1 of +#, fuzzy +msgid "Parameters size mod." +msgstr "Paramètres du modèle." + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +msgid "" +":py:obj:`LocalDpMod `\\ \\(clipping\\_norm\\," +" sensitivity\\, ...\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +#: flwr.client.mod.localdp_mod.LocalDpMod:1 of +#, fuzzy +msgid "Modifier for local differential privacy." +msgstr "Confidentialité différentielle" + +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:2 +msgid "LocalDpMod" +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:3 of +msgid "" +"This mod clips the client model updates and adds noise to the params " +"before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:12 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:10 +#: flwr.client.mod.localdp_mod.LocalDpMod:6 of +msgid "It operates on messages of type `MessageType.TRAIN`." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:8 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 +#: of +msgid "The value of the clipping norm." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:10 of +msgid "The sensitivity of the client model." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:12 of +msgid "" +"The privacy budget. Smaller value of epsilon indicates a higher level of " +"privacy protection." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:15 of +msgid "" +"The failure probability. The probability that the privacy mechanism fails" +" to provide the desired level of privacy. A smaller value of delta " +"indicates a stricter privacy guarantee." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:23 of +msgid "Create an instance of the local DP mod and add it to the client-side mods:" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.adaptiveclipping_mod.rst:2 +msgid "adaptiveclipping\\_mod" +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:3 of +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideAdaptiveClipping server-side strategy " +"wrapper." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:6 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:6 of +msgid "The wrapper sends the clipping_norm value to the client." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:8 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:8 of +msgid "This mod clips the client model updates before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:10 of +msgid "" +"It also sends KEY_NORM_BIT to the server for computing the new clipping " +"value." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:15 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:13 +#: flwr.server.driver.driver.Driver.send_and_receive:18 +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 +#: of +#, fuzzy +msgid "Notes" +msgstr "Aucun" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:16 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:14 of +msgid "Consider the order of mods when using multiple." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:18 of +msgid "Typically, adaptiveclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.fixedclipping_mod.rst:2 +msgid "fixedclipping\\_mod" +msgstr "" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:3 of +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideFixedClipping server-side strategy wrapper." +msgstr "" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:16 of +msgid "Typically, fixedclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.make_ffn.rst:2 +msgid "make\\_ffn" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 +msgid "message\\_size\\_mod" +msgstr "" + +#: flwr.client.mod.comms_mods.message_size_mod:3 of +msgid "This mod logs the size in bytes of the message being transmited." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.parameters_size_mod.rst:2 +#, fuzzy +msgid "parameters\\_size\\_mod" +msgstr "Paramètres du modèle." + +#: flwr.client.mod.comms_mods.parameters_size_mod:3 of +msgid "" +"This mod logs the number of parameters transmitted in the message as well" +" as their size in bytes." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secagg_mod.rst:2 +msgid "secagg\\_mod" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secaggplus_mod.rst:2 +#, fuzzy +msgid "secaggplus\\_mod" +msgstr "Flux de travail" + #: ../../source/ref-api/flwr.client.run_client_app.rst:2 msgid "run\\_client\\_app" msgstr "" @@ -10263,6 +10486,20 @@ msgid "" "`\\" msgstr "serveur.stratégie.Stratégie" +#: flwr.common.EventType.capitalize:1::1 of +#, fuzzy +msgid "" +":py:obj:`RUN_SUPEREXEC_ENTER " +"`\\" +msgstr "serveur.stratégie.Stratégie" + +#: flwr.common.EventType.capitalize:1::1 of +#, fuzzy +msgid "" +":py:obj:`RUN_SUPEREXEC_LEAVE " +"`\\" +msgstr "serveur.stratégie.Stratégie" + #: flwr.common.EventType.capitalize:3 of msgid "" "More specifically, make the first character have upper case and the rest " @@ -11164,116 +11401,96 @@ msgstr "" msgid "server" msgstr "serveur" -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_driver_api `\\ \\(\\)" -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_driver_api:1 of -#, fuzzy -msgid "Run Flower server (Driver API)." -msgstr "flower-driver-api" - -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_fleet_api `\\ \\(\\)" -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_fleet_api:1 of -#, fuzzy -msgid "Run Flower server (Fleet API)." -msgstr "flower-fleet-api" - -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 msgid ":py:obj:`run_server_app `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.run_serverapp.run_server_app:1 of #, fuzzy msgid "Run Flower server app." msgstr "Serveur de Flower" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 msgid ":py:obj:`run_superlink `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.app.run_superlink:1 of #, fuzzy msgid "Run Flower SuperLink (Driver API and Fleet API)." msgstr "flower-fleet-api" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 msgid "" ":py:obj:`start_server `\\ \\(\\*\\[\\, " "server\\_address\\, server\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.app.start_server:1 of msgid "Start a Flower server using the gRPC transport layer." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`ClientManager `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.ClientManager:1 of msgid "Abstract base class for managing Flower clients." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid ":py:obj:`Driver `\\ \\(\\)" msgstr "serveur.stratégie.Stratégie" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.driver.driver.Driver:1 of msgid "Abstract base Driver class for the Driver API." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`History `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.history.History:1 of msgid "History class for training and/or evaluation metrics collection." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`LegacyContext `\\ \\(state\\[\\, " "config\\, strategy\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.compat.legacy_context.LegacyContext:1 of msgid "Legacy Context." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`Server `\\ \\(\\*\\, client\\_manager\\[\\, " "strategy\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid "" ":py:obj:`ServerApp `\\ \\(\\[server\\, config\\, " "strategy\\, ...\\]\\)" msgstr "serveur.stratégie.Stratégie" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_app.ServerApp:1 of #, fuzzy msgid "Flower ServerApp." msgstr "Serveur de Flower" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid "" ":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\," @@ -11283,37 +11500,37 @@ msgstr "" "config=flwr.server.ServerConfig(num_rounds=3, round_timeout=600.0), " "...)``" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_config.ServerConfig:1 of #, fuzzy msgid "Flower server config." msgstr "Serveur de Flower" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`SimpleClientManager `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.SimpleClientManager:1 of msgid "Provides a pool of available clients." msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #, fuzzy msgid ":py:obj:`flwr.server.strategy `\\" msgstr "serveur.stratégie.Stratégie" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.strategy:1 of msgid "Contains the strategy abstraction and different implementations." msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #, fuzzy msgid ":py:obj:`flwr.server.workflow `\\" msgstr "serveur.stratégie.Stratégie" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.workflow:1 of #, fuzzy msgid "Workflows." @@ -11570,14 +11787,6 @@ msgstr "" msgid "**replies** -- An iterable of reply messages received from the SuperLink." msgstr "" -#: flwr.server.driver.driver.Driver.send_and_receive:18 -#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 -#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 -#: of -#, fuzzy -msgid "Notes" -msgstr "Aucun" - #: flwr.server.driver.driver.Driver.send_and_receive:19 of msgid "" "This method uses `push_messages` to send the messages and `pull_messages`" @@ -12987,12 +13196,6 @@ msgid "" "value of 1.0 or higher is recommended for strong privacy." msgstr "" -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 -#: of -msgid "The value of the clipping norm." -msgstr "" - #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:26 #: of msgid "" @@ -13283,7 +13486,7 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst:2 -#: ../../source/ref-changelog.md:905 +#: ../../source/ref-changelog.md:997 msgid "FedAdagrad" msgstr "FedAdagrad" @@ -15155,55 +15358,21 @@ msgstr "Changelog" #: ../../source/ref-changelog.md:3 #, fuzzy -msgid "Unreleased" -msgstr "Inédit" - -#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:19 -#: ../../source/ref-changelog.md:83 ../../source/ref-changelog.md:176 -#: ../../source/ref-changelog.md:276 ../../source/ref-changelog.md:360 -#: ../../source/ref-changelog.md:424 ../../source/ref-changelog.md:482 -#: ../../source/ref-changelog.md:551 ../../source/ref-changelog.md:680 -#: ../../source/ref-changelog.md:722 ../../source/ref-changelog.md:789 -#: ../../source/ref-changelog.md:855 ../../source/ref-changelog.md:900 -#: ../../source/ref-changelog.md:939 ../../source/ref-changelog.md:972 -#: ../../source/ref-changelog.md:1022 -msgid "What's new?" -msgstr "Quoi de neuf ?" - -#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:71 -#: ../../source/ref-changelog.md:146 ../../source/ref-changelog.md:258 -#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:412 -#: ../../source/ref-changelog.md:470 ../../source/ref-changelog.md:539 -#: ../../source/ref-changelog.md:601 ../../source/ref-changelog.md:620 -#: ../../source/ref-changelog.md:776 ../../source/ref-changelog.md:847 -#: ../../source/ref-changelog.md:884 ../../source/ref-changelog.md:927 -msgid "Incompatible changes" -msgstr "Changements incompatibles" - -#: ../../source/ref-changelog.md:9 ../../source/ref-changelog.md:73 -#: ../../source/ref-changelog.md:350 ../../source/ref-changelog.md:414 -#: ../../source/ref-changelog.md:472 ../../source/ref-changelog.md:541 -#: ../../source/ref-changelog.md:603 -msgid "None" -msgstr "Aucun" - -#: ../../source/ref-changelog.md:11 -#, fuzzy -msgid "v1.8.0 (2024-04-03)" +msgid "v1.9.0 (2024-06-10)" msgstr "v1.3.0 (2023-02-06)" -#: ../../source/ref-changelog.md:13 ../../source/ref-changelog.md:77 -#: ../../source/ref-changelog.md:170 ../../source/ref-changelog.md:270 -#: ../../source/ref-changelog.md:354 ../../source/ref-changelog.md:418 -#: ../../source/ref-changelog.md:476 ../../source/ref-changelog.md:545 -#: ../../source/ref-changelog.md:614 +#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:105 +#: ../../source/ref-changelog.md:169 ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:362 ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:510 ../../source/ref-changelog.md:568 +#: ../../source/ref-changelog.md:637 ../../source/ref-changelog.md:706 msgid "Thanks to our contributors" msgstr "Merci à nos contributeurs" -#: ../../source/ref-changelog.md:15 ../../source/ref-changelog.md:79 -#: ../../source/ref-changelog.md:172 ../../source/ref-changelog.md:272 -#: ../../source/ref-changelog.md:356 ../../source/ref-changelog.md:420 -#: ../../source/ref-changelog.md:478 +#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:107 +#: ../../source/ref-changelog.md:171 ../../source/ref-changelog.md:264 +#: ../../source/ref-changelog.md:364 ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:512 ../../source/ref-changelog.md:570 msgid "" "We would like to give our special thanks to all the contributors who made" " the new version of Flower possible (in `git shortlog` order):" @@ -15212,60 +15381,647 @@ msgstr "" "ont rendu possible la nouvelle version de Flower (dans l'ordre `git " "shortlog`) :" -#: ../../source/ref-changelog.md:17 +#: ../../source/ref-changelog.md:9 msgid "" -"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " -"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear " -"Ashimine`, `Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, " -"`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " -"`tabdar-khan` " +"`Adam Narozniak`, `Charles Beauville`, `Chong Shen Ng`, `Daniel J. " +"Beutel`, `Daniel Nata Nugraha`, `Heng Pan`, `Javier`, `Mahdi Beitollahi`," +" `Robert Steiner`, `Taner Topal`, `Yan Gao`, `bapic`, `mohammadnaseri` " msgstr "" -#: ../../source/ref-changelog.md:21 +#: ../../source/ref-changelog.md:11 ../../source/ref-changelog.md:111 +#: ../../source/ref-changelog.md:175 ../../source/ref-changelog.md:268 +#: ../../source/ref-changelog.md:368 ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:516 ../../source/ref-changelog.md:574 +#: ../../source/ref-changelog.md:643 ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:814 ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:947 ../../source/ref-changelog.md:992 +#: ../../source/ref-changelog.md:1031 ../../source/ref-changelog.md:1064 +#: ../../source/ref-changelog.md:1114 +msgid "What's new?" +msgstr "Quoi de neuf ?" + +#: ../../source/ref-changelog.md:13 +#, fuzzy +msgid "" +"**Introduce built-in authentication (preview)** " +"([#2946](https://github.com/adap/flower/pull/2946), " +"[#3388](https://github.com/adap/flower/pull/3388), " +"[#2948](https://github.com/adap/flower/pull/2948), " +"[#2917](https://github.com/adap/flower/pull/2917), " +"[#3386](https://github.com/adap/flower/pull/3386), " +"[#3308](https://github.com/adap/flower/pull/3308), " +"[#3001](https://github.com/adap/flower/pull/3001), " +"[#3409](https://github.com/adap/flower/pull/3409), " +"[#2999](https://github.com/adap/flower/pull/2999), " +"[#2979](https://github.com/adap/flower/pull/2979), " +"[#3389](https://github.com/adap/flower/pull/3389), " +"[#3503](https://github.com/adap/flower/pull/3503), " +"[#3366](https://github.com/adap/flower/pull/3366), " +"[#3357](https://github.com/adap/flower/pull/3357))" +msgstr "" +"**Documentation mise à jour** " +"([#1494](https://github.com/adap/flower/pull/1494), " +"[#1496](https://github.com/adap/flower/pull/1496), " +"[#1500](https://github.com/adap/flower/pull/1500), " +"[#1503](https://github.com/adap/flower/pull/1503), " +"[#1505](https://github.com/adap/flower/pull/1505), " +"[#1524](https://github.com/adap/flower/pull/1524), " +"[#1518](https://github.com/adap/flower/pull/1518), " +"[#1519](https://github.com/adap/flower/pull/1519), " +"[#1515](https://github.com/adap/flower/pull/1515))" + +#: ../../source/ref-changelog.md:15 msgid "" -"**Introduce Flower Next high-level API (stable)** " -"([#3002](https://github.com/adap/flower/pull/3002), " -"[#2934](https://github.com/adap/flower/pull/2934), " -"[#2958](https://github.com/adap/flower/pull/2958), " -"[#3173](https://github.com/adap/flower/pull/3173), " -"[#3174](https://github.com/adap/flower/pull/3174), " -"[#2923](https://github.com/adap/flower/pull/2923), " -"[#2691](https://github.com/adap/flower/pull/2691), " -"[#3079](https://github.com/adap/flower/pull/3079), " -"[#2961](https://github.com/adap/flower/pull/2961), " -"[#2924](https://github.com/adap/flower/pull/2924), " -"[#3166](https://github.com/adap/flower/pull/3166), " -"[#3031](https://github.com/adap/flower/pull/3031), " -"[#3057](https://github.com/adap/flower/pull/3057), " -"[#3000](https://github.com/adap/flower/pull/3000), " -"[#3113](https://github.com/adap/flower/pull/3113), " -"[#2957](https://github.com/adap/flower/pull/2957), " -"[#3183](https://github.com/adap/flower/pull/3183), " -"[#3180](https://github.com/adap/flower/pull/3180), " -"[#3035](https://github.com/adap/flower/pull/3035), " -"[#3189](https://github.com/adap/flower/pull/3189), " -"[#3185](https://github.com/adap/flower/pull/3185), " -"[#3190](https://github.com/adap/flower/pull/3190), " -"[#3191](https://github.com/adap/flower/pull/3191), " -"[#3195](https://github.com/adap/flower/pull/3195), " -"[#3197](https://github.com/adap/flower/pull/3197))" +"Flower 1.9 introduces the first build-in version of client node " +"authentication. In previous releases, users often wrote glue code to " +"connect Flower to external authentication systems. With this release, the" +" SuperLink can authenticate SuperNodes using a built-in authentication " +"system. A new [how-to guide](https://flower.ai/docs/framework/how-to-" +"authenticate-supernodes.html) and a new [code " +"example](https://github.com/adap/flower/tree/main/examples/flower-" +"authentication) help you to get started." msgstr "" -#: ../../source/ref-changelog.md:23 +#: ../../source/ref-changelog.md:17 msgid "" -"The Flower Next high-level API is stable! Flower Next is the future of " -"Flower - all new features (like Flower Mods) will be built on top of it. " -"You can start to migrate your existing projects to Flower Next by using " -"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or " -"`quickstart-tensorflow`, a detailed migration guide will follow shortly)." -" Flower Next allows you to run multiple projects concurrently (we call " +"This is the first preview release of the Flower-native authentication " +"system. Many additional features are on the roadmap for upcoming Flower " +"releases - stay tuned." +msgstr "" + +#: ../../source/ref-changelog.md:19 +#, fuzzy +msgid "" +"**Introduce end-to-end Docker support** " +"([#3483](https://github.com/adap/flower/pull/3483), " +"[#3266](https://github.com/adap/flower/pull/3266), " +"[#3390](https://github.com/adap/flower/pull/3390), " +"[#3283](https://github.com/adap/flower/pull/3283), " +"[#3285](https://github.com/adap/flower/pull/3285), " +"[#3391](https://github.com/adap/flower/pull/3391), " +"[#3403](https://github.com/adap/flower/pull/3403), " +"[#3458](https://github.com/adap/flower/pull/3458), " +"[#3533](https://github.com/adap/flower/pull/3533), " +"[#3453](https://github.com/adap/flower/pull/3453), " +"[#3486](https://github.com/adap/flower/pull/3486), " +"[#3290](https://github.com/adap/flower/pull/3290))" +msgstr "" +"**Introduire l'API REST (expérimentale)** " +"([#1594](https://github.com/adap/flower/pull/1594), " +"[#1690](https://github.com/adap/flower/pull/1690), " +"[#1695](https://github.com/adap/flower/pull/1695), " +"[#1712](https://github.com/adap/flower/pull/1712), " +"[#1802](https://github.com/adap/flower/pull/1802), " +"[#1770](https://github.com/adap/flower/pull/1770), " +"[#1733](https://github.com/adap/flower/pull/1733))" + +#: ../../source/ref-changelog.md:21 +msgid "" +"Full Flower Next Docker support is here! With the release of Flower 1.9, " +"Flower provides stable Docker images for the Flower SuperLink, the Flower" +" SuperNode, and the Flower `ServerApp`. This set of images enables you to" +" run all Flower components in Docker. Check out the new [how-to " +"guide](https://flower.ai/docs/framework/how-to-run-flower-using-" +"docker.html) to get stated." +msgstr "" + +#: ../../source/ref-changelog.md:23 +#, fuzzy +msgid "" +"**Re-architect Flower Next simulation engine** " +"([#3307](https://github.com/adap/flower/pull/3307), " +"[#3355](https://github.com/adap/flower/pull/3355), " +"[#3272](https://github.com/adap/flower/pull/3272), " +"[#3273](https://github.com/adap/flower/pull/3273), " +"[#3417](https://github.com/adap/flower/pull/3417), " +"[#3281](https://github.com/adap/flower/pull/3281), " +"[#3343](https://github.com/adap/flower/pull/3343), " +"[#3326](https://github.com/adap/flower/pull/3326))" +msgstr "" +"**Mise à jour de la documentation** " +"([#1629](https://github.com/adap/flower/pull/1629), " +"[#1628](https://github.com/adap/flower/pull/1628), " +"[#1620](https://github.com/adap/flower/pull/1620), " +"[#1618](https://github.com/adap/flower/pull/1618), " +"[#1617](https://github.com/adap/flower/pull/1617), " +"[#1613](https://github.com/adap/flower/pull/1613), " +"[#1614](https://github.com/adap/flower/pull/1614))" + +#: ../../source/ref-changelog.md:25 +msgid "" +"Flower Next simulations now use a new in-memory `Driver` that improves " +"the reliability of simulations, especially in notebook environments. This" +" is a significant step towards a complete overhaul of the Flower Next " +"simulation architecture." +msgstr "" + +#: ../../source/ref-changelog.md:27 +#, fuzzy +msgid "" +"**Upgrade simulation engine** " +"([#3354](https://github.com/adap/flower/pull/3354), " +"[#3378](https://github.com/adap/flower/pull/3378), " +"[#3262](https://github.com/adap/flower/pull/3262), " +"[#3435](https://github.com/adap/flower/pull/3435), " +"[#3501](https://github.com/adap/flower/pull/3501), " +"[#3482](https://github.com/adap/flower/pull/3482), " +"[#3494](https://github.com/adap/flower/pull/3494))" +msgstr "" +"**Mise à jour de la documentation** " +"([#1629](https://github.com/adap/flower/pull/1629), " +"[#1628](https://github.com/adap/flower/pull/1628), " +"[#1620](https://github.com/adap/flower/pull/1620), " +"[#1618](https://github.com/adap/flower/pull/1618), " +"[#1617](https://github.com/adap/flower/pull/1617), " +"[#1613](https://github.com/adap/flower/pull/1613), " +"[#1614](https://github.com/adap/flower/pull/1614))" + +#: ../../source/ref-changelog.md:29 +msgid "" +"The Flower Next simulation engine comes with improved and configurable " +"logging. The Ray-based simulation backend in Flower 1.9 was updated to " +"use Ray 2.10." +msgstr "" + +#: ../../source/ref-changelog.md:31 +#, fuzzy +msgid "" +"**Introduce FedPFT baseline** " +"([#3268](https://github.com/adap/flower/pull/3268))" +msgstr "" +"**Ajouter une nouvelle stratégie `FedProx`** " +"([#1619](https://github.com/adap/flower/pull/1619))" + +#: ../../source/ref-changelog.md:33 +msgid "" +"FedPFT allows you to perform one-shot Federated Learning by leveraging " +"widely available foundational models, dramatically reducing communication" +" costs while delivering high performing models. This is work led by Mahdi" +" Beitollahi from Huawei Noah's Ark Lab (Montreal, Canada). Read all the " +"details in their paper: \"Parametric Feature Transfer: One-shot Federated" +" Learning with Foundation Models\" " +"([arxiv](https://arxiv.org/abs/2402.01862))" +msgstr "" + +#: ../../source/ref-changelog.md:35 +#, fuzzy +msgid "" +"**Launch additional** `flwr new` **templates for Apple MLX, Hugging Face " +"Transformers, scikit-learn and TensorFlow** " +"([#3291](https://github.com/adap/flower/pull/3291), " +"[#3139](https://github.com/adap/flower/pull/3139), " +"[#3284](https://github.com/adap/flower/pull/3284), " +"[#3251](https://github.com/adap/flower/pull/3251), " +"[#3376](https://github.com/adap/flower/pull/3376), " +"[#3287](https://github.com/adap/flower/pull/3287))" +msgstr "" +"**Nouvel exemple de code MLCube** " +"([#779](https://github.com/adap/flower/pull/779), " +"[#1034](https://github.com/adap/flower/pull/1034), " +"[#1065](https://github.com/adap/flower/pull/1065), " +"[#1090](https://github.com/adap/flower/pull/1090))" + +#: ../../source/ref-changelog.md:37 +msgid "" +"The `flwr` CLI's `flwr new` command is starting to become everone's " +"favorite way of creating new Flower projects. This release introduces " +"additional `flwr new` templates for Apple MLX, Hugging Face Transformers," +" scikit-learn and TensorFlow. In addition to that, existing templates " +"also received updates." +msgstr "" + +#: ../../source/ref-changelog.md:39 +#, fuzzy +msgid "" +"**Refine** `RecordSet` **API** " +"([#3209](https://github.com/adap/flower/pull/3209), " +"[#3331](https://github.com/adap/flower/pull/3331), " +"[#3334](https://github.com/adap/flower/pull/3334), " +"[#3335](https://github.com/adap/flower/pull/3335), " +"[#3375](https://github.com/adap/flower/pull/3375), " +"[#3368](https://github.com/adap/flower/pull/3368))" +msgstr "" +"**Mise à jour de la documentation** " +"([#1629](https://github.com/adap/flower/pull/1629), " +"[#1628](https://github.com/adap/flower/pull/1628), " +"[#1620](https://github.com/adap/flower/pull/1620), " +"[#1618](https://github.com/adap/flower/pull/1618), " +"[#1617](https://github.com/adap/flower/pull/1617), " +"[#1613](https://github.com/adap/flower/pull/1613), " +"[#1614](https://github.com/adap/flower/pull/1614))" + +#: ../../source/ref-changelog.md:41 +msgid "" +"`RecordSet` is part of the Flower Next low-level API preview release. In " +"Flower 1.9, `RecordSet` received a number of usability improvements that " +"make it easier to build `RecordSet`-based `ServerApp`s and `ClientApp`s." +msgstr "" + +#: ../../source/ref-changelog.md:43 +#, fuzzy +msgid "" +"**Beautify logging** ([#3379](https://github.com/adap/flower/pull/3379), " +"[#3430](https://github.com/adap/flower/pull/3430), " +"[#3461](https://github.com/adap/flower/pull/3461), " +"[#3360](https://github.com/adap/flower/pull/3360), " +"[#3433](https://github.com/adap/flower/pull/3433))" +msgstr "" +"Mettre à jour les outils de développement " +"([#1231](https://github.com/adap/flower/pull/1231), " +"[#1276](https://github.com/adap/flower/pull/1276), " +"[#1301](https://github.com/adap/flower/pull/1301), " +"[#1310](https://github.com/adap/flower/pull/1310))" + +#: ../../source/ref-changelog.md:45 +msgid "" +"Logs received a substantial update. Not only are logs now much nicer to " +"look at, but they are also more configurable." +msgstr "" + +#: ../../source/ref-changelog.md:47 +#, fuzzy +msgid "" +"**Improve reliability** " +"([#3564](https://github.com/adap/flower/pull/3564), " +"[#3561](https://github.com/adap/flower/pull/3561), " +"[#3566](https://github.com/adap/flower/pull/3566), " +"[#3462](https://github.com/adap/flower/pull/3462), " +"[#3225](https://github.com/adap/flower/pull/3225), " +"[#3514](https://github.com/adap/flower/pull/3514), " +"[#3535](https://github.com/adap/flower/pull/3535), " +"[#3372](https://github.com/adap/flower/pull/3372))" +msgstr "" +"**Tutoriel amélioré** ([#1468](https://github.com/adap/flower/pull/1468)," +" [#1470](https://github.com/adap/flower/pull/1470), " +"[#1472](https://github.com/adap/flower/pull/1472), " +"[#1473](https://github.com/adap/flower/pull/1473), " +"[#1474](https://github.com/adap/flower/pull/1474), " +"[#1475](https://github.com/adap/flower/pull/1475))" + +#: ../../source/ref-changelog.md:49 +msgid "" +"Flower 1.9 includes reliability improvements across many parts of the " +"system. One example is a much improved SuperNode shutdown procedure." +msgstr "" + +#: ../../source/ref-changelog.md:51 +#, fuzzy +msgid "" +"**Update Swift and C++ SDKs** " +"([#3321](https://github.com/adap/flower/pull/3321), " +"[#2763](https://github.com/adap/flower/pull/2763))" +msgstr "" +"**Exemple de code mis à jour** " +"([#1344](https://github.com/adap/flower/pull/1344), " +"[#1347](https://github.com/adap/flower/pull/1347))" + +#: ../../source/ref-changelog.md:53 +msgid "" +"In the C++ SDK, communication-related code is now separate from main " +"client logic. A new abstract class `Communicator` has been introduced " +"alongside a gRPC implementation of it." +msgstr "" + +#: ../../source/ref-changelog.md:55 +msgid "" +"**Improve testing, tooling and CI/CD infrastructure** " +"([#3294](https://github.com/adap/flower/pull/3294), " +"[#3282](https://github.com/adap/flower/pull/3282), " +"[#3311](https://github.com/adap/flower/pull/3311), " +"[#2878](https://github.com/adap/flower/pull/2878), " +"[#3333](https://github.com/adap/flower/pull/3333), " +"[#3255](https://github.com/adap/flower/pull/3255), " +"[#3349](https://github.com/adap/flower/pull/3349), " +"[#3400](https://github.com/adap/flower/pull/3400), " +"[#3401](https://github.com/adap/flower/pull/3401), " +"[#3399](https://github.com/adap/flower/pull/3399), " +"[#3346](https://github.com/adap/flower/pull/3346), " +"[#3398](https://github.com/adap/flower/pull/3398), " +"[#3397](https://github.com/adap/flower/pull/3397), " +"[#3347](https://github.com/adap/flower/pull/3347), " +"[#3502](https://github.com/adap/flower/pull/3502), " +"[#3387](https://github.com/adap/flower/pull/3387), " +"[#3542](https://github.com/adap/flower/pull/3542), " +"[#3396](https://github.com/adap/flower/pull/3396), " +"[#3496](https://github.com/adap/flower/pull/3496), " +"[#3465](https://github.com/adap/flower/pull/3465), " +"[#3473](https://github.com/adap/flower/pull/3473), " +"[#3484](https://github.com/adap/flower/pull/3484), " +"[#3521](https://github.com/adap/flower/pull/3521), " +"[#3363](https://github.com/adap/flower/pull/3363), " +"[#3497](https://github.com/adap/flower/pull/3497), " +"[#3464](https://github.com/adap/flower/pull/3464), " +"[#3495](https://github.com/adap/flower/pull/3495), " +"[#3478](https://github.com/adap/flower/pull/3478), " +"[#3271](https://github.com/adap/flower/pull/3271))" +msgstr "" + +#: ../../source/ref-changelog.md:57 +msgid "" +"As always, the Flower tooling, testing, and CI/CD infrastructure has " +"received many updates." +msgstr "" + +#: ../../source/ref-changelog.md:59 +msgid "" +"**Improve documentation** " +"([#3530](https://github.com/adap/flower/pull/3530), " +"[#3539](https://github.com/adap/flower/pull/3539), " +"[#3425](https://github.com/adap/flower/pull/3425), " +"[#3520](https://github.com/adap/flower/pull/3520), " +"[#3286](https://github.com/adap/flower/pull/3286), " +"[#3516](https://github.com/adap/flower/pull/3516), " +"[#3523](https://github.com/adap/flower/pull/3523), " +"[#3545](https://github.com/adap/flower/pull/3545), " +"[#3498](https://github.com/adap/flower/pull/3498), " +"[#3439](https://github.com/adap/flower/pull/3439), " +"[#3440](https://github.com/adap/flower/pull/3440), " +"[#3382](https://github.com/adap/flower/pull/3382), " +"[#3559](https://github.com/adap/flower/pull/3559), " +"[#3432](https://github.com/adap/flower/pull/3432), " +"[#3278](https://github.com/adap/flower/pull/3278), " +"[#3371](https://github.com/adap/flower/pull/3371), " +"[#3519](https://github.com/adap/flower/pull/3519), " +"[#3267](https://github.com/adap/flower/pull/3267), " +"[#3204](https://github.com/adap/flower/pull/3204), " +"[#3274](https://github.com/adap/flower/pull/3274))" +msgstr "" + +#: ../../source/ref-changelog.md:61 +msgid "" +"As always, the Flower documentation has received many updates. Notable " +"new pages include:" +msgstr "" + +#: ../../source/ref-changelog.md:63 +msgid "" +"[How-to upgrate to Flower Next (Flower Next migration " +"guide)](https://flower.ai/docs/framework/how-to-upgrade-to-flower-" +"next.html)" +msgstr "" + +#: ../../source/ref-changelog.md:65 +msgid "" +"[How-to run Flower using Docker](https://flower.ai/docs/framework/how-to-" +"run-flower-using-docker.html)" +msgstr "" + +#: ../../source/ref-changelog.md:67 +msgid "" +"[Flower Mods reference](https://flower.ai/docs/framework/ref-" +"api/flwr.client.mod.html#module-flwr.client.mod)" +msgstr "" + +#: ../../source/ref-changelog.md:69 +#, fuzzy +msgid "" +"**General updates to Flower Examples** " +"([#3205](https://github.com/adap/flower/pull/3205), " +"[#3226](https://github.com/adap/flower/pull/3226), " +"[#3211](https://github.com/adap/flower/pull/3211), " +"[#3252](https://github.com/adap/flower/pull/3252), " +"[#3427](https://github.com/adap/flower/pull/3427), " +"[#3410](https://github.com/adap/flower/pull/3410), " +"[#3426](https://github.com/adap/flower/pull/3426), " +"[#3228](https://github.com/adap/flower/pull/3228), " +"[#3342](https://github.com/adap/flower/pull/3342), " +"[#3200](https://github.com/adap/flower/pull/3200), " +"[#3202](https://github.com/adap/flower/pull/3202), " +"[#3394](https://github.com/adap/flower/pull/3394), " +"[#3488](https://github.com/adap/flower/pull/3488), " +"[#3329](https://github.com/adap/flower/pull/3329), " +"[#3526](https://github.com/adap/flower/pull/3526), " +"[#3392](https://github.com/adap/flower/pull/3392), " +"[#3474](https://github.com/adap/flower/pull/3474), " +"[#3269](https://github.com/adap/flower/pull/3269))" +msgstr "" +"**Mise à jour de la documentation** " +"([#1223](https://github.com/adap/flower/pull/1223), " +"[#1209](https://github.com/adap/flower/pull/1209), " +"[#1251](https://github.com/adap/flower/pull/1251), " +"[#1257](https://github.com/adap/flower/pull/1257), " +"[#1267](https://github.com/adap/flower/pull/1267), " +"[#1268](https://github.com/adap/flower/pull/1268), " +"[#1300](https://github.com/adap/flower/pull/1300), " +"[#1304](https://github.com/adap/flower/pull/1304), " +"[#1305](https://github.com/adap/flower/pull/1305), " +"[#1307](https://github.com/adap/flower/pull/1307))" + +#: ../../source/ref-changelog.md:71 +msgid "As always, Flower code examples have received many updates." +msgstr "" + +#: ../../source/ref-changelog.md:73 +msgid "" +"**General improvements** " +"([#3532](https://github.com/adap/flower/pull/3532), " +"[#3318](https://github.com/adap/flower/pull/3318), " +"[#3565](https://github.com/adap/flower/pull/3565), " +"[#3296](https://github.com/adap/flower/pull/3296), " +"[#3305](https://github.com/adap/flower/pull/3305), " +"[#3246](https://github.com/adap/flower/pull/3246), " +"[#3224](https://github.com/adap/flower/pull/3224), " +"[#3475](https://github.com/adap/flower/pull/3475), " +"[#3297](https://github.com/adap/flower/pull/3297), " +"[#3317](https://github.com/adap/flower/pull/3317), " +"[#3429](https://github.com/adap/flower/pull/3429), " +"[#3196](https://github.com/adap/flower/pull/3196), " +"[#3534](https://github.com/adap/flower/pull/3534), " +"[#3240](https://github.com/adap/flower/pull/3240), " +"[#3365](https://github.com/adap/flower/pull/3365), " +"[#3407](https://github.com/adap/flower/pull/3407), " +"[#3563](https://github.com/adap/flower/pull/3563), " +"[#3344](https://github.com/adap/flower/pull/3344), " +"[#3330](https://github.com/adap/flower/pull/3330), " +"[#3436](https://github.com/adap/flower/pull/3436), " +"[#3300](https://github.com/adap/flower/pull/3300), " +"[#3327](https://github.com/adap/flower/pull/3327), " +"[#3254](https://github.com/adap/flower/pull/3254), " +"[#3253](https://github.com/adap/flower/pull/3253), " +"[#3419](https://github.com/adap/flower/pull/3419), " +"[#3289](https://github.com/adap/flower/pull/3289), " +"[#3208](https://github.com/adap/flower/pull/3208), " +"[#3245](https://github.com/adap/flower/pull/3245), " +"[#3319](https://github.com/adap/flower/pull/3319), " +"[#3203](https://github.com/adap/flower/pull/3203), " +"[#3423](https://github.com/adap/flower/pull/3423), " +"[#3352](https://github.com/adap/flower/pull/3352), " +"[#3292](https://github.com/adap/flower/pull/3292), " +"[#3261](https://github.com/adap/flower/pull/3261))" +msgstr "" + +#: ../../source/ref-changelog.md:75 ../../source/ref-changelog.md:1058 +msgid "Deprecations" +msgstr "Dépréciations" + +#: ../../source/ref-changelog.md:77 +#, fuzzy +msgid "**Deprecate Python 3.8 support**" +msgstr "**Créer le PR**" + +#: ../../source/ref-changelog.md:79 +msgid "" +"Python 3.8 will stop receiving security fixes in [October " +"2024](https://devguide.python.org/versions/). Support for Python 3.8 is " +"now deprecated and will be removed in an upcoming release." +msgstr "" + +#: ../../source/ref-changelog.md:81 +#, fuzzy +msgid "" +"**Deprecate (experimental)** `flower-driver-api` **and** `flower-fleet-" +"api` ([#3416](https://github.com/adap/flower/pull/3416), " +"[#3420](https://github.com/adap/flower/pull/3420))" +msgstr "" +"**Rename** `Weights` **to** `NDArrays` " +"([#1258](https://github.com/adap/flower/pull/1258), " +"[#1259](https://github.com/adap/flower/pull/1259))" + +#: ../../source/ref-changelog.md:83 +msgid "" +"Flower 1.9 deprecates the two (experimental) commands `flower-driver-api`" +" and `flower-fleet-api`. Both commands will be removed in an upcoming " +"release. Use `flower-superlink` instead." +msgstr "" + +#: ../../source/ref-changelog.md:85 +#, fuzzy +msgid "" +"**Deprecate** `--server` **in favor of** `--superlink` " +"([#3518](https://github.com/adap/flower/pull/3518))" +msgstr "" +"**Autoriser le passage d'une **instance `Server` à** `start_simulation` " +"([#1281](https://github.com/adap/flower/pull/1281))" + +#: ../../source/ref-changelog.md:87 +msgid "" +"The commands `flower-server-app` and `flower-client-app` should use " +"`--superlink` instead of the now deprecated `--server`. Support for " +"`--server` will be removed in a future release." +msgstr "" + +#: ../../source/ref-changelog.md:89 ../../source/ref-changelog.md:163 +#: ../../source/ref-changelog.md:238 ../../source/ref-changelog.md:350 +#: ../../source/ref-changelog.md:440 ../../source/ref-changelog.md:504 +#: ../../source/ref-changelog.md:562 ../../source/ref-changelog.md:631 +#: ../../source/ref-changelog.md:693 ../../source/ref-changelog.md:712 +#: ../../source/ref-changelog.md:868 ../../source/ref-changelog.md:939 +#: ../../source/ref-changelog.md:976 ../../source/ref-changelog.md:1019 +msgid "Incompatible changes" +msgstr "Changements incompatibles" + +#: ../../source/ref-changelog.md:91 +msgid "" +"**Replace** `flower-superlink` **CLI option** `--certificates` **with** " +"`--ssl-ca-certfile` **,** `--ssl-certfile` **and** `--ssl-keyfile` " +"([#3512](https://github.com/adap/flower/pull/3512), " +"[#3408](https://github.com/adap/flower/pull/3408))" +msgstr "" + +#: ../../source/ref-changelog.md:93 +msgid "" +"SSL-related `flower-superlink` CLI arguments were restructured in an " +"incompatible way. Instead of passing a single `--certificates` flag with " +"three values, you now need to pass three flags (`--ssl-ca-certfile`, " +"`--ssl-certfile` and `--ssl-keyfile`) with one value each. Check out the " +"[SSL connections](https://flower.ai/docs/framework/how-to-enable-ssl-" +"connections.html) documentation page for details." +msgstr "" + +#: ../../source/ref-changelog.md:95 +#, fuzzy +msgid "" +"**Remove SuperLink** `--vce` **option** " +"([#3513](https://github.com/adap/flower/pull/3513))" +msgstr "" +"**Documentation restructurée** " +"([#1387](https://github.com/adap/flower/pull/1387))" + +#: ../../source/ref-changelog.md:97 +msgid "" +"Instead of separately starting a SuperLink and a `ServerApp` for " +"simulation, simulations must now be started using the single `flower-" +"simulation` command." +msgstr "" + +#: ../../source/ref-changelog.md:99 +#, fuzzy +msgid "" +"**Merge** `--grpc-rere` **and** `--rest` **SuperLink options** " +"([#3527](https://github.com/adap/flower/pull/3527))" +msgstr "" +"**Rename** `rnd` **to** `server_round` " +"([#1321](https://github.com/adap/flower/pull/1321))" + +#: ../../source/ref-changelog.md:101 +msgid "" +"To simplify the usage of `flower-superlink`, previously separate sets of " +"CLI options for gRPC and REST were merged into one unified set of " +"options. Consult the [Flower CLI reference " +"documentation](https://flower.ai/docs/framework/ref-api-cli.html) for " +"details." +msgstr "" + +#: ../../source/ref-changelog.md:103 +#, fuzzy +msgid "v1.8.0 (2024-04-03)" +msgstr "v1.3.0 (2023-02-06)" + +#: ../../source/ref-changelog.md:109 +msgid "" +"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " +"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear " +"Ashimine`, `Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, " +"`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " +"`tabdar-khan` " +msgstr "" + +#: ../../source/ref-changelog.md:113 +msgid "" +"**Introduce Flower Next high-level API (stable)** " +"([#3002](https://github.com/adap/flower/pull/3002), " +"[#2934](https://github.com/adap/flower/pull/2934), " +"[#2958](https://github.com/adap/flower/pull/2958), " +"[#3173](https://github.com/adap/flower/pull/3173), " +"[#3174](https://github.com/adap/flower/pull/3174), " +"[#2923](https://github.com/adap/flower/pull/2923), " +"[#2691](https://github.com/adap/flower/pull/2691), " +"[#3079](https://github.com/adap/flower/pull/3079), " +"[#2961](https://github.com/adap/flower/pull/2961), " +"[#2924](https://github.com/adap/flower/pull/2924), " +"[#3166](https://github.com/adap/flower/pull/3166), " +"[#3031](https://github.com/adap/flower/pull/3031), " +"[#3057](https://github.com/adap/flower/pull/3057), " +"[#3000](https://github.com/adap/flower/pull/3000), " +"[#3113](https://github.com/adap/flower/pull/3113), " +"[#2957](https://github.com/adap/flower/pull/2957), " +"[#3183](https://github.com/adap/flower/pull/3183), " +"[#3180](https://github.com/adap/flower/pull/3180), " +"[#3035](https://github.com/adap/flower/pull/3035), " +"[#3189](https://github.com/adap/flower/pull/3189), " +"[#3185](https://github.com/adap/flower/pull/3185), " +"[#3190](https://github.com/adap/flower/pull/3190), " +"[#3191](https://github.com/adap/flower/pull/3191), " +"[#3195](https://github.com/adap/flower/pull/3195), " +"[#3197](https://github.com/adap/flower/pull/3197))" +msgstr "" + +#: ../../source/ref-changelog.md:115 +msgid "" +"The Flower Next high-level API is stable! Flower Next is the future of " +"Flower - all new features (like Flower Mods) will be built on top of it. " +"You can start to migrate your existing projects to Flower Next by using " +"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or " +"`quickstart-tensorflow`, a detailed migration guide will follow shortly)." +" Flower Next allows you to run multiple projects concurrently (we call " "this multi-run) and execute the same project in either simulation " "environments or deployment environments without having to change a single" " line of code. The best part? It's fully compatible with existing Flower " "projects that use `Strategy`, `NumPyClient` & co." msgstr "" -#: ../../source/ref-changelog.md:25 +#: ../../source/ref-changelog.md:117 #, fuzzy msgid "" "**Introduce Flower Next low-level API (preview)** " @@ -15278,7 +16034,7 @@ msgstr "" "[#1286](https://github.com/adap/flower/pull/1286), " "[#1282](https://github.com/adap/flower/pull/1282))" -#: ../../source/ref-changelog.md:27 +#: ../../source/ref-changelog.md:119 msgid "" "In addition to the Flower Next *high-level* API that uses `Strategy`, " "`NumPyClient` & co, Flower 1.8 also comes with a preview version of the " @@ -15295,7 +16051,7 @@ msgid "" "custom SMPC protocols, to name just a few." msgstr "" -#: ../../source/ref-changelog.md:29 +#: ../../source/ref-changelog.md:121 #, fuzzy msgid "" "**Introduce Flower Mods (preview)** " @@ -15308,7 +16064,7 @@ msgstr "" "[#1544](https://github.com/adap/flower/pull/1544), " "[#1584](https://github.com/adap/flower/pull/1584))" -#: ../../source/ref-changelog.md:31 +#: ../../source/ref-changelog.md:123 msgid "" "Flower Modifiers (we call them Mods) can intercept messages and analyze, " "edit or handle them directly. Mods can be used to develop pluggable " @@ -15320,7 +16076,7 @@ msgid "" "can already use it to experiment with arbirtrary SMPC protocols." msgstr "" -#: ../../source/ref-changelog.md:33 +#: ../../source/ref-changelog.md:125 #, fuzzy msgid "" "**Fine-tune LLMs with LLM FlowerTune** " @@ -15339,7 +16095,7 @@ msgstr "" "[#1474](https://github.com/adap/flower/pull/1474), " "[#1475](https://github.com/adap/flower/pull/1475))" -#: ../../source/ref-changelog.md:35 +#: ../../source/ref-changelog.md:127 msgid "" "We are introducing LLM FlowerTune, an introductory example that " "demonstrates federated LLM fine-tuning of pre-trained Llama2 models on " @@ -15349,7 +16105,7 @@ msgid "" "-llm-flowertune-federated-llm-finetuning-with-flower/) for more details." msgstr "" -#: ../../source/ref-changelog.md:37 +#: ../../source/ref-changelog.md:129 #, fuzzy msgid "" "**Introduce built-in Differential Privacy (preview)** " @@ -15370,7 +16126,7 @@ msgstr "" "[#993](https://github.com/adap/flower/pull/993), " "[#994](https://github.com/adap/flower/pull/994))" -#: ../../source/ref-changelog.md:39 +#: ../../source/ref-changelog.md:131 msgid "" "Built-in Differential Privacy is here! Flower supports both central and " "local differential privacy (DP). Central DP can be configured with either" @@ -15383,7 +16139,7 @@ msgid "" "/how-to-use-differential-privacy.html) in Flower." msgstr "" -#: ../../source/ref-changelog.md:41 +#: ../../source/ref-changelog.md:133 #, fuzzy msgid "" "**Introduce built-in Secure Aggregation (preview)** " @@ -15396,7 +16152,7 @@ msgstr "" "[#1544](https://github.com/adap/flower/pull/1544), " "[#1584](https://github.com/adap/flower/pull/1584))" -#: ../../source/ref-changelog.md:43 +#: ../../source/ref-changelog.md:135 msgid "" "Built-in Secure Aggregation is here! Flower now supports different secure" " aggregation protocols out-of-the-box. The best part? You can add secure " @@ -15409,7 +16165,7 @@ msgid "" "in the same project." msgstr "" -#: ../../source/ref-changelog.md:45 +#: ../../source/ref-changelog.md:137 #, fuzzy msgid "" "**Introduce** `flwr` **CLI (preview)** " @@ -15432,13 +16188,13 @@ msgstr "" "[#1613](https://github.com/adap/flower/pull/1613), " "[#1614](https://github.com/adap/flower/pull/1614))" -#: ../../source/ref-changelog.md:47 +#: ../../source/ref-changelog.md:139 msgid "" "A new `flwr` CLI command allows creating new Flower projects (`flwr new`)" " and then running them using the Simulation Engine (`flwr run`)." msgstr "" -#: ../../source/ref-changelog.md:49 +#: ../../source/ref-changelog.md:141 #, fuzzy msgid "" "**Introduce Flower Next Simulation Engine** " @@ -15462,14 +16218,14 @@ msgstr "" "[#1770](https://github.com/adap/flower/pull/1770), " "[#1733](https://github.com/adap/flower/pull/1733))" -#: ../../source/ref-changelog.md:51 +#: ../../source/ref-changelog.md:143 msgid "" "The Flower Simulation Engine can now run Flower Next projects. For " "notebook environments, there's also a new `run_simulation` function that " "can run `ServerApp` and `ClientApp`." msgstr "" -#: ../../source/ref-changelog.md:53 +#: ../../source/ref-changelog.md:145 #, fuzzy msgid "" "**Handle SuperNode connection errors** " @@ -15478,7 +16234,7 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:55 +#: ../../source/ref-changelog.md:147 msgid "" "A SuperNode will now try to reconnect indefinitely to the SuperLink in " "case of connection errors. The arguments `--max-retries` and `--max-wait-" @@ -15489,7 +16245,7 @@ msgid "" "reconnect to the SuperLink." msgstr "" -#: ../../source/ref-changelog.md:57 +#: ../../source/ref-changelog.md:149 #, fuzzy msgid "" "**General updates to Flower Baselines** " @@ -15504,13 +16260,13 @@ msgstr "" "[#1681](https://github.com/adap/flower/pull/1681), " "[#1679](https://github.com/adap/flower/pull/1679))" -#: ../../source/ref-changelog.md:59 +#: ../../source/ref-changelog.md:151 msgid "" "There's a new [FedStar](https://flower.ai/docs/baselines/fedstar.html) " "baseline. Several other baselined have been updated as well." msgstr "" -#: ../../source/ref-changelog.md:61 +#: ../../source/ref-changelog.md:153 msgid "" "**Improve documentation and translations** " "([#3050](https://github.com/adap/flower/pull/3050), " @@ -15531,14 +16287,14 @@ msgid "" "[#2989](https://github.com/adap/flower/pull/2989))" msgstr "" -#: ../../source/ref-changelog.md:63 +#: ../../source/ref-changelog.md:155 msgid "" "As usual, we merged many smaller and larger improvements to the " "documentation. A special thank you goes to [Sebastian van der " "Voort](https://github.com/svdvoort) for landing a big documentation PR!" msgstr "" -#: ../../source/ref-changelog.md:65 +#: ../../source/ref-changelog.md:157 #, fuzzy msgid "" "**General updates to Flower Examples** " @@ -15565,7 +16321,7 @@ msgstr "" "[#1519](https://github.com/adap/flower/pull/1519), " "[#1515](https://github.com/adap/flower/pull/1515))" -#: ../../source/ref-changelog.md:67 +#: ../../source/ref-changelog.md:159 msgid "" "Two new examples show federated training of a Vision Transformer (ViT) " "and federated learning in a medical context using the popular MONAI " @@ -15574,7 +16330,7 @@ msgid "" "received considerable updates as well." msgstr "" -#: ../../source/ref-changelog.md:69 +#: ../../source/ref-changelog.md:161 msgid "" "**General improvements** " "([#3171](https://github.com/adap/flower/pull/3171), " @@ -15652,12 +16408,18 @@ msgid "" "[#2954](https://github.com/adap/flower/pull/2954))" msgstr "" -#: ../../source/ref-changelog.md:75 +#: ../../source/ref-changelog.md:165 ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:506 ../../source/ref-changelog.md:564 +#: ../../source/ref-changelog.md:633 ../../source/ref-changelog.md:695 +msgid "None" +msgstr "Aucun" + +#: ../../source/ref-changelog.md:167 #, fuzzy msgid "v1.7.0 (2024-02-05)" msgstr "v1.3.0 (2023-02-06)" -#: ../../source/ref-changelog.md:81 +#: ../../source/ref-changelog.md:173 msgid "" "`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles " "Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo " @@ -15667,7 +16429,7 @@ msgid "" "Shaaban`, `Yan Gao`, `Yasar Abbas` " msgstr "" -#: ../../source/ref-changelog.md:85 +#: ../../source/ref-changelog.md:177 #, fuzzy msgid "" "**Introduce stateful clients (experimental)** " @@ -15684,7 +16446,7 @@ msgstr "" "[#993](https://github.com/adap/flower/pull/993), " "[#994](https://github.com/adap/flower/pull/994))" -#: ../../source/ref-changelog.md:87 +#: ../../source/ref-changelog.md:179 msgid "" "Subclasses of `Client` and `NumPyClient` can now store local state that " "remains on the client. Let's start with the highlight first: this new " @@ -15697,7 +16459,7 @@ msgid "" "unified way across simulation and deployment." msgstr "" -#: ../../source/ref-changelog.md:89 +#: ../../source/ref-changelog.md:181 #, fuzzy msgid "" "**Improve performance** " @@ -15706,7 +16468,7 @@ msgstr "" "**Supprimer les stratégies expérimentales** " "([#1280](https://github.com/adap/flower/pull/1280))" -#: ../../source/ref-changelog.md:91 +#: ../../source/ref-changelog.md:183 msgid "" "Flower is faster than ever. All `FedAvg`-derived strategies now use in-" "place aggregation to reduce memory consumption. The Flower client " @@ -15715,7 +16477,7 @@ msgid "" "training time is short." msgstr "" -#: ../../source/ref-changelog.md:93 +#: ../../source/ref-changelog.md:185 #, fuzzy msgid "" "**Support Federated Learning with Apple MLX and Flower** " @@ -15724,14 +16486,14 @@ msgstr "" "**Ajouter un nouvel exemple d'apprentissage fédéré utilisant fastai et " "Flower** ([#1598](https://github.com/adap/flower/pull/1598))" -#: ../../source/ref-changelog.md:95 +#: ../../source/ref-changelog.md:187 msgid "" "Flower has official support for federated learning using [Apple " "MLX](https://ml-explore.github.io/mlx) via the new `quickstart-mlx` code " "example." msgstr "" -#: ../../source/ref-changelog.md:97 +#: ../../source/ref-changelog.md:189 #, fuzzy msgid "" "**Introduce new XGBoost cyclic strategy** " @@ -15742,7 +16504,7 @@ msgstr "" "([#1621](https://github.com/adap/flower/pull/1621), " "[#1764](https://github.com/adap/flower/pull/1764))" -#: ../../source/ref-changelog.md:99 +#: ../../source/ref-changelog.md:191 msgid "" "A new strategy called `FedXgbCyclic` supports a client-by-client style of" " training (often called cyclic). The `xgboost-comprehensive` code example" @@ -15751,7 +16513,7 @@ msgid "" "offers best-in-class XGBoost support." msgstr "" -#: ../../source/ref-changelog.md:101 +#: ../../source/ref-changelog.md:193 #, fuzzy msgid "" "**Support Python 3.11** " @@ -15760,13 +16522,13 @@ msgstr "" "**Support Python 3.10** " "([#1320](https://github.com/adap/flower/pull/1320))" -#: ../../source/ref-changelog.md:103 +#: ../../source/ref-changelog.md:195 msgid "" "Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will " "ensure better support for users using more recent Python versions." msgstr "" -#: ../../source/ref-changelog.md:105 +#: ../../source/ref-changelog.md:197 #, fuzzy msgid "" "**Update gRPC and ProtoBuf dependencies** " @@ -15775,13 +16537,13 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:107 +#: ../../source/ref-changelog.md:199 msgid "" "The `grpcio` and `protobuf` dependencies were updated to their latest " "versions for improved security and performance." msgstr "" -#: ../../source/ref-changelog.md:109 +#: ../../source/ref-changelog.md:201 #, fuzzy msgid "" "**Introduce Docker image for Flower server** " @@ -15802,7 +16564,7 @@ msgstr "" "[#993](https://github.com/adap/flower/pull/993), " "[#994](https://github.com/adap/flower/pull/994))" -#: ../../source/ref-changelog.md:111 +#: ../../source/ref-changelog.md:203 msgid "" "The Flower server can now be run using an official Docker image. A new " "how-to guide explains [how to run Flower using " @@ -15810,7 +16572,7 @@ msgid "" "docker.html). An official Flower client Docker image will follow." msgstr "" -#: ../../source/ref-changelog.md:113 +#: ../../source/ref-changelog.md:205 #, fuzzy msgid "" "**Introduce** `flower-via-docker-compose` **example** " @@ -15819,7 +16581,7 @@ msgstr "" "**Introduire une nouvelle ligne de base pour les fleurs : FedAvg " "FEMNIST** ([#1655](https://github.com/adap/flower/pull/1655))" -#: ../../source/ref-changelog.md:115 +#: ../../source/ref-changelog.md:207 #, fuzzy msgid "" "**Introduce** `quickstart-sklearn-tabular` **example** " @@ -15828,7 +16590,7 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:117 +#: ../../source/ref-changelog.md:209 #, fuzzy msgid "" "**Introduce** `custom-metrics` **example** " @@ -15837,7 +16599,7 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:119 +#: ../../source/ref-changelog.md:211 #, fuzzy msgid "" "**Update code examples to use Flower Datasets** " @@ -15852,13 +16614,13 @@ msgstr "" "[#1301](https://github.com/adap/flower/pull/1301), " "[#1310](https://github.com/adap/flower/pull/1310))" -#: ../../source/ref-changelog.md:121 +#: ../../source/ref-changelog.md:213 msgid "" "Several code examples were updated to use [Flower " "Datasets](https://flower.ai/docs/datasets/)." msgstr "" -#: ../../source/ref-changelog.md:123 +#: ../../source/ref-changelog.md:215 #, fuzzy msgid "" "**General updates to Flower Examples** " @@ -15884,16 +16646,16 @@ msgstr "" "[#1662](https://github.com/adap/flower/pull/1662), " "[#1794](https://github.com/adap/flower/pull/1794))" -#: ../../source/ref-changelog.md:125 +#: ../../source/ref-changelog.md:217 msgid "Many Flower code examples received substantial updates." msgstr "" -#: ../../source/ref-changelog.md:127 ../../source/ref-changelog.md:220 +#: ../../source/ref-changelog.md:219 ../../source/ref-changelog.md:312 #, fuzzy msgid "**Update Flower Baselines**" msgstr "Demande pour une nouvelle Flower Baseline" -#: ../../source/ref-changelog.md:129 +#: ../../source/ref-changelog.md:221 #, fuzzy msgid "" "HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), " @@ -15903,38 +16665,38 @@ msgstr "" "([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143))" -#: ../../source/ref-changelog.md:130 +#: ../../source/ref-changelog.md:222 #, fuzzy msgid "FedVSSL ([#2412](https://github.com/adap/flower/pull/2412))" msgstr "" "Amélioration de la documentation sur le serveur gRPC " "([#841](https://github.com/adap/flower/pull/841))" -#: ../../source/ref-changelog.md:131 +#: ../../source/ref-changelog.md:223 #, fuzzy msgid "FedNova ([#2179](https://github.com/adap/flower/pull/2179))" msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:132 +#: ../../source/ref-changelog.md:224 #, fuzzy msgid "HeteroFL ([#2439](https://github.com/adap/flower/pull/2439))" msgstr "Nouvelle référence API ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:133 +#: ../../source/ref-changelog.md:225 #, fuzzy msgid "FedAvgM ([#2246](https://github.com/adap/flower/pull/2246))" msgstr "Nouvelle référence API ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:134 +#: ../../source/ref-changelog.md:226 #, fuzzy msgid "FedPara ([#2722](https://github.com/adap/flower/pull/2722))" msgstr "" "**Renommé stratégie q-FedAvg** " "([#802](https://github.com/adap/flower/pull/802))" -#: ../../source/ref-changelog.md:136 +#: ../../source/ref-changelog.md:228 #, fuzzy msgid "" "**Improve documentation** " @@ -15954,7 +16716,7 @@ msgstr "" "[#1613](https://github.com/adap/flower/pull/1613), " "[#1614](https://github.com/adap/flower/pull/1614))" -#: ../../source/ref-changelog.md:138 +#: ../../source/ref-changelog.md:230 msgid "" "**Improved testing and development infrastructure** " "([#2797](https://github.com/adap/flower/pull/2797), " @@ -15988,13 +16750,13 @@ msgid "" "[#2398](https://github.com/adap/flower/pull/2398))" msgstr "" -#: ../../source/ref-changelog.md:140 +#: ../../source/ref-changelog.md:232 msgid "" "The Flower testing and development infrastructure has received " "substantial updates. This makes Flower 1.7 the most tested release ever." msgstr "" -#: ../../source/ref-changelog.md:142 +#: ../../source/ref-changelog.md:234 msgid "" "**Update dependencies** " "([#2753](https://github.com/adap/flower/pull/2753), " @@ -16018,7 +16780,7 @@ msgid "" "[#2789](https://github.com/adap/flower/pull/2789))" msgstr "" -#: ../../source/ref-changelog.md:144 +#: ../../source/ref-changelog.md:236 msgid "" "**General improvements** " "([#2803](https://github.com/adap/flower/pull/2803), " @@ -16059,7 +16821,7 @@ msgid "" "[#2759](https://github.com/adap/flower/pull/2759))" msgstr "" -#: ../../source/ref-changelog.md:148 +#: ../../source/ref-changelog.md:240 #, fuzzy msgid "" "**Deprecate** `start_numpy_client` " @@ -16070,7 +16832,7 @@ msgstr "" "([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" -#: ../../source/ref-changelog.md:150 +#: ../../source/ref-changelog.md:242 msgid "" "Until now, clients of type `NumPyClient` needed to be started via " "`start_numpy_client`. In our efforts to consolidate framework APIs, we " @@ -16081,7 +16843,7 @@ msgid "" "updated accordingly." msgstr "" -#: ../../source/ref-changelog.md:152 +#: ../../source/ref-changelog.md:244 #, fuzzy msgid "" "**Deprecate legacy DP wrappers** " @@ -16090,14 +16852,14 @@ msgstr "" "**Supprimez KerasClient** " "([#857](https://github.com/adap/flower/pull/857))" -#: ../../source/ref-changelog.md:154 +#: ../../source/ref-changelog.md:246 msgid "" "Legacy DP wrapper classes are deprecated, but still functional. This is " "in preparation for an all-new pluggable version of differential privacy " "support in Flower." msgstr "" -#: ../../source/ref-changelog.md:156 +#: ../../source/ref-changelog.md:248 #, fuzzy msgid "" "**Make optional arg** `--callable` **in** `flower-client` **a required " @@ -16106,7 +16868,7 @@ msgstr "" "**Log** `Client` **exceptions dans le moteur de client virtuel** " "([#1493](https://github.com/adap/flower/pull/1493))" -#: ../../source/ref-changelog.md:158 +#: ../../source/ref-changelog.md:250 #, fuzzy msgid "" "**Rename** `certificates` **to** `root_certificates` **in** `Driver` " @@ -16115,7 +16877,7 @@ msgstr "" "**Rename** `rnd` **to** `server_round` " "([#1321](https://github.com/adap/flower/pull/1321))" -#: ../../source/ref-changelog.md:160 +#: ../../source/ref-changelog.md:252 #, fuzzy msgid "" "**Drop experimental** `Task` **fields** " @@ -16126,14 +16888,14 @@ msgstr "" "([#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" -#: ../../source/ref-changelog.md:162 +#: ../../source/ref-changelog.md:254 msgid "" "Experimental fields `sa`, `legacy_server_message` and " "`legacy_client_message` were removed from `Task` message. The removed " "fields are superseded by the new `RecordSet` abstraction." msgstr "" -#: ../../source/ref-changelog.md:164 +#: ../../source/ref-changelog.md:256 #, fuzzy msgid "" "**Retire MXNet examples** " @@ -16142,19 +16904,19 @@ msgstr "" "**Nouvel exemple de code scikit-learn** " "([#748](https://github.com/adap/flower/pull/748))" -#: ../../source/ref-changelog.md:166 +#: ../../source/ref-changelog.md:258 msgid "" "The development of the MXNet fremework has ended and the project is now " "[archived on GitHub](https://github.com/apache/mxnet). Existing MXNet " "examples won't receive updates." msgstr "" -#: ../../source/ref-changelog.md:168 +#: ../../source/ref-changelog.md:260 #, fuzzy msgid "v1.6.0 (2023-11-28)" msgstr "v1.4.0 (2023-04-21)" -#: ../../source/ref-changelog.md:174 +#: ../../source/ref-changelog.md:266 msgid "" "`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " "`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " @@ -16164,7 +16926,7 @@ msgid "" "`cnxdeveloper`, `k3nfalt` " msgstr "" -#: ../../source/ref-changelog.md:178 +#: ../../source/ref-changelog.md:270 #, fuzzy msgid "" "**Add experimental support for Python 3.12** " @@ -16173,7 +16935,7 @@ msgstr "" "**Ajouter la prise en charge expérimentale de Python 3.10 et Python " "3.11** ([#1135](https://github.com/adap/flower/pull/1135))" -#: ../../source/ref-changelog.md:180 +#: ../../source/ref-changelog.md:272 #, fuzzy msgid "" "**Add new XGBoost examples** " @@ -16192,13 +16954,13 @@ msgstr "" "[#1551](https://github.com/adap/flower/pull/1551), " "[#1567](https://github.com/adap/flower/pull/1567))" -#: ../../source/ref-changelog.md:182 +#: ../../source/ref-changelog.md:274 msgid "" "We have added a new `xgboost-quickstart` example alongside a new " "`xgboost-comprehensive` example that goes more in-depth." msgstr "" -#: ../../source/ref-changelog.md:184 +#: ../../source/ref-changelog.md:276 #, fuzzy msgid "" "**Add Vertical FL example** " @@ -16207,7 +16969,7 @@ msgstr "" "**Nouvel exemple de code CoreML pour iOS** " "([#1289](https://github.com/adap/flower/pull/1289))" -#: ../../source/ref-changelog.md:186 +#: ../../source/ref-changelog.md:278 msgid "" "We had many questions about Vertical Federated Learning using Flower, so " "we decided to add an simple example for it on the [Titanic " @@ -16215,7 +16977,7 @@ msgid "" "tutorial (in the README)." msgstr "" -#: ../../source/ref-changelog.md:188 +#: ../../source/ref-changelog.md:280 #, fuzzy msgid "" "**Support custom** `ClientManager` **in** `start_driver()` " @@ -16225,7 +16987,7 @@ msgstr "" "paramètre de `start_simulation` " "([#1171](https://github.com/adap/flower/pull/1171))" -#: ../../source/ref-changelog.md:190 +#: ../../source/ref-changelog.md:282 #, fuzzy msgid "" "**Update REST API to support create and delete nodes** " @@ -16234,7 +16996,7 @@ msgstr "" "**Nouvelle stratégie expérimentale TensorBoard** " "([#789](https://github.com/adap/flower/pull/789))" -#: ../../source/ref-changelog.md:192 +#: ../../source/ref-changelog.md:284 #, fuzzy msgid "" "**Update the Android SDK** " @@ -16243,11 +17005,11 @@ msgstr "" "**Introduire une nouvelle ligne de base pour les fleurs : FedAvg " "FEMNIST** ([#1655](https://github.com/adap/flower/pull/1655))" -#: ../../source/ref-changelog.md:194 +#: ../../source/ref-changelog.md:286 msgid "Add gRPC request-response capability to the Android SDK." msgstr "" -#: ../../source/ref-changelog.md:196 +#: ../../source/ref-changelog.md:288 #, fuzzy msgid "" "**Update the C++ SDK** " @@ -16262,11 +17024,11 @@ msgstr "" "[#1301](https://github.com/adap/flower/pull/1301), " "[#1310](https://github.com/adap/flower/pull/1310))" -#: ../../source/ref-changelog.md:198 +#: ../../source/ref-changelog.md:290 msgid "Add gRPC request-response capability to the C++ SDK." msgstr "" -#: ../../source/ref-changelog.md:200 +#: ../../source/ref-changelog.md:292 #, fuzzy msgid "" "**Make HTTPS the new default** " @@ -16277,7 +17039,7 @@ msgstr "" "([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" -#: ../../source/ref-changelog.md:202 +#: ../../source/ref-changelog.md:294 msgid "" "Flower is moving to HTTPS by default. The new `flower-server` requires " "passing `--certificates`, but users can enable `--insecure` to use HTTP " @@ -16287,14 +17049,14 @@ msgid "" "enable insecure HTTP connections." msgstr "" -#: ../../source/ref-changelog.md:204 +#: ../../source/ref-changelog.md:296 msgid "" "For backward compatibility, `start_client()` and `start_numpy_client()` " "will still start in insecure mode by default. In a future release, " "insecure connections will require user opt-in by passing `insecure=True`." msgstr "" -#: ../../source/ref-changelog.md:206 +#: ../../source/ref-changelog.md:298 #, fuzzy msgid "" "**Unify client API** ([#2303](https://github.com/adap/flower/pull/2303), " @@ -16306,7 +17068,7 @@ msgstr "" "[#1286](https://github.com/adap/flower/pull/1286), " "[#1282](https://github.com/adap/flower/pull/1282))" -#: ../../source/ref-changelog.md:208 +#: ../../source/ref-changelog.md:300 msgid "" "Using the `client_fn`, Flower clients can interchangeably run as " "standalone processes (i.e. via `start_client`) or in simulation (i.e. via" @@ -16315,7 +17077,7 @@ msgid "" "convert a `NumPyClient` to a `Client`." msgstr "" -#: ../../source/ref-changelog.md:210 +#: ../../source/ref-changelog.md:302 #, fuzzy msgid "" "**Add new** `Bulyan` **strategy** " @@ -16326,7 +17088,7 @@ msgstr "" "([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" -#: ../../source/ref-changelog.md:212 +#: ../../source/ref-changelog.md:304 #, fuzzy msgid "" "The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., " @@ -16336,7 +17098,7 @@ msgstr "" "(FedMedian) par [Yin et al., 2018] " "(https://arxiv.org/pdf/1803.01498v1.pdf)." -#: ../../source/ref-changelog.md:214 +#: ../../source/ref-changelog.md:306 #, fuzzy msgid "" "**Add new** `XGB Bagging` **strategy** " @@ -16345,7 +17107,7 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:216 ../../source/ref-changelog.md:218 +#: ../../source/ref-changelog.md:308 ../../source/ref-changelog.md:310 #, fuzzy msgid "" "**Introduce `WorkloadState`** " @@ -16356,7 +17118,7 @@ msgstr "" "([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" -#: ../../source/ref-changelog.md:222 +#: ../../source/ref-changelog.md:314 #, fuzzy msgid "" "FedProx ([#2210](https://github.com/adap/flower/pull/2210), " @@ -16368,7 +17130,7 @@ msgstr "" "[#1286](https://github.com/adap/flower/pull/1286), " "[#1282](https://github.com/adap/flower/pull/1282))" -#: ../../source/ref-changelog.md:224 +#: ../../source/ref-changelog.md:316 #, fuzzy msgid "" "Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), " @@ -16378,7 +17140,7 @@ msgstr "" "([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143))" -#: ../../source/ref-changelog.md:226 +#: ../../source/ref-changelog.md:318 #, fuzzy msgid "" "FedMLB ([#2340](https://github.com/adap/flower/pull/2340), " @@ -16388,7 +17150,7 @@ msgstr "" "([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" -#: ../../source/ref-changelog.md:228 +#: ../../source/ref-changelog.md:320 #, fuzzy msgid "" "TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " @@ -16398,48 +17160,48 @@ msgstr "" "([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" -#: ../../source/ref-changelog.md:230 +#: ../../source/ref-changelog.md:322 #, fuzzy msgid "FedMeta [#2438](https://github.com/adap/flower/pull/2438)" msgstr "Nouvelle référence API ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:232 +#: ../../source/ref-changelog.md:324 #, fuzzy msgid "FjORD [#2431](https://github.com/adap/flower/pull/2431)" msgstr "" "Amélioration de la documentation sur le serveur gRPC " "([#841](https://github.com/adap/flower/pull/841))" -#: ../../source/ref-changelog.md:234 +#: ../../source/ref-changelog.md:326 #, fuzzy msgid "MOON [#2421](https://github.com/adap/flower/pull/2421)" msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:236 +#: ../../source/ref-changelog.md:328 #, fuzzy msgid "DepthFL [#2295](https://github.com/adap/flower/pull/2295)" msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:238 +#: ../../source/ref-changelog.md:330 #, fuzzy msgid "FedPer [#2266](https://github.com/adap/flower/pull/2266)" msgstr "Nouvelle référence API ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:240 +#: ../../source/ref-changelog.md:332 #, fuzzy msgid "FedWav2vec [#2551](https://github.com/adap/flower/pull/2551)" msgstr "Nouvelle référence API ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:242 +#: ../../source/ref-changelog.md:334 #, fuzzy msgid "niid-Bench [#2428](https://github.com/adap/flower/pull/2428)" msgstr "Nouvelle référence API ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:244 +#: ../../source/ref-changelog.md:336 #, fuzzy msgid "" "FedBN ([#2608](https://github.com/adap/flower/pull/2608), " @@ -16449,7 +17211,7 @@ msgstr "" "([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" -#: ../../source/ref-changelog.md:246 +#: ../../source/ref-changelog.md:338 #, fuzzy msgid "" "**General updates to Flower Examples** " @@ -16465,7 +17227,7 @@ msgstr "" "[#1301](https://github.com/adap/flower/pull/1301), " "[#1310](https://github.com/adap/flower/pull/1310))" -#: ../../source/ref-changelog.md:248 +#: ../../source/ref-changelog.md:340 #, fuzzy msgid "" "**General updates to Flower Baselines** " @@ -16498,7 +17260,7 @@ msgstr "" "[#1564](https://github.com/adap/flower/pull/1564), " "[#1566](https://github.com/adap/flower/pull/1566))" -#: ../../source/ref-changelog.md:250 +#: ../../source/ref-changelog.md:342 #, fuzzy msgid "" "**General updates to the simulation engine** " @@ -16513,7 +17275,7 @@ msgstr "" "[#1301](https://github.com/adap/flower/pull/1301), " "[#1310](https://github.com/adap/flower/pull/1310))" -#: ../../source/ref-changelog.md:252 +#: ../../source/ref-changelog.md:344 #, fuzzy msgid "" "**General updates to Flower SDKs** " @@ -16532,7 +17294,7 @@ msgstr "" "[#1474](https://github.com/adap/flower/pull/1474), " "[#1475](https://github.com/adap/flower/pull/1475))" -#: ../../source/ref-changelog.md:254 +#: ../../source/ref-changelog.md:346 msgid "" "**General improvements** " "([#2309](https://github.com/adap/flower/pull/2309), " @@ -16564,15 +17326,15 @@ msgid "" "[#2596](https://github.com/adap/flower/pull/2596))" msgstr "" -#: ../../source/ref-changelog.md:256 ../../source/ref-changelog.md:346 -#: ../../source/ref-changelog.md:410 ../../source/ref-changelog.md:464 -#: ../../source/ref-changelog.md:531 +#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:502 ../../source/ref-changelog.md:556 +#: ../../source/ref-changelog.md:623 msgid "Flower received many improvements under the hood, too many to list here." msgstr "" "Flower a reçu de nombreuses améliorations sous le capot, trop nombreuses " "pour être énumérées ici." -#: ../../source/ref-changelog.md:260 +#: ../../source/ref-changelog.md:352 #, fuzzy msgid "" "**Remove support for Python 3.7** " @@ -16589,13 +17351,13 @@ msgstr "" "[#1065](https://github.com/adap/flower/pull/1065), " "[#1090](https://github.com/adap/flower/pull/1090))" -#: ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:354 msgid "" "Python 3.7 support was deprecated in Flower 1.5, and this release removes" " support. Flower now requires Python 3.8." msgstr "" -#: ../../source/ref-changelog.md:264 +#: ../../source/ref-changelog.md:356 #, fuzzy msgid "" "**Remove experimental argument** `rest` **from** `start_client` " @@ -16604,19 +17366,19 @@ msgstr "" "**Supprimer les stratégies expérimentales** " "([#1280](https://github.com/adap/flower/pull/1280))" -#: ../../source/ref-changelog.md:266 +#: ../../source/ref-changelog.md:358 msgid "" "The (still experimental) argument `rest` was removed from `start_client` " "and `start_numpy_client`. Use `transport=\"rest\"` to opt into the " "experimental REST API instead." msgstr "" -#: ../../source/ref-changelog.md:268 +#: ../../source/ref-changelog.md:360 #, fuzzy msgid "v1.5.0 (2023-08-31)" msgstr "v1.4.0 (2023-04-21)" -#: ../../source/ref-changelog.md:274 +#: ../../source/ref-changelog.md:366 msgid "" "`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " "`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " @@ -16625,7 +17387,7 @@ msgid "" "TOKEN_v1.5.0-->" msgstr "" -#: ../../source/ref-changelog.md:278 +#: ../../source/ref-changelog.md:370 #, fuzzy msgid "" "**Introduce new simulation engine** " @@ -16638,7 +17400,7 @@ msgstr "" "[#1544](https://github.com/adap/flower/pull/1544), " "[#1584](https://github.com/adap/flower/pull/1584))" -#: ../../source/ref-changelog.md:280 +#: ../../source/ref-changelog.md:372 msgid "" "The new simulation engine has been rewritten from the ground up, yet it " "remains fully backwards compatible. It offers much improved stability and" @@ -16647,7 +17409,7 @@ msgid "" "only, CPU+GPU, multi-GPU, or multi-node multi-GPU environments." msgstr "" -#: ../../source/ref-changelog.md:282 +#: ../../source/ref-changelog.md:374 msgid "" "Comprehensive documentation includes a new [how-to run " "simulations](https://flower.ai/docs/framework/how-to-run-" @@ -16658,7 +17420,7 @@ msgid "" "series](https://www.youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." msgstr "" -#: ../../source/ref-changelog.md:284 +#: ../../source/ref-changelog.md:376 msgid "" "**Restructure Flower Docs** " "([#1824](https://github.com/adap/flower/pull/1824), " @@ -16690,7 +17452,7 @@ msgid "" "[#2227](https://github.com/adap/flower/pull/2227))" msgstr "" -#: ../../source/ref-changelog.md:286 +#: ../../source/ref-changelog.md:378 msgid "" "Much effort went into a completely restructured Flower docs experience. " "The documentation on [flower.ai/docs](https://flower.ai/docs) is now " @@ -16698,7 +17460,7 @@ msgid "" "Flower iOS SDK, and code example projects." msgstr "" -#: ../../source/ref-changelog.md:288 +#: ../../source/ref-changelog.md:380 #, fuzzy msgid "" "**Introduce Flower Swift SDK** " @@ -16709,14 +17471,14 @@ msgstr "" "([#1621](https://github.com/adap/flower/pull/1621), " "[#1764](https://github.com/adap/flower/pull/1764))" -#: ../../source/ref-changelog.md:290 +#: ../../source/ref-changelog.md:382 msgid "" "This is the first preview release of the Flower Swift SDK. Flower support" " on iOS is improving, and alongside the Swift SDK and code example, there" " is now also an iOS quickstart tutorial." msgstr "" -#: ../../source/ref-changelog.md:292 +#: ../../source/ref-changelog.md:384 #, fuzzy msgid "" "**Introduce Flower Android SDK** " @@ -16725,14 +17487,14 @@ msgstr "" "**Introduire une nouvelle ligne de base pour les fleurs : FedAvg " "FEMNIST** ([#1655](https://github.com/adap/flower/pull/1655))" -#: ../../source/ref-changelog.md:294 +#: ../../source/ref-changelog.md:386 msgid "" "This is the first preview release of the Flower Kotlin SDK. Flower " "support on Android is improving, and alongside the Kotlin SDK and code " "example, there is now also an Android quickstart tutorial." msgstr "" -#: ../../source/ref-changelog.md:296 +#: ../../source/ref-changelog.md:388 #, fuzzy msgid "" "**Introduce new end-to-end testing infrastructure** " @@ -16765,24 +17527,24 @@ msgstr "" "[#1662](https://github.com/adap/flower/pull/1662), " "[#1794](https://github.com/adap/flower/pull/1794))" -#: ../../source/ref-changelog.md:298 +#: ../../source/ref-changelog.md:390 msgid "" "A new testing infrastructure ensures that new changes stay compatible " "with existing framework integrations or strategies." msgstr "" -#: ../../source/ref-changelog.md:300 +#: ../../source/ref-changelog.md:392 #, fuzzy msgid "**Deprecate Python 3.7**" msgstr "**Créer le PR**" -#: ../../source/ref-changelog.md:302 +#: ../../source/ref-changelog.md:394 msgid "" "Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for" " Python 3.7 is now deprecated and will be removed in an upcoming release." msgstr "" -#: ../../source/ref-changelog.md:304 +#: ../../source/ref-changelog.md:396 #, fuzzy msgid "" "**Add new** `FedTrimmedAvg` **strategy** " @@ -16793,7 +17555,7 @@ msgstr "" "([#1469](https://github.com/adap/flower/pull/1469), " "[#1535](https://github.com/adap/flower/pull/1535))" -#: ../../source/ref-changelog.md:306 +#: ../../source/ref-changelog.md:398 #, fuzzy msgid "" "The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, " @@ -16803,7 +17565,7 @@ msgstr "" "(FedMedian) par [Yin et al., 2018] " "(https://arxiv.org/pdf/1803.01498v1.pdf)." -#: ../../source/ref-changelog.md:308 +#: ../../source/ref-changelog.md:400 #, fuzzy msgid "" "**Introduce start_driver** " @@ -16812,7 +17574,7 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:310 +#: ../../source/ref-changelog.md:402 msgid "" "In addition to `start_server` and using the raw Driver API, there is a " "new `start_driver` function that allows for running `start_server` " @@ -16821,7 +17583,7 @@ msgid "" "`start_driver`." msgstr "" -#: ../../source/ref-changelog.md:312 +#: ../../source/ref-changelog.md:404 #, fuzzy msgid "" "**Add parameter aggregation to** `mt-pytorch` **code example** " @@ -16830,7 +17592,7 @@ msgstr "" "**Nouvel exemple de code PyTorch avancé** " "([#1007](https://github.com/adap/flower/pull/1007))" -#: ../../source/ref-changelog.md:314 +#: ../../source/ref-changelog.md:406 msgid "" "The `mt-pytorch` example shows how to aggregate parameters when writing a" " driver script. The included `driver.py` and `server.py` have been " @@ -16838,7 +17600,7 @@ msgid "" "building server-side logic." msgstr "" -#: ../../source/ref-changelog.md:316 +#: ../../source/ref-changelog.md:408 #, fuzzy msgid "" "**Migrate experimental REST API to Starlette** " @@ -16847,14 +17609,14 @@ msgstr "" "**Nouvelle stratégie expérimentale TensorBoard** " "([#789](https://github.com/adap/flower/pull/789))" -#: ../../source/ref-changelog.md:318 +#: ../../source/ref-changelog.md:410 msgid "" "The (experimental) REST API used to be implemented in " "[FastAPI](https://fastapi.tiangolo.com/), but it has now been migrated to" " use [Starlette](https://www.starlette.io/) directly." msgstr "" -#: ../../source/ref-changelog.md:320 +#: ../../source/ref-changelog.md:412 #, fuzzy msgid "" "Please note: The REST request-response API is still experimental and will" @@ -16863,7 +17625,7 @@ msgstr "" "Remarque : l'API REST est encore expérimentale et est susceptible de " "changer de manière significative au fil du temps." -#: ../../source/ref-changelog.md:322 +#: ../../source/ref-changelog.md:414 #, fuzzy msgid "" "**Introduce experimental gRPC request-response API** " @@ -16874,14 +17636,14 @@ msgstr "" "([#1357](https://github.com/adap/flower/pull/1357), " "[#1460](https://github.com/adap/flower/pull/1460))" -#: ../../source/ref-changelog.md:324 +#: ../../source/ref-changelog.md:416 msgid "" "In addition to the existing gRPC API (based on bidirectional streaming) " "and the experimental REST API, there is now a new gRPC API that uses a " "request-response model to communicate with client nodes." msgstr "" -#: ../../source/ref-changelog.md:326 +#: ../../source/ref-changelog.md:418 #, fuzzy msgid "" "Please note: The gRPC request-response API is still experimental and will" @@ -16890,7 +17652,7 @@ msgstr "" "Remarque : l'API REST est encore expérimentale et est susceptible de " "changer de manière significative au fil du temps." -#: ../../source/ref-changelog.md:328 +#: ../../source/ref-changelog.md:420 #, fuzzy msgid "" "**Replace the experimental** `start_client(rest=True)` **with the new** " @@ -16900,7 +17662,7 @@ msgstr "" "**Initialise** `start_simulation` **avec une liste d'ID de clients** " "([#860](https://github.com/adap/flower/pull/860))" -#: ../../source/ref-changelog.md:330 +#: ../../source/ref-changelog.md:422 msgid "" "The (experimental) `start_client` argument `rest` was deprecated in " "favour of a new argument `transport`. `start_client(transport=\"rest\")` " @@ -16909,7 +17671,7 @@ msgid "" "argument `rest` will be removed in a future release." msgstr "" -#: ../../source/ref-changelog.md:332 +#: ../../source/ref-changelog.md:424 #, fuzzy msgid "" "**Add a new gRPC option** " @@ -16918,14 +17680,14 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:334 +#: ../../source/ref-changelog.md:426 msgid "" "We now start a gRPC server with the `grpc.keepalive_permit_without_calls`" " option set to 0 by default. This prevents the clients from sending " "keepalive pings when there is no outstanding stream." msgstr "" -#: ../../source/ref-changelog.md:336 +#: ../../source/ref-changelog.md:428 #, fuzzy msgid "" "**Improve example notebooks** " @@ -16934,12 +17696,12 @@ msgstr "" "**Supprimer les stratégies expérimentales** " "([#1280](https://github.com/adap/flower/pull/1280))" -#: ../../source/ref-changelog.md:338 +#: ../../source/ref-changelog.md:430 #, fuzzy msgid "There's a new 30min Federated Learning PyTorch tutorial!" msgstr "Bienvenue au tutoriel sur l'apprentissage fédéré de la fleur !" -#: ../../source/ref-changelog.md:340 +#: ../../source/ref-changelog.md:432 msgid "" "**Example updates** ([#1772](https://github.com/adap/flower/pull/1772), " "[#1873](https://github.com/adap/flower/pull/1873), " @@ -16954,7 +17716,7 @@ msgid "" "[#2183](https://github.com/adap/flower/pull/2183))" msgstr "" -#: ../../source/ref-changelog.md:342 +#: ../../source/ref-changelog.md:434 msgid "" "Many examples have received significant updates, including simplified " "advanced-tensorflow and advanced-pytorch examples, improved macOS " @@ -16963,7 +17725,7 @@ msgid "" "(in addition to `pyproject.toml`)." msgstr "" -#: ../../source/ref-changelog.md:344 +#: ../../source/ref-changelog.md:436 #, fuzzy msgid "" "**General improvements** " @@ -16983,11 +17745,11 @@ msgstr "" "[#1613](https://github.com/adap/flower/pull/1613), " "[#1614](https://github.com/adap/flower/pull/1614))" -#: ../../source/ref-changelog.md:352 +#: ../../source/ref-changelog.md:444 msgid "v1.4.0 (2023-04-21)" msgstr "v1.4.0 (2023-04-21)" -#: ../../source/ref-changelog.md:358 +#: ../../source/ref-changelog.md:450 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, " @@ -17003,7 +17765,7 @@ msgstr "" "Lane`, `Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, " "`Steve Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" -#: ../../source/ref-changelog.md:362 +#: ../../source/ref-changelog.md:454 msgid "" "**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and " "example)** ([#1694](https://github.com/adap/flower/pull/1694), " @@ -17021,7 +17783,7 @@ msgstr "" "[#1763](https://github.com/adap/flower/pull/1763), " "[#1795](https://github.com/adap/flower/pull/1795))" -#: ../../source/ref-changelog.md:364 +#: ../../source/ref-changelog.md:456 msgid "" "XGBoost is a tree-based ensemble machine learning algorithm that uses " "gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg`" @@ -17038,7 +17800,7 @@ msgstr "" "qui démontre l'utilisation de cette nouvelle stratégie dans un projet " "XGBoost." -#: ../../source/ref-changelog.md:366 +#: ../../source/ref-changelog.md:458 msgid "" "**Introduce iOS SDK (preview)** " "([#1621](https://github.com/adap/flower/pull/1621), " @@ -17048,7 +17810,7 @@ msgstr "" "([#1621](https://github.com/adap/flower/pull/1621), " "[#1764](https://github.com/adap/flower/pull/1764))" -#: ../../source/ref-changelog.md:368 +#: ../../source/ref-changelog.md:460 msgid "" "This is a major update for anyone wanting to implement Federated Learning" " on iOS mobile devices. We now have a swift iOS SDK present under " @@ -17067,7 +17829,7 @@ msgstr "" "iOS](https://github.com/adap/flower/tree/main/examples/ios) a également " "été mis à jour !" -#: ../../source/ref-changelog.md:370 +#: ../../source/ref-changelog.md:462 msgid "" "**Introduce new \"What is Federated Learning?\" tutorial** " "([#1657](https://github.com/adap/flower/pull/1657), " @@ -17077,7 +17839,7 @@ msgstr "" " \"** ([#1657](https://github.com/adap/flower/pull/1657), " "[#1721](https://github.com/adap/flower/pull/1721))" -#: ../../source/ref-changelog.md:372 +#: ../../source/ref-changelog.md:464 #, fuzzy msgid "" "A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-" @@ -17093,7 +17855,7 @@ msgstr "" " voyage avec Flower. Fais-le suivre à tous ceux qui s'intéressent à " "l'apprentissage fédéré !" -#: ../../source/ref-changelog.md:374 +#: ../../source/ref-changelog.md:466 msgid "" "**Introduce new Flower Baseline: FedProx MNIST** " "([#1513](https://github.com/adap/flower/pull/1513), " @@ -17107,7 +17869,7 @@ msgstr "" "[#1681](https://github.com/adap/flower/pull/1681), " "[#1679](https://github.com/adap/flower/pull/1679))" -#: ../../source/ref-changelog.md:376 +#: ../../source/ref-changelog.md:468 msgid "" "This new baseline replicates the MNIST+CNN task from the paper [Federated" " Optimization in Heterogeneous Networks (Li et al., " @@ -17120,7 +17882,7 @@ msgstr "" "qui vise à rendre la convergence plus robuste dans des contextes " "hétérogènes." -#: ../../source/ref-changelog.md:378 +#: ../../source/ref-changelog.md:470 msgid "" "**Introduce new Flower Baseline: FedAvg FEMNIST** " "([#1655](https://github.com/adap/flower/pull/1655))" @@ -17128,7 +17890,7 @@ msgstr "" "**Introduire une nouvelle ligne de base pour les fleurs : FedAvg " "FEMNIST** ([#1655](https://github.com/adap/flower/pull/1655))" -#: ../../source/ref-changelog.md:380 +#: ../../source/ref-changelog.md:472 msgid "" "This new baseline replicates an experiment evaluating the performance of " "the FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A " @@ -17140,7 +17902,7 @@ msgstr "" " l'article [LEAF : A Benchmark for Federated Settings (Caldas et al., " "2018)] (https://arxiv.org/abs/1812.01097)." -#: ../../source/ref-changelog.md:382 +#: ../../source/ref-changelog.md:474 msgid "" "**Introduce (experimental) REST API** " "([#1594](https://github.com/adap/flower/pull/1594), " @@ -17160,7 +17922,7 @@ msgstr "" "[#1770](https://github.com/adap/flower/pull/1770), " "[#1733](https://github.com/adap/flower/pull/1733))" -#: ../../source/ref-changelog.md:384 +#: ../../source/ref-changelog.md:476 msgid "" "A new REST API has been introduced as an alternative to the gRPC-based " "communication stack. In this initial version, the REST API only supports " @@ -17170,7 +17932,7 @@ msgstr "" "communication basée sur gRPC. Dans cette version initiale, l'API REST ne " "prend en charge que les clients anonymes." -#: ../../source/ref-changelog.md:386 +#: ../../source/ref-changelog.md:478 msgid "" "Please note: The REST API is still experimental and will likely change " "significantly over time." @@ -17178,7 +17940,7 @@ msgstr "" "Remarque : l'API REST est encore expérimentale et est susceptible de " "changer de manière significative au fil du temps." -#: ../../source/ref-changelog.md:388 +#: ../../source/ref-changelog.md:480 msgid "" "**Improve the (experimental) Driver API** " "([#1663](https://github.com/adap/flower/pull/1663), " @@ -17202,7 +17964,7 @@ msgstr "" "[#1662](https://github.com/adap/flower/pull/1662), " "[#1794](https://github.com/adap/flower/pull/1794))" -#: ../../source/ref-changelog.md:390 +#: ../../source/ref-changelog.md:482 msgid "" "The Driver API is still an experimental feature, but this release " "introduces some major upgrades. One of the main improvements is the " @@ -17220,7 +17982,7 @@ msgstr "" "considérablement l'efficacité de la mémoire d'un serveur Flower " "fonctionnant depuis longtemps." -#: ../../source/ref-changelog.md:392 +#: ../../source/ref-changelog.md:484 msgid "" "**Fix spilling issues related to Ray during simulations** " "([#1698](https://github.com/adap/flower/pull/1698))" @@ -17228,7 +17990,7 @@ msgstr "" "**Répare les problèmes de déversement liés à Ray pendant les " "simulations** ([#1698](https://github.com/adap/flower/pull/1698))" -#: ../../source/ref-changelog.md:394 +#: ../../source/ref-changelog.md:486 #, fuzzy msgid "" "While running long simulations, `ray` was sometimes spilling huge amounts" @@ -17239,7 +18001,7 @@ msgstr "" "d'énormes quantités de données qui rendaient l'entraînement incapable de " "continuer. ce problème est maintenant corrigé ! 🎉" -#: ../../source/ref-changelog.md:396 +#: ../../source/ref-changelog.md:488 msgid "" "**Add new example using** `TabNet` **and Flower** " "([#1725](https://github.com/adap/flower/pull/1725))" @@ -17247,7 +18009,7 @@ msgstr "" "**Ajouter un nouvel exemple utilisant** `TabNet` **et Flower** " "([#1725](https://github.com/adap/flower/pull/1725))" -#: ../../source/ref-changelog.md:398 +#: ../../source/ref-changelog.md:490 msgid "" "TabNet is a powerful and flexible framework for training machine learning" " models on tabular data. We now have a federated example using Flower: " @@ -17260,7 +18022,7 @@ msgstr "" "tabnet](https://github.com/adap/flower/tree/main/examples/quickstart-" "tabnet)." -#: ../../source/ref-changelog.md:400 +#: ../../source/ref-changelog.md:492 msgid "" "**Add new how-to guide for monitoring simulations** " "([#1649](https://github.com/adap/flower/pull/1649))" @@ -17268,7 +18030,7 @@ msgstr "" "**Ajouter un nouveau guide pratique pour le suivi des simulations** " "([#1649](https://github.com/adap/flower/pull/1649))" -#: ../../source/ref-changelog.md:402 +#: ../../source/ref-changelog.md:494 msgid "" "We now have a documentation guide to help users monitor their performance" " during simulations." @@ -17276,7 +18038,7 @@ msgstr "" "Nous avons maintenant un guide de documentation pour aider les " "utilisateurs à surveiller leurs performances pendant les simulations." -#: ../../source/ref-changelog.md:404 +#: ../../source/ref-changelog.md:496 msgid "" "**Add training metrics to** `History` **object during simulations** " "([#1696](https://github.com/adap/flower/pull/1696))" @@ -17284,7 +18046,7 @@ msgstr "" "**Ajouter des mesures de formation à** `History` **objet pendant les " "simulations** ([#1696](https://github.com/adap/flower/pull/1696))" -#: ../../source/ref-changelog.md:406 +#: ../../source/ref-changelog.md:498 msgid "" "The `fit_metrics_aggregation_fn` can be used to aggregate training " "metrics, but previous releases did not save the results in the `History` " @@ -17295,7 +18057,7 @@ msgstr "" "n'enregistraient pas les résultats dans l'objet `History`. c'est " "désormais le cas !" -#: ../../source/ref-changelog.md:408 +#: ../../source/ref-changelog.md:500 msgid "" "**General improvements** " "([#1659](https://github.com/adap/flower/pull/1659), " @@ -17370,11 +18132,11 @@ msgstr "" "[#1692](https://github.com/adap/flower/pull/1692), " "[#1705](https://github.com/ada" -#: ../../source/ref-changelog.md:416 +#: ../../source/ref-changelog.md:508 msgid "v1.3.0 (2023-02-06)" msgstr "v1.3.0 (2023-02-06)" -#: ../../source/ref-changelog.md:422 +#: ../../source/ref-changelog.md:514 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" @@ -17382,7 +18144,7 @@ msgstr "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" -#: ../../source/ref-changelog.md:426 +#: ../../source/ref-changelog.md:518 msgid "" "**Add support for** `workload_id` **and** `group_id` **in Driver API** " "([#1595](https://github.com/adap/flower/pull/1595))" @@ -17390,7 +18152,7 @@ msgstr "" "**Ajouter la prise en charge de** `workload_id` **et** `group_id` **dans " "l'API du pilote** ([#1595](https://github.com/adap/flower/pull/1595))" -#: ../../source/ref-changelog.md:428 +#: ../../source/ref-changelog.md:520 msgid "" "The (experimental) Driver API now supports a `workload_id` that can be " "used to identify which workload a task belongs to. It also supports a new" @@ -17405,7 +18167,7 @@ msgstr "" "en cours. Le `workload_id` et le `group_id` permettent tous deux aux " "nœuds clients de décider s'ils veulent traiter une tâche ou non." -#: ../../source/ref-changelog.md:430 +#: ../../source/ref-changelog.md:522 msgid "" "**Make Driver API and Fleet API address configurable** " "([#1637](https://github.com/adap/flower/pull/1637))" @@ -17414,7 +18176,7 @@ msgstr "" "flotte soit configurable** " "([#1637](https://github.com/adap/flower/pull/1637))" -#: ../../source/ref-changelog.md:432 +#: ../../source/ref-changelog.md:524 msgid "" "The (experimental) long-running Flower server (Driver API and Fleet API) " "can now configure the server address of both Driver API (via `--driver-" @@ -17425,7 +18187,7 @@ msgstr "" "`--driver-api-address`) et de Fleet API (via `--fleet-api-address`) lors " "de son démarrage :" -#: ../../source/ref-changelog.md:434 +#: ../../source/ref-changelog.md:526 #, fuzzy msgid "" "`flower-server --driver-api-address \"0.0.0.0:8081\" --fleet-api-address " @@ -17434,11 +18196,11 @@ msgstr "" "``flower-superlink --driver-api-address \"0.0.0.0:8081\" --fleet-api-" "address \"0.0.0.0:8086\" ``" -#: ../../source/ref-changelog.md:436 +#: ../../source/ref-changelog.md:528 msgid "Both IPv4 and IPv6 addresses are supported." msgstr "Les adresses IPv4 et IPv6 sont toutes deux prises en charge." -#: ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:530 msgid "" "**Add new example of Federated Learning using fastai and Flower** " "([#1598](https://github.com/adap/flower/pull/1598))" @@ -17446,7 +18208,7 @@ msgstr "" "**Ajouter un nouvel exemple d'apprentissage fédéré utilisant fastai et " "Flower** ([#1598](https://github.com/adap/flower/pull/1598))" -#: ../../source/ref-changelog.md:440 +#: ../../source/ref-changelog.md:532 msgid "" "A new code example (`quickstart-fastai`) demonstrates federated learning " "with [fastai](https://www.fast.ai/) and Flower. You can find it here: " @@ -17459,7 +18221,7 @@ msgstr "" "fastai](https://github.com/adap/flower/tree/main/examples/quickstart-" "fastai)." -#: ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:534 msgid "" "**Make Android example compatible with** `flwr >= 1.0.0` **and the latest" " versions of Android** " @@ -17469,7 +18231,7 @@ msgstr "" "dernières versions d'Android** " "([#1603](https://github.com/adap/flower/pull/1603))" -#: ../../source/ref-changelog.md:444 +#: ../../source/ref-changelog.md:536 #, fuzzy msgid "" "The Android code example has received a substantial update: the project " @@ -17483,7 +18245,7 @@ msgstr "" "est mis à jour pour être compatible avec les outils Android les plus " "récents." -#: ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:538 msgid "" "**Add new `FedProx` strategy** " "([#1619](https://github.com/adap/flower/pull/1619))" @@ -17491,7 +18253,7 @@ msgstr "" "**Ajouter une nouvelle stratégie `FedProx`** " "([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:540 msgid "" "This " "[strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedprox.py)" @@ -17511,7 +18273,7 @@ msgstr "" "un paramètre appelé `proximal_mu` pour régulariser les modèles locaux par" " rapport aux modèles globaux." -#: ../../source/ref-changelog.md:450 +#: ../../source/ref-changelog.md:542 msgid "" "**Add new metrics to telemetry events** " "([#1640](https://github.com/adap/flower/pull/1640))" @@ -17519,7 +18281,7 @@ msgstr "" "**Ajouter de nouvelles métriques aux événements de télémétrie** " "([#1640](https://github.com/adap/flower/pull/1640))" -#: ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:544 msgid "" "An updated event structure allows, for example, the clustering of events " "within the same workload." @@ -17527,7 +18289,7 @@ msgstr "" "Une structure d'événements mise à jour permet, par exemple, de regrouper " "des événements au sein d'une même charge de travail." -#: ../../source/ref-changelog.md:454 +#: ../../source/ref-changelog.md:546 msgid "" "**Add new custom strategy tutorial section** " "[#1623](https://github.com/adap/flower/pull/1623)" @@ -17535,7 +18297,7 @@ msgstr "" "**Ajouter une nouvelle section de tutoriel sur les stratégies " "personnalisées** [#1623](https://github.com/adap/flower/pull/1623)" -#: ../../source/ref-changelog.md:456 +#: ../../source/ref-changelog.md:548 #, fuzzy msgid "" "The Flower tutorial now has a new section that covers implementing a " @@ -17549,7 +18311,7 @@ msgstr "" "Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial/Flower-3-Building-a" "-Strategy-PyTorch.ipynb)" -#: ../../source/ref-changelog.md:458 +#: ../../source/ref-changelog.md:550 msgid "" "**Add new custom serialization tutorial section** " "([#1622](https://github.com/adap/flower/pull/1622))" @@ -17557,7 +18319,7 @@ msgstr "" "**Ajouter une nouvelle section de tutoriel sur la sérialisation " "personnalisée** ([#1622](https://github.com/adap/flower/pull/1622))" -#: ../../source/ref-changelog.md:460 +#: ../../source/ref-changelog.md:552 #, fuzzy msgid "" "The Flower tutorial now has a new section that covers custom " @@ -17570,7 +18332,7 @@ msgstr "" "Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source/tutorial/Flower-4" "-Client-and-NumPyClient-PyTorch.ipynb)" -#: ../../source/ref-changelog.md:462 +#: ../../source/ref-changelog.md:554 msgid "" "**General improvements** " "([#1638](https://github.com/adap/flower/pull/1638), " @@ -17629,7 +18391,7 @@ msgstr "" "[#1599](https://github.com/adap/flower/pull/1599), " "[#1600](https://github.com/ada" -#: ../../source/ref-changelog.md:466 +#: ../../source/ref-changelog.md:558 msgid "" "**Updated documentation** " "([#1629](https://github.com/adap/flower/pull/1629), " @@ -17649,7 +18411,7 @@ msgstr "" "[#1613](https://github.com/adap/flower/pull/1613), " "[#1614](https://github.com/adap/flower/pull/1614))" -#: ../../source/ref-changelog.md:468 ../../source/ref-changelog.md:535 +#: ../../source/ref-changelog.md:560 ../../source/ref-changelog.md:627 msgid "" "As usual, the documentation has improved quite a bit. It is another step " "in our effort to make the Flower documentation the best documentation of " @@ -17660,11 +18422,11 @@ msgstr "" " meilleure documentation de tout projet. Reste à l'écoute et comme " "toujours, n'hésite pas à nous faire part de tes commentaires !" -#: ../../source/ref-changelog.md:474 +#: ../../source/ref-changelog.md:566 msgid "v1.2.0 (2023-01-13)" msgstr "v1.2.0 (2023-01-13)" -#: ../../source/ref-changelog.md:480 +#: ../../source/ref-changelog.md:572 msgid "" "`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L." " Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" @@ -17672,7 +18434,7 @@ msgstr "" "adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L. " "Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" -#: ../../source/ref-changelog.md:484 +#: ../../source/ref-changelog.md:576 msgid "" "**Introduce new Flower Baseline: FedAvg MNIST** " "([#1497](https://github.com/adap/flower/pull/1497), " @@ -17682,7 +18444,7 @@ msgstr "" "([#1497](https://github.com/adap/flower/pull/1497), " "[#1552](https://github.com/adap/flower/pull/1552))" -#: ../../source/ref-changelog.md:486 +#: ../../source/ref-changelog.md:578 msgid "" "Over the coming weeks, we will be releasing a number of new reference " "implementations useful especially to FL newcomers. They will typically " @@ -17702,7 +18464,7 @@ msgstr "" "suite.](https://flower.ai/blog/2023-01-12-fl-starter-pack-fedavg-mnist-" "cnn/)" -#: ../../source/ref-changelog.md:488 +#: ../../source/ref-changelog.md:580 msgid "" "**Improve GPU support in simulations** " "([#1555](https://github.com/adap/flower/pull/1555))" @@ -17710,7 +18472,7 @@ msgstr "" "**Améliorer la prise en charge des GPU dans les simulations** " "([#1555](https://github.com/adap/flower/pull/1555))" -#: ../../source/ref-changelog.md:490 +#: ../../source/ref-changelog.md:582 msgid "" "The Ray-based Virtual Client Engine (`start_simulation`) has been updated" " to improve GPU support. The update includes some of the hard-earned " @@ -17724,7 +18486,7 @@ msgstr "" "paramètres par défaut rendent l'exécution des simulations basées sur les " "GPU beaucoup plus robuste." -#: ../../source/ref-changelog.md:492 +#: ../../source/ref-changelog.md:584 msgid "" "**Improve GPU support in Jupyter Notebook tutorials** " "([#1527](https://github.com/adap/flower/pull/1527), " @@ -17734,7 +18496,7 @@ msgstr "" "Notebook** ([#1527](https://github.com/adap/flower/pull/1527), " "[#1558](https://github.com/adap/flower/pull/1558))" -#: ../../source/ref-changelog.md:494 +#: ../../source/ref-changelog.md:586 msgid "" "Some users reported that Jupyter Notebooks have not always been easy to " "use on GPU instances. We listened and made improvements to all of our " @@ -17745,7 +18507,7 @@ msgstr "" "écoutés et avons apporté des améliorations à tous nos carnets Jupyter ! " "Découvre les carnets mis à jour ici :" -#: ../../source/ref-changelog.md:496 +#: ../../source/ref-changelog.md:588 #, fuzzy msgid "" "[An Introduction to Federated Learning](https://flower.ai/docs/framework" @@ -17754,7 +18516,7 @@ msgstr "" "[Une introduction à l'apprentissage fédéré] " "(https://flower.ai/docs/tutorial/Flower-1-Intro-to-FL-PyTorch.html)" -#: ../../source/ref-changelog.md:497 +#: ../../source/ref-changelog.md:589 #, fuzzy msgid "" "[Strategies in Federated Learning](https://flower.ai/docs/framework" @@ -17763,7 +18525,7 @@ msgstr "" "[Stratégies d'apprentissage fédéré] " "(https://flower.ai/docs/tutorial/Flower-2-Strategies-in-FL-PyTorch.html)" -#: ../../source/ref-changelog.md:498 +#: ../../source/ref-changelog.md:590 #, fuzzy msgid "" "[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a" @@ -17773,7 +18535,7 @@ msgstr "" "(https://flower.ai/docs/tutorial/Flower-3-Building-a-Strategy-" "PyTorch.html)" -#: ../../source/ref-changelog.md:499 +#: ../../source/ref-changelog.md:591 #, fuzzy msgid "" "[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-" @@ -17782,7 +18544,7 @@ msgstr "" "[Client et NumPyClient] (https://flower.ai/docs/tutorial/Flower-4-Client-" "and-NumPyClient-PyTorch.html)" -#: ../../source/ref-changelog.md:501 +#: ../../source/ref-changelog.md:593 msgid "" "**Introduce optional telemetry** " "([#1533](https://github.com/adap/flower/pull/1533), " @@ -17794,7 +18556,7 @@ msgstr "" "[#1544](https://github.com/adap/flower/pull/1544), " "[#1584](https://github.com/adap/flower/pull/1584))" -#: ../../source/ref-changelog.md:503 +#: ../../source/ref-changelog.md:595 msgid "" "After a [request for " "feedback](https://github.com/adap/flower/issues/1534) from the community," @@ -17811,7 +18573,7 @@ msgstr "" "comment Flower est utilisé et quels sont les défis auxquels les " "utilisateurs peuvent être confrontés." -#: ../../source/ref-changelog.md:505 +#: ../../source/ref-changelog.md:597 #, fuzzy msgid "" "**Flower is a friendly framework for collaborative AI and data science.**" @@ -17825,7 +18587,7 @@ msgstr "" "souhaitent pas partager des métriques d'utilisation anonymes.[Lire la " "suite.](https://flower.ai/docs/telemetry.html)." -#: ../../source/ref-changelog.md:507 +#: ../../source/ref-changelog.md:599 msgid "" "**Introduce (experimental) Driver API** " "([#1520](https://github.com/adap/flower/pull/1520), " @@ -17844,7 +18606,7 @@ msgstr "" "[#1551](https://github.com/adap/flower/pull/1551), " "[#1567](https://github.com/adap/flower/pull/1567))" -#: ../../source/ref-changelog.md:509 +#: ../../source/ref-changelog.md:601 msgid "" "Flower now has a new (experimental) Driver API which will enable fully " "programmable, async, and multi-tenant Federated Learning and Federated " @@ -17859,7 +18621,7 @@ msgstr "" "laquelle de nombreuses fonctionnalités à venir seront construites - et tu" " peux commencer à construire ces choses dès maintenant, aussi." -#: ../../source/ref-changelog.md:511 +#: ../../source/ref-changelog.md:603 msgid "" "The Driver API also enables a new execution mode in which the server runs" " indefinitely. Multiple individual workloads can run concurrently and " @@ -17872,7 +18634,7 @@ msgstr "" "leur exécution indépendamment du serveur. Ceci est particulièrement utile" " pour les utilisateurs qui souhaitent déployer Flower en production." -#: ../../source/ref-changelog.md:513 +#: ../../source/ref-changelog.md:605 msgid "" "To learn more, check out the `mt-pytorch` code example. We look forward " "to you feedback!" @@ -17880,7 +18642,7 @@ msgstr "" "Pour en savoir plus, consulte l'exemple de code `mt-pytorch`. Nous " "attendons tes commentaires avec impatience !" -#: ../../source/ref-changelog.md:515 +#: ../../source/ref-changelog.md:607 msgid "" "Please note: *The Driver API is still experimental and will likely change" " significantly over time.*" @@ -17888,7 +18650,7 @@ msgstr "" "Remarque : *L'API du pilote est encore expérimentale et est susceptible " "de changer de manière significative au fil du temps.*" -#: ../../source/ref-changelog.md:517 +#: ../../source/ref-changelog.md:609 msgid "" "**Add new Federated Analytics with Pandas example** " "([#1469](https://github.com/adap/flower/pull/1469), " @@ -17898,7 +18660,7 @@ msgstr "" "([#1469](https://github.com/adap/flower/pull/1469), " "[#1535](https://github.com/adap/flower/pull/1535))" -#: ../../source/ref-changelog.md:519 +#: ../../source/ref-changelog.md:611 msgid "" "A new code example (`quickstart-pandas`) demonstrates federated analytics" " with Pandas and Flower. You can find it here: [quickstart-" @@ -17910,7 +18672,7 @@ msgstr "" "pandas](https://github.com/adap/flower/tree/main/examples/quickstart-" "pandas)." -#: ../../source/ref-changelog.md:521 +#: ../../source/ref-changelog.md:613 msgid "" "**Add new strategies: Krum and MultiKrum** " "([#1481](https://github.com/adap/flower/pull/1481))" @@ -17918,7 +18680,7 @@ msgstr "" "**Ajouter de nouvelles stratégies : Krum et MultiKrum** " "([#1481](https://github.com/adap/flower/pull/1481))" -#: ../../source/ref-changelog.md:523 +#: ../../source/ref-changelog.md:615 msgid "" "Edoardo, a computer science student at the Sapienza University of Rome, " "contributed a new `Krum` strategy that enables users to easily use Krum " @@ -17928,7 +18690,7 @@ msgstr "" "contribué à une nouvelle stratégie `Krum` qui permet aux utilisateurs " "d'utiliser facilement Krum et MultiKrum dans leurs charges de travail." -#: ../../source/ref-changelog.md:525 +#: ../../source/ref-changelog.md:617 msgid "" "**Update C++ example to be compatible with Flower v1.2.0** " "([#1495](https://github.com/adap/flower/pull/1495))" @@ -17936,7 +18698,7 @@ msgstr "" "**Mettre à jour l'exemple C++ pour qu'il soit compatible avec Flower " "v1.2.0** ([#1495](https://github.com/adap/flower/pull/1495))" -#: ../../source/ref-changelog.md:527 +#: ../../source/ref-changelog.md:619 msgid "" "The C++ code example has received a substantial update to make it " "compatible with the latest version of Flower." @@ -17944,7 +18706,7 @@ msgstr "" "L'exemple de code C++ a reçu une mise à jour substantielle pour le rendre" " compatible avec la dernière version de Flower." -#: ../../source/ref-changelog.md:529 +#: ../../source/ref-changelog.md:621 msgid "" "**General improvements** " "([#1491](https://github.com/adap/flower/pull/1491), " @@ -17976,7 +18738,7 @@ msgstr "" "[#1564](https://github.com/adap/flower/pull/1564), " "[#1566](https://github.com/adap/flower/pull/1566))" -#: ../../source/ref-changelog.md:533 +#: ../../source/ref-changelog.md:625 msgid "" "**Updated documentation** " "([#1494](https://github.com/adap/flower/pull/1494), " @@ -18000,7 +18762,7 @@ msgstr "" "[#1519](https://github.com/adap/flower/pull/1519), " "[#1515](https://github.com/adap/flower/pull/1515))" -#: ../../source/ref-changelog.md:537 +#: ../../source/ref-changelog.md:629 msgid "" "One highlight is the new [first time contributor " "guide](https://flower.ai/docs/first-time-contributors.html): if you've " @@ -18010,11 +18772,11 @@ msgstr "" "(https://flower.ai/docs/first-time-contributors.html) : si tu n'as jamais" " contribué sur GitHub auparavant, c'est l'endroit idéal pour commencer !" -#: ../../source/ref-changelog.md:543 +#: ../../source/ref-changelog.md:635 msgid "v1.1.0 (2022-10-31)" msgstr "v1.1.0 (2022-10-31)" -#: ../../source/ref-changelog.md:547 +#: ../../source/ref-changelog.md:639 msgid "" "We would like to give our **special thanks** to all the contributors who " "made the new version of Flower possible (in `git shortlog` order):" @@ -18023,7 +18785,7 @@ msgstr "" " qui ont rendu possible la nouvelle version de Flower (dans l'ordre `git " "shortlog`) :" -#: ../../source/ref-changelog.md:549 +#: ../../source/ref-changelog.md:641 msgid "" "`Akis Linardos`, `Christopher S`, `Daniel J. Beutel`, `George`, `Jan " "Schlicht`, `Mohammad Fares`, `Pedro Porto Buarque de Gusmão`, `Philipp " @@ -18035,7 +18797,7 @@ msgstr "" "Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, " "`danielnugraha`, `edogab33`" -#: ../../source/ref-changelog.md:553 +#: ../../source/ref-changelog.md:645 msgid "" "**Introduce Differential Privacy wrappers (preview)** " "([#1357](https://github.com/adap/flower/pull/1357), " @@ -18045,7 +18807,7 @@ msgstr "" "([#1357](https://github.com/adap/flower/pull/1357), " "[#1460](https://github.com/adap/flower/pull/1460))" -#: ../../source/ref-changelog.md:555 +#: ../../source/ref-changelog.md:647 msgid "" "The first (experimental) preview of pluggable Differential Privacy " "wrappers enables easy configuration and usage of differential privacy " @@ -18061,7 +18823,7 @@ msgstr "" "voir les documents de Flower, un nouvel explicatif va plus loin dans les " "détails." -#: ../../source/ref-changelog.md:557 +#: ../../source/ref-changelog.md:649 msgid "" "**New iOS CoreML code example** " "([#1289](https://github.com/adap/flower/pull/1289))" @@ -18069,7 +18831,7 @@ msgstr "" "**Nouvel exemple de code CoreML pour iOS** " "([#1289](https://github.com/adap/flower/pull/1289))" -#: ../../source/ref-changelog.md:559 +#: ../../source/ref-changelog.md:651 msgid "" "Flower goes iOS! A massive new code example shows how Flower clients can " "be built for iOS. The code example contains both Flower iOS SDK " @@ -18082,7 +18844,7 @@ msgstr "" "utilisés pour de nombreuses tâches, et un exemple de tâche fonctionnant " "sur CoreML." -#: ../../source/ref-changelog.md:561 +#: ../../source/ref-changelog.md:653 msgid "" "**New FedMedian strategy** " "([#1461](https://github.com/adap/flower/pull/1461))" @@ -18090,7 +18852,7 @@ msgstr "" "**Nouvelle stratégie de FedMedian** " "([#1461](https://github.com/adap/flower/pull/1461))" -#: ../../source/ref-changelog.md:563 +#: ../../source/ref-changelog.md:655 msgid "" "The new `FedMedian` strategy implements Federated Median (FedMedian) by " "[Yin et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." @@ -18099,7 +18861,7 @@ msgstr "" "(FedMedian) par [Yin et al., 2018] " "(https://arxiv.org/pdf/1803.01498v1.pdf)." -#: ../../source/ref-changelog.md:565 +#: ../../source/ref-changelog.md:657 msgid "" "**Log** `Client` **exceptions in Virtual Client Engine** " "([#1493](https://github.com/adap/flower/pull/1493))" @@ -18107,7 +18869,7 @@ msgstr "" "**Log** `Client` **exceptions dans le moteur de client virtuel** " "([#1493](https://github.com/adap/flower/pull/1493))" -#: ../../source/ref-changelog.md:567 +#: ../../source/ref-changelog.md:659 msgid "" "All `Client` exceptions happening in the VCE are now logged by default " "and not just exposed to the configured `Strategy` (via the `failures` " @@ -18117,7 +18879,7 @@ msgstr "" "maintenant enregistrées par défaut et ne sont pas seulement exposées à la" " `Stratégie` configurée (via l'argument `failures`)." -#: ../../source/ref-changelog.md:569 +#: ../../source/ref-changelog.md:661 msgid "" "**Improve Virtual Client Engine internals** " "([#1401](https://github.com/adap/flower/pull/1401), " @@ -18127,7 +18889,7 @@ msgstr "" "([#1401](https://github.com/adap/flower/pull/1401), " "[#1453](https://github.com/adap/flower/pull/1453))" -#: ../../source/ref-changelog.md:571 +#: ../../source/ref-changelog.md:663 msgid "" "Some internals of the Virtual Client Engine have been revamped. The VCE " "now uses Ray 2.0 under the hood, the value type of the `client_resources`" @@ -18138,7 +18900,7 @@ msgstr "" "dictionnaire `client_resources` a été remplacé par `float` pour permettre" " l'allocation de fractions de ressources." -#: ../../source/ref-changelog.md:573 +#: ../../source/ref-changelog.md:665 msgid "" "**Support optional** `Client`**/**`NumPyClient` **methods in Virtual " "Client Engine**" @@ -18146,7 +18908,7 @@ msgstr "" "**Support optional** `Client`**/**`NumPyClient` **methods in Virtual " "Client Engine**" -#: ../../source/ref-changelog.md:575 +#: ../../source/ref-changelog.md:667 msgid "" "The Virtual Client Engine now has full support for optional `Client` (and" " `NumPyClient`) methods." @@ -18154,7 +18916,7 @@ msgstr "" "Le moteur de client virtuel prend désormais en charge les méthodes " "optionnelles `Client` (et `NumPyClient`)." -#: ../../source/ref-changelog.md:577 +#: ../../source/ref-changelog.md:669 msgid "" "**Provide type information to packages using** `flwr` " "([#1377](https://github.com/adap/flower/pull/1377))" @@ -18162,7 +18924,7 @@ msgstr "" "**Fournir des informations de type aux paquets en utilisant** `flwr` " "([#1377](https://github.com/adap/flower/pull/1377))" -#: ../../source/ref-changelog.md:579 +#: ../../source/ref-changelog.md:671 msgid "" "The package `flwr` is now bundled with a `py.typed` file indicating that " "the package is typed. This enables typing support for projects or " @@ -18175,7 +18937,7 @@ msgstr "" "permettant d'améliorer leur code à l'aide de vérificateurs de types " "statiques comme `mypy`." -#: ../../source/ref-changelog.md:581 +#: ../../source/ref-changelog.md:673 msgid "" "**Updated code example** " "([#1344](https://github.com/adap/flower/pull/1344), " @@ -18185,7 +18947,7 @@ msgstr "" "([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" -#: ../../source/ref-changelog.md:583 +#: ../../source/ref-changelog.md:675 msgid "" "The code examples covering scikit-learn and PyTorch Lightning have been " "updated to work with the latest version of Flower." @@ -18193,7 +18955,7 @@ msgstr "" "Les exemples de code couvrant scikit-learn et PyTorch Lightning ont été " "mis à jour pour fonctionner avec la dernière version de Flower." -#: ../../source/ref-changelog.md:585 +#: ../../source/ref-changelog.md:677 msgid "" "**Updated documentation** " "([#1355](https://github.com/adap/flower/pull/1355), " @@ -18233,7 +18995,7 @@ msgstr "" "[#1465](https://github.com/adap/flower/pull/1465), " "[#1467](https://github.com/adap/flower/pull/1467))" -#: ../../source/ref-changelog.md:587 +#: ../../source/ref-changelog.md:679 msgid "" "There have been so many documentation updates that it doesn't even make " "sense to list them individually." @@ -18241,7 +19003,7 @@ msgstr "" "Il y a eu tellement de mises à jour de la documentation que cela n'a même" " pas de sens de les énumérer individuellement." -#: ../../source/ref-changelog.md:589 +#: ../../source/ref-changelog.md:681 msgid "" "**Restructured documentation** " "([#1387](https://github.com/adap/flower/pull/1387))" @@ -18249,7 +19011,7 @@ msgstr "" "**Documentation restructurée** " "([#1387](https://github.com/adap/flower/pull/1387))" -#: ../../source/ref-changelog.md:591 +#: ../../source/ref-changelog.md:683 msgid "" "The documentation has been restructured to make it easier to navigate. " "This is just the first step in a larger effort to make the Flower " @@ -18259,7 +19021,7 @@ msgstr "" "n'est que la première étape d'un effort plus important visant à faire de " "la documentation de Flower la meilleure documentation de tous les projets" -#: ../../source/ref-changelog.md:593 +#: ../../source/ref-changelog.md:685 msgid "" "**Open in Colab button** " "([#1389](https://github.com/adap/flower/pull/1389))" @@ -18267,7 +19029,7 @@ msgstr "" "**Ouvrir dans le bouton Colab** " "([#1389](https://github.com/adap/flower/pull/1389))" -#: ../../source/ref-changelog.md:595 +#: ../../source/ref-changelog.md:687 msgid "" "The four parts of the Flower Federated Learning Tutorial now come with a " "new `Open in Colab` button. No need to install anything on your local " @@ -18280,7 +19042,7 @@ msgstr "" "maintenant utiliser et apprendre à connaître Flower dans ton navigateur, " "il te suffit d'un simple clic." -#: ../../source/ref-changelog.md:597 +#: ../../source/ref-changelog.md:689 msgid "" "**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468)," " [#1470](https://github.com/adap/flower/pull/1470), " @@ -18296,7 +19058,7 @@ msgstr "" "[#1474](https://github.com/adap/flower/pull/1474), " "[#1475](https://github.com/adap/flower/pull/1475))" -#: ../../source/ref-changelog.md:599 +#: ../../source/ref-changelog.md:691 msgid "" "The Flower Federated Learning Tutorial has two brand-new parts covering " "custom strategies (still WIP) and the distinction between `Client` and " @@ -18309,27 +19071,27 @@ msgstr "" "existantes ont également été améliorées (beaucoup de petits changements " "et de corrections)." -#: ../../source/ref-changelog.md:605 +#: ../../source/ref-changelog.md:697 msgid "v1.0.0 (2022-07-28)" msgstr "v1.0.0 (2022-07-28)" -#: ../../source/ref-changelog.md:607 +#: ../../source/ref-changelog.md:699 msgid "Highlights" msgstr "Points forts" -#: ../../source/ref-changelog.md:609 +#: ../../source/ref-changelog.md:701 msgid "Stable **Virtual Client Engine** (accessible via `start_simulation`)" msgstr "Moteur de client virtuel stable** (accessible via `start_simulation`)" -#: ../../source/ref-changelog.md:610 +#: ../../source/ref-changelog.md:702 msgid "All `Client`/`NumPyClient` methods are now optional" msgstr "Toutes les méthodes `Client`/`NumPyClient` sont maintenant optionnelles" -#: ../../source/ref-changelog.md:611 +#: ../../source/ref-changelog.md:703 msgid "Configurable `get_parameters`" msgstr "`get_parameters` configurable" -#: ../../source/ref-changelog.md:612 +#: ../../source/ref-changelog.md:704 msgid "" "Tons of small API cleanups resulting in a more coherent developer " "experience" @@ -18337,7 +19099,7 @@ msgstr "" "Des tonnes de petits nettoyages d'API résultant en une expérience plus " "cohérente pour les développeurs" -#: ../../source/ref-changelog.md:616 +#: ../../source/ref-changelog.md:708 msgid "" "We would like to give our **special thanks** to all the contributors who " "made Flower 1.0 possible (in reverse [GitHub " @@ -18347,7 +19109,7 @@ msgstr "" "ont rendu Flower 1.0 possible (dans l'ordre inverse de [GitHub " "Contributors](https://github.com/adap/flower/graphs/contributors)) :" -#: ../../source/ref-changelog.md:618 +#: ../../source/ref-changelog.md:710 msgid "" "[@rtaiello](https://github.com/rtaiello), " "[@g-pichler](https://github.com/g-pichler), [@rob-" @@ -18407,7 +19169,7 @@ msgstr "" "/Jueun-Park), [@architjen](https://github.com/architjen), " "[@PratikGarai](https://github.com/PratikGarai), [@mrinaald](" -#: ../../source/ref-changelog.md:622 +#: ../../source/ref-changelog.md:714 msgid "" "**All arguments must be passed as keyword arguments** " "([#1338](https://github.com/adap/flower/pull/1338))" @@ -18415,7 +19177,7 @@ msgstr "" "**Tous les arguments doivent être passés comme des arguments de mot-clé**" " ([#1338](https://github.com/adap/flower/pull/1338))" -#: ../../source/ref-changelog.md:624 +#: ../../source/ref-changelog.md:716 #, fuzzy msgid "" "Pass all arguments as keyword arguments, positional arguments are not " @@ -18431,7 +19193,7 @@ msgstr "" "``start_client(server_address=\"127.0.0.1:8080\", " "client=FlowerClient())`)." -#: ../../source/ref-changelog.md:626 +#: ../../source/ref-changelog.md:718 msgid "" "**Introduce configuration object** `ServerConfig` **in** `start_server` " "**and** `start_simulation` " @@ -18441,7 +19203,7 @@ msgstr "" "`start_server` **et** `start_simulation` " "([#1317](https://github.com/adap/flower/pull/1317))" -#: ../../source/ref-changelog.md:628 +#: ../../source/ref-changelog.md:720 msgid "" "Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": " "600.0}`, `start_server` and `start_simulation` now expect a configuration" @@ -18457,7 +19219,7 @@ msgstr "" "sécurisé plus facile et les valeurs des paramètres par défaut plus " "transparentes." -#: ../../source/ref-changelog.md:630 +#: ../../source/ref-changelog.md:722 msgid "" "**Rename built-in strategy parameters for clarity** " "([#1334](https://github.com/adap/flower/pull/1334))" @@ -18465,7 +19227,7 @@ msgstr "" "**Renommer les paramètres de la stratégie intégrée pour plus de clarté** " "([#1334](https://github.com/adap/flower/pull/1334))" -#: ../../source/ref-changelog.md:632 +#: ../../source/ref-changelog.md:724 msgid "" "The following built-in strategy parameters were renamed to improve " "readability and consistency with other API's:" @@ -18473,19 +19235,19 @@ msgstr "" "Les paramètres de stratégie intégrés suivants ont été renommés pour " "améliorer la lisibilité et la cohérence avec d'autres API :" -#: ../../source/ref-changelog.md:634 +#: ../../source/ref-changelog.md:726 msgid "`fraction_eval` --> `fraction_evaluate`" msgstr "`fraction_eval` --> `fraction_evaluate`" -#: ../../source/ref-changelog.md:635 +#: ../../source/ref-changelog.md:727 msgid "`min_eval_clients` --> `min_evaluate_clients`" msgstr "`min_eval_clients` --> `min_evaluate_clients`" -#: ../../source/ref-changelog.md:636 +#: ../../source/ref-changelog.md:728 msgid "`eval_fn` --> `evaluate_fn`" msgstr "`eval_fn` --> `evaluate_fn`" -#: ../../source/ref-changelog.md:638 +#: ../../source/ref-changelog.md:730 msgid "" "**Update default arguments of built-in strategies** " "([#1278](https://github.com/adap/flower/pull/1278))" @@ -18493,7 +19255,7 @@ msgstr "" "**Mettre à jour les arguments par défaut des stratégies intégrées** " "([#1278](https://github.com/adap/flower/pull/1278))" -#: ../../source/ref-changelog.md:640 +#: ../../source/ref-changelog.md:732 msgid "" "All built-in strategies now use `fraction_fit=1.0` and " "`fraction_evaluate=1.0`, which means they select *all* currently " @@ -18508,11 +19270,11 @@ msgstr "" "peuvent retrouver le comportement antérieur en initialisant la stratégie " "de la manière suivante :" -#: ../../source/ref-changelog.md:642 +#: ../../source/ref-changelog.md:734 msgid "`strategy = FedAvg(fraction_fit=0.1, fraction_evaluate=0.1)`" msgstr "`stratégie = FedAvg(fraction_fit=0.1, fraction_evaluate=0.1)`" -#: ../../source/ref-changelog.md:644 +#: ../../source/ref-changelog.md:736 msgid "" "**Add** `server_round` **to** `Strategy.evaluate` " "([#1334](https://github.com/adap/flower/pull/1334))" @@ -18520,7 +19282,7 @@ msgstr "" "**Ajouter** `server_round` **à** `Strategy.evaluate` " "([#1334](https://github.com/adap/flower/pull/1334))" -#: ../../source/ref-changelog.md:646 +#: ../../source/ref-changelog.md:738 msgid "" "The `Strategy` method `evaluate` now receives the current round of " "federated learning/evaluation as the first parameter." @@ -18528,7 +19290,7 @@ msgstr "" "La méthode `Stratégie` `évaluer` reçoit maintenant le cycle actuel " "d'apprentissage/évaluation fédéré comme premier paramètre." -#: ../../source/ref-changelog.md:648 +#: ../../source/ref-changelog.md:740 msgid "" "**Add** `server_round` **and** `config` **parameters to** `evaluate_fn` " "([#1334](https://github.com/adap/flower/pull/1334))" @@ -18536,7 +19298,7 @@ msgstr "" "**Ajouter** `server_round` **et** `config` **paramètres à** `evaluate_fn`" " ([#1334](https://github.com/adap/flower/pull/1334))" -#: ../../source/ref-changelog.md:650 +#: ../../source/ref-changelog.md:742 msgid "" "The `evaluate_fn` passed to built-in strategies like `FedAvg` now takes " "three parameters: (1) The current round of federated learning/evaluation " @@ -18549,7 +19311,7 @@ msgstr "" " modèle à évaluer (`parameters`), et (3) un dictionnaire de configuration" " (`config`)." -#: ../../source/ref-changelog.md:652 +#: ../../source/ref-changelog.md:744 msgid "" "**Rename** `rnd` **to** `server_round` " "([#1321](https://github.com/adap/flower/pull/1321))" @@ -18557,7 +19319,7 @@ msgstr "" "**Rename** `rnd` **to** `server_round` " "([#1321](https://github.com/adap/flower/pull/1321))" -#: ../../source/ref-changelog.md:654 +#: ../../source/ref-changelog.md:746 msgid "" "Several Flower methods and functions (`evaluate_fn`, `configure_fit`, " "`aggregate_fit`, `configure_evaluate`, `aggregate_evaluate`) receive the " @@ -18572,7 +19334,7 @@ msgstr "" " la fiabilité et éviter la confusion avec *random*, ce paramètre a été " "renommé de `rnd` à `server_round`." -#: ../../source/ref-changelog.md:656 +#: ../../source/ref-changelog.md:748 msgid "" "**Move** `flwr.dataset` **to** `flwr_baselines` " "([#1273](https://github.com/adap/flower/pull/1273))" @@ -18580,11 +19342,11 @@ msgstr "" "**Déplacer** `flwr.dataset` **vers** `flwr_baselines` " "([#1273](https://github.com/adap/flower/pull/1273))" -#: ../../source/ref-changelog.md:658 +#: ../../source/ref-changelog.md:750 msgid "The experimental package `flwr.dataset` was migrated to Flower Baselines." msgstr "Le paquet expérimental `flwr.dataset` a été migré vers Flower Baselines." -#: ../../source/ref-changelog.md:660 +#: ../../source/ref-changelog.md:752 msgid "" "**Remove experimental strategies** " "([#1280](https://github.com/adap/flower/pull/1280))" @@ -18592,7 +19354,7 @@ msgstr "" "**Supprimer les stratégies expérimentales** " "([#1280](https://github.com/adap/flower/pull/1280))" -#: ../../source/ref-changelog.md:662 +#: ../../source/ref-changelog.md:754 msgid "" "Remove unmaintained experimental strategies (`FastAndSlow`, `FedFSv0`, " "`FedFSv1`)." @@ -18600,7 +19362,7 @@ msgstr "" "Supprimer les stratégies expérimentales non maintenues (`FastAndSlow`, " "`FedFSv0`, `FedFSv1`)." -#: ../../source/ref-changelog.md:664 +#: ../../source/ref-changelog.md:756 msgid "" "**Rename** `Weights` **to** `NDArrays` " "([#1258](https://github.com/adap/flower/pull/1258), " @@ -18610,7 +19372,7 @@ msgstr "" "([#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" -#: ../../source/ref-changelog.md:666 +#: ../../source/ref-changelog.md:758 msgid "" "`flwr.common.Weights` was renamed to `flwr.common.NDArrays` to better " "capture what this type is all about." @@ -18618,7 +19380,7 @@ msgstr "" "`flwr.common.Weights` a été renommé en `flwr.common.NDArys` pour mieux " "rendre compte de la nature de ce type." -#: ../../source/ref-changelog.md:668 +#: ../../source/ref-changelog.md:760 msgid "" "**Remove antiquated** `force_final_distributed_eval` **from** " "`start_server` ([#1258](https://github.com/adap/flower/pull/1258), " @@ -18628,7 +19390,7 @@ msgstr "" "`start_server` ([#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" -#: ../../source/ref-changelog.md:670 +#: ../../source/ref-changelog.md:762 msgid "" "The `start_server` parameter `force_final_distributed_eval` has long been" " a historic artefact, in this release it is finally gone for good." @@ -18637,7 +19399,7 @@ msgstr "" "été un artefact historique, dans cette version il a finalement disparu " "pour de bon." -#: ../../source/ref-changelog.md:672 +#: ../../source/ref-changelog.md:764 msgid "" "**Make** `get_parameters` **configurable** " "([#1242](https://github.com/adap/flower/pull/1242))" @@ -18645,7 +19407,7 @@ msgstr "" "**Make** `get_parameters` **configurable** " "([#1242](https://github.com/adap/flower/pull/1242))" -#: ../../source/ref-changelog.md:674 +#: ../../source/ref-changelog.md:766 msgid "" "The `get_parameters` method now accepts a configuration dictionary, just " "like `get_properties`, `fit`, and `evaluate`." @@ -18653,7 +19415,7 @@ msgstr "" "La méthode `get_parameters` accepte maintenant un dictionnaire de " "configuration, tout comme `get_properties`, `fit`, et `evaluate`." -#: ../../source/ref-changelog.md:676 +#: ../../source/ref-changelog.md:768 msgid "" "**Replace** `num_rounds` **in** `start_simulation` **with new** `config` " "**parameter** ([#1281](https://github.com/adap/flower/pull/1281))" @@ -18662,7 +19424,7 @@ msgstr "" " `config` **paramètre** " "([#1281](https://github.com/adap/flower/pull/1281))" -#: ../../source/ref-changelog.md:678 +#: ../../source/ref-changelog.md:770 msgid "" "The `start_simulation` function now accepts a configuration dictionary " "`config` instead of the `num_rounds` integer. This improves the " @@ -18674,7 +19436,7 @@ msgstr "" " cohérence entre `start_simulation` et `start_server` et facilite la " "transition entre les deux." -#: ../../source/ref-changelog.md:682 +#: ../../source/ref-changelog.md:774 msgid "" "**Support Python 3.10** " "([#1320](https://github.com/adap/flower/pull/1320))" @@ -18682,7 +19444,7 @@ msgstr "" "**Support Python 3.10** " "([#1320](https://github.com/adap/flower/pull/1320))" -#: ../../source/ref-changelog.md:684 +#: ../../source/ref-changelog.md:776 msgid "" "The previous Flower release introduced experimental support for Python " "3.10, this release declares Python 3.10 support as stable." @@ -18691,7 +19453,7 @@ msgstr "" "expérimentale de Python 3.10, cette version déclare la prise en charge de" " Python 3.10 comme stable." -#: ../../source/ref-changelog.md:686 +#: ../../source/ref-changelog.md:778 msgid "" "**Make all** `Client` **and** `NumPyClient` **methods optional** " "([#1260](https://github.com/adap/flower/pull/1260), " @@ -18701,7 +19463,7 @@ msgstr "" "**facultatives** ([#1260](https://github.com/adap/flower/pull/1260), " "[#1277](https://github.com/adap/flower/pull/1277))" -#: ../../source/ref-changelog.md:688 +#: ../../source/ref-changelog.md:780 msgid "" "The `Client`/`NumPyClient` methods `get_properties`, `get_parameters`, " "`fit`, and `evaluate` are all optional. This enables writing clients that" @@ -18714,7 +19476,7 @@ msgstr "" "méthode. Pas besoin d'implémenter `evaluate` quand on utilise " "l'évaluation centralisée !" -#: ../../source/ref-changelog.md:690 +#: ../../source/ref-changelog.md:782 msgid "" "**Enable passing a** `Server` **instance to** `start_simulation` " "([#1281](https://github.com/adap/flower/pull/1281))" @@ -18722,7 +19484,7 @@ msgstr "" "**Autoriser le passage d'une **instance `Server` à** `start_simulation` " "([#1281](https://github.com/adap/flower/pull/1281))" -#: ../../source/ref-changelog.md:692 +#: ../../source/ref-changelog.md:784 msgid "" "Similar to `start_server`, `start_simulation` now accepts a full `Server`" " instance. This enables users to heavily customize the execution of " @@ -18735,7 +19497,7 @@ msgstr "" "l'exécution, par exemple, de FL asynchrones à l'aide du moteur de client " "virtuel." -#: ../../source/ref-changelog.md:694 +#: ../../source/ref-changelog.md:786 msgid "" "**Update code examples** " "([#1291](https://github.com/adap/flower/pull/1291), " @@ -18747,7 +19509,7 @@ msgstr "" "[#1286](https://github.com/adap/flower/pull/1286), " "[#1282](https://github.com/adap/flower/pull/1282))" -#: ../../source/ref-changelog.md:696 +#: ../../source/ref-changelog.md:788 msgid "" "Many code examples received small or even large maintenance updates, " "among them are" @@ -18755,31 +19517,31 @@ msgstr "" "De nombreux exemples de code ont reçu de petites ou même de grandes mises" " à jour de maintenance" -#: ../../source/ref-changelog.md:698 +#: ../../source/ref-changelog.md:790 msgid "`scikit-learn`" msgstr "`scikit-learn`" -#: ../../source/ref-changelog.md:699 +#: ../../source/ref-changelog.md:791 msgid "`simulation_pytorch`" msgstr "`simulation_pytorch`" -#: ../../source/ref-changelog.md:700 +#: ../../source/ref-changelog.md:792 msgid "`quickstart_pytorch`" msgstr "`quickstart_pytorch` (démarrage rapide)" -#: ../../source/ref-changelog.md:701 +#: ../../source/ref-changelog.md:793 msgid "`quickstart_simulation`" msgstr "`quickstart_simulation`" -#: ../../source/ref-changelog.md:702 +#: ../../source/ref-changelog.md:794 msgid "`quickstart_tensorflow`" msgstr "`quickstart_tensorflow`" -#: ../../source/ref-changelog.md:703 +#: ../../source/ref-changelog.md:795 msgid "`advanced_tensorflow`" msgstr "`advanced_tensorflow` (en anglais)" -#: ../../source/ref-changelog.md:705 +#: ../../source/ref-changelog.md:797 msgid "" "**Remove the obsolete simulation example** " "([#1328](https://github.com/adap/flower/pull/1328))" @@ -18787,7 +19549,7 @@ msgstr "" "**Supprime l'exemple de simulation obsolète** " "([#1328](https://github.com/adap/flower/pull/1328))" -#: ../../source/ref-changelog.md:707 +#: ../../source/ref-changelog.md:799 msgid "" "Removes the obsolete `simulation` example and renames " "`quickstart_simulation` to `simulation_tensorflow` so it fits withs the " @@ -18797,7 +19559,7 @@ msgstr "" "`quickstart_simulation` en `simulation_tensorflow` pour qu'il corresponde" " au nom de `simulation_pytorch`" -#: ../../source/ref-changelog.md:709 +#: ../../source/ref-changelog.md:801 msgid "" "**Update documentation** " "([#1223](https://github.com/adap/flower/pull/1223), " @@ -18823,7 +19585,7 @@ msgstr "" "[#1305](https://github.com/adap/flower/pull/1305), " "[#1307](https://github.com/adap/flower/pull/1307))" -#: ../../source/ref-changelog.md:711 +#: ../../source/ref-changelog.md:803 msgid "" "One substantial documentation update fixes multiple smaller rendering " "issues, makes titles more succinct to improve navigation, removes a " @@ -18840,12 +19602,12 @@ msgstr "" "markdown, migre le changelog de `.rst` vers `.md`, et corrige un certain " "nombre de détails plus petits !" -#: ../../source/ref-changelog.md:713 ../../source/ref-changelog.md:768 -#: ../../source/ref-changelog.md:837 ../../source/ref-changelog.md:876 +#: ../../source/ref-changelog.md:805 ../../source/ref-changelog.md:860 +#: ../../source/ref-changelog.md:929 ../../source/ref-changelog.md:968 msgid "**Minor updates**" msgstr "**Mises à jour mineures**" -#: ../../source/ref-changelog.md:715 +#: ../../source/ref-changelog.md:807 msgid "" "Add round number to fit and evaluate log messages " "([#1266](https://github.com/adap/flower/pull/1266))" @@ -18853,7 +19615,7 @@ msgstr "" "Ajoute un chiffre rond pour ajuster et évaluer les messages du journal " "([#1266](https://github.com/adap/flower/pull/1266))" -#: ../../source/ref-changelog.md:716 +#: ../../source/ref-changelog.md:808 msgid "" "Add secure gRPC connection to the `advanced_tensorflow` code example " "([#847](https://github.com/adap/flower/pull/847))" @@ -18861,7 +19623,7 @@ msgstr "" "Ajouter une connexion gRPC sécurisée à l'exemple de code " "`advanced_tensorflow` ([#847](https://github.com/adap/flower/pull/847))" -#: ../../source/ref-changelog.md:717 +#: ../../source/ref-changelog.md:809 msgid "" "Update developer tooling " "([#1231](https://github.com/adap/flower/pull/1231), " @@ -18875,7 +19637,7 @@ msgstr "" "[#1301](https://github.com/adap/flower/pull/1301), " "[#1310](https://github.com/adap/flower/pull/1310))" -#: ../../source/ref-changelog.md:718 +#: ../../source/ref-changelog.md:810 msgid "" "Rename ProtoBuf messages to improve consistency " "([#1214](https://github.com/adap/flower/pull/1214), " @@ -18887,11 +19649,11 @@ msgstr "" "[#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" -#: ../../source/ref-changelog.md:720 +#: ../../source/ref-changelog.md:812 msgid "v0.19.0 (2022-05-18)" msgstr "v0.19.0 (2022-05-18)" -#: ../../source/ref-changelog.md:724 +#: ../../source/ref-changelog.md:816 msgid "" "**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** " "([#919](https://github.com/adap/flower/pull/919), " @@ -18903,7 +19665,7 @@ msgstr "" "[#1127](https://github.com/adap/flower/pull/1127), " "[#914](https://github.com/adap/flower/pull/914))" -#: ../../source/ref-changelog.md:726 +#: ../../source/ref-changelog.md:818 #, fuzzy msgid "" "The first preview release of Flower Baselines has arrived! We're " @@ -18922,7 +19684,7 @@ msgstr "" "également la communauté à [contribuer à leurs propres lignes de " "base](https://flower.ai/docs/baselines/how-to-contribute-baselines.html)." -#: ../../source/ref-changelog.md:728 +#: ../../source/ref-changelog.md:820 msgid "" "**C++ client SDK (preview) and code example** " "([#1111](https://github.com/adap/flower/pull/1111))" @@ -18930,7 +19692,7 @@ msgstr "" "**SDK client C++ (aperçu) et exemple de code** " "([#1111](https://github.com/adap/flower/pull/1111))" -#: ../../source/ref-changelog.md:730 +#: ../../source/ref-changelog.md:822 msgid "" "Preview support for Flower clients written in C++. The C++ preview " "includes a Flower client SDK and a quickstart code example that " @@ -18940,7 +19702,7 @@ msgstr "" "code de démarrage rapide qui démontre un client C++ simple utilisant le " "SDK." -#: ../../source/ref-changelog.md:732 +#: ../../source/ref-changelog.md:824 msgid "" "**Add experimental support for Python 3.10 and Python 3.11** " "([#1135](https://github.com/adap/flower/pull/1135))" @@ -18948,7 +19710,7 @@ msgstr "" "**Ajouter la prise en charge expérimentale de Python 3.10 et Python " "3.11** ([#1135](https://github.com/adap/flower/pull/1135))" -#: ../../source/ref-changelog.md:734 +#: ../../source/ref-changelog.md:826 msgid "" "Python 3.10 is the latest stable release of Python and Python 3.11 is due" " to be released in October. This Flower release adds experimental support" @@ -18958,7 +19720,7 @@ msgstr "" "devrait sortir en octobre. Cette version de Flower ajoute une prise en " "charge expérimentale pour les deux versions de Python." -#: ../../source/ref-changelog.md:736 +#: ../../source/ref-changelog.md:828 msgid "" "**Aggregate custom metrics through user-provided functions** " "([#1144](https://github.com/adap/flower/pull/1144))" @@ -18966,7 +19728,7 @@ msgstr "" "**Agréger des mesures personnalisées grâce à des fonctions fournies par " "l'utilisateur** ([#1144](https://github.com/adap/flower/pull/1144))" -#: ../../source/ref-changelog.md:738 +#: ../../source/ref-changelog.md:830 msgid "" "Custom metrics (e.g., `accuracy`) can now be aggregated without having to" " customize the strategy. Built-in strategies support two new arguments, " @@ -18978,7 +19740,7 @@ msgstr "" "permettent de passer des fonctions d'agrégation de métriques " "personnalisées." -#: ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:832 msgid "" "**User-configurable round timeout** " "([#1162](https://github.com/adap/flower/pull/1162))" @@ -18986,7 +19748,7 @@ msgstr "" "**Temps d'attente configurable par l'utilisateur** " "([#1162](https://github.com/adap/flower/pull/1162))" -#: ../../source/ref-changelog.md:742 +#: ../../source/ref-changelog.md:834 msgid "" "A new configuration value allows the round timeout to be set for " "`start_server` and `start_simulation`. If the `config` dictionary " @@ -18998,7 +19760,7 @@ msgstr "" "valeur `float` en secondes), le serveur attendra *au moins* " "`round_timeout` secondes avant de fermer la connexion." -#: ../../source/ref-changelog.md:744 +#: ../../source/ref-changelog.md:836 msgid "" "**Enable both federated evaluation and centralized evaluation to be used " "at the same time in all built-in strategies** " @@ -19008,7 +19770,7 @@ msgstr "" "l'évaluation centralisée dans toutes les stratégies intégrées** " "([#1091](https://github.com/adap/flower/pull/1091))" -#: ../../source/ref-changelog.md:746 +#: ../../source/ref-changelog.md:838 msgid "" "Built-in strategies can now perform both federated evaluation (i.e., " "client-side) and centralized evaluation (i.e., server-side) in the same " @@ -19020,7 +19782,7 @@ msgstr "" "(c'est-à-dire côté serveur) dans le même tour. L'évaluation fédérée peut " "être désactivée en réglant `fraction_eval` sur `0.0`." -#: ../../source/ref-changelog.md:748 +#: ../../source/ref-changelog.md:840 msgid "" "**Two new Jupyter Notebook tutorials** " "([#1141](https://github.com/adap/flower/pull/1141))" @@ -19028,7 +19790,7 @@ msgstr "" "**Deux nouveaux tutoriels Jupyter Notebook** " "([#1141](https://github.com/adap/flower/pull/1141))" -#: ../../source/ref-changelog.md:750 +#: ../../source/ref-changelog.md:842 msgid "" "Two Jupyter Notebook tutorials (compatible with Google Colab) explain " "basic and intermediate Flower features:" @@ -19036,7 +19798,7 @@ msgstr "" "Deux tutoriels Jupyter Notebook (compatibles avec Google Colab) " "expliquent les fonctionnalités de base et intermédiaires de Flower :" -#: ../../source/ref-changelog.md:752 +#: ../../source/ref-changelog.md:844 msgid "" "*An Introduction to Federated Learning*: [Open in " "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" @@ -19046,7 +19808,7 @@ msgstr "" "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" "-Intro-to-FL-PyTorch.ipynb)" -#: ../../source/ref-changelog.md:754 +#: ../../source/ref-changelog.md:846 msgid "" "*Using Strategies in Federated Learning*: [Open in " "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" @@ -19056,7 +19818,7 @@ msgstr "" "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" "-Strategies-in-FL-PyTorch.ipynb)" -#: ../../source/ref-changelog.md:756 +#: ../../source/ref-changelog.md:848 msgid "" "**New FedAvgM strategy (Federated Averaging with Server Momentum)** " "([#1076](https://github.com/adap/flower/pull/1076))" @@ -19064,7 +19826,7 @@ msgstr "" "**Nouvelle stratégie FedAvgM (Federated Averaging with Server Momentum)**" " ([#1076](https://github.com/adap/flower/pull/1076))" -#: ../../source/ref-changelog.md:758 +#: ../../source/ref-changelog.md:850 #, fuzzy msgid "" "The new `FedAvgM` strategy implements Federated Averaging with Server " @@ -19073,7 +19835,7 @@ msgstr "" "La nouvelle stratégie `FedAvgM` met en œuvre la moyenne fédérée avec le " "momentum du serveur [Hsu et al., 2019]." -#: ../../source/ref-changelog.md:760 +#: ../../source/ref-changelog.md:852 msgid "" "**New advanced PyTorch code example** " "([#1007](https://github.com/adap/flower/pull/1007))" @@ -19081,7 +19843,7 @@ msgstr "" "**Nouvel exemple de code PyTorch avancé** " "([#1007](https://github.com/adap/flower/pull/1007))" -#: ../../source/ref-changelog.md:762 +#: ../../source/ref-changelog.md:854 msgid "" "A new code example (`advanced_pytorch`) demonstrates advanced Flower " "concepts with PyTorch." @@ -19089,7 +19851,7 @@ msgstr "" "Un nouvel exemple de code (`advanced_pytorch`) démontre des concepts de " "fleur avancés avec PyTorch." -#: ../../source/ref-changelog.md:764 +#: ../../source/ref-changelog.md:856 msgid "" "**New JAX code example** " "([#906](https://github.com/adap/flower/pull/906), " @@ -19099,7 +19861,7 @@ msgstr "" "([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143))" -#: ../../source/ref-changelog.md:766 +#: ../../source/ref-changelog.md:858 msgid "" "A new code example (`jax_from_centralized_to_federated`) shows federated " "learning with JAX and Flower." @@ -19107,7 +19869,7 @@ msgstr "" "Un nouvel exemple de code (`jax_from_centralized_to_federated`) montre " "l'apprentissage fédéré avec JAX et Flower." -#: ../../source/ref-changelog.md:770 +#: ../../source/ref-changelog.md:862 msgid "" "New option to keep Ray running if Ray was already initialized in " "`start_simulation` ([#1177](https://github.com/adap/flower/pull/1177))" @@ -19116,7 +19878,7 @@ msgstr "" "initialisé dans `start_simulation` " "([#1177](https://github.com/adap/flower/pull/1177))" -#: ../../source/ref-changelog.md:771 +#: ../../source/ref-changelog.md:863 msgid "" "Add support for custom `ClientManager` as a `start_simulation` parameter " "([#1171](https://github.com/adap/flower/pull/1171))" @@ -19125,7 +19887,7 @@ msgstr "" "paramètre de `start_simulation` " "([#1171](https://github.com/adap/flower/pull/1171))" -#: ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:864 #, fuzzy msgid "" "New documentation for [implementing " @@ -19138,7 +19900,7 @@ msgstr "" "strategies.html) ([#1097](https://github.com/adap/flower/pull/1097), " "[#1175](https://github.com/adap/flower/pull/1175))" -#: ../../source/ref-changelog.md:773 +#: ../../source/ref-changelog.md:865 msgid "" "New mobile-friendly documentation theme " "([#1174](https://github.com/adap/flower/pull/1174))" @@ -19146,7 +19908,7 @@ msgstr "" "Nouveau thème de documentation adapté aux mobiles " "([#1174](https://github.com/adap/flower/pull/1174))" -#: ../../source/ref-changelog.md:774 +#: ../../source/ref-changelog.md:866 msgid "" "Limit version range for (optional) `ray` dependency to include only " "compatible releases (`>=1.9.2,<1.12.0`) " @@ -19156,7 +19918,7 @@ msgstr "" "n'inclure que les versions compatibles (`>=1.9.2,<1.12.0`) " "([#1205](https://github.com/adap/flower/pull/1205))" -#: ../../source/ref-changelog.md:778 +#: ../../source/ref-changelog.md:870 msgid "" "**Remove deprecated support for Python 3.6** " "([#871](https://github.com/adap/flower/pull/871))" @@ -19164,7 +19926,7 @@ msgstr "" "**Supprime la prise en charge obsolète de Python 3.6** " "([#871](https://github.com/adap/flower/pull/871))" -#: ../../source/ref-changelog.md:779 +#: ../../source/ref-changelog.md:871 msgid "" "**Remove deprecated KerasClient** " "([#857](https://github.com/adap/flower/pull/857))" @@ -19172,7 +19934,7 @@ msgstr "" "**Supprimez KerasClient** " "([#857](https://github.com/adap/flower/pull/857))" -#: ../../source/ref-changelog.md:780 +#: ../../source/ref-changelog.md:872 msgid "" "**Remove deprecated no-op extra installs** " "([#973](https://github.com/adap/flower/pull/973))" @@ -19180,7 +19942,7 @@ msgstr "" "**Supprimer les installations supplémentaires no-op dépréciées** " "([#973](https://github.com/adap/flower/pull/973))" -#: ../../source/ref-changelog.md:781 +#: ../../source/ref-changelog.md:873 msgid "" "**Remove deprecated proto fields from** `FitRes` **and** `EvaluateRes` " "([#869](https://github.com/adap/flower/pull/869))" @@ -19188,7 +19950,7 @@ msgstr "" "**Supprimez les champs proto obsolètes de** `FitRes` **et** `EvaluateRes`" " ([#869](https://github.com/adap/flower/pull/869))" -#: ../../source/ref-changelog.md:782 +#: ../../source/ref-changelog.md:874 msgid "" "**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** " "([#1107](https://github.com/adap/flower/pull/1107))" @@ -19196,7 +19958,7 @@ msgstr "" "**Supprime la stratégie QffedAvg (remplacée par QFedAvg)** " "([#1107](https://github.com/adap/flower/pull/1107))" -#: ../../source/ref-changelog.md:783 +#: ../../source/ref-changelog.md:875 msgid "" "**Remove deprecated DefaultStrategy strategy** " "([#1142](https://github.com/adap/flower/pull/1142))" @@ -19204,7 +19966,7 @@ msgstr "" "**Supprime la stratégie DefaultStrategy qui est obsolète** " "([#1142](https://github.com/adap/flower/pull/1142))" -#: ../../source/ref-changelog.md:784 +#: ../../source/ref-changelog.md:876 msgid "" "**Remove deprecated support for eval_fn accuracy return value** " "([#1142](https://github.com/adap/flower/pull/1142))" @@ -19212,7 +19974,7 @@ msgstr "" "**Supprimer la prise en charge obsolète de la valeur de retour de la " "précision eval_fn** ([#1142](https://github.com/adap/flower/pull/1142))" -#: ../../source/ref-changelog.md:785 +#: ../../source/ref-changelog.md:877 msgid "" "**Remove deprecated support for passing initial parameters as NumPy " "ndarrays** ([#1142](https://github.com/adap/flower/pull/1142))" @@ -19221,11 +19983,11 @@ msgstr "" " en tant que ndarrays NumPy** " "([#1142](https://github.com/adap/flower/pull/1142))" -#: ../../source/ref-changelog.md:787 +#: ../../source/ref-changelog.md:879 msgid "v0.18.0 (2022-02-28)" msgstr "v0.18.0 (2022-02-28)" -#: ../../source/ref-changelog.md:791 +#: ../../source/ref-changelog.md:883 msgid "" "**Improved Virtual Client Engine compatibility with Jupyter Notebook / " "Google Colab** ([#866](https://github.com/adap/flower/pull/866), " @@ -19240,7 +20002,7 @@ msgstr "" "[#833](https://github.com/adap/flower/pull/833), " "[#1036](https://github.com/adap/flower/pull/1036))" -#: ../../source/ref-changelog.md:793 +#: ../../source/ref-changelog.md:885 msgid "" "Simulations (using the Virtual Client Engine through `start_simulation`) " "now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " @@ -19252,7 +20014,7 @@ msgstr "" "Notebooks Jupyter (y compris Google Colab) après avoir installé Flower " "avec l'option `simulation` (`pip install flwr[simulation]`)." -#: ../../source/ref-changelog.md:795 +#: ../../source/ref-changelog.md:887 msgid "" "**New Jupyter Notebook code example** " "([#833](https://github.com/adap/flower/pull/833))" @@ -19260,7 +20022,7 @@ msgstr "" "**Nouvel exemple de code Jupyter Notebook** " "([#833](https://github.com/adap/flower/pull/833))" -#: ../../source/ref-changelog.md:797 +#: ../../source/ref-changelog.md:889 msgid "" "A new code example (`quickstart_simulation`) demonstrates Flower " "simulations using the Virtual Client Engine through Jupyter Notebook " @@ -19270,7 +20032,7 @@ msgstr "" "simulations de Flower en utilisant le moteur de client virtuel via " "Jupyter Notebook (y compris Google Colab)." -#: ../../source/ref-changelog.md:799 +#: ../../source/ref-changelog.md:891 msgid "" "**Client properties (feature preview)** " "([#795](https://github.com/adap/flower/pull/795))" @@ -19278,7 +20040,7 @@ msgstr "" "**Propriétés du client (aperçu des fonctionnalités)** " "([#795](https://github.com/adap/flower/pull/795))" -#: ../../source/ref-changelog.md:801 +#: ../../source/ref-changelog.md:893 msgid "" "Clients can implement a new method `get_properties` to enable server-side" " strategies to query client properties." @@ -19287,7 +20049,7 @@ msgstr "" "pour permettre aux stratégies côté serveur d'interroger les propriétés du" " client." -#: ../../source/ref-changelog.md:803 +#: ../../source/ref-changelog.md:895 msgid "" "**Experimental Android support with TFLite** " "([#865](https://github.com/adap/flower/pull/865))" @@ -19295,7 +20057,7 @@ msgstr "" "**Support expérimental d'Android avec TFLite** " "([#865](https://github.com/adap/flower/pull/865))" -#: ../../source/ref-changelog.md:805 +#: ../../source/ref-changelog.md:897 msgid "" "Android support has finally arrived in `main`! Flower is both client-" "agnostic and framework-agnostic by design. One can integrate arbitrary " @@ -19307,7 +20069,7 @@ msgstr "" "intégrer des plates-formes client arbitraires et avec cette version, " "l'utilisation de Flower sur Android est devenue beaucoup plus facile." -#: ../../source/ref-changelog.md:807 +#: ../../source/ref-changelog.md:899 msgid "" "The example uses TFLite on the client side, along with a new " "`FedAvgAndroid` strategy. The Android client and `FedAvgAndroid` are " @@ -19321,7 +20083,7 @@ msgstr "" "part entière et une implémentation unifiée de `FedAvg` intégrant la " "nouvelle fonctionnalité de `FedAvgAndroid`." -#: ../../source/ref-changelog.md:809 +#: ../../source/ref-changelog.md:901 msgid "" "**Make gRPC keepalive time user-configurable and decrease default " "keepalive time** ([#1069](https://github.com/adap/flower/pull/1069))" @@ -19330,7 +20092,7 @@ msgstr "" "diminuer le temps de garde par défaut** " "([#1069](https://github.com/adap/flower/pull/1069))" -#: ../../source/ref-changelog.md:811 +#: ../../source/ref-changelog.md:903 msgid "" "The default gRPC keepalive time has been reduced to increase the " "compatibility of Flower with more cloud environments (for example, " @@ -19343,7 +20105,7 @@ msgstr "" "de keepalive pour personnaliser la pile gRPC en fonction d'exigences " "spécifiques." -#: ../../source/ref-changelog.md:813 +#: ../../source/ref-changelog.md:905 msgid "" "**New differential privacy example using Opacus and PyTorch** " "([#805](https://github.com/adap/flower/pull/805))" @@ -19351,7 +20113,7 @@ msgstr "" "**Nouvel exemple de confidentialité différentielle utilisant Opacus et " "PyTorch** ([#805](https://github.com/adap/flower/pull/805))" -#: ../../source/ref-changelog.md:815 +#: ../../source/ref-changelog.md:907 msgid "" "A new code example (`opacus`) demonstrates differentially-private " "federated learning with Opacus, PyTorch, and Flower." @@ -19359,7 +20121,7 @@ msgstr "" "Un nouvel exemple de code (`opacus`) démontre l'apprentissage fédéré " "différentiellement privé avec Opacus, PyTorch et Flower." -#: ../../source/ref-changelog.md:817 +#: ../../source/ref-changelog.md:909 msgid "" "**New Hugging Face Transformers code example** " "([#863](https://github.com/adap/flower/pull/863))" @@ -19367,7 +20129,7 @@ msgstr "" "**Nouvel exemple de code pour les Transformers à visage embrassant** " "([#863](https://github.com/adap/flower/pull/863))" -#: ../../source/ref-changelog.md:819 +#: ../../source/ref-changelog.md:911 msgid "" "A new code example (`quickstart_huggingface`) demonstrates usage of " "Hugging Face Transformers with Flower." @@ -19375,7 +20137,7 @@ msgstr "" "Un nouvel exemple de code (`quickstart_huggingface`) démontre " "l'utilisation des transformateurs Hugging Face avec Flower." -#: ../../source/ref-changelog.md:821 +#: ../../source/ref-changelog.md:913 msgid "" "**New MLCube code example** " "([#779](https://github.com/adap/flower/pull/779), " @@ -19389,7 +20151,7 @@ msgstr "" "[#1065](https://github.com/adap/flower/pull/1065), " "[#1090](https://github.com/adap/flower/pull/1090))" -#: ../../source/ref-changelog.md:823 +#: ../../source/ref-changelog.md:915 msgid "" "A new code example (`quickstart_mlcube`) demonstrates usage of MLCube " "with Flower." @@ -19397,7 +20159,7 @@ msgstr "" "Un nouvel exemple de code (`quickstart_mlcube`) démontre l'utilisation de" " MLCube avec Flower." -#: ../../source/ref-changelog.md:825 +#: ../../source/ref-changelog.md:917 msgid "" "**SSL-enabled server and client** " "([#842](https://github.com/adap/flower/pull/842), " @@ -19414,7 +20176,7 @@ msgstr "" "[#993](https://github.com/adap/flower/pull/993), " "[#994](https://github.com/adap/flower/pull/994))" -#: ../../source/ref-changelog.md:827 +#: ../../source/ref-changelog.md:919 msgid "" "SSL enables secure encrypted connections between clients and servers. " "This release open-sources the Flower secure gRPC implementation to make " @@ -19425,7 +20187,7 @@ msgstr "" "l'implémentation gRPC sécurisée de Flower afin de rendre les canaux de " "communication cryptés accessibles à tous les utilisateurs de Flower." -#: ../../source/ref-changelog.md:829 +#: ../../source/ref-changelog.md:921 msgid "" "**Updated** `FedAdam` **and** `FedYogi` **strategies** " "([#885](https://github.com/adap/flower/pull/885), " @@ -19435,7 +20197,7 @@ msgstr "" "([#885](https://github.com/adap/flower/pull/885), " "[#895](https://github.com/adap/flower/pull/895))" -#: ../../source/ref-changelog.md:831 +#: ../../source/ref-changelog.md:923 msgid "" "`FedAdam` and `FedAdam` match the latest version of the Adaptive " "Federated Optimization paper." @@ -19443,7 +20205,7 @@ msgstr "" "`FedAdam` et `FedAdam` correspondent à la dernière version de l'article " "sur l'optimisation fédérée adaptative." -#: ../../source/ref-changelog.md:833 +#: ../../source/ref-changelog.md:925 msgid "" "**Initialize** `start_simulation` **with a list of client IDs** " "([#860](https://github.com/adap/flower/pull/860))" @@ -19451,7 +20213,7 @@ msgstr "" "**Initialise** `start_simulation` **avec une liste d'ID de clients** " "([#860](https://github.com/adap/flower/pull/860))" -#: ../../source/ref-changelog.md:835 +#: ../../source/ref-changelog.md:927 msgid "" "`start_simulation` can now be called with a list of client IDs " "(`clients_ids`, type: `List[str]`). Those IDs will be passed to the " @@ -19465,7 +20227,7 @@ msgstr "" "être initialisé, ce qui peut faciliter le chargement de partitions de " "données qui ne sont pas accessibles par des identifiants `int`." -#: ../../source/ref-changelog.md:839 +#: ../../source/ref-changelog.md:931 msgid "" "Update `num_examples` calculation in PyTorch code examples in " "([#909](https://github.com/adap/flower/pull/909))" @@ -19473,7 +20235,7 @@ msgstr "" "Mettre à jour le calcul de `num_examples` dans les exemples de code " "PyTorch dans ([#909](https://github.com/adap/flower/pull/909))" -#: ../../source/ref-changelog.md:840 +#: ../../source/ref-changelog.md:932 msgid "" "Expose Flower version through `flwr.__version__` " "([#952](https://github.com/adap/flower/pull/952))" @@ -19481,7 +20243,7 @@ msgstr "" "Exposer la version de Flower à travers `flwr.__version__` " "([#952](https://github.com/adap/flower/pull/952))" -#: ../../source/ref-changelog.md:841 +#: ../../source/ref-changelog.md:933 msgid "" "`start_server` in `app.py` now returns a `History` object containing " "metrics from training ([#974](https://github.com/adap/flower/pull/974))" @@ -19490,7 +20252,7 @@ msgstr "" "contenant les métriques de l'entraînement " "([#974](https://github.com/adap/flower/pull/974))" -#: ../../source/ref-changelog.md:842 +#: ../../source/ref-changelog.md:934 msgid "" "Make `max_workers` (used by `ThreadPoolExecutor`) configurable " "([#978](https://github.com/adap/flower/pull/978))" @@ -19498,7 +20260,7 @@ msgstr "" "Rendre `max_workers` (utilisé par `ThreadPoolExecutor`) configurable " "([#978](https://github.com/adap/flower/pull/978))" -#: ../../source/ref-changelog.md:843 +#: ../../source/ref-changelog.md:935 msgid "" "Increase sleep time after server start to three seconds in all code " "examples ([#1086](https://github.com/adap/flower/pull/1086))" @@ -19507,7 +20269,7 @@ msgstr "" "secondes dans tous les exemples de code " "([#1086](https://github.com/adap/flower/pull/1086))" -#: ../../source/ref-changelog.md:844 +#: ../../source/ref-changelog.md:936 msgid "" "Added a new FAQ section to the documentation " "([#948](https://github.com/adap/flower/pull/948))" @@ -19515,7 +20277,7 @@ msgstr "" "Ajout d'une nouvelle section FAQ à la documentation " "([#948](https://github.com/adap/flower/pull/948))" -#: ../../source/ref-changelog.md:845 +#: ../../source/ref-changelog.md:937 msgid "" "And many more under-the-hood changes, library updates, documentation " "changes, and tooling improvements!" @@ -19524,7 +20286,7 @@ msgstr "" "bibliothèque, des modifications de la documentation et des améliorations " "de l'outillage !" -#: ../../source/ref-changelog.md:849 +#: ../../source/ref-changelog.md:941 msgid "" "**Removed** `flwr_example` **and** `flwr_experimental` **from release " "build** ([#869](https://github.com/adap/flower/pull/869))" @@ -19532,7 +20294,7 @@ msgstr "" "**Supprimé** `flwr_example` **et** `flwr_experimental` **de la version " "release build** ([#869](https://github.com/adap/flower/pull/869))" -#: ../../source/ref-changelog.md:851 +#: ../../source/ref-changelog.md:943 msgid "" "The packages `flwr_example` and `flwr_experimental` have been deprecated " "since Flower 0.12.0 and they are not longer included in Flower release " @@ -19546,11 +20308,11 @@ msgstr "" "tensorflow`, `http-logger`, `ops`) sont maintenant no-op et seront " "supprimés dans une prochaine version." -#: ../../source/ref-changelog.md:853 +#: ../../source/ref-changelog.md:945 msgid "v0.17.0 (2021-09-24)" msgstr "v0.17.0 (2021-09-24)" -#: ../../source/ref-changelog.md:857 +#: ../../source/ref-changelog.md:949 msgid "" "**Experimental virtual client engine** " "([#781](https://github.com/adap/flower/pull/781) " @@ -19562,7 +20324,7 @@ msgstr "" "[#790](https://github.com/adap/flower/pull/790) " "[#791](https://github.com/adap/flower/pull/791))" -#: ../../source/ref-changelog.md:859 +#: ../../source/ref-changelog.md:951 msgid "" "One of Flower's goals is to enable research at scale. This release " "enables a first (experimental) peek at a major new feature, codenamed the" @@ -19580,7 +20342,7 @@ msgstr "" "fonctionnalité est de regarder les deux nouveaux exemples de code appelés" " `quickstart_simulation` et `simulation_pytorch`." -#: ../../source/ref-changelog.md:861 +#: ../../source/ref-changelog.md:953 msgid "" "The feature is still experimental, so there's no stability guarantee for " "the API. It's also not quite ready for prime time and comes with a few " @@ -19593,7 +20355,7 @@ msgstr "" " les personnes curieuses sont encouragées à l'essayer et à faire part de " "leurs réflexions." -#: ../../source/ref-changelog.md:863 +#: ../../source/ref-changelog.md:955 msgid "" "**New built-in strategies** " "([#828](https://github.com/adap/flower/pull/828) " @@ -19603,7 +20365,7 @@ msgstr "" "([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" -#: ../../source/ref-changelog.md:865 +#: ../../source/ref-changelog.md:957 msgid "" "FedYogi - Federated learning strategy using Yogi on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" @@ -19611,7 +20373,7 @@ msgstr "" "FedYogi - Stratégie d'apprentissage fédéré utilisant Yogi côté serveur. " "Mise en oeuvre basée sur https://arxiv.org/abs/2003.00295" -#: ../../source/ref-changelog.md:866 +#: ../../source/ref-changelog.md:958 msgid "" "FedAdam - Federated learning strategy using Adam on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" @@ -19619,7 +20381,7 @@ msgstr "" "FedAdam - Stratégie d'apprentissage fédéré utilisant Adam côté serveur. " "Mise en œuvre basée sur https://arxiv.org/abs/2003.00295" -#: ../../source/ref-changelog.md:868 +#: ../../source/ref-changelog.md:960 msgid "" "**New PyTorch Lightning code example** " "([#617](https://github.com/adap/flower/pull/617))" @@ -19627,7 +20389,7 @@ msgstr "" "**Nouvel exemple de code PyTorch Lightning** " "([#617](https://github.com/adap/flower/pull/617))" -#: ../../source/ref-changelog.md:870 +#: ../../source/ref-changelog.md:962 msgid "" "**New Variational Auto-Encoder code example** " "([#752](https://github.com/adap/flower/pull/752))" @@ -19635,7 +20397,7 @@ msgstr "" "**Nouvel exemple de code d'autocodage variationnel** " "([#752](https://github.com/adap/flower/pull/752))" -#: ../../source/ref-changelog.md:872 +#: ../../source/ref-changelog.md:964 msgid "" "**New scikit-learn code example** " "([#748](https://github.com/adap/flower/pull/748))" @@ -19643,7 +20405,7 @@ msgstr "" "**Nouvel exemple de code scikit-learn** " "([#748](https://github.com/adap/flower/pull/748))" -#: ../../source/ref-changelog.md:874 +#: ../../source/ref-changelog.md:966 msgid "" "**New experimental TensorBoard strategy** " "([#789](https://github.com/adap/flower/pull/789))" @@ -19651,7 +20413,7 @@ msgstr "" "**Nouvelle stratégie expérimentale TensorBoard** " "([#789](https://github.com/adap/flower/pull/789))" -#: ../../source/ref-changelog.md:878 +#: ../../source/ref-changelog.md:970 msgid "" "Improved advanced TensorFlow code example " "([#769](https://github.com/adap/flower/pull/769))" @@ -19659,7 +20421,7 @@ msgstr "" "Amélioration de l'exemple de code TensorFlow avancé " "([#769](https://github.com/adap/flower/pull/769))" -#: ../../source/ref-changelog.md:879 +#: ../../source/ref-changelog.md:971 msgid "" "Warning when `min_available_clients` is misconfigured " "([#830](https://github.com/adap/flower/pull/830))" @@ -19667,7 +20429,7 @@ msgstr "" "Avertissement lorsque `min_available_clients` est mal configuré " "([#830](https://github.com/adap/flower/pull/830))" -#: ../../source/ref-changelog.md:880 +#: ../../source/ref-changelog.md:972 msgid "" "Improved gRPC server docs " "([#841](https://github.com/adap/flower/pull/841))" @@ -19675,7 +20437,7 @@ msgstr "" "Amélioration de la documentation sur le serveur gRPC " "([#841](https://github.com/adap/flower/pull/841))" -#: ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:973 msgid "" "Improved error message in `NumPyClient` " "([#851](https://github.com/adap/flower/pull/851))" @@ -19683,7 +20445,7 @@ msgstr "" "Amélioration du message d'erreur dans `NumPyClient` " "([#851](https://github.com/adap/flower/pull/851))" -#: ../../source/ref-changelog.md:882 +#: ../../source/ref-changelog.md:974 msgid "" "Improved PyTorch quickstart code example " "([#852](https://github.com/adap/flower/pull/852))" @@ -19691,7 +20453,7 @@ msgstr "" "Exemple de code de démarrage rapide PyTorch amélioré " "([#852](https://github.com/adap/flower/pull/852))" -#: ../../source/ref-changelog.md:886 +#: ../../source/ref-changelog.md:978 msgid "" "**Disabled final distributed evaluation** " "([#800](https://github.com/adap/flower/pull/800))" @@ -19699,7 +20461,7 @@ msgstr "" "**Désactivé l'évaluation finale distribuée** " "([#800](https://github.com/adap/flower/pull/800))" -#: ../../source/ref-changelog.md:888 +#: ../../source/ref-changelog.md:980 msgid "" "Prior behaviour was to perform a final round of distributed evaluation on" " all connected clients, which is often not required (e.g., when using " @@ -19712,7 +20474,7 @@ msgstr "" "l'évaluation côté serveur). Le comportement précédent peut être activé en" " passant `force_final_distributed_eval=True` à `start_server`." -#: ../../source/ref-changelog.md:890 +#: ../../source/ref-changelog.md:982 msgid "" "**Renamed q-FedAvg strategy** " "([#802](https://github.com/adap/flower/pull/802))" @@ -19720,7 +20482,7 @@ msgstr "" "**Renommé stratégie q-FedAvg** " "([#802](https://github.com/adap/flower/pull/802))" -#: ../../source/ref-changelog.md:892 +#: ../../source/ref-changelog.md:984 msgid "" "The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect " "the notation given in the original paper (q-FFL is the optimization " @@ -19735,7 +20497,7 @@ msgstr "" "des raisons de compatibilité (elle sera supprimée dans une prochaine " "version)." -#: ../../source/ref-changelog.md:894 +#: ../../source/ref-changelog.md:986 msgid "" "**Deprecated and renamed code example** `simulation_pytorch` **to** " "`simulation_pytorch_legacy` " @@ -19745,7 +20507,7 @@ msgstr "" "`simulation_pytorch_legacy` " "([#791](https://github.com/adap/flower/pull/791))" -#: ../../source/ref-changelog.md:896 +#: ../../source/ref-changelog.md:988 msgid "" "This example has been replaced by a new example. The new example is based" " on the experimental virtual client engine, which will become the new " @@ -19760,11 +20522,11 @@ msgstr "" "conservé à des fins de référence, mais il pourrait être supprimé à " "l'avenir." -#: ../../source/ref-changelog.md:898 +#: ../../source/ref-changelog.md:990 msgid "v0.16.0 (2021-05-11)" msgstr "v0.16.0 (2021-05-11)" -#: ../../source/ref-changelog.md:902 +#: ../../source/ref-changelog.md:994 msgid "" "**New built-in strategies** " "([#549](https://github.com/adap/flower/pull/549))" @@ -19772,11 +20534,11 @@ msgstr "" "**Nouvelles stratégies intégrées** " "([#549](https://github.com/adap/flower/pull/549))" -#: ../../source/ref-changelog.md:904 +#: ../../source/ref-changelog.md:996 msgid "(abstract) FedOpt" msgstr "(résumé) FedOpt" -#: ../../source/ref-changelog.md:907 +#: ../../source/ref-changelog.md:999 msgid "" "**Custom metrics for server and strategies** " "([#717](https://github.com/adap/flower/pull/717))" @@ -19784,7 +20546,7 @@ msgstr "" "**Métriques personnalisées pour le serveur et les stratégies** " "([#717](https://github.com/adap/flower/pull/717))" -#: ../../source/ref-changelog.md:909 +#: ../../source/ref-changelog.md:1001 msgid "" "The Flower server is now fully task-agnostic, all remaining instances of " "task-specific metrics (such as `accuracy`) have been replaced by custom " @@ -19800,7 +20562,7 @@ msgstr "" " À partir de cette version, les métriques personnalisées remplacent les " "métriques spécifiques à une tâche sur le serveur." -#: ../../source/ref-changelog.md:911 +#: ../../source/ref-changelog.md:1003 #, fuzzy msgid "" "Custom metric dictionaries are now used in two user-facing APIs: they are" @@ -19818,7 +20580,7 @@ msgstr "" "stratégies peuvent même renvoyer des dictionnaires de métriques " "*agrégées* pour que le serveur puisse en garder la trace." -#: ../../source/ref-changelog.md:913 +#: ../../source/ref-changelog.md:1005 #, fuzzy msgid "" "Strategy implementations should migrate their `aggregate_fit` and " @@ -19832,7 +20594,7 @@ msgstr "" "d'évaluation côté serveur doivent migrer de `return loss, accuracy` à " "`return loss, {\"accuracy\" : accuracy}`." -#: ../../source/ref-changelog.md:915 +#: ../../source/ref-changelog.md:1007 msgid "" "Flower 0.15-style return types are deprecated (but still supported), " "compatibility will be removed in a future release." @@ -19841,7 +20603,7 @@ msgstr "" "pris en charge), la compatibilité sera supprimée dans une prochaine " "version." -#: ../../source/ref-changelog.md:917 +#: ../../source/ref-changelog.md:1009 msgid "" "**Migration warnings for deprecated functionality** " "([#690](https://github.com/adap/flower/pull/690))" @@ -19849,7 +20611,7 @@ msgstr "" "**Avertissements de migration pour les fonctionnalités obsolètes** " "([#690](https://github.com/adap/flower/pull/690))" -#: ../../source/ref-changelog.md:919 +#: ../../source/ref-changelog.md:1011 msgid "" "Earlier versions of Flower were often migrated to new APIs, while " "maintaining compatibility with legacy APIs. This release introduces " @@ -19865,7 +20627,7 @@ msgstr "" "vers des API plus récentes, facilitant ainsi la transition d'une version " "à l'autre." -#: ../../source/ref-changelog.md:921 +#: ../../source/ref-changelog.md:1013 msgid "" "Improved docs and docstrings " "([#691](https://github.com/adap/flower/pull/691) " @@ -19877,11 +20639,11 @@ msgstr "" "[#692](https://github.com/adap/flower/pull/692) " "[#713](https://github.com/adap/flower/pull/713))" -#: ../../source/ref-changelog.md:923 +#: ../../source/ref-changelog.md:1015 msgid "MXNet example and documentation" msgstr "Exemple et documentation MXNet" -#: ../../source/ref-changelog.md:925 +#: ../../source/ref-changelog.md:1017 msgid "" "FedBN implementation in example PyTorch: From Centralized To Federated " "([#696](https://github.com/adap/flower/pull/696) " @@ -19893,7 +20655,7 @@ msgstr "" "[#702](https://github.com/adap/flower/pull/702) " "[#705](https://github.com/adap/flower/pull/705))" -#: ../../source/ref-changelog.md:929 +#: ../../source/ref-changelog.md:1021 msgid "" "**Serialization-agnostic server** " "([#721](https://github.com/adap/flower/pull/721))" @@ -19901,7 +20663,7 @@ msgstr "" "**Serveur agnostique de sérialisation** " "([#721](https://github.com/adap/flower/pull/721))" -#: ../../source/ref-changelog.md:931 +#: ../../source/ref-changelog.md:1023 msgid "" "The Flower server is now fully serialization-agnostic. Prior usage of " "class `Weights` (which represents parameters as deserialized NumPy " @@ -19921,7 +20683,7 @@ msgstr "" "d'octets doivent être interprétés (par exemple, pour la " "sérialisation/désérialisation)." -#: ../../source/ref-changelog.md:933 +#: ../../source/ref-changelog.md:1025 msgid "" "Built-in strategies implement this approach by handling serialization and" " deserialization to/from `Weights` internally. Custom/3rd-party Strategy " @@ -19938,7 +20700,7 @@ msgstr "" "[#721](https://github.com/adap/flower/pull/721) pour voir comment les " "stratégies peuvent facilement migrer vers le nouveau format." -#: ../../source/ref-changelog.md:935 +#: ../../source/ref-changelog.md:1027 msgid "" "Deprecated `flwr.server.Server.evaluate`, use " "`flwr.server.Server.evaluate_round` instead " @@ -19948,11 +20710,11 @@ msgstr "" "`flwr.server.Server.evaluate_round` à la place " "([#717](https://github.com/adap/flower/pull/717))" -#: ../../source/ref-changelog.md:937 +#: ../../source/ref-changelog.md:1029 msgid "v0.15.0 (2021-03-12)" msgstr "v0.15.0 (2021-03-12)" -#: ../../source/ref-changelog.md:941 +#: ../../source/ref-changelog.md:1033 msgid "" "**Server-side parameter initialization** " "([#658](https://github.com/adap/flower/pull/658))" @@ -19960,7 +20722,7 @@ msgstr "" "**Initialisation des paramètres côté serveur** " "([#658](https://github.com/adap/flower/pull/658))" -#: ../../source/ref-changelog.md:943 +#: ../../source/ref-changelog.md:1035 msgid "" "Model parameters can now be initialized on the server-side. Server-side " "parameter initialization works via a new `Strategy` method called " @@ -19970,7 +20732,7 @@ msgstr "" "serveur. L'initialisation des paramètres côté serveur fonctionne via une " "nouvelle méthode `Strategy` appelée `initialize_parameters`." -#: ../../source/ref-changelog.md:945 +#: ../../source/ref-changelog.md:1037 msgid "" "Built-in strategies support a new constructor argument called " "`initial_parameters` to set the initial parameters. Built-in strategies " @@ -19982,7 +20744,7 @@ msgstr "" "initiaux. Les stratégies intégrées fourniront ces paramètres initiaux au " "serveur au démarrage et les supprimeront ensuite pour libérer la mémoire." -#: ../../source/ref-changelog.md:964 +#: ../../source/ref-changelog.md:1056 msgid "" "If no initial parameters are provided to the strategy, the server will " "continue to use the current behaviour (namely, it will ask one of the " @@ -19994,11 +20756,7 @@ msgstr "" "l'un des clients connectés ses paramètres et les utilisera comme " "paramètres globaux initiaux)." -#: ../../source/ref-changelog.md:966 -msgid "Deprecations" -msgstr "Dépréciations" - -#: ../../source/ref-changelog.md:968 +#: ../../source/ref-changelog.md:1060 msgid "" "Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to " "`flwr.server.strategy.FedAvg`, which is equivalent)" @@ -20006,11 +20764,11 @@ msgstr "" "Déclasser `flwr.server.strategy.DefaultStrategy` (migrer vers " "`flwr.server.strategy.FedAvg`, qui est équivalent)" -#: ../../source/ref-changelog.md:970 +#: ../../source/ref-changelog.md:1062 msgid "v0.14.0 (2021-02-18)" msgstr "v0.14.0 (2021-02-18)" -#: ../../source/ref-changelog.md:974 +#: ../../source/ref-changelog.md:1066 msgid "" "**Generalized** `Client.fit` **and** `Client.evaluate` **return values** " "([#610](https://github.com/adap/flower/pull/610) " @@ -20022,7 +20780,7 @@ msgstr "" "[#572](https://github.com/adap/flower/pull/572) " "[#633](https://github.com/adap/flower/pull/633))" -#: ../../source/ref-changelog.md:976 +#: ../../source/ref-changelog.md:1068 msgid "" "Clients can now return an additional dictionary mapping `str` keys to " "values of the following types: `bool`, `bytes`, `float`, `int`, `str`. " @@ -20035,7 +20793,7 @@ msgstr "" "valeurs presque arbitraires de `fit`/`evaluate` et les utiliser du côté " "du serveur !" -#: ../../source/ref-changelog.md:978 +#: ../../source/ref-changelog.md:1070 msgid "" "This improvement also allowed for more consistent return types between " "`fit` and `evaluate`: `evaluate` should now return a tuple `(float, int, " @@ -20048,7 +20806,7 @@ msgstr "" "d'exemples, et un dictionnaire contenant des valeurs arbitraires " "spécifiques au problème comme la précision." -#: ../../source/ref-changelog.md:980 +#: ../../source/ref-changelog.md:1072 msgid "" "In case you wondered: this feature is compatible with existing projects, " "the additional dictionary return value is optional. New code should " @@ -20065,7 +20823,7 @@ msgstr "" "Scalar]`, `evaluate` : `float, int, Dict[str, Scalar]`). Voir l'exemple " "ci-dessous pour plus de détails." -#: ../../source/ref-changelog.md:982 +#: ../../source/ref-changelog.md:1074 msgid "" "*Code example:* note the additional dictionary return values in both " "`FlwrClient.fit` and `FlwrClient.evaluate`:" @@ -20073,7 +20831,7 @@ msgstr "" "*Exemple de code:* note les valeurs de retour du dictionnaire " "supplémentaires dans `FlwrClient.fit` et `FlwrClient.evaluate` :" -#: ../../source/ref-changelog.md:997 +#: ../../source/ref-changelog.md:1089 msgid "" "**Generalized** `config` **argument in** `Client.fit` **and** " "`Client.evaluate` ([#595](https://github.com/adap/flower/pull/595))" @@ -20081,7 +20839,7 @@ msgstr "" "**Généralisé** `config` **argument dans** `Client.fit` **et** " "`Client.evaluate` ([#595](https://github.com/adap/flower/pull/595))" -#: ../../source/ref-changelog.md:999 +#: ../../source/ref-changelog.md:1091 msgid "" "The `config` argument used to be of type `Dict[str, str]`, which means " "that dictionary values were expected to be strings. The new release " @@ -20093,7 +20851,7 @@ msgstr "" "nouvelle version généralise cela pour permettre les valeurs des types " "suivants : `bool`, `bytes`, `float`, `int`, `str`." -#: ../../source/ref-changelog.md:1001 +#: ../../source/ref-changelog.md:1093 msgid "" "This means one can now pass almost arbitrary values to `fit`/`evaluate` " "using the `config` dictionary. Yay, no more `str(epochs)` on the server-" @@ -20104,7 +20862,7 @@ msgstr "" "Yay, plus de `str(epochs)` du côté serveur et `int(config[\"epochs\"])` " "du côté client !" -#: ../../source/ref-changelog.md:1003 +#: ../../source/ref-changelog.md:1095 msgid "" "*Code example:* note that the `config` dictionary now contains non-`str` " "values in both `Client.fit` and `Client.evaluate`:" @@ -20112,11 +20870,11 @@ msgstr "" "*Exemple de code:* Notez que le dictionnaire `config` contient maintenant" " des valeurs autres que `str` dans `Client.fit` et `Client.evaluate` :" -#: ../../source/ref-changelog.md:1020 +#: ../../source/ref-changelog.md:1112 msgid "v0.13.0 (2021-01-08)" msgstr "v0.13.0 (2021-01-08)" -#: ../../source/ref-changelog.md:1024 +#: ../../source/ref-changelog.md:1116 msgid "" "New example: PyTorch From Centralized To Federated " "([#549](https://github.com/adap/flower/pull/549))" @@ -20124,21 +20882,21 @@ msgstr "" "Nouvel exemple : PyTorch de centralisé à fédéré " "([#549](https://github.com/adap/flower/pull/549))" -#: ../../source/ref-changelog.md:1025 +#: ../../source/ref-changelog.md:1117 msgid "Improved documentation" msgstr "Amélioration de la documentation" -#: ../../source/ref-changelog.md:1026 +#: ../../source/ref-changelog.md:1118 msgid "New documentation theme ([#551](https://github.com/adap/flower/pull/551))" msgstr "" "Nouveau thème de documentation " "([#551](https://github.com/adap/flower/pull/551))" -#: ../../source/ref-changelog.md:1027 +#: ../../source/ref-changelog.md:1119 msgid "New API reference ([#554](https://github.com/adap/flower/pull/554))" msgstr "Nouvelle référence API ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:1028 +#: ../../source/ref-changelog.md:1120 msgid "" "Updated examples documentation " "([#549](https://github.com/adap/flower/pull/549))" @@ -20146,7 +20904,7 @@ msgstr "" "Mise à jour de la documentation des exemples " "([#549](https://github.com/adap/flower/pull/549))" -#: ../../source/ref-changelog.md:1029 +#: ../../source/ref-changelog.md:1121 msgid "" "Removed obsolete documentation " "([#548](https://github.com/adap/flower/pull/548))" @@ -20154,11 +20912,11 @@ msgstr "" "Suppression de la documentation obsolète " "([#548](https://github.com/adap/flower/pull/548))" -#: ../../source/ref-changelog.md:1031 +#: ../../source/ref-changelog.md:1123 msgid "Bugfix:" msgstr "Correction de bogues :" -#: ../../source/ref-changelog.md:1033 +#: ../../source/ref-changelog.md:1125 msgid "" "`Server.fit` does not disconnect clients when finished, disconnecting the" " clients is now handled in `flwr.server.start_server` " @@ -20171,15 +20929,15 @@ msgstr "" "([#553](https://github.com/adap/flower/pull/553) " "[#540](https://github.com/adap/flower/issues/540))." -#: ../../source/ref-changelog.md:1035 +#: ../../source/ref-changelog.md:1127 msgid "v0.12.0 (2020-12-07)" msgstr "v0.12.0 (2020-12-07)" -#: ../../source/ref-changelog.md:1037 ../../source/ref-changelog.md:1053 +#: ../../source/ref-changelog.md:1129 ../../source/ref-changelog.md:1145 msgid "Important changes:" msgstr "Changements importants :" -#: ../../source/ref-changelog.md:1039 +#: ../../source/ref-changelog.md:1131 msgid "" "Added an example for embedded devices " "([#507](https://github.com/adap/flower/pull/507))" @@ -20187,7 +20945,7 @@ msgstr "" "Ajout d'un exemple pour les périphériques embarqués " "([#507](https://github.com/adap/flower/pull/507))" -#: ../../source/ref-changelog.md:1040 +#: ../../source/ref-changelog.md:1132 msgid "" "Added a new NumPyClient (in addition to the existing KerasClient) " "([#504](https://github.com/adap/flower/pull/504) " @@ -20197,7 +20955,7 @@ msgstr "" "([#504](https://github.com/adap/flower/pull/504) " "[#508](https://github.com/adap/flower/pull/508))" -#: ../../source/ref-changelog.md:1041 +#: ../../source/ref-changelog.md:1133 msgid "" "Deprecated `flwr_example` package and started to migrate examples into " "the top-level `examples` directory " @@ -20209,15 +20967,15 @@ msgstr "" "([#494](https://github.com/adap/flower/pull/494) " "[#512](https://github.com/adap/flower/pull/512))" -#: ../../source/ref-changelog.md:1043 +#: ../../source/ref-changelog.md:1135 msgid "v0.11.0 (2020-11-30)" msgstr "v0.11.0 (2020-11-30)" -#: ../../source/ref-changelog.md:1045 +#: ../../source/ref-changelog.md:1137 msgid "Incompatible changes:" msgstr "Changements incompatibles :" -#: ../../source/ref-changelog.md:1047 +#: ../../source/ref-changelog.md:1139 msgid "" "Renamed strategy methods " "([#486](https://github.com/adap/flower/pull/486)) to unify the naming of " @@ -20234,23 +20992,23 @@ msgstr "" "quatre méthodes de Stratégie. Pour migrer, renommez les méthodes de " "`Strategy` suivantes en conséquence :" -#: ../../source/ref-changelog.md:1048 +#: ../../source/ref-changelog.md:1140 msgid "`on_configure_evaluate` => `configure_evaluate`" msgstr "`on_configure_evaluate` => `configure_evaluate`" -#: ../../source/ref-changelog.md:1049 +#: ../../source/ref-changelog.md:1141 msgid "`on_aggregate_evaluate` => `aggregate_evaluate`" msgstr "`on_aggregate_evaluate` => `aggregate_evaluate`" -#: ../../source/ref-changelog.md:1050 +#: ../../source/ref-changelog.md:1142 msgid "`on_configure_fit` => `configure_fit`" msgstr "`on_configure_fit` => `configure_fit`" -#: ../../source/ref-changelog.md:1051 +#: ../../source/ref-changelog.md:1143 msgid "`on_aggregate_fit` => `aggregate_fit`" msgstr "`on_aggregate_fit` => `aggregate_fit`" -#: ../../source/ref-changelog.md:1055 +#: ../../source/ref-changelog.md:1147 msgid "" "Deprecated `DefaultStrategy` " "([#479](https://github.com/adap/flower/pull/479)). To migrate use " @@ -20260,7 +21018,7 @@ msgstr "" "([#479](https://github.com/adap/flower/pull/479)). Pour migrer, utilisez " "`FedAvg` à la place." -#: ../../source/ref-changelog.md:1056 +#: ../../source/ref-changelog.md:1148 msgid "" "Simplified examples and baselines " "([#484](https://github.com/adap/flower/pull/484))." @@ -20268,7 +21026,7 @@ msgstr "" "Exemples simplifiés et lignes de base " "([#484](https://github.com/adap/flower/pull/484))." -#: ../../source/ref-changelog.md:1057 +#: ../../source/ref-changelog.md:1149 msgid "" "Removed presently unused `on_conclude_round` from strategy interface " "([#483](https://github.com/adap/flower/pull/483))." @@ -20276,7 +21034,7 @@ msgstr "" "Suppression de `on_conclude_round` actuellement inutilisé de l'interface " "de stratégie ([#483](https://github.com/adap/flower/pull/483))." -#: ../../source/ref-changelog.md:1058 +#: ../../source/ref-changelog.md:1150 msgid "" "Set minimal Python version to 3.6.1 instead of 3.6.9 " "([#471](https://github.com/adap/flower/pull/471))." @@ -20284,7 +21042,7 @@ msgstr "" "Fixe la version minimale de Python à 3.6.1 au lieu de 3.6.9 " "([#471](https://github.com/adap/flower/pull/471))." -#: ../../source/ref-changelog.md:1059 +#: ../../source/ref-changelog.md:1151 msgid "" "Improved `Strategy` docstrings " "([#470](https://github.com/adap/flower/pull/470))." @@ -24495,7 +25253,7 @@ msgstr "" "chose d'autre, comme la régression linéaire classique." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" +msgid "|93b02017c78049bbbd5ae456dcb2c91b|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 @@ -24514,7 +25272,7 @@ msgstr "" " Go." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -msgid "|5aa1711387d74d0f8b9c499e1a51627e|" +msgid "|01471150fd5144c080a176b43e92a3ff|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 @@ -24545,7 +25303,7 @@ msgstr "" "chanson." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -msgid "|2bc8e069228d4873804061ff4a95048c|" +msgid "|9bc21c7dbd17444a8f070c60786e3484|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 @@ -24566,7 +25324,7 @@ msgstr "" " données pour la même tâche." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -msgid "|c258488766324dc9a6807f0e7c4fd5f4|" +msgid "|3047bbce54b34099ae559963d0420d79|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 @@ -24587,7 +25345,7 @@ msgstr "" "cloud." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -msgid "|d5f962c3f4ec48529efda980868c14b0|" +msgid "|e9f8ce948593444fb838d2f354c7ec5d|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 @@ -24608,7 +25366,7 @@ msgstr "" "appuyés." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -msgid "|a5eccea18d4c43a68b54b65043cabef8|" +msgid "|c24c1478b30e4f74839208628a842d1e|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 @@ -24633,7 +25391,7 @@ msgstr "" " sur un serveur centralisé." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" +msgid "|1b3613d7a58847b59e1d3180802dbc09|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 @@ -24652,7 +25410,7 @@ msgstr "" "suffisantes pour former un bon modèle." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -msgid "|241fc906441a4f038c625a19d30d01b2|" +msgid "|9980b5213db547d0b8024a50992b9e3f|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 @@ -24874,7 +25632,7 @@ msgstr "" "partir d'un point de contrôle précédemment sauvegardé." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -msgid "|0aa5aa05810b44b6a835cecce28f3137|" +msgid "|c7afb4c92d154bfaa5e8cb9a150e17f1|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 @@ -24909,7 +25667,7 @@ msgstr "" "rendements décroissants." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -msgid "|c742940dd4bf4de09d8d0d5e8d179638|" +msgid "|032eb6fed6924ac387b9f13854919196|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 @@ -24942,7 +25700,7 @@ msgstr "" "données locales, ou même de quelques étapes (mini-batchs)." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -msgid "|1f169ab4601a47e1a226f1628f4ebddb|" +msgid "|fbf225add7fd4df5a9bf25a95597d954|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 @@ -24973,7 +25731,7 @@ msgstr "" " l'entraînement local." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" +msgid "|7efbe3d29d8349b89594e8947e910525|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 @@ -25032,7 +25790,7 @@ msgstr "" "times as much as each of the 100 examples." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -msgid "|72939caf6e294b0986fee6dde96614d7|" +msgid "|329fb3c04c744eda83bb51fa444c2266|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 @@ -25175,7 +25933,7 @@ msgstr "" "quel cadre de ML et n'importe quel langage de programmation." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +msgid "|c00bf2750bc24d229737a0fe1395f0fc|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 @@ -31213,3 +31971,414 @@ msgstr "" #~ msgid "|ff726bc5505e432388ee2fdd6ef420b9|" #~ msgstr "" +#~ msgid "" +#~ "Flower provides pre-made docker images" +#~ " on `Docker Hub `_" +#~ " that include all necessary dependencies" +#~ " for running the SuperLink. You can" +#~ " also build your own custom docker" +#~ " images from scratch with a different" +#~ " version of Python or Ubuntu if " +#~ "that is what you need. In this " +#~ "guide, we will explain what images " +#~ "exist and how to build them " +#~ "locally." +#~ msgstr "" + +#~ msgid "" +#~ "Currently, Flower provides two images, a" +#~ " ``base`` image and a ``superlink`` " +#~ "image. The base image, as the name" +#~ " suggests, contains basic dependencies that" +#~ " the SuperLink needs. This includes " +#~ "system dependencies, Python and Python " +#~ "tools. The SuperLink image is based " +#~ "on the base image, but it " +#~ "additionally installs the SuperLink using " +#~ "``pip``." +#~ msgstr "" + +#~ msgid "" +#~ "Both, base and SuperLink image are " +#~ "configured via build arguments. Through " +#~ "build arguments, we can make our " +#~ "build more flexible. For example, in " +#~ "the base image, we can specify the" +#~ " version of Python to install using" +#~ " the ``PYTHON_VERSION`` build argument. " +#~ "Some of the build arguments have " +#~ "default values, others must be specified" +#~ " when building the image. All " +#~ "available build arguments for each image" +#~ " are listed in one of the " +#~ "tables below." +#~ msgstr "" + +#~ msgid "``3.11``" +#~ msgstr "1.0.0rc1" + +#~ msgid "``UBUNTU_VERSION``" +#~ msgstr "" + +#~ msgid "Version of the official Ubuntu Docker image." +#~ msgstr "" + +#~ msgid "Defaults to ``22.04``." +#~ msgstr "" + +#~ msgid "" +#~ "The following example creates a base " +#~ "image with Python 3.11.0, pip 23.0.1 " +#~ "and setuptools 69.0.2:" +#~ msgstr "" + +#~ msgid "Building the SuperLink image" +#~ msgstr "Démarrer le serveur" + +#~ msgid "Defaults to ``flwr/base``." +#~ msgstr "" + +#~ msgid "The Python version of the base image." +#~ msgstr "Évaluer la réponse d'un client." + +#~ msgid "Defaults to ``py3.11``." +#~ msgstr "" + +#~ msgid "Defaults to ``ubuntu22.04``." +#~ msgstr "" + +#~ msgid "The PyPI package to install." +#~ msgstr "" + +#~ msgid "Defaults to ``flwr``." +#~ msgstr "Flux de travail" + +#~ msgid "" +#~ "The following example creates a " +#~ "SuperLink image with the official Flower" +#~ " base image py3.11-ubuntu22.04 and Flower" +#~ " 1.8.0:" +#~ msgstr "" + +#~ msgid "" +#~ "The name of image is ``flwr_superlink``" +#~ " and the tag ``0.1.0``. Remember that" +#~ " the build arguments as well as " +#~ "the name and tag can be adapted" +#~ " to your needs. These values serve" +#~ " as examples only." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to use your own " +#~ "base image instead of the official " +#~ "Flower base image, all you need to" +#~ " do is set the ``BASE_REPOSITORY``, " +#~ "``PYTHON_VERSION`` and ``UBUNTU_VERSION`` build " +#~ "arguments." +#~ msgstr "" + +#~ msgid "Creating New Messages" +#~ msgstr "Création de nouveaux messages" + +#~ msgid "" +#~ "This is a simple guide for " +#~ "creating a new type of message " +#~ "between the server and clients in " +#~ "Flower." +#~ msgstr "" +#~ "Voici un guide simple pour créer " +#~ "un nouveau type de message entre " +#~ "le serveur et les clients dans " +#~ "Flower." + +#~ msgid "" +#~ "Let's suppose we have the following " +#~ "example functions in :code:`server.py` and " +#~ ":code:`numpy_client.py`..." +#~ msgstr "" +#~ "Supposons que nous ayons les fonctions" +#~ " suivantes dans :code:`server.py` et " +#~ ":code:`numpy_client.py`..." + +#~ msgid "Server's side:" +#~ msgstr "Côté serveur :" + +#~ msgid "Client's side:" +#~ msgstr "Côté client :" + +#~ msgid "" +#~ "Let's now see what we need to " +#~ "implement in order to get this " +#~ "simple function between the server and" +#~ " client to work!" +#~ msgstr "" +#~ "Voyons maintenant ce que nous devons " +#~ "mettre en œuvre pour que cette " +#~ "simple fonction entre le serveur et " +#~ "le client fonctionne !" + +#~ msgid "Message Types for Protocol Buffers" +#~ msgstr "Types de messages pour les tampons de protocole" + +#~ msgid "" +#~ "The first thing we need to do " +#~ "is to define a message type for" +#~ " the RPC system in :code:`transport.proto`." +#~ " Note that we have to do it " +#~ "for both the request and response " +#~ "messages. For more details on the " +#~ "syntax of proto3, please see the " +#~ "`official documentation `_." +#~ msgstr "" +#~ "La première chose à faire est de" +#~ " définir un type de message pour " +#~ "le système RPC dans :code:`transport.proto`." +#~ " Notez que nous devons le faire " +#~ "à la fois pour les messages de " +#~ "demande et de réponse. Pour plus " +#~ "de détails sur la syntaxe de " +#~ "proto3, veuillez consulter la `documentation" +#~ " officielle `_." + +#~ msgid "Within the :code:`ServerMessage` block:" +#~ msgstr "Dans le bloc :code:`ServerMessage` :" + +#~ msgid "Within the ClientMessage block:" +#~ msgstr "Dans le bloc ClientMessage :" + +#~ msgid "" +#~ "Make sure to also add a field " +#~ "of the newly created message type " +#~ "in :code:`oneof msg`." +#~ msgstr "" +#~ "Veille à ajouter également un champ " +#~ "du type de message nouvellement créé " +#~ "dans :code:`oneof msg`." + +#~ msgid "Once that is done, we will compile the file with:" +#~ msgstr "Une fois que c'est fait, nous compilerons le fichier avec :" + +#~ msgid "If it compiles successfully, you should see the following message:" +#~ msgstr "S'il se compile avec succès, tu devrais voir le message suivant :" + +#~ msgid "Serialization and Deserialization Functions" +#~ msgstr "Fonctions de sérialisation et de désérialisation" + +#~ msgid "" +#~ "Our next step is to add functions" +#~ " to serialize and deserialize Python " +#~ "datatypes to or from our defined " +#~ "RPC message types. You should add " +#~ "these functions in :code:`serde.py`." +#~ msgstr "" +#~ "La prochaine étape consiste à ajouter" +#~ " des fonctions pour sérialiser et " +#~ "désérialiser les types de données Python" +#~ " vers ou à partir des types de" +#~ " messages RPC définis. Tu dois " +#~ "ajouter ces fonctions dans :code:`serde.py`." + +#~ msgid "The four functions:" +#~ msgstr "Les quatre fonctions :" + +#~ msgid "Sending the Message from the Server" +#~ msgstr "Envoi du message à partir du serveur" + +#~ msgid "" +#~ "Now write the request function in " +#~ "your Client Proxy class (e.g., " +#~ ":code:`grpc_client_proxy.py`) using the serde " +#~ "functions you just created:" +#~ msgstr "" +#~ "Écris maintenant la fonction de demande" +#~ " dans ta classe Client Proxy (par " +#~ "exemple, :code:`grpc_client_proxy.py`) en utilisant" +#~ " les fonctions serde que tu viens " +#~ "de créer :" + +#~ msgid "Receiving the Message by the Client" +#~ msgstr "Réception du message par le client" + +#~ msgid "" +#~ "Last step! Modify the code in " +#~ ":code:`message_handler.py` to check the field" +#~ " of your message and call the " +#~ ":code:`example_response` function. Remember to " +#~ "use the serde functions!" +#~ msgstr "" +#~ "Dernière étape ! Modifie le code " +#~ "dans :code:`message_handler.py` pour vérifier " +#~ "le champ de ton message et appeler" +#~ " la fonction :code:`example_response`. N'oublie" +#~ " pas d'utiliser les fonctions serde !" + +#~ msgid "Within the handle function:" +#~ msgstr "Dans le cadre de la fonction de poignée :" + +#~ msgid "And add a new function:" +#~ msgstr "Et ajoute une nouvelle fonction :" + +#~ msgid "Hopefully, when you run your program you will get the intended result!" +#~ msgstr "" +#~ "Avec un peu de chance, lorsque tu" +#~ " exécuteras ton programme, tu obtiendras" +#~ " le résultat escompté !" + +#~ msgid "" +#~ "The simplest way to get started " +#~ "with Flower is by using the " +#~ "pre-made Docker images, which you can" +#~ " find on `Docker Hub " +#~ "`__." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to persist the state" +#~ " of the SuperLink on your host " +#~ "system, all you need to do is " +#~ "specify a path where you want to" +#~ " save the file on your host " +#~ "system and a name for the database" +#~ " file. In the example below, we " +#~ "tell Docker via the flag ``--volume``" +#~ " to mount the user's home directory" +#~ " (``~/`` on your host) into the " +#~ "``/app/`` directory of the container. " +#~ "Furthermore, we use the flag " +#~ "``--database`` to specify the name of" +#~ " the database file." +#~ msgstr "" + +#~ msgid "" +#~ "As soon as the SuperLink starts, " +#~ "the file ``state.db`` is created in " +#~ "the user's home directory on your " +#~ "host system. If the file already " +#~ "exists, the SuperLink tries to restore" +#~ " the state from the file. To " +#~ "start the SuperLink with an empty " +#~ "database, simply remove the ``state.db`` " +#~ "file." +#~ msgstr "" + +#~ msgid "" +#~ "Assuming all files we need are in" +#~ " the local ``certificates`` directory, we" +#~ " can use the flag ``--volume`` to " +#~ "mount the local directory into the " +#~ "``/app/`` directory of the container. " +#~ "This allows the SuperLink to access " +#~ "the files within the container. Finally," +#~ " we pass the names of the " +#~ "certificates to the SuperLink with the" +#~ " ``--certificates`` flag." +#~ msgstr "" + +#~ msgid "" +#~ "``--server 192.168.1.100:9092``: This option " +#~ "specifies the address of the SuperLinks" +#~ " Fleet" +#~ msgstr "" + +#~ msgid "" +#~ "Assuming the certificate already exists " +#~ "locally, we can use the flag " +#~ "``--volume`` to mount the local " +#~ "certificate into the container's ``/app/`` " +#~ "directory. This allows the SuperNode to" +#~ " access the certificate within the " +#~ "container. Use the ``--certificates`` flag " +#~ "when starting the container." +#~ msgstr "" + +#~ msgid "" +#~ "``--server 192.168.1.100:9091``: This option " +#~ "specifies the address of the SuperLinks" +#~ " Driver" +#~ msgstr "" + +#~ msgid "" +#~ "Assuming the certificate already exists " +#~ "locally, we can use the flag " +#~ "``--volume`` to mount the local " +#~ "certificate into the container's ``/app/`` " +#~ "directory. This allows the ServerApp to" +#~ " access the certificate within the " +#~ "container. Use the ``--certificates`` flag " +#~ "when starting the container." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to use a different " +#~ "version of Flower, for example Flower" +#~ " nightly, you can do so by " +#~ "changing the tag. All available versions" +#~ " are on `Docker Hub " +#~ "`__." +#~ msgstr "" + +#~ msgid "" +#~ "Here's another example to start with " +#~ "HTTPS. Use the ``--certificates`` command " +#~ "line argument to pass paths to (CA" +#~ " certificate, server certificate, and " +#~ "server private key)." +#~ msgstr "" + +#~ msgid ":py:obj:`run_driver_api `\\ \\(\\)" +#~ msgstr "" + +#~ msgid "Run Flower server (Driver API)." +#~ msgstr "flower-driver-api" + +#~ msgid ":py:obj:`run_fleet_api `\\ \\(\\)" +#~ msgstr "" + +#~ msgid "Run Flower server (Fleet API)." +#~ msgstr "flower-fleet-api" + +#~ msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" +#~ msgstr "" + +#~ msgid "|5aa1711387d74d0f8b9c499e1a51627e|" +#~ msgstr "" + +#~ msgid "|2bc8e069228d4873804061ff4a95048c|" +#~ msgstr "" + +#~ msgid "|c258488766324dc9a6807f0e7c4fd5f4|" +#~ msgstr "" + +#~ msgid "|d5f962c3f4ec48529efda980868c14b0|" +#~ msgstr "" + +#~ msgid "|a5eccea18d4c43a68b54b65043cabef8|" +#~ msgstr "" + +#~ msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" +#~ msgstr "" + +#~ msgid "|241fc906441a4f038c625a19d30d01b2|" +#~ msgstr "" + +#~ msgid "|0aa5aa05810b44b6a835cecce28f3137|" +#~ msgstr "" + +#~ msgid "|c742940dd4bf4de09d8d0d5e8d179638|" +#~ msgstr "" + +#~ msgid "|1f169ab4601a47e1a226f1628f4ebddb|" +#~ msgstr "" + +#~ msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" +#~ msgstr "" + +#~ msgid "|72939caf6e294b0986fee6dde96614d7|" +#~ msgstr "" + +#~ msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +#~ msgstr "" + diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 9333ce670e93..9c8d2f5ff19b 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -7,17 +7,16 @@ msgid "" msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-28 11:47+0200\n" +"POT-Creation-Date: 2024-06-17 16:09+0200\n" "PO-Revision-Date: 2024-06-16 09:09+0000\n" "Last-Translator: 박태현 \n" -"Language-Team: Korean \n" "Language: ko\n" +"Language-Team: Korean \n" +"Plural-Forms: nplurals=1; plural=0;\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" -"X-Generator: Weblate 5.6-dev\n" "Generated-By: Babel 2.15.0\n" #: ../../source/contributor-explanation-architecture.rst:2 @@ -30,11 +29,9 @@ msgstr "엣지 클라이언트 엔진" #: ../../source/contributor-explanation-architecture.rst:7 msgid "" -"`Flower `_ core framework architecture with Edge Client " -"Engine" -msgstr "" -"`Flower `_의 핵심 프레임워크 아키텍처와 엣지 클라이언트 엔" -"진" +"`Flower `_ core framework architecture with Edge " +"Client Engine" +msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 엣지 클라이언트 엔진" #: ../../source/contributor-explanation-architecture.rst:13 msgid "Virtual Client Engine" @@ -44,9 +41,7 @@ msgstr "가상 클라이언트 엔진" msgid "" "`Flower `_ core framework architecture with Virtual " "Client Engine" -msgstr "" -"`Flower `_의 핵심 프레임워크 아키텍처와 가상 클라이언트 엔" -"진" +msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 가상 클라이언트 엔진" #: ../../source/contributor-explanation-architecture.rst:21 msgid "Virtual Client Engine and Edge Client Engine in the same workload" @@ -54,277 +49,276 @@ msgstr "동일 작업에서 가상 클라이언트 엔진과 엣지 클라이언 #: ../../source/contributor-explanation-architecture.rst:23 msgid "" -"`Flower `_ core framework architecture with both Virtual " -"Client Engine and Edge Client Engine" -msgstr "" -"`Flower `_의 핵심 프레임워크 아키텍처와 가상 및 엣지 클라" -"이언트 엔진" +"`Flower `_ core framework architecture with both " +"Virtual Client Engine and Edge Client Engine" +msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 가상 및 엣지 클라이언트 엔진" #: ../../source/contributor-how-to-build-docker-images.rst:2 msgid "How to build Docker Flower images locally" msgstr "Docker Flower 이미지를 Locally 구축하는 방법" #: ../../source/contributor-how-to-build-docker-images.rst:4 +#, fuzzy msgid "" -"Flower provides pre-made docker images on `Docker Hub `_ that include all necessary dependencies for running the " -"SuperLink. You can also build your own custom docker images from scratch " -"with a different version of Python or Ubuntu if that is what you need. In " -"this guide, we will explain what images exist and how to build them locally." +"Flower provides pre-made docker images on `Docker Hub " +"`_ that include all necessary dependencies" +" for running the SuperLink, SuperNode or ServerApp. You can also build " +"your own custom docker images from scratch with a different version of " +"Python or Linux distribution (Ubuntu/Alpine) if that is what you need. In" +" this guide, we will explain what images exist and how to build them " +"locally." msgstr "" -"Flower는 'Docker Hub '_에서 미리 만들어진 " -"Docker 이미지들을 제공합니다. 해당 이미지들은 SuperLink, ServerNode 또는 " -"ServerApp을 실행하는 데 필요한 모든 dependencies를 포함합니다. 필요한 경우 다" -"른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해 처음부터 사용자 정" -"의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 이미지들과 " -"이들을 로컬에서 빌드하는 방법에 대해 설명하겠습니다." +"Flower는 'Docker Hub '_에서 미리 만들어진 Docker " +"이미지들을 제공합니다. 해당 이미지들은 SuperLink, ServerNode 또는 ServerApp을 실행하는 데 필요한 모든 " +"dependencies를 포함합니다. 필요한 경우 다른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해" +" 처음부터 사용자 정의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 이미지들과 이들을 로컬에서 빌드하는 " +"방법에 대해 설명하겠습니다." -#: ../../source/contributor-how-to-build-docker-images.rst:9 +#: ../../source/contributor-how-to-build-docker-images.rst:10 msgid "" "Before we can start, we need to meet a few prerequisites in our local " "development environment." msgstr "시작하기 전에, 로컬 개발 환경에서 몇 가지 전제 조건을 충족해야 합니다." -#: ../../source/contributor-how-to-build-docker-images.rst:11 +#: ../../source/contributor-how-to-build-docker-images.rst:12 msgid "Clone the flower repository." msgstr "Flower 리포지토리를 복제합니다." -#: ../../source/contributor-how-to-build-docker-images.rst:17 -#: ../../source/how-to-run-flower-using-docker.rst:144 +#: ../../source/contributor-how-to-build-docker-images.rst:18 +#: ../../source/how-to-run-flower-using-docker.rst:165 msgid "Verify the Docker daemon is running." msgstr "Docker 데몬이 실행 중인지 확인하십시오." -#: ../../source/contributor-how-to-build-docker-images.rst:19 -#: ../../source/how-to-run-flower-using-docker.rst:146 -msgid "" -"Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." -msgstr "" -":doc:Run Flower using Docker 의 첫 번째 섹션" -"을 따라 주십시오. 해당 부분을 더 자세히 설명해 줍니다." - -#: ../../source/contributor-how-to-build-docker-images.rst:23 +#: ../../source/contributor-how-to-build-docker-images.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:167 msgid "" -"Currently, Flower provides two images, a ``base`` image and a ``superlink`` " -"image. The base image, as the name suggests, contains basic dependencies " -"that the SuperLink needs. This includes system dependencies, Python and " -"Python tools. The SuperLink image is based on the base image, but it " -"additionally installs the SuperLink using ``pip``." +"Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." msgstr "" -"현재, Flower는 \"base\" 이미지 그리고 \"superlink\" 이미지를 제공합니다. " -"base 이미지는 이름에서 알 수 있듯이 SuperLink가 필요로 하는 기본 dependencies" -"를 포함하고 있습니다. 여기에는 시스템 dependencies, Python 및 Python 도구가 " -"포함됩니다. SuperLink 이미지는 base 이미지를 기반으로 하지만 \"pip\"을 사용하" -"여 SuperLink를 추가로 설치합니다." +":doc:Run Flower using Docker 의 첫 번째 섹션을 " +"따라 주십시오. 해당 부분을 더 자세히 설명해 줍니다." -#: ../../source/contributor-how-to-build-docker-images.rst:28 +#: ../../source/contributor-how-to-build-docker-images.rst:25 msgid "" "The build instructions that assemble the images are located in the " -"respective Dockerfiles. You can find them in the subdirectories of ``src/" -"docker``." +"respective Dockerfiles. You can find them in the subdirectories of " +"``src/docker``." msgstr "" -"이미지들을 조합하는 빌드 instruction들은 해당 Dockerfile에 있습니다. \"src/" -"docker\" 의 하위 디렉토리에서 찾을 수 있습니다." +"이미지들을 조합하는 빌드 instruction들은 해당 Dockerfile에 있습니다. \"src/docker\" 의 하위 " +"디렉토리에서 찾을 수 있습니다." -#: ../../source/contributor-how-to-build-docker-images.rst:31 -msgid "" -"Both, base and SuperLink image are configured via build arguments. Through " -"build arguments, we can make our build more flexible. For example, in the " -"base image, we can specify the version of Python to install using the " -"``PYTHON_VERSION`` build argument. Some of the build arguments have default " -"values, others must be specified when building the image. All available " -"build arguments for each image are listed in one of the tables below." +#: ../../source/contributor-how-to-build-docker-images.rst:28 +#, fuzzy +msgid "" +"Flower Docker images are configured via build arguments. Through build " +"arguments, we can make the creation of images more flexible. For example," +" in the base image, we can specify the version of Python to install using" +" the ``PYTHON_VERSION`` build argument. Some of the build arguments have " +"default values, others must be specified when building the image. All " +"available build arguments for each image are listed in one of the tables " +"below." msgstr "" -"base 이미지와 SuperLink 이미지 둘 다 빌드 argument들을 통해 구성됩니다. 빌드 " -"argument들을 통해, 빌드를 더 유연하게 만들 수 있습니다. 예를 들어, base 이미" -"지에서 \"PYTHON_VERSION\" 빌드 argument를 사용하여 Python 버전을 지정할 수 있" -"습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 합니" -"다. 각 이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 있습니다." +"base 이미지와 SuperLink 이미지 둘 다 빌드 argument들을 통해 구성됩니다. 빌드 argument들을 통해, 빌드를" +" 더 유연하게 만들 수 있습니다. 예를 들어, base 이미지에서 \"PYTHON_VERSION\" 빌드 argument를 사용하여" +" Python 버전을 지정할 수 있습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 합니다. 각 " +"이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 있습니다." -#: ../../source/contributor-how-to-build-docker-images.rst:38 +#: ../../source/contributor-how-to-build-docker-images.rst:35 msgid "Building the base image" msgstr "base 이미지 빌드" -#: ../../source/contributor-how-to-build-docker-images.rst:44 -#: ../../source/contributor-how-to-build-docker-images.rst:86 +#: ../../source/contributor-how-to-build-docker-images.rst:41 +#: ../../source/contributor-how-to-build-docker-images.rst:98 msgid "Build argument" msgstr "빌드 argument" -#: ../../source/contributor-how-to-build-docker-images.rst:45 -#: ../../source/contributor-how-to-build-docker-images.rst:87 +#: ../../source/contributor-how-to-build-docker-images.rst:42 +#: ../../source/contributor-how-to-build-docker-images.rst:99 msgid "Description" msgstr "설명" -#: ../../source/contributor-how-to-build-docker-images.rst:46 -#: ../../source/contributor-how-to-build-docker-images.rst:88 +#: ../../source/contributor-how-to-build-docker-images.rst:43 +#: ../../source/contributor-how-to-build-docker-images.rst:100 msgid "Required" msgstr "필수" -#: ../../source/contributor-how-to-build-docker-images.rst:47 -#: ../../source/contributor-how-to-build-docker-images.rst:89 +#: ../../source/contributor-how-to-build-docker-images.rst:44 +#: ../../source/contributor-how-to-build-docker-images.rst:101 msgid "Example" msgstr "예시" +#: ../../source/contributor-how-to-build-docker-images.rst:45 +msgid "``DISTRO``" +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:46 +#, fuzzy +msgid "The Linux distribution to use as the base image." +msgstr "base 이미지의 Ubuntu 버전." + +#: ../../source/contributor-how-to-build-docker-images.rst:47 +#: ../../source/contributor-how-to-build-docker-images.rst:51 +#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:71 +#: ../../source/contributor-how-to-build-docker-images.rst:104 +msgid "No" +msgstr "" + #: ../../source/contributor-how-to-build-docker-images.rst:48 -#: ../../source/contributor-how-to-build-docker-images.rst:94 -msgid "``PYTHON_VERSION``" -msgstr "``PYTHON_VERSION``" +#, fuzzy +msgid "``ubuntu``" +msgstr "``UBUNTU_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:49 -msgid "Version of ``python`` to be installed." -msgstr "설치 된 ``python`` 버전." +#, fuzzy +msgid "``DISTRO_VERSION``" +msgstr "``PIP_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:50 +msgid "Version of the Linux distribution." +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:52 +#, fuzzy +msgid "``22.04``" +msgstr "``23.0.1``" + +#: ../../source/contributor-how-to-build-docker-images.rst:53 +msgid "``PYTHON_VERSION``" +msgstr "``PYTHON_VERSION``" + #: ../../source/contributor-how-to-build-docker-images.rst:54 -#: ../../source/contributor-how-to-build-docker-images.rst:58 -#: ../../source/contributor-how-to-build-docker-images.rst:108 -msgid "Yes" -msgstr "예" +msgid "Version of ``python`` to be installed." +msgstr "설치 된 ``python`` 버전." -#: ../../source/contributor-how-to-build-docker-images.rst:51 -msgid "``3.11``" -msgstr "``3.11``" +#: ../../source/contributor-how-to-build-docker-images.rst:56 +msgid "``3.11`` or ``3.11.1``" +msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:52 +#: ../../source/contributor-how-to-build-docker-images.rst:57 msgid "``PIP_VERSION``" msgstr "``PIP_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:53 +#: ../../source/contributor-how-to-build-docker-images.rst:58 msgid "Version of ``pip`` to be installed." msgstr "설치 된 ``pip`` 버전." -#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:59 +#: ../../source/contributor-how-to-build-docker-images.rst:63 +#: ../../source/contributor-how-to-build-docker-images.rst:67 +#: ../../source/contributor-how-to-build-docker-images.rst:108 +msgid "Yes" +msgstr "예" + +#: ../../source/contributor-how-to-build-docker-images.rst:60 msgid "``23.0.1``" msgstr "``23.0.1``" -#: ../../source/contributor-how-to-build-docker-images.rst:56 +#: ../../source/contributor-how-to-build-docker-images.rst:61 msgid "``SETUPTOOLS_VERSION``" msgstr "``SETUPTOOLS_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:57 +#: ../../source/contributor-how-to-build-docker-images.rst:62 msgid "Version of ``setuptools`` to be installed." msgstr "설치 된 ``setuptools`` 버전." -#: ../../source/contributor-how-to-build-docker-images.rst:59 +#: ../../source/contributor-how-to-build-docker-images.rst:64 msgid "``69.0.2``" msgstr "``69.0.2``" -#: ../../source/contributor-how-to-build-docker-images.rst:60 -#: ../../source/contributor-how-to-build-docker-images.rst:98 -msgid "``UBUNTU_VERSION``" -msgstr "``UBUNTU_VERSION``" +#: ../../source/contributor-how-to-build-docker-images.rst:65 +msgid "``FLWR_VERSION``" +msgstr "``FLWR_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:61 -msgid "Version of the official Ubuntu Docker image." -msgstr "공식 Ubuntu Docker 이미지 버전." +#: ../../source/contributor-how-to-build-docker-images.rst:66 +msgid "Version of Flower to be installed." +msgstr "설치 된 Flower 버전." -#: ../../source/contributor-how-to-build-docker-images.rst:62 -msgid "Defaults to ``22.04``." -msgstr "``22.04``이 기본값." +#: ../../source/contributor-how-to-build-docker-images.rst:68 +msgid "``1.8.0``" +msgstr "``1.8.0``" -#: ../../source/contributor-how-to-build-docker-images.rst:65 -msgid "" -"The following example creates a base image with Python 3.11.0, pip 23.0.1 " -"and setuptools 69.0.2:" +#: ../../source/contributor-how-to-build-docker-images.rst:69 +msgid "``FLWR_PACKAGE``" +msgstr "``FLWR_PACKAGE``" + +#: ../../source/contributor-how-to-build-docker-images.rst:70 +#, fuzzy +msgid "The Flower package to be installed." +msgstr "설치 할 PyPI 패키지." + +#: ../../source/contributor-how-to-build-docker-images.rst:72 +msgid "``flwr`` or ``flwr-nightly``" msgstr "" -"다음 예시에서는 Python 3.11.0, pip 23.0.1 그리고 setuptools 69.0.2의 base 이" -"미지를 만듭니다:" -#: ../../source/contributor-how-to-build-docker-images.rst:76 +#: ../../source/contributor-how-to-build-docker-images.rst:75 +#, fuzzy +msgid "" +"The following example creates a base Ubuntu/Alpine image with Python " +"3.11.0, pip 23.0.1, setuptools 69.0.2 and Flower 1.8.0:" +msgstr "다음 예시에서는 Python 3.11.0, pip 23.0.1 그리고 setuptools 69.0.2의 base 이미지를 만듭니다:" + +#: ../../source/contributor-how-to-build-docker-images.rst:88 msgid "" -"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that the " -"build arguments as well as the name and tag can be adapted to your needs. " -"These values serve as examples only." +"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that " +"the build arguments as well as the name and tag can be adapted to your " +"needs. These values serve as examples only." msgstr "" -"이미지의 이름은 ``flwr_base``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌드 " -"argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 뿐입" -"니다." +"이미지의 이름은 ``flwr_base``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌드 argument들 뿐만 아니라 이름과" +" 태그도 정할 수 있습니다. 이 값들은 예시일 뿐입니다." -#: ../../source/contributor-how-to-build-docker-images.rst:80 -msgid "Building the SuperLink image" +#: ../../source/contributor-how-to-build-docker-images.rst:92 +#, fuzzy +msgid "Building the SuperLink/SuperNode or ServerApp image" msgstr "SuperLink 이미지 빌드" -#: ../../source/contributor-how-to-build-docker-images.rst:90 +#: ../../source/contributor-how-to-build-docker-images.rst:102 msgid "``BASE_REPOSITORY``" msgstr "``BASE_REPOSITORY``" -#: ../../source/contributor-how-to-build-docker-images.rst:91 +#: ../../source/contributor-how-to-build-docker-images.rst:103 msgid "The repository name of the base image." msgstr "base 이미지의 리포지토리 이름." -#: ../../source/contributor-how-to-build-docker-images.rst:92 -msgid "Defaults to ``flwr/base``." -msgstr "``flwr/base``이 기본값." - -#: ../../source/contributor-how-to-build-docker-images.rst:95 -msgid "The Python version of the base image." -msgstr "base 이미지의 Python 버전." - -#: ../../source/contributor-how-to-build-docker-images.rst:96 -msgid "Defaults to ``py3.11``." -msgstr "``py3.11``이 기본값." - -#: ../../source/contributor-how-to-build-docker-images.rst:99 -msgid "The Ubuntu version of the base image." -msgstr "base 이미지의 Ubuntu 버전." - -#: ../../source/contributor-how-to-build-docker-images.rst:100 -msgid "Defaults to ``ubuntu22.04``." -msgstr "``ubuntu22.04``이 기본값." - -#: ../../source/contributor-how-to-build-docker-images.rst:102 -msgid "``FLWR_PACKAGE``" +#: ../../source/contributor-how-to-build-docker-images.rst:105 +#, fuzzy +msgid "``flwr/base``" msgstr "``FLWR_PACKAGE``" -#: ../../source/contributor-how-to-build-docker-images.rst:103 -msgid "The PyPI package to install." -msgstr "설치 할 PyPI 패키지." - -#: ../../source/contributor-how-to-build-docker-images.rst:104 -msgid "Defaults to ``flwr``." -msgstr "``flwr``이 기본값." - #: ../../source/contributor-how-to-build-docker-images.rst:106 -msgid "``FLWR_VERSION``" -msgstr "``FLWR_VERSION``" +#, fuzzy +msgid "``BASE_IMAGE``" +msgstr "``BASE_REPOSITORY``" #: ../../source/contributor-how-to-build-docker-images.rst:107 -msgid "Version of Flower to be installed." -msgstr "설치 된 Flower 버전." +#, fuzzy +msgid "The Tag of the Flower base image." +msgstr "base 이미지의 리포지토리 이름." #: ../../source/contributor-how-to-build-docker-images.rst:109 -msgid "``1.8.0``" -msgstr "``1.8.0``" - -#: ../../source/contributor-how-to-build-docker-images.rst:112 -msgid "" -"The following example creates a SuperLink image with the official Flower " -"base image py3.11-ubuntu22.04 and Flower 1.8.0:" +msgid "``1.8.0-py3.10-ubuntu22.04``" msgstr "" -"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base " -"이미지로 SuperLink 이미지를 만듭니다:" -#: ../../source/contributor-how-to-build-docker-images.rst:122 +#: ../../source/contributor-how-to-build-docker-images.rst:111 +#, fuzzy msgid "" -"The name of image is ``flwr_superlink`` and the tag ``0.1.0``. Remember that " -"the build arguments as well as the name and tag can be adapted to your " -"needs. These values serve as examples only." +"The following example creates a SuperLink/SuperNode or ServerApp image " +"with the official Flower base image:" msgstr "" -"이미지의 이름은 ``flwr_superlink``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌" -"드 argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 뿐" -"입니다." +"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base 이미지로 SuperLink" +" 이미지를 만듭니다:" -#: ../../source/contributor-how-to-build-docker-images.rst:125 +#: ../../source/contributor-how-to-build-docker-images.rst:122 +#, fuzzy msgid "" -"If you want to use your own base image instead of the official Flower base " -"image, all you need to do is set the ``BASE_REPOSITORY``, ``PYTHON_VERSION`` " -"and ``UBUNTU_VERSION`` build arguments." +"If you want to use your own base image instead of the official Flower " +"base image, all you need to do is set the ``BASE_REPOSITORY`` build " +"argument." msgstr "" -"공식 Flower base 이미지 대신 자체 base 이미지를 사용 하길 원한다면, " -"``BASE_REPOSITORY``, ``PYTHON_VERSION`` 및 ``UBUNTU_VERSION`` 빌드 argument들" -"을 설정해야 합니다." +"공식 Flower base 이미지 대신 자체 base 이미지를 사용 하길 원한다면, ``BASE_REPOSITORY``, " +"``PYTHON_VERSION`` 및 ``UBUNTU_VERSION`` 빌드 argument들을 설정해야 합니다." -#: ../../source/contributor-how-to-build-docker-images.rst:138 +#: ../../source/contributor-how-to-build-docker-images.rst:133 msgid "After creating the image, we can test whether the image is working:" msgstr "이미지 생성 후에, 이미지가 작동하는지 테스트할 수 있습니다:" @@ -334,30 +328,29 @@ msgstr "번역 기여" #: ../../source/contributor-how-to-contribute-translations.rst:4 msgid "" -"Since `Flower 1.5 `_ we have introduced translations to our doc pages, " -"but, as you might have noticed, the translations are often imperfect. If you " -"speak languages other than English, you might be able to help us in our " -"effort to make Federated Learning accessible to as many people as possible " -"by contributing to those translations! This might also be a great " -"opportunity for those wanting to become open source contributors with little " -"prerequisites." -msgstr "" -"`Flower 1.5 `_ 부터 문서 페이지에 번역을 도입했지만, 아시다시피 번" -"역이 불안전한 경우가 많습니다. 만일 영어 이외의 언어를 사용한다면, 많은 사람" -"들이 Federated Learning에 접근할 수 있도록 번역 작업에 기여함으로써 저희의 노" -"력에 도움을 주실 수 있습니다! 이는 전제 조건이 거의 없는 오픈 소스 기여자가 " -"되고자 하는 사람들에게 좋은 기회가 될 수도 있습니다." +"Since `Flower 1.5 `_ we have introduced translations to " +"our doc pages, but, as you might have noticed, the translations are often" +" imperfect. If you speak languages other than English, you might be able " +"to help us in our effort to make Federated Learning accessible to as many" +" people as possible by contributing to those translations! This might " +"also be a great opportunity for those wanting to become open source " +"contributors with little prerequisites." +msgstr "" +"`Flower 1.5 `_ 부터 문서 페이지에 번역을 도입했지만, 아시다시피 번역이 불안전한 " +"경우가 많습니다. 만일 영어 이외의 언어를 사용한다면, 많은 사람들이 Federated Learning에 접근할 수 있도록 번역 " +"작업에 기여함으로써 저희의 노력에 도움을 주실 수 있습니다! 이는 전제 조건이 거의 없는 오픈 소스 기여자가 되고자 하는 사람들에게" +" 좋은 기회가 될 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:13 msgid "" -"Our translation project is publicly available over on `Weblate `_, this where most of " -"the work will happen." +"Our translation project is publicly available over on `Weblate " +"`_, this " +"where most of the work will happen." msgstr "" -"번역 프로젝트는 `Weblate `_에서 공개적으로 진행되며, 대부분의 작업이 이곳에서 이루어집니다." +"번역 프로젝트는 `Weblate `_에서 공개적으로 진행되며, 대부분의 작업이 이곳에서 이루어집니다." #: ../../source/contributor-how-to-contribute-translations.rst:18 msgid "Contribute to existing languages" @@ -365,43 +358,43 @@ msgstr "기존 언어에 기여하기" #: ../../source/contributor-how-to-contribute-translations.rst:23 msgid "" -"The first thing you will need to do in order to contribute is to create a " -"free Weblate account on this `page `_. More information about profile settings can be found `here " +"The first thing you will need to do in order to contribute is to create a" +" free Weblate account on this `page " +"`_. More information about" +" profile settings can be found `here " "`_." msgstr "" -"기여를 하기 위해 가장 먼저 해야 할 일은 해당 `page `_에서 무료 Weblate 계정을 만드는 것입니다. 프로필 설" -"정에 대한 자세한 정보는 `here `_를 참조하세요." +"기여를 하기 위해 가장 먼저 해야 할 일은 해당 `page " +"`_에서 무료 Weblate 계정을 만드는 " +"것입니다. 프로필 설정에 대한 자세한 정보는 `here " +"`_를 참조하세요." #: ../../source/contributor-how-to-contribute-translations.rst:29 msgid "" -"Once you are signed in to Weblate, you can navigate to the `Flower Framework " -"project `_. " -"Here, you should see the different existing languages that can be found on " -"the website." +"Once you are signed in to Weblate, you can navigate to the `Flower " +"Framework project `_. Here, you should see the different existing languages" +" that can be found on the website." msgstr "" -"Weblate에 로그인한 후, `Flower Framework project `_로 이동할 수 있습니다. 여기에서 웹사이트에 " -"있는 다양한 기존 언어들을 확인할 수 있습니다." +"Weblate에 로그인한 후, `Flower Framework project " +"`_로 이동할 수 " +"있습니다. 여기에서 웹사이트에 있는 다양한 기존 언어들을 확인할 수 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:34 msgid "" -"Once you have selected the language you want to contribute to, you should " -"see a similar interface to this:" +"Once you have selected the language you want to contribute to, you should" +" see a similar interface to this:" msgstr "기여하고자 하는 언어를 선택하면, 다음과 같은 인터페이스가 나타납니다:" #: ../../source/contributor-how-to-contribute-translations.rst:39 msgid "" "The most straight forward option here is to click on the ``Translate`` " -"button on the top right (in the ``Translation status`` section). This will " -"automatically bring you to the translation interface for untranslated " -"strings." +"button on the top right (in the ``Translation status`` section). This " +"will automatically bring you to the translation interface for " +"untranslated strings." msgstr "" -"여기서 가장 간단한 옵션은 오른쪽 상단(``Translation status`` 부분)에 있는 " -"``Translate`` 버튼을 클릭하는 것 입니다. 번역되지 않은 문장에 대한 번역 인터" -"페이스로 자동으로 이동합니다." +"여기서 가장 간단한 옵션은 오른쪽 상단(``Translation status`` 부분)에 있는 ``Translate`` 버튼을 " +"클릭하는 것 입니다. 번역되지 않은 문장에 대한 번역 인터페이스로 자동으로 이동합니다." #: ../../source/contributor-how-to-contribute-translations.rst:43 msgid "This is what the interface looks like:" @@ -409,47 +402,44 @@ msgstr "인터페이스는 다음과 같습니다:" #: ../../source/contributor-how-to-contribute-translations.rst:47 msgid "" -"You input your translation in the text box at the top and then, once you are " -"happy with it, you either press ``Save and continue`` (to save the " -"translation and go to the next untranslated string), ``Save and stay`` (to " -"save the translation and stay on the same page), ``Suggest`` (to add your " -"translation to suggestions for other users to view), or ``Skip`` (to go to " -"the next untranslated string without saving anything)." +"You input your translation in the text box at the top and then, once you " +"are happy with it, you either press ``Save and continue`` (to save the " +"translation and go to the next untranslated string), ``Save and stay`` " +"(to save the translation and stay on the same page), ``Suggest`` (to add " +"your translation to suggestions for other users to view), or ``Skip`` (to" +" go to the next untranslated string without saving anything)." msgstr "" -"번역문을 상단의 텍스트 상자에 입력한 후, 번역이 만족스러우면 ``Save and " -"continue``(번역을 저장하고 다음 미번역 문장으로 이동), ``Save and stay``(번역" -"을 저장하고 해당 페이지에 머무르기), ``Suggest`` (다른 사용자가 볼 수 있도록 " -"번역을 제안 항목에 추가), ``Skip``(아무것도 저장하지 않고 다음 미번역 문장으" -"로 이동) 중 하나를 선택하면 됩니다." +"번역문을 상단의 텍스트 상자에 입력한 후, 번역이 만족스러우면 ``Save and continue``(번역을 저장하고 다음 미번역 " +"문장으로 이동), ``Save and stay``(번역을 저장하고 해당 페이지에 머무르기), ``Suggest`` (다른 사용자가 " +"볼 수 있도록 번역을 제안 항목에 추가), ``Skip``(아무것도 저장하지 않고 다음 미번역 문장으로 이동) 중 하나를 선택하면 " +"됩니다." #: ../../source/contributor-how-to-contribute-translations.rst:54 msgid "" "In order to help with the translations, you can see on the bottom the " "``Nearby strings``, the ``Comments`` (from other contributors), the " "``Automatic suggestions`` (from machine translation engines), the " -"translations in ``Other languages``, and the ``History`` of translations for " -"this string." +"translations in ``Other languages``, and the ``History`` of translations " +"for this string." msgstr "" -"번역에 도움을 주기위해 하단에서 `주변 문자열``, ``의견``(다른 기여자의), ``자" -"동 제안``(기계 번역의), ``다른 언어``의 번역 및 해당 문장의 번역``히스토리``" -"를 볼 수 있습니다." +"번역에 도움을 주기위해 하단에서 `주변 문자열``, ``의견``(다른 기여자의), ``자동 제안``(기계 번역의), ``다른 " +"언어``의 번역 및 해당 문장의 번역``히스토리``를 볼 수 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:59 msgid "" -"On the right, under the ``String information`` section, you can also click " -"the link under ``Source string location`` in order to view the source of the " -"doc file containing the string." -msgstr "" -"오른쪽의 ``문자열 정보``에서 ``원본 문자열 위치``를 클릭하여 해당 문장이 포함" -"된 문서의 파일 소스를 볼 수도 있습니다." +"On the right, under the ``String information`` section, you can also " +"click the link under ``Source string location`` in order to view the " +"source of the doc file containing the string." +msgstr "오른쪽의 ``문자열 정보``에서 ``원본 문자열 위치``를 클릭하여 해당 문장이 포함된 문서의 파일 소스를 볼 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:63 msgid "" -"For more information about translating using Weblate, you can check out this " -"`in-depth guide `_." +"For more information about translating using Weblate, you can check out " +"this `in-depth guide " +"`_." msgstr "" -"Weblate를 통한 번역에 대한 자세한 정보는 `in-depth guide `_를 확인하세요." +"Weblate를 통한 번역에 대한 자세한 정보는 `in-depth guide " +"`_를 확인하세요." #: ../../source/contributor-how-to-contribute-translations.rst:67 msgid "Add new languages" @@ -457,126 +447,12 @@ msgstr "새 언어 추가" #: ../../source/contributor-how-to-contribute-translations.rst:69 msgid "" -"If you want to add a new language, you will first have to contact us, either " -"on `Slack `_, or by opening an issue on our " -"`GitHub repo `_." -msgstr "" -"새 언어를 추가하려면, `Slack `에 문의하거나 " -"`GitHub repo `_에서 issue에 들어가 문의 해야 " -"합니다." - -#: ../../source/contributor-how-to-create-new-messages.rst:2 -msgid "Creating New Messages" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:4 -msgid "" -"This is a simple guide for creating a new type of message between the server " -"and clients in Flower." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:6 -msgid "" -"Let's suppose we have the following example functions in :code:`server.py` " -"and :code:`numpy_client.py`..." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:8 -msgid "Server's side:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:17 -msgid "Client's side:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:26 -msgid "" -"Let's now see what we need to implement in order to get this simple function " -"between the server and client to work!" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:30 -msgid "Message Types for Protocol Buffers" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:32 -msgid "" -"The first thing we need to do is to define a message type for the RPC system " -"in :code:`transport.proto`. Note that we have to do it for both the request " -"and response messages. For more details on the syntax of proto3, please see " -"the `official documentation `_." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:35 -msgid "Within the :code:`ServerMessage` block:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:52 -msgid "Within the ClientMessage block:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:70 -msgid "" -"Make sure to also add a field of the newly created message type in :code:" -"`oneof msg`." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:72 -msgid "Once that is done, we will compile the file with:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:78 -msgid "If it compiles successfully, you should see the following message:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:87 -msgid "Serialization and Deserialization Functions" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:89 -msgid "" -"Our next step is to add functions to serialize and deserialize Python " -"datatypes to or from our defined RPC message types. You should add these " -"functions in :code:`serde.py`." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:91 -msgid "The four functions:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:112 -msgid "Sending the Message from the Server" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:114 -msgid "" -"Now write the request function in your Client Proxy class (e.g., :code:" -"`grpc_client_proxy.py`) using the serde functions you just created:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:128 -msgid "Receiving the Message by the Client" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:130 -msgid "" -"Last step! Modify the code in :code:`message_handler.py` to check the field " -"of your message and call the :code:`example_response` function. Remember to " -"use the serde functions!" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:132 -msgid "Within the handle function:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:139 -msgid "And add a new function:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:149 -msgid "Hopefully, when you run your program you will get the intended result!" +"If you want to add a new language, you will first have to contact us, " +"either on `Slack `_, or by opening an issue" +" on our `GitHub repo `_." msgstr "" +"새 언어를 추가하려면, `Slack `에 문의하거나 `GitHub repo " +"`_에서 issue에 들어가 문의 해야 합니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:2 msgid "Develop in VSCode Dev Containers" @@ -584,52 +460,49 @@ msgstr "VSCode Dev Container에서 개발" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:4 msgid "" -"When working on the Flower framework we want to ensure that all contributors " -"use the same developer environment to format code or run tests. For this " -"purpose we are using the VSCode Remote Containers extension. What is it? " -"Read the following quote:" +"When working on the Flower framework we want to ensure that all " +"contributors use the same developer environment to format code or run " +"tests. For this purpose we are using the VSCode Remote Containers " +"extension. What is it? Read the following quote:" msgstr "" -"Flower 프레임워크 작업시, 모든 기여자들이 코드 포맷팅이나 테스트 실행을 위해 " -"동일한 개발 환경을 사용하길 원합니다. 이를 위해 VSCode Remote Containers 확장" -"을 사용하고 있습니다. 그것이 무엇인지 알아보기 위해 다음 인용문을 읽어보세요:" +"Flower 프레임워크 작업시, 모든 기여자들이 코드 포맷팅이나 테스트 실행을 위해 동일한 개발 환경을 사용하길 원합니다. 이를 " +"위해 VSCode Remote Containers 확장을 사용하고 있습니다. 그것이 무엇인지 알아보기 위해 다음 인용문을 " +"읽어보세요:" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:7 msgid "" -"The Visual Studio Code Remote - Containers extension lets you use a Docker " -"container as a fully-featured development environment. It allows you to open " -"any folder inside (or mounted into) a container and take advantage of Visual " -"Studio Code's full feature set. A :code:`devcontainer.json` file in your " -"project tells VS Code how to access (or create) a development container with " -"a well-defined tool and runtime stack. This container can be used to run an " -"application or to separate tools, libraries, or runtimes needed for working " -"with a codebase." -msgstr "" -"Visual Studio Code Remote - 컨테이너 확장을 사용하면 Docker 컨테이너를 모든 " -"기능을 갖춘 개발 환경으로 사용할 수 있습니다. 이 확장 기능을 사용하면 컨테이" -"너 내부(또는 컨테이너에 마운트된)의 모든 폴더를 열고 Visual Studio Code의 모" -"든 기능을 활용할 수 있습니다. 프로젝트에 있는 :code:`devcontainer.json` 파일" -"은 잘 정의된 도구와 런타임 스택을 사용하여 개발 컨테이너에 액세스(또는 생성)" -"하는 방법을 VS Code에 알려줍니다. 이 컨테이너는 애플리케이션을 실행하거나 코" -"드베이스 작업에 필요한 도구, 라이브러리 또는 런타임을 분리하는 데 사용할 수 " -"있습니다." +"The Visual Studio Code Remote - Containers extension lets you use a " +"Docker container as a fully-featured development environment. It allows " +"you to open any folder inside (or mounted into) a container and take " +"advantage of Visual Studio Code's full feature set. A " +":code:`devcontainer.json` file in your project tells VS Code how to " +"access (or create) a development container with a well-defined tool and " +"runtime stack. This container can be used to run an application or to " +"separate tools, libraries, or runtimes needed for working with a " +"codebase." +msgstr "" +"Visual Studio Code Remote - 컨테이너 확장을 사용하면 Docker 컨테이너를 모든 기능을 갖춘 개발 환경으로 " +"사용할 수 있습니다. 이 확장 기능을 사용하면 컨테이너 내부(또는 컨테이너에 마운트된)의 모든 폴더를 열고 Visual Studio" +" Code의 모든 기능을 활용할 수 있습니다. 프로젝트에 있는 :code:`devcontainer.json` 파일은 잘 정의된 " +"도구와 런타임 스택을 사용하여 개발 컨테이너에 액세스(또는 생성)하는 방법을 VS Code에 알려줍니다. 이 컨테이너는 " +"애플리케이션을 실행하거나 코드베이스 작업에 필요한 도구, 라이브러리 또는 런타임을 분리하는 데 사용할 수 있습니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:9 msgid "" -"Workspace files are mounted from the local file system or copied or cloned " -"into the container. Extensions are installed and run inside the container, " -"where they have full access to the tools, platform, and file system. This " -"means that you can seamlessly switch your entire development environment " -"just by connecting to a different container." +"Workspace files are mounted from the local file system or copied or " +"cloned into the container. Extensions are installed and run inside the " +"container, where they have full access to the tools, platform, and file " +"system. This means that you can seamlessly switch your entire development" +" environment just by connecting to a different container." msgstr "" -"작업 공간 파일은 로컬 파일 시스템에서 마운트되거나 컨테이너에 복사 또는 " -"클론됩니다. 확장 프로그램은 컨테이너 내부에 설치되고 실행되며, 도구, 플랫폼 " -"및 파일 시스템에 완전한 접근 권한을 갖습니다. 이는 다른 컨테이너에 연결하는 " -"것만으로 전체 개발 환경을 원활하게 전환할 수 있음을 의미합니다." +"작업 공간 파일은 로컬 파일 시스템에서 마운트되거나 컨테이너에 복사 또는 클론됩니다. 확장 프로그램은 컨테이너 내부에 설치되고 " +"실행되며, 도구, 플랫폼 및 파일 시스템에 완전한 접근 권한을 갖습니다. 이는 다른 컨테이너에 연결하는 것만으로 전체 개발 환경을 " +"원활하게 전환할 수 있음을 의미합니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:11 msgid "" -"Source: `Official VSCode documentation `_" +"Source: `Official VSCode documentation " +"`_" msgstr "출처 : 공식 VSCode 문서" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:15 @@ -638,57 +511,52 @@ msgstr "시작하기" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:17 msgid "" -"Configuring and setting up the :code:`Dockerfile` as well the configuration " -"for the devcontainer can be a bit more involved. The good thing is you don't " -"have to do it. Usually it should be enough to install `Docker `_ on your system and ensure its available on " -"your command line. Additionally, install the `VSCode Containers Extension " -"`_." +"Configuring and setting up the :code:`Dockerfile` as well the " +"configuration for the devcontainer can be a bit more involved. The good " +"thing is you don't have to do it. Usually it should be enough to install " +"`Docker `_ on your system and " +"ensure its available on your command line. Additionally, install the " +"`VSCode Containers Extension `_." msgstr "" -"`Dockerfile`을 설정하고 구성하는 것과 개발 컨테이너 구성은 약간 복잡할 수 " -"있습니다. 다행히도, 이를 직접 할 필요는 없습니다. 일반적으로 시스템에 `" -"Docker `_를 설치하고 커맨드 " -"라인에서 사용할 수 있는지 확인하는 것으로 충분합니다. 추가로 `VSCode " -"Containers Extension `" -"_을 설치하세요." +"`Dockerfile`을 설정하고 구성하는 것과 개발 컨테이너 구성은 약간 복잡할 수 있습니다. 다행히도, 이를 직접 할 필요는 " +"없습니다. 일반적으로 시스템에 `Docker `_를 " +"설치하고 커맨드 라인에서 사용할 수 있는지 확인하는 것으로 충분합니다. 추가로 `VSCode Containers Extension " +"`_을 설치하세요." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:19 msgid "" -"Now you should be good to go. When starting VSCode, it will ask you to run " -"in the container environment and - if you confirm - automatically build the " -"container and use it. To manually instruct VSCode to use the devcontainer, " -"you can, after installing the extension, click the green area in the bottom " -"left corner of your VSCode window and select the option *(Re)Open Folder in " -"Container*." +"Now you should be good to go. When starting VSCode, it will ask you to " +"run in the container environment and - if you confirm - automatically " +"build the container and use it. To manually instruct VSCode to use the " +"devcontainer, you can, after installing the extension, click the green " +"area in the bottom left corner of your VSCode window and select the " +"option *(Re)Open Folder in Container*." msgstr "" -"이제 준비가 완료되었습니다. VSCode를 시작하면 컨테이너 환경에서 실행할지를 " -"묻고, 확인하면 자동으로 컨테이너를 빌드하고 사용할 것입니다. VSCode에 " -"수동으로 개발 컨테이너를 사용하도록 지시하려면, 확장을 설치한 후, VSCode " -"창의 왼쪽 하단에 있는 초록색 부을 클릭하고 *(Re)Open Folder in Container* " -"옵션을 선택하세요." +"이제 준비가 완료되었습니다. VSCode를 시작하면 컨테이너 환경에서 실행할지를 묻고, 확인하면 자동으로 컨테이너를 빌드하고 사용할" +" 것입니다. VSCode에 수동으로 개발 컨테이너를 사용하도록 지시하려면, 확장을 설치한 후, VSCode 창의 왼쪽 하단에 있는 " +"초록색 부을 클릭하고 *(Re)Open Folder in Container* 옵션을 선택하세요." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:21 msgid "" -"In some cases your setup might be more involved. For those cases consult the " -"following sources:" -msgstr "경우에 따라 설정이 더 복잡할 수도 있습니다. 이러한 경우에는 다음 소스를 " -"참조하세요:" +"In some cases your setup might be more involved. For those cases consult " +"the following sources:" +msgstr "경우에 따라 설정이 더 복잡할 수도 있습니다. 이러한 경우에는 다음 소스를 참조하세요:" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:23 msgid "" -"`Developing inside a Container `_" +"`Developing inside a Container " +"`_" msgstr "" -"`컨테이너 내부 개발`_" +"`컨테이너 내부 개발`_" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:24 msgid "" -"`Remote development in Containers `_" -msgstr "" -"`컨테이너 원격 개발`_" +"`Remote development in Containers " +"`_" +msgstr "`컨테이너 원격 개발`_" #: ../../source/contributor-how-to-install-development-versions.rst:2 msgid "Install development versions" @@ -704,35 +572,33 @@ msgstr "Poetry 사용하기(권장)" #: ../../source/contributor-how-to-install-development-versions.rst:10 msgid "" -"Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency in " -"``pyproject.toml`` and then reinstall (don't forget to delete ``poetry." -"lock`` (``rm poetry.lock``) before running ``poetry install``)." +"Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency " +"in ``pyproject.toml`` and then reinstall (don't forget to delete " +"``poetry.lock`` (``rm poetry.lock``) before running ``poetry install``)." msgstr "" -"PyPI에서 ``flwr`` 사전 릴리스 설치하기: ``pyproject.toml``에서 ``flwr``의 " -"dependency를 업데이트한 다음, 재설치하세요(``poetry 설치``이전에 ``poetry." -"lock`` (``rm poetry.lock``)를 제거하는 것을 잊지 마세요)." +"PyPI에서 ``flwr`` 사전 릴리스 설치하기: ``pyproject.toml``에서 ``flwr``의 dependency를 " +"업데이트한 다음, 재설치하세요(``poetry 설치``이전에 ``poetry.lock`` (``rm poetry.lock``)를 " +"제거하는 것을 잊지 마세요)." #: ../../source/contributor-how-to-install-development-versions.rst:12 msgid "" "``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (without " "extras)" -msgstr "" -"``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (extras 제외)" +msgstr "``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:13 msgid "" "``flwr = { version = \"1.0.0a0\", allow-prereleases = true, extras = " "[\"simulation\"] }`` (with extras)" msgstr "" -"``flwr = { version = \"1.0.0a0\", allow-prereleases = true, extras = [" -"\"simulation\"] }`` (extras 포함)" +"``flwr = { version = \"1.0.0a0\", allow-prereleases = true, extras = " +"[\"simulation\"] }`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:15 msgid "" -"Install ``flwr`` from a local copy of the Flower source code via ``pyproject." -"toml``:" -msgstr "``pyproject.toml``을 통해 Flower 소스 코드의 로컬 복사본에서 ``flwr``을 " -"설치하세요:" +"Install ``flwr`` from a local copy of the Flower source code via " +"``pyproject.toml``:" +msgstr "``pyproject.toml``을 통해 Flower 소스 코드의 로컬 복사본에서 ``flwr``을 설치하세요:" #: ../../source/contributor-how-to-install-development-versions.rst:17 msgid "``flwr = { path = \"../../\", develop = true }`` (without extras)" @@ -740,11 +606,11 @@ msgstr "``flwr = { path = \"../../\", develop = true }`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:18 msgid "" -"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] }`` " -"(with extras)" +"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] " +"}`` (with extras)" msgstr "" -"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] }`` (" -"extras 포함)" +"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] " +"}`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:20 msgid "Install ``flwr`` from a local wheel file via ``pyproject.toml``:" @@ -752,8 +618,8 @@ msgstr "``pyproject.toml``을 통해 로컬 wheel file에서 ``flwr``을 설치 #: ../../source/contributor-how-to-install-development-versions.rst:22 msgid "" -"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (without " -"extras)" +"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (without" +" extras)" msgstr "" "``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (extras " "제외)" @@ -763,8 +629,8 @@ msgid "" "``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\", extras = " "[\"simulation\"] }`` (with extras)" msgstr "" -"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\", extras = [" -"\"simulation\"] }`` (extras 포함)" +"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\", extras = " +"[\"simulation\"] }`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:25 msgid "" @@ -772,8 +638,8 @@ msgid "" "Dependency Specification `_" msgstr "" -"자세한 내용은 Poetry 문서를 참고하세요: `Poetry Dependency Specification " -"`_" +"자세한 내용은 Poetry 문서를 참고하세요: `Poetry Dependency Specification `_" #: ../../source/contributor-how-to-install-development-versions.rst:28 msgid "Using pip (recommended on Colab)" @@ -796,8 +662,8 @@ msgid "" "Python packages can be installed from git repositories. Use one of the " "following commands to install the Flower directly from GitHub." msgstr "" -"Python 패키지는 git 저장소에서 설치할 수 있습니다. 다음 명령어 중 하나를 " -"사용하여 GitHub에서 직접 Flower를 설치하세요." +"Python 패키지는 git 저장소에서 설치할 수 있습니다. 다음 명령어 중 하나를 사용하여 GitHub에서 직접 Flower를 " +"설치하세요." #: ../../source/contributor-how-to-install-development-versions.rst:37 msgid "Install ``flwr`` from the default GitHub branch (``main``):" @@ -805,7 +671,8 @@ msgstr "기본 GitHub branch (``main``)에서 ``flwr`` 를 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:39 msgid "" -"``pip install flwr@git+https://github.com/adap/flower.git`` (without extras)" +"``pip install flwr@git+https://github.com/adap/flower.git`` (without " +"extras)" msgstr "``pip install flwr@git+https://github.com/adap/flower.git`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:40 @@ -813,8 +680,8 @@ msgid "" "``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " "(with extras)" msgstr "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` (" -"extras 포함)" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " +"(extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:42 msgid "Install ``flwr`` from a specific GitHub branch (``branch-name``):" @@ -825,16 +692,16 @@ msgid "" "``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` " "(without extras)" msgstr "" -"``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` (" -"extras 제외)" +"``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` " +"(extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" -"name`` (with extras)" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" +"@branch-name`` (with extras)" msgstr "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" -"name`` (extras 포함)" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" +"@branch-name`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:49 msgid "Open Jupyter Notebooks on Google Colab" @@ -845,32 +712,32 @@ msgid "" "Open the notebook ``doc/source/tutorial-series-get-started-with-flower-" "pytorch.ipynb``:" msgstr "" -"``doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb``" -"notebook을 엽니다:" +"``doc/source/tutorial-series-get-started-with-flower-" +"pytorch.ipynb``notebook을 엽니다:" #: ../../source/contributor-how-to-install-development-versions.rst:53 msgid "" -"https://colab.research.google.com/github/adap/flower/blob/main/doc/source/" -"tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/main/doc/source" +"/tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" -"https://colab.research.google.com/github/adap/flower/blob/main/doc/source/" -"tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/main/doc/source" +"/tutorial-series-get-started-with-flower-pytorch.ipynb" #: ../../source/contributor-how-to-install-development-versions.rst:55 msgid "" -"Open a development version of the same notebook from branch `branch-name` by " -"changing ``main`` to ``branch-name`` (right after ``blob``):" +"Open a development version of the same notebook from branch `branch-name`" +" by changing ``main`` to ``branch-name`` (right after ``blob``):" msgstr "" -"``main``을 ``branch-name``(``blob`` 바로 뒤)으로 변경하여 동일한 notebook의 " -"개발 버전을 브랜치 `branch-name`에서 엽니다 :" +"``main``을 ``branch-name``(``blob`` 바로 뒤)으로 변경하여 동일한 notebook의 개발 버전을 브랜치 " +"`branch-name`에서 엽니다 :" #: ../../source/contributor-how-to-install-development-versions.rst:57 msgid "" -"https://colab.research.google.com/github/adap/flower/blob/branch-name/doc/" -"source/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/branch-" +"name/doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" -"https://colab.research.google.com/github/adap/flower/blob/branch-name/doc/" -"source/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/branch-" +"name/doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb" #: ../../source/contributor-how-to-install-development-versions.rst:59 msgid "Install a `whl` on Google Colab:" @@ -878,10 +745,9 @@ msgstr "Google Colab에서 `whl` 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:61 msgid "" -"In the vertical icon grid on the left hand side, select ``Files`` > ``Upload " -"to session storage``" -msgstr "왼쪽의 수직 아이콘 그리드에서 ``Files`` > ``Upload to session storage``를 " -"선택하세요" +"In the vertical icon grid on the left hand side, select ``Files`` > " +"``Upload to session storage``" +msgstr "왼쪽의 수직 아이콘 그리드에서 ``Files`` > ``Upload to session storage``를 선택하세요" #: ../../source/contributor-how-to-install-development-versions.rst:62 msgid "Upload the whl (e.g., ``flwr-1.8.0-py3-none-any.whl``)" @@ -889,13 +755,13 @@ msgstr "whl (예:``flwr-1.8.0-py3-none-any.whl``)을 업로드하세요" #: ../../source/contributor-how-to-install-development-versions.rst:63 msgid "" -"Change ``!pip install -q 'flwr[simulation]' torch torchvision matplotlib`` " -"to ``!pip install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch " -"torchvision matplotlib``" +"Change ``!pip install -q 'flwr[simulation]' torch torchvision " +"matplotlib`` to ``!pip install -q 'flwr-1.8.0-py3-none-" +"any.whl[simulation]' torch torchvision matplotlib``" msgstr "" -"``!pip install -q 'flwr[simulation]' torch torchvision matplotlib``를 ``!pip " -"install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch torchvision " -"matplotlib``로 바꾸세요" +"``!pip install -q 'flwr[simulation]' torch torchvision matplotlib``를 " +"``!pip install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch " +"torchvision matplotlib``로 바꾸세요" #: ../../source/contributor-how-to-release-flower.rst:2 msgid "Release Flower" @@ -913,11 +779,12 @@ msgstr "릴리즈 동안에" #: ../../source/contributor-how-to-release-flower.rst:9 msgid "" -"The version number of a release is stated in ``pyproject.toml``. To release " -"a new version of Flower, the following things need to happen (in that order):" +"The version number of a release is stated in ``pyproject.toml``. To " +"release a new version of Flower, the following things need to happen (in " +"that order):" msgstr "" -"릴리즈의 버전 번호는 ``pyproject.toml``에 명시되어 있습니다. Flower의 새 " -"버전을 릴리즈하려면 다음 작업이 순서대로 수행되어야 합니다:" +"릴리즈의 버전 번호는 ``pyproject.toml``에 명시되어 있습니다. Flower의 새 버전을 릴리즈하려면 다음 작업이 " +"순서대로 수행되어야 합니다:" #: ../../source/contributor-how-to-release-flower.rst:11 msgid "" @@ -925,158 +792,203 @@ msgid "" "order to add every new change to the changelog (feel free to make manual " "changes to the changelog afterwards until it looks good)." msgstr "" -"모든 새로운 변경 사항을 변경 로그에 추가하기 위해``python3 src/py/flwr_tool/" -"update_changelog.py ``을 실행합니다 (변경 로그가 만족스러워질 " -"때까지 수동으로 변경해도 됩니다)." +"모든 새로운 변경 사항을 변경 로그에 추가하기 위해``python3 " +"src/py/flwr_tool/update_changelog.py ``을 실행합니다 (변경 로그가 " +"만족스러워질 때까지 수동으로 변경해도 됩니다)." #: ../../source/contributor-how-to-release-flower.rst:12 msgid "" -"Once the changelog has been updated with all the changes, run ``./dev/" -"prepare-release-changelog.sh v``, where ```` is " -"the version stated in ``pyproject.toml`` (notice the ``v`` added before it). " -"This will replace the ``Unreleased`` header of the changelog by the version " -"and current date, and it will add a thanking message for the contributors. " -"Open a pull request with those changes." +"Once the changelog has been updated with all the changes, run ``./dev" +"/prepare-release-changelog.sh v``, where ```` " +"is the version stated in ``pyproject.toml`` (notice the ``v`` added " +"before it). This will replace the ``Unreleased`` header of the changelog " +"by the version and current date, and it will add a thanking message for " +"the contributors. Open a pull request with those changes." msgstr "" -"모든 변경 사항으로 변경 로그가 업데이트되면,``./dev/prepare-release-" -"changelog.sh v``을 실행합니다. 여기서 ````은 " -"``pyproject.toml``에 명시된 버전 번호입니다 (앞에 ``v``가 추가된 것을 " -"주의하세요). 이 명령어는 변경 로그의 ``Unreleased``헤더를 해당 버전과 현재 " -"날짜로 교체하고, 기여자들에게 감사 메시지가 추가됩니다. 이러한 변경 사항으로 " -"pull request합니다." +"모든 변경 사항으로 변경 로그가 업데이트되면,``./dev/prepare-release-changelog.sh " +"v``을 실행합니다. 여기서 ````은 ``pyproject.toml``에 명시된 " +"버전 번호입니다 (앞에 ``v``가 추가된 것을 주의하세요). 이 명령어는 변경 로그의 ``Unreleased``헤더를 해당 버전과" +" 현재 날짜로 교체하고, 기여자들에게 감사 메시지가 추가됩니다. 이러한 변경 사항으로 pull request합니다." #: ../../source/contributor-how-to-release-flower.rst:13 msgid "" "Once the pull request is merged, tag the release commit with the version " -"number as soon as the PR is merged: ``git tag v`` (notice the " -"``v`` added before the version number), then ``git push --tags``. This will " -"create a draft release on GitHub containing the correct artifacts and the " -"relevant part of the changelog." +"number as soon as the PR is merged: ``git tag v`` (notice " +"the ``v`` added before the version number), then ``git push --tags``. " +"This will create a draft release on GitHub containing the correct " +"artifacts and the relevant part of the changelog." msgstr "" #: ../../source/contributor-how-to-release-flower.rst:14 -msgid "" -"Check the draft release on GitHub, and if everything is good, publish it." +msgid "Check the draft release on GitHub, and if everything is good, publish it." msgstr "" +#: ../../source/contributor-how-to-release-flower.rst:15 +#, fuzzy +msgid "Trigger the CI for building the Docker images." +msgstr "공식 Ubuntu Docker 이미지 버전." + #: ../../source/contributor-how-to-release-flower.rst:17 +msgid "" +"To trigger the workflow, a collaborator must create a " +"``workflow_dispatch`` event in the GitHub CI. This can be done either " +"through the UI or via the GitHub CLI. The event requires only one input, " +"the Flower version, to be released." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:21 +msgid "**Via the UI**" +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:23 +msgid "" +"Go to the ``Build docker images`` workflow `page " +"`_." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:24 +msgid "" +"Click on the ``Run workflow`` button and type the new version of Flower " +"in the ``Version of Flower`` input field." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:25 +msgid "Click on the **green** ``Run workflow`` button." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:29 +msgid "**Via the GitHub CI**" +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:31 +msgid "" +"Make sure you are logged in via ``gh auth login`` and that the current " +"working directory is the root of the Flower repository." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:32 +msgid "" +"Trigger the workflow via ``gh workflow run docker-images.yml -f flwr-" +"version=``." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:35 msgid "After the release" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:19 +#: ../../source/contributor-how-to-release-flower.rst:37 msgid "Create a pull request which contains the following changes:" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:21 +#: ../../source/contributor-how-to-release-flower.rst:39 msgid "Increase the minor version in ``pyproject.toml`` by one." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:22 +#: ../../source/contributor-how-to-release-flower.rst:40 msgid "Update all files which contain the current version number if necessary." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:23 +#: ../../source/contributor-how-to-release-flower.rst:41 msgid "Add a new ``Unreleased`` section in ``changelog.md``." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:25 +#: ../../source/contributor-how-to-release-flower.rst:43 msgid "" -"Merge the pull request on the same day (i.e., before a new nightly release " -"gets published to PyPI)." +"Merge the pull request on the same day (i.e., before a new nightly " +"release gets published to PyPI)." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:28 +#: ../../source/contributor-how-to-release-flower.rst:46 msgid "Publishing a pre-release" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:31 +#: ../../source/contributor-how-to-release-flower.rst:49 msgid "Pre-release naming" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:33 +#: ../../source/contributor-how-to-release-flower.rst:51 msgid "" -"PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases " -"MUST use one of the following naming patterns:" +"PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases" +" MUST use one of the following naming patterns:" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:35 +#: ../../source/contributor-how-to-release-flower.rst:53 msgid "Alpha: ``MAJOR.MINOR.PATCHaN``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:36 +#: ../../source/contributor-how-to-release-flower.rst:54 msgid "Beta: ``MAJOR.MINOR.PATCHbN``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:37 +#: ../../source/contributor-how-to-release-flower.rst:55 msgid "Release candidate (RC): ``MAJOR.MINOR.PATCHrcN``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:39 +#: ../../source/contributor-how-to-release-flower.rst:57 msgid "Examples include:" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:41 +#: ../../source/contributor-how-to-release-flower.rst:59 msgid "``1.0.0a0``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:42 +#: ../../source/contributor-how-to-release-flower.rst:60 msgid "``1.0.0b0``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:43 +#: ../../source/contributor-how-to-release-flower.rst:61 msgid "``1.0.0rc0``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:44 +#: ../../source/contributor-how-to-release-flower.rst:62 msgid "``1.0.0rc1``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:46 +#: ../../source/contributor-how-to-release-flower.rst:64 msgid "" "This is in line with PEP-440 and the recommendations from the Python " "Packaging Authority (PyPA):" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:49 +#: ../../source/contributor-how-to-release-flower.rst:67 msgid "`PEP-440 `_" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:50 +#: ../../source/contributor-how-to-release-flower.rst:68 msgid "" -"`PyPA Choosing a versioning scheme `_" +"`PyPA Choosing a versioning scheme " +"`_" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:52 +#: ../../source/contributor-how-to-release-flower.rst:70 msgid "" -"Note that the approach defined by PyPA is not compatible with SemVer 2.0.0 " -"spec, for details consult the `Semantic Versioning Specification `_ (specifically item 11 on " -"precedence)." +"Note that the approach defined by PyPA is not compatible with SemVer " +"2.0.0 spec, for details consult the `Semantic Versioning Specification " +"`_ (specifically item " +"11 on precedence)." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:55 +#: ../../source/contributor-how-to-release-flower.rst:73 msgid "Pre-release classification" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:57 -msgid "" -"Should the next pre-release be called alpha, beta, or release candidate?" +#: ../../source/contributor-how-to-release-flower.rst:75 +msgid "Should the next pre-release be called alpha, beta, or release candidate?" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:59 +#: ../../source/contributor-how-to-release-flower.rst:77 msgid "" -"RC: feature complete, no known issues (apart from issues that are classified " -"as \"won't fix\" for the next stable release) - if no issues surface this " -"will become the next stable release" +"RC: feature complete, no known issues (apart from issues that are " +"classified as \"won't fix\" for the next stable release) - if no issues " +"surface this will become the next stable release" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:60 +#: ../../source/contributor-how-to-release-flower.rst:78 msgid "Beta: feature complete, allowed to have known issues" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:61 +#: ../../source/contributor-how-to-release-flower.rst:79 msgid "Alpha: not feature complete, allowed to have known issues" msgstr "" @@ -1088,8 +1000,8 @@ msgstr "" msgid "" "It is recommended to run your Python setup within a virtual environment. " "This guide shows three different examples how to create a virtual " -"environment with pyenv virtualenv, poetry, or Anaconda. You can follow the " -"instructions or choose your preferred setup." +"environment with pyenv virtualenv, poetry, or Anaconda. You can follow " +"the instructions or choose your preferred setup." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:9 @@ -1099,15 +1011,17 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:11 #: ../../source/how-to-install-flower.rst:8 msgid "" -"Flower requires at least `Python 3.8 `_, but " -"`Python 3.10 `_ or above is recommended." +"Flower requires at least `Python 3.8 `_, " +"but `Python 3.10 `_ or above is " +"recommended." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:14 msgid "" -"Due to a known incompatibility with `ray `_, " -"we currently recommend utilizing at most `Python 3.11 `_ for running Flower simulations." +"Due to a known incompatibility with `ray " +"`_, we currently recommend utilizing at " +"most `Python 3.11 `_ for running Flower " +"simulations." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:19 @@ -1116,10 +1030,10 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:21 msgid "" -"One of the recommended virtual environment is `pyenv `_/`virtualenv `_. " -"Please see `Flower examples `_ for details." +"One of the recommended virtual environment is `pyenv " +"`_/`virtualenv `_. Please see `Flower examples " +"`_ for details." msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:23 @@ -1142,15 +1056,15 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:46 msgid "" -"The Flower examples are based on `Poetry `_ " -"to manage dependencies. After installing Poetry you simply create a virtual " -"environment with:" +"The Flower examples are based on `Poetry `_ to manage dependencies. After installing Poetry you " +"simply create a virtual environment with:" msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:52 msgid "" -"If you open a new terminal you can activate the previously created virtual " -"environment with the following command:" +"If you open a new terminal you can activate the previously created " +"virtual environment with the following command:" msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:60 @@ -1159,10 +1073,10 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:62 msgid "" -"If you prefer to use Anaconda for your virtual environment then install and " -"setup the `conda `_ package. After setting it up you can create a virtual " -"environment with:" +"If you prefer to use Anaconda for your virtual environment then install " +"and setup the `conda `_ package. After setting it up you can " +"create a virtual environment with:" msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:68 @@ -1175,8 +1089,8 @@ msgstr "" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:78 msgid "" -"As soon as you created your virtual environment you clone one of the `Flower " -"examples `_." +"As soon as you created your virtual environment you clone one of the " +"`Flower examples `_." msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:2 @@ -1189,17 +1103,18 @@ msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:8 msgid "" -"The Flower documentation lives in the ``doc`` directory. The Sphinx-based " -"documentation system supports both reStructuredText (``.rst`` files) and " -"Markdown (``.md`` files)." +"The Flower documentation lives in the ``doc`` directory. The Sphinx-based" +" documentation system supports both reStructuredText (``.rst`` files) and" +" Markdown (``.md`` files)." msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:10 #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:169 msgid "" -"Note that, in order to build the documentation locally (with ``poetry run " -"make html``, like described below), `Pandoc `_ needs to be installed on the system." +"Note that, in order to build the documentation locally (with ``poetry run" +" make html``, like described below), `Pandoc " +"`_ needs to be installed on the " +"system." msgstr "" #: ../../source/contributor-how-to-write-documentation.rst:14 @@ -1242,10 +1157,10 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:4 msgid "" -"We welcome contributions to Flower! However, it is not always easy to know " -"where to start. We therefore put together a few recommendations on where to " -"start to increase your chances of getting your PR accepted into the Flower " -"codebase." +"We welcome contributions to Flower! However, it is not always easy to " +"know where to start. We therefore put together a few recommendations on " +"where to start to increase your chances of getting your PR accepted into " +"the Flower codebase." msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:11 @@ -1254,9 +1169,9 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:13 msgid "" -"Until the Flower core library matures it will be easier to get PR's accepted " -"if they only touch non-core areas of the codebase. Good candidates to get " -"started are:" +"Until the Flower core library matures it will be easier to get PR's " +"accepted if they only touch non-core areas of the codebase. Good " +"candidates to get started are:" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:17 @@ -1277,23 +1192,24 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:25 msgid "" -"If you are not familiar with Flower Baselines, you should probably check-out " -"our `contributing guide for baselines `_." +"If you are not familiar with Flower Baselines, you should probably check-" +"out our `contributing guide for baselines " +"`_." msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:27 msgid "" -"You should then check out the open `issues `_ for baseline " -"requests. If you find a baseline that you'd like to work on and that has no " -"assignees, feel free to assign it to yourself and start working on it!" +"You should then check out the open `issues " +"`_" +" for baseline requests. If you find a baseline that you'd like to work on" +" and that has no assignees, feel free to assign it to yourself and start " +"working on it!" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:31 msgid "" -"Otherwise, if you don't find a baseline you'd like to work on, be sure to " -"open a new issue with the baseline request template!" +"Otherwise, if you don't find a baseline you'd like to work on, be sure to" +" open a new issue with the baseline request template!" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:34 @@ -1303,8 +1219,8 @@ msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:36 msgid "" "We wish we had more time to write usage examples because we believe they " -"help users to get started with building what they want to build. Here are a " -"few ideas where we'd be happy to accept a PR:" +"help users to get started with building what they want to build. Here are" +" a few ideas where we'd be happy to accept a PR:" msgstr "" #: ../../source/contributor-ref-good-first-contributions.rst:40 @@ -1325,10 +1241,10 @@ msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:4 msgid "" -"Include SecAgg, SecAgg+, and LightSecAgg protocol. The LightSecAgg protocol " -"has not been implemented yet, so its diagram and abstraction may not be " -"accurate in practice. The SecAgg protocol can be considered as a special " -"case of the SecAgg+ protocol." +"Include SecAgg, SecAgg+, and LightSecAgg protocol. The LightSecAgg " +"protocol has not been implemented yet, so its diagram and abstraction may" +" not be accurate in practice. The SecAgg protocol can be considered as a " +"special case of the SecAgg+ protocol." msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:8 @@ -1339,15 +1255,15 @@ msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:161 msgid "" "In this implementation, each client will be assigned with a unique index " -"(int) for secure aggregation, and thus many python dictionaries used have " -"keys of int type rather than ClientProxy type." +"(int) for secure aggregation, and thus many python dictionaries used have" +" keys of int type rather than ClientProxy type." msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:65 #: ../../source/contributor-ref-secure-aggregation-protocols.rst:198 msgid "" -"The Flower server will execute and process received results in the following " -"order:" +"The Flower server will execute and process received results in the " +"following order:" msgstr "" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:159 @@ -1364,15 +1280,15 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:4 msgid "" -"This guide is for people who want to get involved with Flower, but who are " -"not used to contributing to GitHub projects." +"This guide is for people who want to get involved with Flower, but who " +"are not used to contributing to GitHub projects." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:6 msgid "" -"If you're familiar with how contributing on GitHub works, you can directly " -"checkout our :doc:`getting started guide for contributors `." +"If you're familiar with how contributing on GitHub works, you can " +"directly checkout our :doc:`getting started guide for contributors " +"`." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:10 @@ -1388,15 +1304,15 @@ msgid "" "Git is a distributed version control tool. This allows for an entire " "codebase's history to be stored and every developer's machine. It is a " "software that will need to be installed on your local machine, you can " -"follow this `guide `_ to set it up." +"follow this `guide `_ to set it up." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:16 msgid "" "GitHub, itself, is a code hosting platform for version control and " -"collaboration. It allows for everyone to collaborate and work from anywhere " -"on remote repositories." +"collaboration. It allows for everyone to collaborate and work from " +"anywhere on remote repositories." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:18 @@ -1407,10 +1323,10 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:20 msgid "" -"The idea behind the generic Git and GitHub workflow boils down to this: you " -"download code from a remote repository on GitHub, make changes locally and " -"keep track of them using Git and then you upload your new history back to " -"GitHub." +"The idea behind the generic Git and GitHub workflow boils down to this: " +"you download code from a remote repository on GitHub, make changes " +"locally and keep track of them using Git and then you upload your new " +"history back to GitHub." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:32 @@ -1419,18 +1335,18 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:24 msgid "" -"A fork is a personal copy of a GitHub repository. To create one for Flower, " -"you must navigate to ``_ (while connected to " -"your GitHub account) and click the ``Fork`` button situated on the top right " -"of the page." +"A fork is a personal copy of a GitHub repository. To create one for " +"Flower, you must navigate to ``_ (while " +"connected to your GitHub account) and click the ``Fork`` button situated " +"on the top right of the page." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:29 msgid "" "You can change the name if you want, but this is not necessary as this " -"version of Flower will be yours and will sit inside your own account (i.e., " -"in your own list of repositories). Once created, you should see on the top " -"left corner that you are looking at your own version of Flower." +"version of Flower will be yours and will sit inside your own account " +"(i.e., in your own list of repositories). Once created, you should see on" +" the top left corner that you are looking at your own version of Flower." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:47 @@ -1440,9 +1356,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:35 msgid "" "The next step is to download the forked repository on your machine to be " -"able to make changes to it. On your forked repository page, you should first " -"click on the ``Code`` button on the right, this will give you the ability to " -"copy the HTTPS link of the repository." +"able to make changes to it. On your forked repository page, you should " +"first click on the ``Code`` button on the right, this will give you the " +"ability to copy the HTTPS link of the repository." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:41 @@ -1453,8 +1369,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "" -"This will create a ``flower/`` (or the name of your fork if you renamed it) " -"folder in the current working directory." +"This will create a ``flower/`` (or the name of your fork if you renamed " +"it) folder in the current working directory." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:66 @@ -1467,10 +1383,10 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:56 msgid "" -"And here we will need to add an origin to our repository. The origin is the " -"\\ of the remote fork repository. To obtain it, we can do as " -"previously mentioned by going to our fork repository on our GitHub account " -"and copying the link." +"And here we will need to add an origin to our repository. The origin is " +"the \\ of the remote fork repository. To obtain it, we can do as " +"previously mentioned by going to our fork repository on our GitHub " +"account and copying the link." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:61 @@ -1490,16 +1406,16 @@ msgid "" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:76 -msgid "" -"The following diagram visually explains what we did in the previous steps:" +msgid "The following diagram visually explains what we did in the previous steps:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:80 msgid "" -"The upstream is the GitHub remote address of the parent repository (in this " -"case Flower), i.e. the one we eventually want to contribute to and therefore " -"need an up-to-date history of. The origin is just the GitHub remote address " -"of the forked repository we created, i.e. the copy (fork) in our own account." +"The upstream is the GitHub remote address of the parent repository (in " +"this case Flower), i.e. the one we eventually want to contribute to and " +"therefore need an up-to-date history of. The origin is just the GitHub " +"remote address of the forked repository we created, i.e. the copy (fork) " +"in our own account." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:84 @@ -1515,9 +1431,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:95 msgid "" "This can be achieved by following this :doc:`getting started guide for " -"contributors ` (note that " -"you won't need to clone the repository). Once you are able to write code and " -"test it, you can finally start making changes!" +"contributors ` (note " +"that you won't need to clone the repository). Once you are able to write " +"code and test it, you can finally start making changes!" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:100 @@ -1526,7 +1442,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:102 msgid "" -"Before making any changes make sure you are up-to-date with your repository:" +"Before making any changes make sure you are up-to-date with your " +"repository:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:108 @@ -1539,13 +1456,15 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:115 msgid "" -"To make the history cleaner and easier to work with, it is good practice to " -"create a new branch for each feature/project that needs to be implemented." +"To make the history cleaner and easier to work with, it is good practice " +"to create a new branch for each feature/project that needs to be " +"implemented." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:118 msgid "" -"To do so, just run the following command inside the repository's directory:" +"To do so, just run the following command inside the repository's " +"directory:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 @@ -1553,8 +1472,7 @@ msgid "**Make changes**" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 -msgid "" -"Write great code and create wonderful changes using your favorite editor!" +msgid "Write great code and create wonderful changes using your favorite editor!" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:138 @@ -1563,9 +1481,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:128 msgid "" -"Don't forget to test and format your code! Otherwise your code won't be able " -"to be merged into the Flower repository. This is done so the codebase stays " -"consistent and easy to understand." +"Don't forget to test and format your code! Otherwise your code won't be " +"able to be merged into the Flower repository. This is done so the " +"codebase stays consistent and easy to understand." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:131 @@ -1578,8 +1496,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:141 msgid "" -"Before creating a commit that will update your history, you must specify to " -"Git which files it needs to take into account." +"Before creating a commit that will update your history, you must specify " +"to Git which files it needs to take into account." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:143 @@ -1588,9 +1506,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:149 msgid "" -"To check which files have been modified compared to the last version (last " -"commit) and to see which files are staged for commit, you can use the :code:" -"`git status` command." +"To check which files have been modified compared to the last version " +"(last commit) and to see which files are staged for commit, you can use " +"the :code:`git status` command." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:160 @@ -1605,9 +1523,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:159 msgid "" -"The \\ is there to explain to others what the commit does. " -"It should be written in an imperative style and be concise. An example would " -"be :code:`git commit -m \"Add images to README\"`." +"The \\ is there to explain to others what the commit " +"does. It should be written in an imperative style and be concise. An " +"example would be :code:`git commit -m \"Add images to README\"`." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:171 @@ -1616,9 +1534,9 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:163 msgid "" -"Once we have committed our changes, we have effectively updated our local " -"history, but GitHub has no way of knowing this unless we push our changes to " -"our origin's remote address:" +"Once we have committed our changes, we have effectively updated our local" +" history, but GitHub has no way of knowing this unless we push our " +"changes to our origin's remote address:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:170 @@ -1637,8 +1555,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:177 msgid "" -"Once you have pushed changes, on the GitHub webpage of your repository you " -"should see the following message:" +"Once you have pushed changes, on the GitHub webpage of your repository " +"you should see the following message:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:181 @@ -1652,29 +1570,29 @@ msgid "" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:187 -msgid "" -"At the top you have an explanation of which branch will be merged where:" +msgid "At the top you have an explanation of which branch will be merged where:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:191 msgid "" -"In this example you can see that the request is to merge the branch ``doc-" -"fixes`` from my forked repository to branch ``main`` from the Flower " -"repository." +"In this example you can see that the request is to merge the branch " +"``doc-fixes`` from my forked repository to branch ``main`` from the " +"Flower repository." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:193 msgid "" "The title should be changed to adhere to the :ref:`pr_title_format` " -"guidelines, otherwise it won't be possible to merge the PR. So in this case, " -"a correct title might be ``docs(framework:skip) Fix typos``." +"guidelines, otherwise it won't be possible to merge the PR. So in this " +"case, a correct title might be ``docs(framework:skip) Fix typos``." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:196 msgid "" -"The input box in the middle is there for you to describe what your PR does " -"and to link it to existing issues. We have placed comments (that won't be " -"rendered once the PR is opened) to guide you through the process." +"The input box in the middle is there for you to describe what your PR " +"does and to link it to existing issues. We have placed comments (that " +"won't be rendered once the PR is opened) to guide you through the " +"process." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:199 @@ -1684,14 +1602,14 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:201 msgid "" "At the bottom you will find the button to open the PR. This will notify " -"reviewers that a new PR has been opened and that they should look over it to " -"merge or to request changes." +"reviewers that a new PR has been opened and that they should look over it" +" to merge or to request changes." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:204 msgid "" -"If your PR is not yet ready for review, and you don't want to notify anyone, " -"you have the option to create a draft pull request:" +"If your PR is not yet ready for review, and you don't want to notify " +"anyone, you have the option to create a draft pull request:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:209 @@ -1701,8 +1619,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "" "Once the PR has been opened (as draft or not), you can still push new " -"commits to it the same way we did before, by making changes to the branch " -"associated with the PR." +"commits to it the same way we did before, by making changes to the branch" +" associated with the PR." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:231 @@ -1711,14 +1629,14 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:212 msgid "" -"Once the PR has been opened or once the draft PR has been marked as ready, a " -"review from code owners will be automatically requested:" +"Once the PR has been opened or once the draft PR has been marked as " +"ready, a review from code owners will be automatically requested:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:216 msgid "" -"Code owners will then look into the code, ask questions, request changes or " -"validate the PR." +"Code owners will then look into the code, ask questions, request changes " +"or validate the PR." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:218 @@ -1727,8 +1645,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:222 msgid "" -"To resolve them, just push the necessary changes to the branch associated " -"with the PR:" +"To resolve them, just push the necessary changes to the branch associated" +" with the PR:" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:226 @@ -1737,7 +1655,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:230 msgid "" -"Once all the conversations have been resolved, you can re-request a review." +"Once all the conversations have been resolved, you can re-request a " +"review." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:251 @@ -1746,8 +1665,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:234 msgid "" -"If all the automatic tests have passed and reviewers have no more changes to " -"request, they can approve the PR and merge it." +"If all the automatic tests have passed and reviewers have no more changes" +" to request, they can approve the PR and merge it." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:238 @@ -1770,14 +1689,14 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:259 msgid "" -"For our documentation, we've started to use the `Diàtaxis framework `_." +"For our documentation, we've started to use the `Diàtaxis framework " +"`_." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:261 msgid "" -"Our \"How to\" guides should have titles that continue the sentence \"How to " -"…\", for example, \"How to upgrade to Flower 1.0\"." +"Our \"How to\" guides should have titles that continue the sentence \"How" +" to …\", for example, \"How to upgrade to Flower 1.0\"." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:263 @@ -1788,8 +1707,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:265 msgid "" -"This issue is about changing the title of a doc from present continuous to " -"present simple." +"This issue is about changing the title of a doc from present continuous " +"to present simple." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:267 @@ -1828,8 +1747,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:280 msgid "" -"Build the docs and `check the result `_" +"Build the docs and `check the result `_" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:283 @@ -1838,10 +1757,10 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:285 msgid "" -"You might have noticed that the file name still reflects the old wording. If " -"we just change the file, then we break all existing links to it - it is " -"**very important** to avoid that, breaking links can harm our search engine " -"ranking." +"You might have noticed that the file name still reflects the old wording." +" If we just change the file, then we break all existing links to it - it " +"is **very important** to avoid that, breaking links can harm our search " +"engine ranking." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:288 @@ -1858,8 +1777,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:293 msgid "" -"This will cause a redirect from ``saving-progress.html`` to ``save-progress." -"html``, old links will continue to work." +"This will cause a redirect from ``saving-progress.html`` to ``save-" +"progress.html``, old links will continue to work." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:296 @@ -1883,8 +1802,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:306 msgid "" -"Commit the changes (commit messages are always imperative: \"Do something\", " -"in this case \"Change …\")" +"Commit the changes (commit messages are always imperative: \"Do " +"something\", in this case \"Change …\")" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:307 @@ -1893,8 +1812,8 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:308 msgid "" -"Open a PR (as shown above) with title ``docs(framework) Update how-to guide " -"title``" +"Open a PR (as shown above) with title ``docs(framework) Update how-to " +"guide title``" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:309 @@ -1916,14 +1835,15 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:316 msgid "" -"Once you have made your first PR, and want to contribute more, be sure to " -"check out the following :" +"Once you have made your first PR, and want to contribute more, be sure to" +" check out the following :" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:318 msgid "" -":doc:`Good first contributions `, " -"where you should particularly look into the :code:`baselines` contributions." +":doc:`Good first contributions `, where you should particularly look into the " +":code:`baselines` contributions." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:322 @@ -1941,16 +1861,17 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:335 msgid "" -"(or ``(:skip) `` to ignore the PR in the changelog)" +"(or ``(:skip) `` to ignore the PR in the " +"changelog)" msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:337 msgid "" -"Where ```` needs to be in ``{ci, fix, feat, docs, refactor, break}``, " -"```` should be in ``{framework, baselines, datasets, examples, or " -"'*' when modifying multiple projects which requires the ':skip' flag to be " -"used}``, and ```` starts with a capitalised verb in the imperative " -"mood." +"Where ```` needs to be in ``{ci, fix, feat, docs, refactor, " +"break}``, ```` should be in ``{framework, baselines, datasets, " +"examples, or '*' when modifying multiple projects which requires the " +"':skip' flag to be used}``, and ```` starts with a capitalised " +"verb in the imperative mood." msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:341 @@ -2004,7 +1925,7 @@ msgid "Get started as a contributor" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 -#: ../../source/how-to-run-flower-using-docker.rst:132 +#: ../../source/how-to-run-flower-using-docker.rst:153 msgid "Prerequisites" msgstr "" @@ -2021,15 +1942,14 @@ msgid "(Optional) `pyenv `_" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:10 -msgid "" -"(Optional) `pyenv-virtualenv `_" +msgid "(Optional) `pyenv-virtualenv `_" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:12 msgid "" "Flower uses :code:`pyproject.toml` to manage dependencies and configure " -"development tools (the ones which support it). Poetry is a build tool which " -"supports `PEP 517 `_." +"development tools (the ones which support it). Poetry is a build tool " +"which supports `PEP 517 `_." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:18 @@ -2050,14 +1970,14 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:27 msgid "" -"Install `homebrew `_. Don't forget the post-installation " -"actions to add `brew` to your PATH." +"Install `homebrew `_. Don't forget the post-" +"installation actions to add `brew` to your PATH." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:28 msgid "" -"Install `xz` (to install different Python versions) and `pandoc` to build " -"the docs::" +"Install `xz` (to install different Python versions) and `pandoc` to build" +" the docs::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:34 @@ -2066,8 +1986,8 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:35 msgid "" -"Ensure you system (Ubuntu 22.04+) is up-to-date, and you have all necessary " -"packages::" +"Ensure you system (Ubuntu 22.04+) is up-to-date, and you have all " +"necessary packages::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:44 @@ -2082,31 +2002,31 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:52 msgid "" -"Let's create the Python environment for all-things Flower. If you wish to " -"use :code:`pyenv`, we provide two convenience scripts that you can use. If " -"you prefer using something else than :code:`pyenv`, create a new " +"Let's create the Python environment for all-things Flower. If you wish to" +" use :code:`pyenv`, we provide two convenience scripts that you can use. " +"If you prefer using something else than :code:`pyenv`, create a new " "environment, activate and skip to the last point where all packages are " "installed." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:54 msgid "" -"If you don't have :code:`pyenv` installed, the following script that will " -"install it, set it up, and create the virtual environment (with :code:" -"`Python 3.8.17` by default)::" +"If you don't have :code:`pyenv` installed, the following script that will" +" install it, set it up, and create the virtual environment (with " +":code:`Python 3.8.17` by default)::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:58 msgid "" "If you already have :code:`pyenv` installed (along with the :code:`pyenv-" -"virtualenv` plugin), you can use the following convenience script (with :" -"code:`Python 3.8.17` by default)::" +"virtualenv` plugin), you can use the following convenience script (with " +":code:`Python 3.8.17` by default)::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:62 msgid "" -"3. Install the Flower package in development mode (think :code:`pip install -" -"e`) along with all necessary dependencies::" +"3. Install the Flower package in development mode (think :code:`pip " +"install -e`) along with all necessary dependencies::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:69 @@ -2116,9 +2036,9 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:71 msgid "" "The Flower repository contains a number of convenience scripts to make " -"recurring development tasks easier and less error-prone. See the :code:`/" -"dev` subdirectory for a full list. The following scripts are amongst the " -"most important ones:" +"recurring development tasks easier and less error-prone. See the " +":code:`/dev` subdirectory for a full list. The following scripts are " +"amongst the most important ones:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:77 @@ -2143,10 +2063,10 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:108 msgid "" -"Developers may integrate a pre-commit hook into their workflow utilizing the " -"`pre-commit `_ library. The pre-commit hook " -"is configured to execute two primary operations: ``./dev/format.sh`` and ``./" -"dev/test.sh`` scripts." +"Developers may integrate a pre-commit hook into their workflow utilizing " +"the `pre-commit `_ library. The pre-" +"commit hook is configured to execute two primary operations: " +"``./dev/format.sh`` and ``./dev/test.sh`` scripts." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:110 @@ -2154,27 +2074,26 @@ msgid "There are multiple ways developers can use this:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:112 -msgid "" -"Install the pre-commit hook to your local git directory by simply running:" +msgid "Install the pre-commit hook to your local git directory by simply running:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:118 msgid "" -"Each ``git commit`` will trigger the execution of formatting and linting/" -"test scripts." +"Each ``git commit`` will trigger the execution of formatting and " +"linting/test scripts." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:119 msgid "" -"If in a hurry, bypass the hook using ``--no-verify`` with the ``git commit`` " -"command. ::" +"If in a hurry, bypass the hook using ``--no-verify`` with the ``git " +"commit`` command. ::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:124 msgid "" "For developers who prefer not to install the hook permanently, it is " -"possible to execute a one-time check prior to committing changes by using " -"the following command:" +"possible to execute a one-time check prior to committing changes by using" +" the following command:" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:130 @@ -2189,10 +2108,10 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:135 msgid "" -"Developers could run the full set of Github Actions workflows under their " -"local environment by using `Act `_. Please " -"refer to the installation instructions under the linked repository and run " -"the next command under Flower main cloned repository folder::" +"Developers could run the full set of Github Actions workflows under their" +" local environment by using `Act `_. " +"Please refer to the installation instructions under the linked repository" +" and run the next command under Flower main cloned repository folder::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:142 @@ -2207,14 +2126,14 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:149 msgid "" -"Flower uses Poetry to build releases. The necessary command is wrapped in a " -"simple script::" +"Flower uses Poetry to build releases. The necessary command is wrapped in" +" a simple script::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:154 msgid "" -"The resulting :code:`.whl` and :code:`.tar.gz` releases will be stored in " -"the :code:`/dist` subdirectory." +"The resulting :code:`.whl` and :code:`.tar.gz` releases will be stored in" +" the :code:`/dist` subdirectory." msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:159 @@ -2223,9 +2142,9 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:161 msgid "" -"Flower's documentation uses `Sphinx `_. There's " -"no convenience script to re-build the documentation yet, but it's pretty " -"easy::" +"Flower's documentation uses `Sphinx `_. " +"There's no convenience script to re-build the documentation yet, but it's" +" pretty easy::" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:167 @@ -2238,13 +2157,14 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:4 msgid "" -"This tutorial will show you how to use Flower to build a federated version " -"of an existing machine learning workload with `FedBN `_, a federated training strategy designed for non-iid data. We " -"are using PyTorch to train a Convolutional Neural Network(with Batch " -"Normalization layers) on the CIFAR-10 dataset. When applying FedBN, only few " -"changes needed compared to :doc:`Example: PyTorch - From Centralized To " -"Federated `." +"This tutorial will show you how to use Flower to build a federated " +"version of an existing machine learning workload with `FedBN " +"`_, a federated training strategy " +"designed for non-iid data. We are using PyTorch to train a Convolutional " +"Neural Network(with Batch Normalization layers) on the CIFAR-10 dataset. " +"When applying FedBN, only few changes needed compared to :doc:`Example: " +"PyTorch - From Centralized To Federated `." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:9 @@ -2254,10 +2174,10 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:10 msgid "" -"All files are revised based on :doc:`Example: PyTorch - From Centralized To " -"Federated `. The only thing " -"to do is modifying the file called :code:`cifar.py`, revised part is shown " -"below:" +"All files are revised based on :doc:`Example: PyTorch - From Centralized " +"To Federated `. The only " +"thing to do is modifying the file called :code:`cifar.py`, revised part " +"is shown below:" msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:13 @@ -2273,10 +2193,10 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:47 msgid "" -"So far this should all look fairly familiar if you've used PyTorch before. " -"Let's take the next step and use what we've built to create a federated " -"learning system within FedBN, the system consists of one server and two " -"clients." +"So far this should all look fairly familiar if you've used PyTorch " +"before. Let's take the next step and use what we've built to create a " +"federated learning system within FedBN, the system consists of one server" +" and two clients." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:51 @@ -2287,25 +2207,25 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:53 msgid "" "If you have read :doc:`Example: PyTorch - From Centralized To Federated " -"`, the following parts are " -"easy to follow, only :code:`get_parameters` and :code:`set_parameters` " -"function in :code:`client.py` needed to revise. If not, please read the :doc:" -"`Example: PyTorch - From Centralized To Federated `. first." +"`, the following parts are" +" easy to follow, only :code:`get_parameters` and :code:`set_parameters` " +"function in :code:`client.py` needed to revise. If not, please read the " +":doc:`Example: PyTorch - From Centralized To Federated `. first." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:56 msgid "" -"Our example consists of one *server* and two *clients*. In FedBN, :code:" -"`server.py` keeps unchanged, we can start the server directly." +"Our example consists of one *server* and two *clients*. In FedBN, " +":code:`server.py` keeps unchanged, we can start the server directly." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:62 msgid "" -"Finally, we will revise our *client* logic by changing :code:" -"`get_parameters` and :code:`set_parameters` in :code:`client.py`, we will " -"exclude batch normalization parameters from model parameter list when " -"sending to or receiving from the server." +"Finally, we will revise our *client* logic by changing " +":code:`get_parameters` and :code:`set_parameters` in :code:`client.py`, " +"we will exclude batch normalization parameters from model parameter list " +"when sending to or receiving from the server." msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:85 @@ -2314,9 +2234,9 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:91 msgid "" -"in each window (make sure that the server is still running before you do so) " -"and see your (previously centralized) PyTorch project run federated learning " -"with FedBN strategy across two clients. Congratulations!" +"in each window (make sure that the server is still running before you do " +"so) and see your (previously centralized) PyTorch project run federated " +"learning with FedBN strategy across two clients. Congratulations!" msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:94 @@ -2328,12 +2248,13 @@ msgstr "" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:96 msgid "" -"The full source code for this example can be found `here `_. Our " -"example is of course somewhat over-simplified because both clients load the " -"exact same dataset, which isn't realistic. You're now prepared to explore " -"this topic further. How about using different subsets of CIFAR-10 on each " -"client? How about adding more clients?" +"The full source code for this example can be found `here " +"`_. Our example is of course somewhat over-" +"simplified because both clients load the exact same dataset, which isn't " +"realistic. You're now prepared to explore this topic further. How about " +"using different subsets of CIFAR-10 on each client? How about adding more" +" clients?" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:2 @@ -2343,22 +2264,23 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:4 #: ../../source/tutorial-quickstart-jax.rst:10 msgid "" -"This tutorial will show you how to use Flower to build a federated version " -"of an existing JAX workload. We are using JAX to train a linear regression " -"model on a scikit-learn dataset. We will structure the example similar to " -"our `PyTorch - From Centralized To Federated `_ walkthrough. " -"First, we build a centralized training approach based on the `Linear " -"Regression with JAX `_ tutorial`. Then, we build upon the centralized " -"training code to run the training in a federated fashion." +"This tutorial will show you how to use Flower to build a federated " +"version of an existing JAX workload. We are using JAX to train a linear " +"regression model on a scikit-learn dataset. We will structure the example" +" similar to our `PyTorch - From Centralized To Federated " +"`_ walkthrough. First, we build a centralized " +"training approach based on the `Linear Regression with JAX " +"`_" +" tutorial`. Then, we build upon the centralized training code to run the " +"training in a federated fashion." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:10 #: ../../source/tutorial-quickstart-jax.rst:16 msgid "" -"Before we start building our JAX example, we need install the packages :code:" -"`jax`, :code:`jaxlib`, :code:`scikit-learn`, and :code:`flwr`:" +"Before we start building our JAX example, we need install the packages " +":code:`jax`, :code:`jaxlib`, :code:`scikit-learn`, and :code:`flwr`:" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:18 @@ -2369,10 +2291,10 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:20 #: ../../source/tutorial-quickstart-jax.rst:26 msgid "" -"We begin with a brief description of the centralized training code based on " -"a :code:`Linear Regression` model. If you want a more in-depth explanation " -"of what's going on then have a look at the official `JAX documentation " -"`_." +"We begin with a brief description of the centralized training code based " +"on a :code:`Linear Regression` model. If you want a more in-depth " +"explanation of what's going on then have a look at the official `JAX " +"documentation `_." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:23 @@ -2380,51 +2302,53 @@ msgstr "" msgid "" "Let's create a new file called :code:`jax_training.py` with all the " "components required for a traditional (centralized) linear regression " -"training. First, the JAX packages :code:`jax` and :code:`jaxlib` need to be " -"imported. In addition, we need to import :code:`sklearn` since we use :code:" -"`make_regression` for the dataset and :code:`train_test_split` to split the " -"dataset into a training and test set. You can see that we do not yet import " -"the :code:`flwr` package for federated learning. This will be done later." +"training. First, the JAX packages :code:`jax` and :code:`jaxlib` need to " +"be imported. In addition, we need to import :code:`sklearn` since we use " +":code:`make_regression` for the dataset and :code:`train_test_split` to " +"split the dataset into a training and test set. You can see that we do " +"not yet import the :code:`flwr` package for federated learning. This will" +" be done later." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:37 #: ../../source/tutorial-quickstart-jax.rst:43 msgid "" -"The :code:`load_data()` function loads the mentioned training and test sets." +"The :code:`load_data()` function loads the mentioned training and test " +"sets." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:47 #: ../../source/tutorial-quickstart-jax.rst:53 msgid "" -"The model architecture (a very simple :code:`Linear Regression` model) is " -"defined in :code:`load_model()`." +"The model architecture (a very simple :code:`Linear Regression` model) is" +" defined in :code:`load_model()`." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:59 #: ../../source/tutorial-quickstart-jax.rst:65 msgid "" -"We now need to define the training (function :code:`train()`), which loops " -"over the training set and measures the loss (function :code:`loss_fn()`) for " -"each batch of training examples. The loss function is separate since JAX " -"takes derivatives with a :code:`grad()` function (defined in the :code:" -"`main()` function and called in :code:`train()`)." +"We now need to define the training (function :code:`train()`), which " +"loops over the training set and measures the loss (function " +":code:`loss_fn()`) for each batch of training examples. The loss function" +" is separate since JAX takes derivatives with a :code:`grad()` function " +"(defined in the :code:`main()` function and called in :code:`train()`)." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:77 #: ../../source/tutorial-quickstart-jax.rst:83 msgid "" -"The evaluation of the model is defined in the function :code:`evaluation()`. " -"The function takes all test examples and measures the loss of the linear " -"regression model." +"The evaluation of the model is defined in the function " +":code:`evaluation()`. The function takes all test examples and measures " +"the loss of the linear regression model." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:88 #: ../../source/tutorial-quickstart-jax.rst:94 msgid "" "Having defined the data loading, model architecture, training, and " -"evaluation we can put everything together and train our model using JAX. As " -"already mentioned, the :code:`jax.grad()` function is defined in :code:" -"`main()` and passed to :code:`train()`." +"evaluation we can put everything together and train our model using JAX. " +"As already mentioned, the :code:`jax.grad()` function is defined in " +":code:`main()` and passed to :code:`train()`." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:105 @@ -2435,9 +2359,9 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:111 #: ../../source/tutorial-quickstart-jax.rst:117 msgid "" -"So far this should all look fairly familiar if you've used JAX before. Let's " -"take the next step and use what we've built to create a simple federated " -"learning system consisting of one server and two clients." +"So far this should all look fairly familiar if you've used JAX before. " +"Let's take the next step and use what we've built to create a simple " +"federated learning system consisting of one server and two clients." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:115 @@ -2448,24 +2372,24 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:117 #: ../../source/tutorial-quickstart-jax.rst:123 msgid "" -"The concept of federating an existing workload is always the same and easy " -"to understand. We have to start a *server* and then use the code in :code:" -"`jax_training.py` for the *clients* that are connected to the *server*. The " -"*server* sends model parameters to the clients. The *clients* run the " -"training and update the parameters. The updated parameters are sent back to " -"the *server*, which averages all received parameter updates. This describes " -"one round of the federated learning process, and we repeat this for multiple " -"rounds." +"The concept of federating an existing workload is always the same and " +"easy to understand. We have to start a *server* and then use the code in " +":code:`jax_training.py` for the *clients* that are connected to the " +"*server*. The *server* sends model parameters to the clients. The " +"*clients* run the training and update the parameters. The updated " +"parameters are sent back to the *server*, which averages all received " +"parameter updates. This describes one round of the federated learning " +"process, and we repeat this for multiple rounds." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:123 #: ../../source/example-pytorch-from-centralized-to-federated.rst:181 #: ../../source/tutorial-quickstart-jax.rst:129 msgid "" -"Our example consists of one *server* and two *clients*. Let's set up :code:" -"`server.py` first. The *server* needs to import the Flower package :code:" -"`flwr`. Next, we use the :code:`start_server` function to start a server and " -"tell it to perform three rounds of federated learning." +"Our example consists of one *server* and two *clients*. Let's set up " +":code:`server.py` first. The *server* needs to import the Flower package " +":code:`flwr`. Next, we use the :code:`start_server` function to start a " +"server and tell it to perform three rounds of federated learning." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:133 @@ -2477,24 +2401,25 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:139 #: ../../source/tutorial-quickstart-jax.rst:145 msgid "" -"Finally, we will define our *client* logic in :code:`client.py` and build " -"upon the previously defined JAX training in :code:`jax_training.py`. Our " -"*client* needs to import :code:`flwr`, but also :code:`jax` and :code:" -"`jaxlib` to update the parameters on our JAX model:" +"Finally, we will define our *client* logic in :code:`client.py` and build" +" upon the previously defined JAX training in :code:`jax_training.py`. Our" +" *client* needs to import :code:`flwr`, but also :code:`jax` and " +":code:`jaxlib` to update the parameters on our JAX model:" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:154 #: ../../source/tutorial-quickstart-jax.rst:160 msgid "" -"Implementing a Flower *client* basically means implementing a subclass of " -"either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. Our " -"implementation will be based on :code:`flwr.client.NumPyClient` and we'll " -"call it :code:`FlowerClient`. :code:`NumPyClient` is slightly easier to " -"implement than :code:`Client` if you use a framework with good NumPy " -"interoperability (like JAX) because it avoids some of the boilerplate that " -"would otherwise be necessary. :code:`FlowerClient` needs to implement four " -"methods, two methods for getting/setting model parameters, one method for " -"training the model, and one method for testing the model:" +"Implementing a Flower *client* basically means implementing a subclass of" +" either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. " +"Our implementation will be based on :code:`flwr.client.NumPyClient` and " +"we'll call it :code:`FlowerClient`. :code:`NumPyClient` is slightly " +"easier to implement than :code:`Client` if you use a framework with good " +"NumPy interoperability (like JAX) because it avoids some of the " +"boilerplate that would otherwise be necessary. :code:`FlowerClient` needs" +" to implement four methods, two methods for getting/setting model " +"parameters, one method for training the model, and one method for testing" +" the model:" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:161 @@ -2506,7 +2431,8 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 #: ../../source/tutorial-quickstart-jax.rst:166 msgid "" -"set the model parameters on the local model that are received from the server" +"set the model parameters on the local model that are received from the " +"server" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:161 @@ -2518,8 +2444,8 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:220 #: ../../source/tutorial-quickstart-jax.rst:168 msgid "" -"loop over the list of model parameters received as NumPy :code:`ndarray`'s " -"(think list of neural network layers)" +"loop over the list of model parameters received as NumPy " +":code:`ndarray`'s (think list of neural network layers)" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:163 @@ -2534,8 +2460,8 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:222 #: ../../source/tutorial-quickstart-jax.rst:170 msgid "" -"get the model parameters and return them as a list of NumPy :code:" -"`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" +"get the model parameters and return them as a list of NumPy " +":code:`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:167 @@ -2553,8 +2479,8 @@ msgstr "" #: ../../source/tutorial-quickstart-jax.rst:172 #: ../../source/tutorial-quickstart-jax.rst:176 msgid "" -"update the parameters of the local model with the parameters received from " -"the server" +"update the parameters of the local model with the parameters received " +"from the server" msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:167 @@ -2590,20 +2516,21 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:174 #: ../../source/tutorial-quickstart-jax.rst:180 msgid "" -"The challenging part is to transform the JAX model parameters from :code:" -"`DeviceArray` to :code:`NumPy ndarray` to make them compatible with " -"`NumPyClient`." +"The challenging part is to transform the JAX model parameters from " +":code:`DeviceArray` to :code:`NumPy ndarray` to make them compatible with" +" `NumPyClient`." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:176 #: ../../source/tutorial-quickstart-jax.rst:182 msgid "" -"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make " -"use of the functions :code:`train()` and :code:`evaluate()` previously " +"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make" +" use of the functions :code:`train()` and :code:`evaluate()` previously " "defined in :code:`jax_training.py`. So what we really do here is we tell " -"Flower through our :code:`NumPyClient` subclass which of our already defined " -"functions to call for training and evaluation. We included type annotations " -"to give you a better understanding of the data types that get passed around." +"Flower through our :code:`NumPyClient` subclass which of our already " +"defined functions to call for training and evaluation. We included type " +"annotations to give you a better understanding of the data types that get" +" passed around." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:245 @@ -2620,8 +2547,8 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:274 #: ../../source/tutorial-quickstart-jax.rst:280 msgid "" -"in each window (make sure that the server is still running before you do so) " -"and see your JAX project run federated learning across two clients. " +"in each window (make sure that the server is still running before you do " +"so) and see your JAX project run federated learning across two clients. " "Congratulations!" msgstr "" @@ -2629,16 +2556,16 @@ msgstr "" #: ../../source/tutorial-quickstart-jax.rst:285 msgid "" "The source code of this example was improved over time and can be found " -"here: `Quickstart JAX `_. Our example is somewhat over-simplified because both " +"here: `Quickstart JAX `_. Our example is somewhat over-simplified because both " "clients load the same dataset." msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:282 #: ../../source/tutorial-quickstart-jax.rst:288 msgid "" -"You're now prepared to explore this topic further. How about using a more " -"sophisticated model or using a different dataset? How about adding more " +"You're now prepared to explore this topic further. How about using a more" +" sophisticated model or using a different dataset? How about adding more " "clients?" msgstr "" @@ -2648,31 +2575,32 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:4 msgid "" -"This tutorial will show you how to use Flower to build a federated version " -"of an existing machine learning workload. We are using PyTorch to train a " -"Convolutional Neural Network on the CIFAR-10 dataset. First, we introduce " -"this machine learning task with a centralized training approach based on the " -"`Deep Learning with PyTorch `_ tutorial. Then, we build upon the centralized " -"training code to run the training in a federated fashion." +"This tutorial will show you how to use Flower to build a federated " +"version of an existing machine learning workload. We are using PyTorch to" +" train a Convolutional Neural Network on the CIFAR-10 dataset. First, we " +"introduce this machine learning task with a centralized training approach" +" based on the `Deep Learning with PyTorch " +"`_ " +"tutorial. Then, we build upon the centralized training code to run the " +"training in a federated fashion." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:12 msgid "" -"We begin with a brief description of the centralized CNN training code. If " -"you want a more in-depth explanation of what's going on then have a look at " -"the official `PyTorch tutorial `_." +"We begin with a brief description of the centralized CNN training code. " +"If you want a more in-depth explanation of what's going on then have a " +"look at the official `PyTorch tutorial " +"`_." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:15 msgid "" "Let's create a new file called :code:`cifar.py` with all the components " -"required for a traditional (centralized) training on CIFAR-10. First, all " -"required packages (such as :code:`torch` and :code:`torchvision`) need to be " -"imported. You can see that we do not import any package for federated " -"learning. You can keep all these imports as they are even when we add the " -"federated learning components at a later point." +"required for a traditional (centralized) training on CIFAR-10. First, all" +" required packages (such as :code:`torch` and :code:`torchvision`) need " +"to be imported. You can see that we do not import any package for " +"federated learning. You can keep all these imports as they are even when " +"we add the federated learning components at a later point." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:32 @@ -2684,22 +2612,22 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:56 msgid "" -"The :code:`load_data()` function loads the CIFAR-10 training and test sets. " -"The :code:`transform` normalized the data after loading." +"The :code:`load_data()` function loads the CIFAR-10 training and test " +"sets. The :code:`transform` normalized the data after loading." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:74 msgid "" -"We now need to define the training (function :code:`train()`) which loops " -"over the training set, measures the loss, backpropagates it, and then takes " -"one optimizer step for each batch of training examples." +"We now need to define the training (function :code:`train()`) which loops" +" over the training set, measures the loss, backpropagates it, and then " +"takes one optimizer step for each batch of training examples." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:76 msgid "" -"The evaluation of the model is defined in the function :code:`test()`. The " -"function loops over all test samples and measures the loss of the model " -"based on the test dataset." +"The evaluation of the model is defined in the function :code:`test()`. " +"The function loops over all test samples and measures the loss of the " +"model based on the test dataset." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:136 @@ -2710,59 +2638,60 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:163 msgid "" -"So far, this should all look fairly familiar if you've used PyTorch before. " -"Let's take the next step and use what we've built to create a simple " -"federated learning system consisting of one server and two clients." +"So far, this should all look fairly familiar if you've used PyTorch " +"before. Let's take the next step and use what we've built to create a " +"simple federated learning system consisting of one server and two " +"clients." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:169 msgid "" -"The simple machine learning project discussed in the previous section trains " -"the model on a single dataset (CIFAR-10), we call this centralized learning. " -"This concept of centralized learning, as shown in the previous section, is " -"probably known to most of you, and many of you have used it previously. " -"Normally, if you'd want to run machine learning workloads in a federated " -"fashion, then you'd have to change most of your code and set everything up " -"from scratch. This can be a considerable effort." +"The simple machine learning project discussed in the previous section " +"trains the model on a single dataset (CIFAR-10), we call this centralized" +" learning. This concept of centralized learning, as shown in the previous" +" section, is probably known to most of you, and many of you have used it " +"previously. Normally, if you'd want to run machine learning workloads in " +"a federated fashion, then you'd have to change most of your code and set " +"everything up from scratch. This can be a considerable effort." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:173 msgid "" -"However, with Flower you can evolve your pre-existing code into a federated " -"learning setup without the need for a major rewrite." +"However, with Flower you can evolve your pre-existing code into a " +"federated learning setup without the need for a major rewrite." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:175 msgid "" -"The concept is easy to understand. We have to start a *server* and then use " -"the code in :code:`cifar.py` for the *clients* that are connected to the " -"*server*. The *server* sends model parameters to the clients. The *clients* " -"run the training and update the parameters. The updated parameters are sent " -"back to the *server* which averages all received parameter updates. This " -"describes one round of the federated learning process and we repeat this for " -"multiple rounds." +"The concept is easy to understand. We have to start a *server* and then " +"use the code in :code:`cifar.py` for the *clients* that are connected to " +"the *server*. The *server* sends model parameters to the clients. The " +"*clients* run the training and update the parameters. The updated " +"parameters are sent back to the *server* which averages all received " +"parameter updates. This describes one round of the federated learning " +"process and we repeat this for multiple rounds." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:197 msgid "" -"Finally, we will define our *client* logic in :code:`client.py` and build " -"upon the previously defined centralized training in :code:`cifar.py`. Our " -"*client* needs to import :code:`flwr`, but also :code:`torch` to update the " -"parameters on our PyTorch model:" +"Finally, we will define our *client* logic in :code:`client.py` and build" +" upon the previously defined centralized training in :code:`cifar.py`. " +"Our *client* needs to import :code:`flwr`, but also :code:`torch` to " +"update the parameters on our PyTorch model:" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:213 msgid "" -"Implementing a Flower *client* basically means implementing a subclass of " -"either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. Our " -"implementation will be based on :code:`flwr.client.NumPyClient` and we'll " -"call it :code:`CifarClient`. :code:`NumPyClient` is slightly easier to " -"implement than :code:`Client` if you use a framework with good NumPy " -"interoperability (like PyTorch or TensorFlow/Keras) because it avoids some " -"of the boilerplate that would otherwise be necessary. :code:`CifarClient` " -"needs to implement four methods, two methods for getting/setting model " -"parameters, one method for training the model, and one method for testing " -"the model:" +"Implementing a Flower *client* basically means implementing a subclass of" +" either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. " +"Our implementation will be based on :code:`flwr.client.NumPyClient` and " +"we'll call it :code:`CifarClient`. :code:`NumPyClient` is slightly easier" +" to implement than :code:`Client` if you use a framework with good NumPy " +"interoperability (like PyTorch or TensorFlow/Keras) because it avoids " +"some of the boilerplate that would otherwise be necessary. " +":code:`CifarClient` needs to implement four methods, two methods for " +"getting/setting model parameters, one method for training the model, and " +"one method for testing the model:" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 @@ -2779,39 +2708,40 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:232 msgid "" -"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make " -"use of the functions :code:`train()` and :code:`test()` previously defined " -"in :code:`cifar.py`. So what we really do here is we tell Flower through " -"our :code:`NumPyClient` subclass which of our already defined functions to " -"call for training and evaluation. We included type annotations to give you a " -"better understanding of the data types that get passed around." +"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make" +" use of the functions :code:`train()` and :code:`test()` previously " +"defined in :code:`cifar.py`. So what we really do here is we tell Flower " +"through our :code:`NumPyClient` subclass which of our already defined " +"functions to call for training and evaluation. We included type " +"annotations to give you a better understanding of the data types that get" +" passed around." msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:280 msgid "" "All that's left to do it to define a function that loads both model and " -"data, creates a :code:`CifarClient`, and starts this client. You load your " -"data and model by using :code:`cifar.py`. Start :code:`CifarClient` with the " -"function :code:`fl.client.start_client()` by pointing it at the same IP " -"address we used in :code:`server.py`:" +"data, creates a :code:`CifarClient`, and starts this client. You load " +"your data and model by using :code:`cifar.py`. Start :code:`CifarClient` " +"with the function :code:`fl.client.start_client()` by pointing it at the " +"same IP address we used in :code:`server.py`:" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:307 msgid "" -"in each window (make sure that the server is running before you do so) and " -"see your (previously centralized) PyTorch project run federated learning " -"across two clients. Congratulations!" +"in each window (make sure that the server is running before you do so) " +"and see your (previously centralized) PyTorch project run federated " +"learning across two clients. Congratulations!" msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:312 msgid "" "The full source code for this example: `PyTorch: From Centralized To " -"Federated (Code) `_. Our example is, of course, somewhat over-" -"simplified because both clients load the exact same dataset, which isn't " -"realistic. You're now prepared to explore this topic further. How about " -"using different subsets of CIFAR-10 on each client? How about adding more " -"clients?" +"Federated (Code) `_. Our example is, of course, " +"somewhat over-simplified because both clients load the exact same " +"dataset, which isn't realistic. You're now prepared to explore this topic" +" further. How about using different subsets of CIFAR-10 on each client? " +"How about adding more clients?" msgstr "" #: ../../source/explanation-differential-privacy.rst:2 @@ -2822,18 +2752,19 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:3 msgid "" -"The information in datasets like healthcare, financial transactions, user " -"preferences, etc., is valuable and has the potential for scientific " -"breakthroughs and provides important business insights. However, such data " -"is also sensitive and there is a risk of compromising individual privacy." +"The information in datasets like healthcare, financial transactions, user" +" preferences, etc., is valuable and has the potential for scientific " +"breakthroughs and provides important business insights. However, such " +"data is also sensitive and there is a risk of compromising individual " +"privacy." msgstr "" #: ../../source/explanation-differential-privacy.rst:6 msgid "" "Traditional methods like anonymization alone would not work because of " -"attacks like Re-identification and Data Linkage. That's where differential " -"privacy comes in. It provides the possibility of analyzing data while " -"ensuring the privacy of individuals." +"attacks like Re-identification and Data Linkage. That's where " +"differential privacy comes in. It provides the possibility of analyzing " +"data while ensuring the privacy of individuals." msgstr "" #: ../../source/explanation-differential-privacy.rst:12 @@ -2842,8 +2773,8 @@ msgid "" "instance, Alice's data). Differential Privacy (DP) guarantees that any " "analysis (M), like calculating the average income, will produce nearly " "identical results for both datasets (O and O' would be similar). This " -"preserves group patterns while obscuring individual details, ensuring the " -"individual's information remains hidden in the crowd." +"preserves group patterns while obscuring individual details, ensuring the" +" individual's information remains hidden in the crowd." msgstr "" #: ../../source/explanation-differential-privacy.rst:-1 @@ -2854,7 +2785,8 @@ msgstr "" msgid "" "One of the most commonly used mechanisms to achieve DP is adding enough " "noise to the output of the analysis to mask the contribution of each " -"individual in the data while preserving the overall accuracy of the analysis." +"individual in the data while preserving the overall accuracy of the " +"analysis." msgstr "" #: ../../source/explanation-differential-privacy.rst:25 @@ -2865,12 +2797,12 @@ msgstr "" msgid "" "Differential Privacy (DP) provides statistical guarantees against the " "information an adversary can infer through the output of a randomized " -"algorithm. It provides an unconditional upper bound on the influence of a " -"single individual on the output of the algorithm by adding noise [1]. A " -"randomized mechanism M provides (:math:`\\epsilon`, :math:`\\delta`)-" -"differential privacy if for any two neighboring databases, D :sub:`1` and D :" -"sub:`2`, that differ in only a single record, and for all possible outputs S " -"⊆ Range(A):" +"algorithm. It provides an unconditional upper bound on the influence of a" +" single individual on the output of the algorithm by adding noise [1]. A " +"randomized mechanism M provides (:math:`\\epsilon`, " +":math:`\\delta`)-differential privacy if for any two neighboring " +"databases, D :sub:`1` and D :sub:`2`, that differ in only a single " +"record, and for all possible outputs S ⊆ Range(A):" msgstr "" #: ../../source/explanation-differential-privacy.rst:32 @@ -2884,11 +2816,11 @@ msgid "" "The :math:`\\epsilon` parameter, also known as the privacy budget, is a " "metric of privacy loss. It also controls the privacy-utility trade-off; " "lower :math:`\\epsilon` values indicate higher levels of privacy but are " -"likely to reduce utility as well. The :math:`\\delta` parameter accounts for " -"a small probability on which the upper bound :math:`\\epsilon` does not " -"hold. The amount of noise needed to achieve differential privacy is " -"proportional to the sensitivity of the output, which measures the maximum " -"change in the output due to the inclusion or removal of a single record." +"likely to reduce utility as well. The :math:`\\delta` parameter accounts " +"for a small probability on which the upper bound :math:`\\epsilon` does " +"not hold. The amount of noise needed to achieve differential privacy is " +"proportional to the sensitivity of the output, which measures the maximum" +" change in the output due to the inclusion or removal of a single record." msgstr "" #: ../../source/explanation-differential-privacy.rst:45 @@ -2899,14 +2831,15 @@ msgstr "" msgid "" "DP can be utilized in machine learning to preserve the privacy of the " "training data. Differentially private machine learning algorithms are " -"designed in a way to prevent the algorithm to learn any specific information " -"about any individual data points and subsequently prevent the model from " -"revealing sensitive information. Depending on the stage at which noise is " -"introduced, various methods exist for applying DP to machine learning " -"algorithms. One approach involves adding noise to the training data (either " -"to the features or labels), while another method entails injecting noise " -"into the gradients of the loss function during model training. Additionally, " -"such noise can be incorporated into the model's output." +"designed in a way to prevent the algorithm to learn any specific " +"information about any individual data points and subsequently prevent the" +" model from revealing sensitive information. Depending on the stage at " +"which noise is introduced, various methods exist for applying DP to " +"machine learning algorithms. One approach involves adding noise to the " +"training data (either to the features or labels), while another method " +"entails injecting noise into the gradients of the loss function during " +"model training. Additionally, such noise can be incorporated into the " +"model's output." msgstr "" #: ../../source/explanation-differential-privacy.rst:53 @@ -2918,40 +2851,40 @@ msgid "" "Federated learning is a data minimization approach that allows multiple " "parties to collaboratively train a model without sharing their raw data. " "However, federated learning also introduces new privacy challenges. The " -"model updates between parties and the central server can leak information " -"about the local data. These leaks can be exploited by attacks such as " +"model updates between parties and the central server can leak information" +" about the local data. These leaks can be exploited by attacks such as " "membership inference and property inference attacks, or model inversion " "attacks." msgstr "" #: ../../source/explanation-differential-privacy.rst:58 msgid "" -"DP can play a crucial role in federated learning to provide privacy for the " -"clients' data." +"DP can play a crucial role in federated learning to provide privacy for " +"the clients' data." msgstr "" #: ../../source/explanation-differential-privacy.rst:60 msgid "" -"Depending on the granularity of privacy provision or the location of noise " -"addition, different forms of DP exist in federated learning. In this " -"explainer, we focus on two approaches of DP utilization in federated " -"learning based on where the noise is added: at the server (also known as the " -"center) or at the client (also known as the local)." +"Depending on the granularity of privacy provision or the location of " +"noise addition, different forms of DP exist in federated learning. In " +"this explainer, we focus on two approaches of DP utilization in federated" +" learning based on where the noise is added: at the server (also known as" +" the center) or at the client (also known as the local)." msgstr "" #: ../../source/explanation-differential-privacy.rst:63 msgid "" -"**Central Differential Privacy**: DP is applied by the server and the goal " -"is to prevent the aggregated model from leaking information about each " -"client's data." +"**Central Differential Privacy**: DP is applied by the server and the " +"goal is to prevent the aggregated model from leaking information about " +"each client's data." msgstr "" #: ../../source/explanation-differential-privacy.rst:65 msgid "" "**Local Differential Privacy**: DP is applied on the client side before " -"sending any information to the server and the goal is to prevent the updates " -"that are sent to the server from leaking any information about the client's " -"data." +"sending any information to the server and the goal is to prevent the " +"updates that are sent to the server from leaking any information about " +"the client's data." msgstr "" #: ../../source/explanation-differential-privacy.rst:-1 @@ -2962,24 +2895,24 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:69 msgid "" -"In this approach, which is also known as user-level DP, the central server " -"is responsible for adding noise to the globally aggregated parameters. It " -"should be noted that trust in the server is required." +"In this approach, which is also known as user-level DP, the central " +"server is responsible for adding noise to the globally aggregated " +"parameters. It should be noted that trust in the server is required." msgstr "" #: ../../source/explanation-differential-privacy.rst:76 msgid "" -"While there are various ways to implement central DP in federated learning, " -"we concentrate on the algorithms proposed by [2] and [3]. The overall " -"approach is to clip the model updates sent by the clients and add some " -"amount of noise to the aggregated model. In each iteration, a random set of " -"clients is chosen with a specific probability for training. Each client " -"performs local training on its own data. The update of each client is then " -"clipped by some value `S` (sensitivity `S`). This would limit the impact of " -"any individual client which is crucial for privacy and often beneficial for " -"robustness. A common approach to achieve this is by restricting the `L2` " -"norm of the clients' model updates, ensuring that larger updates are scaled " -"down to fit within the norm `S`." +"While there are various ways to implement central DP in federated " +"learning, we concentrate on the algorithms proposed by [2] and [3]. The " +"overall approach is to clip the model updates sent by the clients and add" +" some amount of noise to the aggregated model. In each iteration, a " +"random set of clients is chosen with a specific probability for training." +" Each client performs local training on its own data. The update of each " +"client is then clipped by some value `S` (sensitivity `S`). This would " +"limit the impact of any individual client which is crucial for privacy " +"and often beneficial for robustness. A common approach to achieve this is" +" by restricting the `L2` norm of the clients' model updates, ensuring " +"that larger updates are scaled down to fit within the norm `S`." msgstr "" #: ../../source/explanation-differential-privacy.rst:-1 @@ -2988,11 +2921,11 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:89 msgid "" -"Afterwards, the Gaussian mechanism is used to add noise in order to distort " -"the sum of all clients' updates. The amount of noise is scaled to the " -"sensitivity value to obtain a privacy guarantee. The Gaussian mechanism is " -"used with a noise sampled from `N (0, σ²)` where `σ = ( noise_scale * S ) / " -"(number of sampled clients)`." +"Afterwards, the Gaussian mechanism is used to add noise in order to " +"distort the sum of all clients' updates. The amount of noise is scaled to" +" the sensitivity value to obtain a privacy guarantee. The Gaussian " +"mechanism is used with a noise sampled from `N (0, σ²)` where `σ = ( " +"noise_scale * S ) / (number of sampled clients)`." msgstr "" #: ../../source/explanation-differential-privacy.rst:94 @@ -3001,29 +2934,29 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:96 msgid "" -"There are two forms of clipping commonly used in Central DP: Fixed Clipping " -"and Adaptive Clipping." +"There are two forms of clipping commonly used in Central DP: Fixed " +"Clipping and Adaptive Clipping." msgstr "" #: ../../source/explanation-differential-privacy.rst:98 msgid "" -"**Fixed Clipping** : A predefined fix threshold is set for the magnitude of " -"clients' updates. Any update exceeding this threshold is clipped back to the " -"threshold value." +"**Fixed Clipping** : A predefined fix threshold is set for the magnitude " +"of clients' updates. Any update exceeding this threshold is clipped back " +"to the threshold value." msgstr "" #: ../../source/explanation-differential-privacy.rst:100 msgid "" -"**Adaptive Clipping** : The clipping threshold dynamically adjusts based on " -"the observed update distribution [4]. It means that the clipping value is " -"tuned during the rounds with respect to the quantile of the update norm " -"distribution." +"**Adaptive Clipping** : The clipping threshold dynamically adjusts based " +"on the observed update distribution [4]. It means that the clipping value" +" is tuned during the rounds with respect to the quantile of the update " +"norm distribution." msgstr "" #: ../../source/explanation-differential-privacy.rst:102 msgid "" -"The choice between fixed and adaptive clipping depends on various factors " -"such as privacy requirements, data distribution, model complexity, and " +"The choice between fixed and adaptive clipping depends on various factors" +" such as privacy requirements, data distribution, model complexity, and " "others." msgstr "" @@ -3036,9 +2969,9 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:107 msgid "" "In this approach, each client is responsible for performing DP. Local DP " -"avoids the need for a fully trusted aggregator, but it should be noted that " -"local DP leads to a decrease in accuracy but better privacy in comparison to " -"central DP." +"avoids the need for a fully trusted aggregator, but it should be noted " +"that local DP leads to a decrease in accuracy but better privacy in " +"comparison to central DP." msgstr "" #: ../../source/explanation-differential-privacy.rst:116 @@ -3048,16 +2981,16 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:118 msgid "" "Each client adds noise to the local updates before sending them to the " -"server. To achieve (:math:`\\epsilon`, :math:`\\delta`)-DP, considering the " -"sensitivity of the local model to be ∆, Gaussian noise is applied with a " -"noise scale of σ where:" +"server. To achieve (:math:`\\epsilon`, :math:`\\delta`)-DP, considering " +"the sensitivity of the local model to be ∆, Gaussian noise is applied " +"with a noise scale of σ where:" msgstr "" #: ../../source/explanation-differential-privacy.rst:120 msgid "" "\\small\n" -"\\frac{∆ \\times \\sqrt{2 \\times \\log\\left(\\frac{1.25}{\\delta}\\right)}}" -"{\\epsilon}\n" +"\\frac{∆ \\times \\sqrt{2 \\times " +"\\log\\left(\\frac{1.25}{\\delta}\\right)}}{\\epsilon}\n" "\n" msgstr "" @@ -3084,18 +3017,18 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:135 msgid "" -"[2] McMahan et al. Learning Differentially Private Recurrent Language Models." +"[2] McMahan et al. Learning Differentially Private Recurrent Language " +"Models." msgstr "" #: ../../source/explanation-differential-privacy.rst:137 msgid "" -"[3] Geyer et al. Differentially Private Federated Learning: A Client Level " -"Perspective." +"[3] Geyer et al. Differentially Private Federated Learning: A Client " +"Level Perspective." msgstr "" #: ../../source/explanation-differential-privacy.rst:139 -msgid "" -"[4] Galen et al. Differentially Private Learning with Adaptive Clipping." +msgid "[4] Galen et al. Differentially Private Learning with Adaptive Clipping." msgstr "" #: ../../source/explanation-federated-evaluation.rst:2 @@ -3106,8 +3039,8 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:4 msgid "" "There are two main approaches to evaluating models in federated learning " -"systems: centralized (or server-side) evaluation and federated (or client-" -"side) evaluation." +"systems: centralized (or server-side) evaluation and federated (or " +"client-side) evaluation." msgstr "" #: ../../source/explanation-federated-evaluation.rst:8 @@ -3132,10 +3065,11 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:60 msgid "" -"The :code:`Strategy` abstraction provides a method called :code:`evaluate` " -"that can directly be used to evaluate the current global model parameters. " -"The current server implementation calls :code:`evaluate` after parameter " -"aggregation and before federated evaluation (see next paragraph)." +"The :code:`Strategy` abstraction provides a method called " +":code:`evaluate` that can directly be used to evaluate the current global" +" model parameters. The current server implementation calls " +":code:`evaluate` after parameter aggregation and before federated " +"evaluation (see next paragraph)." msgstr "" #: ../../source/explanation-federated-evaluation.rst:65 @@ -3148,8 +3082,8 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:70 msgid "" -"Client-side evaluation happens in the :code:`Client.evaluate` method and can " -"be configured from the server side." +"Client-side evaluation happens in the :code:`Client.evaluate` method and " +"can be configured from the server side." msgstr "" #: ../../source/explanation-federated-evaluation.rst:101 @@ -3164,39 +3098,40 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:105 msgid "" -":code:`fraction_evaluate`: a :code:`float` defining the fraction of clients " -"that will be selected for evaluation. If :code:`fraction_evaluate` is set " -"to :code:`0.1` and :code:`100` clients are connected to the server, then :" -"code:`10` will be randomly selected for evaluation. If :code:" -"`fraction_evaluate` is set to :code:`0.0`, federated evaluation will be " -"disabled." +":code:`fraction_evaluate`: a :code:`float` defining the fraction of " +"clients that will be selected for evaluation. If " +":code:`fraction_evaluate` is set to :code:`0.1` and :code:`100` clients " +"are connected to the server, then :code:`10` will be randomly selected " +"for evaluation. If :code:`fraction_evaluate` is set to :code:`0.0`, " +"federated evaluation will be disabled." msgstr "" #: ../../source/explanation-federated-evaluation.rst:106 msgid "" -":code:`min_evaluate_clients`: an :code:`int`: the minimum number of clients " -"to be selected for evaluation. If :code:`fraction_evaluate` is set to :code:" -"`0.1`, :code:`min_evaluate_clients` is set to 20, and :code:`100` clients " -"are connected to the server, then :code:`20` clients will be selected for " -"evaluation." +":code:`min_evaluate_clients`: an :code:`int`: the minimum number of " +"clients to be selected for evaluation. If :code:`fraction_evaluate` is " +"set to :code:`0.1`, :code:`min_evaluate_clients` is set to 20, and " +":code:`100` clients are connected to the server, then :code:`20` clients " +"will be selected for evaluation." msgstr "" #: ../../source/explanation-federated-evaluation.rst:107 msgid "" ":code:`min_available_clients`: an :code:`int` that defines the minimum " -"number of clients which need to be connected to the server before a round of " -"federated evaluation can start. If fewer than :code:`min_available_clients` " -"are connected to the server, the server will wait until more clients are " -"connected before it continues to sample clients for evaluation." +"number of clients which need to be connected to the server before a round" +" of federated evaluation can start. If fewer than " +":code:`min_available_clients` are connected to the server, the server " +"will wait until more clients are connected before it continues to sample " +"clients for evaluation." msgstr "" #: ../../source/explanation-federated-evaluation.rst:108 msgid "" ":code:`on_evaluate_config_fn`: a function that returns a configuration " -"dictionary which will be sent to the selected clients. The function will be " -"called during each round and provides a convenient way to customize client-" -"side evaluation from the server side, for example, to configure the number " -"of validation steps performed." +"dictionary which will be sent to the selected clients. The function will " +"be called during each round and provides a convenient way to customize " +"client-side evaluation from the server side, for example, to configure " +"the number of validation steps performed." msgstr "" #: ../../source/explanation-federated-evaluation.rst:135 @@ -3205,8 +3140,9 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:137 msgid "" -"Model parameters can also be evaluated during training. :code:`Client.fit` " -"can return arbitrary evaluation results as a dictionary:" +"Model parameters can also be evaluated during training. " +":code:`Client.fit` can return arbitrary evaluation results as a " +"dictionary:" msgstr "" #: ../../source/explanation-federated-evaluation.rst:177 @@ -3215,10 +3151,10 @@ msgstr "" #: ../../source/explanation-federated-evaluation.rst:179 msgid "" -"For a full code example that uses both centralized and federated evaluation, " -"see the *Advanced TensorFlow Example* (the same approach can be applied to " -"workloads implemented in any other framework): https://github.com/adap/" -"flower/tree/main/examples/advanced-tensorflow" +"For a full code example that uses both centralized and federated " +"evaluation, see the *Advanced TensorFlow Example* (the same approach can " +"be applied to workloads implemented in any other framework): " +"https://github.com/adap/flower/tree/main/examples/advanced-tensorflow" msgstr "" #: ../../source/fed/0000-20200102-fed-template.md:10 @@ -3392,9 +3328,9 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:37 msgid "" -"ensure community participants can successfully drive changes to completion " -"across one or more releases while stakeholders are adequately represented " -"throughout the process" +"ensure community participants can successfully drive changes to " +"completion across one or more releases while stakeholders are adequately " +"represented throughout the process" msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:39 @@ -3422,54 +3358,54 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:49 msgid "" "For far-fetching changes or features proposed to Flower, an abstraction " -"beyond a single GitHub issue or pull request is required to understand and " -"communicate upcoming changes to the project." +"beyond a single GitHub issue or pull request is required to understand " +"and communicate upcoming changes to the project." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:51 msgid "" -"The purpose of this process is to reduce the amount of \"tribal knowledge\" " -"in our community. By moving decisions from Slack threads, video calls, and " -"hallway conversations into a well-tracked artifact, this process aims to " -"enhance communication and discoverability." +"The purpose of this process is to reduce the amount of \"tribal " +"knowledge\" in our community. By moving decisions from Slack threads, " +"video calls, and hallway conversations into a well-tracked artifact, this" +" process aims to enhance communication and discoverability." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:55 msgid "" -"Roughly any larger, user-facing enhancement should follow the Enhancement " -"process. If an enhancement would be described in either written or verbal " -"communication to anyone besides the author or developer, then consider " -"creating an Enhancement Doc." +"Roughly any larger, user-facing enhancement should follow the Enhancement" +" process. If an enhancement would be described in either written or " +"verbal communication to anyone besides the author or developer, then " +"consider creating an Enhancement Doc." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:57 msgid "" -"Similarly, any technical effort (refactoring, major architectural change) " -"that will impact a large section of the development community should also be " -"communicated widely. The Enhancement process is suited for this even if it " -"will have zero impact on the typical user or operator." +"Similarly, any technical effort (refactoring, major architectural change)" +" that will impact a large section of the development community should " +"also be communicated widely. The Enhancement process is suited for this " +"even if it will have zero impact on the typical user or operator." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:61 msgid "" -"For small changes and additions, going through the Enhancement process would " -"be time-consuming and unnecessary. This includes, for example, adding new " -"Federated Learning algorithms, as these only add features without changing " -"how Flower works or is used." +"For small changes and additions, going through the Enhancement process " +"would be time-consuming and unnecessary. This includes, for example, " +"adding new Federated Learning algorithms, as these only add features " +"without changing how Flower works or is used." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:63 msgid "" "Enhancements are different from feature requests, as they are already " -"providing a laid-out path for implementation and are championed by members " -"of the community." +"providing a laid-out path for implementation and are championed by " +"members of the community." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:67 msgid "" "An Enhancement is captured in a Markdown file that follows a defined " -"template and a workflow to review and store enhancement docs for reference " -"— the Enhancement Doc." +"template and a workflow to review and store enhancement docs for " +"reference — the Enhancement Doc." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:69 @@ -3521,8 +3457,8 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:92 msgid "" -"**fed-number** (Required) The `fed-number` of the last Flower Enhancement " -"Doc + 1. With this number, it becomes easy to reference other proposals." +"**fed-number** (Required) The `fed-number` of the last Flower Enhancement" +" Doc + 1. With this number, it becomes easy to reference other proposals." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:94 @@ -3531,20 +3467,20 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:96 msgid "" -"**status** (Required) The current status of the proposal. See [workflow]" -"(#workflow) for the possible states." +"**status** (Required) The current status of the proposal. See " +"[workflow](#workflow) for the possible states." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:98 msgid "" -"**authors** (Required) A list of authors of the proposal. This is simply the " -"GitHub ID." +"**authors** (Required) A list of authors of the proposal. This is simply " +"the GitHub ID." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:100 msgid "" -"**creation-date** (Required) The date that the proposal was first submitted " -"in a PR." +"**creation-date** (Required) The date that the proposal was first " +"submitted in a PR." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:102 @@ -3555,8 +3491,8 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:104 msgid "" -"**see-also** (Optional) A list of other proposals that are relevant to this " -"one." +"**see-also** (Optional) A list of other proposals that are relevant to " +"this one." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:106 @@ -3564,8 +3500,7 @@ msgid "**replaces** (Optional) A list of proposals that this one replaces." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:108 -msgid "" -"**superseded-by** (Optional) A list of proposals that this one supersedes." +msgid "**superseded-by** (Optional) A list of proposals that this one supersedes." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:111 @@ -3575,40 +3510,40 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:113 msgid "" "The idea forming the enhancement should already have been discussed or " -"pitched in the community. As such, it needs a champion, usually the author, " -"who shepherds the enhancement. This person also has to find committers to " -"Flower willing to review the proposal." +"pitched in the community. As such, it needs a champion, usually the " +"author, who shepherds the enhancement. This person also has to find " +"committers to Flower willing to review the proposal." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:115 msgid "" "New enhancements are checked in with a file name in the form of `NNNN-" -"YYYYMMDD-enhancement-title.md`, with `NNNN` being the Flower Enhancement Doc " -"number, to `enhancements`. All enhancements start in `provisional` state as " -"part of a pull request. Discussions are done as part of the pull request " -"review." +"YYYYMMDD-enhancement-title.md`, with `NNNN` being the Flower Enhancement " +"Doc number, to `enhancements`. All enhancements start in `provisional` " +"state as part of a pull request. Discussions are done as part of the pull" +" request review." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:117 msgid "" -"Once an enhancement has been reviewed and approved, its status is changed to " -"`implementable`. The actual implementation is then done in separate pull " -"requests. These pull requests should mention the respective enhancement as " -"part of their description. After the implementation is done, the proposal " -"status is changed to `implemented`." +"Once an enhancement has been reviewed and approved, its status is changed" +" to `implementable`. The actual implementation is then done in separate " +"pull requests. These pull requests should mention the respective " +"enhancement as part of their description. After the implementation is " +"done, the proposal status is changed to `implemented`." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:119 msgid "" -"Under certain conditions, other states are possible. An Enhancement has the " -"following states:" +"Under certain conditions, other states are possible. An Enhancement has " +"the following states:" msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:121 msgid "" "`provisional`: The enhancement has been proposed and is actively being " -"defined. This is the starting state while the proposal is being fleshed out " -"and actively defined and discussed." +"defined. This is the starting state while the proposal is being fleshed " +"out and actively defined and discussed." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:122 @@ -3622,14 +3557,13 @@ msgid "" msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:124 -msgid "" -"`deferred`: The enhancement is proposed but not actively being worked on." +msgid "`deferred`: The enhancement is proposed but not actively being worked on." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:125 msgid "" -"`rejected`: The authors and reviewers have decided that this enhancement is " -"not moving forward." +"`rejected`: The authors and reviewers have decided that this enhancement " +"is not moving forward." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:126 @@ -3642,16 +3576,16 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:131 msgid "" -"Adding an additional process to the ones already provided by GitHub (Issues " -"and Pull Requests) adds more complexity and can be a barrier for potential " -"first-time contributors." +"Adding an additional process to the ones already provided by GitHub " +"(Issues and Pull Requests) adds more complexity and can be a barrier for " +"potential first-time contributors." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:133 msgid "" "Expanding the proposal template beyond the single-sentence description " -"currently required in the features issue template may be a heavy burden for " -"non-native English speakers." +"currently required in the features issue template may be a heavy burden " +"for non-native English speakers." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:137 @@ -3661,12 +3595,12 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:139 msgid "" "Using GitHub Issues for these kinds of enhancements is doable. One could " -"use, for example, tags, to differentiate and filter them from other issues. " -"The main issue is in discussing and reviewing an enhancement: GitHub issues " -"only have a single thread for comments. Enhancements usually have multiple " -"threads of discussion at the same time for various parts of the doc. " -"Managing these multiple discussions can be confusing when using GitHub " -"Issues." +"use, for example, tags, to differentiate and filter them from other " +"issues. The main issue is in discussing and reviewing an enhancement: " +"GitHub issues only have a single thread for comments. Enhancements " +"usually have multiple threads of discussion at the same time for various " +"parts of the doc. Managing these multiple discussions can be confusing " +"when using GitHub Issues." msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:141 @@ -3675,11 +3609,12 @@ msgstr "" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:143 msgid "" -"Google Docs allow for multiple threads of discussions. But as Google Docs " -"are hosted outside the project, their discoverability by the community needs " -"to be taken care of. A list of links to all proposals has to be managed and " -"made available for the community. Compared to shipping proposals as part of " -"Flower's repository, the potential for missing links is much higher." +"Google Docs allow for multiple threads of discussions. But as Google Docs" +" are hosted outside the project, their discoverability by the community " +"needs to be taken care of. A list of links to all proposals has to be " +"managed and made available for the community. Compared to shipping " +"proposals as part of Flower's repository, the potential for missing links" +" is much higher." msgstr "" #: ../../source/fed/index.md:1 @@ -3692,8 +3627,8 @@ msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:4 msgid "" -"The Flower server does not prescribe a way to aggregate evaluation results, " -"but it enables the user to fully customize result aggregation." +"The Flower server does not prescribe a way to aggregate evaluation " +"results, but it enables the user to fully customize result aggregation." msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:8 @@ -3702,9 +3637,9 @@ msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:10 msgid "" -"The same :code:`Strategy`-customization approach can be used to aggregate " -"custom evaluation results coming from individual clients. Clients can return " -"custom metrics to the server by returning a dictionary:" +"The same :code:`Strategy`-customization approach can be used to aggregate" +" custom evaluation results coming from individual clients. Clients can " +"return custom metrics to the server by returning a dictionary:" msgstr "" #: ../../source/how-to-aggregate-evaluation-results.rst:36 @@ -3719,9 +3654,10 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:4 msgid "" -"Flower has built-in support for authenticated SuperNodes that you can use to " -"verify the identities of each SuperNode connecting to a SuperLink. Flower " -"node authentication works similar to how GitHub SSH authentication works:" +"Flower has built-in support for authenticated SuperNodes that you can use" +" to verify the identities of each SuperNode connecting to a SuperLink. " +"Flower node authentication works similar to how GitHub SSH authentication" +" works:" msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:7 @@ -3730,7 +3666,8 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:8 msgid "" -"Using ECDH, both SuperNode and SuperLink independently derive a shared secret" +"Using ECDH, both SuperNode and SuperLink independently derive a shared " +"secret" msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:9 @@ -3745,21 +3682,22 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:12 msgid "" -"We recommend you to check out the complete `code example `_ demonstrating " -"federated learning with Flower in an authenticated setting." +"We recommend you to check out the complete `code example " +"`_ demonstrating federated learning with Flower in an " +"authenticated setting." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:15 msgid "" -"This guide covers a preview feature that might change in future versions of " -"Flower." +"This guide covers a preview feature that might change in future versions " +"of Flower." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:18 msgid "" -"For increased security, node authentication can only be used when encrypted " -"connections (SSL/TLS) are enabled." +"For increased security, node authentication can only be used when " +"encrypted connections (SSL/TLS) are enabled." msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:21 @@ -3769,99 +3707,101 @@ msgstr "" #: ../../source/how-to-authenticate-supernodes.rst:23 msgid "" "To enable node authentication, first you need to configure SSL/TLS " -"connections to secure the SuperLink<>SuperNode communication. You can find " -"the complete guide `here `_. After configuring secure connections, you can enable " -"client authentication in a long-running Flower :code:`SuperLink`. Use the " -"following terminal command to start a Flower :code:`SuperNode` that has both " -"secure connections and node authentication enabled:" +"connections to secure the SuperLink<>SuperNode communication. You can " +"find the complete guide `here `_. After configuring secure connections, you" +" can enable client authentication in a long-running Flower " +":code:`SuperLink`. Use the following terminal command to start a Flower " +":code:`SuperNode` that has both secure connections and node " +"authentication enabled:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:36 +#: ../../source/how-to-authenticate-supernodes.rst:38 msgid "Let's break down the authentication flags:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:38 +#: ../../source/how-to-authenticate-supernodes.rst:40 msgid "" -"The first flag :code:`--auth-list-public-keys` expects a path to a CSV file " -"storing all known node public keys. You need to store all known node public " -"keys that are allowed to participate in a federation in one CSV file (:code:" -"`.csv`)." +"The first flag :code:`--auth-list-public-keys` expects a path to a CSV " +"file storing all known node public keys. You need to store all known node" +" public keys that are allowed to participate in a federation in one CSV " +"file (:code:`.csv`)." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:40 +#: ../../source/how-to-authenticate-supernodes.rst:42 msgid "" "A valid CSV file storing known node public keys should list the keys in " "OpenSSH format, separated by commas and without any comments. For an " -"example, refer to our code sample, which contains a CSV file with two known " -"node public keys." +"example, refer to our code sample, which contains a CSV file with two " +"known node public keys." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:42 +#: ../../source/how-to-authenticate-supernodes.rst:44 msgid "" -"The second and third flags :code:`--auth-superlink-private-key` and :code:`--" -"auth-superlink-public-key` expect paths to the server's private and public " -"keys. For development purposes, you can generate a private and public key " -"pair using :code:`ssh-keygen -t ecdsa -b 384`." +"The second and third flags :code:`--auth-superlink-private-key` and :code" +":`--auth-superlink-public-key` expect paths to the server's private and " +"public keys. For development purposes, you can generate a private and " +"public key pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:45 +#: ../../source/how-to-authenticate-supernodes.rst:47 msgid "" "In Flower 1.9, there is no support for dynamically removing, editing, or " -"adding known node public keys to the SuperLink. To change the set of known " -"nodes, you need to shut the server down, edit the CSV file, and start the " -"server again. Support for dynamically changing the set of known nodes is on " -"the roadmap to be released in Flower 1.10 (ETA: June)." +"adding known node public keys to the SuperLink. To change the set of " +"known nodes, you need to shut the server down, edit the CSV file, and " +"start the server again. Support for dynamically changing the set of known" +" nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:51 +#: ../../source/how-to-authenticate-supernodes.rst:53 msgid "Enable node authentication in :code:`SuperNode`" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:53 +#: ../../source/how-to-authenticate-supernodes.rst:55 msgid "" "Similar to the long-running Flower server (:code:`SuperLink`), you can " -"easily enable node authentication in the long-running Flower client (:code:" -"`SuperNode`). Use the following terminal command to start an authenticated :" -"code:`SuperNode`:" +"easily enable node authentication in the long-running Flower client " +"(:code:`SuperNode`). Use the following terminal command to start an " +"authenticated :code:`SuperNode`:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:64 +#: ../../source/how-to-authenticate-supernodes.rst:66 msgid "" -"The :code:`--auth-supernode-private-key` flag expects a path to the node's " -"private key file and the :code:`--auth-supernode-public-key` flag expects a " -"path to the node's public key file. For development purposes, you can " -"generate a private and public key pair using :code:`ssh-keygen -t ecdsa -b " -"384`." +"The :code:`--auth-supernode-private-key` flag expects a path to the " +"node's private key file and the :code:`--auth-supernode-public-key` flag " +"expects a path to the node's public key file. For development purposes, " +"you can generate a private and public key pair using :code:`ssh-keygen -t" +" ecdsa -b 384`." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:68 +#: ../../source/how-to-authenticate-supernodes.rst:70 msgid "Security notice" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:70 +#: ../../source/how-to-authenticate-supernodes.rst:72 msgid "" -"The system's security relies on the credentials of the SuperLink and each " -"SuperNode. Therefore, it is imperative to safeguard and safely store the " -"credentials to avoid security risks such as Public Key Infrastructure (PKI) " -"impersonation attacks. The node authentication mechanism also involves human " -"interaction, so please ensure that all of the communication is done in a " -"secure manner, using trusted communication methods." +"The system's security relies on the credentials of the SuperLink and each" +" SuperNode. Therefore, it is imperative to safeguard and safely store the" +" credentials to avoid security risks such as Public Key Infrastructure " +"(PKI) impersonation attacks. The node authentication mechanism also " +"involves human interaction, so please ensure that all of the " +"communication is done in a secure manner, using trusted communication " +"methods." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:75 -#: ../../source/how-to-enable-ssl-connections.rst:65 +#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:68 #: ../../source/how-to-use-built-in-mods.rst:85 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:287 msgid "Conclusion" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-authenticate-supernodes.rst:79 msgid "" -"You should now have learned how to start a long-running Flower server (:code:" -"`SuperLink`) and client (:code:`SuperNode`) with node authentication " -"enabled. You should also know the significance of the private key and store " -"it safely to minimize security risks." +"You should now have learned how to start a long-running Flower server " +"(:code:`SuperLink`) and client (:code:`SuperNode`) with node " +"authentication enabled. You should also know the significance of the " +"private key and store it safely to minimize security risks." msgstr "" #: ../../source/how-to-configure-clients.rst:2 @@ -3871,9 +3811,9 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:4 msgid "" "Along with model parameters, Flower can send configuration values to " -"clients. Configuration values can be used for various purposes. They are, " -"for example, a popular way to control client-side hyperparameters from the " -"server." +"clients. Configuration values can be used for various purposes. They are," +" for example, a popular way to control client-side hyperparameters from " +"the server." msgstr "" #: ../../source/how-to-configure-clients.rst:7 @@ -3882,34 +3822,34 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:9 msgid "" -"Configuration values are represented as a dictionary with ``str`` keys and " -"values of type ``bool``, ``bytes``, ``double`` (64-bit precision float), " -"``int``, or ``str`` (or equivalent types in different languages). Here is an " -"example of a configuration dictionary in Python:" +"Configuration values are represented as a dictionary with ``str`` keys " +"and values of type ``bool``, ``bytes``, ``double`` (64-bit precision " +"float), ``int``, or ``str`` (or equivalent types in different languages)." +" Here is an example of a configuration dictionary in Python:" msgstr "" #: ../../source/how-to-configure-clients.rst:20 msgid "" "Flower serializes these configuration dictionaries (or *config dict* for " -"short) to their ProtoBuf representation, transports them to the client using " -"gRPC, and then deserializes them back to Python dictionaries." +"short) to their ProtoBuf representation, transports them to the client " +"using gRPC, and then deserializes them back to Python dictionaries." msgstr "" #: ../../source/how-to-configure-clients.rst:24 msgid "" -"Currently, there is no support for directly sending collection types (e.g., " -"``Set``, ``List``, ``Map``) as values in configuration dictionaries. There " -"are several workarounds to send collections as values by converting them to " -"one of the supported value types (and converting them back on the client-" -"side)." +"Currently, there is no support for directly sending collection types " +"(e.g., ``Set``, ``List``, ``Map``) as values in configuration " +"dictionaries. There are several workarounds to send collections as values" +" by converting them to one of the supported value types (and converting " +"them back on the client-side)." msgstr "" #: ../../source/how-to-configure-clients.rst:26 msgid "" "One can, for example, convert a list of floating-point numbers to a JSON " -"string, then send the JSON string using the configuration dictionary, and " -"then convert the JSON string back to a list of floating-point numbers on the " -"client." +"string, then send the JSON string using the configuration dictionary, and" +" then convert the JSON string back to a list of floating-point numbers on" +" the client." msgstr "" #: ../../source/how-to-configure-clients.rst:30 @@ -3918,50 +3858,49 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:32 msgid "" -"The easiest way to send configuration values to clients is to use a built-in " -"strategy like :code:`FedAvg`. Built-in strategies support so-called " -"configuration functions. A configuration function is a function that the " -"built-in strategy calls to get the configuration dictionary for the current " -"round. It then forwards the configuration dictionary to all the clients " -"selected during that round." +"The easiest way to send configuration values to clients is to use a " +"built-in strategy like :code:`FedAvg`. Built-in strategies support so-" +"called configuration functions. A configuration function is a function " +"that the built-in strategy calls to get the configuration dictionary for " +"the current round. It then forwards the configuration dictionary to all " +"the clients selected during that round." msgstr "" #: ../../source/how-to-configure-clients.rst:34 msgid "" "Let's start with a simple example. Imagine we want to send (a) the batch " -"size that the client should use, (b) the current global round of federated " -"learning, and (c) the number of epochs to train on the client-side. Our " -"configuration function could look like this:" +"size that the client should use, (b) the current global round of " +"federated learning, and (c) the number of epochs to train on the client-" +"side. Our configuration function could look like this:" msgstr "" #: ../../source/how-to-configure-clients.rst:47 msgid "" "To make the built-in strategies use this function, we can pass it to " -"``FedAvg`` during initialization using the parameter :code:" -"`on_fit_config_fn`:" +"``FedAvg`` during initialization using the parameter " +":code:`on_fit_config_fn`:" msgstr "" #: ../../source/how-to-configure-clients.rst:56 -msgid "" -"One the client side, we receive the configuration dictionary in ``fit``:" +msgid "One the client side, we receive the configuration dictionary in ``fit``:" msgstr "" #: ../../source/how-to-configure-clients.rst:67 msgid "" "There is also an `on_evaluate_config_fn` to configure evaluation, which " -"works the same way. They are separate functions because one might want to " -"send different configuration values to `evaluate` (for example, to use a " -"different batch size)." +"works the same way. They are separate functions because one might want to" +" send different configuration values to `evaluate` (for example, to use a" +" different batch size)." msgstr "" #: ../../source/how-to-configure-clients.rst:69 msgid "" -"The built-in strategies call this function every round (that is, every time " -"`Strategy.configure_fit` or `Strategy.configure_evaluate` runs). Calling " -"`on_evaluate_config_fn` every round allows us to vary/change the config dict " -"over consecutive rounds. If we wanted to implement a hyperparameter " -"schedule, for example, to increase the number of local epochs during later " -"rounds, we could do the following:" +"The built-in strategies call this function every round (that is, every " +"time `Strategy.configure_fit` or `Strategy.configure_evaluate` runs). " +"Calling `on_evaluate_config_fn` every round allows us to vary/change the " +"config dict over consecutive rounds. If we wanted to implement a " +"hyperparameter schedule, for example, to increase the number of local " +"epochs during later rounds, we could do the following:" msgstr "" #: ../../source/how-to-configure-clients.rst:82 @@ -3980,12 +3919,13 @@ msgstr "" #: ../../source/how-to-configure-clients.rst:89 msgid "" -"This can be achieved by customizing an existing strategy or by :doc:" -"`implementing a custom strategy from scratch `. " -"Here's a nonsensical example that customizes :code:`FedAvg` by adding a " -"custom ``\"hello\": \"world\"`` configuration key/value pair to the config " -"dict of a *single client* (only the first client in the list, the other " -"clients in this round to not receive this \"special\" config value):" +"This can be achieved by customizing an existing strategy or by " +":doc:`implementing a custom strategy from scratch `. Here's a nonsensical example that customizes :code:`FedAvg`" +" by adding a custom ``\"hello\": \"world\"`` configuration key/value pair" +" to the config dict of a *single client* (only the first client in the " +"list, the other clients in this round to not receive this \"special\" " +"config value):" msgstr "" #: ../../source/how-to-configure-logging.rst:2 @@ -3995,16 +3935,16 @@ msgstr "" #: ../../source/how-to-configure-logging.rst:4 msgid "" "The Flower logger keeps track of all core events that take place in " -"federated learning workloads. It presents information by default following a " -"standard message format:" +"federated learning workloads. It presents information by default " +"following a standard message format:" msgstr "" #: ../../source/how-to-configure-logging.rst:13 msgid "" -"containing relevant information including: log message level (e.g. :code:" -"`INFO`, :code:`DEBUG`), a timestamp, the line where the logging took place " -"from, as well as the log message itself. In this way, the logger would " -"typically display information on your terminal as follows:" +"containing relevant information including: log message level (e.g. " +":code:`INFO`, :code:`DEBUG`), a timestamp, the line where the logging " +"took place from, as well as the log message itself. In this way, the " +"logger would typically display information on your terminal as follows:" msgstr "" #: ../../source/how-to-configure-logging.rst:34 @@ -4015,20 +3955,21 @@ msgstr "" msgid "" "By default, the Flower log is outputted to the terminal where you launch " "your Federated Learning workload from. This applies for both gRPC-based " -"federation (i.e. when you do :code:`fl.server.start_server`) and when using " -"the :code:`VirtualClientEngine` (i.e. when you do :code:`fl.simulation." -"start_simulation`). In some situations you might want to save this log to " -"disk. You can do so by calling the `fl.common.logger.configure() `_ function. " -"For example:" +"federation (i.e. when you do :code:`fl.server.start_server`) and when " +"using the :code:`VirtualClientEngine` (i.e. when you do " +":code:`fl.simulation.start_simulation`). In some situations you might " +"want to save this log to disk. You can do so by calling the " +"`fl.common.logger.configure() " +"`_" +" function. For example:" msgstr "" #: ../../source/how-to-configure-logging.rst:53 msgid "" -"With the above, Flower will record the log you see on your terminal to :code:" -"`log.txt`. This file will be created in the same directory as were you are " -"running the code from. If we inspect we see the log above is also recorded " -"but prefixing with :code:`identifier` each line:" +"With the above, Flower will record the log you see on your terminal to " +":code:`log.txt`. This file will be created in the same directory as were " +"you are running the code from. If we inspect we see the log above is also" +" recorded but prefixing with :code:`identifier` each line:" msgstr "" #: ../../source/how-to-configure-logging.rst:74 @@ -4037,15 +3978,15 @@ msgstr "" #: ../../source/how-to-configure-logging.rst:76 msgid "" -"You might expand the information shown by default with the Flower logger by " -"adding more messages relevant to your application. You can achieve this " -"easily as follows." +"You might expand the information shown by default with the Flower logger " +"by adding more messages relevant to your application. You can achieve " +"this easily as follows." msgstr "" #: ../../source/how-to-configure-logging.rst:102 msgid "" -"In this way your logger will show, in addition to the default messages, the " -"ones introduced by the clients as specified above." +"In this way your logger will show, in addition to the default messages, " +"the ones introduced by the clients as specified above." msgstr "" #: ../../source/how-to-configure-logging.rst:128 @@ -4054,14 +3995,15 @@ msgstr "" #: ../../source/how-to-configure-logging.rst:130 msgid "" -"The :code:`fl.common.logger.configure` function, also allows specifying a " -"host to which logs can be pushed (via :code:`POST`) through a native Python :" -"code:`logging.handler.HTTPHandler`. This is a particularly useful feature " -"in :code:`gRPC`-based Federated Learning workloads where otherwise gathering " -"logs from all entities (i.e. the server and the clients) might be " -"cumbersome. Note that in Flower simulation, the server automatically " -"displays all logs. You can still specify a :code:`HTTPHandler` should you " -"wish to backup or analyze the logs somewhere else." +"The :code:`fl.common.logger.configure` function, also allows specifying a" +" host to which logs can be pushed (via :code:`POST`) through a native " +"Python :code:`logging.handler.HTTPHandler`. This is a particularly useful" +" feature in :code:`gRPC`-based Federated Learning workloads where " +"otherwise gathering logs from all entities (i.e. the server and the " +"clients) might be cumbersome. Note that in Flower simulation, the server " +"automatically displays all logs. You can still specify a " +":code:`HTTPHandler` should you wish to backup or analyze the logs " +"somewhere else." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:2 @@ -4070,23 +4012,24 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:4 msgid "" -"This guide describes how to a SSL-enabled secure Flower server (:code:" -"`SuperLink`) can be started and how a Flower client (:code:`SuperNode`) can " -"establish a secure connections to it." +"This guide describes how to a SSL-enabled secure Flower server " +"(:code:`SuperLink`) can be started and how a Flower client " +"(:code:`SuperNode`) can establish a secure connections to it." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:7 msgid "" -"A complete code example demonstrating a secure connection can be found `here " -"`_." +"A complete code example demonstrating a secure connection can be found " +"`here `_." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:10 msgid "" -"The code example comes with a :code:`README.md` file which explains how to " -"start it. Although it is already SSL-enabled, it might be less descriptive " -"on how it does so. Stick to this guide for a deeper introduction to the " -"topic." +"The code example comes with a :code:`README.md` file which explains how " +"to start it. Although it is already SSL-enabled, it might be less " +"descriptive on how it does so. Stick to this guide for a deeper " +"introduction to the topic." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:16 @@ -4096,27 +4039,27 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:18 msgid "" "Using SSL-enabled connections requires certificates to be passed to the " -"server and client. For the purpose of this guide we are going to generate " -"self-signed certificates. As this can become quite complex we are going to " -"ask you to run the script in :code:`examples/advanced-tensorflow/" -"certificates/generate.sh` with the following command sequence:" +"server and client. For the purpose of this guide we are going to generate" +" self-signed certificates. As this can become quite complex we are going " +"to ask you to run the script in :code:`examples/advanced-" +"tensorflow/certificates/generate.sh` with the following command sequence:" msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:29 msgid "" -"This will generate the certificates in :code:`examples/advanced-tensorflow/." -"cache/certificates`." +"This will generate the certificates in :code:`examples/advanced-" +"tensorflow/.cache/certificates`." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:31 msgid "" -"The approach for generating SSL certificates in the context of this example " -"can serve as an inspiration and starting point, but it should not be used as " -"a reference for production environments. Please refer to other sources " -"regarding the issue of correctly generating certificates for production " -"environments. For non-critical prototyping or research projects, it might be " -"sufficient to use the self-signed certificates generated using the scripts " -"mentioned in this guide." +"The approach for generating SSL certificates in the context of this " +"example can serve as an inspiration and starting point, but it should not" +" be used as a reference for production environments. Please refer to " +"other sources regarding the issue of correctly generating certificates " +"for production environments. For non-critical prototyping or research " +"projects, it might be sufficient to use the self-signed certificates " +"generated using the scripts mentioned in this guide." msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:39 @@ -4125,55 +4068,55 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:41 msgid "" -"Use the following terminal command to start a sever (SuperLink) that uses " -"the previously generated certificates:" +"Use the following terminal command to start a sever (SuperLink) that uses" +" the previously generated certificates:" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:47 +#: ../../source/how-to-enable-ssl-connections.rst:50 msgid "" "When providing certificates, the server expects a tuple of three " -"certificates paths: CA certificate, server certificate and server private " -"key." +"certificates paths: CA certificate, server certificate and server private" +" key." msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:51 +#: ../../source/how-to-enable-ssl-connections.rst:54 msgid "Client (SuperNode)" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:53 +#: ../../source/how-to-enable-ssl-connections.rst:56 msgid "" -"Use the following terminal command to start a client (SuperNode) that uses " -"the previously generated certificates:" +"Use the following terminal command to start a client (SuperNode) that " +"uses the previously generated certificates:" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:61 +#: ../../source/how-to-enable-ssl-connections.rst:64 msgid "" -"When setting :code:`root_certificates`, the client expects a file path to " -"PEM-encoded root certificates." +"When setting :code:`root_certificates`, the client expects a file path to" +" PEM-encoded root certificates." msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:67 +#: ../../source/how-to-enable-ssl-connections.rst:70 msgid "" -"You should now have learned how to generate self-signed certificates using " -"the given script, start an SSL-enabled server and have a client establish a " -"secure connection to it." +"You should now have learned how to generate self-signed certificates " +"using the given script, start an SSL-enabled server and have a client " +"establish a secure connection to it." msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:72 +#: ../../source/how-to-enable-ssl-connections.rst:75 msgid "Additional resources" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:74 +#: ../../source/how-to-enable-ssl-connections.rst:77 msgid "" -"These additional sources might be relevant if you would like to dive deeper " -"into the topic of certificates:" +"These additional sources might be relevant if you would like to dive " +"deeper into the topic of certificates:" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:76 +#: ../../source/how-to-enable-ssl-connections.rst:79 msgid "`Let's Encrypt `_" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:80 msgid "`certbot `_" msgstr "" @@ -4183,12 +4126,12 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:4 msgid "" -"The strategy abstraction enables implementation of fully custom strategies. " -"A strategy is basically the federated learning algorithm that runs on the " -"server. Strategies decide how to sample clients, how to configure clients " -"for training, how to aggregate updates, and how to evaluate models. Flower " -"provides a few built-in strategies which are based on the same API described " -"below." +"The strategy abstraction enables implementation of fully custom " +"strategies. A strategy is basically the federated learning algorithm that" +" runs on the server. Strategies decide how to sample clients, how to " +"configure clients for training, how to aggregate updates, and how to " +"evaluate models. Flower provides a few built-in strategies which are " +"based on the same API described below." msgstr "" #: ../../source/how-to-implement-strategies.rst:11 @@ -4197,10 +4140,11 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:13 msgid "" -"All strategy implementation are derived from the abstract base class :code:" -"`flwr.server.strategy.Strategy`, both built-in implementations and third " -"party implementations. This means that custom strategy implementations have " -"the exact same capabilities at their disposal as built-in ones." +"All strategy implementation are derived from the abstract base class " +":code:`flwr.server.strategy.Strategy`, both built-in implementations and " +"third party implementations. This means that custom strategy " +"implementations have the exact same capabilities at their disposal as " +"built-in ones." msgstr "" #: ../../source/how-to-implement-strategies.rst:18 @@ -4211,9 +4155,9 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:75 msgid "" -"Creating a new strategy means implementing a new :code:`class` (derived from " -"the abstract base class :code:`Strategy`) that implements for the previously " -"shown abstract methods:" +"Creating a new strategy means implementing a new :code:`class` (derived " +"from the abstract base class :code:`Strategy`) that implements for the " +"previously shown abstract methods:" msgstr "" #: ../../source/how-to-implement-strategies.rst:100 @@ -4230,35 +4174,37 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:182 msgid "" -":code:`initialize_parameters` is called only once, at the very beginning of " -"an execution. It is responsible for providing the initial global model " -"parameters in a serialized form (i.e., as a :code:`Parameters` object)." +":code:`initialize_parameters` is called only once, at the very beginning " +"of an execution. It is responsible for providing the initial global model" +" parameters in a serialized form (i.e., as a :code:`Parameters` object)." msgstr "" #: ../../source/how-to-implement-strategies.rst:184 msgid "" -"Built-in strategies return user-provided initial parameters. The following " -"example shows how initial parameters can be passed to :code:`FedAvg`:" +"Built-in strategies return user-provided initial parameters. The " +"following example shows how initial parameters can be passed to " +":code:`FedAvg`:" msgstr "" #: ../../source/how-to-implement-strategies.rst:209 msgid "" "The Flower server will call :code:`initialize_parameters`, which either " -"returns the parameters that were passed to :code:`initial_parameters`, or :" -"code:`None`. If no parameters are returned from :code:" -"`initialize_parameters` (i.e., :code:`None`), the server will randomly " -"select one client and ask it to provide its parameters. This is a " -"convenience feature and not recommended in practice, but it can be useful " -"for prototyping. In practice, it is recommended to always use server-side " -"parameter initialization." +"returns the parameters that were passed to :code:`initial_parameters`, or" +" :code:`None`. If no parameters are returned from " +":code:`initialize_parameters` (i.e., :code:`None`), the server will " +"randomly select one client and ask it to provide its parameters. This is " +"a convenience feature and not recommended in practice, but it can be " +"useful for prototyping. In practice, it is recommended to always use " +"server-side parameter initialization." msgstr "" #: ../../source/how-to-implement-strategies.rst:213 msgid "" "Server-side parameter initialization is a powerful mechanism. It can be " -"used, for example, to resume training from a previously saved checkpoint. It " -"is also the fundamental capability needed to implement hybrid approaches, " -"for example, to fine-tune a pre-trained model using federated learning." +"used, for example, to resume training from a previously saved checkpoint." +" It is also the fundamental capability needed to implement hybrid " +"approaches, for example, to fine-tune a pre-trained model using federated" +" learning." msgstr "" #: ../../source/how-to-implement-strategies.rst:216 @@ -4267,17 +4213,17 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:218 msgid "" -":code:`configure_fit` is responsible for configuring the upcoming round of " -"training. What does *configure* mean in this context? Configuring a round " -"means selecting clients and deciding what instructions to send to these " -"clients. The signature of :code:`configure_fit` makes this clear:" +":code:`configure_fit` is responsible for configuring the upcoming round " +"of training. What does *configure* mean in this context? Configuring a " +"round means selecting clients and deciding what instructions to send to " +"these clients. The signature of :code:`configure_fit` makes this clear:" msgstr "" #: ../../source/how-to-implement-strategies.rst:231 msgid "" "The return value is a list of tuples, each representing the instructions " -"that will be sent to a particular client. Strategy implementations usually " -"perform the following steps in :code:`configure_fit`:" +"that will be sent to a particular client. Strategy implementations " +"usually perform the following steps in :code:`configure_fit`:" msgstr "" #: ../../source/how-to-implement-strategies.rst:233 @@ -4296,18 +4242,19 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:236 msgid "" "More sophisticated implementations can use :code:`configure_fit` to " -"implement custom client selection logic. A client will only participate in a " -"round if the corresponding :code:`ClientProxy` is included in the list " -"returned from :code:`configure_fit`." +"implement custom client selection logic. A client will only participate " +"in a round if the corresponding :code:`ClientProxy` is included in the " +"list returned from :code:`configure_fit`." msgstr "" #: ../../source/how-to-implement-strategies.rst:240 msgid "" "The structure of this return value provides a lot of flexibility to the " "user. Since instructions are defined on a per-client basis, different " -"instructions can be sent to each client. This enables custom strategies to " -"train, for example, different models on different clients, or use different " -"hyperparameters on different clients (via the :code:`config` dict)." +"instructions can be sent to each client. This enables custom strategies " +"to train, for example, different models on different clients, or use " +"different hyperparameters on different clients (via the :code:`config` " +"dict)." msgstr "" #: ../../source/how-to-implement-strategies.rst:243 @@ -4316,23 +4263,24 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:245 msgid "" -":code:`aggregate_fit` is responsible for aggregating the results returned by " -"the clients that were selected and asked to train in :code:`configure_fit`." +":code:`aggregate_fit` is responsible for aggregating the results returned" +" by the clients that were selected and asked to train in " +":code:`configure_fit`." msgstr "" #: ../../source/how-to-implement-strategies.rst:258 msgid "" "Of course, failures can happen, so there is no guarantee that the server " -"will get results from all the clients it sent instructions to (via :code:" -"`configure_fit`). :code:`aggregate_fit` therefore receives a list of :code:" -"`results`, but also a list of :code:`failures`." +"will get results from all the clients it sent instructions to (via " +":code:`configure_fit`). :code:`aggregate_fit` therefore receives a list " +"of :code:`results`, but also a list of :code:`failures`." msgstr "" #: ../../source/how-to-implement-strategies.rst:260 msgid "" -":code:`aggregate_fit` returns an optional :code:`Parameters` object and a " -"dictionary of aggregated metrics. The :code:`Parameters` return value is " -"optional because :code:`aggregate_fit` might decide that the results " +":code:`aggregate_fit` returns an optional :code:`Parameters` object and a" +" dictionary of aggregated metrics. The :code:`Parameters` return value is" +" optional because :code:`aggregate_fit` might decide that the results " "provided are not sufficient for aggregation (e.g., too many failures)." msgstr "" @@ -4342,40 +4290,42 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:265 msgid "" -":code:`configure_evaluate` is responsible for configuring the upcoming round " -"of evaluation. What does *configure* mean in this context? Configuring a " -"round means selecting clients and deciding what instructions to send to " -"these clients. The signature of :code:`configure_evaluate` makes this clear:" +":code:`configure_evaluate` is responsible for configuring the upcoming " +"round of evaluation. What does *configure* mean in this context? " +"Configuring a round means selecting clients and deciding what " +"instructions to send to these clients. The signature of " +":code:`configure_evaluate` makes this clear:" msgstr "" #: ../../source/how-to-implement-strategies.rst:278 msgid "" "The return value is a list of tuples, each representing the instructions " -"that will be sent to a particular client. Strategy implementations usually " -"perform the following steps in :code:`configure_evaluate`:" +"that will be sent to a particular client. Strategy implementations " +"usually perform the following steps in :code:`configure_evaluate`:" msgstr "" #: ../../source/how-to-implement-strategies.rst:281 msgid "" -"Pair each :code:`ClientProxy` with the same :code:`EvaluateIns` holding the " -"current global model :code:`parameters` and :code:`config` dict" +"Pair each :code:`ClientProxy` with the same :code:`EvaluateIns` holding " +"the current global model :code:`parameters` and :code:`config` dict" msgstr "" #: ../../source/how-to-implement-strategies.rst:283 msgid "" "More sophisticated implementations can use :code:`configure_evaluate` to " -"implement custom client selection logic. A client will only participate in a " -"round if the corresponding :code:`ClientProxy` is included in the list " -"returned from :code:`configure_evaluate`." +"implement custom client selection logic. A client will only participate " +"in a round if the corresponding :code:`ClientProxy` is included in the " +"list returned from :code:`configure_evaluate`." msgstr "" #: ../../source/how-to-implement-strategies.rst:287 msgid "" "The structure of this return value provides a lot of flexibility to the " "user. Since instructions are defined on a per-client basis, different " -"instructions can be sent to each client. This enables custom strategies to " -"evaluate, for example, different models on different clients, or use " -"different hyperparameters on different clients (via the :code:`config` dict)." +"instructions can be sent to each client. This enables custom strategies " +"to evaluate, for example, different models on different clients, or use " +"different hyperparameters on different clients (via the :code:`config` " +"dict)." msgstr "" #: ../../source/how-to-implement-strategies.rst:291 @@ -4385,24 +4335,24 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:293 msgid "" ":code:`aggregate_evaluate` is responsible for aggregating the results " -"returned by the clients that were selected and asked to evaluate in :code:" -"`configure_evaluate`." +"returned by the clients that were selected and asked to evaluate in " +":code:`configure_evaluate`." msgstr "" #: ../../source/how-to-implement-strategies.rst:306 msgid "" "Of course, failures can happen, so there is no guarantee that the server " -"will get results from all the clients it sent instructions to (via :code:" -"`configure_evaluate`). :code:`aggregate_evaluate` therefore receives a list " -"of :code:`results`, but also a list of :code:`failures`." +"will get results from all the clients it sent instructions to (via " +":code:`configure_evaluate`). :code:`aggregate_evaluate` therefore " +"receives a list of :code:`results`, but also a list of :code:`failures`." msgstr "" #: ../../source/how-to-implement-strategies.rst:308 msgid "" -":code:`aggregate_evaluate` returns an optional :code:`float` (loss) and a " -"dictionary of aggregated metrics. The :code:`float` return value is optional " -"because :code:`aggregate_evaluate` might decide that the results provided " -"are not sufficient for aggregation (e.g., too many failures)." +":code:`aggregate_evaluate` returns an optional :code:`float` (loss) and a" +" dictionary of aggregated metrics. The :code:`float` return value is " +"optional because :code:`aggregate_evaluate` might decide that the results" +" provided are not sufficient for aggregation (e.g., too many failures)." msgstr "" #: ../../source/how-to-implement-strategies.rst:311 @@ -4412,17 +4362,17 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:313 msgid "" ":code:`evaluate` is responsible for evaluating model parameters on the " -"server-side. Having :code:`evaluate` in addition to :code:" -"`configure_evaluate`/:code:`aggregate_evaluate` enables strategies to " -"perform both servers-side and client-side (federated) evaluation." +"server-side. Having :code:`evaluate` in addition to " +":code:`configure_evaluate`/:code:`aggregate_evaluate` enables strategies " +"to perform both servers-side and client-side (federated) evaluation." msgstr "" #: ../../source/how-to-implement-strategies.rst:323 msgid "" -"The return value is again optional because the strategy might not need to " -"implement server-side evaluation or because the user-defined :code:" -"`evaluate` method might not complete successfully (e.g., it might fail to " -"load the server-side evaluation data)." +"The return value is again optional because the strategy might not need to" +" implement server-side evaluation or because the user-defined " +":code:`evaluate` method might not complete successfully (e.g., it might " +"fail to load the server-side evaluation data)." msgstr "" #: ../../source/how-to-install-flower.rst:2 @@ -4444,7 +4394,8 @@ msgstr "" #: ../../source/how-to-install-flower.rst:17 msgid "" -"Stable releases are available on `PyPI `_::" +"Stable releases are available on `PyPI " +"`_::" msgstr "" #: ../../source/how-to-install-flower.rst:21 @@ -4463,14 +4414,14 @@ msgstr "" #: ../../source/how-to-install-flower.rst:31 msgid "" -"If you have not added ``conda-forge`` to your channels, you will first need " -"to run the following::" +"If you have not added ``conda-forge`` to your channels, you will first " +"need to run the following::" msgstr "" #: ../../source/how-to-install-flower.rst:36 msgid "" -"Once the ``conda-forge`` channel has been enabled, ``flwr`` can be installed " -"with ``conda``::" +"Once the ``conda-forge`` channel has been enabled, ``flwr`` can be " +"installed with ``conda``::" msgstr "" #: ../../source/how-to-install-flower.rst:40 @@ -4484,8 +4435,8 @@ msgstr "" #: ../../source/how-to-install-flower.rst:48 msgid "" "The following command can be used to verify if Flower was successfully " -"installed. If everything worked, it should print the version of Flower to " -"the command line::" +"installed. If everything worked, it should print the version of Flower to" +" the command line::" msgstr "" #: ../../source/how-to-install-flower.rst:55 @@ -4506,15 +4457,15 @@ msgstr "" #: ../../source/how-to-install-flower.rst:65 msgid "" -"New (possibly unstable) versions of Flower are sometimes available as pre-" -"release versions (alpha, beta, release candidate) before the stable release " -"happens::" +"New (possibly unstable) versions of Flower are sometimes available as " +"pre-release versions (alpha, beta, release candidate) before the stable " +"release happens::" msgstr "" #: ../../source/how-to-install-flower.rst:69 msgid "" -"For simulations that use the Virtual Client Engine, ``flwr`` pre-releases " -"should be installed with the ``simulation`` extra::" +"For simulations that use the Virtual Client Engine, ``flwr`` pre-releases" +" should be installed with the ``simulation`` extra::" msgstr "" #: ../../source/how-to-install-flower.rst:74 @@ -4523,14 +4474,14 @@ msgstr "" #: ../../source/how-to-install-flower.rst:76 msgid "" -"The latest (potentially unstable) changes in Flower are available as nightly " -"releases::" +"The latest (potentially unstable) changes in Flower are available as " +"nightly releases::" msgstr "" #: ../../source/how-to-install-flower.rst:80 msgid "" -"For simulations that use the Virtual Client Engine, ``flwr-nightly`` should " -"be installed with the ``simulation`` extra::" +"For simulations that use the Virtual Client Engine, ``flwr-nightly`` " +"should be installed with the ``simulation`` extra::" msgstr "" #: ../../source/how-to-monitor-simulation.rst:2 @@ -4539,17 +4490,17 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:4 msgid "" -"Flower allows you to monitor system resources while running your simulation. " -"Moreover, the Flower simulation engine is powerful and enables you to decide " -"how to allocate resources per client manner and constrain the total usage. " -"Insights from resource consumption can help you make smarter decisions and " -"speed up the execution time." +"Flower allows you to monitor system resources while running your " +"simulation. Moreover, the Flower simulation engine is powerful and " +"enables you to decide how to allocate resources per client manner and " +"constrain the total usage. Insights from resource consumption can help " +"you make smarter decisions and speed up the execution time." msgstr "" #: ../../source/how-to-monitor-simulation.rst:6 msgid "" -"The specific instructions assume you are using macOS and have the `Homebrew " -"`_ package manager installed." +"The specific instructions assume you are using macOS and have the " +"`Homebrew `_ package manager installed." msgstr "" #: ../../source/how-to-monitor-simulation.rst:10 @@ -4558,10 +4509,10 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:16 msgid "" -"`Prometheus `_ is used for data collection, while " -"`Grafana `_ will enable you to visualize the collected " -"data. They are both well integrated with `Ray `_ which " -"Flower uses under the hood." +"`Prometheus `_ is used for data collection, while" +" `Grafana `_ will enable you to visualize the " +"collected data. They are both well integrated with `Ray " +"`_ which Flower uses under the hood." msgstr "" #: ../../source/how-to-monitor-simulation.rst:18 @@ -4580,21 +4531,22 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:34 msgid "" -"Open the respective configuration files and change them. Depending on your " -"device, use one of the two following commands:" +"Open the respective configuration files and change them. Depending on " +"your device, use one of the two following commands:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:44 msgid "" -"and then delete all the text in the file and paste a new Prometheus config " -"you see below. You may adjust the time intervals to your requirements:" +"and then delete all the text in the file and paste a new Prometheus " +"config you see below. You may adjust the time intervals to your " +"requirements:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:59 msgid "" -"Now after you have edited the Prometheus configuration, do the same with the " -"Grafana configuration files. Open those using one of the following commands " -"as before:" +"Now after you have edited the Prometheus configuration, do the same with " +"the Grafana configuration files. Open those using one of the following " +"commands as before:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:69 @@ -4605,8 +4557,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:84 msgid "" -"Congratulations, you just downloaded all the necessary software needed for " -"metrics tracking. Now, let’s start it." +"Congratulations, you just downloaded all the necessary software needed " +"for metrics tracking. Now, let’s start it." msgstr "" #: ../../source/how-to-monitor-simulation.rst:88 @@ -4621,8 +4573,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:97 msgid "" -"Please include the following argument in your Python code when starting a " -"simulation." +"Please include the following argument in your Python code when starting a" +" simulation." msgstr "" #: ../../source/how-to-monitor-simulation.rst:108 @@ -4631,8 +4583,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:110 msgid "" -"Shortly after the simulation starts, you should see the following logs in " -"your terminal:" +"Shortly after the simulation starts, you should see the following logs in" +" your terminal:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:117 @@ -4641,17 +4593,17 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:119 msgid "" -"It's a Ray Dashboard. You can navigate to Metrics (on the left panel, the " -"lowest option)." +"It's a Ray Dashboard. You can navigate to Metrics (on the left panel, the" +" lowest option)." msgstr "" #: ../../source/how-to-monitor-simulation.rst:121 msgid "" -"Or alternatively, you can just see them in Grafana by clicking on the right-" -"up corner, “View in Grafana”. Please note that the Ray dashboard is only " -"accessible during the simulation. After the simulation ends, you can only " -"use Grafana to explore the metrics. You can start Grafana by going to " -"``http://localhost:3000/``." +"Or alternatively, you can just see them in Grafana by clicking on the " +"right-up corner, “View in Grafana”. Please note that the Ray dashboard is" +" only accessible during the simulation. After the simulation ends, you " +"can only use Grafana to explore the metrics. You can start Grafana by " +"going to ``http://localhost:3000/``." msgstr "" #: ../../source/how-to-monitor-simulation.rst:123 @@ -4667,18 +4619,18 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:134 msgid "" -"You must understand how the Ray library works to efficiently allocate system " -"resources to simulation clients on your own." +"You must understand how the Ray library works to efficiently allocate " +"system resources to simulation clients on your own." msgstr "" #: ../../source/how-to-monitor-simulation.rst:136 msgid "" "Initially, the simulation (which Ray handles under the hood) starts by " "default with all the available resources on the system, which it shares " -"among the clients. It doesn't mean it divides it equally among all of them, " -"nor that the model training happens at all of them simultaneously. You will " -"learn more about that in the later part of this blog. You can check the " -"system resources by running the following:" +"among the clients. It doesn't mean it divides it equally among all of " +"them, nor that the model training happens at all of them simultaneously. " +"You will learn more about that in the later part of this blog. You can " +"check the system resources by running the following:" msgstr "" #: ../../source/how-to-monitor-simulation.rst:143 @@ -4687,8 +4639,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:155 msgid "" -"However, you can overwrite the defaults. When starting a simulation, do the " -"following (you don't need to overwrite all of them):" +"However, you can overwrite the defaults. When starting a simulation, do " +"the following (you don't need to overwrite all of them):" msgstr "" #: ../../source/how-to-monitor-simulation.rst:175 @@ -4697,19 +4649,19 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:205 msgid "" -"Now comes the crucial part. Ray will start a new client only when it has all " -"the required resources (such that they run in parallel) when the resources " -"allow." +"Now comes the crucial part. Ray will start a new client only when it has " +"all the required resources (such that they run in parallel) when the " +"resources allow." msgstr "" #: ../../source/how-to-monitor-simulation.rst:207 msgid "" -"In the example above, only one client will be run, so your clients won't run " -"concurrently. Setting :code:`client_num_gpus = 0.5` would allow running two " -"clients and therefore enable them to run concurrently. Be careful not to " -"require more resources than available. If you specified :code:" -"`client_num_gpus = 2`, the simulation wouldn't start (even if you had 2 GPUs " -"but decided to set 1 in :code:`ray_init_args`)." +"In the example above, only one client will be run, so your clients won't " +"run concurrently. Setting :code:`client_num_gpus = 0.5` would allow " +"running two clients and therefore enable them to run concurrently. Be " +"careful not to require more resources than available. If you specified " +":code:`client_num_gpus = 2`, the simulation wouldn't start (even if you " +"had 2 GPUs but decided to set 1 in :code:`ray_init_args`)." msgstr "" #: ../../source/how-to-monitor-simulation.rst:212 ../../source/ref-faq.rst:2 @@ -4722,21 +4674,22 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:216 msgid "" -"A: The timeframe might not be properly set. The setting is in the top right " -"corner (\"Last 30 minutes\" by default). Please change the timeframe to " -"reflect the period when the simulation was running." +"A: The timeframe might not be properly set. The setting is in the top " +"right corner (\"Last 30 minutes\" by default). Please change the " +"timeframe to reflect the period when the simulation was running." msgstr "" #: ../../source/how-to-monitor-simulation.rst:218 msgid "" -"Q: I see “Grafana server not detected. Please make sure the Grafana server " -"is running and refresh this page” after going to the Metrics tab in Ray " -"Dashboard." +"Q: I see “Grafana server not detected. Please make sure the Grafana " +"server is running and refresh this page” after going to the Metrics tab " +"in Ray Dashboard." msgstr "" #: ../../source/how-to-monitor-simulation.rst:220 msgid "" -"A: You probably don't have Grafana running. Please check the running services" +"A: You probably don't have Grafana running. Please check the running " +"services" msgstr "" #: ../../source/how-to-monitor-simulation.rst:226 @@ -4747,8 +4700,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:228 msgid "" -"A: Either the simulation has already finished, or you still need to start " -"Prometheus." +"A: Either the simulation has already finished, or you still need to start" +" Prometheus." msgstr "" #: ../../source/how-to-monitor-simulation.rst:232 @@ -4771,475 +4724,545 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:4 msgid "" -"The simplest way to get started with Flower is by using the pre-made Docker " -"images, which you can find on `Docker Hub `__." +"The simplest way to get started with Flower is by using the pre-made " +"Docker images, which you can find on `Docker Hub " +"`__. Supported architectures include " +"``amd64`` and ``arm64v8``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:7 +#: ../../source/how-to-run-flower-using-docker.rst:8 msgid "Before you start, make sure that the Docker daemon is running:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:14 +#: ../../source/how-to-run-flower-using-docker.rst:15 msgid "" -"If you do not see the version of Docker but instead get an error saying that " -"the command was not found, you will need to install Docker first. You can " -"find installation instruction `here `_." +"If you do not see the version of Docker but instead get an error saying " +"that the command was not found, you will need to install Docker first. " +"You can find installation instruction `here `_." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:21 msgid "" -"On Linux, Docker commands require ``sudo`` privilege. If you want to avoid " -"using ``sudo``, you can follow the `Post-installation steps `_ on the official Docker " -"website." +"On Linux, Docker commands require ``sudo`` privilege. If you want to " +"avoid using ``sudo``, you can follow the `Post-installation steps " +"`_ on the " +"official Docker website." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:26 +#: ../../source/how-to-run-flower-using-docker.rst:27 msgid "" -"To ensure optimal performance and compatibility, the SuperLink, SuperNode " -"and ServerApp image must have the same version when running together. This " -"guarantees seamless integration and avoids potential conflicts or issues " -"that may arise from using different versions." +"To ensure optimal performance and compatibility, the SuperLink, SuperNode" +" and ServerApp image must have the same version when running together. " +"This guarantees seamless integration and avoids potential conflicts or " +"issues that may arise from using different versions." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:31 +#: ../../source/how-to-run-flower-using-docker.rst:32 msgid "Flower SuperLink" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:34 +#: ../../source/how-to-run-flower-using-docker.rst:35 msgid "Quickstart" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:36 +#: ../../source/how-to-run-flower-using-docker.rst:37 msgid "If you're looking to try out Flower, you can use the following command:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:42 +#: ../../source/how-to-run-flower-using-docker.rst:43 msgid "" -"The command pulls the Docker image with the tag ``1.8.0`` from Docker Hub. " -"The tag specifies the Flower version. In this case, Flower 1.8.0. The ``--" -"rm`` flag tells Docker to remove the container after it exits." +"The command pulls the Docker image with the tag ``1.8.0`` from Docker " +"Hub. The tag specifies the Flower version. In this case, Flower 1.8.0. " +"The ``--rm`` flag tells Docker to remove the container after it exits." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:48 +#: ../../source/how-to-run-flower-using-docker.rst:49 msgid "" "By default, the Flower SuperLink keeps state in-memory. When using the " -"Docker flag ``--rm``, the state is not persisted between container starts. " -"We will show below how to save the state in a file on your host system." +"Docker flag ``--rm``, the state is not persisted between container " +"starts. We will show below how to save the state in a file on your host " +"system." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:52 +#: ../../source/how-to-run-flower-using-docker.rst:53 msgid "" -"The ``-p :`` flag tells Docker to map the ports ``9091``/" -"``9092`` of the host to ``9091``/``9092`` of the container, allowing you to " -"access the Driver API on ``http://localhost:9091`` and the Fleet API on " -"``http://localhost:9092``. Lastly, any flag that comes after the tag is " -"passed to the Flower SuperLink. Here, we are passing the flag ``--insecure``." +"The ``-p :`` flag tells Docker to map the ports " +"``9091``/``9092`` of the host to ``9091``/``9092`` of the container, " +"allowing you to access the Driver API on ``http://localhost:9091`` and " +"the Fleet API on ``http://localhost:9092``. Lastly, any flag that comes " +"after the tag is passed to the Flower SuperLink. Here, we are passing the" +" flag ``--insecure``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:59 -#: ../../source/how-to-run-flower-using-docker.rst:238 -#: ../../source/how-to-run-flower-using-docker.rst:354 +#: ../../source/how-to-run-flower-using-docker.rst:60 +#: ../../source/how-to-run-flower-using-docker.rst:259 +#: ../../source/how-to-run-flower-using-docker.rst:376 msgid "" "The ``--insecure`` flag enables insecure communication (using HTTP, not " -"HTTPS) and should only be used for testing purposes. We strongly recommend " -"enabling `SSL `__ when deploying to a " -"production environment." +"HTTPS) and should only be used for testing purposes. We strongly " +"recommend enabling `SSL `__ when " +"deploying to a production environment." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:64 +#: ../../source/how-to-run-flower-using-docker.rst:65 msgid "" "You can use ``--help`` to view all available flags that the SuperLink " "supports:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:71 +#: ../../source/how-to-run-flower-using-docker.rst:72 msgid "Mounting a volume to store the state on the host system" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:73 +#: ../../source/how-to-run-flower-using-docker.rst:74 +msgid "" +"If you want to persist the state of the SuperLink on your host system, " +"all you need to do is specify a directory where you want to save the file" +" on your host system and a name for the database file. By default, the " +"SuperLink container runs with a non-root user called ``app`` with the " +"user ID ``49999``. It is recommended to create new directory and change " +"the user ID of the directory to ``49999`` to ensure the mounted directory" +" has the proper permissions. If you later want to delete the directory, " +"you can change the user ID back to the current user ID by running ``sudo " +"chown -R $USER:$(id -gn) state``." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:82 msgid "" -"If you want to persist the state of the SuperLink on your host system, all " -"you need to do is specify a path where you want to save the file on your " -"host system and a name for the database file. In the example below, we tell " -"Docker via the flag ``--volume`` to mount the user's home directory (``~/`` " -"on your host) into the ``/app/`` directory of the container. Furthermore, we " -"use the flag ``--database`` to specify the name of the database file." +"In the example below, we create a new directory, change the user ID and " +"tell Docker via the flag ``--volume`` to mount the local ``state`` " +"directory into the ``/app/state`` directory of the container. " +"Furthermore, we use the flag ``--database`` to specify the name of the " +"database file." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:86 +#: ../../source/how-to-run-flower-using-docker.rst:95 msgid "" "As soon as the SuperLink starts, the file ``state.db`` is created in the " -"user's home directory on your host system. If the file already exists, the " -"SuperLink tries to restore the state from the file. To start the SuperLink " -"with an empty database, simply remove the ``state.db`` file." +"``state`` directory on your host system. If the file already exists, the " +"SuperLink tries to restore the state from the file. To start the " +"SuperLink with an empty database, simply remove the ``state.db`` file." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:91 -#: ../../source/how-to-run-flower-using-docker.rst:260 -#: ../../source/how-to-run-flower-using-docker.rst:375 +#: ../../source/how-to-run-flower-using-docker.rst:100 +#: ../../source/how-to-run-flower-using-docker.rst:281 +#: ../../source/how-to-run-flower-using-docker.rst:397 msgid "Enabling SSL for secure connections" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:93 +#: ../../source/how-to-run-flower-using-docker.rst:102 +msgid "" +"To enable SSL, you will need a PEM-encoded root certificate, a PEM-" +"encoded private key and a PEM-encoded certificate chain." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:106 msgid "" -"To enable SSL, you will need a PEM-encoded root certificate, a PEM-encoded " -"private key and a PEM-encoded certificate chain." +"For testing purposes, you can generate your own self-signed certificates." +" The `Enable SSL connections `__ page contains a section that" +" will guide you through the process." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:97 +#: ../../source/how-to-run-flower-using-docker.rst:110 msgid "" -"For testing purposes, you can generate your own self-signed certificates. " -"The `Enable SSL connections `__ page contains a section that will " -"guide you through the process." +"Assuming all files we need are in the local ``certificates`` directory, " +"we can use the flag ``--volume`` to mount the local directory into the " +"``/app/certificates/`` directory of the container. This allows the " +"SuperLink to access the files within the container. The ``ro`` stands for" +" ``read-only``. Docker volumes default to ``read-write``; that option " +"tells Docker to make the volume ``read-only`` instead. Finally, we pass " +"the names of the certificates and key file to the SuperLink with the " +"``--ssl-ca-certfile``, ``--ssl-certfile`` and ``--ssl-keyfile`` flag." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:101 +#: ../../source/how-to-run-flower-using-docker.rst:128 msgid "" -"Assuming all files we need are in the local ``certificates`` directory, we " -"can use the flag ``--volume`` to mount the local directory into the ``/app/" -"`` directory of the container. This allows the SuperLink to access the files " -"within the container. Finally, we pass the names of the certificates to the " -"SuperLink with the ``--certificates`` flag." +"Because Flower containers, by default, run with a non-root user ``app``, " +"the mounted files and directories must have the proper permissions for " +"the user ID ``49999``. For example, to change the user ID of all files in" +" the ``certificates/`` directory, you can run ``sudo chown -R 49999:49999" +" certificates/*``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:113 +#: ../../source/how-to-run-flower-using-docker.rst:134 msgid "Flower SuperNode" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:115 +#: ../../source/how-to-run-flower-using-docker.rst:136 msgid "" -"The SuperNode Docker image comes with a pre-installed version of Flower and " -"serves as a base for building your own SuperNode image." +"The SuperNode Docker image comes with a pre-installed version of Flower " +"and serves as a base for building your own SuperNode image." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:120 +#: ../../source/how-to-run-flower-using-docker.rst:141 msgid "" "The SuperNode Docker image currently works only with the 1.9.0-nightly " -"release. A stable version will be available when Flower 1.9.0 (stable) gets " -"released (ETA: May). A SuperNode nightly image must be paired with the " -"corresponding SuperLink and ServerApp nightly images released on the same " -"day. To ensure the versions are in sync, using the concrete tag, e.g., " -"``1.9.0.dev20240501`` instead of ``nightly`` is recommended." +"release. A stable version will be available when Flower 1.9.0 (stable) " +"gets released (ETA: May). A SuperNode nightly image must be paired with " +"the corresponding SuperLink and ServerApp nightly images released on the " +"same day. To ensure the versions are in sync, using the concrete tag, " +"e.g., ``1.9.0.dev20240501`` instead of ``nightly`` is recommended." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:126 +#: ../../source/how-to-run-flower-using-docker.rst:147 msgid "" -"We will use the ``quickstart-pytorch`` example, which you can find in the " -"Flower repository, to illustrate how you can dockerize your ClientApp." +"We will use the ``quickstart-pytorch`` example, which you can find in the" +" Flower repository, to illustrate how you can dockerize your ClientApp." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:134 +#: ../../source/how-to-run-flower-using-docker.rst:155 msgid "" "Before we can start, we need to meet a few prerequisites in our local " -"development environment. You can skip the first part if you want to run your " -"ClientApp instead of the ``quickstart-pytorch`` example." +"development environment. You can skip the first part if you want to run " +"your ClientApp instead of the ``quickstart-pytorch`` example." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:138 +#: ../../source/how-to-run-flower-using-docker.rst:159 msgid "Clone the Flower repository." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:152 +#: ../../source/how-to-run-flower-using-docker.rst:173 msgid "Creating a SuperNode Dockerfile" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:154 -#: ../../source/how-to-run-flower-using-docker.rst:289 +#: ../../source/how-to-run-flower-using-docker.rst:175 +#: ../../source/how-to-run-flower-using-docker.rst:311 msgid "Let's assume the following project layout:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:163 +#: ../../source/how-to-run-flower-using-docker.rst:184 msgid "" -"First, we need to create a ``requirements.txt`` file in the directory where " -"the ``ClientApp`` code is located. In the file, we list all the dependencies " -"that the ClientApp requires." +"First, we need to create a ``requirements.txt`` file in the directory " +"where the ``ClientApp`` code is located. In the file, we list all the " +"dependencies that the ClientApp requires." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:175 +#: ../../source/how-to-run-flower-using-docker.rst:196 msgid "" -"Note that `flwr `__ is already installed in " -"the ``flwr/supernode`` base image, so you only need to include other package " -"dependencies in your ``requirements.txt``, such as ``torch``, " +"Note that `flwr `__ is already installed " +"in the ``flwr/supernode`` base image, so you only need to include other " +"package dependencies in your ``requirements.txt``, such as ``torch``, " "``tensorflow``, etc." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:179 +#: ../../source/how-to-run-flower-using-docker.rst:200 msgid "" -"Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` example, " -"create a new file called ``Dockerfile.supernode`` in ``examples/quickstart-" -"pytorch``." +"Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` " +"example, create a new file called ``Dockerfile.supernode`` in ``examples" +"/quickstart-pytorch``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:182 +#: ../../source/how-to-run-flower-using-docker.rst:203 msgid "" "The ``Dockerfile.supernode`` contains the instructions that assemble the " "SuperNode image." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:196 +#: ../../source/how-to-run-flower-using-docker.rst:217 msgid "" -"In the first two lines, we instruct Docker to use the SuperNode image tagged " -"``nightly`` as a base image and set our working directory to ``/app``. The " -"following instructions will now be executed in the ``/app`` directory. Next, " -"we install the ClientApp dependencies by copying the ``requirements.txt`` " -"file into the image and run ``pip install``. In the last two lines, we copy " -"the ``client.py`` module into the image and set the entry point to ``flower-" -"client-app`` with the argument ``client:app``. The argument is the object " -"reference of the ClientApp (``:``) that will be run " -"inside the ClientApp." +"In the first two lines, we instruct Docker to use the SuperNode image " +"tagged ``nightly`` as a base image and set our working directory to " +"``/app``. The following instructions will now be executed in the ``/app``" +" directory. Next, we install the ClientApp dependencies by copying the " +"``requirements.txt`` file into the image and run ``pip install``. In the " +"last two lines, we copy the ``client.py`` module into the image and set " +"the entry point to ``flower-client-app`` with the argument " +"``client:app``. The argument is the object reference of the ClientApp " +"(``:``) that will be run inside the ClientApp." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:205 +#: ../../source/how-to-run-flower-using-docker.rst:226 msgid "Building the SuperNode Docker image" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:207 +#: ../../source/how-to-run-flower-using-docker.rst:228 msgid "" -"Next, we build the SuperNode Docker image by running the following command " -"in the directory where Dockerfile and ClientApp code are located." +"Next, we build the SuperNode Docker image by running the following " +"command in the directory where Dockerfile and ClientApp code are located." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:214 +#: ../../source/how-to-run-flower-using-docker.rst:235 msgid "" "We gave the image the name ``flwr_supernode``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:219 +#: ../../source/how-to-run-flower-using-docker.rst:240 msgid "Running the SuperNode Docker image" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:221 +#: ../../source/how-to-run-flower-using-docker.rst:242 msgid "Now that we have built the SuperNode image, we can finally run it." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:229 -#: ../../source/how-to-run-flower-using-docker.rst:345 +#: ../../source/how-to-run-flower-using-docker.rst:250 +#: ../../source/how-to-run-flower-using-docker.rst:367 msgid "Let's break down each part of this command:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:231 -#: ../../source/how-to-run-flower-using-docker.rst:347 +#: ../../source/how-to-run-flower-using-docker.rst:252 +#: ../../source/how-to-run-flower-using-docker.rst:369 msgid "``docker run``: This is the command to run a new Docker container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:232 -#: ../../source/how-to-run-flower-using-docker.rst:348 +#: ../../source/how-to-run-flower-using-docker.rst:253 +#: ../../source/how-to-run-flower-using-docker.rst:370 msgid "" -"``--rm``: This option specifies that the container should be automatically " -"removed when it stops." +"``--rm``: This option specifies that the container should be " +"automatically removed when it stops." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:233 +#: ../../source/how-to-run-flower-using-docker.rst:254 msgid "``flwr_supernode:0.0.1``: The name the tag of the Docker image to use." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:234 -#: ../../source/how-to-run-flower-using-docker.rst:350 +#: ../../source/how-to-run-flower-using-docker.rst:255 +#: ../../source/how-to-run-flower-using-docker.rst:372 msgid "``--insecure``: This option enables insecure communication." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--server 192.168.1.100:9092``: This option specifies the address of the " -"SuperLinks Fleet" +"``--superlink 192.168.1.100:9092``: This option specifies the address of " +"the SuperLinks Fleet" msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "API to connect to. Remember to update it with your SuperLink IP." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:248 +#: ../../source/how-to-run-flower-using-docker.rst:269 msgid "" -"To test running Flower locally, you can create a `bridge network `__, use the ``--network`` argument and pass the name of the Docker " -"network to run your SuperNodes." +"To test running Flower locally, you can create a `bridge network " +"`__, use the ``--network`` argument and pass the " +"name of the Docker network to run your SuperNodes." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:252 +#: ../../source/how-to-run-flower-using-docker.rst:273 msgid "" "Any argument that comes after the tag is passed to the Flower SuperNode " "binary. To see all available flags that the SuperNode supports, run:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:262 +#: ../../source/how-to-run-flower-using-docker.rst:283 msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your SuperNode container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:264 +#: ../../source/how-to-run-flower-using-docker.rst:285 msgid "" -"Assuming the certificate already exists locally, we can use the flag ``--" -"volume`` to mount the local certificate into the container's ``/app/`` " -"directory. This allows the SuperNode to access the certificate within the " -"container. Use the ``--certificates`` flag when starting the container." +"Assuming the certificate already exists locally, we can use the flag " +"``--volume`` to mount the local certificate into the container's " +"``/app/`` directory. This allows the SuperNode to access the certificate " +"within the container. Use the ``--root-certificates`` flag when starting " +"the container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:275 +#: ../../source/how-to-run-flower-using-docker.rst:297 msgid "Flower ServerApp" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:277 +#: ../../source/how-to-run-flower-using-docker.rst:299 msgid "" -"The procedure for building and running a ServerApp image is almost identical " -"to the SuperNode image." +"The procedure for building and running a ServerApp image is almost " +"identical to the SuperNode image." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:279 +#: ../../source/how-to-run-flower-using-docker.rst:301 msgid "" -"Similar to the SuperNode image, the ServerApp Docker image comes with a pre-" -"installed version of Flower and serves as a base for building your own " -"ServerApp image." +"Similar to the SuperNode image, the ServerApp Docker image comes with a " +"pre-installed version of Flower and serves as a base for building your " +"own ServerApp image." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:282 +#: ../../source/how-to-run-flower-using-docker.rst:304 msgid "" -"We will use the same ``quickstart-pytorch`` example as we do in the Flower " -"SuperNode section. If you have not already done so, please follow the " -"`SuperNode Prerequisites`_ before proceeding." +"We will use the same ``quickstart-pytorch`` example as we do in the " +"Flower SuperNode section. If you have not already done so, please follow " +"the `SuperNode Prerequisites`_ before proceeding." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:287 +#: ../../source/how-to-run-flower-using-docker.rst:309 msgid "Creating a ServerApp Dockerfile" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:298 +#: ../../source/how-to-run-flower-using-docker.rst:320 msgid "" "First, we need to create a Dockerfile in the directory where the " "``ServerApp`` code is located. If you use the ``quickstart-pytorch`` " -"example, create a new file called ``Dockerfile.serverapp`` in ``examples/" -"quickstart-pytorch``." +"example, create a new file called ``Dockerfile.serverapp`` in ``examples" +"/quickstart-pytorch``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:302 +#: ../../source/how-to-run-flower-using-docker.rst:324 msgid "" "The ``Dockerfile.serverapp`` contains the instructions that assemble the " "ServerApp image." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:313 +#: ../../source/how-to-run-flower-using-docker.rst:335 msgid "" -"In the first two lines, we instruct Docker to use the ServerApp image tagged " -"``1.8.0`` as a base image and set our working directory to ``/app``. The " -"following instructions will now be executed in the ``/app`` directory. In " -"the last two lines, we copy the ``server.py`` module into the image and set " -"the entry point to ``flower-server-app`` with the argument ``server:app``. " -"The argument is the object reference of the ServerApp (``:" -"``) that will be run inside the ServerApp container." +"In the first two lines, we instruct Docker to use the ServerApp image " +"tagged ``1.8.0`` as a base image and set our working directory to " +"``/app``. The following instructions will now be executed in the ``/app``" +" directory. In the last two lines, we copy the ``server.py`` module into " +"the image and set the entry point to ``flower-server-app`` with the " +"argument ``server:app``. The argument is the object reference of the " +"ServerApp (``:``) that will be run inside the " +"ServerApp container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:321 +#: ../../source/how-to-run-flower-using-docker.rst:343 msgid "Building the ServerApp Docker image" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:323 +#: ../../source/how-to-run-flower-using-docker.rst:345 msgid "" -"Next, we build the ServerApp Docker image by running the following command " -"in the directory where Dockerfile and ServerApp code are located." +"Next, we build the ServerApp Docker image by running the following " +"command in the directory where Dockerfile and ServerApp code are located." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:330 +#: ../../source/how-to-run-flower-using-docker.rst:352 msgid "" "We gave the image the name ``flwr_serverapp``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:335 +#: ../../source/how-to-run-flower-using-docker.rst:357 msgid "Running the ServerApp Docker image" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:337 +#: ../../source/how-to-run-flower-using-docker.rst:359 msgid "Now that we have built the ServerApp image, we can finally run it." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:349 +#: ../../source/how-to-run-flower-using-docker.rst:371 msgid "``flwr_serverapp:0.0.1``: The name the tag of the Docker image to use." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--server 192.168.1.100:9091``: This option specifies the address of the " -"SuperLinks Driver" +"``--superlink 192.168.1.100:9091``: This option specifies the address of " +"the SuperLinks Driver" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:363 +#: ../../source/how-to-run-flower-using-docker.rst:385 msgid "" -"To test running Flower locally, you can create a `bridge network `__, use the ``--network`` argument and pass the name of the Docker " -"network to run your ServerApps." +"To test running Flower locally, you can create a `bridge network " +"`__, use the ``--network`` argument and pass the " +"name of the Docker network to run your ServerApps." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:367 +#: ../../source/how-to-run-flower-using-docker.rst:389 msgid "" "Any argument that comes after the tag is passed to the Flower ServerApp " "binary. To see all available flags that the ServerApp supports, run:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:377 +#: ../../source/how-to-run-flower-using-docker.rst:399 msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your ServerApp container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:379 +#: ../../source/how-to-run-flower-using-docker.rst:401 msgid "" -"Assuming the certificate already exists locally, we can use the flag ``--" -"volume`` to mount the local certificate into the container's ``/app/`` " -"directory. This allows the ServerApp to access the certificate within the " -"container. Use the ``--certificates`` flag when starting the container." +"Assuming the certificate already exists locally, we can use the flag " +"``--volume`` to mount the local certificate into the container's " +"``/app/`` directory. This allows the ServerApp to access the certificate " +"within the container. Use the ``--root-certificates`` flags when starting" +" the container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:390 +#: ../../source/how-to-run-flower-using-docker.rst:412 msgid "Advanced Docker options" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:393 +#: ../../source/how-to-run-flower-using-docker.rst:415 +msgid "Run with root user privileges" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:417 +msgid "" +"Flower Docker images, by default, run with a non-root user " +"(username/groupname: ``app``, UID/GID: ``49999``). Using root user is not" +" recommended unless it is necessary for specific tasks during the build " +"process. Always make sure to run the container as a non-root user in " +"production to maintain security best practices." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:422 +msgid "**Run a container with root user privileges**" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:424 +msgid "" +"Run the Docker image with the ``-u`` flag and specify ``root`` as the " +"username:" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:430 +msgid "This command will run the Docker container with root user privileges." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:432 +msgid "**Run the build process with root user privileges**" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:434 +msgid "" +"If you want to switch to the root user during the build process of the " +"Docker image to install missing system dependencies, you can use the " +"``USER root`` directive within your Dockerfile." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:454 msgid "Using a different Flower version" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:395 +#: ../../source/how-to-run-flower-using-docker.rst:456 msgid "" "If you want to use a different version of Flower, for example Flower " -"nightly, you can do so by changing the tag. All available versions are on " -"`Docker Hub `__." +"nightly, you can do so by changing the tag. All available versions are on" +" `Docker Hub `__." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:400 +#: ../../source/how-to-run-flower-using-docker.rst:460 msgid "Pinning a Docker image to a specific version" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:402 +#: ../../source/how-to-run-flower-using-docker.rst:462 msgid "" "It may happen that we update the images behind the tags. Such updates " "usually include security updates of system dependencies that should not " -"change the functionality of Flower. However, if you want to ensure that you " -"always use the same image, you can specify the hash of the image instead of " -"the tag." +"change the functionality of Flower. However, if you want to ensure that " +"you always use the same image, you can specify the hash of the image " +"instead of the tag." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:407 +#: ../../source/how-to-run-flower-using-docker.rst:467 msgid "" "The following command returns the current image hash referenced by the " "``superlink:1.8.0`` tag:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:414 +#: ../../source/how-to-run-flower-using-docker.rst:474 msgid "Next, we can pin the hash when running a new SuperLink container:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:423 +#: ../../source/how-to-run-flower-using-docker.rst:483 msgid "Setting environment variables" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:425 +#: ../../source/how-to-run-flower-using-docker.rst:485 msgid "" "To set a variable inside a Docker container, you can use the ``-e " "=`` flag." @@ -5252,60 +5275,63 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:8 msgid "" "Simulating Federated Learning workloads is useful for a multitude of use-" -"cases: you might want to run your workload on a large cohort of clients but " -"without having to source, configure and mange a large number of physical " -"devices; you might want to run your FL workloads as fast as possible on the " -"compute systems you have access to without having to go through a complex " -"setup process; you might want to validate your algorithm on different " -"scenarios at varying levels of data and system heterogeneity, client " -"availability, privacy budgets, etc. These are among some of the use-cases " -"where simulating FL workloads makes sense. Flower can accommodate these " -"scenarios by means of its `VirtualClientEngine `_ or VCE." +"cases: you might want to run your workload on a large cohort of clients " +"but without having to source, configure and mange a large number of " +"physical devices; you might want to run your FL workloads as fast as " +"possible on the compute systems you have access to without having to go " +"through a complex setup process; you might want to validate your " +"algorithm on different scenarios at varying levels of data and system " +"heterogeneity, client availability, privacy budgets, etc. These are among" +" some of the use-cases where simulating FL workloads makes sense. Flower " +"can accommodate these scenarios by means of its `VirtualClientEngine " +"`_ or " +"VCE." msgstr "" #: ../../source/how-to-run-simulations.rst:10 msgid "" -"The :code:`VirtualClientEngine` schedules, launches and manages `virtual` " -"clients. These clients are identical to `non-virtual` clients (i.e. the ones " -"you launch via the command `flwr.client.start_client `_) in the sense that they can be configure by creating a " -"class inheriting, for example, from `flwr.client.NumPyClient `_ and therefore behave in an identical way. In " -"addition to that, clients managed by the :code:`VirtualClientEngine` are:" +"The :code:`VirtualClientEngine` schedules, launches and manages `virtual`" +" clients. These clients are identical to `non-virtual` clients (i.e. the " +"ones you launch via the command `flwr.client.start_client `_) in the sense that they can be configure by " +"creating a class inheriting, for example, from `flwr.client.NumPyClient " +"`_ and therefore behave in an " +"identical way. In addition to that, clients managed by the " +":code:`VirtualClientEngine` are:" msgstr "" #: ../../source/how-to-run-simulations.rst:12 msgid "" -"resource-aware: this means that each client gets assigned a portion of the " -"compute and memory on your system. You as a user can control this at the " -"beginning of the simulation and allows you to control the degree of " +"resource-aware: this means that each client gets assigned a portion of " +"the compute and memory on your system. You as a user can control this at " +"the beginning of the simulation and allows you to control the degree of " "parallelism of your Flower FL simulation. The fewer the resources per " "client, the more clients can run concurrently on the same hardware." msgstr "" #: ../../source/how-to-run-simulations.rst:13 msgid "" -"self-managed: this means that you as a user do not need to launch clients " -"manually, instead this gets delegated to :code:`VirtualClientEngine`'s " +"self-managed: this means that you as a user do not need to launch clients" +" manually, instead this gets delegated to :code:`VirtualClientEngine`'s " "internals." msgstr "" #: ../../source/how-to-run-simulations.rst:14 msgid "" -"ephemeral: this means that a client is only materialized when it is required " -"in the FL process (e.g. to do `fit() `_). The object is destroyed afterwards, releasing the resources it was " -"assigned and allowing in this way other clients to participate." +"ephemeral: this means that a client is only materialized when it is " +"required in the FL process (e.g. to do `fit() `_). The object is destroyed afterwards," +" releasing the resources it was assigned and allowing in this way other " +"clients to participate." msgstr "" #: ../../source/how-to-run-simulations.rst:16 msgid "" "The :code:`VirtualClientEngine` implements `virtual` clients using `Ray " "`_, an open-source framework for scalable Python " -"workloads. In particular, Flower's :code:`VirtualClientEngine` makes use of " -"`Actors `_ to spawn " -"`virtual` clients and run their workload." +"workloads. In particular, Flower's :code:`VirtualClientEngine` makes use " +"of `Actors `_ to " +"spawn `virtual` clients and run their workload." msgstr "" #: ../../source/how-to-run-simulations.rst:20 @@ -5314,11 +5340,12 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:22 msgid "" -"Running Flower simulations still require you to define your client class, a " -"strategy, and utility functions to download and load (and potentially " -"partition) your dataset. With that out of the way, launching your simulation " -"is done with `start_simulation `_ and a minimal example looks as follows:" +"Running Flower simulations still require you to define your client class," +" a strategy, and utility functions to download and load (and potentially " +"partition) your dataset. With that out of the way, launching your " +"simulation is done with `start_simulation `_ and a minimal example looks" +" as follows:" msgstr "" #: ../../source/how-to-run-simulations.rst:44 @@ -5327,16 +5354,16 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:45 msgid "" -"By default the VCE has access to all system resources (i.e. all CPUs, all " -"GPUs, etc) since that is also the default behavior when starting Ray. " -"However, in some settings you might want to limit how many of your system " -"resources are used for simulation. You can do this via the :code:" -"`ray_init_args` input argument to :code:`start_simulation` which the VCE " -"internally passes to Ray's :code:`ray.init` command. For a complete list of " -"settings you can configure check the `ray.init `_ documentation. Do not set :" -"code:`ray_init_args` if you want the VCE to use all your system's CPUs and " -"GPUs." +"By default the VCE has access to all system resources (i.e. all CPUs, all" +" GPUs, etc) since that is also the default behavior when starting Ray. " +"However, in some settings you might want to limit how many of your system" +" resources are used for simulation. You can do this via the " +":code:`ray_init_args` input argument to :code:`start_simulation` which " +"the VCE internally passes to Ray's :code:`ray.init` command. For a " +"complete list of settings you can configure check the `ray.init " +"`_" +" documentation. Do not set :code:`ray_init_args` if you want the VCE to " +"use all your system's CPUs and GPUs." msgstr "" #: ../../source/how-to-run-simulations.rst:62 @@ -5345,19 +5372,20 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:63 msgid "" -"By default the :code:`VirtualClientEngine` assigns a single CPU core (and " -"nothing else) to each virtual client. This means that if your system has 10 " -"cores, that many virtual clients can be concurrently running." +"By default the :code:`VirtualClientEngine` assigns a single CPU core (and" +" nothing else) to each virtual client. This means that if your system has" +" 10 cores, that many virtual clients can be concurrently running." msgstr "" #: ../../source/how-to-run-simulations.rst:65 msgid "" -"More often than not, you would probably like to adjust the resources your " -"clients get assigned based on the complexity (i.e. compute and memory " -"footprint) of your FL workload. You can do so when starting your simulation " -"by setting the argument `client_resources` to `start_simulation `_. Two keys are internally used " -"by Ray to schedule and spawn workloads (in our case Flower clients):" +"More often than not, you would probably like to adjust the resources your" +" clients get assigned based on the complexity (i.e. compute and memory " +"footprint) of your FL workload. You can do so when starting your " +"simulation by setting the argument `client_resources` to " +"`start_simulation `_." +" Two keys are internally used by Ray to schedule and spawn workloads (in " +"our case Flower clients):" msgstr "" #: ../../source/how-to-run-simulations.rst:67 @@ -5378,21 +5406,21 @@ msgstr "" msgid "" "While the :code:`client_resources` can be used to control the degree of " "concurrency in your FL simulation, this does not stop you from running " -"dozens, hundreds or even thousands of clients in the same round and having " -"orders of magnitude more `dormant` (i.e. not participating in a round) " -"clients. Let's say you want to have 100 clients per round but your system " -"can only accommodate 8 clients concurrently. The :code:`VirtualClientEngine` " -"will schedule 100 jobs to run (each simulating a client sampled by the " -"strategy) and then will execute them in a resource-aware manner in batches " -"of 8." +"dozens, hundreds or even thousands of clients in the same round and " +"having orders of magnitude more `dormant` (i.e. not participating in a " +"round) clients. Let's say you want to have 100 clients per round but your" +" system can only accommodate 8 clients concurrently. The " +":code:`VirtualClientEngine` will schedule 100 jobs to run (each " +"simulating a client sampled by the strategy) and then will execute them " +"in a resource-aware manner in batches of 8." msgstr "" #: ../../source/how-to-run-simulations.rst:91 msgid "" "To understand all the intricate details on how resources are used to " -"schedule FL clients and how to define custom resources, please take a look " -"at the `Ray documentation `_." +"schedule FL clients and how to define custom resources, please take a " +"look at the `Ray documentation `_." msgstr "" #: ../../source/how-to-run-simulations.rst:94 @@ -5401,22 +5429,22 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:96 msgid "" -"A few ready-to-run complete examples for Flower simulation in Tensorflow/" -"Keras and PyTorch are provided in the `Flower repository `_. You can run them on Google Colab too:" +"A few ready-to-run complete examples for Flower simulation in " +"Tensorflow/Keras and PyTorch are provided in the `Flower repository " +"`_. You can run them on Google Colab too:" msgstr "" #: ../../source/how-to-run-simulations.rst:98 msgid "" -"`Tensorflow/Keras Simulation `_: 100 clients collaboratively train a MLP " -"model on MNIST." +"`Tensorflow/Keras Simulation " +"`_: 100 clients collaboratively train a MLP model on MNIST." msgstr "" #: ../../source/how-to-run-simulations.rst:99 msgid "" -"`PyTorch Simulation `_: 100 clients collaboratively train a CNN model on " +"`PyTorch Simulation `_: 100 clients collaboratively train a CNN model on " "MNIST." msgstr "" @@ -5426,9 +5454,9 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:106 msgid "" -"Flower's :code:`VirtualClientEngine` allows you to run FL simulations across " -"multiple compute nodes. Before starting your multi-node simulation ensure " -"that you:" +"Flower's :code:`VirtualClientEngine` allows you to run FL simulations " +"across multiple compute nodes. Before starting your multi-node simulation" +" ensure that you:" msgstr "" #: ../../source/how-to-run-simulations.rst:108 @@ -5441,29 +5469,29 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:110 msgid "" -"Have a copy of your dataset in all nodes (more about this in :ref:" -"`simulation considerations `)" +"Have a copy of your dataset in all nodes (more about this in " +":ref:`simulation considerations `)" msgstr "" #: ../../source/how-to-run-simulations.rst:111 msgid "" -"Pass :code:`ray_init_args={\"address\"=\"auto\"}` to `start_simulation `_ so the :code:" -"`VirtualClientEngine` attaches to a running Ray instance." +"Pass :code:`ray_init_args={\"address\"=\"auto\"}` to `start_simulation " +"`_ so the " +":code:`VirtualClientEngine` attaches to a running Ray instance." msgstr "" #: ../../source/how-to-run-simulations.rst:112 msgid "" -"Start Ray on you head node: on the terminal type :code:`ray start --head`. " -"This command will print a few lines, one of which indicates how to attach " -"other nodes to the head node." +"Start Ray on you head node: on the terminal type :code:`ray start " +"--head`. This command will print a few lines, one of which indicates how " +"to attach other nodes to the head node." msgstr "" #: ../../source/how-to-run-simulations.rst:113 msgid "" -"Attach other nodes to the head node: copy the command shown after starting " -"the head and execute it on terminal of a new node: for example :code:`ray " -"start --address='192.168.1.132:6379'`" +"Attach other nodes to the head node: copy the command shown after " +"starting the head and execute it on terminal of a new node: for example " +":code:`ray start --address='192.168.1.132:6379'`" msgstr "" #: ../../source/how-to-run-simulations.rst:115 @@ -5474,9 +5502,9 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:117 msgid "" -"Once your simulation is finished, if you'd like to dismantle your cluster " -"you simply need to run the command :code:`ray stop` in each node's terminal " -"(including the head node)." +"Once your simulation is finished, if you'd like to dismantle your cluster" +" you simply need to run the command :code:`ray stop` in each node's " +"terminal (including the head node)." msgstr "" #: ../../source/how-to-run-simulations.rst:120 @@ -5491,19 +5519,21 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:124 msgid "" -"User :code:`ray status` to check all nodes connected to your head node as " -"well as the total resources available to the :code:`VirtualClientEngine`." +"User :code:`ray status` to check all nodes connected to your head node as" +" well as the total resources available to the " +":code:`VirtualClientEngine`." msgstr "" #: ../../source/how-to-run-simulations.rst:126 msgid "" -"When attaching a new node to the head, all its resources (i.e. all CPUs, all " -"GPUs) will be visible by the head node. This means that the :code:" -"`VirtualClientEngine` can schedule as many `virtual` clients as that node " -"can possible run. In some settings you might want to exclude certain " -"resources from the simulation. You can do this by appending `--num-" -"cpus=` and/or `--num-gpus=` in any :" -"code:`ray start` command (including when starting the head)" +"When attaching a new node to the head, all its resources (i.e. all CPUs, " +"all GPUs) will be visible by the head node. This means that the " +":code:`VirtualClientEngine` can schedule as many `virtual` clients as " +"that node can possible run. In some settings you might want to exclude " +"certain resources from the simulation. You can do this by appending " +"`--num-cpus=` and/or `--num-" +"gpus=` in any :code:`ray start` command (including " +"when starting the head)" msgstr "" #: ../../source/how-to-run-simulations.rst:132 @@ -5512,19 +5542,19 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:135 msgid "" -"We are actively working on these fronts so to make it trivial to run any FL " -"workload with Flower simulation." +"We are actively working on these fronts so to make it trivial to run any " +"FL workload with Flower simulation." msgstr "" #: ../../source/how-to-run-simulations.rst:138 msgid "" -"The current VCE allows you to run Federated Learning workloads in simulation " -"mode whether you are prototyping simple scenarios on your personal laptop or " -"you want to train a complex FL pipeline across multiple high-performance GPU " -"nodes. While we add more capabilities to the VCE, the points below highlight " -"some of the considerations to keep in mind when designing your FL pipeline " -"with Flower. We also highlight a couple of current limitations in our " -"implementation." +"The current VCE allows you to run Federated Learning workloads in " +"simulation mode whether you are prototyping simple scenarios on your " +"personal laptop or you want to train a complex FL pipeline across " +"multiple high-performance GPU nodes. While we add more capabilities to " +"the VCE, the points below highlight some of the considerations to keep in" +" mind when designing your FL pipeline with Flower. We also highlight a " +"couple of current limitations in our implementation." msgstr "" #: ../../source/how-to-run-simulations.rst:141 @@ -5533,16 +5563,17 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:143 msgid "" -"The VCE assigns a share of GPU memory to a client that specifies the key :" -"code:`num_gpus` in :code:`client_resources`. This being said, Ray (used " +"The VCE assigns a share of GPU memory to a client that specifies the key " +":code:`num_gpus` in :code:`client_resources`. This being said, Ray (used " "internally by the VCE) is by default:" msgstr "" #: ../../source/how-to-run-simulations.rst:146 msgid "" -"not aware of the total VRAM available on the GPUs. This means that if you " -"set :code:`num_gpus=0.5` and you have two GPUs in your system with different " -"(e.g. 32GB and 8GB) VRAM amounts, they both would run 2 clients concurrently." +"not aware of the total VRAM available on the GPUs. This means that if you" +" set :code:`num_gpus=0.5` and you have two GPUs in your system with " +"different (e.g. 32GB and 8GB) VRAM amounts, they both would run 2 clients" +" concurrently." msgstr "" #: ../../source/how-to-run-simulations.rst:147 @@ -5561,16 +5592,17 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:150 msgid "" "If you want to run several independent Flower simulations on the same " -"machine you need to mask-out your GPUs with :code:" -"`CUDA_VISIBLE_DEVICES=\"\"` when launching your experiment." +"machine you need to mask-out your GPUs with " +":code:`CUDA_VISIBLE_DEVICES=\"\"` when launching your " +"experiment." msgstr "" #: ../../source/how-to-run-simulations.rst:153 msgid "" -"In addition, the GPU resource limits passed to :code:`client_resources` are " -"not `enforced` (i.e. they can be exceeded) which can result in the situation " -"of client using more VRAM than the ratio specified when starting the " -"simulation." +"In addition, the GPU resource limits passed to :code:`client_resources` " +"are not `enforced` (i.e. they can be exceeded) which can result in the " +"situation of client using more VRAM than the ratio specified when " +"starting the simulation." msgstr "" #: ../../source/how-to-run-simulations.rst:156 @@ -5579,29 +5611,31 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:158 msgid "" -"When `using a GPU with TensorFlow `_ " -"nearly your entire GPU memory of all your GPUs visible to the process will " -"be mapped. This is done by TensorFlow for optimization purposes. However, in " -"settings such as FL simulations where we want to split the GPU into multiple " -"`virtual` clients, this is not a desirable mechanism. Luckily we can disable " -"this default behavior by `enabling memory growth `_." +"When `using a GPU with TensorFlow " +"`_ nearly your entire GPU memory of" +" all your GPUs visible to the process will be mapped. This is done by " +"TensorFlow for optimization purposes. However, in settings such as FL " +"simulations where we want to split the GPU into multiple `virtual` " +"clients, this is not a desirable mechanism. Luckily we can disable this " +"default behavior by `enabling memory growth " +"`_." msgstr "" #: ../../source/how-to-run-simulations.rst:160 msgid "" -"This would need to be done in the main process (which is where the server " -"would run) and in each Actor created by the VCE. By means of :code:" -"`actor_kwargs` we can pass the reserved key `\"on_actor_init_fn\"` in order " -"to specify a function to be executed upon actor initialization. In this " -"case, to enable GPU growth for TF workloads. It would look as follows:" +"This would need to be done in the main process (which is where the server" +" would run) and in each Actor created by the VCE. By means of " +":code:`actor_kwargs` we can pass the reserved key `\"on_actor_init_fn\"` " +"in order to specify a function to be executed upon actor initialization. " +"In this case, to enable GPU growth for TF workloads. It would look as " +"follows:" msgstr "" #: ../../source/how-to-run-simulations.rst:179 msgid "" "This is precisely the mechanism used in `Tensorflow/Keras Simulation " -"`_ " -"example." +"`_ example." msgstr "" #: ../../source/how-to-run-simulations.rst:183 @@ -5610,25 +5644,26 @@ msgstr "" #: ../../source/how-to-run-simulations.rst:185 msgid "" -"The VCE does not currently offer a way to control on which node a particular " -"`virtual` client is executed. In other words, if more than a single node " -"have the resources needed by a client to run, then any of those nodes could " -"get the client workload scheduled onto. Later in the FL process (i.e. in a " -"different round) the same client could be executed by a different node. " -"Depending on how your clients access their datasets, this might require " -"either having a copy of all dataset partitions on all nodes or a dataset " -"serving mechanism (e.g. using nfs, a database) to circumvent data " -"duplication." +"The VCE does not currently offer a way to control on which node a " +"particular `virtual` client is executed. In other words, if more than a " +"single node have the resources needed by a client to run, then any of " +"those nodes could get the client workload scheduled onto. Later in the FL" +" process (i.e. in a different round) the same client could be executed by" +" a different node. Depending on how your clients access their datasets, " +"this might require either having a copy of all dataset partitions on all " +"nodes or a dataset serving mechanism (e.g. using nfs, a database) to " +"circumvent data duplication." msgstr "" #: ../../source/how-to-run-simulations.rst:187 msgid "" -"By definition virtual clients are `stateless` due to their ephemeral nature. " -"A client state can be implemented as part of the Flower client class but " -"users need to ensure this saved to persistent storage (e.g. a database, " -"disk) and that can be retrieve later by the same client regardless on which " -"node it is running from. This is related to the point above also since, in " -"some way, the client's dataset could be seen as a type of `state`." +"By definition virtual clients are `stateless` due to their ephemeral " +"nature. A client state can be implemented as part of the Flower client " +"class but users need to ensure this saved to persistent storage (e.g. a " +"database, disk) and that can be retrieve later by the same client " +"regardless on which node it is running from. This is related to the point" +" above also since, in some way, the client's dataset could be seen as a " +"type of `state`." msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:2 @@ -5637,9 +5672,9 @@ msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:4 msgid "" -"Flower does not automatically save model updates on the server-side. This " -"how-to guide describes the steps to save (and load) model checkpoints in " -"Flower." +"Flower does not automatically save model updates on the server-side. This" +" how-to guide describes the steps to save (and load) model checkpoints in" +" Flower." msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:8 @@ -5648,15 +5683,16 @@ msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:10 msgid "" -"Model updates can be persisted on the server-side by customizing :code:" -"`Strategy` methods. Implementing custom strategies is always an option, but " -"for many cases it may be more convenient to simply customize an existing " -"strategy. The following code example defines a new :code:`SaveModelStrategy` " -"which customized the existing built-in :code:`FedAvg` strategy. In " -"particular, it customizes :code:`aggregate_fit` by calling :code:" -"`aggregate_fit` in the base class (:code:`FedAvg`). It then continues to " -"save returned (aggregated) weights before it returns those aggregated " -"weights to the caller (i.e., the server):" +"Model updates can be persisted on the server-side by customizing " +":code:`Strategy` methods. Implementing custom strategies is always an " +"option, but for many cases it may be more convenient to simply customize " +"an existing strategy. The following code example defines a new " +":code:`SaveModelStrategy` which customized the existing built-in " +":code:`FedAvg` strategy. In particular, it customizes " +":code:`aggregate_fit` by calling :code:`aggregate_fit` in the base class " +"(:code:`FedAvg`). It then continues to save returned (aggregated) weights" +" before it returns those aggregated weights to the caller (i.e., the " +"server):" msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:47 @@ -5665,25 +5701,25 @@ msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:49 msgid "" -"Similar to the previous example but with a few extra steps, we'll show how " -"to store a PyTorch checkpoint we'll use the ``torch.save`` function. " -"Firstly, ``aggregate_fit`` returns a ``Parameters`` object that has to be " -"transformed into a list of NumPy ``ndarray``'s, then those are transformed " -"into the PyTorch ``state_dict`` following the ``OrderedDict`` class " -"structure." +"Similar to the previous example but with a few extra steps, we'll show " +"how to store a PyTorch checkpoint we'll use the ``torch.save`` function. " +"Firstly, ``aggregate_fit`` returns a ``Parameters`` object that has to be" +" transformed into a list of NumPy ``ndarray``'s, then those are " +"transformed into the PyTorch ``state_dict`` following the ``OrderedDict``" +" class structure." msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:85 msgid "" -"To load your progress, you simply append the following lines to your code. " -"Note that this will iterate over all saved checkpoints and load the latest " -"one:" +"To load your progress, you simply append the following lines to your " +"code. Note that this will iterate over all saved checkpoints and load the" +" latest one:" msgstr "" #: ../../source/how-to-save-and-load-model-checkpoints.rst:97 msgid "" -"Return/use this object of type ``Parameters`` wherever necessary, such as in " -"the ``initial_parameters`` when defining a ``Strategy``." +"Return/use this object of type ``Parameters`` wherever necessary, such as" +" in the ``initial_parameters`` when defining a ``Strategy``." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:2 @@ -5692,10 +5728,10 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:4 msgid "" -"Flower 1.0 is here. Along with new features, Flower 1.0 provides a stable " -"foundation for future growth. Compared to Flower 0.19 (and other 0.x series " -"releases), there are a few breaking changes that make it necessary to change " -"the code of existing 0.x-series projects." +"Flower 1.0 is here. Along with new features, Flower 1.0 provides a stable" +" foundation for future growth. Compared to Flower 0.19 (and other 0.x " +"series releases), there are a few breaking changes that make it necessary" +" to change the code of existing 0.x-series projects." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:8 @@ -5705,8 +5741,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:10 msgid "" -"Here's how to update an existing installation to Flower 1.0 using either pip " -"or Poetry:" +"Here's how to update an existing installation to Flower 1.0 using either " +"pip or Poetry:" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:12 @@ -5733,14 +5769,13 @@ msgid "" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:19 -msgid "" -"``flwr = \"^1.0.0\"`` (when using ``start_server`` and ``start_client``)" +msgid "``flwr = \"^1.0.0\"`` (when using ``start_server`` and ``start_client``)" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:20 msgid "" -"``flwr = { version = \"^1.0.0\", extras = [\"simulation\"] }`` (when using " -"``start_simulation``)" +"``flwr = { version = \"^1.0.0\", extras = [\"simulation\"] }`` (when " +"using ``start_simulation``)" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:24 @@ -5771,7 +5806,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:34 msgid "" "Flower 1.0 (keyword arguments): " -"``start_client(server_address=\"127.0.0.1:8080\", client=FlowerClient())``" +"``start_client(server_address=\"127.0.0.1:8080\", " +"client=FlowerClient())``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:37 @@ -5809,8 +5845,9 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:48 msgid "" -"Flower 1.0: ``start_server(..., config=flwr.server." -"ServerConfig(num_rounds=3, round_timeout=600.0), ...)``" +"Flower 1.0: ``start_server(..., " +"config=flwr.server.ServerConfig(num_rounds=3, round_timeout=600.0), " +"...)``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:50 @@ -5822,9 +5859,9 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:51 msgid "" "Remove ``force_final_distributed_eval`` parameter from calls to " -"``start_server``. Distributed evaluation on all clients can be enabled by " -"configuring the strategy to sample all clients for evaluation after the last " -"round of training." +"``start_server``. Distributed evaluation on all clients can be enabled by" +" configuring the strategy to sample all clients for evaluation after the " +"last round of training." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:52 @@ -5841,12 +5878,12 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:57 msgid "" -"Strategy initialization: if the strategy relies on the default values for " -"``fraction_fit`` and ``fraction_evaluate``, set ``fraction_fit`` and " +"Strategy initialization: if the strategy relies on the default values for" +" ``fraction_fit`` and ``fraction_evaluate``, set ``fraction_fit`` and " "``fraction_evaluate`` manually to ``0.1``. Projects that do not manually " "create a strategy (by calling ``start_server`` or ``start_simulation`` " -"without passing a strategy instance) should now manually initialize FedAvg " -"with ``fraction_fit`` and ``fraction_evaluate`` set to ``0.1``." +"without passing a strategy instance) should now manually initialize " +"FedAvg with ``fraction_fit`` and ``fraction_evaluate`` set to ``0.1``." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:58 @@ -5878,14 +5915,15 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:67 msgid "" -"Flower 0.19: ``def evaluate(parameters: NDArrays) -> Optional[Tuple[float, " -"Dict[str, Scalar]]]:``" +"Flower 0.19: ``def evaluate(parameters: NDArrays) -> " +"Optional[Tuple[float, Dict[str, Scalar]]]:``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:68 msgid "" -"Flower 1.0: ``def evaluate(server_round: int, parameters: NDArrays, config: " -"Dict[str, Scalar]) -> Optional[Tuple[float, Dict[str, Scalar]]]:``" +"Flower 1.0: ``def evaluate(server_round: int, parameters: NDArrays, " +"config: Dict[str, Scalar]) -> Optional[Tuple[float, Dict[str, " +"Scalar]]]:``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:71 @@ -5894,10 +5932,11 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:73 msgid "" -"The type of parameter ``failures`` has changed from ``List[BaseException]`` " -"to ``List[Union[Tuple[ClientProxy, FitRes], BaseException]]`` (in " -"``aggregate_fit``) and ``List[Union[Tuple[ClientProxy, EvaluateRes], " -"BaseException]]`` (in ``aggregate_evaluate``)" +"The type of parameter ``failures`` has changed from " +"``List[BaseException]`` to ``List[Union[Tuple[ClientProxy, FitRes], " +"BaseException]]`` (in ``aggregate_fit``) and " +"``List[Union[Tuple[ClientProxy, EvaluateRes], BaseException]]`` (in " +"``aggregate_evaluate``)" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:74 @@ -5914,8 +5953,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:77 msgid "" -"Flower 1.0: ``def evaluate(self, server_round: int, parameters: Parameters) -" -"> Optional[Tuple[float, Dict[str, Scalar]]]:``" +"Flower 1.0: ``def evaluate(self, server_round: int, parameters: " +"Parameters) -> Optional[Tuple[float, Dict[str, Scalar]]]:``" msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:80 @@ -5931,8 +5970,9 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:84 msgid "" "Remove \"placeholder\" methods from subclasses of ``Client`` or " -"``NumPyClient``. If you, for example, use server-side evaluation, then empty " -"placeholder implementations of ``evaluate`` are no longer necessary." +"``NumPyClient``. If you, for example, use server-side evaluation, then " +"empty placeholder implementations of ``evaluate`` are no longer " +"necessary." msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:85 @@ -5949,11 +5989,11 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:91 msgid "" -"Most official `Flower code examples `_ are already updated to Flower 1.0, they can serve as a " -"reference for using the Flower 1.0 API. If there are further questions, " -"`join the Flower Slack `_ and use the channel " -"``#questions``." +"Most official `Flower code examples " +"`_ are already updated" +" to Flower 1.0, they can serve as a reference for using the Flower 1.0 " +"API. If there are further questions, `join the Flower Slack " +"`_ and use the channel ``#questions``." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:2 @@ -5962,17 +6002,19 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:4 msgid "" -"Welcome to the migration guide for updating Flower to Flower Next! Whether " -"you're a seasoned user or just getting started, this guide will help you " -"smoothly transition your existing setup to take advantage of the latest " -"features and improvements in Flower Next, starting from version 1.8." +"Welcome to the migration guide for updating Flower to Flower Next! " +"Whether you're a seasoned user or just getting started, this guide will " +"help you smoothly transition your existing setup to take advantage of the" +" latest features and improvements in Flower Next, starting from version " +"1.8." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:9 msgid "" "This guide shows how to reuse pre-``1.8`` Flower code with minimum code " -"changes by using the *compatibility layer* in Flower Next. In another guide, " -"we will show how to run Flower Next end-to-end with pure Flower Next APIs." +"changes by using the *compatibility layer* in Flower Next. In another " +"guide, we will show how to run Flower Next end-to-end with pure Flower " +"Next APIs." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:13 @@ -5981,8 +6023,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:48 msgid "" -"Here's how to update an existing installation of Flower to Flower Next with " -"``pip``:" +"Here's how to update an existing installation of Flower to Flower Next " +"with ``pip``:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:54 @@ -5991,7 +6033,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:61 msgid "" -"Ensure you set the following version constraint in your ``requirements.txt``" +"Ensure you set the following version constraint in your " +"``requirements.txt``" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:71 @@ -6011,19 +6054,21 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:86 msgid "" -"Ensure you set the following version constraint in your ``pyproject.toml``:" +"Ensure you set the following version constraint in your " +"``pyproject.toml``:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:102 msgid "" "In Flower Next, the *infrastructure* and *application layers* have been " -"decoupled. Instead of starting a client in code via ``start_client()``, you " -"create a |clientapp_link|_ and start it via the command line. Instead of " -"starting a server in code via ``start_server()``, you create a |" -"serverapp_link|_ and start it via the command line. The long-running " +"decoupled. Instead of starting a client in code via ``start_client()``, " +"you create a |clientapp_link|_ and start it via the command line. Instead" +" of starting a server in code via ``start_server()``, you create a " +"|serverapp_link|_ and start it via the command line. The long-running " "components of server and client are called SuperLink and SuperNode. The " -"following non-breaking changes that require manual updates and allow you to " -"run your project both in the traditional way and in the Flower Next way:" +"following non-breaking changes that require manual updates and allow you " +"to run your project both in the traditional way and in the Flower Next " +"way:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:109 @@ -6032,8 +6077,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:110 msgid "" -"Wrap your existing client with |clientapp_link|_ instead of launching it via " -"|startclient_link|_. Here's an example:" +"Wrap your existing client with |clientapp_link|_ instead of launching it " +"via |startclient_link|_. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:132 @@ -6042,8 +6087,8 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:133 msgid "" -"Wrap your existing strategy with |serverapp_link|_ instead of starting the " -"server via |startserver_link|_. Here's an example:" +"Wrap your existing strategy with |serverapp_link|_ instead of starting " +"the server via |startserver_link|_. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:154 @@ -6052,21 +6097,24 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:155 msgid "" -"Run the ``SuperLink`` using |flowernext_superlink_link|_ before running, in " -"sequence, |flowernext_clientapp_link|_ (2x) and |flowernext_serverapp_link|" -"_. There is no need to execute `client.py` and `server.py` as Python scripts." +"Run the ``SuperLink`` using |flowernext_superlink_link|_ before running, " +"in sequence, |flowernext_clientapp_link|_ (2x) and " +"|flowernext_serverapp_link|_. There is no need to execute `client.py` and" +" `server.py` as Python scripts." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:158 msgid "" -"Here's an example to start the server without HTTPS (only for prototyping):" +"Here's an example to start the server without HTTPS (only for " +"prototyping):" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:174 msgid "" -"Here's another example to start with HTTPS. Use the ``--certificates`` " -"command line argument to pass paths to (CA certificate, server certificate, " -"and server private key)." +"Here's another example to start with HTTPS. Use the ``--ssl-ca-" +"certfile``, ``--ssl-certfile``, and ``--ssl-keyfile`` command line " +"options to pass paths to (CA certificate, server certificate, and server " +"private key)." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:201 @@ -6075,24 +6123,24 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:202 msgid "" -"Wrap your existing client and strategy with |clientapp_link|_ and |" -"serverapp_link|_, respectively. There is no need to use |startsim_link|_ " -"anymore. Here's an example:" +"Wrap your existing client and strategy with |clientapp_link|_ and " +"|serverapp_link|_, respectively. There is no need to use |startsim_link|_" +" anymore. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:232 msgid "" "Run |flower_simulation_link|_ in CLI and point to the ``server_app`` / " -"``client_app`` object in the code instead of executing the Python script. " -"Here's an example (assuming the ``server_app`` and ``client_app`` objects " -"are in a ``sim.py`` module):" +"``client_app`` object in the code instead of executing the Python script." +" Here's an example (assuming the ``server_app`` and ``client_app`` " +"objects are in a ``sim.py`` module):" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:249 msgid "" "Set default resources for each |clientapp_link|_ using the ``--backend-" -"config`` command line argument instead of setting the ``client_resources`` " -"argument in |startsim_link|_. Here's an example:" +"config`` command line argument instead of setting the " +"``client_resources`` argument in |startsim_link|_. Here's an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:275 @@ -6101,19 +6149,19 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:276 msgid "" -"Run |runsim_link|_ in your notebook instead of |startsim_link|_. Here's an " -"example:" +"Run |runsim_link|_ in your notebook instead of |startsim_link|_. Here's " +"an example:" msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:319 msgid "" -"Some official `Flower code examples `_ are " -"already updated to Flower Next so they can serve as a reference for using " -"the Flower Next API. If there are further questions, `join the Flower Slack " -"`_ and use the channel ``#questions``. You " -"can also `participate in Flower Discuss `_ where " -"you can find us answering questions, or share and learn from others about " -"migrating to Flower Next." +"Some official `Flower code examples `_ " +"are already updated to Flower Next so they can serve as a reference for " +"using the Flower Next API. If there are further questions, `join the " +"Flower Slack `_ and use the channel " +"``#questions``. You can also `participate in Flower Discuss " +"`_ where you can find us answering questions," +" or share and learn from others about migrating to Flower Next." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:325 @@ -6137,16 +6185,16 @@ msgstr "" #: ../../source/how-to-use-built-in-mods.rst:4 msgid "" -"**Note: This tutorial covers experimental features. The functionality and " -"interfaces may change in future versions.**" +"**Note: This tutorial covers experimental features. The functionality and" +" interfaces may change in future versions.**" msgstr "" #: ../../source/how-to-use-built-in-mods.rst:6 msgid "" -"In this tutorial, we will learn how to utilize built-in mods to augment the " -"behavior of a ``ClientApp``. Mods (sometimes also called Modifiers) allow us " -"to perform operations before and after a task is processed in the " -"``ClientApp``." +"In this tutorial, we will learn how to utilize built-in mods to augment " +"the behavior of a ``ClientApp``. Mods (sometimes also called Modifiers) " +"allow us to perform operations before and after a task is processed in " +"the ``ClientApp``." msgstr "" #: ../../source/how-to-use-built-in-mods.rst:9 @@ -6155,9 +6203,9 @@ msgstr "" #: ../../source/how-to-use-built-in-mods.rst:11 msgid "" -"A Mod is a callable that wraps around a ``ClientApp``. It can manipulate or " -"inspect the incoming ``Message`` and the resulting outgoing ``Message``. The " -"signature for a ``Mod`` is as follows:" +"A Mod is a callable that wraps around a ``ClientApp``. It can manipulate " +"or inspect the incoming ``Message`` and the resulting outgoing " +"``Message``. The signature for a ``Mod`` is as follows:" msgstr "" #: ../../source/how-to-use-built-in-mods.rst:18 @@ -6234,16 +6282,16 @@ msgstr "" #: ../../source/how-to-use-built-in-mods.rst:82 msgid "" -"Each mod has a chance to inspect and modify the incoming ``Message`` before " -"passing it to the next mod, and likewise with the outgoing ``Message`` " -"before returning it up the stack." +"Each mod has a chance to inspect and modify the incoming ``Message`` " +"before passing it to the next mod, and likewise with the outgoing " +"``Message`` before returning it up the stack." msgstr "" #: ../../source/how-to-use-built-in-mods.rst:87 msgid "" "By following this guide, you have learned how to effectively use mods to " -"enhance your ``ClientApp``'s functionality. Remember that the order of mods " -"is crucial and affects how the input and output are processed." +"enhance your ``ClientApp``'s functionality. Remember that the order of " +"mods is crucial and affects how the input and output are processed." msgstr "" #: ../../source/how-to-use-built-in-mods.rst:89 @@ -6256,25 +6304,25 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:3 msgid "" -"This guide explains how you can utilize differential privacy in the Flower " -"framework. If you are not yet familiar with differential privacy, you can " -"refer to :doc:`explanation-differential-privacy`." +"This guide explains how you can utilize differential privacy in the " +"Flower framework. If you are not yet familiar with differential privacy, " +"you can refer to :doc:`explanation-differential-privacy`." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:7 msgid "" "Differential Privacy in Flower is in a preview phase. If you plan to use " -"these features in a production environment with sensitive data, feel free " -"contact us to discuss your requirements and to receive guidance on how to " -"best use these features." +"these features in a production environment with sensitive data, feel free" +" contact us to discuss your requirements and to receive guidance on how " +"to best use these features." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:12 msgid "" -"This approach consists of two seprate phases: clipping of the updates and " -"adding noise to the aggregated model. For the clipping phase, Flower " -"framework has made it possible to decide whether to perform clipping on the " -"server side or the client side." +"This approach consists of two seprate phases: clipping of the updates and" +" adding noise to the aggregated model. For the clipping phase, Flower " +"framework has made it possible to decide whether to perform clipping on " +"the server side or the client side." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:15 @@ -6282,16 +6330,16 @@ msgid "" "**Server-side Clipping**: This approach has the advantage of the server " "enforcing uniform clipping across all clients' updates and reducing the " "communication overhead for clipping values. However, it also has the " -"disadvantage of increasing the computational load on the server due to the " -"need to perform the clipping operation for all clients." +"disadvantage of increasing the computational load on the server due to " +"the need to perform the clipping operation for all clients." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:16 msgid "" -"**Client-side Clipping**: This approach has the advantage of reducing the " -"computational overhead on the server. However, it also has the disadvantage " -"of lacking centralized control, as the server has less control over the " -"clipping process." +"**Client-side Clipping**: This approach has the advantage of reducing the" +" computational overhead on the server. However, it also has the " +"disadvantage of lacking centralized control, as the server has less " +"control over the clipping process." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:21 @@ -6302,10 +6350,10 @@ msgstr "" msgid "" "For central DP with server-side clipping, there are two :code:`Strategy` " "classes that act as wrappers around the actual :code:`Strategy` instance " -"(for example, :code:`FedAvg`). The two wrapper classes are :code:" -"`DifferentialPrivacyServerSideFixedClipping` and :code:" -"`DifferentialPrivacyServerSideAdaptiveClipping` for fixed and adaptive " -"clipping." +"(for example, :code:`FedAvg`). The two wrapper classes are " +":code:`DifferentialPrivacyServerSideFixedClipping` and " +":code:`DifferentialPrivacyServerSideAdaptiveClipping` for fixed and " +"adaptive clipping." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:-1 @@ -6314,10 +6362,11 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:31 msgid "" -"The code sample below enables the :code:`FedAvg` strategy to use server-side " -"fixed clipping using the :code:`DifferentialPrivacyServerSideFixedClipping` " -"wrapper class. The same approach can be used with :code:" -"`DifferentialPrivacyServerSideAdaptiveClipping` by adjusting the " +"The code sample below enables the :code:`FedAvg` strategy to use server-" +"side fixed clipping using the " +":code:`DifferentialPrivacyServerSideFixedClipping` wrapper class. The " +"same approach can be used with " +":code:`DifferentialPrivacyServerSideAdaptiveClipping` by adjusting the " "corresponding input parameters." msgstr "" @@ -6328,12 +6377,12 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:53 msgid "" "For central DP with client-side clipping, the server sends the clipping " -"value to selected clients on each round. Clients can use existing Flower :" -"code:`Mods` to perform the clipping. Two mods are available for fixed and " -"adaptive client-side clipping: :code:`fixedclipping_mod` and :code:" -"`adaptiveclipping_mod` with corresponding server-side wrappers :code:" -"`DifferentialPrivacyClientSideFixedClipping` and :code:" -"`DifferentialPrivacyClientSideAdaptiveClipping`." +"value to selected clients on each round. Clients can use existing Flower " +":code:`Mods` to perform the clipping. Two mods are available for fixed " +"and adaptive client-side clipping: :code:`fixedclipping_mod` and " +":code:`adaptiveclipping_mod` with corresponding server-side wrappers " +":code:`DifferentialPrivacyClientSideFixedClipping` and " +":code:`DifferentialPrivacyClientSideAdaptiveClipping`." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:-1 @@ -6343,24 +6392,24 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:63 msgid "" "The code sample below enables the :code:`FedAvg` strategy to use " -"differential privacy with client-side fixed clipping using both the :code:" -"`DifferentialPrivacyClientSideFixedClipping` wrapper class and, on the " -"client, :code:`fixedclipping_mod`:" +"differential privacy with client-side fixed clipping using both the " +":code:`DifferentialPrivacyClientSideFixedClipping` wrapper class and, on " +"the client, :code:`fixedclipping_mod`:" msgstr "" #: ../../source/how-to-use-differential-privacy.rst:80 msgid "" -"In addition to the server-side strategy wrapper, the :code:`ClientApp` needs " -"to configure the matching :code:`fixedclipping_mod` to perform the client-" -"side clipping:" +"In addition to the server-side strategy wrapper, the :code:`ClientApp` " +"needs to configure the matching :code:`fixedclipping_mod` to perform the " +"client-side clipping:" msgstr "" #: ../../source/how-to-use-differential-privacy.rst:97 msgid "" -"To utilize local differential privacy (DP) and add noise to the client model " -"parameters before transmitting them to the server in Flower, you can use the " -"`LocalDpMod`. The following hyperparameters need to be set: clipping norm " -"value, sensitivity, epsilon, and delta." +"To utilize local differential privacy (DP) and add noise to the client " +"model parameters before transmitting them to the server in Flower, you " +"can use the `LocalDpMod`. The following hyperparameters need to be set: " +"clipping norm value, sensitivity, epsilon, and delta." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:-1 @@ -6373,9 +6422,10 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:122 msgid "" -"Please note that the order of mods, especially those that modify parameters, " -"is important when using multiple modifiers. Typically, differential privacy " -"(DP) modifiers should be the last to operate on parameters." +"Please note that the order of mods, especially those that modify " +"parameters, is important when using multiple modifiers. Typically, " +"differential privacy (DP) modifiers should be the last to operate on " +"parameters." msgstr "" #: ../../source/how-to-use-differential-privacy.rst:125 @@ -6384,12 +6434,13 @@ msgstr "" #: ../../source/how-to-use-differential-privacy.rst:126 msgid "" -"For ensuring data instance-level privacy during local model training on the " -"client side, consider leveraging privacy engines such as Opacus and " -"TensorFlow Privacy. For examples of using Flower with these engines, please " -"refer to the Flower examples directory (`Opacus `_, `Tensorflow Privacy `_)." +"For ensuring data instance-level privacy during local model training on " +"the client side, consider leveraging privacy engines such as Opacus and " +"TensorFlow Privacy. For examples of using Flower with these engines, " +"please refer to the Flower examples directory (`Opacus " +"`_, `Tensorflow" +" Privacy `_)." msgstr "" #: ../../source/how-to-use-strategies.rst:2 @@ -6398,15 +6449,15 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:4 msgid "" -"Flower allows full customization of the learning process through the :code:" -"`Strategy` abstraction. A number of built-in strategies are provided in the " -"core framework." +"Flower allows full customization of the learning process through the " +":code:`Strategy` abstraction. A number of built-in strategies are " +"provided in the core framework." msgstr "" #: ../../source/how-to-use-strategies.rst:6 msgid "" -"There are three ways to customize the way Flower orchestrates the learning " -"process on the server side:" +"There are three ways to customize the way Flower orchestrates the " +"learning process on the server side:" msgstr "" #: ../../source/how-to-use-strategies.rst:8 @@ -6429,15 +6480,15 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:16 msgid "" -"Flower comes with a number of popular federated learning strategies built-" -"in. A built-in strategy can be instantiated as follows:" +"Flower comes with a number of popular federated learning strategies " +"built-in. A built-in strategy can be instantiated as follows:" msgstr "" #: ../../source/how-to-use-strategies.rst:25 msgid "" -"This creates a strategy with all parameters left at their default values and " -"passes it to the :code:`start_server` function. It is usually recommended to " -"adjust a few parameters during instantiation:" +"This creates a strategy with all parameters left at their default values " +"and passes it to the :code:`start_server` function. It is usually " +"recommended to adjust a few parameters during instantiation:" msgstr "" #: ../../source/how-to-use-strategies.rst:42 @@ -6454,27 +6505,28 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:47 msgid "" "The server can pass new configuration values to the client each round by " -"providing a function to :code:`on_fit_config_fn`. The provided function will " -"be called by the strategy and must return a dictionary of configuration key " -"values pairs that will be sent to the client. It must return a dictionary of " -"arbitrary configuration values :code:`client.fit` and :code:`client." -"evaluate` functions during each round of federated learning." +"providing a function to :code:`on_fit_config_fn`. The provided function " +"will be called by the strategy and must return a dictionary of " +"configuration key values pairs that will be sent to the client. It must " +"return a dictionary of arbitrary configuration values :code:`client.fit`" +" and :code:`client.evaluate` functions during each round of federated " +"learning." msgstr "" #: ../../source/how-to-use-strategies.rst:75 msgid "" "The :code:`on_fit_config_fn` can be used to pass arbitrary configuration " "values from server to client, and poetentially change these values each " -"round, for example, to adjust the learning rate. The client will receive the " -"dictionary returned by the :code:`on_fit_config_fn` in its own :code:`client." -"fit()` function." +"round, for example, to adjust the learning rate. The client will receive " +"the dictionary returned by the :code:`on_fit_config_fn` in its own " +":code:`client.fit()` function." msgstr "" #: ../../source/how-to-use-strategies.rst:78 msgid "" -"Similar to :code:`on_fit_config_fn`, there is also :code:" -"`on_evaluate_config_fn` to customize the configuration sent to :code:`client." -"evaluate()`" +"Similar to :code:`on_fit_config_fn`, there is also " +":code:`on_evaluate_config_fn` to customize the configuration sent to " +":code:`client.evaluate()`" msgstr "" #: ../../source/how-to-use-strategies.rst:81 @@ -6483,15 +6535,15 @@ msgstr "" #: ../../source/how-to-use-strategies.rst:83 msgid "" -"Server-side evaluation can be enabled by passing an evaluation function to :" -"code:`evaluate_fn`." +"Server-side evaluation can be enabled by passing an evaluation function " +"to :code:`evaluate_fn`." msgstr "" #: ../../source/how-to-use-strategies.rst:89 msgid "" -"Writing a fully custom strategy is a bit more involved, but it provides the " -"most flexibility. Read the `Implementing Strategies `_ guide to learn more." +"Writing a fully custom strategy is a bit more involved, but it provides " +"the most flexibility. Read the `Implementing Strategies `_ guide to learn more." msgstr "" #: ../../source/index.rst:34 @@ -6530,11 +6582,11 @@ msgstr "" msgid "Contributor how-to guides" msgstr "" -#: ../../source/index.rst:173 +#: ../../source/index.rst:172 msgid "Contributor explanations" msgstr "" -#: ../../source/index.rst:179 +#: ../../source/index.rst:178 msgid "Contributor references" msgstr "" @@ -6577,8 +6629,8 @@ msgstr "" msgid "" "The user guide is targeted at researchers and developers who want to use " "Flower to bring existing machine learning workloads into a federated " -"setting. One of Flower's design goals was to make this simple. Read on to " -"learn more." +"setting. One of Flower's design goals was to make this simple. Read on to" +" learn more." msgstr "" #: ../../source/index.rst:30 @@ -6587,20 +6639,21 @@ msgstr "" #: ../../source/index.rst:32 msgid "" -"A learning-oriented series of federated learning tutorials, the best place " -"to start." +"A learning-oriented series of federated learning tutorials, the best " +"place to start." msgstr "" #: ../../source/index.rst:61 msgid "" -"QUICKSTART TUTORIALS: :doc:`PyTorch ` | :doc:" -"`TensorFlow ` | :doc:`🤗 Transformers " -"` | :doc:`JAX ` | :" -"doc:`Pandas ` | :doc:`fastai ` | :doc:`PyTorch Lightning ` | :doc:`scikit-learn ` | :doc:" -"`XGBoost ` | :doc:`Android ` | :doc:`iOS `" +"QUICKSTART TUTORIALS: :doc:`PyTorch ` | " +":doc:`TensorFlow ` | :doc:`🤗 Transformers" +" ` | :doc:`JAX ` | :doc:`Pandas ` | :doc:`fastai " +"` | :doc:`PyTorch Lightning ` | :doc:`scikit-learn ` | :doc:`XGBoost ` | " +":doc:`Android ` | :doc:`iOS `" msgstr "" #: ../../source/index.rst:63 @@ -6613,8 +6666,8 @@ msgstr "" #: ../../source/index.rst:76 msgid "" -"Problem-oriented how-to guides show step-by-step how to achieve a specific " -"goal." +"Problem-oriented how-to guides show step-by-step how to achieve a " +"specific goal." msgstr "" #: ../../source/index.rst:110 @@ -6645,8 +6698,8 @@ msgstr "" #: ../../source/index.rst:150 msgid "" -"The Flower community welcomes contributions. The following docs are intended " -"to help along the way." +"The Flower community welcomes contributions. The following docs are " +"intended to help along the way." msgstr "" #: ../../source/ref-api-cli.rst:2 @@ -6673,7 +6726,8 @@ msgstr "" msgid "flwr" msgstr "" -#: ../../source/ref-api/flwr.rst:25 ../../source/ref-api/flwr.server.rst:51 +#: ../../source/ref-api/flwr.client.rst:45 ../../source/ref-api/flwr.rst:25 +#: ../../source/ref-api/flwr.server.rst:49 msgid "Modules" msgstr "" @@ -6698,7 +6752,7 @@ msgid ":py:obj:`flwr.server `\\" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 -#: ../../source/ref-api/flwr.server.rst:40::1 flwr.server:1 +#: ../../source/ref-api/flwr.server.rst:38::1 flwr.server:1 #: flwr.server.server.Server:1 of msgid "Flower server." msgstr "" @@ -6715,6 +6769,7 @@ msgstr "" msgid "client" msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:13 #: ../../source/ref-api/flwr.client.rst:13 #: ../../source/ref-api/flwr.common.rst:13 #: ../../source/ref-api/flwr.server.rst:13 @@ -6753,8 +6808,8 @@ msgstr "" #: ../../source/ref-api/flwr.client.rst:25::1 msgid "" -":py:obj:`start_numpy_client `\\ \\(\\*\\, " -"server\\_address\\, client\\)" +":py:obj:`start_numpy_client `\\ \\(\\*\\," +" server\\_address\\, client\\)" msgstr "" #: ../../source/ref-api/flwr.client.rst:25::1 @@ -6762,9 +6817,10 @@ msgstr "" msgid "Start a Flower NumPyClient which connects to a gRPC server." msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:30 #: ../../source/ref-api/flwr.client.rst:27 #: ../../source/ref-api/flwr.common.rst:32 -#: ../../source/ref-api/flwr.server.rst:28 +#: ../../source/ref-api/flwr.server.rst:26 #: ../../source/ref-api/flwr.server.strategy.rst:17 #: ../../source/ref-api/flwr.server.workflow.rst:17 msgid "Classes" @@ -6781,7 +6837,8 @@ msgstr "" #: ../../source/ref-api/flwr.client.rst:34::1 msgid "" -":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, mods\\]\\)" +":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, " +"mods\\]\\)" msgstr "" #: ../../source/ref-api/flwr.client.rst:34::1 @@ -6798,6 +6855,14 @@ msgstr "" msgid "Abstract base class for Flower clients using NumPy." msgstr "" +#: ../../source/ref-api/flwr.client.rst:52::1 +msgid ":py:obj:`flwr.client.mod `\\" +msgstr "" + +#: ../../source/ref-api/flwr.client.rst:52::1 flwr.client.mod:1 of +msgid "Flower Built-in Mods." +msgstr "" + #: flwr.client.client.Client:1 flwr.client.numpy_client.NumPyClient:1 #: flwr.server.client_manager.ClientManager:1 #: flwr.server.driver.driver.Driver:1 flwr.server.strategy.strategy.Strategy:1 @@ -6808,6 +6873,7 @@ msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:15 #: ../../source/ref-api/flwr.client.ClientApp.rst:15 #: ../../source/ref-api/flwr.client.NumPyClient.rst:15 +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:15 #: ../../source/ref-api/flwr.common.Array.rst:15 #: ../../source/ref-api/flwr.common.ClientMessage.rst:15 #: ../../source/ref-api/flwr.common.ConfigsRecord.rst:15 @@ -6904,8 +6970,7 @@ msgid "Get the run context from this client." msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 -msgid "" -":py:obj:`get_parameters `\\ \\(ins\\)" +msgid ":py:obj:`get_parameters `\\ \\(ins\\)" msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 @@ -6916,8 +6981,7 @@ msgid "Return the current local model parameters." msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 -msgid "" -":py:obj:`get_properties `\\ \\(ins\\)" +msgid ":py:obj:`get_properties `\\ \\(ins\\)" msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:44::1 @@ -6985,6 +7049,7 @@ msgstr "" #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit #: flwr.client.client.Client.get_parameters #: flwr.client.client.Client.get_properties +#: flwr.client.mod.localdp_mod.LocalDpMod #: flwr.client.numpy_client.NumPyClient.evaluate #: flwr.client.numpy_client.NumPyClient.fit #: flwr.client.numpy_client.NumPyClient.get_parameters @@ -7031,9 +7096,9 @@ msgstr "" #: flwr.client.client.Client.evaluate:3 of msgid "" -"The evaluation instructions containing (global) model parameters received " -"from the server and a dictionary of configuration values used to customize " -"the local evaluation process." +"The evaluation instructions containing (global) model parameters received" +" from the server and a dictionary of configuration values used to " +"customize the local evaluation process." msgstr "" #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit @@ -7100,15 +7165,15 @@ msgstr "" #: flwr.client.client.Client.fit:3 of msgid "" -"The training instructions containing (global) model parameters received from " -"the server and a dictionary of configuration values used to customize the " -"local training process." +"The training instructions containing (global) model parameters received " +"from the server and a dictionary of configuration values used to " +"customize the local training process." msgstr "" #: flwr.client.client.Client.fit:8 of msgid "" -"The training result containing updated parameters and other details such as " -"the number of local training examples used for training." +"The training result containing updated parameters and other details such " +"as the number of local training examples used for training." msgstr "" #: flwr.client.client.Client.get_parameters:3 of @@ -7135,10 +7200,11 @@ msgstr "" msgid "ClientApp" msgstr "" -#: flwr.client.client_app.ClientApp:1 flwr.common.constant.MessageType:1 -#: flwr.common.constant.MessageTypeLegacy:1 flwr.common.context.Context:1 -#: flwr.common.message.Error:1 flwr.common.message.Message:1 -#: flwr.common.message.Metadata:1 flwr.common.record.parametersrecord.Array:1 +#: flwr.client.client_app.ClientApp:1 flwr.client.mod.localdp_mod.LocalDpMod:1 +#: flwr.common.constant.MessageType:1 flwr.common.constant.MessageTypeLegacy:1 +#: flwr.common.context.Context:1 flwr.common.message.Error:1 +#: flwr.common.message.Message:1 flwr.common.message.Metadata:1 +#: flwr.common.record.parametersrecord.Array:1 #: flwr.common.record.recordset.RecordSet:1 flwr.common.typing.ClientMessage:1 #: flwr.common.typing.DisconnectRes:1 flwr.common.typing.EvaluateIns:1 #: flwr.common.typing.EvaluateRes:1 flwr.common.typing.FitIns:1 @@ -7159,7 +7225,8 @@ msgstr "" #: flwr.client.client_app.ClientApp:4 #: flwr.client.client_app.ClientApp.evaluate:4 #: flwr.client.client_app.ClientApp.query:4 -#: flwr.client.client_app.ClientApp.train:4 flwr.server.app.start_server:41 +#: flwr.client.client_app.ClientApp.train:4 +#: flwr.client.mod.localdp_mod.LocalDpMod:22 flwr.server.app.start_server:41 #: flwr.server.server_app.ServerApp:4 flwr.server.server_app.ServerApp.main:4 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:29 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:22 @@ -7177,15 +7244,15 @@ msgstr "" #: flwr.client.client_app.ClientApp:16 of msgid "" -"If the above code is in a Python module called `client`, it can be started " -"as follows:" +"If the above code is in a Python module called `client`, it can be " +"started as follows:" msgstr "" #: flwr.client.client_app.ClientApp:21 of msgid "" -"In this `client:app` example, `client` refers to the Python module `client." -"py` in which the previous code lives in and `app` refers to the global " -"attribute `app` that points to an object of type `ClientApp`." +"In this `client:app` example, `client` refers to the Python module " +"`client.py` in which the previous code lives in and `app` refers to the " +"global attribute `app` that points to an object of type `ClientApp`." msgstr "" #: flwr.client.client_app.ClientApp.evaluate:1::1 of @@ -7226,8 +7293,7 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 -msgid "" -":py:obj:`fit `\\ \\(parameters\\, config\\)" +msgid ":py:obj:`fit `\\ \\(parameters\\, config\\)" msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 @@ -7258,7 +7324,8 @@ msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid "" -":py:obj:`set_context `\\ \\(context\\)" +":py:obj:`set_context `\\ " +"\\(context\\)" msgstr "" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 @@ -7286,10 +7353,10 @@ msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:5 of msgid "" -"Configuration parameters which allow the server to influence evaluation on " -"the client. It can be used to communicate arbitrary values from the server " -"to the client, for example, to influence the number of examples used for " -"evaluation." +"Configuration parameters which allow the server to influence evaluation " +"on the client. It can be used to communicate arbitrary values from the " +"server to the client, for example, to influence the number of examples " +"used for evaluation." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:11 of @@ -7297,13 +7364,14 @@ msgid "" "* **loss** (*float*) -- The evaluation loss of the model on the local " "dataset. * **num_examples** (*int*) -- The number of examples used for " "evaluation. * **metrics** (*Dict[str, Scalar]*) -- A dictionary mapping " -"arbitrary string keys to values of type bool, bytes, float, int, or str. " -"It can be used to communicate arbitrary values back to the server." +"arbitrary string keys to values of type bool, bytes, float, int, or " +"str. It can be used to communicate arbitrary values back to the server." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:11 of msgid "" -"**loss** (*float*) -- The evaluation loss of the model on the local dataset." +"**loss** (*float*) -- The evaluation loss of the model on the local " +"dataset." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:12 of @@ -7313,32 +7381,33 @@ msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:13 #: flwr.client.numpy_client.NumPyClient.fit:13 of msgid "" -"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary string " -"keys to values of type bool, bytes, float, int, or str. It can be used to " -"communicate arbitrary values back to the server." +"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary " +"string keys to values of type bool, bytes, float, int, or str. It can be " +"used to communicate arbitrary values back to the server." msgstr "" #: flwr.client.numpy_client.NumPyClient.evaluate:19 of msgid "" -"The previous return type format (int, float, float) and the extended format " -"(int, float, float, Dict[str, Scalar]) have been deprecated and removed " -"since Flower 0.19." +"The previous return type format (int, float, float) and the extended " +"format (int, float, float, Dict[str, Scalar]) have been deprecated and " +"removed since Flower 0.19." msgstr "" #: flwr.client.numpy_client.NumPyClient.fit:5 of msgid "" -"Configuration parameters which allow the server to influence training on the " -"client. It can be used to communicate arbitrary values from the server to " -"the client, for example, to set the number of (local) training epochs." +"Configuration parameters which allow the server to influence training on " +"the client. It can be used to communicate arbitrary values from the " +"server to the client, for example, to set the number of (local) training " +"epochs." msgstr "" #: flwr.client.numpy_client.NumPyClient.fit:11 of msgid "" "* **parameters** (*NDArrays*) -- The locally updated model parameters. * " "**num_examples** (*int*) -- The number of examples used for training. * " -"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary string " -"keys to values of type bool, bytes, float, int, or str. It can be used to " -"communicate arbitrary values back to the server." +"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary " +"string keys to values of type bool, bytes, float, int, or str. It can " +"be used to communicate arbitrary values back to the server." msgstr "" #: flwr.client.numpy_client.NumPyClient.fit:11 of @@ -7351,28 +7420,254 @@ msgstr "" #: flwr.client.numpy_client.NumPyClient.get_parameters:3 of msgid "" -"Configuration parameters requested by the server. This can be used to tell " -"the client which parameters are needed along with some Scalar attributes." +"Configuration parameters requested by the server. This can be used to " +"tell the client which parameters are needed along with some Scalar " +"attributes." msgstr "" #: flwr.client.numpy_client.NumPyClient.get_parameters:8 of -msgid "" -"**parameters** -- The local model parameters as a list of NumPy ndarrays." +msgid "**parameters** -- The local model parameters as a list of NumPy ndarrays." msgstr "" #: flwr.client.numpy_client.NumPyClient.get_properties:3 of msgid "" -"Configuration parameters requested by the server. This can be used to tell " -"the client which properties are needed along with some Scalar attributes." +"Configuration parameters requested by the server. This can be used to " +"tell the client which properties are needed along with some Scalar " +"attributes." msgstr "" #: flwr.client.numpy_client.NumPyClient.get_properties:8 of msgid "" -"**properties** -- A dictionary mapping arbitrary string keys to values of " -"type bool, bytes, float, int, or str. It can be used to communicate " +"**properties** -- A dictionary mapping arbitrary string keys to values of" +" type bool, bytes, float, int, or str. It can be used to communicate " "arbitrary property values back to the server." msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:2 +msgid "mod" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`adaptiveclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:1 of +msgid "Client-side adaptive clipping modifier." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`fixedclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:1 of +msgid "Client-side fixed clipping modifier." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid ":py:obj:`make_ffn `\\ \\(ffn\\, mods\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.utils.make_ffn:1 of +msgid "." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`secagg_mod `\\ \\(msg\\, ctxt\\, " +"call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secagg_mod.secagg_mod:1 of +msgid "Handle incoming message and return results, following the SecAgg protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`secaggplus_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secaggplus_mod.secaggplus_mod:1 of +msgid "" +"Handle incoming message and return results, following the SecAgg+ " +"protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`message_size_mod `\\ \\(msg\\," +" ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.message_size_mod:1 of +msgid "Message size mod." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`parameters_size_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.parameters_size_mod:1 of +msgid "Parameters size mod." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +msgid "" +":py:obj:`LocalDpMod `\\ \\(clipping\\_norm\\," +" sensitivity\\, ...\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +#: flwr.client.mod.localdp_mod.LocalDpMod:1 of +msgid "Modifier for local differential privacy." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:2 +msgid "LocalDpMod" +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:3 of +msgid "" +"This mod clips the client model updates and adds noise to the params " +"before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:12 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:10 +#: flwr.client.mod.localdp_mod.LocalDpMod:6 of +msgid "It operates on messages of type `MessageType.TRAIN`." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:8 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 +#: of +msgid "The value of the clipping norm." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:10 of +msgid "The sensitivity of the client model." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:12 of +msgid "" +"The privacy budget. Smaller value of epsilon indicates a higher level of " +"privacy protection." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:15 of +msgid "" +"The failure probability. The probability that the privacy mechanism fails" +" to provide the desired level of privacy. A smaller value of delta " +"indicates a stricter privacy guarantee." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:23 of +msgid "Create an instance of the local DP mod and add it to the client-side mods:" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.adaptiveclipping_mod.rst:2 +msgid "adaptiveclipping\\_mod" +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:3 of +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideAdaptiveClipping server-side strategy " +"wrapper." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:6 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:6 of +msgid "The wrapper sends the clipping_norm value to the client." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:8 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:8 of +msgid "This mod clips the client model updates before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:10 of +msgid "" +"It also sends KEY_NORM_BIT to the server for computing the new clipping " +"value." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:15 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:13 +#: flwr.server.driver.driver.Driver.send_and_receive:18 +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 +#: of +msgid "Notes" +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:16 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:14 of +msgid "Consider the order of mods when using multiple." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:18 of +msgid "Typically, adaptiveclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.fixedclipping_mod.rst:2 +msgid "fixedclipping\\_mod" +msgstr "" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:3 of +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideFixedClipping server-side strategy wrapper." +msgstr "" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:16 of +msgid "Typically, fixedclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.make_ffn.rst:2 +msgid "make\\_ffn" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 +msgid "message\\_size\\_mod" +msgstr "" + +#: flwr.client.mod.comms_mods.message_size_mod:3 of +msgid "This mod logs the size in bytes of the message being transmited." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.parameters_size_mod.rst:2 +msgid "parameters\\_size\\_mod" +msgstr "" + +#: flwr.client.mod.comms_mods.parameters_size_mod:3 of +msgid "" +"This mod logs the number of parameters transmitted in the message as well" +" as their size in bytes." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secagg_mod.rst:2 +msgid "secagg\\_mod" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secaggplus_mod.rst:2 +msgid "secaggplus\\_mod" +msgstr "" + #: ../../source/ref-api/flwr.client.run_client_app.rst:2 msgid "run\\_client\\_app" msgstr "" @@ -7388,7 +7683,8 @@ msgstr "" #: flwr.client.app.start_client:3 flwr.client.app.start_numpy_client:9 of msgid "" "The IPv4 or IPv6 address of the server. If the Flower server runs on the " -"same machine on port 8080, then `server_address` would be `\"[::]:8080\"`." +"same machine on port 8080, then `server_address` would be " +"`\"[::]:8080\"`." msgstr "" #: flwr.client.app.start_client:7 of @@ -7397,52 +7693,52 @@ msgstr "" #: flwr.client.app.start_client:9 of msgid "" -"An implementation of the abstract base class `flwr.client.Client` (default: " -"None)" +"An implementation of the abstract base class `flwr.client.Client` " +"(default: None)" msgstr "" #: flwr.client.app.start_client:12 flwr.client.app.start_numpy_client:15 of msgid "" -"The maximum length of gRPC messages that can be exchanged with the Flower " -"server. The default should be sufficient for most models. Users who train " -"very large models might need to increase this value. Note that the Flower " -"server needs to be started with the same value (see `flwr.server." -"start_server`), otherwise it will not know about the increased limit and " -"block larger messages." +"The maximum length of gRPC messages that can be exchanged with the Flower" +" server. The default should be sufficient for most models. Users who " +"train very large models might need to increase this value. Note that the " +"Flower server needs to be started with the same value (see " +"`flwr.server.start_server`), otherwise it will not know about the " +"increased limit and block larger messages." msgstr "" #: flwr.client.app.start_client:19 flwr.client.app.start_numpy_client:22 of msgid "" "The PEM-encoded root certificates as a byte string or a path string. If " -"provided, a secure connection using the certificates will be established to " -"an SSL-enabled Flower server." +"provided, a secure connection using the certificates will be established " +"to an SSL-enabled Flower server." msgstr "" #: flwr.client.app.start_client:23 flwr.client.app.start_numpy_client:26 of msgid "" -"Starts an insecure gRPC connection when True. Enables HTTPS connection when " -"False, using system certificates if `root_certificates` is None." +"Starts an insecure gRPC connection when True. Enables HTTPS connection " +"when False, using system certificates if `root_certificates` is None." msgstr "" #: flwr.client.app.start_client:26 flwr.client.app.start_numpy_client:29 of msgid "" "Configure the transport layer. Allowed values: - 'grpc-bidi': gRPC, " -"bidirectional streaming - 'grpc-rere': gRPC, request-response (experimental) " -"- 'rest': HTTP (experimental)" +"bidirectional streaming - 'grpc-rere': gRPC, request-response " +"(experimental) - 'rest': HTTP (experimental)" msgstr "" #: flwr.client.app.start_client:31 of msgid "" "The maximum number of times the client will try to connect to the server " -"before giving up in case of a connection error. If set to None, there is no " -"limit to the number of tries." +"before giving up in case of a connection error. If set to None, there is " +"no limit to the number of tries." msgstr "" #: flwr.client.app.start_client:35 of msgid "" -"The maximum duration before the client stops trying to connect to the server " -"in case of connection error. If set to None, there is no limit to the total " -"time." +"The maximum duration before the client stops trying to connect to the " +"server in case of connection error. If set to None, there is no limit to " +"the total time." msgstr "" #: flwr.client.app.start_client:42 flwr.client.app.start_numpy_client:37 of @@ -7463,9 +7759,10 @@ msgstr "" #: flwr.client.app.start_numpy_client:5 of msgid "" -"This function is deprecated since 1.7.0. Use :code:`flwr.client." -"start_client` instead and first convert your :code:`NumPyClient` to type :" -"code:`flwr.client.Client` by executing its :code:`to_client()` method." +"This function is deprecated since 1.7.0. Use " +":code:`flwr.client.start_client` instead and first convert your " +":code:`NumPyClient` to type :code:`flwr.client.Client` by executing its " +":code:`to_client()` method." msgstr "" #: flwr.client.app.start_numpy_client:13 of @@ -7477,8 +7774,7 @@ msgid "common" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 -msgid "" -":py:obj:`array_from_numpy `\\ \\(ndarray\\)" +msgid ":py:obj:`array_from_numpy `\\ \\(ndarray\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 @@ -7487,8 +7783,7 @@ msgid "Create Array from NumPy ndarray." msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 -msgid "" -":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" +msgid ":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 @@ -7530,8 +7825,7 @@ msgid "Log 'msg % args' with the integer severity 'level'." msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 -msgid "" -":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" +msgid ":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:30::1 @@ -7575,7 +7869,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`Array `\\ \\(dtype\\, shape\\, stype\\, data\\)" +":py:obj:`Array `\\ \\(dtype\\, shape\\, stype\\, " +"data\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7634,7 +7929,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`EvaluateIns `\\ \\(parameters\\, config\\)" +":py:obj:`EvaluateIns `\\ \\(parameters\\, " +"config\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7692,8 +7988,7 @@ msgid "A dataclass that stores information about an error that occurred." msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 -msgid "" -":py:obj:`GetParametersIns `\\ \\(config\\)" +msgid ":py:obj:`GetParametersIns `\\ \\(config\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7713,8 +8008,7 @@ msgid "Response when asked to return parameters." msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 -msgid "" -":py:obj:`GetPropertiesIns `\\ \\(config\\)" +msgid ":py:obj:`GetPropertiesIns `\\ \\(config\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7764,8 +8058,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`Metadata `\\ \\(run\\_id\\, message\\_id\\, " -"src\\_node\\_id\\, ...\\)" +":py:obj:`Metadata `\\ \\(run\\_id\\, " +"message\\_id\\, src\\_node\\_id\\, ...\\)" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7790,8 +8084,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -"alias of :py:class:`~numpy.ndarray`\\ [:py:obj:`~typing.Any`, :py:class:" -"`~numpy.dtype`\\ [:py:obj:`~typing.Any`]]" +"alias of :py:class:`~numpy.ndarray`\\ [:py:obj:`~typing.Any`, " +":py:class:`~numpy.dtype`\\ [:py:obj:`~typing.Any`]]" msgstr "" #: ../../source/ref-api/flwr.common.rst:64::1 @@ -7868,21 +8162,21 @@ msgstr "" #: flwr.common.record.parametersrecord.Array:6 of msgid "" -"A string representing the data type of the serialised object (e.g. `np." -"float32`)" +"A string representing the data type of the serialised object (e.g. " +"`np.float32`)" msgstr "" #: flwr.common.record.parametersrecord.Array:8 of msgid "" -"A list representing the shape of the unserialized array-like object. This is " -"used to deserialize the data (depending on the serialization method) or " -"simply as a metadata field." +"A list representing the shape of the unserialized array-like object. This" +" is used to deserialize the data (depending on the serialization method) " +"or simply as a metadata field." msgstr "" #: flwr.common.record.parametersrecord.Array:12 of msgid "" -"A string indicating the type of serialisation mechanism used to generate the " -"bytes in `data` from an array-like or tensor-like object." +"A string indicating the type of serialisation mechanism used to generate " +"the bytes in `data` from an array-like or tensor-like object." msgstr "" #: flwr.common.record.parametersrecord.Array:15 of @@ -7928,12 +8222,14 @@ msgstr "" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid "" -":py:obj:`get_parameters_res `\\" +":py:obj:`get_parameters_res " +"`\\" msgstr "" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid "" -":py:obj:`get_properties_res `\\" +":py:obj:`get_properties_res " +"`\\" msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:2 @@ -7950,14 +8246,14 @@ msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`GET_PROPERTIES_NOT_IMPLEMENTED `\\" +":py:obj:`GET_PROPERTIES_NOT_IMPLEMENTED " +"`\\" msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`GET_PARAMETERS_NOT_IMPLEMENTED `\\" +":py:obj:`GET_PARAMETERS_NOT_IMPLEMENTED " +"`\\" msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 @@ -7966,8 +8262,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`EVALUATE_NOT_IMPLEMENTED `\\" +":py:obj:`EVALUATE_NOT_IMPLEMENTED " +"`\\" msgstr "" #: ../../source/ref-api/flwr.common.ConfigsRecord.rst:2 @@ -7976,12 +8272,12 @@ msgstr "" #: flwr.common.record.configsrecord.ConfigsRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" -"`str`, :py:class:`int` | :py:class:`float` | :py:class:`str` | :py:class:" -"`bytes` | :py:class:`bool` | :py:class:`~typing.List`\\ [:py:class:`int`] | :" -"py:class:`~typing.List`\\ [:py:class:`float`] | :py:class:`~typing.List`\\ [:" -"py:class:`str`] | :py:class:`~typing.List`\\ [:py:class:`bytes`] | :py:class:" -"`~typing.List`\\ [:py:class:`bool`]]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " +"[:py:class:`str`, :py:class:`int` | :py:class:`float` | :py:class:`str` |" +" :py:class:`bytes` | :py:class:`bool` | :py:class:`~typing.List`\\ " +"[:py:class:`int`] | :py:class:`~typing.List`\\ [:py:class:`float`] | " +":py:class:`~typing.List`\\ [:py:class:`str`] | :py:class:`~typing.List`\\" +" [:py:class:`bytes`] | :py:class:`~typing.List`\\ [:py:class:`bool`]]" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -8027,8 +8323,7 @@ msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 #: flwr.common.record.typeddict.TypedDict.pop:1 of -msgid "" -"If key is not found, d is returned if given, otherwise KeyError is raised." +msgid "If key is not found, d is returned if given, otherwise KeyError is raised." msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -8056,11 +8351,12 @@ msgstr "" #: flwr.common.context.Context:3 of msgid "" -"Holds records added by the entity in a given run and that will stay local. " -"This means that the data it holds will never leave the system it's running " -"from. This can be used as an intermediate storage or scratchpad when " -"executing mods. It can also be used as a memory to access at different " -"points during the lifecycle of this entity (e.g. across multiple rounds)" +"Holds records added by the entity in a given run and that will stay " +"local. This means that the data it holds will never leave the system it's" +" running from. This can be used as an intermediate storage or scratchpad " +"when executing mods. It can also be used as a memory to access at " +"different points during the lifecycle of this entity (e.g. across " +"multiple rounds)" msgstr "" #: ../../source/ref-api/flwr.common.Context.rst:28::1 @@ -8167,19 +8463,21 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`split `\\ \\(\\[sep\\, maxsplit\\]\\)" +":py:obj:`split `\\ \\(\\[sep\\, " +"maxsplit\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rsplit:1 flwr.common.EventType.split:1 of msgid "" -"Return a list of the substrings in the string, using sep as the separator " -"string." +"Return a list of the substrings in the string, using sep as the separator" +" string." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rsplit `\\ \\(\\[sep\\, maxsplit\\]\\)" +":py:obj:`rsplit `\\ \\(\\[sep\\, " +"maxsplit\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8237,13 +8535,14 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the number of non-overlapping occurrences of substring sub in string " -"S[start:end]." +"Return the number of non-overlapping occurrences of substring sub in " +"string S[start:end]." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`expandtabs `\\ \\(\\[tabsize\\]\\)" +":py:obj:`expandtabs `\\ " +"\\(\\[tabsize\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8259,13 +8558,12 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the lowest index in S where substring sub is found, such that sub is " -"contained within S[start:end]." +"Return the lowest index in S where substring sub is found, such that sub " +"is contained within S[start:end]." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid "" -":py:obj:`partition `\\ \\(sep\\, \\/\\)" +msgid ":py:obj:`partition `\\ \\(sep\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8281,7 +8579,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`ljust `\\ \\(width\\[\\, fillchar\\]\\)" +":py:obj:`ljust `\\ \\(width\\[\\, " +"fillchar\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8315,19 +8614,20 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the highest index in S where substring sub is found, such that sub is " -"contained within S[start:end]." +"Return the highest index in S where substring sub is found, such that sub" +" is contained within S[start:end]." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rindex `\\ \\(sub\\[\\, start\\[\\, " -"end\\]\\]\\)" +":py:obj:`rindex `\\ \\(sub\\[\\, " +"start\\[\\, end\\]\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rjust `\\ \\(width\\[\\, fillchar\\]\\)" +":py:obj:`rjust `\\ \\(width\\[\\, " +"fillchar\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8345,8 +8645,7 @@ msgid "Return a copy of the string with trailing whitespace removed." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid "" -":py:obj:`rpartition `\\ \\(sep\\, \\/\\)" +msgid ":py:obj:`rpartition `\\ \\(sep\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8366,8 +8665,7 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.strip:1 of -msgid "" -"Return a copy of the string with leading and trailing whitespace removed." +msgid "Return a copy of the string with leading and trailing whitespace removed." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8382,8 +8680,7 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid "" -":py:obj:`translate `\\ \\(table\\, \\/\\)" +msgid ":py:obj:`translate `\\ \\(table\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8402,8 +8699,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`startswith `\\ \\(prefix\\[\\, " -"start\\[\\, end\\]\\]\\)" +":py:obj:`startswith `\\ \\(prefix\\[\\," +" start\\[\\, end\\]\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8422,8 +8719,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`removeprefix `\\ \\(prefix\\, " -"\\/\\)" +":py:obj:`removeprefix `\\ " +"\\(prefix\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8433,8 +8730,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`removesuffix `\\ \\(suffix\\, " -"\\/\\)" +":py:obj:`removesuffix `\\ " +"\\(suffix\\, \\/\\)" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8538,8 +8835,7 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isidentifier:1 of -msgid "" -"Return True if the string is a valid Python identifier, False otherwise." +msgid "Return True if the string is a valid Python identifier, False otherwise." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8558,8 +8854,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.zfill:1 of msgid "" -"Pad a numeric string with zeros on the left, to fill a field of the given " -"width." +"Pad a numeric string with zeros on the left, to fill a field of the given" +" width." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8569,8 +8865,7 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid "" -"Return a formatted version of S, using substitutions from args and kwargs." +msgid "Return a formatted version of S, using substitutions from args and kwargs." msgstr "" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 @@ -8595,65 +8890,67 @@ msgid ":py:obj:`PING `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid "" -":py:obj:`START_CLIENT_ENTER `\\" +msgid ":py:obj:`START_CLIENT_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid "" -":py:obj:`START_CLIENT_LEAVE `\\" +msgid ":py:obj:`START_CLIENT_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid "" -":py:obj:`START_SERVER_ENTER `\\" +msgid ":py:obj:`START_SERVER_ENTER `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of -msgid "" -":py:obj:`START_SERVER_LEAVE `\\" +msgid ":py:obj:`START_SERVER_LEAVE `\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_DRIVER_API_ENTER `\\" +":py:obj:`RUN_DRIVER_API_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_DRIVER_API_LEAVE `\\" +":py:obj:`RUN_DRIVER_API_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_FLEET_API_ENTER `\\" +":py:obj:`RUN_FLEET_API_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_FLEET_API_LEAVE `\\" +":py:obj:`RUN_FLEET_API_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERLINK_ENTER `\\" +":py:obj:`RUN_SUPERLINK_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERLINK_LEAVE `\\" +":py:obj:`RUN_SUPERLINK_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_SIMULATION_ENTER `\\" +":py:obj:`START_SIMULATION_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_SIMULATION_LEAVE `\\" +":py:obj:`START_SIMULATION_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of @@ -8664,44 +8961,60 @@ msgstr "" msgid ":py:obj:`DRIVER_DISCONNECT `\\" msgstr "" +#: flwr.common.EventType.capitalize:1::1 of +msgid ":py:obj:`START_DRIVER_ENTER `\\" +msgstr "" + +#: flwr.common.EventType.capitalize:1::1 of +msgid ":py:obj:`START_DRIVER_LEAVE `\\" +msgstr "" + #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_DRIVER_ENTER `\\" +":py:obj:`RUN_CLIENT_APP_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_DRIVER_LEAVE `\\" +":py:obj:`RUN_CLIENT_APP_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_CLIENT_APP_ENTER `\\" +":py:obj:`RUN_SERVER_APP_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_CLIENT_APP_LEAVE `\\" +":py:obj:`RUN_SERVER_APP_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SERVER_APP_ENTER `\\" +":py:obj:`RUN_SUPERNODE_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SERVER_APP_LEAVE `\\" +":py:obj:`RUN_SUPERNODE_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERNODE_ENTER `\\" +":py:obj:`RUN_SUPEREXEC_ENTER " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERNODE_LEAVE `\\" +":py:obj:`RUN_SUPEREXEC_LEAVE " +"`\\" msgstr "" #: flwr.common.EventType.capitalize:3 of @@ -8712,15 +9025,14 @@ msgstr "" #: flwr.common.EventType.center:3 flwr.common.EventType.ljust:3 #: flwr.common.EventType.rjust:3 of -msgid "" -"Padding is done using the specified fill character (default is a space)." +msgid "Padding is done using the specified fill character (default is a space)." msgstr "" #: flwr.common.EventType.count:1 of msgid "" -"Return the number of non-overlapping occurrences of substring sub in string " -"S[start:end]. Optional arguments start and end are interpreted as in slice " -"notation." +"Return the number of non-overlapping occurrences of substring sub in " +"string S[start:end]. Optional arguments start and end are interpreted as" +" in slice notation." msgstr "" #: flwr.common.EventType.encode:3 of @@ -8739,16 +9051,17 @@ msgstr "" msgid "" "The error handling scheme to use for encoding errors. The default is " "'strict' meaning that encoding errors raise a UnicodeEncodeError. Other " -"possible values are 'ignore', 'replace' and 'xmlcharrefreplace' as well as " -"any other name registered with codecs.register_error that can handle " +"possible values are 'ignore', 'replace' and 'xmlcharrefreplace' as well " +"as any other name registered with codecs.register_error that can handle " "UnicodeEncodeErrors." msgstr "" #: flwr.common.EventType.endswith:1 of msgid "" "Return True if S ends with the specified suffix, False otherwise. With " -"optional start, test S beginning at that position. With optional end, stop " -"comparing S at that position. suffix can also be a tuple of strings to try." +"optional start, test S beginning at that position. With optional end, " +"stop comparing S at that position. suffix can also be a tuple of strings " +"to try." msgstr "" #: flwr.common.EventType.expandtabs:3 of @@ -8757,8 +9070,8 @@ msgstr "" #: flwr.common.EventType.find:1 flwr.common.EventType.index:1 of msgid "" -"Return the lowest index in S where substring sub is found, such that sub is " -"contained within S[start:end]. Optional arguments start and end are " +"Return the lowest index in S where substring sub is found, such that sub " +"is contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" @@ -8768,8 +9081,8 @@ msgstr "" #: flwr.common.EventType.format:1 of msgid "" -"Return a formatted version of S, using substitutions from args and kwargs. " -"The substitutions are identified by braces ('{' and '}')." +"Return a formatted version of S, using substitutions from args and " +"kwargs. The substitutions are identified by braces ('{' and '}')." msgstr "" #: flwr.common.EventType.format_map:1 of @@ -8784,80 +9097,80 @@ msgstr "" #: flwr.common.EventType.isalnum:3 of msgid "" -"A string is alpha-numeric if all characters in the string are alpha-numeric " -"and there is at least one character in the string." +"A string is alpha-numeric if all characters in the string are alpha-" +"numeric and there is at least one character in the string." msgstr "" #: flwr.common.EventType.isalpha:3 of msgid "" -"A string is alphabetic if all characters in the string are alphabetic and " -"there is at least one character in the string." +"A string is alphabetic if all characters in the string are alphabetic and" +" there is at least one character in the string." msgstr "" #: flwr.common.EventType.isascii:3 of msgid "" -"ASCII characters have code points in the range U+0000-U+007F. Empty string " -"is ASCII too." +"ASCII characters have code points in the range U+0000-U+007F. Empty " +"string is ASCII too." msgstr "" #: flwr.common.EventType.isdecimal:3 of msgid "" -"A string is a decimal string if all characters in the string are decimal and " -"there is at least one character in the string." +"A string is a decimal string if all characters in the string are decimal " +"and there is at least one character in the string." msgstr "" #: flwr.common.EventType.isdigit:3 of msgid "" -"A string is a digit string if all characters in the string are digits and " -"there is at least one character in the string." +"A string is a digit string if all characters in the string are digits and" +" there is at least one character in the string." msgstr "" #: flwr.common.EventType.isidentifier:3 of msgid "" -"Call keyword.iskeyword(s) to test whether string s is a reserved identifier, " -"such as \"def\" or \"class\"." +"Call keyword.iskeyword(s) to test whether string s is a reserved " +"identifier, such as \"def\" or \"class\"." msgstr "" #: flwr.common.EventType.islower:3 of msgid "" -"A string is lowercase if all cased characters in the string are lowercase " -"and there is at least one cased character in the string." +"A string is lowercase if all cased characters in the string are lowercase" +" and there is at least one cased character in the string." msgstr "" #: flwr.common.EventType.isnumeric:3 of msgid "" -"A string is numeric if all characters in the string are numeric and there is " -"at least one character in the string." +"A string is numeric if all characters in the string are numeric and there" +" is at least one character in the string." msgstr "" #: flwr.common.EventType.isprintable:3 of msgid "" -"A string is printable if all of its characters are considered printable in " -"repr() or if it is empty." +"A string is printable if all of its characters are considered printable " +"in repr() or if it is empty." msgstr "" #: flwr.common.EventType.isspace:3 of msgid "" -"A string is whitespace if all characters in the string are whitespace and " -"there is at least one character in the string." +"A string is whitespace if all characters in the string are whitespace and" +" there is at least one character in the string." msgstr "" #: flwr.common.EventType.istitle:3 of msgid "" -"In a title-cased string, upper- and title-case characters may only follow " -"uncased characters and lowercase characters only cased ones." +"In a title-cased string, upper- and title-case characters may only follow" +" uncased characters and lowercase characters only cased ones." msgstr "" #: flwr.common.EventType.isupper:3 of msgid "" -"A string is uppercase if all cased characters in the string are uppercase " -"and there is at least one cased character in the string." +"A string is uppercase if all cased characters in the string are uppercase" +" and there is at least one cased character in the string." msgstr "" #: flwr.common.EventType.join:3 of msgid "" -"The string whose method is called is inserted in between each given string. " -"The result is returned as a new string." +"The string whose method is called is inserted in between each given " +"string. The result is returned as a new string." msgstr "" #: flwr.common.EventType.join:6 of @@ -8875,9 +9188,9 @@ msgid "" "ordinals (integers) or characters to Unicode ordinals, strings or None. " "Character keys will be then converted to ordinals. If there are two " "arguments, they must be strings of equal length, and in the resulting " -"dictionary, each character in x will be mapped to the character at the same " -"position in y. If there is a third argument, it must be a string, whose " -"characters will be mapped to None in the result." +"dictionary, each character in x will be mapped to the character at the " +"same position in y. If there is a third argument, it must be a string, " +"whose characters will be mapped to None in the result." msgstr "" #: flwr.common.EventType.partition:3 of @@ -8895,8 +9208,8 @@ msgstr "" #: flwr.common.EventType.removeprefix:3 of msgid "" -"If the string starts with the prefix string, return string[len(prefix):]. " -"Otherwise, return a copy of the original string." +"If the string starts with the prefix string, return string[len(prefix):]." +" Otherwise, return a copy of the original string." msgstr "" #: flwr.common.EventType.removesuffix:3 of @@ -8918,22 +9231,22 @@ msgstr "" #: flwr.common.EventType.replace:7 of msgid "" -"If the optional argument count is given, only the first count occurrences " -"are replaced." +"If the optional argument count is given, only the first count occurrences" +" are replaced." msgstr "" #: flwr.common.EventType.rfind:1 flwr.common.EventType.rindex:1 of msgid "" -"Return the highest index in S where substring sub is found, such that sub is " -"contained within S[start:end]. Optional arguments start and end are " +"Return the highest index in S where substring sub is found, such that sub" +" is contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" #: flwr.common.EventType.rpartition:3 of msgid "" -"This will search for the separator in the string, starting at the end. If " -"the separator is found, returns a 3-tuple containing the part before the " -"separator, the separator itself, and the part after it." +"This will search for the separator in the string, starting at the end. If" +" the separator is found, returns a 3-tuple containing the part before the" +" separator, the separator itself, and the part after it." msgstr "" #: flwr.common.EventType.rpartition:7 of @@ -8952,9 +9265,9 @@ msgstr "" #: flwr.common.EventType.rsplit:6 flwr.common.EventType.split:6 of msgid "" -"When set to None (the default value), will split on any whitespace character " -"(including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard empty " -"strings from the result." +"When set to None (the default value), will split on any whitespace " +"character (including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard" +" empty strings from the result." msgstr "" #: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of @@ -8963,8 +9276,8 @@ msgstr "" #: flwr.common.EventType.rsplit:10 flwr.common.EventType.split:10 of msgid "" -"Maximum number of splits (starting from the left). -1 (the default value) " -"means no limit." +"Maximum number of splits (starting from the left). -1 (the default value)" +" means no limit." msgstr "" #: flwr.common.EventType.rsplit:13 of @@ -8974,27 +9287,28 @@ msgstr "" #: flwr.common.EventType.split:13 of msgid "" "Note, str.split() is mainly useful for data that has been intentionally " -"delimited. With natural text that includes punctuation, consider using the " -"regular expression module." +"delimited. With natural text that includes punctuation, consider using " +"the regular expression module." msgstr "" #: flwr.common.EventType.splitlines:3 of msgid "" -"Line breaks are not included in the resulting list unless keepends is given " -"and true." +"Line breaks are not included in the resulting list unless keepends is " +"given and true." msgstr "" #: flwr.common.EventType.startswith:1 of msgid "" "Return True if S starts with the specified prefix, False otherwise. With " -"optional start, test S beginning at that position. With optional end, stop " -"comparing S at that position. prefix can also be a tuple of strings to try." +"optional start, test S beginning at that position. With optional end, " +"stop comparing S at that position. prefix can also be a tuple of strings " +"to try." msgstr "" #: flwr.common.EventType.title:3 of msgid "" -"More specifically, words start with uppercased characters and all remaining " -"cased characters have lower case." +"More specifically, words start with uppercased characters and all " +"remaining cased characters have lower case." msgstr "" #: flwr.common.EventType.translate:5 of @@ -9003,15 +9317,15 @@ msgstr "" #: flwr.common.EventType.translate:4 of msgid "" -"Translation table, which must be a mapping of Unicode ordinals to Unicode " -"ordinals, strings, or None." +"Translation table, which must be a mapping of Unicode ordinals to Unicode" +" ordinals, strings, or None." msgstr "" #: flwr.common.EventType.translate:7 of msgid "" "The table must implement lookup/indexing via __getitem__, for instance a " -"dictionary or list. If this operation raises LookupError, the character is " -"left untouched. Characters mapped to None are deleted." +"dictionary or list. If this operation raises LookupError, the character " +"is left untouched. Characters mapped to None are deleted." msgstr "" #: flwr.common.EventType.zfill:3 of @@ -9101,14 +9415,14 @@ msgstr "" #: flwr.common.message.Message:5 of msgid "" -"Holds records either sent by another entity (e.g. sent by the server-side " -"logic to a client, or vice-versa) or that will be sent to it." +"Holds records either sent by another entity (e.g. sent by the server-side" +" logic to a client, or vice-versa) or that will be sent to it." msgstr "" #: flwr.common.message.Message:8 of msgid "" -"A dataclass that captures information about an error that took place when " -"processing another message." +"A dataclass that captures information about an error that took place when" +" processing another message." msgstr "" #: ../../source/ref-api/flwr.common.Message.rst:35::1 @@ -9124,8 +9438,8 @@ msgstr "" #: ../../source/ref-api/flwr.common.Message.rst:35::1 msgid "" -":py:obj:`create_reply `\\ \\(content\\[\\, " -"ttl\\]\\)" +":py:obj:`create_reply `\\ " +"\\(content\\[\\, ttl\\]\\)" msgstr "" #: ../../source/ref-api/flwr.common.Message.rst:35::1 @@ -9179,18 +9493,18 @@ msgstr "" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of msgid "" -"Time-to-live for this message in seconds. If unset, it will be set based on " -"the remaining time for the received message before it expires. This follows " -"the equation: ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta." -"created_at)" +"Time-to-live for this message in seconds. If unset, it will be set based " +"on the remaining time for the received message before it expires. This " +"follows the equation: ttl = msg.meta.ttl - (reply.meta.created_at - " +"msg.meta.created_at)" msgstr "" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of msgid "" -"Time-to-live for this message in seconds. If unset, it will be set based on " -"the remaining time for the received message before it expires. This follows " -"the equation:" +"Time-to-live for this message in seconds. If unset, it will be set based " +"on the remaining time for the received message before it expires. This " +"follows the equation:" msgstr "" #: flwr.common.message.Message.create_error_reply:9 @@ -9200,9 +9514,9 @@ msgstr "" #: flwr.common.message.Message.create_reply:3 of msgid "" -"The method generates a new `Message` as a reply to this message. It inherits " -"'run_id', 'src_node_id', 'dst_node_id', and 'message_type' from this message " -"and sets 'reply_to_message' to the ID of this message." +"The method generates a new `Message` as a reply to this message. It " +"inherits 'run_id', 'src_node_id', 'dst_node_id', and 'message_type' from " +"this message and sets 'reply_to_message' to the ID of this message." msgstr "" #: flwr.common.message.Message.create_reply:7 of @@ -9234,13 +9548,11 @@ msgid "MessageTypeLegacy" msgstr "" #: ../../source/ref-api/flwr.common.MessageTypeLegacy.rst:29::1 -msgid "" -":py:obj:`GET_PARAMETERS `\\" +msgid ":py:obj:`GET_PARAMETERS `\\" msgstr "" #: ../../source/ref-api/flwr.common.MessageTypeLegacy.rst:29::1 -msgid "" -":py:obj:`GET_PROPERTIES `\\" +msgid ":py:obj:`GET_PROPERTIES `\\" msgstr "" #: flwr.common.Metadata.created_at:1::1 @@ -9270,8 +9582,8 @@ msgstr "" #: flwr.common.message.Metadata:13 of msgid "" -"An identifier for grouping messages. In some settings, this is used as the " -"FL round." +"An identifier for grouping messages. In some settings, this is used as " +"the FL round." msgstr "" #: flwr.common.message.Metadata:16 of @@ -9285,9 +9597,9 @@ msgstr "" #: flwr.common.message.Metadata:21 of msgid "" -"An identifier that can be used when loading a particular data partition for " -"a ClientApp. Making use of this identifier is more relevant when conducting " -"simulations." +"An identifier that can be used when loading a particular data partition " +"for a ClientApp. Making use of this identifier is more relevant when " +"conducting simulations." msgstr "" #: flwr.common.Metadata.created_at:1::1 of @@ -9356,9 +9668,10 @@ msgstr "" #: flwr.common.record.metricsrecord.MetricsRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" -"`str`, :py:class:`int` | :py:class:`float` | :py:class:`~typing.List`\\ [:py:" -"class:`int`] | :py:class:`~typing.List`\\ [:py:class:`float`]]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " +"[:py:class:`str`, :py:class:`int` | :py:class:`float` | " +":py:class:`~typing.List`\\ [:py:class:`int`] | :py:class:`~typing.List`\\" +" [:py:class:`float`]]" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -9413,15 +9726,16 @@ msgstr "" #: flwr.common.record.parametersrecord.ParametersRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" -"`str`, :py:class:`~flwr.common.record.parametersrecord.Array`]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " +"[:py:class:`str`, :py:class:`~flwr.common.record.parametersrecord.Array`]" msgstr "" #: flwr.common.record.parametersrecord.ParametersRecord:3 of msgid "" -"A dataclass storing named Arrays in order. This means that it holds entries " -"as an OrderedDict[str, Array]. ParametersRecord objects can be viewed as an " -"equivalent to PyTorch's state_dict, but holding serialised tensors instead." +"A dataclass storing named Arrays in order. This means that it holds " +"entries as an OrderedDict[str, Array]. ParametersRecord objects can be " +"viewed as an equivalent to PyTorch's state_dict, but holding serialised " +"tensors instead." msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -9429,8 +9743,7 @@ msgid ":py:obj:`clear `\\ \\(\\)" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of -msgid "" -":py:obj:`count_bytes `\\ \\(\\)" +msgid ":py:obj:`count_bytes `\\ \\(\\)" msgstr "" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of @@ -9461,9 +9774,9 @@ msgstr "" #: flwr.common.record.parametersrecord.ParametersRecord.count_bytes:3 of msgid "" -"Note that a small amount of Bytes might also be included in this counting " -"that correspond to metadata of the serialized object (e.g. of NumPy array) " -"needed for deseralization." +"Note that a small amount of Bytes might also be included in this counting" +" that correspond to metadata of the serialized object (e.g. of NumPy " +"array) needed for deseralization." msgstr "" #: ../../source/ref-api/flwr.common.ReconnectIns.rst:2 @@ -9497,8 +9810,7 @@ msgid "Dictionary holding MetricsRecord instances." msgstr "" #: flwr.common.RecordSet.configs_records:1::1 of -msgid "" -":py:obj:`parameters_records `\\" +msgid ":py:obj:`parameters_records `\\" msgstr "" #: flwr.common.RecordSet.configs_records:1::1 @@ -9520,12 +9832,14 @@ msgstr "" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid "" -":py:obj:`get_parameters_ins `\\" +":py:obj:`get_parameters_ins " +"`\\" msgstr "" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid "" -":py:obj:`get_properties_ins `\\" +":py:obj:`get_properties_ins " +"`\\" msgstr "" #: ../../source/ref-api/flwr.common.Status.rst:2 @@ -9562,8 +9876,8 @@ msgstr "" #: logging.Logger.log:3 of msgid "" -"To pass exception information, use the keyword argument exc_info with a true " -"value, e.g." +"To pass exception information, use the keyword argument exc_info with a " +"true value, e.g." msgstr "" #: logging.Logger.log:6 of @@ -9591,143 +9905,124 @@ msgstr "" msgid "server" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_driver_api `\\ \\(\\)" +#: ../../source/ref-api/flwr.server.rst:24::1 +msgid ":py:obj:`run_server_app `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_driver_api:1 of -msgid "Run Flower server (Driver API)." +#: ../../source/ref-api/flwr.server.rst:24::1 +#: flwr.server.run_serverapp.run_server_app:1 of +msgid "Run Flower server app." msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_fleet_api `\\ \\(\\)" +#: ../../source/ref-api/flwr.server.rst:24::1 +msgid ":py:obj:`run_superlink `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_fleet_api:1 of -msgid "Run Flower server (Fleet API)." +#: ../../source/ref-api/flwr.server.rst:24::1 +#: flwr.server.app.run_superlink:1 of +msgid "Run Flower SuperLink (Driver API and Fleet API)." msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_server_app `\\ \\(\\)" -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.run_serverapp.run_server_app:1 of -msgid "Run Flower server app." -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_superlink `\\ \\(\\)" -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_superlink:1 of -msgid "Run Flower SuperLink (Driver API and Fleet API)." -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 msgid "" ":py:obj:`start_server `\\ \\(\\*\\[\\, " "server\\_address\\, server\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.app.start_server:1 of msgid "Start a Flower server using the gRPC transport layer." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`ClientManager `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.ClientManager:1 of msgid "Abstract base class for managing Flower clients." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`Driver `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.driver.driver.Driver:1 of msgid "Abstract base Driver class for the Driver API." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`History `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.history.History:1 of msgid "History class for training and/or evaluation metrics collection." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`LegacyContext `\\ \\(state\\[\\, " "config\\, strategy\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.compat.legacy_context.LegacyContext:1 of msgid "Legacy Context." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`Server `\\ \\(\\*\\, client\\_manager\\[\\, " "strategy\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`ServerApp `\\ \\(\\[server\\, config\\, " "strategy\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_app.ServerApp:1 of msgid "Flower ServerApp." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" -":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\, " -"round\\_timeout\\]\\)" +":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\," +" round\\_timeout\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_config.ServerConfig:1 of msgid "Flower server config." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 -msgid "" -":py:obj:`SimpleClientManager `\\ \\(\\)" +#: ../../source/ref-api/flwr.server.rst:38::1 +msgid ":py:obj:`SimpleClientManager `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.SimpleClientManager:1 of msgid "Provides a pool of available clients." msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 msgid ":py:obj:`flwr.server.strategy `\\" msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.strategy:1 of msgid "Contains the strategy abstraction and different implementations." msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 msgid ":py:obj:`flwr.server.workflow `\\" msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.workflow:1 of msgid "Workflows." msgstr "" @@ -9748,8 +10043,7 @@ msgid "Return all available clients." msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of -msgid "" -":py:obj:`num_available `\\ \\(\\)" +msgid ":py:obj:`num_available `\\ \\(\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -9772,8 +10066,8 @@ msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of msgid "" -":py:obj:`sample `\\ \\(num\\_clients\\[\\, " -"min\\_num\\_clients\\, criterion\\]\\)" +":py:obj:`sample `\\ " +"\\(num\\_clients\\[\\, min\\_num\\_clients\\, criterion\\]\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -9784,8 +10078,7 @@ msgid "Sample a number of Flower ClientProxy instances." msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of -msgid "" -":py:obj:`unregister `\\ \\(client\\)" +msgid ":py:obj:`unregister `\\ \\(client\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -9817,7 +10110,8 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.register:6 of msgid "" "**success** -- Indicating if registration was successful. False if " -"ClientProxy is already registered or can not be registered for any reason." +"ClientProxy is already registered or can not be registered for any " +"reason." msgstr "" #: flwr.server.client_manager.ClientManager.unregister:3 @@ -9831,8 +10125,8 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 of msgid "" -":py:obj:`create_message `\\ \\(content\\, " -"message\\_type\\, ...\\[\\, ttl\\]\\)" +":py:obj:`create_message `\\ " +"\\(content\\, message\\_type\\, ...\\[\\, ttl\\]\\)" msgstr "" #: flwr.server.driver.driver.Driver.create_message:1 @@ -9862,7 +10156,8 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 of msgid "" -":py:obj:`push_messages `\\ \\(messages\\)" +":py:obj:`push_messages `\\ " +"\\(messages\\)" msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 @@ -9883,20 +10178,20 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:3 of msgid "" -"This method constructs a new `Message` with given content and metadata. The " -"`run_id` and `src_node_id` will be set automatically." +"This method constructs a new `Message` with given content and metadata. " +"The `run_id` and `src_node_id` will be set automatically." msgstr "" #: flwr.server.driver.driver.Driver.create_message:6 of msgid "" -"The content for the new message. This holds records that are to be sent to " -"the destination node." +"The content for the new message. This holds records that are to be sent " +"to the destination node." msgstr "" #: flwr.server.driver.driver.Driver.create_message:9 of msgid "" -"The type of the message, defining the action to be executed on the receiving " -"end." +"The type of the message, defining the action to be executed on the " +"receiving end." msgstr "" #: flwr.server.driver.driver.Driver.create_message:12 of @@ -9905,16 +10200,17 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:14 of msgid "" -"The ID of the group to which this message is associated. In some settings, " -"this is used as the FL round." +"The ID of the group to which this message is associated. In some " +"settings, this is used as the FL round." msgstr "" #: flwr.server.driver.driver.Driver.create_message:17 of msgid "" -"Time-to-live for the round trip of this message, i.e., the time from sending " -"this message to receiving a reply. It specifies in seconds the duration for " -"which the message and its potential reply are considered valid. If unset, " -"the default TTL (i.e., `common.DEFAULT_TTL`) will be used." +"Time-to-live for the round trip of this message, i.e., the time from " +"sending this message to receiving a reply. It specifies in seconds the " +"duration for which the message and its potential reply are considered " +"valid. If unset, the default TTL (i.e., `common.DEFAULT_TTL`) will be " +"used." msgstr "" #: flwr.server.driver.driver.Driver.create_message:23 of @@ -9925,13 +10221,12 @@ msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:3 of msgid "" -"This method is used to collect messages from the SuperLink that correspond " -"to a set of given message IDs." +"This method is used to collect messages from the SuperLink that " +"correspond to a set of given message IDs." msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:6 of -msgid "" -"An iterable of message IDs for which reply messages are to be retrieved." +msgid "An iterable of message IDs for which reply messages are to be retrieved." msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:9 of @@ -9940,8 +10235,8 @@ msgstr "" #: flwr.server.driver.driver.Driver.push_messages:3 of msgid "" -"This method takes an iterable of messages and sends each message to the node " -"specified in `dst_node_id`." +"This method takes an iterable of messages and sends each message to the " +"node specified in `dst_node_id`." msgstr "" #: flwr.server.driver.driver.Driver.push_messages:6 @@ -9951,42 +10246,34 @@ msgstr "" #: flwr.server.driver.driver.Driver.push_messages:9 of msgid "" -"**message_ids** -- An iterable of IDs for the messages that were sent, which " -"can be used to pull replies." +"**message_ids** -- An iterable of IDs for the messages that were sent, " +"which can be used to pull replies." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:3 of msgid "" -"This method sends a list of messages to their destination node IDs and then " -"waits for the replies. It continues to pull replies until either all replies " -"are received or the specified timeout duration is exceeded." +"This method sends a list of messages to their destination node IDs and " +"then waits for the replies. It continues to pull replies until either all" +" replies are received or the specified timeout duration is exceeded." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:9 of msgid "" "The timeout duration in seconds. If specified, the method will wait for " -"replies for this duration. If `None`, there is no time limit and the method " -"will wait until replies for all messages are received." +"replies for this duration. If `None`, there is no time limit and the " +"method will wait until replies for all messages are received." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:14 of -msgid "" -"**replies** -- An iterable of reply messages received from the SuperLink." -msgstr "" - -#: flwr.server.driver.driver.Driver.send_and_receive:18 -#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 -#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 -#: of -msgid "Notes" +msgid "**replies** -- An iterable of reply messages received from the SuperLink." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:19 of msgid "" -"This method uses `push_messages` to send the messages and `pull_messages` to " -"collect the replies. If `timeout` is set, the method may not return replies " -"for all sent messages. A message remains valid until its TTL, which is not " -"affected by `timeout`." +"This method uses `push_messages` to send the messages and `pull_messages`" +" to collect the replies. If `timeout` is set, the method may not return " +"replies for all sent messages. A message remains valid until its TTL, " +"which is not affected by `timeout`." msgstr "" #: ../../source/ref-api/flwr.server.History.rst:2 @@ -9995,8 +10282,9 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_loss_centralized `\\ " -"\\(server\\_round\\, loss\\)" +":py:obj:`add_loss_centralized " +"`\\ \\(server\\_round\\, " +"loss\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1 @@ -10006,8 +10294,9 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_loss_distributed `\\ " -"\\(server\\_round\\, loss\\)" +":py:obj:`add_loss_distributed " +"`\\ \\(server\\_round\\, " +"loss\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10017,8 +10306,9 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_centralized `\\ \\(server\\_round\\, metrics\\)" +":py:obj:`add_metrics_centralized " +"`\\ \\(server\\_round\\, " +"metrics\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10028,8 +10318,9 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_distributed `\\ \\(server\\_round\\, metrics\\)" +":py:obj:`add_metrics_distributed " +"`\\ \\(server\\_round\\, " +"metrics\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10039,8 +10330,9 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_distributed_fit `\\ \\(server\\_round\\, ...\\)" +":py:obj:`add_metrics_distributed_fit " +"`\\ \\(server\\_round\\," +" ...\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -10091,8 +10383,8 @@ msgstr "" #: flwr.server.server.Server.client_manager:1::1 of msgid "" -":py:obj:`disconnect_all_clients `\\ \\(timeout\\)" +":py:obj:`disconnect_all_clients " +"`\\ \\(timeout\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -10122,8 +10414,8 @@ msgstr "" #: flwr.server.server.Server.client_manager:1::1 of msgid "" -":py:obj:`fit_round `\\ \\(server\\_round\\, " -"timeout\\)" +":py:obj:`fit_round `\\ \\(server\\_round\\," +" timeout\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -10143,8 +10435,7 @@ msgid "Set the max_workers used by ThreadPoolExecutor." msgstr "" #: flwr.server.server.Server.client_manager:1::1 of -msgid "" -":py:obj:`set_strategy `\\ \\(strategy\\)" +msgid ":py:obj:`set_strategy `\\ \\(strategy\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -10179,8 +10470,8 @@ msgstr "" #: flwr.server.server_config.ServerConfig:3 of msgid "" -"All attributes have default values which allows users to configure just the " -"ones they care about." +"All attributes have default values which allows users to configure just " +"the ones they care about." msgstr "" #: ../../source/ref-api/flwr.server.ServerConfig.rst:29::1 @@ -10205,13 +10496,14 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of msgid "" -":py:obj:`num_available `\\ " -"\\(\\)" +":py:obj:`num_available `\\" +" \\(\\)" msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of msgid "" -":py:obj:`register `\\ \\(client\\)" +":py:obj:`register `\\ " +"\\(client\\)" msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of @@ -10234,8 +10526,8 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.wait_for:3 of msgid "" -"Blocks until the requested number of clients is available or until a timeout " -"is reached. Current timeout default: 1 day." +"Blocks until the requested number of clients is available or until a " +"timeout is reached. Current timeout default: 1 day." msgstr "" #: flwr.server.client_manager.SimpleClientManager.wait_for:6 of @@ -10276,8 +10568,8 @@ msgstr "" #: flwr.server.app.start_server:5 of msgid "" -"A server implementation, either `flwr.server.Server` or a subclass thereof. " -"If no instance is provided, then `start_server` will create one." +"A server implementation, either `flwr.server.Server` or a subclass " +"thereof. If no instance is provided, then `start_server` will create one." msgstr "" #: flwr.server.app.start_server:9 flwr.simulation.app.start_simulation:28 of @@ -10288,41 +10580,41 @@ msgstr "" #: flwr.server.app.start_server:12 of msgid "" -"An implementation of the abstract base class `flwr.server.strategy." -"Strategy`. If no strategy is provided, then `start_server` will use `flwr." -"server.strategy.FedAvg`." +"An implementation of the abstract base class " +"`flwr.server.strategy.Strategy`. If no strategy is provided, then " +"`start_server` will use `flwr.server.strategy.FedAvg`." msgstr "" #: flwr.server.app.start_server:16 of msgid "" -"An implementation of the abstract base class `flwr.server.ClientManager`. If " -"no implementation is provided, then `start_server` will use `flwr.server." -"client_manager.SimpleClientManager`." +"An implementation of the abstract base class `flwr.server.ClientManager`." +" If no implementation is provided, then `start_server` will use " +"`flwr.server.client_manager.SimpleClientManager`." msgstr "" #: flwr.server.app.start_server:21 of msgid "" -"The maximum length of gRPC messages that can be exchanged with the Flower " -"clients. The default should be sufficient for most models. Users who train " -"very large models might need to increase this value. Note that the Flower " -"clients need to be started with the same value (see `flwr.client." -"start_client`), otherwise clients will not know about the increased limit " -"and block larger messages." +"The maximum length of gRPC messages that can be exchanged with the Flower" +" clients. The default should be sufficient for most models. Users who " +"train very large models might need to increase this value. Note that the " +"Flower clients need to be started with the same value (see " +"`flwr.client.start_client`), otherwise clients will not know about the " +"increased limit and block larger messages." msgstr "" #: flwr.server.app.start_server:28 of msgid "" -"Tuple containing root certificate, server certificate, and private key to " -"start a secure SSL-enabled server. The tuple is expected to have three bytes " -"elements in the following order: * CA certificate. * server " -"certificate. * server private key." +"Tuple containing root certificate, server certificate, and private key to" +" start a secure SSL-enabled server. The tuple is expected to have three " +"bytes elements in the following order: * CA certificate. * " +"server certificate. * server private key." msgstr "" #: flwr.server.app.start_server:28 of msgid "" -"Tuple containing root certificate, server certificate, and private key to " -"start a secure SSL-enabled server. The tuple is expected to have three bytes " -"elements in the following order:" +"Tuple containing root certificate, server certificate, and private key to" +" start a secure SSL-enabled server. The tuple is expected to have three " +"bytes elements in the following order:" msgstr "" #: flwr.server.app.start_server:32 of @@ -10355,8 +10647,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`Bulyan `\\ \\(\\*\\, fraction\\_fit\\, " -"fraction\\_evaluate\\, ...\\)" +":py:obj:`Bulyan `\\ \\(\\*\\, " +"fraction\\_fit\\, fraction\\_evaluate\\, ...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10388,8 +10680,9 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyClientSideAdaptiveClipping `\\ \\(...\\)" +":py:obj:`DifferentialPrivacyClientSideAdaptiveClipping " +"`\\ " +"\\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10400,8 +10693,9 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyServerSideAdaptiveClipping `\\ \\(...\\)" +":py:obj:`DifferentialPrivacyServerSideAdaptiveClipping " +"`\\ " +"\\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10412,8 +10706,9 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyClientSideFixedClipping `\\ \\(...\\)" +":py:obj:`DifferentialPrivacyClientSideFixedClipping " +"`\\ " +"\\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10424,8 +10719,9 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyServerSideFixedClipping `\\ \\(...\\)" +":py:obj:`DifferentialPrivacyServerSideFixedClipping " +"`\\ " +"\\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10470,8 +10766,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FedAvgAndroid `\\ \\(\\*\\[\\, " -"fraction\\_fit\\, ...\\]\\)" +":py:obj:`FedAvgAndroid `\\ " +"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10520,8 +10816,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FedTrimmedAvg `\\ \\(\\*\\[\\, " -"fraction\\_fit\\, ...\\]\\)" +":py:obj:`FedTrimmedAvg `\\ " +"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10575,8 +10871,9 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FaultTolerantFedAvg `\\ " -"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" +":py:obj:`FaultTolerantFedAvg " +"`\\ \\(\\*\\[\\, " +"fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10586,8 +10883,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`Krum `\\ \\(\\*\\[\\, fraction\\_fit\\, " -"fraction\\_evaluate\\, ...\\]\\)" +":py:obj:`Krum `\\ \\(\\*\\[\\, " +"fraction\\_fit\\, fraction\\_evaluate\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10597,8 +10894,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`QFedAvg `\\ \\(\\*\\[\\, q\\_param\\, " -"qffl\\_learning\\_rate\\, ...\\]\\)" +":py:obj:`QFedAvg `\\ \\(\\*\\[\\, " +"q\\_param\\, qffl\\_learning\\_rate\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -10766,8 +11063,8 @@ msgstr "" #: flwr.server.strategy.bulyan.Bulyan:27 of msgid "" -"Byzantine resilient aggregation rule that is used as the first step of the " -"Bulyan (e.g., Krum)" +"Byzantine resilient aggregation rule that is used as the first step of " +"the Bulyan (e.g., Krum)" msgstr "" #: flwr.server.strategy.bulyan.Bulyan:29 of @@ -10776,8 +11073,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\, " +"results\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1 @@ -10804,8 +11102,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\, " +"parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -10884,8 +11183,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -10902,8 +11202,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -10920,8 +11221,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\" +" \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -10952,8 +11253,9 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1 @@ -10973,8 +11275,9 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dpfedavg_adaptive.DPFedAvgAdaptive.aggregate_fit:1 @@ -10986,8 +11289,9 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -10998,8 +11302,9 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -11019,15 +11324,15 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.evaluate:1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.evaluate:1 of -msgid "" -"Evaluate model parameters using an evaluation function from the strategy." +msgid "Evaluate model parameters using an evaluation function from the strategy." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -11066,9 +11371,9 @@ msgstr "" msgid "" "**evaluate_configuration** -- A list of tuples. Each tuple in the list " "identifies a `ClientProxy` and the `EvaluateIns` for this particular " -"`ClientProxy`. If a particular `ClientProxy` is not included in this list, " -"it means that this `ClientProxy` will not participate in the next round of " -"federated evaluation." +"`ClientProxy`. If a particular `ClientProxy` is not included in this " +"list, it means that this `ClientProxy` will not participate in the next " +"round of federated evaluation." msgstr "" #: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst:2 @@ -11088,14 +11393,16 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " +":py:obj:`aggregate_fit " +"`\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -11107,21 +11414,24 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " +":py:obj:`configure_fit " +"`\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:1 of msgid "" -"Configure the next round of training incorporating Differential Privacy (DP)." +"Configure the next round of training incorporating Differential Privacy " +"(DP)." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -11134,23 +11444,25 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:3 of msgid "" -"Configuration of the next training round includes information related to DP, " -"such as clip norm and noise stddev." +"Configuration of the next training round includes information related to " +"DP, such as clip norm and noise stddev." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:13 #: flwr.server.strategy.strategy.Strategy.configure_fit:10 of msgid "" -"**fit_configuration** -- A list of tuples. Each tuple in the list identifies " -"a `ClientProxy` and the `FitIns` for this particular `ClientProxy`. If a " -"particular `ClientProxy` is not included in this list, it means that this " -"`ClientProxy` will not participate in the next round of federated learning." +"**fit_configuration** -- A list of tuples. Each tuple in the list " +"identifies a `ClientProxy` and the `FitIns` for this particular " +"`ClientProxy`. If a particular `ClientProxy` is not included in this " +"list, it means that this `ClientProxy` will not participate in the next " +"round of federated learning." msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideAdaptiveClipping.rst:2 @@ -11167,8 +11479,9 @@ msgstr "" msgid "" "In comparison to `DifferentialPrivacyServerSideAdaptiveClipping`, which " "performs clipping on the server-side, " -"`DifferentialPrivacyClientSideAdaptiveClipping` expects clipping to happen " -"on the client-side, usually by using the built-in `adaptiveclipping_mod`." +"`DifferentialPrivacyClientSideAdaptiveClipping` expects clipping to " +"happen on the client-side, usually by using the built-in " +"`adaptiveclipping_mod`." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:10 @@ -11204,23 +11517,22 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:19 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:12 #: of -msgid "" -"The desired quantile of updates which should be clipped. Defaults to 0.5." +msgid "The desired quantile of updates which should be clipped. Defaults to 0.5." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:21 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:14 #: of msgid "" -"The learning rate for the clipping norm adaptation. Defaults to 0.2. Andrew " -"et al. recommends to set to 0.2." +"The learning rate for the clipping norm adaptation. Defaults to 0.2. " +"Andrew et al. recommends to set to 0.2." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:24 #: of msgid "" -"The stddev of the noise added to the count of updates currently below the " -"estimate. Andrew et al. recommends to set to `expected_num_records/20`" +"The stddev of the noise added to the count of updates currently below the" +" estimate. Andrew et al. recommends to set to `expected_num_records/20`" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:30 @@ -11234,8 +11546,8 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:34 #: of msgid "" -"Wrap the strategy with the `DifferentialPrivacyClientSideAdaptiveClipping` " -"wrapper:" +"Wrap the strategy with the " +"`DifferentialPrivacyClientSideAdaptiveClipping` wrapper:" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:40 @@ -11246,17 +11558,17 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\" +" \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\" +" \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -11270,33 +11582,33 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate `\\ " -"\\(server\\_round\\, parameters\\)" +":py:obj:`evaluate " +"`\\" +" \\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\" +" \\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideFixedClipping.rst:2 @@ -11313,22 +11625,16 @@ msgstr "" msgid "" "In comparison to `DifferentialPrivacyServerSideFixedClipping`, which " "performs clipping on the server-side, " -"`DifferentialPrivacyClientSideFixedClipping` expects clipping to happen on " -"the client-side, usually by using the built-in `fixedclipping_mod`." +"`DifferentialPrivacyClientSideFixedClipping` expects clipping to happen " +"on the client-side, usually by using the built-in `fixedclipping_mod`." msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:12 #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:5 #: of msgid "" -"The noise multiplier for the Gaussian mechanism for model updates. A value " -"of 1.0 or higher is recommended for strong privacy." -msgstr "" - -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 -#: of -msgid "The value of the clipping norm." +"The noise multiplier for the Gaussian mechanism for model updates. A " +"value of 1.0 or higher is recommended for strong privacy." msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:26 @@ -11346,17 +11652,17 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\" +" \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\" +" \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 @@ -11368,33 +11674,33 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate `\\ \\(server\\_round\\, " -"parameters\\)" +":py:obj:`evaluate " +"`\\" +" \\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\" +" \\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideAdaptiveClipping.rst:2 @@ -11404,8 +11710,9 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:17 #: of msgid "" -"The standard deviation of the noise added to the count of updates below the " -"estimate. Andrew et al. recommends to set to `expected_num_records/20`" +"The standard deviation of the noise added to the count of updates below " +"the estimate. Andrew et al. recommends to set to " +"`expected_num_records/20`" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:27 @@ -11418,49 +11725,49 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\" +" \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\" +" \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate `\\ " -"\\(server\\_round\\, parameters\\)" +":py:obj:`evaluate " +"`\\" +" \\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\" +" \\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideFixedClipping.rst:2 @@ -11470,23 +11777,24 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:19 #: of msgid "" -"Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping wrapper" +"Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping " +"wrapper" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\" +" \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\" +" \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 @@ -11498,33 +11806,33 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate `\\ \\(server\\_round\\, " -"parameters\\)" +":py:obj:`evaluate " +"`\\" +" \\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\" +" \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_fit:3 @@ -11539,15 +11847,17 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -11569,15 +11879,17 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -11590,26 +11902,29 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst:2 -#: ../../source/ref-changelog.md:905 +#: ../../source/ref-changelog.md:997 msgid "FedAdagrad" msgstr "" @@ -11658,26 +11973,28 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit `\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\" +" \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\" +" \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11688,20 +12005,23 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdam.rst:2 @@ -11720,8 +12040,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\," +" results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11732,8 +12053,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\," +" parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11750,19 +12072,22 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " +":py:obj:`num_fit_clients " +"`\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -11779,16 +12104,17 @@ msgstr "" #: of msgid "" "Fraction of clients used during training. In case `min_fit_clients` is " -"larger than `fraction_fit * available_clients`, `min_fit_clients` will still " -"be sampled. Defaults to 1.0." +"larger than `fraction_fit * available_clients`, `min_fit_clients` will " +"still be sampled. Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavg.FedAvg:9 flwr.server.strategy.fedprox.FedProx:41 #: of msgid "" -"Fraction of clients used during validation. In case `min_evaluate_clients` " -"is larger than `fraction_evaluate * available_clients`, " -"`min_evaluate_clients` will still be sampled. Defaults to 1.0." +"Fraction of clients used during validation. In case " +"`min_evaluate_clients` is larger than `fraction_evaluate * " +"available_clients`, `min_evaluate_clients` will still be sampled. " +"Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavg.FedAvg:33 of @@ -11797,8 +12123,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\, " +"results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11809,8 +12136,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\, " +"parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11827,20 +12155,22 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\" +" \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAvgAndroid.rst:2 @@ -11850,22 +12180,24 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " +":py:obj:`aggregate_fit " +"`\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" +":py:obj:`bytes_to_ndarray " +"`\\ \\(tensor\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -11876,14 +12208,16 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " +":py:obj:`configure_fit " +"`\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -11897,15 +12231,16 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" +":py:obj:`ndarray_to_bytes " +"`\\ \\(ndarray\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -11916,29 +12251,33 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`ndarrays_to_parameters `\\ \\(ndarrays\\)" +":py:obj:`ndarrays_to_parameters " +"`\\ " +"\\(ndarrays\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`parameters_to_ndarrays `\\ \\(parameters\\)" +":py:obj:`parameters_to_ndarrays " +"`\\ " +"\\(parameters\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -11957,7 +12296,8 @@ msgstr "" #: flwr.server.strategy.fedavgm.FedAvgM:25 of msgid "" -"Server-side learning rate used in server-side optimization. Defaults to 1.0." +"Server-side learning rate used in server-side optimization. Defaults to " +"1.0." msgstr "" #: flwr.server.strategy.fedavgm.FedAvgM:28 of @@ -11966,8 +12306,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\," +" results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11978,8 +12319,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\," +" parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -11996,19 +12338,22 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " +":py:obj:`num_fit_clients " +"`\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12018,8 +12363,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12035,8 +12381,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12053,19 +12400,22 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " +":py:obj:`num_fit_clients " +"`\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12083,8 +12433,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\, " +"results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12095,8 +12446,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\, " +"parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12113,20 +12465,22 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\" +" \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedProx.rst:2 @@ -12139,9 +12493,9 @@ msgstr "" #: flwr.server.strategy.fedprox.FedProx:5 of msgid "" -"The strategy in itself will not be different than FedAvg, the client needs " -"to be adjusted. A proximal term needs to be added to the loss function " -"during the training:" +"The strategy in itself will not be different than FedAvg, the client " +"needs to be adjusted. A proximal term needs to be added to the loss " +"function during the training:" msgstr "" #: flwr.server.strategy.fedprox.FedProx:9 of @@ -12174,14 +12528,15 @@ msgstr "" msgid "" "The weight of the proximal term used in the optimization. 0.0 makes this " "strategy equivalent to FedAvg, and the higher the coefficient, the more " -"regularization will be used (that is, the client parameters will need to be " -"closer to the server parameters during training)." +"regularization will be used (that is, the client parameters will need to " +"be closer to the server parameters during training)." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\," +" results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12192,8 +12547,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\," +" parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12210,19 +12566,22 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " +":py:obj:`num_fit_clients " +"`\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12244,13 +12603,15 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit `\\ " +":py:obj:`aggregate_fit " +"`\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -12261,13 +12622,15 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit `\\ " +":py:obj:`configure_fit " +"`\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -12279,20 +12642,23 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbBagging.rst:2 @@ -12302,8 +12668,9 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1 @@ -12317,7 +12684,8 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " +":py:obj:`aggregate_fit " +"`\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -12331,14 +12699,16 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " +":py:obj:`configure_fit " +"`\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -12352,22 +12722,25 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbCyclic.rst:2 @@ -12377,29 +12750,33 @@ msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit `\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\ \\(server\\_round\\," +" results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\ \\(server\\_round\\," +" parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 @@ -12412,22 +12789,25 @@ msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbNnAvg.rst:2 @@ -12437,31 +12817,36 @@ msgstr "" #: flwr.server.strategy.fedxgb_nn_avg.FedXgbNnAvg:5 of msgid "" "This strategy is deprecated, but a copy of it is available in Flower " -"Baselines: https://github.com/adap/flower/tree/main/baselines/hfedxgboost." +"Baselines: " +"https://github.com/adap/flower/tree/main/baselines/hfedxgboost." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit `\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit " +"`\\ \\(server\\_round\\, " +"results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit `\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit " +"`\\ \\(server\\_round\\, " +"parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12472,20 +12857,23 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedYogi.rst:2 @@ -12506,8 +12894,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\," +" results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12518,8 +12907,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\," +" parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12536,19 +12926,22 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " +":py:obj:`num_fit_clients " +"`\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12562,14 +12955,15 @@ msgstr "" #: flwr.server.strategy.krum.Krum:17 of msgid "" -"Number of clients to keep before averaging (MultiKrum). Defaults to 0, in " -"that case classical Krum is applied." +"Number of clients to keep before averaging (MultiKrum). Defaults to 0, in" +" that case classical Krum is applied." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\, " +"results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12585,8 +12979,9 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\, " +"parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12603,14 +12998,16 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -12625,8 +13022,9 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ \\(server\\_round\\," +" results\\, ...\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of @@ -12637,8 +13035,9 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ \\(server\\_round\\," +" parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of @@ -12655,19 +13054,22 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients " +"`\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\ " +":py:obj:`num_fit_clients " +"`\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -12678,8 +13080,9 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate " +"`\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1 @@ -12703,8 +13106,9 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate " +"`\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 @@ -12729,8 +13133,9 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" +":py:obj:`initialize_parameters " +"`\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 @@ -12740,18 +13145,17 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:5 of msgid "" -"Successful updates from the previously selected and configured clients. Each " -"pair of `(ClientProxy, FitRes` constitutes a successful update from one of " -"the previously selected clients. Not that not all previously selected " -"clients are necessarily included in this list: a client might drop out and " -"not submit a result. For each client that did not submit an update, there " -"should be an `Exception` in `failures`." +"Successful updates from the previously selected and configured clients. " +"Each pair of `(ClientProxy, FitRes` constitutes a successful update from " +"one of the previously selected clients. Not that not all previously " +"selected clients are necessarily included in this list: a client might " +"drop out and not submit a result. For each client that did not submit an " +"update, there should be an `Exception` in `failures`." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:13 #: flwr.server.strategy.strategy.Strategy.aggregate_fit:13 of -msgid "" -"Exceptions that occurred while the server was waiting for client updates." +msgid "Exceptions that occurred while the server was waiting for client updates." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:16 of @@ -12762,22 +13166,23 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_fit:5 of msgid "" -"Successful updates from the previously selected and configured clients. Each " -"pair of `(ClientProxy, FitRes)` constitutes a successful update from one of " -"the previously selected clients. Not that not all previously selected " -"clients are necessarily included in this list: a client might drop out and " -"not submit a result. For each client that did not submit an update, there " -"should be an `Exception` in `failures`." +"Successful updates from the previously selected and configured clients. " +"Each pair of `(ClientProxy, FitRes)` constitutes a successful update from" +" one of the previously selected clients. Not that not all previously " +"selected clients are necessarily included in this list: a client might " +"drop out and not submit a result. For each client that did not submit an " +"update, there should be an `Exception` in `failures`." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_fit:17 of msgid "" "**parameters** -- If parameters are returned, then the server will treat " -"these as the new global model parameters (i.e., it will replace the previous " -"parameters with the ones returned from this method). If `None` is returned " -"(e.g., because there were only failures and no viable results) then the " -"server will no update the previous model parameters, the updates received in " -"this round are discarded, and the global model parameters remain the same." +"these as the new global model parameters (i.e., it will replace the " +"previous parameters with the ones returned from this method). If `None` " +"is returned (e.g., because there were only failures and no viable " +"results) then the server will no update the previous model parameters, " +"the updates received in this round are discarded, and the global model " +"parameters remain the same." msgstr "" #: flwr.server.strategy.strategy.Strategy.evaluate:3 of @@ -12788,8 +13193,9 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.evaluate:11 of msgid "" -"**evaluation_result** -- The evaluation result, usually a Tuple containing " -"loss and a dictionary containing task-specific metrics (e.g., accuracy)." +"**evaluation_result** -- The evaluation result, usually a Tuple " +"containing loss and a dictionary containing task-specific metrics (e.g., " +"accuracy)." msgstr "" #: flwr.server.strategy.strategy.Strategy.initialize_parameters:6 of @@ -12847,17 +13253,17 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:3 #: of msgid "" -"The SecAgg+ protocol ensures the secure summation of integer vectors owned " -"by multiple parties, without accessing any individual integer vector. This " -"workflow allows the server to compute the weighted average of model " -"parameters across all clients, ensuring individual contributions remain " -"private. This is achieved by clients sending both, a weighting factor and a " -"weighted version of the locally updated parameters, both of which are masked " -"for privacy. Specifically, each client uploads \"[w, w * params]\" with " -"masks, where weighting factor 'w' is the number of examples ('num_examples') " -"and 'params' represents the model parameters ('parameters') from the " -"client's `FitRes`. The server then aggregates these contributions to compute " -"the weighted average of model parameters." +"The SecAgg+ protocol ensures the secure summation of integer vectors " +"owned by multiple parties, without accessing any individual integer " +"vector. This workflow allows the server to compute the weighted average " +"of model parameters across all clients, ensuring individual contributions" +" remain private. This is achieved by clients sending both, a weighting " +"factor and a weighted version of the locally updated parameters, both of " +"which are masked for privacy. Specifically, each client uploads \"[w, w *" +" params]\" with masks, where weighting factor 'w' is the number of " +"examples ('num_examples') and 'params' represents the model parameters " +"('parameters') from the client's `FitRes`. The server then aggregates " +"these contributions to compute the weighted average of model parameters." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:14 @@ -12894,39 +13300,39 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:22 #: of msgid "" -"Only the aggregated model parameters are exposed and passed to `Strategy." -"aggregate_fit`, ensuring individual data privacy." +"Only the aggregated model parameters are exposed and passed to " +"`Strategy.aggregate_fit`, ensuring individual data privacy." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:25 #: of msgid "" -"The number of shares into which each client's private key is split under the " -"SecAgg+ protocol. If specified as a float, it represents the proportion of " -"all selected clients, and the number of shares will be set dynamically in " -"the run time. A private key can be reconstructed from these shares, allowing " -"for the secure aggregation of model updates. Each client sends one share to " -"each of its neighbors while retaining one." +"The number of shares into which each client's private key is split under " +"the SecAgg+ protocol. If specified as a float, it represents the " +"proportion of all selected clients, and the number of shares will be set " +"dynamically in the run time. A private key can be reconstructed from " +"these shares, allowing for the secure aggregation of model updates. Each " +"client sends one share to each of its neighbors while retaining one." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:25 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:32 #: of msgid "" -"The minimum number of shares required to reconstruct a client's private key, " -"or, if specified as a float, it represents the proportion of the total " -"number of shares needed for reconstruction. This threshold ensures privacy " -"by allowing for the recovery of contributions from dropped clients during " -"aggregation, without compromising individual client data." +"The minimum number of shares required to reconstruct a client's private " +"key, or, if specified as a float, it represents the proportion of the " +"total number of shares needed for reconstruction. This threshold ensures " +"privacy by allowing for the recovery of contributions from dropped " +"clients during aggregation, without compromising individual client data." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:31 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:38 #: of msgid "" -"The maximum value of the weight that can be assigned to any single client's " -"update during the weighted average calculation on the server side, e.g., in " -"the FedAvg algorithm." +"The maximum value of the weight that can be assigned to any single " +"client's update during the weighted average calculation on the server " +"side, e.g., in the FedAvg algorithm." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:35 @@ -12934,8 +13340,8 @@ msgstr "" #: of msgid "" "The range within which model parameters are clipped before quantization. " -"This parameter ensures each model parameter is bounded within [-" -"clipping_range, clipping_range], facilitating quantization." +"This parameter ensures each model parameter is bounded within " +"[-clipping_range, clipping_range], facilitating quantization." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:39 @@ -12953,32 +13359,31 @@ msgstr "" #: of msgid "" "The range of values from which random mask entries are uniformly sampled " -"([0, modulus_range-1]). `modulus_range` must be less than 4294967296. Please " -"use 2**n values for `modulus_range` to prevent overflow issues." +"([0, modulus_range-1]). `modulus_range` must be less than 4294967296. " +"Please use 2**n values for `modulus_range` to prevent overflow issues." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:47 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:54 #: of msgid "" -"The timeout duration in seconds. If specified, the workflow will wait for " -"replies for this duration each time. If `None`, there is no time limit and " -"the workflow will wait until replies for all messages are received." +"The timeout duration in seconds. If specified, the workflow will wait for" +" replies for this duration each time. If `None`, there is no time limit " +"and the workflow will wait until replies for all messages are received." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:61 #: of msgid "" "Generally, higher `num_shares` means more robust to dropouts while " -"increasing the computational costs; higher `reconstruction_threshold` means " -"better privacy guarantees but less tolerance to dropouts." +"increasing the computational costs; higher `reconstruction_threshold` " +"means better privacy guarantees but less tolerance to dropouts." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:58 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:64 #: of -msgid "" -"Too large `max_weight` may compromise the precision of the quantization." +msgid "Too large `max_weight` may compromise the precision of the quantization." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:59 @@ -12991,34 +13396,35 @@ msgstr "" #: of msgid "" "When `num_shares` is a float, it is interpreted as the proportion of all " -"selected clients, and hence the number of shares will be determined in the " -"runtime. This allows for dynamic adjustment based on the total number of " -"participating clients." +"selected clients, and hence the number of shares will be determined in " +"the runtime. This allows for dynamic adjustment based on the total number" +" of participating clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:69 #: of msgid "" -"Similarly, when `reconstruction_threshold` is a float, it is interpreted as " -"the proportion of the number of shares needed for the reconstruction of a " -"private key. This feature enables flexibility in setting the security " -"threshold relative to the number of distributed shares." +"Similarly, when `reconstruction_threshold` is a float, it is interpreted " +"as the proportion of the number of shares needed for the reconstruction " +"of a private key. This feature enables flexibility in setting the " +"security threshold relative to the number of distributed shares." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:73 #: of msgid "" -"`num_shares`, `reconstruction_threshold`, and the quantization parameters " -"(`clipping_range`, `quantization_range`, `modulus_range`) play critical " -"roles in balancing privacy, robustness, and efficiency within the SecAgg+ " -"protocol." +"`num_shares`, `reconstruction_threshold`, and the quantization parameters" +" (`clipping_range`, `quantization_range`, `modulus_range`) play critical " +"roles in balancing privacy, robustness, and efficiency within the SecAgg+" +" protocol." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`collect_masked_vectors_stage `\\ \\(driver\\, ...\\)" +":py:obj:`collect_masked_vectors_stage " +"`\\" +" \\(driver\\, ...\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1 @@ -13030,8 +13436,9 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`setup_stage `\\ \\(driver\\, context\\, state\\)" +":py:obj:`setup_stage " +"`\\ \\(driver\\, " +"context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -13043,8 +13450,9 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`share_keys_stage `\\ \\(driver\\, context\\, state\\)" +":py:obj:`share_keys_stage " +"`\\ " +"\\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -13056,8 +13464,9 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`unmask_stage `\\ \\(driver\\, context\\, state\\)" +":py:obj:`unmask_stage " +"`\\ \\(driver\\, " +"context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -13072,50 +13481,51 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:1 of msgid "" -"Bases: :py:class:`~flwr.server.workflow.secure_aggregation." -"secaggplus_workflow.SecAggPlusWorkflow`" +"Bases: " +":py:class:`~flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow`" msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:3 of msgid "" -"The SecAgg protocol ensures the secure summation of integer vectors owned by " -"multiple parties, without accessing any individual integer vector. This " -"workflow allows the server to compute the weighted average of model " +"The SecAgg protocol ensures the secure summation of integer vectors owned" +" by multiple parties, without accessing any individual integer vector. " +"This workflow allows the server to compute the weighted average of model " "parameters across all clients, ensuring individual contributions remain " -"private. This is achieved by clients sending both, a weighting factor and a " -"weighted version of the locally updated parameters, both of which are masked " -"for privacy. Specifically, each client uploads \"[w, w * params]\" with " -"masks, where weighting factor 'w' is the number of examples ('num_examples') " -"and 'params' represents the model parameters ('parameters') from the " -"client's `FitRes`. The server then aggregates these contributions to compute " -"the weighted average of model parameters." +"private. This is achieved by clients sending both, a weighting factor and" +" a weighted version of the locally updated parameters, both of which are " +"masked for privacy. Specifically, each client uploads \"[w, w * params]\"" +" with masks, where weighting factor 'w' is the number of examples " +"('num_examples') and 'params' represents the model parameters " +"('parameters') from the client's `FitRes`. The server then aggregates " +"these contributions to compute the weighted average of model parameters." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:14 of msgid "" -"The protocol involves four main stages: - 'setup': Send SecAgg configuration " -"to clients and collect their public keys. - 'share keys': Broadcast public " -"keys among clients and collect encrypted secret" +"The protocol involves four main stages: - 'setup': Send SecAgg " +"configuration to clients and collect their public keys. - 'share keys': " +"Broadcast public keys among clients and collect encrypted secret" msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:54 of msgid "" -"Each client's private key is split into N shares under the SecAgg protocol, " -"where N is the number of selected clients." +"Each client's private key is split into N shares under the SecAgg " +"protocol, where N is the number of selected clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:56 of msgid "" -"Generally, higher `reconstruction_threshold` means better privacy guarantees " -"but less tolerance to dropouts." +"Generally, higher `reconstruction_threshold` means better privacy " +"guarantees but less tolerance to dropouts." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:60 of msgid "" "When `reconstruction_threshold` is a float, it is interpreted as the " "proportion of the number of all selected clients needed for the " -"reconstruction of a private key. This feature enables flexibility in setting " -"the security threshold relative to the number of selected clients." +"reconstruction of a private key. This feature enables flexibility in " +"setting the security threshold relative to the number of selected " +"clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:64 of @@ -13129,29 +13539,32 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`collect_masked_vectors_stage `\\ \\(driver\\, ...\\)" +":py:obj:`collect_masked_vectors_stage " +"`\\ " +"\\(driver\\, ...\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`setup_stage `\\ " -"\\(driver\\, context\\, state\\)" +":py:obj:`setup_stage `\\" +" \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`share_keys_stage `\\ \\(driver\\, context\\, state\\)" +":py:obj:`share_keys_stage " +"`\\ \\(driver\\, " +"context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`unmask_stage `\\ " -"\\(driver\\, context\\, state\\)" +":py:obj:`unmask_stage " +"`\\ \\(driver\\, " +"context\\, state\\)" msgstr "" #: ../../source/ref-api/flwr.simulation.rst:2 @@ -13160,8 +13573,8 @@ msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 msgid "" -":py:obj:`start_simulation `\\ \\(\\*\\, " -"client\\_fn\\[\\, ...\\]\\)" +":py:obj:`start_simulation `\\ \\(\\*\\," +" client\\_fn\\[\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 @@ -13192,14 +13605,15 @@ msgstr "" #: flwr.simulation.run_simulation.run_simulation:6 of msgid "" -"The `ClientApp` to be executed by each of the SuperNodes. It will receive " -"messages sent by the `ServerApp`." +"The `ClientApp` to be executed by each of the SuperNodes. It will receive" +" messages sent by the `ServerApp`." msgstr "" #: flwr.simulation.run_simulation.run_simulation:9 of msgid "" -"Number of nodes that run a ClientApp. They can be sampled by a Driver in the " -"ServerApp and receive a Message describing what the ClientApp should perform." +"Number of nodes that run a ClientApp. They can be sampled by a Driver in " +"the ServerApp and receive a Message describing what the ClientApp should " +"perform." msgstr "" #: flwr.simulation.run_simulation.run_simulation:13 of @@ -13208,26 +13622,26 @@ msgstr "" #: flwr.simulation.run_simulation.run_simulation:15 of msgid "" -"'A dictionary, e.g {\"\": , \"\": } to configure a " -"backend. Values supported in are those included by `flwr.common." -"typing.ConfigsRecordValues`." +"'A dictionary, e.g {\"\": , \"\": } to " +"configure a backend. Values supported in are those included by " +"`flwr.common.typing.ConfigsRecordValues`." msgstr "" #: flwr.simulation.run_simulation.run_simulation:19 of msgid "" -"A boolean to indicate whether to enable GPU growth on the main thread. This " -"is desirable if you make use of a TensorFlow model on your `ServerApp` while " -"having your `ClientApp` running on the same GPU. Without enabling this, you " -"might encounter an out-of-memory error because TensorFlow, by default, " -"allocates all GPU memory. Read more about how `tf.config.experimental." -"set_memory_growth()` works in the TensorFlow documentation: https://www." -"tensorflow.org/api/stable." +"A boolean to indicate whether to enable GPU growth on the main thread. " +"This is desirable if you make use of a TensorFlow model on your " +"`ServerApp` while having your `ClientApp` running on the same GPU. " +"Without enabling this, you might encounter an out-of-memory error because" +" TensorFlow, by default, allocates all GPU memory. Read more about how " +"`tf.config.experimental.set_memory_growth()` works in the TensorFlow " +"documentation: https://www.tensorflow.org/api/stable." msgstr "" #: flwr.simulation.run_simulation.run_simulation:26 of msgid "" -"When diabled, only INFO, WARNING and ERROR log messages will be shown. If " -"enabled, DEBUG-level logs will be displayed." +"When diabled, only INFO, WARNING and ERROR log messages will be shown. If" +" enabled, DEBUG-level logs will be displayed." msgstr "" #: ../../source/ref-api/flwr.simulation.start_simulation.rst:2 @@ -13236,15 +13650,15 @@ msgstr "" #: flwr.simulation.app.start_simulation:3 of msgid "" -"A function creating client instances. The function must take a single `str` " -"argument called `cid`. It should return a single client instance of type " -"Client. Note that the created client instances are ephemeral and will often " -"be destroyed after a single method invocation. Since client instances are " -"not long-lived, they should not attempt to carry state over method " -"invocations. Any state required by the instance (model, dataset, " +"A function creating client instances. The function must take a single " +"`str` argument called `cid`. It should return a single client instance of" +" type Client. Note that the created client instances are ephemeral and " +"will often be destroyed after a single method invocation. Since client " +"instances are not long-lived, they should not attempt to carry state over" +" method invocations. Any state required by the instance (model, dataset, " "hyperparameters, ...) should be (re-)created in either the call to " -"`client_fn` or the call to any of the client methods (e.g., load evaluation " -"data in the `evaluate` method itself)." +"`client_fn` or the call to any of the client methods (e.g., load " +"evaluation data in the `evaluate` method itself)." msgstr "" #: flwr.simulation.app.start_simulation:13 of @@ -13255,16 +13669,16 @@ msgstr "" #: flwr.simulation.app.start_simulation:16 of msgid "" -"List `client_id`s for each client. This is only required if `num_clients` is " -"not set. Setting both `num_clients` and `clients_ids` with " +"List `client_id`s for each client. This is only required if `num_clients`" +" is not set. Setting both `num_clients` and `clients_ids` with " "`len(clients_ids)` not equal to `num_clients` generates an error." msgstr "" #: flwr.simulation.app.start_simulation:20 of msgid "" -"CPU and GPU resources for a single client. Supported keys are `num_cpus` and " -"`num_gpus`. To understand the GPU utilization caused by `num_gpus`, as well " -"as using custom resources, please consult the Ray documentation." +"CPU and GPU resources for a single client. Supported keys are `num_cpus` " +"and `num_gpus`. To understand the GPU utilization caused by `num_gpus`, " +"as well as using custom resources, please consult the Ray documentation." msgstr "" #: flwr.simulation.app.start_simulation:25 of @@ -13275,16 +13689,16 @@ msgstr "" #: flwr.simulation.app.start_simulation:31 of msgid "" -"An implementation of the abstract base class `flwr.server.Strategy`. If no " -"strategy is provided, then `start_server` will use `flwr.server.strategy." -"FedAvg`." +"An implementation of the abstract base class `flwr.server.Strategy`. If " +"no strategy is provided, then `start_server` will use " +"`flwr.server.strategy.FedAvg`." msgstr "" #: flwr.simulation.app.start_simulation:35 of msgid "" -"An implementation of the abstract base class `flwr.server.ClientManager`. If " -"no implementation is provided, then `start_simulation` will use `flwr.server." -"client_manager.SimpleClientManager`." +"An implementation of the abstract base class `flwr.server.ClientManager`." +" If no implementation is provided, then `start_simulation` will use " +"`flwr.server.client_manager.SimpleClientManager`." msgstr "" #: flwr.simulation.app.start_simulation:39 of @@ -13293,7 +13707,8 @@ msgid "" "ray_init_args is None (the default), Ray will be initialized with the " "following default args: { \"ignore_reinit_error\": True, " "\"include_dashboard\": False } An empty dictionary can be used " -"(ray_init_args={}) to prevent any arguments from being passed to ray.init." +"(ray_init_args={}) to prevent any arguments from being passed to " +"ray.init." msgstr "" #: flwr.simulation.app.start_simulation:39 of @@ -13309,13 +13724,14 @@ msgstr "" #: flwr.simulation.app.start_simulation:45 of msgid "" -"An empty dictionary can be used (ray_init_args={}) to prevent any arguments " -"from being passed to ray.init." +"An empty dictionary can be used (ray_init_args={}) to prevent any " +"arguments from being passed to ray.init." msgstr "" #: flwr.simulation.app.start_simulation:48 of msgid "" -"Set to True to prevent `ray.shutdown()` in case `ray.is_initialized()=True`." +"Set to True to prevent `ray.shutdown()` in case " +"`ray.is_initialized()=True`." msgstr "" #: flwr.simulation.app.start_simulation:50 of @@ -13327,19 +13743,19 @@ msgstr "" #: flwr.simulation.app.start_simulation:54 of msgid "" -"If you want to create your own Actor classes, you might need to pass some " -"input argument. You can use this dictionary for such purpose." +"If you want to create your own Actor classes, you might need to pass some" +" input argument. You can use this dictionary for such purpose." msgstr "" #: flwr.simulation.app.start_simulation:57 of msgid "" -"(default: \"DEFAULT\") Optional string (\"DEFAULT\" or \"SPREAD\") for the " -"VCE to choose in which node the actor is placed. If you are an advanced user " -"needed more control you can use lower-level scheduling strategies to pin " -"actors to specific compute nodes (e.g. via NodeAffinitySchedulingStrategy). " -"Please note this is an advanced feature. For all details, please refer to " -"the Ray documentation: https://docs.ray.io/en/latest/ray-core/scheduling/" -"index.html" +"(default: \"DEFAULT\") Optional string (\"DEFAULT\" or \"SPREAD\") for " +"the VCE to choose in which node the actor is placed. If you are an " +"advanced user needed more control you can use lower-level scheduling " +"strategies to pin actors to specific compute nodes (e.g. via " +"NodeAffinitySchedulingStrategy). Please note this is an advanced feature." +" For all details, please refer to the Ray documentation: " +"https://docs.ray.io/en/latest/ray-core/scheduling/index.html" msgstr "" #: flwr.simulation.app.start_simulation:66 of @@ -13351,2887 +13767,3601 @@ msgid "Changelog" msgstr "" #: ../../source/ref-changelog.md:3 -msgid "Unreleased" -msgstr "" - -#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:19 -#: ../../source/ref-changelog.md:83 ../../source/ref-changelog.md:176 -#: ../../source/ref-changelog.md:276 ../../source/ref-changelog.md:360 -#: ../../source/ref-changelog.md:424 ../../source/ref-changelog.md:482 -#: ../../source/ref-changelog.md:551 ../../source/ref-changelog.md:680 -#: ../../source/ref-changelog.md:722 ../../source/ref-changelog.md:789 -#: ../../source/ref-changelog.md:855 ../../source/ref-changelog.md:900 -#: ../../source/ref-changelog.md:939 ../../source/ref-changelog.md:972 -#: ../../source/ref-changelog.md:1022 -msgid "What's new?" +msgid "v1.9.0 (2024-06-10)" msgstr "" -#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:71 -#: ../../source/ref-changelog.md:146 ../../source/ref-changelog.md:258 -#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:412 -#: ../../source/ref-changelog.md:470 ../../source/ref-changelog.md:539 -#: ../../source/ref-changelog.md:601 ../../source/ref-changelog.md:620 -#: ../../source/ref-changelog.md:776 ../../source/ref-changelog.md:847 -#: ../../source/ref-changelog.md:884 ../../source/ref-changelog.md:927 -msgid "Incompatible changes" +#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:105 +#: ../../source/ref-changelog.md:169 ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:362 ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:510 ../../source/ref-changelog.md:568 +#: ../../source/ref-changelog.md:637 ../../source/ref-changelog.md:706 +msgid "Thanks to our contributors" msgstr "" -#: ../../source/ref-changelog.md:9 ../../source/ref-changelog.md:73 -#: ../../source/ref-changelog.md:350 ../../source/ref-changelog.md:414 -#: ../../source/ref-changelog.md:472 ../../source/ref-changelog.md:541 -#: ../../source/ref-changelog.md:603 -msgid "None" +#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:107 +#: ../../source/ref-changelog.md:171 ../../source/ref-changelog.md:264 +#: ../../source/ref-changelog.md:364 ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:512 ../../source/ref-changelog.md:570 +msgid "" +"We would like to give our special thanks to all the contributors who made" +" the new version of Flower possible (in `git shortlog` order):" msgstr "" -#: ../../source/ref-changelog.md:11 -msgid "v1.8.0 (2024-04-03)" +#: ../../source/ref-changelog.md:9 +msgid "" +"`Adam Narozniak`, `Charles Beauville`, `Chong Shen Ng`, `Daniel J. " +"Beutel`, `Daniel Nata Nugraha`, `Heng Pan`, `Javier`, `Mahdi Beitollahi`," +" `Robert Steiner`, `Taner Topal`, `Yan Gao`, `bapic`, `mohammadnaseri` " msgstr "" -#: ../../source/ref-changelog.md:13 ../../source/ref-changelog.md:77 -#: ../../source/ref-changelog.md:170 ../../source/ref-changelog.md:270 -#: ../../source/ref-changelog.md:354 ../../source/ref-changelog.md:418 -#: ../../source/ref-changelog.md:476 ../../source/ref-changelog.md:545 -#: ../../source/ref-changelog.md:614 -msgid "Thanks to our contributors" +#: ../../source/ref-changelog.md:11 ../../source/ref-changelog.md:111 +#: ../../source/ref-changelog.md:175 ../../source/ref-changelog.md:268 +#: ../../source/ref-changelog.md:368 ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:516 ../../source/ref-changelog.md:574 +#: ../../source/ref-changelog.md:643 ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:814 ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:947 ../../source/ref-changelog.md:992 +#: ../../source/ref-changelog.md:1031 ../../source/ref-changelog.md:1064 +#: ../../source/ref-changelog.md:1114 +msgid "What's new?" msgstr "" -#: ../../source/ref-changelog.md:15 ../../source/ref-changelog.md:79 -#: ../../source/ref-changelog.md:172 ../../source/ref-changelog.md:272 -#: ../../source/ref-changelog.md:356 ../../source/ref-changelog.md:420 -#: ../../source/ref-changelog.md:478 +#: ../../source/ref-changelog.md:13 +msgid "" +"**Introduce built-in authentication (preview)** " +"([#2946](https://github.com/adap/flower/pull/2946), " +"[#3388](https://github.com/adap/flower/pull/3388), " +"[#2948](https://github.com/adap/flower/pull/2948), " +"[#2917](https://github.com/adap/flower/pull/2917), " +"[#3386](https://github.com/adap/flower/pull/3386), " +"[#3308](https://github.com/adap/flower/pull/3308), " +"[#3001](https://github.com/adap/flower/pull/3001), " +"[#3409](https://github.com/adap/flower/pull/3409), " +"[#2999](https://github.com/adap/flower/pull/2999), " +"[#2979](https://github.com/adap/flower/pull/2979), " +"[#3389](https://github.com/adap/flower/pull/3389), " +"[#3503](https://github.com/adap/flower/pull/3503), " +"[#3366](https://github.com/adap/flower/pull/3366), " +"[#3357](https://github.com/adap/flower/pull/3357))" +msgstr "" + +#: ../../source/ref-changelog.md:15 msgid "" -"We would like to give our special thanks to all the contributors who made " -"the new version of Flower possible (in `git shortlog` order):" +"Flower 1.9 introduces the first build-in version of client node " +"authentication. In previous releases, users often wrote glue code to " +"connect Flower to external authentication systems. With this release, the" +" SuperLink can authenticate SuperNodes using a built-in authentication " +"system. A new [how-to guide](https://flower.ai/docs/framework/how-to-" +"authenticate-supernodes.html) and a new [code " +"example](https://github.com/adap/flower/tree/main/examples/flower-" +"authentication) help you to get started." msgstr "" #: ../../source/ref-changelog.md:17 msgid "" -"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " -"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear Ashimine`, " -"`Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, `Sebastian van der " -"Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, `tabdar-khan` " +"This is the first preview release of the Flower-native authentication " +"system. Many additional features are on the roadmap for upcoming Flower " +"releases - stay tuned." +msgstr "" + +#: ../../source/ref-changelog.md:19 +msgid "" +"**Introduce end-to-end Docker support** " +"([#3483](https://github.com/adap/flower/pull/3483), " +"[#3266](https://github.com/adap/flower/pull/3266), " +"[#3390](https://github.com/adap/flower/pull/3390), " +"[#3283](https://github.com/adap/flower/pull/3283), " +"[#3285](https://github.com/adap/flower/pull/3285), " +"[#3391](https://github.com/adap/flower/pull/3391), " +"[#3403](https://github.com/adap/flower/pull/3403), " +"[#3458](https://github.com/adap/flower/pull/3458), " +"[#3533](https://github.com/adap/flower/pull/3533), " +"[#3453](https://github.com/adap/flower/pull/3453), " +"[#3486](https://github.com/adap/flower/pull/3486), " +"[#3290](https://github.com/adap/flower/pull/3290))" msgstr "" #: ../../source/ref-changelog.md:21 msgid "" -"**Introduce Flower Next high-level API (stable)** ([#3002](https://github." -"com/adap/flower/pull/3002), [#2934](https://github.com/adap/flower/" -"pull/2934), [#2958](https://github.com/adap/flower/pull/2958), [#3173]" -"(https://github.com/adap/flower/pull/3173), [#3174](https://github.com/adap/" -"flower/pull/3174), [#2923](https://github.com/adap/flower/pull/2923), [#2691]" -"(https://github.com/adap/flower/pull/2691), [#3079](https://github.com/adap/" -"flower/pull/3079), [#2961](https://github.com/adap/flower/pull/2961), [#2924]" -"(https://github.com/adap/flower/pull/2924), [#3166](https://github.com/adap/" -"flower/pull/3166), [#3031](https://github.com/adap/flower/pull/3031), [#3057]" -"(https://github.com/adap/flower/pull/3057), [#3000](https://github.com/adap/" -"flower/pull/3000), [#3113](https://github.com/adap/flower/pull/3113), [#2957]" -"(https://github.com/adap/flower/pull/2957), [#3183](https://github.com/adap/" -"flower/pull/3183), [#3180](https://github.com/adap/flower/pull/3180), [#3035]" -"(https://github.com/adap/flower/pull/3035), [#3189](https://github.com/adap/" -"flower/pull/3189), [#3185](https://github.com/adap/flower/pull/3185), [#3190]" -"(https://github.com/adap/flower/pull/3190), [#3191](https://github.com/adap/" -"flower/pull/3191), [#3195](https://github.com/adap/flower/pull/3195), [#3197]" -"(https://github.com/adap/flower/pull/3197))" +"Full Flower Next Docker support is here! With the release of Flower 1.9, " +"Flower provides stable Docker images for the Flower SuperLink, the Flower" +" SuperNode, and the Flower `ServerApp`. This set of images enables you to" +" run all Flower components in Docker. Check out the new [how-to " +"guide](https://flower.ai/docs/framework/how-to-run-flower-using-" +"docker.html) to get stated." msgstr "" #: ../../source/ref-changelog.md:23 msgid "" -"The Flower Next high-level API is stable! Flower Next is the future of " -"Flower - all new features (like Flower Mods) will be built on top of it. You " -"can start to migrate your existing projects to Flower Next by using " -"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or `quickstart-" -"tensorflow`, a detailed migration guide will follow shortly). Flower Next " -"allows you to run multiple projects concurrently (we call this multi-run) " -"and execute the same project in either simulation environments or deployment " -"environments without having to change a single line of code. The best part? " -"It's fully compatible with existing Flower projects that use `Strategy`, " -"`NumPyClient` & co." +"**Re-architect Flower Next simulation engine** " +"([#3307](https://github.com/adap/flower/pull/3307), " +"[#3355](https://github.com/adap/flower/pull/3355), " +"[#3272](https://github.com/adap/flower/pull/3272), " +"[#3273](https://github.com/adap/flower/pull/3273), " +"[#3417](https://github.com/adap/flower/pull/3417), " +"[#3281](https://github.com/adap/flower/pull/3281), " +"[#3343](https://github.com/adap/flower/pull/3343), " +"[#3326](https://github.com/adap/flower/pull/3326))" msgstr "" #: ../../source/ref-changelog.md:25 msgid "" -"**Introduce Flower Next low-level API (preview)** ([#3062](https://github." -"com/adap/flower/pull/3062), [#3034](https://github.com/adap/flower/" -"pull/3034), [#3069](https://github.com/adap/flower/pull/3069))" +"Flower Next simulations now use a new in-memory `Driver` that improves " +"the reliability of simulations, especially in notebook environments. This" +" is a significant step towards a complete overhaul of the Flower Next " +"simulation architecture." msgstr "" #: ../../source/ref-changelog.md:27 msgid "" -"In addition to the Flower Next *high-level* API that uses `Strategy`, " -"`NumPyClient` & co, Flower 1.8 also comes with a preview version of the new " -"Flower Next *low-level* API. The low-level API allows for granular control " -"of every aspect of the learning process by sending/receiving individual " -"messages to/from client nodes. The new `ServerApp` supports registering a " -"custom `main` function that allows writing custom training loops for methods " -"like async FL, cyclic training, or federated analytics. The new `ClientApp` " -"supports registering `train`, `evaluate` and `query` functions that can " -"access the raw message received from the `ServerApp`. New abstractions like " -"`RecordSet`, `Message` and `Context` further enable sending multiple models, " -"multiple sets of config values and metrics, stateful computations on the " -"client node and implementations of custom SMPC protocols, to name just a few." +"**Upgrade simulation engine** " +"([#3354](https://github.com/adap/flower/pull/3354), " +"[#3378](https://github.com/adap/flower/pull/3378), " +"[#3262](https://github.com/adap/flower/pull/3262), " +"[#3435](https://github.com/adap/flower/pull/3435), " +"[#3501](https://github.com/adap/flower/pull/3501), " +"[#3482](https://github.com/adap/flower/pull/3482), " +"[#3494](https://github.com/adap/flower/pull/3494))" msgstr "" #: ../../source/ref-changelog.md:29 msgid "" -"**Introduce Flower Mods (preview)** ([#3054](https://github.com/adap/flower/" -"pull/3054), [#2911](https://github.com/adap/flower/pull/2911), [#3083]" -"(https://github.com/adap/flower/pull/3083))" +"The Flower Next simulation engine comes with improved and configurable " +"logging. The Ray-based simulation backend in Flower 1.9 was updated to " +"use Ray 2.10." msgstr "" #: ../../source/ref-changelog.md:31 msgid "" -"Flower Modifiers (we call them Mods) can intercept messages and analyze, " -"edit or handle them directly. Mods can be used to develop pluggable modules " -"that work across different projects. Flower 1.8 already includes mods to log " -"the size of a message, the number of parameters sent over the network, " -"differential privacy with fixed clipping and adaptive clipping, local " -"differential privacy and secure aggregation protocols SecAgg and SecAgg+. " -"The Flower Mods API is released as a preview, but researchers can already " -"use it to experiment with arbirtrary SMPC protocols." +"**Introduce FedPFT baseline** " +"([#3268](https://github.com/adap/flower/pull/3268))" msgstr "" #: ../../source/ref-changelog.md:33 msgid "" -"**Fine-tune LLMs with LLM FlowerTune** ([#3029](https://github.com/adap/" -"flower/pull/3029), [#3089](https://github.com/adap/flower/pull/3089), [#3092]" -"(https://github.com/adap/flower/pull/3092), [#3100](https://github.com/adap/" -"flower/pull/3100), [#3114](https://github.com/adap/flower/pull/3114), [#3162]" -"(https://github.com/adap/flower/pull/3162), [#3172](https://github.com/adap/" -"flower/pull/3172))" +"FedPFT allows you to perform one-shot Federated Learning by leveraging " +"widely available foundational models, dramatically reducing communication" +" costs while delivering high performing models. This is work led by Mahdi" +" Beitollahi from Huawei Noah's Ark Lab (Montreal, Canada). Read all the " +"details in their paper: \"Parametric Feature Transfer: One-shot Federated" +" Learning with Foundation Models\" " +"([arxiv](https://arxiv.org/abs/2402.01862))" msgstr "" #: ../../source/ref-changelog.md:35 msgid "" -"We are introducing LLM FlowerTune, an introductory example that demonstrates " -"federated LLM fine-tuning of pre-trained Llama2 models on the Alpaca-GPT4 " -"dataset. The example is built to be easily adapted to use different models " -"and/or datasets. Read our blog post [LLM FlowerTune: Federated LLM Fine-" -"tuning with Flower](https://flower.ai/blog/2024-03-14-llm-flowertune-" -"federated-llm-finetuning-with-flower/) for more details." +"**Launch additional** `flwr new` **templates for Apple MLX, Hugging Face " +"Transformers, scikit-learn and TensorFlow** " +"([#3291](https://github.com/adap/flower/pull/3291), " +"[#3139](https://github.com/adap/flower/pull/3139), " +"[#3284](https://github.com/adap/flower/pull/3284), " +"[#3251](https://github.com/adap/flower/pull/3251), " +"[#3376](https://github.com/adap/flower/pull/3376), " +"[#3287](https://github.com/adap/flower/pull/3287))" msgstr "" #: ../../source/ref-changelog.md:37 msgid "" -"**Introduce built-in Differential Privacy (preview)** ([#2798](https://" -"github.com/adap/flower/pull/2798), [#2959](https://github.com/adap/flower/" -"pull/2959), [#3038](https://github.com/adap/flower/pull/3038), [#3147]" -"(https://github.com/adap/flower/pull/3147), [#2909](https://github.com/adap/" -"flower/pull/2909), [#2893](https://github.com/adap/flower/pull/2893), [#2892]" -"(https://github.com/adap/flower/pull/2892), [#3039](https://github.com/adap/" -"flower/pull/3039), [#3074](https://github.com/adap/flower/pull/3074))" +"The `flwr` CLI's `flwr new` command is starting to become everone's " +"favorite way of creating new Flower projects. This release introduces " +"additional `flwr new` templates for Apple MLX, Hugging Face Transformers," +" scikit-learn and TensorFlow. In addition to that, existing templates " +"also received updates." msgstr "" #: ../../source/ref-changelog.md:39 msgid "" -"Built-in Differential Privacy is here! Flower supports both central and " -"local differential privacy (DP). Central DP can be configured with either " -"fixed or adaptive clipping. The clipping can happen either on the server-" -"side or the client-side. Local DP does both clipping and noising on the " -"client-side. A new documentation page [explains Differential Privacy " -"approaches](https://flower.ai/docs/framework/explanation-differential-" -"privacy.html) and a new how-to guide describes [how to use the new " -"Differential Privacy components](https://flower.ai/docs/framework/how-to-use-" -"differential-privacy.html) in Flower." +"**Refine** `RecordSet` **API** " +"([#3209](https://github.com/adap/flower/pull/3209), " +"[#3331](https://github.com/adap/flower/pull/3331), " +"[#3334](https://github.com/adap/flower/pull/3334), " +"[#3335](https://github.com/adap/flower/pull/3335), " +"[#3375](https://github.com/adap/flower/pull/3375), " +"[#3368](https://github.com/adap/flower/pull/3368))" msgstr "" #: ../../source/ref-changelog.md:41 msgid "" -"**Introduce built-in Secure Aggregation (preview)** ([#3120](https://github." -"com/adap/flower/pull/3120), [#3110](https://github.com/adap/flower/" -"pull/3110), [#3108](https://github.com/adap/flower/pull/3108))" +"`RecordSet` is part of the Flower Next low-level API preview release. In " +"Flower 1.9, `RecordSet` received a number of usability improvements that " +"make it easier to build `RecordSet`-based `ServerApp`s and `ClientApp`s." msgstr "" #: ../../source/ref-changelog.md:43 msgid "" -"Built-in Secure Aggregation is here! Flower now supports different secure " -"aggregation protocols out-of-the-box. The best part? You can add secure " -"aggregation to your Flower projects with only a few lines of code. In this " -"initial release, we inlcude support for SecAgg and SecAgg+, but more " -"protocols will be implemented shortly. We'll also add detailed docs that " -"explain secure aggregation and how to use it in Flower. You can already " -"check out the new code example that shows how to use Flower to easily " -"combine Federated Learning, Differential Privacy and Secure Aggregation in " -"the same project." +"**Beautify logging** ([#3379](https://github.com/adap/flower/pull/3379), " +"[#3430](https://github.com/adap/flower/pull/3430), " +"[#3461](https://github.com/adap/flower/pull/3461), " +"[#3360](https://github.com/adap/flower/pull/3360), " +"[#3433](https://github.com/adap/flower/pull/3433))" msgstr "" #: ../../source/ref-changelog.md:45 msgid "" -"**Introduce** `flwr` **CLI (preview)** ([#2942](https://github.com/adap/" -"flower/pull/2942), [#3055](https://github.com/adap/flower/pull/3055), [#3111]" -"(https://github.com/adap/flower/pull/3111), [#3130](https://github.com/adap/" -"flower/pull/3130), [#3136](https://github.com/adap/flower/pull/3136), [#3094]" -"(https://github.com/adap/flower/pull/3094), [#3059](https://github.com/adap/" -"flower/pull/3059), [#3049](https://github.com/adap/flower/pull/3049), [#3142]" -"(https://github.com/adap/flower/pull/3142))" +"Logs received a substantial update. Not only are logs now much nicer to " +"look at, but they are also more configurable." msgstr "" #: ../../source/ref-changelog.md:47 msgid "" -"A new `flwr` CLI command allows creating new Flower projects (`flwr new`) " -"and then running them using the Simulation Engine (`flwr run`)." +"**Improve reliability** " +"([#3564](https://github.com/adap/flower/pull/3564), " +"[#3561](https://github.com/adap/flower/pull/3561), " +"[#3566](https://github.com/adap/flower/pull/3566), " +"[#3462](https://github.com/adap/flower/pull/3462), " +"[#3225](https://github.com/adap/flower/pull/3225), " +"[#3514](https://github.com/adap/flower/pull/3514), " +"[#3535](https://github.com/adap/flower/pull/3535), " +"[#3372](https://github.com/adap/flower/pull/3372))" msgstr "" #: ../../source/ref-changelog.md:49 msgid "" -"**Introduce Flower Next Simulation Engine** ([#3024](https://github.com/adap/" -"flower/pull/3024), [#3061](https://github.com/adap/flower/pull/3061), [#2997]" -"(https://github.com/adap/flower/pull/2997), [#2783](https://github.com/adap/" -"flower/pull/2783), [#3184](https://github.com/adap/flower/pull/3184), [#3075]" -"(https://github.com/adap/flower/pull/3075), [#3047](https://github.com/adap/" -"flower/pull/3047), [#2998](https://github.com/adap/flower/pull/2998), [#3009]" -"(https://github.com/adap/flower/pull/3009), [#3008](https://github.com/adap/" -"flower/pull/3008))" +"Flower 1.9 includes reliability improvements across many parts of the " +"system. One example is a much improved SuperNode shutdown procedure." msgstr "" #: ../../source/ref-changelog.md:51 msgid "" -"The Flower Simulation Engine can now run Flower Next projects. For notebook " -"environments, there's also a new `run_simulation` function that can run " -"`ServerApp` and `ClientApp`." +"**Update Swift and C++ SDKs** " +"([#3321](https://github.com/adap/flower/pull/3321), " +"[#2763](https://github.com/adap/flower/pull/2763))" msgstr "" #: ../../source/ref-changelog.md:53 msgid "" -"**Handle SuperNode connection errors** ([#2969](https://github.com/adap/" -"flower/pull/2969))" +"In the C++ SDK, communication-related code is now separate from main " +"client logic. A new abstract class `Communicator` has been introduced " +"alongside a gRPC implementation of it." msgstr "" #: ../../source/ref-changelog.md:55 msgid "" -"A SuperNode will now try to reconnect indefinitely to the SuperLink in case " -"of connection errors. The arguments `--max-retries` and `--max-wait-time` " -"can now be passed to the `flower-client-app` command. `--max-retries` will " -"define the number of tentatives the client should make before it gives up " -"trying to reconnect to the SuperLink, and, `--max-wait-time` defines the " -"time before the SuperNode gives up trying to reconnect to the SuperLink." +"**Improve testing, tooling and CI/CD infrastructure** " +"([#3294](https://github.com/adap/flower/pull/3294), " +"[#3282](https://github.com/adap/flower/pull/3282), " +"[#3311](https://github.com/adap/flower/pull/3311), " +"[#2878](https://github.com/adap/flower/pull/2878), " +"[#3333](https://github.com/adap/flower/pull/3333), " +"[#3255](https://github.com/adap/flower/pull/3255), " +"[#3349](https://github.com/adap/flower/pull/3349), " +"[#3400](https://github.com/adap/flower/pull/3400), " +"[#3401](https://github.com/adap/flower/pull/3401), " +"[#3399](https://github.com/adap/flower/pull/3399), " +"[#3346](https://github.com/adap/flower/pull/3346), " +"[#3398](https://github.com/adap/flower/pull/3398), " +"[#3397](https://github.com/adap/flower/pull/3397), " +"[#3347](https://github.com/adap/flower/pull/3347), " +"[#3502](https://github.com/adap/flower/pull/3502), " +"[#3387](https://github.com/adap/flower/pull/3387), " +"[#3542](https://github.com/adap/flower/pull/3542), " +"[#3396](https://github.com/adap/flower/pull/3396), " +"[#3496](https://github.com/adap/flower/pull/3496), " +"[#3465](https://github.com/adap/flower/pull/3465), " +"[#3473](https://github.com/adap/flower/pull/3473), " +"[#3484](https://github.com/adap/flower/pull/3484), " +"[#3521](https://github.com/adap/flower/pull/3521), " +"[#3363](https://github.com/adap/flower/pull/3363), " +"[#3497](https://github.com/adap/flower/pull/3497), " +"[#3464](https://github.com/adap/flower/pull/3464), " +"[#3495](https://github.com/adap/flower/pull/3495), " +"[#3478](https://github.com/adap/flower/pull/3478), " +"[#3271](https://github.com/adap/flower/pull/3271))" msgstr "" #: ../../source/ref-changelog.md:57 msgid "" -"**General updates to Flower Baselines** ([#2904](https://github.com/adap/" -"flower/pull/2904), [#2482](https://github.com/adap/flower/pull/2482), [#2985]" -"(https://github.com/adap/flower/pull/2985), [#2968](https://github.com/adap/" -"flower/pull/2968))" +"As always, the Flower tooling, testing, and CI/CD infrastructure has " +"received many updates." msgstr "" #: ../../source/ref-changelog.md:59 msgid "" -"There's a new [FedStar](https://flower.ai/docs/baselines/fedstar.html) " -"baseline. Several other baselined have been updated as well." +"**Improve documentation** " +"([#3530](https://github.com/adap/flower/pull/3530), " +"[#3539](https://github.com/adap/flower/pull/3539), " +"[#3425](https://github.com/adap/flower/pull/3425), " +"[#3520](https://github.com/adap/flower/pull/3520), " +"[#3286](https://github.com/adap/flower/pull/3286), " +"[#3516](https://github.com/adap/flower/pull/3516), " +"[#3523](https://github.com/adap/flower/pull/3523), " +"[#3545](https://github.com/adap/flower/pull/3545), " +"[#3498](https://github.com/adap/flower/pull/3498), " +"[#3439](https://github.com/adap/flower/pull/3439), " +"[#3440](https://github.com/adap/flower/pull/3440), " +"[#3382](https://github.com/adap/flower/pull/3382), " +"[#3559](https://github.com/adap/flower/pull/3559), " +"[#3432](https://github.com/adap/flower/pull/3432), " +"[#3278](https://github.com/adap/flower/pull/3278), " +"[#3371](https://github.com/adap/flower/pull/3371), " +"[#3519](https://github.com/adap/flower/pull/3519), " +"[#3267](https://github.com/adap/flower/pull/3267), " +"[#3204](https://github.com/adap/flower/pull/3204), " +"[#3274](https://github.com/adap/flower/pull/3274))" msgstr "" #: ../../source/ref-changelog.md:61 msgid "" -"**Improve documentation and translations** ([#3050](https://github.com/adap/" -"flower/pull/3050), [#3044](https://github.com/adap/flower/pull/3044), [#3043]" -"(https://github.com/adap/flower/pull/3043), [#2986](https://github.com/adap/" -"flower/pull/2986), [#3041](https://github.com/adap/flower/pull/3041), [#3046]" -"(https://github.com/adap/flower/pull/3046), [#3042](https://github.com/adap/" -"flower/pull/3042), [#2978](https://github.com/adap/flower/pull/2978), [#2952]" -"(https://github.com/adap/flower/pull/2952), [#3167](https://github.com/adap/" -"flower/pull/3167), [#2953](https://github.com/adap/flower/pull/2953), [#3045]" -"(https://github.com/adap/flower/pull/3045), [#2654](https://github.com/adap/" -"flower/pull/2654), [#3082](https://github.com/adap/flower/pull/3082), [#2990]" -"(https://github.com/adap/flower/pull/2990), [#2989](https://github.com/adap/" -"flower/pull/2989))" +"As always, the Flower documentation has received many updates. Notable " +"new pages include:" msgstr "" #: ../../source/ref-changelog.md:63 msgid "" -"As usual, we merged many smaller and larger improvements to the " -"documentation. A special thank you goes to [Sebastian van der Voort](https://" -"github.com/svdvoort) for landing a big documentation PR!" +"[How-to upgrate to Flower Next (Flower Next migration " +"guide)](https://flower.ai/docs/framework/how-to-upgrade-to-flower-" +"next.html)" msgstr "" #: ../../source/ref-changelog.md:65 msgid "" -"**General updates to Flower Examples** ([3134](https://github.com/adap/" -"flower/pull/3134), [2996](https://github.com/adap/flower/pull/2996), [2930]" -"(https://github.com/adap/flower/pull/2930), [2967](https://github.com/adap/" -"flower/pull/2967), [2467](https://github.com/adap/flower/pull/2467), [2910]" -"(https://github.com/adap/flower/pull/2910), [#2918](https://github.com/adap/" -"flower/pull/2918), [#2773](https://github.com/adap/flower/pull/2773), [#3063]" -"(https://github.com/adap/flower/pull/3063), [#3116](https://github.com/adap/" -"flower/pull/3116), [#3117](https://github.com/adap/flower/pull/3117))" +"[How-to run Flower using Docker](https://flower.ai/docs/framework/how-to-" +"run-flower-using-docker.html)" msgstr "" #: ../../source/ref-changelog.md:67 msgid "" -"Two new examples show federated training of a Vision Transformer (ViT) and " -"federated learning in a medical context using the popular MONAI library. " -"`quickstart-pytorch` and `quickstart-tensorflow` demonstrate the new Flower " -"Next `ServerApp` and `ClientApp`. Many other examples received considerable " -"updates as well." +"[Flower Mods reference](https://flower.ai/docs/framework/ref-" +"api/flwr.client.mod.html#module-flwr.client.mod)" msgstr "" #: ../../source/ref-changelog.md:69 msgid "" -"**General improvements** ([#3171](https://github.com/adap/flower/pull/3171), " -"[3099](https://github.com/adap/flower/pull/3099), [3003](https://github.com/" -"adap/flower/pull/3003), [3145](https://github.com/adap/flower/pull/3145), " -"[3017](https://github.com/adap/flower/pull/3017), [3085](https://github.com/" -"adap/flower/pull/3085), [3012](https://github.com/adap/flower/pull/3012), " -"[3119](https://github.com/adap/flower/pull/3119), [2991](https://github.com/" -"adap/flower/pull/2991), [2970](https://github.com/adap/flower/pull/2970), " -"[2980](https://github.com/adap/flower/pull/2980), [3086](https://github.com/" -"adap/flower/pull/3086), [2932](https://github.com/adap/flower/pull/2932), " -"[2928](https://github.com/adap/flower/pull/2928), [2941](https://github.com/" -"adap/flower/pull/2941), [2933](https://github.com/adap/flower/pull/2933), " -"[3181](https://github.com/adap/flower/pull/3181), [2973](https://github.com/" -"adap/flower/pull/2973), [2992](https://github.com/adap/flower/pull/2992), " -"[2915](https://github.com/adap/flower/pull/2915), [3040](https://github.com/" -"adap/flower/pull/3040), [3022](https://github.com/adap/flower/pull/3022), " -"[3032](https://github.com/adap/flower/pull/3032), [2902](https://github.com/" -"adap/flower/pull/2902), [2931](https://github.com/adap/flower/pull/2931), " -"[3005](https://github.com/adap/flower/pull/3005), [3132](https://github.com/" -"adap/flower/pull/3132), [3115](https://github.com/adap/flower/pull/3115), " -"[2944](https://github.com/adap/flower/pull/2944), [3064](https://github.com/" -"adap/flower/pull/3064), [3106](https://github.com/adap/flower/pull/3106), " -"[2974](https://github.com/adap/flower/pull/2974), [3178](https://github.com/" -"adap/flower/pull/3178), [2993](https://github.com/adap/flower/pull/2993), " -"[3186](https://github.com/adap/flower/pull/3186), [3091](https://github.com/" -"adap/flower/pull/3091), [3125](https://github.com/adap/flower/pull/3125), " -"[3093](https://github.com/adap/flower/pull/3093), [3013](https://github.com/" -"adap/flower/pull/3013), [3033](https://github.com/adap/flower/pull/3033), " -"[3133](https://github.com/adap/flower/pull/3133), [3068](https://github.com/" -"adap/flower/pull/3068), [2916](https://github.com/adap/flower/pull/2916), " -"[2975](https://github.com/adap/flower/pull/2975), [2984](https://github.com/" -"adap/flower/pull/2984), [2846](https://github.com/adap/flower/pull/2846), " -"[3077](https://github.com/adap/flower/pull/3077), [3143](https://github.com/" -"adap/flower/pull/3143), [2921](https://github.com/adap/flower/pull/2921), " -"[3101](https://github.com/adap/flower/pull/3101), [2927](https://github.com/" -"adap/flower/pull/2927), [2995](https://github.com/adap/flower/pull/2995), " -"[2972](https://github.com/adap/flower/pull/2972), [2912](https://github.com/" -"adap/flower/pull/2912), [3065](https://github.com/adap/flower/pull/3065), " -"[3028](https://github.com/adap/flower/pull/3028), [2922](https://github.com/" -"adap/flower/pull/2922), [2982](https://github.com/adap/flower/pull/2982), " -"[2914](https://github.com/adap/flower/pull/2914), [3179](https://github.com/" -"adap/flower/pull/3179), [3080](https://github.com/adap/flower/pull/3080), " -"[2994](https://github.com/adap/flower/pull/2994), [3187](https://github.com/" -"adap/flower/pull/3187), [2926](https://github.com/adap/flower/pull/2926), " -"[3018](https://github.com/adap/flower/pull/3018), [3144](https://github.com/" -"adap/flower/pull/3144), [3011](https://github.com/adap/flower/pull/3011), " -"[#3152](https://github.com/adap/flower/pull/3152), [#2836](https://github." -"com/adap/flower/pull/2836), [#2929](https://github.com/adap/flower/" -"pull/2929), [#2943](https://github.com/adap/flower/pull/2943), [#2955]" -"(https://github.com/adap/flower/pull/2955), [#2954](https://github.com/adap/" -"flower/pull/2954))" -msgstr "" - -#: ../../source/ref-changelog.md:75 -msgid "v1.7.0 (2024-02-05)" +"**General updates to Flower Examples** " +"([#3205](https://github.com/adap/flower/pull/3205), " +"[#3226](https://github.com/adap/flower/pull/3226), " +"[#3211](https://github.com/adap/flower/pull/3211), " +"[#3252](https://github.com/adap/flower/pull/3252), " +"[#3427](https://github.com/adap/flower/pull/3427), " +"[#3410](https://github.com/adap/flower/pull/3410), " +"[#3426](https://github.com/adap/flower/pull/3426), " +"[#3228](https://github.com/adap/flower/pull/3228), " +"[#3342](https://github.com/adap/flower/pull/3342), " +"[#3200](https://github.com/adap/flower/pull/3200), " +"[#3202](https://github.com/adap/flower/pull/3202), " +"[#3394](https://github.com/adap/flower/pull/3394), " +"[#3488](https://github.com/adap/flower/pull/3488), " +"[#3329](https://github.com/adap/flower/pull/3329), " +"[#3526](https://github.com/adap/flower/pull/3526), " +"[#3392](https://github.com/adap/flower/pull/3392), " +"[#3474](https://github.com/adap/flower/pull/3474), " +"[#3269](https://github.com/adap/flower/pull/3269))" +msgstr "" + +#: ../../source/ref-changelog.md:71 +msgid "As always, Flower code examples have received many updates." +msgstr "" + +#: ../../source/ref-changelog.md:73 +msgid "" +"**General improvements** " +"([#3532](https://github.com/adap/flower/pull/3532), " +"[#3318](https://github.com/adap/flower/pull/3318), " +"[#3565](https://github.com/adap/flower/pull/3565), " +"[#3296](https://github.com/adap/flower/pull/3296), " +"[#3305](https://github.com/adap/flower/pull/3305), " +"[#3246](https://github.com/adap/flower/pull/3246), " +"[#3224](https://github.com/adap/flower/pull/3224), " +"[#3475](https://github.com/adap/flower/pull/3475), " +"[#3297](https://github.com/adap/flower/pull/3297), " +"[#3317](https://github.com/adap/flower/pull/3317), " +"[#3429](https://github.com/adap/flower/pull/3429), " +"[#3196](https://github.com/adap/flower/pull/3196), " +"[#3534](https://github.com/adap/flower/pull/3534), " +"[#3240](https://github.com/adap/flower/pull/3240), " +"[#3365](https://github.com/adap/flower/pull/3365), " +"[#3407](https://github.com/adap/flower/pull/3407), " +"[#3563](https://github.com/adap/flower/pull/3563), " +"[#3344](https://github.com/adap/flower/pull/3344), " +"[#3330](https://github.com/adap/flower/pull/3330), " +"[#3436](https://github.com/adap/flower/pull/3436), " +"[#3300](https://github.com/adap/flower/pull/3300), " +"[#3327](https://github.com/adap/flower/pull/3327), " +"[#3254](https://github.com/adap/flower/pull/3254), " +"[#3253](https://github.com/adap/flower/pull/3253), " +"[#3419](https://github.com/adap/flower/pull/3419), " +"[#3289](https://github.com/adap/flower/pull/3289), " +"[#3208](https://github.com/adap/flower/pull/3208), " +"[#3245](https://github.com/adap/flower/pull/3245), " +"[#3319](https://github.com/adap/flower/pull/3319), " +"[#3203](https://github.com/adap/flower/pull/3203), " +"[#3423](https://github.com/adap/flower/pull/3423), " +"[#3352](https://github.com/adap/flower/pull/3352), " +"[#3292](https://github.com/adap/flower/pull/3292), " +"[#3261](https://github.com/adap/flower/pull/3261))" +msgstr "" + +#: ../../source/ref-changelog.md:75 ../../source/ref-changelog.md:1058 +msgid "Deprecations" +msgstr "" + +#: ../../source/ref-changelog.md:77 +msgid "**Deprecate Python 3.8 support**" +msgstr "" + +#: ../../source/ref-changelog.md:79 +msgid "" +"Python 3.8 will stop receiving security fixes in [October " +"2024](https://devguide.python.org/versions/). Support for Python 3.8 is " +"now deprecated and will be removed in an upcoming release." msgstr "" #: ../../source/ref-changelog.md:81 msgid "" -"`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles Beauville`, " -"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " -"Bertoli`, `HelinLin`, `Heng Pan`, `Javier`, `M S Chaitanya Kumar`, `Mohammad " -"Naseri`, `Nikos Vlachakis`, `Pritam Neog`, `Robert Kuska`, `Robert Steiner`, " -"`Taner Topal`, `Yahia Salaheldin Shaaban`, `Yan Gao`, `Yasar Abbas` " +"**Deprecate (experimental)** `flower-driver-api` **and** `flower-fleet-" +"api` ([#3416](https://github.com/adap/flower/pull/3416), " +"[#3420](https://github.com/adap/flower/pull/3420))" +msgstr "" + +#: ../../source/ref-changelog.md:83 +msgid "" +"Flower 1.9 deprecates the two (experimental) commands `flower-driver-api`" +" and `flower-fleet-api`. Both commands will be removed in an upcoming " +"release. Use `flower-superlink` instead." msgstr "" #: ../../source/ref-changelog.md:85 msgid "" -"**Introduce stateful clients (experimental)** ([#2770](https://github.com/" -"adap/flower/pull/2770), [#2686](https://github.com/adap/flower/pull/2686), " -"[#2696](https://github.com/adap/flower/pull/2696), [#2643](https://github." -"com/adap/flower/pull/2643), [#2769](https://github.com/adap/flower/" -"pull/2769))" +"**Deprecate** `--server` **in favor of** `--superlink` " +"([#3518](https://github.com/adap/flower/pull/3518))" msgstr "" #: ../../source/ref-changelog.md:87 msgid "" -"Subclasses of `Client` and `NumPyClient` can now store local state that " -"remains on the client. Let's start with the highlight first: this new " -"feature is compatible with both simulated clients (via `start_simulation`) " -"and networked clients (via `start_client`). It's also the first preview of " -"new abstractions like `Context` and `RecordSet`. Clients can access state of " -"type `RecordSet` via `state: RecordSet = self.context.state`. Changes to " -"this `RecordSet` are preserved across different rounds of execution to " -"enable stateful computations in a unified way across simulation and " -"deployment." +"The commands `flower-server-app` and `flower-client-app` should use " +"`--superlink` instead of the now deprecated `--server`. Support for " +"`--server` will be removed in a future release." msgstr "" -#: ../../source/ref-changelog.md:89 -msgid "" -"**Improve performance** ([#2293](https://github.com/adap/flower/pull/2293))" +#: ../../source/ref-changelog.md:89 ../../source/ref-changelog.md:163 +#: ../../source/ref-changelog.md:238 ../../source/ref-changelog.md:350 +#: ../../source/ref-changelog.md:440 ../../source/ref-changelog.md:504 +#: ../../source/ref-changelog.md:562 ../../source/ref-changelog.md:631 +#: ../../source/ref-changelog.md:693 ../../source/ref-changelog.md:712 +#: ../../source/ref-changelog.md:868 ../../source/ref-changelog.md:939 +#: ../../source/ref-changelog.md:976 ../../source/ref-changelog.md:1019 +msgid "Incompatible changes" msgstr "" #: ../../source/ref-changelog.md:91 msgid "" -"Flower is faster than ever. All `FedAvg`-derived strategies now use in-place " -"aggregation to reduce memory consumption. The Flower client serialization/" -"deserialization has been rewritten from the ground up, which results in " -"significant speedups, especially when the client-side training time is short." +"**Replace** `flower-superlink` **CLI option** `--certificates` **with** " +"`--ssl-ca-certfile` **,** `--ssl-certfile` **and** `--ssl-keyfile` " +"([#3512](https://github.com/adap/flower/pull/3512), " +"[#3408](https://github.com/adap/flower/pull/3408))" msgstr "" #: ../../source/ref-changelog.md:93 msgid "" -"**Support Federated Learning with Apple MLX and Flower** ([#2693](https://" -"github.com/adap/flower/pull/2693))" +"SSL-related `flower-superlink` CLI arguments were restructured in an " +"incompatible way. Instead of passing a single `--certificates` flag with " +"three values, you now need to pass three flags (`--ssl-ca-certfile`, " +"`--ssl-certfile` and `--ssl-keyfile`) with one value each. Check out the " +"[SSL connections](https://flower.ai/docs/framework/how-to-enable-ssl-" +"connections.html) documentation page for details." msgstr "" #: ../../source/ref-changelog.md:95 msgid "" -"Flower has official support for federated learning using [Apple MLX](https://" -"ml-explore.github.io/mlx) via the new `quickstart-mlx` code example." +"**Remove SuperLink** `--vce` **option** " +"([#3513](https://github.com/adap/flower/pull/3513))" msgstr "" #: ../../source/ref-changelog.md:97 msgid "" -"**Introduce new XGBoost cyclic strategy** ([#2666](https://github.com/adap/" -"flower/pull/2666), [#2668](https://github.com/adap/flower/pull/2668))" +"Instead of separately starting a SuperLink and a `ServerApp` for " +"simulation, simulations must now be started using the single `flower-" +"simulation` command." msgstr "" #: ../../source/ref-changelog.md:99 msgid "" -"A new strategy called `FedXgbCyclic` supports a client-by-client style of " -"training (often called cyclic). The `xgboost-comprehensive` code example " -"shows how to use it in a full project. In addition to that, `xgboost-" -"comprehensive` now also supports simulation mode. With this, Flower offers " -"best-in-class XGBoost support." +"**Merge** `--grpc-rere` **and** `--rest` **SuperLink options** " +"([#3527](https://github.com/adap/flower/pull/3527))" msgstr "" #: ../../source/ref-changelog.md:101 msgid "" -"**Support Python 3.11** ([#2394](https://github.com/adap/flower/pull/2394))" +"To simplify the usage of `flower-superlink`, previously separate sets of " +"CLI options for gRPC and REST were merged into one unified set of " +"options. Consult the [Flower CLI reference " +"documentation](https://flower.ai/docs/framework/ref-api-cli.html) for " +"details." msgstr "" #: ../../source/ref-changelog.md:103 -msgid "" -"Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will ensure " -"better support for users using more recent Python versions." -msgstr "" - -#: ../../source/ref-changelog.md:105 -msgid "" -"**Update gRPC and ProtoBuf dependencies** ([#2814](https://github.com/adap/" -"flower/pull/2814))" -msgstr "" - -#: ../../source/ref-changelog.md:107 -msgid "" -"The `grpcio` and `protobuf` dependencies were updated to their latest " -"versions for improved security and performance." +msgid "v1.8.0 (2024-04-03)" msgstr "" #: ../../source/ref-changelog.md:109 msgid "" -"**Introduce Docker image for Flower server** ([#2700](https://github.com/" -"adap/flower/pull/2700), [#2688](https://github.com/adap/flower/pull/2688), " -"[#2705](https://github.com/adap/flower/pull/2705), [#2695](https://github." -"com/adap/flower/pull/2695), [#2747](https://github.com/adap/flower/" -"pull/2747), [#2746](https://github.com/adap/flower/pull/2746), [#2680]" -"(https://github.com/adap/flower/pull/2680), [#2682](https://github.com/adap/" -"flower/pull/2682), [#2701](https://github.com/adap/flower/pull/2701))" -msgstr "" - -#: ../../source/ref-changelog.md:111 -msgid "" -"The Flower server can now be run using an official Docker image. A new how-" -"to guide explains [how to run Flower using Docker](https://flower.ai/docs/" -"framework/how-to-run-flower-using-docker.html). An official Flower client " -"Docker image will follow." +"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " +"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear " +"Ashimine`, `Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, " +"`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " +"`tabdar-khan` " msgstr "" #: ../../source/ref-changelog.md:113 msgid "" -"**Introduce** `flower-via-docker-compose` **example** ([#2626](https://" -"github.com/adap/flower/pull/2626))" +"**Introduce Flower Next high-level API (stable)** " +"([#3002](https://github.com/adap/flower/pull/3002), " +"[#2934](https://github.com/adap/flower/pull/2934), " +"[#2958](https://github.com/adap/flower/pull/2958), " +"[#3173](https://github.com/adap/flower/pull/3173), " +"[#3174](https://github.com/adap/flower/pull/3174), " +"[#2923](https://github.com/adap/flower/pull/2923), " +"[#2691](https://github.com/adap/flower/pull/2691), " +"[#3079](https://github.com/adap/flower/pull/3079), " +"[#2961](https://github.com/adap/flower/pull/2961), " +"[#2924](https://github.com/adap/flower/pull/2924), " +"[#3166](https://github.com/adap/flower/pull/3166), " +"[#3031](https://github.com/adap/flower/pull/3031), " +"[#3057](https://github.com/adap/flower/pull/3057), " +"[#3000](https://github.com/adap/flower/pull/3000), " +"[#3113](https://github.com/adap/flower/pull/3113), " +"[#2957](https://github.com/adap/flower/pull/2957), " +"[#3183](https://github.com/adap/flower/pull/3183), " +"[#3180](https://github.com/adap/flower/pull/3180), " +"[#3035](https://github.com/adap/flower/pull/3035), " +"[#3189](https://github.com/adap/flower/pull/3189), " +"[#3185](https://github.com/adap/flower/pull/3185), " +"[#3190](https://github.com/adap/flower/pull/3190), " +"[#3191](https://github.com/adap/flower/pull/3191), " +"[#3195](https://github.com/adap/flower/pull/3195), " +"[#3197](https://github.com/adap/flower/pull/3197))" msgstr "" #: ../../source/ref-changelog.md:115 msgid "" -"**Introduce** `quickstart-sklearn-tabular` **example** ([#2719](https://" -"github.com/adap/flower/pull/2719))" +"The Flower Next high-level API is stable! Flower Next is the future of " +"Flower - all new features (like Flower Mods) will be built on top of it. " +"You can start to migrate your existing projects to Flower Next by using " +"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or " +"`quickstart-tensorflow`, a detailed migration guide will follow shortly)." +" Flower Next allows you to run multiple projects concurrently (we call " +"this multi-run) and execute the same project in either simulation " +"environments or deployment environments without having to change a single" +" line of code. The best part? It's fully compatible with existing Flower " +"projects that use `Strategy`, `NumPyClient` & co." msgstr "" #: ../../source/ref-changelog.md:117 msgid "" -"**Introduce** `custom-metrics` **example** ([#1958](https://github.com/adap/" -"flower/pull/1958))" +"**Introduce Flower Next low-level API (preview)** " +"([#3062](https://github.com/adap/flower/pull/3062), " +"[#3034](https://github.com/adap/flower/pull/3034), " +"[#3069](https://github.com/adap/flower/pull/3069))" msgstr "" #: ../../source/ref-changelog.md:119 msgid "" -"**Update code examples to use Flower Datasets** ([#2450](https://github.com/" -"adap/flower/pull/2450), [#2456](https://github.com/adap/flower/pull/2456), " -"[#2318](https://github.com/adap/flower/pull/2318), [#2712](https://github." -"com/adap/flower/pull/2712))" +"In addition to the Flower Next *high-level* API that uses `Strategy`, " +"`NumPyClient` & co, Flower 1.8 also comes with a preview version of the " +"new Flower Next *low-level* API. The low-level API allows for granular " +"control of every aspect of the learning process by sending/receiving " +"individual messages to/from client nodes. The new `ServerApp` supports " +"registering a custom `main` function that allows writing custom training " +"loops for methods like async FL, cyclic training, or federated analytics." +" The new `ClientApp` supports registering `train`, `evaluate` and `query`" +" functions that can access the raw message received from the `ServerApp`." +" New abstractions like `RecordSet`, `Message` and `Context` further " +"enable sending multiple models, multiple sets of config values and " +"metrics, stateful computations on the client node and implementations of " +"custom SMPC protocols, to name just a few." msgstr "" #: ../../source/ref-changelog.md:121 msgid "" -"Several code examples were updated to use [Flower Datasets](https://flower." -"ai/docs/datasets/)." +"**Introduce Flower Mods (preview)** " +"([#3054](https://github.com/adap/flower/pull/3054), " +"[#2911](https://github.com/adap/flower/pull/2911), " +"[#3083](https://github.com/adap/flower/pull/3083))" msgstr "" #: ../../source/ref-changelog.md:123 msgid "" -"**General updates to Flower Examples** ([#2381](https://github.com/adap/" -"flower/pull/2381), [#2805](https://github.com/adap/flower/pull/2805), [#2782]" -"(https://github.com/adap/flower/pull/2782), [#2806](https://github.com/adap/" -"flower/pull/2806), [#2829](https://github.com/adap/flower/pull/2829), [#2825]" -"(https://github.com/adap/flower/pull/2825), [#2816](https://github.com/adap/" -"flower/pull/2816), [#2726](https://github.com/adap/flower/pull/2726), [#2659]" -"(https://github.com/adap/flower/pull/2659), [#2655](https://github.com/adap/" -"flower/pull/2655))" +"Flower Modifiers (we call them Mods) can intercept messages and analyze, " +"edit or handle them directly. Mods can be used to develop pluggable " +"modules that work across different projects. Flower 1.8 already includes " +"mods to log the size of a message, the number of parameters sent over the" +" network, differential privacy with fixed clipping and adaptive clipping," +" local differential privacy and secure aggregation protocols SecAgg and " +"SecAgg+. The Flower Mods API is released as a preview, but researchers " +"can already use it to experiment with arbirtrary SMPC protocols." msgstr "" #: ../../source/ref-changelog.md:125 -msgid "Many Flower code examples received substantial updates." +msgid "" +"**Fine-tune LLMs with LLM FlowerTune** " +"([#3029](https://github.com/adap/flower/pull/3029), " +"[#3089](https://github.com/adap/flower/pull/3089), " +"[#3092](https://github.com/adap/flower/pull/3092), " +"[#3100](https://github.com/adap/flower/pull/3100), " +"[#3114](https://github.com/adap/flower/pull/3114), " +"[#3162](https://github.com/adap/flower/pull/3162), " +"[#3172](https://github.com/adap/flower/pull/3172))" msgstr "" -#: ../../source/ref-changelog.md:127 ../../source/ref-changelog.md:220 -msgid "**Update Flower Baselines**" +#: ../../source/ref-changelog.md:127 +msgid "" +"We are introducing LLM FlowerTune, an introductory example that " +"demonstrates federated LLM fine-tuning of pre-trained Llama2 models on " +"the Alpaca-GPT4 dataset. The example is built to be easily adapted to use" +" different models and/or datasets. Read our blog post [LLM FlowerTune: " +"Federated LLM Fine-tuning with Flower](https://flower.ai/blog/2024-03-14" +"-llm-flowertune-federated-llm-finetuning-with-flower/) for more details." msgstr "" #: ../../source/ref-changelog.md:129 msgid "" -"HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), [#2771]" -"(https://github.com/adap/flower/pull/2771))" -msgstr "" - -#: ../../source/ref-changelog.md:130 -msgid "FedVSSL ([#2412](https://github.com/adap/flower/pull/2412))" +"**Introduce built-in Differential Privacy (preview)** " +"([#2798](https://github.com/adap/flower/pull/2798), " +"[#2959](https://github.com/adap/flower/pull/2959), " +"[#3038](https://github.com/adap/flower/pull/3038), " +"[#3147](https://github.com/adap/flower/pull/3147), " +"[#2909](https://github.com/adap/flower/pull/2909), " +"[#2893](https://github.com/adap/flower/pull/2893), " +"[#2892](https://github.com/adap/flower/pull/2892), " +"[#3039](https://github.com/adap/flower/pull/3039), " +"[#3074](https://github.com/adap/flower/pull/3074))" msgstr "" #: ../../source/ref-changelog.md:131 -msgid "FedNova ([#2179](https://github.com/adap/flower/pull/2179))" +msgid "" +"Built-in Differential Privacy is here! Flower supports both central and " +"local differential privacy (DP). Central DP can be configured with either" +" fixed or adaptive clipping. The clipping can happen either on the " +"server-side or the client-side. Local DP does both clipping and noising " +"on the client-side. A new documentation page [explains Differential " +"Privacy approaches](https://flower.ai/docs/framework/explanation-" +"differential-privacy.html) and a new how-to guide describes [how to use " +"the new Differential Privacy components](https://flower.ai/docs/framework" +"/how-to-use-differential-privacy.html) in Flower." msgstr "" -#: ../../source/ref-changelog.md:132 -msgid "HeteroFL ([#2439](https://github.com/adap/flower/pull/2439))" +#: ../../source/ref-changelog.md:133 +msgid "" +"**Introduce built-in Secure Aggregation (preview)** " +"([#3120](https://github.com/adap/flower/pull/3120), " +"[#3110](https://github.com/adap/flower/pull/3110), " +"[#3108](https://github.com/adap/flower/pull/3108))" msgstr "" -#: ../../source/ref-changelog.md:133 -msgid "FedAvgM ([#2246](https://github.com/adap/flower/pull/2246))" +#: ../../source/ref-changelog.md:135 +msgid "" +"Built-in Secure Aggregation is here! Flower now supports different secure" +" aggregation protocols out-of-the-box. The best part? You can add secure " +"aggregation to your Flower projects with only a few lines of code. In " +"this initial release, we inlcude support for SecAgg and SecAgg+, but more" +" protocols will be implemented shortly. We'll also add detailed docs that" +" explain secure aggregation and how to use it in Flower. You can already " +"check out the new code example that shows how to use Flower to easily " +"combine Federated Learning, Differential Privacy and Secure Aggregation " +"in the same project." msgstr "" -#: ../../source/ref-changelog.md:134 -msgid "FedPara ([#2722](https://github.com/adap/flower/pull/2722))" +#: ../../source/ref-changelog.md:137 +msgid "" +"**Introduce** `flwr` **CLI (preview)** " +"([#2942](https://github.com/adap/flower/pull/2942), " +"[#3055](https://github.com/adap/flower/pull/3055), " +"[#3111](https://github.com/adap/flower/pull/3111), " +"[#3130](https://github.com/adap/flower/pull/3130), " +"[#3136](https://github.com/adap/flower/pull/3136), " +"[#3094](https://github.com/adap/flower/pull/3094), " +"[#3059](https://github.com/adap/flower/pull/3059), " +"[#3049](https://github.com/adap/flower/pull/3049), " +"[#3142](https://github.com/adap/flower/pull/3142))" msgstr "" -#: ../../source/ref-changelog.md:136 -msgid "" -"**Improve documentation** ([#2674](https://github.com/adap/flower/" -"pull/2674), [#2480](https://github.com/adap/flower/pull/2480), [#2826]" -"(https://github.com/adap/flower/pull/2826), [#2727](https://github.com/adap/" -"flower/pull/2727), [#2761](https://github.com/adap/flower/pull/2761), [#2900]" -"(https://github.com/adap/flower/pull/2900))" -msgstr "" - -#: ../../source/ref-changelog.md:138 -msgid "" -"**Improved testing and development infrastructure** ([#2797](https://github." -"com/adap/flower/pull/2797), [#2676](https://github.com/adap/flower/" -"pull/2676), [#2644](https://github.com/adap/flower/pull/2644), [#2656]" -"(https://github.com/adap/flower/pull/2656), [#2848](https://github.com/adap/" -"flower/pull/2848), [#2675](https://github.com/adap/flower/pull/2675), [#2735]" -"(https://github.com/adap/flower/pull/2735), [#2767](https://github.com/adap/" -"flower/pull/2767), [#2732](https://github.com/adap/flower/pull/2732), [#2744]" -"(https://github.com/adap/flower/pull/2744), [#2681](https://github.com/adap/" -"flower/pull/2681), [#2699](https://github.com/adap/flower/pull/2699), [#2745]" -"(https://github.com/adap/flower/pull/2745), [#2734](https://github.com/adap/" -"flower/pull/2734), [#2731](https://github.com/adap/flower/pull/2731), [#2652]" -"(https://github.com/adap/flower/pull/2652), [#2720](https://github.com/adap/" -"flower/pull/2720), [#2721](https://github.com/adap/flower/pull/2721), [#2717]" -"(https://github.com/adap/flower/pull/2717), [#2864](https://github.com/adap/" -"flower/pull/2864), [#2694](https://github.com/adap/flower/pull/2694), [#2709]" -"(https://github.com/adap/flower/pull/2709), [#2658](https://github.com/adap/" -"flower/pull/2658), [#2796](https://github.com/adap/flower/pull/2796), [#2692]" -"(https://github.com/adap/flower/pull/2692), [#2657](https://github.com/adap/" -"flower/pull/2657), [#2813](https://github.com/adap/flower/pull/2813), [#2661]" -"(https://github.com/adap/flower/pull/2661), [#2398](https://github.com/adap/" -"flower/pull/2398))" -msgstr "" - -#: ../../source/ref-changelog.md:140 -msgid "" -"The Flower testing and development infrastructure has received substantial " -"updates. This makes Flower 1.7 the most tested release ever." -msgstr "" - -#: ../../source/ref-changelog.md:142 -msgid "" -"**Update dependencies** ([#2753](https://github.com/adap/flower/pull/2753), " -"[#2651](https://github.com/adap/flower/pull/2651), [#2739](https://github." -"com/adap/flower/pull/2739), [#2837](https://github.com/adap/flower/" -"pull/2837), [#2788](https://github.com/adap/flower/pull/2788), [#2811]" -"(https://github.com/adap/flower/pull/2811), [#2774](https://github.com/adap/" -"flower/pull/2774), [#2790](https://github.com/adap/flower/pull/2790), [#2751]" -"(https://github.com/adap/flower/pull/2751), [#2850](https://github.com/adap/" -"flower/pull/2850), [#2812](https://github.com/adap/flower/pull/2812), [#2872]" -"(https://github.com/adap/flower/pull/2872), [#2736](https://github.com/adap/" -"flower/pull/2736), [#2756](https://github.com/adap/flower/pull/2756), [#2857]" -"(https://github.com/adap/flower/pull/2857), [#2757](https://github.com/adap/" -"flower/pull/2757), [#2810](https://github.com/adap/flower/pull/2810), [#2740]" -"(https://github.com/adap/flower/pull/2740), [#2789](https://github.com/adap/" -"flower/pull/2789))" -msgstr "" - -#: ../../source/ref-changelog.md:144 -msgid "" -"**General improvements** ([#2803](https://github.com/adap/flower/pull/2803), " -"[#2847](https://github.com/adap/flower/pull/2847), [#2877](https://github." -"com/adap/flower/pull/2877), [#2690](https://github.com/adap/flower/" -"pull/2690), [#2889](https://github.com/adap/flower/pull/2889), [#2874]" -"(https://github.com/adap/flower/pull/2874), [#2819](https://github.com/adap/" -"flower/pull/2819), [#2689](https://github.com/adap/flower/pull/2689), [#2457]" -"(https://github.com/adap/flower/pull/2457), [#2870](https://github.com/adap/" -"flower/pull/2870), [#2669](https://github.com/adap/flower/pull/2669), [#2876]" -"(https://github.com/adap/flower/pull/2876), [#2885](https://github.com/adap/" -"flower/pull/2885), [#2858](https://github.com/adap/flower/pull/2858), [#2867]" -"(https://github.com/adap/flower/pull/2867), [#2351](https://github.com/adap/" -"flower/pull/2351), [#2886](https://github.com/adap/flower/pull/2886), [#2860]" -"(https://github.com/adap/flower/pull/2860), [#2828](https://github.com/adap/" -"flower/pull/2828), [#2869](https://github.com/adap/flower/pull/2869), [#2875]" -"(https://github.com/adap/flower/pull/2875), [#2733](https://github.com/adap/" -"flower/pull/2733), [#2488](https://github.com/adap/flower/pull/2488), [#2646]" -"(https://github.com/adap/flower/pull/2646), [#2879](https://github.com/adap/" -"flower/pull/2879), [#2821](https://github.com/adap/flower/pull/2821), [#2855]" -"(https://github.com/adap/flower/pull/2855), [#2800](https://github.com/adap/" -"flower/pull/2800), [#2807](https://github.com/adap/flower/pull/2807), [#2801]" -"(https://github.com/adap/flower/pull/2801), [#2804](https://github.com/adap/" -"flower/pull/2804), [#2851](https://github.com/adap/flower/pull/2851), [#2787]" -"(https://github.com/adap/flower/pull/2787), [#2852](https://github.com/adap/" -"flower/pull/2852), [#2672](https://github.com/adap/flower/pull/2672), [#2759]" -"(https://github.com/adap/flower/pull/2759))" -msgstr "" - -#: ../../source/ref-changelog.md:148 -msgid "" -"**Deprecate** `start_numpy_client` ([#2563](https://github.com/adap/flower/" -"pull/2563), [#2718](https://github.com/adap/flower/pull/2718))" -msgstr "" - -#: ../../source/ref-changelog.md:150 +#: ../../source/ref-changelog.md:139 msgid "" -"Until now, clients of type `NumPyClient` needed to be started via " -"`start_numpy_client`. In our efforts to consolidate framework APIs, we have " -"introduced changes, and now all client types should start via " -"`start_client`. To continue using `NumPyClient` clients, you simply need to " -"first call the `.to_client()` method and then pass returned `Client` object " -"to `start_client`. The examples and the documentation have been updated " -"accordingly." +"A new `flwr` CLI command allows creating new Flower projects (`flwr new`)" +" and then running them using the Simulation Engine (`flwr run`)." msgstr "" -#: ../../source/ref-changelog.md:152 +#: ../../source/ref-changelog.md:141 msgid "" -"**Deprecate legacy DP wrappers** ([#2749](https://github.com/adap/flower/" -"pull/2749))" +"**Introduce Flower Next Simulation Engine** " +"([#3024](https://github.com/adap/flower/pull/3024), " +"[#3061](https://github.com/adap/flower/pull/3061), " +"[#2997](https://github.com/adap/flower/pull/2997), " +"[#2783](https://github.com/adap/flower/pull/2783), " +"[#3184](https://github.com/adap/flower/pull/3184), " +"[#3075](https://github.com/adap/flower/pull/3075), " +"[#3047](https://github.com/adap/flower/pull/3047), " +"[#2998](https://github.com/adap/flower/pull/2998), " +"[#3009](https://github.com/adap/flower/pull/3009), " +"[#3008](https://github.com/adap/flower/pull/3008))" msgstr "" -#: ../../source/ref-changelog.md:154 +#: ../../source/ref-changelog.md:143 msgid "" -"Legacy DP wrapper classes are deprecated, but still functional. This is in " -"preparation for an all-new pluggable version of differential privacy support " -"in Flower." +"The Flower Simulation Engine can now run Flower Next projects. For " +"notebook environments, there's also a new `run_simulation` function that " +"can run `ServerApp` and `ClientApp`." msgstr "" -#: ../../source/ref-changelog.md:156 +#: ../../source/ref-changelog.md:145 msgid "" -"**Make optional arg** `--callable` **in** `flower-client` **a required " -"positional arg** ([#2673](https://github.com/adap/flower/pull/2673))" +"**Handle SuperNode connection errors** " +"([#2969](https://github.com/adap/flower/pull/2969))" msgstr "" -#: ../../source/ref-changelog.md:158 +#: ../../source/ref-changelog.md:147 msgid "" -"**Rename** `certificates` **to** `root_certificates` **in** `Driver` ([#2890]" -"(https://github.com/adap/flower/pull/2890))" +"A SuperNode will now try to reconnect indefinitely to the SuperLink in " +"case of connection errors. The arguments `--max-retries` and `--max-wait-" +"time` can now be passed to the `flower-client-app` command. `--max-" +"retries` will define the number of tentatives the client should make " +"before it gives up trying to reconnect to the SuperLink, and, `--max-" +"wait-time` defines the time before the SuperNode gives up trying to " +"reconnect to the SuperLink." msgstr "" -#: ../../source/ref-changelog.md:160 +#: ../../source/ref-changelog.md:149 msgid "" -"**Drop experimental** `Task` **fields** ([#2866](https://github.com/adap/" -"flower/pull/2866), [#2865](https://github.com/adap/flower/pull/2865))" +"**General updates to Flower Baselines** " +"([#2904](https://github.com/adap/flower/pull/2904), " +"[#2482](https://github.com/adap/flower/pull/2482), " +"[#2985](https://github.com/adap/flower/pull/2985), " +"[#2968](https://github.com/adap/flower/pull/2968))" msgstr "" -#: ../../source/ref-changelog.md:162 +#: ../../source/ref-changelog.md:151 msgid "" -"Experimental fields `sa`, `legacy_server_message` and " -"`legacy_client_message` were removed from `Task` message. The removed fields " -"are superseded by the new `RecordSet` abstraction." +"There's a new [FedStar](https://flower.ai/docs/baselines/fedstar.html) " +"baseline. Several other baselined have been updated as well." msgstr "" -#: ../../source/ref-changelog.md:164 +#: ../../source/ref-changelog.md:153 msgid "" -"**Retire MXNet examples** ([#2724](https://github.com/adap/flower/pull/2724))" +"**Improve documentation and translations** " +"([#3050](https://github.com/adap/flower/pull/3050), " +"[#3044](https://github.com/adap/flower/pull/3044), " +"[#3043](https://github.com/adap/flower/pull/3043), " +"[#2986](https://github.com/adap/flower/pull/2986), " +"[#3041](https://github.com/adap/flower/pull/3041), " +"[#3046](https://github.com/adap/flower/pull/3046), " +"[#3042](https://github.com/adap/flower/pull/3042), " +"[#2978](https://github.com/adap/flower/pull/2978), " +"[#2952](https://github.com/adap/flower/pull/2952), " +"[#3167](https://github.com/adap/flower/pull/3167), " +"[#2953](https://github.com/adap/flower/pull/2953), " +"[#3045](https://github.com/adap/flower/pull/3045), " +"[#2654](https://github.com/adap/flower/pull/2654), " +"[#3082](https://github.com/adap/flower/pull/3082), " +"[#2990](https://github.com/adap/flower/pull/2990), " +"[#2989](https://github.com/adap/flower/pull/2989))" msgstr "" -#: ../../source/ref-changelog.md:166 +#: ../../source/ref-changelog.md:155 msgid "" -"The development of the MXNet fremework has ended and the project is now " -"[archived on GitHub](https://github.com/apache/mxnet). Existing MXNet " -"examples won't receive updates." +"As usual, we merged many smaller and larger improvements to the " +"documentation. A special thank you goes to [Sebastian van der " +"Voort](https://github.com/svdvoort) for landing a big documentation PR!" +msgstr "" + +#: ../../source/ref-changelog.md:157 +msgid "" +"**General updates to Flower Examples** " +"([3134](https://github.com/adap/flower/pull/3134), " +"[2996](https://github.com/adap/flower/pull/2996), " +"[2930](https://github.com/adap/flower/pull/2930), " +"[2967](https://github.com/adap/flower/pull/2967), " +"[2467](https://github.com/adap/flower/pull/2467), " +"[2910](https://github.com/adap/flower/pull/2910), " +"[#2918](https://github.com/adap/flower/pull/2918), " +"[#2773](https://github.com/adap/flower/pull/2773), " +"[#3063](https://github.com/adap/flower/pull/3063), " +"[#3116](https://github.com/adap/flower/pull/3116), " +"[#3117](https://github.com/adap/flower/pull/3117))" +msgstr "" + +#: ../../source/ref-changelog.md:159 +msgid "" +"Two new examples show federated training of a Vision Transformer (ViT) " +"and federated learning in a medical context using the popular MONAI " +"library. `quickstart-pytorch` and `quickstart-tensorflow` demonstrate the" +" new Flower Next `ServerApp` and `ClientApp`. Many other examples " +"received considerable updates as well." +msgstr "" + +#: ../../source/ref-changelog.md:161 +msgid "" +"**General improvements** " +"([#3171](https://github.com/adap/flower/pull/3171), " +"[3099](https://github.com/adap/flower/pull/3099), " +"[3003](https://github.com/adap/flower/pull/3003), " +"[3145](https://github.com/adap/flower/pull/3145), " +"[3017](https://github.com/adap/flower/pull/3017), " +"[3085](https://github.com/adap/flower/pull/3085), " +"[3012](https://github.com/adap/flower/pull/3012), " +"[3119](https://github.com/adap/flower/pull/3119), " +"[2991](https://github.com/adap/flower/pull/2991), " +"[2970](https://github.com/adap/flower/pull/2970), " +"[2980](https://github.com/adap/flower/pull/2980), " +"[3086](https://github.com/adap/flower/pull/3086), " +"[2932](https://github.com/adap/flower/pull/2932), " +"[2928](https://github.com/adap/flower/pull/2928), " +"[2941](https://github.com/adap/flower/pull/2941), " +"[2933](https://github.com/adap/flower/pull/2933), " +"[3181](https://github.com/adap/flower/pull/3181), " +"[2973](https://github.com/adap/flower/pull/2973), " +"[2992](https://github.com/adap/flower/pull/2992), " +"[2915](https://github.com/adap/flower/pull/2915), " +"[3040](https://github.com/adap/flower/pull/3040), " +"[3022](https://github.com/adap/flower/pull/3022), " +"[3032](https://github.com/adap/flower/pull/3032), " +"[2902](https://github.com/adap/flower/pull/2902), " +"[2931](https://github.com/adap/flower/pull/2931), " +"[3005](https://github.com/adap/flower/pull/3005), " +"[3132](https://github.com/adap/flower/pull/3132), " +"[3115](https://github.com/adap/flower/pull/3115), " +"[2944](https://github.com/adap/flower/pull/2944), " +"[3064](https://github.com/adap/flower/pull/3064), " +"[3106](https://github.com/adap/flower/pull/3106), " +"[2974](https://github.com/adap/flower/pull/2974), " +"[3178](https://github.com/adap/flower/pull/3178), " +"[2993](https://github.com/adap/flower/pull/2993), " +"[3186](https://github.com/adap/flower/pull/3186), " +"[3091](https://github.com/adap/flower/pull/3091), " +"[3125](https://github.com/adap/flower/pull/3125), " +"[3093](https://github.com/adap/flower/pull/3093), " +"[3013](https://github.com/adap/flower/pull/3013), " +"[3033](https://github.com/adap/flower/pull/3033), " +"[3133](https://github.com/adap/flower/pull/3133), " +"[3068](https://github.com/adap/flower/pull/3068), " +"[2916](https://github.com/adap/flower/pull/2916), " +"[2975](https://github.com/adap/flower/pull/2975), " +"[2984](https://github.com/adap/flower/pull/2984), " +"[2846](https://github.com/adap/flower/pull/2846), " +"[3077](https://github.com/adap/flower/pull/3077), " +"[3143](https://github.com/adap/flower/pull/3143), " +"[2921](https://github.com/adap/flower/pull/2921), " +"[3101](https://github.com/adap/flower/pull/3101), " +"[2927](https://github.com/adap/flower/pull/2927), " +"[2995](https://github.com/adap/flower/pull/2995), " +"[2972](https://github.com/adap/flower/pull/2972), " +"[2912](https://github.com/adap/flower/pull/2912), " +"[3065](https://github.com/adap/flower/pull/3065), " +"[3028](https://github.com/adap/flower/pull/3028), " +"[2922](https://github.com/adap/flower/pull/2922), " +"[2982](https://github.com/adap/flower/pull/2982), " +"[2914](https://github.com/adap/flower/pull/2914), " +"[3179](https://github.com/adap/flower/pull/3179), " +"[3080](https://github.com/adap/flower/pull/3080), " +"[2994](https://github.com/adap/flower/pull/2994), " +"[3187](https://github.com/adap/flower/pull/3187), " +"[2926](https://github.com/adap/flower/pull/2926), " +"[3018](https://github.com/adap/flower/pull/3018), " +"[3144](https://github.com/adap/flower/pull/3144), " +"[3011](https://github.com/adap/flower/pull/3011), " +"[#3152](https://github.com/adap/flower/pull/3152), " +"[#2836](https://github.com/adap/flower/pull/2836), " +"[#2929](https://github.com/adap/flower/pull/2929), " +"[#2943](https://github.com/adap/flower/pull/2943), " +"[#2955](https://github.com/adap/flower/pull/2955), " +"[#2954](https://github.com/adap/flower/pull/2954))" +msgstr "" + +#: ../../source/ref-changelog.md:165 ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:506 ../../source/ref-changelog.md:564 +#: ../../source/ref-changelog.md:633 ../../source/ref-changelog.md:695 +msgid "None" msgstr "" -#: ../../source/ref-changelog.md:168 -msgid "v1.6.0 (2023-11-28)" +#: ../../source/ref-changelog.md:167 +msgid "v1.7.0 (2024-02-05)" msgstr "" -#: ../../source/ref-changelog.md:174 +#: ../../source/ref-changelog.md:173 msgid "" -"`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " -"`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " -"Mota`, `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`, " -"`Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, `Steve " -"Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, `cnxdeveloper`, " -"`k3nfalt` " +"`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles " +"Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo " +"Gabrielli`, `Gustavo Bertoli`, `HelinLin`, `Heng Pan`, `Javier`, `M S " +"Chaitanya Kumar`, `Mohammad Naseri`, `Nikos Vlachakis`, `Pritam Neog`, " +"`Robert Kuska`, `Robert Steiner`, `Taner Topal`, `Yahia Salaheldin " +"Shaaban`, `Yan Gao`, `Yasar Abbas` " msgstr "" -#: ../../source/ref-changelog.md:178 +#: ../../source/ref-changelog.md:177 msgid "" -"**Add experimental support for Python 3.12** ([#2565](https://github.com/" -"adap/flower/pull/2565))" +"**Introduce stateful clients (experimental)** " +"([#2770](https://github.com/adap/flower/pull/2770), " +"[#2686](https://github.com/adap/flower/pull/2686), " +"[#2696](https://github.com/adap/flower/pull/2696), " +"[#2643](https://github.com/adap/flower/pull/2643), " +"[#2769](https://github.com/adap/flower/pull/2769))" msgstr "" -#: ../../source/ref-changelog.md:180 +#: ../../source/ref-changelog.md:179 msgid "" -"**Add new XGBoost examples** ([#2612](https://github.com/adap/flower/" -"pull/2612), [#2554](https://github.com/adap/flower/pull/2554), [#2617]" -"(https://github.com/adap/flower/pull/2617), [#2618](https://github.com/adap/" -"flower/pull/2618), [#2619](https://github.com/adap/flower/pull/2619), [#2567]" -"(https://github.com/adap/flower/pull/2567))" +"Subclasses of `Client` and `NumPyClient` can now store local state that " +"remains on the client. Let's start with the highlight first: this new " +"feature is compatible with both simulated clients (via " +"`start_simulation`) and networked clients (via `start_client`). It's also" +" the first preview of new abstractions like `Context` and `RecordSet`. " +"Clients can access state of type `RecordSet` via `state: RecordSet = " +"self.context.state`. Changes to this `RecordSet` are preserved across " +"different rounds of execution to enable stateful computations in a " +"unified way across simulation and deployment." msgstr "" -#: ../../source/ref-changelog.md:182 +#: ../../source/ref-changelog.md:181 msgid "" -"We have added a new `xgboost-quickstart` example alongside a new `xgboost-" -"comprehensive` example that goes more in-depth." +"**Improve performance** " +"([#2293](https://github.com/adap/flower/pull/2293))" msgstr "" -#: ../../source/ref-changelog.md:184 +#: ../../source/ref-changelog.md:183 msgid "" -"**Add Vertical FL example** ([#2598](https://github.com/adap/flower/" -"pull/2598))" +"Flower is faster than ever. All `FedAvg`-derived strategies now use in-" +"place aggregation to reduce memory consumption. The Flower client " +"serialization/deserialization has been rewritten from the ground up, " +"which results in significant speedups, especially when the client-side " +"training time is short." msgstr "" -#: ../../source/ref-changelog.md:186 +#: ../../source/ref-changelog.md:185 msgid "" -"We had many questions about Vertical Federated Learning using Flower, so we " -"decided to add an simple example for it on the [Titanic dataset](https://www." -"kaggle.com/competitions/titanic/data) alongside a tutorial (in the README)." +"**Support Federated Learning with Apple MLX and Flower** " +"([#2693](https://github.com/adap/flower/pull/2693))" msgstr "" -#: ../../source/ref-changelog.md:188 +#: ../../source/ref-changelog.md:187 msgid "" -"**Support custom** `ClientManager` **in** `start_driver()` ([#2292](https://" -"github.com/adap/flower/pull/2292))" +"Flower has official support for federated learning using [Apple " +"MLX](https://ml-explore.github.io/mlx) via the new `quickstart-mlx` code " +"example." msgstr "" -#: ../../source/ref-changelog.md:190 +#: ../../source/ref-changelog.md:189 msgid "" -"**Update REST API to support create and delete nodes** ([#2283](https://" -"github.com/adap/flower/pull/2283))" +"**Introduce new XGBoost cyclic strategy** " +"([#2666](https://github.com/adap/flower/pull/2666), " +"[#2668](https://github.com/adap/flower/pull/2668))" msgstr "" -#: ../../source/ref-changelog.md:192 +#: ../../source/ref-changelog.md:191 msgid "" -"**Update the Android SDK** ([#2187](https://github.com/adap/flower/" -"pull/2187))" +"A new strategy called `FedXgbCyclic` supports a client-by-client style of" +" training (often called cyclic). The `xgboost-comprehensive` code example" +" shows how to use it in a full project. In addition to that, `xgboost-" +"comprehensive` now also supports simulation mode. With this, Flower " +"offers best-in-class XGBoost support." msgstr "" -#: ../../source/ref-changelog.md:194 -msgid "Add gRPC request-response capability to the Android SDK." +#: ../../source/ref-changelog.md:193 +msgid "" +"**Support Python 3.11** " +"([#2394](https://github.com/adap/flower/pull/2394))" msgstr "" -#: ../../source/ref-changelog.md:196 +#: ../../source/ref-changelog.md:195 msgid "" -"**Update the C++ SDK** ([#2537](https://github.com/adap/flower/pull/2537), " -"[#2528](https://github.com/adap/flower/pull/2528), [#2523](https://github." -"com/adap/flower/pull/2523), [#2522](https://github.com/adap/flower/" -"pull/2522))" +"Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will " +"ensure better support for users using more recent Python versions." msgstr "" -#: ../../source/ref-changelog.md:198 -msgid "Add gRPC request-response capability to the C++ SDK." +#: ../../source/ref-changelog.md:197 +msgid "" +"**Update gRPC and ProtoBuf dependencies** " +"([#2814](https://github.com/adap/flower/pull/2814))" msgstr "" -#: ../../source/ref-changelog.md:200 +#: ../../source/ref-changelog.md:199 msgid "" -"**Make HTTPS the new default** ([#2591](https://github.com/adap/flower/" -"pull/2591), [#2636](https://github.com/adap/flower/pull/2636))" +"The `grpcio` and `protobuf` dependencies were updated to their latest " +"versions for improved security and performance." msgstr "" -#: ../../source/ref-changelog.md:202 +#: ../../source/ref-changelog.md:201 msgid "" -"Flower is moving to HTTPS by default. The new `flower-server` requires " -"passing `--certificates`, but users can enable `--insecure` to use HTTP for " -"prototyping. The same applies to `flower-client`, which can either use user-" -"provided credentials or gRPC-bundled certificates to connect to an HTTPS-" -"enabled server or requires opt-out via passing `--insecure` to enable " -"insecure HTTP connections." +"**Introduce Docker image for Flower server** " +"([#2700](https://github.com/adap/flower/pull/2700), " +"[#2688](https://github.com/adap/flower/pull/2688), " +"[#2705](https://github.com/adap/flower/pull/2705), " +"[#2695](https://github.com/adap/flower/pull/2695), " +"[#2747](https://github.com/adap/flower/pull/2747), " +"[#2746](https://github.com/adap/flower/pull/2746), " +"[#2680](https://github.com/adap/flower/pull/2680), " +"[#2682](https://github.com/adap/flower/pull/2682), " +"[#2701](https://github.com/adap/flower/pull/2701))" msgstr "" -#: ../../source/ref-changelog.md:204 +#: ../../source/ref-changelog.md:203 msgid "" -"For backward compatibility, `start_client()` and `start_numpy_client()` will " -"still start in insecure mode by default. In a future release, insecure " -"connections will require user opt-in by passing `insecure=True`." +"The Flower server can now be run using an official Docker image. A new " +"how-to guide explains [how to run Flower using " +"Docker](https://flower.ai/docs/framework/how-to-run-flower-using-" +"docker.html). An official Flower client Docker image will follow." msgstr "" -#: ../../source/ref-changelog.md:206 +#: ../../source/ref-changelog.md:205 msgid "" -"**Unify client API** ([#2303](https://github.com/adap/flower/pull/2303), " -"[#2390](https://github.com/adap/flower/pull/2390), [#2493](https://github." -"com/adap/flower/pull/2493))" +"**Introduce** `flower-via-docker-compose` **example** " +"([#2626](https://github.com/adap/flower/pull/2626))" msgstr "" -#: ../../source/ref-changelog.md:208 +#: ../../source/ref-changelog.md:207 msgid "" -"Using the `client_fn`, Flower clients can interchangeably run as standalone " -"processes (i.e. via `start_client`) or in simulation (i.e. via " -"`start_simulation`) without requiring changes to how the client class is " -"defined and instantiated. The `to_client()` function is introduced to " -"convert a `NumPyClient` to a `Client`." +"**Introduce** `quickstart-sklearn-tabular` **example** " +"([#2719](https://github.com/adap/flower/pull/2719))" msgstr "" -#: ../../source/ref-changelog.md:210 +#: ../../source/ref-changelog.md:209 msgid "" -"**Add new** `Bulyan` **strategy** ([#1817](https://github.com/adap/flower/" -"pull/1817), [#1891](https://github.com/adap/flower/pull/1891))" +"**Introduce** `custom-metrics` **example** " +"([#1958](https://github.com/adap/flower/pull/1958))" msgstr "" -#: ../../source/ref-changelog.md:212 +#: ../../source/ref-changelog.md:211 msgid "" -"The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., 2018]" -"(https://arxiv.org/abs/1802.07927)" +"**Update code examples to use Flower Datasets** " +"([#2450](https://github.com/adap/flower/pull/2450), " +"[#2456](https://github.com/adap/flower/pull/2456), " +"[#2318](https://github.com/adap/flower/pull/2318), " +"[#2712](https://github.com/adap/flower/pull/2712))" msgstr "" -#: ../../source/ref-changelog.md:214 +#: ../../source/ref-changelog.md:213 msgid "" -"**Add new** `XGB Bagging` **strategy** ([#2611](https://github.com/adap/" -"flower/pull/2611))" +"Several code examples were updated to use [Flower " +"Datasets](https://flower.ai/docs/datasets/)." msgstr "" -#: ../../source/ref-changelog.md:216 ../../source/ref-changelog.md:218 +#: ../../source/ref-changelog.md:215 msgid "" -"**Introduce `WorkloadState`** ([#2564](https://github.com/adap/flower/" -"pull/2564), [#2632](https://github.com/adap/flower/pull/2632))" +"**General updates to Flower Examples** " +"([#2381](https://github.com/adap/flower/pull/2381), " +"[#2805](https://github.com/adap/flower/pull/2805), " +"[#2782](https://github.com/adap/flower/pull/2782), " +"[#2806](https://github.com/adap/flower/pull/2806), " +"[#2829](https://github.com/adap/flower/pull/2829), " +"[#2825](https://github.com/adap/flower/pull/2825), " +"[#2816](https://github.com/adap/flower/pull/2816), " +"[#2726](https://github.com/adap/flower/pull/2726), " +"[#2659](https://github.com/adap/flower/pull/2659), " +"[#2655](https://github.com/adap/flower/pull/2655))" msgstr "" -#: ../../source/ref-changelog.md:222 +#: ../../source/ref-changelog.md:217 +msgid "Many Flower code examples received substantial updates." +msgstr "" + +#: ../../source/ref-changelog.md:219 ../../source/ref-changelog.md:312 +msgid "**Update Flower Baselines**" +msgstr "" + +#: ../../source/ref-changelog.md:221 msgid "" -"FedProx ([#2210](https://github.com/adap/flower/pull/2210), [#2286](https://" -"github.com/adap/flower/pull/2286), [#2509](https://github.com/adap/flower/" -"pull/2509))" +"HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), " +"[#2771](https://github.com/adap/flower/pull/2771))" +msgstr "" + +#: ../../source/ref-changelog.md:222 +msgid "FedVSSL ([#2412](https://github.com/adap/flower/pull/2412))" +msgstr "" + +#: ../../source/ref-changelog.md:223 +msgid "FedNova ([#2179](https://github.com/adap/flower/pull/2179))" msgstr "" #: ../../source/ref-changelog.md:224 -msgid "" -"Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), [#2400]" -"(https://github.com/adap/flower/pull/2400))" +msgid "HeteroFL ([#2439](https://github.com/adap/flower/pull/2439))" +msgstr "" + +#: ../../source/ref-changelog.md:225 +msgid "FedAvgM ([#2246](https://github.com/adap/flower/pull/2246))" msgstr "" #: ../../source/ref-changelog.md:226 -msgid "" -"FedMLB ([#2340](https://github.com/adap/flower/pull/2340), [#2507](https://" -"github.com/adap/flower/pull/2507))" +msgid "FedPara ([#2722](https://github.com/adap/flower/pull/2722))" msgstr "" #: ../../source/ref-changelog.md:228 msgid "" -"TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), [#2508](https://" -"github.com/adap/flower/pull/2508))" +"**Improve documentation** " +"([#2674](https://github.com/adap/flower/pull/2674), " +"[#2480](https://github.com/adap/flower/pull/2480), " +"[#2826](https://github.com/adap/flower/pull/2826), " +"[#2727](https://github.com/adap/flower/pull/2727), " +"[#2761](https://github.com/adap/flower/pull/2761), " +"[#2900](https://github.com/adap/flower/pull/2900))" msgstr "" #: ../../source/ref-changelog.md:230 -msgid "FedMeta [#2438](https://github.com/adap/flower/pull/2438)" +msgid "" +"**Improved testing and development infrastructure** " +"([#2797](https://github.com/adap/flower/pull/2797), " +"[#2676](https://github.com/adap/flower/pull/2676), " +"[#2644](https://github.com/adap/flower/pull/2644), " +"[#2656](https://github.com/adap/flower/pull/2656), " +"[#2848](https://github.com/adap/flower/pull/2848), " +"[#2675](https://github.com/adap/flower/pull/2675), " +"[#2735](https://github.com/adap/flower/pull/2735), " +"[#2767](https://github.com/adap/flower/pull/2767), " +"[#2732](https://github.com/adap/flower/pull/2732), " +"[#2744](https://github.com/adap/flower/pull/2744), " +"[#2681](https://github.com/adap/flower/pull/2681), " +"[#2699](https://github.com/adap/flower/pull/2699), " +"[#2745](https://github.com/adap/flower/pull/2745), " +"[#2734](https://github.com/adap/flower/pull/2734), " +"[#2731](https://github.com/adap/flower/pull/2731), " +"[#2652](https://github.com/adap/flower/pull/2652), " +"[#2720](https://github.com/adap/flower/pull/2720), " +"[#2721](https://github.com/adap/flower/pull/2721), " +"[#2717](https://github.com/adap/flower/pull/2717), " +"[#2864](https://github.com/adap/flower/pull/2864), " +"[#2694](https://github.com/adap/flower/pull/2694), " +"[#2709](https://github.com/adap/flower/pull/2709), " +"[#2658](https://github.com/adap/flower/pull/2658), " +"[#2796](https://github.com/adap/flower/pull/2796), " +"[#2692](https://github.com/adap/flower/pull/2692), " +"[#2657](https://github.com/adap/flower/pull/2657), " +"[#2813](https://github.com/adap/flower/pull/2813), " +"[#2661](https://github.com/adap/flower/pull/2661), " +"[#2398](https://github.com/adap/flower/pull/2398))" msgstr "" #: ../../source/ref-changelog.md:232 -msgid "FjORD [#2431](https://github.com/adap/flower/pull/2431)" +msgid "" +"The Flower testing and development infrastructure has received " +"substantial updates. This makes Flower 1.7 the most tested release ever." msgstr "" #: ../../source/ref-changelog.md:234 -msgid "MOON [#2421](https://github.com/adap/flower/pull/2421)" +msgid "" +"**Update dependencies** " +"([#2753](https://github.com/adap/flower/pull/2753), " +"[#2651](https://github.com/adap/flower/pull/2651), " +"[#2739](https://github.com/adap/flower/pull/2739), " +"[#2837](https://github.com/adap/flower/pull/2837), " +"[#2788](https://github.com/adap/flower/pull/2788), " +"[#2811](https://github.com/adap/flower/pull/2811), " +"[#2774](https://github.com/adap/flower/pull/2774), " +"[#2790](https://github.com/adap/flower/pull/2790), " +"[#2751](https://github.com/adap/flower/pull/2751), " +"[#2850](https://github.com/adap/flower/pull/2850), " +"[#2812](https://github.com/adap/flower/pull/2812), " +"[#2872](https://github.com/adap/flower/pull/2872), " +"[#2736](https://github.com/adap/flower/pull/2736), " +"[#2756](https://github.com/adap/flower/pull/2756), " +"[#2857](https://github.com/adap/flower/pull/2857), " +"[#2757](https://github.com/adap/flower/pull/2757), " +"[#2810](https://github.com/adap/flower/pull/2810), " +"[#2740](https://github.com/adap/flower/pull/2740), " +"[#2789](https://github.com/adap/flower/pull/2789))" msgstr "" #: ../../source/ref-changelog.md:236 -msgid "DepthFL [#2295](https://github.com/adap/flower/pull/2295)" -msgstr "" - -#: ../../source/ref-changelog.md:238 -msgid "FedPer [#2266](https://github.com/adap/flower/pull/2266)" +msgid "" +"**General improvements** " +"([#2803](https://github.com/adap/flower/pull/2803), " +"[#2847](https://github.com/adap/flower/pull/2847), " +"[#2877](https://github.com/adap/flower/pull/2877), " +"[#2690](https://github.com/adap/flower/pull/2690), " +"[#2889](https://github.com/adap/flower/pull/2889), " +"[#2874](https://github.com/adap/flower/pull/2874), " +"[#2819](https://github.com/adap/flower/pull/2819), " +"[#2689](https://github.com/adap/flower/pull/2689), " +"[#2457](https://github.com/adap/flower/pull/2457), " +"[#2870](https://github.com/adap/flower/pull/2870), " +"[#2669](https://github.com/adap/flower/pull/2669), " +"[#2876](https://github.com/adap/flower/pull/2876), " +"[#2885](https://github.com/adap/flower/pull/2885), " +"[#2858](https://github.com/adap/flower/pull/2858), " +"[#2867](https://github.com/adap/flower/pull/2867), " +"[#2351](https://github.com/adap/flower/pull/2351), " +"[#2886](https://github.com/adap/flower/pull/2886), " +"[#2860](https://github.com/adap/flower/pull/2860), " +"[#2828](https://github.com/adap/flower/pull/2828), " +"[#2869](https://github.com/adap/flower/pull/2869), " +"[#2875](https://github.com/adap/flower/pull/2875), " +"[#2733](https://github.com/adap/flower/pull/2733), " +"[#2488](https://github.com/adap/flower/pull/2488), " +"[#2646](https://github.com/adap/flower/pull/2646), " +"[#2879](https://github.com/adap/flower/pull/2879), " +"[#2821](https://github.com/adap/flower/pull/2821), " +"[#2855](https://github.com/adap/flower/pull/2855), " +"[#2800](https://github.com/adap/flower/pull/2800), " +"[#2807](https://github.com/adap/flower/pull/2807), " +"[#2801](https://github.com/adap/flower/pull/2801), " +"[#2804](https://github.com/adap/flower/pull/2804), " +"[#2851](https://github.com/adap/flower/pull/2851), " +"[#2787](https://github.com/adap/flower/pull/2787), " +"[#2852](https://github.com/adap/flower/pull/2852), " +"[#2672](https://github.com/adap/flower/pull/2672), " +"[#2759](https://github.com/adap/flower/pull/2759))" msgstr "" #: ../../source/ref-changelog.md:240 -msgid "FedWav2vec [#2551](https://github.com/adap/flower/pull/2551)" +msgid "" +"**Deprecate** `start_numpy_client` " +"([#2563](https://github.com/adap/flower/pull/2563), " +"[#2718](https://github.com/adap/flower/pull/2718))" msgstr "" #: ../../source/ref-changelog.md:242 -msgid "niid-Bench [#2428](https://github.com/adap/flower/pull/2428)" +msgid "" +"Until now, clients of type `NumPyClient` needed to be started via " +"`start_numpy_client`. In our efforts to consolidate framework APIs, we " +"have introduced changes, and now all client types should start via " +"`start_client`. To continue using `NumPyClient` clients, you simply need " +"to first call the `.to_client()` method and then pass returned `Client` " +"object to `start_client`. The examples and the documentation have been " +"updated accordingly." msgstr "" #: ../../source/ref-changelog.md:244 msgid "" -"FedBN ([#2608](https://github.com/adap/flower/pull/2608), [#2615](https://" -"github.com/adap/flower/pull/2615))" +"**Deprecate legacy DP wrappers** " +"([#2749](https://github.com/adap/flower/pull/2749))" msgstr "" #: ../../source/ref-changelog.md:246 msgid "" -"**General updates to Flower Examples** ([#2384](https://github.com/adap/" -"flower/pull/2384), [#2425](https://github.com/adap/flower/pull/2425), [#2526]" -"(https://github.com/adap/flower/pull/2526), [#2302](https://github.com/adap/" -"flower/pull/2302), [#2545](https://github.com/adap/flower/pull/2545))" +"Legacy DP wrapper classes are deprecated, but still functional. This is " +"in preparation for an all-new pluggable version of differential privacy " +"support in Flower." msgstr "" #: ../../source/ref-changelog.md:248 msgid "" -"**General updates to Flower Baselines** ([#2301](https://github.com/adap/" -"flower/pull/2301), [#2305](https://github.com/adap/flower/pull/2305), [#2307]" -"(https://github.com/adap/flower/pull/2307), [#2327](https://github.com/adap/" -"flower/pull/2327), [#2435](https://github.com/adap/flower/pull/2435), [#2462]" -"(https://github.com/adap/flower/pull/2462), [#2463](https://github.com/adap/" -"flower/pull/2463), [#2461](https://github.com/adap/flower/pull/2461), [#2469]" -"(https://github.com/adap/flower/pull/2469), [#2466](https://github.com/adap/" -"flower/pull/2466), [#2471](https://github.com/adap/flower/pull/2471), [#2472]" -"(https://github.com/adap/flower/pull/2472), [#2470](https://github.com/adap/" -"flower/pull/2470))" +"**Make optional arg** `--callable` **in** `flower-client` **a required " +"positional arg** ([#2673](https://github.com/adap/flower/pull/2673))" msgstr "" #: ../../source/ref-changelog.md:250 msgid "" -"**General updates to the simulation engine** ([#2331](https://github.com/" -"adap/flower/pull/2331), [#2447](https://github.com/adap/flower/pull/2447), " -"[#2448](https://github.com/adap/flower/pull/2448), [#2294](https://github." -"com/adap/flower/pull/2294))" +"**Rename** `certificates` **to** `root_certificates` **in** `Driver` " +"([#2890](https://github.com/adap/flower/pull/2890))" msgstr "" #: ../../source/ref-changelog.md:252 msgid "" -"**General updates to Flower SDKs** ([#2288](https://github.com/adap/flower/" -"pull/2288), [#2429](https://github.com/adap/flower/pull/2429), [#2555]" -"(https://github.com/adap/flower/pull/2555), [#2543](https://github.com/adap/" -"flower/pull/2543), [#2544](https://github.com/adap/flower/pull/2544), [#2597]" -"(https://github.com/adap/flower/pull/2597), [#2623](https://github.com/adap/" -"flower/pull/2623))" +"**Drop experimental** `Task` **fields** " +"([#2866](https://github.com/adap/flower/pull/2866), " +"[#2865](https://github.com/adap/flower/pull/2865))" msgstr "" #: ../../source/ref-changelog.md:254 msgid "" -"**General improvements** ([#2309](https://github.com/adap/flower/pull/2309), " -"[#2310](https://github.com/adap/flower/pull/2310), [#2313](https://github." -"com/adap/flower/pull/2313), [#2316](https://github.com/adap/flower/" -"pull/2316), [#2317](https://github.com/adap/flower/pull/2317), [#2349]" -"(https://github.com/adap/flower/pull/2349), [#2360](https://github.com/adap/" -"flower/pull/2360), [#2402](https://github.com/adap/flower/pull/2402), [#2446]" -"(https://github.com/adap/flower/pull/2446), [#2561](https://github.com/adap/" -"flower/pull/2561), [#2273](https://github.com/adap/flower/pull/2273), [#2267]" -"(https://github.com/adap/flower/pull/2267), [#2274](https://github.com/adap/" -"flower/pull/2274), [#2275](https://github.com/adap/flower/pull/2275), [#2432]" -"(https://github.com/adap/flower/pull/2432), [#2251](https://github.com/adap/" -"flower/pull/2251), [#2321](https://github.com/adap/flower/pull/2321), [#1936]" -"(https://github.com/adap/flower/pull/1936), [#2408](https://github.com/adap/" -"flower/pull/2408), [#2413](https://github.com/adap/flower/pull/2413), [#2401]" -"(https://github.com/adap/flower/pull/2401), [#2531](https://github.com/adap/" -"flower/pull/2531), [#2534](https://github.com/adap/flower/pull/2534), [#2535]" -"(https://github.com/adap/flower/pull/2535), [#2521](https://github.com/adap/" -"flower/pull/2521), [#2553](https://github.com/adap/flower/pull/2553), [#2596]" -"(https://github.com/adap/flower/pull/2596))" -msgstr "" - -#: ../../source/ref-changelog.md:256 ../../source/ref-changelog.md:346 -#: ../../source/ref-changelog.md:410 ../../source/ref-changelog.md:464 -#: ../../source/ref-changelog.md:531 -msgid "" -"Flower received many improvements under the hood, too many to list here." +"Experimental fields `sa`, `legacy_server_message` and " +"`legacy_client_message` were removed from `Task` message. The removed " +"fields are superseded by the new `RecordSet` abstraction." msgstr "" -#: ../../source/ref-changelog.md:260 +#: ../../source/ref-changelog.md:256 msgid "" -"**Remove support for Python 3.7** ([#2280](https://github.com/adap/flower/" -"pull/2280), [#2299](https://github.com/adap/flower/pull/2299), [#2304]" -"(https://github.com/adap/flower/pull/2304), [#2306](https://github.com/adap/" -"flower/pull/2306), [#2355](https://github.com/adap/flower/pull/2355), [#2356]" -"(https://github.com/adap/flower/pull/2356))" +"**Retire MXNet examples** " +"([#2724](https://github.com/adap/flower/pull/2724))" msgstr "" -#: ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:258 msgid "" -"Python 3.7 support was deprecated in Flower 1.5, and this release removes " -"support. Flower now requires Python 3.8." +"The development of the MXNet fremework has ended and the project is now " +"[archived on GitHub](https://github.com/apache/mxnet). Existing MXNet " +"examples won't receive updates." msgstr "" -#: ../../source/ref-changelog.md:264 -msgid "" -"**Remove experimental argument** `rest` **from** `start_client` ([#2324]" -"(https://github.com/adap/flower/pull/2324))" +#: ../../source/ref-changelog.md:260 +msgid "v1.6.0 (2023-11-28)" msgstr "" #: ../../source/ref-changelog.md:266 msgid "" -"The (still experimental) argument `rest` was removed from `start_client` and " -"`start_numpy_client`. Use `transport=\"rest\"` to opt into the experimental " -"REST API instead." +"`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " +"`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " +"Mota`, `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`," +" `Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, " +"`Steve Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, " +"`cnxdeveloper`, `k3nfalt` " +msgstr "" + +#: ../../source/ref-changelog.md:270 +msgid "" +"**Add experimental support for Python 3.12** " +"([#2565](https://github.com/adap/flower/pull/2565))" msgstr "" -#: ../../source/ref-changelog.md:268 -msgid "v1.5.0 (2023-08-31)" +#: ../../source/ref-changelog.md:272 +msgid "" +"**Add new XGBoost examples** " +"([#2612](https://github.com/adap/flower/pull/2612), " +"[#2554](https://github.com/adap/flower/pull/2554), " +"[#2617](https://github.com/adap/flower/pull/2617), " +"[#2618](https://github.com/adap/flower/pull/2618), " +"[#2619](https://github.com/adap/flower/pull/2619), " +"[#2567](https://github.com/adap/flower/pull/2567))" msgstr "" #: ../../source/ref-changelog.md:274 msgid "" -"`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " -"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " -"Bertoli`, `Heng Pan`, `Javier`, `Mahdi`, `Steven Hé (Sīchàng)`, `Taner " -"Topal`, `achiverram28`, `danielnugraha`, `eunchung`, `ruthgal` " +"We have added a new `xgboost-quickstart` example alongside a new " +"`xgboost-comprehensive` example that goes more in-depth." +msgstr "" + +#: ../../source/ref-changelog.md:276 +msgid "" +"**Add Vertical FL example** " +"([#2598](https://github.com/adap/flower/pull/2598))" msgstr "" #: ../../source/ref-changelog.md:278 msgid "" -"**Introduce new simulation engine** ([#1969](https://github.com/adap/flower/" -"pull/1969), [#2221](https://github.com/adap/flower/pull/2221), [#2248]" -"(https://github.com/adap/flower/pull/2248))" +"We had many questions about Vertical Federated Learning using Flower, so " +"we decided to add an simple example for it on the [Titanic " +"dataset](https://www.kaggle.com/competitions/titanic/data) alongside a " +"tutorial (in the README)." msgstr "" #: ../../source/ref-changelog.md:280 msgid "" -"The new simulation engine has been rewritten from the ground up, yet it " -"remains fully backwards compatible. It offers much improved stability and " -"memory handling, especially when working with GPUs. Simulations " -"transparently adapt to different settings to scale simulation in CPU-only, " -"CPU+GPU, multi-GPU, or multi-node multi-GPU environments." +"**Support custom** `ClientManager` **in** `start_driver()` " +"([#2292](https://github.com/adap/flower/pull/2292))" msgstr "" #: ../../source/ref-changelog.md:282 msgid "" -"Comprehensive documentation includes a new [how-to run simulations](https://" -"flower.ai/docs/framework/how-to-run-simulations.html) guide, new [simulation-" -"pytorch](https://flower.ai/docs/examples/simulation-pytorch.html) and " -"[simulation-tensorflow](https://flower.ai/docs/examples/simulation-" -"tensorflow.html) notebooks, and a new [YouTube tutorial series](https://www." -"youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." +"**Update REST API to support create and delete nodes** " +"([#2283](https://github.com/adap/flower/pull/2283))" msgstr "" #: ../../source/ref-changelog.md:284 msgid "" -"**Restructure Flower Docs** ([#1824](https://github.com/adap/flower/" -"pull/1824), [#1865](https://github.com/adap/flower/pull/1865), [#1884]" -"(https://github.com/adap/flower/pull/1884), [#1887](https://github.com/adap/" -"flower/pull/1887), [#1919](https://github.com/adap/flower/pull/1919), [#1922]" -"(https://github.com/adap/flower/pull/1922), [#1920](https://github.com/adap/" -"flower/pull/1920), [#1923](https://github.com/adap/flower/pull/1923), [#1924]" -"(https://github.com/adap/flower/pull/1924), [#1962](https://github.com/adap/" -"flower/pull/1962), [#2006](https://github.com/adap/flower/pull/2006), [#2133]" -"(https://github.com/adap/flower/pull/2133), [#2203](https://github.com/adap/" -"flower/pull/2203), [#2215](https://github.com/adap/flower/pull/2215), [#2122]" -"(https://github.com/adap/flower/pull/2122), [#2223](https://github.com/adap/" -"flower/pull/2223), [#2219](https://github.com/adap/flower/pull/2219), [#2232]" -"(https://github.com/adap/flower/pull/2232), [#2233](https://github.com/adap/" -"flower/pull/2233), [#2234](https://github.com/adap/flower/pull/2234), [#2235]" -"(https://github.com/adap/flower/pull/2235), [#2237](https://github.com/adap/" -"flower/pull/2237), [#2238](https://github.com/adap/flower/pull/2238), [#2242]" -"(https://github.com/adap/flower/pull/2242), [#2231](https://github.com/adap/" -"flower/pull/2231), [#2243](https://github.com/adap/flower/pull/2243), [#2227]" -"(https://github.com/adap/flower/pull/2227))" +"**Update the Android SDK** " +"([#2187](https://github.com/adap/flower/pull/2187))" msgstr "" #: ../../source/ref-changelog.md:286 -msgid "" -"Much effort went into a completely restructured Flower docs experience. The " -"documentation on [flower.ai/docs](https://flower.ai/docs) is now divided " -"into Flower Framework, Flower Baselines, Flower Android SDK, Flower iOS SDK, " -"and code example projects." +msgid "Add gRPC request-response capability to the Android SDK." msgstr "" #: ../../source/ref-changelog.md:288 msgid "" -"**Introduce Flower Swift SDK** ([#1858](https://github.com/adap/flower/" -"pull/1858), [#1897](https://github.com/adap/flower/pull/1897))" +"**Update the C++ SDK** " +"([#2537](https://github.com/adap/flower/pull/2537), " +"[#2528](https://github.com/adap/flower/pull/2528), " +"[#2523](https://github.com/adap/flower/pull/2523), " +"[#2522](https://github.com/adap/flower/pull/2522))" msgstr "" #: ../../source/ref-changelog.md:290 -msgid "" -"This is the first preview release of the Flower Swift SDK. Flower support on " -"iOS is improving, and alongside the Swift SDK and code example, there is now " -"also an iOS quickstart tutorial." +msgid "Add gRPC request-response capability to the C++ SDK." msgstr "" #: ../../source/ref-changelog.md:292 msgid "" -"**Introduce Flower Android SDK** ([#2131](https://github.com/adap/flower/" -"pull/2131))" +"**Make HTTPS the new default** " +"([#2591](https://github.com/adap/flower/pull/2591), " +"[#2636](https://github.com/adap/flower/pull/2636))" msgstr "" #: ../../source/ref-changelog.md:294 msgid "" -"This is the first preview release of the Flower Kotlin SDK. Flower support " -"on Android is improving, and alongside the Kotlin SDK and code example, " -"there is now also an Android quickstart tutorial." +"Flower is moving to HTTPS by default. The new `flower-server` requires " +"passing `--certificates`, but users can enable `--insecure` to use HTTP " +"for prototyping. The same applies to `flower-client`, which can either " +"use user-provided credentials or gRPC-bundled certificates to connect to " +"an HTTPS-enabled server or requires opt-out via passing `--insecure` to " +"enable insecure HTTP connections." msgstr "" #: ../../source/ref-changelog.md:296 msgid "" -"**Introduce new end-to-end testing infrastructure** ([#1842](https://github." -"com/adap/flower/pull/1842), [#2071](https://github.com/adap/flower/" -"pull/2071), [#2072](https://github.com/adap/flower/pull/2072), [#2068]" -"(https://github.com/adap/flower/pull/2068), [#2067](https://github.com/adap/" -"flower/pull/2067), [#2069](https://github.com/adap/flower/pull/2069), [#2073]" -"(https://github.com/adap/flower/pull/2073), [#2070](https://github.com/adap/" -"flower/pull/2070), [#2074](https://github.com/adap/flower/pull/2074), [#2082]" -"(https://github.com/adap/flower/pull/2082), [#2084](https://github.com/adap/" -"flower/pull/2084), [#2093](https://github.com/adap/flower/pull/2093), [#2109]" -"(https://github.com/adap/flower/pull/2109), [#2095](https://github.com/adap/" -"flower/pull/2095), [#2140](https://github.com/adap/flower/pull/2140), [#2137]" -"(https://github.com/adap/flower/pull/2137), [#2165](https://github.com/adap/" -"flower/pull/2165))" +"For backward compatibility, `start_client()` and `start_numpy_client()` " +"will still start in insecure mode by default. In a future release, " +"insecure connections will require user opt-in by passing `insecure=True`." msgstr "" #: ../../source/ref-changelog.md:298 msgid "" -"A new testing infrastructure ensures that new changes stay compatible with " -"existing framework integrations or strategies." +"**Unify client API** ([#2303](https://github.com/adap/flower/pull/2303), " +"[#2390](https://github.com/adap/flower/pull/2390), " +"[#2493](https://github.com/adap/flower/pull/2493))" msgstr "" #: ../../source/ref-changelog.md:300 -msgid "**Deprecate Python 3.7**" +msgid "" +"Using the `client_fn`, Flower clients can interchangeably run as " +"standalone processes (i.e. via `start_client`) or in simulation (i.e. via" +" `start_simulation`) without requiring changes to how the client class is" +" defined and instantiated. The `to_client()` function is introduced to " +"convert a `NumPyClient` to a `Client`." msgstr "" #: ../../source/ref-changelog.md:302 msgid "" -"Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for " -"Python 3.7 is now deprecated and will be removed in an upcoming release." +"**Add new** `Bulyan` **strategy** " +"([#1817](https://github.com/adap/flower/pull/1817), " +"[#1891](https://github.com/adap/flower/pull/1891))" msgstr "" #: ../../source/ref-changelog.md:304 msgid "" -"**Add new** `FedTrimmedAvg` **strategy** ([#1769](https://github.com/adap/" -"flower/pull/1769), [#1853](https://github.com/adap/flower/pull/1853))" +"The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., " +"2018](https://arxiv.org/abs/1802.07927)" msgstr "" #: ../../source/ref-changelog.md:306 msgid "" -"The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, 2018]" -"(https://arxiv.org/abs/1803.01498)." +"**Add new** `XGB Bagging` **strategy** " +"([#2611](https://github.com/adap/flower/pull/2611))" msgstr "" -#: ../../source/ref-changelog.md:308 +#: ../../source/ref-changelog.md:308 ../../source/ref-changelog.md:310 msgid "" -"**Introduce start_driver** ([#1697](https://github.com/adap/flower/" -"pull/1697))" -msgstr "" - -#: ../../source/ref-changelog.md:310 -msgid "" -"In addition to `start_server` and using the raw Driver API, there is a new " -"`start_driver` function that allows for running `start_server` scripts as a " -"Flower driver with only a single-line code change. Check out the `mt-" -"pytorch` code example to see a working example using `start_driver`." -msgstr "" - -#: ../../source/ref-changelog.md:312 -msgid "" -"**Add parameter aggregation to** `mt-pytorch` **code example** ([#1785]" -"(https://github.com/adap/flower/pull/1785))" +"**Introduce `WorkloadState`** " +"([#2564](https://github.com/adap/flower/pull/2564), " +"[#2632](https://github.com/adap/flower/pull/2632))" msgstr "" #: ../../source/ref-changelog.md:314 msgid "" -"The `mt-pytorch` example shows how to aggregate parameters when writing a " -"driver script. The included `driver.py` and `server.py` have been aligned to " -"demonstrate both the low-level way and the high-level way of building server-" -"side logic." +"FedProx ([#2210](https://github.com/adap/flower/pull/2210), " +"[#2286](https://github.com/adap/flower/pull/2286), " +"[#2509](https://github.com/adap/flower/pull/2509))" msgstr "" #: ../../source/ref-changelog.md:316 msgid "" -"**Migrate experimental REST API to Starlette** ([2171](https://github.com/" -"adap/flower/pull/2171))" +"Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), " +"[#2400](https://github.com/adap/flower/pull/2400))" msgstr "" #: ../../source/ref-changelog.md:318 msgid "" -"The (experimental) REST API used to be implemented in [FastAPI](https://" -"fastapi.tiangolo.com/), but it has now been migrated to use [Starlette]" -"(https://www.starlette.io/) directly." +"FedMLB ([#2340](https://github.com/adap/flower/pull/2340), " +"[#2507](https://github.com/adap/flower/pull/2507))" msgstr "" #: ../../source/ref-changelog.md:320 msgid "" -"Please note: The REST request-response API is still experimental and will " -"likely change significantly over time." +"TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " +"[#2508](https://github.com/adap/flower/pull/2508))" msgstr "" #: ../../source/ref-changelog.md:322 -msgid "" -"**Introduce experimental gRPC request-response API** ([#1867](https://github." -"com/adap/flower/pull/1867), [#1901](https://github.com/adap/flower/" -"pull/1901))" +msgid "FedMeta [#2438](https://github.com/adap/flower/pull/2438)" msgstr "" #: ../../source/ref-changelog.md:324 -msgid "" -"In addition to the existing gRPC API (based on bidirectional streaming) and " -"the experimental REST API, there is now a new gRPC API that uses a request-" -"response model to communicate with client nodes." +msgid "FjORD [#2431](https://github.com/adap/flower/pull/2431)" msgstr "" #: ../../source/ref-changelog.md:326 -msgid "" -"Please note: The gRPC request-response API is still experimental and will " -"likely change significantly over time." +msgid "MOON [#2421](https://github.com/adap/flower/pull/2421)" msgstr "" #: ../../source/ref-changelog.md:328 -msgid "" -"**Replace the experimental** `start_client(rest=True)` **with the new** " -"`start_client(transport=\"rest\")` ([#1880](https://github.com/adap/flower/" -"pull/1880))" +msgid "DepthFL [#2295](https://github.com/adap/flower/pull/2295)" msgstr "" #: ../../source/ref-changelog.md:330 -msgid "" -"The (experimental) `start_client` argument `rest` was deprecated in favour " -"of a new argument `transport`. `start_client(transport=\"rest\")` will yield " -"the same behaviour as `start_client(rest=True)` did before. All code should " -"migrate to the new argument `transport`. The deprecated argument `rest` will " -"be removed in a future release." +msgid "FedPer [#2266](https://github.com/adap/flower/pull/2266)" msgstr "" #: ../../source/ref-changelog.md:332 -msgid "" -"**Add a new gRPC option** ([#2197](https://github.com/adap/flower/pull/2197))" +msgid "FedWav2vec [#2551](https://github.com/adap/flower/pull/2551)" msgstr "" #: ../../source/ref-changelog.md:334 -msgid "" -"We now start a gRPC server with the `grpc.keepalive_permit_without_calls` " -"option set to 0 by default. This prevents the clients from sending keepalive " -"pings when there is no outstanding stream." +msgid "niid-Bench [#2428](https://github.com/adap/flower/pull/2428)" msgstr "" #: ../../source/ref-changelog.md:336 msgid "" -"**Improve example notebooks** ([#2005](https://github.com/adap/flower/" -"pull/2005))" +"FedBN ([#2608](https://github.com/adap/flower/pull/2608), " +"[#2615](https://github.com/adap/flower/pull/2615))" msgstr "" #: ../../source/ref-changelog.md:338 -msgid "There's a new 30min Federated Learning PyTorch tutorial!" +msgid "" +"**General updates to Flower Examples** " +"([#2384](https://github.com/adap/flower/pull/2384), " +"[#2425](https://github.com/adap/flower/pull/2425), " +"[#2526](https://github.com/adap/flower/pull/2526), " +"[#2302](https://github.com/adap/flower/pull/2302), " +"[#2545](https://github.com/adap/flower/pull/2545))" msgstr "" #: ../../source/ref-changelog.md:340 msgid "" -"**Example updates** ([#1772](https://github.com/adap/flower/pull/1772), " -"[#1873](https://github.com/adap/flower/pull/1873), [#1981](https://github." -"com/adap/flower/pull/1981), [#1988](https://github.com/adap/flower/" -"pull/1988), [#1984](https://github.com/adap/flower/pull/1984), [#1982]" -"(https://github.com/adap/flower/pull/1982), [#2112](https://github.com/adap/" -"flower/pull/2112), [#2144](https://github.com/adap/flower/pull/2144), [#2174]" -"(https://github.com/adap/flower/pull/2174), [#2225](https://github.com/adap/" -"flower/pull/2225), [#2183](https://github.com/adap/flower/pull/2183))" +"**General updates to Flower Baselines** " +"([#2301](https://github.com/adap/flower/pull/2301), " +"[#2305](https://github.com/adap/flower/pull/2305), " +"[#2307](https://github.com/adap/flower/pull/2307), " +"[#2327](https://github.com/adap/flower/pull/2327), " +"[#2435](https://github.com/adap/flower/pull/2435), " +"[#2462](https://github.com/adap/flower/pull/2462), " +"[#2463](https://github.com/adap/flower/pull/2463), " +"[#2461](https://github.com/adap/flower/pull/2461), " +"[#2469](https://github.com/adap/flower/pull/2469), " +"[#2466](https://github.com/adap/flower/pull/2466), " +"[#2471](https://github.com/adap/flower/pull/2471), " +"[#2472](https://github.com/adap/flower/pull/2472), " +"[#2470](https://github.com/adap/flower/pull/2470))" msgstr "" #: ../../source/ref-changelog.md:342 msgid "" -"Many examples have received significant updates, including simplified " -"advanced-tensorflow and advanced-pytorch examples, improved macOS " -"compatibility of TensorFlow examples, and code examples for simulation. A " -"major upgrade is that all code examples now have a `requirements.txt` (in " -"addition to `pyproject.toml`)." +"**General updates to the simulation engine** " +"([#2331](https://github.com/adap/flower/pull/2331), " +"[#2447](https://github.com/adap/flower/pull/2447), " +"[#2448](https://github.com/adap/flower/pull/2448), " +"[#2294](https://github.com/adap/flower/pull/2294))" msgstr "" #: ../../source/ref-changelog.md:344 msgid "" -"**General improvements** ([#1872](https://github.com/adap/flower/pull/1872), " -"[#1866](https://github.com/adap/flower/pull/1866), [#1884](https://github." -"com/adap/flower/pull/1884), [#1837](https://github.com/adap/flower/" -"pull/1837), [#1477](https://github.com/adap/flower/pull/1477), [#2171]" -"(https://github.com/adap/flower/pull/2171))" +"**General updates to Flower SDKs** " +"([#2288](https://github.com/adap/flower/pull/2288), " +"[#2429](https://github.com/adap/flower/pull/2429), " +"[#2555](https://github.com/adap/flower/pull/2555), " +"[#2543](https://github.com/adap/flower/pull/2543), " +"[#2544](https://github.com/adap/flower/pull/2544), " +"[#2597](https://github.com/adap/flower/pull/2597), " +"[#2623](https://github.com/adap/flower/pull/2623))" +msgstr "" + +#: ../../source/ref-changelog.md:346 +msgid "" +"**General improvements** " +"([#2309](https://github.com/adap/flower/pull/2309), " +"[#2310](https://github.com/adap/flower/pull/2310), " +"[#2313](https://github.com/adap/flower/pull/2313), " +"[#2316](https://github.com/adap/flower/pull/2316), " +"[#2317](https://github.com/adap/flower/pull/2317), " +"[#2349](https://github.com/adap/flower/pull/2349), " +"[#2360](https://github.com/adap/flower/pull/2360), " +"[#2402](https://github.com/adap/flower/pull/2402), " +"[#2446](https://github.com/adap/flower/pull/2446), " +"[#2561](https://github.com/adap/flower/pull/2561), " +"[#2273](https://github.com/adap/flower/pull/2273), " +"[#2267](https://github.com/adap/flower/pull/2267), " +"[#2274](https://github.com/adap/flower/pull/2274), " +"[#2275](https://github.com/adap/flower/pull/2275), " +"[#2432](https://github.com/adap/flower/pull/2432), " +"[#2251](https://github.com/adap/flower/pull/2251), " +"[#2321](https://github.com/adap/flower/pull/2321), " +"[#1936](https://github.com/adap/flower/pull/1936), " +"[#2408](https://github.com/adap/flower/pull/2408), " +"[#2413](https://github.com/adap/flower/pull/2413), " +"[#2401](https://github.com/adap/flower/pull/2401), " +"[#2531](https://github.com/adap/flower/pull/2531), " +"[#2534](https://github.com/adap/flower/pull/2534), " +"[#2535](https://github.com/adap/flower/pull/2535), " +"[#2521](https://github.com/adap/flower/pull/2521), " +"[#2553](https://github.com/adap/flower/pull/2553), " +"[#2596](https://github.com/adap/flower/pull/2596))" +msgstr "" + +#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:502 ../../source/ref-changelog.md:556 +#: ../../source/ref-changelog.md:623 +msgid "Flower received many improvements under the hood, too many to list here." msgstr "" #: ../../source/ref-changelog.md:352 -msgid "v1.4.0 (2023-04-21)" +msgid "" +"**Remove support for Python 3.7** " +"([#2280](https://github.com/adap/flower/pull/2280), " +"[#2299](https://github.com/adap/flower/pull/2299), " +"[#2304](https://github.com/adap/flower/pull/2304), " +"[#2306](https://github.com/adap/flower/pull/2306), " +"[#2355](https://github.com/adap/flower/pull/2355), " +"[#2356](https://github.com/adap/flower/pull/2356))" msgstr "" -#: ../../source/ref-changelog.md:358 +#: ../../source/ref-changelog.md:354 msgid "" -"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " -"`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, `Iacob-" -"Alexandru-Andrei`, `JDRanpariya`, `Jean Charle Yaacoub`, `Kunal Sarkhel`, " -"`L. Jiang`, `Lennart Behme`, `Max Kapsecker`, `Michał`, `Nic Lane`, " -"`Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, `Steve " -"Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" +"Python 3.7 support was deprecated in Flower 1.5, and this release removes" +" support. Flower now requires Python 3.8." msgstr "" -#: ../../source/ref-changelog.md:362 +#: ../../source/ref-changelog.md:356 msgid "" -"**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and example)** " -"([#1694](https://github.com/adap/flower/pull/1694), [#1709](https://github." -"com/adap/flower/pull/1709), [#1715](https://github.com/adap/flower/" -"pull/1715), [#1717](https://github.com/adap/flower/pull/1717), [#1763]" -"(https://github.com/adap/flower/pull/1763), [#1795](https://github.com/adap/" -"flower/pull/1795))" +"**Remove experimental argument** `rest` **from** `start_client` " +"([#2324](https://github.com/adap/flower/pull/2324))" msgstr "" -#: ../../source/ref-changelog.md:364 +#: ../../source/ref-changelog.md:358 msgid "" -"XGBoost is a tree-based ensemble machine learning algorithm that uses " -"gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg` " -"[strategy](https://github.com/adap/flower/tree/main/src/py/flwr/server/" -"strategy/fedxgb_nn_avg.py), and a [code example](https://github.com/adap/" -"flower/tree/main/examples/xgboost-quickstart) that demonstrates the usage of " -"this new strategy in an XGBoost project." +"The (still experimental) argument `rest` was removed from `start_client` " +"and `start_numpy_client`. Use `transport=\"rest\"` to opt into the " +"experimental REST API instead." msgstr "" -#: ../../source/ref-changelog.md:366 -msgid "" -"**Introduce iOS SDK (preview)** ([#1621](https://github.com/adap/flower/" -"pull/1621), [#1764](https://github.com/adap/flower/pull/1764))" +#: ../../source/ref-changelog.md:360 +msgid "v1.5.0 (2023-08-31)" msgstr "" -#: ../../source/ref-changelog.md:368 +#: ../../source/ref-changelog.md:366 msgid "" -"This is a major update for anyone wanting to implement Federated Learning on " -"iOS mobile devices. We now have a swift iOS SDK present under [src/swift/" -"flwr](https://github.com/adap/flower/tree/main/src/swift/flwr) that will " -"facilitate greatly the app creating process. To showcase its use, the [iOS " -"example](https://github.com/adap/flower/tree/main/examples/ios) has also " -"been updated!" +"`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " +"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " +"Bertoli`, `Heng Pan`, `Javier`, `Mahdi`, `Steven Hé (Sīchàng)`, `Taner " +"Topal`, `achiverram28`, `danielnugraha`, `eunchung`, `ruthgal` " msgstr "" #: ../../source/ref-changelog.md:370 msgid "" -"**Introduce new \"What is Federated Learning?\" tutorial** ([#1657](https://" -"github.com/adap/flower/pull/1657), [#1721](https://github.com/adap/flower/" -"pull/1721))" +"**Introduce new simulation engine** " +"([#1969](https://github.com/adap/flower/pull/1969), " +"[#2221](https://github.com/adap/flower/pull/2221), " +"[#2248](https://github.com/adap/flower/pull/2248))" msgstr "" #: ../../source/ref-changelog.md:372 msgid "" -"A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-what-" -"is-federated-learning.html) in our documentation explains the basics of " -"Fedetated Learning. It enables anyone who's unfamiliar with Federated " -"Learning to start their journey with Flower. Forward it to anyone who's " -"interested in Federated Learning!" +"The new simulation engine has been rewritten from the ground up, yet it " +"remains fully backwards compatible. It offers much improved stability and" +" memory handling, especially when working with GPUs. Simulations " +"transparently adapt to different settings to scale simulation in CPU-" +"only, CPU+GPU, multi-GPU, or multi-node multi-GPU environments." msgstr "" #: ../../source/ref-changelog.md:374 msgid "" -"**Introduce new Flower Baseline: FedProx MNIST** ([#1513](https://github.com/" -"adap/flower/pull/1513), [#1680](https://github.com/adap/flower/pull/1680), " -"[#1681](https://github.com/adap/flower/pull/1681), [#1679](https://github." -"com/adap/flower/pull/1679))" +"Comprehensive documentation includes a new [how-to run " +"simulations](https://flower.ai/docs/framework/how-to-run-" +"simulations.html) guide, new [simulation-" +"pytorch](https://flower.ai/docs/examples/simulation-pytorch.html) and " +"[simulation-tensorflow](https://flower.ai/docs/examples/simulation-" +"tensorflow.html) notebooks, and a new [YouTube tutorial " +"series](https://www.youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." msgstr "" #: ../../source/ref-changelog.md:376 msgid "" -"This new baseline replicates the MNIST+CNN task from the paper [Federated " -"Optimization in Heterogeneous Networks (Li et al., 2018)](https://arxiv.org/" -"abs/1812.06127). It uses the `FedProx` strategy, which aims at making " -"convergence more robust in heterogeneous settings." +"**Restructure Flower Docs** " +"([#1824](https://github.com/adap/flower/pull/1824), " +"[#1865](https://github.com/adap/flower/pull/1865), " +"[#1884](https://github.com/adap/flower/pull/1884), " +"[#1887](https://github.com/adap/flower/pull/1887), " +"[#1919](https://github.com/adap/flower/pull/1919), " +"[#1922](https://github.com/adap/flower/pull/1922), " +"[#1920](https://github.com/adap/flower/pull/1920), " +"[#1923](https://github.com/adap/flower/pull/1923), " +"[#1924](https://github.com/adap/flower/pull/1924), " +"[#1962](https://github.com/adap/flower/pull/1962), " +"[#2006](https://github.com/adap/flower/pull/2006), " +"[#2133](https://github.com/adap/flower/pull/2133), " +"[#2203](https://github.com/adap/flower/pull/2203), " +"[#2215](https://github.com/adap/flower/pull/2215), " +"[#2122](https://github.com/adap/flower/pull/2122), " +"[#2223](https://github.com/adap/flower/pull/2223), " +"[#2219](https://github.com/adap/flower/pull/2219), " +"[#2232](https://github.com/adap/flower/pull/2232), " +"[#2233](https://github.com/adap/flower/pull/2233), " +"[#2234](https://github.com/adap/flower/pull/2234), " +"[#2235](https://github.com/adap/flower/pull/2235), " +"[#2237](https://github.com/adap/flower/pull/2237), " +"[#2238](https://github.com/adap/flower/pull/2238), " +"[#2242](https://github.com/adap/flower/pull/2242), " +"[#2231](https://github.com/adap/flower/pull/2231), " +"[#2243](https://github.com/adap/flower/pull/2243), " +"[#2227](https://github.com/adap/flower/pull/2227))" msgstr "" #: ../../source/ref-changelog.md:378 msgid "" -"**Introduce new Flower Baseline: FedAvg FEMNIST** ([#1655](https://github." -"com/adap/flower/pull/1655))" +"Much effort went into a completely restructured Flower docs experience. " +"The documentation on [flower.ai/docs](https://flower.ai/docs) is now " +"divided into Flower Framework, Flower Baselines, Flower Android SDK, " +"Flower iOS SDK, and code example projects." msgstr "" #: ../../source/ref-changelog.md:380 msgid "" -"This new baseline replicates an experiment evaluating the performance of the " -"FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A Benchmark " -"for Federated Settings (Caldas et al., 2018)](https://arxiv.org/" -"abs/1812.01097)." +"**Introduce Flower Swift SDK** " +"([#1858](https://github.com/adap/flower/pull/1858), " +"[#1897](https://github.com/adap/flower/pull/1897))" msgstr "" #: ../../source/ref-changelog.md:382 msgid "" -"**Introduce (experimental) REST API** ([#1594](https://github.com/adap/" -"flower/pull/1594), [#1690](https://github.com/adap/flower/pull/1690), [#1695]" -"(https://github.com/adap/flower/pull/1695), [#1712](https://github.com/adap/" -"flower/pull/1712), [#1802](https://github.com/adap/flower/pull/1802), [#1770]" -"(https://github.com/adap/flower/pull/1770), [#1733](https://github.com/adap/" -"flower/pull/1733))" +"This is the first preview release of the Flower Swift SDK. Flower support" +" on iOS is improving, and alongside the Swift SDK and code example, there" +" is now also an iOS quickstart tutorial." msgstr "" #: ../../source/ref-changelog.md:384 msgid "" -"A new REST API has been introduced as an alternative to the gRPC-based " -"communication stack. In this initial version, the REST API only supports " -"anonymous clients." +"**Introduce Flower Android SDK** " +"([#2131](https://github.com/adap/flower/pull/2131))" msgstr "" #: ../../source/ref-changelog.md:386 msgid "" -"Please note: The REST API is still experimental and will likely change " -"significantly over time." +"This is the first preview release of the Flower Kotlin SDK. Flower " +"support on Android is improving, and alongside the Kotlin SDK and code " +"example, there is now also an Android quickstart tutorial." msgstr "" #: ../../source/ref-changelog.md:388 msgid "" -"**Improve the (experimental) Driver API** ([#1663](https://github.com/adap/" -"flower/pull/1663), [#1666](https://github.com/adap/flower/pull/1666), [#1667]" -"(https://github.com/adap/flower/pull/1667), [#1664](https://github.com/adap/" -"flower/pull/1664), [#1675](https://github.com/adap/flower/pull/1675), [#1676]" -"(https://github.com/adap/flower/pull/1676), [#1693](https://github.com/adap/" -"flower/pull/1693), [#1662](https://github.com/adap/flower/pull/1662), [#1794]" -"(https://github.com/adap/flower/pull/1794))" +"**Introduce new end-to-end testing infrastructure** " +"([#1842](https://github.com/adap/flower/pull/1842), " +"[#2071](https://github.com/adap/flower/pull/2071), " +"[#2072](https://github.com/adap/flower/pull/2072), " +"[#2068](https://github.com/adap/flower/pull/2068), " +"[#2067](https://github.com/adap/flower/pull/2067), " +"[#2069](https://github.com/adap/flower/pull/2069), " +"[#2073](https://github.com/adap/flower/pull/2073), " +"[#2070](https://github.com/adap/flower/pull/2070), " +"[#2074](https://github.com/adap/flower/pull/2074), " +"[#2082](https://github.com/adap/flower/pull/2082), " +"[#2084](https://github.com/adap/flower/pull/2084), " +"[#2093](https://github.com/adap/flower/pull/2093), " +"[#2109](https://github.com/adap/flower/pull/2109), " +"[#2095](https://github.com/adap/flower/pull/2095), " +"[#2140](https://github.com/adap/flower/pull/2140), " +"[#2137](https://github.com/adap/flower/pull/2137), " +"[#2165](https://github.com/adap/flower/pull/2165))" msgstr "" #: ../../source/ref-changelog.md:390 msgid "" -"The Driver API is still an experimental feature, but this release introduces " -"some major upgrades. One of the main improvements is the introduction of an " -"SQLite database to store server state on disk (instead of in-memory). " -"Another improvement is that tasks (instructions or results) that have been " -"delivered will now be deleted. This greatly improves the memory efficiency " -"of a long-running Flower server." +"A new testing infrastructure ensures that new changes stay compatible " +"with existing framework integrations or strategies." msgstr "" #: ../../source/ref-changelog.md:392 -msgid "" -"**Fix spilling issues related to Ray during simulations** ([#1698](https://" -"github.com/adap/flower/pull/1698))" +msgid "**Deprecate Python 3.7**" msgstr "" #: ../../source/ref-changelog.md:394 msgid "" -"While running long simulations, `ray` was sometimes spilling huge amounts of " -"data that would make the training unable to continue. This is now fixed! 🎉" +"Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for" +" Python 3.7 is now deprecated and will be removed in an upcoming release." msgstr "" #: ../../source/ref-changelog.md:396 msgid "" -"**Add new example using** `TabNet` **and Flower** ([#1725](https://github." -"com/adap/flower/pull/1725))" +"**Add new** `FedTrimmedAvg` **strategy** " +"([#1769](https://github.com/adap/flower/pull/1769), " +"[#1853](https://github.com/adap/flower/pull/1853))" msgstr "" #: ../../source/ref-changelog.md:398 msgid "" -"TabNet is a powerful and flexible framework for training machine learning " -"models on tabular data. We now have a federated example using Flower: " -"[quickstart-tabnet](https://github.com/adap/flower/tree/main/examples/" -"quickstart-tabnet)." +"The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, " +"2018](https://arxiv.org/abs/1803.01498)." msgstr "" #: ../../source/ref-changelog.md:400 msgid "" -"**Add new how-to guide for monitoring simulations** ([#1649](https://github." -"com/adap/flower/pull/1649))" +"**Introduce start_driver** " +"([#1697](https://github.com/adap/flower/pull/1697))" msgstr "" #: ../../source/ref-changelog.md:402 msgid "" -"We now have a documentation guide to help users monitor their performance " -"during simulations." +"In addition to `start_server` and using the raw Driver API, there is a " +"new `start_driver` function that allows for running `start_server` " +"scripts as a Flower driver with only a single-line code change. Check out" +" the `mt-pytorch` code example to see a working example using " +"`start_driver`." msgstr "" #: ../../source/ref-changelog.md:404 msgid "" -"**Add training metrics to** `History` **object during simulations** ([#1696]" -"(https://github.com/adap/flower/pull/1696))" +"**Add parameter aggregation to** `mt-pytorch` **code example** " +"([#1785](https://github.com/adap/flower/pull/1785))" msgstr "" #: ../../source/ref-changelog.md:406 msgid "" -"The `fit_metrics_aggregation_fn` can be used to aggregate training metrics, " -"but previous releases did not save the results in the `History` object. This " -"is now the case!" +"The `mt-pytorch` example shows how to aggregate parameters when writing a" +" driver script. The included `driver.py` and `server.py` have been " +"aligned to demonstrate both the low-level way and the high-level way of " +"building server-side logic." msgstr "" #: ../../source/ref-changelog.md:408 msgid "" -"**General improvements** ([#1659](https://github.com/adap/flower/pull/1659), " -"[#1646](https://github.com/adap/flower/pull/1646), [#1647](https://github." -"com/adap/flower/pull/1647), [#1471](https://github.com/adap/flower/" -"pull/1471), [#1648](https://github.com/adap/flower/pull/1648), [#1651]" -"(https://github.com/adap/flower/pull/1651), [#1652](https://github.com/adap/" -"flower/pull/1652), [#1653](https://github.com/adap/flower/pull/1653), [#1659]" -"(https://github.com/adap/flower/pull/1659), [#1665](https://github.com/adap/" -"flower/pull/1665), [#1670](https://github.com/adap/flower/pull/1670), [#1672]" -"(https://github.com/adap/flower/pull/1672), [#1677](https://github.com/adap/" -"flower/pull/1677), [#1684](https://github.com/adap/flower/pull/1684), [#1683]" -"(https://github.com/adap/flower/pull/1683), [#1686](https://github.com/adap/" -"flower/pull/1686), [#1682](https://github.com/adap/flower/pull/1682), [#1685]" -"(https://github.com/adap/flower/pull/1685), [#1692](https://github.com/adap/" -"flower/pull/1692), [#1705](https://github.com/adap/flower/pull/1705), [#1708]" -"(https://github.com/adap/flower/pull/1708), [#1711](https://github.com/adap/" -"flower/pull/1711), [#1713](https://github.com/adap/flower/pull/1713), [#1714]" -"(https://github.com/adap/flower/pull/1714), [#1718](https://github.com/adap/" -"flower/pull/1718), [#1716](https://github.com/adap/flower/pull/1716), [#1723]" -"(https://github.com/adap/flower/pull/1723), [#1735](https://github.com/adap/" -"flower/pull/1735), [#1678](https://github.com/adap/flower/pull/1678), [#1750]" -"(https://github.com/adap/flower/pull/1750), [#1753](https://github.com/adap/" -"flower/pull/1753), [#1736](https://github.com/adap/flower/pull/1736), [#1766]" -"(https://github.com/adap/flower/pull/1766), [#1760](https://github.com/adap/" -"flower/pull/1760), [#1775](https://github.com/adap/flower/pull/1775), [#1776]" -"(https://github.com/adap/flower/pull/1776), [#1777](https://github.com/adap/" -"flower/pull/1777), [#1779](https://github.com/adap/flower/pull/1779), [#1784]" -"(https://github.com/adap/flower/pull/1784), [#1773](https://github.com/adap/" -"flower/pull/1773), [#1755](https://github.com/adap/flower/pull/1755), [#1789]" -"(https://github.com/adap/flower/pull/1789), [#1788](https://github.com/adap/" -"flower/pull/1788), [#1798](https://github.com/adap/flower/pull/1798), [#1799]" -"(https://github.com/adap/flower/pull/1799), [#1739](https://github.com/adap/" -"flower/pull/1739), [#1800](https://github.com/adap/flower/pull/1800), [#1804]" -"(https://github.com/adap/flower/pull/1804), [#1805](https://github.com/adap/" -"flower/pull/1805))" +"**Migrate experimental REST API to Starlette** " +"([2171](https://github.com/adap/flower/pull/2171))" +msgstr "" + +#: ../../source/ref-changelog.md:410 +msgid "" +"The (experimental) REST API used to be implemented in " +"[FastAPI](https://fastapi.tiangolo.com/), but it has now been migrated to" +" use [Starlette](https://www.starlette.io/) directly." +msgstr "" + +#: ../../source/ref-changelog.md:412 +msgid "" +"Please note: The REST request-response API is still experimental and will" +" likely change significantly over time." +msgstr "" + +#: ../../source/ref-changelog.md:414 +msgid "" +"**Introduce experimental gRPC request-response API** " +"([#1867](https://github.com/adap/flower/pull/1867), " +"[#1901](https://github.com/adap/flower/pull/1901))" msgstr "" #: ../../source/ref-changelog.md:416 -msgid "v1.3.0 (2023-02-06)" +msgid "" +"In addition to the existing gRPC API (based on bidirectional streaming) " +"and the experimental REST API, there is now a new gRPC API that uses a " +"request-response model to communicate with client nodes." +msgstr "" + +#: ../../source/ref-changelog.md:418 +msgid "" +"Please note: The gRPC request-response API is still experimental and will" +" likely change significantly over time." +msgstr "" + +#: ../../source/ref-changelog.md:420 +msgid "" +"**Replace the experimental** `start_client(rest=True)` **with the new** " +"`start_client(transport=\"rest\")` " +"([#1880](https://github.com/adap/flower/pull/1880))" msgstr "" #: ../../source/ref-changelog.md:422 msgid "" -"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, `Daniel " -"J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" +"The (experimental) `start_client` argument `rest` was deprecated in " +"favour of a new argument `transport`. `start_client(transport=\"rest\")` " +"will yield the same behaviour as `start_client(rest=True)` did before. " +"All code should migrate to the new argument `transport`. The deprecated " +"argument `rest` will be removed in a future release." +msgstr "" + +#: ../../source/ref-changelog.md:424 +msgid "" +"**Add a new gRPC option** " +"([#2197](https://github.com/adap/flower/pull/2197))" msgstr "" #: ../../source/ref-changelog.md:426 msgid "" -"**Add support for** `workload_id` **and** `group_id` **in Driver API** " -"([#1595](https://github.com/adap/flower/pull/1595))" +"We now start a gRPC server with the `grpc.keepalive_permit_without_calls`" +" option set to 0 by default. This prevents the clients from sending " +"keepalive pings when there is no outstanding stream." msgstr "" #: ../../source/ref-changelog.md:428 msgid "" -"The (experimental) Driver API now supports a `workload_id` that can be used " -"to identify which workload a task belongs to. It also supports a new " -"`group_id` that can be used, for example, to indicate the current training " -"round. Both the `workload_id` and `group_id` enable client nodes to decide " -"whether they want to handle a task or not." +"**Improve example notebooks** " +"([#2005](https://github.com/adap/flower/pull/2005))" msgstr "" #: ../../source/ref-changelog.md:430 -msgid "" -"**Make Driver API and Fleet API address configurable** ([#1637](https://" -"github.com/adap/flower/pull/1637))" +msgid "There's a new 30min Federated Learning PyTorch tutorial!" msgstr "" #: ../../source/ref-changelog.md:432 msgid "" -"The (experimental) long-running Flower server (Driver API and Fleet API) can " -"now configure the server address of both Driver API (via `--driver-api-" -"address`) and Fleet API (via `--fleet-api-address`) when starting:" +"**Example updates** ([#1772](https://github.com/adap/flower/pull/1772), " +"[#1873](https://github.com/adap/flower/pull/1873), " +"[#1981](https://github.com/adap/flower/pull/1981), " +"[#1988](https://github.com/adap/flower/pull/1988), " +"[#1984](https://github.com/adap/flower/pull/1984), " +"[#1982](https://github.com/adap/flower/pull/1982), " +"[#2112](https://github.com/adap/flower/pull/2112), " +"[#2144](https://github.com/adap/flower/pull/2144), " +"[#2174](https://github.com/adap/flower/pull/2174), " +"[#2225](https://github.com/adap/flower/pull/2225), " +"[#2183](https://github.com/adap/flower/pull/2183))" msgstr "" #: ../../source/ref-changelog.md:434 msgid "" +"Many examples have received significant updates, including simplified " +"advanced-tensorflow and advanced-pytorch examples, improved macOS " +"compatibility of TensorFlow examples, and code examples for simulation. A" +" major upgrade is that all code examples now have a `requirements.txt` " +"(in addition to `pyproject.toml`)." +msgstr "" + +#: ../../source/ref-changelog.md:436 +msgid "" +"**General improvements** " +"([#1872](https://github.com/adap/flower/pull/1872), " +"[#1866](https://github.com/adap/flower/pull/1866), " +"[#1884](https://github.com/adap/flower/pull/1884), " +"[#1837](https://github.com/adap/flower/pull/1837), " +"[#1477](https://github.com/adap/flower/pull/1477), " +"[#2171](https://github.com/adap/flower/pull/2171))" +msgstr "" + +#: ../../source/ref-changelog.md:444 +msgid "v1.4.0 (2023-04-21)" +msgstr "" + +#: ../../source/ref-changelog.md:450 +msgid "" +"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " +"`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, " +"`Iacob-Alexandru-Andrei`, `JDRanpariya`, `Jean Charle Yaacoub`, `Kunal " +"Sarkhel`, `L. Jiang`, `Lennart Behme`, `Max Kapsecker`, `Michał`, `Nic " +"Lane`, `Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, " +"`Steve Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" +msgstr "" + +#: ../../source/ref-changelog.md:454 +msgid "" +"**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and " +"example)** ([#1694](https://github.com/adap/flower/pull/1694), " +"[#1709](https://github.com/adap/flower/pull/1709), " +"[#1715](https://github.com/adap/flower/pull/1715), " +"[#1717](https://github.com/adap/flower/pull/1717), " +"[#1763](https://github.com/adap/flower/pull/1763), " +"[#1795](https://github.com/adap/flower/pull/1795))" +msgstr "" + +#: ../../source/ref-changelog.md:456 +msgid "" +"XGBoost is a tree-based ensemble machine learning algorithm that uses " +"gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg`" +" " +"[strategy](https://github.com/adap/flower/tree/main/src/py/flwr/server/strategy/fedxgb_nn_avg.py)," +" and a [code example](https://github.com/adap/flower/tree/main/examples" +"/xgboost-quickstart) that demonstrates the usage of this new strategy in " +"an XGBoost project." +msgstr "" + +#: ../../source/ref-changelog.md:458 +msgid "" +"**Introduce iOS SDK (preview)** " +"([#1621](https://github.com/adap/flower/pull/1621), " +"[#1764](https://github.com/adap/flower/pull/1764))" +msgstr "" + +#: ../../source/ref-changelog.md:460 +msgid "" +"This is a major update for anyone wanting to implement Federated Learning" +" on iOS mobile devices. We now have a swift iOS SDK present under " +"[src/swift/flwr](https://github.com/adap/flower/tree/main/src/swift/flwr)" +" that will facilitate greatly the app creating process. To showcase its " +"use, the [iOS " +"example](https://github.com/adap/flower/tree/main/examples/ios) has also " +"been updated!" +msgstr "" + +#: ../../source/ref-changelog.md:462 +msgid "" +"**Introduce new \"What is Federated Learning?\" tutorial** " +"([#1657](https://github.com/adap/flower/pull/1657), " +"[#1721](https://github.com/adap/flower/pull/1721))" +msgstr "" + +#: ../../source/ref-changelog.md:464 +msgid "" +"A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-" +"what-is-federated-learning.html) in our documentation explains the basics" +" of Fedetated Learning. It enables anyone who's unfamiliar with Federated" +" Learning to start their journey with Flower. Forward it to anyone who's " +"interested in Federated Learning!" +msgstr "" + +#: ../../source/ref-changelog.md:466 +msgid "" +"**Introduce new Flower Baseline: FedProx MNIST** " +"([#1513](https://github.com/adap/flower/pull/1513), " +"[#1680](https://github.com/adap/flower/pull/1680), " +"[#1681](https://github.com/adap/flower/pull/1681), " +"[#1679](https://github.com/adap/flower/pull/1679))" +msgstr "" + +#: ../../source/ref-changelog.md:468 +msgid "" +"This new baseline replicates the MNIST+CNN task from the paper [Federated" +" Optimization in Heterogeneous Networks (Li et al., " +"2018)](https://arxiv.org/abs/1812.06127). It uses the `FedProx` strategy," +" which aims at making convergence more robust in heterogeneous settings." +msgstr "" + +#: ../../source/ref-changelog.md:470 +msgid "" +"**Introduce new Flower Baseline: FedAvg FEMNIST** " +"([#1655](https://github.com/adap/flower/pull/1655))" +msgstr "" + +#: ../../source/ref-changelog.md:472 +msgid "" +"This new baseline replicates an experiment evaluating the performance of " +"the FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A " +"Benchmark for Federated Settings (Caldas et al., " +"2018)](https://arxiv.org/abs/1812.01097)." +msgstr "" + +#: ../../source/ref-changelog.md:474 +msgid "" +"**Introduce (experimental) REST API** " +"([#1594](https://github.com/adap/flower/pull/1594), " +"[#1690](https://github.com/adap/flower/pull/1690), " +"[#1695](https://github.com/adap/flower/pull/1695), " +"[#1712](https://github.com/adap/flower/pull/1712), " +"[#1802](https://github.com/adap/flower/pull/1802), " +"[#1770](https://github.com/adap/flower/pull/1770), " +"[#1733](https://github.com/adap/flower/pull/1733))" +msgstr "" + +#: ../../source/ref-changelog.md:476 +msgid "" +"A new REST API has been introduced as an alternative to the gRPC-based " +"communication stack. In this initial version, the REST API only supports " +"anonymous clients." +msgstr "" + +#: ../../source/ref-changelog.md:478 +msgid "" +"Please note: The REST API is still experimental and will likely change " +"significantly over time." +msgstr "" + +#: ../../source/ref-changelog.md:480 +msgid "" +"**Improve the (experimental) Driver API** " +"([#1663](https://github.com/adap/flower/pull/1663), " +"[#1666](https://github.com/adap/flower/pull/1666), " +"[#1667](https://github.com/adap/flower/pull/1667), " +"[#1664](https://github.com/adap/flower/pull/1664), " +"[#1675](https://github.com/adap/flower/pull/1675), " +"[#1676](https://github.com/adap/flower/pull/1676), " +"[#1693](https://github.com/adap/flower/pull/1693), " +"[#1662](https://github.com/adap/flower/pull/1662), " +"[#1794](https://github.com/adap/flower/pull/1794))" +msgstr "" + +#: ../../source/ref-changelog.md:482 +msgid "" +"The Driver API is still an experimental feature, but this release " +"introduces some major upgrades. One of the main improvements is the " +"introduction of an SQLite database to store server state on disk (instead" +" of in-memory). Another improvement is that tasks (instructions or " +"results) that have been delivered will now be deleted. This greatly " +"improves the memory efficiency of a long-running Flower server." +msgstr "" + +#: ../../source/ref-changelog.md:484 +msgid "" +"**Fix spilling issues related to Ray during simulations** " +"([#1698](https://github.com/adap/flower/pull/1698))" +msgstr "" + +#: ../../source/ref-changelog.md:486 +msgid "" +"While running long simulations, `ray` was sometimes spilling huge amounts" +" of data that would make the training unable to continue. This is now " +"fixed! 🎉" +msgstr "" + +#: ../../source/ref-changelog.md:488 +msgid "" +"**Add new example using** `TabNet` **and Flower** " +"([#1725](https://github.com/adap/flower/pull/1725))" +msgstr "" + +#: ../../source/ref-changelog.md:490 +msgid "" +"TabNet is a powerful and flexible framework for training machine learning" +" models on tabular data. We now have a federated example using Flower: " +"[quickstart-tabnet](https://github.com/adap/flower/tree/main/examples" +"/quickstart-tabnet)." +msgstr "" + +#: ../../source/ref-changelog.md:492 +msgid "" +"**Add new how-to guide for monitoring simulations** " +"([#1649](https://github.com/adap/flower/pull/1649))" +msgstr "" + +#: ../../source/ref-changelog.md:494 +msgid "" +"We now have a documentation guide to help users monitor their performance" +" during simulations." +msgstr "" + +#: ../../source/ref-changelog.md:496 +msgid "" +"**Add training metrics to** `History` **object during simulations** " +"([#1696](https://github.com/adap/flower/pull/1696))" +msgstr "" + +#: ../../source/ref-changelog.md:498 +msgid "" +"The `fit_metrics_aggregation_fn` can be used to aggregate training " +"metrics, but previous releases did not save the results in the `History` " +"object. This is now the case!" +msgstr "" + +#: ../../source/ref-changelog.md:500 +msgid "" +"**General improvements** " +"([#1659](https://github.com/adap/flower/pull/1659), " +"[#1646](https://github.com/adap/flower/pull/1646), " +"[#1647](https://github.com/adap/flower/pull/1647), " +"[#1471](https://github.com/adap/flower/pull/1471), " +"[#1648](https://github.com/adap/flower/pull/1648), " +"[#1651](https://github.com/adap/flower/pull/1651), " +"[#1652](https://github.com/adap/flower/pull/1652), " +"[#1653](https://github.com/adap/flower/pull/1653), " +"[#1659](https://github.com/adap/flower/pull/1659), " +"[#1665](https://github.com/adap/flower/pull/1665), " +"[#1670](https://github.com/adap/flower/pull/1670), " +"[#1672](https://github.com/adap/flower/pull/1672), " +"[#1677](https://github.com/adap/flower/pull/1677), " +"[#1684](https://github.com/adap/flower/pull/1684), " +"[#1683](https://github.com/adap/flower/pull/1683), " +"[#1686](https://github.com/adap/flower/pull/1686), " +"[#1682](https://github.com/adap/flower/pull/1682), " +"[#1685](https://github.com/adap/flower/pull/1685), " +"[#1692](https://github.com/adap/flower/pull/1692), " +"[#1705](https://github.com/adap/flower/pull/1705), " +"[#1708](https://github.com/adap/flower/pull/1708), " +"[#1711](https://github.com/adap/flower/pull/1711), " +"[#1713](https://github.com/adap/flower/pull/1713), " +"[#1714](https://github.com/adap/flower/pull/1714), " +"[#1718](https://github.com/adap/flower/pull/1718), " +"[#1716](https://github.com/adap/flower/pull/1716), " +"[#1723](https://github.com/adap/flower/pull/1723), " +"[#1735](https://github.com/adap/flower/pull/1735), " +"[#1678](https://github.com/adap/flower/pull/1678), " +"[#1750](https://github.com/adap/flower/pull/1750), " +"[#1753](https://github.com/adap/flower/pull/1753), " +"[#1736](https://github.com/adap/flower/pull/1736), " +"[#1766](https://github.com/adap/flower/pull/1766), " +"[#1760](https://github.com/adap/flower/pull/1760), " +"[#1775](https://github.com/adap/flower/pull/1775), " +"[#1776](https://github.com/adap/flower/pull/1776), " +"[#1777](https://github.com/adap/flower/pull/1777), " +"[#1779](https://github.com/adap/flower/pull/1779), " +"[#1784](https://github.com/adap/flower/pull/1784), " +"[#1773](https://github.com/adap/flower/pull/1773), " +"[#1755](https://github.com/adap/flower/pull/1755), " +"[#1789](https://github.com/adap/flower/pull/1789), " +"[#1788](https://github.com/adap/flower/pull/1788), " +"[#1798](https://github.com/adap/flower/pull/1798), " +"[#1799](https://github.com/adap/flower/pull/1799), " +"[#1739](https://github.com/adap/flower/pull/1739), " +"[#1800](https://github.com/adap/flower/pull/1800), " +"[#1804](https://github.com/adap/flower/pull/1804), " +"[#1805](https://github.com/adap/flower/pull/1805))" +msgstr "" + +#: ../../source/ref-changelog.md:508 +msgid "v1.3.0 (2023-02-06)" +msgstr "" + +#: ../../source/ref-changelog.md:514 +msgid "" +"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " +"`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" +msgstr "" + +#: ../../source/ref-changelog.md:518 +msgid "" +"**Add support for** `workload_id` **and** `group_id` **in Driver API** " +"([#1595](https://github.com/adap/flower/pull/1595))" +msgstr "" + +#: ../../source/ref-changelog.md:520 +msgid "" +"The (experimental) Driver API now supports a `workload_id` that can be " +"used to identify which workload a task belongs to. It also supports a new" +" `group_id` that can be used, for example, to indicate the current " +"training round. Both the `workload_id` and `group_id` enable client nodes" +" to decide whether they want to handle a task or not." +msgstr "" + +#: ../../source/ref-changelog.md:522 +msgid "" +"**Make Driver API and Fleet API address configurable** " +"([#1637](https://github.com/adap/flower/pull/1637))" +msgstr "" + +#: ../../source/ref-changelog.md:524 +msgid "" +"The (experimental) long-running Flower server (Driver API and Fleet API) " +"can now configure the server address of both Driver API (via `--driver-" +"api-address`) and Fleet API (via `--fleet-api-address`) when starting:" +msgstr "" + +#: ../../source/ref-changelog.md:526 +msgid "" "`flower-server --driver-api-address \"0.0.0.0:8081\" --fleet-api-address " "\"0.0.0.0:8086\"`" msgstr "" -#: ../../source/ref-changelog.md:436 +#: ../../source/ref-changelog.md:528 msgid "Both IPv4 and IPv6 addresses are supported." msgstr "" -#: ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:530 msgid "" -"**Add new example of Federated Learning using fastai and Flower** ([#1598]" -"(https://github.com/adap/flower/pull/1598))" +"**Add new example of Federated Learning using fastai and Flower** " +"([#1598](https://github.com/adap/flower/pull/1598))" msgstr "" -#: ../../source/ref-changelog.md:440 +#: ../../source/ref-changelog.md:532 msgid "" "A new code example (`quickstart-fastai`) demonstrates federated learning " "with [fastai](https://www.fast.ai/) and Flower. You can find it here: " -"[quickstart-fastai](https://github.com/adap/flower/tree/main/examples/" -"quickstart-fastai)." +"[quickstart-fastai](https://github.com/adap/flower/tree/main/examples" +"/quickstart-fastai)." msgstr "" -#: ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:534 msgid "" -"**Make Android example compatible with** `flwr >= 1.0.0` **and the latest " -"versions of Android** ([#1603](https://github.com/adap/flower/pull/1603))" +"**Make Android example compatible with** `flwr >= 1.0.0` **and the latest" +" versions of Android** " +"([#1603](https://github.com/adap/flower/pull/1603))" msgstr "" -#: ../../source/ref-changelog.md:444 +#: ../../source/ref-changelog.md:536 msgid "" -"The Android code example has received a substantial update: the project is " -"compatible with Flower 1.0 (and later), the UI received a full refresh, and " -"the project is updated to be compatible with newer Android tooling." +"The Android code example has received a substantial update: the project " +"is compatible with Flower 1.0 (and later), the UI received a full " +"refresh, and the project is updated to be compatible with newer Android " +"tooling." msgstr "" -#: ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:538 msgid "" -"**Add new `FedProx` strategy** ([#1619](https://github.com/adap/flower/" -"pull/1619))" +"**Add new `FedProx` strategy** " +"([#1619](https://github.com/adap/flower/pull/1619))" msgstr "" -#: ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:540 msgid "" -"This [strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/" -"strategy/fedprox.py) is almost identical to [`FedAvg`](https://github.com/" -"adap/flower/blob/main/src/py/flwr/server/strategy/fedavg.py), but helps " -"users replicate what is described in this [paper](https://arxiv.org/" -"abs/1812.06127). It essentially adds a parameter called `proximal_mu` to " -"regularize the local models with respect to the global models." +"This " +"[strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedprox.py)" +" is almost identical to " +"[`FedAvg`](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedavg.py)," +" but helps users replicate what is described in this " +"[paper](https://arxiv.org/abs/1812.06127). It essentially adds a " +"parameter called `proximal_mu` to regularize the local models with " +"respect to the global models." msgstr "" -#: ../../source/ref-changelog.md:450 +#: ../../source/ref-changelog.md:542 msgid "" -"**Add new metrics to telemetry events** ([#1640](https://github.com/adap/" -"flower/pull/1640))" +"**Add new metrics to telemetry events** " +"([#1640](https://github.com/adap/flower/pull/1640))" msgstr "" -#: ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:544 msgid "" "An updated event structure allows, for example, the clustering of events " "within the same workload." msgstr "" -#: ../../source/ref-changelog.md:454 +#: ../../source/ref-changelog.md:546 msgid "" -"**Add new custom strategy tutorial section** [#1623](https://github.com/adap/" -"flower/pull/1623)" +"**Add new custom strategy tutorial section** " +"[#1623](https://github.com/adap/flower/pull/1623)" msgstr "" -#: ../../source/ref-changelog.md:456 +#: ../../source/ref-changelog.md:548 msgid "" -"The Flower tutorial now has a new section that covers implementing a custom " -"strategy from scratch: [Open in Colab](https://colab.research.google.com/" -"github/adap/flower/blob/main/doc/source/tutorial-build-a-strategy-from-" -"scratch-pytorch.ipynb)" +"The Flower tutorial now has a new section that covers implementing a " +"custom strategy from scratch: [Open in " +"Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" +"/tutorial-build-a-strategy-from-scratch-pytorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:458 +#: ../../source/ref-changelog.md:550 msgid "" -"**Add new custom serialization tutorial section** ([#1622](https://github." -"com/adap/flower/pull/1622))" +"**Add new custom serialization tutorial section** " +"([#1622](https://github.com/adap/flower/pull/1622))" msgstr "" -#: ../../source/ref-changelog.md:460 +#: ../../source/ref-changelog.md:552 msgid "" -"The Flower tutorial now has a new section that covers custom serialization: " -"[Open in Colab](https://colab.research.google.com/github/adap/flower/blob/" -"main/doc/source/tutorial-customize-the-client-pytorch.ipynb)" +"The Flower tutorial now has a new section that covers custom " +"serialization: [Open in " +"Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" +"/tutorial-customize-the-client-pytorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:462 +#: ../../source/ref-changelog.md:554 msgid "" -"**General improvements** ([#1638](https://github.com/adap/flower/pull/1638), " -"[#1634](https://github.com/adap/flower/pull/1634), [#1636](https://github." -"com/adap/flower/pull/1636), [#1635](https://github.com/adap/flower/" -"pull/1635), [#1633](https://github.com/adap/flower/pull/1633), [#1632]" -"(https://github.com/adap/flower/pull/1632), [#1631](https://github.com/adap/" -"flower/pull/1631), [#1630](https://github.com/adap/flower/pull/1630), [#1627]" -"(https://github.com/adap/flower/pull/1627), [#1593](https://github.com/adap/" -"flower/pull/1593), [#1616](https://github.com/adap/flower/pull/1616), [#1615]" -"(https://github.com/adap/flower/pull/1615), [#1607](https://github.com/adap/" -"flower/pull/1607), [#1609](https://github.com/adap/flower/pull/1609), [#1608]" -"(https://github.com/adap/flower/pull/1608), [#1603](https://github.com/adap/" -"flower/pull/1603), [#1590](https://github.com/adap/flower/pull/1590), [#1580]" -"(https://github.com/adap/flower/pull/1580), [#1599](https://github.com/adap/" -"flower/pull/1599), [#1600](https://github.com/adap/flower/pull/1600), [#1601]" -"(https://github.com/adap/flower/pull/1601), [#1597](https://github.com/adap/" -"flower/pull/1597), [#1595](https://github.com/adap/flower/pull/1595), [#1591]" -"(https://github.com/adap/flower/pull/1591), [#1588](https://github.com/adap/" -"flower/pull/1588), [#1589](https://github.com/adap/flower/pull/1589), [#1587]" -"(https://github.com/adap/flower/pull/1587), [#1573](https://github.com/adap/" -"flower/pull/1573), [#1581](https://github.com/adap/flower/pull/1581), [#1578]" -"(https://github.com/adap/flower/pull/1578), [#1574](https://github.com/adap/" -"flower/pull/1574), [#1572](https://github.com/adap/flower/pull/1572), [#1586]" -"(https://github.com/adap/flower/pull/1586))" +"**General improvements** " +"([#1638](https://github.com/adap/flower/pull/1638), " +"[#1634](https://github.com/adap/flower/pull/1634), " +"[#1636](https://github.com/adap/flower/pull/1636), " +"[#1635](https://github.com/adap/flower/pull/1635), " +"[#1633](https://github.com/adap/flower/pull/1633), " +"[#1632](https://github.com/adap/flower/pull/1632), " +"[#1631](https://github.com/adap/flower/pull/1631), " +"[#1630](https://github.com/adap/flower/pull/1630), " +"[#1627](https://github.com/adap/flower/pull/1627), " +"[#1593](https://github.com/adap/flower/pull/1593), " +"[#1616](https://github.com/adap/flower/pull/1616), " +"[#1615](https://github.com/adap/flower/pull/1615), " +"[#1607](https://github.com/adap/flower/pull/1607), " +"[#1609](https://github.com/adap/flower/pull/1609), " +"[#1608](https://github.com/adap/flower/pull/1608), " +"[#1603](https://github.com/adap/flower/pull/1603), " +"[#1590](https://github.com/adap/flower/pull/1590), " +"[#1580](https://github.com/adap/flower/pull/1580), " +"[#1599](https://github.com/adap/flower/pull/1599), " +"[#1600](https://github.com/adap/flower/pull/1600), " +"[#1601](https://github.com/adap/flower/pull/1601), " +"[#1597](https://github.com/adap/flower/pull/1597), " +"[#1595](https://github.com/adap/flower/pull/1595), " +"[#1591](https://github.com/adap/flower/pull/1591), " +"[#1588](https://github.com/adap/flower/pull/1588), " +"[#1589](https://github.com/adap/flower/pull/1589), " +"[#1587](https://github.com/adap/flower/pull/1587), " +"[#1573](https://github.com/adap/flower/pull/1573), " +"[#1581](https://github.com/adap/flower/pull/1581), " +"[#1578](https://github.com/adap/flower/pull/1578), " +"[#1574](https://github.com/adap/flower/pull/1574), " +"[#1572](https://github.com/adap/flower/pull/1572), " +"[#1586](https://github.com/adap/flower/pull/1586))" msgstr "" -#: ../../source/ref-changelog.md:466 +#: ../../source/ref-changelog.md:558 msgid "" -"**Updated documentation** ([#1629](https://github.com/adap/flower/" -"pull/1629), [#1628](https://github.com/adap/flower/pull/1628), [#1620]" -"(https://github.com/adap/flower/pull/1620), [#1618](https://github.com/adap/" -"flower/pull/1618), [#1617](https://github.com/adap/flower/pull/1617), [#1613]" -"(https://github.com/adap/flower/pull/1613), [#1614](https://github.com/adap/" -"flower/pull/1614))" +"**Updated documentation** " +"([#1629](https://github.com/adap/flower/pull/1629), " +"[#1628](https://github.com/adap/flower/pull/1628), " +"[#1620](https://github.com/adap/flower/pull/1620), " +"[#1618](https://github.com/adap/flower/pull/1618), " +"[#1617](https://github.com/adap/flower/pull/1617), " +"[#1613](https://github.com/adap/flower/pull/1613), " +"[#1614](https://github.com/adap/flower/pull/1614))" msgstr "" -#: ../../source/ref-changelog.md:468 ../../source/ref-changelog.md:535 +#: ../../source/ref-changelog.md:560 ../../source/ref-changelog.md:627 msgid "" -"As usual, the documentation has improved quite a bit. It is another step in " -"our effort to make the Flower documentation the best documentation of any " -"project. Stay tuned and as always, feel free to provide feedback!" +"As usual, the documentation has improved quite a bit. It is another step " +"in our effort to make the Flower documentation the best documentation of " +"any project. Stay tuned and as always, feel free to provide feedback!" msgstr "" -#: ../../source/ref-changelog.md:474 +#: ../../source/ref-changelog.md:566 msgid "v1.2.0 (2023-01-13)" msgstr "" -#: ../../source/ref-changelog.md:480 +#: ../../source/ref-changelog.md:572 msgid "" -"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L. " -"Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" +"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L." +" Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" msgstr "" -#: ../../source/ref-changelog.md:484 +#: ../../source/ref-changelog.md:576 msgid "" -"**Introduce new Flower Baseline: FedAvg MNIST** ([#1497](https://github.com/" -"adap/flower/pull/1497), [#1552](https://github.com/adap/flower/pull/1552))" +"**Introduce new Flower Baseline: FedAvg MNIST** " +"([#1497](https://github.com/adap/flower/pull/1497), " +"[#1552](https://github.com/adap/flower/pull/1552))" msgstr "" -#: ../../source/ref-changelog.md:486 +#: ../../source/ref-changelog.md:578 msgid "" "Over the coming weeks, we will be releasing a number of new reference " "implementations useful especially to FL newcomers. They will typically " "revisit well known papers from the literature, and be suitable for " "integration in your own application or for experimentation, in order to " -"deepen your knowledge of FL in general. Today's release is the first in this " -"series. [Read more.](https://flower.ai/blog/2023-01-12-fl-starter-pack-" -"fedavg-mnist-cnn/)" +"deepen your knowledge of FL in general. Today's release is the first in " +"this series. [Read more.](https://flower.ai/blog/2023-01-12-fl-starter-" +"pack-fedavg-mnist-cnn/)" msgstr "" -#: ../../source/ref-changelog.md:488 +#: ../../source/ref-changelog.md:580 msgid "" -"**Improve GPU support in simulations** ([#1555](https://github.com/adap/" -"flower/pull/1555))" +"**Improve GPU support in simulations** " +"([#1555](https://github.com/adap/flower/pull/1555))" msgstr "" -#: ../../source/ref-changelog.md:490 +#: ../../source/ref-changelog.md:582 msgid "" -"The Ray-based Virtual Client Engine (`start_simulation`) has been updated to " -"improve GPU support. The update includes some of the hard-earned lessons " -"from scaling simulations in GPU cluster environments. New defaults make " -"running GPU-based simulations substantially more robust." +"The Ray-based Virtual Client Engine (`start_simulation`) has been updated" +" to improve GPU support. The update includes some of the hard-earned " +"lessons from scaling simulations in GPU cluster environments. New " +"defaults make running GPU-based simulations substantially more robust." msgstr "" -#: ../../source/ref-changelog.md:492 +#: ../../source/ref-changelog.md:584 msgid "" -"**Improve GPU support in Jupyter Notebook tutorials** ([#1527](https://" -"github.com/adap/flower/pull/1527), [#1558](https://github.com/adap/flower/" -"pull/1558))" +"**Improve GPU support in Jupyter Notebook tutorials** " +"([#1527](https://github.com/adap/flower/pull/1527), " +"[#1558](https://github.com/adap/flower/pull/1558))" msgstr "" -#: ../../source/ref-changelog.md:494 +#: ../../source/ref-changelog.md:586 msgid "" -"Some users reported that Jupyter Notebooks have not always been easy to use " -"on GPU instances. We listened and made improvements to all of our Jupyter " -"notebooks! Check out the updated notebooks here:" +"Some users reported that Jupyter Notebooks have not always been easy to " +"use on GPU instances. We listened and made improvements to all of our " +"Jupyter notebooks! Check out the updated notebooks here:" msgstr "" -#: ../../source/ref-changelog.md:496 +#: ../../source/ref-changelog.md:588 msgid "" -"[An Introduction to Federated Learning](https://flower.ai/docs/framework/" -"tutorial-get-started-with-flower-pytorch.html)" +"[An Introduction to Federated Learning](https://flower.ai/docs/framework" +"/tutorial-get-started-with-flower-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:497 +#: ../../source/ref-changelog.md:589 msgid "" -"[Strategies in Federated Learning](https://flower.ai/docs/framework/tutorial-" -"use-a-federated-learning-strategy-pytorch.html)" +"[Strategies in Federated Learning](https://flower.ai/docs/framework" +"/tutorial-use-a-federated-learning-strategy-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:498 +#: ../../source/ref-changelog.md:590 msgid "" -"[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a-" -"strategy-from-scratch-pytorch.html)" +"[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a" +"-strategy-from-scratch-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:499 +#: ../../source/ref-changelog.md:591 msgid "" -"[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-customize-" -"the-client-pytorch.html)" +"[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-" +"customize-the-client-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:501 +#: ../../source/ref-changelog.md:593 msgid "" -"**Introduce optional telemetry** ([#1533](https://github.com/adap/flower/" -"pull/1533), [#1544](https://github.com/adap/flower/pull/1544), [#1584]" -"(https://github.com/adap/flower/pull/1584))" +"**Introduce optional telemetry** " +"([#1533](https://github.com/adap/flower/pull/1533), " +"[#1544](https://github.com/adap/flower/pull/1544), " +"[#1584](https://github.com/adap/flower/pull/1584))" msgstr "" -#: ../../source/ref-changelog.md:503 +#: ../../source/ref-changelog.md:595 msgid "" -"After a [request for feedback](https://github.com/adap/flower/issues/1534) " -"from the community, the Flower open-source project introduces optional " -"collection of *anonymous* usage metrics to make well-informed decisions to " -"improve Flower. Doing this enables the Flower team to understand how Flower " -"is used and what challenges users might face." +"After a [request for " +"feedback](https://github.com/adap/flower/issues/1534) from the community," +" the Flower open-source project introduces optional collection of " +"*anonymous* usage metrics to make well-informed decisions to improve " +"Flower. Doing this enables the Flower team to understand how Flower is " +"used and what challenges users might face." msgstr "" -#: ../../source/ref-changelog.md:505 +#: ../../source/ref-changelog.md:597 msgid "" -"**Flower is a friendly framework for collaborative AI and data science.** " -"Staying true to this statement, Flower makes it easy to disable telemetry " -"for users who do not want to share anonymous usage metrics. [Read more.]" -"(https://flower.ai/docs/telemetry.html)." +"**Flower is a friendly framework for collaborative AI and data science.**" +" Staying true to this statement, Flower makes it easy to disable " +"telemetry for users who do not want to share anonymous usage metrics. " +"[Read more.](https://flower.ai/docs/telemetry.html)." msgstr "" -#: ../../source/ref-changelog.md:507 +#: ../../source/ref-changelog.md:599 msgid "" -"**Introduce (experimental) Driver API** ([#1520](https://github.com/adap/" -"flower/pull/1520), [#1525](https://github.com/adap/flower/pull/1525), [#1545]" -"(https://github.com/adap/flower/pull/1545), [#1546](https://github.com/adap/" -"flower/pull/1546), [#1550](https://github.com/adap/flower/pull/1550), [#1551]" -"(https://github.com/adap/flower/pull/1551), [#1567](https://github.com/adap/" -"flower/pull/1567))" +"**Introduce (experimental) Driver API** " +"([#1520](https://github.com/adap/flower/pull/1520), " +"[#1525](https://github.com/adap/flower/pull/1525), " +"[#1545](https://github.com/adap/flower/pull/1545), " +"[#1546](https://github.com/adap/flower/pull/1546), " +"[#1550](https://github.com/adap/flower/pull/1550), " +"[#1551](https://github.com/adap/flower/pull/1551), " +"[#1567](https://github.com/adap/flower/pull/1567))" msgstr "" -#: ../../source/ref-changelog.md:509 +#: ../../source/ref-changelog.md:601 msgid "" "Flower now has a new (experimental) Driver API which will enable fully " "programmable, async, and multi-tenant Federated Learning and Federated " -"Analytics applications. Phew, that's a lot! Going forward, the Driver API " -"will be the abstraction that many upcoming features will be built on - and " -"you can start building those things now, too." +"Analytics applications. Phew, that's a lot! Going forward, the Driver API" +" will be the abstraction that many upcoming features will be built on - " +"and you can start building those things now, too." msgstr "" -#: ../../source/ref-changelog.md:511 +#: ../../source/ref-changelog.md:603 msgid "" -"The Driver API also enables a new execution mode in which the server runs " -"indefinitely. Multiple individual workloads can run concurrently and start " -"and stop their execution independent of the server. This is especially " -"useful for users who want to deploy Flower in production." +"The Driver API also enables a new execution mode in which the server runs" +" indefinitely. Multiple individual workloads can run concurrently and " +"start and stop their execution independent of the server. This is " +"especially useful for users who want to deploy Flower in production." msgstr "" -#: ../../source/ref-changelog.md:513 +#: ../../source/ref-changelog.md:605 msgid "" -"To learn more, check out the `mt-pytorch` code example. We look forward to " -"you feedback!" +"To learn more, check out the `mt-pytorch` code example. We look forward " +"to you feedback!" msgstr "" -#: ../../source/ref-changelog.md:515 +#: ../../source/ref-changelog.md:607 msgid "" -"Please note: *The Driver API is still experimental and will likely change " -"significantly over time.*" +"Please note: *The Driver API is still experimental and will likely change" +" significantly over time.*" msgstr "" -#: ../../source/ref-changelog.md:517 +#: ../../source/ref-changelog.md:609 msgid "" -"**Add new Federated Analytics with Pandas example** ([#1469](https://github." -"com/adap/flower/pull/1469), [#1535](https://github.com/adap/flower/" -"pull/1535))" +"**Add new Federated Analytics with Pandas example** " +"([#1469](https://github.com/adap/flower/pull/1469), " +"[#1535](https://github.com/adap/flower/pull/1535))" msgstr "" -#: ../../source/ref-changelog.md:519 +#: ../../source/ref-changelog.md:611 msgid "" -"A new code example (`quickstart-pandas`) demonstrates federated analytics " -"with Pandas and Flower. You can find it here: [quickstart-pandas](https://" -"github.com/adap/flower/tree/main/examples/quickstart-pandas)." +"A new code example (`quickstart-pandas`) demonstrates federated analytics" +" with Pandas and Flower. You can find it here: [quickstart-" +"pandas](https://github.com/adap/flower/tree/main/examples/quickstart-" +"pandas)." msgstr "" -#: ../../source/ref-changelog.md:521 +#: ../../source/ref-changelog.md:613 msgid "" -"**Add new strategies: Krum and MultiKrum** ([#1481](https://github.com/adap/" -"flower/pull/1481))" +"**Add new strategies: Krum and MultiKrum** " +"([#1481](https://github.com/adap/flower/pull/1481))" msgstr "" -#: ../../source/ref-changelog.md:523 +#: ../../source/ref-changelog.md:615 msgid "" "Edoardo, a computer science student at the Sapienza University of Rome, " -"contributed a new `Krum` strategy that enables users to easily use Krum and " -"MultiKrum in their workloads." +"contributed a new `Krum` strategy that enables users to easily use Krum " +"and MultiKrum in their workloads." msgstr "" -#: ../../source/ref-changelog.md:525 +#: ../../source/ref-changelog.md:617 msgid "" -"**Update C++ example to be compatible with Flower v1.2.0** ([#1495](https://" -"github.com/adap/flower/pull/1495))" +"**Update C++ example to be compatible with Flower v1.2.0** " +"([#1495](https://github.com/adap/flower/pull/1495))" msgstr "" -#: ../../source/ref-changelog.md:527 +#: ../../source/ref-changelog.md:619 msgid "" -"The C++ code example has received a substantial update to make it compatible " -"with the latest version of Flower." +"The C++ code example has received a substantial update to make it " +"compatible with the latest version of Flower." msgstr "" -#: ../../source/ref-changelog.md:529 +#: ../../source/ref-changelog.md:621 msgid "" -"**General improvements** ([#1491](https://github.com/adap/flower/pull/1491), " -"[#1504](https://github.com/adap/flower/pull/1504), [#1506](https://github." -"com/adap/flower/pull/1506), [#1514](https://github.com/adap/flower/" -"pull/1514), [#1522](https://github.com/adap/flower/pull/1522), [#1523]" -"(https://github.com/adap/flower/pull/1523), [#1526](https://github.com/adap/" -"flower/pull/1526), [#1528](https://github.com/adap/flower/pull/1528), [#1547]" -"(https://github.com/adap/flower/pull/1547), [#1549](https://github.com/adap/" -"flower/pull/1549), [#1560](https://github.com/adap/flower/pull/1560), [#1564]" -"(https://github.com/adap/flower/pull/1564), [#1566](https://github.com/adap/" -"flower/pull/1566))" +"**General improvements** " +"([#1491](https://github.com/adap/flower/pull/1491), " +"[#1504](https://github.com/adap/flower/pull/1504), " +"[#1506](https://github.com/adap/flower/pull/1506), " +"[#1514](https://github.com/adap/flower/pull/1514), " +"[#1522](https://github.com/adap/flower/pull/1522), " +"[#1523](https://github.com/adap/flower/pull/1523), " +"[#1526](https://github.com/adap/flower/pull/1526), " +"[#1528](https://github.com/adap/flower/pull/1528), " +"[#1547](https://github.com/adap/flower/pull/1547), " +"[#1549](https://github.com/adap/flower/pull/1549), " +"[#1560](https://github.com/adap/flower/pull/1560), " +"[#1564](https://github.com/adap/flower/pull/1564), " +"[#1566](https://github.com/adap/flower/pull/1566))" msgstr "" -#: ../../source/ref-changelog.md:533 +#: ../../source/ref-changelog.md:625 msgid "" -"**Updated documentation** ([#1494](https://github.com/adap/flower/" -"pull/1494), [#1496](https://github.com/adap/flower/pull/1496), [#1500]" -"(https://github.com/adap/flower/pull/1500), [#1503](https://github.com/adap/" -"flower/pull/1503), [#1505](https://github.com/adap/flower/pull/1505), [#1524]" -"(https://github.com/adap/flower/pull/1524), [#1518](https://github.com/adap/" -"flower/pull/1518), [#1519](https://github.com/adap/flower/pull/1519), [#1515]" -"(https://github.com/adap/flower/pull/1515))" +"**Updated documentation** " +"([#1494](https://github.com/adap/flower/pull/1494), " +"[#1496](https://github.com/adap/flower/pull/1496), " +"[#1500](https://github.com/adap/flower/pull/1500), " +"[#1503](https://github.com/adap/flower/pull/1503), " +"[#1505](https://github.com/adap/flower/pull/1505), " +"[#1524](https://github.com/adap/flower/pull/1524), " +"[#1518](https://github.com/adap/flower/pull/1518), " +"[#1519](https://github.com/adap/flower/pull/1519), " +"[#1515](https://github.com/adap/flower/pull/1515))" msgstr "" -#: ../../source/ref-changelog.md:537 +#: ../../source/ref-changelog.md:629 msgid "" -"One highlight is the new [first time contributor guide](https://flower.ai/" -"docs/first-time-contributors.html): if you've never contributed on GitHub " -"before, this is the perfect place to start!" +"One highlight is the new [first time contributor " +"guide](https://flower.ai/docs/first-time-contributors.html): if you've " +"never contributed on GitHub before, this is the perfect place to start!" msgstr "" -#: ../../source/ref-changelog.md:543 +#: ../../source/ref-changelog.md:635 msgid "v1.1.0 (2022-10-31)" msgstr "" -#: ../../source/ref-changelog.md:547 +#: ../../source/ref-changelog.md:639 msgid "" "We would like to give our **special thanks** to all the contributors who " "made the new version of Flower possible (in `git shortlog` order):" msgstr "" -#: ../../source/ref-changelog.md:549 +#: ../../source/ref-changelog.md:641 msgid "" "`Akis Linardos`, `Christopher S`, `Daniel J. Beutel`, `George`, `Jan " "Schlicht`, `Mohammad Fares`, `Pedro Porto Buarque de Gusmão`, `Philipp " -"Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, `danielnugraha`, " -"`edogab33`" +"Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, " +"`danielnugraha`, `edogab33`" msgstr "" -#: ../../source/ref-changelog.md:553 +#: ../../source/ref-changelog.md:645 msgid "" -"**Introduce Differential Privacy wrappers (preview)** ([#1357](https://" -"github.com/adap/flower/pull/1357), [#1460](https://github.com/adap/flower/" -"pull/1460))" +"**Introduce Differential Privacy wrappers (preview)** " +"([#1357](https://github.com/adap/flower/pull/1357), " +"[#1460](https://github.com/adap/flower/pull/1460))" msgstr "" -#: ../../source/ref-changelog.md:555 +#: ../../source/ref-changelog.md:647 msgid "" -"The first (experimental) preview of pluggable Differential Privacy wrappers " -"enables easy configuration and usage of differential privacy (DP). The " -"pluggable DP wrappers enable framework-agnostic **and** strategy-agnostic " -"usage of both client-side DP and server-side DP. Head over to the Flower " -"docs, a new explainer goes into more detail." +"The first (experimental) preview of pluggable Differential Privacy " +"wrappers enables easy configuration and usage of differential privacy " +"(DP). The pluggable DP wrappers enable framework-agnostic **and** " +"strategy-agnostic usage of both client-side DP and server-side DP. Head " +"over to the Flower docs, a new explainer goes into more detail." msgstr "" -#: ../../source/ref-changelog.md:557 +#: ../../source/ref-changelog.md:649 msgid "" -"**New iOS CoreML code example** ([#1289](https://github.com/adap/flower/" -"pull/1289))" +"**New iOS CoreML code example** " +"([#1289](https://github.com/adap/flower/pull/1289))" msgstr "" -#: ../../source/ref-changelog.md:559 +#: ../../source/ref-changelog.md:651 msgid "" -"Flower goes iOS! A massive new code example shows how Flower clients can be " -"built for iOS. The code example contains both Flower iOS SDK components that " -"can be used for many tasks, and one task example running on CoreML." +"Flower goes iOS! A massive new code example shows how Flower clients can " +"be built for iOS. The code example contains both Flower iOS SDK " +"components that can be used for many tasks, and one task example running " +"on CoreML." msgstr "" -#: ../../source/ref-changelog.md:561 +#: ../../source/ref-changelog.md:653 msgid "" -"**New FedMedian strategy** ([#1461](https://github.com/adap/flower/" -"pull/1461))" +"**New FedMedian strategy** " +"([#1461](https://github.com/adap/flower/pull/1461))" msgstr "" -#: ../../source/ref-changelog.md:563 +#: ../../source/ref-changelog.md:655 msgid "" -"The new `FedMedian` strategy implements Federated Median (FedMedian) by [Yin " -"et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." +"The new `FedMedian` strategy implements Federated Median (FedMedian) by " +"[Yin et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." msgstr "" -#: ../../source/ref-changelog.md:565 +#: ../../source/ref-changelog.md:657 msgid "" -"**Log** `Client` **exceptions in Virtual Client Engine** ([#1493](https://" -"github.com/adap/flower/pull/1493))" +"**Log** `Client` **exceptions in Virtual Client Engine** " +"([#1493](https://github.com/adap/flower/pull/1493))" msgstr "" -#: ../../source/ref-changelog.md:567 +#: ../../source/ref-changelog.md:659 msgid "" -"All `Client` exceptions happening in the VCE are now logged by default and " -"not just exposed to the configured `Strategy` (via the `failures` argument)." +"All `Client` exceptions happening in the VCE are now logged by default " +"and not just exposed to the configured `Strategy` (via the `failures` " +"argument)." msgstr "" -#: ../../source/ref-changelog.md:569 +#: ../../source/ref-changelog.md:661 msgid "" -"**Improve Virtual Client Engine internals** ([#1401](https://github.com/adap/" -"flower/pull/1401), [#1453](https://github.com/adap/flower/pull/1453))" +"**Improve Virtual Client Engine internals** " +"([#1401](https://github.com/adap/flower/pull/1401), " +"[#1453](https://github.com/adap/flower/pull/1453))" msgstr "" -#: ../../source/ref-changelog.md:571 +#: ../../source/ref-changelog.md:663 msgid "" -"Some internals of the Virtual Client Engine have been revamped. The VCE now " -"uses Ray 2.0 under the hood, the value type of the `client_resources` " -"dictionary changed to `float` to allow fractions of resources to be " +"Some internals of the Virtual Client Engine have been revamped. The VCE " +"now uses Ray 2.0 under the hood, the value type of the `client_resources`" +" dictionary changed to `float` to allow fractions of resources to be " "allocated." msgstr "" -#: ../../source/ref-changelog.md:573 +#: ../../source/ref-changelog.md:665 msgid "" -"**Support optional** `Client`**/**`NumPyClient` **methods in Virtual Client " -"Engine**" +"**Support optional** `Client`**/**`NumPyClient` **methods in Virtual " +"Client Engine**" msgstr "" -#: ../../source/ref-changelog.md:575 +#: ../../source/ref-changelog.md:667 msgid "" -"The Virtual Client Engine now has full support for optional `Client` (and " -"`NumPyClient`) methods." +"The Virtual Client Engine now has full support for optional `Client` (and" +" `NumPyClient`) methods." msgstr "" -#: ../../source/ref-changelog.md:577 +#: ../../source/ref-changelog.md:669 msgid "" -"**Provide type information to packages using** `flwr` ([#1377](https://" -"github.com/adap/flower/pull/1377))" +"**Provide type information to packages using** `flwr` " +"([#1377](https://github.com/adap/flower/pull/1377))" msgstr "" -#: ../../source/ref-changelog.md:579 +#: ../../source/ref-changelog.md:671 msgid "" -"The package `flwr` is now bundled with a `py.typed` file indicating that the " -"package is typed. This enables typing support for projects or packages that " -"use `flwr` by enabling them to improve their code using static type checkers " -"like `mypy`." +"The package `flwr` is now bundled with a `py.typed` file indicating that " +"the package is typed. This enables typing support for projects or " +"packages that use `flwr` by enabling them to improve their code using " +"static type checkers like `mypy`." msgstr "" -#: ../../source/ref-changelog.md:581 +#: ../../source/ref-changelog.md:673 msgid "" -"**Updated code example** ([#1344](https://github.com/adap/flower/pull/1344), " +"**Updated code example** " +"([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" msgstr "" -#: ../../source/ref-changelog.md:583 +#: ../../source/ref-changelog.md:675 msgid "" "The code examples covering scikit-learn and PyTorch Lightning have been " "updated to work with the latest version of Flower." msgstr "" -#: ../../source/ref-changelog.md:585 +#: ../../source/ref-changelog.md:677 msgid "" -"**Updated documentation** ([#1355](https://github.com/adap/flower/" -"pull/1355), [#1558](https://github.com/adap/flower/pull/1558), [#1379]" -"(https://github.com/adap/flower/pull/1379), [#1380](https://github.com/adap/" -"flower/pull/1380), [#1381](https://github.com/adap/flower/pull/1381), [#1332]" -"(https://github.com/adap/flower/pull/1332), [#1391](https://github.com/adap/" -"flower/pull/1391), [#1403](https://github.com/adap/flower/pull/1403), [#1364]" -"(https://github.com/adap/flower/pull/1364), [#1409](https://github.com/adap/" -"flower/pull/1409), [#1419](https://github.com/adap/flower/pull/1419), [#1444]" -"(https://github.com/adap/flower/pull/1444), [#1448](https://github.com/adap/" -"flower/pull/1448), [#1417](https://github.com/adap/flower/pull/1417), [#1449]" -"(https://github.com/adap/flower/pull/1449), [#1465](https://github.com/adap/" -"flower/pull/1465), [#1467](https://github.com/adap/flower/pull/1467))" +"**Updated documentation** " +"([#1355](https://github.com/adap/flower/pull/1355), " +"[#1558](https://github.com/adap/flower/pull/1558), " +"[#1379](https://github.com/adap/flower/pull/1379), " +"[#1380](https://github.com/adap/flower/pull/1380), " +"[#1381](https://github.com/adap/flower/pull/1381), " +"[#1332](https://github.com/adap/flower/pull/1332), " +"[#1391](https://github.com/adap/flower/pull/1391), " +"[#1403](https://github.com/adap/flower/pull/1403), " +"[#1364](https://github.com/adap/flower/pull/1364), " +"[#1409](https://github.com/adap/flower/pull/1409), " +"[#1419](https://github.com/adap/flower/pull/1419), " +"[#1444](https://github.com/adap/flower/pull/1444), " +"[#1448](https://github.com/adap/flower/pull/1448), " +"[#1417](https://github.com/adap/flower/pull/1417), " +"[#1449](https://github.com/adap/flower/pull/1449), " +"[#1465](https://github.com/adap/flower/pull/1465), " +"[#1467](https://github.com/adap/flower/pull/1467))" msgstr "" -#: ../../source/ref-changelog.md:587 +#: ../../source/ref-changelog.md:679 msgid "" "There have been so many documentation updates that it doesn't even make " "sense to list them individually." msgstr "" -#: ../../source/ref-changelog.md:589 +#: ../../source/ref-changelog.md:681 msgid "" -"**Restructured documentation** ([#1387](https://github.com/adap/flower/" -"pull/1387))" +"**Restructured documentation** " +"([#1387](https://github.com/adap/flower/pull/1387))" msgstr "" -#: ../../source/ref-changelog.md:591 +#: ../../source/ref-changelog.md:683 msgid "" -"The documentation has been restructured to make it easier to navigate. This " -"is just the first step in a larger effort to make the Flower documentation " -"the best documentation of any project ever. Stay tuned!" +"The documentation has been restructured to make it easier to navigate. " +"This is just the first step in a larger effort to make the Flower " +"documentation the best documentation of any project ever. Stay tuned!" msgstr "" -#: ../../source/ref-changelog.md:593 +#: ../../source/ref-changelog.md:685 msgid "" -"**Open in Colab button** ([#1389](https://github.com/adap/flower/pull/1389))" +"**Open in Colab button** " +"([#1389](https://github.com/adap/flower/pull/1389))" msgstr "" -#: ../../source/ref-changelog.md:595 +#: ../../source/ref-changelog.md:687 msgid "" -"The four parts of the Flower Federated Learning Tutorial now come with a new " -"`Open in Colab` button. No need to install anything on your local machine, " -"you can now use and learn about Flower in your browser, it's only a single " -"click away." +"The four parts of the Flower Federated Learning Tutorial now come with a " +"new `Open in Colab` button. No need to install anything on your local " +"machine, you can now use and learn about Flower in your browser, it's " +"only a single click away." msgstr "" -#: ../../source/ref-changelog.md:597 +#: ../../source/ref-changelog.md:689 msgid "" -"**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468), " -"[#1470](https://github.com/adap/flower/pull/1470), [#1472](https://github." -"com/adap/flower/pull/1472), [#1473](https://github.com/adap/flower/" -"pull/1473), [#1474](https://github.com/adap/flower/pull/1474), [#1475]" -"(https://github.com/adap/flower/pull/1475))" +"**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468)," +" [#1470](https://github.com/adap/flower/pull/1470), " +"[#1472](https://github.com/adap/flower/pull/1472), " +"[#1473](https://github.com/adap/flower/pull/1473), " +"[#1474](https://github.com/adap/flower/pull/1474), " +"[#1475](https://github.com/adap/flower/pull/1475))" msgstr "" -#: ../../source/ref-changelog.md:599 +#: ../../source/ref-changelog.md:691 msgid "" "The Flower Federated Learning Tutorial has two brand-new parts covering " "custom strategies (still WIP) and the distinction between `Client` and " -"`NumPyClient`. The existing parts one and two have also been improved (many " -"small changes and fixes)." +"`NumPyClient`. The existing parts one and two have also been improved " +"(many small changes and fixes)." msgstr "" -#: ../../source/ref-changelog.md:605 +#: ../../source/ref-changelog.md:697 msgid "v1.0.0 (2022-07-28)" msgstr "" -#: ../../source/ref-changelog.md:607 +#: ../../source/ref-changelog.md:699 msgid "Highlights" msgstr "" -#: ../../source/ref-changelog.md:609 +#: ../../source/ref-changelog.md:701 msgid "Stable **Virtual Client Engine** (accessible via `start_simulation`)" msgstr "" -#: ../../source/ref-changelog.md:610 +#: ../../source/ref-changelog.md:702 msgid "All `Client`/`NumPyClient` methods are now optional" msgstr "" -#: ../../source/ref-changelog.md:611 +#: ../../source/ref-changelog.md:703 msgid "Configurable `get_parameters`" msgstr "" -#: ../../source/ref-changelog.md:612 +#: ../../source/ref-changelog.md:704 msgid "" -"Tons of small API cleanups resulting in a more coherent developer experience" +"Tons of small API cleanups resulting in a more coherent developer " +"experience" msgstr "" -#: ../../source/ref-changelog.md:616 +#: ../../source/ref-changelog.md:708 msgid "" "We would like to give our **special thanks** to all the contributors who " -"made Flower 1.0 possible (in reverse [GitHub Contributors](https://github." -"com/adap/flower/graphs/contributors) order):" -msgstr "" - -#: ../../source/ref-changelog.md:618 -msgid "" -"[@rtaiello](https://github.com/rtaiello), [@g-pichler](https://github.com/g-" -"pichler), [@rob-luke](https://github.com/rob-luke), [@andreea-zaharia]" -"(https://github.com/andreea-zaharia), [@kinshukdua](https://github.com/" -"kinshukdua), [@nfnt](https://github.com/nfnt), [@tatiana-s](https://github." -"com/tatiana-s), [@TParcollet](https://github.com/TParcollet), [@vballoli]" -"(https://github.com/vballoli), [@negedng](https://github.com/negedng), " -"[@RISHIKESHAVAN](https://github.com/RISHIKESHAVAN), [@hei411](https://github." -"com/hei411), [@SebastianSpeitel](https://github.com/SebastianSpeitel), " -"[@AmitChaulwar](https://github.com/AmitChaulwar), [@Rubiel1](https://github." -"com/Rubiel1), [@FANTOME-PAN](https://github.com/FANTOME-PAN), [@Rono-BC]" -"(https://github.com/Rono-BC), [@lbhm](https://github.com/lbhm), [@sishtiaq]" -"(https://github.com/sishtiaq), [@remde](https://github.com/remde), [@Jueun-" -"Park](https://github.com/Jueun-Park), [@architjen](https://github.com/" -"architjen), [@PratikGarai](https://github.com/PratikGarai), [@mrinaald]" -"(https://github.com/mrinaald), [@zliel](https://github.com/zliel), " -"[@MeiruiJiang](https://github.com/MeiruiJiang), [@sancarlim](https://github." -"com/sancarlim), [@gubertoli](https://github.com/gubertoli), [@Vingt100]" -"(https://github.com/Vingt100), [@MakGulati](https://github.com/MakGulati), " -"[@cozek](https://github.com/cozek), [@jafermarq](https://github.com/" -"jafermarq), [@sisco0](https://github.com/sisco0), [@akhilmathurs](https://" -"github.com/akhilmathurs), [@CanTuerk](https://github.com/CanTuerk), " -"[@mariaboerner1987](https://github.com/mariaboerner1987), [@pedropgusmao]" -"(https://github.com/pedropgusmao), [@tanertopal](https://github.com/" -"tanertopal), [@danieljanes](https://github.com/danieljanes)." -msgstr "" - -#: ../../source/ref-changelog.md:622 -msgid "" -"**All arguments must be passed as keyword arguments** ([#1338](https://" -"github.com/adap/flower/pull/1338))" +"made Flower 1.0 possible (in reverse [GitHub " +"Contributors](https://github.com/adap/flower/graphs/contributors) order):" +msgstr "" + +#: ../../source/ref-changelog.md:710 +msgid "" +"[@rtaiello](https://github.com/rtaiello), " +"[@g-pichler](https://github.com/g-pichler), [@rob-" +"luke](https://github.com/rob-luke), [@andreea-zaharia](https://github.com" +"/andreea-zaharia), [@kinshukdua](https://github.com/kinshukdua), " +"[@nfnt](https://github.com/nfnt), " +"[@tatiana-s](https://github.com/tatiana-s), " +"[@TParcollet](https://github.com/TParcollet), " +"[@vballoli](https://github.com/vballoli), " +"[@negedng](https://github.com/negedng), " +"[@RISHIKESHAVAN](https://github.com/RISHIKESHAVAN), " +"[@hei411](https://github.com/hei411), " +"[@SebastianSpeitel](https://github.com/SebastianSpeitel), " +"[@AmitChaulwar](https://github.com/AmitChaulwar), " +"[@Rubiel1](https://github.com/Rubiel1), [@FANTOME-PAN](https://github.com" +"/FANTOME-PAN), [@Rono-BC](https://github.com/Rono-BC), " +"[@lbhm](https://github.com/lbhm), " +"[@sishtiaq](https://github.com/sishtiaq), " +"[@remde](https://github.com/remde), [@Jueun-Park](https://github.com" +"/Jueun-Park), [@architjen](https://github.com/architjen), " +"[@PratikGarai](https://github.com/PratikGarai), " +"[@mrinaald](https://github.com/mrinaald), " +"[@zliel](https://github.com/zliel), " +"[@MeiruiJiang](https://github.com/MeiruiJiang), " +"[@sancarlim](https://github.com/sancarlim), " +"[@gubertoli](https://github.com/gubertoli), " +"[@Vingt100](https://github.com/Vingt100), " +"[@MakGulati](https://github.com/MakGulati), " +"[@cozek](https://github.com/cozek), " +"[@jafermarq](https://github.com/jafermarq), " +"[@sisco0](https://github.com/sisco0), " +"[@akhilmathurs](https://github.com/akhilmathurs), " +"[@CanTuerk](https://github.com/CanTuerk), " +"[@mariaboerner1987](https://github.com/mariaboerner1987), " +"[@pedropgusmao](https://github.com/pedropgusmao), " +"[@tanertopal](https://github.com/tanertopal), " +"[@danieljanes](https://github.com/danieljanes)." +msgstr "" + +#: ../../source/ref-changelog.md:714 +msgid "" +"**All arguments must be passed as keyword arguments** " +"([#1338](https://github.com/adap/flower/pull/1338))" msgstr "" -#: ../../source/ref-changelog.md:624 +#: ../../source/ref-changelog.md:716 msgid "" -"Pass all arguments as keyword arguments, positional arguments are not longer " -"supported. Code that uses positional arguments (e.g., " -"`start_client(\"127.0.0.1:8080\", FlowerClient())`) must add the keyword for " -"each positional argument (e.g., " -"`start_client(server_address=\"127.0.0.1:8080\", client=FlowerClient())`)." +"Pass all arguments as keyword arguments, positional arguments are not " +"longer supported. Code that uses positional arguments (e.g., " +"`start_client(\"127.0.0.1:8080\", FlowerClient())`) must add the keyword " +"for each positional argument (e.g., " +"`start_client(server_address=\"127.0.0.1:8080\", " +"client=FlowerClient())`)." msgstr "" -#: ../../source/ref-changelog.md:626 +#: ../../source/ref-changelog.md:718 msgid "" "**Introduce configuration object** `ServerConfig` **in** `start_server` " -"**and** `start_simulation` ([#1317](https://github.com/adap/flower/" -"pull/1317))" +"**and** `start_simulation` " +"([#1317](https://github.com/adap/flower/pull/1317))" msgstr "" -#: ../../source/ref-changelog.md:628 +#: ../../source/ref-changelog.md:720 msgid "" -"Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": 600.0}" -"`, `start_server` and `start_simulation` now expect a configuration object " -"of type `flwr.server.ServerConfig`. `ServerConfig` takes the same arguments " -"that as the previous config dict, but it makes writing type-safe code easier " -"and the default parameters values more transparent." +"Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": " +"600.0}`, `start_server` and `start_simulation` now expect a configuration" +" object of type `flwr.server.ServerConfig`. `ServerConfig` takes the same" +" arguments that as the previous config dict, but it makes writing type-" +"safe code easier and the default parameters values more transparent." msgstr "" -#: ../../source/ref-changelog.md:630 +#: ../../source/ref-changelog.md:722 msgid "" -"**Rename built-in strategy parameters for clarity** ([#1334](https://github." -"com/adap/flower/pull/1334))" +"**Rename built-in strategy parameters for clarity** " +"([#1334](https://github.com/adap/flower/pull/1334))" msgstr "" -#: ../../source/ref-changelog.md:632 +#: ../../source/ref-changelog.md:724 msgid "" "The following built-in strategy parameters were renamed to improve " "readability and consistency with other API's:" msgstr "" -#: ../../source/ref-changelog.md:634 +#: ../../source/ref-changelog.md:726 msgid "`fraction_eval` --> `fraction_evaluate`" msgstr "" -#: ../../source/ref-changelog.md:635 +#: ../../source/ref-changelog.md:727 msgid "`min_eval_clients` --> `min_evaluate_clients`" msgstr "" -#: ../../source/ref-changelog.md:636 +#: ../../source/ref-changelog.md:728 msgid "`eval_fn` --> `evaluate_fn`" msgstr "" -#: ../../source/ref-changelog.md:638 +#: ../../source/ref-changelog.md:730 msgid "" -"**Update default arguments of built-in strategies** ([#1278](https://github." -"com/adap/flower/pull/1278))" +"**Update default arguments of built-in strategies** " +"([#1278](https://github.com/adap/flower/pull/1278))" msgstr "" -#: ../../source/ref-changelog.md:640 +#: ../../source/ref-changelog.md:732 msgid "" "All built-in strategies now use `fraction_fit=1.0` and " -"`fraction_evaluate=1.0`, which means they select *all* currently available " -"clients for training and evaluation. Projects that relied on the previous " -"default values can get the previous behaviour by initializing the strategy " -"in the following way:" +"`fraction_evaluate=1.0`, which means they select *all* currently " +"available clients for training and evaluation. Projects that relied on " +"the previous default values can get the previous behaviour by " +"initializing the strategy in the following way:" msgstr "" -#: ../../source/ref-changelog.md:642 +#: ../../source/ref-changelog.md:734 msgid "`strategy = FedAvg(fraction_fit=0.1, fraction_evaluate=0.1)`" msgstr "" -#: ../../source/ref-changelog.md:644 +#: ../../source/ref-changelog.md:736 msgid "" -"**Add** `server_round` **to** `Strategy.evaluate` ([#1334](https://github." -"com/adap/flower/pull/1334))" +"**Add** `server_round` **to** `Strategy.evaluate` " +"([#1334](https://github.com/adap/flower/pull/1334))" msgstr "" -#: ../../source/ref-changelog.md:646 +#: ../../source/ref-changelog.md:738 msgid "" -"The `Strategy` method `evaluate` now receives the current round of federated " -"learning/evaluation as the first parameter." +"The `Strategy` method `evaluate` now receives the current round of " +"federated learning/evaluation as the first parameter." msgstr "" -#: ../../source/ref-changelog.md:648 +#: ../../source/ref-changelog.md:740 msgid "" "**Add** `server_round` **and** `config` **parameters to** `evaluate_fn` " "([#1334](https://github.com/adap/flower/pull/1334))" msgstr "" -#: ../../source/ref-changelog.md:650 +#: ../../source/ref-changelog.md:742 msgid "" "The `evaluate_fn` passed to built-in strategies like `FedAvg` now takes " "three parameters: (1) The current round of federated learning/evaluation " -"(`server_round`), (2) the model parameters to evaluate (`parameters`), and " -"(3) a config dictionary (`config`)." +"(`server_round`), (2) the model parameters to evaluate (`parameters`), " +"and (3) a config dictionary (`config`)." msgstr "" -#: ../../source/ref-changelog.md:652 +#: ../../source/ref-changelog.md:744 msgid "" -"**Rename** `rnd` **to** `server_round` ([#1321](https://github.com/adap/" -"flower/pull/1321))" +"**Rename** `rnd` **to** `server_round` " +"([#1321](https://github.com/adap/flower/pull/1321))" msgstr "" -#: ../../source/ref-changelog.md:654 +#: ../../source/ref-changelog.md:746 msgid "" "Several Flower methods and functions (`evaluate_fn`, `configure_fit`, " "`aggregate_fit`, `configure_evaluate`, `aggregate_evaluate`) receive the " -"current round of federated learning/evaluation as their first parameter. To " -"improve reaability and avoid confusion with *random*, this parameter has " -"been renamed from `rnd` to `server_round`." +"current round of federated learning/evaluation as their first parameter. " +"To improve reaability and avoid confusion with *random*, this parameter " +"has been renamed from `rnd` to `server_round`." msgstr "" -#: ../../source/ref-changelog.md:656 +#: ../../source/ref-changelog.md:748 msgid "" -"**Move** `flwr.dataset` **to** `flwr_baselines` ([#1273](https://github.com/" -"adap/flower/pull/1273))" +"**Move** `flwr.dataset` **to** `flwr_baselines` " +"([#1273](https://github.com/adap/flower/pull/1273))" msgstr "" -#: ../../source/ref-changelog.md:658 -msgid "" -"The experimental package `flwr.dataset` was migrated to Flower Baselines." +#: ../../source/ref-changelog.md:750 +msgid "The experimental package `flwr.dataset` was migrated to Flower Baselines." msgstr "" -#: ../../source/ref-changelog.md:660 +#: ../../source/ref-changelog.md:752 msgid "" -"**Remove experimental strategies** ([#1280](https://github.com/adap/flower/" -"pull/1280))" +"**Remove experimental strategies** " +"([#1280](https://github.com/adap/flower/pull/1280))" msgstr "" -#: ../../source/ref-changelog.md:662 +#: ../../source/ref-changelog.md:754 msgid "" "Remove unmaintained experimental strategies (`FastAndSlow`, `FedFSv0`, " "`FedFSv1`)." msgstr "" -#: ../../source/ref-changelog.md:664 +#: ../../source/ref-changelog.md:756 msgid "" -"**Rename** `Weights` **to** `NDArrays` ([#1258](https://github.com/adap/" -"flower/pull/1258), [#1259](https://github.com/adap/flower/pull/1259))" +"**Rename** `Weights` **to** `NDArrays` " +"([#1258](https://github.com/adap/flower/pull/1258), " +"[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" -#: ../../source/ref-changelog.md:666 +#: ../../source/ref-changelog.md:758 msgid "" "`flwr.common.Weights` was renamed to `flwr.common.NDArrays` to better " "capture what this type is all about." msgstr "" -#: ../../source/ref-changelog.md:668 +#: ../../source/ref-changelog.md:760 msgid "" -"**Remove antiquated** `force_final_distributed_eval` **from** `start_server` " -"([#1258](https://github.com/adap/flower/pull/1258), [#1259](https://github." -"com/adap/flower/pull/1259))" +"**Remove antiquated** `force_final_distributed_eval` **from** " +"`start_server` ([#1258](https://github.com/adap/flower/pull/1258), " +"[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" -#: ../../source/ref-changelog.md:670 +#: ../../source/ref-changelog.md:762 msgid "" -"The `start_server` parameter `force_final_distributed_eval` has long been a " -"historic artefact, in this release it is finally gone for good." +"The `start_server` parameter `force_final_distributed_eval` has long been" +" a historic artefact, in this release it is finally gone for good." msgstr "" -#: ../../source/ref-changelog.md:672 +#: ../../source/ref-changelog.md:764 msgid "" -"**Make** `get_parameters` **configurable** ([#1242](https://github.com/adap/" -"flower/pull/1242))" +"**Make** `get_parameters` **configurable** " +"([#1242](https://github.com/adap/flower/pull/1242))" msgstr "" -#: ../../source/ref-changelog.md:674 +#: ../../source/ref-changelog.md:766 msgid "" "The `get_parameters` method now accepts a configuration dictionary, just " "like `get_properties`, `fit`, and `evaluate`." msgstr "" -#: ../../source/ref-changelog.md:676 +#: ../../source/ref-changelog.md:768 msgid "" "**Replace** `num_rounds` **in** `start_simulation` **with new** `config` " "**parameter** ([#1281](https://github.com/adap/flower/pull/1281))" msgstr "" -#: ../../source/ref-changelog.md:678 +#: ../../source/ref-changelog.md:770 msgid "" "The `start_simulation` function now accepts a configuration dictionary " -"`config` instead of the `num_rounds` integer. This improves the consistency " -"between `start_simulation` and `start_server` and makes transitioning " -"between the two easier." +"`config` instead of the `num_rounds` integer. This improves the " +"consistency between `start_simulation` and `start_server` and makes " +"transitioning between the two easier." msgstr "" -#: ../../source/ref-changelog.md:682 +#: ../../source/ref-changelog.md:774 msgid "" -"**Support Python 3.10** ([#1320](https://github.com/adap/flower/pull/1320))" +"**Support Python 3.10** " +"([#1320](https://github.com/adap/flower/pull/1320))" msgstr "" -#: ../../source/ref-changelog.md:684 +#: ../../source/ref-changelog.md:776 msgid "" -"The previous Flower release introduced experimental support for Python 3.10, " -"this release declares Python 3.10 support as stable." +"The previous Flower release introduced experimental support for Python " +"3.10, this release declares Python 3.10 support as stable." msgstr "" -#: ../../source/ref-changelog.md:686 +#: ../../source/ref-changelog.md:778 msgid "" -"**Make all** `Client` **and** `NumPyClient` **methods optional** ([#1260]" -"(https://github.com/adap/flower/pull/1260), [#1277](https://github.com/adap/" -"flower/pull/1277))" +"**Make all** `Client` **and** `NumPyClient` **methods optional** " +"([#1260](https://github.com/adap/flower/pull/1260), " +"[#1277](https://github.com/adap/flower/pull/1277))" msgstr "" -#: ../../source/ref-changelog.md:688 +#: ../../source/ref-changelog.md:780 msgid "" "The `Client`/`NumPyClient` methods `get_properties`, `get_parameters`, " -"`fit`, and `evaluate` are all optional. This enables writing clients that " -"implement, for example, only `fit`, but no other method. No need to " +"`fit`, and `evaluate` are all optional. This enables writing clients that" +" implement, for example, only `fit`, but no other method. No need to " "implement `evaluate` when using centralized evaluation!" msgstr "" -#: ../../source/ref-changelog.md:690 +#: ../../source/ref-changelog.md:782 msgid "" -"**Enable passing a** `Server` **instance to** `start_simulation` ([#1281]" -"(https://github.com/adap/flower/pull/1281))" +"**Enable passing a** `Server` **instance to** `start_simulation` " +"([#1281](https://github.com/adap/flower/pull/1281))" msgstr "" -#: ../../source/ref-changelog.md:692 +#: ../../source/ref-changelog.md:784 msgid "" -"Similar to `start_server`, `start_simulation` now accepts a full `Server` " -"instance. This enables users to heavily customize the execution of " -"eperiments and opens the door to running, for example, async FL using the " -"Virtual Client Engine." +"Similar to `start_server`, `start_simulation` now accepts a full `Server`" +" instance. This enables users to heavily customize the execution of " +"eperiments and opens the door to running, for example, async FL using the" +" Virtual Client Engine." msgstr "" -#: ../../source/ref-changelog.md:694 +#: ../../source/ref-changelog.md:786 msgid "" -"**Update code examples** ([#1291](https://github.com/adap/flower/pull/1291), " -"[#1286](https://github.com/adap/flower/pull/1286), [#1282](https://github." -"com/adap/flower/pull/1282))" +"**Update code examples** " +"([#1291](https://github.com/adap/flower/pull/1291), " +"[#1286](https://github.com/adap/flower/pull/1286), " +"[#1282](https://github.com/adap/flower/pull/1282))" msgstr "" -#: ../../source/ref-changelog.md:696 +#: ../../source/ref-changelog.md:788 msgid "" -"Many code examples received small or even large maintenance updates, among " -"them are" +"Many code examples received small or even large maintenance updates, " +"among them are" msgstr "" -#: ../../source/ref-changelog.md:698 +#: ../../source/ref-changelog.md:790 msgid "`scikit-learn`" msgstr "" -#: ../../source/ref-changelog.md:699 +#: ../../source/ref-changelog.md:791 msgid "`simulation_pytorch`" msgstr "" -#: ../../source/ref-changelog.md:700 +#: ../../source/ref-changelog.md:792 msgid "`quickstart_pytorch`" msgstr "" -#: ../../source/ref-changelog.md:701 +#: ../../source/ref-changelog.md:793 msgid "`quickstart_simulation`" msgstr "" -#: ../../source/ref-changelog.md:702 +#: ../../source/ref-changelog.md:794 msgid "`quickstart_tensorflow`" msgstr "" -#: ../../source/ref-changelog.md:703 +#: ../../source/ref-changelog.md:795 msgid "`advanced_tensorflow`" msgstr "" -#: ../../source/ref-changelog.md:705 +#: ../../source/ref-changelog.md:797 msgid "" -"**Remove the obsolete simulation example** ([#1328](https://github.com/adap/" -"flower/pull/1328))" +"**Remove the obsolete simulation example** " +"([#1328](https://github.com/adap/flower/pull/1328))" msgstr "" -#: ../../source/ref-changelog.md:707 +#: ../../source/ref-changelog.md:799 msgid "" "Removes the obsolete `simulation` example and renames " "`quickstart_simulation` to `simulation_tensorflow` so it fits withs the " "naming of `simulation_pytorch`" msgstr "" -#: ../../source/ref-changelog.md:709 +#: ../../source/ref-changelog.md:801 msgid "" -"**Update documentation** ([#1223](https://github.com/adap/flower/pull/1223), " -"[#1209](https://github.com/adap/flower/pull/1209), [#1251](https://github." -"com/adap/flower/pull/1251), [#1257](https://github.com/adap/flower/" -"pull/1257), [#1267](https://github.com/adap/flower/pull/1267), [#1268]" -"(https://github.com/adap/flower/pull/1268), [#1300](https://github.com/adap/" -"flower/pull/1300), [#1304](https://github.com/adap/flower/pull/1304), [#1305]" -"(https://github.com/adap/flower/pull/1305), [#1307](https://github.com/adap/" -"flower/pull/1307))" +"**Update documentation** " +"([#1223](https://github.com/adap/flower/pull/1223), " +"[#1209](https://github.com/adap/flower/pull/1209), " +"[#1251](https://github.com/adap/flower/pull/1251), " +"[#1257](https://github.com/adap/flower/pull/1257), " +"[#1267](https://github.com/adap/flower/pull/1267), " +"[#1268](https://github.com/adap/flower/pull/1268), " +"[#1300](https://github.com/adap/flower/pull/1300), " +"[#1304](https://github.com/adap/flower/pull/1304), " +"[#1305](https://github.com/adap/flower/pull/1305), " +"[#1307](https://github.com/adap/flower/pull/1307))" msgstr "" -#: ../../source/ref-changelog.md:711 +#: ../../source/ref-changelog.md:803 msgid "" "One substantial documentation update fixes multiple smaller rendering " "issues, makes titles more succinct to improve navigation, removes a " -"deprecated library, updates documentation dependencies, includes the `flwr." -"common` module in the API reference, includes support for markdown-based " -"documentation, migrates the changelog from `.rst` to `.md`, and fixes a " -"number of smaller details!" +"deprecated library, updates documentation dependencies, includes the " +"`flwr.common` module in the API reference, includes support for markdown-" +"based documentation, migrates the changelog from `.rst` to `.md`, and " +"fixes a number of smaller details!" msgstr "" -#: ../../source/ref-changelog.md:713 ../../source/ref-changelog.md:768 -#: ../../source/ref-changelog.md:837 ../../source/ref-changelog.md:876 +#: ../../source/ref-changelog.md:805 ../../source/ref-changelog.md:860 +#: ../../source/ref-changelog.md:929 ../../source/ref-changelog.md:968 msgid "**Minor updates**" msgstr "" -#: ../../source/ref-changelog.md:715 +#: ../../source/ref-changelog.md:807 msgid "" -"Add round number to fit and evaluate log messages ([#1266](https://github." -"com/adap/flower/pull/1266))" +"Add round number to fit and evaluate log messages " +"([#1266](https://github.com/adap/flower/pull/1266))" msgstr "" -#: ../../source/ref-changelog.md:716 +#: ../../source/ref-changelog.md:808 msgid "" -"Add secure gRPC connection to the `advanced_tensorflow` code example ([#847]" -"(https://github.com/adap/flower/pull/847))" +"Add secure gRPC connection to the `advanced_tensorflow` code example " +"([#847](https://github.com/adap/flower/pull/847))" msgstr "" -#: ../../source/ref-changelog.md:717 +#: ../../source/ref-changelog.md:809 msgid "" -"Update developer tooling ([#1231](https://github.com/adap/flower/pull/1231), " -"[#1276](https://github.com/adap/flower/pull/1276), [#1301](https://github." -"com/adap/flower/pull/1301), [#1310](https://github.com/adap/flower/" -"pull/1310))" +"Update developer tooling " +"([#1231](https://github.com/adap/flower/pull/1231), " +"[#1276](https://github.com/adap/flower/pull/1276), " +"[#1301](https://github.com/adap/flower/pull/1301), " +"[#1310](https://github.com/adap/flower/pull/1310))" msgstr "" -#: ../../source/ref-changelog.md:718 +#: ../../source/ref-changelog.md:810 msgid "" -"Rename ProtoBuf messages to improve consistency ([#1214](https://github.com/" -"adap/flower/pull/1214), [#1258](https://github.com/adap/flower/pull/1258), " +"Rename ProtoBuf messages to improve consistency " +"([#1214](https://github.com/adap/flower/pull/1214), " +"[#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" -#: ../../source/ref-changelog.md:720 +#: ../../source/ref-changelog.md:812 msgid "v0.19.0 (2022-05-18)" msgstr "" -#: ../../source/ref-changelog.md:724 +#: ../../source/ref-changelog.md:816 msgid "" -"**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** ([#919](https://" -"github.com/adap/flower/pull/919), [#1127](https://github.com/adap/flower/" -"pull/1127), [#914](https://github.com/adap/flower/pull/914))" +"**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** " +"([#919](https://github.com/adap/flower/pull/919), " +"[#1127](https://github.com/adap/flower/pull/1127), " +"[#914](https://github.com/adap/flower/pull/914))" msgstr "" -#: ../../source/ref-changelog.md:726 +#: ../../source/ref-changelog.md:818 msgid "" "The first preview release of Flower Baselines has arrived! We're " "kickstarting Flower Baselines with implementations of FedOpt (FedYogi, " -"FedAdam, FedAdagrad), FedBN, and FedAvgM. Check the documentation on how to " -"use [Flower Baselines](https://flower.ai/docs/using-baselines.html). With " -"this first preview release we're also inviting the community to [contribute " -"their own baselines](https://flower.ai/docs/baselines/how-to-contribute-" -"baselines.html)." +"FedAdam, FedAdagrad), FedBN, and FedAvgM. Check the documentation on how " +"to use [Flower Baselines](https://flower.ai/docs/using-baselines.html). " +"With this first preview release we're also inviting the community to " +"[contribute their own baselines](https://flower.ai/docs/baselines/how-to-" +"contribute-baselines.html)." msgstr "" -#: ../../source/ref-changelog.md:728 +#: ../../source/ref-changelog.md:820 msgid "" -"**C++ client SDK (preview) and code example** ([#1111](https://github.com/" -"adap/flower/pull/1111))" +"**C++ client SDK (preview) and code example** " +"([#1111](https://github.com/adap/flower/pull/1111))" msgstr "" -#: ../../source/ref-changelog.md:730 +#: ../../source/ref-changelog.md:822 msgid "" -"Preview support for Flower clients written in C++. The C++ preview includes " -"a Flower client SDK and a quickstart code example that demonstrates a simple " -"C++ client using the SDK." +"Preview support for Flower clients written in C++. The C++ preview " +"includes a Flower client SDK and a quickstart code example that " +"demonstrates a simple C++ client using the SDK." msgstr "" -#: ../../source/ref-changelog.md:732 +#: ../../source/ref-changelog.md:824 msgid "" -"**Add experimental support for Python 3.10 and Python 3.11** ([#1135]" -"(https://github.com/adap/flower/pull/1135))" +"**Add experimental support for Python 3.10 and Python 3.11** " +"([#1135](https://github.com/adap/flower/pull/1135))" msgstr "" -#: ../../source/ref-changelog.md:734 +#: ../../source/ref-changelog.md:826 msgid "" -"Python 3.10 is the latest stable release of Python and Python 3.11 is due to " -"be released in October. This Flower release adds experimental support for " -"both Python versions." +"Python 3.10 is the latest stable release of Python and Python 3.11 is due" +" to be released in October. This Flower release adds experimental support" +" for both Python versions." msgstr "" -#: ../../source/ref-changelog.md:736 +#: ../../source/ref-changelog.md:828 msgid "" -"**Aggregate custom metrics through user-provided functions** ([#1144]" -"(https://github.com/adap/flower/pull/1144))" +"**Aggregate custom metrics through user-provided functions** " +"([#1144](https://github.com/adap/flower/pull/1144))" msgstr "" -#: ../../source/ref-changelog.md:738 +#: ../../source/ref-changelog.md:830 msgid "" -"Custom metrics (e.g., `accuracy`) can now be aggregated without having to " -"customize the strategy. Built-in strategies support two new arguments, " +"Custom metrics (e.g., `accuracy`) can now be aggregated without having to" +" customize the strategy. Built-in strategies support two new arguments, " "`fit_metrics_aggregation_fn` and `evaluate_metrics_aggregation_fn`, that " "allow passing custom metric aggregation functions." msgstr "" -#: ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:832 msgid "" -"**User-configurable round timeout** ([#1162](https://github.com/adap/flower/" -"pull/1162))" +"**User-configurable round timeout** " +"([#1162](https://github.com/adap/flower/pull/1162))" msgstr "" -#: ../../source/ref-changelog.md:742 +#: ../../source/ref-changelog.md:834 msgid "" "A new configuration value allows the round timeout to be set for " -"`start_server` and `start_simulation`. If the `config` dictionary contains a " -"`round_timeout` key (with a `float` value in seconds), the server will wait " -"*at least* `round_timeout` seconds before it closes the connection." +"`start_server` and `start_simulation`. If the `config` dictionary " +"contains a `round_timeout` key (with a `float` value in seconds), the " +"server will wait *at least* `round_timeout` seconds before it closes the " +"connection." msgstr "" -#: ../../source/ref-changelog.md:744 +#: ../../source/ref-changelog.md:836 msgid "" -"**Enable both federated evaluation and centralized evaluation to be used at " -"the same time in all built-in strategies** ([#1091](https://github.com/adap/" -"flower/pull/1091))" +"**Enable both federated evaluation and centralized evaluation to be used " +"at the same time in all built-in strategies** " +"([#1091](https://github.com/adap/flower/pull/1091))" msgstr "" -#: ../../source/ref-changelog.md:746 +#: ../../source/ref-changelog.md:838 msgid "" -"Built-in strategies can now perform both federated evaluation (i.e., client-" -"side) and centralized evaluation (i.e., server-side) in the same round. " -"Federated evaluation can be disabled by setting `fraction_eval` to `0.0`." +"Built-in strategies can now perform both federated evaluation (i.e., " +"client-side) and centralized evaluation (i.e., server-side) in the same " +"round. Federated evaluation can be disabled by setting `fraction_eval` to" +" `0.0`." msgstr "" -#: ../../source/ref-changelog.md:748 +#: ../../source/ref-changelog.md:840 msgid "" -"**Two new Jupyter Notebook tutorials** ([#1141](https://github.com/adap/" -"flower/pull/1141))" +"**Two new Jupyter Notebook tutorials** " +"([#1141](https://github.com/adap/flower/pull/1141))" msgstr "" -#: ../../source/ref-changelog.md:750 +#: ../../source/ref-changelog.md:842 msgid "" -"Two Jupyter Notebook tutorials (compatible with Google Colab) explain basic " -"and intermediate Flower features:" +"Two Jupyter Notebook tutorials (compatible with Google Colab) explain " +"basic and intermediate Flower features:" msgstr "" -#: ../../source/ref-changelog.md:752 +#: ../../source/ref-changelog.md:844 msgid "" -"*An Introduction to Federated Learning*: [Open in Colab](https://colab." -"research.google.com/github/adap/flower/blob/main/tutorials/Flower-1-Intro-to-" -"FL-PyTorch.ipynb)" +"*An Introduction to Federated Learning*: [Open in " +"Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" +"-Intro-to-FL-PyTorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:754 +#: ../../source/ref-changelog.md:846 msgid "" -"*Using Strategies in Federated Learning*: [Open in Colab](https://colab." -"research.google.com/github/adap/flower/blob/main/tutorials/Flower-2-" -"Strategies-in-FL-PyTorch.ipynb)" +"*Using Strategies in Federated Learning*: [Open in " +"Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" +"-Strategies-in-FL-PyTorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:756 +#: ../../source/ref-changelog.md:848 msgid "" -"**New FedAvgM strategy (Federated Averaging with Server Momentum)** ([#1076]" -"(https://github.com/adap/flower/pull/1076))" +"**New FedAvgM strategy (Federated Averaging with Server Momentum)** " +"([#1076](https://github.com/adap/flower/pull/1076))" msgstr "" -#: ../../source/ref-changelog.md:758 +#: ../../source/ref-changelog.md:850 msgid "" "The new `FedAvgM` strategy implements Federated Averaging with Server " "Momentum \\[Hsu et al., 2019\\]." msgstr "" -#: ../../source/ref-changelog.md:760 +#: ../../source/ref-changelog.md:852 msgid "" -"**New advanced PyTorch code example** ([#1007](https://github.com/adap/" -"flower/pull/1007))" +"**New advanced PyTorch code example** " +"([#1007](https://github.com/adap/flower/pull/1007))" msgstr "" -#: ../../source/ref-changelog.md:762 +#: ../../source/ref-changelog.md:854 msgid "" "A new code example (`advanced_pytorch`) demonstrates advanced Flower " "concepts with PyTorch." msgstr "" -#: ../../source/ref-changelog.md:764 +#: ../../source/ref-changelog.md:856 msgid "" -"**New JAX code example** ([#906](https://github.com/adap/flower/pull/906), " +"**New JAX code example** " +"([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143))" msgstr "" -#: ../../source/ref-changelog.md:766 +#: ../../source/ref-changelog.md:858 msgid "" "A new code example (`jax_from_centralized_to_federated`) shows federated " "learning with JAX and Flower." msgstr "" -#: ../../source/ref-changelog.md:770 +#: ../../source/ref-changelog.md:862 msgid "" "New option to keep Ray running if Ray was already initialized in " "`start_simulation` ([#1177](https://github.com/adap/flower/pull/1177))" msgstr "" -#: ../../source/ref-changelog.md:771 +#: ../../source/ref-changelog.md:863 msgid "" "Add support for custom `ClientManager` as a `start_simulation` parameter " "([#1171](https://github.com/adap/flower/pull/1171))" msgstr "" -#: ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:864 msgid "" -"New documentation for [implementing strategies](https://flower.ai/docs/" -"framework/how-to-implement-strategies.html) ([#1097](https://github.com/adap/" -"flower/pull/1097), [#1175](https://github.com/adap/flower/pull/1175))" +"New documentation for [implementing " +"strategies](https://flower.ai/docs/framework/how-to-implement-" +"strategies.html) ([#1097](https://github.com/adap/flower/pull/1097), " +"[#1175](https://github.com/adap/flower/pull/1175))" msgstr "" -#: ../../source/ref-changelog.md:773 +#: ../../source/ref-changelog.md:865 msgid "" -"New mobile-friendly documentation theme ([#1174](https://github.com/adap/" -"flower/pull/1174))" +"New mobile-friendly documentation theme " +"([#1174](https://github.com/adap/flower/pull/1174))" msgstr "" -#: ../../source/ref-changelog.md:774 +#: ../../source/ref-changelog.md:866 msgid "" "Limit version range for (optional) `ray` dependency to include only " -"compatible releases (`>=1.9.2,<1.12.0`) ([#1205](https://github.com/adap/" -"flower/pull/1205))" +"compatible releases (`>=1.9.2,<1.12.0`) " +"([#1205](https://github.com/adap/flower/pull/1205))" msgstr "" -#: ../../source/ref-changelog.md:778 +#: ../../source/ref-changelog.md:870 msgid "" -"**Remove deprecated support for Python 3.6** ([#871](https://github.com/adap/" -"flower/pull/871))" +"**Remove deprecated support for Python 3.6** " +"([#871](https://github.com/adap/flower/pull/871))" msgstr "" -#: ../../source/ref-changelog.md:779 +#: ../../source/ref-changelog.md:871 msgid "" -"**Remove deprecated KerasClient** ([#857](https://github.com/adap/flower/" -"pull/857))" +"**Remove deprecated KerasClient** " +"([#857](https://github.com/adap/flower/pull/857))" msgstr "" -#: ../../source/ref-changelog.md:780 +#: ../../source/ref-changelog.md:872 msgid "" -"**Remove deprecated no-op extra installs** ([#973](https://github.com/adap/" -"flower/pull/973))" +"**Remove deprecated no-op extra installs** " +"([#973](https://github.com/adap/flower/pull/973))" msgstr "" -#: ../../source/ref-changelog.md:781 +#: ../../source/ref-changelog.md:873 msgid "" "**Remove deprecated proto fields from** `FitRes` **and** `EvaluateRes` " "([#869](https://github.com/adap/flower/pull/869))" msgstr "" -#: ../../source/ref-changelog.md:782 +#: ../../source/ref-changelog.md:874 msgid "" -"**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** ([#1107]" -"(https://github.com/adap/flower/pull/1107))" +"**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** " +"([#1107](https://github.com/adap/flower/pull/1107))" msgstr "" -#: ../../source/ref-changelog.md:783 +#: ../../source/ref-changelog.md:875 msgid "" -"**Remove deprecated DefaultStrategy strategy** ([#1142](https://github.com/" -"adap/flower/pull/1142))" +"**Remove deprecated DefaultStrategy strategy** " +"([#1142](https://github.com/adap/flower/pull/1142))" msgstr "" -#: ../../source/ref-changelog.md:784 +#: ../../source/ref-changelog.md:876 msgid "" -"**Remove deprecated support for eval_fn accuracy return value** ([#1142]" -"(https://github.com/adap/flower/pull/1142))" +"**Remove deprecated support for eval_fn accuracy return value** " +"([#1142](https://github.com/adap/flower/pull/1142))" msgstr "" -#: ../../source/ref-changelog.md:785 +#: ../../source/ref-changelog.md:877 msgid "" "**Remove deprecated support for passing initial parameters as NumPy " "ndarrays** ([#1142](https://github.com/adap/flower/pull/1142))" msgstr "" -#: ../../source/ref-changelog.md:787 +#: ../../source/ref-changelog.md:879 msgid "v0.18.0 (2022-02-28)" msgstr "" -#: ../../source/ref-changelog.md:791 +#: ../../source/ref-changelog.md:883 msgid "" "**Improved Virtual Client Engine compatibility with Jupyter Notebook / " -"Google Colab** ([#866](https://github.com/adap/flower/pull/866), [#872]" -"(https://github.com/adap/flower/pull/872), [#833](https://github.com/adap/" -"flower/pull/833), [#1036](https://github.com/adap/flower/pull/1036))" +"Google Colab** ([#866](https://github.com/adap/flower/pull/866), " +"[#872](https://github.com/adap/flower/pull/872), " +"[#833](https://github.com/adap/flower/pull/833), " +"[#1036](https://github.com/adap/flower/pull/1036))" msgstr "" -#: ../../source/ref-changelog.md:793 +#: ../../source/ref-changelog.md:885 msgid "" -"Simulations (using the Virtual Client Engine through `start_simulation`) now " -"work more smoothly on Jupyter Notebooks (incl. Google Colab) after " +"Simulations (using the Virtual Client Engine through `start_simulation`) " +"now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " "installing Flower with the `simulation` extra (`pip install " "flwr[simulation]`)." msgstr "" -#: ../../source/ref-changelog.md:795 +#: ../../source/ref-changelog.md:887 msgid "" -"**New Jupyter Notebook code example** ([#833](https://github.com/adap/flower/" -"pull/833))" +"**New Jupyter Notebook code example** " +"([#833](https://github.com/adap/flower/pull/833))" msgstr "" -#: ../../source/ref-changelog.md:797 +#: ../../source/ref-changelog.md:889 msgid "" -"A new code example (`quickstart_simulation`) demonstrates Flower simulations " -"using the Virtual Client Engine through Jupyter Notebook (incl. Google " -"Colab)." +"A new code example (`quickstart_simulation`) demonstrates Flower " +"simulations using the Virtual Client Engine through Jupyter Notebook " +"(incl. Google Colab)." msgstr "" -#: ../../source/ref-changelog.md:799 +#: ../../source/ref-changelog.md:891 msgid "" -"**Client properties (feature preview)** ([#795](https://github.com/adap/" -"flower/pull/795))" +"**Client properties (feature preview)** " +"([#795](https://github.com/adap/flower/pull/795))" msgstr "" -#: ../../source/ref-changelog.md:801 +#: ../../source/ref-changelog.md:893 msgid "" -"Clients can implement a new method `get_properties` to enable server-side " -"strategies to query client properties." +"Clients can implement a new method `get_properties` to enable server-side" +" strategies to query client properties." msgstr "" -#: ../../source/ref-changelog.md:803 +#: ../../source/ref-changelog.md:895 msgid "" -"**Experimental Android support with TFLite** ([#865](https://github.com/adap/" -"flower/pull/865))" +"**Experimental Android support with TFLite** " +"([#865](https://github.com/adap/flower/pull/865))" msgstr "" -#: ../../source/ref-changelog.md:805 +#: ../../source/ref-changelog.md:897 msgid "" "Android support has finally arrived in `main`! Flower is both client-" "agnostic and framework-agnostic by design. One can integrate arbitrary " -"client platforms and with this release, using Flower on Android has become a " -"lot easier." +"client platforms and with this release, using Flower on Android has " +"become a lot easier." msgstr "" -#: ../../source/ref-changelog.md:807 +#: ../../source/ref-changelog.md:899 msgid "" -"The example uses TFLite on the client side, along with a new `FedAvgAndroid` " -"strategy. The Android client and `FedAvgAndroid` are still experimental, but " -"they are a first step towards a fully-fledged Android SDK and a unified " -"`FedAvg` implementation that integrated the new functionality from " -"`FedAvgAndroid`." +"The example uses TFLite on the client side, along with a new " +"`FedAvgAndroid` strategy. The Android client and `FedAvgAndroid` are " +"still experimental, but they are a first step towards a fully-fledged " +"Android SDK and a unified `FedAvg` implementation that integrated the new" +" functionality from `FedAvgAndroid`." msgstr "" -#: ../../source/ref-changelog.md:809 +#: ../../source/ref-changelog.md:901 msgid "" -"**Make gRPC keepalive time user-configurable and decrease default keepalive " -"time** ([#1069](https://github.com/adap/flower/pull/1069))" +"**Make gRPC keepalive time user-configurable and decrease default " +"keepalive time** ([#1069](https://github.com/adap/flower/pull/1069))" msgstr "" -#: ../../source/ref-changelog.md:811 +#: ../../source/ref-changelog.md:903 msgid "" "The default gRPC keepalive time has been reduced to increase the " -"compatibility of Flower with more cloud environments (for example, Microsoft " -"Azure). Users can configure the keepalive time to customize the gRPC stack " -"based on specific requirements." +"compatibility of Flower with more cloud environments (for example, " +"Microsoft Azure). Users can configure the keepalive time to customize the" +" gRPC stack based on specific requirements." msgstr "" -#: ../../source/ref-changelog.md:813 +#: ../../source/ref-changelog.md:905 msgid "" -"**New differential privacy example using Opacus and PyTorch** ([#805]" -"(https://github.com/adap/flower/pull/805))" +"**New differential privacy example using Opacus and PyTorch** " +"([#805](https://github.com/adap/flower/pull/805))" msgstr "" -#: ../../source/ref-changelog.md:815 +#: ../../source/ref-changelog.md:907 msgid "" -"A new code example (`opacus`) demonstrates differentially-private federated " -"learning with Opacus, PyTorch, and Flower." +"A new code example (`opacus`) demonstrates differentially-private " +"federated learning with Opacus, PyTorch, and Flower." msgstr "" -#: ../../source/ref-changelog.md:817 +#: ../../source/ref-changelog.md:909 msgid "" -"**New Hugging Face Transformers code example** ([#863](https://github.com/" -"adap/flower/pull/863))" +"**New Hugging Face Transformers code example** " +"([#863](https://github.com/adap/flower/pull/863))" msgstr "" -#: ../../source/ref-changelog.md:819 +#: ../../source/ref-changelog.md:911 msgid "" -"A new code example (`quickstart_huggingface`) demonstrates usage of Hugging " -"Face Transformers with Flower." +"A new code example (`quickstart_huggingface`) demonstrates usage of " +"Hugging Face Transformers with Flower." msgstr "" -#: ../../source/ref-changelog.md:821 +#: ../../source/ref-changelog.md:913 msgid "" -"**New MLCube code example** ([#779](https://github.com/adap/flower/" -"pull/779), [#1034](https://github.com/adap/flower/pull/1034), [#1065]" -"(https://github.com/adap/flower/pull/1065), [#1090](https://github.com/adap/" -"flower/pull/1090))" +"**New MLCube code example** " +"([#779](https://github.com/adap/flower/pull/779), " +"[#1034](https://github.com/adap/flower/pull/1034), " +"[#1065](https://github.com/adap/flower/pull/1065), " +"[#1090](https://github.com/adap/flower/pull/1090))" msgstr "" -#: ../../source/ref-changelog.md:823 +#: ../../source/ref-changelog.md:915 msgid "" -"A new code example (`quickstart_mlcube`) demonstrates usage of MLCube with " -"Flower." +"A new code example (`quickstart_mlcube`) demonstrates usage of MLCube " +"with Flower." msgstr "" -#: ../../source/ref-changelog.md:825 +#: ../../source/ref-changelog.md:917 msgid "" -"**SSL-enabled server and client** ([#842](https://github.com/adap/flower/" -"pull/842), [#844](https://github.com/adap/flower/pull/844), [#845](https://" -"github.com/adap/flower/pull/845), [#847](https://github.com/adap/flower/" -"pull/847), [#993](https://github.com/adap/flower/pull/993), [#994](https://" -"github.com/adap/flower/pull/994))" +"**SSL-enabled server and client** " +"([#842](https://github.com/adap/flower/pull/842), " +"[#844](https://github.com/adap/flower/pull/844), " +"[#845](https://github.com/adap/flower/pull/845), " +"[#847](https://github.com/adap/flower/pull/847), " +"[#993](https://github.com/adap/flower/pull/993), " +"[#994](https://github.com/adap/flower/pull/994))" msgstr "" -#: ../../source/ref-changelog.md:827 +#: ../../source/ref-changelog.md:919 msgid "" -"SSL enables secure encrypted connections between clients and servers. This " -"release open-sources the Flower secure gRPC implementation to make encrypted " -"communication channels accessible to all Flower users." +"SSL enables secure encrypted connections between clients and servers. " +"This release open-sources the Flower secure gRPC implementation to make " +"encrypted communication channels accessible to all Flower users." msgstr "" -#: ../../source/ref-changelog.md:829 +#: ../../source/ref-changelog.md:921 msgid "" -"**Updated** `FedAdam` **and** `FedYogi` **strategies** ([#885](https://" -"github.com/adap/flower/pull/885), [#895](https://github.com/adap/flower/" -"pull/895))" +"**Updated** `FedAdam` **and** `FedYogi` **strategies** " +"([#885](https://github.com/adap/flower/pull/885), " +"[#895](https://github.com/adap/flower/pull/895))" msgstr "" -#: ../../source/ref-changelog.md:831 +#: ../../source/ref-changelog.md:923 msgid "" -"`FedAdam` and `FedAdam` match the latest version of the Adaptive Federated " -"Optimization paper." +"`FedAdam` and `FedAdam` match the latest version of the Adaptive " +"Federated Optimization paper." msgstr "" -#: ../../source/ref-changelog.md:833 +#: ../../source/ref-changelog.md:925 msgid "" -"**Initialize** `start_simulation` **with a list of client IDs** ([#860]" -"(https://github.com/adap/flower/pull/860))" +"**Initialize** `start_simulation` **with a list of client IDs** " +"([#860](https://github.com/adap/flower/pull/860))" msgstr "" -#: ../../source/ref-changelog.md:835 +#: ../../source/ref-changelog.md:927 msgid "" "`start_simulation` can now be called with a list of client IDs " "(`clients_ids`, type: `List[str]`). Those IDs will be passed to the " @@ -16240,227 +17370,232 @@ msgid "" "identifiers." msgstr "" -#: ../../source/ref-changelog.md:839 +#: ../../source/ref-changelog.md:931 msgid "" -"Update `num_examples` calculation in PyTorch code examples in ([#909]" -"(https://github.com/adap/flower/pull/909))" +"Update `num_examples` calculation in PyTorch code examples in " +"([#909](https://github.com/adap/flower/pull/909))" msgstr "" -#: ../../source/ref-changelog.md:840 +#: ../../source/ref-changelog.md:932 msgid "" -"Expose Flower version through `flwr.__version__` ([#952](https://github.com/" -"adap/flower/pull/952))" +"Expose Flower version through `flwr.__version__` " +"([#952](https://github.com/adap/flower/pull/952))" msgstr "" -#: ../../source/ref-changelog.md:841 +#: ../../source/ref-changelog.md:933 msgid "" -"`start_server` in `app.py` now returns a `History` object containing metrics " -"from training ([#974](https://github.com/adap/flower/pull/974))" +"`start_server` in `app.py` now returns a `History` object containing " +"metrics from training ([#974](https://github.com/adap/flower/pull/974))" msgstr "" -#: ../../source/ref-changelog.md:842 +#: ../../source/ref-changelog.md:934 msgid "" -"Make `max_workers` (used by `ThreadPoolExecutor`) configurable ([#978]" -"(https://github.com/adap/flower/pull/978))" +"Make `max_workers` (used by `ThreadPoolExecutor`) configurable " +"([#978](https://github.com/adap/flower/pull/978))" msgstr "" -#: ../../source/ref-changelog.md:843 +#: ../../source/ref-changelog.md:935 msgid "" -"Increase sleep time after server start to three seconds in all code examples " -"([#1086](https://github.com/adap/flower/pull/1086))" +"Increase sleep time after server start to three seconds in all code " +"examples ([#1086](https://github.com/adap/flower/pull/1086))" msgstr "" -#: ../../source/ref-changelog.md:844 +#: ../../source/ref-changelog.md:936 msgid "" -"Added a new FAQ section to the documentation ([#948](https://github.com/adap/" -"flower/pull/948))" +"Added a new FAQ section to the documentation " +"([#948](https://github.com/adap/flower/pull/948))" msgstr "" -#: ../../source/ref-changelog.md:845 +#: ../../source/ref-changelog.md:937 msgid "" "And many more under-the-hood changes, library updates, documentation " "changes, and tooling improvements!" msgstr "" -#: ../../source/ref-changelog.md:849 +#: ../../source/ref-changelog.md:941 msgid "" "**Removed** `flwr_example` **and** `flwr_experimental` **from release " "build** ([#869](https://github.com/adap/flower/pull/869))" msgstr "" -#: ../../source/ref-changelog.md:851 +#: ../../source/ref-changelog.md:943 msgid "" "The packages `flwr_example` and `flwr_experimental` have been deprecated " "since Flower 0.12.0 and they are not longer included in Flower release " "builds. The associated extras (`baseline`, `examples-pytorch`, `examples-" -"tensorflow`, `http-logger`, `ops`) are now no-op and will be removed in an " -"upcoming release." +"tensorflow`, `http-logger`, `ops`) are now no-op and will be removed in " +"an upcoming release." msgstr "" -#: ../../source/ref-changelog.md:853 +#: ../../source/ref-changelog.md:945 msgid "v0.17.0 (2021-09-24)" msgstr "" -#: ../../source/ref-changelog.md:857 +#: ../../source/ref-changelog.md:949 msgid "" -"**Experimental virtual client engine** ([#781](https://github.com/adap/" -"flower/pull/781) [#790](https://github.com/adap/flower/pull/790) [#791]" -"(https://github.com/adap/flower/pull/791))" +"**Experimental virtual client engine** " +"([#781](https://github.com/adap/flower/pull/781) " +"[#790](https://github.com/adap/flower/pull/790) " +"[#791](https://github.com/adap/flower/pull/791))" msgstr "" -#: ../../source/ref-changelog.md:859 +#: ../../source/ref-changelog.md:951 msgid "" -"One of Flower's goals is to enable research at scale. This release enables a " -"first (experimental) peek at a major new feature, codenamed the virtual " -"client engine. Virtual clients enable simulations that scale to a (very) " -"large number of clients on a single machine or compute cluster. The easiest " -"way to test the new functionality is to look at the two new code examples " -"called `quickstart_simulation` and `simulation_pytorch`." +"One of Flower's goals is to enable research at scale. This release " +"enables a first (experimental) peek at a major new feature, codenamed the" +" virtual client engine. Virtual clients enable simulations that scale to " +"a (very) large number of clients on a single machine or compute cluster. " +"The easiest way to test the new functionality is to look at the two new " +"code examples called `quickstart_simulation` and `simulation_pytorch`." msgstr "" -#: ../../source/ref-changelog.md:861 +#: ../../source/ref-changelog.md:953 msgid "" -"The feature is still experimental, so there's no stability guarantee for the " -"API. It's also not quite ready for prime time and comes with a few known " -"caveats. However, those who are curious are encouraged to try it out and " -"share their thoughts." +"The feature is still experimental, so there's no stability guarantee for " +"the API. It's also not quite ready for prime time and comes with a few " +"known caveats. However, those who are curious are encouraged to try it " +"out and share their thoughts." msgstr "" -#: ../../source/ref-changelog.md:863 +#: ../../source/ref-changelog.md:955 msgid "" -"**New built-in strategies** ([#828](https://github.com/adap/flower/pull/828) " +"**New built-in strategies** " +"([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" msgstr "" -#: ../../source/ref-changelog.md:865 +#: ../../source/ref-changelog.md:957 msgid "" "FedYogi - Federated learning strategy using Yogi on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" msgstr "" -#: ../../source/ref-changelog.md:866 +#: ../../source/ref-changelog.md:958 msgid "" "FedAdam - Federated learning strategy using Adam on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" msgstr "" -#: ../../source/ref-changelog.md:868 +#: ../../source/ref-changelog.md:960 msgid "" -"**New PyTorch Lightning code example** ([#617](https://github.com/adap/" -"flower/pull/617))" +"**New PyTorch Lightning code example** " +"([#617](https://github.com/adap/flower/pull/617))" msgstr "" -#: ../../source/ref-changelog.md:870 +#: ../../source/ref-changelog.md:962 msgid "" -"**New Variational Auto-Encoder code example** ([#752](https://github.com/" -"adap/flower/pull/752))" +"**New Variational Auto-Encoder code example** " +"([#752](https://github.com/adap/flower/pull/752))" msgstr "" -#: ../../source/ref-changelog.md:872 +#: ../../source/ref-changelog.md:964 msgid "" -"**New scikit-learn code example** ([#748](https://github.com/adap/flower/" -"pull/748))" +"**New scikit-learn code example** " +"([#748](https://github.com/adap/flower/pull/748))" msgstr "" -#: ../../source/ref-changelog.md:874 +#: ../../source/ref-changelog.md:966 msgid "" -"**New experimental TensorBoard strategy** ([#789](https://github.com/adap/" -"flower/pull/789))" +"**New experimental TensorBoard strategy** " +"([#789](https://github.com/adap/flower/pull/789))" msgstr "" -#: ../../source/ref-changelog.md:878 +#: ../../source/ref-changelog.md:970 msgid "" -"Improved advanced TensorFlow code example ([#769](https://github.com/adap/" -"flower/pull/769))" +"Improved advanced TensorFlow code example " +"([#769](https://github.com/adap/flower/pull/769))" msgstr "" -#: ../../source/ref-changelog.md:879 +#: ../../source/ref-changelog.md:971 msgid "" -"Warning when `min_available_clients` is misconfigured ([#830](https://github." -"com/adap/flower/pull/830))" +"Warning when `min_available_clients` is misconfigured " +"([#830](https://github.com/adap/flower/pull/830))" msgstr "" -#: ../../source/ref-changelog.md:880 +#: ../../source/ref-changelog.md:972 msgid "" -"Improved gRPC server docs ([#841](https://github.com/adap/flower/pull/841))" +"Improved gRPC server docs " +"([#841](https://github.com/adap/flower/pull/841))" msgstr "" -#: ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:973 msgid "" -"Improved error message in `NumPyClient` ([#851](https://github.com/adap/" -"flower/pull/851))" +"Improved error message in `NumPyClient` " +"([#851](https://github.com/adap/flower/pull/851))" msgstr "" -#: ../../source/ref-changelog.md:882 +#: ../../source/ref-changelog.md:974 msgid "" -"Improved PyTorch quickstart code example ([#852](https://github.com/adap/" -"flower/pull/852))" +"Improved PyTorch quickstart code example " +"([#852](https://github.com/adap/flower/pull/852))" msgstr "" -#: ../../source/ref-changelog.md:886 +#: ../../source/ref-changelog.md:978 msgid "" -"**Disabled final distributed evaluation** ([#800](https://github.com/adap/" -"flower/pull/800))" +"**Disabled final distributed evaluation** " +"([#800](https://github.com/adap/flower/pull/800))" msgstr "" -#: ../../source/ref-changelog.md:888 +#: ../../source/ref-changelog.md:980 msgid "" -"Prior behaviour was to perform a final round of distributed evaluation on " -"all connected clients, which is often not required (e.g., when using server-" -"side evaluation). The prior behaviour can be enabled by passing " +"Prior behaviour was to perform a final round of distributed evaluation on" +" all connected clients, which is often not required (e.g., when using " +"server-side evaluation). The prior behaviour can be enabled by passing " "`force_final_distributed_eval=True` to `start_server`." msgstr "" -#: ../../source/ref-changelog.md:890 +#: ../../source/ref-changelog.md:982 msgid "" -"**Renamed q-FedAvg strategy** ([#802](https://github.com/adap/flower/" -"pull/802))" +"**Renamed q-FedAvg strategy** " +"([#802](https://github.com/adap/flower/pull/802))" msgstr "" -#: ../../source/ref-changelog.md:892 +#: ../../source/ref-changelog.md:984 msgid "" -"The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect the " -"notation given in the original paper (q-FFL is the optimization objective, q-" -"FedAvg is the proposed solver). Note the original (now deprecated) " -"`QffedAvg` class is still available for compatibility reasons (it will be " -"removed in a future release)." +"The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect " +"the notation given in the original paper (q-FFL is the optimization " +"objective, q-FedAvg is the proposed solver). Note the original (now " +"deprecated) `QffedAvg` class is still available for compatibility reasons" +" (it will be removed in a future release)." msgstr "" -#: ../../source/ref-changelog.md:894 +#: ../../source/ref-changelog.md:986 msgid "" "**Deprecated and renamed code example** `simulation_pytorch` **to** " -"`simulation_pytorch_legacy` ([#791](https://github.com/adap/flower/pull/791))" +"`simulation_pytorch_legacy` " +"([#791](https://github.com/adap/flower/pull/791))" msgstr "" -#: ../../source/ref-changelog.md:896 +#: ../../source/ref-changelog.md:988 msgid "" -"This example has been replaced by a new example. The new example is based on " -"the experimental virtual client engine, which will become the new default " -"way of doing most types of large-scale simulations in Flower. The existing " -"example was kept for reference purposes, but it might be removed in the " -"future." +"This example has been replaced by a new example. The new example is based" +" on the experimental virtual client engine, which will become the new " +"default way of doing most types of large-scale simulations in Flower. The" +" existing example was kept for reference purposes, but it might be " +"removed in the future." msgstr "" -#: ../../source/ref-changelog.md:898 +#: ../../source/ref-changelog.md:990 msgid "v0.16.0 (2021-05-11)" msgstr "" -#: ../../source/ref-changelog.md:902 +#: ../../source/ref-changelog.md:994 msgid "" -"**New built-in strategies** ([#549](https://github.com/adap/flower/pull/549))" +"**New built-in strategies** " +"([#549](https://github.com/adap/flower/pull/549))" msgstr "" -#: ../../source/ref-changelog.md:904 +#: ../../source/ref-changelog.md:996 msgid "(abstract) FedOpt" msgstr "" -#: ../../source/ref-changelog.md:907 +#: ../../source/ref-changelog.md:999 msgid "" -"**Custom metrics for server and strategies** ([#717](https://github.com/adap/" -"flower/pull/717))" +"**Custom metrics for server and strategies** " +"([#717](https://github.com/adap/flower/pull/717))" msgstr "" -#: ../../source/ref-changelog.md:909 +#: ../../source/ref-changelog.md:1001 msgid "" "The Flower server is now fully task-agnostic, all remaining instances of " "task-specific metrics (such as `accuracy`) have been replaced by custom " @@ -16469,119 +17604,125 @@ msgid "" "release, custom metrics replace task-specific metrics on the server." msgstr "" -#: ../../source/ref-changelog.md:911 +#: ../../source/ref-changelog.md:1003 msgid "" -"Custom metric dictionaries are now used in two user-facing APIs: they are " -"returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and they " -"enable evaluation functions passed to built-in strategies (via `eval_fn`) to " -"return more than two evaluation metrics. Strategies can even return " -"*aggregated* metrics dictionaries for the server to keep track of." +"Custom metric dictionaries are now used in two user-facing APIs: they are" +" returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and " +"they enable evaluation functions passed to built-in strategies (via " +"`eval_fn`) to return more than two evaluation metrics. Strategies can " +"even return *aggregated* metrics dictionaries for the server to keep " +"track of." msgstr "" -#: ../../source/ref-changelog.md:913 +#: ../../source/ref-changelog.md:1005 msgid "" "Strategy implementations should migrate their `aggregate_fit` and " "`aggregate_evaluate` methods to the new return type (e.g., by simply " -"returning an empty `{}`), server-side evaluation functions should migrate " -"from `return loss, accuracy` to `return loss, {\"accuracy\": accuracy}`." +"returning an empty `{}`), server-side evaluation functions should migrate" +" from `return loss, accuracy` to `return loss, {\"accuracy\": accuracy}`." msgstr "" -#: ../../source/ref-changelog.md:915 +#: ../../source/ref-changelog.md:1007 msgid "" "Flower 0.15-style return types are deprecated (but still supported), " "compatibility will be removed in a future release." msgstr "" -#: ../../source/ref-changelog.md:917 +#: ../../source/ref-changelog.md:1009 msgid "" -"**Migration warnings for deprecated functionality** ([#690](https://github." -"com/adap/flower/pull/690))" +"**Migration warnings for deprecated functionality** " +"([#690](https://github.com/adap/flower/pull/690))" msgstr "" -#: ../../source/ref-changelog.md:919 +#: ../../source/ref-changelog.md:1011 msgid "" "Earlier versions of Flower were often migrated to new APIs, while " -"maintaining compatibility with legacy APIs. This release introduces detailed " -"warning messages if usage of deprecated APIs is detected. The new warning " -"messages often provide details on how to migrate to more recent APIs, thus " -"easing the transition from one release to another." +"maintaining compatibility with legacy APIs. This release introduces " +"detailed warning messages if usage of deprecated APIs is detected. The " +"new warning messages often provide details on how to migrate to more " +"recent APIs, thus easing the transition from one release to another." msgstr "" -#: ../../source/ref-changelog.md:921 +#: ../../source/ref-changelog.md:1013 msgid "" -"Improved docs and docstrings ([#691](https://github.com/adap/flower/" -"pull/691) [#692](https://github.com/adap/flower/pull/692) [#713](https://" -"github.com/adap/flower/pull/713))" +"Improved docs and docstrings " +"([#691](https://github.com/adap/flower/pull/691) " +"[#692](https://github.com/adap/flower/pull/692) " +"[#713](https://github.com/adap/flower/pull/713))" msgstr "" -#: ../../source/ref-changelog.md:923 +#: ../../source/ref-changelog.md:1015 msgid "MXNet example and documentation" msgstr "" -#: ../../source/ref-changelog.md:925 +#: ../../source/ref-changelog.md:1017 msgid "" "FedBN implementation in example PyTorch: From Centralized To Federated " -"([#696](https://github.com/adap/flower/pull/696) [#702](https://github.com/" -"adap/flower/pull/702) [#705](https://github.com/adap/flower/pull/705))" +"([#696](https://github.com/adap/flower/pull/696) " +"[#702](https://github.com/adap/flower/pull/702) " +"[#705](https://github.com/adap/flower/pull/705))" msgstr "" -#: ../../source/ref-changelog.md:929 +#: ../../source/ref-changelog.md:1021 msgid "" -"**Serialization-agnostic server** ([#721](https://github.com/adap/flower/" -"pull/721))" +"**Serialization-agnostic server** " +"([#721](https://github.com/adap/flower/pull/721))" msgstr "" -#: ../../source/ref-changelog.md:931 +#: ../../source/ref-changelog.md:1023 msgid "" -"The Flower server is now fully serialization-agnostic. Prior usage of class " -"`Weights` (which represents parameters as deserialized NumPy ndarrays) was " -"replaced by class `Parameters` (e.g., in `Strategy`). `Parameters` objects " -"are fully serialization-agnostic and represents parameters as byte arrays, " -"the `tensor_type` attributes indicates how these byte arrays should be " -"interpreted (e.g., for serialization/deserialization)." +"The Flower server is now fully serialization-agnostic. Prior usage of " +"class `Weights` (which represents parameters as deserialized NumPy " +"ndarrays) was replaced by class `Parameters` (e.g., in `Strategy`). " +"`Parameters` objects are fully serialization-agnostic and represents " +"parameters as byte arrays, the `tensor_type` attributes indicates how " +"these byte arrays should be interpreted (e.g., for " +"serialization/deserialization)." msgstr "" -#: ../../source/ref-changelog.md:933 +#: ../../source/ref-changelog.md:1025 msgid "" -"Built-in strategies implement this approach by handling serialization and " -"deserialization to/from `Weights` internally. Custom/3rd-party Strategy " +"Built-in strategies implement this approach by handling serialization and" +" deserialization to/from `Weights` internally. Custom/3rd-party Strategy " "implementations should update to the slightly changed Strategy method " -"definitions. Strategy authors can consult PR [#721](https://github.com/adap/" -"flower/pull/721) to see how strategies can easily migrate to the new format." +"definitions. Strategy authors can consult PR " +"[#721](https://github.com/adap/flower/pull/721) to see how strategies can" +" easily migrate to the new format." msgstr "" -#: ../../source/ref-changelog.md:935 +#: ../../source/ref-changelog.md:1027 msgid "" -"Deprecated `flwr.server.Server.evaluate`, use `flwr.server.Server." -"evaluate_round` instead ([#717](https://github.com/adap/flower/pull/717))" +"Deprecated `flwr.server.Server.evaluate`, use " +"`flwr.server.Server.evaluate_round` instead " +"([#717](https://github.com/adap/flower/pull/717))" msgstr "" -#: ../../source/ref-changelog.md:937 +#: ../../source/ref-changelog.md:1029 msgid "v0.15.0 (2021-03-12)" msgstr "" -#: ../../source/ref-changelog.md:941 +#: ../../source/ref-changelog.md:1033 msgid "" -"**Server-side parameter initialization** ([#658](https://github.com/adap/" -"flower/pull/658))" +"**Server-side parameter initialization** " +"([#658](https://github.com/adap/flower/pull/658))" msgstr "" -#: ../../source/ref-changelog.md:943 +#: ../../source/ref-changelog.md:1035 msgid "" "Model parameters can now be initialized on the server-side. Server-side " "parameter initialization works via a new `Strategy` method called " "`initialize_parameters`." msgstr "" -#: ../../source/ref-changelog.md:945 +#: ../../source/ref-changelog.md:1037 msgid "" "Built-in strategies support a new constructor argument called " -"`initial_parameters` to set the initial parameters. Built-in strategies will " -"provide these initial parameters to the server on startup and then delete " -"them to free the memory afterwards." +"`initial_parameters` to set the initial parameters. Built-in strategies " +"will provide these initial parameters to the server on startup and then " +"delete them to free the memory afterwards." msgstr "" -#: ../../source/ref-changelog.md:964 +#: ../../source/ref-changelog.md:1056 msgid "" "If no initial parameters are provided to the strategy, the server will " "continue to use the current behaviour (namely, it will ask one of the " @@ -16589,220 +17730,221 @@ msgid "" "parameters)." msgstr "" -#: ../../source/ref-changelog.md:966 -msgid "Deprecations" -msgstr "" - -#: ../../source/ref-changelog.md:968 +#: ../../source/ref-changelog.md:1060 msgid "" -"Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to `flwr.server." -"strategy.FedAvg`, which is equivalent)" +"Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to " +"`flwr.server.strategy.FedAvg`, which is equivalent)" msgstr "" -#: ../../source/ref-changelog.md:970 +#: ../../source/ref-changelog.md:1062 msgid "v0.14.0 (2021-02-18)" msgstr "" -#: ../../source/ref-changelog.md:974 +#: ../../source/ref-changelog.md:1066 msgid "" "**Generalized** `Client.fit` **and** `Client.evaluate` **return values** " -"([#610](https://github.com/adap/flower/pull/610) [#572](https://github.com/" -"adap/flower/pull/572) [#633](https://github.com/adap/flower/pull/633))" +"([#610](https://github.com/adap/flower/pull/610) " +"[#572](https://github.com/adap/flower/pull/572) " +"[#633](https://github.com/adap/flower/pull/633))" msgstr "" -#: ../../source/ref-changelog.md:976 +#: ../../source/ref-changelog.md:1068 msgid "" -"Clients can now return an additional dictionary mapping `str` keys to values " -"of the following types: `bool`, `bytes`, `float`, `int`, `str`. This means " -"one can return almost arbitrary values from `fit`/`evaluate` and make use of " -"them on the server side!" +"Clients can now return an additional dictionary mapping `str` keys to " +"values of the following types: `bool`, `bytes`, `float`, `int`, `str`. " +"This means one can return almost arbitrary values from `fit`/`evaluate` " +"and make use of them on the server side!" msgstr "" -#: ../../source/ref-changelog.md:978 +#: ../../source/ref-changelog.md:1070 msgid "" -"This improvement also allowed for more consistent return types between `fit` " -"and `evaluate`: `evaluate` should now return a tuple `(float, int, dict)` " -"representing the loss, number of examples, and a dictionary holding " -"arbitrary problem-specific values like accuracy." +"This improvement also allowed for more consistent return types between " +"`fit` and `evaluate`: `evaluate` should now return a tuple `(float, int, " +"dict)` representing the loss, number of examples, and a dictionary " +"holding arbitrary problem-specific values like accuracy." msgstr "" -#: ../../source/ref-changelog.md:980 +#: ../../source/ref-changelog.md:1072 msgid "" -"In case you wondered: this feature is compatible with existing projects, the " -"additional dictionary return value is optional. New code should however " -"migrate to the new return types to be compatible with upcoming Flower " -"releases (`fit`: `List[np.ndarray], int, Dict[str, Scalar]`, `evaluate`: " -"`float, int, Dict[str, Scalar]`). See the example below for details." +"In case you wondered: this feature is compatible with existing projects, " +"the additional dictionary return value is optional. New code should " +"however migrate to the new return types to be compatible with upcoming " +"Flower releases (`fit`: `List[np.ndarray], int, Dict[str, Scalar]`, " +"`evaluate`: `float, int, Dict[str, Scalar]`). See the example below for " +"details." msgstr "" -#: ../../source/ref-changelog.md:982 +#: ../../source/ref-changelog.md:1074 msgid "" "*Code example:* note the additional dictionary return values in both " "`FlwrClient.fit` and `FlwrClient.evaluate`:" msgstr "" -#: ../../source/ref-changelog.md:997 +#: ../../source/ref-changelog.md:1089 msgid "" -"**Generalized** `config` **argument in** `Client.fit` **and** `Client." -"evaluate` ([#595](https://github.com/adap/flower/pull/595))" +"**Generalized** `config` **argument in** `Client.fit` **and** " +"`Client.evaluate` ([#595](https://github.com/adap/flower/pull/595))" msgstr "" -#: ../../source/ref-changelog.md:999 +#: ../../source/ref-changelog.md:1091 msgid "" -"The `config` argument used to be of type `Dict[str, str]`, which means that " -"dictionary values were expected to be strings. The new release generalizes " -"this to enable values of the following types: `bool`, `bytes`, `float`, " -"`int`, `str`." +"The `config` argument used to be of type `Dict[str, str]`, which means " +"that dictionary values were expected to be strings. The new release " +"generalizes this to enable values of the following types: `bool`, " +"`bytes`, `float`, `int`, `str`." msgstr "" -#: ../../source/ref-changelog.md:1001 +#: ../../source/ref-changelog.md:1093 msgid "" "This means one can now pass almost arbitrary values to `fit`/`evaluate` " -"using the `config` dictionary. Yay, no more `str(epochs)` on the server-side " -"and `int(config[\"epochs\"])` on the client side!" +"using the `config` dictionary. Yay, no more `str(epochs)` on the server-" +"side and `int(config[\"epochs\"])` on the client side!" msgstr "" -#: ../../source/ref-changelog.md:1003 +#: ../../source/ref-changelog.md:1095 msgid "" "*Code example:* note that the `config` dictionary now contains non-`str` " "values in both `Client.fit` and `Client.evaluate`:" msgstr "" -#: ../../source/ref-changelog.md:1020 +#: ../../source/ref-changelog.md:1112 msgid "v0.13.0 (2021-01-08)" msgstr "" -#: ../../source/ref-changelog.md:1024 +#: ../../source/ref-changelog.md:1116 msgid "" -"New example: PyTorch From Centralized To Federated ([#549](https://github." -"com/adap/flower/pull/549))" +"New example: PyTorch From Centralized To Federated " +"([#549](https://github.com/adap/flower/pull/549))" msgstr "" -#: ../../source/ref-changelog.md:1025 +#: ../../source/ref-changelog.md:1117 msgid "Improved documentation" msgstr "" -#: ../../source/ref-changelog.md:1026 -msgid "" -"New documentation theme ([#551](https://github.com/adap/flower/pull/551))" +#: ../../source/ref-changelog.md:1118 +msgid "New documentation theme ([#551](https://github.com/adap/flower/pull/551))" msgstr "" -#: ../../source/ref-changelog.md:1027 +#: ../../source/ref-changelog.md:1119 msgid "New API reference ([#554](https://github.com/adap/flower/pull/554))" msgstr "" -#: ../../source/ref-changelog.md:1028 +#: ../../source/ref-changelog.md:1120 msgid "" -"Updated examples documentation ([#549](https://github.com/adap/flower/" -"pull/549))" +"Updated examples documentation " +"([#549](https://github.com/adap/flower/pull/549))" msgstr "" -#: ../../source/ref-changelog.md:1029 +#: ../../source/ref-changelog.md:1121 msgid "" -"Removed obsolete documentation ([#548](https://github.com/adap/flower/" -"pull/548))" +"Removed obsolete documentation " +"([#548](https://github.com/adap/flower/pull/548))" msgstr "" -#: ../../source/ref-changelog.md:1031 +#: ../../source/ref-changelog.md:1123 msgid "Bugfix:" msgstr "" -#: ../../source/ref-changelog.md:1033 +#: ../../source/ref-changelog.md:1125 msgid "" -"`Server.fit` does not disconnect clients when finished, disconnecting the " -"clients is now handled in `flwr.server.start_server` ([#553](https://github." -"com/adap/flower/pull/553) [#540](https://github.com/adap/flower/issues/540))." +"`Server.fit` does not disconnect clients when finished, disconnecting the" +" clients is now handled in `flwr.server.start_server` " +"([#553](https://github.com/adap/flower/pull/553) " +"[#540](https://github.com/adap/flower/issues/540))." msgstr "" -#: ../../source/ref-changelog.md:1035 +#: ../../source/ref-changelog.md:1127 msgid "v0.12.0 (2020-12-07)" msgstr "" -#: ../../source/ref-changelog.md:1037 ../../source/ref-changelog.md:1053 +#: ../../source/ref-changelog.md:1129 ../../source/ref-changelog.md:1145 msgid "Important changes:" msgstr "" -#: ../../source/ref-changelog.md:1039 +#: ../../source/ref-changelog.md:1131 msgid "" -"Added an example for embedded devices ([#507](https://github.com/adap/flower/" -"pull/507))" +"Added an example for embedded devices " +"([#507](https://github.com/adap/flower/pull/507))" msgstr "" -#: ../../source/ref-changelog.md:1040 +#: ../../source/ref-changelog.md:1132 msgid "" -"Added a new NumPyClient (in addition to the existing KerasClient) ([#504]" -"(https://github.com/adap/flower/pull/504) [#508](https://github.com/adap/" -"flower/pull/508))" +"Added a new NumPyClient (in addition to the existing KerasClient) " +"([#504](https://github.com/adap/flower/pull/504) " +"[#508](https://github.com/adap/flower/pull/508))" msgstr "" -#: ../../source/ref-changelog.md:1041 +#: ../../source/ref-changelog.md:1133 msgid "" -"Deprecated `flwr_example` package and started to migrate examples into the " -"top-level `examples` directory ([#494](https://github.com/adap/flower/" -"pull/494) [#512](https://github.com/adap/flower/pull/512))" +"Deprecated `flwr_example` package and started to migrate examples into " +"the top-level `examples` directory " +"([#494](https://github.com/adap/flower/pull/494) " +"[#512](https://github.com/adap/flower/pull/512))" msgstr "" -#: ../../source/ref-changelog.md:1043 +#: ../../source/ref-changelog.md:1135 msgid "v0.11.0 (2020-11-30)" msgstr "" -#: ../../source/ref-changelog.md:1045 +#: ../../source/ref-changelog.md:1137 msgid "Incompatible changes:" msgstr "" -#: ../../source/ref-changelog.md:1047 +#: ../../source/ref-changelog.md:1139 msgid "" -"Renamed strategy methods ([#486](https://github.com/adap/flower/pull/486)) " -"to unify the naming of Flower's public APIs. Other public methods/functions " -"(e.g., every method in `Client`, but also `Strategy.evaluate`) do not use " -"the `on_` prefix, which is why we're removing it from the four methods in " -"Strategy. To migrate rename the following `Strategy` methods accordingly:" +"Renamed strategy methods " +"([#486](https://github.com/adap/flower/pull/486)) to unify the naming of " +"Flower's public APIs. Other public methods/functions (e.g., every method " +"in `Client`, but also `Strategy.evaluate`) do not use the `on_` prefix, " +"which is why we're removing it from the four methods in Strategy. To " +"migrate rename the following `Strategy` methods accordingly:" msgstr "" -#: ../../source/ref-changelog.md:1048 +#: ../../source/ref-changelog.md:1140 msgid "`on_configure_evaluate` => `configure_evaluate`" msgstr "" -#: ../../source/ref-changelog.md:1049 +#: ../../source/ref-changelog.md:1141 msgid "`on_aggregate_evaluate` => `aggregate_evaluate`" msgstr "" -#: ../../source/ref-changelog.md:1050 +#: ../../source/ref-changelog.md:1142 msgid "`on_configure_fit` => `configure_fit`" msgstr "" -#: ../../source/ref-changelog.md:1051 +#: ../../source/ref-changelog.md:1143 msgid "`on_aggregate_fit` => `aggregate_fit`" msgstr "" -#: ../../source/ref-changelog.md:1055 +#: ../../source/ref-changelog.md:1147 msgid "" -"Deprecated `DefaultStrategy` ([#479](https://github.com/adap/flower/" -"pull/479)). To migrate use `FedAvg` instead." +"Deprecated `DefaultStrategy` " +"([#479](https://github.com/adap/flower/pull/479)). To migrate use " +"`FedAvg` instead." msgstr "" -#: ../../source/ref-changelog.md:1056 +#: ../../source/ref-changelog.md:1148 msgid "" -"Simplified examples and baselines ([#484](https://github.com/adap/flower/" -"pull/484))." +"Simplified examples and baselines " +"([#484](https://github.com/adap/flower/pull/484))." msgstr "" -#: ../../source/ref-changelog.md:1057 +#: ../../source/ref-changelog.md:1149 msgid "" -"Removed presently unused `on_conclude_round` from strategy interface ([#483]" -"(https://github.com/adap/flower/pull/483))." +"Removed presently unused `on_conclude_round` from strategy interface " +"([#483](https://github.com/adap/flower/pull/483))." msgstr "" -#: ../../source/ref-changelog.md:1058 +#: ../../source/ref-changelog.md:1150 msgid "" -"Set minimal Python version to 3.6.1 instead of 3.6.9 ([#471](https://github." -"com/adap/flower/pull/471))." +"Set minimal Python version to 3.6.1 instead of 3.6.9 " +"([#471](https://github.com/adap/flower/pull/471))." msgstr "" -#: ../../source/ref-changelog.md:1059 +#: ../../source/ref-changelog.md:1151 msgid "" -"Improved `Strategy` docstrings ([#470](https://github.com/adap/flower/" -"pull/470))." +"Improved `Strategy` docstrings " +"([#470](https://github.com/adap/flower/pull/470))." msgstr "" #: ../../source/ref-example-projects.rst:2 @@ -16811,11 +17953,11 @@ msgstr "" #: ../../source/ref-example-projects.rst:4 msgid "" -"Flower comes with a number of usage examples. The examples demonstrate how " -"Flower can be used to federate different kinds of existing machine learning " -"pipelines, usually leveraging popular machine learning frameworks such as " -"`PyTorch `_ or `TensorFlow `_." +"Flower comes with a number of usage examples. The examples demonstrate " +"how Flower can be used to federate different kinds of existing machine " +"learning pipelines, usually leveraging popular machine learning " +"frameworks such as `PyTorch `_ or `TensorFlow " +"`_." msgstr "" #: ../../source/ref-example-projects.rst:10 @@ -16826,25 +17968,25 @@ msgstr "" #: ../../source/ref-example-projects.rst:14 msgid "" -"The TensorFlow/Keras quickstart example shows CIFAR-10 image classification " -"with MobileNetV2:" +"The TensorFlow/Keras quickstart example shows CIFAR-10 image " +"classification with MobileNetV2:" msgstr "" #: ../../source/ref-example-projects.rst:17 msgid "" -"`Quickstart TensorFlow (Code) `_" +"`Quickstart TensorFlow (Code) " +"`_" msgstr "" #: ../../source/ref-example-projects.rst:18 -msgid "" -":doc:`Quickstart TensorFlow (Tutorial) `" +msgid ":doc:`Quickstart TensorFlow (Tutorial) `" msgstr "" #: ../../source/ref-example-projects.rst:19 msgid "" -"`Quickstart TensorFlow (Blog Post) `_" +"`Quickstart TensorFlow (Blog Post) `_" msgstr "" #: ../../source/ref-example-projects.rst:23 @@ -16854,14 +17996,14 @@ msgstr "" #: ../../source/ref-example-projects.rst:25 msgid "" -"The PyTorch quickstart example shows CIFAR-10 image classification with a " -"simple Convolutional Neural Network:" +"The PyTorch quickstart example shows CIFAR-10 image classification with a" +" simple Convolutional Neural Network:" msgstr "" #: ../../source/ref-example-projects.rst:28 msgid "" -"`Quickstart PyTorch (Code) `_" +"`Quickstart PyTorch (Code) " +"`_" msgstr "" #: ../../source/ref-example-projects.rst:29 @@ -16880,8 +18022,9 @@ msgstr "" #: ../../source/ref-example-projects.rst:37 msgid "" -"`PyTorch: From Centralized To Federated (Code) `_" +"`PyTorch: From Centralized To Federated (Code) " +"`_" msgstr "" #: ../../source/ref-example-projects.rst:38 @@ -16902,15 +18045,14 @@ msgstr "" #: ../../source/ref-example-projects.rst:46 msgid "" -"`Federated Learning on Raspberry Pi and Nvidia Jetson (Code) `_" +"`Federated Learning on Raspberry Pi and Nvidia Jetson (Code) " +"`_" msgstr "" #: ../../source/ref-example-projects.rst:47 msgid "" -"`Federated Learning on Raspberry Pi and Nvidia Jetson (Blog Post) `_" +"`Federated Learning on Raspberry Pi and Nvidia Jetson (Blog Post) " +"`_" msgstr "" #: ../../source/ref-faq.rst:4 @@ -16925,20 +18067,22 @@ msgstr "" #: ../../source/ref-faq.rst:8 msgid "" -"Yes, it can! Flower even comes with a few under-the-hood optimizations to " -"make it work even better on Colab. Here's a quickstart example:" +"Yes, it can! Flower even comes with a few under-the-hood optimizations to" +" make it work even better on Colab. Here's a quickstart example:" msgstr "" #: ../../source/ref-faq.rst:10 msgid "" -"`Flower simulation PyTorch `_" +"`Flower simulation PyTorch " +"`_" msgstr "" #: ../../source/ref-faq.rst:11 msgid "" -"`Flower simulation TensorFlow/Keras `_" +"`Flower simulation TensorFlow/Keras " +"`_" msgstr "" #: ../../source/ref-faq.rst @@ -16948,28 +18092,26 @@ msgstr "" #: ../../source/ref-faq.rst:15 msgid "" "Find the `blog post about federated learning on embedded device here " -"`_ " -"and the corresponding `GitHub code example `_." +"`_" +" and the corresponding `GitHub code example " +"`_." msgstr "" #: ../../source/ref-faq.rst -msgid "" -":fa:`eye,mr-1` Does Flower support federated learning on Android devices?" +msgid ":fa:`eye,mr-1` Does Flower support federated learning on Android devices?" msgstr "" #: ../../source/ref-faq.rst:19 msgid "" -"Yes, it does. Please take a look at our `blog post `_ or " -"check out the code examples:" +"Yes, it does. Please take a look at our `blog post " +"`_ or check out the code examples:" msgstr "" #: ../../source/ref-faq.rst:21 msgid "" -"`Android Kotlin example `_" +"`Android Kotlin example `_" msgstr "" #: ../../source/ref-faq.rst:22 @@ -16988,33 +18130,33 @@ msgstr "" #: ../../source/ref-faq.rst:28 msgid "" -"`Flower meets Nevermined GitHub Repository `_." +"`Flower meets Nevermined GitHub Repository `_." msgstr "" #: ../../source/ref-faq.rst:29 msgid "" -"`Flower meets Nevermined YouTube video `_." +"`Flower meets Nevermined YouTube video " +"`_." msgstr "" #: ../../source/ref-faq.rst:30 msgid "" -"`Flower meets KOSMoS `_." +"`Flower meets KOSMoS `_." msgstr "" #: ../../source/ref-faq.rst:31 msgid "" "`Flower meets Talan blog post `_ ." +"learning-same-mask-different-faces-imen-" +"ayari/?trackingId=971oIlxLQ9%2BA9RB0IQ73XQ%3D%3D>`_ ." msgstr "" #: ../../source/ref-faq.rst:32 msgid "" -"`Flower meets Talan GitHub Repository `_ ." +"`Flower meets Talan GitHub Repository " +"`_ ." msgstr "" #: ../../source/ref-telemetry.md:1 @@ -17023,16 +18165,17 @@ msgstr "" #: ../../source/ref-telemetry.md:3 msgid "" -"The Flower open-source project collects **anonymous** usage metrics to make " -"well-informed decisions to improve Flower. Doing this enables the Flower " -"team to understand how Flower is used and what challenges users might face." +"The Flower open-source project collects **anonymous** usage metrics to " +"make well-informed decisions to improve Flower. Doing this enables the " +"Flower team to understand how Flower is used and what challenges users " +"might face." msgstr "" #: ../../source/ref-telemetry.md:5 msgid "" -"**Flower is a friendly framework for collaborative AI and data science.** " -"Staying true to this statement, Flower makes it easy to disable telemetry " -"for users that do not want to share anonymous usage metrics." +"**Flower is a friendly framework for collaborative AI and data science.**" +" Staying true to this statement, Flower makes it easy to disable " +"telemetry for users that do not want to share anonymous usage metrics." msgstr "" #: ../../source/ref-telemetry.md:7 @@ -17040,34 +18183,35 @@ msgid "Principles" msgstr "" #: ../../source/ref-telemetry.md:9 -msgid "" -"We follow strong principles guarding anonymous usage metrics collection:" +msgid "We follow strong principles guarding anonymous usage metrics collection:" msgstr "" #: ../../source/ref-telemetry.md:11 msgid "" -"**Optional:** You will always be able to disable telemetry; read on to learn " -"“[How to opt-out](#how-to-opt-out)”." +"**Optional:** You will always be able to disable telemetry; read on to " +"learn “[How to opt-out](#how-to-opt-out)”." msgstr "" #: ../../source/ref-telemetry.md:12 msgid "" -"**Anonymous:** The reported usage metrics are anonymous and do not contain " -"any personally identifiable information (PII). See “[Collected metrics]" -"(#collected-metrics)” to understand what metrics are being reported." +"**Anonymous:** The reported usage metrics are anonymous and do not " +"contain any personally identifiable information (PII). See “[Collected " +"metrics](#collected-metrics)” to understand what metrics are being " +"reported." msgstr "" #: ../../source/ref-telemetry.md:13 msgid "" "**Transparent:** You can easily inspect what anonymous metrics are being " -"reported; see the section “[How to inspect what is being reported](#how-to-" -"inspect-what-is-being-reported)”" +"reported; see the section “[How to inspect what is being reported](#how-" +"to-inspect-what-is-being-reported)”" msgstr "" #: ../../source/ref-telemetry.md:14 msgid "" -"**Open for feedback:** You can always reach out to us if you have feedback; " -"see the section “[How to contact us](#how-to-contact-us)” for details." +"**Open for feedback:** You can always reach out to us if you have " +"feedback; see the section “[How to contact us](#how-to-contact-us)” for " +"details." msgstr "" #: ../../source/ref-telemetry.md:16 @@ -17084,9 +18228,9 @@ msgstr "" #: ../../source/ref-telemetry.md:24 msgid "" -"Alternatively, you can export `FLWR_TELEMETRY_ENABLED=0` in, for example, `." -"bashrc` (or whatever configuration file applies to your environment) to " -"disable Flower telemetry permanently." +"Alternatively, you can export `FLWR_TELEMETRY_ENABLED=0` in, for example," +" `.bashrc` (or whatever configuration file applies to your environment) " +"to disable Flower telemetry permanently." msgstr "" #: ../../source/ref-telemetry.md:26 @@ -17099,10 +18243,10 @@ msgstr "" #: ../../source/ref-telemetry.md:30 msgid "" -"**Flower version.** Understand which versions of Flower are currently being " -"used. This helps us to decide whether we should invest effort into releasing " -"a patch version for an older version of Flower or instead use the bandwidth " -"to build new features." +"**Flower version.** Understand which versions of Flower are currently " +"being used. This helps us to decide whether we should invest effort into " +"releasing a patch version for an older version of Flower or instead use " +"the bandwidth to build new features." msgstr "" #: ../../source/ref-telemetry.md:32 @@ -17121,15 +18265,15 @@ msgstr "" #: ../../source/ref-telemetry.md:36 msgid "" -"**Hardware properties.** Understanding the hardware environment that Flower " -"is being used in helps to decide whether we should, for example, put more " -"effort into supporting low-resource environments." +"**Hardware properties.** Understanding the hardware environment that " +"Flower is being used in helps to decide whether we should, for example, " +"put more effort into supporting low-resource environments." msgstr "" #: ../../source/ref-telemetry.md:38 msgid "" -"**Execution mode.** Knowing what execution mode Flower starts in enables us " -"to understand how heavily certain features are being used and better " +"**Execution mode.** Knowing what execution mode Flower starts in enables " +"us to understand how heavily certain features are being used and better " "prioritize based on that." msgstr "" @@ -17137,34 +18281,37 @@ msgstr "" msgid "" "**Cluster.** Flower telemetry assigns a random in-memory cluster ID each " "time a Flower workload starts. This allows us to understand which device " -"types not only start Flower workloads but also successfully complete them." +"types not only start Flower workloads but also successfully complete " +"them." msgstr "" #: ../../source/ref-telemetry.md:42 msgid "" -"**Source.** Flower telemetry tries to store a random source ID in `~/.flwr/" -"source` the first time a telemetry event is generated. The source ID is " -"important to identify whether an issue is recurring or whether an issue is " -"triggered by multiple clusters running concurrently (which often happens in " -"simulation). For example, if a device runs multiple workloads at the same " -"time, and this results in an issue, then, in order to reproduce the issue, " -"multiple workloads must be started at the same time." +"**Source.** Flower telemetry tries to store a random source ID in " +"`~/.flwr/source` the first time a telemetry event is generated. The " +"source ID is important to identify whether an issue is recurring or " +"whether an issue is triggered by multiple clusters running concurrently " +"(which often happens in simulation). For example, if a device runs " +"multiple workloads at the same time, and this results in an issue, then, " +"in order to reproduce the issue, multiple workloads must be started at " +"the same time." msgstr "" #: ../../source/ref-telemetry.md:44 msgid "" -"You may delete the source ID at any time. If you wish for all events logged " -"under a specific source ID to be deleted, you can send a deletion request " -"mentioning the source ID to `telemetry@flower.ai`. All events related to " -"that source ID will then be permanently deleted." +"You may delete the source ID at any time. If you wish for all events " +"logged under a specific source ID to be deleted, you can send a deletion " +"request mentioning the source ID to `telemetry@flower.ai`. All events " +"related to that source ID will then be permanently deleted." msgstr "" #: ../../source/ref-telemetry.md:46 msgid "" -"We will not collect any personally identifiable information. If you think " -"any of the metrics collected could be misused in any way, please [get in " -"touch with us](#how-to-contact-us). We will update this page to reflect any " -"changes to the metrics collected and publish changes in the changelog." +"We will not collect any personally identifiable information. If you think" +" any of the metrics collected could be misused in any way, please [get in" +" touch with us](#how-to-contact-us). We will update this page to reflect " +"any changes to the metrics collected and publish changes in the " +"changelog." msgstr "" #: ../../source/ref-telemetry.md:48 @@ -17181,17 +18328,17 @@ msgstr "" #: ../../source/ref-telemetry.md:52 msgid "" "We wanted to make it very easy for you to inspect what anonymous usage " -"metrics are reported. You can view all the reported telemetry information by " -"setting the environment variable `FLWR_TELEMETRY_LOGGING=1`. Logging is " -"disabled by default. You may use logging independently from " +"metrics are reported. You can view all the reported telemetry information" +" by setting the environment variable `FLWR_TELEMETRY_LOGGING=1`. Logging " +"is disabled by default. You may use logging independently from " "`FLWR_TELEMETRY_ENABLED` so that you can inspect the telemetry feature " "without sending any metrics." msgstr "" #: ../../source/ref-telemetry.md:58 msgid "" -"The inspect Flower telemetry without sending any anonymous usage metrics, " -"use both environment variables:" +"The inspect Flower telemetry without sending any anonymous usage metrics," +" use both environment variables:" msgstr "" #: ../../source/ref-telemetry.md:64 @@ -17208,8 +18355,8 @@ msgstr "" #: ../../source/tutorial-quickstart-android.rst:-1 msgid "" -"Read this Federated Learning quickstart tutorial for creating an Android app " -"using Flower." +"Read this Federated Learning quickstart tutorial for creating an Android " +"app using Flower." msgstr "" #: ../../source/tutorial-quickstart-android.rst:5 @@ -17218,19 +18365,21 @@ msgstr "" #: ../../source/tutorial-quickstart-android.rst:10 msgid "" -"Let's build a federated learning system using TFLite and Flower on Android!" +"Let's build a federated learning system using TFLite and Flower on " +"Android!" msgstr "" #: ../../source/tutorial-quickstart-android.rst:12 msgid "" -"Please refer to the `full code example `_ to learn more." +"Please refer to the `full code example " +"`_ to learn " +"more." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"FastAI to train a vision model on CIFAR-10." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with FastAI to train a vision model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:5 @@ -17243,14 +18392,15 @@ msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:12 msgid "" -"Please refer to the `full code example `_ to learn more." +"Please refer to the `full code example " +"`_ " +"to learn more." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:-1 msgid "" -"Check out this Federating Learning quickstart tutorial for using Flower with " -"HuggingFace Transformers in order to fine-tune an LLM." +"Check out this Federating Learning quickstart tutorial for using Flower " +"with HuggingFace Transformers in order to fine-tune an LLM." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:5 @@ -17259,17 +18409,17 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:10 msgid "" -"Let's build a federated learning system using Hugging Face Transformers and " -"Flower!" +"Let's build a federated learning system using Hugging Face Transformers " +"and Flower!" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:12 msgid "" -"We will leverage Hugging Face to federate the training of language models " -"over multiple clients using Flower. More specifically, we will fine-tune a " -"pre-trained Transformer model (distilBERT) for sequence classification over " -"a dataset of IMDB ratings. The end goal is to detect if a movie rating is " -"positive or negative." +"We will leverage Hugging Face to federate the training of language models" +" over multiple clients using Flower. More specifically, we will fine-tune" +" a pre-trained Transformer model (distilBERT) for sequence classification" +" over a dataset of IMDB ratings. The end goal is to detect if a movie " +"rating is positive or negative." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:18 @@ -17279,8 +18429,9 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:20 msgid "" "To follow along this tutorial you will need to install the following " -"packages: :code:`datasets`, :code:`evaluate`, :code:`flwr`, :code:`torch`, " -"and :code:`transformers`. This can be done using :code:`pip`:" +"packages: :code:`datasets`, :code:`evaluate`, :code:`flwr`, " +":code:`torch`, and :code:`transformers`. This can be done using " +":code:`pip`:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:30 @@ -17304,9 +18455,9 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:83 msgid "" -"Once we have a way of creating our trainloader and testloader, we can take " -"care of the training and testing. This is very similar to any :code:" -"`PyTorch` training or testing loop:" +"Once we have a way of creating our trainloader and testloader, we can " +"take care of the training and testing. This is very similar to any " +":code:`PyTorch` training or testing loop:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:121 @@ -17315,8 +18466,8 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:123 msgid "" -"To create the model itself, we will just load the pre-trained distillBERT " -"model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" +"To create the model itself, we will just load the pre-trained distillBERT" +" model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:136 @@ -17330,17 +18481,18 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:141 msgid "" "To federate our example to multiple clients, we first need to write our " -"Flower client class (inheriting from :code:`flwr.client.NumPyClient`). This " -"is very easy, as our model is a standard :code:`PyTorch` model:" +"Flower client class (inheriting from :code:`flwr.client.NumPyClient`). " +"This is very easy, as our model is a standard :code:`PyTorch` model:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:169 msgid "" "The :code:`get_parameters` function lets the server get the client's " -"parameters. Inversely, the :code:`set_parameters` function allows the server " -"to send its parameters to the client. Finally, the :code:`fit` function " -"trains the model locally for the client, and the :code:`evaluate` function " -"tests the model locally and returns the relevant metrics." +"parameters. Inversely, the :code:`set_parameters` function allows the " +"server to send its parameters to the client. Finally, the :code:`fit` " +"function trains the model locally for the client, and the " +":code:`evaluate` function tests the model locally and returns the " +"relevant metrics." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:175 @@ -17349,19 +18501,19 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:177 msgid "" -"Now that we have a way to instantiate clients, we need to create our server " -"in order to aggregate the results. Using Flower, this can be done very " -"easily by first choosing a strategy (here, we are using :code:`FedAvg`, " -"which will define the global weights as the average of all the clients' " -"weights at each round) and then using the :code:`flwr.server.start_server` " -"function:" +"Now that we have a way to instantiate clients, we need to create our " +"server in order to aggregate the results. Using Flower, this can be done " +"very easily by first choosing a strategy (here, we are using " +":code:`FedAvg`, which will define the global weights as the average of " +"all the clients' weights at each round) and then using the " +":code:`flwr.server.start_server` function:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:205 msgid "" -"The :code:`weighted_average` function is there to provide a way to aggregate " -"the metrics distributed amongst the clients (basically this allows us to " -"display a nice average accuracy and loss for every round)." +"The :code:`weighted_average` function is there to provide a way to " +"aggregate the metrics distributed amongst the clients (basically this " +"allows us to display a nice average accuracy and loss for every round)." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:209 @@ -17380,22 +18532,22 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:223 msgid "" -"If you want to check out everything put together, you should check out the " -"`full code example `_ ." +"If you want to check out everything put together, you should check out " +"the `full code example `_ ." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:226 msgid "" -"Of course, this is a very basic example, and a lot can be added or modified, " -"it was just to showcase how simply we could federate a Hugging Face workflow " -"using Flower." +"Of course, this is a very basic example, and a lot can be added or " +"modified, it was just to showcase how simply we could federate a Hugging " +"Face workflow using Flower." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:229 msgid "" -"Note that in this example we used :code:`PyTorch`, but we could have very " -"well used :code:`TensorFlow`." +"Note that in this example we used :code:`PyTorch`, but we could have very" +" well used :code:`TensorFlow`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:-1 @@ -17410,38 +18562,38 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:10 msgid "" -"In this tutorial we will learn how to train a Neural Network on MNIST using " -"Flower and CoreML on iOS devices." +"In this tutorial we will learn how to train a Neural Network on MNIST " +"using Flower and CoreML on iOS devices." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:12 msgid "" "First of all, for running the Flower Python server, it is recommended to " -"create a virtual environment and run everything within a :doc:`virtualenv " -"`. For the Flower client " +"create a virtual environment and run everything within a :doc:`virtualenv" +" `. For the Flower client " "implementation in iOS, it is recommended to use Xcode as our IDE." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:15 msgid "" -"Our example consists of one Python *server* and two iPhone *clients* that " -"all have the same model." +"Our example consists of one Python *server* and two iPhone *clients* that" +" all have the same model." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:17 msgid "" -"*Clients* are responsible for generating individual weight updates for the " -"model based on their local datasets. These updates are then sent to the " -"*server* which will aggregate them to produce a better model. Finally, the " -"*server* sends this improved version of the model back to each *client*. A " -"complete cycle of weight updates is called a *round*." +"*Clients* are responsible for generating individual weight updates for " +"the model based on their local datasets. These updates are then sent to " +"the *server* which will aggregate them to produce a better model. " +"Finally, the *server* sends this improved version of the model back to " +"each *client*. A complete cycle of weight updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:21 msgid "" "Now that we have a rough idea of what is going on, let's get started to " -"setup our Flower server environment. We first need to install Flower. You " -"can do this by using pip:" +"setup our Flower server environment. We first need to install Flower. You" +" can do this by using pip:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:27 @@ -17459,20 +18611,21 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:36 msgid "" "Now that we have all our dependencies installed, let's run a simple " -"distributed training using CoreML as our local training pipeline and MNIST " -"as our dataset. For simplicity reasons we will use the complete Flower " -"client with CoreML, that has been implemented and stored inside the Swift " -"SDK. The client implementation can be seen below:" +"distributed training using CoreML as our local training pipeline and " +"MNIST as our dataset. For simplicity reasons we will use the complete " +"Flower client with CoreML, that has been implemented and stored inside " +"the Swift SDK. The client implementation can be seen below:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:72 msgid "" -"Let's create a new application project in Xcode and add :code:`flwr` as a " -"dependency in your project. For our application, we will store the logic of " -"our app in :code:`FLiOSModel.swift` and the UI elements in :code:" -"`ContentView.swift`. We will focus more on :code:`FLiOSModel.swift` in this " -"quickstart. Please refer to the `full code example `_ to learn more about the app." +"Let's create a new application project in Xcode and add :code:`flwr` as a" +" dependency in your project. For our application, we will store the logic" +" of our app in :code:`FLiOSModel.swift` and the UI elements in " +":code:`ContentView.swift`. We will focus more on :code:`FLiOSModel.swift`" +" in this quickstart. Please refer to the `full code example " +"`_ to learn more " +"about the app." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:75 @@ -17482,21 +18635,22 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:83 msgid "" "Then add the mlmodel to the project simply by drag-and-drop, the mlmodel " -"will be bundled inside the application during deployment to your iOS device. " -"We need to pass the url to access mlmodel and run CoreML machine learning " -"processes, it can be retrieved by calling the function :code:`Bundle.main." -"url`. For the MNIST dataset, we need to preprocess it into :code:" -"`MLBatchProvider` object. The preprocessing is done inside :code:`DataLoader." -"swift`." +"will be bundled inside the application during deployment to your iOS " +"device. We need to pass the url to access mlmodel and run CoreML machine " +"learning processes, it can be retrieved by calling the function " +":code:`Bundle.main.url`. For the MNIST dataset, we need to preprocess it " +"into :code:`MLBatchProvider` object. The preprocessing is done inside " +":code:`DataLoader.swift`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:99 msgid "" -"Since CoreML does not allow the model parameters to be seen before training, " -"and accessing the model parameters during or after the training can only be " -"done by specifying the layer name, we need to know this information " -"beforehand, through looking at the model specification, which are written as " -"proto files. The implementation can be seen in :code:`MLModelInspect`." +"Since CoreML does not allow the model parameters to be seen before " +"training, and accessing the model parameters during or after the training" +" can only be done by specifying the layer name, we need to know this " +"information beforehand, through looking at the model specification, which" +" are written as proto files. The implementation can be seen in " +":code:`MLModelInspect`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:102 @@ -17507,18 +18661,18 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:117 msgid "" -"Then start the Flower gRPC client and start communicating to the server by " -"passing our Flower client to the function :code:`startFlwrGRPC`." +"Then start the Flower gRPC client and start communicating to the server " +"by passing our Flower client to the function :code:`startFlwrGRPC`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:124 msgid "" -"That's it for the client. We only have to implement :code:`Client` or call " -"the provided :code:`MLFlwrClient` and call :code:`startFlwrGRPC()`. The " -"attribute :code:`hostname` and :code:`port` tells the client which server to " -"connect to. This can be done by entering the hostname and port in the " -"application before clicking the start button to start the federated learning " -"process." +"That's it for the client. We only have to implement :code:`Client` or " +"call the provided :code:`MLFlwrClient` and call :code:`startFlwrGRPC()`. " +"The attribute :code:`hostname` and :code:`port` tells the client which " +"server to connect to. This can be done by entering the hostname and port " +"in the application before clicking the start button to start the " +"federated learning process." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:129 @@ -17534,8 +18688,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:100 msgid "" "For simple workloads we can start a Flower server and leave all the " -"configuration possibilities at their default values. In a file named :code:" -"`server.py`, import Flower and start the server:" +"configuration possibilities at their default values. In a file named " +":code:`server.py`, import Flower and start the server:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:142 @@ -17551,31 +18705,32 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:525 msgid "" "With both client and server ready, we can now run everything and see " -"federated learning in action. FL systems usually have a server and multiple " -"clients. We therefore have to start the server first:" +"federated learning in action. FL systems usually have a server and " +"multiple clients. We therefore have to start the server first:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:152 msgid "" -"Once the server is running we can start the clients in different terminals. " -"Build and run the client through your Xcode, one through Xcode Simulator and " -"the other by deploying it to your iPhone. To see more about how to deploy " -"your app to iPhone or Simulator visit `here `_." +"Once the server is running we can start the clients in different " +"terminals. Build and run the client through your Xcode, one through Xcode" +" Simulator and the other by deploying it to your iPhone. To see more " +"about how to deploy your app to iPhone or Simulator visit `here " +"`_." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:156 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system in your ios device. The full `source code `_ for this example can be found in :" -"code:`examples/ios`." +"learning system in your ios device. The full `source code " +"`_ for this " +"example can be found in :code:`examples/ios`." msgstr "" #: ../../source/tutorial-quickstart-jax.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"Jax to train a linear regression model on a scikit-learn dataset." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with Jax to train a linear regression model on a scikit-learn dataset." msgstr "" #: ../../source/tutorial-quickstart-jax.rst:5 @@ -17584,8 +18739,8 @@ msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"Pandas to perform Federated Analytics." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with Pandas to perform Federated Analytics." msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:5 @@ -17598,44 +18753,45 @@ msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:12 msgid "" -"Please refer to the `full code example `_ to learn more." +"Please refer to the `full code example " +"`_ " +"to learn more." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"PyTorch to train a CNN model on MNIST." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with PyTorch to train a CNN model on MNIST." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:13 msgid "" -"In this tutorial we will learn how to train a Convolutional Neural Network " -"on CIFAR10 using Flower and PyTorch." +"In this tutorial we will learn how to train a Convolutional Neural " +"Network on CIFAR10 using Flower and PyTorch." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:15 #: ../../source/tutorial-quickstart-xgboost.rst:39 msgid "" "First of all, it is recommended to create a virtual environment and run " -"everything within a :doc:`virtualenv `." +"everything within a :doc:`virtualenv `." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:17 #: ../../source/tutorial-quickstart-scikitlearn.rst:14 msgid "" -"Our example consists of one *server* and two *clients* all having the same " -"model." +"Our example consists of one *server* and two *clients* all having the " +"same model." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:19 msgid "" -"*Clients* are responsible for generating individual weight-updates for the " -"model based on their local datasets. These updates are then sent to the " -"*server* which will aggregate them to produce a better model. Finally, the " -"*server* sends this improved version of the model back to each *client*. A " -"complete cycle of weight updates is called a *round*." +"*Clients* are responsible for generating individual weight-updates for " +"the model based on their local datasets. These updates are then sent to " +"the *server* which will aggregate them to produce a better model. " +"Finally, the *server* sends this improved version of the model back to " +"each *client*. A complete cycle of weight updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:23 @@ -17646,15 +18802,16 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:29 msgid "" -"Since we want to use PyTorch to solve a computer vision task, let's go ahead " -"and install PyTorch and the **torchvision** library:" +"Since we want to use PyTorch to solve a computer vision task, let's go " +"ahead and install PyTorch and the **torchvision** library:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:39 msgid "" "Now that we have all our dependencies installed, let's run a simple " -"distributed training with two clients and one server. Our training procedure " -"and network architecture are based on PyTorch's `Deep Learning with PyTorch " +"distributed training with two clients and one server. Our training " +"procedure and network architecture are based on PyTorch's `Deep Learning " +"with PyTorch " "`_." msgstr "" @@ -17671,33 +18828,33 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:62 msgid "" "We use PyTorch to load CIFAR10, a popular colored image classification " -"dataset for machine learning. The PyTorch :code:`DataLoader()` downloads the " -"training and test data that are then normalized." +"dataset for machine learning. The PyTorch :code:`DataLoader()` downloads " +"the training and test data that are then normalized." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:78 msgid "" -"Define the loss and optimizer with PyTorch. The training of the dataset is " -"done by looping over the dataset, measure the corresponding loss and " +"Define the loss and optimizer with PyTorch. The training of the dataset " +"is done by looping over the dataset, measure the corresponding loss and " "optimize it." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:94 msgid "" -"Define then the validation of the machine learning network. We loop over " -"the test set and measure the loss and accuracy of the test set." +"Define then the validation of the machine learning network. We loop over" +" the test set and measure the loss and accuracy of the test set." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:113 msgid "" -"After defining the training and testing of a PyTorch machine learning model, " -"we use the functions for the Flower clients." +"After defining the training and testing of a PyTorch machine learning " +"model, we use the functions for the Flower clients." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:115 msgid "" -"The Flower clients will use a simple CNN adapted from 'PyTorch: A 60 Minute " -"Blitz':" +"The Flower clients will use a simple CNN adapted from 'PyTorch: A 60 " +"Minute Blitz':" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:142 @@ -17709,19 +18866,20 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:144 #: ../../source/tutorial-quickstart-tensorflow.rst:54 msgid "" -"The Flower server interacts with clients through an interface called :code:" -"`Client`. When the server selects a particular client for training, it sends " -"training instructions over the network. The client receives those " -"instructions and calls one of the :code:`Client` methods to run your code (i." -"e., to train the neural network we defined earlier)." +"The Flower server interacts with clients through an interface called " +":code:`Client`. When the server selects a particular client for training," +" it sends training instructions over the network. The client receives " +"those instructions and calls one of the :code:`Client` methods to run " +"your code (i.e., to train the neural network we defined earlier)." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:150 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which makes " -"it easier to implement the :code:`Client` interface when your workload uses " -"PyTorch. Implementing :code:`NumPyClient` usually means defining the " -"following methods (:code:`set_parameters` is optional though):" +"Flower provides a convenience class called :code:`NumPyClient` which " +"makes it easier to implement the :code:`Client` interface when your " +"workload uses PyTorch. Implementing :code:`NumPyClient` usually means " +"defining the following methods (:code:`set_parameters` is optional " +"though):" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:156 @@ -17737,7 +18895,8 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:158 #: ../../source/tutorial-quickstart-scikitlearn.rst:121 msgid "" -"update the local model weights with the parameters received from the server" +"update the local model weights with the parameters received from the " +"server" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:160 @@ -17767,22 +18926,22 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:189 #: ../../source/tutorial-quickstart-tensorflow.rst:82 msgid "" -"We can now create an instance of our class :code:`CifarClient` and add one " -"line to actually run this client:" +"We can now create an instance of our class :code:`CifarClient` and add " +"one line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:196 #: ../../source/tutorial-quickstart-tensorflow.rst:90 msgid "" -"That's it for the client. We only have to implement :code:`Client` or :code:" -"`NumPyClient` and call :code:`fl.client.start_client()`. If you implement a " -"client of type :code:`NumPyClient` you'll need to first call its :code:" -"`to_client()` method. The string :code:`\"[::]:8080\"` tells the client " -"which server to connect to. In our case we can run the server and the client " -"on the same machine, therefore we use :code:`\"[::]:8080\"`. If we run a " -"truly federated workload with the server and clients running on different " -"machines, all that needs to change is the :code:`server_address` we point " -"the client at." +"That's it for the client. We only have to implement :code:`Client` or " +":code:`NumPyClient` and call :code:`fl.client.start_client()`. If you " +"implement a client of type :code:`NumPyClient` you'll need to first call " +"its :code:`to_client()` method. The string :code:`\"[::]:8080\"` tells " +"the client which server to connect to. In our case we can run the server " +"and the client on the same machine, therefore we use " +":code:`\"[::]:8080\"`. If we run a truly federated workload with the " +"server and clients running on different machines, all that needs to " +"change is the :code:`server_address` we point the client at." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:226 @@ -17790,8 +18949,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:122 #: ../../source/tutorial-quickstart-xgboost.rst:533 msgid "" -"Once the server is running we can start the clients in different terminals. " -"Open a new terminal and start the first client:" +"Once the server is running we can start the clients in different " +"terminals. Open a new terminal and start the first client:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:233 @@ -17805,22 +18964,24 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:252 #: ../../source/tutorial-quickstart-xgboost.rst:546 msgid "" -"Each client will have its own dataset. You should now see how the training " -"does in the very first terminal (the one that started the server):" +"Each client will have its own dataset. You should now see how the " +"training does in the very first terminal (the one that started the " +"server):" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:271 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code `_ for this example can be found " -"in :code:`examples/quickstart-pytorch`." +"learning system. The full `source code " +"`_ for this example can be found in :code:`examples" +"/quickstart-pytorch`." msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"PyTorch Lightning to train an Auto Encoder model on MNIST." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with PyTorch Lightning to train an Auto Encoder model on MNIST." msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 @@ -17829,20 +18990,21 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:10 msgid "" -"Let's build a horizontal federated learning system using PyTorch Lightning " -"and Flower!" +"Let's build a horizontal federated learning system using PyTorch " +"Lightning and Flower!" msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 msgid "" -"Please refer to the `full code example `_ to learn more." +"Please refer to the `full code example " +"`_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"scikit-learn to train a linear regression model." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with scikit-learn to train a linear regression model." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:5 @@ -17851,23 +19013,24 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:10 msgid "" -"In this tutorial, we will learn how to train a :code:`Logistic Regression` " -"model on MNIST using Flower and scikit-learn." +"In this tutorial, we will learn how to train a :code:`Logistic " +"Regression` model on MNIST using Flower and scikit-learn." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:12 msgid "" -"It is recommended to create a virtual environment and run everything within " -"this :doc:`virtualenv `." +"It is recommended to create a virtual environment and run everything " +"within this :doc:`virtualenv `." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:16 msgid "" -"*Clients* are responsible for generating individual model parameter updates " -"for the model based on their local datasets. These updates are then sent to " -"the *server* which will aggregate them to produce an updated global model. " -"Finally, the *server* sends this improved version of the model back to each " -"*client*. A complete cycle of parameters updates is called a *round*." +"*Clients* are responsible for generating individual model parameter " +"updates for the model based on their local datasets. These updates are " +"then sent to the *server* which will aggregate them to produce an updated" +" global model. Finally, the *server* sends this improved version of the " +"model back to each *client*. A complete cycle of parameters updates is " +"called a *round*." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:20 @@ -17888,10 +19051,10 @@ msgstr "" msgid "" "Now that we have all our dependencies installed, let's run a simple " "distributed training with two clients and one server. However, before " -"setting up the client and server, we will define all functionalities that we " -"need for our federated learning setup within :code:`utils.py`. The :code:" -"`utils.py` contains different functions defining all the machine learning " -"basics:" +"setting up the client and server, we will define all functionalities that" +" we need for our federated learning setup within :code:`utils.py`. The " +":code:`utils.py` contains different functions defining all the machine " +"learning basics:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:45 @@ -17920,44 +19083,46 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:52 msgid "" -"Please check out :code:`utils.py` `here `_ for more details. The pre-" -"defined functions are used in the :code:`client.py` and imported. The :code:" -"`client.py` also requires to import several packages such as Flower and " -"scikit-learn:" +"Please check out :code:`utils.py` `here " +"`_ for more details. The pre-defined functions are used in" +" the :code:`client.py` and imported. The :code:`client.py` also requires " +"to import several packages such as Flower and scikit-learn:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:67 msgid "" -"Prior to local training, we need to load the MNIST dataset, a popular image " -"classification dataset of handwritten digits for machine learning, and " -"partition the dataset for FL. This can be conveniently achieved using " -"`Flower Datasets `_. The :code:" -"`FederatedDataset.load_partition()` method loads the partitioned training " -"set for each partition ID defined in the :code:`--partition-id` argument." +"Prior to local training, we need to load the MNIST dataset, a popular " +"image classification dataset of handwritten digits for machine learning, " +"and partition the dataset for FL. This can be conveniently achieved using" +" `Flower Datasets `_. The " +":code:`FederatedDataset.load_partition()` method loads the partitioned " +"training set for each partition ID defined in the :code:`--partition-id` " +"argument." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:95 msgid "" -"Next, the logistic regression model is defined and initialized with :code:" -"`utils.set_initial_params()`." +"Next, the logistic regression model is defined and initialized with " +":code:`utils.set_initial_params()`." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:107 msgid "" -"The Flower server interacts with clients through an interface called :code:" -"`Client`. When the server selects a particular client for training, it sends " -"training instructions over the network. The client receives those " -"instructions and calls one of the :code:`Client` methods to run your code (i." -"e., to fit the logistic regression we defined earlier)." +"The Flower server interacts with clients through an interface called " +":code:`Client`. When the server selects a particular client for training," +" it sends training instructions over the network. The client receives " +"those instructions and calls one of the :code:`Client` methods to run " +"your code (i.e., to fit the logistic regression we defined earlier)." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:113 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which makes " -"it easier to implement the :code:`Client` interface when your workload uses " -"scikit-learn. Implementing :code:`NumPyClient` usually means defining the " -"following methods (:code:`set_parameters` is optional though):" +"Flower provides a convenience class called :code:`NumPyClient` which " +"makes it easier to implement the :code:`Client` interface when your " +"workload uses scikit-learn. Implementing :code:`NumPyClient` usually " +"means defining the following methods (:code:`set_parameters` is optional " +"though):" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:122 @@ -17970,28 +19135,28 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:153 msgid "" -"We can now create an instance of our class :code:`MnistClient` and add one " -"line to actually run this client:" +"We can now create an instance of our class :code:`MnistClient` and add " +"one line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:160 msgid "" -"That's it for the client. We only have to implement :code:`Client` or :code:" -"`NumPyClient` and call :code:`fl.client.start_client()`. If you implement a " -"client of type :code:`NumPyClient` you'll need to first call its :code:" -"`to_client()` method. The string :code:`\"0.0.0.0:8080\"` tells the client " -"which server to connect to. In our case we can run the server and the client " -"on the same machine, therefore we use :code:`\"0.0.0.0:8080\"`. If we run a " -"truly federated workload with the server and clients running on different " -"machines, all that needs to change is the :code:`server_address` we pass to " -"the client." +"That's it for the client. We only have to implement :code:`Client` or " +":code:`NumPyClient` and call :code:`fl.client.start_client()`. If you " +"implement a client of type :code:`NumPyClient` you'll need to first call " +"its :code:`to_client()` method. The string :code:`\"0.0.0.0:8080\"` tells" +" the client which server to connect to. In our case we can run the server" +" and the client on the same machine, therefore we use " +":code:`\"0.0.0.0:8080\"`. If we run a truly federated workload with the " +"server and clients running on different machines, all that needs to " +"change is the :code:`server_address` we pass to the client." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:169 msgid "" "The following Flower server is a little bit more advanced and returns an " -"evaluation function for the server-side evaluation. First, we import again " -"all required libraries such as Flower and scikit-learn." +"evaluation function for the server-side evaluation. First, we import " +"again all required libraries such as Flower and scikit-learn." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:172 @@ -18000,44 +19165,46 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:185 msgid "" -"The number of federated learning rounds is set in :code:`fit_round()` and " -"the evaluation is defined in :code:`get_evaluate_fn()`. The evaluation " +"The number of federated learning rounds is set in :code:`fit_round()` and" +" the evaluation is defined in :code:`get_evaluate_fn()`. The evaluation " "function is called after each federated learning round and gives you " -"information about loss and accuracy. Note that we also make use of Flower " -"Datasets here to load the test split of the MNIST dataset for server-side " -"evaluation." +"information about loss and accuracy. Note that we also make use of Flower" +" Datasets here to load the test split of the MNIST dataset for server-" +"side evaluation." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:213 msgid "" -"The :code:`main` contains the server-side parameter initialization :code:" -"`utils.set_initial_params()` as well as the aggregation strategy :code:`fl." -"server.strategy:FedAvg()`. The strategy is the default one, federated " -"averaging (or FedAvg), with two clients and evaluation after each federated " -"learning round. The server can be started with the command :code:`fl.server." -"start_server(server_address=\"0.0.0.0:8080\", strategy=strategy, config=fl." -"server.ServerConfig(num_rounds=3))`." +"The :code:`main` contains the server-side parameter initialization " +":code:`utils.set_initial_params()` as well as the aggregation strategy " +":code:`fl.server.strategy:FedAvg()`. The strategy is the default one, " +"federated averaging (or FedAvg), with two clients and evaluation after " +"each federated learning round. The server can be started with the command" +" :code:`fl.server.start_server(server_address=\"0.0.0.0:8080\", " +"strategy=strategy, config=fl.server.ServerConfig(num_rounds=3))`." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:232 msgid "" "With both client and server ready, we can now run everything and see " "federated learning in action. Federated learning systems usually have a " -"server and multiple clients. We, therefore, have to start the server first:" +"server and multiple clients. We, therefore, have to start the server " +"first:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:286 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code `_ for this example can be found in :code:" -"`examples/sklearn-logreg-mnist`." +"learning system. The full `source code " +"`_ for this example can be found in :code:`examples/sklearn-logreg-" +"mnist`." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"TensorFlow to train a MobilNetV2 model on CIFAR-10." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with TensorFlow to train a MobilNetV2 model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:5 @@ -18054,8 +19221,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:21 msgid "" -"Since we want to use the Keras API of TensorFlow (TF), we have to install TF " -"as well:" +"Since we want to use the Keras API of TensorFlow (TF), we have to install" +" TF as well:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:31 @@ -18064,24 +19231,25 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:38 msgid "" -"We use the Keras utilities of TF to load CIFAR10, a popular colored image " -"classification dataset for machine learning. The call to :code:`tf.keras." -"datasets.cifar10.load_data()` downloads CIFAR10, caches it locally, and then " -"returns the entire training and test set as NumPy ndarrays." +"We use the Keras utilities of TF to load CIFAR10, a popular colored image" +" classification dataset for machine learning. The call to " +":code:`tf.keras.datasets.cifar10.load_data()` downloads CIFAR10, caches " +"it locally, and then returns the entire training and test set as NumPy " +"ndarrays." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:47 msgid "" -"Next, we need a model. For the purpose of this tutorial, we use MobilNetV2 " -"with 10 output classes:" +"Next, we need a model. For the purpose of this tutorial, we use " +"MobilNetV2 with 10 output classes:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:60 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which makes " -"it easier to implement the :code:`Client` interface when your workload uses " -"Keras. The :code:`NumPyClient` interface defines three methods which can be " -"implemented in the following way:" +"Flower provides a convenience class called :code:`NumPyClient` which " +"makes it easier to implement the :code:`Client` interface when your " +"workload uses Keras. The :code:`NumPyClient` interface defines three " +"methods which can be implemented in the following way:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:135 @@ -18090,22 +19258,23 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:137 msgid "" -"You should now see how the training does in the very first terminal (the one " -"that started the server):" +"You should now see how the training does in the very first terminal (the " +"one that started the server):" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:169 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code `_ for this can be found in :" -"code:`examples/quickstart-tensorflow/client.py`." +"learning system. The full `source code " +"`_ for this can be found in :code:`examples" +"/quickstart-tensorflow/client.py`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower with " -"XGBoost to train classification models on trees." +"Check out this Federated Learning quickstart tutorial for using Flower " +"with XGBoost to train classification models on trees." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:5 @@ -18119,17 +19288,18 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:16 msgid "" "EXtreme Gradient Boosting (**XGBoost**) is a robust and efficient " -"implementation of gradient-boosted decision tree (**GBDT**), that maximises " -"the computational boundaries for boosted tree methods. It's primarily " -"designed to enhance both the performance and computational speed of machine " -"learning models. In XGBoost, trees are constructed concurrently, unlike the " -"sequential approach taken by GBDT." +"implementation of gradient-boosted decision tree (**GBDT**), that " +"maximises the computational boundaries for boosted tree methods. It's " +"primarily designed to enhance both the performance and computational " +"speed of machine learning models. In XGBoost, trees are constructed " +"concurrently, unlike the sequential approach taken by GBDT." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:20 msgid "" "Often, for tabular data on medium-sized datasets with fewer than 10k " -"training examples, XGBoost surpasses the results of deep learning techniques." +"training examples, XGBoost surpasses the results of deep learning " +"techniques." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:23 @@ -18139,30 +19309,30 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:25 msgid "" "Indeed, as the demand for data privacy and decentralized learning grows, " -"there's an increasing requirement to implement federated XGBoost systems for " -"specialised applications, like survival analysis and financial fraud " +"there's an increasing requirement to implement federated XGBoost systems " +"for specialised applications, like survival analysis and financial fraud " "detection." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:27 msgid "" -"Federated learning ensures that raw data remains on the local device, making " -"it an attractive approach for sensitive domains where data security and " -"privacy are paramount. Given the robustness and efficiency of XGBoost, " -"combining it with federated learning offers a promising solution for these " -"specific challenges." +"Federated learning ensures that raw data remains on the local device, " +"making it an attractive approach for sensitive domains where data " +"security and privacy are paramount. Given the robustness and efficiency " +"of XGBoost, combining it with federated learning offers a promising " +"solution for these specific challenges." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:30 msgid "" "In this tutorial we will learn how to train a federated XGBoost model on " "HIGGS dataset using Flower and :code:`xgboost` package. We use a simple " -"example (`full code xgboost-quickstart `_) with two *clients* and one *server* to " -"demonstrate how federated XGBoost works, and then we dive into a more " -"complex example (`full code xgboost-comprehensive `_) to run various " -"experiments." +"example (`full code xgboost-quickstart " +"`_)" +" with two *clients* and one *server* to demonstrate how federated XGBoost" +" works, and then we dive into a more complex example (`full code xgboost-" +"comprehensive `_) to run various experiments." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:37 @@ -18183,16 +19353,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:57 msgid "" -"*Clients* are responsible for generating individual weight-updates for the " -"model based on their local datasets. Now that we have all our dependencies " -"installed, let's run a simple distributed training with two clients and one " -"server." +"*Clients* are responsible for generating individual weight-updates for " +"the model based on their local datasets. Now that we have all our " +"dependencies installed, let's run a simple distributed training with two " +"clients and one server." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:60 msgid "" -"In a file called :code:`client.py`, import xgboost, Flower, Flower Datasets " -"and other related functions:" +"In a file called :code:`client.py`, import xgboost, Flower, Flower " +"Datasets and other related functions:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:87 @@ -18201,15 +19371,15 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:89 msgid "" -"Prior to local training, we require loading the HIGGS dataset from Flower " -"Datasets and conduct data partitioning for FL:" +"Prior to local training, we require loading the HIGGS dataset from Flower" +" Datasets and conduct data partitioning for FL:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:102 msgid "" "In this example, we split the dataset into two partitions with uniform " -"distribution (:code:`IidPartitioner(num_partitions=2)`). Then, we load the " -"partition for the given client based on :code:`node_id`:" +"distribution (:code:`IidPartitioner(num_partitions=2)`). Then, we load " +"the partition for the given client based on :code:`node_id`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:121 @@ -18220,8 +19390,8 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:134 msgid "" -"The functions of :code:`train_test_split` and :code:" -"`transform_dataset_to_dmatrix` are defined as below:" +"The functions of :code:`train_test_split` and " +":code:`transform_dataset_to_dmatrix` are defined as below:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:158 @@ -18230,10 +19400,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:174 msgid "" -"The :code:`num_local_round` represents the number of iterations for local " -"tree boost. We use CPU for the training in default. One can shift it to GPU " -"by setting :code:`tree_method` to :code:`gpu_hist`. We use AUC as evaluation " -"metric." +"The :code:`num_local_round` represents the number of iterations for local" +" tree boost. We use CPU for the training in default. One can shift it to " +"GPU by setting :code:`tree_method` to :code:`gpu_hist`. We use AUC as " +"evaluation metric." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:181 @@ -18242,85 +19412,86 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:183 msgid "" -"After loading the dataset we define the Flower client. We follow the general " -"rule to define :code:`XgbClient` class inherited from :code:`fl.client." -"Client`." +"After loading the dataset we define the Flower client. We follow the " +"general rule to define :code:`XgbClient` class inherited from " +":code:`fl.client.Client`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:193 msgid "" "The :code:`self.bst` is used to keep the Booster objects that remain " "consistent across rounds, allowing them to store predictions from trees " -"integrated in earlier rounds and maintain other essential data structures " -"for training." +"integrated in earlier rounds and maintain other essential data structures" +" for training." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:196 msgid "" -"Then, we override :code:`get_parameters`, :code:`fit` and :code:`evaluate` " -"methods insides :code:`XgbClient` class as follows." +"Then, we override :code:`get_parameters`, :code:`fit` and " +":code:`evaluate` methods insides :code:`XgbClient` class as follows." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:210 msgid "" "Unlike neural network training, XGBoost trees are not started from a " -"specified random weights. In this case, we do not use :code:`get_parameters` " -"and :code:`set_parameters` to initialise model parameters for XGBoost. As a " -"result, let's return an empty tensor in :code:`get_parameters` when it is " -"called by the server at the first round." +"specified random weights. In this case, we do not use " +":code:`get_parameters` and :code:`set_parameters` to initialise model " +"parameters for XGBoost. As a result, let's return an empty tensor in " +":code:`get_parameters` when it is called by the server at the first " +"round." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:251 msgid "" -"In :code:`fit`, at the first round, we call :code:`xgb.train()` to build up " -"the first set of trees. the returned Booster object and config are stored " -"in :code:`self.bst` and :code:`self.config`, respectively. From the second " -"round, we load the global model sent from server to :code:`self.bst`, and " -"then update model weights on local training data with function :code:" -"`local_boost` as follows:" +"In :code:`fit`, at the first round, we call :code:`xgb.train()` to build " +"up the first set of trees. the returned Booster object and config are " +"stored in :code:`self.bst` and :code:`self.config`, respectively. From " +"the second round, we load the global model sent from server to " +":code:`self.bst`, and then update model weights on local training data " +"with function :code:`local_boost` as follows:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:269 msgid "" -"Given :code:`num_local_round`, we update trees by calling :code:`self.bst." -"update` method. After training, the last :code:`N=num_local_round` trees " -"will be extracted to send to the server." +"Given :code:`num_local_round`, we update trees by calling " +":code:`self.bst.update` method. After training, the last " +":code:`N=num_local_round` trees will be extracted to send to the server." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:291 msgid "" -"In :code:`evaluate`, we call :code:`self.bst.eval_set` function to conduct " -"evaluation on valid set. The AUC value will be returned." +"In :code:`evaluate`, we call :code:`self.bst.eval_set` function to " +"conduct evaluation on valid set. The AUC value will be returned." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:294 msgid "" -"Now, we can create an instance of our class :code:`XgbClient` and add one " -"line to actually run this client:" +"Now, we can create an instance of our class :code:`XgbClient` and add one" +" line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:300 msgid "" -"That's it for the client. We only have to implement :code:`Client`and call :" -"code:`fl.client.start_client()`. The string :code:`\"[::]:8080\"` tells the " -"client which server to connect to. In our case we can run the server and the " -"client on the same machine, therefore we use :code:`\"[::]:8080\"`. If we " -"run a truly federated workload with the server and clients running on " -"different machines, all that needs to change is the :code:`server_address` " -"we point the client at." +"That's it for the client. We only have to implement :code:`Client`and " +"call :code:`fl.client.start_client()`. The string :code:`\"[::]:8080\"` " +"tells the client which server to connect to. In our case we can run the " +"server and the client on the same machine, therefore we use " +":code:`\"[::]:8080\"`. If we run a truly federated workload with the " +"server and clients running on different machines, all that needs to " +"change is the :code:`server_address` we point the client at." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:311 msgid "" "These updates are then sent to the *server* which will aggregate them to " -"produce a better model. Finally, the *server* sends this improved version of " -"the model back to each *client* to finish a complete FL round." +"produce a better model. Finally, the *server* sends this improved version" +" of the model back to each *client* to finish a complete FL round." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:314 msgid "" -"In a file named :code:`server.py`, import Flower and FedXgbBagging from :" -"code:`flwr.server.strategy`." +"In a file named :code:`server.py`, import Flower and FedXgbBagging from " +":code:`flwr.server.strategy`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:316 @@ -18329,9 +19500,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:339 msgid "" -"We use two clients for this example. An :code:`evaluate_metrics_aggregation` " -"function is defined to collect and wighted average the AUC values from " -"clients." +"We use two clients for this example. An " +":code:`evaluate_metrics_aggregation` function is defined to collect and " +"wighted average the AUC values from clients." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:342 @@ -18344,16 +19515,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:356 msgid "" -"You must be curious about how bagging aggregation works. Let's look into the " -"details." +"You must be curious about how bagging aggregation works. Let's look into " +"the details." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:358 msgid "" -"In file :code:`flwr.server.strategy.fedxgb_bagging.py`, we define :code:" -"`FedXgbBagging` inherited from :code:`flwr.server.strategy.FedAvg`. Then, we " -"override the :code:`aggregate_fit`, :code:`aggregate_evaluate` and :code:" -"`evaluate` methods as follows:" +"In file :code:`flwr.server.strategy.fedxgb_bagging.py`, we define " +":code:`FedXgbBagging` inherited from :code:`flwr.server.strategy.FedAvg`." +" Then, we override the :code:`aggregate_fit`, :code:`aggregate_evaluate` " +"and :code:`evaluate` methods as follows:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:454 @@ -18365,10 +19536,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:513 msgid "" "In this function, we first fetch the number of trees and the number of " -"parallel trees for the current and previous model by calling :code:" -"`_get_tree_nums`. Then, the fetched information will be aggregated. After " -"that, the trees (containing model weights) are aggregated to generate a new " -"tree model." +"parallel trees for the current and previous model by calling " +":code:`_get_tree_nums`. Then, the fetched information will be aggregated." +" After that, the trees (containing model weights) are aggregated to " +"generate a new tree model." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:518 @@ -18384,16 +19555,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:585 msgid "" "Congratulations! You've successfully built and run your first federated " -"XGBoost system. The AUC values can be checked in :code:" -"`metrics_distributed`. One can see that the average AUC increases over FL " -"rounds." +"XGBoost system. The AUC values can be checked in " +":code:`metrics_distributed`. One can see that the average AUC increases " +"over FL rounds." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:590 msgid "" -"The full `source code `_ for this example can be found in :code:`examples/" -"xgboost-quickstart`." +"The full `source code `_ for this example can be found in :code:`examples" +"/xgboost-quickstart`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:594 @@ -18402,15 +19573,15 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:596 msgid "" -"Now that you have known how federated XGBoost work with Flower, it's time to " -"run some more comprehensive experiments by customising the experimental " -"settings. In the xgboost-comprehensive example (`full code `_), we provide " -"more options to define various experimental setups, including aggregation " -"strategies, data partitioning and centralised/distributed evaluation. We " -"also support :doc:`Flower simulation ` making it " -"easy to simulate large client cohorts in a resource-aware manner. Let's take " -"a look!" +"Now that you have known how federated XGBoost work with Flower, it's time" +" to run some more comprehensive experiments by customising the " +"experimental settings. In the xgboost-comprehensive example (`full code " +"`_), we provide more options to define various experimental" +" setups, including aggregation strategies, data partitioning and " +"centralised/distributed evaluation. We also support :doc:`Flower " +"simulation ` making it easy to simulate large " +"client cohorts in a resource-aware manner. Let's take a look!" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:603 @@ -18419,40 +19590,41 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:605 msgid "" -"In addition to bagging aggregation, we offer a cyclic training scheme, which " -"performs FL in a client-by-client fashion. Instead of aggregating multiple " -"clients, there is only one single client participating in the training per " -"round in the cyclic training scenario. The trained local XGBoost trees will " -"be passed to the next client as an initialised model for next round's " -"boosting." +"In addition to bagging aggregation, we offer a cyclic training scheme, " +"which performs FL in a client-by-client fashion. Instead of aggregating " +"multiple clients, there is only one single client participating in the " +"training per round in the cyclic training scenario. The trained local " +"XGBoost trees will be passed to the next client as an initialised model " +"for next round's boosting." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:609 msgid "" -"To do this, we first customise a :code:`ClientManager` in :code:" -"`server_utils.py`:" +"To do this, we first customise a :code:`ClientManager` in " +":code:`server_utils.py`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:649 msgid "" -"The customised :code:`ClientManager` samples all available clients in each " -"FL round based on the order of connection to the server. Then, we define a " -"new strategy :code:`FedXgbCyclic` in :code:`flwr.server.strategy." -"fedxgb_cyclic.py`, in order to sequentially select only one client in given " -"round and pass the received model to next client." +"The customised :code:`ClientManager` samples all available clients in " +"each FL round based on the order of connection to the server. Then, we " +"define a new strategy :code:`FedXgbCyclic` in " +":code:`flwr.server.strategy.fedxgb_cyclic.py`, in order to sequentially " +"select only one client in given round and pass the received model to next" +" client." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:690 msgid "" "Unlike the original :code:`FedAvg`, we don't perform aggregation here. " -"Instead, we just make a copy of the received client model as global model by " -"overriding :code:`aggregate_fit`." +"Instead, we just make a copy of the received client model as global model" +" by overriding :code:`aggregate_fit`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:693 msgid "" -"Also, the customised :code:`configure_fit` and :code:`configure_evaluate` " -"methods ensure the clients to be sequentially selected given FL round:" +"Also, the customised :code:`configure_fit` and :code:`configure_evaluate`" +" methods ensure the clients to be sequentially selected given FL round:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:757 @@ -18461,11 +19633,11 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:759 msgid "" -"In :code:`dataset.py`, we have a function :code:`instantiate_partitioner` to " -"instantiate the data partitioner based on the given :code:`num_partitions` " -"and :code:`partitioner_type`. Currently, we provide four supported " -"partitioner type to simulate the uniformity/non-uniformity in data quantity " -"(uniform, linear, square, exponential)." +"In :code:`dataset.py`, we have a function :code:`instantiate_partitioner`" +" to instantiate the data partitioner based on the given " +":code:`num_partitions` and :code:`partitioner_type`. Currently, we " +"provide four supported partitioner type to simulate the uniformity/non-" +"uniformity in data quantity (uniform, linear, square, exponential)." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:790 @@ -18474,23 +19646,23 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:792 msgid "" -"To facilitate centralised evaluation, we define a function in :code:" -"`server_utils.py`:" +"To facilitate centralised evaluation, we define a function in " +":code:`server_utils.py`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:824 msgid "" -"This function returns a evaluation function which instantiates a :code:" -"`Booster` object and loads the global model weights to it. The evaluation is " -"conducted by calling :code:`eval_set()` method, and the tested AUC value is " -"reported." +"This function returns a evaluation function which instantiates a " +":code:`Booster` object and loads the global model weights to it. The " +"evaluation is conducted by calling :code:`eval_set()` method, and the " +"tested AUC value is reported." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:827 msgid "" -"As for distributed evaluation on the clients, it's same as the quick-start " -"example by overriding the :code:`evaluate()` method insides the :code:" -"`XgbClient` class in :code:`client_utils.py`." +"As for distributed evaluation on the clients, it's same as the quick-" +"start example by overriding the :code:`evaluate()` method insides the " +":code:`XgbClient` class in :code:`client_utils.py`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:831 @@ -18500,21 +19672,21 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:832 msgid "" "We also provide an example code (:code:`sim.py`) to use the simulation " -"capabilities of Flower to simulate federated XGBoost training on either a " -"single machine or a cluster of machines." +"capabilities of Flower to simulate federated XGBoost training on either a" +" single machine or a cluster of machines." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:866 msgid "" -"After importing all required packages, we define a :code:`main()` function " -"to perform the simulation process:" +"After importing all required packages, we define a :code:`main()` " +"function to perform the simulation process:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:921 msgid "" "We first load the dataset and perform data partitioning, and the pre-" -"processed data is stored in a :code:`list`. After the simulation begins, the " -"clients won't need to pre-process their partitions again." +"processed data is stored in a :code:`list`. After the simulation begins, " +"the clients won't need to pre-process their partitions again." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:924 @@ -18523,8 +19695,8 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:975 msgid "" -"After that, we start the simulation by calling :code:`fl.simulation." -"start_simulation`:" +"After that, we start the simulation by calling " +":code:`fl.simulation.start_simulation`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:995 @@ -18539,18 +19711,18 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1040 msgid "" -"In :code:`utils.py`, we define the arguments parsers for clients, server and " -"simulation, allowing users to specify different experimental settings. Let's " -"first see the sever side:" +"In :code:`utils.py`, we define the arguments parsers for clients, server " +"and simulation, allowing users to specify different experimental " +"settings. Let's first see the sever side:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1086 msgid "" "This allows user to specify training strategies / the number of total " -"clients / FL rounds / participating clients / clients for evaluation, and " -"evaluation fashion. Note that with :code:`--centralised-eval`, the sever " -"will do centralised evaluation and all functionalities for client evaluation " -"will be disabled." +"clients / FL rounds / participating clients / clients for evaluation, and" +" evaluation fashion. Note that with :code:`--centralised-eval`, the sever" +" will do centralised evaluation and all functionalities for client " +"evaluation will be disabled." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1090 @@ -18559,10 +19731,11 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1144 msgid "" -"This defines various options for client data partitioning. Besides, clients " -"also have an option to conduct evaluation on centralised test set by " -"setting :code:`--centralised-eval`, as well as an option to perform scaled " -"learning rate based on the number of clients by setting :code:`--scaled-lr`." +"This defines various options for client data partitioning. Besides, " +"clients also have an option to conduct evaluation on centralised test set" +" by setting :code:`--centralised-eval`, as well as an option to perform " +"scaled learning rate based on the number of clients by setting :code" +":`--scaled-lr`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1148 @@ -18579,9 +19752,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1231 msgid "" -"To run a centralised evaluated experiment with bagging strategy on 5 clients " -"with exponential distribution for 50 rounds, we first start the server as " -"below:" +"To run a centralised evaluated experiment with bagging strategy on 5 " +"clients with exponential distribution for 50 rounds, we first start the " +"server as below:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1238 @@ -18594,9 +19767,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1250 msgid "" -"The full `code `_ for this comprehensive example can be found in :code:" -"`examples/xgboost-comprehensive`." +"The full `code `_ for this comprehensive example can be found in" +" :code:`examples/xgboost-comprehensive`." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:9 @@ -18607,18 +19780,19 @@ msgstr "" msgid "" "Welcome to the third part of the Flower federated learning tutorial. In " "previous parts of this tutorial, we introduced federated learning with " -"PyTorch and Flower (`part 1 `__) and we learned how strategies can be " -"used to customize the execution on both the server and the clients (`part 2 " -"`__)." +"PyTorch and Flower (`part 1 `__) and we learned how strategies " +"can be used to customize the execution on both the server and the clients" +" (`part 2 `__)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:13 msgid "" -"In this notebook, we'll continue to customize the federated learning system " -"we built previously by creating a custom version of FedAvg (again, using " -"`Flower `__ and `PyTorch `__)." +"In this notebook, we'll continue to customize the federated learning " +"system we built previously by creating a custom version of FedAvg (again," +" using `Flower `__ and `PyTorch " +"`__)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:15 @@ -18626,11 +19800,11 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:15 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:15 msgid "" -"`Star Flower on GitHub `__ ⭐️ and join the " -"Flower community on Slack to connect, ask questions, and get help: `Join " -"Slack `__ 🌼 We'd love to hear from you in the " -"``#introductions`` channel! And if anything is unclear, head over to the " -"``#questions`` channel." +"`Star Flower on GitHub `__ ⭐️ and join " +"the Flower community on Slack to connect, ask questions, and get help: " +"`Join Slack `__ 🌼 We'd love to hear from " +"you in the ``#introductions`` channel! And if anything is unclear, head " +"over to the ``#questions`` channel." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:17 @@ -18676,14 +19850,14 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:102 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:101 msgid "" -"It is possible to switch to a runtime that has GPU acceleration enabled (on " -"Google Colab: ``Runtime > Change runtime type > Hardware acclerator: GPU > " -"Save``). Note, however, that Google Colab is not always able to offer GPU " -"acceleration. If you see an error related to GPU availability in one of the " -"following sections, consider switching back to CPU-based execution by " -"setting ``DEVICE = torch.device(\"cpu\")``. If the runtime has GPU " -"acceleration enabled, you should see the output ``Training on cuda``, " -"otherwise it'll say ``Training on cpu``." +"It is possible to switch to a runtime that has GPU acceleration enabled " +"(on Google Colab: ``Runtime > Change runtime type > Hardware acclerator: " +"GPU > Save``). Note, however, that Google Colab is not always able to " +"offer GPU acceleration. If you see an error related to GPU availability " +"in one of the following sections, consider switching back to CPU-based " +"execution by setting ``DEVICE = torch.device(\"cpu\")``. If the runtime " +"has GPU acceleration enabled, you should see the output ``Training on " +"cuda``, otherwise it'll say ``Training on cpu``." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:114 @@ -18695,11 +19869,11 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:116 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:116 msgid "" -"Let's now load the CIFAR-10 training and test set, partition them into ten " -"smaller datasets (each split into training and validation set), and wrap " -"everything in their own ``DataLoader``. We introduce a new parameter " -"``num_clients`` which allows us to call ``load_datasets`` with different " -"numbers of clients." +"Let's now load the CIFAR-10 training and test set, partition them into " +"ten smaller datasets (each split into training and validation set), and " +"wrap everything in their own ``DataLoader``. We introduce a new parameter" +" ``num_clients`` which allows us to call ``load_datasets`` with different" +" numbers of clients." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:167 @@ -18712,8 +19886,8 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:170 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:169 msgid "" -"Let's continue with the usual model definition (including ``set_parameters`` " -"and ``get_parameters``), training and test functions:" +"Let's continue with the usual model definition (including " +"``set_parameters`` and ``get_parameters``), training and test functions:" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:258 @@ -18724,10 +19898,10 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:260 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:260 msgid "" -"To implement the Flower client, we (again) create a subclass of ``flwr." -"client.NumPyClient`` and implement the three methods ``get_parameters``, " -"``fit``, and ``evaluate``. Here, we also pass the ``cid`` to the client and " -"use it log additional details:" +"To implement the Flower client, we (again) create a subclass of " +"``flwr.client.NumPyClient`` and implement the three methods " +"``get_parameters``, ``fit``, and ``evaluate``. Here, we also pass the " +"``cid`` to the client and use it log additional details:" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:308 @@ -18740,11 +19914,11 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:341 msgid "" -"Let’s overwrite the ``configure_fit`` method such that it passes a higher " -"learning rate (potentially also other hyperparameters) to the optimizer of a " -"fraction of the clients. We will keep the sampling of the clients as it is " -"in ``FedAvg`` and then change the configuration dictionary (one of the " -"``FitIns`` attributes)." +"Let’s overwrite the ``configure_fit`` method such that it passes a higher" +" learning rate (potentially also other hyperparameters) to the optimizer " +"of a fraction of the clients. We will keep the sampling of the clients as" +" it is in ``FedAvg`` and then change the configuration dictionary (one of" +" the ``FitIns`` attributes)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:507 @@ -18761,13 +19935,13 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:536 msgid "" -"In this notebook, we’ve seen how to implement a custom strategy. A custom " -"strategy enables granular control over client node configuration, result " -"aggregation, and more. To define a custom strategy, you only have to " -"overwrite the abstract methods of the (abstract) base class ``Strategy``. To " -"make custom strategies even more powerful, you can pass custom functions to " -"the constructor of your new class (``__init__``) and then call these " -"functions whenever needed." +"In this notebook, we’ve seen how to implement a custom strategy. A custom" +" strategy enables granular control over client node configuration, result" +" aggregation, and more. To define a custom strategy, you only have to " +"overwrite the abstract methods of the (abstract) base class ``Strategy``." +" To make custom strategies even more powerful, you can pass custom " +"functions to the constructor of your new class (``__init__``) and then " +"call these functions whenever needed." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:550 @@ -18776,8 +19950,8 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:715 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:369 msgid "" -"Before you continue, make sure to join the Flower community on Slack: `Join " -"Slack `__" +"Before you continue, make sure to join the Flower community on Slack: " +"`Join Slack `__" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:552 @@ -18786,15 +19960,16 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:717 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:371 msgid "" -"There's a dedicated ``#questions`` channel if you need help, but we'd also " -"love to hear who you are in ``#introductions``!" +"There's a dedicated ``#questions`` channel if you need help, but we'd " +"also love to hear who you are in ``#introductions``!" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:554 msgid "" -"The `Flower Federated Learning Tutorial - Part 4 `__ introduces " -"``Client``, the flexible API underlying ``NumPyClient``." +"The `Flower Federated Learning Tutorial - Part 4 " +"`__ introduces ``Client``, the flexible API underlying " +"``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:9 @@ -18803,26 +19978,26 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:11 msgid "" -"Welcome to the fourth part of the Flower federated learning tutorial. In the " -"previous parts of this tutorial, we introduced federated learning with " -"PyTorch and Flower (`part 1 `__), we learned how strategies can be used " -"to customize the execution on both the server and the clients (`part 2 " -"`__), and we built our own custom strategy from scratch (`part " -"3 `__)." +"Welcome to the fourth part of the Flower federated learning tutorial. In " +"the previous parts of this tutorial, we introduced federated learning " +"with PyTorch and Flower (`part 1 `__), we learned how " +"strategies can be used to customize the execution on both the server and " +"the clients (`part 2 `__), and we built our own " +"custom strategy from scratch (`part 3 `__)." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:14 msgid "" -"In this notebook, we revisit ``NumPyClient`` and introduce a new baseclass " -"for building clients, simply named ``Client``. In previous parts of this " -"tutorial, we've based our client on ``NumPyClient``, a convenience class " -"which makes it easy to work with machine learning libraries that have good " -"NumPy interoperability. With ``Client``, we gain a lot of flexibility that " -"we didn't have before, but we'll also have to do a few things the we didn't " -"have to do before." +"In this notebook, we revisit ``NumPyClient`` and introduce a new " +"baseclass for building clients, simply named ``Client``. In previous " +"parts of this tutorial, we've based our client on ``NumPyClient``, a " +"convenience class which makes it easy to work with machine learning " +"libraries that have good NumPy interoperability. With ``Client``, we gain" +" a lot of flexibility that we didn't have before, but we'll also have to " +"do a few things the we didn't have to do before." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:18 @@ -18838,9 +20013,9 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:117 msgid "" -"Let's now load the CIFAR-10 training and test set, partition them into ten " -"smaller datasets (each split into training and validation set), and wrap " -"everything in their own ``DataLoader``." +"Let's now load the CIFAR-10 training and test set, partition them into " +"ten smaller datasets (each split into training and validation set), and " +"wrap everything in their own ``DataLoader``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:259 @@ -18849,10 +20024,10 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:261 msgid "" -"So far, we've implemented our client by subclassing ``flwr.client." -"NumPyClient``. The three methods we implemented are ``get_parameters``, " -"``fit``, and ``evaluate``. Finally, we wrap the creation of instances of " -"this class in a function called ``client_fn``:" +"So far, we've implemented our client by subclassing " +"``flwr.client.NumPyClient``. The three methods we implemented are " +"``get_parameters``, ``fit``, and ``evaluate``. Finally, we wrap the " +"creation of instances of this class in a function called ``client_fn``:" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:309 @@ -18874,24 +20049,25 @@ msgid "" "Let's dive a little bit deeper and discuss how Flower executes this " "simulation. Whenever a client is selected to do some work, " "``start_simulation`` calls the function ``numpyclient_fn`` to create an " -"instance of our ``FlowerNumPyClient`` (along with loading the model and the " -"data)." +"instance of our ``FlowerNumPyClient`` (along with loading the model and " +"the data)." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:343 msgid "" "But here's the perhaps surprising part: Flower doesn't actually use the " -"``FlowerNumPyClient`` object directly. Instead, it wraps the object to makes " -"it look like a subclass of ``flwr.client.Client``, not ``flwr.client." -"NumPyClient``. In fact, the Flower core framework doesn't know how to handle " -"``NumPyClient``'s, it only knows how to handle ``Client``'s. ``NumPyClient`` " -"is just a convenience abstraction built on top of ``Client``." +"``FlowerNumPyClient`` object directly. Instead, it wraps the object to " +"makes it look like a subclass of ``flwr.client.Client``, not " +"``flwr.client.NumPyClient``. In fact, the Flower core framework doesn't " +"know how to handle ``NumPyClient``'s, it only knows how to handle " +"``Client``'s. ``NumPyClient`` is just a convenience abstraction built on " +"top of ``Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:345 msgid "" -"Instead of building on top of ``NumPyClient``, we can directly build on top " -"of ``Client``." +"Instead of building on top of ``NumPyClient``, we can directly build on " +"top of ``Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:357 @@ -18900,13 +20076,14 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:359 msgid "" -"Let's try to do the same thing using ``Client`` instead of ``NumPyClient``." +"Let's try to do the same thing using ``Client`` instead of " +"``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:465 msgid "" -"Before we discuss the code in more detail, let's try to run it! Gotta make " -"sure our new ``Client``-based client works, right?" +"Before we discuss the code in more detail, let's try to run it! Gotta " +"make sure our new ``Client``-based client works, right?" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:490 @@ -18917,40 +20094,40 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:492 msgid "" -"First of all, it's more code. But why? The difference comes from the fact " -"that ``Client`` expects us to take care of parameter serialization and " -"deserialization. For Flower to be able to send parameters over the network, " -"it eventually needs to turn these parameters into ``bytes``. Turning " -"parameters (e.g., NumPy ``ndarray``'s) into raw bytes is called " +"First of all, it's more code. But why? The difference comes from the fact" +" that ``Client`` expects us to take care of parameter serialization and " +"deserialization. For Flower to be able to send parameters over the " +"network, it eventually needs to turn these parameters into ``bytes``. " +"Turning parameters (e.g., NumPy ``ndarray``'s) into raw bytes is called " "serialization. Turning raw bytes into something more useful (like NumPy " -"``ndarray``'s) is called deserialization. Flower needs to do both: it needs " -"to serialize parameters on the server-side and send them to the client, the " -"client needs to deserialize them to use them for local training, and then " -"serialize the updated parameters again to send them back to the server, " -"which (finally!) deserializes them again in order to aggregate them with the " -"updates received from other clients." +"``ndarray``'s) is called deserialization. Flower needs to do both: it " +"needs to serialize parameters on the server-side and send them to the " +"client, the client needs to deserialize them to use them for local " +"training, and then serialize the updated parameters again to send them " +"back to the server, which (finally!) deserializes them again in order to " +"aggregate them with the updates received from other clients." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:495 msgid "" "The only *real* difference between Client and NumPyClient is that " -"NumPyClient takes care of serialization and deserialization for you. It can " -"do so because it expects you to return parameters as NumPy ndarray's, and it " -"knows how to handle these. This makes working with machine learning " -"libraries that have good NumPy support (most of them) a breeze." +"NumPyClient takes care of serialization and deserialization for you. It " +"can do so because it expects you to return parameters as NumPy ndarray's," +" and it knows how to handle these. This makes working with machine " +"learning libraries that have good NumPy support (most of them) a breeze." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:497 msgid "" -"In terms of API, there's one major difference: all methods in Client take " -"exactly one argument (e.g., ``FitIns`` in ``Client.fit``) and return exactly " -"one value (e.g., ``FitRes`` in ``Client.fit``). The methods in " +"In terms of API, there's one major difference: all methods in Client take" +" exactly one argument (e.g., ``FitIns`` in ``Client.fit``) and return " +"exactly one value (e.g., ``FitRes`` in ``Client.fit``). The methods in " "``NumPyClient`` on the other hand have multiple arguments (e.g., " -"``parameters`` and ``config`` in ``NumPyClient.fit``) and multiple return " -"values (e.g., ``parameters``, ``num_example``, and ``metrics`` in " -"``NumPyClient.fit``) if there are multiple things to handle. These ``*Ins`` " -"and ``*Res`` objects in ``Client`` wrap all the individual values you're " -"used to from ``NumPyClient``." +"``parameters`` and ``config`` in ``NumPyClient.fit``) and multiple return" +" values (e.g., ``parameters``, ``num_example``, and ``metrics`` in " +"``NumPyClient.fit``) if there are multiple things to handle. These " +"``*Ins`` and ``*Res`` objects in ``Client`` wrap all the individual " +"values you're used to from ``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:510 @@ -18967,16 +20144,17 @@ msgstr "" msgid "" "But first what is serialization? Serialization is just the process of " "converting an object into raw bytes, and equally as important, " -"deserialization is the process of converting raw bytes back into an object. " -"This is very useful for network communication. Indeed, without " +"deserialization is the process of converting raw bytes back into an " +"object. This is very useful for network communication. Indeed, without " "serialization, you could not just a Python object through the internet." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:516 msgid "" -"Federated Learning relies heavily on internet communication for training by " -"sending Python objects back and forth between the clients and the server. " -"This means that serialization is an essential part of Federated Learning." +"Federated Learning relies heavily on internet communication for training " +"by sending Python objects back and forth between the clients and the " +"server. This means that serialization is an essential part of Federated " +"Learning." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:518 @@ -18996,15 +20174,15 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:523 msgid "" -"This is where the real serialization/deserialization will happen, especially " -"in ``ndarray_to_sparse_bytes`` for serialization and " +"This is where the real serialization/deserialization will happen, " +"especially in ``ndarray_to_sparse_bytes`` for serialization and " "``sparse_bytes_to_ndarray`` for deserialization." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:525 msgid "" -"Note that we imported the ``scipy.sparse`` library in order to convert our " -"arrays." +"Note that we imported the ``scipy.sparse`` library in order to convert " +"our arrays." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:613 @@ -19013,28 +20191,30 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:615 msgid "" -"To be able to serialize our ``ndarray``\\ s into sparse parameters, we will " -"just have to call our custom functions in our ``flwr.client.Client``." +"To be able to serialize our ``ndarray``\\ s into sparse parameters, we " +"will just have to call our custom functions in our " +"``flwr.client.Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:617 msgid "" "Indeed, in ``get_parameters`` we need to serialize the parameters we got " -"from our network using our custom ``ndarrays_to_sparse_parameters`` defined " -"above." +"from our network using our custom ``ndarrays_to_sparse_parameters`` " +"defined above." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:619 msgid "" "In ``fit``, we first need to deserialize the parameters coming from the " -"server using our custom ``sparse_parameters_to_ndarrays`` and then we need " -"to serialize our local results with ``ndarrays_to_sparse_parameters``." +"server using our custom ``sparse_parameters_to_ndarrays`` and then we " +"need to serialize our local results with " +"``ndarrays_to_sparse_parameters``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:621 msgid "" -"In ``evaluate``, we will only need to deserialize the global parameters with " -"our custom function." +"In ``evaluate``, we will only need to deserialize the global parameters " +"with our custom function." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:725 @@ -19043,10 +20223,11 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:727 msgid "" -"For this example, we will just use ``FedAvg`` as a strategy. To change the " -"serialization and deserialization here, we only need to reimplement the " -"``evaluate`` and ``aggregate_fit`` functions of ``FedAvg``. The other " -"functions of the strategy will be inherited from the super class ``FedAvg``." +"For this example, we will just use ``FedAvg`` as a strategy. To change " +"the serialization and deserialization here, we only need to reimplement " +"the ``evaluate`` and ``aggregate_fit`` functions of ``FedAvg``. The other" +" functions of the strategy will be inherited from the super class " +"``FedAvg``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:729 @@ -19071,19 +20252,19 @@ msgstr "" msgid "" "In this part of the tutorial, we've seen how we can build clients by " "subclassing either ``NumPyClient`` or ``Client``. ``NumPyClient`` is a " -"convenience abstraction that makes it easier to work with machine learning " -"libraries that have good NumPy interoperability. ``Client`` is a more " -"flexible abstraction that allows us to do things that are not possible in " -"``NumPyClient``. In order to do so, it requires us to handle parameter " -"serialization and deserialization ourselves." +"convenience abstraction that makes it easier to work with machine " +"learning libraries that have good NumPy interoperability. ``Client`` is a" +" more flexible abstraction that allows us to do things that are not " +"possible in ``NumPyClient``. In order to do so, it requires us to handle " +"parameter serialization and deserialization ourselves." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:952 msgid "" -"This is the final part of the Flower tutorial (for now!), congratulations! " -"You're now well equipped to understand the rest of the documentation. There " -"are many topics we didn't cover in the tutorial, we recommend the following " -"resources:" +"This is the final part of the Flower tutorial (for now!), " +"congratulations! You're now well equipped to understand the rest of the " +"documentation. There are many topics we didn't cover in the tutorial, we " +"recommend the following resources:" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:954 @@ -19092,20 +20273,20 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:955 msgid "" -"`Check out Flower Code Examples `__" +"`Check out Flower Code Examples " +"`__" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:956 msgid "" -"`Use Flower Baselines for your research `__" +"`Use Flower Baselines for your research " +"`__" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:957 msgid "" -"`Watch Flower Summit 2023 videos `__" +"`Watch Flower Summit 2023 videos `__" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:9 @@ -19120,9 +20301,10 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:13 msgid "" "In this notebook, we'll build a federated learning system using Flower, " -"`Flower Datasets `__ and PyTorch. In part " -"1, we use PyTorch for the model training pipeline and data loading. In part " -"2, we continue to federate the PyTorch-based pipeline using Flower." +"`Flower Datasets `__ and PyTorch. In " +"part 1, we use PyTorch for the model training pipeline and data loading. " +"In part 2, we continue to federate the PyTorch-based pipeline using " +"Flower." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:17 @@ -19139,19 +20321,20 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:45 msgid "" "Next, we install the necessary packages for PyTorch (``torch`` and " -"``torchvision``), Flower Datasets (``flwr-datasets``) and Flower (``flwr``):" +"``torchvision``), Flower Datasets (``flwr-datasets``) and Flower " +"(``flwr``):" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:105 msgid "" -"It is possible to switch to a runtime that has GPU acceleration enabled (on " -"Google Colab: ``Runtime > Change runtime type > Hardware accelerator: GPU > " -"Save``). Note, however, that Google Colab is not always able to offer GPU " -"acceleration. If you see an error related to GPU availability in one of the " -"following sections, consider switching back to CPU-based execution by " -"setting ``DEVICE = torch.device(\"cpu\")``. If the runtime has GPU " -"acceleration enabled, you should see the output ``Training on cuda``, " -"otherwise it'll say ``Training on cpu``." +"It is possible to switch to a runtime that has GPU acceleration enabled " +"(on Google Colab: ``Runtime > Change runtime type > Hardware accelerator:" +" GPU > Save``). Note, however, that Google Colab is not always able to " +"offer GPU acceleration. If you see an error related to GPU availability " +"in one of the following sections, consider switching back to CPU-based " +"execution by setting ``DEVICE = torch.device(\"cpu\")``. If the runtime " +"has GPU acceleration enabled, you should see the output ``Training on " +"cuda``, otherwise it'll say ``Training on cpu``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:118 @@ -19160,50 +20343,51 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:120 msgid "" -"Federated learning can be applied to many different types of tasks across " -"different domains. In this tutorial, we introduce federated learning by " -"training a simple convolutional neural network (CNN) on the popular CIFAR-10 " -"dataset. CIFAR-10 can be used to train image classifiers that distinguish " -"between images from ten different classes: 'airplane', 'automobile', 'bird', " -"'cat', 'deer', 'dog', 'frog', 'horse', 'ship', and 'truck'." +"Federated learning can be applied to many different types of tasks across" +" different domains. In this tutorial, we introduce federated learning by " +"training a simple convolutional neural network (CNN) on the popular " +"CIFAR-10 dataset. CIFAR-10 can be used to train image classifiers that " +"distinguish between images from ten different classes: 'airplane', " +"'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', and " +"'truck'." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:131 msgid "" "We simulate having multiple datasets from multiple organizations (also " -"called the \"cross-silo\" setting in federated learning) by splitting the " -"original CIFAR-10 dataset into multiple partitions. Each partition will " -"represent the data from a single organization. We're doing this purely for " -"experimentation purposes, in the real world there's no need for data " -"splitting because each organization already has their own data (so the data " -"is naturally partitioned)." +"called the \"cross-silo\" setting in federated learning) by splitting the" +" original CIFAR-10 dataset into multiple partitions. Each partition will " +"represent the data from a single organization. We're doing this purely " +"for experimentation purposes, in the real world there's no need for data " +"splitting because each organization already has their own data (so the " +"data is naturally partitioned)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:133 msgid "" -"Each organization will act as a client in the federated learning system. So " -"having ten organizations participate in a federation means having ten " +"Each organization will act as a client in the federated learning system. " +"So having ten organizations participate in a federation means having ten " "clients connected to the federated learning server." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:144 msgid "" "Let's now create the Federated Dataset abstraction that from ``flwr-" -"datasets`` that partitions the CIFAR-10. We will create small training and " -"test set for each edge device and wrap each of them into a PyTorch " +"datasets`` that partitions the CIFAR-10. We will create small training " +"and test set for each edge device and wrap each of them into a PyTorch " "``DataLoader``:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:198 msgid "" "We now have a list of ten training sets and ten validation sets " -"(``trainloaders`` and ``valloaders``) representing the data of ten different " -"organizations. Each ``trainloader``/``valloader`` pair contains 4000 " -"training examples and 1000 validation examples. There's also a single " -"``testloader`` (we did not split the test set). Again, this is only " -"necessary for building research or educational systems, actual federated " -"learning systems have their data naturally distributed across multiple " -"partitions." +"(``trainloaders`` and ``valloaders``) representing the data of ten " +"different organizations. Each ``trainloader``/``valloader`` pair contains" +" 4000 training examples and 1000 validation examples. There's also a " +"single ``testloader`` (we did not split the test set). Again, this is " +"only necessary for building research or educational systems, actual " +"federated learning systems have their data naturally distributed across " +"multiple partitions." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:201 @@ -19217,8 +20401,8 @@ msgid "" "The output above shows a random batch of images from the first " "``trainloader`` in our list of ten ``trainloaders``. It also prints the " "labels associated with each image (i.e., one of the ten possible labels " -"we've seen above). If you run the cell again, you should see another batch " -"of images." +"we've seen above). If you run the cell again, you should see another " +"batch of images." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:252 @@ -19231,8 +20415,8 @@ msgid "" "network. This introduction assumes basic familiarity with PyTorch, so it " "doesn't cover the PyTorch-related aspects in full detail. If you want to " "dive deeper into PyTorch, we recommend `DEEP LEARNING WITH PYTORCH: A 60 " -"MINUTE BLITZ `__." +"MINUTE BLITZ " +"`__." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:275 @@ -19241,9 +20425,9 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:277 msgid "" -"We use the simple CNN described in the `PyTorch tutorial `__:" +"We use the simple CNN described in the `PyTorch tutorial " +"`__:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:314 @@ -19257,19 +20441,20 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:376 msgid "" "We now have all the basic building blocks we need: a dataset, a model, a " -"training function, and a test function. Let's put them together to train the " -"model on the dataset of one of our organizations (``trainloaders[0]``). This " -"simulates the reality of most machine learning projects today: each " -"organization has their own data and trains models only on this internal data:" +"training function, and a test function. Let's put them together to train " +"the model on the dataset of one of our organizations " +"(``trainloaders[0]``). This simulates the reality of most machine " +"learning projects today: each organization has their own data and trains " +"models only on this internal data:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:406 msgid "" -"Training the simple CNN on our CIFAR-10 split for 5 epochs should result in " -"a test set accuracy of about 41%, which is not good, but at the same time, " -"it doesn't really matter for the purposes of this tutorial. The intent was " -"just to show a simplistic centralized training pipeline that sets the stage " -"for what comes next - federated learning!" +"Training the simple CNN on our CIFAR-10 split for 5 epochs should result " +"in a test set accuracy of about 41%, which is not good, but at the same " +"time, it doesn't really matter for the purposes of this tutorial. The " +"intent was just to show a simplistic centralized training pipeline that " +"sets the stage for what comes next - federated learning!" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:418 @@ -19278,11 +20463,11 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:420 msgid "" -"Step 1 demonstrated a simple centralized training pipeline. All data was in " -"one place (i.e., a single ``trainloader`` and a single ``valloader``). Next, " -"we'll simulate a situation where we have multiple datasets in multiple " -"organizations and where we train a model over these organizations using " -"federated learning." +"Step 1 demonstrated a simple centralized training pipeline. All data was " +"in one place (i.e., a single ``trainloader`` and a single ``valloader``)." +" Next, we'll simulate a situation where we have multiple datasets in " +"multiple organizations and where we train a model over these " +"organizations using federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:432 @@ -19291,29 +20476,30 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:434 msgid "" -"In federated learning, the server sends the global model parameters to the " -"client, and the client updates the local model with the parameters received " -"from the server. It then trains the model on the local data (which changes " -"the model parameters locally) and sends the updated/changed model parameters " -"back to the server (or, alternatively, it sends just the gradients back to " -"the server, not the full model parameters)." +"In federated learning, the server sends the global model parameters to " +"the client, and the client updates the local model with the parameters " +"received from the server. It then trains the model on the local data " +"(which changes the model parameters locally) and sends the " +"updated/changed model parameters back to the server (or, alternatively, " +"it sends just the gradients back to the server, not the full model " +"parameters)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:436 msgid "" "We need two helper functions to update the local model with parameters " -"received from the server and to get the updated model parameters from the " -"local model: ``set_parameters`` and ``get_parameters``. The following two " -"functions do just that for the PyTorch model above." +"received from the server and to get the updated model parameters from the" +" local model: ``set_parameters`` and ``get_parameters``. The following " +"two functions do just that for the PyTorch model above." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:438 msgid "" -"The details of how this works are not really important here (feel free to " -"consult the PyTorch documentation if you want to learn more). In essence, we " -"use ``state_dict`` to access PyTorch model parameter tensors. The parameter " -"tensors are then converted to/from a list of NumPy ndarray's (which Flower " -"knows how to serialize/deserialize):" +"The details of how this works are not really important here (feel free to" +" consult the PyTorch documentation if you want to learn more). In " +"essence, we use ``state_dict`` to access PyTorch model parameter tensors." +" The parameter tensors are then converted to/from a list of NumPy " +"ndarray's (which Flower knows how to serialize/deserialize):" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:466 @@ -19322,18 +20508,19 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:468 msgid "" -"With that out of the way, let's move on to the interesting part. Federated " -"learning systems consist of a server and multiple clients. In Flower, we " -"create clients by implementing subclasses of ``flwr.client.Client`` or " -"``flwr.client.NumPyClient``. We use ``NumPyClient`` in this tutorial because " -"it is easier to implement and requires us to write less boilerplate." +"With that out of the way, let's move on to the interesting part. " +"Federated learning systems consist of a server and multiple clients. In " +"Flower, we create clients by implementing subclasses of " +"``flwr.client.Client`` or ``flwr.client.NumPyClient``. We use " +"``NumPyClient`` in this tutorial because it is easier to implement and " +"requires us to write less boilerplate." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:470 msgid "" -"To implement the Flower client, we create a subclass of ``flwr.client." -"NumPyClient`` and implement the three methods ``get_parameters``, ``fit``, " -"and ``evaluate``:" +"To implement the Flower client, we create a subclass of " +"``flwr.client.NumPyClient`` and implement the three methods " +"``get_parameters``, ``fit``, and ``evaluate``:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:472 @@ -19343,14 +20530,15 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:473 msgid "" "``fit``: Receive model parameters from the server, train the model " -"parameters on the local data, and return the (updated) model parameters to " -"the server" +"parameters on the local data, and return the (updated) model parameters " +"to the server" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:474 msgid "" -"``evaluate``: Receive model parameters from the server, evaluate the model " -"parameters on the local data, and return the evaluation result to the server" +"``evaluate``: Receive model parameters from the server, evaluate the " +"model parameters on the local data, and return the evaluation result to " +"the server" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:476 @@ -19363,15 +20551,16 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:513 msgid "" "Our class ``FlowerClient`` defines how local training/evaluation will be " -"performed and allows Flower to call the local training/evaluation through " -"``fit`` and ``evaluate``. Each instance of ``FlowerClient`` represents a " -"*single client* in our federated learning system. Federated learning systems " -"have multiple clients (otherwise, there's not much to federate), so each " -"client will be represented by its own instance of ``FlowerClient``. If we " -"have, for example, three clients in our workload, then we'd have three " -"instances of ``FlowerClient``. Flower calls ``FlowerClient.fit`` on the " -"respective instance when the server selects a particular client for training " -"(and ``FlowerClient.evaluate`` for evaluation)." +"performed and allows Flower to call the local training/evaluation through" +" ``fit`` and ``evaluate``. Each instance of ``FlowerClient`` represents a" +" *single client* in our federated learning system. Federated learning " +"systems have multiple clients (otherwise, there's not much to federate), " +"so each client will be represented by its own instance of " +"``FlowerClient``. If we have, for example, three clients in our workload," +" then we'd have three instances of ``FlowerClient``. Flower calls " +"``FlowerClient.fit`` on the respective instance when the server selects a" +" particular client for training (and ``FlowerClient.evaluate`` for " +"evaluation)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:517 @@ -19380,13 +20569,13 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:519 msgid "" -"In this notebook, we want to simulate a federated learning system with 10 " -"clients on a single machine. This means that the server and all 10 clients " -"will live on a single machine and share resources such as CPU, GPU, and " -"memory. Having 10 clients would mean having 10 instances of ``FlowerClient`` " -"in memory. Doing this on a single machine can quickly exhaust the available " -"memory resources, even if only a subset of these clients participates in a " -"single round of federated learning." +"In this notebook, we want to simulate a federated learning system with 10" +" clients on a single machine. This means that the server and all 10 " +"clients will live on a single machine and share resources such as CPU, " +"GPU, and memory. Having 10 clients would mean having 10 instances of " +"``FlowerClient`` in memory. Doing this on a single machine can quickly " +"exhaust the available memory resources, even if only a subset of these " +"clients participates in a single round of federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:521 @@ -19395,14 +20584,14 @@ msgid "" "multiple machines, Flower, therefore, provides special simulation " "capabilities that create ``FlowerClient`` instances only when they are " "actually necessary for training or evaluation. To enable the Flower " -"framework to create clients when necessary, we need to implement a function " -"called ``client_fn`` that creates a ``FlowerClient`` instance on demand. " -"Flower calls ``client_fn`` whenever it needs an instance of one particular " -"client to call ``fit`` or ``evaluate`` (those instances are usually " -"discarded after use, so they should not keep any local state). Clients are " -"identified by a client ID, or short ``cid``. The ``cid`` can be used, for " -"example, to load different local data partitions for different clients, as " -"can be seen below:" +"framework to create clients when necessary, we need to implement a " +"function called ``client_fn`` that creates a ``FlowerClient`` instance on" +" demand. Flower calls ``client_fn`` whenever it needs an instance of one " +"particular client to call ``fit`` or ``evaluate`` (those instances are " +"usually discarded after use, so they should not keep any local state). " +"Clients are identified by a client ID, or short ``cid``. The ``cid`` can " +"be used, for example, to load different local data partitions for " +"different clients, as can be seen below:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:556 @@ -19411,31 +20600,31 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:558 msgid "" -"We now have the class ``FlowerClient`` which defines client-side training/" -"evaluation and ``client_fn`` which allows Flower to create ``FlowerClient`` " -"instances whenever it needs to call ``fit`` or ``evaluate`` on one " -"particular client. The last step is to start the actual simulation using " -"``flwr.simulation.start_simulation``." +"We now have the class ``FlowerClient`` which defines client-side " +"training/evaluation and ``client_fn`` which allows Flower to create " +"``FlowerClient`` instances whenever it needs to call ``fit`` or " +"``evaluate`` on one particular client. The last step is to start the " +"actual simulation using ``flwr.simulation.start_simulation``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:560 msgid "" "The function ``start_simulation`` accepts a number of arguments, amongst " -"them the ``client_fn`` used to create ``FlowerClient`` instances, the number " -"of clients to simulate (``num_clients``), the number of federated learning " -"rounds (``num_rounds``), and the strategy. The strategy encapsulates the " -"federated learning approach/algorithm, for example, *Federated Averaging* " -"(FedAvg)." +"them the ``client_fn`` used to create ``FlowerClient`` instances, the " +"number of clients to simulate (``num_clients``), the number of federated " +"learning rounds (``num_rounds``), and the strategy. The strategy " +"encapsulates the federated learning approach/algorithm, for example, " +"*Federated Averaging* (FedAvg)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:562 msgid "" "Flower has a number of built-in strategies, but we can also use our own " -"strategy implementations to customize nearly all aspects of the federated " -"learning approach. For this example, we use the built-in ``FedAvg`` " -"implementation and customize it using a few basic parameters. The last step " -"is the actual call to ``start_simulation`` which - you guessed it - starts " -"the simulation:" +"strategy implementations to customize nearly all aspects of the federated" +" learning approach. For this example, we use the built-in ``FedAvg`` " +"implementation and customize it using a few basic parameters. The last " +"step is the actual call to ``start_simulation`` which - you guessed it - " +"starts the simulation:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:608 @@ -19449,20 +20638,20 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:612 #, python-format msgid "" -"When we call ``start_simulation``, we tell Flower that there are 10 clients " -"(``num_clients=10``). Flower then goes ahead an asks the ``FedAvg`` strategy " -"to select clients. ``FedAvg`` knows that it should select 100% of the " -"available clients (``fraction_fit=1.0``), so it goes ahead and selects 10 " -"random clients (i.e., 100% of 10)." +"When we call ``start_simulation``, we tell Flower that there are 10 " +"clients (``num_clients=10``). Flower then goes ahead an asks the " +"``FedAvg`` strategy to select clients. ``FedAvg`` knows that it should " +"select 100% of the available clients (``fraction_fit=1.0``), so it goes " +"ahead and selects 10 random clients (i.e., 100% of 10)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:614 msgid "" -"Flower then asks the selected 10 clients to train the model. When the server " -"receives the model parameter updates from the clients, it hands those " -"updates over to the strategy (*FedAvg*) for aggregation. The strategy " -"aggregates those updates and returns the new global model, which then gets " -"used in the next round of federated learning." +"Flower then asks the selected 10 clients to train the model. When the " +"server receives the model parameter updates from the clients, it hands " +"those updates over to the strategy (*FedAvg*) for aggregation. The " +"strategy aggregates those updates and returns the new global model, which" +" then gets used in the next round of federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:626 @@ -19471,27 +20660,28 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:628 msgid "" -"You may have noticed that all metrics except for ``losses_distributed`` are " -"empty. Where did the ``{\"accuracy\": float(accuracy)}`` go?" +"You may have noticed that all metrics except for ``losses_distributed`` " +"are empty. Where did the ``{\"accuracy\": float(accuracy)}`` go?" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:630 msgid "" -"Flower can automatically aggregate losses returned by individual clients, " -"but it cannot do the same for metrics in the generic metrics dictionary (the " -"one with the ``accuracy`` key). Metrics dictionaries can contain very " -"different kinds of metrics and even key/value pairs that are not metrics at " -"all, so the framework does not (and can not) know how to handle these " -"automatically." +"Flower can automatically aggregate losses returned by individual clients," +" but it cannot do the same for metrics in the generic metrics dictionary " +"(the one with the ``accuracy`` key). Metrics dictionaries can contain " +"very different kinds of metrics and even key/value pairs that are not " +"metrics at all, so the framework does not (and can not) know how to " +"handle these automatically." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:632 msgid "" -"As users, we need to tell the framework how to handle/aggregate these custom " -"metrics, and we do so by passing metric aggregation functions to the " -"strategy. The strategy will then call these functions whenever it receives " -"fit or evaluate metrics from clients. The two possible functions are " -"``fit_metrics_aggregation_fn`` and ``evaluate_metrics_aggregation_fn``." +"As users, we need to tell the framework how to handle/aggregate these " +"custom metrics, and we do so by passing metric aggregation functions to " +"the strategy. The strategy will then call these functions whenever it " +"receives fit or evaluate metrics from clients. The two possible functions" +" are ``fit_metrics_aggregation_fn`` and " +"``evaluate_metrics_aggregation_fn``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:634 @@ -19509,17 +20699,17 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:697 msgid "" "We now have a full system that performs federated training and federated " -"evaluation. It uses the ``weighted_average`` function to aggregate custom " -"evaluation metrics and calculates a single ``accuracy`` metric across all " -"clients on the server side." +"evaluation. It uses the ``weighted_average`` function to aggregate custom" +" evaluation metrics and calculates a single ``accuracy`` metric across " +"all clients on the server side." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:699 msgid "" "The other two categories of metrics (``losses_centralized`` and " "``metrics_centralized``) are still empty because they only apply when " -"centralized evaluation is being used. Part two of the Flower tutorial will " -"cover centralized evaluation." +"centralized evaluation is being used. Part two of the Flower tutorial " +"will cover centralized evaluation." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:711 @@ -19529,28 +20719,28 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:713 msgid "" -"Congratulations, you just trained a convolutional neural network, federated " -"over 10 clients! With that, you understand the basics of federated learning " -"with Flower. The same approach you've seen can be used with other machine " -"learning frameworks (not just PyTorch) and tasks (not just CIFAR-10 images " -"classification), for example NLP with Hugging Face Transformers or speech " -"with SpeechBrain." +"Congratulations, you just trained a convolutional neural network, " +"federated over 10 clients! With that, you understand the basics of " +"federated learning with Flower. The same approach you've seen can be used" +" with other machine learning frameworks (not just PyTorch) and tasks (not" +" just CIFAR-10 images classification), for example NLP with Hugging Face " +"Transformers or speech with SpeechBrain." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:715 msgid "" -"In the next notebook, we're going to cover some more advanced concepts. Want " -"to customize your strategy? Initialize parameters on the server side? Or " -"evaluate the aggregated model on the server side? We'll cover all this and " -"more in the next tutorial." +"In the next notebook, we're going to cover some more advanced concepts. " +"Want to customize your strategy? Initialize parameters on the server " +"side? Or evaluate the aggregated model on the server side? We'll cover " +"all this and more in the next tutorial." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:733 msgid "" -"The `Flower Federated Learning Tutorial - Part 2 `__ goes " -"into more depth about strategies and all the advanced things you can build " -"with them." +"The `Flower Federated Learning Tutorial - Part 2 " +"`__ goes into more depth about strategies and all " +"the advanced things you can build with them." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:9 @@ -19560,16 +20750,16 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:11 msgid "" "Welcome to the next part of the federated learning tutorial. In previous " -"parts of this tutorial, we introduced federated learning with PyTorch and " -"Flower (`part 1 `__)." +"parts of this tutorial, we introduced federated learning with PyTorch and" +" Flower (`part 1 `__)." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:13 msgid "" -"In this notebook, we'll begin to customize the federated learning system we " -"built in the introductory notebook (again, using `Flower `__ and `PyTorch `__)." +"In this notebook, we'll begin to customize the federated learning system " +"we built in the introductory notebook (again, using `Flower " +"`__ and `PyTorch `__)." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:17 @@ -19583,8 +20773,8 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:311 msgid "" "So far, everything should look familiar if you've worked through the " -"introductory notebook. With that, we're ready to introduce a number of new " -"features." +"introductory notebook. With that, we're ready to introduce a number of " +"new features." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:323 @@ -19593,16 +20783,16 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:325 msgid "" -"Flower, by default, initializes the global model by asking one random client " -"for the initial parameters. In many cases, we want more control over " -"parameter initialization though. Flower therefore allows you to directly " -"pass the initial parameters to the Strategy:" +"Flower, by default, initializes the global model by asking one random " +"client for the initial parameters. In many cases, we want more control " +"over parameter initialization though. Flower therefore allows you to " +"directly pass the initial parameters to the Strategy:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:370 msgid "" -"Passing ``initial_parameters`` to the ``FedAvg`` strategy prevents Flower " -"from asking one of the clients for the initial parameters. If we look " +"Passing ``initial_parameters`` to the ``FedAvg`` strategy prevents Flower" +" from asking one of the clients for the initial parameters. If we look " "closely, we can see that the logs do not show any calls to the " "``FlowerClient.get_parameters`` method." msgstr "" @@ -19613,17 +20803,17 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:384 msgid "" -"We've seen the function ``start_simulation`` before. It accepts a number of " -"arguments, amongst them the ``client_fn`` used to create ``FlowerClient`` " -"instances, the number of clients to simulate ``num_clients``, the number of " -"rounds ``num_rounds``, and the strategy." +"We've seen the function ``start_simulation`` before. It accepts a number " +"of arguments, amongst them the ``client_fn`` used to create " +"``FlowerClient`` instances, the number of clients to simulate " +"``num_clients``, the number of rounds ``num_rounds``, and the strategy." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:386 msgid "" "The strategy encapsulates the federated learning approach/algorithm, for " -"example, ``FedAvg`` or ``FedAdagrad``. Let's try to use a different strategy " -"this time:" +"example, ``FedAvg`` or ``FedAdagrad``. Let's try to use a different " +"strategy this time:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:424 @@ -19632,9 +20822,9 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:426 msgid "" -"Flower can evaluate the aggregated model on the server-side or on the client-" -"side. Client-side and server-side evaluation are similar in some ways, but " -"different in others." +"Flower can evaluate the aggregated model on the server-side or on the " +"client-side. Client-side and server-side evaluation are similar in some " +"ways, but different in others." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:428 @@ -19642,33 +20832,33 @@ msgid "" "**Centralized Evaluation** (or *server-side evaluation*) is conceptually " "simple: it works the same way that evaluation in centralized machine " "learning does. If there is a server-side dataset that can be used for " -"evaluation purposes, then that's great. We can evaluate the newly aggregated " -"model after each round of training without having to send the model to " -"clients. We're also fortunate in the sense that our entire evaluation " -"dataset is available at all times." +"evaluation purposes, then that's great. We can evaluate the newly " +"aggregated model after each round of training without having to send the " +"model to clients. We're also fortunate in the sense that our entire " +"evaluation dataset is available at all times." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:430 msgid "" -"**Federated Evaluation** (or *client-side evaluation*) is more complex, but " -"also more powerful: it doesn't require a centralized dataset and allows us " -"to evaluate models over a larger set of data, which often yields more " -"realistic evaluation results. In fact, many scenarios require us to use " -"**Federated Evaluation** if we want to get representative evaluation results " -"at all. But this power comes at a cost: once we start to evaluate on the " -"client side, we should be aware that our evaluation dataset can change over " -"consecutive rounds of learning if those clients are not always available. " -"Moreover, the dataset held by each client can also change over consecutive " -"rounds. This can lead to evaluation results that are not stable, so even if " -"we would not change the model, we'd see our evaluation results fluctuate " -"over consecutive rounds." +"**Federated Evaluation** (or *client-side evaluation*) is more complex, " +"but also more powerful: it doesn't require a centralized dataset and " +"allows us to evaluate models over a larger set of data, which often " +"yields more realistic evaluation results. In fact, many scenarios require" +" us to use **Federated Evaluation** if we want to get representative " +"evaluation results at all. But this power comes at a cost: once we start " +"to evaluate on the client side, we should be aware that our evaluation " +"dataset can change over consecutive rounds of learning if those clients " +"are not always available. Moreover, the dataset held by each client can " +"also change over consecutive rounds. This can lead to evaluation results " +"that are not stable, so even if we would not change the model, we'd see " +"our evaluation results fluctuate over consecutive rounds." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:433 msgid "" "We've seen how federated evaluation works on the client side (i.e., by " -"implementing the ``evaluate`` method in ``FlowerClient``). Now let's see how " -"we can evaluate aggregated model parameters on the server-side:" +"implementing the ``evaluate`` method in ``FlowerClient``). Now let's see " +"how we can evaluate aggregated model parameters on the server-side:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:490 @@ -19677,48 +20867,50 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:492 msgid "" -"In some situations, we want to configure client-side execution (training, " -"evaluation) from the server-side. One example for that is the server asking " -"the clients to train for a certain number of local epochs. Flower provides a " -"way to send configuration values from the server to the clients using a " -"dictionary. Let's look at an example where the clients receive values from " -"the server through the ``config`` parameter in ``fit`` (``config`` is also " -"available in ``evaluate``). The ``fit`` method receives the configuration " -"dictionary through the ``config`` parameter and can then read values from " -"this dictionary. In this example, it reads ``server_round`` and " -"``local_epochs`` and uses those values to improve the logging and configure " -"the number of local training epochs:" +"In some situations, we want to configure client-side execution (training," +" evaluation) from the server-side. One example for that is the server " +"asking the clients to train for a certain number of local epochs. Flower " +"provides a way to send configuration values from the server to the " +"clients using a dictionary. Let's look at an example where the clients " +"receive values from the server through the ``config`` parameter in " +"``fit`` (``config`` is also available in ``evaluate``). The ``fit`` " +"method receives the configuration dictionary through the ``config`` " +"parameter and can then read values from this dictionary. In this example," +" it reads ``server_round`` and ``local_epochs`` and uses those values to " +"improve the logging and configure the number of local training epochs:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:546 msgid "" -"So how can we send this config dictionary from server to clients? The built-" -"in Flower Strategies provide way to do this, and it works similarly to the " -"way server-side evaluation works. We provide a function to the strategy, and " -"the strategy calls this function for every round of federated learning:" +"So how can we send this config dictionary from server to clients? The " +"built-in Flower Strategies provide way to do this, and it works similarly" +" to the way server-side evaluation works. We provide a function to the " +"strategy, and the strategy calls this function for every round of " +"federated learning:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:576 msgid "" -"Next, we'll just pass this function to the FedAvg strategy before starting " -"the simulation:" +"Next, we'll just pass this function to the FedAvg strategy before " +"starting the simulation:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:613 msgid "" -"As we can see, the client logs now include the current round of federated " -"learning (which they read from the ``config`` dictionary). We can also " -"configure local training to run for one epoch during the first and second " -"round of federated learning, and then for two epochs during the third round." +"As we can see, the client logs now include the current round of federated" +" learning (which they read from the ``config`` dictionary). We can also " +"configure local training to run for one epoch during the first and second" +" round of federated learning, and then for two epochs during the third " +"round." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:615 msgid "" "Clients can also return arbitrary values to the server. To do so, they " -"return a dictionary from ``fit`` and/or ``evaluate``. We have seen and used " -"this concept throughout this notebook without mentioning it explicitly: our " -"``FlowerClient`` returns a dictionary containing a custom key/value pair as " -"the third return value in ``evaluate``." +"return a dictionary from ``fit`` and/or ``evaluate``. We have seen and " +"used this concept throughout this notebook without mentioning it " +"explicitly: our ``FlowerClient`` returns a dictionary containing a custom" +" key/value pair as the third return value in ``evaluate``." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:627 @@ -19735,13 +20927,14 @@ msgstr "" #, python-format msgid "" "We now have 1000 partitions, each holding 45 training and 5 validation " -"examples. Given that the number of training examples on each client is quite " -"small, we should probably train the model a bit longer, so we configure the " -"clients to perform 3 local training epochs. We should also adjust the " -"fraction of clients selected for training during each round (we don't want " -"all 1000 clients participating in every round), so we adjust " -"``fraction_fit`` to ``0.05``, which means that only 5% of available clients " -"(so 50 clients) will be selected for training each round:" +"examples. Given that the number of training examples on each client is " +"quite small, we should probably train the model a bit longer, so we " +"configure the clients to perform 3 local training epochs. We should also " +"adjust the fraction of clients selected for training during each round " +"(we don't want all 1000 clients participating in every round), so we " +"adjust ``fraction_fit`` to ``0.05``, which means that only 5% of " +"available clients (so 50 clients) will be selected for training each " +"round:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:699 @@ -19754,18 +20947,19 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:701 msgid "" -"In the later sections, we've seen how we can communicate arbitrary values " -"between server and clients to fully customize client-side execution. With " -"that capability, we built a large-scale Federated Learning simulation using " -"the Flower Virtual Client Engine and ran an experiment involving 1000 " -"clients in the same workload - all in a Jupyter Notebook!" +"In the later sections, we've seen how we can communicate arbitrary values" +" between server and clients to fully customize client-side execution. " +"With that capability, we built a large-scale Federated Learning " +"simulation using the Flower Virtual Client Engine and ran an experiment " +"involving 1000 clients in the same workload - all in a Jupyter Notebook!" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:719 msgid "" -"The `Flower Federated Learning Tutorial - Part 3 `__ shows how " -"to build a fully custom ``Strategy`` from scratch." +"The `Flower Federated Learning Tutorial - Part 3 " +"`__ shows how to build a fully custom ``Strategy`` from " +"scratch." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:9 @@ -19776,24 +20970,24 @@ msgstr "" msgid "" "In this tutorial, you will learn what federated learning is, build your " "first system in Flower, and gradually extend it. If you work through all " -"parts of the tutorial, you will be able to build advanced federated learning " -"systems that approach the current state of the art in the field." +"parts of the tutorial, you will be able to build advanced federated " +"learning systems that approach the current state of the art in the field." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:15 msgid "" -"🧑‍🏫 This tutorial starts at zero and expects no familiarity with federated " -"learning. Only a basic understanding of data science and Python programming " -"is assumed." +"🧑‍🏫 This tutorial starts at zero and expects no familiarity with " +"federated learning. Only a basic understanding of data science and Python" +" programming is assumed." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:17 msgid "" -"`Star Flower on GitHub `__ ⭐️ and join the " -"open-source Flower community on Slack to connect, ask questions, and get " -"help: `Join Slack `__ 🌼 We'd love to hear " -"from you in the ``#introductions`` channel! And if anything is unclear, head " -"over to the ``#questions`` channel." +"`Star Flower on GitHub `__ ⭐️ and join " +"the open-source Flower community on Slack to connect, ask questions, and " +"get help: `Join Slack `__ 🌼 We'd love to " +"hear from you in the ``#introductions`` channel! And if anything is " +"unclear, head over to the ``#questions`` channel." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:31 @@ -19802,19 +20996,19 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:33 msgid "" -"Before we begin to discuss federated learning, let us quickly recap how most " -"machine learning works today." +"Before we begin to discuss federated learning, let us quickly recap how " +"most machine learning works today." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:35 msgid "" -"In machine learning, we have a model, and we have data. The model could be a " -"neural network (as depicted here), or something else, like classical linear " -"regression." +"In machine learning, we have a model, and we have data. The model could " +"be a neural network (as depicted here), or something else, like classical" +" linear regression." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" +msgid "|93b02017c78049bbbd5ae456dcb2c91b|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 @@ -19823,13 +21017,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:47 msgid "" -"We train the model using the data to perform a useful task. A task could be " -"to detect objects in images, transcribe an audio recording, or play a game " -"like Go." +"We train the model using the data to perform a useful task. A task could " +"be to detect objects in images, transcribe an audio recording, or play a " +"game like Go." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -msgid "|5aa1711387d74d0f8b9c499e1a51627e|" +msgid "|01471150fd5144c080a176b43e92a3ff|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 @@ -19838,8 +21032,8 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:59 msgid "" -"Now, in practice, the training data we work with doesn't originate on the " -"machine we train the model on. It gets created somewhere else." +"Now, in practice, the training data we work with doesn't originate on the" +" machine we train the model on. It gets created somewhere else." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:61 @@ -19850,7 +21044,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -msgid "|2bc8e069228d4873804061ff4a95048c|" +msgid "|9bc21c7dbd17444a8f070c60786e3484|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 @@ -19860,13 +21054,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:73 msgid "" "What's also important to mention, this \"somewhere else\" is usually not " -"just one place, it's many places. It could be several devices all running " -"the same app. But it could also be several organizations, all generating " -"data for the same task." +"just one place, it's many places. It could be several devices all running" +" the same app. But it could also be several organizations, all generating" +" data for the same task." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -msgid "|c258488766324dc9a6807f0e7c4fd5f4|" +msgid "|3047bbce54b34099ae559963d0420d79|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 @@ -19875,13 +21069,14 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:85 msgid "" -"So to use machine learning, or any kind of data analysis, the approach that " -"has been used in the past was to collect all data on a central server. This " -"server can be somewhere in a data center, or somewhere in the cloud." +"So to use machine learning, or any kind of data analysis, the approach " +"that has been used in the past was to collect all data on a central " +"server. This server can be somewhere in a data center, or somewhere in " +"the cloud." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -msgid "|d5f962c3f4ec48529efda980868c14b0|" +msgid "|e9f8ce948593444fb838d2f354c7ec5d|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 @@ -19896,7 +21091,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -msgid "|a5eccea18d4c43a68b54b65043cabef8|" +msgid "|c24c1478b30e4f74839208628a842d1e|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 @@ -19909,14 +21104,14 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:132 msgid "" -"The classic machine learning approach we've just seen can be used in some " -"cases. Great examples include categorizing holiday photos, or analyzing web " -"traffic. Cases, where all the data is naturally available on a centralized " -"server." +"The classic machine learning approach we've just seen can be used in some" +" cases. Great examples include categorizing holiday photos, or analyzing " +"web traffic. Cases, where all the data is naturally available on a " +"centralized server." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" +msgid "|1b3613d7a58847b59e1d3180802dbc09|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 @@ -19925,13 +21120,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:144 msgid "" -"But the approach can not be used in many other cases. Cases, where the data " -"is not available on a centralized server, or cases where the data available " -"on one server is not enough to train a good model." +"But the approach can not be used in many other cases. Cases, where the " +"data is not available on a centralized server, or cases where the data " +"available on one server is not enough to train a good model." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -msgid "|241fc906441a4f038c625a19d30d01b2|" +msgid "|9980b5213db547d0b8024a50992b9e3f|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 @@ -19940,9 +21135,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:156 msgid "" -"There are many reasons why the classic centralized machine learning approach " -"does not work for a large number of highly important real-world use cases. " -"Those reasons include:" +"There are many reasons why the classic centralized machine learning " +"approach does not work for a large number of highly important real-world " +"use cases. Those reasons include:" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:158 @@ -19952,32 +21147,32 @@ msgid "" "(Russia), CDPR (China), PDPB (India), PIPA (Korea), APPI (Japan), PDP " "(Indonesia), PDPA (Singapore), APP (Australia), and other regulations " "protect sensitive data from being moved. In fact, those regulations " -"sometimes even prevent single organizations from combining their own users' " -"data for artificial intelligence training because those users live in " -"different parts of the world, and their data is governed by different data " -"protection regulations." +"sometimes even prevent single organizations from combining their own " +"users' data for artificial intelligence training because those users live" +" in different parts of the world, and their data is governed by different" +" data protection regulations." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:160 msgid "" -"**User preference**: In addition to regulation, there are use cases where " -"users just expect that no data leaves their device, ever. If you type your " -"passwords and credit card info into the digital keyboard of your phone, you " -"don't expect those passwords to end up on the server of the company that " -"developed that keyboard, do you? In fact, that use case was the reason " -"federated learning was invented in the first place." +"**User preference**: In addition to regulation, there are use cases where" +" users just expect that no data leaves their device, ever. If you type " +"your passwords and credit card info into the digital keyboard of your " +"phone, you don't expect those passwords to end up on the server of the " +"company that developed that keyboard, do you? In fact, that use case was " +"the reason federated learning was invented in the first place." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:161 msgid "" -"**Data volume**: Some sensors, like cameras, produce such a high data volume " -"that it is neither feasible nor economic to collect all the data (due to, " -"for example, bandwidth or communication efficiency). Think about a national " -"rail service with hundreds of train stations across the country. If each of " -"these train stations is outfitted with a number of security cameras, the " -"volume of raw on-device data they produce requires incredibly powerful and " -"exceedingly expensive infrastructure to process and store. And most of the " -"data isn't even useful." +"**Data volume**: Some sensors, like cameras, produce such a high data " +"volume that it is neither feasible nor economic to collect all the data " +"(due to, for example, bandwidth or communication efficiency). Think about" +" a national rail service with hundreds of train stations across the " +"country. If each of these train stations is outfitted with a number of " +"security cameras, the volume of raw on-device data they produce requires " +"incredibly powerful and exceedingly expensive infrastructure to process " +"and store. And most of the data isn't even useful." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:164 @@ -19992,7 +21187,8 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:167 msgid "" -"Financial information from different organizations to detect financial fraud" +"Financial information from different organizations to detect financial " +"fraud" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:168 @@ -20005,13 +21201,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:171 msgid "" -"The popularity of privacy-enhancing systems like the `Brave `__ browser or the `Signal `__ messenger shows " -"that users care about privacy. In fact, they choose the privacy-enhancing " -"version over other alternatives, if such an alternative exists. But what can " -"we do to apply machine learning and data science to these cases to utilize " -"private data? After all, these are all areas that would benefit " -"significantly from recent advances in AI." +"The popularity of privacy-enhancing systems like the `Brave " +"`__ browser or the `Signal `__ " +"messenger shows that users care about privacy. In fact, they choose the " +"privacy-enhancing version over other alternatives, if such an alternative" +" exists. But what can we do to apply machine learning and data science to" +" these cases to utilize private data? After all, these are all areas that" +" would benefit significantly from recent advances in AI." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:186 @@ -20021,8 +21217,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:188 msgid "" "Federated learning simply reverses this approach. It enables machine " -"learning on distributed data by moving the training to the data, instead of " -"moving the data to the training. Here's the single-sentence explanation:" +"learning on distributed data by moving the training to the data, instead " +"of moving the data to the training. Here's the single-sentence " +"explanation:" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:190 @@ -20035,22 +21232,22 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:193 msgid "" -"By doing so, it enables us to use machine learning (and other data science " -"approaches) in areas where it wasn't possible before. We can now train " -"excellent medical AI models by enabling different hospitals to work " -"together. We can solve financial fraud by training AI models on the data of " -"different financial institutions. We can build novel privacy-enhancing " -"applications (such as secure messaging) that have better built-in AI than " -"their non-privacy-enhancing alternatives. And those are just a few of the " -"examples that come to mind. As we deploy federated learning, we discover " -"more and more areas that can suddenly be reinvented because they now have " -"access to vast amounts of previously inaccessible data." +"By doing so, it enables us to use machine learning (and other data " +"science approaches) in areas where it wasn't possible before. We can now " +"train excellent medical AI models by enabling different hospitals to work" +" together. We can solve financial fraud by training AI models on the data" +" of different financial institutions. We can build novel privacy-" +"enhancing applications (such as secure messaging) that have better built-" +"in AI than their non-privacy-enhancing alternatives. And those are just a" +" few of the examples that come to mind. As we deploy federated learning, " +"we discover more and more areas that can suddenly be reinvented because " +"they now have access to vast amounts of previously inaccessible data." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:196 msgid "" -"So how does federated learning work, exactly? Let's start with an intuitive " -"explanation." +"So how does federated learning work, exactly? Let's start with an " +"intuitive explanation." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:199 @@ -20063,13 +21260,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:204 msgid "" -"We start by initializing the model on the server. This is exactly the same " -"in classic centralized learning: we initialize the model parameters, either " -"randomly or from a previously saved checkpoint." +"We start by initializing the model on the server. This is exactly the " +"same in classic centralized learning: we initialize the model parameters," +" either randomly or from a previously saved checkpoint." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -msgid "|0aa5aa05810b44b6a835cecce28f3137|" +msgid "|c7afb4c92d154bfaa5e8cb9a150e17f1|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 @@ -20078,22 +21275,22 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:217 msgid "" -"Step 1: Send model to a number of connected organizations/devices (client " -"nodes)" +"Step 1: Send model to a number of connected organizations/devices (client" +" nodes)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:219 msgid "" "Next, we send the parameters of the global model to the connected client " "nodes (think: edge devices like smartphones or servers belonging to " -"organizations). This is to ensure that each participating node starts their " -"local training using the same model parameters. We often use only a few of " -"the connected nodes instead of all nodes. The reason for this is that " -"selecting more and more client nodes has diminishing returns." +"organizations). This is to ensure that each participating node starts " +"their local training using the same model parameters. We often use only a" +" few of the connected nodes instead of all nodes. The reason for this is " +"that selecting more and more client nodes has diminishing returns." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -msgid "|c742940dd4bf4de09d8d0d5e8d179638|" +msgid "|032eb6fed6924ac387b9f13854919196|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 @@ -20102,22 +21299,22 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:232 msgid "" -"Step 2: Train model locally on the data of each organization/device (client " -"node)" +"Step 2: Train model locally on the data of each organization/device " +"(client node)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:234 msgid "" -"Now that all (selected) client nodes have the latest version of the global " -"model parameters, they start the local training. They use their own local " -"dataset to train their own local model. They don't train the model until " -"full convergence, but they only train for a little while. This could be as " -"little as one epoch on the local data, or even just a few steps (mini-" -"batches)." +"Now that all (selected) client nodes have the latest version of the " +"global model parameters, they start the local training. They use their " +"own local dataset to train their own local model. They don't train the " +"model until full convergence, but they only train for a little while. " +"This could be as little as one epoch on the local data, or even just a " +"few steps (mini-batches)." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -msgid "|1f169ab4601a47e1a226f1628f4ebddb|" +msgid "|fbf225add7fd4df5a9bf25a95597d954|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 @@ -20130,16 +21327,17 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:249 msgid "" -"After local training, each client node has a slightly different version of " -"the model parameters they originally received. The parameters are all " +"After local training, each client node has a slightly different version " +"of the model parameters they originally received. The parameters are all " "different because each client node has different examples in its local " -"dataset. The client nodes then send those model updates back to the server. " -"The model updates they send can either be the full model parameters or just " -"the gradients that were accumulated during local training." +"dataset. The client nodes then send those model updates back to the " +"server. The model updates they send can either be the full model " +"parameters or just the gradients that were accumulated during local " +"training." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" +msgid "|7efbe3d29d8349b89594e8947e910525|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 @@ -20153,30 +21351,31 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:264 msgid "" "The server receives model updates from the selected client nodes. If it " -"selected 100 client nodes, it now has 100 slightly different versions of the " -"original global model, each trained on the local data of one client. But " -"didn't we want to have one model that contains the learnings from the data " -"of all 100 client nodes?" +"selected 100 client nodes, it now has 100 slightly different versions of " +"the original global model, each trained on the local data of one client. " +"But didn't we want to have one model that contains the learnings from the" +" data of all 100 client nodes?" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:266 msgid "" -"In order to get one single model, we have to combine all the model updates " -"we received from the client nodes. This process is called *aggregation*, and " -"there are many different ways to do it. The most basic way to do it is " -"called *Federated Averaging* (`McMahan et al., 2016 `__), often abbreviated as *FedAvg*. *FedAvg* takes the 100 " -"model updates and, as the name suggests, averages them. To be more precise, " -"it takes the *weighted average* of the model updates, weighted by the number " -"of examples each client used for training. The weighting is important to " -"make sure that each data example has the same \"influence\" on the resulting " -"global model. If one client has 10 examples, and another client has 100 " -"examples, then - without weighting - each of the 10 examples would influence " -"the global model ten times as much as each of the 100 examples." +"In order to get one single model, we have to combine all the model " +"updates we received from the client nodes. This process is called " +"*aggregation*, and there are many different ways to do it. The most basic" +" way to do it is called *Federated Averaging* (`McMahan et al., 2016 " +"`__), often abbreviated as *FedAvg*. " +"*FedAvg* takes the 100 model updates and, as the name suggests, averages " +"them. To be more precise, it takes the *weighted average* of the model " +"updates, weighted by the number of examples each client used for " +"training. The weighting is important to make sure that each data example " +"has the same \"influence\" on the resulting global model. If one client " +"has 10 examples, and another client has 100 examples, then - without " +"weighting - each of the 10 examples would influence the global model ten " +"times as much as each of the 100 examples." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -msgid "|72939caf6e294b0986fee6dde96614d7|" +msgid "|329fb3c04c744eda83bb51fa444c2266|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 @@ -20190,39 +21389,41 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:282 msgid "" "Steps 1 to 4 are what we call a single round of federated learning. The " -"global model parameters get sent to the participating client nodes (step 1), " -"the client nodes train on their local data (step 2), they send their updated " -"models to the server (step 3), and the server then aggregates the model " -"updates to get a new version of the global model (step 4)." +"global model parameters get sent to the participating client nodes (step " +"1), the client nodes train on their local data (step 2), they send their " +"updated models to the server (step 3), and the server then aggregates the" +" model updates to get a new version of the global model (step 4)." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:284 msgid "" -"During a single round, each client node that participates in that iteration " -"only trains for a little while. This means that after the aggregation step " -"(step 4), we have a model that has been trained on all the data of all " -"participating client nodes, but only for a little while. We then have to " -"repeat this training process over and over again to eventually arrive at a " -"fully trained model that performs well across the data of all client nodes." +"During a single round, each client node that participates in that " +"iteration only trains for a little while. This means that after the " +"aggregation step (step 4), we have a model that has been trained on all " +"the data of all participating client nodes, but only for a little while. " +"We then have to repeat this training process over and over again to " +"eventually arrive at a fully trained model that performs well across the " +"data of all client nodes." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:289 msgid "" "Congratulations, you now understand the basics of federated learning. " -"There's a lot more to discuss, of course, but that was federated learning in " -"a nutshell. In later parts of this tutorial, we will go into more detail. " -"Interesting questions include: How can we select the best client nodes that " -"should participate in the next round? What's the best way to aggregate model " -"updates? How can we handle failing client nodes (stragglers)?" +"There's a lot more to discuss, of course, but that was federated learning" +" in a nutshell. In later parts of this tutorial, we will go into more " +"detail. Interesting questions include: How can we select the best client " +"nodes that should participate in the next round? What's the best way to " +"aggregate model updates? How can we handle failing client nodes " +"(stragglers)?" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:294 msgid "" -"Just like we can train a model on the decentralized data of different client " -"nodes, we can also evaluate the model on that data to receive valuable " -"metrics. This is called federated evaluation, sometimes abbreviated as FE. " -"In fact, federated evaluation is an integral part of most federated learning " -"systems." +"Just like we can train a model on the decentralized data of different " +"client nodes, we can also evaluate the model on that data to receive " +"valuable metrics. This is called federated evaluation, sometimes " +"abbreviated as FE. In fact, federated evaluation is an integral part of " +"most federated learning systems." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:297 @@ -20231,24 +21432,25 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:299 msgid "" -"In many cases, machine learning isn't necessary to derive value from data. " -"Data analysis can yield valuable insights, but again, there's often not " -"enough data to get a clear answer. What's the average age at which people " -"develop a certain type of health condition? Federated analytics enables such " -"queries over multiple client nodes. It is usually used in conjunction with " -"other privacy-enhancing technologies like secure aggregation to prevent the " -"server from seeing the results submitted by individual client nodes." +"In many cases, machine learning isn't necessary to derive value from " +"data. Data analysis can yield valuable insights, but again, there's often" +" not enough data to get a clear answer. What's the average age at which " +"people develop a certain type of health condition? Federated analytics " +"enables such queries over multiple client nodes. It is usually used in " +"conjunction with other privacy-enhancing technologies like secure " +"aggregation to prevent the server from seeing the results submitted by " +"individual client nodes." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:305 msgid "" "Differential privacy (DP) is often mentioned in the context of Federated " -"Learning. It is a privacy-preserving method used when analyzing and sharing " -"statistical data, ensuring the privacy of individual participants. DP " -"achieves this by adding statistical noise to the model updates, ensuring any " -"individual participants’ information cannot be distinguished or re-" -"identified. This technique can be considered an optimization that provides a " -"quantifiable privacy protection measure." +"Learning. It is a privacy-preserving method used when analyzing and " +"sharing statistical data, ensuring the privacy of individual " +"participants. DP achieves this by adding statistical noise to the model " +"updates, ensuring any individual participants’ information cannot be " +"distinguished or re-identified. This technique can be considered an " +"optimization that provides a quantifiable privacy protection measure." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:326 @@ -20257,40 +21459,360 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:328 msgid "" -"Federated learning, federated evaluation, and federated analytics require " -"infrastructure to move machine learning models back and forth, train and " -"evaluate them on local data, and then aggregate the updated models. Flower " -"provides the infrastructure to do exactly that in an easy, scalable, and " -"secure way. In short, Flower presents a unified approach to federated " -"learning, analytics, and evaluation. It allows the user to federate any " -"workload, any ML framework, and any programming language." +"Federated learning, federated evaluation, and federated analytics require" +" infrastructure to move machine learning models back and forth, train and" +" evaluate them on local data, and then aggregate the updated models. " +"Flower provides the infrastructure to do exactly that in an easy, " +"scalable, and secure way. In short, Flower presents a unified approach to" +" federated learning, analytics, and evaluation. It allows the user to " +"federate any workload, any ML framework, and any programming language." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +msgid "|c00bf2750bc24d229737a0fe1395f0fc|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 msgid "" -"Flower federated learning server and client nodes (car, scooter, personal " -"computer, roomba, and phone)" +"Flower federated learning server and client nodes (car, scooter, personal" +" computer, roomba, and phone)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:353 msgid "" -"Congratulations, you just learned the basics of federated learning and how " -"it relates to the classic (centralized) machine learning!" +"Congratulations, you just learned the basics of federated learning and " +"how it relates to the classic (centralized) machine learning!" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:355 msgid "" -"In the next part of this tutorial, we are going to build a first federated " -"learning system with Flower." +"In the next part of this tutorial, we are going to build a first " +"federated learning system with Flower." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:373 msgid "" -"The `Flower Federated Learning Tutorial - Part 1 `__ shows how to " -"build a simple federated learning system with PyTorch and Flower." -msgstr "" +"The `Flower Federated Learning Tutorial - Part 1 " +"`__ shows how to build a simple federated learning system " +"with PyTorch and Flower." +msgstr "" + +#~ msgid "" +#~ "Currently, Flower provides two images, a" +#~ " ``base`` image and a ``superlink`` " +#~ "image. The base image, as the name" +#~ " suggests, contains basic dependencies that" +#~ " the SuperLink needs. This includes " +#~ "system dependencies, Python and Python " +#~ "tools. The SuperLink image is based " +#~ "on the base image, but it " +#~ "additionally installs the SuperLink using " +#~ "``pip``." +#~ msgstr "" +#~ "현재, Flower는 \"base\" 이미지 그리고 " +#~ "\"superlink\" 이미지를 제공합니다. base 이미지는 이름에서" +#~ " 알 수 있듯이 SuperLink가 필요로 하는 기본" +#~ " dependencies를 포함하고 있습니다. 여기에는 시스템 " +#~ "dependencies, Python 및 Python 도구가 포함됩니다." +#~ " SuperLink 이미지는 base 이미지를 기반으로 하지만" +#~ " \"pip\"을 사용하여 SuperLink를 추가로 설치합니다." + +#~ msgid "``3.11``" +#~ msgstr "``3.11``" + +#~ msgid "Defaults to ``22.04``." +#~ msgstr "``22.04``이 기본값." + +#~ msgid "Defaults to ``flwr/base``." +#~ msgstr "``flwr/base``이 기본값." + +#~ msgid "The Python version of the base image." +#~ msgstr "base 이미지의 Python 버전." + +#~ msgid "Defaults to ``py3.11``." +#~ msgstr "``py3.11``이 기본값." + +#~ msgid "Defaults to ``ubuntu22.04``." +#~ msgstr "``ubuntu22.04``이 기본값." + +#~ msgid "Defaults to ``flwr``." +#~ msgstr "``flwr``이 기본값." + +#~ msgid "" +#~ "The name of image is ``flwr_superlink``" +#~ " and the tag ``0.1.0``. Remember that" +#~ " the build arguments as well as " +#~ "the name and tag can be adapted" +#~ " to your needs. These values serve" +#~ " as examples only." +#~ msgstr "" +#~ "이미지의 이름은 ``flwr_superlink``이고 태그는 " +#~ "``0.1.0``입니다. 필요에 따라 빌드 argument들 뿐만 " +#~ "아니라 이름과 태그도 정할 수 있습니다. 이 값들은" +#~ " 예시일 뿐입니다." + +#~ msgid "Creating New Messages" +#~ msgstr "" + +#~ msgid "" +#~ "This is a simple guide for " +#~ "creating a new type of message " +#~ "between the server and clients in " +#~ "Flower." +#~ msgstr "" + +#~ msgid "" +#~ "Let's suppose we have the following " +#~ "example functions in :code:`server.py` and " +#~ ":code:`numpy_client.py`..." +#~ msgstr "" + +#~ msgid "Server's side:" +#~ msgstr "" + +#~ msgid "Client's side:" +#~ msgstr "" + +#~ msgid "" +#~ "Let's now see what we need to " +#~ "implement in order to get this " +#~ "simple function between the server and" +#~ " client to work!" +#~ msgstr "" + +#~ msgid "Message Types for Protocol Buffers" +#~ msgstr "" + +#~ msgid "" +#~ "The first thing we need to do " +#~ "is to define a message type for" +#~ " the RPC system in :code:`transport.proto`." +#~ " Note that we have to do it " +#~ "for both the request and response " +#~ "messages. For more details on the " +#~ "syntax of proto3, please see the " +#~ "`official documentation `_." +#~ msgstr "" + +#~ msgid "Within the :code:`ServerMessage` block:" +#~ msgstr "" + +#~ msgid "Within the ClientMessage block:" +#~ msgstr "" + +#~ msgid "" +#~ "Make sure to also add a field " +#~ "of the newly created message type " +#~ "in :code:`oneof msg`." +#~ msgstr "" + +#~ msgid "Once that is done, we will compile the file with:" +#~ msgstr "" + +#~ msgid "If it compiles successfully, you should see the following message:" +#~ msgstr "" + +#~ msgid "Serialization and Deserialization Functions" +#~ msgstr "" + +#~ msgid "" +#~ "Our next step is to add functions" +#~ " to serialize and deserialize Python " +#~ "datatypes to or from our defined " +#~ "RPC message types. You should add " +#~ "these functions in :code:`serde.py`." +#~ msgstr "" + +#~ msgid "The four functions:" +#~ msgstr "" + +#~ msgid "Sending the Message from the Server" +#~ msgstr "" + +#~ msgid "" +#~ "Now write the request function in " +#~ "your Client Proxy class (e.g., " +#~ ":code:`grpc_client_proxy.py`) using the serde " +#~ "functions you just created:" +#~ msgstr "" + +#~ msgid "Receiving the Message by the Client" +#~ msgstr "" + +#~ msgid "" +#~ "Last step! Modify the code in " +#~ ":code:`message_handler.py` to check the field" +#~ " of your message and call the " +#~ ":code:`example_response` function. Remember to " +#~ "use the serde functions!" +#~ msgstr "" + +#~ msgid "Within the handle function:" +#~ msgstr "" + +#~ msgid "And add a new function:" +#~ msgstr "" + +#~ msgid "Hopefully, when you run your program you will get the intended result!" +#~ msgstr "" + +#~ msgid "" +#~ "The simplest way to get started " +#~ "with Flower is by using the " +#~ "pre-made Docker images, which you can" +#~ " find on `Docker Hub " +#~ "`__." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to persist the state" +#~ " of the SuperLink on your host " +#~ "system, all you need to do is " +#~ "specify a path where you want to" +#~ " save the file on your host " +#~ "system and a name for the database" +#~ " file. In the example below, we " +#~ "tell Docker via the flag ``--volume``" +#~ " to mount the user's home directory" +#~ " (``~/`` on your host) into the " +#~ "``/app/`` directory of the container. " +#~ "Furthermore, we use the flag " +#~ "``--database`` to specify the name of" +#~ " the database file." +#~ msgstr "" + +#~ msgid "" +#~ "As soon as the SuperLink starts, " +#~ "the file ``state.db`` is created in " +#~ "the user's home directory on your " +#~ "host system. If the file already " +#~ "exists, the SuperLink tries to restore" +#~ " the state from the file. To " +#~ "start the SuperLink with an empty " +#~ "database, simply remove the ``state.db`` " +#~ "file." +#~ msgstr "" + +#~ msgid "" +#~ "Assuming all files we need are in" +#~ " the local ``certificates`` directory, we" +#~ " can use the flag ``--volume`` to " +#~ "mount the local directory into the " +#~ "``/app/`` directory of the container. " +#~ "This allows the SuperLink to access " +#~ "the files within the container. Finally," +#~ " we pass the names of the " +#~ "certificates to the SuperLink with the" +#~ " ``--certificates`` flag." +#~ msgstr "" + +#~ msgid "" +#~ "``--server 192.168.1.100:9092``: This option " +#~ "specifies the address of the SuperLinks" +#~ " Fleet" +#~ msgstr "" + +#~ msgid "" +#~ "Assuming the certificate already exists " +#~ "locally, we can use the flag " +#~ "``--volume`` to mount the local " +#~ "certificate into the container's ``/app/`` " +#~ "directory. This allows the SuperNode to" +#~ " access the certificate within the " +#~ "container. Use the ``--certificates`` flag " +#~ "when starting the container." +#~ msgstr "" + +#~ msgid "" +#~ "``--server 192.168.1.100:9091``: This option " +#~ "specifies the address of the SuperLinks" +#~ " Driver" +#~ msgstr "" + +#~ msgid "" +#~ "Assuming the certificate already exists " +#~ "locally, we can use the flag " +#~ "``--volume`` to mount the local " +#~ "certificate into the container's ``/app/`` " +#~ "directory. This allows the ServerApp to" +#~ " access the certificate within the " +#~ "container. Use the ``--certificates`` flag " +#~ "when starting the container." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to use a different " +#~ "version of Flower, for example Flower" +#~ " nightly, you can do so by " +#~ "changing the tag. All available versions" +#~ " are on `Docker Hub " +#~ "`__." +#~ msgstr "" + +#~ msgid "" +#~ "Here's another example to start with " +#~ "HTTPS. Use the ``--certificates`` command " +#~ "line argument to pass paths to (CA" +#~ " certificate, server certificate, and " +#~ "server private key)." +#~ msgstr "" + +#~ msgid ":py:obj:`run_driver_api `\\ \\(\\)" +#~ msgstr "" + +#~ msgid "Run Flower server (Driver API)." +#~ msgstr "" + +#~ msgid ":py:obj:`run_fleet_api `\\ \\(\\)" +#~ msgstr "" + +#~ msgid "Run Flower server (Fleet API)." +#~ msgstr "" + +#~ msgid "Unreleased" +#~ msgstr "" + +#~ msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" +#~ msgstr "" + +#~ msgid "|5aa1711387d74d0f8b9c499e1a51627e|" +#~ msgstr "" + +#~ msgid "|2bc8e069228d4873804061ff4a95048c|" +#~ msgstr "" + +#~ msgid "|c258488766324dc9a6807f0e7c4fd5f4|" +#~ msgstr "" + +#~ msgid "|d5f962c3f4ec48529efda980868c14b0|" +#~ msgstr "" + +#~ msgid "|a5eccea18d4c43a68b54b65043cabef8|" +#~ msgstr "" + +#~ msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" +#~ msgstr "" + +#~ msgid "|241fc906441a4f038c625a19d30d01b2|" +#~ msgstr "" + +#~ msgid "|0aa5aa05810b44b6a835cecce28f3137|" +#~ msgstr "" + +#~ msgid "|c742940dd4bf4de09d8d0d5e8d179638|" +#~ msgstr "" + +#~ msgid "|1f169ab4601a47e1a226f1628f4ebddb|" +#~ msgstr "" + +#~ msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" +#~ msgstr "" + +#~ msgid "|72939caf6e294b0986fee6dde96614d7|" +#~ msgstr "" + +#~ msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +#~ msgstr "" + diff --git a/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po b/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po index f0127ad93ed7..49bb01908421 100644 --- a/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po +++ b/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-28 11:47+0200\n" +"POT-Creation-Date: 2024-06-17 16:09+0200\n" "PO-Revision-Date: 2024-05-25 11:09+0000\n" "Last-Translator: Gustavo Bertoli \n" "Language: pt_BR\n" @@ -70,10 +70,11 @@ msgstr "Como construir imagens Docker do Flower localmente" msgid "" "Flower provides pre-made docker images on `Docker Hub " "`_ that include all necessary dependencies" -" for running the SuperLink. You can also build your own custom docker " -"images from scratch with a different version of Python or Ubuntu if that " -"is what you need. In this guide, we will explain what images exist and " -"how to build them locally." +" for running the SuperLink, SuperNode or ServerApp. You can also build " +"your own custom docker images from scratch with a different version of " +"Python or Linux distribution (Ubuntu/Alpine) if that is what you need. In" +" this guide, we will explain what images exist and how to build them " +"locally." msgstr "" "Flower disponibiliza imagens docker em `Docker Hub " "`_ que incluem todas as " @@ -82,7 +83,7 @@ msgstr "" " diferente do Python ou do Ubuntu se isso for o que você precisa. Neste " "guia, explicaremos quais imagens existem e como compilar localmente." -#: ../../source/contributor-how-to-build-docker-images.rst:9 +#: ../../source/contributor-how-to-build-docker-images.rst:10 msgid "" "Before we can start, we need to meet a few prerequisites in our local " "development environment." @@ -90,17 +91,17 @@ msgstr "" "Antes de começarmos, precisamos encontrar alguns pré-requisitos em nosso " "ambiente de desenvolvimento local." -#: ../../source/contributor-how-to-build-docker-images.rst:11 +#: ../../source/contributor-how-to-build-docker-images.rst:12 msgid "Clone the flower repository." msgstr "Clone o repositório do flower." -#: ../../source/contributor-how-to-build-docker-images.rst:17 -#: ../../source/how-to-run-flower-using-docker.rst:144 +#: ../../source/contributor-how-to-build-docker-images.rst:18 +#: ../../source/how-to-run-flower-using-docker.rst:165 msgid "Verify the Docker daemon is running." msgstr "Verifique que o serviço Docker está rodando." -#: ../../source/contributor-how-to-build-docker-images.rst:19 -#: ../../source/how-to-run-flower-using-docker.rst:146 +#: ../../source/contributor-how-to-build-docker-images.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:167 msgid "" "Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." @@ -108,23 +109,7 @@ msgstr "" "Por favor, siga a primeira seção em :doc:`Execute o Flower usando Docker " "` que cobre este passo em mais detalhes." -#: ../../source/contributor-how-to-build-docker-images.rst:23 -#, fuzzy -msgid "" -"Currently, Flower provides two images, a ``base`` image and a " -"``superlink`` image. The base image, as the name suggests, contains basic" -" dependencies that the SuperLink needs. This includes system " -"dependencies, Python and Python tools. The SuperLink image is based on " -"the base image, but it additionally installs the SuperLink using ``pip``." -msgstr "" -"Atualmente, Flower fornece duas imagens, uma imagem base e uma imagem de " -"servidor. Também haverá uma imagem de cliente em breve. A imagem base, " -"como o nome sugere, contém dependências básicas que tanto o servidor " -"quanto o cliente precisam. Isso inclui dependências do sistema, Python e " -"ferramentas Python. A imagem do servidor é baseada na imagem base, mas " -"também instala o servidor Flower usando ``pip```." - -#: ../../source/contributor-how-to-build-docker-images.rst:28 +#: ../../source/contributor-how-to-build-docker-images.rst:25 msgid "" "The build instructions that assemble the images are located in the " "respective Dockerfiles. You can find them in the subdirectories of " @@ -134,16 +119,16 @@ msgstr "" "respectivos Dockerfiles. Você pode encontrá-los nos subdiretórios " "``src/docker```." -#: ../../source/contributor-how-to-build-docker-images.rst:31 +#: ../../source/contributor-how-to-build-docker-images.rst:28 #, fuzzy msgid "" -"Both, base and SuperLink image are configured via build arguments. " -"Through build arguments, we can make our build more flexible. For " -"example, in the base image, we can specify the version of Python to " -"install using the ``PYTHON_VERSION`` build argument. Some of the build " -"arguments have default values, others must be specified when building the" -" image. All available build arguments for each image are listed in one of" -" the tables below." +"Flower Docker images are configured via build arguments. Through build " +"arguments, we can make the creation of images more flexible. For example," +" in the base image, we can specify the version of Python to install using" +" the ``PYTHON_VERSION`` build argument. Some of the build arguments have " +"default values, others must be specified when building the image. All " +"available build arguments for each image are listed in one of the tables " +"below." msgstr "" "Ambas, imagens base e do servidor são configuradas através dos argumentos" " de compilação. Através dos argumentos de compilação, podemos tornar " @@ -154,96 +139,146 @@ msgstr "" "Todos os argumentos de compilação disponíveis para cada imagem estão " "listados em uma das tabelas abaixo." -#: ../../source/contributor-how-to-build-docker-images.rst:38 +#: ../../source/contributor-how-to-build-docker-images.rst:35 msgid "Building the base image" msgstr "Construindo a imagem base" -#: ../../source/contributor-how-to-build-docker-images.rst:44 -#: ../../source/contributor-how-to-build-docker-images.rst:86 +#: ../../source/contributor-how-to-build-docker-images.rst:41 +#: ../../source/contributor-how-to-build-docker-images.rst:98 msgid "Build argument" msgstr "Argumento de compilação" -#: ../../source/contributor-how-to-build-docker-images.rst:45 -#: ../../source/contributor-how-to-build-docker-images.rst:87 +#: ../../source/contributor-how-to-build-docker-images.rst:42 +#: ../../source/contributor-how-to-build-docker-images.rst:99 msgid "Description" msgstr "Descrição" -#: ../../source/contributor-how-to-build-docker-images.rst:46 -#: ../../source/contributor-how-to-build-docker-images.rst:88 +#: ../../source/contributor-how-to-build-docker-images.rst:43 +#: ../../source/contributor-how-to-build-docker-images.rst:100 msgid "Required" msgstr "Necessário" -#: ../../source/contributor-how-to-build-docker-images.rst:47 -#: ../../source/contributor-how-to-build-docker-images.rst:89 +#: ../../source/contributor-how-to-build-docker-images.rst:44 +#: ../../source/contributor-how-to-build-docker-images.rst:101 msgid "Example" msgstr "Exemplo" +#: ../../source/contributor-how-to-build-docker-images.rst:45 +msgid "``DISTRO``" +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:46 +#, fuzzy +msgid "The Linux distribution to use as the base image." +msgstr "O nome do repositório da imagem base." + +#: ../../source/contributor-how-to-build-docker-images.rst:47 +#: ../../source/contributor-how-to-build-docker-images.rst:51 +#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:71 +#: ../../source/contributor-how-to-build-docker-images.rst:104 +msgid "No" +msgstr "" + #: ../../source/contributor-how-to-build-docker-images.rst:48 -#: ../../source/contributor-how-to-build-docker-images.rst:94 -msgid "``PYTHON_VERSION``" -msgstr "``PYTHON_VERSION``" +#, fuzzy +msgid "``ubuntu``" +msgstr "``UBUNTU_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:49 -msgid "Version of ``python`` to be installed." -msgstr "Versão do ``python`` a ser instalada." +#, fuzzy +msgid "``DISTRO_VERSION``" +msgstr "``PIP_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:50 +msgid "Version of the Linux distribution." +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:52 +#, fuzzy +msgid "``22.04``" +msgstr "``23.0.1``" + +#: ../../source/contributor-how-to-build-docker-images.rst:53 +msgid "``PYTHON_VERSION``" +msgstr "``PYTHON_VERSION``" + #: ../../source/contributor-how-to-build-docker-images.rst:54 -#: ../../source/contributor-how-to-build-docker-images.rst:58 -#: ../../source/contributor-how-to-build-docker-images.rst:108 -msgid "Yes" -msgstr "Sim" +msgid "Version of ``python`` to be installed." +msgstr "Versão do ``python`` a ser instalada." -#: ../../source/contributor-how-to-build-docker-images.rst:51 -msgid "``3.11``" -msgstr "``3.11``" +#: ../../source/contributor-how-to-build-docker-images.rst:56 +msgid "``3.11`` or ``3.11.1``" +msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:52 +#: ../../source/contributor-how-to-build-docker-images.rst:57 msgid "``PIP_VERSION``" msgstr "``PIP_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:53 +#: ../../source/contributor-how-to-build-docker-images.rst:58 msgid "Version of ``pip`` to be installed." msgstr "Versão do ``pip`` a ser instalada." -#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:59 +#: ../../source/contributor-how-to-build-docker-images.rst:63 +#: ../../source/contributor-how-to-build-docker-images.rst:67 +#: ../../source/contributor-how-to-build-docker-images.rst:108 +msgid "Yes" +msgstr "Sim" + +#: ../../source/contributor-how-to-build-docker-images.rst:60 msgid "``23.0.1``" msgstr "``23.0.1``" -#: ../../source/contributor-how-to-build-docker-images.rst:56 +#: ../../source/contributor-how-to-build-docker-images.rst:61 msgid "``SETUPTOOLS_VERSION``" msgstr "``SETUPTOOLS_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:57 +#: ../../source/contributor-how-to-build-docker-images.rst:62 msgid "Version of ``setuptools`` to be installed." msgstr "Versão do ``setuptools`` a ser instalada." -#: ../../source/contributor-how-to-build-docker-images.rst:59 +#: ../../source/contributor-how-to-build-docker-images.rst:64 msgid "``69.0.2``" msgstr "``69.0.2``" -#: ../../source/contributor-how-to-build-docker-images.rst:60 -#: ../../source/contributor-how-to-build-docker-images.rst:98 -msgid "``UBUNTU_VERSION``" -msgstr "``UBUNTU_VERSION``" +#: ../../source/contributor-how-to-build-docker-images.rst:65 +msgid "``FLWR_VERSION``" +msgstr "``FLWR_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:61 -msgid "Version of the official Ubuntu Docker image." -msgstr "Versão da imagem Docker oficial do Ubuntu." +#: ../../source/contributor-how-to-build-docker-images.rst:66 +msgid "Version of Flower to be installed." +msgstr "Versão do Flower a ser instalada." -#: ../../source/contributor-how-to-build-docker-images.rst:62 -msgid "Defaults to ``22.04``." -msgstr "Como padrão ``22.04``." +#: ../../source/contributor-how-to-build-docker-images.rst:68 +#, fuzzy +msgid "``1.8.0``" +msgstr "``1.7.0``" -#: ../../source/contributor-how-to-build-docker-images.rst:65 +#: ../../source/contributor-how-to-build-docker-images.rst:69 +#, fuzzy +msgid "``FLWR_PACKAGE``" +msgstr "``FLWR_VERSION``" + +#: ../../source/contributor-how-to-build-docker-images.rst:70 +#, fuzzy +msgid "The Flower package to be installed." +msgstr "Versão do Flower a ser instalada." + +#: ../../source/contributor-how-to-build-docker-images.rst:72 +msgid "``flwr`` or ``flwr-nightly``" +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:75 +#, fuzzy msgid "" -"The following example creates a base image with Python 3.11.0, pip 23.0.1" -" and setuptools 69.0.2:" +"The following example creates a base Ubuntu/Alpine image with Python " +"3.11.0, pip 23.0.1, setuptools 69.0.2 and Flower 1.8.0:" msgstr "" "O exemplo seguinte cria uma imagem base com Python 3.11.0, pip 23.0.1 e " "setuptools 69.0.2:" -#: ../../source/contributor-how-to-build-docker-images.rst:76 +#: ../../source/contributor-how-to-build-docker-images.rst:88 msgid "" "The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that " "the build arguments as well as the name and tag can be adapted to your " @@ -253,76 +288,43 @@ msgstr "" "argumentos de construção assim como o nome e a tag podem ser adaptados de" " acordo com suas necessidades. Estes valores servem apenas como exemplo." -#: ../../source/contributor-how-to-build-docker-images.rst:80 +#: ../../source/contributor-how-to-build-docker-images.rst:92 #, fuzzy -msgid "Building the SuperLink image" +msgid "Building the SuperLink/SuperNode or ServerApp image" msgstr "Construindo a imagem do servidor" -#: ../../source/contributor-how-to-build-docker-images.rst:90 +#: ../../source/contributor-how-to-build-docker-images.rst:102 msgid "``BASE_REPOSITORY``" msgstr "``BASE_REPOSITORY``" -#: ../../source/contributor-how-to-build-docker-images.rst:91 +#: ../../source/contributor-how-to-build-docker-images.rst:103 msgid "The repository name of the base image." msgstr "O nome do repositório da imagem base." -#: ../../source/contributor-how-to-build-docker-images.rst:92 -#, fuzzy -msgid "Defaults to ``flwr/base``." -msgstr "Pré-definido para ``flwr/server``." - -#: ../../source/contributor-how-to-build-docker-images.rst:95 -#, fuzzy -msgid "The Python version of the base image." -msgstr "O nome do repositório da imagem base." - -#: ../../source/contributor-how-to-build-docker-images.rst:96 -#, fuzzy -msgid "Defaults to ``py3.11``." -msgstr "Como padrão ``22.04``." - -#: ../../source/contributor-how-to-build-docker-images.rst:99 -#, fuzzy -msgid "The Ubuntu version of the base image." -msgstr "O nome do repositório da imagem base." - -#: ../../source/contributor-how-to-build-docker-images.rst:100 -#, fuzzy -msgid "Defaults to ``ubuntu22.04``." -msgstr "Pré-definido para ``py3.11-ubuntu22.04``." - -#: ../../source/contributor-how-to-build-docker-images.rst:102 +#: ../../source/contributor-how-to-build-docker-images.rst:105 #, fuzzy -msgid "``FLWR_PACKAGE``" +msgid "``flwr/base``" msgstr "``FLWR_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:103 -msgid "The PyPI package to install." -msgstr "" - -#: ../../source/contributor-how-to-build-docker-images.rst:104 -#, fuzzy -msgid "Defaults to ``flwr``." -msgstr "Pré-definido para ``flwr/server``." - #: ../../source/contributor-how-to-build-docker-images.rst:106 -msgid "``FLWR_VERSION``" -msgstr "``FLWR_VERSION``" +#, fuzzy +msgid "``BASE_IMAGE``" +msgstr "``BASE_REPOSITORY``" #: ../../source/contributor-how-to-build-docker-images.rst:107 -msgid "Version of Flower to be installed." -msgstr "Versão do Flower a ser instalada." +#, fuzzy +msgid "The Tag of the Flower base image." +msgstr "O nome do repositório da imagem base." #: ../../source/contributor-how-to-build-docker-images.rst:109 -#, fuzzy -msgid "``1.8.0``" -msgstr "``1.7.0``" +msgid "``1.8.0-py3.10-ubuntu22.04``" +msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:112 +#: ../../source/contributor-how-to-build-docker-images.rst:111 #, fuzzy msgid "" -"The following example creates a SuperLink image with the official Flower " -"base image py3.11-ubuntu22.04 and Flower 1.8.0:" +"The following example creates a SuperLink/SuperNode or ServerApp image " +"with the official Flower base image:" msgstr "" "O exemplo a seguir cria uma imagem de servidor com a imagem base oficial " "do Flower py3.11-ubuntu22.04 e Flower 1.7.0:" @@ -330,20 +332,9 @@ msgstr "" #: ../../source/contributor-how-to-build-docker-images.rst:122 #, fuzzy msgid "" -"The name of image is ``flwr_superlink`` and the tag ``0.1.0``. Remember " -"that the build arguments as well as the name and tag can be adapted to " -"your needs. These values serve as examples only." -msgstr "" -"O nome da imagem é ``flwr_server`` e a tag ``0.1.0``. Lembre-se que os " -"argumentos de compilação, bem como o nome e a tag podem ser adaptados às " -"suas necessidades. Esses valores servem apenas como exemplos." - -#: ../../source/contributor-how-to-build-docker-images.rst:125 -#, fuzzy -msgid "" "If you want to use your own base image instead of the official Flower " -"base image, all you need to do is set the ``BASE_REPOSITORY``, " -"``PYTHON_VERSION`` and ``UBUNTU_VERSION`` build arguments." +"base image, all you need to do is set the ``BASE_REPOSITORY`` build " +"argument." msgstr "" "Se você quiser usar sua própria imagem base ao invés da imagem oficial " "base do Flower, tudo que você precisa fazer é definir os argumentos " @@ -352,7 +343,7 @@ msgstr "" "sua imagem e o valor de ``BASE_IMAGE_TAG`` deve corresponder à tag da sua" " imagem." -#: ../../source/contributor-how-to-build-docker-images.rst:138 +#: ../../source/contributor-how-to-build-docker-images.rst:133 msgid "After creating the image, we can test whether the image is working:" msgstr "Depois de criar a imagem, podemos testar se a imagem está funcionando:" @@ -516,120 +507,6 @@ msgstr "" "abrindo uma issue no nosso `repositório GitHub " "`_." -#: ../../source/contributor-how-to-create-new-messages.rst:2 -#, fuzzy -msgid "Creating New Messages" -msgstr "Criando novas mensagens" - -#: ../../source/contributor-how-to-create-new-messages.rst:4 -msgid "" -"This is a simple guide for creating a new type of message between the " -"server and clients in Flower." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:6 -msgid "" -"Let's suppose we have the following example functions in " -":code:`server.py` and :code:`numpy_client.py`..." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:8 -msgid "Server's side:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:17 -msgid "Client's side:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:26 -msgid "" -"Let's now see what we need to implement in order to get this simple " -"function between the server and client to work!" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:30 -msgid "Message Types for Protocol Buffers" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:32 -msgid "" -"The first thing we need to do is to define a message type for the RPC " -"system in :code:`transport.proto`. Note that we have to do it for both " -"the request and response messages. For more details on the syntax of " -"proto3, please see the `official documentation `_." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:35 -msgid "Within the :code:`ServerMessage` block:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:52 -msgid "Within the ClientMessage block:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:70 -msgid "" -"Make sure to also add a field of the newly created message type in " -":code:`oneof msg`." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:72 -msgid "Once that is done, we will compile the file with:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:78 -msgid "If it compiles successfully, you should see the following message:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:87 -msgid "Serialization and Deserialization Functions" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:89 -msgid "" -"Our next step is to add functions to serialize and deserialize Python " -"datatypes to or from our defined RPC message types. You should add these " -"functions in :code:`serde.py`." -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:91 -msgid "The four functions:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:112 -msgid "Sending the Message from the Server" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:114 -msgid "" -"Now write the request function in your Client Proxy class (e.g., " -":code:`grpc_client_proxy.py`) using the serde functions you just created:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:128 -msgid "Receiving the Message by the Client" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:130 -msgid "" -"Last step! Modify the code in :code:`message_handler.py` to check the " -"field of your message and call the :code:`example_response` function. " -"Remember to use the serde functions!" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:132 -msgid "Within the handle function:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:139 -msgid "And add a new function:" -msgstr "" - -#: ../../source/contributor-how-to-create-new-messages.rst:149 -msgid "Hopefully, when you run your program you will get the intended result!" -msgstr "" - #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:2 msgid "Develop in VSCode Dev Containers" msgstr "" @@ -938,96 +815,145 @@ msgstr "" msgid "Check the draft release on GitHub, and if everything is good, publish it." msgstr "" +#: ../../source/contributor-how-to-release-flower.rst:15 +#, fuzzy +msgid "Trigger the CI for building the Docker images." +msgstr "Versão da imagem Docker oficial do Ubuntu." + #: ../../source/contributor-how-to-release-flower.rst:17 +msgid "" +"To trigger the workflow, a collaborator must create a " +"``workflow_dispatch`` event in the GitHub CI. This can be done either " +"through the UI or via the GitHub CLI. The event requires only one input, " +"the Flower version, to be released." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:21 +msgid "**Via the UI**" +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:23 +msgid "" +"Go to the ``Build docker images`` workflow `page " +"`_." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:24 +msgid "" +"Click on the ``Run workflow`` button and type the new version of Flower " +"in the ``Version of Flower`` input field." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:25 +msgid "Click on the **green** ``Run workflow`` button." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:29 +msgid "**Via the GitHub CI**" +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:31 +msgid "" +"Make sure you are logged in via ``gh auth login`` and that the current " +"working directory is the root of the Flower repository." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:32 +msgid "" +"Trigger the workflow via ``gh workflow run docker-images.yml -f flwr-" +"version=``." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:35 msgid "After the release" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:19 +#: ../../source/contributor-how-to-release-flower.rst:37 msgid "Create a pull request which contains the following changes:" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:21 +#: ../../source/contributor-how-to-release-flower.rst:39 msgid "Increase the minor version in ``pyproject.toml`` by one." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:22 +#: ../../source/contributor-how-to-release-flower.rst:40 msgid "Update all files which contain the current version number if necessary." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:23 +#: ../../source/contributor-how-to-release-flower.rst:41 msgid "Add a new ``Unreleased`` section in ``changelog.md``." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:25 +#: ../../source/contributor-how-to-release-flower.rst:43 msgid "" "Merge the pull request on the same day (i.e., before a new nightly " "release gets published to PyPI)." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:28 +#: ../../source/contributor-how-to-release-flower.rst:46 msgid "Publishing a pre-release" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:31 +#: ../../source/contributor-how-to-release-flower.rst:49 msgid "Pre-release naming" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:33 +#: ../../source/contributor-how-to-release-flower.rst:51 msgid "" "PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases" " MUST use one of the following naming patterns:" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:35 +#: ../../source/contributor-how-to-release-flower.rst:53 msgid "Alpha: ``MAJOR.MINOR.PATCHaN``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:36 +#: ../../source/contributor-how-to-release-flower.rst:54 msgid "Beta: ``MAJOR.MINOR.PATCHbN``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:37 +#: ../../source/contributor-how-to-release-flower.rst:55 msgid "Release candidate (RC): ``MAJOR.MINOR.PATCHrcN``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:39 +#: ../../source/contributor-how-to-release-flower.rst:57 msgid "Examples include:" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:41 +#: ../../source/contributor-how-to-release-flower.rst:59 msgid "``1.0.0a0``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:42 +#: ../../source/contributor-how-to-release-flower.rst:60 msgid "``1.0.0b0``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:43 +#: ../../source/contributor-how-to-release-flower.rst:61 msgid "``1.0.0rc0``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:44 +#: ../../source/contributor-how-to-release-flower.rst:62 msgid "``1.0.0rc1``" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:46 +#: ../../source/contributor-how-to-release-flower.rst:64 msgid "" "This is in line with PEP-440 and the recommendations from the Python " "Packaging Authority (PyPA):" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:49 +#: ../../source/contributor-how-to-release-flower.rst:67 msgid "`PEP-440 `_" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:50 +#: ../../source/contributor-how-to-release-flower.rst:68 msgid "" "`PyPA Choosing a versioning scheme " "`_" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:52 +#: ../../source/contributor-how-to-release-flower.rst:70 msgid "" "Note that the approach defined by PyPA is not compatible with SemVer " "2.0.0 spec, for details consult the `Semantic Versioning Specification " @@ -1035,26 +961,26 @@ msgid "" "11 on precedence)." msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:55 +#: ../../source/contributor-how-to-release-flower.rst:73 msgid "Pre-release classification" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:57 +#: ../../source/contributor-how-to-release-flower.rst:75 msgid "Should the next pre-release be called alpha, beta, or release candidate?" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:59 +#: ../../source/contributor-how-to-release-flower.rst:77 msgid "" "RC: feature complete, no known issues (apart from issues that are " "classified as \"won't fix\" for the next stable release) - if no issues " "surface this will become the next stable release" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:60 +#: ../../source/contributor-how-to-release-flower.rst:78 msgid "Beta: feature complete, allowed to have known issues" msgstr "" -#: ../../source/contributor-how-to-release-flower.rst:61 +#: ../../source/contributor-how-to-release-flower.rst:79 msgid "Alpha: not feature complete, allowed to have known issues" msgstr "" @@ -1992,7 +1918,7 @@ msgid "Get started as a contributor" msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 -#: ../../source/how-to-run-flower-using-docker.rst:132 +#: ../../source/how-to-run-flower-using-docker.rst:153 msgid "Prerequisites" msgstr "" @@ -3783,11 +3709,11 @@ msgid "" "authentication enabled:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:36 +#: ../../source/how-to-authenticate-supernodes.rst:38 msgid "Let's break down the authentication flags:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:38 +#: ../../source/how-to-authenticate-supernodes.rst:40 msgid "" "The first flag :code:`--auth-list-public-keys` expects a path to a CSV " "file storing all known node public keys. You need to store all known node" @@ -3795,7 +3721,7 @@ msgid "" "file (:code:`.csv`)." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:40 +#: ../../source/how-to-authenticate-supernodes.rst:42 msgid "" "A valid CSV file storing known node public keys should list the keys in " "OpenSSH format, separated by commas and without any comments. For an " @@ -3803,7 +3729,7 @@ msgid "" "known node public keys." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:42 +#: ../../source/how-to-authenticate-supernodes.rst:44 msgid "" "The second and third flags :code:`--auth-superlink-private-key` and :code" ":`--auth-superlink-public-key` expect paths to the server's private and " @@ -3811,7 +3737,7 @@ msgid "" "public key pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:45 +#: ../../source/how-to-authenticate-supernodes.rst:47 msgid "" "In Flower 1.9, there is no support for dynamically removing, editing, or " "adding known node public keys to the SuperLink. To change the set of " @@ -3820,11 +3746,11 @@ msgid "" " nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:51 +#: ../../source/how-to-authenticate-supernodes.rst:53 msgid "Enable node authentication in :code:`SuperNode`" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:53 +#: ../../source/how-to-authenticate-supernodes.rst:55 msgid "" "Similar to the long-running Flower server (:code:`SuperLink`), you can " "easily enable node authentication in the long-running Flower client " @@ -3832,7 +3758,7 @@ msgid "" "authenticated :code:`SuperNode`:" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:64 +#: ../../source/how-to-authenticate-supernodes.rst:66 msgid "" "The :code:`--auth-supernode-private-key` flag expects a path to the " "node's private key file and the :code:`--auth-supernode-public-key` flag " @@ -3841,11 +3767,11 @@ msgid "" " ecdsa -b 384`." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:68 +#: ../../source/how-to-authenticate-supernodes.rst:70 msgid "Security notice" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:70 +#: ../../source/how-to-authenticate-supernodes.rst:72 msgid "" "The system's security relies on the credentials of the SuperLink and each" " SuperNode. Therefore, it is imperative to safeguard and safely store the" @@ -3856,14 +3782,14 @@ msgid "" "methods." msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:75 -#: ../../source/how-to-enable-ssl-connections.rst:65 +#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:68 #: ../../source/how-to-use-built-in-mods.rst:85 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:287 msgid "Conclusion" msgstr "" -#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-authenticate-supernodes.rst:79 msgid "" "You should now have learned how to start a long-running Flower server " "(:code:`SuperLink`) and client (:code:`SuperNode`) with node " @@ -4139,51 +4065,51 @@ msgid "" " the previously generated certificates:" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:47 +#: ../../source/how-to-enable-ssl-connections.rst:50 msgid "" "When providing certificates, the server expects a tuple of three " "certificates paths: CA certificate, server certificate and server private" " key." msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:51 +#: ../../source/how-to-enable-ssl-connections.rst:54 msgid "Client (SuperNode)" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:53 +#: ../../source/how-to-enable-ssl-connections.rst:56 msgid "" "Use the following terminal command to start a client (SuperNode) that " "uses the previously generated certificates:" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:61 +#: ../../source/how-to-enable-ssl-connections.rst:64 msgid "" "When setting :code:`root_certificates`, the client expects a file path to" " PEM-encoded root certificates." msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:67 +#: ../../source/how-to-enable-ssl-connections.rst:70 msgid "" "You should now have learned how to generate self-signed certificates " "using the given script, start an SSL-enabled server and have a client " "establish a secure connection to it." msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:72 +#: ../../source/how-to-enable-ssl-connections.rst:75 msgid "Additional resources" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:74 +#: ../../source/how-to-enable-ssl-connections.rst:77 msgid "" "These additional sources might be relevant if you would like to dive " "deeper into the topic of certificates:" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:76 +#: ../../source/how-to-enable-ssl-connections.rst:79 msgid "`Let's Encrypt `_" msgstr "" -#: ../../source/how-to-enable-ssl-connections.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:80 msgid "`certbot `_" msgstr "" @@ -4793,14 +4719,15 @@ msgstr "" msgid "" "The simplest way to get started with Flower is by using the pre-made " "Docker images, which you can find on `Docker Hub " -"`__." +"`__. Supported architectures include " +"``amd64`` and ``arm64v8``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:7 +#: ../../source/how-to-run-flower-using-docker.rst:8 msgid "Before you start, make sure that the Docker daemon is running:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:14 +#: ../../source/how-to-run-flower-using-docker.rst:15 msgid "" "If you do not see the version of Docker but instead get an error saying " "that the command was not found, you will need to install Docker first. " @@ -4808,7 +4735,7 @@ msgid "" "docker/>`_." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:21 msgid "" "On Linux, Docker commands require ``sudo`` privilege. If you want to " "avoid using ``sudo``, you can follow the `Post-installation steps " @@ -4816,7 +4743,7 @@ msgid "" "official Docker website." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:26 +#: ../../source/how-to-run-flower-using-docker.rst:27 msgid "" "To ensure optimal performance and compatibility, the SuperLink, SuperNode" " and ServerApp image must have the same version when running together. " @@ -4824,26 +4751,26 @@ msgid "" "issues that may arise from using different versions." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:31 +#: ../../source/how-to-run-flower-using-docker.rst:32 msgid "Flower SuperLink" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:34 +#: ../../source/how-to-run-flower-using-docker.rst:35 msgid "Quickstart" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:36 +#: ../../source/how-to-run-flower-using-docker.rst:37 msgid "If you're looking to try out Flower, you can use the following command:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:42 +#: ../../source/how-to-run-flower-using-docker.rst:43 msgid "" "The command pulls the Docker image with the tag ``1.8.0`` from Docker " "Hub. The tag specifies the Flower version. In this case, Flower 1.8.0. " "The ``--rm`` flag tells Docker to remove the container after it exits." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:48 +#: ../../source/how-to-run-flower-using-docker.rst:49 msgid "" "By default, the Flower SuperLink keeps state in-memory. When using the " "Docker flag ``--rm``, the state is not persisted between container " @@ -4851,7 +4778,7 @@ msgid "" "system." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:52 +#: ../../source/how-to-run-flower-using-docker.rst:53 msgid "" "The ``-p :`` flag tells Docker to map the ports " "``9091``/``9092`` of the host to ``9091``/``9092`` of the container, " @@ -4861,9 +4788,9 @@ msgid "" " flag ``--insecure``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:59 -#: ../../source/how-to-run-flower-using-docker.rst:238 -#: ../../source/how-to-run-flower-using-docker.rst:354 +#: ../../source/how-to-run-flower-using-docker.rst:60 +#: ../../source/how-to-run-flower-using-docker.rst:259 +#: ../../source/how-to-run-flower-using-docker.rst:376 msgid "" "The ``--insecure`` flag enables insecure communication (using HTTP, not " "HTTPS) and should only be used for testing purposes. We strongly " @@ -4872,48 +4799,59 @@ msgid "" "deploying to a production environment." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:64 +#: ../../source/how-to-run-flower-using-docker.rst:65 msgid "" "You can use ``--help`` to view all available flags that the SuperLink " "supports:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:71 +#: ../../source/how-to-run-flower-using-docker.rst:72 msgid "Mounting a volume to store the state on the host system" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:73 +#: ../../source/how-to-run-flower-using-docker.rst:74 msgid "" "If you want to persist the state of the SuperLink on your host system, " -"all you need to do is specify a path where you want to save the file on " -"your host system and a name for the database file. In the example below, " -"we tell Docker via the flag ``--volume`` to mount the user's home " -"directory (``~/`` on your host) into the ``/app/`` directory of the " -"container. Furthermore, we use the flag ``--database`` to specify the " -"name of the database file." +"all you need to do is specify a directory where you want to save the file" +" on your host system and a name for the database file. By default, the " +"SuperLink container runs with a non-root user called ``app`` with the " +"user ID ``49999``. It is recommended to create new directory and change " +"the user ID of the directory to ``49999`` to ensure the mounted directory" +" has the proper permissions. If you later want to delete the directory, " +"you can change the user ID back to the current user ID by running ``sudo " +"chown -R $USER:$(id -gn) state``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:86 +#: ../../source/how-to-run-flower-using-docker.rst:82 +msgid "" +"In the example below, we create a new directory, change the user ID and " +"tell Docker via the flag ``--volume`` to mount the local ``state`` " +"directory into the ``/app/state`` directory of the container. " +"Furthermore, we use the flag ``--database`` to specify the name of the " +"database file." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:95 msgid "" "As soon as the SuperLink starts, the file ``state.db`` is created in the " -"user's home directory on your host system. If the file already exists, " -"the SuperLink tries to restore the state from the file. To start the " +"``state`` directory on your host system. If the file already exists, the " +"SuperLink tries to restore the state from the file. To start the " "SuperLink with an empty database, simply remove the ``state.db`` file." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:91 -#: ../../source/how-to-run-flower-using-docker.rst:260 -#: ../../source/how-to-run-flower-using-docker.rst:375 +#: ../../source/how-to-run-flower-using-docker.rst:100 +#: ../../source/how-to-run-flower-using-docker.rst:281 +#: ../../source/how-to-run-flower-using-docker.rst:397 msgid "Enabling SSL for secure connections" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:93 +#: ../../source/how-to-run-flower-using-docker.rst:102 msgid "" "To enable SSL, you will need a PEM-encoded root certificate, a PEM-" "encoded private key and a PEM-encoded certificate chain." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:97 +#: ../../source/how-to-run-flower-using-docker.rst:106 msgid "" "For testing purposes, you can generate your own self-signed certificates." " The `Enable SSL connections `__ is already installed " "in the ``flwr/supernode`` base image, so you only need to include other " @@ -4995,20 +4945,20 @@ msgid "" "``tensorflow``, etc." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:179 +#: ../../source/how-to-run-flower-using-docker.rst:200 msgid "" "Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` " "example, create a new file called ``Dockerfile.supernode`` in ``examples" "/quickstart-pytorch``." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:182 +#: ../../source/how-to-run-flower-using-docker.rst:203 msgid "" "The ``Dockerfile.supernode`` contains the instructions that assemble the " "SuperNode image." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:196 +#: ../../source/how-to-run-flower-using-docker.rst:217 msgid "" "In the first two lines, we instruct Docker to use the SuperNode image " "tagged ``nightly`` as a base image and set our working directory to " @@ -5021,70 +4971,70 @@ msgid "" "(``:``) that will be run inside the ClientApp." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:205 +#: ../../source/how-to-run-flower-using-docker.rst:226 #, fuzzy msgid "Building the SuperNode Docker image" msgstr "Construindo a imagem do servidor" -#: ../../source/how-to-run-flower-using-docker.rst:207 +#: ../../source/how-to-run-flower-using-docker.rst:228 msgid "" "Next, we build the SuperNode Docker image by running the following " "command in the directory where Dockerfile and ClientApp code are located." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:214 +#: ../../source/how-to-run-flower-using-docker.rst:235 msgid "" "We gave the image the name ``flwr_supernode``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:219 +#: ../../source/how-to-run-flower-using-docker.rst:240 #, fuzzy msgid "Running the SuperNode Docker image" msgstr "Construindo a imagem do servidor" -#: ../../source/how-to-run-flower-using-docker.rst:221 +#: ../../source/how-to-run-flower-using-docker.rst:242 msgid "Now that we have built the SuperNode image, we can finally run it." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:229 -#: ../../source/how-to-run-flower-using-docker.rst:345 +#: ../../source/how-to-run-flower-using-docker.rst:250 +#: ../../source/how-to-run-flower-using-docker.rst:367 msgid "Let's break down each part of this command:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:231 -#: ../../source/how-to-run-flower-using-docker.rst:347 +#: ../../source/how-to-run-flower-using-docker.rst:252 +#: ../../source/how-to-run-flower-using-docker.rst:369 msgid "``docker run``: This is the command to run a new Docker container." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:232 -#: ../../source/how-to-run-flower-using-docker.rst:348 +#: ../../source/how-to-run-flower-using-docker.rst:253 +#: ../../source/how-to-run-flower-using-docker.rst:370 msgid "" "``--rm``: This option specifies that the container should be " "automatically removed when it stops." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:233 +#: ../../source/how-to-run-flower-using-docker.rst:254 msgid "``flwr_supernode:0.0.1``: The name the tag of the Docker image to use." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:234 -#: ../../source/how-to-run-flower-using-docker.rst:350 +#: ../../source/how-to-run-flower-using-docker.rst:255 +#: ../../source/how-to-run-flower-using-docker.rst:372 msgid "``--insecure``: This option enables insecure communication." msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--server 192.168.1.100:9092``: This option specifies the address of the" -" SuperLinks Fleet" +"``--superlink 192.168.1.100:9092``: This option specifies the address of " +"the SuperLinks Fleet" msgstr "" #: ../../source/how-to-run-flower-using-docker.rst msgid "API to connect to. Remember to update it with your SuperLink IP." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:248 +#: ../../source/how-to-run-flower-using-docker.rst:269 msgid "" "To test running Flower locally, you can create a `bridge network " "`__." +" `Docker Hub `__." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:400 +#: ../../source/how-to-run-flower-using-docker.rst:460 msgid "Pinning a Docker image to a specific version" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:402 +#: ../../source/how-to-run-flower-using-docker.rst:462 msgid "" "It may happen that we update the images behind the tags. Such updates " "usually include security updates of system dependencies that should not " @@ -5261,21 +5249,21 @@ msgid "" "instead of the tag." msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:407 +#: ../../source/how-to-run-flower-using-docker.rst:467 msgid "" "The following command returns the current image hash referenced by the " "``superlink:1.8.0`` tag:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:414 +#: ../../source/how-to-run-flower-using-docker.rst:474 msgid "Next, we can pin the hash when running a new SuperLink container:" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:423 +#: ../../source/how-to-run-flower-using-docker.rst:483 msgid "Setting environment variables" msgstr "" -#: ../../source/how-to-run-flower-using-docker.rst:425 +#: ../../source/how-to-run-flower-using-docker.rst:485 msgid "" "To set a variable inside a Docker container, you can use the ``-e " "=`` flag." @@ -6124,9 +6112,10 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:174 msgid "" -"Here's another example to start with HTTPS. Use the ``--certificates`` " -"command line argument to pass paths to (CA certificate, server " -"certificate, and server private key)." +"Here's another example to start with HTTPS. Use the ``--ssl-ca-" +"certfile``, ``--ssl-certfile``, and ``--ssl-keyfile`` command line " +"options to pass paths to (CA certificate, server certificate, and server " +"private key)." msgstr "" #: ../../source/how-to-upgrade-to-flower-next.rst:201 @@ -6594,11 +6583,11 @@ msgstr "" msgid "Contributor how-to guides" msgstr "" -#: ../../source/index.rst:173 +#: ../../source/index.rst:172 msgid "Contributor explanations" msgstr "" -#: ../../source/index.rst:179 +#: ../../source/index.rst:178 msgid "Contributor references" msgstr "" @@ -6738,7 +6727,8 @@ msgstr "" msgid "flwr" msgstr "" -#: ../../source/ref-api/flwr.rst:25 ../../source/ref-api/flwr.server.rst:51 +#: ../../source/ref-api/flwr.client.rst:45 ../../source/ref-api/flwr.rst:25 +#: ../../source/ref-api/flwr.server.rst:49 msgid "Modules" msgstr "" @@ -6763,7 +6753,7 @@ msgid ":py:obj:`flwr.server `\\" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 -#: ../../source/ref-api/flwr.server.rst:40::1 flwr.server:1 +#: ../../source/ref-api/flwr.server.rst:38::1 flwr.server:1 #: flwr.server.server.Server:1 of msgid "Flower server." msgstr "" @@ -6780,6 +6770,7 @@ msgstr "" msgid "client" msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:13 #: ../../source/ref-api/flwr.client.rst:13 #: ../../source/ref-api/flwr.common.rst:13 #: ../../source/ref-api/flwr.server.rst:13 @@ -6827,9 +6818,10 @@ msgstr "" msgid "Start a Flower NumPyClient which connects to a gRPC server." msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:30 #: ../../source/ref-api/flwr.client.rst:27 #: ../../source/ref-api/flwr.common.rst:32 -#: ../../source/ref-api/flwr.server.rst:28 +#: ../../source/ref-api/flwr.server.rst:26 #: ../../source/ref-api/flwr.server.strategy.rst:17 #: ../../source/ref-api/flwr.server.workflow.rst:17 msgid "Classes" @@ -6864,6 +6856,14 @@ msgstr "" msgid "Abstract base class for Flower clients using NumPy." msgstr "" +#: ../../source/ref-api/flwr.client.rst:52::1 +msgid ":py:obj:`flwr.client.mod `\\" +msgstr "" + +#: ../../source/ref-api/flwr.client.rst:52::1 flwr.client.mod:1 of +msgid "Flower Built-in Mods." +msgstr "" + #: flwr.client.client.Client:1 flwr.client.numpy_client.NumPyClient:1 #: flwr.server.client_manager.ClientManager:1 #: flwr.server.driver.driver.Driver:1 flwr.server.strategy.strategy.Strategy:1 @@ -6874,6 +6874,7 @@ msgstr "" #: ../../source/ref-api/flwr.client.Client.rst:15 #: ../../source/ref-api/flwr.client.ClientApp.rst:15 #: ../../source/ref-api/flwr.client.NumPyClient.rst:15 +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:15 #: ../../source/ref-api/flwr.common.Array.rst:15 #: ../../source/ref-api/flwr.common.ClientMessage.rst:15 #: ../../source/ref-api/flwr.common.ConfigsRecord.rst:15 @@ -7049,6 +7050,7 @@ msgstr "" #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit #: flwr.client.client.Client.get_parameters #: flwr.client.client.Client.get_properties +#: flwr.client.mod.localdp_mod.LocalDpMod #: flwr.client.numpy_client.NumPyClient.evaluate #: flwr.client.numpy_client.NumPyClient.fit #: flwr.client.numpy_client.NumPyClient.get_parameters @@ -7199,10 +7201,11 @@ msgstr "" msgid "ClientApp" msgstr "" -#: flwr.client.client_app.ClientApp:1 flwr.common.constant.MessageType:1 -#: flwr.common.constant.MessageTypeLegacy:1 flwr.common.context.Context:1 -#: flwr.common.message.Error:1 flwr.common.message.Message:1 -#: flwr.common.message.Metadata:1 flwr.common.record.parametersrecord.Array:1 +#: flwr.client.client_app.ClientApp:1 flwr.client.mod.localdp_mod.LocalDpMod:1 +#: flwr.common.constant.MessageType:1 flwr.common.constant.MessageTypeLegacy:1 +#: flwr.common.context.Context:1 flwr.common.message.Error:1 +#: flwr.common.message.Message:1 flwr.common.message.Metadata:1 +#: flwr.common.record.parametersrecord.Array:1 #: flwr.common.record.recordset.RecordSet:1 flwr.common.typing.ClientMessage:1 #: flwr.common.typing.DisconnectRes:1 flwr.common.typing.EvaluateIns:1 #: flwr.common.typing.EvaluateRes:1 flwr.common.typing.FitIns:1 @@ -7223,7 +7226,8 @@ msgstr "" #: flwr.client.client_app.ClientApp:4 #: flwr.client.client_app.ClientApp.evaluate:4 #: flwr.client.client_app.ClientApp.query:4 -#: flwr.client.client_app.ClientApp.train:4 flwr.server.app.start_server:41 +#: flwr.client.client_app.ClientApp.train:4 +#: flwr.client.mod.localdp_mod.LocalDpMod:22 flwr.server.app.start_server:41 #: flwr.server.server_app.ServerApp:4 flwr.server.server_app.ServerApp.main:4 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:29 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:22 @@ -7440,6 +7444,231 @@ msgid "" "arbitrary property values back to the server." msgstr "" +#: ../../source/ref-api/flwr.client.mod.rst:2 +msgid "mod" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`adaptiveclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:1 of +msgid "Client-side adaptive clipping modifier." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`fixedclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:1 of +msgid "Client-side fixed clipping modifier." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid ":py:obj:`make_ffn `\\ \\(ffn\\, mods\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.utils.make_ffn:1 of +msgid "." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`secagg_mod `\\ \\(msg\\, ctxt\\, " +"call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secagg_mod.secagg_mod:1 of +msgid "Handle incoming message and return results, following the SecAgg protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`secaggplus_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secaggplus_mod.secaggplus_mod:1 of +msgid "" +"Handle incoming message and return results, following the SecAgg+ " +"protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`message_size_mod `\\ \\(msg\\," +" ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.message_size_mod:1 of +msgid "Message size mod." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`parameters_size_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.parameters_size_mod:1 of +msgid "Parameters size mod." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +msgid "" +":py:obj:`LocalDpMod `\\ \\(clipping\\_norm\\," +" sensitivity\\, ...\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +#: flwr.client.mod.localdp_mod.LocalDpMod:1 of +msgid "Modifier for local differential privacy." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:2 +msgid "LocalDpMod" +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:3 of +msgid "" +"This mod clips the client model updates and adds noise to the params " +"before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:12 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:10 +#: flwr.client.mod.localdp_mod.LocalDpMod:6 of +msgid "It operates on messages of type `MessageType.TRAIN`." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:8 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 +#: of +msgid "The value of the clipping norm." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:10 of +msgid "The sensitivity of the client model." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:12 of +msgid "" +"The privacy budget. Smaller value of epsilon indicates a higher level of " +"privacy protection." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:15 of +msgid "" +"The failure probability. The probability that the privacy mechanism fails" +" to provide the desired level of privacy. A smaller value of delta " +"indicates a stricter privacy guarantee." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:23 of +msgid "Create an instance of the local DP mod and add it to the client-side mods:" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.adaptiveclipping_mod.rst:2 +msgid "adaptiveclipping\\_mod" +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:3 of +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideAdaptiveClipping server-side strategy " +"wrapper." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:6 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:6 of +msgid "The wrapper sends the clipping_norm value to the client." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:8 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:8 of +msgid "This mod clips the client model updates before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:10 of +msgid "" +"It also sends KEY_NORM_BIT to the server for computing the new clipping " +"value." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:15 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:13 +#: flwr.server.driver.driver.Driver.send_and_receive:18 +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 +#: of +msgid "Notes" +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:16 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:14 of +msgid "Consider the order of mods when using multiple." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:18 of +msgid "Typically, adaptiveclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.fixedclipping_mod.rst:2 +msgid "fixedclipping\\_mod" +msgstr "" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:3 of +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideFixedClipping server-side strategy wrapper." +msgstr "" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:16 of +msgid "Typically, fixedclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.make_ffn.rst:2 +msgid "make\\_ffn" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 +msgid "message\\_size\\_mod" +msgstr "" + +#: flwr.client.mod.comms_mods.message_size_mod:3 of +msgid "This mod logs the size in bytes of the message being transmited." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.parameters_size_mod.rst:2 +msgid "parameters\\_size\\_mod" +msgstr "" + +#: flwr.client.mod.comms_mods.parameters_size_mod:3 of +msgid "" +"This mod logs the number of parameters transmitted in the message as well" +" as their size in bytes." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secagg_mod.rst:2 +msgid "secagg\\_mod" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secaggplus_mod.rst:2 +msgid "secaggplus\\_mod" +msgstr "" + #: ../../source/ref-api/flwr.client.run_client_app.rst:2 msgid "run\\_client\\_app" msgstr "" @@ -8777,6 +9006,18 @@ msgid "" "`\\" msgstr "" +#: flwr.common.EventType.capitalize:1::1 of +msgid "" +":py:obj:`RUN_SUPEREXEC_ENTER " +"`\\" +msgstr "" + +#: flwr.common.EventType.capitalize:1::1 of +msgid "" +":py:obj:`RUN_SUPEREXEC_LEAVE " +"`\\" +msgstr "" + #: flwr.common.EventType.capitalize:3 of msgid "" "More specifically, make the first character have upper case and the rest " @@ -9665,142 +9906,124 @@ msgstr "" msgid "server" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_driver_api `\\ \\(\\)" -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_driver_api:1 of -msgid "Run Flower server (Driver API)." -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -msgid ":py:obj:`run_fleet_api `\\ \\(\\)" -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_fleet_api:1 of -msgid "Run Flower server (Fleet API)." -msgstr "" - -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 msgid ":py:obj:`run_server_app `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.run_serverapp.run_server_app:1 of msgid "Run Flower server app." msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 msgid ":py:obj:`run_superlink `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.app.run_superlink:1 of msgid "Run Flower SuperLink (Driver API and Fleet API)." msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 msgid "" ":py:obj:`start_server `\\ \\(\\*\\[\\, " "server\\_address\\, server\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.app.start_server:1 of msgid "Start a Flower server using the gRPC transport layer." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`ClientManager `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.ClientManager:1 of msgid "Abstract base class for managing Flower clients." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`Driver `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.driver.driver.Driver:1 of msgid "Abstract base Driver class for the Driver API." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`History `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.history.History:1 of msgid "History class for training and/or evaluation metrics collection." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`LegacyContext `\\ \\(state\\[\\, " "config\\, strategy\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.compat.legacy_context.LegacyContext:1 of msgid "Legacy Context." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`Server `\\ \\(\\*\\, client\\_manager\\[\\, " "strategy\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`ServerApp `\\ \\(\\[server\\, config\\, " "strategy\\, ...\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_app.ServerApp:1 of msgid "Flower ServerApp." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid "" ":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\," " round\\_timeout\\]\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_config.ServerConfig:1 of msgid "Flower server config." msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 msgid ":py:obj:`SimpleClientManager `\\ \\(\\)" msgstr "" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.SimpleClientManager:1 of msgid "Provides a pool of available clients." msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 msgid ":py:obj:`flwr.server.strategy `\\" msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.strategy:1 of msgid "Contains the strategy abstraction and different implementations." msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 msgid ":py:obj:`flwr.server.workflow `\\" msgstr "" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.workflow:1 of msgid "Workflows." msgstr "" @@ -10046,13 +10269,6 @@ msgstr "" msgid "**replies** -- An iterable of reply messages received from the SuperLink." msgstr "" -#: flwr.server.driver.driver.Driver.send_and_receive:18 -#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 -#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 -#: of -msgid "Notes" -msgstr "" - #: flwr.server.driver.driver.Driver.send_and_receive:19 of msgid "" "This method uses `push_messages` to send the messages and `pull_messages`" @@ -11422,12 +11638,6 @@ msgid "" "value of 1.0 or higher is recommended for strong privacy." msgstr "" -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 -#: of -msgid "The value of the clipping norm." -msgstr "" - #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:26 #: of msgid "" @@ -11715,7 +11925,7 @@ msgid "" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst:2 -#: ../../source/ref-changelog.md:905 +#: ../../source/ref-changelog.md:997 msgid "FedAdagrad" msgstr "" @@ -13558,122 +13768,576 @@ msgid "Changelog" msgstr "" #: ../../source/ref-changelog.md:3 -msgid "Unreleased" -msgstr "" - -#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:19 -#: ../../source/ref-changelog.md:83 ../../source/ref-changelog.md:176 -#: ../../source/ref-changelog.md:276 ../../source/ref-changelog.md:360 -#: ../../source/ref-changelog.md:424 ../../source/ref-changelog.md:482 -#: ../../source/ref-changelog.md:551 ../../source/ref-changelog.md:680 -#: ../../source/ref-changelog.md:722 ../../source/ref-changelog.md:789 -#: ../../source/ref-changelog.md:855 ../../source/ref-changelog.md:900 -#: ../../source/ref-changelog.md:939 ../../source/ref-changelog.md:972 -#: ../../source/ref-changelog.md:1022 -msgid "What's new?" +msgid "v1.9.0 (2024-06-10)" msgstr "" -#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:71 -#: ../../source/ref-changelog.md:146 ../../source/ref-changelog.md:258 -#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:412 -#: ../../source/ref-changelog.md:470 ../../source/ref-changelog.md:539 -#: ../../source/ref-changelog.md:601 ../../source/ref-changelog.md:620 -#: ../../source/ref-changelog.md:776 ../../source/ref-changelog.md:847 -#: ../../source/ref-changelog.md:884 ../../source/ref-changelog.md:927 -msgid "Incompatible changes" +#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:105 +#: ../../source/ref-changelog.md:169 ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:362 ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:510 ../../source/ref-changelog.md:568 +#: ../../source/ref-changelog.md:637 ../../source/ref-changelog.md:706 +msgid "Thanks to our contributors" msgstr "" -#: ../../source/ref-changelog.md:9 ../../source/ref-changelog.md:73 -#: ../../source/ref-changelog.md:350 ../../source/ref-changelog.md:414 -#: ../../source/ref-changelog.md:472 ../../source/ref-changelog.md:541 -#: ../../source/ref-changelog.md:603 -msgid "None" +#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:107 +#: ../../source/ref-changelog.md:171 ../../source/ref-changelog.md:264 +#: ../../source/ref-changelog.md:364 ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:512 ../../source/ref-changelog.md:570 +msgid "" +"We would like to give our special thanks to all the contributors who made" +" the new version of Flower possible (in `git shortlog` order):" msgstr "" -#: ../../source/ref-changelog.md:11 -msgid "v1.8.0 (2024-04-03)" +#: ../../source/ref-changelog.md:9 +msgid "" +"`Adam Narozniak`, `Charles Beauville`, `Chong Shen Ng`, `Daniel J. " +"Beutel`, `Daniel Nata Nugraha`, `Heng Pan`, `Javier`, `Mahdi Beitollahi`," +" `Robert Steiner`, `Taner Topal`, `Yan Gao`, `bapic`, `mohammadnaseri` " msgstr "" -#: ../../source/ref-changelog.md:13 ../../source/ref-changelog.md:77 -#: ../../source/ref-changelog.md:170 ../../source/ref-changelog.md:270 -#: ../../source/ref-changelog.md:354 ../../source/ref-changelog.md:418 -#: ../../source/ref-changelog.md:476 ../../source/ref-changelog.md:545 -#: ../../source/ref-changelog.md:614 -msgid "Thanks to our contributors" +#: ../../source/ref-changelog.md:11 ../../source/ref-changelog.md:111 +#: ../../source/ref-changelog.md:175 ../../source/ref-changelog.md:268 +#: ../../source/ref-changelog.md:368 ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:516 ../../source/ref-changelog.md:574 +#: ../../source/ref-changelog.md:643 ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:814 ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:947 ../../source/ref-changelog.md:992 +#: ../../source/ref-changelog.md:1031 ../../source/ref-changelog.md:1064 +#: ../../source/ref-changelog.md:1114 +msgid "What's new?" msgstr "" -#: ../../source/ref-changelog.md:15 ../../source/ref-changelog.md:79 -#: ../../source/ref-changelog.md:172 ../../source/ref-changelog.md:272 -#: ../../source/ref-changelog.md:356 ../../source/ref-changelog.md:420 -#: ../../source/ref-changelog.md:478 +#: ../../source/ref-changelog.md:13 msgid "" -"We would like to give our special thanks to all the contributors who made" -" the new version of Flower possible (in `git shortlog` order):" +"**Introduce built-in authentication (preview)** " +"([#2946](https://github.com/adap/flower/pull/2946), " +"[#3388](https://github.com/adap/flower/pull/3388), " +"[#2948](https://github.com/adap/flower/pull/2948), " +"[#2917](https://github.com/adap/flower/pull/2917), " +"[#3386](https://github.com/adap/flower/pull/3386), " +"[#3308](https://github.com/adap/flower/pull/3308), " +"[#3001](https://github.com/adap/flower/pull/3001), " +"[#3409](https://github.com/adap/flower/pull/3409), " +"[#2999](https://github.com/adap/flower/pull/2999), " +"[#2979](https://github.com/adap/flower/pull/2979), " +"[#3389](https://github.com/adap/flower/pull/3389), " +"[#3503](https://github.com/adap/flower/pull/3503), " +"[#3366](https://github.com/adap/flower/pull/3366), " +"[#3357](https://github.com/adap/flower/pull/3357))" +msgstr "" + +#: ../../source/ref-changelog.md:15 +msgid "" +"Flower 1.9 introduces the first build-in version of client node " +"authentication. In previous releases, users often wrote glue code to " +"connect Flower to external authentication systems. With this release, the" +" SuperLink can authenticate SuperNodes using a built-in authentication " +"system. A new [how-to guide](https://flower.ai/docs/framework/how-to-" +"authenticate-supernodes.html) and a new [code " +"example](https://github.com/adap/flower/tree/main/examples/flower-" +"authentication) help you to get started." msgstr "" #: ../../source/ref-changelog.md:17 msgid "" -"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " -"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear " -"Ashimine`, `Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, " -"`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " -"`tabdar-khan` " +"This is the first preview release of the Flower-native authentication " +"system. Many additional features are on the roadmap for upcoming Flower " +"releases - stay tuned." +msgstr "" + +#: ../../source/ref-changelog.md:19 +msgid "" +"**Introduce end-to-end Docker support** " +"([#3483](https://github.com/adap/flower/pull/3483), " +"[#3266](https://github.com/adap/flower/pull/3266), " +"[#3390](https://github.com/adap/flower/pull/3390), " +"[#3283](https://github.com/adap/flower/pull/3283), " +"[#3285](https://github.com/adap/flower/pull/3285), " +"[#3391](https://github.com/adap/flower/pull/3391), " +"[#3403](https://github.com/adap/flower/pull/3403), " +"[#3458](https://github.com/adap/flower/pull/3458), " +"[#3533](https://github.com/adap/flower/pull/3533), " +"[#3453](https://github.com/adap/flower/pull/3453), " +"[#3486](https://github.com/adap/flower/pull/3486), " +"[#3290](https://github.com/adap/flower/pull/3290))" msgstr "" #: ../../source/ref-changelog.md:21 msgid "" -"**Introduce Flower Next high-level API (stable)** " -"([#3002](https://github.com/adap/flower/pull/3002), " -"[#2934](https://github.com/adap/flower/pull/2934), " -"[#2958](https://github.com/adap/flower/pull/2958), " -"[#3173](https://github.com/adap/flower/pull/3173), " -"[#3174](https://github.com/adap/flower/pull/3174), " -"[#2923](https://github.com/adap/flower/pull/2923), " -"[#2691](https://github.com/adap/flower/pull/2691), " -"[#3079](https://github.com/adap/flower/pull/3079), " -"[#2961](https://github.com/adap/flower/pull/2961), " -"[#2924](https://github.com/adap/flower/pull/2924), " -"[#3166](https://github.com/adap/flower/pull/3166), " -"[#3031](https://github.com/adap/flower/pull/3031), " -"[#3057](https://github.com/adap/flower/pull/3057), " -"[#3000](https://github.com/adap/flower/pull/3000), " -"[#3113](https://github.com/adap/flower/pull/3113), " -"[#2957](https://github.com/adap/flower/pull/2957), " -"[#3183](https://github.com/adap/flower/pull/3183), " -"[#3180](https://github.com/adap/flower/pull/3180), " -"[#3035](https://github.com/adap/flower/pull/3035), " -"[#3189](https://github.com/adap/flower/pull/3189), " -"[#3185](https://github.com/adap/flower/pull/3185), " -"[#3190](https://github.com/adap/flower/pull/3190), " -"[#3191](https://github.com/adap/flower/pull/3191), " -"[#3195](https://github.com/adap/flower/pull/3195), " -"[#3197](https://github.com/adap/flower/pull/3197))" +"Full Flower Next Docker support is here! With the release of Flower 1.9, " +"Flower provides stable Docker images for the Flower SuperLink, the Flower" +" SuperNode, and the Flower `ServerApp`. This set of images enables you to" +" run all Flower components in Docker. Check out the new [how-to " +"guide](https://flower.ai/docs/framework/how-to-run-flower-using-" +"docker.html) to get stated." msgstr "" #: ../../source/ref-changelog.md:23 msgid "" -"The Flower Next high-level API is stable! Flower Next is the future of " -"Flower - all new features (like Flower Mods) will be built on top of it. " -"You can start to migrate your existing projects to Flower Next by using " -"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or " -"`quickstart-tensorflow`, a detailed migration guide will follow shortly)." -" Flower Next allows you to run multiple projects concurrently (we call " -"this multi-run) and execute the same project in either simulation " -"environments or deployment environments without having to change a single" -" line of code. The best part? It's fully compatible with existing Flower " -"projects that use `Strategy`, `NumPyClient` & co." +"**Re-architect Flower Next simulation engine** " +"([#3307](https://github.com/adap/flower/pull/3307), " +"[#3355](https://github.com/adap/flower/pull/3355), " +"[#3272](https://github.com/adap/flower/pull/3272), " +"[#3273](https://github.com/adap/flower/pull/3273), " +"[#3417](https://github.com/adap/flower/pull/3417), " +"[#3281](https://github.com/adap/flower/pull/3281), " +"[#3343](https://github.com/adap/flower/pull/3343), " +"[#3326](https://github.com/adap/flower/pull/3326))" msgstr "" #: ../../source/ref-changelog.md:25 msgid "" -"**Introduce Flower Next low-level API (preview)** " -"([#3062](https://github.com/adap/flower/pull/3062), " -"[#3034](https://github.com/adap/flower/pull/3034), " -"[#3069](https://github.com/adap/flower/pull/3069))" +"Flower Next simulations now use a new in-memory `Driver` that improves " +"the reliability of simulations, especially in notebook environments. This" +" is a significant step towards a complete overhaul of the Flower Next " +"simulation architecture." msgstr "" #: ../../source/ref-changelog.md:27 msgid "" +"**Upgrade simulation engine** " +"([#3354](https://github.com/adap/flower/pull/3354), " +"[#3378](https://github.com/adap/flower/pull/3378), " +"[#3262](https://github.com/adap/flower/pull/3262), " +"[#3435](https://github.com/adap/flower/pull/3435), " +"[#3501](https://github.com/adap/flower/pull/3501), " +"[#3482](https://github.com/adap/flower/pull/3482), " +"[#3494](https://github.com/adap/flower/pull/3494))" +msgstr "" + +#: ../../source/ref-changelog.md:29 +msgid "" +"The Flower Next simulation engine comes with improved and configurable " +"logging. The Ray-based simulation backend in Flower 1.9 was updated to " +"use Ray 2.10." +msgstr "" + +#: ../../source/ref-changelog.md:31 +msgid "" +"**Introduce FedPFT baseline** " +"([#3268](https://github.com/adap/flower/pull/3268))" +msgstr "" + +#: ../../source/ref-changelog.md:33 +msgid "" +"FedPFT allows you to perform one-shot Federated Learning by leveraging " +"widely available foundational models, dramatically reducing communication" +" costs while delivering high performing models. This is work led by Mahdi" +" Beitollahi from Huawei Noah's Ark Lab (Montreal, Canada). Read all the " +"details in their paper: \"Parametric Feature Transfer: One-shot Federated" +" Learning with Foundation Models\" " +"([arxiv](https://arxiv.org/abs/2402.01862))" +msgstr "" + +#: ../../source/ref-changelog.md:35 +msgid "" +"**Launch additional** `flwr new` **templates for Apple MLX, Hugging Face " +"Transformers, scikit-learn and TensorFlow** " +"([#3291](https://github.com/adap/flower/pull/3291), " +"[#3139](https://github.com/adap/flower/pull/3139), " +"[#3284](https://github.com/adap/flower/pull/3284), " +"[#3251](https://github.com/adap/flower/pull/3251), " +"[#3376](https://github.com/adap/flower/pull/3376), " +"[#3287](https://github.com/adap/flower/pull/3287))" +msgstr "" + +#: ../../source/ref-changelog.md:37 +msgid "" +"The `flwr` CLI's `flwr new` command is starting to become everone's " +"favorite way of creating new Flower projects. This release introduces " +"additional `flwr new` templates for Apple MLX, Hugging Face Transformers," +" scikit-learn and TensorFlow. In addition to that, existing templates " +"also received updates." +msgstr "" + +#: ../../source/ref-changelog.md:39 +msgid "" +"**Refine** `RecordSet` **API** " +"([#3209](https://github.com/adap/flower/pull/3209), " +"[#3331](https://github.com/adap/flower/pull/3331), " +"[#3334](https://github.com/adap/flower/pull/3334), " +"[#3335](https://github.com/adap/flower/pull/3335), " +"[#3375](https://github.com/adap/flower/pull/3375), " +"[#3368](https://github.com/adap/flower/pull/3368))" +msgstr "" + +#: ../../source/ref-changelog.md:41 +msgid "" +"`RecordSet` is part of the Flower Next low-level API preview release. In " +"Flower 1.9, `RecordSet` received a number of usability improvements that " +"make it easier to build `RecordSet`-based `ServerApp`s and `ClientApp`s." +msgstr "" + +#: ../../source/ref-changelog.md:43 +msgid "" +"**Beautify logging** ([#3379](https://github.com/adap/flower/pull/3379), " +"[#3430](https://github.com/adap/flower/pull/3430), " +"[#3461](https://github.com/adap/flower/pull/3461), " +"[#3360](https://github.com/adap/flower/pull/3360), " +"[#3433](https://github.com/adap/flower/pull/3433))" +msgstr "" + +#: ../../source/ref-changelog.md:45 +msgid "" +"Logs received a substantial update. Not only are logs now much nicer to " +"look at, but they are also more configurable." +msgstr "" + +#: ../../source/ref-changelog.md:47 +msgid "" +"**Improve reliability** " +"([#3564](https://github.com/adap/flower/pull/3564), " +"[#3561](https://github.com/adap/flower/pull/3561), " +"[#3566](https://github.com/adap/flower/pull/3566), " +"[#3462](https://github.com/adap/flower/pull/3462), " +"[#3225](https://github.com/adap/flower/pull/3225), " +"[#3514](https://github.com/adap/flower/pull/3514), " +"[#3535](https://github.com/adap/flower/pull/3535), " +"[#3372](https://github.com/adap/flower/pull/3372))" +msgstr "" + +#: ../../source/ref-changelog.md:49 +msgid "" +"Flower 1.9 includes reliability improvements across many parts of the " +"system. One example is a much improved SuperNode shutdown procedure." +msgstr "" + +#: ../../source/ref-changelog.md:51 +msgid "" +"**Update Swift and C++ SDKs** " +"([#3321](https://github.com/adap/flower/pull/3321), " +"[#2763](https://github.com/adap/flower/pull/2763))" +msgstr "" + +#: ../../source/ref-changelog.md:53 +msgid "" +"In the C++ SDK, communication-related code is now separate from main " +"client logic. A new abstract class `Communicator` has been introduced " +"alongside a gRPC implementation of it." +msgstr "" + +#: ../../source/ref-changelog.md:55 +msgid "" +"**Improve testing, tooling and CI/CD infrastructure** " +"([#3294](https://github.com/adap/flower/pull/3294), " +"[#3282](https://github.com/adap/flower/pull/3282), " +"[#3311](https://github.com/adap/flower/pull/3311), " +"[#2878](https://github.com/adap/flower/pull/2878), " +"[#3333](https://github.com/adap/flower/pull/3333), " +"[#3255](https://github.com/adap/flower/pull/3255), " +"[#3349](https://github.com/adap/flower/pull/3349), " +"[#3400](https://github.com/adap/flower/pull/3400), " +"[#3401](https://github.com/adap/flower/pull/3401), " +"[#3399](https://github.com/adap/flower/pull/3399), " +"[#3346](https://github.com/adap/flower/pull/3346), " +"[#3398](https://github.com/adap/flower/pull/3398), " +"[#3397](https://github.com/adap/flower/pull/3397), " +"[#3347](https://github.com/adap/flower/pull/3347), " +"[#3502](https://github.com/adap/flower/pull/3502), " +"[#3387](https://github.com/adap/flower/pull/3387), " +"[#3542](https://github.com/adap/flower/pull/3542), " +"[#3396](https://github.com/adap/flower/pull/3396), " +"[#3496](https://github.com/adap/flower/pull/3496), " +"[#3465](https://github.com/adap/flower/pull/3465), " +"[#3473](https://github.com/adap/flower/pull/3473), " +"[#3484](https://github.com/adap/flower/pull/3484), " +"[#3521](https://github.com/adap/flower/pull/3521), " +"[#3363](https://github.com/adap/flower/pull/3363), " +"[#3497](https://github.com/adap/flower/pull/3497), " +"[#3464](https://github.com/adap/flower/pull/3464), " +"[#3495](https://github.com/adap/flower/pull/3495), " +"[#3478](https://github.com/adap/flower/pull/3478), " +"[#3271](https://github.com/adap/flower/pull/3271))" +msgstr "" + +#: ../../source/ref-changelog.md:57 +msgid "" +"As always, the Flower tooling, testing, and CI/CD infrastructure has " +"received many updates." +msgstr "" + +#: ../../source/ref-changelog.md:59 +msgid "" +"**Improve documentation** " +"([#3530](https://github.com/adap/flower/pull/3530), " +"[#3539](https://github.com/adap/flower/pull/3539), " +"[#3425](https://github.com/adap/flower/pull/3425), " +"[#3520](https://github.com/adap/flower/pull/3520), " +"[#3286](https://github.com/adap/flower/pull/3286), " +"[#3516](https://github.com/adap/flower/pull/3516), " +"[#3523](https://github.com/adap/flower/pull/3523), " +"[#3545](https://github.com/adap/flower/pull/3545), " +"[#3498](https://github.com/adap/flower/pull/3498), " +"[#3439](https://github.com/adap/flower/pull/3439), " +"[#3440](https://github.com/adap/flower/pull/3440), " +"[#3382](https://github.com/adap/flower/pull/3382), " +"[#3559](https://github.com/adap/flower/pull/3559), " +"[#3432](https://github.com/adap/flower/pull/3432), " +"[#3278](https://github.com/adap/flower/pull/3278), " +"[#3371](https://github.com/adap/flower/pull/3371), " +"[#3519](https://github.com/adap/flower/pull/3519), " +"[#3267](https://github.com/adap/flower/pull/3267), " +"[#3204](https://github.com/adap/flower/pull/3204), " +"[#3274](https://github.com/adap/flower/pull/3274))" +msgstr "" + +#: ../../source/ref-changelog.md:61 +msgid "" +"As always, the Flower documentation has received many updates. Notable " +"new pages include:" +msgstr "" + +#: ../../source/ref-changelog.md:63 +msgid "" +"[How-to upgrate to Flower Next (Flower Next migration " +"guide)](https://flower.ai/docs/framework/how-to-upgrade-to-flower-" +"next.html)" +msgstr "" + +#: ../../source/ref-changelog.md:65 +msgid "" +"[How-to run Flower using Docker](https://flower.ai/docs/framework/how-to-" +"run-flower-using-docker.html)" +msgstr "" + +#: ../../source/ref-changelog.md:67 +msgid "" +"[Flower Mods reference](https://flower.ai/docs/framework/ref-" +"api/flwr.client.mod.html#module-flwr.client.mod)" +msgstr "" + +#: ../../source/ref-changelog.md:69 +msgid "" +"**General updates to Flower Examples** " +"([#3205](https://github.com/adap/flower/pull/3205), " +"[#3226](https://github.com/adap/flower/pull/3226), " +"[#3211](https://github.com/adap/flower/pull/3211), " +"[#3252](https://github.com/adap/flower/pull/3252), " +"[#3427](https://github.com/adap/flower/pull/3427), " +"[#3410](https://github.com/adap/flower/pull/3410), " +"[#3426](https://github.com/adap/flower/pull/3426), " +"[#3228](https://github.com/adap/flower/pull/3228), " +"[#3342](https://github.com/adap/flower/pull/3342), " +"[#3200](https://github.com/adap/flower/pull/3200), " +"[#3202](https://github.com/adap/flower/pull/3202), " +"[#3394](https://github.com/adap/flower/pull/3394), " +"[#3488](https://github.com/adap/flower/pull/3488), " +"[#3329](https://github.com/adap/flower/pull/3329), " +"[#3526](https://github.com/adap/flower/pull/3526), " +"[#3392](https://github.com/adap/flower/pull/3392), " +"[#3474](https://github.com/adap/flower/pull/3474), " +"[#3269](https://github.com/adap/flower/pull/3269))" +msgstr "" + +#: ../../source/ref-changelog.md:71 +msgid "As always, Flower code examples have received many updates." +msgstr "" + +#: ../../source/ref-changelog.md:73 +msgid "" +"**General improvements** " +"([#3532](https://github.com/adap/flower/pull/3532), " +"[#3318](https://github.com/adap/flower/pull/3318), " +"[#3565](https://github.com/adap/flower/pull/3565), " +"[#3296](https://github.com/adap/flower/pull/3296), " +"[#3305](https://github.com/adap/flower/pull/3305), " +"[#3246](https://github.com/adap/flower/pull/3246), " +"[#3224](https://github.com/adap/flower/pull/3224), " +"[#3475](https://github.com/adap/flower/pull/3475), " +"[#3297](https://github.com/adap/flower/pull/3297), " +"[#3317](https://github.com/adap/flower/pull/3317), " +"[#3429](https://github.com/adap/flower/pull/3429), " +"[#3196](https://github.com/adap/flower/pull/3196), " +"[#3534](https://github.com/adap/flower/pull/3534), " +"[#3240](https://github.com/adap/flower/pull/3240), " +"[#3365](https://github.com/adap/flower/pull/3365), " +"[#3407](https://github.com/adap/flower/pull/3407), " +"[#3563](https://github.com/adap/flower/pull/3563), " +"[#3344](https://github.com/adap/flower/pull/3344), " +"[#3330](https://github.com/adap/flower/pull/3330), " +"[#3436](https://github.com/adap/flower/pull/3436), " +"[#3300](https://github.com/adap/flower/pull/3300), " +"[#3327](https://github.com/adap/flower/pull/3327), " +"[#3254](https://github.com/adap/flower/pull/3254), " +"[#3253](https://github.com/adap/flower/pull/3253), " +"[#3419](https://github.com/adap/flower/pull/3419), " +"[#3289](https://github.com/adap/flower/pull/3289), " +"[#3208](https://github.com/adap/flower/pull/3208), " +"[#3245](https://github.com/adap/flower/pull/3245), " +"[#3319](https://github.com/adap/flower/pull/3319), " +"[#3203](https://github.com/adap/flower/pull/3203), " +"[#3423](https://github.com/adap/flower/pull/3423), " +"[#3352](https://github.com/adap/flower/pull/3352), " +"[#3292](https://github.com/adap/flower/pull/3292), " +"[#3261](https://github.com/adap/flower/pull/3261))" +msgstr "" + +#: ../../source/ref-changelog.md:75 ../../source/ref-changelog.md:1058 +msgid "Deprecations" +msgstr "" + +#: ../../source/ref-changelog.md:77 +msgid "**Deprecate Python 3.8 support**" +msgstr "" + +#: ../../source/ref-changelog.md:79 +msgid "" +"Python 3.8 will stop receiving security fixes in [October " +"2024](https://devguide.python.org/versions/). Support for Python 3.8 is " +"now deprecated and will be removed in an upcoming release." +msgstr "" + +#: ../../source/ref-changelog.md:81 +msgid "" +"**Deprecate (experimental)** `flower-driver-api` **and** `flower-fleet-" +"api` ([#3416](https://github.com/adap/flower/pull/3416), " +"[#3420](https://github.com/adap/flower/pull/3420))" +msgstr "" + +#: ../../source/ref-changelog.md:83 +msgid "" +"Flower 1.9 deprecates the two (experimental) commands `flower-driver-api`" +" and `flower-fleet-api`. Both commands will be removed in an upcoming " +"release. Use `flower-superlink` instead." +msgstr "" + +#: ../../source/ref-changelog.md:85 +msgid "" +"**Deprecate** `--server` **in favor of** `--superlink` " +"([#3518](https://github.com/adap/flower/pull/3518))" +msgstr "" + +#: ../../source/ref-changelog.md:87 +msgid "" +"The commands `flower-server-app` and `flower-client-app` should use " +"`--superlink` instead of the now deprecated `--server`. Support for " +"`--server` will be removed in a future release." +msgstr "" + +#: ../../source/ref-changelog.md:89 ../../source/ref-changelog.md:163 +#: ../../source/ref-changelog.md:238 ../../source/ref-changelog.md:350 +#: ../../source/ref-changelog.md:440 ../../source/ref-changelog.md:504 +#: ../../source/ref-changelog.md:562 ../../source/ref-changelog.md:631 +#: ../../source/ref-changelog.md:693 ../../source/ref-changelog.md:712 +#: ../../source/ref-changelog.md:868 ../../source/ref-changelog.md:939 +#: ../../source/ref-changelog.md:976 ../../source/ref-changelog.md:1019 +msgid "Incompatible changes" +msgstr "" + +#: ../../source/ref-changelog.md:91 +msgid "" +"**Replace** `flower-superlink` **CLI option** `--certificates` **with** " +"`--ssl-ca-certfile` **,** `--ssl-certfile` **and** `--ssl-keyfile` " +"([#3512](https://github.com/adap/flower/pull/3512), " +"[#3408](https://github.com/adap/flower/pull/3408))" +msgstr "" + +#: ../../source/ref-changelog.md:93 +msgid "" +"SSL-related `flower-superlink` CLI arguments were restructured in an " +"incompatible way. Instead of passing a single `--certificates` flag with " +"three values, you now need to pass three flags (`--ssl-ca-certfile`, " +"`--ssl-certfile` and `--ssl-keyfile`) with one value each. Check out the " +"[SSL connections](https://flower.ai/docs/framework/how-to-enable-ssl-" +"connections.html) documentation page for details." +msgstr "" + +#: ../../source/ref-changelog.md:95 +msgid "" +"**Remove SuperLink** `--vce` **option** " +"([#3513](https://github.com/adap/flower/pull/3513))" +msgstr "" + +#: ../../source/ref-changelog.md:97 +msgid "" +"Instead of separately starting a SuperLink and a `ServerApp` for " +"simulation, simulations must now be started using the single `flower-" +"simulation` command." +msgstr "" + +#: ../../source/ref-changelog.md:99 +msgid "" +"**Merge** `--grpc-rere` **and** `--rest` **SuperLink options** " +"([#3527](https://github.com/adap/flower/pull/3527))" +msgstr "" + +#: ../../source/ref-changelog.md:101 +msgid "" +"To simplify the usage of `flower-superlink`, previously separate sets of " +"CLI options for gRPC and REST were merged into one unified set of " +"options. Consult the [Flower CLI reference " +"documentation](https://flower.ai/docs/framework/ref-api-cli.html) for " +"details." +msgstr "" + +#: ../../source/ref-changelog.md:103 +msgid "v1.8.0 (2024-04-03)" +msgstr "" + +#: ../../source/ref-changelog.md:109 +msgid "" +"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " +"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear " +"Ashimine`, `Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, " +"`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " +"`tabdar-khan` " +msgstr "" + +#: ../../source/ref-changelog.md:113 +msgid "" +"**Introduce Flower Next high-level API (stable)** " +"([#3002](https://github.com/adap/flower/pull/3002), " +"[#2934](https://github.com/adap/flower/pull/2934), " +"[#2958](https://github.com/adap/flower/pull/2958), " +"[#3173](https://github.com/adap/flower/pull/3173), " +"[#3174](https://github.com/adap/flower/pull/3174), " +"[#2923](https://github.com/adap/flower/pull/2923), " +"[#2691](https://github.com/adap/flower/pull/2691), " +"[#3079](https://github.com/adap/flower/pull/3079), " +"[#2961](https://github.com/adap/flower/pull/2961), " +"[#2924](https://github.com/adap/flower/pull/2924), " +"[#3166](https://github.com/adap/flower/pull/3166), " +"[#3031](https://github.com/adap/flower/pull/3031), " +"[#3057](https://github.com/adap/flower/pull/3057), " +"[#3000](https://github.com/adap/flower/pull/3000), " +"[#3113](https://github.com/adap/flower/pull/3113), " +"[#2957](https://github.com/adap/flower/pull/2957), " +"[#3183](https://github.com/adap/flower/pull/3183), " +"[#3180](https://github.com/adap/flower/pull/3180), " +"[#3035](https://github.com/adap/flower/pull/3035), " +"[#3189](https://github.com/adap/flower/pull/3189), " +"[#3185](https://github.com/adap/flower/pull/3185), " +"[#3190](https://github.com/adap/flower/pull/3190), " +"[#3191](https://github.com/adap/flower/pull/3191), " +"[#3195](https://github.com/adap/flower/pull/3195), " +"[#3197](https://github.com/adap/flower/pull/3197))" +msgstr "" + +#: ../../source/ref-changelog.md:115 +msgid "" +"The Flower Next high-level API is stable! Flower Next is the future of " +"Flower - all new features (like Flower Mods) will be built on top of it. " +"You can start to migrate your existing projects to Flower Next by using " +"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or " +"`quickstart-tensorflow`, a detailed migration guide will follow shortly)." +" Flower Next allows you to run multiple projects concurrently (we call " +"this multi-run) and execute the same project in either simulation " +"environments or deployment environments without having to change a single" +" line of code. The best part? It's fully compatible with existing Flower " +"projects that use `Strategy`, `NumPyClient` & co." +msgstr "" + +#: ../../source/ref-changelog.md:117 +msgid "" +"**Introduce Flower Next low-level API (preview)** " +"([#3062](https://github.com/adap/flower/pull/3062), " +"[#3034](https://github.com/adap/flower/pull/3034), " +"[#3069](https://github.com/adap/flower/pull/3069))" +msgstr "" + +#: ../../source/ref-changelog.md:119 +msgid "" "In addition to the Flower Next *high-level* API that uses `Strategy`, " "`NumPyClient` & co, Flower 1.8 also comes with a preview version of the " "new Flower Next *low-level* API. The low-level API allows for granular " @@ -13689,7 +14353,7 @@ msgid "" "custom SMPC protocols, to name just a few." msgstr "" -#: ../../source/ref-changelog.md:29 +#: ../../source/ref-changelog.md:121 msgid "" "**Introduce Flower Mods (preview)** " "([#3054](https://github.com/adap/flower/pull/3054), " @@ -13697,7 +14361,7 @@ msgid "" "[#3083](https://github.com/adap/flower/pull/3083))" msgstr "" -#: ../../source/ref-changelog.md:31 +#: ../../source/ref-changelog.md:123 msgid "" "Flower Modifiers (we call them Mods) can intercept messages and analyze, " "edit or handle them directly. Mods can be used to develop pluggable " @@ -13709,7 +14373,7 @@ msgid "" "can already use it to experiment with arbirtrary SMPC protocols." msgstr "" -#: ../../source/ref-changelog.md:33 +#: ../../source/ref-changelog.md:125 msgid "" "**Fine-tune LLMs with LLM FlowerTune** " "([#3029](https://github.com/adap/flower/pull/3029), " @@ -13721,7 +14385,7 @@ msgid "" "[#3172](https://github.com/adap/flower/pull/3172))" msgstr "" -#: ../../source/ref-changelog.md:35 +#: ../../source/ref-changelog.md:127 msgid "" "We are introducing LLM FlowerTune, an introductory example that " "demonstrates federated LLM fine-tuning of pre-trained Llama2 models on " @@ -13731,7 +14395,7 @@ msgid "" "-llm-flowertune-federated-llm-finetuning-with-flower/) for more details." msgstr "" -#: ../../source/ref-changelog.md:37 +#: ../../source/ref-changelog.md:129 msgid "" "**Introduce built-in Differential Privacy (preview)** " "([#2798](https://github.com/adap/flower/pull/2798), " @@ -13745,7 +14409,7 @@ msgid "" "[#3074](https://github.com/adap/flower/pull/3074))" msgstr "" -#: ../../source/ref-changelog.md:39 +#: ../../source/ref-changelog.md:131 msgid "" "Built-in Differential Privacy is here! Flower supports both central and " "local differential privacy (DP). Central DP can be configured with either" @@ -13758,7 +14422,7 @@ msgid "" "/how-to-use-differential-privacy.html) in Flower." msgstr "" -#: ../../source/ref-changelog.md:41 +#: ../../source/ref-changelog.md:133 msgid "" "**Introduce built-in Secure Aggregation (preview)** " "([#3120](https://github.com/adap/flower/pull/3120), " @@ -13766,7 +14430,7 @@ msgid "" "[#3108](https://github.com/adap/flower/pull/3108))" msgstr "" -#: ../../source/ref-changelog.md:43 +#: ../../source/ref-changelog.md:135 msgid "" "Built-in Secure Aggregation is here! Flower now supports different secure" " aggregation protocols out-of-the-box. The best part? You can add secure " @@ -13779,7 +14443,7 @@ msgid "" "in the same project." msgstr "" -#: ../../source/ref-changelog.md:45 +#: ../../source/ref-changelog.md:137 msgid "" "**Introduce** `flwr` **CLI (preview)** " "([#2942](https://github.com/adap/flower/pull/2942), " @@ -13793,13 +14457,13 @@ msgid "" "[#3142](https://github.com/adap/flower/pull/3142))" msgstr "" -#: ../../source/ref-changelog.md:47 +#: ../../source/ref-changelog.md:139 msgid "" "A new `flwr` CLI command allows creating new Flower projects (`flwr new`)" " and then running them using the Simulation Engine (`flwr run`)." msgstr "" -#: ../../source/ref-changelog.md:49 +#: ../../source/ref-changelog.md:141 msgid "" "**Introduce Flower Next Simulation Engine** " "([#3024](https://github.com/adap/flower/pull/3024), " @@ -13814,20 +14478,20 @@ msgid "" "[#3008](https://github.com/adap/flower/pull/3008))" msgstr "" -#: ../../source/ref-changelog.md:51 +#: ../../source/ref-changelog.md:143 msgid "" "The Flower Simulation Engine can now run Flower Next projects. For " "notebook environments, there's also a new `run_simulation` function that " "can run `ServerApp` and `ClientApp`." msgstr "" -#: ../../source/ref-changelog.md:53 +#: ../../source/ref-changelog.md:145 msgid "" "**Handle SuperNode connection errors** " "([#2969](https://github.com/adap/flower/pull/2969))" msgstr "" -#: ../../source/ref-changelog.md:55 +#: ../../source/ref-changelog.md:147 msgid "" "A SuperNode will now try to reconnect indefinitely to the SuperLink in " "case of connection errors. The arguments `--max-retries` and `--max-wait-" @@ -13838,7 +14502,7 @@ msgid "" "reconnect to the SuperLink." msgstr "" -#: ../../source/ref-changelog.md:57 +#: ../../source/ref-changelog.md:149 msgid "" "**General updates to Flower Baselines** " "([#2904](https://github.com/adap/flower/pull/2904), " @@ -13847,13 +14511,13 @@ msgid "" "[#2968](https://github.com/adap/flower/pull/2968))" msgstr "" -#: ../../source/ref-changelog.md:59 +#: ../../source/ref-changelog.md:151 msgid "" "There's a new [FedStar](https://flower.ai/docs/baselines/fedstar.html) " "baseline. Several other baselined have been updated as well." msgstr "" -#: ../../source/ref-changelog.md:61 +#: ../../source/ref-changelog.md:153 msgid "" "**Improve documentation and translations** " "([#3050](https://github.com/adap/flower/pull/3050), " @@ -13874,14 +14538,14 @@ msgid "" "[#2989](https://github.com/adap/flower/pull/2989))" msgstr "" -#: ../../source/ref-changelog.md:63 +#: ../../source/ref-changelog.md:155 msgid "" "As usual, we merged many smaller and larger improvements to the " "documentation. A special thank you goes to [Sebastian van der " "Voort](https://github.com/svdvoort) for landing a big documentation PR!" msgstr "" -#: ../../source/ref-changelog.md:65 +#: ../../source/ref-changelog.md:157 msgid "" "**General updates to Flower Examples** " "([3134](https://github.com/adap/flower/pull/3134), " @@ -13897,7 +14561,7 @@ msgid "" "[#3117](https://github.com/adap/flower/pull/3117))" msgstr "" -#: ../../source/ref-changelog.md:67 +#: ../../source/ref-changelog.md:159 msgid "" "Two new examples show federated training of a Vision Transformer (ViT) " "and federated learning in a medical context using the popular MONAI " @@ -13906,7 +14570,7 @@ msgid "" "received considerable updates as well." msgstr "" -#: ../../source/ref-changelog.md:69 +#: ../../source/ref-changelog.md:161 msgid "" "**General improvements** " "([#3171](https://github.com/adap/flower/pull/3171), " @@ -13984,11 +14648,17 @@ msgid "" "[#2954](https://github.com/adap/flower/pull/2954))" msgstr "" -#: ../../source/ref-changelog.md:75 +#: ../../source/ref-changelog.md:165 ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:506 ../../source/ref-changelog.md:564 +#: ../../source/ref-changelog.md:633 ../../source/ref-changelog.md:695 +msgid "None" +msgstr "" + +#: ../../source/ref-changelog.md:167 msgid "v1.7.0 (2024-02-05)" msgstr "" -#: ../../source/ref-changelog.md:81 +#: ../../source/ref-changelog.md:173 msgid "" "`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles " "Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo " @@ -13998,7 +14668,7 @@ msgid "" "Shaaban`, `Yan Gao`, `Yasar Abbas` " msgstr "" -#: ../../source/ref-changelog.md:85 +#: ../../source/ref-changelog.md:177 msgid "" "**Introduce stateful clients (experimental)** " "([#2770](https://github.com/adap/flower/pull/2770), " @@ -14008,7 +14678,7 @@ msgid "" "[#2769](https://github.com/adap/flower/pull/2769))" msgstr "" -#: ../../source/ref-changelog.md:87 +#: ../../source/ref-changelog.md:179 msgid "" "Subclasses of `Client` and `NumPyClient` can now store local state that " "remains on the client. Let's start with the highlight first: this new " @@ -14021,13 +14691,13 @@ msgid "" "unified way across simulation and deployment." msgstr "" -#: ../../source/ref-changelog.md:89 +#: ../../source/ref-changelog.md:181 msgid "" "**Improve performance** " "([#2293](https://github.com/adap/flower/pull/2293))" msgstr "" -#: ../../source/ref-changelog.md:91 +#: ../../source/ref-changelog.md:183 msgid "" "Flower is faster than ever. All `FedAvg`-derived strategies now use in-" "place aggregation to reduce memory consumption. The Flower client " @@ -14036,27 +14706,27 @@ msgid "" "training time is short." msgstr "" -#: ../../source/ref-changelog.md:93 +#: ../../source/ref-changelog.md:185 msgid "" "**Support Federated Learning with Apple MLX and Flower** " "([#2693](https://github.com/adap/flower/pull/2693))" msgstr "" -#: ../../source/ref-changelog.md:95 +#: ../../source/ref-changelog.md:187 msgid "" "Flower has official support for federated learning using [Apple " "MLX](https://ml-explore.github.io/mlx) via the new `quickstart-mlx` code " "example." msgstr "" -#: ../../source/ref-changelog.md:97 +#: ../../source/ref-changelog.md:189 msgid "" "**Introduce new XGBoost cyclic strategy** " "([#2666](https://github.com/adap/flower/pull/2666), " "[#2668](https://github.com/adap/flower/pull/2668))" msgstr "" -#: ../../source/ref-changelog.md:99 +#: ../../source/ref-changelog.md:191 msgid "" "A new strategy called `FedXgbCyclic` supports a client-by-client style of" " training (often called cyclic). The `xgboost-comprehensive` code example" @@ -14065,31 +14735,31 @@ msgid "" "offers best-in-class XGBoost support." msgstr "" -#: ../../source/ref-changelog.md:101 +#: ../../source/ref-changelog.md:193 msgid "" "**Support Python 3.11** " "([#2394](https://github.com/adap/flower/pull/2394))" msgstr "" -#: ../../source/ref-changelog.md:103 +#: ../../source/ref-changelog.md:195 msgid "" "Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will " "ensure better support for users using more recent Python versions." msgstr "" -#: ../../source/ref-changelog.md:105 +#: ../../source/ref-changelog.md:197 msgid "" "**Update gRPC and ProtoBuf dependencies** " "([#2814](https://github.com/adap/flower/pull/2814))" msgstr "" -#: ../../source/ref-changelog.md:107 +#: ../../source/ref-changelog.md:199 msgid "" "The `grpcio` and `protobuf` dependencies were updated to their latest " "versions for improved security and performance." msgstr "" -#: ../../source/ref-changelog.md:109 +#: ../../source/ref-changelog.md:201 msgid "" "**Introduce Docker image for Flower server** " "([#2700](https://github.com/adap/flower/pull/2700), " @@ -14103,7 +14773,7 @@ msgid "" "[#2701](https://github.com/adap/flower/pull/2701))" msgstr "" -#: ../../source/ref-changelog.md:111 +#: ../../source/ref-changelog.md:203 msgid "" "The Flower server can now be run using an official Docker image. A new " "how-to guide explains [how to run Flower using " @@ -14111,25 +14781,25 @@ msgid "" "docker.html). An official Flower client Docker image will follow." msgstr "" -#: ../../source/ref-changelog.md:113 +#: ../../source/ref-changelog.md:205 msgid "" "**Introduce** `flower-via-docker-compose` **example** " "([#2626](https://github.com/adap/flower/pull/2626))" msgstr "" -#: ../../source/ref-changelog.md:115 +#: ../../source/ref-changelog.md:207 msgid "" "**Introduce** `quickstart-sklearn-tabular` **example** " "([#2719](https://github.com/adap/flower/pull/2719))" msgstr "" -#: ../../source/ref-changelog.md:117 +#: ../../source/ref-changelog.md:209 msgid "" "**Introduce** `custom-metrics` **example** " "([#1958](https://github.com/adap/flower/pull/1958))" msgstr "" -#: ../../source/ref-changelog.md:119 +#: ../../source/ref-changelog.md:211 msgid "" "**Update code examples to use Flower Datasets** " "([#2450](https://github.com/adap/flower/pull/2450), " @@ -14138,13 +14808,13 @@ msgid "" "[#2712](https://github.com/adap/flower/pull/2712))" msgstr "" -#: ../../source/ref-changelog.md:121 +#: ../../source/ref-changelog.md:213 msgid "" "Several code examples were updated to use [Flower " "Datasets](https://flower.ai/docs/datasets/)." msgstr "" -#: ../../source/ref-changelog.md:123 +#: ../../source/ref-changelog.md:215 msgid "" "**General updates to Flower Examples** " "([#2381](https://github.com/adap/flower/pull/2381), " @@ -14159,41 +14829,41 @@ msgid "" "[#2655](https://github.com/adap/flower/pull/2655))" msgstr "" -#: ../../source/ref-changelog.md:125 +#: ../../source/ref-changelog.md:217 msgid "Many Flower code examples received substantial updates." msgstr "" -#: ../../source/ref-changelog.md:127 ../../source/ref-changelog.md:220 +#: ../../source/ref-changelog.md:219 ../../source/ref-changelog.md:312 msgid "**Update Flower Baselines**" msgstr "" -#: ../../source/ref-changelog.md:129 +#: ../../source/ref-changelog.md:221 msgid "" "HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), " "[#2771](https://github.com/adap/flower/pull/2771))" msgstr "" -#: ../../source/ref-changelog.md:130 +#: ../../source/ref-changelog.md:222 msgid "FedVSSL ([#2412](https://github.com/adap/flower/pull/2412))" msgstr "" -#: ../../source/ref-changelog.md:131 +#: ../../source/ref-changelog.md:223 msgid "FedNova ([#2179](https://github.com/adap/flower/pull/2179))" msgstr "" -#: ../../source/ref-changelog.md:132 +#: ../../source/ref-changelog.md:224 msgid "HeteroFL ([#2439](https://github.com/adap/flower/pull/2439))" msgstr "" -#: ../../source/ref-changelog.md:133 +#: ../../source/ref-changelog.md:225 msgid "FedAvgM ([#2246](https://github.com/adap/flower/pull/2246))" msgstr "" -#: ../../source/ref-changelog.md:134 +#: ../../source/ref-changelog.md:226 msgid "FedPara ([#2722](https://github.com/adap/flower/pull/2722))" msgstr "" -#: ../../source/ref-changelog.md:136 +#: ../../source/ref-changelog.md:228 msgid "" "**Improve documentation** " "([#2674](https://github.com/adap/flower/pull/2674), " @@ -14204,7 +14874,7 @@ msgid "" "[#2900](https://github.com/adap/flower/pull/2900))" msgstr "" -#: ../../source/ref-changelog.md:138 +#: ../../source/ref-changelog.md:230 msgid "" "**Improved testing and development infrastructure** " "([#2797](https://github.com/adap/flower/pull/2797), " @@ -14238,13 +14908,13 @@ msgid "" "[#2398](https://github.com/adap/flower/pull/2398))" msgstr "" -#: ../../source/ref-changelog.md:140 +#: ../../source/ref-changelog.md:232 msgid "" "The Flower testing and development infrastructure has received " "substantial updates. This makes Flower 1.7 the most tested release ever." msgstr "" -#: ../../source/ref-changelog.md:142 +#: ../../source/ref-changelog.md:234 msgid "" "**Update dependencies** " "([#2753](https://github.com/adap/flower/pull/2753), " @@ -14268,7 +14938,7 @@ msgid "" "[#2789](https://github.com/adap/flower/pull/2789))" msgstr "" -#: ../../source/ref-changelog.md:144 +#: ../../source/ref-changelog.md:236 msgid "" "**General improvements** " "([#2803](https://github.com/adap/flower/pull/2803), " @@ -14309,14 +14979,14 @@ msgid "" "[#2759](https://github.com/adap/flower/pull/2759))" msgstr "" -#: ../../source/ref-changelog.md:148 +#: ../../source/ref-changelog.md:240 msgid "" "**Deprecate** `start_numpy_client` " "([#2563](https://github.com/adap/flower/pull/2563), " "[#2718](https://github.com/adap/flower/pull/2718))" msgstr "" -#: ../../source/ref-changelog.md:150 +#: ../../source/ref-changelog.md:242 msgid "" "Until now, clients of type `NumPyClient` needed to be started via " "`start_numpy_client`. In our efforts to consolidate framework APIs, we " @@ -14327,63 +14997,63 @@ msgid "" "updated accordingly." msgstr "" -#: ../../source/ref-changelog.md:152 +#: ../../source/ref-changelog.md:244 msgid "" "**Deprecate legacy DP wrappers** " "([#2749](https://github.com/adap/flower/pull/2749))" msgstr "" -#: ../../source/ref-changelog.md:154 +#: ../../source/ref-changelog.md:246 msgid "" "Legacy DP wrapper classes are deprecated, but still functional. This is " "in preparation for an all-new pluggable version of differential privacy " "support in Flower." msgstr "" -#: ../../source/ref-changelog.md:156 +#: ../../source/ref-changelog.md:248 msgid "" "**Make optional arg** `--callable` **in** `flower-client` **a required " "positional arg** ([#2673](https://github.com/adap/flower/pull/2673))" msgstr "" -#: ../../source/ref-changelog.md:158 +#: ../../source/ref-changelog.md:250 msgid "" "**Rename** `certificates` **to** `root_certificates` **in** `Driver` " "([#2890](https://github.com/adap/flower/pull/2890))" msgstr "" -#: ../../source/ref-changelog.md:160 +#: ../../source/ref-changelog.md:252 msgid "" "**Drop experimental** `Task` **fields** " "([#2866](https://github.com/adap/flower/pull/2866), " "[#2865](https://github.com/adap/flower/pull/2865))" msgstr "" -#: ../../source/ref-changelog.md:162 +#: ../../source/ref-changelog.md:254 msgid "" "Experimental fields `sa`, `legacy_server_message` and " "`legacy_client_message` were removed from `Task` message. The removed " "fields are superseded by the new `RecordSet` abstraction." msgstr "" -#: ../../source/ref-changelog.md:164 +#: ../../source/ref-changelog.md:256 msgid "" "**Retire MXNet examples** " "([#2724](https://github.com/adap/flower/pull/2724))" msgstr "" -#: ../../source/ref-changelog.md:166 +#: ../../source/ref-changelog.md:258 msgid "" "The development of the MXNet fremework has ended and the project is now " "[archived on GitHub](https://github.com/apache/mxnet). Existing MXNet " "examples won't receive updates." msgstr "" -#: ../../source/ref-changelog.md:168 +#: ../../source/ref-changelog.md:260 msgid "v1.6.0 (2023-11-28)" msgstr "" -#: ../../source/ref-changelog.md:174 +#: ../../source/ref-changelog.md:266 msgid "" "`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " "`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " @@ -14393,13 +15063,13 @@ msgid "" "`cnxdeveloper`, `k3nfalt` " msgstr "" -#: ../../source/ref-changelog.md:178 +#: ../../source/ref-changelog.md:270 msgid "" "**Add experimental support for Python 3.12** " "([#2565](https://github.com/adap/flower/pull/2565))" msgstr "" -#: ../../source/ref-changelog.md:180 +#: ../../source/ref-changelog.md:272 msgid "" "**Add new XGBoost examples** " "([#2612](https://github.com/adap/flower/pull/2612), " @@ -14410,19 +15080,19 @@ msgid "" "[#2567](https://github.com/adap/flower/pull/2567))" msgstr "" -#: ../../source/ref-changelog.md:182 +#: ../../source/ref-changelog.md:274 msgid "" "We have added a new `xgboost-quickstart` example alongside a new " "`xgboost-comprehensive` example that goes more in-depth." msgstr "" -#: ../../source/ref-changelog.md:184 +#: ../../source/ref-changelog.md:276 msgid "" "**Add Vertical FL example** " "([#2598](https://github.com/adap/flower/pull/2598))" msgstr "" -#: ../../source/ref-changelog.md:186 +#: ../../source/ref-changelog.md:278 msgid "" "We had many questions about Vertical Federated Learning using Flower, so " "we decided to add an simple example for it on the [Titanic " @@ -14430,29 +15100,29 @@ msgid "" "tutorial (in the README)." msgstr "" -#: ../../source/ref-changelog.md:188 +#: ../../source/ref-changelog.md:280 msgid "" "**Support custom** `ClientManager` **in** `start_driver()` " "([#2292](https://github.com/adap/flower/pull/2292))" msgstr "" -#: ../../source/ref-changelog.md:190 +#: ../../source/ref-changelog.md:282 msgid "" "**Update REST API to support create and delete nodes** " "([#2283](https://github.com/adap/flower/pull/2283))" msgstr "" -#: ../../source/ref-changelog.md:192 +#: ../../source/ref-changelog.md:284 msgid "" "**Update the Android SDK** " "([#2187](https://github.com/adap/flower/pull/2187))" msgstr "" -#: ../../source/ref-changelog.md:194 +#: ../../source/ref-changelog.md:286 msgid "Add gRPC request-response capability to the Android SDK." msgstr "" -#: ../../source/ref-changelog.md:196 +#: ../../source/ref-changelog.md:288 msgid "" "**Update the C++ SDK** " "([#2537](https://github.com/adap/flower/pull/2537), " @@ -14461,18 +15131,18 @@ msgid "" "[#2522](https://github.com/adap/flower/pull/2522))" msgstr "" -#: ../../source/ref-changelog.md:198 +#: ../../source/ref-changelog.md:290 msgid "Add gRPC request-response capability to the C++ SDK." msgstr "" -#: ../../source/ref-changelog.md:200 +#: ../../source/ref-changelog.md:292 msgid "" "**Make HTTPS the new default** " "([#2591](https://github.com/adap/flower/pull/2591), " "[#2636](https://github.com/adap/flower/pull/2636))" msgstr "" -#: ../../source/ref-changelog.md:202 +#: ../../source/ref-changelog.md:294 msgid "" "Flower is moving to HTTPS by default. The new `flower-server` requires " "passing `--certificates`, but users can enable `--insecure` to use HTTP " @@ -14482,21 +15152,21 @@ msgid "" "enable insecure HTTP connections." msgstr "" -#: ../../source/ref-changelog.md:204 +#: ../../source/ref-changelog.md:296 msgid "" "For backward compatibility, `start_client()` and `start_numpy_client()` " "will still start in insecure mode by default. In a future release, " "insecure connections will require user opt-in by passing `insecure=True`." msgstr "" -#: ../../source/ref-changelog.md:206 +#: ../../source/ref-changelog.md:298 msgid "" "**Unify client API** ([#2303](https://github.com/adap/flower/pull/2303), " "[#2390](https://github.com/adap/flower/pull/2390), " "[#2493](https://github.com/adap/flower/pull/2493))" msgstr "" -#: ../../source/ref-changelog.md:208 +#: ../../source/ref-changelog.md:300 msgid "" "Using the `client_fn`, Flower clients can interchangeably run as " "standalone processes (i.e. via `start_client`) or in simulation (i.e. via" @@ -14505,92 +15175,92 @@ msgid "" "convert a `NumPyClient` to a `Client`." msgstr "" -#: ../../source/ref-changelog.md:210 +#: ../../source/ref-changelog.md:302 msgid "" "**Add new** `Bulyan` **strategy** " "([#1817](https://github.com/adap/flower/pull/1817), " "[#1891](https://github.com/adap/flower/pull/1891))" msgstr "" -#: ../../source/ref-changelog.md:212 +#: ../../source/ref-changelog.md:304 msgid "" "The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., " "2018](https://arxiv.org/abs/1802.07927)" msgstr "" -#: ../../source/ref-changelog.md:214 +#: ../../source/ref-changelog.md:306 msgid "" "**Add new** `XGB Bagging` **strategy** " "([#2611](https://github.com/adap/flower/pull/2611))" msgstr "" -#: ../../source/ref-changelog.md:216 ../../source/ref-changelog.md:218 +#: ../../source/ref-changelog.md:308 ../../source/ref-changelog.md:310 msgid "" "**Introduce `WorkloadState`** " "([#2564](https://github.com/adap/flower/pull/2564), " "[#2632](https://github.com/adap/flower/pull/2632))" msgstr "" -#: ../../source/ref-changelog.md:222 +#: ../../source/ref-changelog.md:314 msgid "" "FedProx ([#2210](https://github.com/adap/flower/pull/2210), " "[#2286](https://github.com/adap/flower/pull/2286), " "[#2509](https://github.com/adap/flower/pull/2509))" msgstr "" -#: ../../source/ref-changelog.md:224 +#: ../../source/ref-changelog.md:316 msgid "" "Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), " "[#2400](https://github.com/adap/flower/pull/2400))" msgstr "" -#: ../../source/ref-changelog.md:226 +#: ../../source/ref-changelog.md:318 msgid "" "FedMLB ([#2340](https://github.com/adap/flower/pull/2340), " "[#2507](https://github.com/adap/flower/pull/2507))" msgstr "" -#: ../../source/ref-changelog.md:228 +#: ../../source/ref-changelog.md:320 msgid "" "TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " "[#2508](https://github.com/adap/flower/pull/2508))" msgstr "" -#: ../../source/ref-changelog.md:230 +#: ../../source/ref-changelog.md:322 msgid "FedMeta [#2438](https://github.com/adap/flower/pull/2438)" msgstr "" -#: ../../source/ref-changelog.md:232 +#: ../../source/ref-changelog.md:324 msgid "FjORD [#2431](https://github.com/adap/flower/pull/2431)" msgstr "" -#: ../../source/ref-changelog.md:234 +#: ../../source/ref-changelog.md:326 msgid "MOON [#2421](https://github.com/adap/flower/pull/2421)" msgstr "" -#: ../../source/ref-changelog.md:236 +#: ../../source/ref-changelog.md:328 msgid "DepthFL [#2295](https://github.com/adap/flower/pull/2295)" msgstr "" -#: ../../source/ref-changelog.md:238 +#: ../../source/ref-changelog.md:330 msgid "FedPer [#2266](https://github.com/adap/flower/pull/2266)" msgstr "" -#: ../../source/ref-changelog.md:240 +#: ../../source/ref-changelog.md:332 msgid "FedWav2vec [#2551](https://github.com/adap/flower/pull/2551)" msgstr "" -#: ../../source/ref-changelog.md:242 +#: ../../source/ref-changelog.md:334 msgid "niid-Bench [#2428](https://github.com/adap/flower/pull/2428)" msgstr "" -#: ../../source/ref-changelog.md:244 +#: ../../source/ref-changelog.md:336 msgid "" "FedBN ([#2608](https://github.com/adap/flower/pull/2608), " "[#2615](https://github.com/adap/flower/pull/2615))" msgstr "" -#: ../../source/ref-changelog.md:246 +#: ../../source/ref-changelog.md:338 msgid "" "**General updates to Flower Examples** " "([#2384](https://github.com/adap/flower/pull/2384), " @@ -14600,7 +15270,7 @@ msgid "" "[#2545](https://github.com/adap/flower/pull/2545))" msgstr "" -#: ../../source/ref-changelog.md:248 +#: ../../source/ref-changelog.md:340 msgid "" "**General updates to Flower Baselines** " "([#2301](https://github.com/adap/flower/pull/2301), " @@ -14618,7 +15288,7 @@ msgid "" "[#2470](https://github.com/adap/flower/pull/2470))" msgstr "" -#: ../../source/ref-changelog.md:250 +#: ../../source/ref-changelog.md:342 msgid "" "**General updates to the simulation engine** " "([#2331](https://github.com/adap/flower/pull/2331), " @@ -14627,7 +15297,7 @@ msgid "" "[#2294](https://github.com/adap/flower/pull/2294))" msgstr "" -#: ../../source/ref-changelog.md:252 +#: ../../source/ref-changelog.md:344 msgid "" "**General updates to Flower SDKs** " "([#2288](https://github.com/adap/flower/pull/2288), " @@ -14639,7 +15309,7 @@ msgid "" "[#2623](https://github.com/adap/flower/pull/2623))" msgstr "" -#: ../../source/ref-changelog.md:254 +#: ../../source/ref-changelog.md:346 msgid "" "**General improvements** " "([#2309](https://github.com/adap/flower/pull/2309), " @@ -14671,13 +15341,13 @@ msgid "" "[#2596](https://github.com/adap/flower/pull/2596))" msgstr "" -#: ../../source/ref-changelog.md:256 ../../source/ref-changelog.md:346 -#: ../../source/ref-changelog.md:410 ../../source/ref-changelog.md:464 -#: ../../source/ref-changelog.md:531 +#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:502 ../../source/ref-changelog.md:556 +#: ../../source/ref-changelog.md:623 msgid "Flower received many improvements under the hood, too many to list here." msgstr "" -#: ../../source/ref-changelog.md:260 +#: ../../source/ref-changelog.md:352 msgid "" "**Remove support for Python 3.7** " "([#2280](https://github.com/adap/flower/pull/2280), " @@ -14688,30 +15358,30 @@ msgid "" "[#2356](https://github.com/adap/flower/pull/2356))" msgstr "" -#: ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:354 msgid "" "Python 3.7 support was deprecated in Flower 1.5, and this release removes" " support. Flower now requires Python 3.8." msgstr "" -#: ../../source/ref-changelog.md:264 +#: ../../source/ref-changelog.md:356 msgid "" "**Remove experimental argument** `rest` **from** `start_client` " "([#2324](https://github.com/adap/flower/pull/2324))" msgstr "" -#: ../../source/ref-changelog.md:266 +#: ../../source/ref-changelog.md:358 msgid "" "The (still experimental) argument `rest` was removed from `start_client` " "and `start_numpy_client`. Use `transport=\"rest\"` to opt into the " "experimental REST API instead." msgstr "" -#: ../../source/ref-changelog.md:268 +#: ../../source/ref-changelog.md:360 msgid "v1.5.0 (2023-08-31)" msgstr "" -#: ../../source/ref-changelog.md:274 +#: ../../source/ref-changelog.md:366 msgid "" "`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " "`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " @@ -14720,7 +15390,7 @@ msgid "" "TOKEN_v1.5.0-->" msgstr "" -#: ../../source/ref-changelog.md:278 +#: ../../source/ref-changelog.md:370 msgid "" "**Introduce new simulation engine** " "([#1969](https://github.com/adap/flower/pull/1969), " @@ -14728,7 +15398,7 @@ msgid "" "[#2248](https://github.com/adap/flower/pull/2248))" msgstr "" -#: ../../source/ref-changelog.md:280 +#: ../../source/ref-changelog.md:372 msgid "" "The new simulation engine has been rewritten from the ground up, yet it " "remains fully backwards compatible. It offers much improved stability and" @@ -14737,7 +15407,7 @@ msgid "" "only, CPU+GPU, multi-GPU, or multi-node multi-GPU environments." msgstr "" -#: ../../source/ref-changelog.md:282 +#: ../../source/ref-changelog.md:374 msgid "" "Comprehensive documentation includes a new [how-to run " "simulations](https://flower.ai/docs/framework/how-to-run-" @@ -14748,7 +15418,7 @@ msgid "" "series](https://www.youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." msgstr "" -#: ../../source/ref-changelog.md:284 +#: ../../source/ref-changelog.md:376 msgid "" "**Restructure Flower Docs** " "([#1824](https://github.com/adap/flower/pull/1824), " @@ -14780,7 +15450,7 @@ msgid "" "[#2227](https://github.com/adap/flower/pull/2227))" msgstr "" -#: ../../source/ref-changelog.md:286 +#: ../../source/ref-changelog.md:378 msgid "" "Much effort went into a completely restructured Flower docs experience. " "The documentation on [flower.ai/docs](https://flower.ai/docs) is now " @@ -14788,34 +15458,34 @@ msgid "" "Flower iOS SDK, and code example projects." msgstr "" -#: ../../source/ref-changelog.md:288 +#: ../../source/ref-changelog.md:380 msgid "" "**Introduce Flower Swift SDK** " "([#1858](https://github.com/adap/flower/pull/1858), " "[#1897](https://github.com/adap/flower/pull/1897))" msgstr "" -#: ../../source/ref-changelog.md:290 +#: ../../source/ref-changelog.md:382 msgid "" "This is the first preview release of the Flower Swift SDK. Flower support" " on iOS is improving, and alongside the Swift SDK and code example, there" " is now also an iOS quickstart tutorial." msgstr "" -#: ../../source/ref-changelog.md:292 +#: ../../source/ref-changelog.md:384 msgid "" "**Introduce Flower Android SDK** " "([#2131](https://github.com/adap/flower/pull/2131))" msgstr "" -#: ../../source/ref-changelog.md:294 +#: ../../source/ref-changelog.md:386 msgid "" "This is the first preview release of the Flower Kotlin SDK. Flower " "support on Android is improving, and alongside the Kotlin SDK and code " "example, there is now also an Android quickstart tutorial." msgstr "" -#: ../../source/ref-changelog.md:296 +#: ../../source/ref-changelog.md:388 msgid "" "**Introduce new end-to-end testing infrastructure** " "([#1842](https://github.com/adap/flower/pull/1842), " @@ -14837,42 +15507,42 @@ msgid "" "[#2165](https://github.com/adap/flower/pull/2165))" msgstr "" -#: ../../source/ref-changelog.md:298 +#: ../../source/ref-changelog.md:390 msgid "" "A new testing infrastructure ensures that new changes stay compatible " "with existing framework integrations or strategies." msgstr "" -#: ../../source/ref-changelog.md:300 +#: ../../source/ref-changelog.md:392 msgid "**Deprecate Python 3.7**" msgstr "" -#: ../../source/ref-changelog.md:302 +#: ../../source/ref-changelog.md:394 msgid "" "Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for" " Python 3.7 is now deprecated and will be removed in an upcoming release." msgstr "" -#: ../../source/ref-changelog.md:304 +#: ../../source/ref-changelog.md:396 msgid "" "**Add new** `FedTrimmedAvg` **strategy** " "([#1769](https://github.com/adap/flower/pull/1769), " "[#1853](https://github.com/adap/flower/pull/1853))" msgstr "" -#: ../../source/ref-changelog.md:306 +#: ../../source/ref-changelog.md:398 msgid "" "The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, " "2018](https://arxiv.org/abs/1803.01498)." msgstr "" -#: ../../source/ref-changelog.md:308 +#: ../../source/ref-changelog.md:400 msgid "" "**Introduce start_driver** " "([#1697](https://github.com/adap/flower/pull/1697))" msgstr "" -#: ../../source/ref-changelog.md:310 +#: ../../source/ref-changelog.md:402 msgid "" "In addition to `start_server` and using the raw Driver API, there is a " "new `start_driver` function that allows for running `start_server` " @@ -14881,13 +15551,13 @@ msgid "" "`start_driver`." msgstr "" -#: ../../source/ref-changelog.md:312 +#: ../../source/ref-changelog.md:404 msgid "" "**Add parameter aggregation to** `mt-pytorch` **code example** " "([#1785](https://github.com/adap/flower/pull/1785))" msgstr "" -#: ../../source/ref-changelog.md:314 +#: ../../source/ref-changelog.md:406 msgid "" "The `mt-pytorch` example shows how to aggregate parameters when writing a" " driver script. The included `driver.py` and `server.py` have been " @@ -14895,53 +15565,53 @@ msgid "" "building server-side logic." msgstr "" -#: ../../source/ref-changelog.md:316 +#: ../../source/ref-changelog.md:408 msgid "" "**Migrate experimental REST API to Starlette** " "([2171](https://github.com/adap/flower/pull/2171))" msgstr "" -#: ../../source/ref-changelog.md:318 +#: ../../source/ref-changelog.md:410 msgid "" "The (experimental) REST API used to be implemented in " "[FastAPI](https://fastapi.tiangolo.com/), but it has now been migrated to" " use [Starlette](https://www.starlette.io/) directly." msgstr "" -#: ../../source/ref-changelog.md:320 +#: ../../source/ref-changelog.md:412 msgid "" "Please note: The REST request-response API is still experimental and will" " likely change significantly over time." msgstr "" -#: ../../source/ref-changelog.md:322 +#: ../../source/ref-changelog.md:414 msgid "" "**Introduce experimental gRPC request-response API** " "([#1867](https://github.com/adap/flower/pull/1867), " "[#1901](https://github.com/adap/flower/pull/1901))" msgstr "" -#: ../../source/ref-changelog.md:324 +#: ../../source/ref-changelog.md:416 msgid "" "In addition to the existing gRPC API (based on bidirectional streaming) " "and the experimental REST API, there is now a new gRPC API that uses a " "request-response model to communicate with client nodes." msgstr "" -#: ../../source/ref-changelog.md:326 +#: ../../source/ref-changelog.md:418 msgid "" "Please note: The gRPC request-response API is still experimental and will" " likely change significantly over time." msgstr "" -#: ../../source/ref-changelog.md:328 +#: ../../source/ref-changelog.md:420 msgid "" "**Replace the experimental** `start_client(rest=True)` **with the new** " "`start_client(transport=\"rest\")` " "([#1880](https://github.com/adap/flower/pull/1880))" msgstr "" -#: ../../source/ref-changelog.md:330 +#: ../../source/ref-changelog.md:422 msgid "" "The (experimental) `start_client` argument `rest` was deprecated in " "favour of a new argument `transport`. `start_client(transport=\"rest\")` " @@ -14950,30 +15620,30 @@ msgid "" "argument `rest` will be removed in a future release." msgstr "" -#: ../../source/ref-changelog.md:332 +#: ../../source/ref-changelog.md:424 msgid "" "**Add a new gRPC option** " "([#2197](https://github.com/adap/flower/pull/2197))" msgstr "" -#: ../../source/ref-changelog.md:334 +#: ../../source/ref-changelog.md:426 msgid "" "We now start a gRPC server with the `grpc.keepalive_permit_without_calls`" " option set to 0 by default. This prevents the clients from sending " "keepalive pings when there is no outstanding stream." msgstr "" -#: ../../source/ref-changelog.md:336 +#: ../../source/ref-changelog.md:428 msgid "" "**Improve example notebooks** " "([#2005](https://github.com/adap/flower/pull/2005))" msgstr "" -#: ../../source/ref-changelog.md:338 +#: ../../source/ref-changelog.md:430 msgid "There's a new 30min Federated Learning PyTorch tutorial!" msgstr "" -#: ../../source/ref-changelog.md:340 +#: ../../source/ref-changelog.md:432 msgid "" "**Example updates** ([#1772](https://github.com/adap/flower/pull/1772), " "[#1873](https://github.com/adap/flower/pull/1873), " @@ -14988,7 +15658,7 @@ msgid "" "[#2183](https://github.com/adap/flower/pull/2183))" msgstr "" -#: ../../source/ref-changelog.md:342 +#: ../../source/ref-changelog.md:434 msgid "" "Many examples have received significant updates, including simplified " "advanced-tensorflow and advanced-pytorch examples, improved macOS " @@ -14997,7 +15667,7 @@ msgid "" "(in addition to `pyproject.toml`)." msgstr "" -#: ../../source/ref-changelog.md:344 +#: ../../source/ref-changelog.md:436 msgid "" "**General improvements** " "([#1872](https://github.com/adap/flower/pull/1872), " @@ -15008,11 +15678,11 @@ msgid "" "[#2171](https://github.com/adap/flower/pull/2171))" msgstr "" -#: ../../source/ref-changelog.md:352 +#: ../../source/ref-changelog.md:444 msgid "v1.4.0 (2023-04-21)" msgstr "" -#: ../../source/ref-changelog.md:358 +#: ../../source/ref-changelog.md:450 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, " @@ -15022,7 +15692,7 @@ msgid "" "`Steve Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" msgstr "" -#: ../../source/ref-changelog.md:362 +#: ../../source/ref-changelog.md:454 msgid "" "**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and " "example)** ([#1694](https://github.com/adap/flower/pull/1694), " @@ -15033,7 +15703,7 @@ msgid "" "[#1795](https://github.com/adap/flower/pull/1795))" msgstr "" -#: ../../source/ref-changelog.md:364 +#: ../../source/ref-changelog.md:456 msgid "" "XGBoost is a tree-based ensemble machine learning algorithm that uses " "gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg`" @@ -15044,14 +15714,14 @@ msgid "" "an XGBoost project." msgstr "" -#: ../../source/ref-changelog.md:366 +#: ../../source/ref-changelog.md:458 msgid "" "**Introduce iOS SDK (preview)** " "([#1621](https://github.com/adap/flower/pull/1621), " "[#1764](https://github.com/adap/flower/pull/1764))" msgstr "" -#: ../../source/ref-changelog.md:368 +#: ../../source/ref-changelog.md:460 msgid "" "This is a major update for anyone wanting to implement Federated Learning" " on iOS mobile devices. We now have a swift iOS SDK present under " @@ -15062,14 +15732,14 @@ msgid "" "been updated!" msgstr "" -#: ../../source/ref-changelog.md:370 +#: ../../source/ref-changelog.md:462 msgid "" "**Introduce new \"What is Federated Learning?\" tutorial** " "([#1657](https://github.com/adap/flower/pull/1657), " "[#1721](https://github.com/adap/flower/pull/1721))" msgstr "" -#: ../../source/ref-changelog.md:372 +#: ../../source/ref-changelog.md:464 msgid "" "A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-" "what-is-federated-learning.html) in our documentation explains the basics" @@ -15078,7 +15748,7 @@ msgid "" "interested in Federated Learning!" msgstr "" -#: ../../source/ref-changelog.md:374 +#: ../../source/ref-changelog.md:466 msgid "" "**Introduce new Flower Baseline: FedProx MNIST** " "([#1513](https://github.com/adap/flower/pull/1513), " @@ -15087,7 +15757,7 @@ msgid "" "[#1679](https://github.com/adap/flower/pull/1679))" msgstr "" -#: ../../source/ref-changelog.md:376 +#: ../../source/ref-changelog.md:468 msgid "" "This new baseline replicates the MNIST+CNN task from the paper [Federated" " Optimization in Heterogeneous Networks (Li et al., " @@ -15095,13 +15765,13 @@ msgid "" " which aims at making convergence more robust in heterogeneous settings." msgstr "" -#: ../../source/ref-changelog.md:378 +#: ../../source/ref-changelog.md:470 msgid "" "**Introduce new Flower Baseline: FedAvg FEMNIST** " "([#1655](https://github.com/adap/flower/pull/1655))" msgstr "" -#: ../../source/ref-changelog.md:380 +#: ../../source/ref-changelog.md:472 msgid "" "This new baseline replicates an experiment evaluating the performance of " "the FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A " @@ -15109,7 +15779,7 @@ msgid "" "2018)](https://arxiv.org/abs/1812.01097)." msgstr "" -#: ../../source/ref-changelog.md:382 +#: ../../source/ref-changelog.md:474 msgid "" "**Introduce (experimental) REST API** " "([#1594](https://github.com/adap/flower/pull/1594), " @@ -15121,20 +15791,20 @@ msgid "" "[#1733](https://github.com/adap/flower/pull/1733))" msgstr "" -#: ../../source/ref-changelog.md:384 +#: ../../source/ref-changelog.md:476 msgid "" "A new REST API has been introduced as an alternative to the gRPC-based " "communication stack. In this initial version, the REST API only supports " "anonymous clients." msgstr "" -#: ../../source/ref-changelog.md:386 +#: ../../source/ref-changelog.md:478 msgid "" "Please note: The REST API is still experimental and will likely change " "significantly over time." msgstr "" -#: ../../source/ref-changelog.md:388 +#: ../../source/ref-changelog.md:480 msgid "" "**Improve the (experimental) Driver API** " "([#1663](https://github.com/adap/flower/pull/1663), " @@ -15148,7 +15818,7 @@ msgid "" "[#1794](https://github.com/adap/flower/pull/1794))" msgstr "" -#: ../../source/ref-changelog.md:390 +#: ../../source/ref-changelog.md:482 msgid "" "The Driver API is still an experimental feature, but this release " "introduces some major upgrades. One of the main improvements is the " @@ -15158,26 +15828,26 @@ msgid "" "improves the memory efficiency of a long-running Flower server." msgstr "" -#: ../../source/ref-changelog.md:392 +#: ../../source/ref-changelog.md:484 msgid "" "**Fix spilling issues related to Ray during simulations** " "([#1698](https://github.com/adap/flower/pull/1698))" msgstr "" -#: ../../source/ref-changelog.md:394 +#: ../../source/ref-changelog.md:486 msgid "" "While running long simulations, `ray` was sometimes spilling huge amounts" " of data that would make the training unable to continue. This is now " "fixed! 🎉" msgstr "" -#: ../../source/ref-changelog.md:396 +#: ../../source/ref-changelog.md:488 msgid "" "**Add new example using** `TabNet` **and Flower** " "([#1725](https://github.com/adap/flower/pull/1725))" msgstr "" -#: ../../source/ref-changelog.md:398 +#: ../../source/ref-changelog.md:490 msgid "" "TabNet is a powerful and flexible framework for training machine learning" " models on tabular data. We now have a federated example using Flower: " @@ -15185,32 +15855,32 @@ msgid "" "/quickstart-tabnet)." msgstr "" -#: ../../source/ref-changelog.md:400 +#: ../../source/ref-changelog.md:492 msgid "" "**Add new how-to guide for monitoring simulations** " "([#1649](https://github.com/adap/flower/pull/1649))" msgstr "" -#: ../../source/ref-changelog.md:402 +#: ../../source/ref-changelog.md:494 msgid "" "We now have a documentation guide to help users monitor their performance" " during simulations." msgstr "" -#: ../../source/ref-changelog.md:404 +#: ../../source/ref-changelog.md:496 msgid "" "**Add training metrics to** `History` **object during simulations** " "([#1696](https://github.com/adap/flower/pull/1696))" msgstr "" -#: ../../source/ref-changelog.md:406 +#: ../../source/ref-changelog.md:498 msgid "" "The `fit_metrics_aggregation_fn` can be used to aggregate training " "metrics, but previous releases did not save the results in the `History` " "object. This is now the case!" msgstr "" -#: ../../source/ref-changelog.md:408 +#: ../../source/ref-changelog.md:500 msgid "" "**General improvements** " "([#1659](https://github.com/adap/flower/pull/1659), " @@ -15264,23 +15934,23 @@ msgid "" "[#1805](https://github.com/adap/flower/pull/1805))" msgstr "" -#: ../../source/ref-changelog.md:416 +#: ../../source/ref-changelog.md:508 msgid "v1.3.0 (2023-02-06)" msgstr "" -#: ../../source/ref-changelog.md:422 +#: ../../source/ref-changelog.md:514 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" msgstr "" -#: ../../source/ref-changelog.md:426 +#: ../../source/ref-changelog.md:518 msgid "" "**Add support for** `workload_id` **and** `group_id` **in Driver API** " "([#1595](https://github.com/adap/flower/pull/1595))" msgstr "" -#: ../../source/ref-changelog.md:428 +#: ../../source/ref-changelog.md:520 msgid "" "The (experimental) Driver API now supports a `workload_id` that can be " "used to identify which workload a task belongs to. It also supports a new" @@ -15289,36 +15959,36 @@ msgid "" " to decide whether they want to handle a task or not." msgstr "" -#: ../../source/ref-changelog.md:430 +#: ../../source/ref-changelog.md:522 msgid "" "**Make Driver API and Fleet API address configurable** " "([#1637](https://github.com/adap/flower/pull/1637))" msgstr "" -#: ../../source/ref-changelog.md:432 +#: ../../source/ref-changelog.md:524 msgid "" "The (experimental) long-running Flower server (Driver API and Fleet API) " "can now configure the server address of both Driver API (via `--driver-" "api-address`) and Fleet API (via `--fleet-api-address`) when starting:" msgstr "" -#: ../../source/ref-changelog.md:434 +#: ../../source/ref-changelog.md:526 msgid "" "`flower-server --driver-api-address \"0.0.0.0:8081\" --fleet-api-address " "\"0.0.0.0:8086\"`" msgstr "" -#: ../../source/ref-changelog.md:436 +#: ../../source/ref-changelog.md:528 msgid "Both IPv4 and IPv6 addresses are supported." msgstr "" -#: ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:530 msgid "" "**Add new example of Federated Learning using fastai and Flower** " "([#1598](https://github.com/adap/flower/pull/1598))" msgstr "" -#: ../../source/ref-changelog.md:440 +#: ../../source/ref-changelog.md:532 msgid "" "A new code example (`quickstart-fastai`) demonstrates federated learning " "with [fastai](https://www.fast.ai/) and Flower. You can find it here: " @@ -15326,14 +15996,14 @@ msgid "" "/quickstart-fastai)." msgstr "" -#: ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:534 msgid "" "**Make Android example compatible with** `flwr >= 1.0.0` **and the latest" " versions of Android** " "([#1603](https://github.com/adap/flower/pull/1603))" msgstr "" -#: ../../source/ref-changelog.md:444 +#: ../../source/ref-changelog.md:536 msgid "" "The Android code example has received a substantial update: the project " "is compatible with Flower 1.0 (and later), the UI received a full " @@ -15341,13 +16011,13 @@ msgid "" "tooling." msgstr "" -#: ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:538 msgid "" "**Add new `FedProx` strategy** " "([#1619](https://github.com/adap/flower/pull/1619))" msgstr "" -#: ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:540 msgid "" "This " "[strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedprox.py)" @@ -15359,25 +16029,25 @@ msgid "" "respect to the global models." msgstr "" -#: ../../source/ref-changelog.md:450 +#: ../../source/ref-changelog.md:542 msgid "" "**Add new metrics to telemetry events** " "([#1640](https://github.com/adap/flower/pull/1640))" msgstr "" -#: ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:544 msgid "" "An updated event structure allows, for example, the clustering of events " "within the same workload." msgstr "" -#: ../../source/ref-changelog.md:454 +#: ../../source/ref-changelog.md:546 msgid "" "**Add new custom strategy tutorial section** " "[#1623](https://github.com/adap/flower/pull/1623)" msgstr "" -#: ../../source/ref-changelog.md:456 +#: ../../source/ref-changelog.md:548 msgid "" "The Flower tutorial now has a new section that covers implementing a " "custom strategy from scratch: [Open in " @@ -15385,13 +16055,13 @@ msgid "" "/tutorial-build-a-strategy-from-scratch-pytorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:458 +#: ../../source/ref-changelog.md:550 msgid "" "**Add new custom serialization tutorial section** " "([#1622](https://github.com/adap/flower/pull/1622))" msgstr "" -#: ../../source/ref-changelog.md:460 +#: ../../source/ref-changelog.md:552 msgid "" "The Flower tutorial now has a new section that covers custom " "serialization: [Open in " @@ -15399,7 +16069,7 @@ msgid "" "/tutorial-customize-the-client-pytorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:462 +#: ../../source/ref-changelog.md:554 msgid "" "**General improvements** " "([#1638](https://github.com/adap/flower/pull/1638), " @@ -15437,7 +16107,7 @@ msgid "" "[#1586](https://github.com/adap/flower/pull/1586))" msgstr "" -#: ../../source/ref-changelog.md:466 +#: ../../source/ref-changelog.md:558 msgid "" "**Updated documentation** " "([#1629](https://github.com/adap/flower/pull/1629), " @@ -15449,31 +16119,31 @@ msgid "" "[#1614](https://github.com/adap/flower/pull/1614))" msgstr "" -#: ../../source/ref-changelog.md:468 ../../source/ref-changelog.md:535 +#: ../../source/ref-changelog.md:560 ../../source/ref-changelog.md:627 msgid "" "As usual, the documentation has improved quite a bit. It is another step " "in our effort to make the Flower documentation the best documentation of " "any project. Stay tuned and as always, feel free to provide feedback!" msgstr "" -#: ../../source/ref-changelog.md:474 +#: ../../source/ref-changelog.md:566 msgid "v1.2.0 (2023-01-13)" msgstr "" -#: ../../source/ref-changelog.md:480 +#: ../../source/ref-changelog.md:572 msgid "" "`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L." " Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" msgstr "" -#: ../../source/ref-changelog.md:484 +#: ../../source/ref-changelog.md:576 msgid "" "**Introduce new Flower Baseline: FedAvg MNIST** " "([#1497](https://github.com/adap/flower/pull/1497), " "[#1552](https://github.com/adap/flower/pull/1552))" msgstr "" -#: ../../source/ref-changelog.md:486 +#: ../../source/ref-changelog.md:578 msgid "" "Over the coming weeks, we will be releasing a number of new reference " "implementations useful especially to FL newcomers. They will typically " @@ -15484,13 +16154,13 @@ msgid "" "pack-fedavg-mnist-cnn/)" msgstr "" -#: ../../source/ref-changelog.md:488 +#: ../../source/ref-changelog.md:580 msgid "" "**Improve GPU support in simulations** " "([#1555](https://github.com/adap/flower/pull/1555))" msgstr "" -#: ../../source/ref-changelog.md:490 +#: ../../source/ref-changelog.md:582 msgid "" "The Ray-based Virtual Client Engine (`start_simulation`) has been updated" " to improve GPU support. The update includes some of the hard-earned " @@ -15498,45 +16168,45 @@ msgid "" "defaults make running GPU-based simulations substantially more robust." msgstr "" -#: ../../source/ref-changelog.md:492 +#: ../../source/ref-changelog.md:584 msgid "" "**Improve GPU support in Jupyter Notebook tutorials** " "([#1527](https://github.com/adap/flower/pull/1527), " "[#1558](https://github.com/adap/flower/pull/1558))" msgstr "" -#: ../../source/ref-changelog.md:494 +#: ../../source/ref-changelog.md:586 msgid "" "Some users reported that Jupyter Notebooks have not always been easy to " "use on GPU instances. We listened and made improvements to all of our " "Jupyter notebooks! Check out the updated notebooks here:" msgstr "" -#: ../../source/ref-changelog.md:496 +#: ../../source/ref-changelog.md:588 msgid "" "[An Introduction to Federated Learning](https://flower.ai/docs/framework" "/tutorial-get-started-with-flower-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:497 +#: ../../source/ref-changelog.md:589 msgid "" "[Strategies in Federated Learning](https://flower.ai/docs/framework" "/tutorial-use-a-federated-learning-strategy-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:498 +#: ../../source/ref-changelog.md:590 msgid "" "[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a" "-strategy-from-scratch-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:499 +#: ../../source/ref-changelog.md:591 msgid "" "[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-" "customize-the-client-pytorch.html)" msgstr "" -#: ../../source/ref-changelog.md:501 +#: ../../source/ref-changelog.md:593 msgid "" "**Introduce optional telemetry** " "([#1533](https://github.com/adap/flower/pull/1533), " @@ -15544,7 +16214,7 @@ msgid "" "[#1584](https://github.com/adap/flower/pull/1584))" msgstr "" -#: ../../source/ref-changelog.md:503 +#: ../../source/ref-changelog.md:595 msgid "" "After a [request for " "feedback](https://github.com/adap/flower/issues/1534) from the community," @@ -15554,7 +16224,7 @@ msgid "" "used and what challenges users might face." msgstr "" -#: ../../source/ref-changelog.md:505 +#: ../../source/ref-changelog.md:597 msgid "" "**Flower is a friendly framework for collaborative AI and data science.**" " Staying true to this statement, Flower makes it easy to disable " @@ -15562,7 +16232,7 @@ msgid "" "[Read more.](https://flower.ai/docs/telemetry.html)." msgstr "" -#: ../../source/ref-changelog.md:507 +#: ../../source/ref-changelog.md:599 msgid "" "**Introduce (experimental) Driver API** " "([#1520](https://github.com/adap/flower/pull/1520), " @@ -15574,7 +16244,7 @@ msgid "" "[#1567](https://github.com/adap/flower/pull/1567))" msgstr "" -#: ../../source/ref-changelog.md:509 +#: ../../source/ref-changelog.md:601 msgid "" "Flower now has a new (experimental) Driver API which will enable fully " "programmable, async, and multi-tenant Federated Learning and Federated " @@ -15583,7 +16253,7 @@ msgid "" "and you can start building those things now, too." msgstr "" -#: ../../source/ref-changelog.md:511 +#: ../../source/ref-changelog.md:603 msgid "" "The Driver API also enables a new execution mode in which the server runs" " indefinitely. Multiple individual workloads can run concurrently and " @@ -15591,26 +16261,26 @@ msgid "" "especially useful for users who want to deploy Flower in production." msgstr "" -#: ../../source/ref-changelog.md:513 +#: ../../source/ref-changelog.md:605 msgid "" "To learn more, check out the `mt-pytorch` code example. We look forward " "to you feedback!" msgstr "" -#: ../../source/ref-changelog.md:515 +#: ../../source/ref-changelog.md:607 msgid "" "Please note: *The Driver API is still experimental and will likely change" " significantly over time.*" msgstr "" -#: ../../source/ref-changelog.md:517 +#: ../../source/ref-changelog.md:609 msgid "" "**Add new Federated Analytics with Pandas example** " "([#1469](https://github.com/adap/flower/pull/1469), " "[#1535](https://github.com/adap/flower/pull/1535))" msgstr "" -#: ../../source/ref-changelog.md:519 +#: ../../source/ref-changelog.md:611 msgid "" "A new code example (`quickstart-pandas`) demonstrates federated analytics" " with Pandas and Flower. You can find it here: [quickstart-" @@ -15618,32 +16288,32 @@ msgid "" "pandas)." msgstr "" -#: ../../source/ref-changelog.md:521 +#: ../../source/ref-changelog.md:613 msgid "" "**Add new strategies: Krum and MultiKrum** " "([#1481](https://github.com/adap/flower/pull/1481))" msgstr "" -#: ../../source/ref-changelog.md:523 +#: ../../source/ref-changelog.md:615 msgid "" "Edoardo, a computer science student at the Sapienza University of Rome, " "contributed a new `Krum` strategy that enables users to easily use Krum " "and MultiKrum in their workloads." msgstr "" -#: ../../source/ref-changelog.md:525 +#: ../../source/ref-changelog.md:617 msgid "" "**Update C++ example to be compatible with Flower v1.2.0** " "([#1495](https://github.com/adap/flower/pull/1495))" msgstr "" -#: ../../source/ref-changelog.md:527 +#: ../../source/ref-changelog.md:619 msgid "" "The C++ code example has received a substantial update to make it " "compatible with the latest version of Flower." msgstr "" -#: ../../source/ref-changelog.md:529 +#: ../../source/ref-changelog.md:621 msgid "" "**General improvements** " "([#1491](https://github.com/adap/flower/pull/1491), " @@ -15661,7 +16331,7 @@ msgid "" "[#1566](https://github.com/adap/flower/pull/1566))" msgstr "" -#: ../../source/ref-changelog.md:533 +#: ../../source/ref-changelog.md:625 msgid "" "**Updated documentation** " "([#1494](https://github.com/adap/flower/pull/1494), " @@ -15675,24 +16345,24 @@ msgid "" "[#1515](https://github.com/adap/flower/pull/1515))" msgstr "" -#: ../../source/ref-changelog.md:537 +#: ../../source/ref-changelog.md:629 msgid "" "One highlight is the new [first time contributor " "guide](https://flower.ai/docs/first-time-contributors.html): if you've " "never contributed on GitHub before, this is the perfect place to start!" msgstr "" -#: ../../source/ref-changelog.md:543 +#: ../../source/ref-changelog.md:635 msgid "v1.1.0 (2022-10-31)" msgstr "" -#: ../../source/ref-changelog.md:547 +#: ../../source/ref-changelog.md:639 msgid "" "We would like to give our **special thanks** to all the contributors who " "made the new version of Flower possible (in `git shortlog` order):" msgstr "" -#: ../../source/ref-changelog.md:549 +#: ../../source/ref-changelog.md:641 msgid "" "`Akis Linardos`, `Christopher S`, `Daniel J. Beutel`, `George`, `Jan " "Schlicht`, `Mohammad Fares`, `Pedro Porto Buarque de Gusmão`, `Philipp " @@ -15700,14 +16370,14 @@ msgid "" "`danielnugraha`, `edogab33`" msgstr "" -#: ../../source/ref-changelog.md:553 +#: ../../source/ref-changelog.md:645 msgid "" "**Introduce Differential Privacy wrappers (preview)** " "([#1357](https://github.com/adap/flower/pull/1357), " "[#1460](https://github.com/adap/flower/pull/1460))" msgstr "" -#: ../../source/ref-changelog.md:555 +#: ../../source/ref-changelog.md:647 msgid "" "The first (experimental) preview of pluggable Differential Privacy " "wrappers enables easy configuration and usage of differential privacy " @@ -15716,13 +16386,13 @@ msgid "" "over to the Flower docs, a new explainer goes into more detail." msgstr "" -#: ../../source/ref-changelog.md:557 +#: ../../source/ref-changelog.md:649 msgid "" "**New iOS CoreML code example** " "([#1289](https://github.com/adap/flower/pull/1289))" msgstr "" -#: ../../source/ref-changelog.md:559 +#: ../../source/ref-changelog.md:651 msgid "" "Flower goes iOS! A massive new code example shows how Flower clients can " "be built for iOS. The code example contains both Flower iOS SDK " @@ -15730,39 +16400,39 @@ msgid "" "on CoreML." msgstr "" -#: ../../source/ref-changelog.md:561 +#: ../../source/ref-changelog.md:653 msgid "" "**New FedMedian strategy** " "([#1461](https://github.com/adap/flower/pull/1461))" msgstr "" -#: ../../source/ref-changelog.md:563 +#: ../../source/ref-changelog.md:655 msgid "" "The new `FedMedian` strategy implements Federated Median (FedMedian) by " "[Yin et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." msgstr "" -#: ../../source/ref-changelog.md:565 +#: ../../source/ref-changelog.md:657 msgid "" "**Log** `Client` **exceptions in Virtual Client Engine** " "([#1493](https://github.com/adap/flower/pull/1493))" msgstr "" -#: ../../source/ref-changelog.md:567 +#: ../../source/ref-changelog.md:659 msgid "" "All `Client` exceptions happening in the VCE are now logged by default " "and not just exposed to the configured `Strategy` (via the `failures` " "argument)." msgstr "" -#: ../../source/ref-changelog.md:569 +#: ../../source/ref-changelog.md:661 msgid "" "**Improve Virtual Client Engine internals** " "([#1401](https://github.com/adap/flower/pull/1401), " "[#1453](https://github.com/adap/flower/pull/1453))" msgstr "" -#: ../../source/ref-changelog.md:571 +#: ../../source/ref-changelog.md:663 msgid "" "Some internals of the Virtual Client Engine have been revamped. The VCE " "now uses Ray 2.0 under the hood, the value type of the `client_resources`" @@ -15770,25 +16440,25 @@ msgid "" "allocated." msgstr "" -#: ../../source/ref-changelog.md:573 +#: ../../source/ref-changelog.md:665 msgid "" "**Support optional** `Client`**/**`NumPyClient` **methods in Virtual " "Client Engine**" msgstr "" -#: ../../source/ref-changelog.md:575 +#: ../../source/ref-changelog.md:667 msgid "" "The Virtual Client Engine now has full support for optional `Client` (and" " `NumPyClient`) methods." msgstr "" -#: ../../source/ref-changelog.md:577 +#: ../../source/ref-changelog.md:669 msgid "" "**Provide type information to packages using** `flwr` " "([#1377](https://github.com/adap/flower/pull/1377))" msgstr "" -#: ../../source/ref-changelog.md:579 +#: ../../source/ref-changelog.md:671 msgid "" "The package `flwr` is now bundled with a `py.typed` file indicating that " "the package is typed. This enables typing support for projects or " @@ -15796,20 +16466,20 @@ msgid "" "static type checkers like `mypy`." msgstr "" -#: ../../source/ref-changelog.md:581 +#: ../../source/ref-changelog.md:673 msgid "" "**Updated code example** " "([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" msgstr "" -#: ../../source/ref-changelog.md:583 +#: ../../source/ref-changelog.md:675 msgid "" "The code examples covering scikit-learn and PyTorch Lightning have been " "updated to work with the latest version of Flower." msgstr "" -#: ../../source/ref-changelog.md:585 +#: ../../source/ref-changelog.md:677 msgid "" "**Updated documentation** " "([#1355](https://github.com/adap/flower/pull/1355), " @@ -15831,32 +16501,32 @@ msgid "" "[#1467](https://github.com/adap/flower/pull/1467))" msgstr "" -#: ../../source/ref-changelog.md:587 +#: ../../source/ref-changelog.md:679 msgid "" "There have been so many documentation updates that it doesn't even make " "sense to list them individually." msgstr "" -#: ../../source/ref-changelog.md:589 +#: ../../source/ref-changelog.md:681 msgid "" "**Restructured documentation** " "([#1387](https://github.com/adap/flower/pull/1387))" msgstr "" -#: ../../source/ref-changelog.md:591 +#: ../../source/ref-changelog.md:683 msgid "" "The documentation has been restructured to make it easier to navigate. " "This is just the first step in a larger effort to make the Flower " "documentation the best documentation of any project ever. Stay tuned!" msgstr "" -#: ../../source/ref-changelog.md:593 +#: ../../source/ref-changelog.md:685 msgid "" "**Open in Colab button** " "([#1389](https://github.com/adap/flower/pull/1389))" msgstr "" -#: ../../source/ref-changelog.md:595 +#: ../../source/ref-changelog.md:687 msgid "" "The four parts of the Flower Federated Learning Tutorial now come with a " "new `Open in Colab` button. No need to install anything on your local " @@ -15864,7 +16534,7 @@ msgid "" "only a single click away." msgstr "" -#: ../../source/ref-changelog.md:597 +#: ../../source/ref-changelog.md:689 msgid "" "**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468)," " [#1470](https://github.com/adap/flower/pull/1470), " @@ -15874,7 +16544,7 @@ msgid "" "[#1475](https://github.com/adap/flower/pull/1475))" msgstr "" -#: ../../source/ref-changelog.md:599 +#: ../../source/ref-changelog.md:691 msgid "" "The Flower Federated Learning Tutorial has two brand-new parts covering " "custom strategies (still WIP) and the distinction between `Client` and " @@ -15882,40 +16552,40 @@ msgid "" "(many small changes and fixes)." msgstr "" -#: ../../source/ref-changelog.md:605 +#: ../../source/ref-changelog.md:697 msgid "v1.0.0 (2022-07-28)" msgstr "" -#: ../../source/ref-changelog.md:607 +#: ../../source/ref-changelog.md:699 msgid "Highlights" msgstr "" -#: ../../source/ref-changelog.md:609 +#: ../../source/ref-changelog.md:701 msgid "Stable **Virtual Client Engine** (accessible via `start_simulation`)" msgstr "" -#: ../../source/ref-changelog.md:610 +#: ../../source/ref-changelog.md:702 msgid "All `Client`/`NumPyClient` methods are now optional" msgstr "" -#: ../../source/ref-changelog.md:611 +#: ../../source/ref-changelog.md:703 msgid "Configurable `get_parameters`" msgstr "" -#: ../../source/ref-changelog.md:612 +#: ../../source/ref-changelog.md:704 msgid "" "Tons of small API cleanups resulting in a more coherent developer " "experience" msgstr "" -#: ../../source/ref-changelog.md:616 +#: ../../source/ref-changelog.md:708 msgid "" "We would like to give our **special thanks** to all the contributors who " "made Flower 1.0 possible (in reverse [GitHub " "Contributors](https://github.com/adap/flower/graphs/contributors) order):" msgstr "" -#: ../../source/ref-changelog.md:618 +#: ../../source/ref-changelog.md:710 msgid "" "[@rtaiello](https://github.com/rtaiello), " "[@g-pichler](https://github.com/g-pichler), [@rob-" @@ -15955,13 +16625,13 @@ msgid "" "[@danieljanes](https://github.com/danieljanes)." msgstr "" -#: ../../source/ref-changelog.md:622 +#: ../../source/ref-changelog.md:714 msgid "" "**All arguments must be passed as keyword arguments** " "([#1338](https://github.com/adap/flower/pull/1338))" msgstr "" -#: ../../source/ref-changelog.md:624 +#: ../../source/ref-changelog.md:716 msgid "" "Pass all arguments as keyword arguments, positional arguments are not " "longer supported. Code that uses positional arguments (e.g., " @@ -15971,14 +16641,14 @@ msgid "" "client=FlowerClient())`)." msgstr "" -#: ../../source/ref-changelog.md:626 +#: ../../source/ref-changelog.md:718 msgid "" "**Introduce configuration object** `ServerConfig` **in** `start_server` " "**and** `start_simulation` " "([#1317](https://github.com/adap/flower/pull/1317))" msgstr "" -#: ../../source/ref-changelog.md:628 +#: ../../source/ref-changelog.md:720 msgid "" "Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": " "600.0}`, `start_server` and `start_simulation` now expect a configuration" @@ -15987,37 +16657,37 @@ msgid "" "safe code easier and the default parameters values more transparent." msgstr "" -#: ../../source/ref-changelog.md:630 +#: ../../source/ref-changelog.md:722 msgid "" "**Rename built-in strategy parameters for clarity** " "([#1334](https://github.com/adap/flower/pull/1334))" msgstr "" -#: ../../source/ref-changelog.md:632 +#: ../../source/ref-changelog.md:724 msgid "" "The following built-in strategy parameters were renamed to improve " "readability and consistency with other API's:" msgstr "" -#: ../../source/ref-changelog.md:634 +#: ../../source/ref-changelog.md:726 msgid "`fraction_eval` --> `fraction_evaluate`" msgstr "" -#: ../../source/ref-changelog.md:635 +#: ../../source/ref-changelog.md:727 msgid "`min_eval_clients` --> `min_evaluate_clients`" msgstr "" -#: ../../source/ref-changelog.md:636 +#: ../../source/ref-changelog.md:728 msgid "`eval_fn` --> `evaluate_fn`" msgstr "" -#: ../../source/ref-changelog.md:638 +#: ../../source/ref-changelog.md:730 msgid "" "**Update default arguments of built-in strategies** " "([#1278](https://github.com/adap/flower/pull/1278))" msgstr "" -#: ../../source/ref-changelog.md:640 +#: ../../source/ref-changelog.md:732 msgid "" "All built-in strategies now use `fraction_fit=1.0` and " "`fraction_evaluate=1.0`, which means they select *all* currently " @@ -16026,29 +16696,29 @@ msgid "" "initializing the strategy in the following way:" msgstr "" -#: ../../source/ref-changelog.md:642 +#: ../../source/ref-changelog.md:734 msgid "`strategy = FedAvg(fraction_fit=0.1, fraction_evaluate=0.1)`" msgstr "" -#: ../../source/ref-changelog.md:644 +#: ../../source/ref-changelog.md:736 msgid "" "**Add** `server_round` **to** `Strategy.evaluate` " "([#1334](https://github.com/adap/flower/pull/1334))" msgstr "" -#: ../../source/ref-changelog.md:646 +#: ../../source/ref-changelog.md:738 msgid "" "The `Strategy` method `evaluate` now receives the current round of " "federated learning/evaluation as the first parameter." msgstr "" -#: ../../source/ref-changelog.md:648 +#: ../../source/ref-changelog.md:740 msgid "" "**Add** `server_round` **and** `config` **parameters to** `evaluate_fn` " "([#1334](https://github.com/adap/flower/pull/1334))" msgstr "" -#: ../../source/ref-changelog.md:650 +#: ../../source/ref-changelog.md:742 msgid "" "The `evaluate_fn` passed to built-in strategies like `FedAvg` now takes " "three parameters: (1) The current round of federated learning/evaluation " @@ -16056,13 +16726,13 @@ msgid "" "and (3) a config dictionary (`config`)." msgstr "" -#: ../../source/ref-changelog.md:652 +#: ../../source/ref-changelog.md:744 msgid "" "**Rename** `rnd` **to** `server_round` " "([#1321](https://github.com/adap/flower/pull/1321))" msgstr "" -#: ../../source/ref-changelog.md:654 +#: ../../source/ref-changelog.md:746 msgid "" "Several Flower methods and functions (`evaluate_fn`, `configure_fit`, " "`aggregate_fit`, `configure_evaluate`, `aggregate_evaluate`) receive the " @@ -16071,73 +16741,73 @@ msgid "" "has been renamed from `rnd` to `server_round`." msgstr "" -#: ../../source/ref-changelog.md:656 +#: ../../source/ref-changelog.md:748 msgid "" "**Move** `flwr.dataset` **to** `flwr_baselines` " "([#1273](https://github.com/adap/flower/pull/1273))" msgstr "" -#: ../../source/ref-changelog.md:658 +#: ../../source/ref-changelog.md:750 msgid "The experimental package `flwr.dataset` was migrated to Flower Baselines." msgstr "" -#: ../../source/ref-changelog.md:660 +#: ../../source/ref-changelog.md:752 msgid "" "**Remove experimental strategies** " "([#1280](https://github.com/adap/flower/pull/1280))" msgstr "" -#: ../../source/ref-changelog.md:662 +#: ../../source/ref-changelog.md:754 msgid "" "Remove unmaintained experimental strategies (`FastAndSlow`, `FedFSv0`, " "`FedFSv1`)." msgstr "" -#: ../../source/ref-changelog.md:664 +#: ../../source/ref-changelog.md:756 msgid "" "**Rename** `Weights` **to** `NDArrays` " "([#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" -#: ../../source/ref-changelog.md:666 +#: ../../source/ref-changelog.md:758 msgid "" "`flwr.common.Weights` was renamed to `flwr.common.NDArrays` to better " "capture what this type is all about." msgstr "" -#: ../../source/ref-changelog.md:668 +#: ../../source/ref-changelog.md:760 msgid "" "**Remove antiquated** `force_final_distributed_eval` **from** " "`start_server` ([#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" -#: ../../source/ref-changelog.md:670 +#: ../../source/ref-changelog.md:762 msgid "" "The `start_server` parameter `force_final_distributed_eval` has long been" " a historic artefact, in this release it is finally gone for good." msgstr "" -#: ../../source/ref-changelog.md:672 +#: ../../source/ref-changelog.md:764 msgid "" "**Make** `get_parameters` **configurable** " "([#1242](https://github.com/adap/flower/pull/1242))" msgstr "" -#: ../../source/ref-changelog.md:674 +#: ../../source/ref-changelog.md:766 msgid "" "The `get_parameters` method now accepts a configuration dictionary, just " "like `get_properties`, `fit`, and `evaluate`." msgstr "" -#: ../../source/ref-changelog.md:676 +#: ../../source/ref-changelog.md:768 msgid "" "**Replace** `num_rounds` **in** `start_simulation` **with new** `config` " "**parameter** ([#1281](https://github.com/adap/flower/pull/1281))" msgstr "" -#: ../../source/ref-changelog.md:678 +#: ../../source/ref-changelog.md:770 msgid "" "The `start_simulation` function now accepts a configuration dictionary " "`config` instead of the `num_rounds` integer. This improves the " @@ -16145,26 +16815,26 @@ msgid "" "transitioning between the two easier." msgstr "" -#: ../../source/ref-changelog.md:682 +#: ../../source/ref-changelog.md:774 msgid "" "**Support Python 3.10** " "([#1320](https://github.com/adap/flower/pull/1320))" msgstr "" -#: ../../source/ref-changelog.md:684 +#: ../../source/ref-changelog.md:776 msgid "" "The previous Flower release introduced experimental support for Python " "3.10, this release declares Python 3.10 support as stable." msgstr "" -#: ../../source/ref-changelog.md:686 +#: ../../source/ref-changelog.md:778 msgid "" "**Make all** `Client` **and** `NumPyClient` **methods optional** " "([#1260](https://github.com/adap/flower/pull/1260), " "[#1277](https://github.com/adap/flower/pull/1277))" msgstr "" -#: ../../source/ref-changelog.md:688 +#: ../../source/ref-changelog.md:780 msgid "" "The `Client`/`NumPyClient` methods `get_properties`, `get_parameters`, " "`fit`, and `evaluate` are all optional. This enables writing clients that" @@ -16172,13 +16842,13 @@ msgid "" "implement `evaluate` when using centralized evaluation!" msgstr "" -#: ../../source/ref-changelog.md:690 +#: ../../source/ref-changelog.md:782 msgid "" "**Enable passing a** `Server` **instance to** `start_simulation` " "([#1281](https://github.com/adap/flower/pull/1281))" msgstr "" -#: ../../source/ref-changelog.md:692 +#: ../../source/ref-changelog.md:784 msgid "" "Similar to `start_server`, `start_simulation` now accepts a full `Server`" " instance. This enables users to heavily customize the execution of " @@ -16186,7 +16856,7 @@ msgid "" " Virtual Client Engine." msgstr "" -#: ../../source/ref-changelog.md:694 +#: ../../source/ref-changelog.md:786 msgid "" "**Update code examples** " "([#1291](https://github.com/adap/flower/pull/1291), " @@ -16194,50 +16864,50 @@ msgid "" "[#1282](https://github.com/adap/flower/pull/1282))" msgstr "" -#: ../../source/ref-changelog.md:696 +#: ../../source/ref-changelog.md:788 msgid "" "Many code examples received small or even large maintenance updates, " "among them are" msgstr "" -#: ../../source/ref-changelog.md:698 +#: ../../source/ref-changelog.md:790 msgid "`scikit-learn`" msgstr "" -#: ../../source/ref-changelog.md:699 +#: ../../source/ref-changelog.md:791 msgid "`simulation_pytorch`" msgstr "" -#: ../../source/ref-changelog.md:700 +#: ../../source/ref-changelog.md:792 msgid "`quickstart_pytorch`" msgstr "" -#: ../../source/ref-changelog.md:701 +#: ../../source/ref-changelog.md:793 msgid "`quickstart_simulation`" msgstr "" -#: ../../source/ref-changelog.md:702 +#: ../../source/ref-changelog.md:794 msgid "`quickstart_tensorflow`" msgstr "" -#: ../../source/ref-changelog.md:703 +#: ../../source/ref-changelog.md:795 msgid "`advanced_tensorflow`" msgstr "" -#: ../../source/ref-changelog.md:705 +#: ../../source/ref-changelog.md:797 msgid "" "**Remove the obsolete simulation example** " "([#1328](https://github.com/adap/flower/pull/1328))" msgstr "" -#: ../../source/ref-changelog.md:707 +#: ../../source/ref-changelog.md:799 msgid "" "Removes the obsolete `simulation` example and renames " "`quickstart_simulation` to `simulation_tensorflow` so it fits withs the " "naming of `simulation_pytorch`" msgstr "" -#: ../../source/ref-changelog.md:709 +#: ../../source/ref-changelog.md:801 msgid "" "**Update documentation** " "([#1223](https://github.com/adap/flower/pull/1223), " @@ -16252,7 +16922,7 @@ msgid "" "[#1307](https://github.com/adap/flower/pull/1307))" msgstr "" -#: ../../source/ref-changelog.md:711 +#: ../../source/ref-changelog.md:803 msgid "" "One substantial documentation update fixes multiple smaller rendering " "issues, makes titles more succinct to improve navigation, removes a " @@ -16262,24 +16932,24 @@ msgid "" "fixes a number of smaller details!" msgstr "" -#: ../../source/ref-changelog.md:713 ../../source/ref-changelog.md:768 -#: ../../source/ref-changelog.md:837 ../../source/ref-changelog.md:876 +#: ../../source/ref-changelog.md:805 ../../source/ref-changelog.md:860 +#: ../../source/ref-changelog.md:929 ../../source/ref-changelog.md:968 msgid "**Minor updates**" msgstr "" -#: ../../source/ref-changelog.md:715 +#: ../../source/ref-changelog.md:807 msgid "" "Add round number to fit and evaluate log messages " "([#1266](https://github.com/adap/flower/pull/1266))" msgstr "" -#: ../../source/ref-changelog.md:716 +#: ../../source/ref-changelog.md:808 msgid "" "Add secure gRPC connection to the `advanced_tensorflow` code example " "([#847](https://github.com/adap/flower/pull/847))" msgstr "" -#: ../../source/ref-changelog.md:717 +#: ../../source/ref-changelog.md:809 msgid "" "Update developer tooling " "([#1231](https://github.com/adap/flower/pull/1231), " @@ -16288,7 +16958,7 @@ msgid "" "[#1310](https://github.com/adap/flower/pull/1310))" msgstr "" -#: ../../source/ref-changelog.md:718 +#: ../../source/ref-changelog.md:810 msgid "" "Rename ProtoBuf messages to improve consistency " "([#1214](https://github.com/adap/flower/pull/1214), " @@ -16296,11 +16966,11 @@ msgid "" "[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" -#: ../../source/ref-changelog.md:720 +#: ../../source/ref-changelog.md:812 msgid "v0.19.0 (2022-05-18)" msgstr "" -#: ../../source/ref-changelog.md:724 +#: ../../source/ref-changelog.md:816 msgid "" "**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** " "([#919](https://github.com/adap/flower/pull/919), " @@ -16308,7 +16978,7 @@ msgid "" "[#914](https://github.com/adap/flower/pull/914))" msgstr "" -#: ../../source/ref-changelog.md:726 +#: ../../source/ref-changelog.md:818 msgid "" "The first preview release of Flower Baselines has arrived! We're " "kickstarting Flower Baselines with implementations of FedOpt (FedYogi, " @@ -16319,39 +16989,39 @@ msgid "" "contribute-baselines.html)." msgstr "" -#: ../../source/ref-changelog.md:728 +#: ../../source/ref-changelog.md:820 msgid "" "**C++ client SDK (preview) and code example** " "([#1111](https://github.com/adap/flower/pull/1111))" msgstr "" -#: ../../source/ref-changelog.md:730 +#: ../../source/ref-changelog.md:822 msgid "" "Preview support for Flower clients written in C++. The C++ preview " "includes a Flower client SDK and a quickstart code example that " "demonstrates a simple C++ client using the SDK." msgstr "" -#: ../../source/ref-changelog.md:732 +#: ../../source/ref-changelog.md:824 msgid "" "**Add experimental support for Python 3.10 and Python 3.11** " "([#1135](https://github.com/adap/flower/pull/1135))" msgstr "" -#: ../../source/ref-changelog.md:734 +#: ../../source/ref-changelog.md:826 msgid "" "Python 3.10 is the latest stable release of Python and Python 3.11 is due" " to be released in October. This Flower release adds experimental support" " for both Python versions." msgstr "" -#: ../../source/ref-changelog.md:736 +#: ../../source/ref-changelog.md:828 msgid "" "**Aggregate custom metrics through user-provided functions** " "([#1144](https://github.com/adap/flower/pull/1144))" msgstr "" -#: ../../source/ref-changelog.md:738 +#: ../../source/ref-changelog.md:830 msgid "" "Custom metrics (e.g., `accuracy`) can now be aggregated without having to" " customize the strategy. Built-in strategies support two new arguments, " @@ -16359,13 +17029,13 @@ msgid "" "allow passing custom metric aggregation functions." msgstr "" -#: ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:832 msgid "" "**User-configurable round timeout** " "([#1162](https://github.com/adap/flower/pull/1162))" msgstr "" -#: ../../source/ref-changelog.md:742 +#: ../../source/ref-changelog.md:834 msgid "" "A new configuration value allows the round timeout to be set for " "`start_server` and `start_simulation`. If the `config` dictionary " @@ -16374,14 +17044,14 @@ msgid "" "connection." msgstr "" -#: ../../source/ref-changelog.md:744 +#: ../../source/ref-changelog.md:836 msgid "" "**Enable both federated evaluation and centralized evaluation to be used " "at the same time in all built-in strategies** " "([#1091](https://github.com/adap/flower/pull/1091))" msgstr "" -#: ../../source/ref-changelog.md:746 +#: ../../source/ref-changelog.md:838 msgid "" "Built-in strategies can now perform both federated evaluation (i.e., " "client-side) and centralized evaluation (i.e., server-side) in the same " @@ -16389,82 +17059,82 @@ msgid "" " `0.0`." msgstr "" -#: ../../source/ref-changelog.md:748 +#: ../../source/ref-changelog.md:840 msgid "" "**Two new Jupyter Notebook tutorials** " "([#1141](https://github.com/adap/flower/pull/1141))" msgstr "" -#: ../../source/ref-changelog.md:750 +#: ../../source/ref-changelog.md:842 msgid "" "Two Jupyter Notebook tutorials (compatible with Google Colab) explain " "basic and intermediate Flower features:" msgstr "" -#: ../../source/ref-changelog.md:752 +#: ../../source/ref-changelog.md:844 msgid "" "*An Introduction to Federated Learning*: [Open in " "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" "-Intro-to-FL-PyTorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:754 +#: ../../source/ref-changelog.md:846 msgid "" "*Using Strategies in Federated Learning*: [Open in " "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" "-Strategies-in-FL-PyTorch.ipynb)" msgstr "" -#: ../../source/ref-changelog.md:756 +#: ../../source/ref-changelog.md:848 msgid "" "**New FedAvgM strategy (Federated Averaging with Server Momentum)** " "([#1076](https://github.com/adap/flower/pull/1076))" msgstr "" -#: ../../source/ref-changelog.md:758 +#: ../../source/ref-changelog.md:850 msgid "" "The new `FedAvgM` strategy implements Federated Averaging with Server " "Momentum \\[Hsu et al., 2019\\]." msgstr "" -#: ../../source/ref-changelog.md:760 +#: ../../source/ref-changelog.md:852 msgid "" "**New advanced PyTorch code example** " "([#1007](https://github.com/adap/flower/pull/1007))" msgstr "" -#: ../../source/ref-changelog.md:762 +#: ../../source/ref-changelog.md:854 msgid "" "A new code example (`advanced_pytorch`) demonstrates advanced Flower " "concepts with PyTorch." msgstr "" -#: ../../source/ref-changelog.md:764 +#: ../../source/ref-changelog.md:856 msgid "" "**New JAX code example** " "([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143))" msgstr "" -#: ../../source/ref-changelog.md:766 +#: ../../source/ref-changelog.md:858 msgid "" "A new code example (`jax_from_centralized_to_federated`) shows federated " "learning with JAX and Flower." msgstr "" -#: ../../source/ref-changelog.md:770 +#: ../../source/ref-changelog.md:862 msgid "" "New option to keep Ray running if Ray was already initialized in " "`start_simulation` ([#1177](https://github.com/adap/flower/pull/1177))" msgstr "" -#: ../../source/ref-changelog.md:771 +#: ../../source/ref-changelog.md:863 msgid "" "Add support for custom `ClientManager` as a `start_simulation` parameter " "([#1171](https://github.com/adap/flower/pull/1171))" msgstr "" -#: ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:864 msgid "" "New documentation for [implementing " "strategies](https://flower.ai/docs/framework/how-to-implement-" @@ -16472,72 +17142,72 @@ msgid "" "[#1175](https://github.com/adap/flower/pull/1175))" msgstr "" -#: ../../source/ref-changelog.md:773 +#: ../../source/ref-changelog.md:865 msgid "" "New mobile-friendly documentation theme " "([#1174](https://github.com/adap/flower/pull/1174))" msgstr "" -#: ../../source/ref-changelog.md:774 +#: ../../source/ref-changelog.md:866 msgid "" "Limit version range for (optional) `ray` dependency to include only " "compatible releases (`>=1.9.2,<1.12.0`) " "([#1205](https://github.com/adap/flower/pull/1205))" msgstr "" -#: ../../source/ref-changelog.md:778 +#: ../../source/ref-changelog.md:870 msgid "" "**Remove deprecated support for Python 3.6** " "([#871](https://github.com/adap/flower/pull/871))" msgstr "" -#: ../../source/ref-changelog.md:779 +#: ../../source/ref-changelog.md:871 msgid "" "**Remove deprecated KerasClient** " "([#857](https://github.com/adap/flower/pull/857))" msgstr "" -#: ../../source/ref-changelog.md:780 +#: ../../source/ref-changelog.md:872 msgid "" "**Remove deprecated no-op extra installs** " "([#973](https://github.com/adap/flower/pull/973))" msgstr "" -#: ../../source/ref-changelog.md:781 +#: ../../source/ref-changelog.md:873 msgid "" "**Remove deprecated proto fields from** `FitRes` **and** `EvaluateRes` " "([#869](https://github.com/adap/flower/pull/869))" msgstr "" -#: ../../source/ref-changelog.md:782 +#: ../../source/ref-changelog.md:874 msgid "" "**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** " "([#1107](https://github.com/adap/flower/pull/1107))" msgstr "" -#: ../../source/ref-changelog.md:783 +#: ../../source/ref-changelog.md:875 msgid "" "**Remove deprecated DefaultStrategy strategy** " "([#1142](https://github.com/adap/flower/pull/1142))" msgstr "" -#: ../../source/ref-changelog.md:784 +#: ../../source/ref-changelog.md:876 msgid "" "**Remove deprecated support for eval_fn accuracy return value** " "([#1142](https://github.com/adap/flower/pull/1142))" msgstr "" -#: ../../source/ref-changelog.md:785 +#: ../../source/ref-changelog.md:877 msgid "" "**Remove deprecated support for passing initial parameters as NumPy " "ndarrays** ([#1142](https://github.com/adap/flower/pull/1142))" msgstr "" -#: ../../source/ref-changelog.md:787 +#: ../../source/ref-changelog.md:879 msgid "v0.18.0 (2022-02-28)" msgstr "" -#: ../../source/ref-changelog.md:791 +#: ../../source/ref-changelog.md:883 msgid "" "**Improved Virtual Client Engine compatibility with Jupyter Notebook / " "Google Colab** ([#866](https://github.com/adap/flower/pull/866), " @@ -16546,7 +17216,7 @@ msgid "" "[#1036](https://github.com/adap/flower/pull/1036))" msgstr "" -#: ../../source/ref-changelog.md:793 +#: ../../source/ref-changelog.md:885 msgid "" "Simulations (using the Virtual Client Engine through `start_simulation`) " "now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " @@ -16554,38 +17224,38 @@ msgid "" "flwr[simulation]`)." msgstr "" -#: ../../source/ref-changelog.md:795 +#: ../../source/ref-changelog.md:887 msgid "" "**New Jupyter Notebook code example** " "([#833](https://github.com/adap/flower/pull/833))" msgstr "" -#: ../../source/ref-changelog.md:797 +#: ../../source/ref-changelog.md:889 msgid "" "A new code example (`quickstart_simulation`) demonstrates Flower " "simulations using the Virtual Client Engine through Jupyter Notebook " "(incl. Google Colab)." msgstr "" -#: ../../source/ref-changelog.md:799 +#: ../../source/ref-changelog.md:891 msgid "" "**Client properties (feature preview)** " "([#795](https://github.com/adap/flower/pull/795))" msgstr "" -#: ../../source/ref-changelog.md:801 +#: ../../source/ref-changelog.md:893 msgid "" "Clients can implement a new method `get_properties` to enable server-side" " strategies to query client properties." msgstr "" -#: ../../source/ref-changelog.md:803 +#: ../../source/ref-changelog.md:895 msgid "" "**Experimental Android support with TFLite** " "([#865](https://github.com/adap/flower/pull/865))" msgstr "" -#: ../../source/ref-changelog.md:805 +#: ../../source/ref-changelog.md:897 msgid "" "Android support has finally arrived in `main`! Flower is both client-" "agnostic and framework-agnostic by design. One can integrate arbitrary " @@ -16593,7 +17263,7 @@ msgid "" "become a lot easier." msgstr "" -#: ../../source/ref-changelog.md:807 +#: ../../source/ref-changelog.md:899 msgid "" "The example uses TFLite on the client side, along with a new " "`FedAvgAndroid` strategy. The Android client and `FedAvgAndroid` are " @@ -16602,13 +17272,13 @@ msgid "" " functionality from `FedAvgAndroid`." msgstr "" -#: ../../source/ref-changelog.md:809 +#: ../../source/ref-changelog.md:901 msgid "" "**Make gRPC keepalive time user-configurable and decrease default " "keepalive time** ([#1069](https://github.com/adap/flower/pull/1069))" msgstr "" -#: ../../source/ref-changelog.md:811 +#: ../../source/ref-changelog.md:903 msgid "" "The default gRPC keepalive time has been reduced to increase the " "compatibility of Flower with more cloud environments (for example, " @@ -16616,31 +17286,31 @@ msgid "" " gRPC stack based on specific requirements." msgstr "" -#: ../../source/ref-changelog.md:813 +#: ../../source/ref-changelog.md:905 msgid "" "**New differential privacy example using Opacus and PyTorch** " "([#805](https://github.com/adap/flower/pull/805))" msgstr "" -#: ../../source/ref-changelog.md:815 +#: ../../source/ref-changelog.md:907 msgid "" "A new code example (`opacus`) demonstrates differentially-private " "federated learning with Opacus, PyTorch, and Flower." msgstr "" -#: ../../source/ref-changelog.md:817 +#: ../../source/ref-changelog.md:909 msgid "" "**New Hugging Face Transformers code example** " "([#863](https://github.com/adap/flower/pull/863))" msgstr "" -#: ../../source/ref-changelog.md:819 +#: ../../source/ref-changelog.md:911 msgid "" "A new code example (`quickstart_huggingface`) demonstrates usage of " "Hugging Face Transformers with Flower." msgstr "" -#: ../../source/ref-changelog.md:821 +#: ../../source/ref-changelog.md:913 msgid "" "**New MLCube code example** " "([#779](https://github.com/adap/flower/pull/779), " @@ -16649,13 +17319,13 @@ msgid "" "[#1090](https://github.com/adap/flower/pull/1090))" msgstr "" -#: ../../source/ref-changelog.md:823 +#: ../../source/ref-changelog.md:915 msgid "" "A new code example (`quickstart_mlcube`) demonstrates usage of MLCube " "with Flower." msgstr "" -#: ../../source/ref-changelog.md:825 +#: ../../source/ref-changelog.md:917 msgid "" "**SSL-enabled server and client** " "([#842](https://github.com/adap/flower/pull/842), " @@ -16666,33 +17336,33 @@ msgid "" "[#994](https://github.com/adap/flower/pull/994))" msgstr "" -#: ../../source/ref-changelog.md:827 +#: ../../source/ref-changelog.md:919 msgid "" "SSL enables secure encrypted connections between clients and servers. " "This release open-sources the Flower secure gRPC implementation to make " "encrypted communication channels accessible to all Flower users." msgstr "" -#: ../../source/ref-changelog.md:829 +#: ../../source/ref-changelog.md:921 msgid "" "**Updated** `FedAdam` **and** `FedYogi` **strategies** " "([#885](https://github.com/adap/flower/pull/885), " "[#895](https://github.com/adap/flower/pull/895))" msgstr "" -#: ../../source/ref-changelog.md:831 +#: ../../source/ref-changelog.md:923 msgid "" "`FedAdam` and `FedAdam` match the latest version of the Adaptive " "Federated Optimization paper." msgstr "" -#: ../../source/ref-changelog.md:833 +#: ../../source/ref-changelog.md:925 msgid "" "**Initialize** `start_simulation` **with a list of client IDs** " "([#860](https://github.com/adap/flower/pull/860))" msgstr "" -#: ../../source/ref-changelog.md:835 +#: ../../source/ref-changelog.md:927 msgid "" "`start_simulation` can now be called with a list of client IDs " "(`clients_ids`, type: `List[str]`). Those IDs will be passed to the " @@ -16701,55 +17371,55 @@ msgid "" "identifiers." msgstr "" -#: ../../source/ref-changelog.md:839 +#: ../../source/ref-changelog.md:931 msgid "" "Update `num_examples` calculation in PyTorch code examples in " "([#909](https://github.com/adap/flower/pull/909))" msgstr "" -#: ../../source/ref-changelog.md:840 +#: ../../source/ref-changelog.md:932 msgid "" "Expose Flower version through `flwr.__version__` " "([#952](https://github.com/adap/flower/pull/952))" msgstr "" -#: ../../source/ref-changelog.md:841 +#: ../../source/ref-changelog.md:933 msgid "" "`start_server` in `app.py` now returns a `History` object containing " "metrics from training ([#974](https://github.com/adap/flower/pull/974))" msgstr "" -#: ../../source/ref-changelog.md:842 +#: ../../source/ref-changelog.md:934 msgid "" "Make `max_workers` (used by `ThreadPoolExecutor`) configurable " "([#978](https://github.com/adap/flower/pull/978))" msgstr "" -#: ../../source/ref-changelog.md:843 +#: ../../source/ref-changelog.md:935 msgid "" "Increase sleep time after server start to three seconds in all code " "examples ([#1086](https://github.com/adap/flower/pull/1086))" msgstr "" -#: ../../source/ref-changelog.md:844 +#: ../../source/ref-changelog.md:936 msgid "" "Added a new FAQ section to the documentation " "([#948](https://github.com/adap/flower/pull/948))" msgstr "" -#: ../../source/ref-changelog.md:845 +#: ../../source/ref-changelog.md:937 msgid "" "And many more under-the-hood changes, library updates, documentation " "changes, and tooling improvements!" msgstr "" -#: ../../source/ref-changelog.md:849 +#: ../../source/ref-changelog.md:941 msgid "" "**Removed** `flwr_example` **and** `flwr_experimental` **from release " "build** ([#869](https://github.com/adap/flower/pull/869))" msgstr "" -#: ../../source/ref-changelog.md:851 +#: ../../source/ref-changelog.md:943 msgid "" "The packages `flwr_example` and `flwr_experimental` have been deprecated " "since Flower 0.12.0 and they are not longer included in Flower release " @@ -16758,11 +17428,11 @@ msgid "" "an upcoming release." msgstr "" -#: ../../source/ref-changelog.md:853 +#: ../../source/ref-changelog.md:945 msgid "v0.17.0 (2021-09-24)" msgstr "" -#: ../../source/ref-changelog.md:857 +#: ../../source/ref-changelog.md:949 msgid "" "**Experimental virtual client engine** " "([#781](https://github.com/adap/flower/pull/781) " @@ -16770,7 +17440,7 @@ msgid "" "[#791](https://github.com/adap/flower/pull/791))" msgstr "" -#: ../../source/ref-changelog.md:859 +#: ../../source/ref-changelog.md:951 msgid "" "One of Flower's goals is to enable research at scale. This release " "enables a first (experimental) peek at a major new feature, codenamed the" @@ -16780,7 +17450,7 @@ msgid "" "code examples called `quickstart_simulation` and `simulation_pytorch`." msgstr "" -#: ../../source/ref-changelog.md:861 +#: ../../source/ref-changelog.md:953 msgid "" "The feature is still experimental, so there's no stability guarantee for " "the API. It's also not quite ready for prime time and comes with a few " @@ -16788,86 +17458,86 @@ msgid "" "out and share their thoughts." msgstr "" -#: ../../source/ref-changelog.md:863 +#: ../../source/ref-changelog.md:955 msgid "" "**New built-in strategies** " "([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" msgstr "" -#: ../../source/ref-changelog.md:865 +#: ../../source/ref-changelog.md:957 msgid "" "FedYogi - Federated learning strategy using Yogi on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" msgstr "" -#: ../../source/ref-changelog.md:866 +#: ../../source/ref-changelog.md:958 msgid "" "FedAdam - Federated learning strategy using Adam on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" msgstr "" -#: ../../source/ref-changelog.md:868 +#: ../../source/ref-changelog.md:960 msgid "" "**New PyTorch Lightning code example** " "([#617](https://github.com/adap/flower/pull/617))" msgstr "" -#: ../../source/ref-changelog.md:870 +#: ../../source/ref-changelog.md:962 msgid "" "**New Variational Auto-Encoder code example** " "([#752](https://github.com/adap/flower/pull/752))" msgstr "" -#: ../../source/ref-changelog.md:872 +#: ../../source/ref-changelog.md:964 msgid "" "**New scikit-learn code example** " "([#748](https://github.com/adap/flower/pull/748))" msgstr "" -#: ../../source/ref-changelog.md:874 +#: ../../source/ref-changelog.md:966 msgid "" "**New experimental TensorBoard strategy** " "([#789](https://github.com/adap/flower/pull/789))" msgstr "" -#: ../../source/ref-changelog.md:878 +#: ../../source/ref-changelog.md:970 msgid "" "Improved advanced TensorFlow code example " "([#769](https://github.com/adap/flower/pull/769))" msgstr "" -#: ../../source/ref-changelog.md:879 +#: ../../source/ref-changelog.md:971 msgid "" "Warning when `min_available_clients` is misconfigured " "([#830](https://github.com/adap/flower/pull/830))" msgstr "" -#: ../../source/ref-changelog.md:880 +#: ../../source/ref-changelog.md:972 msgid "" "Improved gRPC server docs " "([#841](https://github.com/adap/flower/pull/841))" msgstr "" -#: ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:973 msgid "" "Improved error message in `NumPyClient` " "([#851](https://github.com/adap/flower/pull/851))" msgstr "" -#: ../../source/ref-changelog.md:882 +#: ../../source/ref-changelog.md:974 msgid "" "Improved PyTorch quickstart code example " "([#852](https://github.com/adap/flower/pull/852))" msgstr "" -#: ../../source/ref-changelog.md:886 +#: ../../source/ref-changelog.md:978 msgid "" "**Disabled final distributed evaluation** " "([#800](https://github.com/adap/flower/pull/800))" msgstr "" -#: ../../source/ref-changelog.md:888 +#: ../../source/ref-changelog.md:980 msgid "" "Prior behaviour was to perform a final round of distributed evaluation on" " all connected clients, which is often not required (e.g., when using " @@ -16875,13 +17545,13 @@ msgid "" "`force_final_distributed_eval=True` to `start_server`." msgstr "" -#: ../../source/ref-changelog.md:890 +#: ../../source/ref-changelog.md:982 msgid "" "**Renamed q-FedAvg strategy** " "([#802](https://github.com/adap/flower/pull/802))" msgstr "" -#: ../../source/ref-changelog.md:892 +#: ../../source/ref-changelog.md:984 msgid "" "The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect " "the notation given in the original paper (q-FFL is the optimization " @@ -16890,14 +17560,14 @@ msgid "" " (it will be removed in a future release)." msgstr "" -#: ../../source/ref-changelog.md:894 +#: ../../source/ref-changelog.md:986 msgid "" "**Deprecated and renamed code example** `simulation_pytorch` **to** " "`simulation_pytorch_legacy` " "([#791](https://github.com/adap/flower/pull/791))" msgstr "" -#: ../../source/ref-changelog.md:896 +#: ../../source/ref-changelog.md:988 msgid "" "This example has been replaced by a new example. The new example is based" " on the experimental virtual client engine, which will become the new " @@ -16906,27 +17576,27 @@ msgid "" "removed in the future." msgstr "" -#: ../../source/ref-changelog.md:898 +#: ../../source/ref-changelog.md:990 msgid "v0.16.0 (2021-05-11)" msgstr "" -#: ../../source/ref-changelog.md:902 +#: ../../source/ref-changelog.md:994 msgid "" "**New built-in strategies** " "([#549](https://github.com/adap/flower/pull/549))" msgstr "" -#: ../../source/ref-changelog.md:904 +#: ../../source/ref-changelog.md:996 msgid "(abstract) FedOpt" msgstr "" -#: ../../source/ref-changelog.md:907 +#: ../../source/ref-changelog.md:999 msgid "" "**Custom metrics for server and strategies** " "([#717](https://github.com/adap/flower/pull/717))" msgstr "" -#: ../../source/ref-changelog.md:909 +#: ../../source/ref-changelog.md:1001 msgid "" "The Flower server is now fully task-agnostic, all remaining instances of " "task-specific metrics (such as `accuracy`) have been replaced by custom " @@ -16935,7 +17605,7 @@ msgid "" "release, custom metrics replace task-specific metrics on the server." msgstr "" -#: ../../source/ref-changelog.md:911 +#: ../../source/ref-changelog.md:1003 msgid "" "Custom metric dictionaries are now used in two user-facing APIs: they are" " returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and " @@ -16945,7 +17615,7 @@ msgid "" "track of." msgstr "" -#: ../../source/ref-changelog.md:913 +#: ../../source/ref-changelog.md:1005 msgid "" "Strategy implementations should migrate their `aggregate_fit` and " "`aggregate_evaluate` methods to the new return type (e.g., by simply " @@ -16953,19 +17623,19 @@ msgid "" " from `return loss, accuracy` to `return loss, {\"accuracy\": accuracy}`." msgstr "" -#: ../../source/ref-changelog.md:915 +#: ../../source/ref-changelog.md:1007 msgid "" "Flower 0.15-style return types are deprecated (but still supported), " "compatibility will be removed in a future release." msgstr "" -#: ../../source/ref-changelog.md:917 +#: ../../source/ref-changelog.md:1009 msgid "" "**Migration warnings for deprecated functionality** " "([#690](https://github.com/adap/flower/pull/690))" msgstr "" -#: ../../source/ref-changelog.md:919 +#: ../../source/ref-changelog.md:1011 msgid "" "Earlier versions of Flower were often migrated to new APIs, while " "maintaining compatibility with legacy APIs. This release introduces " @@ -16974,7 +17644,7 @@ msgid "" "recent APIs, thus easing the transition from one release to another." msgstr "" -#: ../../source/ref-changelog.md:921 +#: ../../source/ref-changelog.md:1013 msgid "" "Improved docs and docstrings " "([#691](https://github.com/adap/flower/pull/691) " @@ -16982,11 +17652,11 @@ msgid "" "[#713](https://github.com/adap/flower/pull/713))" msgstr "" -#: ../../source/ref-changelog.md:923 +#: ../../source/ref-changelog.md:1015 msgid "MXNet example and documentation" msgstr "" -#: ../../source/ref-changelog.md:925 +#: ../../source/ref-changelog.md:1017 msgid "" "FedBN implementation in example PyTorch: From Centralized To Federated " "([#696](https://github.com/adap/flower/pull/696) " @@ -16994,13 +17664,13 @@ msgid "" "[#705](https://github.com/adap/flower/pull/705))" msgstr "" -#: ../../source/ref-changelog.md:929 +#: ../../source/ref-changelog.md:1021 msgid "" "**Serialization-agnostic server** " "([#721](https://github.com/adap/flower/pull/721))" msgstr "" -#: ../../source/ref-changelog.md:931 +#: ../../source/ref-changelog.md:1023 msgid "" "The Flower server is now fully serialization-agnostic. Prior usage of " "class `Weights` (which represents parameters as deserialized NumPy " @@ -17011,7 +17681,7 @@ msgid "" "serialization/deserialization)." msgstr "" -#: ../../source/ref-changelog.md:933 +#: ../../source/ref-changelog.md:1025 msgid "" "Built-in strategies implement this approach by handling serialization and" " deserialization to/from `Weights` internally. Custom/3rd-party Strategy " @@ -17021,31 +17691,31 @@ msgid "" " easily migrate to the new format." msgstr "" -#: ../../source/ref-changelog.md:935 +#: ../../source/ref-changelog.md:1027 msgid "" "Deprecated `flwr.server.Server.evaluate`, use " "`flwr.server.Server.evaluate_round` instead " "([#717](https://github.com/adap/flower/pull/717))" msgstr "" -#: ../../source/ref-changelog.md:937 +#: ../../source/ref-changelog.md:1029 msgid "v0.15.0 (2021-03-12)" msgstr "" -#: ../../source/ref-changelog.md:941 +#: ../../source/ref-changelog.md:1033 msgid "" "**Server-side parameter initialization** " "([#658](https://github.com/adap/flower/pull/658))" msgstr "" -#: ../../source/ref-changelog.md:943 +#: ../../source/ref-changelog.md:1035 msgid "" "Model parameters can now be initialized on the server-side. Server-side " "parameter initialization works via a new `Strategy` method called " "`initialize_parameters`." msgstr "" -#: ../../source/ref-changelog.md:945 +#: ../../source/ref-changelog.md:1037 msgid "" "Built-in strategies support a new constructor argument called " "`initial_parameters` to set the initial parameters. Built-in strategies " @@ -17053,7 +17723,7 @@ msgid "" "delete them to free the memory afterwards." msgstr "" -#: ../../source/ref-changelog.md:964 +#: ../../source/ref-changelog.md:1056 msgid "" "If no initial parameters are provided to the strategy, the server will " "continue to use the current behaviour (namely, it will ask one of the " @@ -17061,21 +17731,17 @@ msgid "" "parameters)." msgstr "" -#: ../../source/ref-changelog.md:966 -msgid "Deprecations" -msgstr "" - -#: ../../source/ref-changelog.md:968 +#: ../../source/ref-changelog.md:1060 msgid "" "Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to " "`flwr.server.strategy.FedAvg`, which is equivalent)" msgstr "" -#: ../../source/ref-changelog.md:970 +#: ../../source/ref-changelog.md:1062 msgid "v0.14.0 (2021-02-18)" msgstr "" -#: ../../source/ref-changelog.md:974 +#: ../../source/ref-changelog.md:1066 msgid "" "**Generalized** `Client.fit` **and** `Client.evaluate` **return values** " "([#610](https://github.com/adap/flower/pull/610) " @@ -17083,7 +17749,7 @@ msgid "" "[#633](https://github.com/adap/flower/pull/633))" msgstr "" -#: ../../source/ref-changelog.md:976 +#: ../../source/ref-changelog.md:1068 msgid "" "Clients can now return an additional dictionary mapping `str` keys to " "values of the following types: `bool`, `bytes`, `float`, `int`, `str`. " @@ -17091,7 +17757,7 @@ msgid "" "and make use of them on the server side!" msgstr "" -#: ../../source/ref-changelog.md:978 +#: ../../source/ref-changelog.md:1070 msgid "" "This improvement also allowed for more consistent return types between " "`fit` and `evaluate`: `evaluate` should now return a tuple `(float, int, " @@ -17099,7 +17765,7 @@ msgid "" "holding arbitrary problem-specific values like accuracy." msgstr "" -#: ../../source/ref-changelog.md:980 +#: ../../source/ref-changelog.md:1072 msgid "" "In case you wondered: this feature is compatible with existing projects, " "the additional dictionary return value is optional. New code should " @@ -17109,19 +17775,19 @@ msgid "" "details." msgstr "" -#: ../../source/ref-changelog.md:982 +#: ../../source/ref-changelog.md:1074 msgid "" "*Code example:* note the additional dictionary return values in both " "`FlwrClient.fit` and `FlwrClient.evaluate`:" msgstr "" -#: ../../source/ref-changelog.md:997 +#: ../../source/ref-changelog.md:1089 msgid "" "**Generalized** `config` **argument in** `Client.fit` **and** " "`Client.evaluate` ([#595](https://github.com/adap/flower/pull/595))" msgstr "" -#: ../../source/ref-changelog.md:999 +#: ../../source/ref-changelog.md:1091 msgid "" "The `config` argument used to be of type `Dict[str, str]`, which means " "that dictionary values were expected to be strings. The new release " @@ -17129,58 +17795,58 @@ msgid "" "`bytes`, `float`, `int`, `str`." msgstr "" -#: ../../source/ref-changelog.md:1001 +#: ../../source/ref-changelog.md:1093 msgid "" "This means one can now pass almost arbitrary values to `fit`/`evaluate` " "using the `config` dictionary. Yay, no more `str(epochs)` on the server-" "side and `int(config[\"epochs\"])` on the client side!" msgstr "" -#: ../../source/ref-changelog.md:1003 +#: ../../source/ref-changelog.md:1095 msgid "" "*Code example:* note that the `config` dictionary now contains non-`str` " "values in both `Client.fit` and `Client.evaluate`:" msgstr "" -#: ../../source/ref-changelog.md:1020 +#: ../../source/ref-changelog.md:1112 msgid "v0.13.0 (2021-01-08)" msgstr "" -#: ../../source/ref-changelog.md:1024 +#: ../../source/ref-changelog.md:1116 msgid "" "New example: PyTorch From Centralized To Federated " "([#549](https://github.com/adap/flower/pull/549))" msgstr "" -#: ../../source/ref-changelog.md:1025 +#: ../../source/ref-changelog.md:1117 msgid "Improved documentation" msgstr "" -#: ../../source/ref-changelog.md:1026 +#: ../../source/ref-changelog.md:1118 msgid "New documentation theme ([#551](https://github.com/adap/flower/pull/551))" msgstr "" -#: ../../source/ref-changelog.md:1027 +#: ../../source/ref-changelog.md:1119 msgid "New API reference ([#554](https://github.com/adap/flower/pull/554))" msgstr "" -#: ../../source/ref-changelog.md:1028 +#: ../../source/ref-changelog.md:1120 msgid "" "Updated examples documentation " "([#549](https://github.com/adap/flower/pull/549))" msgstr "" -#: ../../source/ref-changelog.md:1029 +#: ../../source/ref-changelog.md:1121 msgid "" "Removed obsolete documentation " "([#548](https://github.com/adap/flower/pull/548))" msgstr "" -#: ../../source/ref-changelog.md:1031 +#: ../../source/ref-changelog.md:1123 msgid "Bugfix:" msgstr "" -#: ../../source/ref-changelog.md:1033 +#: ../../source/ref-changelog.md:1125 msgid "" "`Server.fit` does not disconnect clients when finished, disconnecting the" " clients is now handled in `flwr.server.start_server` " @@ -17188,28 +17854,28 @@ msgid "" "[#540](https://github.com/adap/flower/issues/540))." msgstr "" -#: ../../source/ref-changelog.md:1035 +#: ../../source/ref-changelog.md:1127 msgid "v0.12.0 (2020-12-07)" msgstr "" -#: ../../source/ref-changelog.md:1037 ../../source/ref-changelog.md:1053 +#: ../../source/ref-changelog.md:1129 ../../source/ref-changelog.md:1145 msgid "Important changes:" msgstr "" -#: ../../source/ref-changelog.md:1039 +#: ../../source/ref-changelog.md:1131 msgid "" "Added an example for embedded devices " "([#507](https://github.com/adap/flower/pull/507))" msgstr "" -#: ../../source/ref-changelog.md:1040 +#: ../../source/ref-changelog.md:1132 msgid "" "Added a new NumPyClient (in addition to the existing KerasClient) " "([#504](https://github.com/adap/flower/pull/504) " "[#508](https://github.com/adap/flower/pull/508))" msgstr "" -#: ../../source/ref-changelog.md:1041 +#: ../../source/ref-changelog.md:1133 msgid "" "Deprecated `flwr_example` package and started to migrate examples into " "the top-level `examples` directory " @@ -17217,15 +17883,15 @@ msgid "" "[#512](https://github.com/adap/flower/pull/512))" msgstr "" -#: ../../source/ref-changelog.md:1043 +#: ../../source/ref-changelog.md:1135 msgid "v0.11.0 (2020-11-30)" msgstr "" -#: ../../source/ref-changelog.md:1045 +#: ../../source/ref-changelog.md:1137 msgid "Incompatible changes:" msgstr "" -#: ../../source/ref-changelog.md:1047 +#: ../../source/ref-changelog.md:1139 msgid "" "Renamed strategy methods " "([#486](https://github.com/adap/flower/pull/486)) to unify the naming of " @@ -17235,48 +17901,48 @@ msgid "" "migrate rename the following `Strategy` methods accordingly:" msgstr "" -#: ../../source/ref-changelog.md:1048 +#: ../../source/ref-changelog.md:1140 msgid "`on_configure_evaluate` => `configure_evaluate`" msgstr "" -#: ../../source/ref-changelog.md:1049 +#: ../../source/ref-changelog.md:1141 msgid "`on_aggregate_evaluate` => `aggregate_evaluate`" msgstr "" -#: ../../source/ref-changelog.md:1050 +#: ../../source/ref-changelog.md:1142 msgid "`on_configure_fit` => `configure_fit`" msgstr "" -#: ../../source/ref-changelog.md:1051 +#: ../../source/ref-changelog.md:1143 msgid "`on_aggregate_fit` => `aggregate_fit`" msgstr "" -#: ../../source/ref-changelog.md:1055 +#: ../../source/ref-changelog.md:1147 msgid "" "Deprecated `DefaultStrategy` " "([#479](https://github.com/adap/flower/pull/479)). To migrate use " "`FedAvg` instead." msgstr "" -#: ../../source/ref-changelog.md:1056 +#: ../../source/ref-changelog.md:1148 msgid "" "Simplified examples and baselines " "([#484](https://github.com/adap/flower/pull/484))." msgstr "" -#: ../../source/ref-changelog.md:1057 +#: ../../source/ref-changelog.md:1149 msgid "" "Removed presently unused `on_conclude_round` from strategy interface " "([#483](https://github.com/adap/flower/pull/483))." msgstr "" -#: ../../source/ref-changelog.md:1058 +#: ../../source/ref-changelog.md:1150 msgid "" "Set minimal Python version to 3.6.1 instead of 3.6.9 " "([#471](https://github.com/adap/flower/pull/471))." msgstr "" -#: ../../source/ref-changelog.md:1059 +#: ../../source/ref-changelog.md:1151 msgid "" "Improved `Strategy` docstrings " "([#470](https://github.com/adap/flower/pull/470))." @@ -20343,7 +21009,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" +msgid "|93b02017c78049bbbd5ae456dcb2c91b|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 @@ -20358,7 +21024,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -msgid "|5aa1711387d74d0f8b9c499e1a51627e|" +msgid "|01471150fd5144c080a176b43e92a3ff|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 @@ -20379,7 +21045,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -msgid "|2bc8e069228d4873804061ff4a95048c|" +msgid "|9bc21c7dbd17444a8f070c60786e3484|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 @@ -20395,7 +21061,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -msgid "|c258488766324dc9a6807f0e7c4fd5f4|" +msgid "|3047bbce54b34099ae559963d0420d79|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 @@ -20411,7 +21077,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -msgid "|d5f962c3f4ec48529efda980868c14b0|" +msgid "|e9f8ce948593444fb838d2f354c7ec5d|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 @@ -20426,7 +21092,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -msgid "|a5eccea18d4c43a68b54b65043cabef8|" +msgid "|c24c1478b30e4f74839208628a842d1e|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 @@ -20446,7 +21112,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" +msgid "|1b3613d7a58847b59e1d3180802dbc09|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 @@ -20461,7 +21127,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -msgid "|241fc906441a4f038c625a19d30d01b2|" +msgid "|9980b5213db547d0b8024a50992b9e3f|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 @@ -20601,7 +21267,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -msgid "|0aa5aa05810b44b6a835cecce28f3137|" +msgid "|c7afb4c92d154bfaa5e8cb9a150e17f1|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 @@ -20625,7 +21291,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -msgid "|c742940dd4bf4de09d8d0d5e8d179638|" +msgid "|032eb6fed6924ac387b9f13854919196|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 @@ -20649,7 +21315,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -msgid "|1f169ab4601a47e1a226f1628f4ebddb|" +msgid "|fbf225add7fd4df5a9bf25a95597d954|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 @@ -20672,7 +21338,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" +msgid "|7efbe3d29d8349b89594e8947e910525|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 @@ -20710,7 +21376,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -msgid "|72939caf6e294b0986fee6dde96614d7|" +msgid "|329fb3c04c744eda83bb51fa444c2266|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 @@ -20804,7 +21470,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +msgid "|c00bf2750bc24d229737a0fe1395f0fc|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 @@ -23908,3 +24574,335 @@ msgstr "" #~ msgid "|ff726bc5505e432388ee2fdd6ef420b9|" #~ msgstr "" +#~ msgid "" +#~ "Currently, Flower provides two images, a" +#~ " ``base`` image and a ``superlink`` " +#~ "image. The base image, as the name" +#~ " suggests, contains basic dependencies that" +#~ " the SuperLink needs. This includes " +#~ "system dependencies, Python and Python " +#~ "tools. The SuperLink image is based " +#~ "on the base image, but it " +#~ "additionally installs the SuperLink using " +#~ "``pip``." +#~ msgstr "" +#~ "Atualmente, Flower fornece duas imagens, " +#~ "uma imagem base e uma imagem de" +#~ " servidor. Também haverá uma imagem " +#~ "de cliente em breve. A imagem " +#~ "base, como o nome sugere, contém " +#~ "dependências básicas que tanto o " +#~ "servidor quanto o cliente precisam. Isso" +#~ " inclui dependências do sistema, Python " +#~ "e ferramentas Python. A imagem do " +#~ "servidor é baseada na imagem base, " +#~ "mas também instala o servidor Flower " +#~ "usando ``pip```." + +#~ msgid "``3.11``" +#~ msgstr "``3.11``" + +#~ msgid "Defaults to ``22.04``." +#~ msgstr "Como padrão ``22.04``." + +#~ msgid "Building the SuperLink image" +#~ msgstr "Construindo a imagem do servidor" + +#~ msgid "Defaults to ``flwr/base``." +#~ msgstr "Pré-definido para ``flwr/server``." + +#~ msgid "The Python version of the base image." +#~ msgstr "O nome do repositório da imagem base." + +#~ msgid "Defaults to ``py3.11``." +#~ msgstr "Como padrão ``22.04``." + +#~ msgid "Defaults to ``ubuntu22.04``." +#~ msgstr "Pré-definido para ``py3.11-ubuntu22.04``." + +#~ msgid "The PyPI package to install." +#~ msgstr "" + +#~ msgid "Defaults to ``flwr``." +#~ msgstr "Pré-definido para ``flwr/server``." + +#~ msgid "" +#~ "The name of image is ``flwr_superlink``" +#~ " and the tag ``0.1.0``. Remember that" +#~ " the build arguments as well as " +#~ "the name and tag can be adapted" +#~ " to your needs. These values serve" +#~ " as examples only." +#~ msgstr "" +#~ "O nome da imagem é ``flwr_server`` " +#~ "e a tag ``0.1.0``. Lembre-se que" +#~ " os argumentos de compilação, bem " +#~ "como o nome e a tag podem " +#~ "ser adaptados às suas necessidades. " +#~ "Esses valores servem apenas como " +#~ "exemplos." + +#~ msgid "Creating New Messages" +#~ msgstr "Criando novas mensagens" + +#~ msgid "" +#~ "This is a simple guide for " +#~ "creating a new type of message " +#~ "between the server and clients in " +#~ "Flower." +#~ msgstr "" + +#~ msgid "" +#~ "Let's suppose we have the following " +#~ "example functions in :code:`server.py` and " +#~ ":code:`numpy_client.py`..." +#~ msgstr "" + +#~ msgid "Server's side:" +#~ msgstr "" + +#~ msgid "Client's side:" +#~ msgstr "" + +#~ msgid "" +#~ "Let's now see what we need to " +#~ "implement in order to get this " +#~ "simple function between the server and" +#~ " client to work!" +#~ msgstr "" + +#~ msgid "Message Types for Protocol Buffers" +#~ msgstr "" + +#~ msgid "" +#~ "The first thing we need to do " +#~ "is to define a message type for" +#~ " the RPC system in :code:`transport.proto`." +#~ " Note that we have to do it " +#~ "for both the request and response " +#~ "messages. For more details on the " +#~ "syntax of proto3, please see the " +#~ "`official documentation `_." +#~ msgstr "" + +#~ msgid "Within the :code:`ServerMessage` block:" +#~ msgstr "" + +#~ msgid "Within the ClientMessage block:" +#~ msgstr "" + +#~ msgid "" +#~ "Make sure to also add a field " +#~ "of the newly created message type " +#~ "in :code:`oneof msg`." +#~ msgstr "" + +#~ msgid "Once that is done, we will compile the file with:" +#~ msgstr "" + +#~ msgid "If it compiles successfully, you should see the following message:" +#~ msgstr "" + +#~ msgid "Serialization and Deserialization Functions" +#~ msgstr "" + +#~ msgid "" +#~ "Our next step is to add functions" +#~ " to serialize and deserialize Python " +#~ "datatypes to or from our defined " +#~ "RPC message types. You should add " +#~ "these functions in :code:`serde.py`." +#~ msgstr "" + +#~ msgid "The four functions:" +#~ msgstr "" + +#~ msgid "Sending the Message from the Server" +#~ msgstr "" + +#~ msgid "" +#~ "Now write the request function in " +#~ "your Client Proxy class (e.g., " +#~ ":code:`grpc_client_proxy.py`) using the serde " +#~ "functions you just created:" +#~ msgstr "" + +#~ msgid "Receiving the Message by the Client" +#~ msgstr "" + +#~ msgid "" +#~ "Last step! Modify the code in " +#~ ":code:`message_handler.py` to check the field" +#~ " of your message and call the " +#~ ":code:`example_response` function. Remember to " +#~ "use the serde functions!" +#~ msgstr "" + +#~ msgid "Within the handle function:" +#~ msgstr "" + +#~ msgid "And add a new function:" +#~ msgstr "" + +#~ msgid "Hopefully, when you run your program you will get the intended result!" +#~ msgstr "" + +#~ msgid "" +#~ "The simplest way to get started " +#~ "with Flower is by using the " +#~ "pre-made Docker images, which you can" +#~ " find on `Docker Hub " +#~ "`__." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to persist the state" +#~ " of the SuperLink on your host " +#~ "system, all you need to do is " +#~ "specify a path where you want to" +#~ " save the file on your host " +#~ "system and a name for the database" +#~ " file. In the example below, we " +#~ "tell Docker via the flag ``--volume``" +#~ " to mount the user's home directory" +#~ " (``~/`` on your host) into the " +#~ "``/app/`` directory of the container. " +#~ "Furthermore, we use the flag " +#~ "``--database`` to specify the name of" +#~ " the database file." +#~ msgstr "" + +#~ msgid "" +#~ "As soon as the SuperLink starts, " +#~ "the file ``state.db`` is created in " +#~ "the user's home directory on your " +#~ "host system. If the file already " +#~ "exists, the SuperLink tries to restore" +#~ " the state from the file. To " +#~ "start the SuperLink with an empty " +#~ "database, simply remove the ``state.db`` " +#~ "file." +#~ msgstr "" + +#~ msgid "" +#~ "Assuming all files we need are in" +#~ " the local ``certificates`` directory, we" +#~ " can use the flag ``--volume`` to " +#~ "mount the local directory into the " +#~ "``/app/`` directory of the container. " +#~ "This allows the SuperLink to access " +#~ "the files within the container. Finally," +#~ " we pass the names of the " +#~ "certificates to the SuperLink with the" +#~ " ``--certificates`` flag." +#~ msgstr "" + +#~ msgid "" +#~ "``--server 192.168.1.100:9092``: This option " +#~ "specifies the address of the SuperLinks" +#~ " Fleet" +#~ msgstr "" + +#~ msgid "" +#~ "Assuming the certificate already exists " +#~ "locally, we can use the flag " +#~ "``--volume`` to mount the local " +#~ "certificate into the container's ``/app/`` " +#~ "directory. This allows the SuperNode to" +#~ " access the certificate within the " +#~ "container. Use the ``--certificates`` flag " +#~ "when starting the container." +#~ msgstr "" + +#~ msgid "" +#~ "``--server 192.168.1.100:9091``: This option " +#~ "specifies the address of the SuperLinks" +#~ " Driver" +#~ msgstr "" + +#~ msgid "" +#~ "Assuming the certificate already exists " +#~ "locally, we can use the flag " +#~ "``--volume`` to mount the local " +#~ "certificate into the container's ``/app/`` " +#~ "directory. This allows the ServerApp to" +#~ " access the certificate within the " +#~ "container. Use the ``--certificates`` flag " +#~ "when starting the container." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to use a different " +#~ "version of Flower, for example Flower" +#~ " nightly, you can do so by " +#~ "changing the tag. All available versions" +#~ " are on `Docker Hub " +#~ "`__." +#~ msgstr "" + +#~ msgid "" +#~ "Here's another example to start with " +#~ "HTTPS. Use the ``--certificates`` command " +#~ "line argument to pass paths to (CA" +#~ " certificate, server certificate, and " +#~ "server private key)." +#~ msgstr "" + +#~ msgid ":py:obj:`run_driver_api `\\ \\(\\)" +#~ msgstr "" + +#~ msgid "Run Flower server (Driver API)." +#~ msgstr "" + +#~ msgid ":py:obj:`run_fleet_api `\\ \\(\\)" +#~ msgstr "" + +#~ msgid "Run Flower server (Fleet API)." +#~ msgstr "" + +#~ msgid "Unreleased" +#~ msgstr "" + +#~ msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" +#~ msgstr "" + +#~ msgid "|5aa1711387d74d0f8b9c499e1a51627e|" +#~ msgstr "" + +#~ msgid "|2bc8e069228d4873804061ff4a95048c|" +#~ msgstr "" + +#~ msgid "|c258488766324dc9a6807f0e7c4fd5f4|" +#~ msgstr "" + +#~ msgid "|d5f962c3f4ec48529efda980868c14b0|" +#~ msgstr "" + +#~ msgid "|a5eccea18d4c43a68b54b65043cabef8|" +#~ msgstr "" + +#~ msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" +#~ msgstr "" + +#~ msgid "|241fc906441a4f038c625a19d30d01b2|" +#~ msgstr "" + +#~ msgid "|0aa5aa05810b44b6a835cecce28f3137|" +#~ msgstr "" + +#~ msgid "|c742940dd4bf4de09d8d0d5e8d179638|" +#~ msgstr "" + +#~ msgid "|1f169ab4601a47e1a226f1628f4ebddb|" +#~ msgstr "" + +#~ msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" +#~ msgstr "" + +#~ msgid "|72939caf6e294b0986fee6dde96614d7|" +#~ msgstr "" + +#~ msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +#~ msgstr "" + diff --git a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po b/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po index 7ca5b00176fb..d07217ea35f7 100644 --- a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po +++ b/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po @@ -7,17 +7,16 @@ msgid "" msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-05-28 11:47+0200\n" +"POT-Creation-Date: 2024-06-17 16:09+0200\n" "PO-Revision-Date: 2024-06-12 10:09+0000\n" "Last-Translator: Yan Gao \n" -"Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" +"Language-Team: Chinese (Simplified) \n" +"Plural-Forms: nplurals=1; plural=0;\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" -"X-Generator: Weblate 5.6-dev\n" "Generated-By: Babel 2.15.0\n" #: ../../source/contributor-explanation-architecture.rst:2 @@ -63,35 +62,36 @@ msgstr "如何在本地搭建Docker Flower images" msgid "" "Flower provides pre-made docker images on `Docker Hub " "`_ that include all necessary dependencies" -" for running the SuperLink. You can also build your own custom docker " -"images from scratch with a different version of Python or Ubuntu if that " -"is what you need. In this guide, we will explain what images exist and " -"how to build them locally." +" for running the SuperLink, SuperNode or ServerApp. You can also build " +"your own custom docker images from scratch with a different version of " +"Python or Linux distribution (Ubuntu/Alpine) if that is what you need. In" +" this guide, we will explain what images exist and how to build them " +"locally." msgstr "" "Flower 在 `Docker Hub `_ " "上提供了预制的 docker 镜像,其中包括运行服务器所需的所有依赖项。如果你需要,也可以使用不同版本的 Python 或 Ubuntu " "从头开始构建自己的定制 docker 镜像。在本指南中,我们将介绍有哪些镜像,以及如何在本地构建它们。" -#: ../../source/contributor-how-to-build-docker-images.rst:9 +#: ../../source/contributor-how-to-build-docker-images.rst:10 #, fuzzy msgid "" "Before we can start, we need to meet a few prerequisites in our local " "development environment." msgstr "在开始之前,我们需要在本地开发环境中满足一些先决条件。" -#: ../../source/contributor-how-to-build-docker-images.rst:11 +#: ../../source/contributor-how-to-build-docker-images.rst:12 #, fuzzy msgid "Clone the flower repository." msgstr "**叉花仓库**" -#: ../../source/contributor-how-to-build-docker-images.rst:17 -#: ../../source/how-to-run-flower-using-docker.rst:144 +#: ../../source/contributor-how-to-build-docker-images.rst:18 +#: ../../source/how-to-run-flower-using-docker.rst:165 #, fuzzy msgid "Verify the Docker daemon is running." msgstr "验证 Docker 守护进程是否正在运行。" -#: ../../source/contributor-how-to-build-docker-images.rst:19 -#: ../../source/how-to-run-flower-using-docker.rst:146 +#: ../../source/contributor-how-to-build-docker-images.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:167 #, fuzzy msgid "" "Please follow the first section on :doc:`Run Flower using Docker ` " "的第一节,其中更详细地介绍了这一步骤。" -#: ../../source/contributor-how-to-build-docker-images.rst:23 -#, fuzzy -msgid "" -"Currently, Flower provides two images, a ``base`` image and a " -"``superlink`` image. The base image, as the name suggests, contains basic" -" dependencies that the SuperLink needs. This includes system " -"dependencies, Python and Python tools. The SuperLink image is based on " -"the base image, but it additionally installs the SuperLink using ``pip``." -msgstr "" -"目前,Flower " -"提供两个镜像,一个基础镜像和一个服务器镜像。不久还将推出客户端镜像。基础镜像,顾名思义,包含服务器和客户端都需要的基本依赖项。其中包括系统依赖项、Python" -" 和 Python 工具。服务器镜像基于基础镜像,但它会使用 ``pip`` 额外安装 Flower 服务器。" - -#: ../../source/contributor-how-to-build-docker-images.rst:28 +#: ../../source/contributor-how-to-build-docker-images.rst:25 #, fuzzy msgid "" "The build instructions that assemble the images are located in the " @@ -121,128 +108,176 @@ msgid "" "``src/docker``." msgstr "组装镜像的构建说明位于各自的 Dockerfile 中。你可以在 ``src/docker`` 的子目录中找到它们。" -#: ../../source/contributor-how-to-build-docker-images.rst:31 +#: ../../source/contributor-how-to-build-docker-images.rst:28 #, fuzzy msgid "" -"Both, base and SuperLink image are configured via build arguments. " -"Through build arguments, we can make our build more flexible. For " -"example, in the base image, we can specify the version of Python to " -"install using the ``PYTHON_VERSION`` build argument. Some of the build " -"arguments have default values, others must be specified when building the" -" image. All available build arguments for each image are listed in one of" -" the tables below." +"Flower Docker images are configured via build arguments. Through build " +"arguments, we can make the creation of images more flexible. For example," +" in the base image, we can specify the version of Python to install using" +" the ``PYTHON_VERSION`` build argument. Some of the build arguments have " +"default values, others must be specified when building the image. All " +"available build arguments for each image are listed in one of the tables " +"below." msgstr "" "基础镜像和服务器镜像都是通过构建参数配置的。通过联编参数,我们可以使联编更加灵活。例如,在基础镜像中,我们可以使用 " "``PYTHON_VERSION`` 联编参数指定要安装的 Python " "版本。有些联编参数有默认值,有些则必须在联编映像时指定。每个映像的所有可用联编参数都列在下表中。" -#: ../../source/contributor-how-to-build-docker-images.rst:38 +#: ../../source/contributor-how-to-build-docker-images.rst:35 #, fuzzy msgid "Building the base image" msgstr "加载数据" -#: ../../source/contributor-how-to-build-docker-images.rst:44 -#: ../../source/contributor-how-to-build-docker-images.rst:86 +#: ../../source/contributor-how-to-build-docker-images.rst:41 +#: ../../source/contributor-how-to-build-docker-images.rst:98 #, fuzzy msgid "Build argument" msgstr "构建文档" -#: ../../source/contributor-how-to-build-docker-images.rst:45 -#: ../../source/contributor-how-to-build-docker-images.rst:87 +#: ../../source/contributor-how-to-build-docker-images.rst:42 +#: ../../source/contributor-how-to-build-docker-images.rst:99 #, fuzzy msgid "Description" msgstr "停用" -#: ../../source/contributor-how-to-build-docker-images.rst:46 -#: ../../source/contributor-how-to-build-docker-images.rst:88 +#: ../../source/contributor-how-to-build-docker-images.rst:43 +#: ../../source/contributor-how-to-build-docker-images.rst:100 #, fuzzy msgid "Required" msgstr "所需变更" -#: ../../source/contributor-how-to-build-docker-images.rst:47 -#: ../../source/contributor-how-to-build-docker-images.rst:89 +#: ../../source/contributor-how-to-build-docker-images.rst:44 +#: ../../source/contributor-how-to-build-docker-images.rst:101 #, fuzzy msgid "Example" msgstr "实例" +#: ../../source/contributor-how-to-build-docker-images.rst:45 +msgid "``DISTRO``" +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:46 +#, fuzzy +msgid "The Linux distribution to use as the base image." +msgstr "基础镜像的存储库名称。" + +#: ../../source/contributor-how-to-build-docker-images.rst:47 +#: ../../source/contributor-how-to-build-docker-images.rst:51 +#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:71 +#: ../../source/contributor-how-to-build-docker-images.rst:104 +#, fuzzy +msgid "No" +msgstr "现在" + #: ../../source/contributor-how-to-build-docker-images.rst:48 -#: ../../source/contributor-how-to-build-docker-images.rst:94 #, fuzzy -msgid "``PYTHON_VERSION``" -msgstr "Python 版本" +msgid "``ubuntu``" +msgstr "``UBUNTU_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:49 #, fuzzy -msgid "Version of ``python`` to be installed." -msgstr "要安装的 ``python`` 版本。" +msgid "``DISTRO_VERSION``" +msgstr "``PIP_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:50 -#: ../../source/contributor-how-to-build-docker-images.rst:54 -#: ../../source/contributor-how-to-build-docker-images.rst:58 -#: ../../source/contributor-how-to-build-docker-images.rst:108 -#, fuzzy -msgid "Yes" -msgstr "类型" +msgid "Version of the Linux distribution." +msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:51 +#: ../../source/contributor-how-to-build-docker-images.rst:52 #, fuzzy -msgid "``3.11``" +msgid "``22.04``" msgstr "``1.0.0rc1``" -#: ../../source/contributor-how-to-build-docker-images.rst:52 +#: ../../source/contributor-how-to-build-docker-images.rst:53 +#, fuzzy +msgid "``PYTHON_VERSION``" +msgstr "Python 版本" + +#: ../../source/contributor-how-to-build-docker-images.rst:54 +#, fuzzy +msgid "Version of ``python`` to be installed." +msgstr "要安装的 ``python`` 版本。" + +#: ../../source/contributor-how-to-build-docker-images.rst:56 +msgid "``3.11`` or ``3.11.1``" +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:57 #, fuzzy msgid "``PIP_VERSION``" msgstr "``PIP_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:53 +#: ../../source/contributor-how-to-build-docker-images.rst:58 #, fuzzy msgid "Version of ``pip`` to be installed." msgstr "要安装的 ``pip` 版本。" -#: ../../source/contributor-how-to-build-docker-images.rst:55 +#: ../../source/contributor-how-to-build-docker-images.rst:59 +#: ../../source/contributor-how-to-build-docker-images.rst:63 +#: ../../source/contributor-how-to-build-docker-images.rst:67 +#: ../../source/contributor-how-to-build-docker-images.rst:108 +#, fuzzy +msgid "Yes" +msgstr "类型" + +#: ../../source/contributor-how-to-build-docker-images.rst:60 #, fuzzy msgid "``23.0.1``" msgstr "``1.0.0rc1``" -#: ../../source/contributor-how-to-build-docker-images.rst:56 +#: ../../source/contributor-how-to-build-docker-images.rst:61 #, fuzzy msgid "``SETUPTOOLS_VERSION``" msgstr "设置工具版本" -#: ../../source/contributor-how-to-build-docker-images.rst:57 +#: ../../source/contributor-how-to-build-docker-images.rst:62 #, fuzzy msgid "Version of ``setuptools`` to be installed." msgstr "要安装的 `setuptools`` 版本。" -#: ../../source/contributor-how-to-build-docker-images.rst:59 +#: ../../source/contributor-how-to-build-docker-images.rst:64 #, fuzzy msgid "``69.0.2``" msgstr "``1.0.0b0``" -#: ../../source/contributor-how-to-build-docker-images.rst:60 -#: ../../source/contributor-how-to-build-docker-images.rst:98 +#: ../../source/contributor-how-to-build-docker-images.rst:65 #, fuzzy -msgid "``UBUNTU_VERSION``" -msgstr "``UBUNTU_VERSION``" +msgid "``FLWR_VERSION``" +msgstr "``FLWR_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:61 +#: ../../source/contributor-how-to-build-docker-images.rst:66 #, fuzzy -msgid "Version of the official Ubuntu Docker image." -msgstr "官方 Ubuntu Docker 映像的版本。" +msgid "Version of Flower to be installed." +msgstr "要安装的 Flower 版本。" -#: ../../source/contributor-how-to-build-docker-images.rst:62 +#: ../../source/contributor-how-to-build-docker-images.rst:68 +#, fuzzy +msgid "``1.8.0``" +msgstr "``1.0.0b0``" + +#: ../../source/contributor-how-to-build-docker-images.rst:69 #, fuzzy -msgid "Defaults to ``22.04``." -msgstr "默认为 ``22.04``。" +msgid "``FLWR_PACKAGE``" +msgstr "``FLWR_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:65 +#: ../../source/contributor-how-to-build-docker-images.rst:70 +#, fuzzy +msgid "The Flower package to be installed." +msgstr "要安装的 PyPI 软件包。" + +#: ../../source/contributor-how-to-build-docker-images.rst:72 +msgid "``flwr`` or ``flwr-nightly``" +msgstr "" + +#: ../../source/contributor-how-to-build-docker-images.rst:75 #, fuzzy msgid "" -"The following example creates a base image with Python 3.11.0, pip 23.0.1" -" and setuptools 69.0.2:" +"The following example creates a base Ubuntu/Alpine image with Python " +"3.11.0, pip 23.0.1, setuptools 69.0.2 and Flower 1.8.0:" msgstr "下面的示例使用 Python 3.11.0、pip 23.0.1 和 setuptools 69.0.2 创建了基本映像:" -#: ../../source/contributor-how-to-build-docker-images.rst:76 +#: ../../source/contributor-how-to-build-docker-images.rst:88 #, fuzzy msgid "" "The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that " @@ -250,103 +285,59 @@ msgid "" "needs. These values serve as examples only." msgstr "图像名称为 ``flwr_base``,标记为 ``0.1.0``。请记住,编译参数以及名称和标记都可以根据需要进行调整。这些值仅供参考。" -#: ../../source/contributor-how-to-build-docker-images.rst:80 +#: ../../source/contributor-how-to-build-docker-images.rst:92 #, fuzzy -msgid "Building the SuperLink image" +msgid "Building the SuperLink/SuperNode or ServerApp image" msgstr "启动服务器" -#: ../../source/contributor-how-to-build-docker-images.rst:90 +#: ../../source/contributor-how-to-build-docker-images.rst:102 #, fuzzy msgid "``BASE_REPOSITORY``" msgstr "基础存储库" -#: ../../source/contributor-how-to-build-docker-images.rst:91 +#: ../../source/contributor-how-to-build-docker-images.rst:103 #, fuzzy msgid "The repository name of the base image." msgstr "基础镜像的存储库名称。" -#: ../../source/contributor-how-to-build-docker-images.rst:92 -#, fuzzy -msgid "Defaults to ``flwr/base``." -msgstr "默认为 ``flwr/server``。" - -#: ../../source/contributor-how-to-build-docker-images.rst:95 -#, fuzzy -msgid "The Python version of the base image." -msgstr "基础镜像的存储库名称。" - -#: ../../source/contributor-how-to-build-docker-images.rst:96 -#, fuzzy -msgid "Defaults to ``py3.11``." -msgstr "默认为 ``22.04``。" - -#: ../../source/contributor-how-to-build-docker-images.rst:99 -#, fuzzy -msgid "The Ubuntu version of the base image." -msgstr "基础镜像的存储库名称。" - -#: ../../source/contributor-how-to-build-docker-images.rst:100 -#, fuzzy -msgid "Defaults to ``ubuntu22.04``." -msgstr "默认为 ``py3.11-ubuntu22.04``。" - -#: ../../source/contributor-how-to-build-docker-images.rst:102 +#: ../../source/contributor-how-to-build-docker-images.rst:105 #, fuzzy -msgid "``FLWR_PACKAGE``" +msgid "``flwr/base``" msgstr "``FLWR_VERSION``" -#: ../../source/contributor-how-to-build-docker-images.rst:103 -#, fuzzy -msgid "The PyPI package to install." -msgstr "要安装的 PyPI 软件包。" - -#: ../../source/contributor-how-to-build-docker-images.rst:104 -#, fuzzy -msgid "Defaults to ``flwr``." -msgstr "默认为 ``flwr/server``。" - #: ../../source/contributor-how-to-build-docker-images.rst:106 #, fuzzy -msgid "``FLWR_VERSION``" -msgstr "``FLWR_VERSION``" +msgid "``BASE_IMAGE``" +msgstr "基础存储库" #: ../../source/contributor-how-to-build-docker-images.rst:107 #, fuzzy -msgid "Version of Flower to be installed." -msgstr "要安装的 Flower 版本。" +msgid "The Tag of the Flower base image." +msgstr "基础镜像的存储库名称。" #: ../../source/contributor-how-to-build-docker-images.rst:109 -#, fuzzy -msgid "``1.8.0``" -msgstr "``1.0.0b0``" +msgid "``1.8.0-py3.10-ubuntu22.04``" +msgstr "" -#: ../../source/contributor-how-to-build-docker-images.rst:112 +#: ../../source/contributor-how-to-build-docker-images.rst:111 #, fuzzy msgid "" -"The following example creates a SuperLink image with the official Flower " -"base image py3.11-ubuntu22.04 and Flower 1.8.0:" +"The following example creates a SuperLink/SuperNode or ServerApp image " +"with the official Flower base image:" msgstr "下面的示例使用官方的 Flower 基本镜像 py3.11-ubuntu22.04 和 Flower 1.7.0 创建了一个服务器镜像:" #: ../../source/contributor-how-to-build-docker-images.rst:122 #, fuzzy msgid "" -"The name of image is ``flwr_superlink`` and the tag ``0.1.0``. Remember " -"that the build arguments as well as the name and tag can be adapted to " -"your needs. These values serve as examples only." -msgstr "图像名称为 ``flwr_server``,标记为 ``0.1.0``。请记住,编译参数以及名称和标记都可以根据需要进行调整。这些值仅供参考。" - -#: ../../source/contributor-how-to-build-docker-images.rst:125 -#, fuzzy -msgid "" "If you want to use your own base image instead of the official Flower " -"base image, all you need to do is set the ``BASE_REPOSITORY``, " -"``PYTHON_VERSION`` and ``UBUNTU_VERSION`` build arguments." +"base image, all you need to do is set the ``BASE_REPOSITORY`` build " +"argument." msgstr "" "如果您想使用自己的基础图片而不是 Flower 官方的基础图片,只需设置 ``BASE_REPOSITORY`` 和 " "``BASE_IMAGE_TAG`` " "联编参数即可。`BASE_REPOSITORY``的值必须与您的图像名称一致,`BASE_IMAGE_TAG``的值必须与您的图像标签一致。" -#: ../../source/contributor-how-to-build-docker-images.rst:138 +#: ../../source/contributor-how-to-build-docker-images.rst:133 #, fuzzy msgid "After creating the image, we can test whether the image is working:" msgstr "创建图像后,我们可以测试图像是否正常工作:" @@ -479,127 +470,6 @@ msgstr "" "如果您想添加新语言,请先联系我们,可以在 `Slack `_ 上联系,也可以在我们的 " "`GitHub repo `_ 上提交问题。" -#: ../../source/contributor-how-to-create-new-messages.rst:2 -msgid "Creating New Messages" -msgstr "创建新信息" - -#: ../../source/contributor-how-to-create-new-messages.rst:4 -msgid "" -"This is a simple guide for creating a new type of message between the " -"server and clients in Flower." -msgstr "这是一个如何用Flower在服务器和客户端之间创建新类型的信息的简要指导。" - -#: ../../source/contributor-how-to-create-new-messages.rst:6 -msgid "" -"Let's suppose we have the following example functions in " -":code:`server.py` and :code:`numpy_client.py`..." -msgstr "假设我们在脚本code:`server.py`和code:`numpy_client.py`中有以下的示例函数..." - -#: ../../source/contributor-how-to-create-new-messages.rst:8 -msgid "Server's side:" -msgstr "在服务器端:" - -#: ../../source/contributor-how-to-create-new-messages.rst:17 -msgid "Client's side:" -msgstr "在客户端:" - -#: ../../source/contributor-how-to-create-new-messages.rst:26 -msgid "" -"Let's now see what we need to implement in order to get this simple " -"function between the server and client to work!" -msgstr "现在让我们来看看,为了让服务器和客户端之间的这个简单的函数正常工作,我们需要实现哪些功能!" - -#: ../../source/contributor-how-to-create-new-messages.rst:30 -msgid "Message Types for Protocol Buffers" -msgstr "协议缓冲区的信息类型" - -#: ../../source/contributor-how-to-create-new-messages.rst:32 -#, fuzzy -msgid "" -"The first thing we need to do is to define a message type for the RPC " -"system in :code:`transport.proto`. Note that we have to do it for both " -"the request and response messages. For more details on the syntax of " -"proto3, please see the `official documentation `_." -msgstr "" -"我们需要做的第一件事是在脚本code:`transport.proto`中定义 RPC " -"系统的消息类型。请注意,我们必须对请求信息和响应信息都这样做。有关 proto3 语法的更多详情,请参阅官方文档 " -"`_。" - -#: ../../source/contributor-how-to-create-new-messages.rst:35 -msgid "Within the :code:`ServerMessage` block:" -msgstr "在 :code:`ServerMessage` 代码块中:" - -#: ../../source/contributor-how-to-create-new-messages.rst:52 -msgid "Within the ClientMessage block:" -msgstr "在 ClientMessage 代码块中:" - -#: ../../source/contributor-how-to-create-new-messages.rst:70 -msgid "" -"Make sure to also add a field of the newly created message type in " -":code:`oneof msg`." -msgstr "确保在 :code:`oneof msg` 中也添加一个新创建的消息类型字段。" - -#: ../../source/contributor-how-to-create-new-messages.rst:72 -msgid "Once that is done, we will compile the file with:" -msgstr "完成后,我们将使用:" - -#: ../../source/contributor-how-to-create-new-messages.rst:78 -msgid "If it compiles successfully, you should see the following message:" -msgstr "如果编译成功,你应该会看到以下信息:" - -#: ../../source/contributor-how-to-create-new-messages.rst:87 -msgid "Serialization and Deserialization Functions" -msgstr "序列化和反序列化函数" - -#: ../../source/contributor-how-to-create-new-messages.rst:89 -msgid "" -"Our next step is to add functions to serialize and deserialize Python " -"datatypes to or from our defined RPC message types. You should add these " -"functions in :code:`serde.py`." -msgstr "" -"下一步是添加函数,以便将 Python 数据类型序列化和反序列化为我们定义的 RPC 消息类型或从我们定义的 RPC 消息类型反序列化和反序列化 " -"Python 数据类型。您应该在 :code:`serde.py` 中添加这些函数。" - -#: ../../source/contributor-how-to-create-new-messages.rst:91 -msgid "The four functions:" -msgstr "四种函数:" - -#: ../../source/contributor-how-to-create-new-messages.rst:112 -msgid "Sending the Message from the Server" -msgstr "从服务器发送信息" - -#: ../../source/contributor-how-to-create-new-messages.rst:114 -msgid "" -"Now write the request function in your Client Proxy class (e.g., " -":code:`grpc_client_proxy.py`) using the serde functions you just created:" -msgstr "现在,在客户端代理类(例如 :code:`grpc_client_proxy.py`)中使用刚才创建的 serde 函数编写请求函数:" - -#: ../../source/contributor-how-to-create-new-messages.rst:128 -msgid "Receiving the Message by the Client" -msgstr "由客户端接收信息" - -#: ../../source/contributor-how-to-create-new-messages.rst:130 -msgid "" -"Last step! Modify the code in :code:`message_handler.py` to check the " -"field of your message and call the :code:`example_response` function. " -"Remember to use the serde functions!" -msgstr "" -"最后一步 修改 :code:`message_handler.py` 中的代码,检查信息的字段并调用 " -":code:`example_response` 函数。记住使用 serde 函数!" - -#: ../../source/contributor-how-to-create-new-messages.rst:132 -msgid "Within the handle function:" -msgstr "在句柄函数内:" - -#: ../../source/contributor-how-to-create-new-messages.rst:139 -msgid "And add a new function:" -msgstr "并增加一个新函数:" - -#: ../../source/contributor-how-to-create-new-messages.rst:149 -msgid "Hopefully, when you run your program you will get the intended result!" -msgstr "希望您在运行程序时能得到预期的结果!" - #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:2 msgid "Develop in VSCode Dev Containers" msgstr "使用 VSCode Dev Containers 进行开发" @@ -966,89 +836,139 @@ msgstr "" msgid "Check the draft release on GitHub, and if everything is good, publish it." msgstr "检查 GitHub 上的发布稿,如果一切正常,就发布它。" +#: ../../source/contributor-how-to-release-flower.rst:15 +#, fuzzy +msgid "Trigger the CI for building the Docker images." +msgstr "官方 Ubuntu Docker 映像的版本。" + #: ../../source/contributor-how-to-release-flower.rst:17 +msgid "" +"To trigger the workflow, a collaborator must create a " +"``workflow_dispatch`` event in the GitHub CI. This can be done either " +"through the UI or via the GitHub CLI. The event requires only one input, " +"the Flower version, to be released." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:21 +#, fuzzy +msgid "**Via the UI**" +msgstr "**审查 PR**" + +#: ../../source/contributor-how-to-release-flower.rst:23 +msgid "" +"Go to the ``Build docker images`` workflow `page " +"`_." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:24 +msgid "" +"Click on the ``Run workflow`` button and type the new version of Flower " +"in the ``Version of Flower`` input field." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:25 +msgid "Click on the **green** ``Run workflow`` button." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:29 +msgid "**Via the GitHub CI**" +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:31 +msgid "" +"Make sure you are logged in via ``gh auth login`` and that the current " +"working directory is the root of the Flower repository." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:32 +msgid "" +"Trigger the workflow via ``gh workflow run docker-images.yml -f flwr-" +"version=``." +msgstr "" + +#: ../../source/contributor-how-to-release-flower.rst:35 msgid "After the release" msgstr "发布后" -#: ../../source/contributor-how-to-release-flower.rst:19 +#: ../../source/contributor-how-to-release-flower.rst:37 msgid "Create a pull request which contains the following changes:" msgstr "创建包含以下更改的拉取请求:" -#: ../../source/contributor-how-to-release-flower.rst:21 +#: ../../source/contributor-how-to-release-flower.rst:39 msgid "Increase the minor version in ``pyproject.toml`` by one." msgstr "将 ``pyproject.toml`` 中的次要版本增加一个。" -#: ../../source/contributor-how-to-release-flower.rst:22 +#: ../../source/contributor-how-to-release-flower.rst:40 msgid "Update all files which contain the current version number if necessary." msgstr "如有必要,更新包含当前版本号的所有文件。" -#: ../../source/contributor-how-to-release-flower.rst:23 +#: ../../source/contributor-how-to-release-flower.rst:41 msgid "Add a new ``Unreleased`` section in ``changelog.md``." msgstr "在 ``changelog.md`` 中添加新的 ``Unreleased`` 部分。" -#: ../../source/contributor-how-to-release-flower.rst:25 +#: ../../source/contributor-how-to-release-flower.rst:43 msgid "" "Merge the pull request on the same day (i.e., before a new nightly " "release gets published to PyPI)." msgstr "在同一天合并拉取请求(即在新版本发布到 PyPI 之前)。" -#: ../../source/contributor-how-to-release-flower.rst:28 +#: ../../source/contributor-how-to-release-flower.rst:46 msgid "Publishing a pre-release" msgstr "发布预发布版本" -#: ../../source/contributor-how-to-release-flower.rst:31 +#: ../../source/contributor-how-to-release-flower.rst:49 msgid "Pre-release naming" msgstr "释放前命名" -#: ../../source/contributor-how-to-release-flower.rst:33 +#: ../../source/contributor-how-to-release-flower.rst:51 msgid "" "PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases" " MUST use one of the following naming patterns:" msgstr "PyPI 支持预发布版本(alpha、beta、release candidate)。预发布版本必须使用以下命名模式之一:" -#: ../../source/contributor-how-to-release-flower.rst:35 +#: ../../source/contributor-how-to-release-flower.rst:53 msgid "Alpha: ``MAJOR.MINOR.PATCHaN``" msgstr "阿尔法 ``MAJOR.MINOR.PATCHaN``" -#: ../../source/contributor-how-to-release-flower.rst:36 +#: ../../source/contributor-how-to-release-flower.rst:54 msgid "Beta: ``MAJOR.MINOR.PATCHbN``" msgstr "贝塔: ``MAJOR.MINOR.PATCHbN``" -#: ../../source/contributor-how-to-release-flower.rst:37 +#: ../../source/contributor-how-to-release-flower.rst:55 msgid "Release candidate (RC): ``MAJOR.MINOR.PATCHrcN``" msgstr "版本代号 (RC): ``MAJOR.MINOR.PATCHrcN``" -#: ../../source/contributor-how-to-release-flower.rst:39 +#: ../../source/contributor-how-to-release-flower.rst:57 msgid "Examples include:" msgstr "例子包括:" -#: ../../source/contributor-how-to-release-flower.rst:41 +#: ../../source/contributor-how-to-release-flower.rst:59 msgid "``1.0.0a0``" msgstr "``1.0.0a0``" -#: ../../source/contributor-how-to-release-flower.rst:42 +#: ../../source/contributor-how-to-release-flower.rst:60 msgid "``1.0.0b0``" msgstr "``1.0.0b0``" -#: ../../source/contributor-how-to-release-flower.rst:43 +#: ../../source/contributor-how-to-release-flower.rst:61 msgid "``1.0.0rc0``" msgstr "``1.0.0rc0``" -#: ../../source/contributor-how-to-release-flower.rst:44 +#: ../../source/contributor-how-to-release-flower.rst:62 msgid "``1.0.0rc1``" msgstr "``1.0.0rc1``" -#: ../../source/contributor-how-to-release-flower.rst:46 +#: ../../source/contributor-how-to-release-flower.rst:64 msgid "" "This is in line with PEP-440 and the recommendations from the Python " "Packaging Authority (PyPA):" msgstr "这符合 PEP-440 和 Python 包装管理局 (PyPA) 的建议:" -#: ../../source/contributor-how-to-release-flower.rst:49 +#: ../../source/contributor-how-to-release-flower.rst:67 msgid "`PEP-440 `_" msgstr "`PEP-440 `_" -#: ../../source/contributor-how-to-release-flower.rst:50 +#: ../../source/contributor-how-to-release-flower.rst:68 msgid "" "`PyPA Choosing a versioning scheme " "`_" -#: ../../source/contributor-how-to-release-flower.rst:52 +#: ../../source/contributor-how-to-release-flower.rst:70 msgid "" "Note that the approach defined by PyPA is not compatible with SemVer " "2.0.0 spec, for details consult the `Semantic Versioning Specification " @@ -1068,26 +988,26 @@ msgstr "" "规范不兼容,详情请查阅《语义版本规范》`_(特别是关于优先级的第 11 项)。" -#: ../../source/contributor-how-to-release-flower.rst:55 +#: ../../source/contributor-how-to-release-flower.rst:73 msgid "Pre-release classification" msgstr "发布前分类" -#: ../../source/contributor-how-to-release-flower.rst:57 +#: ../../source/contributor-how-to-release-flower.rst:75 msgid "Should the next pre-release be called alpha, beta, or release candidate?" msgstr "下一个预发布版应该叫阿尔法版、贝塔版还是候选发布版?" -#: ../../source/contributor-how-to-release-flower.rst:59 +#: ../../source/contributor-how-to-release-flower.rst:77 msgid "" "RC: feature complete, no known issues (apart from issues that are " "classified as \"won't fix\" for the next stable release) - if no issues " "surface this will become the next stable release" msgstr "RC:功能完整,无已知问题(除了下一个稳定版中被列为 \"不会修复 \"的问题)--如果没有问题出现,这将成为下一个稳定版" -#: ../../source/contributor-how-to-release-flower.rst:60 +#: ../../source/contributor-how-to-release-flower.rst:78 msgid "Beta: feature complete, allowed to have known issues" msgstr "贝塔版:功能完整,允许存在已知问题" -#: ../../source/contributor-how-to-release-flower.rst:61 +#: ../../source/contributor-how-to-release-flower.rst:79 msgid "Alpha: not feature complete, allowed to have known issues" msgstr "阿尔法版:功能不完整,允许存在已知问题" @@ -1127,9 +1047,8 @@ msgid "" "most `Python 3.11 `_ for running Flower " "simulations." msgstr "" -"由于已知与 `ray `_ 不兼容," -"我们目前建议最多使用 `Python 3.11 `_ 运行 " -"Flower 仿真。" +"由于已知与 `ray `_ 不兼容,我们目前建议最多使用 `Python 3.11" +" `_ 运行 Flower 仿真。" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:19 #, fuzzy @@ -1750,8 +1669,8 @@ msgid "" "guidelines, otherwise it won't be possible to merge the PR. So in this " "case, a correct title might be ``docs(framework:skip) Fix typos``." msgstr "" -"应该修改标题以符合 :ref:`pr_title_format` 准则,否则将无法合并 " -"PR。因此,在这种情况下,正确的标题可能是 ``docs(framework:skip)修复错字``。" +"应该修改标题以符合 :ref:`pr_title_format` 准则,否则将无法合并 PR。因此,在这种情况下,正确的标题可能是 " +"``docs(framework:skip)修复错字``。" #: ../../source/contributor-tutorial-contribute-on-github.rst:196 msgid "" @@ -2003,8 +1922,7 @@ msgstr "将更改推送到分叉" msgid "" "Open a PR (as shown above) with title ``docs(framework) Update how-to " "guide title``" -msgstr "打开一个 PR(如上图所示),标题为\"`docs(framework) Update how-to guide " -"title```\"。" +msgstr "打开一个 PR(如上图所示),标题为\"`docs(framework) Update how-to guide title```\"。" #: ../../source/contributor-tutorial-contribute-on-github.rst:309 msgid "Wait for it to be approved!" @@ -2072,8 +1990,7 @@ msgid "" msgstr "" "其中 ```` 需要使用 ``{ci, fix, feat, docs, refactor, break}``, " "```` 应该使用 ``{framework, baselines, datasets, examples, 或者 '*' " -"当修改多个项目时需要使用 ':skip'标记}``, 并且 ```` " -"应该以一个大写的动词开始。" +"当修改多个项目时需要使用 ':skip'标记}``, 并且 ```` 应该以一个大写的动词开始。" #: ../../source/contributor-tutorial-contribute-on-github.rst:341 #, fuzzy @@ -2131,15 +2048,15 @@ msgstr "feat(框架) 添加 flwr 构建 CLI 命令。" #, fuzzy msgid "``Add flwr build CLI command.`` (missing ``()``)" msgstr "" -"``添加 flwr build CLI 命令.``(缺少``()``) ``Add flwr build " -"CLI command.`` (missing ``()``)" +"``添加 flwr build CLI 命令.``(缺少``()``) ``Add flwr build CLI " +"command.`` (missing ``()``)" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:2 msgid "Get started as a contributor" msgstr "成为贡献者" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 -#: ../../source/how-to-run-flower-using-docker.rst:132 +#: ../../source/how-to-run-flower-using-docker.rst:153 msgid "Prerequisites" msgstr "先决条件" @@ -2308,8 +2225,8 @@ msgid "" "``./dev/format.sh`` and ``./dev/test.sh`` scripts." msgstr "" "开发人员可利用 `pre-commit `_ " -"库将预提交钩子集成到工作流程中。预提交钩子被配置为执行两个主要操作: `./dev/" -"format.sh`` 和 ``./dev/test.sh`` 脚本。" +"库将预提交钩子集成到工作流程中。预提交钩子被配置为执行两个主要操作: `./dev/format.sh`` 和 ``./dev/test.sh``" +" 脚本。" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:110 #, fuzzy @@ -2341,16 +2258,14 @@ msgid "" "For developers who prefer not to install the hook permanently, it is " "possible to execute a one-time check prior to committing changes by using" " the following command:" -msgstr "对于不想永久安装钩子的开发人员,可以使用以下命令在提交更改之前执行一次性检查" -":" +msgstr "对于不想永久安装钩子的开发人员,可以使用以下命令在提交更改之前执行一次性检查:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:130 #, fuzzy msgid "" "This executes the formatting and linting checks/tests on all the files " "without modifying the default behavior of ``git commit``." -msgstr "这将在不修改 ``git commit`` " -"默认行为的情况下对所有文件执行格式化和词排检查/测试。" +msgstr "这将在不修改 ``git commit`` 默认行为的情况下对所有文件执行格式化和词排检查/测试。" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:133 msgid "Run Github Actions (CI) locally" @@ -4124,9 +4039,8 @@ msgid "" "Flower node authentication works similar to how GitHub SSH authentication" " works:" msgstr "" -"Flower 内置了对经过身份验证的超级节点的支持,您可以用它来验证连接到超级链接的" -"每个超级节点的身份。Flower 节点身份验证的工作方式与 GitHub SSH " -"身份验证的工作方式类似:" +"Flower 内置了对经过身份验证的超级节点的支持,您可以用它来验证连接到超级链接的每个超级节点的身份。Flower 节点身份验证的工作方式与 " +"GitHub SSH 身份验证的工作方式类似:" #: ../../source/how-to-authenticate-supernodes.rst:7 #, fuzzy @@ -4194,19 +4108,18 @@ msgid "" ":code:`SuperNode` that has both secure connections and node " "authentication enabled:" msgstr "" -"要启用节点验证,首先需要配置 SSL/TLS 连接,以确保 SuperLink<>SuperNode " -"通信的安全。您可以在 `_ 找到完整的指南。配置安全连接后,您就可以在长期运行的 " -"Flower :code:`SuperLink`中启用客户端身份验证。" -"使用以下终端命令启动一个同时启用了安全连接和节点验证的 Flower " +"要启用节点验证,首先需要配置 SSL/TLS 连接,以确保 SuperLink<>SuperNode 通信的安全。您可以在 " +"`_ " +"找到完整的指南。配置安全连接后,您就可以在长期运行的 Flower " +":code:`SuperLink`中启用客户端身份验证。使用以下终端命令启动一个同时启用了安全连接和节点验证的 Flower " ":code:`SuperNode`:" -#: ../../source/how-to-authenticate-supernodes.rst:36 +#: ../../source/how-to-authenticate-supernodes.rst:38 #, fuzzy msgid "Let's break down the authentication flags:" msgstr "让我们来分析一下身份验证标志:" -#: ../../source/how-to-authenticate-supernodes.rst:38 +#: ../../source/how-to-authenticate-supernodes.rst:40 #, fuzzy msgid "" "The first flag :code:`--auth-list-public-keys` expects a path to a CSV " @@ -4214,11 +4127,10 @@ msgid "" " public keys that are allowed to participate in a federation in one CSV " "file (:code:`.csv`)." msgstr "" -"第一个标志 :code:`--auth-list-public-keys`(密码:`--auth-list-public-keys`)" -"需要一个 CSV 文件路径,该文件存储了所有已知节点的公钥。您需要在一个 CSV " -"文件(:code:`.csv`)中存储所有允许参与联盟的已知节点公钥。" +"第一个标志 :code:`--auth-list-public-keys`(密码:`--auth-list-public-keys`)需要一个 " +"CSV 文件路径,该文件存储了所有已知节点的公钥。您需要在一个 CSV 文件(:code:`.csv`)中存储所有允许参与联盟的已知节点公钥。" -#: ../../source/how-to-authenticate-supernodes.rst:40 +#: ../../source/how-to-authenticate-supernodes.rst:42 #, fuzzy msgid "" "A valid CSV file storing known node public keys should list the keys in " @@ -4227,10 +4139,9 @@ msgid "" "known node public keys." msgstr "" "存储已知节点公开密钥的有效 CSV 文件应以 OpenSSH " -"格式列出密钥,以逗号分隔,不含任何注释。有关示例,请参阅我们的代码示例," -"其中包含一个包含两个已知节点公钥的 CSV 文件。" +"格式列出密钥,以逗号分隔,不含任何注释。有关示例,请参阅我们的代码示例,其中包含一个包含两个已知节点公钥的 CSV 文件。" -#: ../../source/how-to-authenticate-supernodes.rst:42 +#: ../../source/how-to-authenticate-supernodes.rst:44 #, fuzzy msgid "" "The second and third flags :code:`--auth-superlink-private-key` and :code" @@ -4238,11 +4149,11 @@ msgid "" "public keys. For development purposes, you can generate a private and " "public key pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" -"第二和第三个标记 :code:`--auth-superlink-private-key` 和 :code:`--auth-" -"superlink-public-key` 希望指向服务器私钥和公钥的路径。出于开发目的," -"您可以使用 :code:`ssh-keygen -t ecdsa -b 384` 生成一对私钥和公钥。" +"第二和第三个标记 :code:`--auth-superlink-private-key` 和 :code:`--auth-superlink-" +"public-key` 希望指向服务器私钥和公钥的路径。出于开发目的,您可以使用 :code:`ssh-keygen -t ecdsa -b " +"384` 生成一对私钥和公钥。" -#: ../../source/how-to-authenticate-supernodes.rst:45 +#: ../../source/how-to-authenticate-supernodes.rst:47 #, fuzzy msgid "" "In Flower 1.9, there is no support for dynamically removing, editing, or " @@ -4251,16 +4162,15 @@ msgid "" "start the server again. Support for dynamically changing the set of known" " nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" -"在 Flower 1.9 中,超级链接不支持动态删除、编辑或添加已知节点公钥。要更改已知" -"节点集,您需要关闭服务器,编辑 CSV 文件,然后重新启动服务器。" -"动态更改已知节点集的支持已列入 Flower 1.10(预计发布时间:6 月)的路线图。" +"在 Flower 1.9 中,超级链接不支持动态删除、编辑或添加已知节点公钥。要更改已知节点集,您需要关闭服务器,编辑 CSV " +"文件,然后重新启动服务器。动态更改已知节点集的支持已列入 Flower 1.10(预计发布时间:6 月)的路线图。" -#: ../../source/how-to-authenticate-supernodes.rst:51 +#: ../../source/how-to-authenticate-supernodes.rst:53 #, fuzzy msgid "Enable node authentication in :code:`SuperNode`" msgstr "在 :code:`SuperNode` 中启用节点验证" -#: ../../source/how-to-authenticate-supernodes.rst:53 +#: ../../source/how-to-authenticate-supernodes.rst:55 #, fuzzy msgid "" "Similar to the long-running Flower server (:code:`SuperLink`), you can " @@ -4268,11 +4178,10 @@ msgid "" "(:code:`SuperNode`). Use the following terminal command to start an " "authenticated :code:`SuperNode`:" msgstr "" -"与长期运行的 Flower 服务器(:code:`SuperLink`)类似,您也可以在长期运行的 " -"Flower 客户端(:code:`SuperNode`)中轻松启用节点身份验证。" -"使用以下终端命令启动已验证的 :code:`SuperNode`:" +"与长期运行的 Flower 服务器(:code:`SuperLink`)类似,您也可以在长期运行的 Flower " +"客户端(:code:`SuperNode`)中轻松启用节点身份验证。使用以下终端命令启动已验证的 :code:`SuperNode`:" -#: ../../source/how-to-authenticate-supernodes.rst:64 +#: ../../source/how-to-authenticate-supernodes.rst:66 #, fuzzy msgid "" "The :code:`--auth-supernode-private-key` flag expects a path to the " @@ -4281,16 +4190,16 @@ msgid "" "you can generate a private and public key pair using :code:`ssh-keygen -t" " ecdsa -b 384`." msgstr "" -":code:`--auth-supernode-private-key`标志需要节点私钥文件的路径,:code:`-auth-" -"supernode-public-key`标志需要节点公钥文件的路径。出于开发目的,可以使用 :code" -":`ssh-keygen -t ecdsa -b 384` 生成一对私钥和公钥。" +":code:`--auth-supernode-private-key`标志需要节点私钥文件的路径,:code:`-auth-supernode-" +"public-key`标志需要节点公钥文件的路径。出于开发目的,可以使用 :code:`ssh-keygen -t ecdsa -b 384` " +"生成一对私钥和公钥。" -#: ../../source/how-to-authenticate-supernodes.rst:68 +#: ../../source/how-to-authenticate-supernodes.rst:70 #, fuzzy msgid "Security notice" msgstr "安全通知" -#: ../../source/how-to-authenticate-supernodes.rst:70 +#: ../../source/how-to-authenticate-supernodes.rst:72 #, fuzzy msgid "" "The system's security relies on the credentials of the SuperLink and each" @@ -4301,18 +4210,17 @@ msgid "" "communication is done in a secure manner, using trusted communication " "methods." msgstr "" -"系统的安全性依赖于超级链接和每个超级节点的凭证。因此,必须保护和安全存储凭证" -",以避免公钥基础设施 (PKI) 假冒攻击等安全风险。节点验证机制还涉及人机交互,因" -"此请确保使用可信的通信方法,以安全的方式进行所有通信。" +"系统的安全性依赖于超级链接和每个超级节点的凭证。因此,必须保护和安全存储凭证,以避免公钥基础设施 (PKI) " +"假冒攻击等安全风险。节点验证机制还涉及人机交互,因此请确保使用可信的通信方法,以安全的方式进行所有通信。" -#: ../../source/how-to-authenticate-supernodes.rst:75 -#: ../../source/how-to-enable-ssl-connections.rst:65 +#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:68 #: ../../source/how-to-use-built-in-mods.rst:85 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:287 msgid "Conclusion" msgstr "总结" -#: ../../source/how-to-authenticate-supernodes.rst:77 +#: ../../source/how-to-authenticate-supernodes.rst:79 #, fuzzy msgid "" "You should now have learned how to start a long-running Flower server " @@ -4320,9 +4228,8 @@ msgid "" "authentication enabled. You should also know the significance of the " "private key and store it safely to minimize security risks." msgstr "" -"现在,您应该已经学会了如何启动长期运行的 Flower 服务器(:code:`SuperLink`)和" -"客户端(:code:`SuperNode`)并启用节点身份验证。您还应该知道私钥的重要性,并将" -"其安全存储,以尽量减少安全风险。" +"现在,您应该已经学会了如何启动长期运行的 Flower " +"服务器(:code:`SuperLink`)和客户端(:code:`SuperNode`)并启用节点身份验证。您还应该知道私钥的重要性,并将其安全存储,以尽量减少安全风险。" #: ../../source/how-to-configure-clients.rst:2 msgid "Configure clients" @@ -4636,7 +4543,7 @@ msgid "" " the previously generated certificates:" msgstr "现在我们将演示如何编写一个客户端,使用之前生成的脚本:" -#: ../../source/how-to-enable-ssl-connections.rst:47 +#: ../../source/how-to-enable-ssl-connections.rst:50 #, fuzzy msgid "" "When providing certificates, the server expects a tuple of three " @@ -4644,19 +4551,19 @@ msgid "" " key." msgstr "要启用 SSL,需要 CA 证书、服务器证书和服务器私钥。" -#: ../../source/how-to-enable-ssl-connections.rst:51 +#: ../../source/how-to-enable-ssl-connections.rst:54 #, fuzzy msgid "Client (SuperNode)" msgstr "客户端状态代码。" -#: ../../source/how-to-enable-ssl-connections.rst:53 +#: ../../source/how-to-enable-ssl-connections.rst:56 #, fuzzy msgid "" "Use the following terminal command to start a client (SuperNode) that " "uses the previously generated certificates:" msgstr "现在我们将演示如何编写一个客户端,使用之前生成的脚本:" -#: ../../source/how-to-enable-ssl-connections.rst:61 +#: ../../source/how-to-enable-ssl-connections.rst:64 #, fuzzy msgid "" "When setting :code:`root_certificates`, the client expects a file path to" @@ -4665,7 +4572,7 @@ msgstr "" "当设置 :code:`root_certificates` 时,客户端希望 PEM 编码的根证书是字节字符串。我们再次使用 " ":code:`Path` 来简化以字节字符串形式读取证书的过程。" -#: ../../source/how-to-enable-ssl-connections.rst:67 +#: ../../source/how-to-enable-ssl-connections.rst:70 #, fuzzy msgid "" "You should now have learned how to generate self-signed certificates " @@ -4673,21 +4580,21 @@ msgid "" "establish a secure connection to it." msgstr "现在,你应该已经学会了如何使用给定的脚本生成自签名证书、启动启用 SSL 的服务器并让客户端与其建立安全连接。" -#: ../../source/how-to-enable-ssl-connections.rst:72 +#: ../../source/how-to-enable-ssl-connections.rst:75 msgid "Additional resources" msgstr "补充资源" -#: ../../source/how-to-enable-ssl-connections.rst:74 +#: ../../source/how-to-enable-ssl-connections.rst:77 msgid "" "These additional sources might be relevant if you would like to dive " "deeper into the topic of certificates:" msgstr "如果您想更深入地了解证书主题,这些额外的资料来源可能有帮助:" -#: ../../source/how-to-enable-ssl-connections.rst:76 +#: ../../source/how-to-enable-ssl-connections.rst:79 msgid "`Let's Encrypt `_" msgstr "`让我们加密 `_" -#: ../../source/how-to-enable-ssl-connections.rst:77 +#: ../../source/how-to-enable-ssl-connections.rst:80 msgid "`certbot `_" msgstr "`certbot `_" @@ -5377,17 +5284,18 @@ msgstr "使用 Docker 运行 Flower" msgid "" "The simplest way to get started with Flower is by using the pre-made " "Docker images, which you can find on `Docker Hub " -"`__." +"`__. Supported architectures include " +"``amd64`` and ``arm64v8``." msgstr "" "开始使用 Flower 的最简单方法是使用预制的 Docker 镜像,您可以在 `Docker Hub " "`_ 上找到这些镜像。" -#: ../../source/how-to-run-flower-using-docker.rst:7 +#: ../../source/how-to-run-flower-using-docker.rst:8 #, fuzzy msgid "Before you start, make sure that the Docker daemon is running:" msgstr "开始之前,请确保 Docker 守护进程正在运行:" -#: ../../source/how-to-run-flower-using-docker.rst:14 +#: ../../source/how-to-run-flower-using-docker.rst:15 #, fuzzy msgid "" "If you do not see the version of Docker but instead get an error saying " @@ -5398,7 +5306,7 @@ msgstr "" "如果没有看到 Docker 的版本,而是出现找不到命令的错误,则需要先安装 Docker。你可以在 " "`_ 找到安装说明。" -#: ../../source/how-to-run-flower-using-docker.rst:20 +#: ../../source/how-to-run-flower-using-docker.rst:21 #, fuzzy msgid "" "On Linux, Docker commands require ``sudo`` privilege. If you want to " @@ -5409,7 +5317,7 @@ msgstr "" "在 Linux 上,Docker 命令需要 ``sudo`` 权限。如果你想避免使用 ``sudo``,可以按照 Docker 官方网站上的 " "`安装后步骤 `_进行操作。" -#: ../../source/how-to-run-flower-using-docker.rst:26 +#: ../../source/how-to-run-flower-using-docker.rst:27 #, fuzzy msgid "" "To ensure optimal performance and compatibility, the SuperLink, SuperNode" @@ -5417,26 +5325,25 @@ msgid "" "This guarantees seamless integration and avoids potential conflicts or " "issues that may arise from using different versions." msgstr "" -"为确保最佳性能和兼容性,SuperLink、SuperNode 和 ServerApp 映像在一起运行时必" -"须具有相同的版本。这可确保无缝集成,并避免因使用不同版本而可能产生的潜在冲突" -"或问题。" +"为确保最佳性能和兼容性,SuperLink、SuperNode 和 ServerApp " +"映像在一起运行时必须具有相同的版本。这可确保无缝集成,并避免因使用不同版本而可能产生的潜在冲突或问题。" -#: ../../source/how-to-run-flower-using-docker.rst:31 +#: ../../source/how-to-run-flower-using-docker.rst:32 #, fuzzy msgid "Flower SuperLink" msgstr "flower-superlink" -#: ../../source/how-to-run-flower-using-docker.rst:34 +#: ../../source/how-to-run-flower-using-docker.rst:35 #, fuzzy msgid "Quickstart" msgstr "快速入门 JAX" -#: ../../source/how-to-run-flower-using-docker.rst:36 +#: ../../source/how-to-run-flower-using-docker.rst:37 #, fuzzy msgid "If you're looking to try out Flower, you can use the following command:" msgstr "如果您想试用 Flower,可以使用以下命令:" -#: ../../source/how-to-run-flower-using-docker.rst:42 +#: ../../source/how-to-run-flower-using-docker.rst:43 #, fuzzy msgid "" "The command pulls the Docker image with the tag ``1.8.0`` from Docker " @@ -5447,7 +5354,7 @@ msgstr "" "Flower、Python 和 Ubuntu 的信息。在本例中,它使用了 Flower 1.7.0、Python 3.11 和 Ubuntu " "22.04。rm \"标记告诉 Docker 在退出后移除容器。" -#: ../../source/how-to-run-flower-using-docker.rst:48 +#: ../../source/how-to-run-flower-using-docker.rst:49 #, fuzzy msgid "" "By default, the Flower SuperLink keeps state in-memory. When using the " @@ -5458,7 +5365,7 @@ msgstr "" "默认情况下,Flower 服务器会将状态保存在内存中。使用 Docker 标志 ``--rm`` " "时,状态不会在容器启动之间持久化。下面我们将展示如何将状态保存到主机系统上的文件中。" -#: ../../source/how-to-run-flower-using-docker.rst:52 +#: ../../source/how-to-run-flower-using-docker.rst:53 #, fuzzy msgid "" "The ``-p :`` flag tells Docker to map the ports " @@ -5473,9 +5380,9 @@ msgstr "" "``http://localhost:9092`` 上访问 Fleet API。最后,标签后面的任何标志都会传递给 Flower " "服务器。在这里,我们传递的标志是 ``--insecure`` 。" -#: ../../source/how-to-run-flower-using-docker.rst:59 -#: ../../source/how-to-run-flower-using-docker.rst:238 -#: ../../source/how-to-run-flower-using-docker.rst:354 +#: ../../source/how-to-run-flower-using-docker.rst:60 +#: ../../source/how-to-run-flower-using-docker.rst:259 +#: ../../source/how-to-run-flower-using-docker.rst:376 #, fuzzy msgid "" "The ``--insecure`` flag enables insecure communication (using HTTP, not " @@ -5488,59 +5395,70 @@ msgstr "" "`_。" -#: ../../source/how-to-run-flower-using-docker.rst:64 +#: ../../source/how-to-run-flower-using-docker.rst:65 #, fuzzy msgid "" "You can use ``--help`` to view all available flags that the SuperLink " "supports:" msgstr "您可以使用 ``--help`` 查看服务器支持的所有可用标记:" -#: ../../source/how-to-run-flower-using-docker.rst:71 +#: ../../source/how-to-run-flower-using-docker.rst:72 #, fuzzy msgid "Mounting a volume to store the state on the host system" msgstr "在主机系统上挂载卷以存储状态" -#: ../../source/how-to-run-flower-using-docker.rst:73 -#, fuzzy +#: ../../source/how-to-run-flower-using-docker.rst:74 msgid "" "If you want to persist the state of the SuperLink on your host system, " -"all you need to do is specify a path where you want to save the file on " -"your host system and a name for the database file. In the example below, " -"we tell Docker via the flag ``--volume`` to mount the user's home " -"directory (``~/`` on your host) into the ``/app/`` directory of the " -"container. Furthermore, we use the flag ``--database`` to specify the " -"name of the database file." +"all you need to do is specify a directory where you want to save the file" +" on your host system and a name for the database file. By default, the " +"SuperLink container runs with a non-root user called ``app`` with the " +"user ID ``49999``. It is recommended to create new directory and change " +"the user ID of the directory to ``49999`` to ensure the mounted directory" +" has the proper permissions. If you later want to delete the directory, " +"you can change the user ID back to the current user ID by running ``sudo " +"chown -R $USER:$(id -gn) state``." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:82 +#, fuzzy +msgid "" +"In the example below, we create a new directory, change the user ID and " +"tell Docker via the flag ``--volume`` to mount the local ``state`` " +"directory into the ``/app/state`` directory of the container. " +"Furthermore, we use the flag ``--database`` to specify the name of the " +"database file." msgstr "" "如果想在主机系统上持久保存服务器的状态,只需在主机系统上指定保存文件的路径和数据库文件的名称即可。在下面的示例中,我们通过标志 ``-v`` 告诉" " Docker 将用户的主目录(主机上的 ``~/``)挂载到容器的 ``/app/`` 目录中。此外,我们使用标志 ``--database``" " 来指定数据库文件的名称。" -#: ../../source/how-to-run-flower-using-docker.rst:86 +#: ../../source/how-to-run-flower-using-docker.rst:95 #, fuzzy msgid "" "As soon as the SuperLink starts, the file ``state.db`` is created in the " -"user's home directory on your host system. If the file already exists, " -"the SuperLink tries to restore the state from the file. To start the " +"``state`` directory on your host system. If the file already exists, the " +"SuperLink tries to restore the state from the file. To start the " "SuperLink with an empty database, simply remove the ``state.db`` file." msgstr "" "服务器一启动,就会在主机系统的用户主目录下创建文件 " "``state.db``。如果该文件已经存在,服务器会尝试从该文件恢复状态。要以空数据库启动服务器,只需删除 ``state.db`` 文件即可。" -#: ../../source/how-to-run-flower-using-docker.rst:91 -#: ../../source/how-to-run-flower-using-docker.rst:260 -#: ../../source/how-to-run-flower-using-docker.rst:375 +#: ../../source/how-to-run-flower-using-docker.rst:100 +#: ../../source/how-to-run-flower-using-docker.rst:281 +#: ../../source/how-to-run-flower-using-docker.rst:397 #, fuzzy msgid "Enabling SSL for secure connections" msgstr "启用 SSL 连接" -#: ../../source/how-to-run-flower-using-docker.rst:93 +#: ../../source/how-to-run-flower-using-docker.rst:102 #, fuzzy msgid "" "To enable SSL, you will need a PEM-encoded root certificate, a PEM-" "encoded private key and a PEM-encoded certificate chain." msgstr "要启用 SSL,需要 CA 证书、服务器证书和服务器私钥。" -#: ../../source/how-to-run-flower-using-docker.rst:97 +#: ../../source/how-to-run-flower-using-docker.rst:106 #, fuzzy msgid "" "For testing purposes, you can generate your own self-signed certificates." @@ -5551,31 +5469,43 @@ msgstr "" "出于测试目的,你可以生成自己的自签名证书。启用 SSL 连接 `_ 页面中有一个部分将指导你完成这一过程。" -#: ../../source/how-to-run-flower-using-docker.rst:101 +#: ../../source/how-to-run-flower-using-docker.rst:110 #, fuzzy msgid "" "Assuming all files we need are in the local ``certificates`` directory, " "we can use the flag ``--volume`` to mount the local directory into the " -"``/app/`` directory of the container. This allows the SuperLink to access" -" the files within the container. Finally, we pass the names of the " -"certificates to the SuperLink with the ``--certificates`` flag." +"``/app/certificates/`` directory of the container. This allows the " +"SuperLink to access the files within the container. The ``ro`` stands for" +" ``read-only``. Docker volumes default to ``read-write``; that option " +"tells Docker to make the volume ``read-only`` instead. Finally, we pass " +"the names of the certificates and key file to the SuperLink with the " +"``--ssl-ca-certfile``, ``--ssl-certfile`` and ``--ssl-keyfile`` flag." msgstr "" "假设我们需要的所有文件都在本地的 ``certificates`` 目录中,我们可以使用标记 ``-v`` 将本地目录挂载到容器的 " "``/app/`` 目录中。这样,服务器就可以访问容器内的文件。最后,我们使用 ``--certificates`` 标志将证书名称传递给服务器。" -#: ../../source/how-to-run-flower-using-docker.rst:113 -#, fuzzy -msgid "Flower SuperNode" -msgstr "Flower 服务器" +#: ../../source/how-to-run-flower-using-docker.rst:128 +msgid "" +"Because Flower containers, by default, run with a non-root user ``app``, " +"the mounted files and directories must have the proper permissions for " +"the user ID ``49999``. For example, to change the user ID of all files in" +" the ``certificates/`` directory, you can run ``sudo chown -R 49999:49999" +" certificates/*``." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:134 +#, fuzzy +msgid "Flower SuperNode" +msgstr "Flower 服务器" -#: ../../source/how-to-run-flower-using-docker.rst:115 +#: ../../source/how-to-run-flower-using-docker.rst:136 #, fuzzy msgid "" "The SuperNode Docker image comes with a pre-installed version of Flower " "and serves as a base for building your own SuperNode image." msgstr "超级节点 Docker 镜像预装了 Flower 版本,可作为构建自己的超级节点镜像的基础。" -#: ../../source/how-to-run-flower-using-docker.rst:120 +#: ../../source/how-to-run-flower-using-docker.rst:141 #, fuzzy msgid "" "The SuperNode Docker image currently works only with the 1.9.0-nightly " @@ -5585,21 +5515,18 @@ msgid "" "same day. To ensure the versions are in sync, using the concrete tag, " "e.g., ``1.9.0.dev20240501`` instead of ``nightly`` is recommended." msgstr "" -"超级节点 Docker 映像目前仅适用于 1.9.0-nightly 版本。稳定版将在 Flower 1.9." -"0(稳定版)发布时推出(预计发布时间:5 月)。超级节点夜间镜像必须与同一天发布" -"的相应超级链接和服务器应用程序夜间镜像配对。为确保版本同步,建议使用具体标签" -",例如``1.9.0.dev20240501``,而不是``nightly``。" +"超级节点 Docker 映像目前仅适用于 1.9.0-nightly 版本。稳定版将在 Flower " +"1.9.0(稳定版)发布时推出(预计发布时间:5 " +"月)。超级节点夜间镜像必须与同一天发布的相应超级链接和服务器应用程序夜间镜像配对。为确保版本同步,建议使用具体标签,例如``1.9.0.dev20240501``,而不是``nightly``。" -#: ../../source/how-to-run-flower-using-docker.rst:126 +#: ../../source/how-to-run-flower-using-docker.rst:147 #, fuzzy msgid "" "We will use the ``quickstart-pytorch`` example, which you can find in the" " Flower repository, to illustrate how you can dockerize your ClientApp." -msgstr "" -"我们将使用 \"quickstart-pytorch\"(快速启动-pytorch)示例来说明如何对 " -"ClientApp 进行 docker 化。" +msgstr "我们将使用 \"quickstart-pytorch\"(快速启动-pytorch)示例来说明如何对 ClientApp 进行 docker 化。" -#: ../../source/how-to-run-flower-using-docker.rst:134 +#: ../../source/how-to-run-flower-using-docker.rst:155 #, fuzzy msgid "" "Before we can start, we need to meet a few prerequisites in our local " @@ -5607,33 +5534,33 @@ msgid "" "your ClientApp instead of the ``quickstart-pytorch`` example." msgstr "在开始之前,我们需要在本地开发环境中满足一些先决条件。" -#: ../../source/how-to-run-flower-using-docker.rst:138 +#: ../../source/how-to-run-flower-using-docker.rst:159 #, fuzzy msgid "Clone the Flower repository." msgstr "**叉花仓库**" -#: ../../source/how-to-run-flower-using-docker.rst:152 +#: ../../source/how-to-run-flower-using-docker.rst:173 #, fuzzy msgid "Creating a SuperNode Dockerfile" msgstr "创建超级节点 Dockerfile" -#: ../../source/how-to-run-flower-using-docker.rst:154 -#: ../../source/how-to-run-flower-using-docker.rst:289 +#: ../../source/how-to-run-flower-using-docker.rst:175 +#: ../../source/how-to-run-flower-using-docker.rst:311 #, fuzzy msgid "Let's assume the following project layout:" msgstr "假设项目布局如下" -#: ../../source/how-to-run-flower-using-docker.rst:163 +#: ../../source/how-to-run-flower-using-docker.rst:184 #, fuzzy msgid "" "First, we need to create a ``requirements.txt`` file in the directory " "where the ``ClientApp`` code is located. In the file, we list all the " "dependencies that the ClientApp requires." msgstr "" -"首先,我们需要在 ``ClientApp`` 代码所在的目录中创建一个 ``requirements.txt`` " -"文件。在该文件中,我们列出了 ClientApp 需要的所有依赖项。" +"首先,我们需要在 ``ClientApp`` 代码所在的目录中创建一个 ``requirements.txt`` 文件。在该文件中,我们列出了 " +"ClientApp 需要的所有依赖项。" -#: ../../source/how-to-run-flower-using-docker.rst:175 +#: ../../source/how-to-run-flower-using-docker.rst:196 #, fuzzy msgid "" "Note that `flwr `__ is already installed " @@ -5641,29 +5568,27 @@ msgid "" "package dependencies in your ``requirements.txt``, such as ``torch``, " "``tensorflow``, etc." msgstr "" -"请注意,`flwr `__ 已经安装在`flwr/" -"supernode``基础镜像中,因此只需在`requirements." -"txt``中包含其他依赖包,如`torch``、`tensorflow`等。" +"请注意,`flwr `__ " +"已经安装在`flwr/supernode``基础镜像中,因此只需在`requirements.txt``中包含其他依赖包,如`torch``、`tensorflow`等。" -#: ../../source/how-to-run-flower-using-docker.rst:179 +#: ../../source/how-to-run-flower-using-docker.rst:200 #, fuzzy msgid "" "Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` " "example, create a new file called ``Dockerfile.supernode`` in ``examples" "/quickstart-pytorch``." msgstr "" -"接下来,我们创建一个 Dockerfile。如果使用 ``quickstart-pytorch`` 示例,请在 " -"``examples/quickstart-pytorch`` 中创建一个名为 ``Dockerfile.supernode`` " -"的新文件。" +"接下来,我们创建一个 Dockerfile。如果使用 ``quickstart-pytorch`` 示例,请在 ``examples" +"/quickstart-pytorch`` 中创建一个名为 ``Dockerfile.supernode`` 的新文件。" -#: ../../source/how-to-run-flower-using-docker.rst:182 +#: ../../source/how-to-run-flower-using-docker.rst:203 #, fuzzy msgid "" "The ``Dockerfile.supernode`` contains the instructions that assemble the " "SuperNode image." msgstr "Dockerfile.supernode \"包含组装超级节点映像的指令。" -#: ../../source/how-to-run-flower-using-docker.rst:196 +#: ../../source/how-to-run-flower-using-docker.rst:217 #, fuzzy msgid "" "In the first two lines, we instruct Docker to use the SuperNode image " @@ -5676,74 +5601,69 @@ msgid "" "``client:app``. The argument is the object reference of the ClientApp " "(``:``) that will be run inside the ClientApp." msgstr "" -"在前两行中,我们指示 Docker 使用标记为 ``nightly`` 的 SuperNode " -"镜像作为基础镜像,并将工作目录设置为 ``/app``。下面的指令将在 ``/app`` " -"目录中执行。接下来,我们通过将 ``requirements.txt`` 文件复制到映像中并运行 ``" -"pip install`` 来安装 ClientApp 依赖项。最后两行,我们将 ``client.py`` " -"模块复制到映像中,并将入口点设置为 ``flower-client-app``,参数为 " -"``client:app``。参数是将在 ClientApp 内运行的 ClientApp " -"的对象引用(``<模块>:<属性>``)。" +"在前两行中,我们指示 Docker 使用标记为 ``nightly`` 的 SuperNode 镜像作为基础镜像,并将工作目录设置为 " +"``/app``。下面的指令将在 ``/app`` 目录中执行。接下来,我们通过将 ``requirements.txt`` " +"文件复制到映像中并运行 ``pip install`` 来安装 ClientApp 依赖项。最后两行,我们将 ``client.py`` " +"模块复制到映像中,并将入口点设置为 ``flower-client-app``,参数为 ``client:app``。参数是将在 " +"ClientApp 内运行的 ClientApp 的对象引用(``<模块>:<属性>``)。" -#: ../../source/how-to-run-flower-using-docker.rst:205 +#: ../../source/how-to-run-flower-using-docker.rst:226 #, fuzzy msgid "Building the SuperNode Docker image" msgstr "启动服务器" -#: ../../source/how-to-run-flower-using-docker.rst:207 +#: ../../source/how-to-run-flower-using-docker.rst:228 #, fuzzy msgid "" "Next, we build the SuperNode Docker image by running the following " "command in the directory where Dockerfile and ClientApp code are located." -msgstr "接下来,我们在 Dockerfile 和 ClientApp 代码所在的目录下运行以下命令,构建 " -"SuperNode Docker 映像。" +msgstr "接下来,我们在 Dockerfile 和 ClientApp 代码所在的目录下运行以下命令,构建 SuperNode Docker 映像。" -#: ../../source/how-to-run-flower-using-docker.rst:214 +#: ../../source/how-to-run-flower-using-docker.rst:235 #, fuzzy msgid "" "We gave the image the name ``flwr_supernode``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." -msgstr "" -"我们将图像命名为 ``flwr_supernode``,标签为 ``0.0." -"1``。请记住,这里选择的值只是一个示例。您可以根据自己的需要进行更改。" +msgstr "我们将图像命名为 ``flwr_supernode``,标签为 ``0.0.1``。请记住,这里选择的值只是一个示例。您可以根据自己的需要进行更改。" -#: ../../source/how-to-run-flower-using-docker.rst:219 +#: ../../source/how-to-run-flower-using-docker.rst:240 #, fuzzy msgid "Running the SuperNode Docker image" msgstr "启动服务器" -#: ../../source/how-to-run-flower-using-docker.rst:221 +#: ../../source/how-to-run-flower-using-docker.rst:242 #, fuzzy msgid "Now that we have built the SuperNode image, we can finally run it." msgstr "现在,我们已经构建了超级节点镜像,终于可以运行它了。" -#: ../../source/how-to-run-flower-using-docker.rst:229 -#: ../../source/how-to-run-flower-using-docker.rst:345 +#: ../../source/how-to-run-flower-using-docker.rst:250 +#: ../../source/how-to-run-flower-using-docker.rst:367 #, fuzzy msgid "Let's break down each part of this command:" msgstr "让我们来分析一下这条命令的各个部分:" -#: ../../source/how-to-run-flower-using-docker.rst:231 -#: ../../source/how-to-run-flower-using-docker.rst:347 +#: ../../source/how-to-run-flower-using-docker.rst:252 +#: ../../source/how-to-run-flower-using-docker.rst:369 #, fuzzy msgid "``docker run``: This is the command to run a new Docker container." msgstr "`docker run``: 这是运行新 Docker 容器的命令。" -#: ../../source/how-to-run-flower-using-docker.rst:232 -#: ../../source/how-to-run-flower-using-docker.rst:348 +#: ../../source/how-to-run-flower-using-docker.rst:253 +#: ../../source/how-to-run-flower-using-docker.rst:370 #, fuzzy msgid "" "``--rm``: This option specifies that the container should be " "automatically removed when it stops." msgstr "`-rm``: 该选项指定容器停止时应自动移除。" -#: ../../source/how-to-run-flower-using-docker.rst:233 +#: ../../source/how-to-run-flower-using-docker.rst:254 #, fuzzy msgid "``flwr_supernode:0.0.1``: The name the tag of the Docker image to use." msgstr "flwr_supernode:0.0.1``: 要使用的 Docker 映像的名称和标记。" -#: ../../source/how-to-run-flower-using-docker.rst:234 -#: ../../source/how-to-run-flower-using-docker.rst:350 +#: ../../source/how-to-run-flower-using-docker.rst:255 +#: ../../source/how-to-run-flower-using-docker.rst:372 #, fuzzy msgid "``--insecure``: This option enables insecure communication." msgstr "不安全\": 该选项启用不安全通信。" @@ -5751,8 +5671,8 @@ msgstr "不安全\": 该选项启用不安全通信。" #: ../../source/how-to-run-flower-using-docker.rst #, fuzzy msgid "" -"``--server 192.168.1.100:9092``: This option specifies the address of the" -" SuperLinks Fleet" +"``--superlink 192.168.1.100:9092``: This option specifies the address of " +"the SuperLinks Fleet" msgstr "``--server 192.168.1.100:9092``: 该选项指定超级链接舰队的地址" #: ../../source/how-to-run-flower-using-docker.rst @@ -5760,7 +5680,7 @@ msgstr "``--server 192.168.1.100:9092``: 该选项指定超级链接舰队的地 msgid "API to connect to. Remember to update it with your SuperLink IP." msgstr "要连接的 API。记住用您的超级链接 IP 更新它。" -#: ../../source/how-to-run-flower-using-docker.rst:248 +#: ../../source/how-to-run-flower-using-docker.rst:269 #, fuzzy msgid "" "To test running Flower locally, you can create a `bridge network " @@ -5768,75 +5688,74 @@ msgid "" "defined-bridge-networks>`__, use the ``--network`` argument and pass the " "name of the Docker network to run your SuperNodes." msgstr "" -"要测试在本地运行 Flower,可以创建一个 \"桥接网络 `__\"," -"使用\"--网络 \"参数并传递 Docker 网络的名称,以运行超级节点。" +"要测试在本地运行 Flower,可以创建一个 \"桥接网络 `__\",使用\"--网络 " +"\"参数并传递 Docker 网络的名称,以运行超级节点。" -#: ../../source/how-to-run-flower-using-docker.rst:252 +#: ../../source/how-to-run-flower-using-docker.rst:273 #, fuzzy msgid "" "Any argument that comes after the tag is passed to the Flower SuperNode " "binary. To see all available flags that the SuperNode supports, run:" -msgstr "标记后的任何参数都将传递给 Flower " -"超级节点二进制文件。要查看超级节点支持的所有可用标记,请运行" +msgstr "标记后的任何参数都将传递给 Flower 超级节点二进制文件。要查看超级节点支持的所有可用标记,请运行" -#: ../../source/how-to-run-flower-using-docker.rst:262 +#: ../../source/how-to-run-flower-using-docker.rst:283 #, fuzzy msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your SuperNode container." msgstr "要启用 SSL,我们需要将 PEM 编码的根证书挂载到 SuperNode 容器中。" -#: ../../source/how-to-run-flower-using-docker.rst:264 +#: ../../source/how-to-run-flower-using-docker.rst:285 #, fuzzy msgid "" "Assuming the certificate already exists locally, we can use the flag " "``--volume`` to mount the local certificate into the container's " "``/app/`` directory. This allows the SuperNode to access the certificate " -"within the container. Use the ``--certificates`` flag when starting the " -"container." +"within the container. Use the ``--root-certificates`` flag when starting " +"the container." msgstr "" "假设我们需要的所有文件都在本地的 ``certificates`` 目录中,我们可以使用标记 ``-v`` 将本地目录挂载到容器的 " "``/app/`` 目录中。这样,服务器就可以访问容器内的文件。最后,我们使用 ``--certificates`` 标志将证书名称传递给服务器。" -#: ../../source/how-to-run-flower-using-docker.rst:275 +#: ../../source/how-to-run-flower-using-docker.rst:297 #, fuzzy msgid "Flower ServerApp" msgstr "Flower 服务器。" -#: ../../source/how-to-run-flower-using-docker.rst:277 +#: ../../source/how-to-run-flower-using-docker.rst:299 #, fuzzy msgid "" "The procedure for building and running a ServerApp image is almost " "identical to the SuperNode image." msgstr "构建和运行 ServerApp 映像的程序与 SuperNode 映像几乎完全相同。" -#: ../../source/how-to-run-flower-using-docker.rst:279 +#: ../../source/how-to-run-flower-using-docker.rst:301 #, fuzzy msgid "" "Similar to the SuperNode image, the ServerApp Docker image comes with a " "pre-installed version of Flower and serves as a base for building your " "own ServerApp image." msgstr "" -"与 SuperNode 映像类似,ServerApp Docker 映像也预装了 Flower 版本," -"可作为构建自己的 ServerApp 映像的基础。" +"与 SuperNode 映像类似,ServerApp Docker 映像也预装了 Flower 版本,可作为构建自己的 ServerApp " +"映像的基础。" -#: ../../source/how-to-run-flower-using-docker.rst:282 +#: ../../source/how-to-run-flower-using-docker.rst:304 #, fuzzy msgid "" "We will use the same ``quickstart-pytorch`` example as we do in the " "Flower SuperNode section. If you have not already done so, please follow " "the `SuperNode Prerequisites`_ before proceeding." msgstr "" -"我们将使用与 \"Flower SuperNode \"部分相同的 \"quickstart-pytorch \"示例" -"。如果您还没有这样做,请在继续之前遵循 \"SuperNode 先决条件\"。" +"我们将使用与 \"Flower SuperNode \"部分相同的 \"quickstart-pytorch " +"\"示例。如果您还没有这样做,请在继续之前遵循 \"SuperNode 先决条件\"。" -#: ../../source/how-to-run-flower-using-docker.rst:287 +#: ../../source/how-to-run-flower-using-docker.rst:309 #, fuzzy msgid "Creating a ServerApp Dockerfile" msgstr "创建 ServerApp Dockerfile" -#: ../../source/how-to-run-flower-using-docker.rst:298 +#: ../../source/how-to-run-flower-using-docker.rst:320 #, fuzzy msgid "" "First, we need to create a Dockerfile in the directory where the " @@ -5844,18 +5763,18 @@ msgid "" "example, create a new file called ``Dockerfile.serverapp`` in ``examples" "/quickstart-pytorch``." msgstr "" -"首先,我们需要在 ``ServerApp`` 代码所在的目录中创建一个 Dockerfile。如果使用 " -"``quickstart-pytorch`` 示例,请在 ``examples/quickstart-pytorch`` " -"中创建一个名为 ``Dockerfile.serverapp`` 的新文件。" +"首先,我们需要在 ``ServerApp`` 代码所在的目录中创建一个 Dockerfile。如果使用 ``quickstart-" +"pytorch`` 示例,请在 ``examples/quickstart-pytorch`` 中创建一个名为 " +"``Dockerfile.serverapp`` 的新文件。" -#: ../../source/how-to-run-flower-using-docker.rst:302 +#: ../../source/how-to-run-flower-using-docker.rst:324 #, fuzzy msgid "" "The ``Dockerfile.serverapp`` contains the instructions that assemble the " "ServerApp image." msgstr "Dockerfile.serverapp \"包含组装 ServerApp 镜像的说明。" -#: ../../source/how-to-run-flower-using-docker.rst:313 +#: ../../source/how-to-run-flower-using-docker.rst:335 #, fuzzy msgid "" "In the first two lines, we instruct Docker to use the ServerApp image " @@ -5867,46 +5786,42 @@ msgid "" "ServerApp (``:``) that will be run inside the " "ServerApp container." msgstr "" -"在前两行中,我们指示 Docker 使用标记为 ``1.8.0`` 的 ServerApp " -"镜像作为基础镜像,并将工作目录设置为 ``/app``。下面的指令将在 ``/app`` " -"目录中执行。在最后两行中,我们将 ``server.py`` 模块复制到映像中," -"并将入口点设置为 ``flower-server-app``,参数为 ``server:app``。参数是将在 " +"在前两行中,我们指示 Docker 使用标记为 ``1.8.0`` 的 ServerApp 镜像作为基础镜像,并将工作目录设置为 " +"``/app``。下面的指令将在 ``/app`` 目录中执行。在最后两行中,我们将 ``server.py`` " +"模块复制到映像中,并将入口点设置为 ``flower-server-app``,参数为 ``server:app``。参数是将在 " "ServerApp 容器内运行的 ServerApp 的对象引用(``<模块>:<属性>``)。" -#: ../../source/how-to-run-flower-using-docker.rst:321 +#: ../../source/how-to-run-flower-using-docker.rst:343 #, fuzzy msgid "Building the ServerApp Docker image" msgstr "启动服务器" -#: ../../source/how-to-run-flower-using-docker.rst:323 +#: ../../source/how-to-run-flower-using-docker.rst:345 #, fuzzy msgid "" "Next, we build the ServerApp Docker image by running the following " "command in the directory where Dockerfile and ServerApp code are located." -msgstr "接下来,我们在 Dockerfile 和 ServerApp 代码所在的目录下运行以下命令,构建 " -"ServerApp Docker 镜像。" +msgstr "接下来,我们在 Dockerfile 和 ServerApp 代码所在的目录下运行以下命令,构建 ServerApp Docker 镜像。" -#: ../../source/how-to-run-flower-using-docker.rst:330 +#: ../../source/how-to-run-flower-using-docker.rst:352 #, fuzzy msgid "" "We gave the image the name ``flwr_serverapp``, and the tag ``0.0.1``. " "Remember that the here chosen values only serve as an example. You can " "change them to your needs." -msgstr "" -"我们给图片命名为 ``flwr_serverapp``,标签为 ``0.0." -"1``。请记住,这里选择的值只是一个示例。您可以根据自己的需要进行更改。" +msgstr "我们给图片命名为 ``flwr_serverapp``,标签为 ``0.0.1``。请记住,这里选择的值只是一个示例。您可以根据自己的需要进行更改。" -#: ../../source/how-to-run-flower-using-docker.rst:335 +#: ../../source/how-to-run-flower-using-docker.rst:357 #, fuzzy msgid "Running the ServerApp Docker image" msgstr "启动服务器" -#: ../../source/how-to-run-flower-using-docker.rst:337 +#: ../../source/how-to-run-flower-using-docker.rst:359 #, fuzzy msgid "Now that we have built the ServerApp image, we can finally run it." msgstr "现在我们已经构建了 ServerApp 镜像,终于可以运行它了。" -#: ../../source/how-to-run-flower-using-docker.rst:349 +#: ../../source/how-to-run-flower-using-docker.rst:371 #, fuzzy msgid "``flwr_serverapp:0.0.1``: The name the tag of the Docker image to use." msgstr "flwr_serverapp:0.0.1``: 要使用的 Docker 映像的名称和标记。" @@ -5914,11 +5829,11 @@ msgstr "flwr_serverapp:0.0.1``: 要使用的 Docker 映像的名称和标记 #: ../../source/how-to-run-flower-using-docker.rst #, fuzzy msgid "" -"``--server 192.168.1.100:9091``: This option specifies the address of the" -" SuperLinks Driver" +"``--superlink 192.168.1.100:9091``: This option specifies the address of " +"the SuperLinks Driver" msgstr "``--server 192.168.1.100:9091``: 此选项指定超级链接驱动程序的地址" -#: ../../source/how-to-run-flower-using-docker.rst:363 +#: ../../source/how-to-run-flower-using-docker.rst:385 #, fuzzy msgid "" "To test running Flower locally, you can create a `bridge network " @@ -5926,64 +5841,100 @@ msgid "" "defined-bridge-networks>`__, use the ``--network`` argument and pass the " "name of the Docker network to run your ServerApps." msgstr "" -"要测试在本地运行 Flower,可以创建一个 ``bridge network `___,使用 ``--network`` 参数并传递 Docker 网络的名称,以运行 " -"ServerApps。" +"要测试在本地运行 Flower,可以创建一个 ``bridge network `___,使用 " +"``--network`` 参数并传递 Docker 网络的名称,以运行 ServerApps。" -#: ../../source/how-to-run-flower-using-docker.rst:367 +#: ../../source/how-to-run-flower-using-docker.rst:389 #, fuzzy msgid "" "Any argument that comes after the tag is passed to the Flower ServerApp " "binary. To see all available flags that the ServerApp supports, run:" -msgstr "标记后的任何参数都将传递给 Flower ServerApp 二进制文件。要查看 ServerApp " -"支持的所有可用标记,请运行" +msgstr "标记后的任何参数都将传递给 Flower ServerApp 二进制文件。要查看 ServerApp 支持的所有可用标记,请运行" -#: ../../source/how-to-run-flower-using-docker.rst:377 +#: ../../source/how-to-run-flower-using-docker.rst:399 #, fuzzy msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your ServerApp container." msgstr "要启用 SSL,需要 CA 证书、服务器证书和服务器私钥。" -#: ../../source/how-to-run-flower-using-docker.rst:379 +#: ../../source/how-to-run-flower-using-docker.rst:401 #, fuzzy msgid "" "Assuming the certificate already exists locally, we can use the flag " "``--volume`` to mount the local certificate into the container's " "``/app/`` directory. This allows the ServerApp to access the certificate " -"within the container. Use the ``--certificates`` flag when starting the " -"container." +"within the container. Use the ``--root-certificates`` flags when starting" +" the container." msgstr "" "假设我们需要的所有文件都在本地的 ``certificates`` 目录中,我们可以使用标记 ``-v`` 将本地目录挂载到容器的 " "``/app/`` 目录中。这样,服务器就可以访问容器内的文件。最后,我们使用 ``--certificates`` 标志将证书名称传递给服务器。" -#: ../../source/how-to-run-flower-using-docker.rst:390 +#: ../../source/how-to-run-flower-using-docker.rst:412 #, fuzzy msgid "Advanced Docker options" msgstr "高级安装选项" -#: ../../source/how-to-run-flower-using-docker.rst:393 +#: ../../source/how-to-run-flower-using-docker.rst:415 +msgid "Run with root user privileges" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:417 +msgid "" +"Flower Docker images, by default, run with a non-root user " +"(username/groupname: ``app``, UID/GID: ``49999``). Using root user is not" +" recommended unless it is necessary for specific tasks during the build " +"process. Always make sure to run the container as a non-root user in " +"production to maintain security best practices." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:422 +msgid "**Run a container with root user privileges**" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:424 +msgid "" +"Run the Docker image with the ``-u`` flag and specify ``root`` as the " +"username:" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:430 +msgid "This command will run the Docker container with root user privileges." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:432 +msgid "**Run the build process with root user privileges**" +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:434 +msgid "" +"If you want to switch to the root user during the build process of the " +"Docker image to install missing system dependencies, you can use the " +"``USER root`` directive within your Dockerfile." +msgstr "" + +#: ../../source/how-to-run-flower-using-docker.rst:454 #, fuzzy msgid "Using a different Flower version" msgstr "使用不同的 Flower 或 Python 版本" -#: ../../source/how-to-run-flower-using-docker.rst:395 +#: ../../source/how-to-run-flower-using-docker.rst:456 #, fuzzy msgid "" "If you want to use a different version of Flower, for example Flower " "nightly, you can do so by changing the tag. All available versions are on" -" `Docker Hub `__." +" `Docker Hub `__." msgstr "" "如果您想使用不同版本的 Flower 或 Python,可以通过更改标签来实现。我们提供的所有版本都可以在 `Docker Hub " "`_ 上找到。" -#: ../../source/how-to-run-flower-using-docker.rst:400 +#: ../../source/how-to-run-flower-using-docker.rst:460 #, fuzzy msgid "Pinning a Docker image to a specific version" msgstr "将 Docker 映像固定到特定版本" -#: ../../source/how-to-run-flower-using-docker.rst:402 +#: ../../source/how-to-run-flower-using-docker.rst:462 #, fuzzy msgid "" "It may happen that we update the images behind the tags. Such updates " @@ -5995,24 +5946,24 @@ msgstr "" "我们可能会更新标签后面的图像。此类更新通常包括系统依赖项的安全更新,不会改变 Flower " "的功能。不过,如果您想确保始终使用同一张图片,可以指定图片的哈希值而不是标签。" -#: ../../source/how-to-run-flower-using-docker.rst:407 +#: ../../source/how-to-run-flower-using-docker.rst:467 #, fuzzy msgid "" "The following command returns the current image hash referenced by the " "``superlink:1.8.0`` tag:" msgstr "下面的命令将返回由 ``server:1.7.0-py3.11-ubuntu22.04`` 标记引用的当前图像哈希值:" -#: ../../source/how-to-run-flower-using-docker.rst:414 +#: ../../source/how-to-run-flower-using-docker.rst:474 #, fuzzy msgid "Next, we can pin the hash when running a new SuperLink container:" msgstr "接下来,我们可以在运行新服务器容器时将哈希值固定下来:" -#: ../../source/how-to-run-flower-using-docker.rst:423 +#: ../../source/how-to-run-flower-using-docker.rst:483 #, fuzzy msgid "Setting environment variables" msgstr "设置编码环境" -#: ../../source/how-to-run-flower-using-docker.rst:425 +#: ../../source/how-to-run-flower-using-docker.rst:485 #, fuzzy msgid "" "To set a variable inside a Docker container, you can use the ``-e " @@ -6910,10 +6861,8 @@ msgid "" " latest features and improvements in Flower Next, starting from version " "1.8." msgstr "" -"欢迎阅读从 Flower 升级到 Flower Next 的迁移指南!" -"无论您是经验丰富的用户还是刚刚开始使用 " -"Flower,本指南都将帮助您顺利过渡现有设置,以利用 Flower Next 从 1.8 " -"版开始的最新功能和改进。" +"欢迎阅读从 Flower 升级到 Flower Next 的迁移指南!无论您是经验丰富的用户还是刚刚开始使用 " +"Flower,本指南都将帮助您顺利过渡现有设置,以利用 Flower Next 从 1.8 版开始的最新功能和改进。" #: ../../source/how-to-upgrade-to-flower-next.rst:9 #, fuzzy @@ -6923,9 +6872,8 @@ msgid "" "guide, we will show how to run Flower Next end-to-end with pure Flower " "Next APIs." msgstr "" -"本指南展示了如何通过使用 Flower Next 中的*可兼容层*,以最小的代码改动重用```1" -".8```前的 Flower 代码。在另一个指南中,我们将介绍如何使用纯 Flower Next API " -"端到端运行 Flower Next。" +"本指南展示了如何通过使用 Flower Next 中的*可兼容层*,以最小的代码改动重用```1.8```前的 Flower " +"代码。在另一个指南中,我们将介绍如何使用纯 Flower Next API 端到端运行 Flower Next。" #: ../../source/how-to-upgrade-to-flower-next.rst:13 #, fuzzy @@ -6991,12 +6939,11 @@ msgid "" "to run your project both in the traditional way and in the Flower Next " "way:" msgstr "" -"在 Flower Next 中,*基础架构层*和*应用层*已经解耦。你不再需要在代码中通过``st" -"art_client()``启动客户端,而是创建一个|clientapp_link|_,然后通过命令行启动它" -"。无需通过``start_server()``在代码中启动服务器,而是创建一个 |serverapp_link|" -"_ 并通过命令行启动它。服务器和客户端的长期运行组件被称为超级链接(SuperLink)" -"和超级节点(SuperNode)。以下是无需手动更新的非破坏性更改," -"可让您以传统方式和 Flower Next 方式运行项目:" +"在 Flower Next " +"中,*基础架构层*和*应用层*已经解耦。你不再需要在代码中通过``start_client()``启动客户端,而是创建一个|clientapp_link|_,然后通过命令行启动它。无需通过``start_server()``在代码中启动服务器,而是创建一个" +" |serverapp_link|_ " +"并通过命令行启动它。服务器和客户端的长期运行组件被称为超级链接(SuperLink)和超级节点(SuperNode)。以下是无需手动更新的非破坏性更改,可让您以传统方式和" +" Flower Next 方式运行项目:" #: ../../source/how-to-upgrade-to-flower-next.rst:109 #, fuzzy @@ -7008,8 +6955,7 @@ msgstr "客户端" msgid "" "Wrap your existing client with |clientapp_link|_ instead of launching it " "via |startclient_link|_. Here's an example:" -msgstr "用 |clientapp_link|_ 封装现有客户端,而不是通过 |startclient_link|_ " -"启动。下面是一个例子:" +msgstr "用 |clientapp_link|_ 封装现有客户端,而不是通过 |startclient_link|_ 启动。下面是一个例子:" #: ../../source/how-to-upgrade-to-flower-next.rst:132 #, fuzzy @@ -7021,8 +6967,7 @@ msgstr "服务器" msgid "" "Wrap your existing strategy with |serverapp_link|_ instead of starting " "the server via |startserver_link|_. Here's an example:" -msgstr "用 |serverapp_link|_ 包住现有策略,而不是通过 |startserver_link|_ " -"启动服务器。下面是一个例子:" +msgstr "用 |serverapp_link|_ 包住现有策略,而不是通过 |startserver_link|_ 启动服务器。下面是一个例子:" #: ../../source/how-to-upgrade-to-flower-next.rst:154 #, fuzzy @@ -7038,8 +6983,8 @@ msgid "" " `server.py` as Python scripts." msgstr "" "在依次运行 |flowernext_clientapp_link|_ (2x) 和 |flowernext_serverapp_link|_ " -"之前,使用 |flowernext_superlink_link|_ 运行 ``SuperLink`` 。无需将 |client." -"py` 和 `server.py` 作为 Python 脚本执行。" +"之前,使用 |flowernext_superlink_link|_ 运行 ``SuperLink`` 。无需将 |client.py` 和 " +"`server.py` 作为 Python 脚本执行。" #: ../../source/how-to-upgrade-to-flower-next.rst:158 #, fuzzy @@ -7051,11 +6996,11 @@ msgstr "下面是一个在不使用 HTTPS 的情况下启动服务器的示例 #: ../../source/how-to-upgrade-to-flower-next.rst:174 #, fuzzy msgid "" -"Here's another example to start with HTTPS. Use the ``--certificates`` " -"command line argument to pass paths to (CA certificate, server " -"certificate, and server private key)." -msgstr "下面是另一个使用 HTTPS 的示例。使用 ``--certificates`` 命令行参数传递路径(" -"CA 证书、服务器证书和服务器私钥)。" +"Here's another example to start with HTTPS. Use the ``--ssl-ca-" +"certfile``, ``--ssl-certfile``, and ``--ssl-keyfile`` command line " +"options to pass paths to (CA certificate, server certificate, and server " +"private key)." +msgstr "下面是另一个使用 HTTPS 的示例。使用 ``--certificates`` 命令行参数传递路径(CA 证书、服务器证书和服务器私钥)。" #: ../../source/how-to-upgrade-to-flower-next.rst:201 #, fuzzy @@ -7069,8 +7014,8 @@ msgid "" "|serverapp_link|_, respectively. There is no need to use |startsim_link|_" " anymore. Here's an example:" msgstr "" -"分别用 |clientapp_link|_ 和 |serverapp_link|_ 封装现有的客户端和策略。" -"无需再使用 |startsim_link|_。下面是一个示例:" +"分别用 |clientapp_link|_ 和 |serverapp_link|_ 封装现有的客户端和策略。无需再使用 " +"|startsim_link|_。下面是一个示例:" #: ../../source/how-to-upgrade-to-flower-next.rst:232 #, fuzzy @@ -7081,8 +7026,8 @@ msgid "" "objects are in a ``sim.py`` module):" msgstr "" "在 CLI 中运行 |flower_simulation_link|_ 并指向代码中的 ``server_app`` " -"/``client_app`` 对象,而不是执行 Python 脚本。下面是一个示例(假定 " -"`server_app`` 和 `client_app`` 对象位于 `sim.py`` 模块中):" +"/``client_app`` 对象,而不是执行 Python 脚本。下面是一个示例(假定 `server_app`` 和 " +"`client_app`` 对象位于 `sim.py`` 模块中):" #: ../../source/how-to-upgrade-to-flower-next.rst:249 #, fuzzy @@ -7091,8 +7036,8 @@ msgid "" "config`` command line argument instead of setting the " "``client_resources`` argument in |startsim_link|_. Here's an example:" msgstr "" -"使用 ``--backend-config`` 命令行参数为每个 |clientapp_link|_ 设置默认资源," -"而不是在 |startsim_link|_ 中设置 ``client_resources`` 参数。下面是一个例子:" +"使用 ``--backend-config`` 命令行参数为每个 |clientapp_link|_ 设置默认资源,而不是在 " +"|startsim_link|_ 中设置 ``client_resources`` 参数。下面是一个例子:" #: ../../source/how-to-upgrade-to-flower-next.rst:275 #, fuzzy @@ -7132,8 +7077,7 @@ msgid "" "As we continuously enhance Flower Next at a rapid pace, we'll be " "periodically updating this guide. Please feel free to share any feedback " "with us!" -msgstr "随着 Flower Next " -"的不断快速改进,我们将定期更新本指南。如有任何反馈,请随时与我们分享!" +msgstr "随着 Flower Next 的不断快速改进,我们将定期更新本指南。如有任何反馈,请随时与我们分享!" #: ../../source/how-to-upgrade-to-flower-next.rst:334 #, fuzzy @@ -7632,11 +7576,11 @@ msgstr "贡献者教程" msgid "Contributor how-to guides" msgstr "投稿指南" -#: ../../source/index.rst:173 +#: ../../source/index.rst:172 msgid "Contributor explanations" msgstr "贡献者解释" -#: ../../source/index.rst:179 +#: ../../source/index.rst:178 msgid "Contributor references" msgstr "贡献者参考资料" @@ -7794,7 +7738,8 @@ msgstr "flower-driver-api" msgid "flwr" msgstr "Flower" -#: ../../source/ref-api/flwr.rst:25 ../../source/ref-api/flwr.server.rst:51 +#: ../../source/ref-api/flwr.client.rst:45 ../../source/ref-api/flwr.rst:25 +#: ../../source/ref-api/flwr.server.rst:49 #, fuzzy msgid "Modules" msgstr "模块" @@ -7823,7 +7768,7 @@ msgid ":py:obj:`flwr.server `\\" msgstr ":py:obj:`flwr.server `\\" #: ../../source/ref-api/flwr.rst:35::1 -#: ../../source/ref-api/flwr.server.rst:40::1 flwr.server:1 +#: ../../source/ref-api/flwr.server.rst:38::1 flwr.server:1 #: flwr.server.server.Server:1 of msgid "Flower server." msgstr "Flower 服务器。" @@ -7842,6 +7787,7 @@ msgstr "运行模拟" msgid "client" msgstr "客户端" +#: ../../source/ref-api/flwr.client.mod.rst:13 #: ../../source/ref-api/flwr.client.rst:13 #: ../../source/ref-api/flwr.common.rst:13 #: ../../source/ref-api/flwr.server.rst:13 @@ -7900,9 +7846,10 @@ msgstr "" msgid "Start a Flower NumPyClient which connects to a gRPC server." msgstr "启动 Flower NumPyClient,连接到 gRPC 服务器。" +#: ../../source/ref-api/flwr.client.mod.rst:30 #: ../../source/ref-api/flwr.client.rst:27 #: ../../source/ref-api/flwr.common.rst:32 -#: ../../source/ref-api/flwr.server.rst:28 +#: ../../source/ref-api/flwr.server.rst:26 #: ../../source/ref-api/flwr.server.strategy.rst:17 #: ../../source/ref-api/flwr.server.workflow.rst:17 #, fuzzy @@ -7944,6 +7891,16 @@ msgstr ":py:obj:`NumPyClient `\\ \\(\\)" msgid "Abstract base class for Flower clients using NumPy." msgstr "使用 NumPy 的 Flower 客户端的抽象基类。" +#: ../../source/ref-api/flwr.client.rst:52::1 +#, fuzzy +msgid ":py:obj:`flwr.client.mod `\\" +msgstr ":py:obj:`flwr.client `\\" + +#: ../../source/ref-api/flwr.client.rst:52::1 flwr.client.mod:1 of +#, fuzzy +msgid "Flower Built-in Mods." +msgstr "使用内置调制器" + #: flwr.client.client.Client:1 flwr.client.numpy_client.NumPyClient:1 #: flwr.server.client_manager.ClientManager:1 #: flwr.server.driver.driver.Driver:1 flwr.server.strategy.strategy.Strategy:1 @@ -7955,6 +7912,7 @@ msgstr "Bases: :py:class:`~abc.ABC`" #: ../../source/ref-api/flwr.client.Client.rst:15 #: ../../source/ref-api/flwr.client.ClientApp.rst:15 #: ../../source/ref-api/flwr.client.NumPyClient.rst:15 +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:15 #: ../../source/ref-api/flwr.common.Array.rst:15 #: ../../source/ref-api/flwr.common.ClientMessage.rst:15 #: ../../source/ref-api/flwr.common.ConfigsRecord.rst:15 @@ -8142,6 +8100,7 @@ msgstr ":py:obj:`context `\\" #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit #: flwr.client.client.Client.get_parameters #: flwr.client.client.Client.get_properties +#: flwr.client.mod.localdp_mod.LocalDpMod #: flwr.client.numpy_client.NumPyClient.evaluate #: flwr.client.numpy_client.NumPyClient.fit #: flwr.client.numpy_client.NumPyClient.get_parameters @@ -8293,10 +8252,11 @@ msgstr "当前客户端属性。" msgid "ClientApp" msgstr "客户端" -#: flwr.client.client_app.ClientApp:1 flwr.common.constant.MessageType:1 -#: flwr.common.constant.MessageTypeLegacy:1 flwr.common.context.Context:1 -#: flwr.common.message.Error:1 flwr.common.message.Message:1 -#: flwr.common.message.Metadata:1 flwr.common.record.parametersrecord.Array:1 +#: flwr.client.client_app.ClientApp:1 flwr.client.mod.localdp_mod.LocalDpMod:1 +#: flwr.common.constant.MessageType:1 flwr.common.constant.MessageTypeLegacy:1 +#: flwr.common.context.Context:1 flwr.common.message.Error:1 +#: flwr.common.message.Message:1 flwr.common.message.Metadata:1 +#: flwr.common.record.parametersrecord.Array:1 #: flwr.common.record.recordset.RecordSet:1 flwr.common.typing.ClientMessage:1 #: flwr.common.typing.DisconnectRes:1 flwr.common.typing.EvaluateIns:1 #: flwr.common.typing.EvaluateRes:1 flwr.common.typing.FitIns:1 @@ -8318,7 +8278,8 @@ msgstr "Bases: :py:class:`object`" #: flwr.client.client_app.ClientApp:4 #: flwr.client.client_app.ClientApp.evaluate:4 #: flwr.client.client_app.ClientApp.query:4 -#: flwr.client.client_app.ClientApp.train:4 flwr.server.app.start_server:41 +#: flwr.client.client_app.ClientApp.train:4 +#: flwr.client.mod.localdp_mod.LocalDpMod:22 flwr.server.app.start_server:41 #: flwr.server.server_app.ServerApp:4 flwr.server.server_app.ServerApp.main:4 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:29 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:22 @@ -8574,6 +8535,260 @@ msgstr "" "**properties** -- 将任意字符串键映射到 bool、bytes、float、int 或 str " "类型值的字典。它可用于将任意属性值传回服务器。" +#: ../../source/ref-api/flwr.client.mod.rst:2 +#, fuzzy +msgid "mod" +msgstr "模块" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`adaptiveclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:1 of +#, fuzzy +msgid "Client-side adaptive clipping modifier." +msgstr "客户端逻辑" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +msgid "" +":py:obj:`fixedclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:1 of +#, fuzzy +msgid "Client-side fixed clipping modifier." +msgstr "客户端逻辑" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#, fuzzy +msgid ":py:obj:`make_ffn `\\ \\(ffn\\, mods\\)" +msgstr ":py:obj:`Client `\\ \\(\\)" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.utils.make_ffn:1 of +msgid "." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#, fuzzy +msgid "" +":py:obj:`secagg_mod `\\ \\(msg\\, ctxt\\, " +"call\\_next\\)" +msgstr ":py:obj:`set_context `\\ \\(context\\)" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secagg_mod.secagg_mod:1 of +msgid "Handle incoming message and return results, following the SecAgg protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#, fuzzy +msgid "" +":py:obj:`secaggplus_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" +msgstr "" +":py:obj:`SecAggPlusWorkflow `\\ " +"\\(num\\_shares\\, ...\\[\\, ...\\]\\)" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.secure_aggregation.secaggplus_mod.secaggplus_mod:1 of +msgid "" +"Handle incoming message and return results, following the SecAgg+ " +"protocol." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#, fuzzy +msgid "" +":py:obj:`message_size_mod `\\ \\(msg\\," +" ctxt\\, call\\_next\\)" +msgstr "" +":py:obj:`Message `\\ \\(metadata\\[\\, content\\, " +"error\\]\\)" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.message_size_mod:1 of +#, fuzzy +msgid "Message size mod." +msgstr "信息类型。" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#, fuzzy +msgid "" +":py:obj:`parameters_size_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" +msgstr "" +":py:obj:`ParametersRecord `\\ " +"\\(\\[array\\_dict\\, keep\\_input\\]\\)" + +#: ../../source/ref-api/flwr.client.mod.rst:28::1 +#: flwr.client.mod.comms_mods.parameters_size_mod:1 of +#, fuzzy +msgid "Parameters size mod." +msgstr "参数" + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +#, fuzzy +msgid "" +":py:obj:`LocalDpMod `\\ \\(clipping\\_norm\\," +" sensitivity\\, ...\\)" +msgstr "" +":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, " +"mods\\]\\)" + +#: ../../source/ref-api/flwr.client.mod.rst:35::1 +#: flwr.client.mod.localdp_mod.LocalDpMod:1 of +#, fuzzy +msgid "Modifier for local differential privacy." +msgstr "差分隐私" + +#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:2 +#, fuzzy +msgid "LocalDpMod" +msgstr "本地 DP 模式" + +#: flwr.client.mod.localdp_mod.LocalDpMod:3 of +msgid "" +"This mod clips the client model updates and adds noise to the params " +"before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:12 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:10 +#: flwr.client.mod.localdp_mod.LocalDpMod:6 of +msgid "It operates on messages of type `MessageType.TRAIN`." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:8 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 +#: of +#, fuzzy +msgid "The value of the clipping norm." +msgstr "削波法线的值。" + +#: flwr.client.mod.localdp_mod.LocalDpMod:10 of +msgid "The sensitivity of the client model." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:12 of +msgid "" +"The privacy budget. Smaller value of epsilon indicates a higher level of " +"privacy protection." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:15 of +msgid "" +"The failure probability. The probability that the privacy mechanism fails" +" to provide the desired level of privacy. A smaller value of delta " +"indicates a stricter privacy guarantee." +msgstr "" + +#: flwr.client.mod.localdp_mod.LocalDpMod:23 of +msgid "Create an instance of the local DP mod and add it to the client-side mods:" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.adaptiveclipping_mod.rst:2 +msgid "adaptiveclipping\\_mod" +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:3 of +#, fuzzy +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideAdaptiveClipping server-side strategy " +"wrapper." +msgstr "用 \"DifferentialPrivacyClientSideAdaptiveClipping \"包装器对策略进行包装:" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:6 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:6 of +#, fuzzy +msgid "The wrapper sends the clipping_norm value to the client." +msgstr "向客户发送近端因子mu" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:8 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:8 of +msgid "This mod clips the client model updates before sending them to the server." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:10 of +msgid "" +"It also sends KEY_NORM_BIT to the server for computing the new clipping " +"value." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:15 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:13 +#: flwr.server.driver.driver.Driver.send_and_receive:18 +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 +#: of +#, fuzzy +msgid "Notes" +msgstr "无" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:16 +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:14 of +msgid "Consider the order of mods when using multiple." +msgstr "" + +#: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:18 of +msgid "Typically, adaptiveclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.fixedclipping_mod.rst:2 +#, fuzzy +msgid "fixedclipping\\_mod" +msgstr "剪贴" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:3 of +#, fuzzy +msgid "" +"This mod needs to be used with the " +"DifferentialPrivacyClientSideFixedClipping server-side strategy wrapper." +msgstr "用 \"DifferentialPrivacyClientSideFixedClipping \"包装器包装策略:" + +#: flwr.client.mod.centraldp_mods.fixedclipping_mod:16 of +msgid "Typically, fixedclipping_mod should be the last to operate on params." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.make_ffn.rst:2 +msgid "make\\_ffn" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 +msgid "message\\_size\\_mod" +msgstr "" + +#: flwr.client.mod.comms_mods.message_size_mod:3 of +msgid "This mod logs the size in bytes of the message being transmited." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.parameters_size_mod.rst:2 +#, fuzzy +msgid "parameters\\_size\\_mod" +msgstr "参数" + +#: flwr.client.mod.comms_mods.parameters_size_mod:3 of +msgid "" +"This mod logs the number of parameters transmitted in the message as well" +" as their size in bytes." +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secagg_mod.rst:2 +msgid "secagg\\_mod" +msgstr "" + +#: ../../source/ref-api/flwr.client.mod.secaggplus_mod.rst:2 +#, fuzzy +msgid "secaggplus\\_mod" +msgstr "工作流程" + #: ../../source/ref-api/flwr.client.run_client_app.rst:2 #, fuzzy msgid "run\\_client\\_app" @@ -10249,6 +10464,24 @@ msgstr "" ":py:obj:`RUN_SUPERLINK_LEAVE " "`\\" +#: flwr.common.EventType.capitalize:1::1 of +#, fuzzy +msgid "" +":py:obj:`RUN_SUPEREXEC_ENTER " +"`\\" +msgstr "" +":py:obj:`RUN_SUPERLINK_ENTER " +"`\\" + +#: flwr.common.EventType.capitalize:1::1 of +#, fuzzy +msgid "" +":py:obj:`RUN_SUPEREXEC_LEAVE " +"`\\" +msgstr "" +":py:obj:`RUN_SUPERLINK_LEAVE " +"`\\" + #: flwr.common.EventType.capitalize:3 of #, fuzzy msgid "" @@ -10268,8 +10501,7 @@ msgid "" "Return the number of non-overlapping occurrences of substring sub in " "string S[start:end]. Optional arguments start and end are interpreted as" " in slice notation." -msgstr "返回子串 sub 在字符串 S[start:end] 中非重叠出现的次数。 可选参数 start 和 " -"end 按切分符号解释。" +msgstr "返回子串 sub 在字符串 S[start:end] 中非重叠出现的次数。 可选参数 start 和 end 按切分符号解释。" #: flwr.common.EventType.encode:3 of #, fuzzy @@ -10295,10 +10527,9 @@ msgid "" "as any other name registered with codecs.register_error that can handle " "UnicodeEncodeErrors." msgstr "" -"编码错误的错误处理方案。默认值为 \"strict\",即编码错误会引发 " -"UnicodeEncodeError。 其他可能的值包括 \"ignore\"、\"replace \"和 " -"\"xmlcharrefreplace\",以及通过 codecs.register_error 注册的、可处理 " -"UnicodeEncodeErrror 的其他名称。" +"编码错误的错误处理方案。默认值为 \"strict\",即编码错误会引发 UnicodeEncodeError。 其他可能的值包括 " +"\"ignore\"、\"replace \"和 \"xmlcharrefreplace\",以及通过 codecs.register_error" +" 注册的、可处理 UnicodeEncodeErrror 的其他名称。" #: flwr.common.EventType.endswith:1 of #, fuzzy @@ -10308,9 +10539,8 @@ msgid "" "stop comparing S at that position. suffix can also be a tuple of strings " "to try." msgstr "" -"如果 S 以指定后缀结束,则返回 True,否则返回 False。如果起始位置可选," -"则从该位置开始测试 S。如果使用可选的 end,则在该位置停止比较 " -"S。后缀也可以是要尝试的字符串元组。" +"如果 S 以指定后缀结束,则返回 True,否则返回 False。如果起始位置可选,则从该位置开始测试 S。如果使用可选的 " +"end,则在该位置停止比较 S。后缀也可以是要尝试的字符串元组。" #: flwr.common.EventType.expandtabs:3 of #, fuzzy @@ -10323,9 +10553,7 @@ msgid "" "Return the lowest index in S where substring sub is found, such that sub " "is contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." -msgstr "" -"返回在 S 中找到子串 sub 的最低索引,即 sub 包含在 S[start:end] 中。 可选参数 " -"start 和 end 按切分符号解释。" +msgstr "返回在 S 中找到子串 sub 的最低索引,即 sub 包含在 S[start:end] 中。 可选参数 start 和 end 按切分符号解释。" #: flwr.common.EventType.find:5 flwr.common.EventType.rfind:5 of #, fuzzy @@ -10337,8 +10565,7 @@ msgstr "失败时返回-1。" msgid "" "Return a formatted version of S, using substitutions from args and " "kwargs. The substitutions are identified by braces ('{' and '}')." -msgstr "使用来自 args 和 kwargs 的替换,返回 S " -"的格式化版本。替换用大括号('{'和'}')标识。" +msgstr "使用来自 args 和 kwargs 的替换,返回 S 的格式化版本。替换用大括号('{'和'}')标识。" #: flwr.common.EventType.format_map:1 of #, fuzzy @@ -10357,16 +10584,14 @@ msgstr "如果未找到子串,则引发 ValueError。" msgid "" "A string is alpha-numeric if all characters in the string are alpha-" "numeric and there is at least one character in the string." -msgstr "如果字符串中的所有字符都是字母数字,且字符串中至少有一个字符,则该字符串为字" -"母数字字符串。" +msgstr "如果字符串中的所有字符都是字母数字,且字符串中至少有一个字符,则该字符串为字母数字字符串。" #: flwr.common.EventType.isalpha:3 of #, fuzzy msgid "" "A string is alphabetic if all characters in the string are alphabetic and" " there is at least one character in the string." -msgstr "如果字符串中的所有字符都是字母,并且字符串中至少有一个字符,那么该字符串就是" -"字母字符串。" +msgstr "如果字符串中的所有字符都是字母,并且字符串中至少有一个字符,那么该字符串就是字母字符串。" #: flwr.common.EventType.isascii:3 of #, fuzzy @@ -10380,48 +10605,42 @@ msgstr "ASCII 字符的码位范围为 U+0000-U+007F。空字符串也是 ASCII msgid "" "A string is a decimal string if all characters in the string are decimal " "and there is at least one character in the string." -msgstr "如果字符串中的所有字符都是十进制,并且字符串中至少有一个字符是十进制,那么该" -"字符串就是十进制字符串。" +msgstr "如果字符串中的所有字符都是十进制,并且字符串中至少有一个字符是十进制,那么该字符串就是十进制字符串。" #: flwr.common.EventType.isdigit:3 of #, fuzzy msgid "" "A string is a digit string if all characters in the string are digits and" " there is at least one character in the string." -msgstr "如果字符串中的所有字符都是数字,并且字符串中至少有一个字符,那么该字符串就是" -"数字字符串。" +msgstr "如果字符串中的所有字符都是数字,并且字符串中至少有一个字符,那么该字符串就是数字字符串。" #: flwr.common.EventType.isidentifier:3 of #, fuzzy msgid "" "Call keyword.iskeyword(s) to test whether string s is a reserved " "identifier, such as \"def\" or \"class\"." -msgstr "调用 keyword.iskeyword(s) 测试字符串 s 是否为保留标识符,如 \"def \"或 " -"\"class\"。" +msgstr "调用 keyword.iskeyword(s) 测试字符串 s 是否为保留标识符,如 \"def \"或 \"class\"。" #: flwr.common.EventType.islower:3 of #, fuzzy msgid "" "A string is lowercase if all cased characters in the string are lowercase" " and there is at least one cased character in the string." -msgstr "如果字符串中的所有大小写字符都是小写,且字符串中至少有一个大小写字符,则该字" -"符串为小写字符串。" +msgstr "如果字符串中的所有大小写字符都是小写,且字符串中至少有一个大小写字符,则该字符串为小写字符串。" #: flwr.common.EventType.isnumeric:3 of #, fuzzy msgid "" "A string is numeric if all characters in the string are numeric and there" " is at least one character in the string." -msgstr "如果字符串中的所有字符都是数字,且字符串中至少有一个字符,则该字符串为数字字" -"符串。" +msgstr "如果字符串中的所有字符都是数字,且字符串中至少有一个字符,则该字符串为数字字符串。" #: flwr.common.EventType.isprintable:3 of #, fuzzy msgid "" "A string is printable if all of its characters are considered printable " "in repr() or if it is empty." -msgstr "如果字符串的所有字符在 repr() " -"中都被认为是可打印的,或者字符串为空,那么该字符串就是可打印的。" +msgstr "如果字符串的所有字符在 repr() 中都被认为是可打印的,或者字符串为空,那么该字符串就是可打印的。" #: flwr.common.EventType.isspace:3 of #, fuzzy @@ -10435,16 +10654,14 @@ msgstr "如果字符串中的所有字符都是空格,且字符串中至少有 msgid "" "In a title-cased string, upper- and title-case characters may only follow" " uncased characters and lowercase characters only cased ones." -msgstr "在标题大小写字符串中,大写和标题大小写字符只能跟在无大小写字符之后,小写字符" -"只能跟在有大小写字符之后。" +msgstr "在标题大小写字符串中,大写和标题大小写字符只能跟在无大小写字符之后,小写字符只能跟在有大小写字符之后。" #: flwr.common.EventType.isupper:3 of #, fuzzy msgid "" "A string is uppercase if all cased characters in the string are uppercase" " and there is at least one cased character in the string." -msgstr "如果字符串中所有带大小写的字符都是大写,并且字符串中至少有一个带大小写的字符" -",则该字符串为大写字符串。" +msgstr "如果字符串中所有带大小写的字符都是大写,并且字符串中至少有一个带大小写的字符,则该字符串为大写字符串。" #: flwr.common.EventType.join:3 of #, fuzzy @@ -10475,10 +10692,8 @@ msgid "" "same position in y. If there is a third argument, it must be a string, " "whose characters will be mapped to None in the result." msgstr "" -"如果只有一个参数,则必须是一个将 Unicode 序号(整数)或字符映射到 Unicode " -"序号、字符串或 None 的字典。字符键将被转换为序号。如果有两个参数,它们必须是" -"长度相等的字符串,在生成的字典中,x 中的每个字符将被映射到 y " -"中相同位置的字符。" +"如果只有一个参数,则必须是一个将 Unicode 序号(整数)或字符映射到 Unicode 序号、字符串或 None " +"的字典。字符键将被转换为序号。如果有两个参数,它们必须是长度相等的字符串,在生成的字典中,x 中的每个字符将被映射到 y 中相同位置的字符。" #: flwr.common.EventType.partition:3 of #, fuzzy @@ -10486,8 +10701,7 @@ msgid "" "This will search for the separator in the string. If the separator is " "found, returns a 3-tuple containing the part before the separator, the " "separator itself, and the part after it." -msgstr "它会在字符串中搜索分隔符。 如果找到分隔符,则返回一个包含分隔符前部分、" -"分隔符本身和分隔符后部分的 3 元组。" +msgstr "它会在字符串中搜索分隔符。 如果找到分隔符,则返回一个包含分隔符前部分、分隔符本身和分隔符后部分的 3 元组。" #: flwr.common.EventType.partition:7 of #, fuzzy @@ -10501,8 +10715,7 @@ msgstr "如果找不到分隔符,则返回一个包含原始字符串和两个 msgid "" "If the string starts with the prefix string, return string[len(prefix):]." " Otherwise, return a copy of the original string." -msgstr "如果字符串以前缀字符串开始,则返回 " -"string[len(prefix):]。否则,返回原始字符串的副本。" +msgstr "如果字符串以前缀字符串开始,则返回 string[len(prefix):]。否则,返回原始字符串的副本。" #: flwr.common.EventType.removesuffix:3 of #, fuzzy @@ -10510,8 +10723,7 @@ msgid "" "If the string ends with the suffix string and that suffix is not empty, " "return string[:-len(suffix)]. Otherwise, return a copy of the original " "string." -msgstr "如果字符串以后缀字符串结尾,且后缀不为空,则返回 " -"string[:-len(suffix)]。否则,返回原始字符串的副本。" +msgstr "如果字符串以后缀字符串结尾,且后缀不为空,则返回 string[:-len(suffix)]。否则,返回原始字符串的副本。" #: flwr.common.EventType.replace:5 of #, fuzzy @@ -10538,9 +10750,7 @@ msgid "" "Return the highest index in S where substring sub is found, such that sub" " is contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." -msgstr "" -"返回在 S 中找到子串 sub 且 sub 包含在 S[start:end] 中的最高索引。 可选参数 " -"start 和 end 按切分符号解释。" +msgstr "返回在 S 中找到子串 sub 且 sub 包含在 S[start:end] 中的最高索引。 可选参数 start 和 end 按切分符号解释。" #: flwr.common.EventType.rpartition:3 of #, fuzzy @@ -10548,8 +10758,7 @@ msgid "" "This will search for the separator in the string, starting at the end. If" " the separator is found, returns a 3-tuple containing the part before the" " separator, the separator itself, and the part after it." -msgstr "它会从字符串的末尾开始搜索分隔符。如果找到分隔符,则返回一个包含分隔符前部分" -"、分隔符本身和分隔符后部分的 3 元组。" +msgstr "它会从字符串的末尾开始搜索分隔符。如果找到分隔符,则返回一个包含分隔符前部分、分隔符本身和分隔符后部分的 3 元组。" #: flwr.common.EventType.rpartition:7 of #, fuzzy @@ -10574,9 +10783,7 @@ msgid "" "When set to None (the default value), will split on any whitespace " "character (including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard" " empty strings from the result." -msgstr "" -"当设置为 \"无\"(默认值)时,将对任何空白字符(包括 \\n" -" \\r \\t \\f 和空格)进行分割,并从结果中剔除空字符串。" +msgstr "当设置为 \"无\"(默认值)时,将对任何空白字符(包括 \\n \\r \\t \\f 和空格)进行分割,并从结果中剔除空字符串。" #: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of #, fuzzy @@ -10601,8 +10808,7 @@ msgid "" "Note, str.split() is mainly useful for data that has been intentionally " "delimited. With natural text that includes punctuation, consider using " "the regular expression module." -msgstr "注意,str.split() 主要适用于有意分隔的数据。 " -"对于包含标点符号的自然文本,可以考虑使用正则表达式模块。" +msgstr "注意,str.split() 主要适用于有意分隔的数据。 对于包含标点符号的自然文本,可以考虑使用正则表达式模块。" #: flwr.common.EventType.splitlines:3 of #, fuzzy @@ -10619,8 +10825,8 @@ msgid "" "stop comparing S at that position. prefix can also be a tuple of strings " "to try." msgstr "" -"如果 S 以指定的前缀开始,则返回 True,否则返回 False。如果选择 start," -"则从该位置开始测试 S。如果使用可选的 end,则在该位置停止比较 S。" +"如果 S 以指定的前缀开始,则返回 True,否则返回 False。如果选择 start,则从该位置开始测试 S。如果使用可选的 " +"end,则在该位置停止比较 S。" #: flwr.common.EventType.title:3 of #, fuzzy @@ -10648,8 +10854,8 @@ msgid "" "dictionary or list. If this operation raises LookupError, the character " "is left untouched. Characters mapped to None are deleted." msgstr "" -"表必须通过 __getitem__ 实现查找/索引,例如字典或列表。 如果该操作引发 " -"LookupError,该字符将保持不变。 映射为 None 的字符将被删除。" +"表必须通过 __getitem__ 实现查找/索引,例如字典或列表。 如果该操作引发 LookupError,该字符将保持不变。 映射为 None" +" 的字符将被删除。" #: flwr.common.EventType.zfill:3 of #, fuzzy @@ -10863,9 +11069,8 @@ msgid "" "follows the equation: ttl = msg.meta.ttl - (reply.meta.created_at - " "msg.meta.created_at)" msgstr "" -"该信息的有效时间(秒)。如果未设置,则将根据收到的信息过期前的剩余时间来设置" -"。其计算公式为:ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta." -"created_at)" +"该信息的有效时间(秒)。如果未设置,则将根据收到的信息过期前的剩余时间来设置。其计算公式为:ttl = msg.meta.ttl - " +"(reply.meta.created_at - msg.meta.created_at)" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of @@ -10874,8 +11079,7 @@ msgid "" "Time-to-live for this message in seconds. If unset, it will be set based " "on the remaining time for the received message before it expires. This " "follows the equation:" -msgstr "该信息的有效时间(秒)。如果未设置,则将根据接收到的信息过期前的剩余时间来设" -"置。其计算公式如下" +msgstr "该信息的有效时间(秒)。如果未设置,则将根据接收到的信息过期前的剩余时间来设置。其计算公式如下" #: flwr.common.message.Message.create_error_reply:9 #: flwr.common.message.Message.create_reply:13 of @@ -11379,51 +11583,29 @@ msgstr "parameters\\_to\\_ndarrays" msgid "server" msgstr "服务器" -#: ../../source/ref-api/flwr.server.rst:26::1 -#, fuzzy -msgid ":py:obj:`run_driver_api `\\ \\(\\)" -msgstr ":py:obj:`run_driver_api `\\ \\(\\)" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_driver_api:1 of -#, fuzzy -msgid "Run Flower server (Driver API)." -msgstr "flower-driver-api" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#, fuzzy -msgid ":py:obj:`run_fleet_api `\\ \\(\\)" -msgstr ":py:obj:`run_fleet_api `\\ \\(\\)" - -#: ../../source/ref-api/flwr.server.rst:26::1 -#: flwr.server.app.run_fleet_api:1 of -#, fuzzy -msgid "Run Flower server (Fleet API)." -msgstr "Flower 服务器。" - -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #, fuzzy msgid ":py:obj:`run_server_app `\\ \\(\\)" msgstr ":py:obj:`run_server_app `\\ \\(\\)" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.run_serverapp.run_server_app:1 of #, fuzzy msgid "Run Flower server app." msgstr "Flower 服务器。" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #, fuzzy msgid ":py:obj:`run_superlink `\\ \\(\\)" msgstr ":py:obj:`run_superlink `\\ \\(\\)" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.app.run_superlink:1 of #, fuzzy msgid "Run Flower SuperLink (Driver API and Fleet API)." msgstr "运行 Flower 服务器(Driver API 和 Fleet API)。" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #, fuzzy msgid "" ":py:obj:`start_server `\\ \\(\\*\\[\\, " @@ -11432,45 +11614,45 @@ msgstr "" ":py:obj:`start_server `\\ \\(\\*\\[\\, " "server\\_address\\, server\\, ...\\]\\)" -#: ../../source/ref-api/flwr.server.rst:26::1 +#: ../../source/ref-api/flwr.server.rst:24::1 #: flwr.server.app.start_server:1 of msgid "Start a Flower server using the gRPC transport layer." msgstr "使用 gRPC 传输层启动 Flower 服务器。" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid ":py:obj:`ClientManager `\\ \\(\\)" msgstr ":py:obj:`ClientManager `\\ \\(\\)" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.ClientManager:1 of #, fuzzy msgid "Abstract base class for managing Flower clients." msgstr "Flower 客户端的抽象基类。" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid ":py:obj:`Driver `\\ \\(\\)" msgstr ":py:obj:`run_driver_api `\\ \\(\\)" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.driver.driver.Driver:1 of #, fuzzy msgid "Abstract base Driver class for the Driver API." msgstr "Flower 客户端的抽象基类。" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid ":py:obj:`History `\\ \\(\\)" msgstr ":py:obj:`History `\\ \\(\\)" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.history.History:1 of #, fuzzy msgid "History class for training and/or evaluation metrics collection." msgstr "**hist** -- 包含训练和评估指标的对象。" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid "" ":py:obj:`LegacyContext `\\ \\(state\\[\\, " @@ -11479,13 +11661,13 @@ msgstr "" ":py:obj:`LegacyContext `\\ \\(state\\[\\, " "config\\, strategy\\, ...\\]\\)" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.compat.legacy_context.LegacyContext:1 of #, fuzzy msgid "Legacy Context." msgstr "传承背景。" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid "" ":py:obj:`Server `\\ \\(\\*\\, client\\_manager\\[\\, " @@ -11494,20 +11676,20 @@ msgstr "" ":py:obj:`Server `\\ \\(\\*\\, client\\_manager\\[\\, " "strategy\\]\\)" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid "" ":py:obj:`ServerApp `\\ \\(\\[server\\, config\\, " "strategy\\, ...\\]\\)" msgstr "server.strategy.Strategy" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_app.ServerApp:1 of #, fuzzy msgid "Flower ServerApp." msgstr "Flower 服务器。" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid "" ":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\," @@ -11517,39 +11699,39 @@ msgstr "" "config=flwr.server.ServerConfig(num_rounds=3, round_timeout=600.0), " "...)``" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.server_config.ServerConfig:1 of #, fuzzy msgid "Flower server config." msgstr "Flower 服务器。" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #, fuzzy msgid ":py:obj:`SimpleClientManager `\\ \\(\\)" msgstr ":py:obj:`SimpleClientManager `\\ \\(\\)" -#: ../../source/ref-api/flwr.server.rst:40::1 +#: ../../source/ref-api/flwr.server.rst:38::1 #: flwr.server.client_manager.SimpleClientManager:1 of #, fuzzy msgid "Provides a pool of available clients." msgstr "使用部分可用客户进行评估。" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #, fuzzy msgid ":py:obj:`flwr.server.strategy `\\" msgstr "server.strategy.Strategy" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.strategy:1 of msgid "Contains the strategy abstraction and different implementations." msgstr "包含策略抽象和不同的实现方法。" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #, fuzzy msgid ":py:obj:`flwr.server.workflow `\\" msgstr "server.strategy.Strategy" -#: ../../source/ref-api/flwr.server.rst:59::1 +#: ../../source/ref-api/flwr.server.rst:57::1 #: flwr.server.workflow:1 of #, fuzzy msgid "Workflows." @@ -11852,14 +12034,6 @@ msgstr "超时时间(秒)。如果指定,该方法将在此期限内等待 msgid "**replies** -- An iterable of reply messages received from the SuperLink." msgstr "**replies** -- 从超级链接收到的回复信息的迭代。" -#: flwr.server.driver.driver.Driver.send_and_receive:18 -#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:53 -#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 -#: of -#, fuzzy -msgid "Notes" -msgstr "无" - #: flwr.server.driver.driver.Driver.send_and_receive:19 of #, fuzzy msgid "" @@ -13548,13 +13722,6 @@ msgid "" "value of 1.0 or higher is recommended for strong privacy." msgstr "模型更新高斯机制的噪声乘数。建议使用 1.0 或更高的值,以获得较强的隐私性。" -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 -#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 -#: of -#, fuzzy -msgid "The value of the clipping norm." -msgstr "削波法线的值。" - #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:26 #: of #, fuzzy @@ -13956,7 +14123,7 @@ msgstr "" "\\(num\\_available\\_clients\\)" #: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst:2 -#: ../../source/ref-changelog.md:905 +#: ../../source/ref-changelog.md:997 msgid "FedAdagrad" msgstr "FedAdagrad" @@ -16107,8 +16274,8 @@ msgid "" "Bases: " ":py:class:`~flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow`" msgstr "" -"基础: :py:class:`~flwr.server.workflow.secure_aggregation." -"secaggplus_workflow.SecAggPlusWorkflow`." +"基础: " +":py:class:`~flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow`." #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:3 of #, fuzzy @@ -16125,13 +16292,10 @@ msgid "" "('parameters') from the client's `FitRes`. The server then aggregates " "these contributions to compute the weighted average of model parameters." msgstr "" -"SecAgg 协议可确保对多方拥有的整数向量进行安全求和,而不会访问任何单个整数向量" -"。该工作流程允许服务器计算所有客户端模型参数的加权平均值,确保个人贡献保持私" -"密。这可以通过客户端同时发送加权因子和本地更新参数的加权版本来实现,为了保护" -"隐私,两者都会被屏蔽。具体来说,每个客户端都会上传带掩码的\"[w, w * params]\"" -",其中加权因子 \"w \"是示例数(\"num_examples\"),\"params \"代表客户端 " -"\"FitRes \"中的模型参数(\"parameters\"" -")。然后,服务器会汇总这些贡献,计算模型参数的加权平均值。" +"SecAgg " +"协议可确保对多方拥有的整数向量进行安全求和,而不会访问任何单个整数向量。该工作流程允许服务器计算所有客户端模型参数的加权平均值,确保个人贡献保持私密。这可以通过客户端同时发送加权因子和本地更新参数的加权版本来实现,为了保护隐私,两者都会被屏蔽。具体来说,每个客户端都会上传带掩码的\"[w," +" w * params]\",其中加权因子 \"w \"是示例数(\"num_examples\"),\"params \"代表客户端 " +"\"FitRes \"中的模型参数(\"parameters\")。然后,服务器会汇总这些贡献,计算模型参数的加权平均值。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:14 of #, fuzzy @@ -16139,9 +16303,7 @@ msgid "" "The protocol involves four main stages: - 'setup': Send SecAgg " "configuration to clients and collect their public keys. - 'share keys': " "Broadcast public keys among clients and collect encrypted secret" -msgstr "" -"协议包括四个主要阶段: - 设置\": 向客户端发送 SecAgg 配置并收集它们的公钥。-" -" 共享密钥\": 在客户端之间广播公钥并收集加密密钥。" +msgstr "协议包括四个主要阶段: - 设置\": 向客户端发送 SecAgg 配置并收集它们的公钥。- 共享密钥\": 在客户端之间广播公钥并收集加密密钥。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:54 of #, fuzzy @@ -16166,8 +16328,8 @@ msgid "" "setting the security threshold relative to the number of selected " "clients." msgstr "" -"当 `reconstruction_threshold` 为浮点数时,它被解释为重建私钥所需的所有选定客" -"户端数量的比例。此功能可根据所选客户端的数量灵活设置安全阈值。" +"当 `reconstruction_threshold` " +"为浮点数时,它被解释为重建私钥所需的所有选定客户端数量的比例。此功能可根据所选客户端的数量灵活设置安全阈值。" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:64 of #, fuzzy @@ -16188,8 +16350,9 @@ msgid "" "`\\ " "\\(driver\\, ...\\)" msgstr "" -":py:obj:`collect_masked_vectors_stage `\\(driver\\, ...\\)" +":py:obj:`collect_masked_vectors_stage " +"`\\(driver\\," +" ...\\)" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of @@ -16198,8 +16361,9 @@ msgid "" ":py:obj:`setup_stage `\\" " \\(driver\\, context\\, state\\)" msgstr "" -":py:obj:`setup_stage `\\(" -"driver\\, context\\, state\\)" +":py:obj:`setup_stage " +"`\\(driver\\, context\\," +" state\\)" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of @@ -16209,8 +16373,9 @@ msgid "" "`\\ \\(driver\\, " "context\\, state\\)" msgstr "" -"py:obj:`share_keys_stage `\\(driver\\, context\\, state\\)" +"py:obj:`share_keys_stage " +"`\\(driver\\, " +"context\\, state\\)" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of @@ -16220,8 +16385,9 @@ msgid "" "`\\ \\(driver\\, " "context\\, state\\)" msgstr "" -":py:obj:`unmask_stage `\\ " -"\\(driver\\, context\\, state\\)" +":py:obj:`unmask_stage " +"`\\ \\(driver\\, " +"context\\, state\\)" #: ../../source/ref-api/flwr.simulation.rst:2 #, fuzzy @@ -16234,8 +16400,8 @@ msgid "" ":py:obj:`start_simulation `\\ \\(\\*\\," " client\\_fn\\[\\, ...\\]\\)" msgstr "" -":py:obj:`start_simulation `\\ \\(\\*\\, " -"client\\_fn\\[\\, ...\\]\\)" +":py:obj:`start_simulation `\\ \\(\\*\\," +" client\\_fn\\[\\, ...\\]\\)" #: ../../source/ref-api/flwr.simulation.rst:18::1 #: flwr.simulation.app.start_simulation:1 of @@ -16248,8 +16414,8 @@ msgid "" ":py:obj:`run_simulation `\\ " "\\(server\\_app\\, client\\_app\\, ...\\)" msgstr "" -":py:obj:`run_simulation `\\ \\(server\\_app\\" -", client\\_app\\, ...\\)" +":py:obj:`run_simulation `\\ " +"\\(server\\_app\\, client\\_app\\, ...\\)" #: ../../source/ref-api/flwr.simulation.rst:18::1 #: flwr.simulation.run_simulation.run_simulation:1 of @@ -16267,8 +16433,7 @@ msgstr "运行模拟" msgid "" "The `ServerApp` to be executed. It will send messages to different " "`ClientApp` instances running on different (virtual) SuperNodes." -msgstr "要执行的 `ServerApp`。它将向运行在不同(虚拟)超级节点上的不同 " -"`ClientApp`实例发送消息。" +msgstr "要执行的 `ServerApp`。它将向运行在不同(虚拟)超级节点上的不同 `ClientApp`实例发送消息。" #: flwr.simulation.run_simulation.run_simulation:6 of #, fuzzy @@ -16283,8 +16448,7 @@ msgid "" "Number of nodes that run a ClientApp. They can be sampled by a Driver in " "the ServerApp and receive a Message describing what the ClientApp should " "perform." -msgstr "运行 ClientApp 的节点数。它们可被 ServerApp 中的驱动程序采样,并接收描述 " -"ClientApp 应执行的操作的信息。" +msgstr "运行 ClientApp 的节点数。它们可被 ServerApp 中的驱动程序采样,并接收描述 ClientApp 应执行的操作的信息。" #: flwr.simulation.run_simulation.run_simulation:13 of #, fuzzy @@ -16298,8 +16462,8 @@ msgid "" "configure a backend. Values supported in are those included by " "`flwr.common.typing.ConfigsRecordValues`." msgstr "" -"字典,例如 {\"\": , \"\": } 来配置后端。 " -"中支持的值是 `flwr.common.typing.ConfigsRecordValues`中包含的值。" +"字典,例如 {\"\": , \"\": } 来配置后端。 中支持的值是 " +"`flwr.common.typing.ConfigsRecordValues`中包含的值。" #: flwr.simulation.run_simulation.run_simulation:19 of #, fuzzy @@ -16312,20 +16476,17 @@ msgid "" "`tf.config.experimental.set_memory_growth()` works in the TensorFlow " "documentation: https://www.tensorflow.org/api/stable." msgstr "" -"布尔值,用于指示是否在主线程上启用 GPU 增长。如果您在 \"ServerApp \"上使用 " -"TensorFlow 模型,同时让 \"ClientApp \"在同一 GPU " -"上运行,则最好启用此选项。如果不启用此功能,您可能会遇到内存不足的错误,因为 " -"TensorFlow 默认会分配所有 GPU 内存。有关 `tf.config.experimental." -"set_memory_growth()` 如何工作的更多信息,请参阅 TensorFlow 文档:https://www." -"tensorflow.org/api/stable。" +"布尔值,用于指示是否在主线程上启用 GPU 增长。如果您在 \"ServerApp \"上使用 TensorFlow 模型,同时让 " +"\"ClientApp \"在同一 GPU 上运行,则最好启用此选项。如果不启用此功能,您可能会遇到内存不足的错误,因为 TensorFlow " +"默认会分配所有 GPU 内存。有关 `tf.config.experimental.set_memory_growth()` " +"如何工作的更多信息,请参阅 TensorFlow 文档:https://www.tensorflow.org/api/stable。" #: flwr.simulation.run_simulation.run_simulation:26 of #, fuzzy msgid "" "When diabled, only INFO, WARNING and ERROR log messages will be shown. If" " enabled, DEBUG-level logs will be displayed." -msgstr "启用后,将只显示 INFO、WARNING 和 ERROR 日志信息。启用后,将显示 DEBUG " -"级日志。" +msgstr "启用后,将只显示 INFO、WARNING 和 ERROR 日志信息。启用后,将显示 DEBUG 级日志。" #: ../../source/ref-api/flwr.simulation.start_simulation.rst:2 #, fuzzy @@ -16471,61 +16632,614 @@ msgid "Changelog" msgstr "更新日志" #: ../../source/ref-changelog.md:3 -msgid "Unreleased" -msgstr "尚未发布" - -#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:19 -#: ../../source/ref-changelog.md:83 ../../source/ref-changelog.md:176 -#: ../../source/ref-changelog.md:276 ../../source/ref-changelog.md:360 -#: ../../source/ref-changelog.md:424 ../../source/ref-changelog.md:482 -#: ../../source/ref-changelog.md:551 ../../source/ref-changelog.md:680 -#: ../../source/ref-changelog.md:722 ../../source/ref-changelog.md:789 -#: ../../source/ref-changelog.md:855 ../../source/ref-changelog.md:900 -#: ../../source/ref-changelog.md:939 ../../source/ref-changelog.md:972 -#: ../../source/ref-changelog.md:1022 -msgid "What's new?" -msgstr "有什么新内容?" +#, fuzzy +msgid "v1.9.0 (2024-06-10)" +msgstr "v1.3.0 (2023-02-06)" -#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:71 -#: ../../source/ref-changelog.md:146 ../../source/ref-changelog.md:258 -#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:412 -#: ../../source/ref-changelog.md:470 ../../source/ref-changelog.md:539 -#: ../../source/ref-changelog.md:601 ../../source/ref-changelog.md:620 -#: ../../source/ref-changelog.md:776 ../../source/ref-changelog.md:847 -#: ../../source/ref-changelog.md:884 ../../source/ref-changelog.md:927 -msgid "Incompatible changes" -msgstr "不兼容的更改" +#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:105 +#: ../../source/ref-changelog.md:169 ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:362 ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:510 ../../source/ref-changelog.md:568 +#: ../../source/ref-changelog.md:637 ../../source/ref-changelog.md:706 +msgid "Thanks to our contributors" +msgstr "感谢我们的贡献者" -#: ../../source/ref-changelog.md:9 ../../source/ref-changelog.md:73 -#: ../../source/ref-changelog.md:350 ../../source/ref-changelog.md:414 -#: ../../source/ref-changelog.md:472 ../../source/ref-changelog.md:541 -#: ../../source/ref-changelog.md:603 -msgid "None" -msgstr "无" +#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:107 +#: ../../source/ref-changelog.md:171 ../../source/ref-changelog.md:264 +#: ../../source/ref-changelog.md:364 ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:512 ../../source/ref-changelog.md:570 +msgid "" +"We would like to give our special thanks to all the contributors who made" +" the new version of Flower possible (in `git shortlog` order):" +msgstr "在此,我们要特别感谢所有为 Flower 的新版本做出贡献的人员(按 `git shortlog` 顺序排列):" -#: ../../source/ref-changelog.md:11 +#: ../../source/ref-changelog.md:9 #, fuzzy -msgid "v1.8.0 (2024-04-03)" -msgstr "v1.3.0 (2023-02-06)" +msgid "" +"`Adam Narozniak`, `Charles Beauville`, `Chong Shen Ng`, `Daniel J. " +"Beutel`, `Daniel Nata Nugraha`, `Heng Pan`, `Javier`, `Mahdi Beitollahi`," +" `Robert Steiner`, `Taner Topal`, `Yan Gao`, `bapic`, `mohammadnaseri` " +msgstr "" +"`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " +"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " +"Bertoli`, `Heng Pan`, `Javier`, `Mahdi`, `Steven Hé (Sīchàng)`, `Taner " +"Topal`, `achiverram28`, `danielnugraha`, `eunchung`, `ruthgal` " -#: ../../source/ref-changelog.md:13 ../../source/ref-changelog.md:77 -#: ../../source/ref-changelog.md:170 ../../source/ref-changelog.md:270 -#: ../../source/ref-changelog.md:354 ../../source/ref-changelog.md:418 -#: ../../source/ref-changelog.md:476 ../../source/ref-changelog.md:545 -#: ../../source/ref-changelog.md:614 -msgid "Thanks to our contributors" -msgstr "感谢我们的贡献者" +#: ../../source/ref-changelog.md:11 ../../source/ref-changelog.md:111 +#: ../../source/ref-changelog.md:175 ../../source/ref-changelog.md:268 +#: ../../source/ref-changelog.md:368 ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:516 ../../source/ref-changelog.md:574 +#: ../../source/ref-changelog.md:643 ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:814 ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:947 ../../source/ref-changelog.md:992 +#: ../../source/ref-changelog.md:1031 ../../source/ref-changelog.md:1064 +#: ../../source/ref-changelog.md:1114 +msgid "What's new?" +msgstr "有什么新内容?" -#: ../../source/ref-changelog.md:15 ../../source/ref-changelog.md:79 -#: ../../source/ref-changelog.md:172 ../../source/ref-changelog.md:272 -#: ../../source/ref-changelog.md:356 ../../source/ref-changelog.md:420 -#: ../../source/ref-changelog.md:478 +#: ../../source/ref-changelog.md:13 +#, fuzzy +msgid "" +"**Introduce built-in authentication (preview)** " +"([#2946](https://github.com/adap/flower/pull/2946), " +"[#3388](https://github.com/adap/flower/pull/3388), " +"[#2948](https://github.com/adap/flower/pull/2948), " +"[#2917](https://github.com/adap/flower/pull/2917), " +"[#3386](https://github.com/adap/flower/pull/3386), " +"[#3308](https://github.com/adap/flower/pull/3308), " +"[#3001](https://github.com/adap/flower/pull/3001), " +"[#3409](https://github.com/adap/flower/pull/3409), " +"[#2999](https://github.com/adap/flower/pull/2999), " +"[#2979](https://github.com/adap/flower/pull/2979), " +"[#3389](https://github.com/adap/flower/pull/3389), " +"[#3503](https://github.com/adap/flower/pull/3503), " +"[#3366](https://github.com/adap/flower/pull/3366), " +"[#3357](https://github.com/adap/flower/pull/3357))" +msgstr "" +"** 更新文档** ([#1494](https://github.com/adap/flower/pull/1494), " +"[#1496](https://github.com/adap/flower/pull/1496), " +"[#1500](https://github.com/adap/flower/pull/1500), " +"[#1503](https://github.com/adap/flower/pull/1503), " +"[#1505](https://github.com/adap/flower/pull/1505), " +"[#1524](https://github.com/adap/flower/pull/1524), " +"[#1518](https://github.com/adap/flower/pull/1518), " +"[#1519](https://github.com/adap/flower/pull/1519), " +"[#1515](https://github.com/adap/flower/pull/1515))" + +#: ../../source/ref-changelog.md:15 msgid "" -"We would like to give our special thanks to all the contributors who made" -" the new version of Flower possible (in `git shortlog` order):" -msgstr "在此,我们要特别感谢所有为 Flower 的新版本做出贡献的人员(按 `git shortlog` 顺序排列):" +"Flower 1.9 introduces the first build-in version of client node " +"authentication. In previous releases, users often wrote glue code to " +"connect Flower to external authentication systems. With this release, the" +" SuperLink can authenticate SuperNodes using a built-in authentication " +"system. A new [how-to guide](https://flower.ai/docs/framework/how-to-" +"authenticate-supernodes.html) and a new [code " +"example](https://github.com/adap/flower/tree/main/examples/flower-" +"authentication) help you to get started." +msgstr "" #: ../../source/ref-changelog.md:17 +msgid "" +"This is the first preview release of the Flower-native authentication " +"system. Many additional features are on the roadmap for upcoming Flower " +"releases - stay tuned." +msgstr "" + +#: ../../source/ref-changelog.md:19 +#, fuzzy +msgid "" +"**Introduce end-to-end Docker support** " +"([#3483](https://github.com/adap/flower/pull/3483), " +"[#3266](https://github.com/adap/flower/pull/3266), " +"[#3390](https://github.com/adap/flower/pull/3390), " +"[#3283](https://github.com/adap/flower/pull/3283), " +"[#3285](https://github.com/adap/flower/pull/3285), " +"[#3391](https://github.com/adap/flower/pull/3391), " +"[#3403](https://github.com/adap/flower/pull/3403), " +"[#3458](https://github.com/adap/flower/pull/3458), " +"[#3533](https://github.com/adap/flower/pull/3533), " +"[#3453](https://github.com/adap/flower/pull/3453), " +"[#3486](https://github.com/adap/flower/pull/3486), " +"[#3290](https://github.com/adap/flower/pull/3290))" +msgstr "" +"**引入(试验性)REST API** ([#1594](https://github.com/adap/flower/pull/1594), " +"[#1690](https://github.com/adap/flower/pull/1690), " +"[#1695](https://github.com/adap/flower/pull/1695), " +"[#1712](https://github.com/adap/flower/pull/1712), " +"[#1802](https://github.com/adap/flower/pull/1802), " +"[#1770](https://github.com/adap/flower/pull/1770), " +"[#1733](https://github.com/adap/flower/pull/1733))" + +#: ../../source/ref-changelog.md:21 +msgid "" +"Full Flower Next Docker support is here! With the release of Flower 1.9, " +"Flower provides stable Docker images for the Flower SuperLink, the Flower" +" SuperNode, and the Flower `ServerApp`. This set of images enables you to" +" run all Flower components in Docker. Check out the new [how-to " +"guide](https://flower.ai/docs/framework/how-to-run-flower-using-" +"docker.html) to get stated." +msgstr "" + +#: ../../source/ref-changelog.md:23 +#, fuzzy +msgid "" +"**Re-architect Flower Next simulation engine** " +"([#3307](https://github.com/adap/flower/pull/3307), " +"[#3355](https://github.com/adap/flower/pull/3355), " +"[#3272](https://github.com/adap/flower/pull/3272), " +"[#3273](https://github.com/adap/flower/pull/3273), " +"[#3417](https://github.com/adap/flower/pull/3417), " +"[#3281](https://github.com/adap/flower/pull/3281), " +"[#3343](https://github.com/adap/flower/pull/3343), " +"[#3326](https://github.com/adap/flower/pull/3326))" +msgstr "" +"** 更新文档** ([#1629](https://github.com/adap/flower/pull/1629), " +"[#1628](https://github.com/adap/flower/pull/1628), " +"[#1620](https://github.com/adap/flower/pull/1620), " +"[#1618](https://github.com/adap/flower/pull/1618), " +"[#1617](https://github.com/adap/flower/pull/1617), " +"[#1613](https://github.com/adap/flower/pull/1613), " +"[#1614](https://github.com/adap/flower/pull/1614)))" + +#: ../../source/ref-changelog.md:25 +msgid "" +"Flower Next simulations now use a new in-memory `Driver` that improves " +"the reliability of simulations, especially in notebook environments. This" +" is a significant step towards a complete overhaul of the Flower Next " +"simulation architecture." +msgstr "" + +#: ../../source/ref-changelog.md:27 +#, fuzzy +msgid "" +"**Upgrade simulation engine** " +"([#3354](https://github.com/adap/flower/pull/3354), " +"[#3378](https://github.com/adap/flower/pull/3378), " +"[#3262](https://github.com/adap/flower/pull/3262), " +"[#3435](https://github.com/adap/flower/pull/3435), " +"[#3501](https://github.com/adap/flower/pull/3501), " +"[#3482](https://github.com/adap/flower/pull/3482), " +"[#3494](https://github.com/adap/flower/pull/3494))" +msgstr "" +"** 更新文档** ([#1629](https://github.com/adap/flower/pull/1629), " +"[#1628](https://github.com/adap/flower/pull/1628), " +"[#1620](https://github.com/adap/flower/pull/1620), " +"[#1618](https://github.com/adap/flower/pull/1618), " +"[#1617](https://github.com/adap/flower/pull/1617), " +"[#1613](https://github.com/adap/flower/pull/1613), " +"[#1614](https://github.com/adap/flower/pull/1614)))" + +#: ../../source/ref-changelog.md:29 +msgid "" +"The Flower Next simulation engine comes with improved and configurable " +"logging. The Ray-based simulation backend in Flower 1.9 was updated to " +"use Ray 2.10." +msgstr "" + +#: ../../source/ref-changelog.md:31 +#, fuzzy +msgid "" +"**Introduce FedPFT baseline** " +"([#3268](https://github.com/adap/flower/pull/3268))" +msgstr "**引入 start_driver**([#1697](https://github.com/adap/flower/pull/1697))" + +#: ../../source/ref-changelog.md:33 +msgid "" +"FedPFT allows you to perform one-shot Federated Learning by leveraging " +"widely available foundational models, dramatically reducing communication" +" costs while delivering high performing models. This is work led by Mahdi" +" Beitollahi from Huawei Noah's Ark Lab (Montreal, Canada). Read all the " +"details in their paper: \"Parametric Feature Transfer: One-shot Federated" +" Learning with Foundation Models\" " +"([arxiv](https://arxiv.org/abs/2402.01862))" +msgstr "" + +#: ../../source/ref-changelog.md:35 +#, fuzzy +msgid "" +"**Launch additional** `flwr new` **templates for Apple MLX, Hugging Face " +"Transformers, scikit-learn and TensorFlow** " +"([#3291](https://github.com/adap/flower/pull/3291), " +"[#3139](https://github.com/adap/flower/pull/3139), " +"[#3284](https://github.com/adap/flower/pull/3284), " +"[#3251](https://github.com/adap/flower/pull/3251), " +"[#3376](https://github.com/adap/flower/pull/3376), " +"[#3287](https://github.com/adap/flower/pull/3287))" +msgstr "" +"**移除对 Python 3.7 的支持** " +"([#2280](https://github.com/adap/flower/pull/2280), " +"[#2299](https://github.com/adap/flower/pull/2299), " +"[#2304](https://github.com/adap/flower/pull/2304), " +"[#2306](https://github.com/adap/flower/pull/2306), " +"[#2355](https://github.com/adap/flower/pull/2355), " +"[#2356](https://github.com/adap/flower/pull/2356))" + +#: ../../source/ref-changelog.md:37 +msgid "" +"The `flwr` CLI's `flwr new` command is starting to become everone's " +"favorite way of creating new Flower projects. This release introduces " +"additional `flwr new` templates for Apple MLX, Hugging Face Transformers," +" scikit-learn and TensorFlow. In addition to that, existing templates " +"also received updates." +msgstr "" + +#: ../../source/ref-changelog.md:39 +#, fuzzy +msgid "" +"**Refine** `RecordSet` **API** " +"([#3209](https://github.com/adap/flower/pull/3209), " +"[#3331](https://github.com/adap/flower/pull/3331), " +"[#3334](https://github.com/adap/flower/pull/3334), " +"[#3335](https://github.com/adap/flower/pull/3335), " +"[#3375](https://github.com/adap/flower/pull/3375), " +"[#3368](https://github.com/adap/flower/pull/3368))" +msgstr "" +"**普通改进**([#1872](https://github.com/adap/flower/pull/1872), " +"[#1866](https://github.com/adap/flower/pull/1866), " +"[#1884](https://github.com/adap/flower/pull/1884), " +"[#1837](https://github.com/adap/flower/pull/1837), " +"[#1477](https://github.com/adap/flower/pull/1477), " +"[#2171](https://github.com/adap/flower/pull/2171))" + +#: ../../source/ref-changelog.md:41 +msgid "" +"`RecordSet` is part of the Flower Next low-level API preview release. In " +"Flower 1.9, `RecordSet` received a number of usability improvements that " +"make it easier to build `RecordSet`-based `ServerApp`s and `ClientApp`s." +msgstr "" + +#: ../../source/ref-changelog.md:43 +#, fuzzy +msgid "" +"**Beautify logging** ([#3379](https://github.com/adap/flower/pull/3379), " +"[#3430](https://github.com/adap/flower/pull/3430), " +"[#3461](https://github.com/adap/flower/pull/3461), " +"[#3360](https://github.com/adap/flower/pull/3360), " +"[#3433](https://github.com/adap/flower/pull/3433))" +msgstr "" +"** 更新 C++ SDK** ([#2537](https://github/com/adap/flower/pull/2537), " +"[#2528](https://github/com/adap/flower/pull/2528), " +"[#2523](https://github.com/adap/flower/pull/2523), " +"[#2522](https://github.com/adap/flower/pull/2522))" + +#: ../../source/ref-changelog.md:45 +msgid "" +"Logs received a substantial update. Not only are logs now much nicer to " +"look at, but they are also more configurable." +msgstr "" + +#: ../../source/ref-changelog.md:47 +#, fuzzy +msgid "" +"**Improve reliability** " +"([#3564](https://github.com/adap/flower/pull/3564), " +"[#3561](https://github.com/adap/flower/pull/3561), " +"[#3566](https://github.com/adap/flower/pull/3566), " +"[#3462](https://github.com/adap/flower/pull/3462), " +"[#3225](https://github.com/adap/flower/pull/3225), " +"[#3514](https://github.com/adap/flower/pull/3514), " +"[#3535](https://github.com/adap/flower/pull/3535), " +"[#3372](https://github.com/adap/flower/pull/3372))" +msgstr "" +"**改进教程** ([#1468](https://github.com/adap/flower/pull/1468), " +"[#1470](https://github.com/adap/flower/pull/1470), " +"[#1472](https://github.com/adap/flower/pull/1472), " +"[#1473](https://github.com/adap/flower/pull/1473), " +"[#1474](https://github.com/adap/flower/pull/1474), " +"[#1475](https://github.com/adap/flower/pull/1475)))" + +#: ../../source/ref-changelog.md:49 +msgid "" +"Flower 1.9 includes reliability improvements across many parts of the " +"system. One example is a much improved SuperNode shutdown procedure." +msgstr "" + +#: ../../source/ref-changelog.md:51 +#, fuzzy +msgid "" +"**Update Swift and C++ SDKs** " +"([#3321](https://github.com/adap/flower/pull/3321), " +"[#2763](https://github.com/adap/flower/pull/2763))" +msgstr "" +"** 更新代码示例** ([#1344](https://github.com/adap/flower/pull/1344), " +"[#1347](https://github.com/adap/flower/pull/1347))" + +#: ../../source/ref-changelog.md:53 +msgid "" +"In the C++ SDK, communication-related code is now separate from main " +"client logic. A new abstract class `Communicator` has been introduced " +"alongside a gRPC implementation of it." +msgstr "" + +#: ../../source/ref-changelog.md:55 +msgid "" +"**Improve testing, tooling and CI/CD infrastructure** " +"([#3294](https://github.com/adap/flower/pull/3294), " +"[#3282](https://github.com/adap/flower/pull/3282), " +"[#3311](https://github.com/adap/flower/pull/3311), " +"[#2878](https://github.com/adap/flower/pull/2878), " +"[#3333](https://github.com/adap/flower/pull/3333), " +"[#3255](https://github.com/adap/flower/pull/3255), " +"[#3349](https://github.com/adap/flower/pull/3349), " +"[#3400](https://github.com/adap/flower/pull/3400), " +"[#3401](https://github.com/adap/flower/pull/3401), " +"[#3399](https://github.com/adap/flower/pull/3399), " +"[#3346](https://github.com/adap/flower/pull/3346), " +"[#3398](https://github.com/adap/flower/pull/3398), " +"[#3397](https://github.com/adap/flower/pull/3397), " +"[#3347](https://github.com/adap/flower/pull/3347), " +"[#3502](https://github.com/adap/flower/pull/3502), " +"[#3387](https://github.com/adap/flower/pull/3387), " +"[#3542](https://github.com/adap/flower/pull/3542), " +"[#3396](https://github.com/adap/flower/pull/3396), " +"[#3496](https://github.com/adap/flower/pull/3496), " +"[#3465](https://github.com/adap/flower/pull/3465), " +"[#3473](https://github.com/adap/flower/pull/3473), " +"[#3484](https://github.com/adap/flower/pull/3484), " +"[#3521](https://github.com/adap/flower/pull/3521), " +"[#3363](https://github.com/adap/flower/pull/3363), " +"[#3497](https://github.com/adap/flower/pull/3497), " +"[#3464](https://github.com/adap/flower/pull/3464), " +"[#3495](https://github.com/adap/flower/pull/3495), " +"[#3478](https://github.com/adap/flower/pull/3478), " +"[#3271](https://github.com/adap/flower/pull/3271))" +msgstr "" + +#: ../../source/ref-changelog.md:57 +msgid "" +"As always, the Flower tooling, testing, and CI/CD infrastructure has " +"received many updates." +msgstr "" + +#: ../../source/ref-changelog.md:59 +msgid "" +"**Improve documentation** " +"([#3530](https://github.com/adap/flower/pull/3530), " +"[#3539](https://github.com/adap/flower/pull/3539), " +"[#3425](https://github.com/adap/flower/pull/3425), " +"[#3520](https://github.com/adap/flower/pull/3520), " +"[#3286](https://github.com/adap/flower/pull/3286), " +"[#3516](https://github.com/adap/flower/pull/3516), " +"[#3523](https://github.com/adap/flower/pull/3523), " +"[#3545](https://github.com/adap/flower/pull/3545), " +"[#3498](https://github.com/adap/flower/pull/3498), " +"[#3439](https://github.com/adap/flower/pull/3439), " +"[#3440](https://github.com/adap/flower/pull/3440), " +"[#3382](https://github.com/adap/flower/pull/3382), " +"[#3559](https://github.com/adap/flower/pull/3559), " +"[#3432](https://github.com/adap/flower/pull/3432), " +"[#3278](https://github.com/adap/flower/pull/3278), " +"[#3371](https://github.com/adap/flower/pull/3371), " +"[#3519](https://github.com/adap/flower/pull/3519), " +"[#3267](https://github.com/adap/flower/pull/3267), " +"[#3204](https://github.com/adap/flower/pull/3204), " +"[#3274](https://github.com/adap/flower/pull/3274))" +msgstr "" + +#: ../../source/ref-changelog.md:61 +msgid "" +"As always, the Flower documentation has received many updates. Notable " +"new pages include:" +msgstr "" + +#: ../../source/ref-changelog.md:63 +msgid "" +"[How-to upgrate to Flower Next (Flower Next migration " +"guide)](https://flower.ai/docs/framework/how-to-upgrade-to-flower-" +"next.html)" +msgstr "" + +#: ../../source/ref-changelog.md:65 +#, fuzzy +msgid "" +"[How-to run Flower using Docker](https://flower.ai/docs/framework/how-to-" +"run-flower-using-docker.html)" +msgstr "" +"`TensorFlow快速入门 (教程) `_" + +#: ../../source/ref-changelog.md:67 +msgid "" +"[Flower Mods reference](https://flower.ai/docs/framework/ref-" +"api/flwr.client.mod.html#module-flwr.client.mod)" +msgstr "" + +#: ../../source/ref-changelog.md:69 +#, fuzzy +msgid "" +"**General updates to Flower Examples** " +"([#3205](https://github.com/adap/flower/pull/3205), " +"[#3226](https://github.com/adap/flower/pull/3226), " +"[#3211](https://github.com/adap/flower/pull/3211), " +"[#3252](https://github.com/adap/flower/pull/3252), " +"[#3427](https://github.com/adap/flower/pull/3427), " +"[#3410](https://github.com/adap/flower/pull/3410), " +"[#3426](https://github.com/adap/flower/pull/3426), " +"[#3228](https://github.com/adap/flower/pull/3228), " +"[#3342](https://github.com/adap/flower/pull/3342), " +"[#3200](https://github.com/adap/flower/pull/3200), " +"[#3202](https://github.com/adap/flower/pull/3202), " +"[#3394](https://github.com/adap/flower/pull/3394), " +"[#3488](https://github.com/adap/flower/pull/3488), " +"[#3329](https://github.com/adap/flower/pull/3329), " +"[#3526](https://github.com/adap/flower/pull/3526), " +"[#3392](https://github.com/adap/flower/pull/3392), " +"[#3474](https://github.com/adap/flower/pull/3474), " +"[#3269](https://github.com/adap/flower/pull/3269))" +msgstr "" +"**更新文档** ([#1223](https://github.com/adap/flower/pull/1223), " +"[#1209](https://github.com/adap/flower/pull/1209), " +"[#1251](https://github.com/adap/flower/pull/1251), " +"[#1257](https://github.com/adap/flower/pull/1257), " +"[#1267](https://github.com/adap/flower/pull/1267), " +"[#1268](https://github.com/adap/flower/pull/1268), " +"[#1300](https://github.com/adap/flower/pull/1300), " +"[#1304](https://github.com/adap/flower/pull/1304), " +"[#1305](https://github.com/adap/flower/pull/1305), " +"[#1307](https://github.com/adap/flower/pull/1307))" + +#: ../../source/ref-changelog.md:71 +#, fuzzy +msgid "As always, Flower code examples have received many updates." +msgstr "许多 \"Flower \"代码示例得到了大幅更新。" + +#: ../../source/ref-changelog.md:73 +msgid "" +"**General improvements** " +"([#3532](https://github.com/adap/flower/pull/3532), " +"[#3318](https://github.com/adap/flower/pull/3318), " +"[#3565](https://github.com/adap/flower/pull/3565), " +"[#3296](https://github.com/adap/flower/pull/3296), " +"[#3305](https://github.com/adap/flower/pull/3305), " +"[#3246](https://github.com/adap/flower/pull/3246), " +"[#3224](https://github.com/adap/flower/pull/3224), " +"[#3475](https://github.com/adap/flower/pull/3475), " +"[#3297](https://github.com/adap/flower/pull/3297), " +"[#3317](https://github.com/adap/flower/pull/3317), " +"[#3429](https://github.com/adap/flower/pull/3429), " +"[#3196](https://github.com/adap/flower/pull/3196), " +"[#3534](https://github.com/adap/flower/pull/3534), " +"[#3240](https://github.com/adap/flower/pull/3240), " +"[#3365](https://github.com/adap/flower/pull/3365), " +"[#3407](https://github.com/adap/flower/pull/3407), " +"[#3563](https://github.com/adap/flower/pull/3563), " +"[#3344](https://github.com/adap/flower/pull/3344), " +"[#3330](https://github.com/adap/flower/pull/3330), " +"[#3436](https://github.com/adap/flower/pull/3436), " +"[#3300](https://github.com/adap/flower/pull/3300), " +"[#3327](https://github.com/adap/flower/pull/3327), " +"[#3254](https://github.com/adap/flower/pull/3254), " +"[#3253](https://github.com/adap/flower/pull/3253), " +"[#3419](https://github.com/adap/flower/pull/3419), " +"[#3289](https://github.com/adap/flower/pull/3289), " +"[#3208](https://github.com/adap/flower/pull/3208), " +"[#3245](https://github.com/adap/flower/pull/3245), " +"[#3319](https://github.com/adap/flower/pull/3319), " +"[#3203](https://github.com/adap/flower/pull/3203), " +"[#3423](https://github.com/adap/flower/pull/3423), " +"[#3352](https://github.com/adap/flower/pull/3352), " +"[#3292](https://github.com/adap/flower/pull/3292), " +"[#3261](https://github.com/adap/flower/pull/3261))" +msgstr "" + +#: ../../source/ref-changelog.md:75 ../../source/ref-changelog.md:1058 +msgid "Deprecations" +msgstr "停用" + +#: ../../source/ref-changelog.md:77 +#, fuzzy +msgid "**Deprecate Python 3.8 support**" +msgstr "** 过时的 Python 3.7**" + +#: ../../source/ref-changelog.md:79 +#, fuzzy +msgid "" +"Python 3.8 will stop receiving security fixes in [October " +"2024](https://devguide.python.org/versions/). Support for Python 3.8 is " +"now deprecated and will be removed in an upcoming release." +msgstr "由于 Python 3.7 已于 2023-06-27 弃用 (EOL),对 Python 3.7 的支持现已废弃,并将在即将发布的版本中移除。" + +#: ../../source/ref-changelog.md:81 +#, fuzzy +msgid "" +"**Deprecate (experimental)** `flower-driver-api` **and** `flower-fleet-" +"api` ([#3416](https://github.com/adap/flower/pull/3416), " +"[#3420](https://github.com/adap/flower/pull/3420))" +msgstr "" +"FedBN ([#2608](https://github.com/adap/flower/pull/2608), " +"[#2615](https://github.com/adap/flower/pull/2615))" + +#: ../../source/ref-changelog.md:83 +msgid "" +"Flower 1.9 deprecates the two (experimental) commands `flower-driver-api`" +" and `flower-fleet-api`. Both commands will be removed in an upcoming " +"release. Use `flower-superlink` instead." +msgstr "" + +#: ../../source/ref-changelog.md:85 +#, fuzzy +msgid "" +"**Deprecate** `--server` **in favor of** `--superlink` " +"([#3518](https://github.com/adap/flower/pull/3518))" +msgstr "" +"**启用向** `start_simulation` 传递** `Server` 实例 " +"([#1281](https://github.com/adap/flower/pull/1281))" + +#: ../../source/ref-changelog.md:87 +msgid "" +"The commands `flower-server-app` and `flower-client-app` should use " +"`--superlink` instead of the now deprecated `--server`. Support for " +"`--server` will be removed in a future release." +msgstr "" + +#: ../../source/ref-changelog.md:89 ../../source/ref-changelog.md:163 +#: ../../source/ref-changelog.md:238 ../../source/ref-changelog.md:350 +#: ../../source/ref-changelog.md:440 ../../source/ref-changelog.md:504 +#: ../../source/ref-changelog.md:562 ../../source/ref-changelog.md:631 +#: ../../source/ref-changelog.md:693 ../../source/ref-changelog.md:712 +#: ../../source/ref-changelog.md:868 ../../source/ref-changelog.md:939 +#: ../../source/ref-changelog.md:976 ../../source/ref-changelog.md:1019 +msgid "Incompatible changes" +msgstr "不兼容的更改" + +#: ../../source/ref-changelog.md:91 +msgid "" +"**Replace** `flower-superlink` **CLI option** `--certificates` **with** " +"`--ssl-ca-certfile` **,** `--ssl-certfile` **and** `--ssl-keyfile` " +"([#3512](https://github.com/adap/flower/pull/3512), " +"[#3408](https://github.com/adap/flower/pull/3408))" +msgstr "" + +#: ../../source/ref-changelog.md:93 +msgid "" +"SSL-related `flower-superlink` CLI arguments were restructured in an " +"incompatible way. Instead of passing a single `--certificates` flag with " +"three values, you now need to pass three flags (`--ssl-ca-certfile`, " +"`--ssl-certfile` and `--ssl-keyfile`) with one value each. Check out the " +"[SSL connections](https://flower.ai/docs/framework/how-to-enable-ssl-" +"connections.html) documentation page for details." +msgstr "" + +#: ../../source/ref-changelog.md:95 +#, fuzzy +msgid "" +"**Remove SuperLink** `--vce` **option** " +"([#3513](https://github.com/adap/flower/pull/3513))" +msgstr "**重构文档**([#1387](https://github.com/adap/flower/pull/1387))" + +#: ../../source/ref-changelog.md:97 +msgid "" +"Instead of separately starting a SuperLink and a `ServerApp` for " +"simulation, simulations must now be started using the single `flower-" +"simulation` command." +msgstr "" + +#: ../../source/ref-changelog.md:99 +#, fuzzy +msgid "" +"**Merge** `--grpc-rere` **and** `--rest` **SuperLink options** " +"([#3527](https://github.com/adap/flower/pull/3527))" +msgstr "" +"**重新命名** `rnd` ** to** `server_round` " +"([#1321](https://github.com/adap/flower/pull/1321))" + +#: ../../source/ref-changelog.md:101 +msgid "" +"To simplify the usage of `flower-superlink`, previously separate sets of " +"CLI options for gRPC and REST were merged into one unified set of " +"options. Consult the [Flower CLI reference " +"documentation](https://flower.ai/docs/framework/ref-api-cli.html) for " +"details." +msgstr "" + +#: ../../source/ref-changelog.md:103 +#, fuzzy +msgid "v1.8.0 (2024-04-03)" +msgstr "v1.3.0 (2023-02-06)" + +#: ../../source/ref-changelog.md:109 #, fuzzy msgid "" "`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " @@ -16534,16 +17248,43 @@ msgid "" "`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " "`tabdar-khan` " msgstr "" -"`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " -"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " -"Bertoli`, `Heng Pan`, `Javier`, `Mahdi`, `Steven Hé (Sīchàng)`, `Taner " -"Topal`, `achiverram28`, `danielnugraha`, `eunchung`, `ruthgal` " - -#: ../../source/ref-changelog.md:21 -#, fuzzy -msgid "" -"**Introduce Flower Next high-level API (stable)** " +"`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " +"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " +"Bertoli`, `Heng Pan`, `Javier`, `Mahdi`, `Steven Hé (Sīchàng)`, `Taner " +"Topal`, `achiverram28`, `danielnugraha`, `eunchung`, `ruthgal` " + +#: ../../source/ref-changelog.md:113 +#, fuzzy +msgid "" +"**Introduce Flower Next high-level API (stable)** " +"([#3002](https://github.com/adap/flower/pull/3002), " +"[#2934](https://github.com/adap/flower/pull/2934), " +"[#2958](https://github.com/adap/flower/pull/2958), " +"[#3173](https://github.com/adap/flower/pull/3173), " +"[#3174](https://github.com/adap/flower/pull/3174), " +"[#2923](https://github.com/adap/flower/pull/2923), " +"[#2691](https://github.com/adap/flower/pull/2691), " +"[#3079](https://github.com/adap/flower/pull/3079), " +"[#2961](https://github.com/adap/flower/pull/2961), " +"[#2924](https://github.com/adap/flower/pull/2924), " +"[#3166](https://github.com/adap/flower/pull/3166), " +"[#3031](https://github.com/adap/flower/pull/3031), " +"[#3057](https://github.com/adap/flower/pull/3057), " +"[#3000](https://github.com/adap/flower/pull/3000), " +"[#3113](https://github.com/adap/flower/pull/3113), " +"[#2957](https://github.com/adap/flower/pull/2957), " +"[#3183](https://github.com/adap/flower/pull/3183), " +"[#3180](https://github.com/adap/flower/pull/3180), " +"[#3035](https://github.com/adap/flower/pull/3035), " +"[#3189](https://github.com/adap/flower/pull/3189), " +"[#3185](https://github.com/adap/flower/pull/3185), " +"[#3190](https://github.com/adap/flower/pull/3190), " +"[#3191](https://github.com/adap/flower/pull/3191), " +"[#3195](https://github.com/adap/flower/pull/3195), " +"[#3197](https://github.com/adap/flower/pull/3197))" +msgstr "" +"**介绍 Flower Next 高级应用程序接口(稳定版)** " "([#3002](https://github.com/adap/flower/pull/3002), " "[#2934](https://github.com/adap/flower/pull/2934), " "[#2958](https://github.com/adap/flower/pull/2958), " @@ -16569,27 +17310,8 @@ msgid "" "[#3191](https://github.com/adap/flower/pull/3191), " "[#3195](https://github.com/adap/flower/pull/3195), " "[#3197](https://github.com/adap/flower/pull/3197))" -msgstr "" -"**介绍 Flower Next 高级应用程序接口(稳定版)** ([#3002](https://github.com/" -"adap/flower/pull/3002), [#2934](https://github.com/adap/flower/pull/2934), " -"[#2958](https://github.com/adap/flower/pull/2958), [#3173](https://github." -"com/adap/flower/pull/3173), [#3174](https://github.com/adap/flower/pull/3174)" -", [#2923](https://github.com/adap/flower/pull/2923), [#2691](https://github." -"com/adap/flower/pull/2691), [#3079](https://github.com/adap/flower/pull/3079)" -", [#2961](https://github.com/adap/flower/pull/2961), [#2924](https://github." -"com/adap/flower/pull/2924), [#3166](https://github.com/adap/flower/pull/3166)" -", [#3031](https://github.com/adap/flower/pull/3031), [#3057](https://github." -"com/adap/flower/pull/3057), [#3000](https://github.com/adap/flower/pull/3000)" -", [#3113](https://github.com/adap/flower/pull/3113), [#2957](https://github." -"com/adap/flower/pull/2957), [#3183](https://github.com/adap/flower/pull/3183)" -", [#3180](https://github.com/adap/flower/pull/3180), [#3035](https://github." -"com/adap/flower/pull/3035), [#3189](https://github.com/adap/flower/pull/3189)" -", [#3185](https://github.com/adap/flower/pull/3185), [#3190](https://github." -"com/adap/flower/pull/3190), [#3191](https://github.com/adap/flower/pull/3191)" -", [#3195](https://github.com/adap/flower/pull/3195), [#3197](https://github." -"com/adap/flower/pull/3197))" -#: ../../source/ref-changelog.md:23 +#: ../../source/ref-changelog.md:115 #, fuzzy msgid "" "The Flower Next high-level API is stable! Flower Next is the future of " @@ -16603,15 +17325,13 @@ msgid "" " line of code. The best part? It's fully compatible with existing Flower " "projects that use `Strategy`, `NumPyClient` & co." msgstr "" -"Flower Next 高级应用程序接口已经稳定!Flower Next 是 Flower 的未来 - " -"所有新功能(如 Flower Mods)都将构建在它之上。您可以使用 `ServerApp` 和 " -"`ClientApp` 开始将现有项目迁移到 Flower Next(请查看 `quickstart-pytorch` 或 " -"`quickstart-tensorflow` ,详细的迁移指南将在不久后发布)。Flower Next 允许您" -"同时运行多个项目(我们称之为多重运行),并在模拟环境或部署环境中执行同一项目" -",而无需更改任何代码。最棒的是什么?它与使用 `Strategy`、`NumPyClient` " -"等的现有 Flower 项目完全兼容。" +"Flower Next 高级应用程序接口已经稳定!Flower Next 是 Flower 的未来 - 所有新功能(如 Flower " +"Mods)都将构建在它之上。您可以使用 `ServerApp` 和 `ClientApp` 开始将现有项目迁移到 Flower Next(请查看 " +"`quickstart-pytorch` 或 `quickstart-tensorflow` ,详细的迁移指南将在不久后发布)。Flower " +"Next 允许您同时运行多个项目(我们称之为多重运行),并在模拟环境或部署环境中执行同一项目,而无需更改任何代码。最棒的是什么?它与使用 " +"`Strategy`、`NumPyClient` 等的现有 Flower 项目完全兼容。" -#: ../../source/ref-changelog.md:25 +#: ../../source/ref-changelog.md:117 #, fuzzy msgid "" "**Introduce Flower Next low-level API (preview)** " @@ -16623,7 +17343,7 @@ msgstr "" "[#2390](https://github.com/adap/flower/pull/2390), " "[#2493](https://github.com/adap/flower/pull/2493))" -#: ../../source/ref-changelog.md:27 +#: ../../source/ref-changelog.md:119 #, fuzzy msgid "" "In addition to the Flower Next *high-level* API that uses `Strategy`, " @@ -16640,17 +17360,15 @@ msgid "" "metrics, stateful computations on the client node and implementations of " "custom SMPC protocols, to name just a few." msgstr "" -"除了使用 \"Strategy\"、\"NumPyClient \"等的 Flower Next 高级应用程序接口外," -"Flower 1.8 还提供了新的 Flower Next 低级应用程序接口的预览版。低级应用程序接" -"口允许通过向/从客户端节点发送/接收单个消息,对学习过程的各个方面进行细粒度控" -"制。新的 \"ServerApp \"支持注册一个自定义的 \"main \"函数" -",允许为异步FL、循环训练或联合分析等方法编写自定义训练循环。新的 \"ClientApp " -"\"支持注册 \"训练\"、\"评估 \"和 \"查询 \"函数,这些函数可以访问从 " -"\"ServerApp \"接收到的原始信息。新的抽象(如 \"RecordSet\"、\"Message \"和 " -"\"Context\")进一步支持发送多个模型、多套配置值和指标、" -"客户端节点上的有状态计算以及自定义 SMPC 协议的实现等。" +"除了使用 \"Strategy\"、\"NumPyClient \"等的 Flower Next 高级应用程序接口外,Flower 1.8 " +"还提供了新的 Flower Next " +"低级应用程序接口的预览版。低级应用程序接口允许通过向/从客户端节点发送/接收单个消息,对学习过程的各个方面进行细粒度控制。新的 " +"\"ServerApp \"支持注册一个自定义的 \"main \"函数,允许为异步FL、循环训练或联合分析等方法编写自定义训练循环。新的 " +"\"ClientApp \"支持注册 \"训练\"、\"评估 \"和 \"查询 \"函数,这些函数可以访问从 \"ServerApp " +"\"接收到的原始信息。新的抽象(如 \"RecordSet\"、\"Message \"和 " +"\"Context\")进一步支持发送多个模型、多套配置值和指标、客户端节点上的有状态计算以及自定义 SMPC 协议的实现等。" -#: ../../source/ref-changelog.md:29 +#: ../../source/ref-changelog.md:121 #, fuzzy msgid "" "**Introduce Flower Mods (preview)** " @@ -16662,7 +17380,7 @@ msgstr "" "[#2221](https://github.com/adap/flower/pull/2221), " "[#2248](https://github.com/adap/flower/pull/2248))" -#: ../../source/ref-changelog.md:31 +#: ../../source/ref-changelog.md:123 #, fuzzy msgid "" "Flower Modifiers (we call them Mods) can intercept messages and analyze, " @@ -16674,13 +17392,12 @@ msgid "" "SecAgg+. The Flower Mods API is released as a preview, but researchers " "can already use it to experiment with arbirtrary SMPC protocols." msgstr "" -"Flower Modifiers(我们称之为 Mods)可以拦截信息,并直接对其进行分析、编辑或处" -"理。修改器可用于开发可在不同项目中使用的可插拔模块。Flower 1.8 已经包含了记录" -"信息大小、通过网络发送的参数数量、固定剪切和自适应剪切的差分隐私、" -"本地差分隐私以及安全聚合协议 SecAgg 和 SecAgg+ 的 Mods。Flower Mods API " -"作为预览版发布,但研究人员已经可以用它来试验任意的 SMPC 协议。" +"Flower Modifiers(我们称之为 " +"Mods)可以拦截信息,并直接对其进行分析、编辑或处理。修改器可用于开发可在不同项目中使用的可插拔模块。Flower 1.8 " +"已经包含了记录信息大小、通过网络发送的参数数量、固定剪切和自适应剪切的差分隐私、本地差分隐私以及安全聚合协议 SecAgg 和 SecAgg+ 的" +" Mods。Flower Mods API 作为预览版发布,但研究人员已经可以用它来试验任意的 SMPC 协议。" -#: ../../source/ref-changelog.md:33 +#: ../../source/ref-changelog.md:125 #, fuzzy msgid "" "**Fine-tune LLMs with LLM FlowerTune** " @@ -16699,7 +17416,7 @@ msgstr "" "[#1474](https://github.com/adap/flower/pull/1474), " "[#1475](https://github.com/adap/flower/pull/1475)))" -#: ../../source/ref-changelog.md:35 +#: ../../source/ref-changelog.md:127 #, fuzzy msgid "" "We are introducing LLM FlowerTune, an introductory example that " @@ -16709,14 +17426,12 @@ msgid "" "Federated LLM Fine-tuning with Flower](https://flower.ai/blog/2024-03-14" "-llm-flowertune-federated-llm-finetuning-with-flower/) for more details." msgstr "" -"我们将介绍 LLM FlowerTune,这是一个介绍性示例,演示了在 Alpaca-GPT4 " -"数据集上对预先训练好的 Llama2 模型进行联合 LLM " -"微调。该示例可轻松调整以使用不同的模型和/或数据集。请阅读我们的博文 [LLM " -"FlowerTune: Federated LLM Fine-tuning with Flower](https://flower.ai/blog/" -"2024-03-14-llm-flowertune-federated-llm-finetuning-with-flower/) " -"了解更多详情。" +"我们将介绍 LLM FlowerTune,这是一个介绍性示例,演示了在 Alpaca-GPT4 数据集上对预先训练好的 Llama2 模型进行联合" +" LLM 微调。该示例可轻松调整以使用不同的模型和/或数据集。请阅读我们的博文 [LLM FlowerTune: Federated LLM " +"Fine-tuning with Flower](https://flower.ai/blog/2024-03-14-llm-" +"flowertune-federated-llm-finetuning-with-flower/) 了解更多详情。" -#: ../../source/ref-changelog.md:37 +#: ../../source/ref-changelog.md:129 #, fuzzy msgid "" "**Introduce built-in Differential Privacy (preview)** " @@ -16737,7 +17452,7 @@ msgstr "" "[#993](https://github.com/adap/flower/pull/993), " "[#994](https://github.com/adap/flower/pull/994))" -#: ../../source/ref-changelog.md:39 +#: ../../source/ref-changelog.md:131 #, fuzzy msgid "" "Built-in Differential Privacy is here! Flower supports both central and " @@ -16750,15 +17465,13 @@ msgid "" "the new Differential Privacy components](https://flower.ai/docs/framework" "/how-to-use-differential-privacy.html) in Flower." msgstr "" -"内置差分保密功能!Flower 支持中央和本地差分保密 " -"(DP)。中央差分隐私可配置为固定或自适应剪切。剪切可以发生在服务器端或客户端。" -"本地 DP " -"在客户端进行剪切和噪声处理。新的文档页面[解释差分隐私方法](https://flower.ai/" -"docs/framework/explanation-differential-privacy.html) " -"和新的操作指南[如何使用新的差分隐私组件](https://flower.ai/docs/framework/" -"how-to-use-differential-privacy.html) 介绍了 Flower 的使用方法。" +"内置差分保密功能!Flower 支持中央和本地差分保密 (DP)。中央差分隐私可配置为固定或自适应剪切。剪切可以发生在服务器端或客户端。本地 DP" +" 在客户端进行剪切和噪声处理。新的文档页面[解释差分隐私方法](https://flower.ai/docs/framework" +"/explanation-differential-privacy.html) " +"和新的操作指南[如何使用新的差分隐私组件](https://flower.ai/docs/framework/how-to-use-" +"differential-privacy.html) 介绍了 Flower 的使用方法。" -#: ../../source/ref-changelog.md:41 +#: ../../source/ref-changelog.md:133 #, fuzzy msgid "" "**Introduce built-in Secure Aggregation (preview)** " @@ -16770,7 +17483,7 @@ msgstr "" "[#2221](https://github.com/adap/flower/pull/2221), " "[#2248](https://github.com/adap/flower/pull/2248))" -#: ../../source/ref-changelog.md:43 +#: ../../source/ref-changelog.md:135 #, fuzzy msgid "" "Built-in Secure Aggregation is here! Flower now supports different secure" @@ -16783,14 +17496,12 @@ msgid "" "combine Federated Learning, Differential Privacy and Secure Aggregation " "in the same project." msgstr "" -"内置安全聚合功能!Flower " -"现在支持不同的安全聚合协议。最棒的是什么?只需几行代码," -"您就可以将安全聚合添加到 Flower 项目中。在这个初始版本中,我们包含了对 " -"SecAgg 和 SecAgg+ 的支持,但更多协议将很快实现。我们还将添加详细的文档," -"解释安全聚合以及如何在 Flower 中使用它。您可以查看新的代码示例,了解如何使用 " +"内置安全聚合功能!Flower 现在支持不同的安全聚合协议。最棒的是什么?只需几行代码,您就可以将安全聚合添加到 Flower " +"项目中。在这个初始版本中,我们包含了对 SecAgg 和 SecAgg+ " +"的支持,但更多协议将很快实现。我们还将添加详细的文档,解释安全聚合以及如何在 Flower 中使用它。您可以查看新的代码示例,了解如何使用 " "Flower 在同一项目中轻松结合联合学习、差分隐私和安全聚合。" -#: ../../source/ref-changelog.md:45 +#: ../../source/ref-changelog.md:137 #, fuzzy msgid "" "**Introduce** `flwr` **CLI (preview)** " @@ -16811,15 +17522,14 @@ msgstr "" "[#1477](https://github.com/adap/flower/pull/1477), " "[#2171](https://github.com/adap/flower/pull/2171))" -#: ../../source/ref-changelog.md:47 +#: ../../source/ref-changelog.md:139 #, fuzzy msgid "" "A new `flwr` CLI command allows creating new Flower projects (`flwr new`)" " and then running them using the Simulation Engine (`flwr run`)." -msgstr "新的 `flwr` CLI 命令允许创建新的 Flower 项目(`flwr " -"new`),然后使用仿真引擎运行它们(`flwr run`)。" +msgstr "新的 `flwr` CLI 命令允许创建新的 Flower 项目(`flwr new`),然后使用仿真引擎运行它们(`flwr run`)。" -#: ../../source/ref-changelog.md:49 +#: ../../source/ref-changelog.md:141 #, fuzzy msgid "" "**Introduce Flower Next Simulation Engine** " @@ -16842,24 +17552,24 @@ msgstr "" "[#1770](https://github.com/adap/flower/pull/1770), " "[#1733](https://github.com/adap/flower/pull/1733))" -#: ../../source/ref-changelog.md:51 +#: ../../source/ref-changelog.md:143 #, fuzzy msgid "" "The Flower Simulation Engine can now run Flower Next projects. For " "notebook environments, there's also a new `run_simulation` function that " "can run `ServerApp` and `ClientApp`." msgstr "" -"Flower 模拟引擎现在可以运行 Flower Next 项目。对于笔记本环境,还有一个新的 " -"`run_simulation` 函数,可以运行 `ServerApp` 和 `ClientApp`。" +"Flower 模拟引擎现在可以运行 Flower Next 项目。对于笔记本环境,还有一个新的 `run_simulation` 函数,可以运行 " +"`ServerApp` 和 `ClientApp`。" -#: ../../source/ref-changelog.md:53 +#: ../../source/ref-changelog.md:145 #, fuzzy msgid "" "**Handle SuperNode connection errors** " "([#2969](https://github.com/adap/flower/pull/2969))" msgstr "** 添加一个新的 gRPC 选项**([#2197](https://github.com/adap/flower/pull/2197))" -#: ../../source/ref-changelog.md:55 +#: ../../source/ref-changelog.md:147 #, fuzzy msgid "" "A SuperNode will now try to reconnect indefinitely to the SuperLink in " @@ -16870,12 +17580,11 @@ msgid "" "wait-time` defines the time before the SuperNode gives up trying to " "reconnect to the SuperLink." msgstr "" -"如果出现连接错误,超级节点现在会尝试无限期地重新连接超级链接。现在可以向 " -"`flower-client-app` 命令传递参数 `-ax-retries` 和 `-max-wait-time`。" -"最大重试次数 \"将定义客户端在放弃重新连接超级链接之前的重试次数,而 " -"\"最大等待时间 \"则定义超级节点放弃重新连接超级链接之前的等待时间。" +"如果出现连接错误,超级节点现在会尝试无限期地重新连接超级链接。现在可以向 `flower-client-app` 命令传递参数 `-ax-" +"retries` 和 `-max-wait-time`。最大重试次数 \"将定义客户端在放弃重新连接超级链接之前的重试次数,而 \"最大等待时间 " +"\"则定义超级节点放弃重新连接超级链接之前的等待时间。" -#: ../../source/ref-changelog.md:57 +#: ../../source/ref-changelog.md:149 #, fuzzy msgid "" "**General updates to Flower Baselines** " @@ -16890,7 +17599,7 @@ msgstr "" "[#1681](https://github.com/adap/flower/pull/1681), " "[#1679](https://github.com/adap/flower/pull/1679)" -#: ../../source/ref-changelog.md:59 +#: ../../source/ref-changelog.md:151 #, fuzzy msgid "" "There's a new [FedStar](https://flower.ai/docs/baselines/fedstar.html) " @@ -16899,7 +17608,7 @@ msgstr "" "有一条新的 [FedStar](https://flower.ai/docs/baselines/fedstar.html) " "基准线。其他几条基准线也已更新。" -#: ../../source/ref-changelog.md:61 +#: ../../source/ref-changelog.md:153 #, fuzzy msgid "" "**Improve documentation and translations** " @@ -16921,30 +17630,33 @@ msgid "" "[#2989](https://github.com/adap/flower/pull/2989))" msgstr "" "**改进文件和翻译** ([#3050](https://github.com/adap/flower/pull/3050), " -"[#3044](https://github.com/adap/flower/pull/3044), [#3043](https://github." -"com/adap/flower/pull/3043), [#2986](https://github.com/adap/flower/pull/2986)" -", [#3041](https://github.com/adap/flower/pull/3041), [#3046](https://github." -"com/adap/flower/pull/3046), [#3042](https://github.com/adap/flower/pull/3042)" -", [#2978](https://github.com/adap/flower/pull/2978), [#2952](https://github." -"com/adap/flower/pull/2952), [#3167](https://github.com/adap/flower/pull/3167)" -", [#2953](https://github.com/adap/flower/pull/2953), [#3045](https://github." -"com/adap/flower/pull/3045), [#2654](https://github.com/adap/flower/pull/2654)" -", [#3082](https://github.com/adap/flower/pull/3082), [#2990](https://github." -"com/adap/flower/pull/2990), [#2989](https://github.com/adap/flower/pull/" -"2989))" +"[#3044](https://github.com/adap/flower/pull/3044), " +"[#3043](https://github.com/adap/flower/pull/3043), " +"[#2986](https://github.com/adap/flower/pull/2986), " +"[#3041](https://github.com/adap/flower/pull/3041), " +"[#3046](https://github.com/adap/flower/pull/3046), " +"[#3042](https://github.com/adap/flower/pull/3042), " +"[#2978](https://github.com/adap/flower/pull/2978), " +"[#2952](https://github.com/adap/flower/pull/2952), " +"[#3167](https://github.com/adap/flower/pull/3167), " +"[#2953](https://github.com/adap/flower/pull/2953), " +"[#3045](https://github.com/adap/flower/pull/3045), " +"[#2654](https://github.com/adap/flower/pull/2654), " +"[#3082](https://github.com/adap/flower/pull/3082), " +"[#2990](https://github.com/adap/flower/pull/2990), " +"[#2989](https://github.com/adap/flower/pull/2989))" -#: ../../source/ref-changelog.md:63 +#: ../../source/ref-changelog.md:155 #, fuzzy msgid "" "As usual, we merged many smaller and larger improvements to the " "documentation. A special thank you goes to [Sebastian van der " "Voort](https://github.com/svdvoort) for landing a big documentation PR!" msgstr "" -"像往常一样,我们合并了许多对文档的较大和较小的改进。特别要感谢 [Sebastian " -"van der Voort](https://github.com/svdvoort),他为我们带来了一份重要的文档 " -"PR!" +"像往常一样,我们合并了许多对文档的较大和较小的改进。特别要感谢 [Sebastian van der " +"Voort](https://github.com/svdvoort),他为我们带来了一份重要的文档 PR!" -#: ../../source/ref-changelog.md:65 +#: ../../source/ref-changelog.md:157 #, fuzzy msgid "" "**General updates to Flower Examples** " @@ -16970,7 +17682,7 @@ msgstr "" "[#1519](https://github.com/adap/flower/pull/1519), " "[#1515](https://github.com/adap/flower/pull/1515))" -#: ../../source/ref-changelog.md:67 +#: ../../source/ref-changelog.md:159 #, fuzzy msgid "" "Two new examples show federated training of a Vision Transformer (ViT) " @@ -16979,12 +17691,11 @@ msgid "" " new Flower Next `ServerApp` and `ClientApp`. Many other examples " "received considerable updates as well." msgstr "" -"两个新示例展示了视觉转换器(ViT)的联合训练,以及使用流行的 MONAI " -"库在医疗环境中进行的联合学习。quickstart-pytorch \"和 \"quickstart-" -"tensorflow \"展示了新的 Flower Next \"ServerApp \"和 \"ClientApp\"" -"。许多其他示例也得到了大量更新。" +"两个新示例展示了视觉转换器(ViT)的联合训练,以及使用流行的 MONAI 库在医疗环境中进行的联合学习。quickstart-pytorch " +"\"和 \"quickstart-tensorflow \"展示了新的 Flower Next \"ServerApp \"和 " +"\"ClientApp\"。许多其他示例也得到了大量更新。" -#: ../../source/ref-changelog.md:69 +#: ../../source/ref-changelog.md:161 #, fuzzy msgid "" "**General improvements** " @@ -17063,62 +17774,91 @@ msgid "" "[#2954](https://github.com/adap/flower/pull/2954))" msgstr "" "**一般改进**([#3171](https://github.com/adap/flower/pull/3171), " -"[3099](https://github.com/adap/flower/pull/3099), [3003](https://github.com/" -"adap/flower/pull/3003), [3145](https://github.com/adap/flower/pull/3145), " -"[3017](https://github.com/adap/flower/pull/3017), [3085](https://github.com/" -"adap/flower/pull/3085), [3012](https://github.com/adap/flower/pull/3012), " -"[3119](https://github.com/adap/flower/pull/3119), [2991](https://github.com/" -"adap/flower/pull/2991), [2970](https://github.com/adap/flower/pull/2970), " -"[2980](https://github.com/adap/flower/pull/2980), [3086](https://github.com/" -"adap/flower/pull/3086), [2932](https://github.com/adap/flower/pull/2932), " -"[2928](https://github.com/adap/flower/pull/2928), [2941](https://github.com/" -"adap/flower/pull/2941), [2933](https://github.com/adap/flower/pull/2933), " -"[3181](https://github.com/adap/flower/pull/3181), [2973](https://github.com/" -"adap/flower/pull/2973), [2992](https://github.com/adap/flower/pull/2992), " -"[2915](https://github.com/adap/flower/pull/2915), [3040](https://github.com/" -"adap/flower/pull/3040), [3022](https://github.com/adap/flower/pull/3022), " -"[3032](https://github.com/adap/flower/pull/3032), [2902](https://github.com/" -"adap/flower/pull/2902), [2931](https://github.com/adap/flower/pull/2931), " -"[3005](https://github.com/adap/flower/pull/3005), [3132](https://github.com/" -"adap/flower/pull/3132), [3115](https://github.com/adap/flower/pull/3115), " -"[2944](https://github.com/adap/flower/pull/2944), [3064](https://github.com/" -"adap/flower/pull/3064), [3106](https://github.com/adap/flower/pull/3106), " -"[2974](https://github.com/adap/flower/pull/2974), [3178](https://github.com/" -"adap/flower/pull/3178), [2993](https://github.com/adap/flower/pull/2993), " -"[3186](https://github.com/adap/flower/pull/3186), [3091](https://github.com/" -"adap/flower/pull/3091), [3125](https://github.com/adap/flower/pull/3125), " -"[3093](https://github.com/adap/flower/pull/3093), [3013](https://github.com/" -"adap/flower/pull/3013), [3033](https://github.com/adap/flower/pull/3033), " -"[3133](https://github.com/adap/flower/pull/3133), [3068](https://github.com/" -"adap/flower/pull/3068), [2916](https://github.com/adap/flower/pull/2916), " -"[2975](https://github.com/adap/flower/pull/2975), [2984](https://github.com/" -"adap/flower/pull/2984), [2846](https://github.com/adap/flower/pull/2846), " -"[3077](https://github.com/adap/flower/pull/3077), [3143](https://github.com/" -"adap/flower/pull/3143), [2921](https://github.com/adap/flower/pull/2921), " -"[3101](https://github.com/adap/flower/pull/3101), [2927](https://github.com/" -"adap/flower/pull/2927), [2995](https://github.com/adap/flower/pull/2995), " -"[2972](https://github.com/adap/flower/pull/2972), [2912](https://github.com/" -"adap/flower/pull/2912), [3065](https://github.com/adap/flower/pull/3065), " -"[3028](https://github.com/adap/flower/pull/3028), [2922](https://github.com/" -"adap/flower/pull/2922), [2982](https://github.com/adap/flower/pull/2982), " -"[2914](https://github.com/adap/flower/pull/2914), [3179](https://github.com/" -"adap/flower/pull/3179), [3080](https://github.com/adap/flower/pull/3080), " -"[2994](https://github.com/adap/flower/pull/2994), [3187](https://github.com/" -"adap/flower/pull/3187), [2926](https://github.com/adap/flower/pull/2926), " -"[3018](https://github.com/adap/flower/pull/3018), [3144](https://github.com/" -"adap/flower/pull/3144), [3011](https://github.com/adap/flower/pull/3011), " -"[#3152](https://github.com/adap/flower/pull/3152), [#2836](https://github." -"com/adap/flower/pull/2836), [#2929](https://github.com/adap/flower/pull/2929)" -", [#2943](https://github.com/adap/flower/pull/2943), [#2955](https://github." -"com/adap/flower/pull/2955), [#2954](https://github.com/adap/flower/pull/" -"2954))" - -#: ../../source/ref-changelog.md:75 +"[3099](https://github.com/adap/flower/pull/3099), " +"[3003](https://github.com/adap/flower/pull/3003), " +"[3145](https://github.com/adap/flower/pull/3145), " +"[3017](https://github.com/adap/flower/pull/3017), " +"[3085](https://github.com/adap/flower/pull/3085), " +"[3012](https://github.com/adap/flower/pull/3012), " +"[3119](https://github.com/adap/flower/pull/3119), " +"[2991](https://github.com/adap/flower/pull/2991), " +"[2970](https://github.com/adap/flower/pull/2970), " +"[2980](https://github.com/adap/flower/pull/2980), " +"[3086](https://github.com/adap/flower/pull/3086), " +"[2932](https://github.com/adap/flower/pull/2932), " +"[2928](https://github.com/adap/flower/pull/2928), " +"[2941](https://github.com/adap/flower/pull/2941), " +"[2933](https://github.com/adap/flower/pull/2933), " +"[3181](https://github.com/adap/flower/pull/3181), " +"[2973](https://github.com/adap/flower/pull/2973), " +"[2992](https://github.com/adap/flower/pull/2992), " +"[2915](https://github.com/adap/flower/pull/2915), " +"[3040](https://github.com/adap/flower/pull/3040), " +"[3022](https://github.com/adap/flower/pull/3022), " +"[3032](https://github.com/adap/flower/pull/3032), " +"[2902](https://github.com/adap/flower/pull/2902), " +"[2931](https://github.com/adap/flower/pull/2931), " +"[3005](https://github.com/adap/flower/pull/3005), " +"[3132](https://github.com/adap/flower/pull/3132), " +"[3115](https://github.com/adap/flower/pull/3115), " +"[2944](https://github.com/adap/flower/pull/2944), " +"[3064](https://github.com/adap/flower/pull/3064), " +"[3106](https://github.com/adap/flower/pull/3106), " +"[2974](https://github.com/adap/flower/pull/2974), " +"[3178](https://github.com/adap/flower/pull/3178), " +"[2993](https://github.com/adap/flower/pull/2993), " +"[3186](https://github.com/adap/flower/pull/3186), " +"[3091](https://github.com/adap/flower/pull/3091), " +"[3125](https://github.com/adap/flower/pull/3125), " +"[3093](https://github.com/adap/flower/pull/3093), " +"[3013](https://github.com/adap/flower/pull/3013), " +"[3033](https://github.com/adap/flower/pull/3033), " +"[3133](https://github.com/adap/flower/pull/3133), " +"[3068](https://github.com/adap/flower/pull/3068), " +"[2916](https://github.com/adap/flower/pull/2916), " +"[2975](https://github.com/adap/flower/pull/2975), " +"[2984](https://github.com/adap/flower/pull/2984), " +"[2846](https://github.com/adap/flower/pull/2846), " +"[3077](https://github.com/adap/flower/pull/3077), " +"[3143](https://github.com/adap/flower/pull/3143), " +"[2921](https://github.com/adap/flower/pull/2921), " +"[3101](https://github.com/adap/flower/pull/3101), " +"[2927](https://github.com/adap/flower/pull/2927), " +"[2995](https://github.com/adap/flower/pull/2995), " +"[2972](https://github.com/adap/flower/pull/2972), " +"[2912](https://github.com/adap/flower/pull/2912), " +"[3065](https://github.com/adap/flower/pull/3065), " +"[3028](https://github.com/adap/flower/pull/3028), " +"[2922](https://github.com/adap/flower/pull/2922), " +"[2982](https://github.com/adap/flower/pull/2982), " +"[2914](https://github.com/adap/flower/pull/2914), " +"[3179](https://github.com/adap/flower/pull/3179), " +"[3080](https://github.com/adap/flower/pull/3080), " +"[2994](https://github.com/adap/flower/pull/2994), " +"[3187](https://github.com/adap/flower/pull/3187), " +"[2926](https://github.com/adap/flower/pull/2926), " +"[3018](https://github.com/adap/flower/pull/3018), " +"[3144](https://github.com/adap/flower/pull/3144), " +"[3011](https://github.com/adap/flower/pull/3011), " +"[#3152](https://github.com/adap/flower/pull/3152), " +"[#2836](https://github.com/adap/flower/pull/2836), " +"[#2929](https://github.com/adap/flower/pull/2929), " +"[#2943](https://github.com/adap/flower/pull/2943), " +"[#2955](https://github.com/adap/flower/pull/2955), " +"[#2954](https://github.com/adap/flower/pull/2954))" + +#: ../../source/ref-changelog.md:165 ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:506 ../../source/ref-changelog.md:564 +#: ../../source/ref-changelog.md:633 ../../source/ref-changelog.md:695 +msgid "None" +msgstr "无" + +#: ../../source/ref-changelog.md:167 #, fuzzy msgid "v1.7.0 (2024-02-05)" msgstr "v1.3.0 (2023-02-06)" -#: ../../source/ref-changelog.md:81 +#: ../../source/ref-changelog.md:173 #, fuzzy msgid "" "`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles " @@ -17134,7 +17874,7 @@ msgstr "" "Topal`, `achiverram28`, `danielnugraha`, `eunchung`, `ruthgal` " -#: ../../source/ref-changelog.md:85 +#: ../../source/ref-changelog.md:177 #, fuzzy msgid "" "**Introduce stateful clients (experimental)** " @@ -17150,7 +17890,7 @@ msgstr "" "[#2327](https://github.com/adap/flower/pull/2327), " "[#2435](https://github.com/adap/flower/pull/2435))" -#: ../../source/ref-changelog.md:87 +#: ../../source/ref-changelog.md:179 #, fuzzy msgid "" "Subclasses of `Client` and `NumPyClient` can now store local state that " @@ -17163,21 +17903,20 @@ msgid "" "different rounds of execution to enable stateful computations in a " "unified way across simulation and deployment." msgstr "" -"客户端 \"和 \"NumPyClient \"的子类现在可以存储保留在客户端上的本地状态" -"。让我们先从亮点开始:这一新功能与模拟客户端(通过 " -"`start_simulation`)和网络客户端(通过 `start_client`)兼容。这也是 `Context`" -" 和 `RecordSet` 等新抽象的首次预览。客户端可以通过 `state.RecordSet` 访问 " -"`RecordSet` 类型的状态: RecordSet = self.context.state`。对该 `RecordSet` " +"客户端 \"和 \"NumPyClient \"的子类现在可以存储保留在客户端上的本地状态。让我们先从亮点开始:这一新功能与模拟客户端(通过 " +"`start_simulation`)和网络客户端(通过 `start_client`)兼容。这也是 `Context` 和 " +"`RecordSet` 等新抽象的首次预览。客户端可以通过 `state.RecordSet` 访问 `RecordSet` 类型的状态: " +"RecordSet = self.context.state`。对该 `RecordSet` " "的更改会在不同轮执行中保留,以便在模拟和部署中以统一的方式进行有状态计算。" -#: ../../source/ref-changelog.md:89 +#: ../../source/ref-changelog.md:181 #, fuzzy msgid "" "**Improve performance** " "([#2293](https://github.com/adap/flower/pull/2293))" msgstr "**改进示例笔记** ([#2005](https://github.com/adap/flower/pull/2005))" -#: ../../source/ref-changelog.md:91 +#: ../../source/ref-changelog.md:183 #, fuzzy msgid "" "Flower is faster than ever. All `FedAvg`-derived strategies now use in-" @@ -17186,11 +17925,10 @@ msgid "" "which results in significant speedups, especially when the client-side " "training time is short." msgstr "" -"Flower 的速度比以往更快。所有源于 `FedAvg` " -"的策略现在都使用就地聚合,以减少内存消耗。Flower 客户端序列化/解序列化已从头" -"开始重写,从而显著提高了速度,尤其是在客户端训练时间较短的情况下。" +"Flower 的速度比以往更快。所有源于 `FedAvg` 的策略现在都使用就地聚合,以减少内存消耗。Flower " +"客户端序列化/解序列化已从头开始重写,从而显著提高了速度,尤其是在客户端训练时间较短的情况下。" -#: ../../source/ref-changelog.md:93 +#: ../../source/ref-changelog.md:185 #, fuzzy msgid "" "**Support Federated Learning with Apple MLX and Flower** " @@ -17199,17 +17937,17 @@ msgstr "" "** 添加使用 fastai 和 Flower 进行联邦学习的新示例** " "([#1598](https://github.com/adap/flower/pull/1598))" -#: ../../source/ref-changelog.md:95 +#: ../../source/ref-changelog.md:187 #, fuzzy msgid "" "Flower has official support for federated learning using [Apple " "MLX](https://ml-explore.github.io/mlx) via the new `quickstart-mlx` code " "example." msgstr "" -"通过新的 `quickstart-mlx` 代码示例,Flower 正式支持使用 [Apple MLX](https" -"://ml-explore.github.io/mlx)的联合学习。" +"通过新的 `quickstart-mlx` 代码示例,Flower 正式支持使用 [Apple MLX](https://ml-" +"explore.github.io/mlx)的联合学习。" -#: ../../source/ref-changelog.md:97 +#: ../../source/ref-changelog.md:189 #, fuzzy msgid "" "**Introduce new XGBoost cyclic strategy** " @@ -17219,7 +17957,7 @@ msgstr "" "**介绍 iOS SDK(预览版)** ([#1621](https://github.com/adap/flower/pull/1621), " "[#1764](https://github.com/adap/flower/pull/1764))" -#: ../../source/ref-changelog.md:99 +#: ../../source/ref-changelog.md:191 #, fuzzy msgid "" "A new strategy called `FedXgbCyclic` supports a client-by-client style of" @@ -17228,27 +17966,25 @@ msgid "" "comprehensive` now also supports simulation mode. With this, Flower " "offers best-in-class XGBoost support." msgstr "" -"名为 `FedXgbCyclic` 的新策略支持逐个客户端的训练风格(通常称为循环" -")。xgboost-comprehensive \"代码示例展示了如何在一个完整的项目中使用它" -"。除此之外,`xgboost-comprehensive` 现在还支持模拟模式。由此,Flower " +"名为 `FedXgbCyclic` 的新策略支持逐个客户端的训练风格(通常称为循环)。xgboost-comprehensive " +"\"代码示例展示了如何在一个完整的项目中使用它。除此之外,`xgboost-comprehensive` 现在还支持模拟模式。由此,Flower " "提供了同类最佳的 XGBoost 支持。" -#: ../../source/ref-changelog.md:101 +#: ../../source/ref-changelog.md:193 #, fuzzy msgid "" "**Support Python 3.11** " "([#2394](https://github.com/adap/flower/pull/2394))" msgstr "** 支持 Python 3.10** ([#1320](https://github.com/adap/flower/pull/1320))" -#: ../../source/ref-changelog.md:103 +#: ../../source/ref-changelog.md:195 #, fuzzy msgid "" "Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will " "ensure better support for users using more recent Python versions." -msgstr "框架测试现在可在 Python 3.8、3.9、3.10 和 3.11 上运行。这将确保为使用最新 " -"Python 版本的用户提供更好的支持。" +msgstr "框架测试现在可在 Python 3.8、3.9、3.10 和 3.11 上运行。这将确保为使用最新 Python 版本的用户提供更好的支持。" -#: ../../source/ref-changelog.md:105 +#: ../../source/ref-changelog.md:197 #, fuzzy msgid "" "**Update gRPC and ProtoBuf dependencies** " @@ -17257,14 +17993,14 @@ msgstr "" "**更新 REST API 以支持创建和删除节点** " "([#2283](https://github.com/adap/flower/pull/2283))" -#: ../../source/ref-changelog.md:107 +#: ../../source/ref-changelog.md:199 #, fuzzy msgid "" "The `grpcio` and `protobuf` dependencies were updated to their latest " "versions for improved security and performance." msgstr "为提高安全性和性能,\"grpcio \"和 \"protobuf \"依赖项已更新至最新版本。" -#: ../../source/ref-changelog.md:109 +#: ../../source/ref-changelog.md:201 #, fuzzy msgid "" "**Introduce Docker image for Flower server** " @@ -17285,7 +18021,7 @@ msgstr "" "[#993](https://github.com/adap/flower/pull/993), " "[#994](https://github.com/adap/flower/pull/994))" -#: ../../source/ref-changelog.md:111 +#: ../../source/ref-changelog.md:203 #, fuzzy msgid "" "The Flower server can now be run using an official Docker image. A new " @@ -17293,11 +18029,11 @@ msgid "" "Docker](https://flower.ai/docs/framework/how-to-run-flower-using-" "docker.html). An official Flower client Docker image will follow." msgstr "" -"现在可以使用官方 Docker 映像运行 Flower 服务器了。新的操作指南介绍了 [" -"如何使用 Docker 运行 Flower](https://flower.ai/docs/framework/how-to-run-" -"flower-using-docker.html)。Flower 客户端 Docker 官方镜像将随后发布。" +"现在可以使用官方 Docker 映像运行 Flower 服务器了。新的操作指南介绍了 [如何使用 Docker 运行 " +"Flower](https://flower.ai/docs/framework/how-to-run-flower-using-" +"docker.html)。Flower 客户端 Docker 官方镜像将随后发布。" -#: ../../source/ref-changelog.md:113 +#: ../../source/ref-changelog.md:205 #, fuzzy msgid "" "**Introduce** `flower-via-docker-compose` **example** " @@ -17306,21 +18042,21 @@ msgstr "" "**介绍Flower Android SDK** " "([#2131](https://github.com/adap/flower/pull/2131))" -#: ../../source/ref-changelog.md:115 +#: ../../source/ref-changelog.md:207 #, fuzzy msgid "" "**Introduce** `quickstart-sklearn-tabular` **example** " "([#2719](https://github.com/adap/flower/pull/2719))" msgstr "**引入 start_driver**([#1697](https://github.com/adap/flower/pull/1697))" -#: ../../source/ref-changelog.md:117 +#: ../../source/ref-changelog.md:209 #, fuzzy msgid "" "**Introduce** `custom-metrics` **example** " "([#1958](https://github.com/adap/flower/pull/1958))" msgstr "**引入 start_driver**([#1697](https://github.com/adap/flower/pull/1697))" -#: ../../source/ref-changelog.md:119 +#: ../../source/ref-changelog.md:211 #, fuzzy msgid "" "**Update code examples to use Flower Datasets** " @@ -17334,15 +18070,14 @@ msgstr "" "[#1301](https://github.com/adap/flower/pull/1301), " "[#1310](https://github.com/adap/flower/pull/1310)" -#: ../../source/ref-changelog.md:121 +#: ../../source/ref-changelog.md:213 #, fuzzy msgid "" "Several code examples were updated to use [Flower " "Datasets](https://flower.ai/docs/datasets/)." -msgstr "更新了多个代码示例,以使用 [Flower Datasets](https://flower.ai/docs/datasets/" -") 。" +msgstr "更新了多个代码示例,以使用 [Flower Datasets](https://flower.ai/docs/datasets/) 。" -#: ../../source/ref-changelog.md:123 +#: ../../source/ref-changelog.md:215 #, fuzzy msgid "" "**General updates to Flower Examples** " @@ -17367,16 +18102,16 @@ msgstr "" "[#1662](https://github.com/adap/flower/pull/1662), " "[#1794](https://github.com/adap/flower/pull/1794))" -#: ../../source/ref-changelog.md:125 +#: ../../source/ref-changelog.md:217 #, fuzzy msgid "Many Flower code examples received substantial updates." msgstr "许多 \"Flower \"代码示例得到了大幅更新。" -#: ../../source/ref-changelog.md:127 ../../source/ref-changelog.md:220 +#: ../../source/ref-changelog.md:219 ../../source/ref-changelog.md:312 msgid "**Update Flower Baselines**" msgstr "**更新 Flower Baselines**" -#: ../../source/ref-changelog.md:129 +#: ../../source/ref-changelog.md:221 #, fuzzy msgid "" "HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), " @@ -17385,32 +18120,32 @@ msgstr "" "FedBN ([#2608](https://github.com/adap/flower/pull/2608), " "[#2615](https://github.com/adap/flower/pull/2615))" -#: ../../source/ref-changelog.md:130 +#: ../../source/ref-changelog.md:222 #, fuzzy msgid "FedVSSL ([#2412](https://github.com/adap/flower/pull/2412))" msgstr "FjORD [#2431](https://github.com/adap/flower/pull/2431)" -#: ../../source/ref-changelog.md:131 +#: ../../source/ref-changelog.md:223 #, fuzzy msgid "FedNova ([#2179](https://github.com/adap/flower/pull/2179))" msgstr "FjORD [#2431](https://github.com/adap/flower/pull/2431)" -#: ../../source/ref-changelog.md:132 +#: ../../source/ref-changelog.md:224 #, fuzzy msgid "HeteroFL ([#2439](https://github.com/adap/flower/pull/2439))" msgstr "FedMeta [#2438](https://github.com/adap/flower/pull/2438)" -#: ../../source/ref-changelog.md:133 +#: ../../source/ref-changelog.md:225 #, fuzzy msgid "FedAvgM ([#2246](https://github.com/adap/flower/pull/2246))" msgstr "FedPer [#2266](https://github.com/adap/flower/pull/2266)" -#: ../../source/ref-changelog.md:134 +#: ../../source/ref-changelog.md:226 #, fuzzy msgid "FedPara ([#2722](https://github.com/adap/flower/pull/2722))" msgstr "FedPer [#2266](https://github.com/adap/flower/pull/2266)" -#: ../../source/ref-changelog.md:136 +#: ../../source/ref-changelog.md:228 #, fuzzy msgid "" "**Improve documentation** " @@ -17429,7 +18164,7 @@ msgstr "" "[#1613](https://github.com/adap/flower/pull/1613), " "[#1614](https://github.com/adap/flower/pull/1614)))" -#: ../../source/ref-changelog.md:138 +#: ../../source/ref-changelog.md:230 #, fuzzy msgid "" "**Improved testing and development infrastructure** " @@ -17463,36 +18198,44 @@ msgid "" "[#2661](https://github.com/adap/flower/pull/2661), " "[#2398](https://github.com/adap/flower/pull/2398))" msgstr "" -"**改进测试和开发基础设施** ([#2797](https://github.com/adap/flower/pull/2797)" -", [#2676](https://github.com/adap/flower/pull/2676), [#2644](https://github." -"com/adap/flower/pull/2644), [#2656](https://github.com/adap/flower/pull/2656)" -", [#2848](https://github.com/adap/flower/pull/2848), [#2675](https://github." -"com/adap/flower/pull/2675), [#2735](https://github.com/adap/flower/pull/2735)" -", [#2767](https://github.com/adap/flower/pull/2767), [#2732](https://github." -"com/adap/flower/pull/2732), [#2744](https://github.com/adap/flower/pull/2744)" -", [#2681](https://github.com/adap/flower/pull/2681), [#2699](https://github." -"com/adap/flower/pull/2699), [#2745](https://github.com/adap/flower/pull/2745)" -", [#2734](https://github.com/adap/flower/pull/2734), [#2731](https://github." -"com/adap/flower/pull/2731), [#2652](https://github.com/adap/flower/pull/2652)" -", [#2720](https://github.com/adap/flower/pull/2720), [#2721](https://github." -"com/adap/flower/pull/2721), [#2717](https://github.com/adap/flower/pull/2717)" -", [#2864](https://github.com/adap/flower/pull/2864), [#2694](https://github." -"com/adap/flower/pull/2694), [#2709](https://github.com/adap/flower/pull/2709)" -", [#2658](https://github.com/adap/flower/pull/2658), [#2796](https://github." -"com/adap/flower/pull/2796), [#2692](https://github.com/adap/flower/pull/2692)" -", [#2657](https://github.com/adap/flower/pull/2657), [#2813](https://github." -"com/adap/flower/pull/2813), [#2661](https://github.com/adap/flower/pull/2661)" -", [#2398](https://github.com/adap/flower/pull/2398))" - -#: ../../source/ref-changelog.md:140 +"**改进测试和开发基础设施** ([#2797](https://github.com/adap/flower/pull/2797), " +"[#2676](https://github.com/adap/flower/pull/2676), " +"[#2644](https://github.com/adap/flower/pull/2644), " +"[#2656](https://github.com/adap/flower/pull/2656), " +"[#2848](https://github.com/adap/flower/pull/2848), " +"[#2675](https://github.com/adap/flower/pull/2675), " +"[#2735](https://github.com/adap/flower/pull/2735), " +"[#2767](https://github.com/adap/flower/pull/2767), " +"[#2732](https://github.com/adap/flower/pull/2732), " +"[#2744](https://github.com/adap/flower/pull/2744), " +"[#2681](https://github.com/adap/flower/pull/2681), " +"[#2699](https://github.com/adap/flower/pull/2699), " +"[#2745](https://github.com/adap/flower/pull/2745), " +"[#2734](https://github.com/adap/flower/pull/2734), " +"[#2731](https://github.com/adap/flower/pull/2731), " +"[#2652](https://github.com/adap/flower/pull/2652), " +"[#2720](https://github.com/adap/flower/pull/2720), " +"[#2721](https://github.com/adap/flower/pull/2721), " +"[#2717](https://github.com/adap/flower/pull/2717), " +"[#2864](https://github.com/adap/flower/pull/2864), " +"[#2694](https://github.com/adap/flower/pull/2694), " +"[#2709](https://github.com/adap/flower/pull/2709), " +"[#2658](https://github.com/adap/flower/pull/2658), " +"[#2796](https://github.com/adap/flower/pull/2796), " +"[#2692](https://github.com/adap/flower/pull/2692), " +"[#2657](https://github.com/adap/flower/pull/2657), " +"[#2813](https://github.com/adap/flower/pull/2813), " +"[#2661](https://github.com/adap/flower/pull/2661), " +"[#2398](https://github.com/adap/flower/pull/2398))" + +#: ../../source/ref-changelog.md:232 #, fuzzy msgid "" "The Flower testing and development infrastructure has received " "substantial updates. This makes Flower 1.7 the most tested release ever." -msgstr "Flower 测试和开发基础架构已得到大幅更新。这使得 Flower 1.7 " -"成为有史以来经过最多测试的版本。" +msgstr "Flower 测试和开发基础架构已得到大幅更新。这使得 Flower 1.7 成为有史以来经过最多测试的版本。" -#: ../../source/ref-changelog.md:142 +#: ../../source/ref-changelog.md:234 #, fuzzy msgid "" "**Update dependencies** " @@ -17528,7 +18271,7 @@ msgstr "" "[#2225](https://github.com/adap/flower/pull/2225), " "[#2183](https://github.com/adap/flower/pull/2183))" -#: ../../source/ref-changelog.md:144 +#: ../../source/ref-changelog.md:236 #, fuzzy msgid "" "**General improvements** " @@ -17570,32 +18313,43 @@ msgid "" "[#2759](https://github.com/adap/flower/pull/2759))" msgstr "" "**一般改进** ([#2803](https://github.com/adap/flower/pull/2803), " -"[#2847](https://github.com/adap/flower/pull/2847), [#2877](https://github." -"com/adap/flower/pull/2877), [#2690](https://github.com/adap/flower/pull/2690)" -", [#2889](https://github.com/adap/flower/pull/2889), [#2874](https://github." -"com/adap/flower/pull/2874), [#2819](https://github.com/adap/flower/pull/2819)" -", [#2689](https://github.com/adap/flower/pull/2689), [#2457](https://github." -"com/adap/flower/pull/2457), [#2870](https://github.com/adap/flower/pull/2870)" -", [#2669](https://github.com/adap/flower/pull/2669), [#2876](https://github." -"com/adap/flower/pull/2876), [#2885](https://github.com/adap/flower/pull/2885)" -", [#2858](https://github.com/adap/flower/pull/2858), [#2867](https://github." -"com/adap/flower/pull/2867), [#2351](https://github.com/adap/flower/pull/2351)" -", [#2886](https://github.com/adap/flower/pull/2886), [#2860](https://github." -"com/adap/flower/pull/2860), [#2828](https://github.com/adap/flower/pull/2828)" -", [#2869](https://github.com/adap/flower/pull/2869), [#2875](https://github." -"com/adap/flower/pull/2875), [#2733](https://github.com/adap/flower/pull/2733)" -", [#2488](https://github.com/adap/flower/pull/2488), [#2646](https://github." -"com/adap/flower/pull/2646), [#2879](https://github.com/adap/flower/pull/2879)" -", [#2821](https://github.com/adap/flower/pull/2821), [#2855](https://github." -"com/adap/flower/pull/2855), [#2800](https://github.com/adap/flower/pull/2800)" -", [#2807](https://github.com/adap/flower/pull/2807), [#2801](https://github." -"com/adap/flower/pull/2801), [#2804](https://github.com/adap/flower/pull/2804)" -", [#2851](https://github.com/adap/flower/pull/2851), [#2787](https://github." -"com/adap/flower/pull/2787), [#2852](https://github.com/adap/flower/pull/2852)" -", [#2672](https://github.com/adap/flower/pull/2672), [#2759](https://github." -"com/adap/flower/pull/2759))" - -#: ../../source/ref-changelog.md:148 +"[#2847](https://github.com/adap/flower/pull/2847), " +"[#2877](https://github.com/adap/flower/pull/2877), " +"[#2690](https://github.com/adap/flower/pull/2690), " +"[#2889](https://github.com/adap/flower/pull/2889), " +"[#2874](https://github.com/adap/flower/pull/2874), " +"[#2819](https://github.com/adap/flower/pull/2819), " +"[#2689](https://github.com/adap/flower/pull/2689), " +"[#2457](https://github.com/adap/flower/pull/2457), " +"[#2870](https://github.com/adap/flower/pull/2870), " +"[#2669](https://github.com/adap/flower/pull/2669), " +"[#2876](https://github.com/adap/flower/pull/2876), " +"[#2885](https://github.com/adap/flower/pull/2885), " +"[#2858](https://github.com/adap/flower/pull/2858), " +"[#2867](https://github.com/adap/flower/pull/2867), " +"[#2351](https://github.com/adap/flower/pull/2351), " +"[#2886](https://github.com/adap/flower/pull/2886), " +"[#2860](https://github.com/adap/flower/pull/2860), " +"[#2828](https://github.com/adap/flower/pull/2828), " +"[#2869](https://github.com/adap/flower/pull/2869), " +"[#2875](https://github.com/adap/flower/pull/2875), " +"[#2733](https://github.com/adap/flower/pull/2733), " +"[#2488](https://github.com/adap/flower/pull/2488), " +"[#2646](https://github.com/adap/flower/pull/2646), " +"[#2879](https://github.com/adap/flower/pull/2879), " +"[#2821](https://github.com/adap/flower/pull/2821), " +"[#2855](https://github.com/adap/flower/pull/2855), " +"[#2800](https://github.com/adap/flower/pull/2800), " +"[#2807](https://github.com/adap/flower/pull/2807), " +"[#2801](https://github.com/adap/flower/pull/2801), " +"[#2804](https://github.com/adap/flower/pull/2804), " +"[#2851](https://github.com/adap/flower/pull/2851), " +"[#2787](https://github.com/adap/flower/pull/2787), " +"[#2852](https://github.com/adap/flower/pull/2852), " +"[#2672](https://github.com/adap/flower/pull/2672), " +"[#2759](https://github.com/adap/flower/pull/2759))" + +#: ../../source/ref-changelog.md:240 #, fuzzy msgid "" "**Deprecate** `start_numpy_client` " @@ -17605,7 +18359,7 @@ msgstr "" "TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " "[#2508](https://github.com/adap/flower/pull/2508))" -#: ../../source/ref-changelog.md:150 +#: ../../source/ref-changelog.md:242 #, fuzzy msgid "" "Until now, clients of type `NumPyClient` needed to be started via " @@ -17616,29 +18370,27 @@ msgid "" "object to `start_client`. The examples and the documentation have been " "updated accordingly." msgstr "" -"到目前为止,\"NumPyClient \"类型的客户端需要通过 \"start_numpy_client \"启动" -"。为了整合框架 API,我们引入了一些变化,现在所有客户端类型都应通过 " -"`start_client` 启动。要继续使用 `NumPyClient` 客户端,只需首先调用 `." -"to_client()` 方法,然后将返回的 `Client` 对象传递给 " +"到目前为止,\"NumPyClient \"类型的客户端需要通过 \"start_numpy_client \"启动。为了整合框架 " +"API,我们引入了一些变化,现在所有客户端类型都应通过 `start_client` 启动。要继续使用 `NumPyClient` " +"客户端,只需首先调用 `.to_client()` 方法,然后将返回的 `Client` 对象传递给 " "`start_client`。示例和文档已相应更新。" -#: ../../source/ref-changelog.md:152 +#: ../../source/ref-changelog.md:244 #, fuzzy msgid "" "**Deprecate legacy DP wrappers** " "([#2749](https://github.com/adap/flower/pull/2749))" msgstr "**移除过时的 KerasClient**([#857](https://github.com/adap/flower/pull/857))" -#: ../../source/ref-changelog.md:154 +#: ../../source/ref-changelog.md:246 #, fuzzy msgid "" "Legacy DP wrapper classes are deprecated, but still functional. This is " "in preparation for an all-new pluggable version of differential privacy " "support in Flower." -msgstr "传统的 DP 封装类已废弃,但仍可正常使用。这是为 Flower " -"中的全新可插拔差分隐私支持版本做准备。" +msgstr "传统的 DP 封装类已废弃,但仍可正常使用。这是为 Flower 中的全新可插拔差分隐私支持版本做准备。" -#: ../../source/ref-changelog.md:156 +#: ../../source/ref-changelog.md:248 #, fuzzy msgid "" "**Make optional arg** `--callable` **in** `flower-client` **a required " @@ -17647,7 +18399,7 @@ msgstr "" "**从** `start_client` 中移除** `rest` **实验参数 " "([#2324](https://github.com/adap/flower/pull/2324))" -#: ../../source/ref-changelog.md:158 +#: ../../source/ref-changelog.md:250 #, fuzzy msgid "" "**Rename** `certificates` **to** `root_certificates` **in** `Driver` " @@ -17656,7 +18408,7 @@ msgstr "" "**重新命名** `rnd` ** to** `server_round` " "([#1321](https://github.com/adap/flower/pull/1321))" -#: ../../source/ref-changelog.md:160 +#: ../../source/ref-changelog.md:252 #, fuzzy msgid "" "**Drop experimental** `Task` **fields** " @@ -17666,7 +18418,7 @@ msgstr "" "FedBN ([#2608](https://github.com/adap/flower/pull/2608), " "[#2615](https://github.com/adap/flower/pull/2615))" -#: ../../source/ref-changelog.md:162 +#: ../../source/ref-changelog.md:254 #, fuzzy msgid "" "Experimental fields `sa`, `legacy_server_message` and " @@ -17676,29 +18428,29 @@ msgstr "" "从 `Task` 消息中删除了试验性字段 `sa`、 `legacy_server_message` 和 " "`legacy_client_message`。删除的字段已被新的 `RecordSet` 抽象所取代。" -#: ../../source/ref-changelog.md:164 +#: ../../source/ref-changelog.md:256 #, fuzzy msgid "" "**Retire MXNet examples** " "([#2724](https://github.com/adap/flower/pull/2724))" msgstr "**新的 scikit-learn 代码示例** ([#748](https://github.com/adap/flower/pull/748))" -#: ../../source/ref-changelog.md:166 +#: ../../source/ref-changelog.md:258 #, fuzzy msgid "" "The development of the MXNet fremework has ended and the project is now " "[archived on GitHub](https://github.com/apache/mxnet). Existing MXNet " "examples won't receive updates." msgstr "" -"MXNet fremework 的开发工作已经结束,该项目现已[归档于 GitHub](https://github." -"com/apache/mxnet)。现有的 MXNet 示例不会收到更新。" +"MXNet fremework 的开发工作已经结束,该项目现已[归档于 " +"GitHub](https://github.com/apache/mxnet)。现有的 MXNet 示例不会收到更新。" -#: ../../source/ref-changelog.md:168 +#: ../../source/ref-changelog.md:260 #, fuzzy msgid "v1.6.0 (2023-11-28)" msgstr "v1.4.0 (2023-04-21)" -#: ../../source/ref-changelog.md:174 +#: ../../source/ref-changelog.md:266 #, fuzzy msgid "" "`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " @@ -17707,15 +18459,15 @@ msgid "" " `Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, " "`Steve Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, " "`cnxdeveloper`, `k3nfalt` " -msgstr "" -"`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, `" -"Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel Mota`" -", `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`, `Navin " -"Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, `Steve " -"Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, `cnxdeveloper`, " -"`k3nfalt` " +msgstr "" +"`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " +"`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " +"Mota`, `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`," +" `Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, " +"`Steve Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, " +"`cnxdeveloper`, `k3nfalt` " -#: ../../source/ref-changelog.md:178 +#: ../../source/ref-changelog.md:270 msgid "" "**Add experimental support for Python 3.12** " "([#2565](https://github.com/adap/flower/pull/2565))" @@ -17723,7 +18475,7 @@ msgstr "" "** 增加对 Python 3.12 的实验支持** " "([#2565](https://github.com/adap/flower/pull/2565))" -#: ../../source/ref-changelog.md:180 +#: ../../source/ref-changelog.md:272 #, fuzzy msgid "" "**Add new XGBoost examples** " @@ -17742,23 +18494,23 @@ msgstr "" "[#1551](https://github.com/adap/flower/pull/1551), " "[#1567](https://github.com/adap/flower/pull/1567))" -#: ../../source/ref-changelog.md:182 +#: ../../source/ref-changelog.md:274 #, fuzzy msgid "" "We have added a new `xgboost-quickstart` example alongside a new " "`xgboost-comprehensive` example that goes more in-depth." msgstr "" -"我们添加了一个新的 \"xgboost-quickstart \"示例和一个新的 \"xgboost-" -"comprehensive \"示例,后者更加深入。" +"我们添加了一个新的 \"xgboost-quickstart \"示例和一个新的 \"xgboost-comprehensive " +"\"示例,后者更加深入。" -#: ../../source/ref-changelog.md:184 +#: ../../source/ref-changelog.md:276 #, fuzzy msgid "" "**Add Vertical FL example** " "([#2598](https://github.com/adap/flower/pull/2598))" msgstr "**新的 iOS CoreML 代码示例**([#1289](https://github.com/adap/flower/pull/1289))" -#: ../../source/ref-changelog.md:186 +#: ../../source/ref-changelog.md:278 #, fuzzy msgid "" "We had many questions about Vertical Federated Learning using Flower, so " @@ -17766,17 +18518,17 @@ msgid "" "dataset](https://www.kaggle.com/competitions/titanic/data) alongside a " "tutorial (in the README)." msgstr "" -"我们收到了许多关于使用 Flower 进行垂直联合学习的问题,因此我们决定在 [" -"Titanic 数据集](https://www.kaggle.com/competitions/titanic/data) " -"上添加一个简单的示例,并附上教程(在 README 中)。" +"我们收到了许多关于使用 Flower 进行垂直联合学习的问题,因此我们决定在 [Titanic " +"数据集](https://www.kaggle.com/competitions/titanic/data) 上添加一个简单的示例,并附上教程(在" +" README 中)。" -#: ../../source/ref-changelog.md:188 +#: ../../source/ref-changelog.md:280 msgid "" "**Support custom** `ClientManager` **in** `start_driver()` " "([#2292](https://github.com/adap/flower/pull/2292))" msgstr "**在***`start_driver()`中支持自定义***`ClientManager([#2292](https://github.com/adap/flower/pull/2292))" -#: ../../source/ref-changelog.md:190 +#: ../../source/ref-changelog.md:282 msgid "" "**Update REST API to support create and delete nodes** " "([#2283](https://github.com/adap/flower/pull/2283))" @@ -17784,7 +18536,7 @@ msgstr "" "**更新 REST API 以支持创建和删除节点** " "([#2283](https://github.com/adap/flower/pull/2283))" -#: ../../source/ref-changelog.md:192 +#: ../../source/ref-changelog.md:284 #, fuzzy msgid "" "**Update the Android SDK** " @@ -17793,12 +18545,12 @@ msgstr "" "**介绍Flower Android SDK** " "([#2131](https://github.com/adap/flower/pull/2131))" -#: ../../source/ref-changelog.md:194 +#: ../../source/ref-changelog.md:286 #, fuzzy msgid "Add gRPC request-response capability to the Android SDK." msgstr "为 C++ SDK 添加 gRPC 请求-响应功能。" -#: ../../source/ref-changelog.md:196 +#: ../../source/ref-changelog.md:288 #, fuzzy msgid "" "**Update the C++ SDK** " @@ -17812,11 +18564,11 @@ msgstr "" "[#2523](https://github.com/adap/flower/pull/2523), " "[#2522](https://github.com/adap/flower/pull/2522))" -#: ../../source/ref-changelog.md:198 +#: ../../source/ref-changelog.md:290 msgid "Add gRPC request-response capability to the C++ SDK." msgstr "为 C++ SDK 添加 gRPC 请求-响应功能。" -#: ../../source/ref-changelog.md:200 +#: ../../source/ref-changelog.md:292 #, fuzzy msgid "" "**Make HTTPS the new default** " @@ -17826,7 +18578,7 @@ msgstr "" "Baselines文档([#2290](https://github.com/adap/flower/pull/2290), " "[#2400](https://github.com/adap/flower/pull/2400)" -#: ../../source/ref-changelog.md:202 +#: ../../source/ref-changelog.md:294 #, fuzzy msgid "" "Flower is moving to HTTPS by default. The new `flower-server` requires " @@ -17836,12 +18588,11 @@ msgid "" "an HTTPS-enabled server or requires opt-out via passing `--insecure` to " "enable insecure HTTP connections." msgstr "" -"Flower 默认使用 HTTPS。新的 \"flower-server \"需要通过\"--证书\"," -"但用户可以启用\"--不安全 \"来使用 HTTP 进行原型开发。这同样适用于 `flower-" -"client`,它可以使用用户提供的凭证或 gRPC 绑定证书连接到支持 HTTPS 的服务器," -"也可以通过传递 `--insecure`来启用不安全的 HTTP 连接。" +"Flower 默认使用 HTTPS。新的 \"flower-server \"需要通过\"--证书\",但用户可以启用\"--不安全 \"来使用 " +"HTTP 进行原型开发。这同样适用于 `flower-client`,它可以使用用户提供的凭证或 gRPC 绑定证书连接到支持 HTTPS " +"的服务器,也可以通过传递 `--insecure`来启用不安全的 HTTP 连接。" -#: ../../source/ref-changelog.md:204 +#: ../../source/ref-changelog.md:296 #, fuzzy msgid "" "For backward compatibility, `start_client()` and `start_numpy_client()` " @@ -17849,10 +18600,9 @@ msgid "" "insecure connections will require user opt-in by passing `insecure=True`." msgstr "" "为了向后兼容,`start_client()` 和 `start_numpy_client()` " -"默认仍以不安全模式启动。在未来的版本中,不安全连接将需要用户通过传递 " -"`insecure=True` 进行选择。" +"默认仍以不安全模式启动。在未来的版本中,不安全连接将需要用户通过传递 `insecure=True` 进行选择。" -#: ../../source/ref-changelog.md:206 +#: ../../source/ref-changelog.md:298 msgid "" "**Unify client API** ([#2303](https://github.com/adap/flower/pull/2303), " "[#2390](https://github.com/adap/flower/pull/2390), " @@ -17862,7 +18612,7 @@ msgstr "" "[#2390](https://github.com/adap/flower/pull/2390), " "[#2493](https://github.com/adap/flower/pull/2493))" -#: ../../source/ref-changelog.md:208 +#: ../../source/ref-changelog.md:300 #, fuzzy msgid "" "Using the `client_fn`, Flower clients can interchangeably run as " @@ -17874,7 +18624,7 @@ msgstr "" "使用 `client_fn`,Flower 客户端可以作为独立进程(即通过 `start_client`)或在模拟中(即通过 " "`start_simulation`)交替运行,而无需更改客户端类的定义和实例化方式。调用 `start_numpy_client` 现已过时。" -#: ../../source/ref-changelog.md:210 +#: ../../source/ref-changelog.md:302 msgid "" "**Add new** `Bulyan` **strategy** " "([#1817](https://github.com/adap/flower/pull/1817), " @@ -17884,20 +18634,20 @@ msgstr "" "\"**策略**([#1817](https://github.com/adap/flower/pull/1817), " "[#1891](https://github.com/adap/flower/pull/1891)" -#: ../../source/ref-changelog.md:212 +#: ../../source/ref-changelog.md:304 msgid "" "The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., " "2018](https://arxiv.org/abs/1802.07927)" msgstr "新的 \"Bulyan\"策略通过[El Mhamdi 等人,2018](https://arxiv.org/abs/1802.07927)实现" -#: ../../source/ref-changelog.md:214 +#: ../../source/ref-changelog.md:306 #, fuzzy msgid "" "**Add new** `XGB Bagging` **strategy** " "([#2611](https://github.com/adap/flower/pull/2611))" msgstr "**添加新的`FedProx`策略** ([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:216 ../../source/ref-changelog.md:218 +#: ../../source/ref-changelog.md:308 ../../source/ref-changelog.md:310 #, fuzzy msgid "" "**Introduce `WorkloadState`** " @@ -17907,7 +18657,7 @@ msgstr "" "**新的内置策略**([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822)" -#: ../../source/ref-changelog.md:222 +#: ../../source/ref-changelog.md:314 msgid "" "FedProx ([#2210](https://github.com/adap/flower/pull/2210), " "[#2286](https://github.com/adap/flower/pull/2286), " @@ -17917,7 +18667,7 @@ msgstr "" "[#2286](https://github.com/adap/flower/pull/2286), " "[#2509](https://github.com/adap/flower/pull/2509))" -#: ../../source/ref-changelog.md:224 +#: ../../source/ref-changelog.md:316 msgid "" "Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), " "[#2400](https://github.com/adap/flower/pull/2400))" @@ -17925,7 +18675,7 @@ msgstr "" "Baselines文档([#2290](https://github.com/adap/flower/pull/2290), " "[#2400](https://github.com/adap/flower/pull/2400)" -#: ../../source/ref-changelog.md:226 +#: ../../source/ref-changelog.md:318 msgid "" "FedMLB ([#2340](https://github.com/adap/flower/pull/2340), " "[#2507](https://github.com/adap/flower/pull/2507))" @@ -17933,7 +18683,7 @@ msgstr "" "FedMLB ([#2340](https://github.com/adap/flower/pull/2340), " "[#2507](https://github.com/adap/flower/pull/2507))" -#: ../../source/ref-changelog.md:228 +#: ../../source/ref-changelog.md:320 msgid "" "TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " "[#2508](https://github.com/adap/flower/pull/2508))" @@ -17941,35 +18691,35 @@ msgstr "" "TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " "[#2508](https://github.com/adap/flower/pull/2508))" -#: ../../source/ref-changelog.md:230 +#: ../../source/ref-changelog.md:322 msgid "FedMeta [#2438](https://github.com/adap/flower/pull/2438)" msgstr "FedMeta [#2438](https://github.com/adap/flower/pull/2438)" -#: ../../source/ref-changelog.md:232 +#: ../../source/ref-changelog.md:324 msgid "FjORD [#2431](https://github.com/adap/flower/pull/2431)" msgstr "FjORD [#2431](https://github.com/adap/flower/pull/2431)" -#: ../../source/ref-changelog.md:234 +#: ../../source/ref-changelog.md:326 msgid "MOON [#2421](https://github.com/adap/flower/pull/2421)" msgstr "MOON [#2421](https://github.com/adap/flower/pull/2421)" -#: ../../source/ref-changelog.md:236 +#: ../../source/ref-changelog.md:328 msgid "DepthFL [#2295](https://github.com/adap/flower/pull/2295)" msgstr "DepthFL [#2295](https://github.com/adap/flower/pull/2295)" -#: ../../source/ref-changelog.md:238 +#: ../../source/ref-changelog.md:330 msgid "FedPer [#2266](https://github.com/adap/flower/pull/2266)" msgstr "FedPer [#2266](https://github.com/adap/flower/pull/2266)" -#: ../../source/ref-changelog.md:240 +#: ../../source/ref-changelog.md:332 msgid "FedWav2vec [#2551](https://github.com/adap/flower/pull/2551)" msgstr "FedWav2vec [#2551](https://github.com/adap/flower/pull/2551)" -#: ../../source/ref-changelog.md:242 +#: ../../source/ref-changelog.md:334 msgid "niid-Bench [#2428](https://github.com/adap/flower/pull/2428)" msgstr "niid-Bench [#2428](https://github.com/adap/flower/pull/2428)" -#: ../../source/ref-changelog.md:244 +#: ../../source/ref-changelog.md:336 msgid "" "FedBN ([#2608](https://github.com/adap/flower/pull/2608), " "[#2615](https://github.com/adap/flower/pull/2615))" @@ -17977,7 +18727,7 @@ msgstr "" "FedBN ([#2608](https://github.com/adap/flower/pull/2608), " "[#2615](https://github.com/adap/flower/pull/2615))" -#: ../../source/ref-changelog.md:246 +#: ../../source/ref-changelog.md:338 #, fuzzy msgid "" "**General updates to Flower Examples** " @@ -17992,7 +18742,7 @@ msgstr "" "[#2523](https://github.com/adap/flower/pull/2523), " "[#2522](https://github.com/adap/flower/pull/2522))" -#: ../../source/ref-changelog.md:248 +#: ../../source/ref-changelog.md:340 #, fuzzy msgid "" "**General updates to Flower Baselines** " @@ -18020,7 +18770,7 @@ msgstr "" "[#2446](https://github.com/adap/flower/pull/2446) " "[#2561](https://github.com/adap/flower/pull/2561))" -#: ../../source/ref-changelog.md:250 +#: ../../source/ref-changelog.md:342 #, fuzzy msgid "" "**General updates to the simulation engine** " @@ -18033,7 +18783,7 @@ msgstr "" "[#2447](https://github.com/adap/flower/pull/2447), " "[#2448](https://github.com/adap/flower/pull/2448))" -#: ../../source/ref-changelog.md:252 +#: ../../source/ref-changelog.md:344 #, fuzzy msgid "" "**General updates to Flower SDKs** " @@ -18052,7 +18802,7 @@ msgstr "" "[#1474](https://github.com/adap/flower/pull/1474), " "[#1475](https://github.com/adap/flower/pull/1475)))" -#: ../../source/ref-changelog.md:254 +#: ../../source/ref-changelog.md:346 #, fuzzy msgid "" "**General improvements** " @@ -18085,32 +18835,40 @@ msgid "" "[#2596](https://github.com/adap/flower/pull/2596))" msgstr "" "**一般改进** ([#2309](https://github.com/adap/flower/pull/2309), " -"[#2310](https://github.com/adap/flower/pull/2310), [#2313](https://github." -"com/adap/flower/pull/2313), [#2316](https://github.com/adap/flower/pull/2316)" -", [#2317](https://github.com/adap/flower/pull/2317), [#2349](https://github." -"com/adap/flower/pull/2349), [#2360](https://github.com/adap/flower/pull/2360)" -", [#2402](https://github.com/adap/flower/pull/2402), [#2446](https://github." -"com/adap/flower/pull/2446), [#2561](https://github.com/adap/flower/pull/2561)" -", [#2273](https://github.com/adap/flower/pull/2273), [#2267](https://github." -"com/adap/flower/pull/2267), [#2274](https://github.com/adap/flower/pull/2274)" -", [#2275](https://github.com/adap/flower/pull/2275), [#2432](https://github." -"com/adap/flower/pull/2432), [#2251](https://github.com/adap/flower/pull/2251)" -", [#2321](https://github.com/adap/flower/pull/2321), [#1936](https://github." -"com/adap/flower/pull/1936), [#2408](https://github.com/adap/flower/pull/2408)" -", [#2413](https://github.com/adap/flower/pull/2413), [#2401](https://github." -"com/adap/flower/pull/2401), [#2531](https://github.com/adap/flower/pull/2531)" -", [#2534](https://github.com/adap/flower/pull/2534), [#2535](https://github." -"com/adap/flower/pull/2535), [#2521](https://github.com/adap/flower/pull/2521)" -", [#2553](https://github.com/adap/flower/pull/2553), [#2596](https://github." -"com/adap/flower/pull/2596))" - -#: ../../source/ref-changelog.md:256 ../../source/ref-changelog.md:346 -#: ../../source/ref-changelog.md:410 ../../source/ref-changelog.md:464 -#: ../../source/ref-changelog.md:531 +"[#2310](https://github.com/adap/flower/pull/2310), " +"[#2313](https://github.com/adap/flower/pull/2313), " +"[#2316](https://github.com/adap/flower/pull/2316), " +"[#2317](https://github.com/adap/flower/pull/2317), " +"[#2349](https://github.com/adap/flower/pull/2349), " +"[#2360](https://github.com/adap/flower/pull/2360), " +"[#2402](https://github.com/adap/flower/pull/2402), " +"[#2446](https://github.com/adap/flower/pull/2446), " +"[#2561](https://github.com/adap/flower/pull/2561), " +"[#2273](https://github.com/adap/flower/pull/2273), " +"[#2267](https://github.com/adap/flower/pull/2267), " +"[#2274](https://github.com/adap/flower/pull/2274), " +"[#2275](https://github.com/adap/flower/pull/2275), " +"[#2432](https://github.com/adap/flower/pull/2432), " +"[#2251](https://github.com/adap/flower/pull/2251), " +"[#2321](https://github.com/adap/flower/pull/2321), " +"[#1936](https://github.com/adap/flower/pull/1936), " +"[#2408](https://github.com/adap/flower/pull/2408), " +"[#2413](https://github.com/adap/flower/pull/2413), " +"[#2401](https://github.com/adap/flower/pull/2401), " +"[#2531](https://github.com/adap/flower/pull/2531), " +"[#2534](https://github.com/adap/flower/pull/2534), " +"[#2535](https://github.com/adap/flower/pull/2535), " +"[#2521](https://github.com/adap/flower/pull/2521), " +"[#2553](https://github.com/adap/flower/pull/2553), " +"[#2596](https://github.com/adap/flower/pull/2596))" + +#: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:502 ../../source/ref-changelog.md:556 +#: ../../source/ref-changelog.md:623 msgid "Flower received many improvements under the hood, too many to list here." msgstr "Flower 进行了许多改进,这里就不一一列举了。" -#: ../../source/ref-changelog.md:260 +#: ../../source/ref-changelog.md:352 msgid "" "**Remove support for Python 3.7** " "([#2280](https://github.com/adap/flower/pull/2280), " @@ -18128,13 +18886,13 @@ msgstr "" "[#2355](https://github.com/adap/flower/pull/2355), " "[#2356](https://github.com/adap/flower/pull/2356))" -#: ../../source/ref-changelog.md:262 +#: ../../source/ref-changelog.md:354 msgid "" "Python 3.7 support was deprecated in Flower 1.5, and this release removes" " support. Flower now requires Python 3.8." msgstr "在 Flower 1.5 中,Python 3.7 支持已被弃用,本版本将删除该支持。Flower 现在需要 Python 3.8。" -#: ../../source/ref-changelog.md:264 +#: ../../source/ref-changelog.md:356 msgid "" "**Remove experimental argument** `rest` **from** `start_client` " "([#2324](https://github.com/adap/flower/pull/2324))" @@ -18142,7 +18900,7 @@ msgstr "" "**从** `start_client` 中移除** `rest` **实验参数 " "([#2324](https://github.com/adap/flower/pull/2324))" -#: ../../source/ref-changelog.md:266 +#: ../../source/ref-changelog.md:358 msgid "" "The (still experimental) argument `rest` was removed from `start_client` " "and `start_numpy_client`. Use `transport=\"rest\"` to opt into the " @@ -18151,11 +18909,11 @@ msgstr "" "删除了 `start_client` 和 `start_numpy_client` 中的参数 `rest`(仍属试验性质)。请使用 " "`transport=\"rest\"` 来选择使用试验性 REST API。" -#: ../../source/ref-changelog.md:268 +#: ../../source/ref-changelog.md:360 msgid "v1.5.0 (2023-08-31)" msgstr "v1.5.0 (2023-08-31)" -#: ../../source/ref-changelog.md:274 +#: ../../source/ref-changelog.md:366 msgid "" "`Adam Narozniak`, `Anass Anhari`, `Charles Beauville`, `Dana-Farber`, " "`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " @@ -18169,7 +18927,7 @@ msgstr "" "Topal`, `achiverram28`, `danielnugraha`, `eunchung`, `ruthgal` " -#: ../../source/ref-changelog.md:278 +#: ../../source/ref-changelog.md:370 msgid "" "**Introduce new simulation engine** " "([#1969](https://github.com/adap/flower/pull/1969), " @@ -18180,7 +18938,7 @@ msgstr "" "[#2221](https://github.com/adap/flower/pull/2221), " "[#2248](https://github.com/adap/flower/pull/2248))" -#: ../../source/ref-changelog.md:280 +#: ../../source/ref-changelog.md:372 msgid "" "The new simulation engine has been rewritten from the ground up, yet it " "remains fully backwards compatible. It offers much improved stability and" @@ -18191,7 +18949,7 @@ msgstr "" "新的模拟引擎从头开始重新编写,但仍完全向后兼容。它的稳定性和内存处理能力大大提高,尤其是在使用 GPU 时。仿真可透明地适应不同的设置,以在仅 " "CPU、CPU+GPU、多 GPU 或多节点多 GPU 环境中扩展模拟。" -#: ../../source/ref-changelog.md:282 +#: ../../source/ref-changelog.md:374 msgid "" "Comprehensive documentation includes a new [how-to run " "simulations](https://flower.ai/docs/framework/how-to-run-" @@ -18208,7 +18966,7 @@ msgstr "" "tensorflow.html) notebooks, and a new [YouTube tutorial " "series](https://www.youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)。" -#: ../../source/ref-changelog.md:284 +#: ../../source/ref-changelog.md:376 msgid "" "**Restructure Flower Docs** " "([#1824](https://github.com/adap/flower/pull/1824), " @@ -18267,7 +19025,7 @@ msgstr "" "[#2243](https://github.com/adap/flower/pull/2243), " "[#2227](https://github.com/adap/flower/pull/2227))" -#: ../../source/ref-changelog.md:286 +#: ../../source/ref-changelog.md:378 #, fuzzy msgid "" "Much effort went into a completely restructured Flower docs experience. " @@ -18278,7 +19036,7 @@ msgstr "" "Flower 文档体验的全面重构耗费了大量精力。现在,[flower.ai/docs](flower.ai/docs)上的文档分为 Flower " "Framework、Flower Baselines、Flower Android SDK、Flower iOS SDK 和代码示例项目。" -#: ../../source/ref-changelog.md:288 +#: ../../source/ref-changelog.md:380 msgid "" "**Introduce Flower Swift SDK** " "([#1858](https://github.com/adap/flower/pull/1858), " @@ -18288,7 +19046,7 @@ msgstr "" "([#1858](https://github.com/adap/flower/pull/1858), " "[#1897](https://github.com/adap/flower/pull/1897))" -#: ../../source/ref-changelog.md:290 +#: ../../source/ref-changelog.md:382 msgid "" "This is the first preview release of the Flower Swift SDK. Flower support" " on iOS is improving, and alongside the Swift SDK and code example, there" @@ -18297,7 +19055,7 @@ msgstr "" "这是 Flower Swift SDK 的首个预览版。Flower 对 iOS 的支持正在不断改进,除了 Swift SDK " "和代码示例外,现在还有 iOS 快速入门教程。" -#: ../../source/ref-changelog.md:292 +#: ../../source/ref-changelog.md:384 msgid "" "**Introduce Flower Android SDK** " "([#2131](https://github.com/adap/flower/pull/2131))" @@ -18305,7 +19063,7 @@ msgstr "" "**介绍Flower Android SDK** " "([#2131](https://github.com/adap/flower/pull/2131))" -#: ../../source/ref-changelog.md:294 +#: ../../source/ref-changelog.md:386 msgid "" "This is the first preview release of the Flower Kotlin SDK. Flower " "support on Android is improving, and alongside the Kotlin SDK and code " @@ -18314,7 +19072,7 @@ msgstr "" "这是 Flower Kotlin SDK 的首个预览版。Flower 对 Android 的支持正在不断改进,除了 Kotlin SDK " "和代码示例,现在还有 Android 快速入门教程。" -#: ../../source/ref-changelog.md:296 +#: ../../source/ref-changelog.md:388 msgid "" "**Introduce new end-to-end testing infrastructure** " "([#1842](https://github.com/adap/flower/pull/1842), " @@ -18353,23 +19111,23 @@ msgstr "" "[#2137](https://github.com/adap/flower/pull/2137), " "[#2165](https://github.com/adap/flower/pull/2165))" -#: ../../source/ref-changelog.md:298 +#: ../../source/ref-changelog.md:390 msgid "" "A new testing infrastructure ensures that new changes stay compatible " "with existing framework integrations or strategies." msgstr "新的测试设施可确保新的变更与现有的框架集成或策略保持兼容。" -#: ../../source/ref-changelog.md:300 +#: ../../source/ref-changelog.md:392 msgid "**Deprecate Python 3.7**" msgstr "** 过时的 Python 3.7**" -#: ../../source/ref-changelog.md:302 +#: ../../source/ref-changelog.md:394 msgid "" "Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for" " Python 3.7 is now deprecated and will be removed in an upcoming release." msgstr "由于 Python 3.7 已于 2023-06-27 弃用 (EOL),对 Python 3.7 的支持现已废弃,并将在即将发布的版本中移除。" -#: ../../source/ref-changelog.md:304 +#: ../../source/ref-changelog.md:396 msgid "" "**Add new** `FedTrimmedAvg` **strategy** " "([#1769](https://github.com/adap/flower/pull/1769), " @@ -18378,7 +19136,7 @@ msgstr "" "**添加新的**`FedTrimmedAvg`**策略**([#1769](https://github.com/adap/flower/pull/1769)," " [#1853](https://github.com/adap/flower/pull/1853)" -#: ../../source/ref-changelog.md:306 +#: ../../source/ref-changelog.md:398 msgid "" "The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, " "2018](https://arxiv.org/abs/1803.01498)." @@ -18386,13 +19144,13 @@ msgstr "" "新的 \"FedTrimmedAvg \"策略实现了[Dong Yin, " "2018](https://arxiv.org/abs/1803.01498)的 \"Trimmed Mean\"。" -#: ../../source/ref-changelog.md:308 +#: ../../source/ref-changelog.md:400 msgid "" "**Introduce start_driver** " "([#1697](https://github.com/adap/flower/pull/1697))" msgstr "**引入 start_driver**([#1697](https://github.com/adap/flower/pull/1697))" -#: ../../source/ref-changelog.md:310 +#: ../../source/ref-changelog.md:402 msgid "" "In addition to `start_server` and using the raw Driver API, there is a " "new `start_driver` function that allows for running `start_server` " @@ -18404,7 +19162,7 @@ msgstr "" "`start_server` 脚本作为 Flower 驱动程序运行。请查看 `mt-pytorch` 代码示例,了解使用 " "`start_driver` 的工作示例。" -#: ../../source/ref-changelog.md:312 +#: ../../source/ref-changelog.md:404 msgid "" "**Add parameter aggregation to** `mt-pytorch` **code example** " "([#1785](https://github.com/adap/flower/pull/1785))" @@ -18412,7 +19170,7 @@ msgstr "" "为 `mt-pytorch` **代码示例**添加参数聚合 " "([#1785](https://github.com/adap/flower/pull/1785))" -#: ../../source/ref-changelog.md:314 +#: ../../source/ref-changelog.md:406 msgid "" "The `mt-pytorch` example shows how to aggregate parameters when writing a" " driver script. The included `driver.py` and `server.py` have been " @@ -18422,7 +19180,7 @@ msgstr "" "`mt-pytorch`示例展示了如何在编写驱动程序脚本时聚合参数。附带的 `driver.py` 和 `server.py` " "已经进行了调整,以演示构建服务器端逻辑的低级方法和高级方法。" -#: ../../source/ref-changelog.md:316 +#: ../../source/ref-changelog.md:408 msgid "" "**Migrate experimental REST API to Starlette** " "([2171](https://github.com/adap/flower/pull/2171))" @@ -18430,7 +19188,7 @@ msgstr "" "**将实验性 REST API 移植到 Starlette** " "([2171](https://github.com/adap/flower/pull/2171))" -#: ../../source/ref-changelog.md:318 +#: ../../source/ref-changelog.md:410 msgid "" "The (experimental) REST API used to be implemented in " "[FastAPI](https://fastapi.tiangolo.com/), but it has now been migrated to" @@ -18439,13 +19197,13 @@ msgstr "" "REST API(试验性)曾在 [FastAPI](https://fastapi.tiangolo.com/) 中实现,但现在已迁移到直接使用 " "[Starlette](https://www.starlette.io/) 。" -#: ../../source/ref-changelog.md:320 +#: ../../source/ref-changelog.md:412 msgid "" "Please note: The REST request-response API is still experimental and will" " likely change significantly over time." msgstr "请注意:REST 请求-响应 API 仍处于试验阶段,随着时间的推移可能会发生重大变化。" -#: ../../source/ref-changelog.md:322 +#: ../../source/ref-changelog.md:414 msgid "" "**Introduce experimental gRPC request-response API** " "([#1867](https://github.com/adap/flower/pull/1867), " @@ -18455,7 +19213,7 @@ msgstr "" "([#1867](https://github.com/adap/flower/pull/1867), " "[#1901](https://github.com/adap/flower/pull/1901)" -#: ../../source/ref-changelog.md:324 +#: ../../source/ref-changelog.md:416 msgid "" "In addition to the existing gRPC API (based on bidirectional streaming) " "and the experimental REST API, there is now a new gRPC API that uses a " @@ -18464,13 +19222,13 @@ msgstr "" "除了现有的 gRPC 应用程序接口(基于双向流)和试验性 REST 应用程序接口外,现在还有一个新的 gRPC " "应用程序接口,它使用请求-响应模型与客户端节点通信。" -#: ../../source/ref-changelog.md:326 +#: ../../source/ref-changelog.md:418 msgid "" "Please note: The gRPC request-response API is still experimental and will" " likely change significantly over time." msgstr "请注意:gRPC 请求-响应 API 仍处于试验阶段,随着时间的推移可能会发生重大变化。" -#: ../../source/ref-changelog.md:328 +#: ../../source/ref-changelog.md:420 msgid "" "**Replace the experimental** `start_client(rest=True)` **with the new** " "`start_client(transport=\"rest\")` " @@ -18480,7 +19238,7 @@ msgstr "" "`start_client(rest=True)` " "([#1880](https://github.com/adap/flower/pull/1880))" -#: ../../source/ref-changelog.md:330 +#: ../../source/ref-changelog.md:422 msgid "" "The (experimental) `start_client` argument `rest` was deprecated in " "favour of a new argument `transport`. `start_client(transport=\"rest\")` " @@ -18491,13 +19249,13 @@ msgstr "" "已废弃(试验性的)`start_client`参数`rest`,改用新参数`transport`。`start_client(transport=\"rest\")`将产生与以前的`start_client(rest=True)`相同的行为。所有代码都应迁移到新参数" " `transport`。过时的参数 `rest` 将在今后的版本中删除。" -#: ../../source/ref-changelog.md:332 +#: ../../source/ref-changelog.md:424 msgid "" "**Add a new gRPC option** " "([#2197](https://github.com/adap/flower/pull/2197))" msgstr "** 添加一个新的 gRPC 选项**([#2197](https://github.com/adap/flower/pull/2197))" -#: ../../source/ref-changelog.md:334 +#: ../../source/ref-changelog.md:426 msgid "" "We now start a gRPC server with the `grpc.keepalive_permit_without_calls`" " option set to 0 by default. This prevents the clients from sending " @@ -18506,17 +19264,17 @@ msgstr "" "现在我们启动一个 gRPC 服务器,并将 `grpc.keepalive_permit_without_calls` 选项默认设置为 " "0。这将防止客户端在没有未处理数据流时发送 keepalive pings。" -#: ../../source/ref-changelog.md:336 +#: ../../source/ref-changelog.md:428 msgid "" "**Improve example notebooks** " "([#2005](https://github.com/adap/flower/pull/2005))" msgstr "**改进示例笔记** ([#2005](https://github.com/adap/flower/pull/2005))" -#: ../../source/ref-changelog.md:338 +#: ../../source/ref-changelog.md:430 msgid "There's a new 30min Federated Learning PyTorch tutorial!" msgstr "有一个新的 30 分钟的联邦学习 PyTorch 教程!" -#: ../../source/ref-changelog.md:340 +#: ../../source/ref-changelog.md:432 msgid "" "**Example updates** ([#1772](https://github.com/adap/flower/pull/1772), " "[#1873](https://github.com/adap/flower/pull/1873), " @@ -18542,7 +19300,7 @@ msgstr "" "[#2225](https://github.com/adap/flower/pull/2225), " "[#2183](https://github.com/adap/flower/pull/2183))" -#: ../../source/ref-changelog.md:342 +#: ../../source/ref-changelog.md:434 msgid "" "Many examples have received significant updates, including simplified " "advanced-tensorflow and advanced-pytorch examples, improved macOS " @@ -18554,7 +19312,7 @@ msgstr "" "TensorFlow 示例的 macOS 兼容性,以及模拟代码示例。一项重大升级是所有代码示例现在都有了 " "\"requirements.txt\"(除 \"pyproject.toml \"外)。" -#: ../../source/ref-changelog.md:344 +#: ../../source/ref-changelog.md:436 msgid "" "**General improvements** " "([#1872](https://github.com/adap/flower/pull/1872), " @@ -18571,11 +19329,11 @@ msgstr "" "[#1477](https://github.com/adap/flower/pull/1477), " "[#2171](https://github.com/adap/flower/pull/2171))" -#: ../../source/ref-changelog.md:352 +#: ../../source/ref-changelog.md:444 msgid "v1.4.0 (2023-04-21)" msgstr "v1.4.0 (2023-04-21)" -#: ../../source/ref-changelog.md:358 +#: ../../source/ref-changelog.md:450 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, " @@ -18591,7 +19349,7 @@ msgstr "" "Lane`, `Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, " "`Steve Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" -#: ../../source/ref-changelog.md:362 +#: ../../source/ref-changelog.md:454 msgid "" "**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and " "example)** ([#1694](https://github.com/adap/flower/pull/1694), " @@ -18609,7 +19367,7 @@ msgstr "" "[#1763](https://github.com/adap/flower/pull/1763), " "[#1795](https://github.com/adap/flower/pull/1795))" -#: ../../source/ref-changelog.md:364 +#: ../../source/ref-changelog.md:456 msgid "" "XGBoost is a tree-based ensemble machine learning algorithm that uses " "gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg`" @@ -18623,7 +19381,7 @@ msgstr "" "\"FedXgbNnAvg\"[策略](https://github.com/adap/flower/tree/main/src/py/flwr/server/strategy/fedxgb_nn_avg.py)和一个[代码示例](https://github.com/adap/flower/tree/main/examples" "/xgboost-quickstart),演示如何在 XGBoost 项目中使用这个新策略。" -#: ../../source/ref-changelog.md:366 +#: ../../source/ref-changelog.md:458 msgid "" "**Introduce iOS SDK (preview)** " "([#1621](https://github.com/adap/flower/pull/1621), " @@ -18632,7 +19390,7 @@ msgstr "" "**介绍 iOS SDK(预览版)** ([#1621](https://github.com/adap/flower/pull/1621), " "[#1764](https://github.com/adap/flower/pull/1764))" -#: ../../source/ref-changelog.md:368 +#: ../../source/ref-changelog.md:460 msgid "" "This is a major update for anyone wanting to implement Federated Learning" " on iOS mobile devices. We now have a swift iOS SDK present under " @@ -18647,7 +19405,7 @@ msgstr "" " 下提供了一个迅捷的 iOS SDK,这将大大方便应用程序的创建过程。为了展示其使用情况,我们还更新了 [iOS " "示例](https://github.com/adap/flower/tree/main/examples/ios)!" -#: ../../source/ref-changelog.md:370 +#: ../../source/ref-changelog.md:462 msgid "" "**Introduce new \"What is Federated Learning?\" tutorial** " "([#1657](https://github.com/adap/flower/pull/1657), " @@ -18657,7 +19415,7 @@ msgstr "" "\"什么是联邦学习?\"教程**([#1657](https://github.com/adap/flower/pull/1657), " "[#1721](https://github.com/adap/flower/pull/1721)" -#: ../../source/ref-changelog.md:372 +#: ../../source/ref-changelog.md:464 msgid "" "A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-" "what-is-federated-learning.html) in our documentation explains the basics" @@ -18669,7 +19427,7 @@ msgstr "" "federated-learning.html),解释了联邦学习的基础知识。它让任何不熟悉联邦学习的人都能开始 Flower " "之旅。请转发给对联邦学习感兴趣的人!" -#: ../../source/ref-changelog.md:374 +#: ../../source/ref-changelog.md:466 msgid "" "**Introduce new Flower Baseline: FedProx MNIST** " "([#1513](https://github.com/adap/flower/pull/1513), " @@ -18683,7 +19441,7 @@ msgstr "" "[#1681](https://github.com/adap/flower/pull/1681), " "[#1679](https://github.com/adap/flower/pull/1679)" -#: ../../source/ref-changelog.md:376 +#: ../../source/ref-changelog.md:468 msgid "" "This new baseline replicates the MNIST+CNN task from the paper [Federated" " Optimization in Heterogeneous Networks (Li et al., " @@ -18694,7 +19452,7 @@ msgstr "" "al., 2018)](https://arxiv.org/abs/1812.06127)中的 MNIST+CNN 任务。它使用 " "\"FedProx \"策略,旨在使收敛在异构环境中更加稳健。" -#: ../../source/ref-changelog.md:378 +#: ../../source/ref-changelog.md:470 msgid "" "**Introduce new Flower Baseline: FedAvg FEMNIST** " "([#1655](https://github.com/adap/flower/pull/1655))" @@ -18702,7 +19460,7 @@ msgstr "" "**引入新的 Flower Baseline: FedAvg FEMNIST** " "([#1655](https://github.com/adap/flower/pull/1655))" -#: ../../source/ref-changelog.md:380 +#: ../../source/ref-changelog.md:472 msgid "" "This new baseline replicates an experiment evaluating the performance of " "the FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A " @@ -18712,7 +19470,7 @@ msgstr "" "这一新Baseline复现了论文[LEAF: A Benchmark for Federated Settings(Caldas 等人,2018 " "年)](https://arxiv.org/abs/1812.01097)中评估 FedAvg 算法在 FEMNIST 数据集上性能的实验。" -#: ../../source/ref-changelog.md:382 +#: ../../source/ref-changelog.md:474 msgid "" "**Introduce (experimental) REST API** " "([#1594](https://github.com/adap/flower/pull/1594), " @@ -18731,20 +19489,20 @@ msgstr "" "[#1770](https://github.com/adap/flower/pull/1770), " "[#1733](https://github.com/adap/flower/pull/1733))" -#: ../../source/ref-changelog.md:384 +#: ../../source/ref-changelog.md:476 msgid "" "A new REST API has been introduced as an alternative to the gRPC-based " "communication stack. In this initial version, the REST API only supports " "anonymous clients." msgstr "作为基于 gRPC 的通信栈的替代方案,我们引入了新的 REST API。在初始版本中,REST API 仅支持匿名客户端。" -#: ../../source/ref-changelog.md:386 +#: ../../source/ref-changelog.md:478 msgid "" "Please note: The REST API is still experimental and will likely change " "significantly over time." msgstr "请注意:REST API 仍处于试验阶段,随着时间的推移可能会发生重大变化。" -#: ../../source/ref-changelog.md:388 +#: ../../source/ref-changelog.md:480 msgid "" "**Improve the (experimental) Driver API** " "([#1663](https://github.com/adap/flower/pull/1663), " @@ -18767,7 +19525,7 @@ msgstr "" "[#1662](https://github.com/adap/flower/pull/1662), " "[#1794](https://github.com/adap/flower/pull/1794))" -#: ../../source/ref-changelog.md:390 +#: ../../source/ref-changelog.md:482 msgid "" "The Driver API is still an experimental feature, but this release " "introduces some major upgrades. One of the main improvements is the " @@ -18780,20 +19538,20 @@ msgstr "" "数据库,将服务器状态存储在磁盘上(而不是内存中)。另一项改进是,已交付的任务(指令或结果)现在将被删除。这大大提高了长期运行的 Flower " "服务器的内存效率。" -#: ../../source/ref-changelog.md:392 +#: ../../source/ref-changelog.md:484 msgid "" "**Fix spilling issues related to Ray during simulations** " "([#1698](https://github.com/adap/flower/pull/1698))" msgstr "**修复模拟过程中与Ray有关的溢出问题** ([#1698](https://github.com/adap/flower/pull/1698))" -#: ../../source/ref-changelog.md:394 +#: ../../source/ref-changelog.md:486 msgid "" "While running long simulations, `ray` was sometimes spilling huge amounts" " of data that would make the training unable to continue. This is now " "fixed! 🎉" msgstr "在运行长时间模拟时,`ray` 有时会溢出大量数据,导致训练无法继续。现在这个问题已经解决!🎉" -#: ../../source/ref-changelog.md:396 +#: ../../source/ref-changelog.md:488 msgid "" "**Add new example using** `TabNet` **and Flower** " "([#1725](https://github.com/adap/flower/pull/1725))" @@ -18801,7 +19559,7 @@ msgstr "" "** 添加使用** `TabNet` ** 的新示例** " "([#1725](https://github.com/adap/flower/pull/1725))" -#: ../../source/ref-changelog.md:398 +#: ../../source/ref-changelog.md:490 msgid "" "TabNet is a powerful and flexible framework for training machine learning" " models on tabular data. We now have a federated example using Flower: " @@ -18812,19 +19570,19 @@ msgstr "" "tabnet](https://github.com/adap/flower/tree/main/examples/quickstart-" "tabnet)。" -#: ../../source/ref-changelog.md:400 +#: ../../source/ref-changelog.md:492 msgid "" "**Add new how-to guide for monitoring simulations** " "([#1649](https://github.com/adap/flower/pull/1649))" msgstr "** 添加新的模拟监控指南** ([#1649](https://github.com/adap/flower/pull/1649))" -#: ../../source/ref-changelog.md:402 +#: ../../source/ref-changelog.md:494 msgid "" "We now have a documentation guide to help users monitor their performance" " during simulations." msgstr "我们现在有一份文档指南,可帮助用户在模拟过程中监控其性能。" -#: ../../source/ref-changelog.md:404 +#: ../../source/ref-changelog.md:496 msgid "" "**Add training metrics to** `History` **object during simulations** " "([#1696](https://github.com/adap/flower/pull/1696))" @@ -18832,7 +19590,7 @@ msgstr "" "**在模拟过程中为***`历史`***对象添加训练指标*** " "([#1696](https://github.com/adap/flower/pull/1696))" -#: ../../source/ref-changelog.md:406 +#: ../../source/ref-changelog.md:498 msgid "" "The `fit_metrics_aggregation_fn` can be used to aggregate training " "metrics, but previous releases did not save the results in the `History` " @@ -18841,7 +19599,7 @@ msgstr "" "`fit_metrics_aggregation_fn`可用于汇总训练指标,但以前的版本不会将结果保存在 \"History " "\"对象中。现在可以了!" -#: ../../source/ref-changelog.md:408 +#: ../../source/ref-changelog.md:500 msgid "" "**General improvements** " "([#1659](https://github.com/adap/flower/pull/1659), " @@ -18944,11 +19702,11 @@ msgstr "" "[#1804](https://github.com/adap/flower/pull/1804), " "[#1805](https://github.com/adap/flower/pull/1805))" -#: ../../source/ref-changelog.md:416 +#: ../../source/ref-changelog.md:508 msgid "v1.3.0 (2023-02-06)" msgstr "v1.3.0 (2023-02-06)" -#: ../../source/ref-changelog.md:422 +#: ../../source/ref-changelog.md:514 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" @@ -18956,7 +19714,7 @@ msgstr "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " "`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" -#: ../../source/ref-changelog.md:426 +#: ../../source/ref-changelog.md:518 msgid "" "**Add support for** `workload_id` **and** `group_id` **in Driver API** " "([#1595](https://github.com/adap/flower/pull/1595))" @@ -18964,7 +19722,7 @@ msgstr "" "**在驱动程序应用程序接口中添加对** `workload_id` **和** `group_id` **的支持** " "([#1595](https://github.com/adap/flower/pull/1595))" -#: ../../source/ref-changelog.md:428 +#: ../../source/ref-changelog.md:520 msgid "" "The (experimental) Driver API now supports a `workload_id` that can be " "used to identify which workload a task belongs to. It also supports a new" @@ -18976,7 +19734,7 @@ msgstr "" "`group_id`,例如,可用于指示当前的训练轮次。通过 `workload_id` 和 `group_id` " "客户端节点可以决定是否要处理某个任务。" -#: ../../source/ref-changelog.md:430 +#: ../../source/ref-changelog.md:522 msgid "" "**Make Driver API and Fleet API address configurable** " "([#1637](https://github.com/adap/flower/pull/1637))" @@ -18984,7 +19742,7 @@ msgstr "" "**使Driver API 和Fleet " "API地址可配置**([#1637](https://github.com/adap/flower/pull/1637))" -#: ../../source/ref-changelog.md:432 +#: ../../source/ref-changelog.md:524 msgid "" "The (experimental) long-running Flower server (Driver API and Fleet API) " "can now configure the server address of both Driver API (via `--driver-" @@ -18993,7 +19751,7 @@ msgstr "" "长期运行的 Flower 服务器(Driver API 和 Fleet API)现在可以在启动时配置 Driver API(通过 " "`--driver-api-address`)和 Fleet API(通过 `-fleet-api-address`)的服务器地址:" -#: ../../source/ref-changelog.md:434 +#: ../../source/ref-changelog.md:526 #, fuzzy msgid "" "`flower-server --driver-api-address \"0.0.0.0:8081\" --fleet-api-address " @@ -19002,11 +19760,11 @@ msgstr "" "`flower-server --driver-api-address \"0.0.0.0:8081\" --fleet-api-address " "\"0.0.0.0:8086\"`" -#: ../../source/ref-changelog.md:436 +#: ../../source/ref-changelog.md:528 msgid "Both IPv4 and IPv6 addresses are supported." msgstr "支持 IPv4 和 IPv6 地址。" -#: ../../source/ref-changelog.md:438 +#: ../../source/ref-changelog.md:530 msgid "" "**Add new example of Federated Learning using fastai and Flower** " "([#1598](https://github.com/adap/flower/pull/1598))" @@ -19014,7 +19772,7 @@ msgstr "" "** 添加使用 fastai 和 Flower 进行联邦学习的新示例** " "([#1598](https://github.com/adap/flower/pull/1598))" -#: ../../source/ref-changelog.md:440 +#: ../../source/ref-changelog.md:532 msgid "" "A new code example (`quickstart-fastai`) demonstrates federated learning " "with [fastai](https://www.fast.ai/) and Flower. You can find it here: " @@ -19026,7 +19784,7 @@ msgstr "" "fastai](https://github.com/adap/flower/tree/main/examples/quickstart-" "fastai)。" -#: ../../source/ref-changelog.md:442 +#: ../../source/ref-changelog.md:534 msgid "" "**Make Android example compatible with** `flwr >= 1.0.0` **and the latest" " versions of Android** " @@ -19035,7 +19793,7 @@ msgstr "" "**使安卓示例兼容** `flwr >= 1.0.0` **和最新版本的安卓** " "([#1603](https://github.com/adap/flower/pull/1603))" -#: ../../source/ref-changelog.md:444 +#: ../../source/ref-changelog.md:536 msgid "" "The Android code example has received a substantial update: the project " "is compatible with Flower 1.0 (and later), the UI received a full " @@ -19045,13 +19803,13 @@ msgstr "" "Android 代码示例已进行了大幅更新:项目兼容 Flower 1.0(及更高版本),用户界面已全面刷新,项目已更新为兼容较新的 Android" " 工具。" -#: ../../source/ref-changelog.md:446 +#: ../../source/ref-changelog.md:538 msgid "" "**Add new `FedProx` strategy** " "([#1619](https://github.com/adap/flower/pull/1619))" msgstr "**添加新的`FedProx`策略** ([#1619](https://github.com/adap/flower/pull/1619))" -#: ../../source/ref-changelog.md:448 +#: ../../source/ref-changelog.md:540 msgid "" "This " "[strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedprox.py)" @@ -19065,25 +19823,25 @@ msgstr "" "该[策略](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedprox.py)与[`FedAvg`](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedavg.py)几乎相同,但可以帮助用户复现本[论文](https://arxiv.org/abs/1812.06127)中的描述。它的本质是添加一个名为" " `proximal_mu`的参数,使局部模型与全局模型正则化。" -#: ../../source/ref-changelog.md:450 +#: ../../source/ref-changelog.md:542 msgid "" "**Add new metrics to telemetry events** " "([#1640](https://github.com/adap/flower/pull/1640))" msgstr "**为遥测事件添加新指标**([#1640](https://github.com/adap/flower/pull/1640))" -#: ../../source/ref-changelog.md:452 +#: ../../source/ref-changelog.md:544 msgid "" "An updated event structure allows, for example, the clustering of events " "within the same workload." msgstr "例如,更新后的事件结构可以将同一工作负载中的事件集中在一起。" -#: ../../source/ref-changelog.md:454 +#: ../../source/ref-changelog.md:546 msgid "" "**Add new custom strategy tutorial section** " "[#1623](https://github.com/adap/flower/pull/1623)" msgstr "**添加新的自定义策略教程部分** [#1623](https://github.com/adap/flower/pull/1623)" -#: ../../source/ref-changelog.md:456 +#: ../../source/ref-changelog.md:548 msgid "" "The Flower tutorial now has a new section that covers implementing a " "custom strategy from scratch: [Open in " @@ -19094,13 +19852,13 @@ msgstr "" "中打开](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" "/tutorial-build-a-strategy-from-scratch-pytorch.ipynb)" -#: ../../source/ref-changelog.md:458 +#: ../../source/ref-changelog.md:550 msgid "" "**Add new custom serialization tutorial section** " "([#1622](https://github.com/adap/flower/pull/1622))" msgstr "** 添加新的自定义序列化教程部分** ([#1622](https://github.com/adap/flower/pull/1622))" -#: ../../source/ref-changelog.md:460 +#: ../../source/ref-changelog.md:552 msgid "" "The Flower tutorial now has a new section that covers custom " "serialization: [Open in " @@ -19111,7 +19869,7 @@ msgstr "" "中打开](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" "/tutorial-customize-the-client-pytorch.ipynb)" -#: ../../source/ref-changelog.md:462 +#: ../../source/ref-changelog.md:554 msgid "" "**General improvements** " "([#1638](https://github.com/adap/flower/pull/1638), " @@ -19182,7 +19940,7 @@ msgstr "" "[#1572](https://github.com/adap/flower/pull/1572), " "[#1586](https://github.com/adap/flower/pull/1586))" -#: ../../source/ref-changelog.md:466 +#: ../../source/ref-changelog.md:558 msgid "" "**Updated documentation** " "([#1629](https://github.com/adap/flower/pull/1629), " @@ -19201,18 +19959,18 @@ msgstr "" "[#1613](https://github.com/adap/flower/pull/1613), " "[#1614](https://github.com/adap/flower/pull/1614)))" -#: ../../source/ref-changelog.md:468 ../../source/ref-changelog.md:535 +#: ../../source/ref-changelog.md:560 ../../source/ref-changelog.md:627 msgid "" "As usual, the documentation has improved quite a bit. It is another step " "in our effort to make the Flower documentation the best documentation of " "any project. Stay tuned and as always, feel free to provide feedback!" msgstr "和往常一样,我们的文档有了很大的改进。这是我们努力使 Flower 文档成为所有项目中最好文档的又一步骤。请继续关注,并随时提供反馈意见!" -#: ../../source/ref-changelog.md:474 +#: ../../source/ref-changelog.md:566 msgid "v1.2.0 (2023-01-13)" msgstr "v1.2.0 (2023-01-13)" -#: ../../source/ref-changelog.md:480 +#: ../../source/ref-changelog.md:572 msgid "" "`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L." " Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" @@ -19220,7 +19978,7 @@ msgstr "" "`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L." " Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" -#: ../../source/ref-changelog.md:484 +#: ../../source/ref-changelog.md:576 msgid "" "**Introduce new Flower Baseline: FedAvg MNIST** " "([#1497](https://github.com/adap/flower/pull/1497), " @@ -19230,7 +19988,7 @@ msgstr "" "([#1497](https://github.com/adap/flower/pull/1497), " "[#1552](https://github.com/adap/flower/pull/1552))" -#: ../../source/ref-changelog.md:486 +#: ../../source/ref-changelog.md:578 msgid "" "Over the coming weeks, we will be releasing a number of new reference " "implementations useful especially to FL newcomers. They will typically " @@ -19245,13 +20003,13 @@ msgstr "" "的总体了解。今天发布的是该系列中的第一篇。[阅读全文](https://flower.ai/blog/2023-01-12-fl-starter-" "pack-fedavg-mnist-cnn/)" -#: ../../source/ref-changelog.md:488 +#: ../../source/ref-changelog.md:580 msgid "" "**Improve GPU support in simulations** " "([#1555](https://github.com/adap/flower/pull/1555))" msgstr "**改进模拟中的 GPU 支持**([#1555](https://github.com/adap/flower/pull/1555))" -#: ../../source/ref-changelog.md:490 +#: ../../source/ref-changelog.md:582 msgid "" "The Ray-based Virtual Client Engine (`start_simulation`) has been updated" " to improve GPU support. The update includes some of the hard-earned " @@ -19261,7 +20019,7 @@ msgstr "" "基于 Ray 的虚拟客户端引擎 (`start_simulation`)已更新,以改进对 GPU 的支持。此次更新包含了在 GPU " "集群环境中扩展模拟的一些经验教训。新的默认设置使基于 GPU 的模拟运行更加稳健。" -#: ../../source/ref-changelog.md:492 +#: ../../source/ref-changelog.md:584 msgid "" "**Improve GPU support in Jupyter Notebook tutorials** " "([#1527](https://github.com/adap/flower/pull/1527), " @@ -19271,7 +20029,7 @@ msgstr "" "([#1527](https://github.com/adap/flower/pull/1527), " "[#1558](https://github.com/adap/flower/pull/1558))" -#: ../../source/ref-changelog.md:494 +#: ../../source/ref-changelog.md:586 msgid "" "Some users reported that Jupyter Notebooks have not always been easy to " "use on GPU instances. We listened and made improvements to all of our " @@ -19280,7 +20038,7 @@ msgstr "" "一些用户报告说,在 GPU 实例上使用 Jupyter 笔记本并不是很方便。我们听取了他们的意见,并对所有 Jupyter " "笔记本进行了改进!点击这里查看更新后的笔记本:" -#: ../../source/ref-changelog.md:496 +#: ../../source/ref-changelog.md:588 msgid "" "[An Introduction to Federated Learning](https://flower.ai/docs/framework" "/tutorial-get-started-with-flower-pytorch.html)" @@ -19288,7 +20046,7 @@ msgstr "" "[联邦学习简介](https://flower.ai/docs/framework/tutorial-get-started-with-" "flower-pytorch.html)" -#: ../../source/ref-changelog.md:497 +#: ../../source/ref-changelog.md:589 msgid "" "[Strategies in Federated Learning](https://flower.ai/docs/framework" "/tutorial-use-a-federated-learning-strategy-pytorch.html)" @@ -19296,7 +20054,7 @@ msgstr "" "[联邦学习策略](https://flower.ai/docs/framework/tutorial-use-a-federated-" "learning-strategy-pytorch.html)" -#: ../../source/ref-changelog.md:498 +#: ../../source/ref-changelog.md:590 msgid "" "[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a" "-strategy-from-scratch-pytorch.html)" @@ -19304,7 +20062,7 @@ msgstr "" "[制定策略](https://flower.ai/docs/framework/tutorial-build-a-strategy-from-" "scratch-pytorch.html)" -#: ../../source/ref-changelog.md:499 +#: ../../source/ref-changelog.md:591 msgid "" "[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-" "customize-the-client-pytorch.html)" @@ -19312,7 +20070,7 @@ msgstr "" "[客户端和 NumPyClient](https://flower.ai/docs/framework/tutorial-customize-" "the-client-pytorch.html)" -#: ../../source/ref-changelog.md:501 +#: ../../source/ref-changelog.md:593 msgid "" "**Introduce optional telemetry** " "([#1533](https://github.com/adap/flower/pull/1533), " @@ -19323,7 +20081,7 @@ msgstr "" "[#1544](https://github.com/adap/flower/pull/1544), " "[#1584](https://github.com/adap/flower/pull/1584)" -#: ../../source/ref-changelog.md:503 +#: ../../source/ref-changelog.md:595 msgid "" "After a [request for " "feedback](https://github.com/adap/flower/issues/1534) from the community," @@ -19336,7 +20094,7 @@ msgstr "" "开放源码项目引入了可选的*匿名*使用指标收集,以便在充分知情的情况下做出改进 Flower 的决定。这样做能让 Flower 团队了解 " "Flower 的使用情况以及用户可能面临的挑战。" -#: ../../source/ref-changelog.md:505 +#: ../../source/ref-changelog.md:597 msgid "" "**Flower is a friendly framework for collaborative AI and data science.**" " Staying true to this statement, Flower makes it easy to disable " @@ -19346,7 +20104,7 @@ msgstr "" "**Flower 是一个用于协作式人工智能和数据科学的友好框架。** Flower " "遵循这一声明,让不想分享匿名使用指标的用户可以轻松禁用遥测技术。[阅读全文](https://flower.ai/docs/telemetry.html)。" -#: ../../source/ref-changelog.md:507 +#: ../../source/ref-changelog.md:599 msgid "" "**Introduce (experimental) Driver API** " "([#1520](https://github.com/adap/flower/pull/1520), " @@ -19365,7 +20123,7 @@ msgstr "" "[#1551](https://github.com/adap/flower/pull/1551), " "[#1567](https://github.com/adap/flower/pull/1567))" -#: ../../source/ref-changelog.md:509 +#: ../../source/ref-changelog.md:601 msgid "" "Flower now has a new (experimental) Driver API which will enable fully " "programmable, async, and multi-tenant Federated Learning and Federated " @@ -19377,7 +20135,7 @@ msgstr "" "API),它将支持完全可编程、异步和多租户的联邦学习(Federated Learning)和联邦分析(Federated " "Analytics)应用程序。展望未来,Driver API 将成为许多即将推出的功能的抽象基础,您现在就可以开始构建这些功能。" -#: ../../source/ref-changelog.md:511 +#: ../../source/ref-changelog.md:603 msgid "" "The Driver API also enables a new execution mode in which the server runs" " indefinitely. Multiple individual workloads can run concurrently and " @@ -19387,19 +20145,19 @@ msgstr "" "驱动程序应用程序接口还支持一种新的执行模式,在这种模式下,服务器可无限期运行。多个单独的工作负载可以同时运行,并独立于服务器启动和停止执行。这对于希望在生产中部署" " Flower 的用户来说尤其有用。" -#: ../../source/ref-changelog.md:513 +#: ../../source/ref-changelog.md:605 msgid "" "To learn more, check out the `mt-pytorch` code example. We look forward " "to you feedback!" msgstr "要了解更多信息,请查看 `mt-pytorch` 代码示例。我们期待您的反馈!" -#: ../../source/ref-changelog.md:515 +#: ../../source/ref-changelog.md:607 msgid "" "Please note: *The Driver API is still experimental and will likely change" " significantly over time.*" msgstr "请注意:Driver API仍处于试验阶段,随着时间的推移可能会发生重大变化。*" -#: ../../source/ref-changelog.md:517 +#: ../../source/ref-changelog.md:609 msgid "" "**Add new Federated Analytics with Pandas example** " "([#1469](https://github.com/adap/flower/pull/1469), " @@ -19409,7 +20167,7 @@ msgstr "" "的联邦分析示例**([#1469](https://github.com/adap/flower/pull/1469), " "[#1535](https://github.com/adap/flower/pull/1535)" -#: ../../source/ref-changelog.md:519 +#: ../../source/ref-changelog.md:611 msgid "" "A new code example (`quickstart-pandas`) demonstrates federated analytics" " with Pandas and Flower. You can find it here: [quickstart-" @@ -19420,7 +20178,7 @@ msgstr "" "[quickstart-pandas](https://github.com/adap/flower/tree/main/examples" "/quickstart-pandas)。" -#: ../../source/ref-changelog.md:521 +#: ../../source/ref-changelog.md:613 msgid "" "**Add new strategies: Krum and MultiKrum** " "([#1481](https://github.com/adap/flower/pull/1481))" @@ -19428,7 +20186,7 @@ msgstr "" "**添加新策略: Krum 和 MultiKrum** " "([#1481](https://github.com/adap/flower/pull/1481))" -#: ../../source/ref-changelog.md:523 +#: ../../source/ref-changelog.md:615 msgid "" "Edoardo, a computer science student at the Sapienza University of Rome, " "contributed a new `Krum` strategy that enables users to easily use Krum " @@ -19437,7 +20195,7 @@ msgstr "" "罗马萨皮恩扎大学(Sapienza University)计算机科学专业的学生埃多尔多(Edoardo)提出了一种新的 \"Krum " "\"策略,使用户能够在其工作负载中轻松使用 Krum 和 MultiKrum。" -#: ../../source/ref-changelog.md:525 +#: ../../source/ref-changelog.md:617 msgid "" "**Update C++ example to be compatible with Flower v1.2.0** " "([#1495](https://github.com/adap/flower/pull/1495))" @@ -19445,13 +20203,13 @@ msgstr "" "** 更新 C++ 示例,与 Flower v1.2.0 兼容** " "([#1495](https://github.com/adap/flower/pull/1495))" -#: ../../source/ref-changelog.md:527 +#: ../../source/ref-changelog.md:619 msgid "" "The C++ code example has received a substantial update to make it " "compatible with the latest version of Flower." msgstr "为了与最新版本的 Flower 兼容,C++ 示例代码进行了大幅更新。" -#: ../../source/ref-changelog.md:529 +#: ../../source/ref-changelog.md:621 msgid "" "**General improvements** " "([#1491](https://github.com/adap/flower/pull/1491), " @@ -19482,7 +20240,7 @@ msgstr "" "[#1564](https://github.com/adap/flower/pull/1564), " "[#1566](https://github.com/adap/flower/pull/1566))" -#: ../../source/ref-changelog.md:533 +#: ../../source/ref-changelog.md:625 msgid "" "**Updated documentation** " "([#1494](https://github.com/adap/flower/pull/1494), " @@ -19505,7 +20263,7 @@ msgstr "" "[#1519](https://github.com/adap/flower/pull/1519), " "[#1515](https://github.com/adap/flower/pull/1515))" -#: ../../source/ref-changelog.md:537 +#: ../../source/ref-changelog.md:629 msgid "" "One highlight is the new [first time contributor " "guide](https://flower.ai/docs/first-time-contributors.html): if you've " @@ -19514,17 +20272,17 @@ msgstr "" "其中一个亮点是新的[首次贡献者指南](https://flower.ai/docs/first-time-" "contributors.html):如果你以前从未在 GitHub 上做过贡献,这将是一个完美的开始!" -#: ../../source/ref-changelog.md:543 +#: ../../source/ref-changelog.md:635 msgid "v1.1.0 (2022-10-31)" msgstr "v1.1.0 (2022-10-31)" -#: ../../source/ref-changelog.md:547 +#: ../../source/ref-changelog.md:639 msgid "" "We would like to give our **special thanks** to all the contributors who " "made the new version of Flower possible (in `git shortlog` order):" msgstr "在此,我们向所有促成 Flower 新版本的贡献者致以**特别的谢意(按 \"git shortlog \"顺序排列):" -#: ../../source/ref-changelog.md:549 +#: ../../source/ref-changelog.md:641 msgid "" "`Akis Linardos`, `Christopher S`, `Daniel J. Beutel`, `George`, `Jan " "Schlicht`, `Mohammad Fares`, `Pedro Porto Buarque de Gusmão`, `Philipp " @@ -19536,7 +20294,7 @@ msgstr "" "Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, " "`danielnugraha`, `edogab33`" -#: ../../source/ref-changelog.md:553 +#: ../../source/ref-changelog.md:645 msgid "" "**Introduce Differential Privacy wrappers (preview)** " "([#1357](https://github.com/adap/flower/pull/1357), " @@ -19545,7 +20303,7 @@ msgstr "" "**引入差分隐私包装器(预览)** ([#1357](https://github.com/adap/flower/pull/1357), " "[#1460](https://github.com/adap/flower/pull/1460))" -#: ../../source/ref-changelog.md:555 +#: ../../source/ref-changelog.md:647 msgid "" "The first (experimental) preview of pluggable Differential Privacy " "wrappers enables easy configuration and usage of differential privacy " @@ -19556,13 +20314,13 @@ msgstr "" "可插拔差分隐私封装器的首个(实验性)预览版可轻松配置和使用差分隐私(DP)。可插拔的差分隐私封装器可实现客户端差分隐私和服务器端差分隐私的框架无关**以及**策略无关的使用。请访问" " Flower 文档,新的解释器会提供更多细节。" -#: ../../source/ref-changelog.md:557 +#: ../../source/ref-changelog.md:649 msgid "" "**New iOS CoreML code example** " "([#1289](https://github.com/adap/flower/pull/1289))" msgstr "**新的 iOS CoreML 代码示例**([#1289](https://github.com/adap/flower/pull/1289))" -#: ../../source/ref-changelog.md:559 +#: ../../source/ref-changelog.md:651 msgid "" "Flower goes iOS! A massive new code example shows how Flower clients can " "be built for iOS. The code example contains both Flower iOS SDK " @@ -19572,13 +20330,13 @@ msgstr "" "Flower 进入 iOS!大量新代码示例展示了如何为 iOS 构建 Flower 客户端。该代码示例包含可用于多种任务的 Flower iOS " "SDK 组件,以及在 CoreML 上运行的一个任务示例。" -#: ../../source/ref-changelog.md:561 +#: ../../source/ref-changelog.md:653 msgid "" "**New FedMedian strategy** " "([#1461](https://github.com/adap/flower/pull/1461))" msgstr "**新的联邦医疗策略** ([#1461](https://github.com/adap/flower/pull/1461))" -#: ../../source/ref-changelog.md:563 +#: ../../source/ref-changelog.md:655 msgid "" "The new `FedMedian` strategy implements Federated Median (FedMedian) by " "[Yin et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." @@ -19586,27 +20344,27 @@ msgstr "" "新的 \"FedMedian \"战略实现了[Yin " "等人,2018]的联邦中值(FedMedian)(https://arxiv.org/pdf/1803.01498v1.pdf)。" -#: ../../source/ref-changelog.md:565 +#: ../../source/ref-changelog.md:657 msgid "" "**Log** `Client` **exceptions in Virtual Client Engine** " "([#1493](https://github.com/adap/flower/pull/1493))" msgstr "**虚拟客户端引擎中的**日志**`客户端`**异常([#1493](https://github.com/adap/flower/pull/1493))" -#: ../../source/ref-changelog.md:567 +#: ../../source/ref-changelog.md:659 msgid "" "All `Client` exceptions happening in the VCE are now logged by default " "and not just exposed to the configured `Strategy` (via the `failures` " "argument)." msgstr "VCE 中发生的所有 \"客户端 \"异常现在都会被默认记录下来,而不只是暴露给配置的 `Strategy`(通过 `failures`参数)。" -#: ../../source/ref-changelog.md:569 +#: ../../source/ref-changelog.md:661 msgid "" "**Improve Virtual Client Engine internals** " "([#1401](https://github.com/adap/flower/pull/1401), " "[#1453](https://github.com/adap/flower/pull/1453))" msgstr "**改进虚拟客户端引擎内部**([#1401](https://github.com/adap/flower/pull/1401)、[#1453](https://github.com/adap/flower/pull/1453))" -#: ../../source/ref-changelog.md:571 +#: ../../source/ref-changelog.md:663 msgid "" "Some internals of the Virtual Client Engine have been revamped. The VCE " "now uses Ray 2.0 under the hood, the value type of the `client_resources`" @@ -19616,19 +20374,19 @@ msgstr "" "虚拟客户端引擎的部分内部结构已进行了修改。VCE 现在使用 Ray 2.0,\"client_resources \"字典的值类型改为 " "\"float\",以允许分配分数资源。" -#: ../../source/ref-changelog.md:573 +#: ../../source/ref-changelog.md:665 msgid "" "**Support optional** `Client`**/**`NumPyClient` **methods in Virtual " "Client Engine**" msgstr "**支持虚拟客户端引擎中的可选** `Client`**/**`NumPyClient` **方法**" -#: ../../source/ref-changelog.md:575 +#: ../../source/ref-changelog.md:667 msgid "" "The Virtual Client Engine now has full support for optional `Client` (and" " `NumPyClient`) methods." msgstr "虚拟客户端引擎现在完全支持可选的 `Client`(和 `NumPyClient`)方法。" -#: ../../source/ref-changelog.md:577 +#: ../../source/ref-changelog.md:669 msgid "" "**Provide type information to packages using** `flwr` " "([#1377](https://github.com/adap/flower/pull/1377))" @@ -19636,7 +20394,7 @@ msgstr "" "**使用** `flwr`向软件包提供类型信息 " "([#1377](https://github.com/adap/flower/pull/1377))" -#: ../../source/ref-changelog.md:579 +#: ../../source/ref-changelog.md:671 msgid "" "The package `flwr` is now bundled with a `py.typed` file indicating that " "the package is typed. This enables typing support for projects or " @@ -19646,7 +20404,7 @@ msgstr "" "软件包 `flwr` 现在捆绑了一个 `py.typed` 文件,表明该软件包是类型化的。这样,使用 `flwr` 的项目或软件包就可以使用 " "`mypy` 等静态类型检查器改进代码,从而获得类型支持。" -#: ../../source/ref-changelog.md:581 +#: ../../source/ref-changelog.md:673 msgid "" "**Updated code example** " "([#1344](https://github.com/adap/flower/pull/1344), " @@ -19655,13 +20413,13 @@ msgstr "" "** 更新代码示例** ([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" -#: ../../source/ref-changelog.md:583 +#: ../../source/ref-changelog.md:675 msgid "" "The code examples covering scikit-learn and PyTorch Lightning have been " "updated to work with the latest version of Flower." msgstr "涵盖 scikit-learn 和 PyTorch Lightning 的代码示例已更新,以便与最新版本的 Flower 配合使用。" -#: ../../source/ref-changelog.md:585 +#: ../../source/ref-changelog.md:677 msgid "" "**Updated documentation** " "([#1355](https://github.com/adap/flower/pull/1355), " @@ -19700,32 +20458,32 @@ msgstr "" "[#1465](https://github.com/adap/flower/pull/1465), " "[#1467](https://github.com/adap/flower/pull/1467))" -#: ../../source/ref-changelog.md:587 +#: ../../source/ref-changelog.md:679 msgid "" "There have been so many documentation updates that it doesn't even make " "sense to list them individually." msgstr "文档更新的数量之多,甚至没有必要逐一列出。" -#: ../../source/ref-changelog.md:589 +#: ../../source/ref-changelog.md:681 msgid "" "**Restructured documentation** " "([#1387](https://github.com/adap/flower/pull/1387))" msgstr "**重构文档**([#1387](https://github.com/adap/flower/pull/1387))" -#: ../../source/ref-changelog.md:591 +#: ../../source/ref-changelog.md:683 msgid "" "The documentation has been restructured to make it easier to navigate. " "This is just the first step in a larger effort to make the Flower " "documentation the best documentation of any project ever. Stay tuned!" msgstr "我们对文档进行了重组,使其更易于浏览。这只是让 Flower 文档成为所有项目中最好文档的第一步。敬请期待!" -#: ../../source/ref-changelog.md:593 +#: ../../source/ref-changelog.md:685 msgid "" "**Open in Colab button** " "([#1389](https://github.com/adap/flower/pull/1389))" msgstr "**在 Colab 中打开按钮** ([#1389](https://github.com/adap/flower/pull/1389))" -#: ../../source/ref-changelog.md:595 +#: ../../source/ref-changelog.md:687 msgid "" "The four parts of the Flower Federated Learning Tutorial now come with a " "new `Open in Colab` button. No need to install anything on your local " @@ -19735,7 +20493,7 @@ msgstr "" "Flower 联邦学习教程的四个部分现在都带有一个新的 \"在 Colab 中打开 " "\"按钮。现在,您无需在本地计算机上安装任何软件,只需点击一下,就可以在浏览器中使用和学习 Flower。" -#: ../../source/ref-changelog.md:597 +#: ../../source/ref-changelog.md:689 msgid "" "**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468)," " [#1470](https://github.com/adap/flower/pull/1470), " @@ -19751,7 +20509,7 @@ msgstr "" "[#1474](https://github.com/adap/flower/pull/1474), " "[#1475](https://github.com/adap/flower/pull/1475)))" -#: ../../source/ref-changelog.md:599 +#: ../../source/ref-changelog.md:691 msgid "" "The Flower Federated Learning Tutorial has two brand-new parts covering " "custom strategies (still WIP) and the distinction between `Client` and " @@ -19761,33 +20519,33 @@ msgstr "" "Flower 联邦学习教程有两个全新的部分,涉及自定义策略(仍处于 WIP 阶段)和 `Client` 与 `NumPyClient` " "之间的区别。现有的第一和第二部分也得到了改进(许多小改动和修正)。" -#: ../../source/ref-changelog.md:605 +#: ../../source/ref-changelog.md:697 msgid "v1.0.0 (2022-07-28)" msgstr "v1.0.0 (2022-07-28)" -#: ../../source/ref-changelog.md:607 +#: ../../source/ref-changelog.md:699 msgid "Highlights" msgstr "亮点" -#: ../../source/ref-changelog.md:609 +#: ../../source/ref-changelog.md:701 msgid "Stable **Virtual Client Engine** (accessible via `start_simulation`)" msgstr "稳定的**虚拟客户端引擎**(可通过`start_simulation`访问)" -#: ../../source/ref-changelog.md:610 +#: ../../source/ref-changelog.md:702 msgid "All `Client`/`NumPyClient` methods are now optional" msgstr "所有 `Client`/`NumPyClient` 方法现在都是可选的了" -#: ../../source/ref-changelog.md:611 +#: ../../source/ref-changelog.md:703 msgid "Configurable `get_parameters`" msgstr "可配置的`get_parameters`" -#: ../../source/ref-changelog.md:612 +#: ../../source/ref-changelog.md:704 msgid "" "Tons of small API cleanups resulting in a more coherent developer " "experience" msgstr "对大量小型应用程序接口进行了清理,使开发人员的体验更加一致" -#: ../../source/ref-changelog.md:616 +#: ../../source/ref-changelog.md:708 msgid "" "We would like to give our **special thanks** to all the contributors who " "made Flower 1.0 possible (in reverse [GitHub " @@ -19796,7 +20554,7 @@ msgstr "" "在此,我们谨向所有促成 Flower 1.0 的贡献者致以**特别的谢意(按[GitHub " "贡献者](https://github.com/adap/flower/graphs/contributors) 倒序排列):" -#: ../../source/ref-changelog.md:618 +#: ../../source/ref-changelog.md:710 msgid "" "[@rtaiello](https://github.com/rtaiello), " "[@g-pichler](https://github.com/g-pichler), [@rob-" @@ -19872,13 +20630,13 @@ msgstr "" "[@tanertopal](https://github.com/tanertopal), " "[@danieljanes](https://github.com/danieljanes)." -#: ../../source/ref-changelog.md:622 +#: ../../source/ref-changelog.md:714 msgid "" "**All arguments must be passed as keyword arguments** " "([#1338](https://github.com/adap/flower/pull/1338))" msgstr "** 所有参数必须作为关键字参数传递** ([#1338](https://github.com/adap/flower/pull/1338))" -#: ../../source/ref-changelog.md:624 +#: ../../source/ref-changelog.md:716 msgid "" "Pass all arguments as keyword arguments, positional arguments are not " "longer supported. Code that uses positional arguments (e.g., " @@ -19891,7 +20649,7 @@ msgstr "" "FlowerClient())`)必须为每个位置参数添加关键字(例如,`start_client(server_address=\"127.0.0.1:8080\"," " client=FlowerClient())`)。" -#: ../../source/ref-changelog.md:626 +#: ../../source/ref-changelog.md:718 msgid "" "**Introduce configuration object** `ServerConfig` **in** `start_server` " "**and** `start_simulation` " @@ -19900,7 +20658,7 @@ msgstr "" "**在*** `start_server` ***和*** `start_simulation` 中引入配置对象*** " "`ServerConfig` ([#1317](https://github.com/adap/flower/pull/1317))" -#: ../../source/ref-changelog.md:628 +#: ../../source/ref-changelog.md:720 msgid "" "Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": " "600.0}`, `start_server` and `start_simulation` now expect a configuration" @@ -19913,37 +20671,37 @@ msgstr "" "`flwr.server.ServerConfig`的配置对象。`ServerConfig`接收的参数与之前的 config dict " "相同,但它使编写类型安全代码变得更容易,默认参数值也更加透明。" -#: ../../source/ref-changelog.md:630 +#: ../../source/ref-changelog.md:722 msgid "" "**Rename built-in strategy parameters for clarity** " "([#1334](https://github.com/adap/flower/pull/1334))" msgstr "**重新命名内置策略参数,使其更加清晰** ([#1334](https://github.com/adap/flower/pull/1334))" -#: ../../source/ref-changelog.md:632 +#: ../../source/ref-changelog.md:724 msgid "" "The following built-in strategy parameters were renamed to improve " "readability and consistency with other API's:" msgstr "以下内置策略参数已重新命名,以提高可读性并与其他 API 保持一致:" -#: ../../source/ref-changelog.md:634 +#: ../../source/ref-changelog.md:726 msgid "`fraction_eval` --> `fraction_evaluate`" msgstr "`fraction_eval` --> `fraction_evaluate`" -#: ../../source/ref-changelog.md:635 +#: ../../source/ref-changelog.md:727 msgid "`min_eval_clients` --> `min_evaluate_clients`" msgstr "`min_eval_clients` --> `min_evaluate_clients`" -#: ../../source/ref-changelog.md:636 +#: ../../source/ref-changelog.md:728 msgid "`eval_fn` --> `evaluate_fn`" msgstr "`eval_fn` --> `evaluate_fn`" -#: ../../source/ref-changelog.md:638 +#: ../../source/ref-changelog.md:730 msgid "" "**Update default arguments of built-in strategies** " "([#1278](https://github.com/adap/flower/pull/1278))" msgstr "**更新内置策略的默认参数** ([#1278](https://github.com/adap/flower/pull/1278))" -#: ../../source/ref-changelog.md:640 +#: ../../source/ref-changelog.md:732 msgid "" "All built-in strategies now use `fraction_fit=1.0` and " "`fraction_evaluate=1.0`, which means they select *all* currently " @@ -19954,11 +20712,11 @@ msgstr "" "所有内置策略现在都使用 \"fraction_fit=1.0 \"和 " "\"fraction_evaluate=1.0\",这意味着它们会选择*所有*当前可用的客户端进行训练和评估。依赖以前默认值的项目可以通过以下方式初始化策略,获得以前的行为:" -#: ../../source/ref-changelog.md:642 +#: ../../source/ref-changelog.md:734 msgid "`strategy = FedAvg(fraction_fit=0.1, fraction_evaluate=0.1)`" msgstr "`strategy = FedAvg(fraction_fit=0.1, fraction_evaluate=0.1)`" -#: ../../source/ref-changelog.md:644 +#: ../../source/ref-changelog.md:736 msgid "" "**Add** `server_round` **to** `Strategy.evaluate` " "([#1334](https://github.com/adap/flower/pull/1334))" @@ -19966,13 +20724,13 @@ msgstr "" "**添加*** `server_round` ***到*** `Strategy.evaluate` " "([#1334](https://github.com/adap/flower/pull/1334))" -#: ../../source/ref-changelog.md:646 +#: ../../source/ref-changelog.md:738 msgid "" "The `Strategy` method `evaluate` now receives the current round of " "federated learning/evaluation as the first parameter." msgstr "`Strategy`的`evaluate` 方法现在会接收当前一轮联邦学习/评估作为第一个参数。" -#: ../../source/ref-changelog.md:648 +#: ../../source/ref-changelog.md:740 msgid "" "**Add** `server_round` **and** `config` **parameters to** `evaluate_fn` " "([#1334](https://github.com/adap/flower/pull/1334))" @@ -19980,7 +20738,7 @@ msgstr "" "**将*** `server_round` **和*** `config` **参数添加到*** `evaluate_fn` " "([#1334](https://github.com/adap/flower/pull/1334))" -#: ../../source/ref-changelog.md:650 +#: ../../source/ref-changelog.md:742 msgid "" "The `evaluate_fn` passed to built-in strategies like `FedAvg` now takes " "three parameters: (1) The current round of federated learning/evaluation " @@ -19990,7 +20748,7 @@ msgstr "" "传递给内置策略(如 `FedAvg`)的 `evaluate_fn` 现在需要三个参数:(1) 当前一轮联邦学习/评估 " "(`server_round`),(2) 要评估的模型参数 (`parameters`),(3) 配置字典 (`config`)。" -#: ../../source/ref-changelog.md:652 +#: ../../source/ref-changelog.md:744 msgid "" "**Rename** `rnd` **to** `server_round` " "([#1321](https://github.com/adap/flower/pull/1321))" @@ -19998,7 +20756,7 @@ msgstr "" "**重新命名** `rnd` ** to** `server_round` " "([#1321](https://github.com/adap/flower/pull/1321))" -#: ../../source/ref-changelog.md:654 +#: ../../source/ref-changelog.md:746 msgid "" "Several Flower methods and functions (`evaluate_fn`, `configure_fit`, " "`aggregate_fit`, `configure_evaluate`, `aggregate_evaluate`) receive the " @@ -20010,7 +20768,7 @@ msgstr "" "方法和函数(`evaluate_fn`、`configure_fit`、`aggregate_fit`、`configure_evaluate`、`aggregate_evaluate`)的第一个参数是当前一轮的联邦学习/评估。为提高可重复性并避免与" " *random* 混淆,该参数已从 `rnd` 更名为 `server_round`。" -#: ../../source/ref-changelog.md:656 +#: ../../source/ref-changelog.md:748 msgid "" "**Move** `flwr.dataset` **to** `flwr_baselines` " "([#1273](https://github.com/adap/flower/pull/1273))" @@ -20018,23 +20776,23 @@ msgstr "" "**移动*** `flwr.dataset` **到*** `flwr_baselines` " "([#1273](https://github.com/adap/flower/pull/1273))" -#: ../../source/ref-changelog.md:658 +#: ../../source/ref-changelog.md:750 msgid "The experimental package `flwr.dataset` was migrated to Flower Baselines." msgstr "实验软件包 `flwr.dataset` 已迁移至 Flower Baselines。" -#: ../../source/ref-changelog.md:660 +#: ../../source/ref-changelog.md:752 msgid "" "**Remove experimental strategies** " "([#1280](https://github.com/adap/flower/pull/1280))" msgstr "**删除实验策略** ([#1280](https://github.com/adap/flower/pull/1280))" -#: ../../source/ref-changelog.md:662 +#: ../../source/ref-changelog.md:754 msgid "" "Remove unmaintained experimental strategies (`FastAndSlow`, `FedFSv0`, " "`FedFSv1`)." msgstr "移除未维护的试验性策略(`FastAndSlow`、`FedFSv0`、`FedFSv1`)。" -#: ../../source/ref-changelog.md:664 +#: ../../source/ref-changelog.md:756 msgid "" "**Rename** `Weights` **to** `NDArrays` " "([#1258](https://github.com/adap/flower/pull/1258), " @@ -20044,13 +20802,13 @@ msgstr "" "([#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" -#: ../../source/ref-changelog.md:666 +#: ../../source/ref-changelog.md:758 msgid "" "`flwr.common.Weights` was renamed to `flwr.common.NDArrays` to better " "capture what this type is all about." msgstr "flwr.common.Weights \"更名为 \"flwr.common.NDArrays\",以更好地反映该类型的含义。" -#: ../../source/ref-changelog.md:668 +#: ../../source/ref-changelog.md:760 msgid "" "**Remove antiquated** `force_final_distributed_eval` **from** " "`start_server` ([#1258](https://github.com/adap/flower/pull/1258), " @@ -20060,7 +20818,7 @@ msgstr "" "([#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" -#: ../../source/ref-changelog.md:670 +#: ../../source/ref-changelog.md:762 msgid "" "The `start_server` parameter `force_final_distributed_eval` has long been" " a historic artefact, in this release it is finally gone for good." @@ -20068,7 +20826,7 @@ msgstr "" "start_server \"参数 \"force_final_distributed_eval " "\"长期以来一直是个历史遗留问题,在此版本中终于永远消失了。" -#: ../../source/ref-changelog.md:672 +#: ../../source/ref-changelog.md:764 msgid "" "**Make** `get_parameters` **configurable** " "([#1242](https://github.com/adap/flower/pull/1242))" @@ -20076,7 +20834,7 @@ msgstr "" "**使** `get_parameters` **可配置** " "([#1242](https://github.com/adap/flower/pull/1242))" -#: ../../source/ref-changelog.md:674 +#: ../../source/ref-changelog.md:766 msgid "" "The `get_parameters` method now accepts a configuration dictionary, just " "like `get_properties`, `fit`, and `evaluate`." @@ -20084,7 +20842,7 @@ msgstr "" "现在,\"get_parameters \"方法与 \"get_properties\"、\"fit \"和 \"evaluate " "\"一样,都接受配置字典。" -#: ../../source/ref-changelog.md:676 +#: ../../source/ref-changelog.md:768 msgid "" "**Replace** `num_rounds` **in** `start_simulation` **with new** `config` " "**parameter** ([#1281](https://github.com/adap/flower/pull/1281))" @@ -20092,7 +20850,7 @@ msgstr "" "**用新的** `config` 参数** 替换** `num_rounds` ** in** `start_simulation` ** " "([#1281](https://github.com/adap/flower/pull/1281))" -#: ../../source/ref-changelog.md:678 +#: ../../source/ref-changelog.md:770 msgid "" "The `start_simulation` function now accepts a configuration dictionary " "`config` instead of the `num_rounds` integer. This improves the " @@ -20102,19 +20860,19 @@ msgstr "" "现在,`start_simulation`(开始模拟)` 函数接受配置字典 `config` 而不是 `num_rounds` 整数。这改进了 " "`start_simulation` 和 `start_server` 之间的一致性,并使两者之间的转换更容易。" -#: ../../source/ref-changelog.md:682 +#: ../../source/ref-changelog.md:774 msgid "" "**Support Python 3.10** " "([#1320](https://github.com/adap/flower/pull/1320))" msgstr "** 支持 Python 3.10** ([#1320](https://github.com/adap/flower/pull/1320))" -#: ../../source/ref-changelog.md:684 +#: ../../source/ref-changelog.md:776 msgid "" "The previous Flower release introduced experimental support for Python " "3.10, this release declares Python 3.10 support as stable." msgstr "上一个 Flower 版本引入了对 Python 3.10 的实验支持,而本版本则宣布对 Python 3.10 的支持为稳定支持。" -#: ../../source/ref-changelog.md:686 +#: ../../source/ref-changelog.md:778 msgid "" "**Make all** `Client` **and** `NumPyClient` **methods optional** " "([#1260](https://github.com/adap/flower/pull/1260), " @@ -20124,7 +20882,7 @@ msgstr "" "([#1260](https://github.com/adap/flower/pull/1260), " "[#1277](https://github.com/adap/flower/pull/1277))" -#: ../../source/ref-changelog.md:688 +#: ../../source/ref-changelog.md:780 msgid "" "The `Client`/`NumPyClient` methods `get_properties`, `get_parameters`, " "`fit`, and `evaluate` are all optional. This enables writing clients that" @@ -20135,7 +20893,7 @@ msgstr "" "\"evaluate \"方法都是可选的。这样就可以编写只实现 `fit` 而不实现其他方法的客户端。使用集中评估时,无需实现 " "`evaluate`!" -#: ../../source/ref-changelog.md:690 +#: ../../source/ref-changelog.md:782 msgid "" "**Enable passing a** `Server` **instance to** `start_simulation` " "([#1281](https://github.com/adap/flower/pull/1281))" @@ -20143,7 +20901,7 @@ msgstr "" "**启用向** `start_simulation` 传递** `Server` 实例 " "([#1281](https://github.com/adap/flower/pull/1281))" -#: ../../source/ref-changelog.md:692 +#: ../../source/ref-changelog.md:784 msgid "" "Similar to `start_server`, `start_simulation` now accepts a full `Server`" " instance. This enables users to heavily customize the execution of " @@ -20153,7 +20911,7 @@ msgstr "" "与 `start_server` 类似,`start_simulation` 现在也接受一个完整的 `Server` " "实例。这使得用户可以对实验的执行进行大量自定义,并为使用虚拟客户端引擎运行异步 FL 等打开了大门。" -#: ../../source/ref-changelog.md:694 +#: ../../source/ref-changelog.md:786 msgid "" "**Update code examples** " "([#1291](https://github.com/adap/flower/pull/1291), " @@ -20164,43 +20922,43 @@ msgstr "" "[#1286](https://github.com/adap/flower/pull/1286), " "[#1282](https://github.com/adap/flower/pull/1282))" -#: ../../source/ref-changelog.md:696 +#: ../../source/ref-changelog.md:788 msgid "" "Many code examples received small or even large maintenance updates, " "among them are" msgstr "许多代码示例都进行了小规模甚至大规模的维护更新,其中包括" -#: ../../source/ref-changelog.md:698 +#: ../../source/ref-changelog.md:790 msgid "`scikit-learn`" msgstr "`scikit-learn`" -#: ../../source/ref-changelog.md:699 +#: ../../source/ref-changelog.md:791 msgid "`simulation_pytorch`" msgstr "`simulation_pytorch`" -#: ../../source/ref-changelog.md:700 +#: ../../source/ref-changelog.md:792 msgid "`quickstart_pytorch`" msgstr "`quickstart_pytorch`" -#: ../../source/ref-changelog.md:701 +#: ../../source/ref-changelog.md:793 msgid "`quickstart_simulation`" msgstr "`quickstart_simulation`" -#: ../../source/ref-changelog.md:702 +#: ../../source/ref-changelog.md:794 msgid "`quickstart_tensorflow`" msgstr "`quickstart_tensorflow`" -#: ../../source/ref-changelog.md:703 +#: ../../source/ref-changelog.md:795 msgid "`advanced_tensorflow`" msgstr "`advanced_tensorflow`" -#: ../../source/ref-changelog.md:705 +#: ../../source/ref-changelog.md:797 msgid "" "**Remove the obsolete simulation example** " "([#1328](https://github.com/adap/flower/pull/1328))" msgstr "**删除过时的模拟示例** ([#1328](https://github.com/adap/flower/pull/1328))" -#: ../../source/ref-changelog.md:707 +#: ../../source/ref-changelog.md:799 msgid "" "Removes the obsolete `simulation` example and renames " "`quickstart_simulation` to `simulation_tensorflow` so it fits withs the " @@ -20209,7 +20967,7 @@ msgstr "" "删除过时的 \"simulation \"示例,并将 \"quickstart_simulation \"重命名为 " "\"simulation_tensorflow\",使其与 \"simulation_pytorch \"的命名一致" -#: ../../source/ref-changelog.md:709 +#: ../../source/ref-changelog.md:801 msgid "" "**Update documentation** " "([#1223](https://github.com/adap/flower/pull/1223), " @@ -20234,7 +20992,7 @@ msgstr "" "[#1305](https://github.com/adap/flower/pull/1305), " "[#1307](https://github.com/adap/flower/pull/1307))" -#: ../../source/ref-changelog.md:711 +#: ../../source/ref-changelog.md:803 msgid "" "One substantial documentation update fixes multiple smaller rendering " "issues, makes titles more succinct to improve navigation, removes a " @@ -20247,18 +21005,18 @@ msgstr "" "`flwr.common` 模块,包含了对基于 markdown 的文档的支持,将更新日志从 `.rst` 移植到了 " "`.md`,并修复了一些较小的细节!" -#: ../../source/ref-changelog.md:713 ../../source/ref-changelog.md:768 -#: ../../source/ref-changelog.md:837 ../../source/ref-changelog.md:876 +#: ../../source/ref-changelog.md:805 ../../source/ref-changelog.md:860 +#: ../../source/ref-changelog.md:929 ../../source/ref-changelog.md:968 msgid "**Minor updates**" msgstr "**小规模更新**" -#: ../../source/ref-changelog.md:715 +#: ../../source/ref-changelog.md:807 msgid "" "Add round number to fit and evaluate log messages " "([#1266](https://github.com/adap/flower/pull/1266))" msgstr "添加四舍五入数字,以适应和评估日志信息([#1266](https://github.com/adap/flower/pull/1266))" -#: ../../source/ref-changelog.md:716 +#: ../../source/ref-changelog.md:808 msgid "" "Add secure gRPC connection to the `advanced_tensorflow` code example " "([#847](https://github.com/adap/flower/pull/847))" @@ -20266,7 +21024,7 @@ msgstr "" "为 `advanced_tensorflow` 代码示例添加安全 gRPC 连接 " "([#847](https://github.com/adap/flower/pull/847))" -#: ../../source/ref-changelog.md:717 +#: ../../source/ref-changelog.md:809 msgid "" "Update developer tooling " "([#1231](https://github.com/adap/flower/pull/1231), " @@ -20279,7 +21037,7 @@ msgstr "" "[#1301](https://github.com/adap/flower/pull/1301), " "[#1310](https://github.com/adap/flower/pull/1310)" -#: ../../source/ref-changelog.md:718 +#: ../../source/ref-changelog.md:810 msgid "" "Rename ProtoBuf messages to improve consistency " "([#1214](https://github.com/adap/flower/pull/1214), " @@ -20290,11 +21048,11 @@ msgstr "" "[#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259)" -#: ../../source/ref-changelog.md:720 +#: ../../source/ref-changelog.md:812 msgid "v0.19.0 (2022-05-18)" msgstr "v0.19.0 (2022-05-18)" -#: ../../source/ref-changelog.md:724 +#: ../../source/ref-changelog.md:816 msgid "" "**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** " "([#919](https://github.com/adap/flower/pull/919), " @@ -20306,7 +21064,7 @@ msgstr "" "[#1127](https://github.com/adap/flower/pull/1127), " "[#914](https://github.com/adap/flower/pull/914))" -#: ../../source/ref-changelog.md:726 +#: ../../source/ref-changelog.md:818 #, fuzzy msgid "" "The first preview release of Flower Baselines has arrived! We're " @@ -20323,13 +21081,13 @@ msgstr "" "baselines.html)。在首次发布预览版时,我们还邀请社区成员[贡献自己的Baselines](https://flower.ai/docs" "/contributing-baselines.html)。" -#: ../../source/ref-changelog.md:728 +#: ../../source/ref-changelog.md:820 msgid "" "**C++ client SDK (preview) and code example** " "([#1111](https://github.com/adap/flower/pull/1111))" msgstr "**C++客户端SDK(预览版)和代码示例**([#1111](https://github.com/adap/flower/pull/1111))" -#: ../../source/ref-changelog.md:730 +#: ../../source/ref-changelog.md:822 msgid "" "Preview support for Flower clients written in C++. The C++ preview " "includes a Flower client SDK and a quickstart code example that " @@ -20338,7 +21096,7 @@ msgstr "" "预览版支持用 C++ 编写的 Flower 客户端。C++ 预览版包括一个 Flower 客户端 SDK 和一个快速入门代码示例,使用 SDK " "演示了一个简单的 C++ 客户端。" -#: ../../source/ref-changelog.md:732 +#: ../../source/ref-changelog.md:824 msgid "" "**Add experimental support for Python 3.10 and Python 3.11** " "([#1135](https://github.com/adap/flower/pull/1135))" @@ -20346,7 +21104,7 @@ msgstr "" "** 增加对 Python 3.10 和 Python 3.11 的实验支持** " "([#1135](https://github.com/adap/flower/pull/1135))" -#: ../../source/ref-changelog.md:734 +#: ../../source/ref-changelog.md:826 msgid "" "Python 3.10 is the latest stable release of Python and Python 3.11 is due" " to be released in October. This Flower release adds experimental support" @@ -20355,13 +21113,13 @@ msgstr "" "Python 3.10 是 Python 的最新稳定版本,Python 3.11 将于 10 月份发布。Flower 版本增加了对这两个 " "Python 版本的实验支持。" -#: ../../source/ref-changelog.md:736 +#: ../../source/ref-changelog.md:828 msgid "" "**Aggregate custom metrics through user-provided functions** " "([#1144](https://github.com/adap/flower/pull/1144))" msgstr "**通过用户提供的函数聚合自定义指标**([#1144](https://github.com/adap/flower/pull/1144))" -#: ../../source/ref-changelog.md:738 +#: ../../source/ref-changelog.md:830 msgid "" "Custom metrics (e.g., `accuracy`) can now be aggregated without having to" " customize the strategy. Built-in strategies support two new arguments, " @@ -20371,13 +21129,13 @@ msgstr "" "现在无需定制策略即可聚合自定义度量(如`准确度`)。内置策略支持两个新参数:`fit_metrics_aggregation_fn` " "和`evaluate_metrics_aggregation_fn`,允许传递自定义度量聚合函数。" -#: ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:832 msgid "" "**User-configurable round timeout** " "([#1162](https://github.com/adap/flower/pull/1162))" msgstr "**用户可配置的回合超时**([#1162](https://github.com/adap/flower/pull/1162))" -#: ../../source/ref-changelog.md:742 +#: ../../source/ref-changelog.md:834 msgid "" "A new configuration value allows the round timeout to be set for " "`start_server` and `start_simulation`. If the `config` dictionary " @@ -20388,7 +21146,7 @@ msgstr "" "新的配置值允许为 `start_server` 和 `start_simulation` 设置回合超时。如果 `config` 字典中包含一个 " "`round_timeout` 键(以秒为单位的 `float`值),服务器将至少等待 ** `round_timeout` 秒后才关闭连接。" -#: ../../source/ref-changelog.md:744 +#: ../../source/ref-changelog.md:836 msgid "" "**Enable both federated evaluation and centralized evaluation to be used " "at the same time in all built-in strategies** " @@ -20397,7 +21155,7 @@ msgstr "" "**允许在所有内置策略中同时使用联邦评价和集中评估** " "([#1091](https://github.com/adap/flower/pull/1091))" -#: ../../source/ref-changelog.md:746 +#: ../../source/ref-changelog.md:838 msgid "" "Built-in strategies can now perform both federated evaluation (i.e., " "client-side) and centralized evaluation (i.e., server-side) in the same " @@ -20407,7 +21165,7 @@ msgstr "" "内置策略现在可以在同一轮中同时执行联邦评估(即客户端)和集中评估(即服务器端)。可以通过将 `fraction_eval` 设置为 " "`0.0`来禁用联邦评估。" -#: ../../source/ref-changelog.md:748 +#: ../../source/ref-changelog.md:840 msgid "" "**Two new Jupyter Notebook tutorials** " "([#1141](https://github.com/adap/flower/pull/1141))" @@ -20415,13 +21173,13 @@ msgstr "" "**两本新的 Jupyter Notebook 教程** " "([#1141](https://github.com/adap/flower/pull/1141))" -#: ../../source/ref-changelog.md:750 +#: ../../source/ref-changelog.md:842 msgid "" "Two Jupyter Notebook tutorials (compatible with Google Colab) explain " "basic and intermediate Flower features:" msgstr "两本 Jupyter Notebook 教程(与 Google Colab 兼容)介绍了 Flower 的基本和中级功能:" -#: ../../source/ref-changelog.md:752 +#: ../../source/ref-changelog.md:844 msgid "" "*An Introduction to Federated Learning*: [Open in " "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" @@ -20431,7 +21189,7 @@ msgstr "" "中打开](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" "-Intro-to-FL-PyTorch.ipynb)" -#: ../../source/ref-changelog.md:754 +#: ../../source/ref-changelog.md:846 msgid "" "*Using Strategies in Federated Learning*: [Open in " "Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" @@ -20441,7 +21199,7 @@ msgstr "" "中打开](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" "-Strategies-in-FL-PyTorch.ipynb)" -#: ../../source/ref-changelog.md:756 +#: ../../source/ref-changelog.md:848 msgid "" "**New FedAvgM strategy (Federated Averaging with Server Momentum)** " "([#1076](https://github.com/adap/flower/pull/1076))" @@ -20449,25 +21207,25 @@ msgstr "" "**新的 FedAvgM 策略(带服务器动量的联邦平均)** " "([#1076](https://github.com/adap/flower/pull/1076))" -#: ../../source/ref-changelog.md:758 +#: ../../source/ref-changelog.md:850 msgid "" "The new `FedAvgM` strategy implements Federated Averaging with Server " "Momentum \\[Hsu et al., 2019\\]." msgstr "新的 \"FedAvgM \"策略实现了带服务器动量的联邦平均[Hsu et al., 2019\\]." -#: ../../source/ref-changelog.md:760 +#: ../../source/ref-changelog.md:852 msgid "" "**New advanced PyTorch code example** " "([#1007](https://github.com/adap/flower/pull/1007))" msgstr "**新的 PyTorch 高级代码示例** ([#1007](https://github.com/adap/flower/pull/1007))" -#: ../../source/ref-changelog.md:762 +#: ../../source/ref-changelog.md:854 msgid "" "A new code example (`advanced_pytorch`) demonstrates advanced Flower " "concepts with PyTorch." msgstr "新代码示例 (`advanced_pytorch`) 演示了 PyTorch 的高级 Flower 概念。" -#: ../../source/ref-changelog.md:764 +#: ../../source/ref-changelog.md:856 msgid "" "**New JAX code example** " "([#906](https://github.com/adap/flower/pull/906), " @@ -20476,13 +21234,13 @@ msgstr "" "**新的 JAX 代码示例**([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143)" -#: ../../source/ref-changelog.md:766 +#: ../../source/ref-changelog.md:858 msgid "" "A new code example (`jax_from_centralized_to_federated`) shows federated " "learning with JAX and Flower." msgstr "新代码示例(`jax_from_centralized_to_federated`)展示了使用 JAX 和 Flower 的联邦学习。" -#: ../../source/ref-changelog.md:770 +#: ../../source/ref-changelog.md:862 msgid "" "New option to keep Ray running if Ray was already initialized in " "`start_simulation` ([#1177](https://github.com/adap/flower/pull/1177))" @@ -20490,7 +21248,7 @@ msgstr "" "新增选项,用于在 \"start_simulation\"(开始模拟)中已初始化 Ray 的情况下保持 Ray " "运行([#1177](https://github.com/adap/flower/pull/1177))" -#: ../../source/ref-changelog.md:771 +#: ../../source/ref-changelog.md:863 msgid "" "Add support for custom `ClientManager` as a `start_simulation` parameter " "([#1171](https://github.com/adap/flower/pull/1171))" @@ -20498,7 +21256,7 @@ msgstr "" "添加对自定义 \"客户端管理器 \"作为 \"start_simulation " "\"参数的支持([#1171](https://github.com/adap/flower/pull/1171))" -#: ../../source/ref-changelog.md:772 +#: ../../source/ref-changelog.md:864 msgid "" "New documentation for [implementing " "strategies](https://flower.ai/docs/framework/how-to-implement-" @@ -20509,13 +21267,13 @@ msgstr "" " 的新文件([#1097](https://github.com/adap/flower/pull/1097), " "[#1175](https://github.com/adap/flower/pull/1175)" -#: ../../source/ref-changelog.md:773 +#: ../../source/ref-changelog.md:865 msgid "" "New mobile-friendly documentation theme " "([#1174](https://github.com/adap/flower/pull/1174))" msgstr "新的移动友好型文档主题 ([#1174](https://github.com/adap/flower/pull/1174))" -#: ../../source/ref-changelog.md:774 +#: ../../source/ref-changelog.md:866 msgid "" "Limit version range for (optional) `ray` dependency to include only " "compatible releases (`>=1.9.2,<1.12.0`) " @@ -20524,25 +21282,25 @@ msgstr "" "限制(可选)`ray`依赖的版本范围,使其仅包含兼容版本(`>=1.9.2,<1.12.0`) " "([#1205](https://github.com/adap/flower/pull/1205))" -#: ../../source/ref-changelog.md:778 +#: ../../source/ref-changelog.md:870 msgid "" "**Remove deprecated support for Python 3.6** " "([#871](https://github.com/adap/flower/pull/871))" msgstr "**删除对 Python 3.6 的过时支持** ([#871](https://github.com/adap/flower/pull/871))" -#: ../../source/ref-changelog.md:779 +#: ../../source/ref-changelog.md:871 msgid "" "**Remove deprecated KerasClient** " "([#857](https://github.com/adap/flower/pull/857))" msgstr "**移除过时的 KerasClient**([#857](https://github.com/adap/flower/pull/857))" -#: ../../source/ref-changelog.md:780 +#: ../../source/ref-changelog.md:872 msgid "" "**Remove deprecated no-op extra installs** " "([#973](https://github.com/adap/flower/pull/973))" msgstr "**移除过时的不操作额外安装** ([#973](https://github.com/adap/flower/pull/973))" -#: ../../source/ref-changelog.md:781 +#: ../../source/ref-changelog.md:873 msgid "" "**Remove deprecated proto fields from** `FitRes` **and** `EvaluateRes` " "([#869](https://github.com/adap/flower/pull/869))" @@ -20550,7 +21308,7 @@ msgstr "" "**从** `FitRes` **和** `EvaluateRes` 中移除已废弃的 proto 字段 " "([#869](https://github.com/adap/flower/pull/869))" -#: ../../source/ref-changelog.md:782 +#: ../../source/ref-changelog.md:874 msgid "" "**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** " "([#1107](https://github.com/adap/flower/pull/1107))" @@ -20558,7 +21316,7 @@ msgstr "" "**移除过时的 QffedAvg 策略(由 QFedAvg 取代)** " "([#1107](https://github.com/adap/flower/pull/1107))" -#: ../../source/ref-changelog.md:783 +#: ../../source/ref-changelog.md:875 msgid "" "**Remove deprecated DefaultStrategy strategy** " "([#1142](https://github.com/adap/flower/pull/1142))" @@ -20566,7 +21324,7 @@ msgstr "" "**删除过时的 DefaultStrategy 策略** " "([#1142](https://github.com/adap/flower/pull/1142))" -#: ../../source/ref-changelog.md:784 +#: ../../source/ref-changelog.md:876 msgid "" "**Remove deprecated support for eval_fn accuracy return value** " "([#1142](https://github.com/adap/flower/pull/1142))" @@ -20574,7 +21332,7 @@ msgstr "" "**删除已过时的对 eval_fn 返回值准确性的支持** " "([#1142](https://github.com/adap/flower/pull/1142))" -#: ../../source/ref-changelog.md:785 +#: ../../source/ref-changelog.md:877 msgid "" "**Remove deprecated support for passing initial parameters as NumPy " "ndarrays** ([#1142](https://github.com/adap/flower/pull/1142))" @@ -20582,11 +21340,11 @@ msgstr "" "**移除对以 NumPy ndarrays 传递初始参数的过时支持** " "([#1142](https://github.com/adap/flower/pull/1142))" -#: ../../source/ref-changelog.md:787 +#: ../../source/ref-changelog.md:879 msgid "v0.18.0 (2022-02-28)" msgstr "v0.18.0 (2022-02-28)" -#: ../../source/ref-changelog.md:791 +#: ../../source/ref-changelog.md:883 msgid "" "**Improved Virtual Client Engine compatibility with Jupyter Notebook / " "Google Colab** ([#866](https://github.com/adap/flower/pull/866), " @@ -20600,7 +21358,7 @@ msgstr "" "[#833](https://github.com/adap/flower/pull/833), " "[#1036](https://github.com/adap/flower/pull/1036))" -#: ../../source/ref-changelog.md:793 +#: ../../source/ref-changelog.md:885 msgid "" "Simulations (using the Virtual Client Engine through `start_simulation`) " "now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " @@ -20611,7 +21369,7 @@ msgstr "" "`simulation` (`pip install flwr[simulation]`)后,模拟(通过 `start_simulation` " "使用虚拟客户端引擎)现在可以更流畅地运行。" -#: ../../source/ref-changelog.md:795 +#: ../../source/ref-changelog.md:887 msgid "" "**New Jupyter Notebook code example** " "([#833](https://github.com/adap/flower/pull/833))" @@ -20619,7 +21377,7 @@ msgstr "" "**新的 Jupyter Notebook 代码示例** " "([#833](https://github.com/adap/flower/pull/833))" -#: ../../source/ref-changelog.md:797 +#: ../../source/ref-changelog.md:889 msgid "" "A new code example (`quickstart_simulation`) demonstrates Flower " "simulations using the Virtual Client Engine through Jupyter Notebook " @@ -20628,25 +21386,25 @@ msgstr "" "新代码示例(`quickstart_simulation`)通过 Jupyter Notebook(包括 Google " "Colab)演示了使用虚拟客户端引擎进行 Flower 模拟。" -#: ../../source/ref-changelog.md:799 +#: ../../source/ref-changelog.md:891 msgid "" "**Client properties (feature preview)** " "([#795](https://github.com/adap/flower/pull/795))" msgstr "**客户端属性(功能预览)** ([#795](https://github.com/adap/flower/pull/795))" -#: ../../source/ref-changelog.md:801 +#: ../../source/ref-changelog.md:893 msgid "" "Clients can implement a new method `get_properties` to enable server-side" " strategies to query client properties." msgstr "客户端可以实现一个新方法 `get_properties`,以启用服务器端策略来查询客户端属性。" -#: ../../source/ref-changelog.md:803 +#: ../../source/ref-changelog.md:895 msgid "" "**Experimental Android support with TFLite** " "([#865](https://github.com/adap/flower/pull/865))" msgstr "** 使用 TFLite 实验性支持安卓系统** ([#865](https://github.com/adap/flower/pull/865))" -#: ../../source/ref-changelog.md:805 +#: ../../source/ref-changelog.md:897 msgid "" "Android support has finally arrived in `main`! Flower is both client-" "agnostic and framework-agnostic by design. One can integrate arbitrary " @@ -20656,7 +21414,7 @@ msgstr "" "`main`终于支持 Android 了!Flower 的设计与客户端和框架无关。我们可以集成任意客户端平台,有了这个版本,在安卓系统上使用 " "Flower 就变得更容易了。" -#: ../../source/ref-changelog.md:807 +#: ../../source/ref-changelog.md:899 msgid "" "The example uses TFLite on the client side, along with a new " "`FedAvgAndroid` strategy. The Android client and `FedAvgAndroid` are " @@ -20668,7 +21426,7 @@ msgstr "" "`FedAvgAndroid`仍处于试验阶段,但这是向成熟的 Android SDK 和集成了 `FedAvgAndroid`新功能的统一 " "`FedAvg`实现迈出的第一步。" -#: ../../source/ref-changelog.md:809 +#: ../../source/ref-changelog.md:901 msgid "" "**Make gRPC keepalive time user-configurable and decrease default " "keepalive time** ([#1069](https://github.com/adap/flower/pull/1069))" @@ -20676,7 +21434,7 @@ msgstr "" "**使 gRPC 保持连接时间可由用户配置,并缩短默认保持连接时间** " "([#1069](https://github.com/adap/flower/pull/1069))" -#: ../../source/ref-changelog.md:811 +#: ../../source/ref-changelog.md:903 msgid "" "The default gRPC keepalive time has been reduced to increase the " "compatibility of Flower with more cloud environments (for example, " @@ -20686,7 +21444,7 @@ msgstr "" "为提高 Flower 与更多云环境(如 Microsoft Azure)的兼容性,缩短了默认 gRPC 保持时间。用户可以根据具体要求配置 " "keepalive 时间,自定义 gRPC 堆栈。" -#: ../../source/ref-changelog.md:813 +#: ../../source/ref-changelog.md:905 msgid "" "**New differential privacy example using Opacus and PyTorch** " "([#805](https://github.com/adap/flower/pull/805))" @@ -20694,13 +21452,13 @@ msgstr "" "**使用 Opacus 和 PyTorch 的新差分隐私示例** " "([#805](https://github.com/adap/flower/pull/805))" -#: ../../source/ref-changelog.md:815 +#: ../../source/ref-changelog.md:907 msgid "" "A new code example (`opacus`) demonstrates differentially-private " "federated learning with Opacus, PyTorch, and Flower." msgstr "一个新的代码示例(\"opacus\")演示了使用 Opacus、PyTorch 和 Flower 进行差分隐私的联邦学习。" -#: ../../source/ref-changelog.md:817 +#: ../../source/ref-changelog.md:909 msgid "" "**New Hugging Face Transformers code example** " "([#863](https://github.com/adap/flower/pull/863))" @@ -20708,13 +21466,13 @@ msgstr "" "**新的Hugging Face Transformers代码示例** " "([#863](https://github.com/adap/flower/pull/863))" -#: ../../source/ref-changelog.md:819 +#: ../../source/ref-changelog.md:911 msgid "" "A new code example (`quickstart_huggingface`) demonstrates usage of " "Hugging Face Transformers with Flower." msgstr "新的代码示例(`quickstart_huggingface`)证明了结合Flower和Hugging Face Transformers的实用性。" -#: ../../source/ref-changelog.md:821 +#: ../../source/ref-changelog.md:913 msgid "" "**New MLCube code example** " "([#779](https://github.com/adap/flower/pull/779), " @@ -20727,13 +21485,13 @@ msgstr "" "[#1065](https://github.com/adap/flower/pull/1065), " "[#1090](https://github.com/adap/flower/pull/1090))" -#: ../../source/ref-changelog.md:823 +#: ../../source/ref-changelog.md:915 msgid "" "A new code example (`quickstart_mlcube`) demonstrates usage of MLCube " "with Flower." msgstr "新代码示例(\"quickstart_mlcube\")演示了 MLCube 与 Flower 的用法。" -#: ../../source/ref-changelog.md:825 +#: ../../source/ref-changelog.md:917 msgid "" "**SSL-enabled server and client** " "([#842](https://github.com/adap/flower/pull/842), " @@ -20750,14 +21508,14 @@ msgstr "" "[#993](https://github.com/adap/flower/pull/993), " "[#994](https://github.com/adap/flower/pull/994))" -#: ../../source/ref-changelog.md:827 +#: ../../source/ref-changelog.md:919 msgid "" "SSL enables secure encrypted connections between clients and servers. " "This release open-sources the Flower secure gRPC implementation to make " "encrypted communication channels accessible to all Flower users." msgstr "SSL 可实现客户端与服务器之间的安全加密连接。该版本开源了 Flower 安全 gRPC 实现,使所有 Flower 用户都能访问加密通信通道。" -#: ../../source/ref-changelog.md:829 +#: ../../source/ref-changelog.md:921 msgid "" "**Updated** `FedAdam` **and** `FedYogi` **strategies** " "([#885](https://github.com/adap/flower/pull/885), " @@ -20767,13 +21525,13 @@ msgstr "" "([#885](https://github.com/adap/flower/pull/885), " "[#895](https://github.com/adap/flower/pull/895))" -#: ../../source/ref-changelog.md:831 +#: ../../source/ref-changelog.md:923 msgid "" "`FedAdam` and `FedAdam` match the latest version of the Adaptive " "Federated Optimization paper." msgstr "FedAdam \"和 \"FedAdam \"与最新版本的 \"自适应联邦优化 \"论文相匹配。" -#: ../../source/ref-changelog.md:833 +#: ../../source/ref-changelog.md:925 msgid "" "**Initialize** `start_simulation` **with a list of client IDs** " "([#860](https://github.com/adap/flower/pull/860))" @@ -20781,7 +21539,7 @@ msgstr "" "**初始化** `start_simulation` **使用客户端 ID 列表** " "([#860](https://github.com/adap/flower/pull/860))" -#: ../../source/ref-changelog.md:835 +#: ../../source/ref-changelog.md:927 msgid "" "`start_simulation` can now be called with a list of client IDs " "(`clients_ids`, type: `List[str]`). Those IDs will be passed to the " @@ -20793,7 +21551,7 @@ msgstr "" "`start_simulation`。每当需要初始化客户端时,这些 ID 就会被传递到 `client_fn` 中,这样就能更轻松地加载无法通过 " "`int` 标识符访问的数据分区。" -#: ../../source/ref-changelog.md:839 +#: ../../source/ref-changelog.md:931 msgid "" "Update `num_examples` calculation in PyTorch code examples in " "([#909](https://github.com/adap/flower/pull/909))" @@ -20801,7 +21559,7 @@ msgstr "" "更新 PyTorch 代码示例中的 \"num_examples \"计算 " "([#909](https://github.com/adap/flower/pull/909))" -#: ../../source/ref-changelog.md:840 +#: ../../source/ref-changelog.md:932 msgid "" "Expose Flower version through `flwr.__version__` " "([#952](https://github.com/adap/flower/pull/952))" @@ -20809,7 +21567,7 @@ msgstr "" "通过 `flwr.__version__` 公开 Flower 版本 " "([#952](https://github.com/adap/flower/pull/952))" -#: ../../source/ref-changelog.md:841 +#: ../../source/ref-changelog.md:933 msgid "" "`start_server` in `app.py` now returns a `History` object containing " "metrics from training ([#974](https://github.com/adap/flower/pull/974))" @@ -20817,7 +21575,7 @@ msgstr "" "`app.py`中的 `start_server`现在会返回一个 `History` " "对象,其中包含训练中的指标([#974](https://github.com/adap/flower/pull/974))" -#: ../../source/ref-changelog.md:842 +#: ../../source/ref-changelog.md:934 msgid "" "Make `max_workers` (used by `ThreadPoolExecutor`) configurable " "([#978](https://github.com/adap/flower/pull/978))" @@ -20825,25 +21583,25 @@ msgstr "" "使 `max_workers`(由 " "`ThreadPoolExecutor`使用)可配置([#978](https://github.com/adap/flower/pull/978))" -#: ../../source/ref-changelog.md:843 +#: ../../source/ref-changelog.md:935 msgid "" "Increase sleep time after server start to three seconds in all code " "examples ([#1086](https://github.com/adap/flower/pull/1086))" msgstr "在所有代码示例中,将服务器启动后的休眠时间延长至三秒([#1086](https://github.com/adap/flower/pull/1086))" -#: ../../source/ref-changelog.md:844 +#: ../../source/ref-changelog.md:936 msgid "" "Added a new FAQ section to the documentation " "([#948](https://github.com/adap/flower/pull/948))" msgstr "在文档中添加了新的常见问题部分 ([#948](https://github.com/adap/flower/pull/948))" -#: ../../source/ref-changelog.md:845 +#: ../../source/ref-changelog.md:937 msgid "" "And many more under-the-hood changes, library updates, documentation " "changes, and tooling improvements!" msgstr "还有更多底层更改、库更新、文档更改和工具改进!" -#: ../../source/ref-changelog.md:849 +#: ../../source/ref-changelog.md:941 msgid "" "**Removed** `flwr_example` **and** `flwr_experimental` **from release " "build** ([#869](https://github.com/adap/flower/pull/869))" @@ -20851,7 +21609,7 @@ msgstr "" "**从发布版中删除**`flwr_example`**和**`flwr_experimental`** " "([#869](https://github.com/adap/flower/pull/869))" -#: ../../source/ref-changelog.md:851 +#: ../../source/ref-changelog.md:943 msgid "" "The packages `flwr_example` and `flwr_experimental` have been deprecated " "since Flower 0.12.0 and they are not longer included in Flower release " @@ -20863,11 +21621,11 @@ msgstr "" "Flower 的发布版本中。相关的额外包(`baseline`, `examples-pytorch`, `examples-" "tensorflow`, `http-logger`, `ops`)现在已不再使用,并将在即将发布的版本中移除。" -#: ../../source/ref-changelog.md:853 +#: ../../source/ref-changelog.md:945 msgid "v0.17.0 (2021-09-24)" msgstr "v0.17.0 (2021-09-24)" -#: ../../source/ref-changelog.md:857 +#: ../../source/ref-changelog.md:949 msgid "" "**Experimental virtual client engine** " "([#781](https://github.com/adap/flower/pull/781) " @@ -20878,7 +21636,7 @@ msgstr "" "[#790](https://github.com/adap/flower/pull/790) " "[#791](https://github.com/adap/flower/pull/791))" -#: ../../source/ref-changelog.md:859 +#: ../../source/ref-changelog.md:951 msgid "" "One of Flower's goals is to enable research at scale. This release " "enables a first (experimental) peek at a major new feature, codenamed the" @@ -20891,7 +21649,7 @@ msgstr "" "\"的重要新功能。虚拟客户端可以在单台机器或计算集群上对大量客户端进行模拟。测试新功能的最简单方法是查看名为 " "\"quickstart_simulation \"和 \"simulation_pytorch \"的两个新代码示例。" -#: ../../source/ref-changelog.md:861 +#: ../../source/ref-changelog.md:953 msgid "" "The feature is still experimental, so there's no stability guarantee for " "the API. It's also not quite ready for prime time and comes with a few " @@ -20901,7 +21659,7 @@ msgstr "" "该功能仍处于试验阶段,因此无法保证 API " "的稳定性。此外,它还没有完全准备好进入黄金时间,并有一些已知的注意事项。不过,我们鼓励好奇的用户尝试使用并分享他们的想法。" -#: ../../source/ref-changelog.md:863 +#: ../../source/ref-changelog.md:955 msgid "" "**New built-in strategies** " "([#828](https://github.com/adap/flower/pull/828) " @@ -20910,19 +21668,19 @@ msgstr "" "**新的内置策略**([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822)" -#: ../../source/ref-changelog.md:865 +#: ../../source/ref-changelog.md:957 msgid "" "FedYogi - Federated learning strategy using Yogi on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" msgstr "FedYogi - 在服务器端使用 Yogi 的联邦学习策略。基于 https://arxiv.org/abs/2003.00295 实现" -#: ../../source/ref-changelog.md:866 +#: ../../source/ref-changelog.md:958 msgid "" "FedAdam - Federated learning strategy using Adam on server-side. " "Implementation based on https://arxiv.org/abs/2003.00295" msgstr "FedAdam - 在服务器端使用 Adam 的联邦学习策略。基于 https://arxiv.org/abs/2003.00295 实现" -#: ../../source/ref-changelog.md:868 +#: ../../source/ref-changelog.md:960 msgid "" "**New PyTorch Lightning code example** " "([#617](https://github.com/adap/flower/pull/617))" @@ -20930,31 +21688,31 @@ msgstr "" "**新的 PyTorch Lightning 代码示例** " "([#617](https://github.com/adap/flower/pull/617))" -#: ../../source/ref-changelog.md:870 +#: ../../source/ref-changelog.md:962 msgid "" "**New Variational Auto-Encoder code example** " "([#752](https://github.com/adap/flower/pull/752))" msgstr "**新的变分自动编码器代码示例** ([#752](https://github.com/adap/flower/pull/752))" -#: ../../source/ref-changelog.md:872 +#: ../../source/ref-changelog.md:964 msgid "" "**New scikit-learn code example** " "([#748](https://github.com/adap/flower/pull/748))" msgstr "**新的 scikit-learn 代码示例** ([#748](https://github.com/adap/flower/pull/748))" -#: ../../source/ref-changelog.md:874 +#: ../../source/ref-changelog.md:966 msgid "" "**New experimental TensorBoard strategy** " "([#789](https://github.com/adap/flower/pull/789))" msgstr "**新的实验性 TensorBoard 策略**([#789](https://github.com/adap/flower/pull/789))" -#: ../../source/ref-changelog.md:878 +#: ../../source/ref-changelog.md:970 msgid "" "Improved advanced TensorFlow code example " "([#769](https://github.com/adap/flower/pull/769))" msgstr "改进的高级 TensorFlow 代码示例([#769](https://github.com/adap/flower/pull/769)" -#: ../../source/ref-changelog.md:879 +#: ../../source/ref-changelog.md:971 msgid "" "Warning when `min_available_clients` is misconfigured " "([#830](https://github.com/adap/flower/pull/830))" @@ -20962,31 +21720,31 @@ msgstr "" "当 `min_available_clients` 配置错误时发出警告 " "([#830](https://github.com/adap/flower/pull/830))" -#: ../../source/ref-changelog.md:880 +#: ../../source/ref-changelog.md:972 msgid "" "Improved gRPC server docs " "([#841](https://github.com/adap/flower/pull/841))" msgstr "改进了 gRPC 服务器文档([#841](https://github.com/adap/flower/pull/841))" -#: ../../source/ref-changelog.md:881 +#: ../../source/ref-changelog.md:973 msgid "" "Improved error message in `NumPyClient` " "([#851](https://github.com/adap/flower/pull/851))" msgstr "改进了 `NumPyClient` 中的错误信息 ([#851](https://github.com/adap/flower/pull/851))" -#: ../../source/ref-changelog.md:882 +#: ../../source/ref-changelog.md:974 msgid "" "Improved PyTorch quickstart code example " "([#852](https://github.com/adap/flower/pull/852))" msgstr "改进的 PyTorch 快速启动代码示例 ([#852](https://github.com/adap/flower/pull/852))" -#: ../../source/ref-changelog.md:886 +#: ../../source/ref-changelog.md:978 msgid "" "**Disabled final distributed evaluation** " "([#800](https://github.com/adap/flower/pull/800))" msgstr "**禁用最终分布式评价** ([#800](https://github.com/adap/flower/pull/800))" -#: ../../source/ref-changelog.md:888 +#: ../../source/ref-changelog.md:980 msgid "" "Prior behaviour was to perform a final round of distributed evaluation on" " all connected clients, which is often not required (e.g., when using " @@ -20996,13 +21754,13 @@ msgstr "" "之前的行为是在所有连接的客户端上执行最后一轮分布式评估,而这通常是不需要的(例如,在使用服务器端评估时)。可以通过向 `start_server`" " 传递 `force_final_distributed_eval=True` 来启用之前的行为。" -#: ../../source/ref-changelog.md:890 +#: ../../source/ref-changelog.md:982 msgid "" "**Renamed q-FedAvg strategy** " "([#802](https://github.com/adap/flower/pull/802))" msgstr "**更名为 q-FedAvg 策略** ([#802](https://github.com/adap/flower/pull/802))" -#: ../../source/ref-changelog.md:892 +#: ../../source/ref-changelog.md:984 msgid "" "The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect " "the notation given in the original paper (q-FFL is the optimization " @@ -21013,7 +21771,7 @@ msgstr "" "名为 `QffedAvg` 的策略已更名为 `QFedAvg`,以更好地反映原始论文中给出的符号(q-FFL 是优化目标,q-FedAvg " "是建议的求解器)。请注意,出于兼容性原因,原始(现已废弃)的 `QffedAvg` 类仍然可用(它将在未来的版本中移除)。" -#: ../../source/ref-changelog.md:894 +#: ../../source/ref-changelog.md:986 msgid "" "**Deprecated and renamed code example** `simulation_pytorch` **to** " "`simulation_pytorch_legacy` " @@ -21022,7 +21780,7 @@ msgstr "" "**删除并重命名代码示例**`simulation_pytorch`**为**`simulation_pytorch_legacy` " "([#791](https://github.com/adap/flower/pull/791))" -#: ../../source/ref-changelog.md:896 +#: ../../source/ref-changelog.md:988 msgid "" "This example has been replaced by a new example. The new example is based" " on the experimental virtual client engine, which will become the new " @@ -21033,27 +21791,27 @@ msgstr "" "该示例已被新示例取代。新示例基于试验性虚拟客户端引擎,它将成为在 Flower " "中进行大多数类型大规模模拟的新的默认方式。现有示例将作为参考保留,但将来可能会删除。" -#: ../../source/ref-changelog.md:898 +#: ../../source/ref-changelog.md:990 msgid "v0.16.0 (2021-05-11)" msgstr "v0.16.0 (2021-05-11)" -#: ../../source/ref-changelog.md:902 +#: ../../source/ref-changelog.md:994 msgid "" "**New built-in strategies** " "([#549](https://github.com/adap/flower/pull/549))" msgstr "**新的内置策略** ([#549](https://github.com/adap/flower/pull/549))" -#: ../../source/ref-changelog.md:904 +#: ../../source/ref-changelog.md:996 msgid "(abstract) FedOpt" msgstr "(摘要) FedOpt" -#: ../../source/ref-changelog.md:907 +#: ../../source/ref-changelog.md:999 msgid "" "**Custom metrics for server and strategies** " "([#717](https://github.com/adap/flower/pull/717))" msgstr "**服务器和策略的自定义指标** ([#717](https://github.com/adap/flower/pull/717))" -#: ../../source/ref-changelog.md:909 +#: ../../source/ref-changelog.md:1001 msgid "" "The Flower server is now fully task-agnostic, all remaining instances of " "task-specific metrics (such as `accuracy`) have been replaced by custom " @@ -21064,7 +21822,7 @@ msgstr "" "Flower 服务器现在完全与任务无关,所有剩余的任务特定度量(如 \"准确度\")都已被自定义度量字典取代。Flower 0.15 " "引入了从客户端向服务器传递包含自定义指标的字典的功能。从本版本开始,自定义指标将取代服务器上的特定任务指标。" -#: ../../source/ref-changelog.md:911 +#: ../../source/ref-changelog.md:1003 msgid "" "Custom metric dictionaries are now used in two user-facing APIs: they are" " returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and " @@ -21077,7 +21835,7 @@ msgstr "" "返回,还可使传递给内置策略(通过 `eval_fn`)的评估函数返回两个以上的评估度量。策略甚至可以返回 *aggregated* " "指标字典,以便服务器跟踪。" -#: ../../source/ref-changelog.md:913 +#: ../../source/ref-changelog.md:1005 msgid "" "Strategy implementations should migrate their `aggregate_fit` and " "`aggregate_evaluate` methods to the new return type (e.g., by simply " @@ -21088,19 +21846,19 @@ msgstr "" "方法迁移到新的返回类型(例如,只需返回空的 `{}`),服务器端评估函数应从 `return loss, accuracy` 迁移到 " "`return loss, {\"accuracy\": accuracy}`。" -#: ../../source/ref-changelog.md:915 +#: ../../source/ref-changelog.md:1007 msgid "" "Flower 0.15-style return types are deprecated (but still supported), " "compatibility will be removed in a future release." msgstr "Flower 0.15 风格的返回类型已被弃用(但仍受支持),兼容性将在未来的版本中移除。" -#: ../../source/ref-changelog.md:917 +#: ../../source/ref-changelog.md:1009 msgid "" "**Migration warnings for deprecated functionality** " "([#690](https://github.com/adap/flower/pull/690))" msgstr "** 过时功能的迁移警告** ([#690](https://github.com/adap/flower/pull/690))" -#: ../../source/ref-changelog.md:919 +#: ../../source/ref-changelog.md:1011 msgid "" "Earlier versions of Flower were often migrated to new APIs, while " "maintaining compatibility with legacy APIs. This release introduces " @@ -21111,7 +21869,7 @@ msgstr "" "Flower 早期版本通常会迁移到新的应用程序接口,同时保持与旧版应用程序接口的兼容。如果检测到使用了过时的 " "API,本版本将引入详细的警告信息。新的警告信息通常会详细说明如何迁移到更新的 API,从而简化从一个版本到另一个版本的过渡。" -#: ../../source/ref-changelog.md:921 +#: ../../source/ref-changelog.md:1013 msgid "" "Improved docs and docstrings " "([#691](https://github.com/adap/flower/pull/691) " @@ -21122,11 +21880,11 @@ msgstr "" "[#692](https://github.com/adap/flower/pull/692) " "[#713](https://github.com/adap/flower/pull/713))" -#: ../../source/ref-changelog.md:923 +#: ../../source/ref-changelog.md:1015 msgid "MXNet example and documentation" msgstr "MXNet 示例和文档" -#: ../../source/ref-changelog.md:925 +#: ../../source/ref-changelog.md:1017 msgid "" "FedBN implementation in example PyTorch: From Centralized To Federated " "([#696](https://github.com/adap/flower/pull/696) " @@ -21138,13 +21896,13 @@ msgstr "" "[#702](https://github.com/adap/flower/pull/702) " "[#705](https://github.com/adap/flower/pull/705))" -#: ../../source/ref-changelog.md:929 +#: ../../source/ref-changelog.md:1021 msgid "" "**Serialization-agnostic server** " "([#721](https://github.com/adap/flower/pull/721))" msgstr "**序列化无关服务器** ([#721](https://github.com/adap/flower/pull/721))" -#: ../../source/ref-changelog.md:931 +#: ../../source/ref-changelog.md:1023 msgid "" "The Flower server is now fully serialization-agnostic. Prior usage of " "class `Weights` (which represents parameters as deserialized NumPy " @@ -21158,7 +21916,7 @@ msgstr "" "`Parameters` 类取代(例如在 `Strategy`中)。参数 " "\"对象与序列化完全无关,它以字节数组的形式表示参数,\"tensor_type \"属性表示如何解释这些字节数组(例如,用于序列化/反序列化)。" -#: ../../source/ref-changelog.md:933 +#: ../../source/ref-changelog.md:1025 msgid "" "Built-in strategies implement this approach by handling serialization and" " deserialization to/from `Weights` internally. Custom/3rd-party Strategy " @@ -21170,7 +21928,7 @@ msgstr "" "内置策略通过在内部处理序列化和反序列化到/从`Weights`来实现这种方法。自定义/第三方策略实现应更新为稍有改动的策略方法定义。策略作者可查阅" " PR [#721](https://github.com/adap/flower/pull/721) 以了解如何将策略轻松迁移到新格式。" -#: ../../source/ref-changelog.md:935 +#: ../../source/ref-changelog.md:1027 msgid "" "Deprecated `flwr.server.Server.evaluate`, use " "`flwr.server.Server.evaluate_round` instead " @@ -21179,17 +21937,17 @@ msgstr "" "已弃用 `flwr.server.Server.evaluate`,改用 " "`flwr.server.Server.evaluate_round`([#717](https://github.com/adap/flower/pull/717)" -#: ../../source/ref-changelog.md:937 +#: ../../source/ref-changelog.md:1029 msgid "v0.15.0 (2021-03-12)" msgstr "v0.15.0 (2021-03-12)" -#: ../../source/ref-changelog.md:941 +#: ../../source/ref-changelog.md:1033 msgid "" "**Server-side parameter initialization** " "([#658](https://github.com/adap/flower/pull/658))" msgstr "**服务器端参数初始化** ([#658](https://github.com/adap/flower/pull/658))" -#: ../../source/ref-changelog.md:943 +#: ../../source/ref-changelog.md:1035 msgid "" "Model parameters can now be initialized on the server-side. Server-side " "parameter initialization works via a new `Strategy` method called " @@ -21198,7 +21956,7 @@ msgstr "" "现在可以在服务器端初始化模型参数。服务器端参数初始化通过名为 \"initialize_parameters \"的新 \"Strategy " "\"方法进行。" -#: ../../source/ref-changelog.md:945 +#: ../../source/ref-changelog.md:1037 msgid "" "Built-in strategies support a new constructor argument called " "`initial_parameters` to set the initial parameters. Built-in strategies " @@ -21208,7 +21966,7 @@ msgstr "" "内置策略支持名为 \"initial_parameters " "\"的新构造函数参数,用于设置初始参数。内置策略会在启动时向服务器提供这些初始参数,然后删除它们以释放内存。" -#: ../../source/ref-changelog.md:964 +#: ../../source/ref-changelog.md:1056 msgid "" "If no initial parameters are provided to the strategy, the server will " "continue to use the current behaviour (namely, it will ask one of the " @@ -21216,11 +21974,7 @@ msgid "" "parameters)." msgstr "如果没有向策略提供初始参数,服务器将继续使用当前行为(即向其中一个已连接的客户端询问参数,并将这些参数用作初始全局参数)。" -#: ../../source/ref-changelog.md:966 -msgid "Deprecations" -msgstr "停用" - -#: ../../source/ref-changelog.md:968 +#: ../../source/ref-changelog.md:1060 msgid "" "Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to " "`flwr.server.strategy.FedAvg`, which is equivalent)" @@ -21228,11 +21982,11 @@ msgstr "" "停用 `flwr.server.strategy.DefaultStrategy`(迁移到等价的 " "`flwr.server.strategy.FedAvg`)" -#: ../../source/ref-changelog.md:970 +#: ../../source/ref-changelog.md:1062 msgid "v0.14.0 (2021-02-18)" msgstr "v0.14.0 (2021-02-18)" -#: ../../source/ref-changelog.md:974 +#: ../../source/ref-changelog.md:1066 msgid "" "**Generalized** `Client.fit` **and** `Client.evaluate` **return values** " "([#610](https://github.com/adap/flower/pull/610) " @@ -21244,7 +21998,7 @@ msgstr "" "[#572](https://github.com/adap/flower/pull/572) " "[#633](https://github.com/adap/flower/pull/633))" -#: ../../source/ref-changelog.md:976 +#: ../../source/ref-changelog.md:1068 msgid "" "Clients can now return an additional dictionary mapping `str` keys to " "values of the following types: `bool`, `bytes`, `float`, `int`, `str`. " @@ -21255,7 +22009,7 @@ msgstr "" "bool`、`bytes`、`float`、`int`、`str`。这意味着我们可以从 `fit`/`evaluate` " "返回几乎任意的值,并在服务器端使用它们!" -#: ../../source/ref-changelog.md:978 +#: ../../source/ref-changelog.md:1070 msgid "" "This improvement also allowed for more consistent return types between " "`fit` and `evaluate`: `evaluate` should now return a tuple `(float, int, " @@ -21265,7 +22019,7 @@ msgstr "" "这一改进还使 `fit` 和 `evaluate` 之间的返回类型更加一致:`evaluate` 现在应返回一个元组`(float, int, " "dict)`,代表损失、示例数和一个包含特定问题任意值(如准确度)的字典。" -#: ../../source/ref-changelog.md:980 +#: ../../source/ref-changelog.md:1072 msgid "" "In case you wondered: this feature is compatible with existing projects, " "the additional dictionary return value is optional. New code should " @@ -21278,13 +22032,13 @@ msgstr "" "版本兼容(`fit`: `List[np.ndarray], int, Dict[str, Scalar]`,`evaluate`: " "`float, int, Dict[str, Scalar]`)。详见下面的示例。" -#: ../../source/ref-changelog.md:982 +#: ../../source/ref-changelog.md:1074 msgid "" "*Code example:* note the additional dictionary return values in both " "`FlwrClient.fit` and `FlwrClient.evaluate`:" msgstr "*代码示例:* 注意 `FlwrClient.fit` 和 `FlwrClient.evaluate` 中的附加字典返回值:" -#: ../../source/ref-changelog.md:997 +#: ../../source/ref-changelog.md:1089 msgid "" "**Generalized** `config` **argument in** `Client.fit` **and** " "`Client.evaluate` ([#595](https://github.com/adap/flower/pull/595))" @@ -21292,7 +22046,7 @@ msgstr "" "**在**`Client.fit` " "**和**`Client.evaluate`中泛化**`config`参数([#595](https://github.com/adap/flower/pull/595))" -#: ../../source/ref-changelog.md:999 +#: ../../source/ref-changelog.md:1091 msgid "" "The `config` argument used to be of type `Dict[str, str]`, which means " "that dictionary values were expected to be strings. The new release " @@ -21302,7 +22056,7 @@ msgstr "" "`config`参数曾是 \"字典[str, str]\"类型,这意味着字典值应是字符串。新版本将其扩展为以下类型的值: " "bool`、`bytes`、`float`、`int`、`str`。" -#: ../../source/ref-changelog.md:1001 +#: ../../source/ref-changelog.md:1093 msgid "" "This means one can now pass almost arbitrary values to `fit`/`evaluate` " "using the `config` dictionary. Yay, no more `str(epochs)` on the server-" @@ -21311,51 +22065,51 @@ msgstr "" "这意味着现在可以使用 `config` 字典向 `fit`/`evaluate` 传递几乎任意的值。耶,服务器端不再需要 " "`str(epochs)`,客户端不再需要 `int(config[\"epochs\"])`!" -#: ../../source/ref-changelog.md:1003 +#: ../../source/ref-changelog.md:1095 msgid "" "*Code example:* note that the `config` dictionary now contains non-`str` " "values in both `Client.fit` and `Client.evaluate`:" msgstr "*代码示例:* 注意 `config` 字典现在在 `Client.fit` 和 `Client.evaluate` 中都包含非 `str` 值:" -#: ../../source/ref-changelog.md:1020 +#: ../../source/ref-changelog.md:1112 msgid "v0.13.0 (2021-01-08)" msgstr "v0.13.0 (2021-01-08)" -#: ../../source/ref-changelog.md:1024 +#: ../../source/ref-changelog.md:1116 msgid "" "New example: PyTorch From Centralized To Federated " "([#549](https://github.com/adap/flower/pull/549))" msgstr "新示例: PyTorch 从集中到联邦 ([#549](https://github.com/adap/flower/pull/549))" -#: ../../source/ref-changelog.md:1025 +#: ../../source/ref-changelog.md:1117 msgid "Improved documentation" msgstr "改进文档" -#: ../../source/ref-changelog.md:1026 +#: ../../source/ref-changelog.md:1118 msgid "New documentation theme ([#551](https://github.com/adap/flower/pull/551))" msgstr "新文档主题 ([#551](https://github.com/adap/flower/pull/551))" -#: ../../source/ref-changelog.md:1027 +#: ../../source/ref-changelog.md:1119 msgid "New API reference ([#554](https://github.com/adap/flower/pull/554))" msgstr "新的 API 参考 ([#554](https://github.com/adap/flower/pull/554))" -#: ../../source/ref-changelog.md:1028 +#: ../../source/ref-changelog.md:1120 msgid "" "Updated examples documentation " "([#549](https://github.com/adap/flower/pull/549))" msgstr "更新了示例文档 ([#549](https://github.com/adap/flower/pull/549))" -#: ../../source/ref-changelog.md:1029 +#: ../../source/ref-changelog.md:1121 msgid "" "Removed obsolete documentation " "([#548](https://github.com/adap/flower/pull/548))" msgstr "删除了过时的文档 ([#548](https://github.com/adap/flower/pull/548))" -#: ../../source/ref-changelog.md:1031 +#: ../../source/ref-changelog.md:1123 msgid "Bugfix:" msgstr "错误修正:" -#: ../../source/ref-changelog.md:1033 +#: ../../source/ref-changelog.md:1125 msgid "" "`Server.fit` does not disconnect clients when finished, disconnecting the" " clients is now handled in `flwr.server.start_server` " @@ -21366,21 +22120,21 @@ msgstr "" "\"中处理的([#553](https://github.com/adap/flower/pull/553) " "[#540](https://github.com/adap/flower/issues/540))。" -#: ../../source/ref-changelog.md:1035 +#: ../../source/ref-changelog.md:1127 msgid "v0.12.0 (2020-12-07)" msgstr "v0.12.0 (2020-12-07)" -#: ../../source/ref-changelog.md:1037 ../../source/ref-changelog.md:1053 +#: ../../source/ref-changelog.md:1129 ../../source/ref-changelog.md:1145 msgid "Important changes:" msgstr "重要变更:" -#: ../../source/ref-changelog.md:1039 +#: ../../source/ref-changelog.md:1131 msgid "" "Added an example for embedded devices " "([#507](https://github.com/adap/flower/pull/507))" msgstr "添加了嵌入式设备示例 ([#507](https://github.com/adap/flower/pull/507))" -#: ../../source/ref-changelog.md:1040 +#: ../../source/ref-changelog.md:1132 msgid "" "Added a new NumPyClient (in addition to the existing KerasClient) " "([#504](https://github.com/adap/flower/pull/504) " @@ -21390,7 +22144,7 @@ msgstr "" "之外)([#504](https://github.com/adap/flower/pull/504) " "[#508](https://github.com/adap/flower/pull/508)" -#: ../../source/ref-changelog.md:1041 +#: ../../source/ref-changelog.md:1133 msgid "" "Deprecated `flwr_example` package and started to migrate examples into " "the top-level `examples` directory " @@ -21401,15 +22155,15 @@ msgstr "" "([#494](https://github.com/adap/flower/pull/494) " "[#512](https://github.com/adap/flower/pull/512))" -#: ../../source/ref-changelog.md:1043 +#: ../../source/ref-changelog.md:1135 msgid "v0.11.0 (2020-11-30)" msgstr "v0.11.0 (2020-11-30)" -#: ../../source/ref-changelog.md:1045 +#: ../../source/ref-changelog.md:1137 msgid "Incompatible changes:" msgstr "不兼容的更改:" -#: ../../source/ref-changelog.md:1047 +#: ../../source/ref-changelog.md:1139 msgid "" "Renamed strategy methods " "([#486](https://github.com/adap/flower/pull/486)) to unify the naming of " @@ -21422,23 +22176,23 @@ msgstr "" "API 的命名。其他公共方法/函数(例如 `Client` 中的每个方法,以及 `Strategy.evaluate`)不使用 `on_` " "前缀,这就是我们从 Strategy 中的四个方法中移除它的原因。迁移时,请相应地重命名以下 `Strategy` 方法:" -#: ../../source/ref-changelog.md:1048 +#: ../../source/ref-changelog.md:1140 msgid "`on_configure_evaluate` => `configure_evaluate`" msgstr "`on_configure_evaluate` => `configure_evaluate`" -#: ../../source/ref-changelog.md:1049 +#: ../../source/ref-changelog.md:1141 msgid "`on_aggregate_evaluate` => `aggregate_evaluate`" msgstr "`on_aggregate_evaluate` => `aggregate_evaluate`" -#: ../../source/ref-changelog.md:1050 +#: ../../source/ref-changelog.md:1142 msgid "`on_configure_fit` => `configure_fit`" msgstr "`on_configure_fit` => `configure_fit`" -#: ../../source/ref-changelog.md:1051 +#: ../../source/ref-changelog.md:1143 msgid "`on_aggregate_fit` => `aggregate_fit`" msgstr "`on_aggregate_fit` => `aggregate_fit`" -#: ../../source/ref-changelog.md:1055 +#: ../../source/ref-changelog.md:1147 msgid "" "Deprecated `DefaultStrategy` " "([#479](https://github.com/adap/flower/pull/479)). To migrate use " @@ -21447,13 +22201,13 @@ msgstr "" "已废弃的 `DefaultStrategy` ([#479](https://github.com/adap/flower/pull/479)) " "。迁移时请使用 `FedAvg`。" -#: ../../source/ref-changelog.md:1056 +#: ../../source/ref-changelog.md:1148 msgid "" "Simplified examples and baselines " "([#484](https://github.com/adap/flower/pull/484))." msgstr "简化示例和baselines([#484](https://github.com/adap/flower/pull/484))。" -#: ../../source/ref-changelog.md:1057 +#: ../../source/ref-changelog.md:1149 msgid "" "Removed presently unused `on_conclude_round` from strategy interface " "([#483](https://github.com/adap/flower/pull/483))." @@ -21461,7 +22215,7 @@ msgstr "" "删除了策略界面中目前未使用的 " "\"on_conclude_round\"([#483](https://github.com/adap/flower/pull/483))。" -#: ../../source/ref-changelog.md:1058 +#: ../../source/ref-changelog.md:1150 msgid "" "Set minimal Python version to 3.6.1 instead of 3.6.9 " "([#471](https://github.com/adap/flower/pull/471))." @@ -21469,7 +22223,7 @@ msgstr "" "将最小 Python 版本设为 3.6.1,而不是 3.6.9 " "([#471](https://github.com/adap/flower/pull/471))." -#: ../../source/ref-changelog.md:1059 +#: ../../source/ref-changelog.md:1151 msgid "" "Improved `Strategy` docstrings " "([#470](https://github.com/adap/flower/pull/470))." @@ -22805,11 +23559,10 @@ msgid "" "training set for each partition ID defined in the :code:`--partition-id` " "argument." msgstr "" -"在本地训练之前,我们需要加载 MNIST " -"数据集(一个用于机器学习的流行手写数字图像分类数据集),并对数据集进行 FL " -"分区。使用 \"Flower Datasets `" -"_\"可以方便地实现这一点。:code:`FederatedDataset.load_partition()` 方法为 " -":code:`--partition-id` 参数中定义的每个分区 ID 加载分区训练集。" +"在本地训练之前,我们需要加载 MNIST 数据集(一个用于机器学习的流行手写数字图像分类数据集),并对数据集进行 FL 分区。使用 " +"\"Flower Datasets " +"`_\"可以方便地实现这一点。:code:`FederatedDataset.load_partition()`" +" 方法为 :code:`--partition-id` 参数中定义的每个分区 ID 加载分区训练集。" #: ../../source/tutorial-quickstart-scikitlearn.rst:95 msgid "" @@ -23400,17 +24153,16 @@ msgid "" "XGBoost trees will be passed to the next client as an initialised model " "for next round's boosting." msgstr "" -"除了袋式聚合,我们还提供了一种循环训练方案,它以逐个客户端的方式执行 FL。在循" -"环训练方案中,每轮只有一个客户端参与训练,而不是多个客户端聚合在一起。" -"训练好的本地 XGBoost 树将传递给下一个客户端,作为下一轮提升的初始化模型。" +"除了袋式聚合,我们还提供了一种循环训练方案,它以逐个客户端的方式执行 " +"FL。在循环训练方案中,每轮只有一个客户端参与训练,而不是多个客户端聚合在一起。训练好的本地 XGBoost " +"树将传递给下一个客户端,作为下一轮提升的初始化模型。" #: ../../source/tutorial-quickstart-xgboost.rst:609 #, fuzzy msgid "" "To do this, we first customise a :code:`ClientManager` in " ":code:`server_utils.py`:" -msgstr "为此,我们首先要在 :code:`server_utils.py` 中自定义一个 " -":code:`ClientManager`:" +msgstr "为此,我们首先要在 :code:`server_utils.py` 中自定义一个 :code:`ClientManager`:" #: ../../source/tutorial-quickstart-xgboost.rst:649 #, fuzzy @@ -23422,10 +24174,9 @@ msgid "" "select only one client in given round and pass the received model to next" " client." msgstr "" -"定制的 :code:`ClientManager` 会根据连接服务器的顺序,在每轮 FL " -"中对所有可用客户端进行采样。然后,我们在 :code:`flwr.server.strategy." -"fedxgb_cyclic.py`\"中定义了一个新策略 :code:`FedXgbCyclic`,以便在给定回合中" -"按顺序只选择一个客户端,并将接收到的模型传递给下一个客户端。" +"定制的 :code:`ClientManager` 会根据连接服务器的顺序,在每轮 FL 中对所有可用客户端进行采样。然后,我们在 " +":code:`flwr.server.strategy.fedxgb_cyclic.py`\"中定义了一个新策略 " +":code:`FedXgbCyclic`,以便在给定回合中按顺序只选择一个客户端,并将接收到的模型传递给下一个客户端。" #: ../../source/tutorial-quickstart-xgboost.rst:690 #, fuzzy @@ -23434,8 +24185,8 @@ msgid "" "Instead, we just make a copy of the received client model as global model" " by overriding :code:`aggregate_fit`." msgstr "" -"与最初的 :code:`FedAvg` 不同,我们在这里不执行聚合。相反,我们只是通过覆盖 " -":code:`aggregate_fit` 将接收到的客户端模型复制为全局模型。" +"与最初的 :code:`FedAvg` 不同,我们在这里不执行聚合。相反,我们只是通过覆盖 :code:`aggregate_fit` " +"将接收到的客户端模型复制为全局模型。" #: ../../source/tutorial-quickstart-xgboost.rst:693 #, fuzzy @@ -23443,8 +24194,8 @@ msgid "" "Also, the customised :code:`configure_fit` and :code:`configure_evaluate`" " methods ensure the clients to be sequentially selected given FL round:" msgstr "" -"此外,定制的 :code:`configure_fit` 和 :code:`configure_evaluate` " -"方法可确保在 FL 轮中按顺序选择客户:" +"此外,定制的 :code:`configure_fit` 和 :code:`configure_evaluate` 方法可确保在 FL " +"轮中按顺序选择客户:" #: ../../source/tutorial-quickstart-xgboost.rst:757 msgid "Customised data partitioning" @@ -23504,9 +24255,7 @@ msgid "" "We also provide an example code (:code:`sim.py`) to use the simulation " "capabilities of Flower to simulate federated XGBoost training on either a" " single machine or a cluster of machines." -msgstr "" -"我们还提供了一个示例代码(:code:`sim.py`),用于使用 Flower " -"的模拟功能在单台机器或机器集群上模拟联合 XGBoost 训练。" +msgstr "我们还提供了一个示例代码(:code:`sim.py`),用于使用 Flower 的模拟功能在单台机器或机器集群上模拟联合 XGBoost 训练。" #: ../../source/tutorial-quickstart-xgboost.rst:866 #, fuzzy @@ -23521,8 +24270,7 @@ msgid "" "We first load the dataset and perform data partitioning, and the pre-" "processed data is stored in a :code:`list`. After the simulation begins, " "the clients won't need to pre-process their partitions again." -msgstr "我们首先加载数据集并执行数据分区,预处理后的数据存储在 :code:`list` " -"中。模拟开始后,客户端就不需要再预处理分区了。" +msgstr "我们首先加载数据集并执行数据分区,预处理后的数据存储在 :code:`list` 中。模拟开始后,客户端就不需要再预处理分区了。" #: ../../source/tutorial-quickstart-xgboost.rst:924 #, fuzzy @@ -24325,8 +25073,7 @@ msgid "" "``DataLoader``:" msgstr "" "现在,让我们从 ``flwr-datasets`` 中创建 Federated Dataset 抽象,以分割 " -"CIFAR-10。我们将为每个边缘设备创建小型训练集和测试集,并将它们分别封装到 " -"PyTorch ``DataLoader`` 中:" +"CIFAR-10。我们将为每个边缘设备创建小型训练集和测试集,并将它们分别封装到 PyTorch ``DataLoader`` 中:" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:198 #, fuzzy @@ -25069,9 +25816,8 @@ msgid "" msgstr "在机器学习中,我们有一个模型和数据。模型可以是一个神经网络(如图所示),也可以是其他东西,比如经典的线性回归。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -#, fuzzy -msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" -msgstr "|d8bf04f23d9b46d8a23cc6f4887d7873|" +msgid "|93b02017c78049bbbd5ae456dcb2c91b|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 msgid "Model and data" @@ -25085,9 +25831,8 @@ msgid "" msgstr "我们使用数据来训练模型,以完成一项有用的任务。任务可以是检测图像中的物体、转录音频或玩围棋等游戏。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -#, fuzzy -msgid "|5aa1711387d74d0f8b9c499e1a51627e|" -msgstr "|5aa1711387d74d0f8b9c499e1a51627e|" +msgid "|01471150fd5144c080a176b43e92a3ff|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 msgid "Train model using data" @@ -25107,9 +25852,8 @@ msgid "" msgstr "它源于智能手机上用户与应用程序的交互、汽车上传感器数据的收集、笔记本电脑上键盘输入的接收,或者智能扬声器上某人试着唱的歌。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -#, fuzzy -msgid "|2bc8e069228d4873804061ff4a95048c|" -msgstr "|2bc8e069228d4873804061ff4a95048c|" +msgid "|9bc21c7dbd17444a8f070c60786e3484|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 msgid "Data on a phone" @@ -25126,9 +25870,8 @@ msgstr "" "\"通常不只是一个地方,而是很多地方。它可能是多个运行同一应用程序的设备。但也可能是多个组织,都在为同一任务生成数据。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -#, fuzzy -msgid "|c258488766324dc9a6807f0e7c4fd5f4|" -msgstr "|c258488766324dc9a6807f0e7c4fd5f4|" +msgid "|3047bbce54b34099ae559963d0420d79|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 msgid "Data is on many devices" @@ -25143,9 +25886,8 @@ msgid "" msgstr "因此,要使用机器学习或任何类型的数据分析,过去使用的方法是在中央服务器上收集所有数据。这个服务器可以在数据中心的某个地方,也可以在云端的某个地方。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -#, fuzzy -msgid "|d5f962c3f4ec48529efda980868c14b0|" -msgstr "|d5f962c3f4ec48529efda980868c14b0|" +msgid "|e9f8ce948593444fb838d2f354c7ec5d|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 msgid "Central data collection" @@ -25159,9 +25901,8 @@ msgid "" msgstr "一旦所有数据都收集到一处,我们最终就可以使用机器学习算法在数据上训练我们的模型。这就是我们基本上一直依赖的机器学习方法。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -#, fuzzy -msgid "|a5eccea18d4c43a68b54b65043cabef8|" -msgstr "|a5eccea18d4c43a68b54b65043cabef8|" +msgid "|c24c1478b30e4f74839208628a842d1e|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 msgid "Central model training" @@ -25180,9 +25921,8 @@ msgid "" msgstr "我们刚刚看到的经典机器学习方法可以在某些情况下使用。很好的例子包括对假日照片进行分类或分析网络流量。在这些案例中,所有数据自然都可以在中央服务器上获得。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -#, fuzzy -msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" -msgstr "|f17662f7df2d42f68cac70a1fdeda8a7|" +msgid "|1b3613d7a58847b59e1d3180802dbc09|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 msgid "Centralized possible" @@ -25196,9 +25936,8 @@ msgid "" msgstr "但这种方法并不适用于许多其他情况。例如,集中服务器上没有数据,或者一台服务器上的数据不足以训练出一个好的模型。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -#, fuzzy -msgid "|241fc906441a4f038c625a19d30d01b2|" -msgstr "|241fc906441a4f038c625a19d30d01b2|" +msgid "|9980b5213db547d0b8024a50992b9e3f|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 msgid "Centralized impossible" @@ -25345,9 +26084,8 @@ msgid "" msgstr "我们首先在服务器上初始化模型。这与经典的集中式学习完全相同:我们随机或从先前保存的检查点初始化模型参数。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -#, fuzzy -msgid "|0aa5aa05810b44b6a835cecce28f3137|" -msgstr "|0aa5aa05810b44b6a835cecce28f3137|" +msgid "|c7afb4c92d154bfaa5e8cb9a150e17f1|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 msgid "Initialize global model" @@ -25370,9 +26108,8 @@ msgid "" msgstr "接下来,我们会将全局模型的参数发送到连接的客户端节点(如智能手机等边缘设备或企业的服务器)。这是为了确保每个参与节点都使用相同的模型参数开始本地训练。我们通常只使用几个连接节点,而不是所有节点。这样做的原因是,选择越来越多的客户端节点会导致收益递减。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -#, fuzzy -msgid "|c742940dd4bf4de09d8d0d5e8d179638|" -msgstr "|c742940dd4bf4de09d8d0d5e8d179638|" +msgid "|032eb6fed6924ac387b9f13854919196|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 msgid "Send global model" @@ -25397,9 +26134,8 @@ msgstr "" "(mini-batches)。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -#, fuzzy -msgid "|1f169ab4601a47e1a226f1628f4ebddb|" -msgstr "|1f169ab4601a47e1a226f1628f4ebddb|" +msgid "|fbf225add7fd4df5a9bf25a95597d954|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 msgid "Train on local data" @@ -25421,9 +26157,8 @@ msgid "" msgstr "经过本地训练后,每个客户节点最初收到的模型参数都会略有不同。参数之所以不同,是因为每个客户端节点的本地数据集中都有不同的数据。然后,客户端节点将这些模型更新发回服务器。它们发送的模型更新既可以是完整的模型参数,也可以只是本地训练过程中积累的梯度。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -#, fuzzy -msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" -msgstr "|12cfa9cde14440ecb8c8f6c1d7185bec|" +msgid "|7efbe3d29d8349b89594e8947e910525|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 msgid "Send model updates" @@ -25468,9 +26203,8 @@ msgstr "" " 100 个示例的 10 倍。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -#, fuzzy -msgid "|72939caf6e294b0986fee6dde96614d7|" -msgstr "|72939caf6e294b0986fee6dde96614d7|" +msgid "|329fb3c04c744eda83bb51fa444c2266|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 msgid "Aggregate model updates" @@ -25576,9 +26310,8 @@ msgstr "" "为联邦学习、分析和评估提供了一种统一的方法。它允许用户联邦化任何工作负载、任何 ML 框架和任何编程语言。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -#, fuzzy -msgid "|83a8daee45da4a98b8d6f24ae098fc50|" -msgstr "|83a8daee45da4a98b8d6f24ae098fc50|" +msgid "|c00bf2750bc24d229737a0fe1395f0fc|" +msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 msgid "" @@ -27698,3 +28431,226 @@ msgstr "" #~ msgid "|ff726bc5505e432388ee2fdd6ef420b9|" #~ msgstr "" + +#~ msgid "" +#~ "Currently, Flower provides two images, a" +#~ " ``base`` image and a ``superlink`` " +#~ "image. The base image, as the name" +#~ " suggests, contains basic dependencies that" +#~ " the SuperLink needs. This includes " +#~ "system dependencies, Python and Python " +#~ "tools. The SuperLink image is based " +#~ "on the base image, but it " +#~ "additionally installs the SuperLink using " +#~ "``pip``." +#~ msgstr "" +#~ "目前,Flower " +#~ "提供两个镜像,一个基础镜像和一个服务器镜像。不久还将推出客户端镜像。基础镜像,顾名思义,包含服务器和客户端都需要的基本依赖项。其中包括系统依赖项、Python" +#~ " 和 Python 工具。服务器镜像基于基础镜像,但它会使用 ``pip`` 额外安装" +#~ " Flower 服务器。" + +#~ msgid "``3.11``" +#~ msgstr "``1.0.0rc1``" + +#~ msgid "Defaults to ``22.04``." +#~ msgstr "默认为 ``22.04``。" + +#~ msgid "Building the SuperLink image" +#~ msgstr "启动服务器" + +#~ msgid "Defaults to ``flwr/base``." +#~ msgstr "默认为 ``flwr/server``。" + +#~ msgid "The Python version of the base image." +#~ msgstr "基础镜像的存储库名称。" + +#~ msgid "Defaults to ``py3.11``." +#~ msgstr "默认为 ``22.04``。" + +#~ msgid "Defaults to ``ubuntu22.04``." +#~ msgstr "默认为 ``py3.11-ubuntu22.04``。" + +#~ msgid "Defaults to ``flwr``." +#~ msgstr "默认为 ``flwr/server``。" + +#~ msgid "" +#~ "The name of image is ``flwr_superlink``" +#~ " and the tag ``0.1.0``. Remember that" +#~ " the build arguments as well as " +#~ "the name and tag can be adapted" +#~ " to your needs. These values serve" +#~ " as examples only." +#~ msgstr "图像名称为 ``flwr_server``,标记为 ``0.1.0``。请记住,编译参数以及名称和标记都可以根据需要进行调整。这些值仅供参考。" + +#~ msgid "Creating New Messages" +#~ msgstr "创建新信息" + +#~ msgid "" +#~ "This is a simple guide for " +#~ "creating a new type of message " +#~ "between the server and clients in " +#~ "Flower." +#~ msgstr "这是一个如何用Flower在服务器和客户端之间创建新类型的信息的简要指导。" + +#~ msgid "" +#~ "Let's suppose we have the following " +#~ "example functions in :code:`server.py` and " +#~ ":code:`numpy_client.py`..." +#~ msgstr "假设我们在脚本code:`server.py`和code:`numpy_client.py`中有以下的示例函数..." + +#~ msgid "Server's side:" +#~ msgstr "在服务器端:" + +#~ msgid "Client's side:" +#~ msgstr "在客户端:" + +#~ msgid "" +#~ "Let's now see what we need to " +#~ "implement in order to get this " +#~ "simple function between the server and" +#~ " client to work!" +#~ msgstr "现在让我们来看看,为了让服务器和客户端之间的这个简单的函数正常工作,我们需要实现哪些功能!" + +#~ msgid "Message Types for Protocol Buffers" +#~ msgstr "协议缓冲区的信息类型" + +#~ msgid "" +#~ "The first thing we need to do " +#~ "is to define a message type for" +#~ " the RPC system in :code:`transport.proto`." +#~ " Note that we have to do it " +#~ "for both the request and response " +#~ "messages. For more details on the " +#~ "syntax of proto3, please see the " +#~ "`official documentation `_." +#~ msgstr "" +#~ "我们需要做的第一件事是在脚本code:`transport.proto`中定义 RPC " +#~ "系统的消息类型。请注意,我们必须对请求信息和响应信息都这样做。有关 proto3 语法的更多详情,请参阅官方文档" +#~ " `_。" + +#~ msgid "Within the :code:`ServerMessage` block:" +#~ msgstr "在 :code:`ServerMessage` 代码块中:" + +#~ msgid "Within the ClientMessage block:" +#~ msgstr "在 ClientMessage 代码块中:" + +#~ msgid "" +#~ "Make sure to also add a field " +#~ "of the newly created message type " +#~ "in :code:`oneof msg`." +#~ msgstr "确保在 :code:`oneof msg` 中也添加一个新创建的消息类型字段。" + +#~ msgid "Once that is done, we will compile the file with:" +#~ msgstr "完成后,我们将使用:" + +#~ msgid "If it compiles successfully, you should see the following message:" +#~ msgstr "如果编译成功,你应该会看到以下信息:" + +#~ msgid "Serialization and Deserialization Functions" +#~ msgstr "序列化和反序列化函数" + +#~ msgid "" +#~ "Our next step is to add functions" +#~ " to serialize and deserialize Python " +#~ "datatypes to or from our defined " +#~ "RPC message types. You should add " +#~ "these functions in :code:`serde.py`." +#~ msgstr "" +#~ "下一步是添加函数,以便将 Python 数据类型序列化和反序列化为我们定义的 RPC " +#~ "消息类型或从我们定义的 RPC 消息类型反序列化和反序列化 Python 数据类型。您应该在" +#~ " :code:`serde.py` 中添加这些函数。" + +#~ msgid "The four functions:" +#~ msgstr "四种函数:" + +#~ msgid "Sending the Message from the Server" +#~ msgstr "从服务器发送信息" + +#~ msgid "" +#~ "Now write the request function in " +#~ "your Client Proxy class (e.g., " +#~ ":code:`grpc_client_proxy.py`) using the serde " +#~ "functions you just created:" +#~ msgstr "现在,在客户端代理类(例如 :code:`grpc_client_proxy.py`)中使用刚才创建的 serde 函数编写请求函数:" + +#~ msgid "Receiving the Message by the Client" +#~ msgstr "由客户端接收信息" + +#~ msgid "" +#~ "Last step! Modify the code in " +#~ ":code:`message_handler.py` to check the field" +#~ " of your message and call the " +#~ ":code:`example_response` function. Remember to " +#~ "use the serde functions!" +#~ msgstr "" +#~ "最后一步 修改 :code:`message_handler.py` 中的代码,检查信息的字段并调用" +#~ " :code:`example_response` 函数。记住使用 serde 函数!" + +#~ msgid "Within the handle function:" +#~ msgstr "在句柄函数内:" + +#~ msgid "And add a new function:" +#~ msgstr "并增加一个新函数:" + +#~ msgid "Hopefully, when you run your program you will get the intended result!" +#~ msgstr "希望您在运行程序时能得到预期的结果!" + +#~ msgid ":py:obj:`run_driver_api `\\ \\(\\)" +#~ msgstr ":py:obj:`run_driver_api `\\ \\(\\)" + +#~ msgid "Run Flower server (Driver API)." +#~ msgstr "flower-driver-api" + +#~ msgid ":py:obj:`run_fleet_api `\\ \\(\\)" +#~ msgstr ":py:obj:`run_fleet_api `\\ \\(\\)" + +#~ msgid "Run Flower server (Fleet API)." +#~ msgstr "Flower 服务器。" + +#~ msgid "Unreleased" +#~ msgstr "尚未发布" + +#~ msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" +#~ msgstr "|d8bf04f23d9b46d8a23cc6f4887d7873|" + +#~ msgid "|5aa1711387d74d0f8b9c499e1a51627e|" +#~ msgstr "|5aa1711387d74d0f8b9c499e1a51627e|" + +#~ msgid "|2bc8e069228d4873804061ff4a95048c|" +#~ msgstr "|2bc8e069228d4873804061ff4a95048c|" + +#~ msgid "|c258488766324dc9a6807f0e7c4fd5f4|" +#~ msgstr "|c258488766324dc9a6807f0e7c4fd5f4|" + +#~ msgid "|d5f962c3f4ec48529efda980868c14b0|" +#~ msgstr "|d5f962c3f4ec48529efda980868c14b0|" + +#~ msgid "|a5eccea18d4c43a68b54b65043cabef8|" +#~ msgstr "|a5eccea18d4c43a68b54b65043cabef8|" + +#~ msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" +#~ msgstr "|f17662f7df2d42f68cac70a1fdeda8a7|" + +#~ msgid "|241fc906441a4f038c625a19d30d01b2|" +#~ msgstr "|241fc906441a4f038c625a19d30d01b2|" + +#~ msgid "|0aa5aa05810b44b6a835cecce28f3137|" +#~ msgstr "|0aa5aa05810b44b6a835cecce28f3137|" + +#~ msgid "|c742940dd4bf4de09d8d0d5e8d179638|" +#~ msgstr "|c742940dd4bf4de09d8d0d5e8d179638|" + +#~ msgid "|1f169ab4601a47e1a226f1628f4ebddb|" +#~ msgstr "|1f169ab4601a47e1a226f1628f4ebddb|" + +#~ msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" +#~ msgstr "|12cfa9cde14440ecb8c8f6c1d7185bec|" + +#~ msgid "|72939caf6e294b0986fee6dde96614d7|" +#~ msgstr "|72939caf6e294b0986fee6dde96614d7|" + +#~ msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +#~ msgstr "|83a8daee45da4a98b8d6f24ae098fc50|" + From 64682df42168d7cfa7f4fb32bde30db0d3496855 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Mon, 17 Jun 2024 22:12:24 +0100 Subject: [PATCH 047/595] feat(framework) Update proto files for SuperExec logstream (#3622) --- src/proto/flwr/proto/exec.proto | 5 ++++ src/py/flwr/proto/exec_pb2.py | 10 +++++--- src/py/flwr/proto/exec_pb2.pyi | 23 +++++++++++++++++ src/py/flwr/proto/exec_pb2_grpc.py | 34 ++++++++++++++++++++++++++ src/py/flwr/proto/exec_pb2_grpc.pyi | 14 +++++++++++ src/py/flwr/superexec/exec_servicer.py | 13 +++++++++- 6 files changed, 95 insertions(+), 4 deletions(-) diff --git a/src/proto/flwr/proto/exec.proto b/src/proto/flwr/proto/exec.proto index 05885c9ceed3..8e5f53b02ca8 100644 --- a/src/proto/flwr/proto/exec.proto +++ b/src/proto/flwr/proto/exec.proto @@ -20,7 +20,12 @@ package flwr.proto; service Exec { // Start run upon request rpc StartRun(StartRunRequest) returns (StartRunResponse) {} + + // Start log stream upon request + rpc StreamLogs(StreamLogsRequest) returns (stream StreamLogsResponse) {} } message StartRunRequest { bytes fab_file = 1; } message StartRunResponse { sint64 run_id = 1; } +message StreamLogsRequest { sint64 run_id = 1; } +message StreamLogsResponse { string log_output = 1; } diff --git a/src/py/flwr/proto/exec_pb2.py b/src/py/flwr/proto/exec_pb2.py index a1d1f24af7d0..7b037a9454c0 100644 --- a/src/py/flwr/proto/exec_pb2.py +++ b/src/py/flwr/proto/exec_pb2.py @@ -14,7 +14,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x32O\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -25,6 +25,10 @@ _globals['_STARTRUNREQUEST']._serialized_end=72 _globals['_STARTRUNRESPONSE']._serialized_start=74 _globals['_STARTRUNRESPONSE']._serialized_end=108 - _globals['_EXEC']._serialized_start=110 - _globals['_EXEC']._serialized_end=189 + _globals['_STREAMLOGSREQUEST']._serialized_start=110 + _globals['_STREAMLOGSREQUEST']._serialized_end=145 + _globals['_STREAMLOGSRESPONSE']._serialized_start=147 + _globals['_STREAMLOGSRESPONSE']._serialized_end=187 + _globals['_EXEC']._serialized_start=190 + _globals['_EXEC']._serialized_end=350 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/exec_pb2.pyi b/src/py/flwr/proto/exec_pb2.pyi index 8a0122062dcf..466812808da8 100644 --- a/src/py/flwr/proto/exec_pb2.pyi +++ b/src/py/flwr/proto/exec_pb2.pyi @@ -5,6 +5,7 @@ isort:skip_file import builtins import google.protobuf.descriptor import google.protobuf.message +import typing import typing_extensions DESCRIPTOR: google.protobuf.descriptor.FileDescriptor @@ -30,3 +31,25 @@ class StartRunResponse(google.protobuf.message.Message): ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ... global___StartRunResponse = StartRunResponse + +class StreamLogsRequest(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_ID_FIELD_NUMBER: builtins.int + run_id: builtins.int + def __init__(self, + *, + run_id: builtins.int = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ... +global___StreamLogsRequest = StreamLogsRequest + +class StreamLogsResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + LOG_OUTPUT_FIELD_NUMBER: builtins.int + log_output: typing.Text + def __init__(self, + *, + log_output: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["log_output",b"log_output"]) -> None: ... +global___StreamLogsResponse = StreamLogsResponse diff --git a/src/py/flwr/proto/exec_pb2_grpc.py b/src/py/flwr/proto/exec_pb2_grpc.py index 349148eb9926..8cf4ce52a300 100644 --- a/src/py/flwr/proto/exec_pb2_grpc.py +++ b/src/py/flwr/proto/exec_pb2_grpc.py @@ -19,6 +19,11 @@ def __init__(self, channel): request_serializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.SerializeToString, response_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString, ) + self.StreamLogs = channel.unary_stream( + '/flwr.proto.Exec/StreamLogs', + request_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString, + ) class ExecServicer(object): @@ -31,6 +36,13 @@ def StartRun(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def StreamLogs(self, request, context): + """Start log stream upon request + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_ExecServicer_to_server(servicer, server): rpc_method_handlers = { @@ -39,6 +51,11 @@ def add_ExecServicer_to_server(servicer, server): request_deserializer=flwr_dot_proto_dot_exec__pb2.StartRunRequest.FromString, response_serializer=flwr_dot_proto_dot_exec__pb2.StartRunResponse.SerializeToString, ), + 'StreamLogs': grpc.unary_stream_rpc_method_handler( + servicer.StreamLogs, + request_deserializer=flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.FromString, + response_serializer=flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'flwr.proto.Exec', rpc_method_handlers) @@ -65,3 +82,20 @@ def StartRun(request, flwr_dot_proto_dot_exec__pb2.StartRunResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def StreamLogs(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_stream(request, target, '/flwr.proto.Exec/StreamLogs', + flwr_dot_proto_dot_exec__pb2.StreamLogsRequest.SerializeToString, + flwr_dot_proto_dot_exec__pb2.StreamLogsResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/exec_pb2_grpc.pyi b/src/py/flwr/proto/exec_pb2_grpc.pyi index 6cab594babd9..20da3a53f4a8 100644 --- a/src/py/flwr/proto/exec_pb2_grpc.pyi +++ b/src/py/flwr/proto/exec_pb2_grpc.pyi @@ -5,6 +5,7 @@ isort:skip_file import abc import flwr.proto.exec_pb2 import grpc +import typing class ExecStub: def __init__(self, channel: grpc.Channel) -> None: ... @@ -13,6 +14,11 @@ class ExecStub: flwr.proto.exec_pb2.StartRunResponse] """Start run upon request""" + StreamLogs: grpc.UnaryStreamMultiCallable[ + flwr.proto.exec_pb2.StreamLogsRequest, + flwr.proto.exec_pb2.StreamLogsResponse] + """Start log stream upon request""" + class ExecServicer(metaclass=abc.ABCMeta): @abc.abstractmethod @@ -23,5 +29,13 @@ class ExecServicer(metaclass=abc.ABCMeta): """Start run upon request""" pass + @abc.abstractmethod + def StreamLogs(self, + request: flwr.proto.exec_pb2.StreamLogsRequest, + context: grpc.ServicerContext, + ) -> typing.Iterator[flwr.proto.exec_pb2.StreamLogsResponse]: + """Start log stream upon request""" + pass + def add_ExecServicer_to_server(servicer: ExecServicer, server: grpc.Server) -> None: ... diff --git a/src/py/flwr/superexec/exec_servicer.py b/src/py/flwr/superexec/exec_servicer.py index aa8172c18704..e5ef2bd59a79 100644 --- a/src/py/flwr/superexec/exec_servicer.py +++ b/src/py/flwr/superexec/exec_servicer.py @@ -16,7 +16,7 @@ from logging import ERROR, INFO -from typing import Dict +from typing import Any, Dict, Generator import grpc @@ -25,6 +25,8 @@ from flwr.proto.exec_pb2 import ( # pylint: disable=E0611 StartRunRequest, StartRunResponse, + StreamLogsRequest, + StreamLogsResponse, ) from .executor import Executor, RunTracker @@ -52,3 +54,12 @@ def StartRun( self.runs[run.run_id] = run return StartRunResponse(run_id=run.run_id) + + def StreamLogs( + self, request: StreamLogsRequest, context: grpc.ServicerContext + ) -> Generator[StreamLogsResponse, Any, None]: + """Get logs.""" + logs = ["a", "b", "c"] + while context.is_active(): + for i in range(len(logs)): # pylint: disable=C0200 + yield StreamLogsResponse(log_output=logs[i]) From 461ed3945cc1f66427e1910b77cb372e942a090c Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 18 Jun 2024 16:35:13 +0200 Subject: [PATCH 048/595] ci(*:skip) Simplify nightly release CI (#3599) Signed-off-by: Robert Steiner Co-authored-by: Taner Topal --- .github/workflows/release-nightly.yml | 13 ++++--------- dev/publish-nightly.sh | 8 ++------ 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 939a9581871c..2b72190bede5 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -3,7 +3,6 @@ name: Release nightly on: schedule: - cron: "0 23 * * *" - - cron: "30 23 * * *" env: FLWR_TELEMETRY_ENABLED: 0 @@ -25,15 +24,11 @@ jobs: id: bootstrap uses: ./.github/actions/bootstrap - name: Release nightly - if: github.event.schedule == '0 23 * * *' + id: release env: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - run: ./dev/publish-nightly.sh - - name: Read nightly version and name - if: github.event.schedule == '30 23 * * *' - id: release run: | - RESULT=$(./dev/publish-nightly.sh --skip-publish) + RESULT=$(./dev/publish-nightly.sh) if [ "$RESULT" == "There were no commits in the last 24 hours." ]; then echo "skip=true" >> $GITHUB_OUTPUT fi @@ -45,7 +40,7 @@ jobs: build-docker-base-images: name: Build nightly base images - if: github.repository == 'adap/flower' && needs.release-nightly.outputs.skip != 'true' && github.event.schedule == '30 23 * * *' + if: github.repository == 'adap/flower' && needs.release-nightly.outputs.skip != 'true' uses: ./.github/workflows/_docker-build.yml needs: release-nightly with: @@ -65,7 +60,7 @@ jobs: build-docker-binary-images: name: Build nightly binary images - if: github.repository == 'adap/flower' && needs.release-nightly.outputs.skip != 'true' && github.event.schedule == '30 23 * * *' + if: github.repository == 'adap/flower' && needs.release-nightly.outputs.skip != 'true' uses: ./.github/workflows/_docker-build.yml needs: [release-nightly, build-docker-base-images] strategy: diff --git a/dev/publish-nightly.sh b/dev/publish-nightly.sh index a42af1f17cfc..0c03cdda9f49 100755 --- a/dev/publish-nightly.sh +++ b/dev/publish-nightly.sh @@ -24,16 +24,12 @@ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../ # The version name in the pyproject.toml will be appended with "-dev" and the current date. # The result will be a release on PyPi of the package "flwr-nightly" of version e.g. # "0.1.1.dev20200716" as seen at https://pypi.org/project/flwr-nightly/ -# If the script is called with the flag `--skip-publish`, the name and version are changed -# in the pyproject.toml but the package won't be published. if [[ $(git log --since="24 hours ago" --pretty=oneline) ]]; then sed -i -E "s/^name = \"(.+)\"/name = \"\1-nightly\"/" pyproject.toml sed -i -E "s/^version = \"(.+)\"/version = \"\1.dev$(date '+%Y%m%d')\"/" pyproject.toml - if [ "$1" != "--skip-publish" ]; then - python -m poetry build - python -m poetry publish -u __token__ -p $PYPI_TOKEN - fi + python -m poetry build + python -m poetry publish -u __token__ -p $PYPI_TOKEN else echo "There were no commits in the last 24 hours." fi From 0e007b42cb45fef10baa099f62669c1e9bc4fc94 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Tue, 18 Jun 2024 16:47:07 +0100 Subject: [PATCH 049/595] refactor(framework:skip) Introduce `Run` class and update `State` accordingly (#3639) --- src/py/flwr/common/typing.py | 9 +++++++++ .../fleet/message_handler/message_handler.py | 6 +++--- .../flwr/server/superlink/fleet/vce/vce_api_test.py | 3 ++- .../flwr/server/superlink/state/in_memory_state.py | 13 ++++++++----- src/py/flwr/server/superlink/state/sqlite_state.py | 9 ++++++--- src/py/flwr/server/superlink/state/state.py | 9 +++++---- src/py/flwr/server/superlink/state/state_test.py | 9 +++++---- 7 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/py/flwr/common/typing.py b/src/py/flwr/common/typing.py index d6b2ec9b158c..f51830955679 100644 --- a/src/py/flwr/common/typing.py +++ b/src/py/flwr/common/typing.py @@ -185,3 +185,12 @@ class ClientMessage: get_parameters_res: Optional[GetParametersRes] = None fit_res: Optional[FitRes] = None evaluate_res: Optional[EvaluateRes] = None + + +@dataclass +class Run: + """Run details.""" + + run_id: int + fab_id: str + fab_version: str diff --git a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py index 4c796502436b..dceb18cab453 100644 --- a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py +++ b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py @@ -112,6 +112,6 @@ def get_run( request: GetRunRequest, state: State # pylint: disable=W0613 ) -> GetRunResponse: """Get run information.""" - run_id, fab_id, fab_version = state.get_run(request.run_id) - run = Run(run_id=run_id, fab_id=fab_id, fab_version=fab_version) - return GetRunResponse(run=run) + run = state.get_run(request.run_id) + run_proto = None if run is None else Run(**vars(run)) + return GetRunResponse(run=run_proto) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py index 1da726f88f1e..df9f2cc96f95 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py @@ -37,6 +37,7 @@ ) from flwr.common.recordset_compat import getpropertiesins_to_recordset from flwr.common.serde import message_from_taskres, message_to_taskins +from flwr.common.typing import Run from flwr.server.superlink.fleet.vce.vce_api import ( NodeToPartitionMapping, _register_nodes, @@ -81,7 +82,7 @@ def register_messages_into_state( ) -> Dict[UUID, float]: """Register `num_messages` into the state factory.""" state: InMemoryState = state_factory.state() # type: ignore - state.run_ids[run_id] = ("Mock/mock", "v1.0.0") + state.run_ids[run_id] = Run(run_id=run_id, fab_id="Mock/mock", fab_version="v1.0.0") # Artificially add TaskIns to state so they can be processed # by the Simulation Engine logic nodes_cycle = cycle(nodes_mapping.keys()) # we have more messages than supernodes diff --git a/src/py/flwr/server/superlink/state/in_memory_state.py b/src/py/flwr/server/superlink/state/in_memory_state.py index f86bf79d9dfa..e03260355db9 100644 --- a/src/py/flwr/server/superlink/state/in_memory_state.py +++ b/src/py/flwr/server/superlink/state/in_memory_state.py @@ -23,6 +23,7 @@ from uuid import UUID, uuid4 from flwr.common import log, now +from flwr.common.typing import Run from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 from flwr.server.superlink.state.state import State from flwr.server.utils import validate_task_ins_or_res @@ -40,7 +41,7 @@ def __init__(self) -> None: self.public_key_to_node_id: Dict[bytes, int] = {} # Map run_id to (fab_id, fab_version) - self.run_ids: Dict[int, Tuple[str, str]] = {} + self.run_ids: Dict[int, Run] = {} self.task_ins_store: Dict[UUID, TaskIns] = {} self.task_res_store: Dict[UUID, TaskRes] = {} @@ -281,7 +282,9 @@ def create_run(self, fab_id: str, fab_version: str) -> int: run_id: int = int.from_bytes(os.urandom(8), "little", signed=True) if run_id not in self.run_ids: - self.run_ids[run_id] = (fab_id, fab_version) + self.run_ids[run_id] = Run( + run_id=run_id, fab_id=fab_id, fab_version=fab_version + ) return run_id log(ERROR, "Unexpected run creation failure.") return 0 @@ -319,13 +322,13 @@ def get_client_public_keys(self) -> Set[bytes]: """Retrieve all currently stored `client_public_keys` as a set.""" return self.client_public_keys - def get_run(self, run_id: int) -> Tuple[int, str, str]: + def get_run(self, run_id: int) -> Optional[Run]: """Retrieve information about the run with the specified `run_id`.""" with self.lock: if run_id not in self.run_ids: log(ERROR, "`run_id` is invalid") - return 0, "", "" - return run_id, *self.run_ids[run_id] + return None + return self.run_ids[run_id] def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool: """Acknowledge a ping received from a node, serving as a heartbeat.""" diff --git a/src/py/flwr/server/superlink/state/sqlite_state.py b/src/py/flwr/server/superlink/state/sqlite_state.py index acf2054f08b6..b9672757b0e6 100644 --- a/src/py/flwr/server/superlink/state/sqlite_state.py +++ b/src/py/flwr/server/superlink/state/sqlite_state.py @@ -24,6 +24,7 @@ from uuid import UUID, uuid4 from flwr.common import log, now +from flwr.common.typing import Run from flwr.proto.node_pb2 import Node # pylint: disable=E0611 from flwr.proto.recordset_pb2 import RecordSet # pylint: disable=E0611 from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611 @@ -680,15 +681,17 @@ def get_client_public_keys(self) -> Set[bytes]: result: Set[bytes] = {row["public_key"] for row in rows} return result - def get_run(self, run_id: int) -> Tuple[int, str, str]: + def get_run(self, run_id: int) -> Optional[Run]: """Retrieve information about the run with the specified `run_id`.""" query = "SELECT * FROM run WHERE run_id = ?;" try: row = self.query(query, (run_id,))[0] - return run_id, row["fab_id"], row["fab_version"] + return Run( + run_id=run_id, fab_id=row["fab_id"], fab_version=row["fab_version"] + ) except sqlite3.IntegrityError: log(ERROR, "`run_id` does not exist.") - return 0, "", "" + return None def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool: """Acknowledge a ping received from a node, serving as a heartbeat.""" diff --git a/src/py/flwr/server/superlink/state/state.py b/src/py/flwr/server/superlink/state/state.py index a72062f2a938..d1fc9465c9f2 100644 --- a/src/py/flwr/server/superlink/state/state.py +++ b/src/py/flwr/server/superlink/state/state.py @@ -16,9 +16,10 @@ import abc -from typing import List, Optional, Set, Tuple +from typing import List, Optional, Set from uuid import UUID +from flwr.common.typing import Run from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 @@ -160,7 +161,7 @@ def create_run(self, fab_id: str, fab_version: str) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" @abc.abstractmethod - def get_run(self, run_id: int) -> Tuple[int, str, str]: + def get_run(self, run_id: int) -> Optional[Run]: """Retrieve information about the run with the specified `run_id`. Parameters @@ -170,8 +171,8 @@ def get_run(self, run_id: int) -> Tuple[int, str, str]: Returns ------- - Tuple[int, str, str] - A tuple containing three elements: + Optional[Run] + A dataclass instance containing three elements if `run_id` is valid: - `run_id`: The identifier of the run, same as the specified `run_id`. - `fab_id`: The identifier of the FAB used in the specified run. - `fab_version`: The version of the FAB used in the specified run. diff --git a/src/py/flwr/server/superlink/state/state_test.py b/src/py/flwr/server/superlink/state/state_test.py index 9b0153ca548a..81307d938400 100644 --- a/src/py/flwr/server/superlink/state/state_test.py +++ b/src/py/flwr/server/superlink/state/state_test.py @@ -55,12 +55,13 @@ def test_create_and_get_run(self) -> None: run_id = state.create_run("Mock/mock", "v1.0.0") # Execute - actual_run_id, fab_id, fab_version = state.get_run(run_id) + run = state.get_run(run_id) # Assert - assert actual_run_id == run_id - assert fab_id == "Mock/mock" - assert fab_version == "v1.0.0" + assert run is not None + assert run.run_id == run_id + assert run.fab_id == "Mock/mock" + assert run.fab_version == "v1.0.0" def test_get_task_ins_empty(self) -> None: """Validate that a new state has no TaskIns.""" From 2cf54b0c574fa6344222a0ceaba489c699a3953f Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Tue, 18 Jun 2024 21:14:41 +0100 Subject: [PATCH 050/595] feat(framework) Add `GrpcAdapter` class (#3536) --- .../client/grpc_adapter_client/__init__.py | 15 ++ .../client/grpc_adapter_client/connection.py | 94 +++++++++++++ src/py/flwr/client/grpc_client/connection.py | 6 +- .../client/grpc_rere_client/connection.py | 9 +- .../client/grpc_rere_client/grpc_adapter.py | 133 ++++++++++++++++++ .../grpc_rere_client/grpc_adapter_test.py | 38 +++++ src/py/flwr/client/rest_client/connection.py | 10 +- src/py/flwr/common/constant.py | 4 + 8 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 src/py/flwr/client/grpc_adapter_client/__init__.py create mode 100644 src/py/flwr/client/grpc_adapter_client/connection.py create mode 100644 src/py/flwr/client/grpc_rere_client/grpc_adapter.py create mode 100644 src/py/flwr/client/grpc_rere_client/grpc_adapter_test.py diff --git a/src/py/flwr/client/grpc_adapter_client/__init__.py b/src/py/flwr/client/grpc_adapter_client/__init__.py new file mode 100644 index 000000000000..5900e2dc2d06 --- /dev/null +++ b/src/py/flwr/client/grpc_adapter_client/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Client-side part of the GrpcAdapter transport layer.""" diff --git a/src/py/flwr/client/grpc_adapter_client/connection.py b/src/py/flwr/client/grpc_adapter_client/connection.py new file mode 100644 index 000000000000..e4e32b3accd0 --- /dev/null +++ b/src/py/flwr/client/grpc_adapter_client/connection.py @@ -0,0 +1,94 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Contextmanager for a GrpcAdapter channel to the Flower server.""" + + +from contextlib import contextmanager +from logging import ERROR +from typing import Callable, Iterator, Optional, Tuple, Union + +from cryptography.hazmat.primitives.asymmetric import ec + +from flwr.client.grpc_rere_client.connection import grpc_request_response +from flwr.client.grpc_rere_client.grpc_adapter import GrpcAdapter +from flwr.common import GRPC_MAX_MESSAGE_LENGTH +from flwr.common.logger import log +from flwr.common.message import Message +from flwr.common.retry_invoker import RetryInvoker + + +@contextmanager +def grpc_adapter( # pylint: disable=R0913 + server_address: str, + insecure: bool, + retry_invoker: RetryInvoker, + max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, # pylint: disable=W0613 + root_certificates: Optional[Union[bytes, str]] = None, + authentication_keys: Optional[ # pylint: disable=unused-argument + Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] + ] = None, +) -> Iterator[ + Tuple[ + Callable[[], Optional[Message]], + Callable[[Message], None], + Optional[Callable[[], None]], + Optional[Callable[[], None]], + Optional[Callable[[int], Tuple[str, str]]], + ] +]: + """Primitives for request/response-based interaction with a server via GrpcAdapter. + + Parameters + ---------- + server_address : str + The IPv6 address of the server with `http://` or `https://`. + If the Flower server runs on the same machine + on port 8080, then `server_address` would be `"http://[::]:8080"`. + insecure : bool + Starts an insecure gRPC connection when True. Enables HTTPS connection + when False, using system certificates if `root_certificates` is None. + retry_invoker: RetryInvoker + `RetryInvoker` object that will try to reconnect the client to the server + after gRPC errors. If None, the client will only try to + reconnect once after a failure. + max_message_length : int + Ignored, only present to preserve API-compatibility. + root_certificates : Optional[Union[bytes, str]] (default: None) + Path of the root certificate. If provided, a secure + connection using the certificates will be established to an SSL-enabled + Flower server. Bytes won't work for the REST API. + authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None) + Client authentication is not supported for this transport type. + + Returns + ------- + receive : Callable + send : Callable + create_node : Optional[Callable] + delete_node : Optional[Callable] + get_run : Optional[Callable] + """ + if authentication_keys is not None: + log(ERROR, "Client authentication is not supported for this transport type.") + with grpc_request_response( + server_address=server_address, + insecure=insecure, + retry_invoker=retry_invoker, + max_message_length=max_message_length, + root_certificates=root_certificates, + authentication_keys=None, # Authentication is not supported + adapter_cls=GrpcAdapter, + ) as conn: + yield conn diff --git a/src/py/flwr/client/grpc_client/connection.py b/src/py/flwr/client/grpc_client/connection.py index 6e5227cf5e5f..8c049861c672 100644 --- a/src/py/flwr/client/grpc_client/connection.py +++ b/src/py/flwr/client/grpc_client/connection.py @@ -17,7 +17,7 @@ import uuid from contextlib import contextmanager -from logging import DEBUG +from logging import DEBUG, ERROR from pathlib import Path from queue import Queue from typing import Callable, Iterator, Optional, Tuple, Union, cast @@ -101,6 +101,8 @@ def grpc_connection( # pylint: disable=R0913, R0915 The PEM-encoded root certificates as a byte string or a path string. If provided, a secure connection using the certificates will be established to an SSL-enabled Flower server. + authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None) + Client authentication is not supported for this transport type. Returns ------- @@ -123,6 +125,8 @@ def grpc_connection( # pylint: disable=R0913, R0915 """ if isinstance(root_certificates, str): root_certificates = Path(root_certificates).read_bytes() + if authentication_keys is not None: + log(ERROR, "Client authentication is not supported for this transport type.") channel = create_channel( server_address=server_address, diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index 52d0cc58b2bb..b1c268d51d55 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -55,6 +55,7 @@ from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611 from .client_interceptor import AuthenticateClientInterceptor +from .grpc_adapter import GrpcAdapter def on_channel_state_change(channel_connectivity: str) -> None: @@ -72,7 +73,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915 authentication_keys: Optional[ Tuple[ec.EllipticCurvePrivateKey, ec.EllipticCurvePublicKey] ] = None, - adapter_cls: Optional[Type[FleetStub]] = None, + adapter_cls: Optional[Union[Type[FleetStub], Type[GrpcAdapter]]] = None, ) -> Iterator[ Tuple[ Callable[[], Optional[Message]], @@ -106,6 +107,11 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915 Path of the root certificate. If provided, a secure connection using the certificates will be established to an SSL-enabled Flower server. Bytes won't work for the REST API. + authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None) + Tuple containing the elliptic curve private key and public key for + authentication from the cryptography library. + Source: https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/ + Used to establish an authenticated connection with the server. Returns ------- @@ -113,6 +119,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915 send : Callable create_node : Optional[Callable] delete_node : Optional[Callable] + get_run : Optional[Callable] """ if isinstance(root_certificates, str): root_certificates = Path(root_certificates).read_bytes() diff --git a/src/py/flwr/client/grpc_rere_client/grpc_adapter.py b/src/py/flwr/client/grpc_rere_client/grpc_adapter.py new file mode 100644 index 000000000000..77c3d601020d --- /dev/null +++ b/src/py/flwr/client/grpc_rere_client/grpc_adapter.py @@ -0,0 +1,133 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""GrpcAdapter implementation.""" + + +import sys +from logging import DEBUG +from typing import Any, Type, TypeVar, cast + +import grpc +from google.protobuf.message import Message as GrpcMessage + +from flwr.common import log +from flwr.common.constant import ( + GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY, + GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY, +) +from flwr.common.version import package_version +from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 + CreateNodeRequest, + CreateNodeResponse, + DeleteNodeRequest, + DeleteNodeResponse, + PingRequest, + PingResponse, + PullTaskInsRequest, + PullTaskInsResponse, + PushTaskResRequest, + PushTaskResResponse, +) +from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611 +from flwr.proto.grpcadapter_pb2_grpc import GrpcAdapterStub +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 + +T = TypeVar("T", bound=GrpcMessage) + + +class GrpcAdapter: + """Adapter class to send and receive gRPC messages via the ``GrpcAdapterStub``. + + This class utilizes the ``GrpcAdapterStub`` to send and receive gRPC messages + which are defined and used by the Fleet API, as defined in ``fleet.proto``. + """ + + def __init__(self, channel: grpc.Channel) -> None: + self.stub = GrpcAdapterStub(channel) + + def _send_and_receive( + self, request: GrpcMessage, response_type: Type[T], **kwargs: Any + ) -> T: + # Serialize request + container_req = MessageContainer( + metadata={GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY: package_version}, + grpc_message_name=request.__class__.__qualname__, + grpc_message_content=request.SerializeToString(), + ) + + # Send via the stub + container_res = cast( + MessageContainer, self.stub.SendReceive(container_req, **kwargs) + ) + + # Handle control message + should_exit = ( + container_res.metadata.get(GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY, "false") + == "true" + ) + if should_exit: + log( + DEBUG, + 'Received shutdown signal: exit flag is set to ``"true"``. Exiting...', + ) + sys.exit(0) + + # Check the grpc_message_name of the response + if container_res.grpc_message_name != response_type.__qualname__: + raise ValueError( + f"Invalid grpc_message_name. Expected {response_type.__qualname__}" + f", but got {container_res.grpc_message_name}." + ) + + # Deserialize response + response = response_type() + response.ParseFromString(container_res.grpc_message_content) + return response + + def CreateNode( # pylint: disable=C0103 + self, request: CreateNodeRequest, **kwargs: Any + ) -> CreateNodeResponse: + """.""" + return self._send_and_receive(request, CreateNodeResponse, **kwargs) + + def DeleteNode( # pylint: disable=C0103 + self, request: DeleteNodeRequest, **kwargs: Any + ) -> DeleteNodeResponse: + """.""" + return self._send_and_receive(request, DeleteNodeResponse, **kwargs) + + def Ping( # pylint: disable=C0103 + self, request: PingRequest, **kwargs: Any + ) -> PingResponse: + """.""" + return self._send_and_receive(request, PingResponse, **kwargs) + + def PullTaskIns( # pylint: disable=C0103 + self, request: PullTaskInsRequest, **kwargs: Any + ) -> PullTaskInsResponse: + """.""" + return self._send_and_receive(request, PullTaskInsResponse, **kwargs) + + def PushTaskRes( # pylint: disable=C0103 + self, request: PushTaskResRequest, **kwargs: Any + ) -> PushTaskResResponse: + """.""" + return self._send_and_receive(request, PushTaskResResponse, **kwargs) + + def GetRun( # pylint: disable=C0103 + self, request: GetRunRequest, **kwargs: Any + ) -> GetRunResponse: + """.""" + return self._send_and_receive(request, GetRunResponse, **kwargs) diff --git a/src/py/flwr/client/grpc_rere_client/grpc_adapter_test.py b/src/py/flwr/client/grpc_rere_client/grpc_adapter_test.py new file mode 100644 index 000000000000..e62111e084bc --- /dev/null +++ b/src/py/flwr/client/grpc_rere_client/grpc_adapter_test.py @@ -0,0 +1,38 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Tests for the GrpcAdapter class.""" + + +import inspect + +from flwr.proto.fleet_pb2_grpc import FleetServicer + +from .grpc_adapter import GrpcAdapter + + +def test_grpc_adapter_methods() -> None: + """Test if GrpcAdapter implements all required methods.""" + # Prepare + methods = { + name for name, ref in inspect.getmembers(GrpcAdapter) if inspect.isfunction(ref) + } + expected_methods = { + name + for name, ref in inspect.getmembers(FleetServicer) + if inspect.isfunction(ref) + } + + # Assert + assert expected_methods.issubset(methods) diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index 7383eae3d22b..5f5e153f9d8d 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -117,10 +117,16 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915 Path of the root certificate. If provided, a secure connection using the certificates will be established to an SSL-enabled Flower server. Bytes won't work for the REST API. + authentication_keys : Optional[Tuple[PrivateKey, PublicKey]] (default: None) + Client authentication is not supported for this transport type. Returns ------- - receive, send : Callable, Callable + receive : Callable + send : Callable + create_node : Optional[Callable] + delete_node : Optional[Callable] + get_run : Optional[Callable] """ log( WARN, @@ -145,6 +151,8 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915 "For the REST API, the root certificates " "must be provided as a string path to the client.", ) + if authentication_keys is not None: + log(ERROR, "Client authentication is not supported for this transport type.") # Shared variables for inner functions metadata: Optional[Metadata] = None diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index 1548694858fe..ac35549d2051 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -51,6 +51,10 @@ FLWR_HOME = "FLWR_HOME" +GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version" +GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit" + + class MessageType: """Message type.""" From ce6daf09c6868a005e042509de3ba788b4d3a741 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 19 Jun 2024 13:22:23 +0100 Subject: [PATCH 051/595] feat(framework) Add `GrpcAdapterServicer` (#3538) --- .../superlink/fleet/grpc_adapter/__init__.py | 15 ++ .../grpc_adapter/grpc_adapter_servicer.py | 131 ++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 src/py/flwr/server/superlink/fleet/grpc_adapter/__init__.py create mode 100644 src/py/flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py diff --git a/src/py/flwr/server/superlink/fleet/grpc_adapter/__init__.py b/src/py/flwr/server/superlink/fleet/grpc_adapter/__init__.py new file mode 100644 index 000000000000..cf875a1b9666 --- /dev/null +++ b/src/py/flwr/server/superlink/fleet/grpc_adapter/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Server-side part of the GrpcAdapter transport layer.""" diff --git a/src/py/flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py b/src/py/flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py new file mode 100644 index 000000000000..9325041061ac --- /dev/null +++ b/src/py/flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py @@ -0,0 +1,131 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Fleet API gRPC adapter servicer.""" + + +from logging import DEBUG, INFO +from typing import Callable, Type, TypeVar + +import grpc +from google.protobuf.message import Message as GrpcMessage + +from flwr.common.logger import log +from flwr.proto import grpcadapter_pb2_grpc # pylint: disable=E0611 +from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 + CreateNodeRequest, + CreateNodeResponse, + DeleteNodeRequest, + DeleteNodeResponse, + PingRequest, + PingResponse, + PullTaskInsRequest, + PullTaskInsResponse, + PushTaskResRequest, + PushTaskResResponse, +) +from flwr.proto.grpcadapter_pb2 import MessageContainer # pylint: disable=E0611 +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 +from flwr.server.superlink.fleet.message_handler import message_handler +from flwr.server.superlink.state import StateFactory + +T = TypeVar("T", bound=GrpcMessage) + + +def _handle( + msg_container: MessageContainer, + request_type: Type[T], + handler: Callable[[T], GrpcMessage], +) -> MessageContainer: + req = request_type.FromString(msg_container.grpc_message_content) + res = handler(req) + return MessageContainer( + metadata={}, + grpc_message_name=res.__class__.__qualname__, + grpc_message_content=res.SerializeToString(), + ) + + +class GrpcAdapterServicer(grpcadapter_pb2_grpc.GrpcAdapterServicer): + """Fleet API via GrpcAdapter servicer.""" + + def __init__(self, state_factory: StateFactory) -> None: + self.state_factory = state_factory + + def SendReceive( + self, request: MessageContainer, context: grpc.ServicerContext + ) -> MessageContainer: + """.""" + log(DEBUG, "GrpcAdapterServicer.SendReceive") + if request.grpc_message_name == CreateNodeRequest.__qualname__: + return _handle(request, CreateNodeRequest, self._create_node) + if request.grpc_message_name == DeleteNodeRequest.__qualname__: + return _handle(request, DeleteNodeRequest, self._delete_node) + if request.grpc_message_name == PingRequest.__qualname__: + return _handle(request, PingRequest, self._ping) + if request.grpc_message_name == PullTaskInsRequest.__qualname__: + return _handle(request, PullTaskInsRequest, self._pull_task_ins) + if request.grpc_message_name == PushTaskResRequest.__qualname__: + return _handle(request, PushTaskResRequest, self._push_task_res) + if request.grpc_message_name == GetRunRequest.__qualname__: + return _handle(request, GetRunRequest, self._get_run) + raise ValueError(f"Invalid grpc_message_name: {request.grpc_message_name}") + + def _create_node(self, request: CreateNodeRequest) -> CreateNodeResponse: + """.""" + log(INFO, "GrpcAdapter.CreateNode") + return message_handler.create_node( + request=request, + state=self.state_factory.state(), + ) + + def _delete_node(self, request: DeleteNodeRequest) -> DeleteNodeResponse: + """.""" + log(INFO, "GrpcAdapter.DeleteNode") + return message_handler.delete_node( + request=request, + state=self.state_factory.state(), + ) + + def _ping(self, request: PingRequest) -> PingResponse: + """.""" + log(DEBUG, "GrpcAdapter.Ping") + return message_handler.ping( + request=request, + state=self.state_factory.state(), + ) + + def _pull_task_ins(self, request: PullTaskInsRequest) -> PullTaskInsResponse: + """Pull TaskIns.""" + log(INFO, "GrpcAdapter.PullTaskIns") + return message_handler.pull_task_ins( + request=request, + state=self.state_factory.state(), + ) + + def _push_task_res(self, request: PushTaskResRequest) -> PushTaskResResponse: + """Push TaskRes.""" + log(INFO, "GrpcAdapter.PushTaskRes") + return message_handler.push_task_res( + request=request, + state=self.state_factory.state(), + ) + + def _get_run(self, request: GetRunRequest) -> GetRunResponse: + """Get run information.""" + log(INFO, "GrpcAdapter.GetRun") + return message_handler.get_run( + request=request, + state=self.state_factory.state(), + ) From 1e9a1c7234f55bb3174d63a4699807e65eff96c2 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 19 Jun 2024 14:32:44 +0200 Subject: [PATCH 052/595] ci(*:skip) Update CODEOWNERS for `flwr new` templates (#3642) --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0b8702a8360c..5270bf89ae33 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,9 @@ README.md @jafermarq @tanertopal @danieljanes # Flower Examples /examples @jafermarq @tanertopal @danieljanes +# Flower Templates +/src/py/flwr/cli/new/templates @jafermarq @tanertopal @danieljanes + # Changelog /doc/source/ref-changelog.md @jafermarq @tanertopal @danieljanes From 86d204766dddf87cd57ae9c2ba020600265655c6 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 19 Jun 2024 14:39:55 +0200 Subject: [PATCH 053/595] ci(framework:skip) Add test and format for `__init__.__all__` (#3644) --- dev/format.sh | 1 + src/py/flwr/client/__init__.py | 2 +- src/py/flwr/client/mod/__init__.py | 6 +-- src/py/flwr/common/__init__.py | 24 ++++----- src/py/flwr/common/record/__init__.py | 2 +- src/py/flwr/server/__init__.py | 4 +- src/py/flwr/server/strategy/__init__.py | 4 +- src/py/flwr/simulation/__init__.py | 5 +- src/py/flwr_tool/init_py_check.py | 72 +++++++++++++++++++++++-- src/py/flwr_tool/init_py_fix.py | 69 ++++++++++++++++++++++++ 10 files changed, 163 insertions(+), 26 deletions(-) create mode 100755 src/py/flwr_tool/init_py_fix.py diff --git a/dev/format.sh b/dev/format.sh index 05248b5eed3d..b9e3b00dffe1 100755 --- a/dev/format.sh +++ b/dev/format.sh @@ -3,6 +3,7 @@ set -e cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"/../ # Python +python -m flwr_tool.init_py_fix src/py/flwr python -m isort --skip src/py/flwr/proto src/py python -m black -q --exclude src/py/flwr/proto src/py python -m docformatter -i -r src/py/flwr -e src/py/flwr/proto diff --git a/src/py/flwr/client/__init__.py b/src/py/flwr/client/__init__.py index b4da71302cb4..58fd94448586 100644 --- a/src/py/flwr/client/__init__.py +++ b/src/py/flwr/client/__init__.py @@ -28,8 +28,8 @@ "Client", "ClientApp", "ClientFn", - "mod", "NumPyClient", + "mod", "run_client_app", "run_supernode", "start_client", diff --git a/src/py/flwr/client/mod/__init__.py b/src/py/flwr/client/mod/__init__.py index 1774e4b8ca0a..0b4cf6488421 100644 --- a/src/py/flwr/client/mod/__init__.py +++ b/src/py/flwr/client/mod/__init__.py @@ -22,12 +22,12 @@ from .utils import make_ffn __all__ = [ + "LocalDpMod", "adaptiveclipping_mod", "fixedclipping_mod", - "LocalDpMod", "make_ffn", - "secagg_mod", - "secaggplus_mod", "message_size_mod", "parameters_size_mod", + "secagg_mod", + "secaggplus_mod", ] diff --git a/src/py/flwr/common/__init__.py b/src/py/flwr/common/__init__.py index 2fb98c82dd6f..bbdf48425e0a 100644 --- a/src/py/flwr/common/__init__.py +++ b/src/py/flwr/common/__init__.py @@ -63,43 +63,34 @@ __all__ = [ "Array", - "array_from_numpy", - "bytes_to_ndarray", "ClientMessage", "Code", "Config", "ConfigsRecord", - "configure", "Context", + "DEFAULT_TTL", "DisconnectRes", + "Error", "EvaluateIns", "EvaluateRes", - "event", "EventType", "FitIns", "FitRes", - "Error", + "GRPC_MAX_MESSAGE_LENGTH", "GetParametersIns", "GetParametersRes", "GetPropertiesIns", "GetPropertiesRes", - "GRPC_MAX_MESSAGE_LENGTH", - "log", "Message", "MessageType", "MessageTypeLegacy", - "DEFAULT_TTL", "Metadata", "Metrics", "MetricsAggregationFn", "MetricsRecord", - "ndarray_to_bytes", - "now", "NDArray", "NDArrays", - "ndarrays_to_parameters", "Parameters", - "parameters_to_ndarrays", "ParametersRecord", "Properties", "ReconnectIns", @@ -107,4 +98,13 @@ "Scalar", "ServerMessage", "Status", + "array_from_numpy", + "bytes_to_ndarray", + "configure", + "event", + "log", + "ndarray_to_bytes", + "ndarrays_to_parameters", + "now", + "parameters_to_ndarrays", ] diff --git a/src/py/flwr/common/record/__init__.py b/src/py/flwr/common/record/__init__.py index 60bc54b8552a..88eef5f7aea1 100644 --- a/src/py/flwr/common/record/__init__.py +++ b/src/py/flwr/common/record/__init__.py @@ -22,9 +22,9 @@ __all__ = [ "Array", - "array_from_numpy", "ConfigsRecord", "MetricsRecord", "ParametersRecord", "RecordSet", + "array_from_numpy", ] diff --git a/src/py/flwr/server/__init__.py b/src/py/flwr/server/__init__.py index 19c6034bcaa1..546ce263e2d5 100644 --- a/src/py/flwr/server/__init__.py +++ b/src/py/flwr/server/__init__.py @@ -34,12 +34,12 @@ "Driver", "History", "LegacyContext", - "run_server_app", - "run_superlink", "Server", "ServerApp", "ServerConfig", "SimpleClientManager", + "run_server_app", + "run_superlink", "start_server", "strategy", "workflow", diff --git a/src/py/flwr/server/strategy/__init__.py b/src/py/flwr/server/strategy/__init__.py index b7de9a946fff..e5bc30009819 100644 --- a/src/py/flwr/server/strategy/__init__.py +++ b/src/py/flwr/server/strategy/__init__.py @@ -53,9 +53,10 @@ "DPFedAvgAdaptive", "DPFedAvgFixed", "DifferentialPrivacyClientSideAdaptiveClipping", - "DifferentialPrivacyServerSideAdaptiveClipping", "DifferentialPrivacyClientSideFixedClipping", + "DifferentialPrivacyServerSideAdaptiveClipping", "DifferentialPrivacyServerSideFixedClipping", + "FaultTolerantFedAvg", "FedAdagrad", "FedAdam", "FedAvg", @@ -69,7 +70,6 @@ "FedXgbCyclic", "FedXgbNnAvg", "FedYogi", - "FaultTolerantFedAvg", "Krum", "QFedAvg", "Strategy", diff --git a/src/py/flwr/simulation/__init__.py b/src/py/flwr/simulation/__init__.py index 57b0b01eb319..3d648b14edba 100644 --- a/src/py/flwr/simulation/__init__.py +++ b/src/py/flwr/simulation/__init__.py @@ -36,4 +36,7 @@ def start_simulation(*args, **kwargs): # type: ignore raise ImportError(RAY_IMPORT_ERROR) -__all__ = ["start_simulation", "run_simulation"] +__all__ = [ + "run_simulation", + "start_simulation", +] diff --git a/src/py/flwr_tool/init_py_check.py b/src/py/flwr_tool/init_py_check.py index 67425139f991..1fb08513bb6a 100755 --- a/src/py/flwr_tool/init_py_check.py +++ b/src/py/flwr_tool/init_py_check.py @@ -6,15 +6,19 @@ """ +import ast import os import re import sys +from pathlib import Path +from typing import List, Tuple -def check_missing_init_files(absolute_path: str) -> None: - """Search absolute_path and look for missing __init__.py files.""" +def get_init_dir_list_and_warnings(absolute_path: str) -> Tuple[List[str], List[str]]: + """Search given path and return list of dirs containing __init__.py files.""" path = os.walk(absolute_path) warning_list = [] + dir_list = [] ignore_list = ["__pycache__$", ".pytest_cache.*$", "dist", "flwr.egg-info$"] for dir_path, _, files_in_dir in path: @@ -26,6 +30,14 @@ def check_missing_init_files(absolute_path: str) -> None: if not any(filename == "__init__.py" for filename in files_in_dir): warning_message = "- " + dir_path warning_list.append(warning_message) + else: + dir_list.append(dir_path) + return warning_list, dir_list + + +def check_missing_init_files(absolute_path: str) -> List[str]: + """Search absolute_path and look for missing __init__.py files.""" + warning_list, dir_list = get_init_dir_list_and_warnings(absolute_path) if len(warning_list) > 0: print("Could not find '__init__.py' in the following directories:") @@ -33,12 +45,64 @@ def check_missing_init_files(absolute_path: str) -> None: print(warning) sys.exit(1) + return dir_list + + +def get_all_var_list(init_dir: str) -> Tuple[Path, List[str], List[str]]: + """Get the __all__ list of a __init__.py file. + + The function returns the path of the '__init__.py' file of the given dir, as well as + the list itself, and the list of lines corresponding to the list. + """ + init_file = Path(init_dir) / "__init__.py" + all_lines = [] + all_list = [] + capture = False + for line in init_file.read_text().splitlines(): + stripped_line = line.strip() + if stripped_line.startswith("__all__"): + capture = True + if capture: + all_lines.append(line) + if stripped_line.endswith("]"): + capture = False + break + + if all_lines: + all_string = "".join(all_lines) + all_list = ast.literal_eval(all_string.split("=", 1)[1].strip()) + + return init_file, all_list, all_lines + + +def check_all_init_files(dir_list: List[str]) -> None: + """Check if __all__ is in alphabetical order in __init__.py files.""" + warning_list = [] + + for init_dir in dir_list: + init_file, all_list, _ = get_all_var_list(init_dir) + + if all_list and not all_list == sorted(all_list): + warning_message = "- " + str(init_file) + warning_list.append(warning_message) + + if len(warning_list) > 0: + print( + "'__all__' lists in the following '__init__.py' files are " + "incorrectly sorted:" + ) + for warning in warning_list: + print(warning) + sys.exit(1) + if __name__ == "__main__": if len(sys.argv) == 0: raise Exception( # pylint: disable=W0719 - "Please provide at least one directory path relative to your current working directory." + "Please provide at least one directory path relative " + "to your current working directory." ) for i, _ in enumerate(sys.argv): abs_path: str = os.path.abspath(os.path.join(os.getcwd(), sys.argv[i])) - check_missing_init_files(abs_path) + init_dirs = check_missing_init_files(abs_path) + check_all_init_files(init_dirs) diff --git a/src/py/flwr_tool/init_py_fix.py b/src/py/flwr_tool/init_py_fix.py new file mode 100755 index 000000000000..5ad27829ae8e --- /dev/null +++ b/src/py/flwr_tool/init_py_fix.py @@ -0,0 +1,69 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +"""Fix provided directory and sub-directories for unsorted __all__ in __init__.py files. + +Example: + python -m flwr_tool.init_py_fix src/py/flwr +""" + + +import os +import sys +from typing import List + +import black + +from flwr_tool.init_py_check import get_all_var_list, get_init_dir_list_and_warnings + + +def fix_all_init_files(dir_list: List[str]) -> None: + """Sort the __all__ variables that are in __init__.py files.""" + warning_list = [] + + for init_dir in dir_list: + init_file, all_list, all_lines = get_all_var_list(init_dir) + + if all_list: + sorted_all_list = sorted(all_list) + if not all_list == sorted_all_list: + warning_message = "- " + str(init_dir) + warning_list.append(warning_message) + + old_all_lines = "\n".join(all_lines) + new_all_lines = ( + old_all_lines.split("=", 1)[0] + + "= " + + str(sorted_all_list)[:-1] + + ",]" + ) + + new_content = init_file.read_text().replace( + old_all_lines, new_all_lines + ) + + # Write the fixed content back to the file + init_file.write_text(new_content) + + # Format the file with black + black.format_file_in_place( + init_file, + fast=False, + mode=black.FileMode(), + write_back=black.WriteBack.YES, + ) + + if len(warning_list) > 0: + print("'__all__' lists in the following '__init__.py' files have been sorted:") + for warning in warning_list: + print(warning) + + +if __name__ == "__main__": + if len(sys.argv) == 0: + raise Exception( # pylint: disable=W0719 + "Please provide at least one directory path relative " + "to your current working directory." + ) + for i, _ in enumerate(sys.argv): + abs_path: str = os.path.abspath(os.path.join(os.getcwd(), sys.argv[i])) + warnings, init_dirs = get_init_dir_list_and_warnings(abs_path) + fix_all_init_files(init_dirs) From 8816a23cc4a2530275ecec85c9d823b421fab982 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 19 Jun 2024 14:32:37 +0100 Subject: [PATCH 054/595] refactor(framework) Reload the module and its dependencies in `load_app` (#3597) --- src/py/flwr/client/supernode/app.py | 2 +- src/py/flwr/common/object_ref.py | 44 ++++++++++++++++--- src/py/flwr/server/run_serverapp.py | 4 +- .../server/superlink/fleet/vce/vce_api.py | 2 +- src/py/flwr/superexec/app.py | 2 +- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 97951f2a3279..362b09c5d5b5 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -230,7 +230,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: "Loading ClientApp `%s`", client_app_ref, ) - client_app = load_app(client_app_ref, LoadClientAppError) + client_app = load_app(client_app_ref, LoadClientAppError, sys_path) if not isinstance(client_app, ClientApp): raise LoadClientAppError( diff --git a/src/py/flwr/common/object_ref.py b/src/py/flwr/common/object_ref.py index b56c69a4f36b..ac52be160c2e 100644 --- a/src/py/flwr/common/object_ref.py +++ b/src/py/flwr/common/object_ref.py @@ -17,8 +17,13 @@ import ast import importlib +import sys from importlib.util import find_spec -from typing import Any, Optional, Tuple, Type +from logging import WARN +from pathlib import Path +from typing import Any, Optional, Tuple, Type, Union + +from .logger import log OBJECT_REF_HELP_STR = """ \n\nThe object reference string should have the form :. Valid @@ -77,9 +82,10 @@ def validate( ) -def load_app( +def load_app( # pylint: disable= too-many-branches module_attribute_str: str, error_type: Type[Exception], + project_dir: Optional[Union[str, Path]] = None, ) -> Any: """Return the object specified in a module attribute string. @@ -95,11 +101,39 @@ def load_app( module_str, _, attributes_str = module_attribute_str.partition(":") try: - module = importlib.import_module(module_str) - except ModuleNotFoundError: + if module_str not in sys.modules: + module = importlib.import_module(module_str) + # Hack: `tabnet` does not work with `importlib.reload` + elif "tabnet" in sys.modules: + log( + WARN, + "Cannot reload module `%s` from disk due to compatibility issues " + "with the `tabnet` library. The module will be loaded from the " + "cache instead. If you experience issues, consider restarting " + "the application.", + module_str, + ) + module = sys.modules[module_str] + else: + module = sys.modules[module_str] + if project_dir is None: + path: Optional[str] = getattr(module, "__file__", None) + if path is not None: + project_dir = str(Path(path).parent) + else: + project_dir = str(Path(project_dir).absolute()) + + # Reload cached modules in the project directory + if project_dir is not None: + for m in list(sys.modules.values()): + path = getattr(m, "__file__", None) + if path is not None and path.startswith(project_dir): + importlib.reload(m) + + except ModuleNotFoundError as err: raise error_type( f"Unable to load module {module_str}{OBJECT_REF_HELP_STR}", - ) from None + ) from err # Recursively load attribute attribute = module diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index fd0214a040bc..efd3f6846264 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -50,7 +50,9 @@ def run( # Load ServerApp if needed def _load() -> ServerApp: if server_app_attr: - server_app: ServerApp = load_app(server_app_attr, LoadServerAppError) + server_app: ServerApp = load_app( + server_app_attr, LoadServerAppError, server_app_dir + ) if not isinstance(server_app, ServerApp): raise LoadServerAppError( diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index dcd37aba09c0..3c9628a6d2a3 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -325,7 +325,7 @@ def _load() -> ClientApp: if app_dir is not None: sys.path.insert(0, app_dir) - app: ClientApp = load_app(client_app_attr, LoadClientAppError) + app: ClientApp = load_app(client_app_attr, LoadClientAppError, app_dir) if not isinstance(app, ClientApp): raise LoadClientAppError( diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py index e1cb4f609e9c..fa89e83b5e75 100644 --- a/src/py/flwr/superexec/app.py +++ b/src/py/flwr/superexec/app.py @@ -164,7 +164,7 @@ def _load_executor( if not valid and error_msg: raise LoadExecutorError(error_msg) from None - executor = load_app(executor_ref, LoadExecutorError) + executor = load_app(executor_ref, LoadExecutorError, args.executor_dir) if not isinstance(executor, Executor): raise LoadExecutorError( From 31afc932da503ae3fce90e0ea2a7a50e179eb9dc Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 19 Jun 2024 17:58:39 +0200 Subject: [PATCH 055/595] ci(*:skip) Allow amend to be use as a valid verb for title (#3651) --- dev/changelog_config.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/changelog_config.toml b/dev/changelog_config.toml index 898f9bfdb221..c5ff1bcdd1c1 100644 --- a/dev/changelog_config.toml +++ b/dev/changelog_config.toml @@ -44,6 +44,7 @@ allowed_verbs=[ "Align", "Allow", "Alter", + "Amend", "Analyse", "Analyze", "Anchor", From 88b08f49da20f983b6ab5c62bfee4e3b565b091e Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 19 Jun 2024 18:26:32 +0100 Subject: [PATCH 056/595] feat(framework) Add `grpc-adapter` transport (#3540) --- src/py/flwr/client/app.py | 4 ++ src/py/flwr/client/supernode/app.py | 31 +++++++++-- src/py/flwr/common/constant.py | 4 ++ src/py/flwr/server/app.py | 54 ++++++++++++++++--- .../superlink/fleet/grpc_bidi/grpc_server.py | 4 ++ 5 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index cdb7b25cbf6b..1226a0d7bc21 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -31,6 +31,7 @@ from flwr.common.address import parse_address from flwr.common.constant import ( MISSING_EXTRA_REST, + TRANSPORT_TYPE_GRPC_ADAPTER, TRANSPORT_TYPE_GRPC_BIDI, TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_REST, @@ -41,6 +42,7 @@ from flwr.common.message import Error from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential +from .grpc_adapter_client.connection import grpc_adapter from .grpc_client.connection import grpc_connection from .grpc_rere_client.connection import grpc_request_response from .message_handler.message_handler import handle_control_message @@ -600,6 +602,8 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[ connection, error_type = http_request_response, RequestsConnectionError elif transport == TRANSPORT_TYPE_GRPC_RERE: connection, error_type = grpc_request_response, RpcError + elif transport == TRANSPORT_TYPE_GRPC_ADAPTER: + connection, error_type = grpc_adapter, RpcError elif transport == TRANSPORT_TYPE_GRPC_BIDI: connection, error_type = grpc_connection, RpcError else: diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 362b09c5d5b5..742281f5c011 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -30,6 +30,11 @@ from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir +from flwr.common.constant import ( + TRANSPORT_TYPE_GRPC_ADAPTER, + TRANSPORT_TYPE_GRPC_RERE, + TRANSPORT_TYPE_REST, +) from flwr.common.exit_handlers import register_exit_handlers from flwr.common.logger import log, warn_deprecated_feature from flwr.common.object_ref import load_app, validate @@ -56,7 +61,7 @@ def run_supernode() -> None: _start_client_internal( server_address=args.superlink, load_client_app_fn=load_fn, - transport="rest" if args.rest else "grpc-rere", + transport=args.transport, root_certificates=root_certificates, insecure=args.insecure, authentication_keys=authentication_keys, @@ -87,7 +92,7 @@ def run_client_app() -> None: _start_client_internal( server_address=args.superlink, load_client_app_fn=load_fn, - transport="rest" if args.rest else "grpc-rere", + transport=args.transport, root_certificates=root_certificates, insecure=args.insecure, authentication_keys=authentication_keys, @@ -295,9 +300,27 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: help="Run the client without HTTPS. By default, the client runs with " "HTTPS enabled. Use this flag only if you understand the risks.", ) - parser.add_argument( + ex_group = parser.add_mutually_exclusive_group() + ex_group.add_argument( + "--grpc-rere", + action="store_const", + dest="transport", + const=TRANSPORT_TYPE_GRPC_RERE, + default=TRANSPORT_TYPE_GRPC_RERE, + help="Use grpc-rere as a transport layer for the client.", + ) + ex_group.add_argument( + "--grpc-adapter", + action="store_const", + dest="transport", + const=TRANSPORT_TYPE_GRPC_ADAPTER, + help="Use grpc-adapter as a transport layer for the client.", + ) + ex_group.add_argument( "--rest", - action="store_true", + action="store_const", + dest="transport", + const=TRANSPORT_TYPE_REST, help="Use REST as a transport layer for the client.", ) parser.add_argument( diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index ac35549d2051..193f000ca42e 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -27,6 +27,7 @@ TRANSPORT_TYPE_GRPC_BIDI = "grpc-bidi" TRANSPORT_TYPE_GRPC_RERE = "grpc-rere" +TRANSPORT_TYPE_GRPC_ADAPTER = "grpc-adapter" TRANSPORT_TYPE_REST = "rest" TRANSPORT_TYPE_VCE = "vce" TRANSPORT_TYPES = [ @@ -45,6 +46,9 @@ PING_RANDOM_RANGE = (-0.1, 0.1) PING_MAX_INTERVAL = 1e300 +GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version" +GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit" + # Constants for FAB APP_DIR = "apps" FAB_CONFIG_FILE = "pyproject.toml" diff --git a/src/py/flwr/server/app.py b/src/py/flwr/server/app.py index 1574ec46f968..822defdb5b13 100644 --- a/src/py/flwr/server/app.py +++ b/src/py/flwr/server/app.py @@ -36,6 +36,7 @@ from flwr.common.address import parse_address from flwr.common.constant import ( MISSING_EXTRA_REST, + TRANSPORT_TYPE_GRPC_ADAPTER, TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_REST, ) @@ -48,6 +49,7 @@ from flwr.proto.fleet_pb2_grpc import ( # pylint: disable=E0611 add_FleetServicer_to_server, ) +from flwr.proto.grpcadapter_pb2_grpc import add_GrpcAdapterServicer_to_server from .client_manager import ClientManager from .history import History @@ -55,6 +57,7 @@ from .server_config import ServerConfig from .strategy import Strategy from .superlink.driver.driver_grpc import run_driver_api_grpc +from .superlink.fleet.grpc_adapter.grpc_adapter_servicer import GrpcAdapterServicer from .superlink.fleet.grpc_bidi.grpc_server import ( generic_create_grpc_server, start_grpc_server, @@ -218,11 +221,13 @@ def run_superlink() -> None: grpc_servers = [driver_server] bckg_threads = [] if not args.fleet_api_address: - args.fleet_api_address = ( - ADDRESS_FLEET_API_GRPC_RERE - if args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE - else ADDRESS_FLEET_API_REST - ) + if args.fleet_api_type in [ + TRANSPORT_TYPE_GRPC_RERE, + TRANSPORT_TYPE_GRPC_ADAPTER, + ]: + args.fleet_api_address = ADDRESS_FLEET_API_GRPC_RERE + elif args.fleet_api_type == TRANSPORT_TYPE_REST: + args.fleet_api_address = ADDRESS_FLEET_API_REST fleet_address, host, port = _format_address(args.fleet_api_address) @@ -293,6 +298,13 @@ def run_superlink() -> None: interceptors=interceptors, ) grpc_servers.append(fleet_server) + elif args.fleet_api_type == TRANSPORT_TYPE_GRPC_ADAPTER: + fleet_server = _run_fleet_api_grpc_adapter( + address=fleet_address, + state_factory=state_factory, + certificates=certificates, + ) + grpc_servers.append(fleet_server) else: raise ValueError(f"Unknown fleet_api_type: {args.fleet_api_type}") @@ -419,7 +431,7 @@ def _try_obtain_certificates( log(WARN, "Option `--insecure` was set. Starting insecure HTTP server.") return None # Check if certificates are provided - if args.fleet_api_type == TRANSPORT_TYPE_GRPC_RERE: + if args.fleet_api_type in [TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_GRPC_ADAPTER]: if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile: if not isfile(args.ssl_ca_certfile): sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.") @@ -491,6 +503,30 @@ def _run_fleet_api_grpc_rere( return fleet_grpc_server +def _run_fleet_api_grpc_adapter( + address: str, + state_factory: StateFactory, + certificates: Optional[Tuple[bytes, bytes, bytes]], +) -> grpc.Server: + """Run Fleet API (GrpcAdapter).""" + # Create Fleet API gRPC server + fleet_servicer = GrpcAdapterServicer( + state_factory=state_factory, + ) + fleet_add_servicer_to_server_fn = add_GrpcAdapterServicer_to_server + fleet_grpc_server = generic_create_grpc_server( + servicer_and_add_fn=(fleet_servicer, fleet_add_servicer_to_server_fn), + server_address=address, + max_message_length=GRPC_MAX_MESSAGE_LENGTH, + certificates=certificates, + ) + + log(INFO, "Flower ECE: Starting Fleet API (GrpcAdapter) on %s", address) + fleet_grpc_server.start() + + return fleet_grpc_server + + # pylint: disable=import-outside-toplevel,too-many-arguments def _run_fleet_api_rest( host: str, @@ -606,7 +642,11 @@ def _add_args_fleet_api(parser: argparse.ArgumentParser) -> None: "--fleet-api-type", default=TRANSPORT_TYPE_GRPC_RERE, type=str, - choices=[TRANSPORT_TYPE_GRPC_RERE, TRANSPORT_TYPE_REST], + choices=[ + TRANSPORT_TYPE_GRPC_RERE, + TRANSPORT_TYPE_GRPC_ADAPTER, + TRANSPORT_TYPE_REST, + ], help="Start a gRPC-rere or REST (experimental) Fleet API server.", ) parser.add_argument( diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py index 6aeaa7ef413f..ae685fda91a7 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py @@ -29,6 +29,9 @@ ) from flwr.server.client_manager import ClientManager from flwr.server.superlink.driver.driver_servicer import DriverServicer +from flwr.server.superlink.fleet.grpc_adapter.grpc_adapter_servicer import ( + GrpcAdapterServicer, +) from flwr.server.superlink.fleet.grpc_bidi.flower_service_servicer import ( FlowerServiceServicer, ) @@ -154,6 +157,7 @@ def start_grpc_server( # pylint: disable=too-many-arguments def generic_create_grpc_server( # pylint: disable=too-many-arguments servicer_and_add_fn: Union[ Tuple[FleetServicer, AddServicerToServerFn], + Tuple[GrpcAdapterServicer, AddServicerToServerFn], Tuple[FlowerServiceServicer, AddServicerToServerFn], Tuple[DriverServicer, AddServicerToServerFn], ], From 0a16d99eb3213b928df1ed8636b2fa7a3add2f49 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 20 Jun 2024 07:35:45 +0200 Subject: [PATCH 057/595] docs(framework) Add `flwr` cli docs (#3384) --- doc/source/conf.py | 1 + doc/source/ref-api-cli.rst | 9 +++++++++ pyproject.toml | 1 + src/py/flwr/cli/app.py | 3 +++ src/py/flwr/cli/build.py | 10 +++------- src/py/flwr/cli/run/run.py | 5 ++++- 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/doc/source/conf.py b/doc/source/conf.py index 85bfe9404521..1452f9c9a7b0 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -108,6 +108,7 @@ "sphinxcontrib.youtube", "sphinx_reredirects", "nbsphinx", + "sphinx_click", ] # Generate .rst files diff --git a/doc/source/ref-api-cli.rst b/doc/source/ref-api-cli.rst index 296c2219a065..4397ae056941 100644 --- a/doc/source/ref-api-cli.rst +++ b/doc/source/ref-api-cli.rst @@ -1,6 +1,15 @@ Flower CLI reference ==================== +.. _flwr-apiref: + +flwr CLI +~~~~~~~~ + +.. click:: flwr.cli.app:typer_click_object + :prog: flwr + :nested: full + .. _flower-simulation-apiref: flower-simulation diff --git a/pyproject.toml b/pyproject.toml index 2dd592050468..dbab703c7671 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,7 @@ rope = "==1.11.0" semver = "==3.0.2" sphinx = "==6.2.1" sphinx-intl = "==2.2.0" +sphinx-click = "==5.1.0" myst-parser = "==1.0.0" sphinx-design = "==0.5.0" sphinx-copybutton = "==0.5.2" diff --git a/src/py/flwr/cli/app.py b/src/py/flwr/cli/app.py index 477b990bf1da..d1b270026cd7 100644 --- a/src/py/flwr/cli/app.py +++ b/src/py/flwr/cli/app.py @@ -15,6 +15,7 @@ """Flower command line interface.""" import typer +from typer.main import get_command from .build import build from .example import example @@ -37,5 +38,7 @@ app.command()(build) app.command()(install) +typer_click_object = get_command(app) + if __name__ == "__main__": app() diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index 2981eacf925d..4a9b54f9223f 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -36,13 +36,9 @@ def build( ) -> str: """Build a Flower project into a Flower App Bundle (FAB). - You can run `flwr build` without any argument to bundle the current directory: - - `flwr build` - - You can also build a specific directory: - - `flwr build --directory ./projects/flower-hello-world` + You can run ``flwr build`` without any arguments to bundle the current directory, + or you can use ``--directory`` to build a specific directory: + ``flwr build --directory ./projects/flower-hello-world``. """ if directory is None: directory = Path.cwd() diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 7577d9efbd8c..28fa67f9d4f6 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -41,7 +41,10 @@ class Engine(str, Enum): def run( engine: Annotated[ Optional[Engine], - typer.Option(case_sensitive=False, help="The execution engine to run the app"), + typer.Option( + case_sensitive=False, + help="The engine to run FL with (currently only simulation is supported).", + ), ] = None, use_superexec: Annotated[ bool, From 1b72bafa98c0146f08d89e67bf8c8327ef80b45b Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:27:15 +0200 Subject: [PATCH 058/595] fix(datasets) Fix sorting in `__all__` (#3655) --- datasets/flwr_datasets/__init__.py | 5 +++-- datasets/flwr_datasets/partitioner/__init__.py | 12 ++++++------ datasets/flwr_datasets/preprocessor/__init__.py | 2 +- datasets/flwr_datasets/visualization/__init__.py | 2 +- datasets/flwr_datasets/visualization/bar_plot.py | 1 - datasets/flwr_datasets/visualization/heatmap_plot.py | 2 -- 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/datasets/flwr_datasets/__init__.py b/datasets/flwr_datasets/__init__.py index d084780102ce..bd68fa43c606 100644 --- a/datasets/flwr_datasets/__init__.py +++ b/datasets/flwr_datasets/__init__.py @@ -23,11 +23,12 @@ __all__ = [ "FederatedDataset", - "partitioner", "metrics", - "visualization", + "partitioner", "preprocessor", "utils", + "visualization", ] + __version__ = _package_version diff --git a/datasets/flwr_datasets/partitioner/__init__.py b/datasets/flwr_datasets/partitioner/__init__.py index 0d1edbfcb04a..1fc00ed90323 100644 --- a/datasets/flwr_datasets/partitioner/__init__.py +++ b/datasets/flwr_datasets/partitioner/__init__.py @@ -27,14 +27,14 @@ from .square_partitioner import SquarePartitioner __all__ = [ + "DirichletPartitioner", + "ExponentialPartitioner", "IidPartitioner", - "Partitioner", + "InnerDirichletPartitioner", + "LinearPartitioner", "NaturalIdPartitioner", - "DirichletPartitioner", + "Partitioner", + "ShardPartitioner", "SizePartitioner", - "LinearPartitioner", - "InnerDirichletPartitioner", "SquarePartitioner", - "ShardPartitioner", - "ExponentialPartitioner", ] diff --git a/datasets/flwr_datasets/preprocessor/__init__.py b/datasets/flwr_datasets/preprocessor/__init__.py index bab5d82a2035..67b2aaadc3d2 100644 --- a/datasets/flwr_datasets/preprocessor/__init__.py +++ b/datasets/flwr_datasets/preprocessor/__init__.py @@ -20,7 +20,7 @@ from .preprocessor import Preprocessor __all__ = [ + "Divider", "Merger", "Preprocessor", - "Divider", ] diff --git a/datasets/flwr_datasets/visualization/__init__.py b/datasets/flwr_datasets/visualization/__init__.py index 801a38dcafc6..b55e406c71db 100644 --- a/datasets/flwr_datasets/visualization/__init__.py +++ b/datasets/flwr_datasets/visualization/__init__.py @@ -19,6 +19,6 @@ from .label_distribution import plot_label_distributions __all__ = [ - "plot_label_distributions", "plot_comparison_label_distribution", + "plot_label_distributions", ] diff --git a/datasets/flwr_datasets/visualization/bar_plot.py b/datasets/flwr_datasets/visualization/bar_plot.py index 6326b24a9695..339ff0967906 100644 --- a/datasets/flwr_datasets/visualization/bar_plot.py +++ b/datasets/flwr_datasets/visualization/bar_plot.py @@ -38,7 +38,6 @@ def _plot_bar( plot_kwargs: Optional[Dict[str, Any]], legend_kwargs: Optional[Dict[str, Any]], ) -> Axes: - if axis is None: if figsize is None: figsize = _initialize_figsize( diff --git a/datasets/flwr_datasets/visualization/heatmap_plot.py b/datasets/flwr_datasets/visualization/heatmap_plot.py index 2e593a79368e..3c87de7693ae 100644 --- a/datasets/flwr_datasets/visualization/heatmap_plot.py +++ b/datasets/flwr_datasets/visualization/heatmap_plot.py @@ -39,7 +39,6 @@ def _plot_heatmap( plot_kwargs: Optional[Dict[str, Any]], legend_kwargs: Optional[Dict[str, Any]], ) -> Axes: - if axis is None: if figsize is None: figsize = _initialize_figsize( @@ -92,7 +91,6 @@ def _initialize_figsize( num_partitions: int, num_labels: int, ) -> Tuple[float, float]: - figsize = (0.0, 0.0) if partition_id_axis == "x": figsize = (3 * np.sqrt(num_partitions), np.sqrt(num_labels)) From 2ede64758dbc8fc5bcadada8e2fddefa693c5633 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Thu, 20 Jun 2024 13:43:11 +0200 Subject: [PATCH 059/595] feat(examples) Add Tabular Dataset Example (#3568) Co-authored-by: jafermarq --- README.md | 1 + examples/fl-tabular/README.md | 39 +++++++++++ examples/fl-tabular/client.py | 38 ++++++++++ examples/fl-tabular/pyproject.toml | 20 ++++++ examples/fl-tabular/server.py | 24 +++++++ examples/fl-tabular/task.py | 108 +++++++++++++++++++++++++++++ 6 files changed, 230 insertions(+) create mode 100644 examples/fl-tabular/README.md create mode 100644 examples/fl-tabular/client.py create mode 100644 examples/fl-tabular/pyproject.toml create mode 100644 examples/fl-tabular/server.py create mode 100644 examples/fl-tabular/task.py diff --git a/README.md b/README.md index a010abfcb2f5..16cb7f1cfaf6 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ Other [examples](https://github.com/adap/flower/tree/main/examples): - [Flower with KaplanMeierFitter from the lifelines library](https://github.com/adap/flower/tree/main/examples/federated-kaplan-meier-fitter) - [Sample Level Privacy with Opacus](https://github.com/adap/flower/tree/main/examples/opacus) - [Sample Level Privacy with TensorFlow-Privacy](https://github.com/adap/flower/tree/main/examples/tensorflow-privacy) +- [Flower with a Tabular Dataset] (https://github.com/adap/flower/tree/main/examples/fl-tabular) ## Community diff --git a/examples/fl-tabular/README.md b/examples/fl-tabular/README.md new file mode 100644 index 000000000000..58afd1080b70 --- /dev/null +++ b/examples/fl-tabular/README.md @@ -0,0 +1,39 @@ +# Flower Example on Adult Census Income Tabular Dataset + +This code exemplifies a federated learning setup using the Flower framework on the ["Adult Census Income"](https://huggingface.co/datasets/scikit-learn/adult-census-income) tabular dataset. The "Adult Census Income" dataset contains demographic information such as age, education, occupation, etc., with the target attribute being income level (\<=50K or >50K). The dataset is partitioned into subsets, simulating a federated environment with 5 clients, each holding a distinct portion of the data. Categorical variables are one-hot encoded, and the data is split into training and testing sets. Federated learning is conducted using the FedAvg strategy for 5 rounds. + +This example uses [Flower Datasets](https://flower.ai/docs/datasets/) to download, partition and preprocess the dataset. + +## Environments Setup + +Start by cloning the example. We prepared a single-line command that you can copy into your shell which will checkout the example for you: + +```shell +git clone --depth=1 https://github.com/adap/flower.git && mv flower/examples/fl-tabular . && rm -rf flower && cd fl-tabular +``` + +This will create a new directory called `fl-tabular` containing the following files: + +```shell +-- pyproject.toml +-- client.py +-- server.py +-- task.py +-- README.md +``` + +### Installing dependencies + +Project dependencies are defined in `pyproject.toml`. Install them with: + +```shell +pip install . +``` + +## Running Code + +### Federated Using Flower Simulation + +```bash +flower-simulation --server-app server:app --client-app client:app --num-supernodes 5 +``` diff --git a/examples/fl-tabular/client.py b/examples/fl-tabular/client.py new file mode 100644 index 000000000000..228183f4edc4 --- /dev/null +++ b/examples/fl-tabular/client.py @@ -0,0 +1,38 @@ +from flwr.client import Client, ClientApp, NumPyClient +from flwr_datasets import FederatedDataset +from task import set_weights, get_weights, train, evaluate, IncomeClassifier, load_data + +NUMBER_OF_CLIENTS = 5 + + +class FlowerClient(NumPyClient): + def __init__(self, net, trainloader, testloader): + self.net = net + self.trainloader = trainloader + self.testloader = testloader + + def fit(self, parameters, config): + set_weights(self.net, parameters) + train(self.net, self.trainloader) + return get_weights(self.net), len(self.trainloader), {} + + def evaluate(self, parameters, config): + set_weights(self.net, parameters) + loss, accuracy = evaluate(self.net, self.testloader) + return loss, len(self.testloader), {"accuracy": accuracy} + + +def get_client_fn(dataset: FederatedDataset): + def client_fn(cid: str) -> Client: + train_loader, test_loader = load_data(partition_id=int(cid), fds=dataset) + net = IncomeClassifier(14) + return FlowerClient(net, train_loader, test_loader).to_client() + + return client_fn + + +fds = FederatedDataset( + dataset="scikit-learn/adult-census-income", + partitioners={"train": NUMBER_OF_CLIENTS}, +) +app = ClientApp(client_fn=get_client_fn(fds)) diff --git a/examples/fl-tabular/pyproject.toml b/examples/fl-tabular/pyproject.toml new file mode 100644 index 000000000000..07ff226d5d06 --- /dev/null +++ b/examples/fl-tabular/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "fl-tabular" +version = "0.1.0" +description = "Adult Census Income Tabular Dataset and Federated Learning in Flower" +authors = [ + { name = "The Flower Authors", email = "hello@flower.ai" }, +] +dependencies = [ + "flwr[simulation]>=1.9.0,<2.0", + "flwr-datasets>=0.1.0,<1.0.0", + "torch==2.1.1", + "scikit-learn==1.4.2", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] diff --git a/examples/fl-tabular/server.py b/examples/fl-tabular/server.py new file mode 100644 index 000000000000..376726f832f7 --- /dev/null +++ b/examples/fl-tabular/server.py @@ -0,0 +1,24 @@ +from flwr.common import ndarrays_to_parameters +from flwr.server import ServerApp, ServerConfig +from flwr.server.strategy import FedAvg +from task import IncomeClassifier, get_weights + +net = IncomeClassifier(input_dim=14) +params = ndarrays_to_parameters(get_weights(net)) + + +def weighted_average(metrics): + accuracies = [num_examples * m["accuracy"] for num_examples, m in metrics] + examples = [num_examples for num_examples, _ in metrics] + + return {"accuracy": sum(accuracies) / sum(examples)} + + +strategy = FedAvg( + initial_parameters=params, + evaluate_metrics_aggregation_fn=weighted_average, +) +app = ServerApp( + strategy=strategy, + config=ServerConfig(num_rounds=5), +) diff --git a/examples/fl-tabular/task.py b/examples/fl-tabular/task.py new file mode 100644 index 000000000000..b07365c733d6 --- /dev/null +++ b/examples/fl-tabular/task.py @@ -0,0 +1,108 @@ +import torch +import torch.nn as nn +import torch.optim as optim +from torch.utils.data import TensorDataset, DataLoader +from sklearn.model_selection import train_test_split +from sklearn.preprocessing import StandardScaler, OrdinalEncoder +from sklearn.compose import ColumnTransformer +from sklearn.pipeline import Pipeline +from collections import OrderedDict +from flwr_datasets import FederatedDataset + + +def load_data(partition_id: int, fds: FederatedDataset): + dataset = fds.load_partition(partition_id, "train").with_format("pandas")[:] + + dataset.dropna(inplace=True) + + categorical_cols = dataset.select_dtypes(include=["object"]).columns + ordinal_encoder = OrdinalEncoder() + dataset[categorical_cols] = ordinal_encoder.fit_transform(dataset[categorical_cols]) + + X = dataset.drop("income", axis=1) + y = dataset["income"] + + X_train, X_test, y_train, y_test = train_test_split( + X, y, test_size=0.2, random_state=42 + ) + + numeric_features = X.select_dtypes(include=["float64", "int64"]).columns + numeric_transformer = Pipeline(steps=[("scaler", StandardScaler())]) + + preprocessor = ColumnTransformer( + transformers=[("num", numeric_transformer, numeric_features)] + ) + + X_train = preprocessor.fit_transform(X_train) + X_test = preprocessor.transform(X_test) + + X_train_tensor = torch.tensor(X_train, dtype=torch.float32) + X_test_tensor = torch.tensor(X_test, dtype=torch.float32) + y_train_tensor = torch.tensor(y_train.values, dtype=torch.float32).view(-1, 1) + y_test_tensor = torch.tensor(y_test.values, dtype=torch.float32).view(-1, 1) + + train_dataset = TensorDataset(X_train_tensor, y_train_tensor) + test_dataset = TensorDataset(X_test_tensor, y_test_tensor) + train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True) + test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False) + + return train_loader, test_loader + + +class IncomeClassifier(nn.Module): + def __init__(self, input_dim: int): + super(IncomeClassifier, self).__init__() + self.layer1 = nn.Linear(input_dim, 128) + self.layer2 = nn.Linear(128, 64) + self.output = nn.Linear(64, 1) + self.relu = nn.ReLU() + self.sigmoid = nn.Sigmoid() + + def forward(self, x): + x = self.relu(self.layer1(x)) + x = self.relu(self.layer2(x)) + x = self.sigmoid(self.output(x)) + return x + + +def train(model, train_loader, num_epochs=1): + criterion = nn.BCELoss() + optimizer = optim.Adam(model.parameters(), lr=0.001) + model.train() + for epoch in range(num_epochs): + for X_batch, y_batch in train_loader: + optimizer.zero_grad() + outputs = model(X_batch) + loss = criterion(outputs, y_batch) + loss.backward() + optimizer.step() + + +def evaluate(model, test_loader): + model.eval() + criterion = nn.BCELoss() + loss = 0.0 + correct = 0 + total = 0 + with torch.no_grad(): + for X_batch, y_batch in test_loader: + outputs = model(X_batch) + batch_loss = criterion(outputs, y_batch) + loss += batch_loss.item() + predicted = (outputs > 0.5).float() + total += y_batch.size(0) + correct += (predicted == y_batch).sum().item() + accuracy = correct / total + loss = loss / len(test_loader) + return loss, accuracy + + +def set_weights(net, parameters): + params_dict = zip(net.state_dict().keys(), parameters) + state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict}) + net.load_state_dict(state_dict, strict=True) + + +def get_weights(net): + ndarrays = [val.cpu().numpy() for _, val in net.state_dict().items()] + return ndarrays From 02fe0519b9be4124ef6a244c3d85a7a1eaef40d6 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 20 Jun 2024 12:55:50 +0100 Subject: [PATCH 060/595] feat(framework) Implement DriverAPI `GetRun` (#3580) Co-authored-by: Daniel J. Beutel Co-authored-by: Javier --- src/py/flwr/server/compat/app_utils.py | 2 +- src/py/flwr/server/driver/driver.py | 6 + src/py/flwr/server/driver/grpc_driver.py | 146 ++++++++++-------- src/py/flwr/server/driver/grpc_driver_test.py | 87 ++++------- src/py/flwr/server/driver/inmemory_driver.py | 54 +++---- .../server/driver/inmemory_driver_test.py | 58 ++++--- src/py/flwr/server/run_serverapp.py | 19 ++- .../superlink/driver/driver_servicer.py | 16 +- src/py/flwr/simulation/run_simulation.py | 23 ++- 9 files changed, 232 insertions(+), 179 deletions(-) diff --git a/src/py/flwr/server/compat/app_utils.py b/src/py/flwr/server/compat/app_utils.py index 1cdf1efbffb9..baff27307b88 100644 --- a/src/py/flwr/server/compat/app_utils.py +++ b/src/py/flwr/server/compat/app_utils.py @@ -91,7 +91,7 @@ def _update_client_manager( node_id=node_id, driver=driver, anonymous=False, - run_id=driver.run_id, # type: ignore + run_id=driver.run.run_id, ) if client_manager.register(client_proxy): registered_nodes[node_id] = client_proxy diff --git a/src/py/flwr/server/driver/driver.py b/src/py/flwr/server/driver/driver.py index b95cec95ab47..4f888323e586 100644 --- a/src/py/flwr/server/driver/driver.py +++ b/src/py/flwr/server/driver/driver.py @@ -19,11 +19,17 @@ from typing import Iterable, List, Optional from flwr.common import Message, RecordSet +from flwr.common.typing import Run class Driver(ABC): """Abstract base Driver class for the Driver API.""" + @property + @abstractmethod + def run(self) -> Run: + """Run information.""" + @abstractmethod def create_message( # pylint: disable=too-many-arguments self, diff --git a/src/py/flwr/server/driver/grpc_driver.py b/src/py/flwr/server/driver/grpc_driver.py index d339f1b232f9..2016d54b655a 100644 --- a/src/py/flwr/server/driver/grpc_driver.py +++ b/src/py/flwr/server/driver/grpc_driver.py @@ -17,7 +17,7 @@ import time import warnings from logging import DEBUG, ERROR, WARNING -from typing import Iterable, List, Optional, Tuple +from typing import Iterable, List, Optional, Tuple, cast import grpc @@ -25,6 +25,7 @@ from flwr.common.grpc import create_channel from flwr.common.logger import log from flwr.common.serde import message_from_taskres, message_to_taskins +from flwr.common.typing import Run from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 CreateRunRequest, CreateRunResponse, @@ -37,6 +38,7 @@ ) from flwr.proto.driver_pb2_grpc import DriverStub # pylint: disable=E0611 from flwr.proto.node_pb2 import Node # pylint: disable=E0611 +from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611 from .driver import Driver @@ -46,13 +48,24 @@ ERROR_MESSAGE_DRIVER_NOT_CONNECTED = """ [Driver] Error: Not connected. -Call `connect()` on the `GrpcDriverHelper` instance before calling any of the other -`GrpcDriverHelper` methods. +Call `connect()` on the `GrpcDriverStub` instance before calling any of the other +`GrpcDriverStub` methods. """ -class GrpcDriverHelper: - """`GrpcDriverHelper` provides access to the gRPC Driver API/service.""" +class GrpcDriverStub: + """`GrpcDriverStub` provides access to the gRPC Driver API/service. + + Parameters + ---------- + driver_service_address : Optional[str] + The IPv4 or IPv6 address of the Driver API server. + Defaults to `"[::]:9091"`. + root_certificates : Optional[bytes] (default: None) + The PEM-encoded root certificates as a byte string. + If provided, a secure connection using the certificates will be + established to an SSL-enabled Flower server. + """ def __init__( self, @@ -64,6 +77,10 @@ def __init__( self.channel: Optional[grpc.Channel] = None self.stub: Optional[DriverStub] = None + def is_connected(self) -> bool: + """Return True if connected to the Driver API server, otherwise False.""" + return self.channel is not None + def connect(self) -> None: """Connect to the Driver API.""" event(EventType.DRIVER_CONNECT) @@ -95,18 +112,29 @@ def create_run(self, req: CreateRunRequest) -> CreateRunResponse: # Check if channel is open if self.stub is None: log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverHelper` instance not connected") + raise ConnectionError("`GrpcDriverStub` instance not connected") # Call Driver API res: CreateRunResponse = self.stub.CreateRun(request=req) return res + def get_run(self, req: GetRunRequest) -> GetRunResponse: + """Get run information.""" + # Check if channel is open + if self.stub is None: + log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) + raise ConnectionError("`GrpcDriverStub` instance not connected") + + # Call gRPC Driver API + res: GetRunResponse = self.stub.GetRun(request=req) + return res + def get_nodes(self, req: GetNodesRequest) -> GetNodesResponse: """Get client IDs.""" # Check if channel is open if self.stub is None: log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverHelper` instance not connected") + raise ConnectionError("`GrpcDriverStub` instance not connected") # Call gRPC Driver API res: GetNodesResponse = self.stub.GetNodes(request=req) @@ -117,7 +145,7 @@ def push_task_ins(self, req: PushTaskInsRequest) -> PushTaskInsResponse: # Check if channel is open if self.stub is None: log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverHelper` instance not connected") + raise ConnectionError("`GrpcDriverStub` instance not connected") # Call gRPC Driver API res: PushTaskInsResponse = self.stub.PushTaskIns(request=req) @@ -128,7 +156,7 @@ def pull_task_res(self, req: PullTaskResRequest) -> PullTaskResResponse: # Check if channel is open if self.stub is None: log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverHelper` instance not connected") + raise ConnectionError("`GrpcDriverStub` instance not connected") # Call Driver API res: PullTaskResResponse = self.stub.PullTaskRes(request=req) @@ -140,56 +168,52 @@ class GrpcDriver(Driver): Parameters ---------- - driver_service_address : Optional[str] - The IPv4 or IPv6 address of the Driver API server. - Defaults to `"[::]:9091"`. - certificates : bytes (default: None) - Tuple containing root certificate, server certificate, and private key - to start a secure SSL-enabled server. The tuple is expected to have - three bytes elements in the following order: - - * CA certificate. - * server certificate. - * server private key. - fab_id : str (default: None) - The identifier of the FAB used in the run. - fab_version : str (default: None) - The version of the FAB used in the run. + run_id : int + The identifier of the run. + stub : Optional[GrpcDriverStub] (default: None) + The ``GrpcDriverStub`` instance used to communicate with the SuperLink. + If None, an instance connected to "[::]:9091" will be created. """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, - driver_service_address: str = DEFAULT_SERVER_ADDRESS_DRIVER, - root_certificates: Optional[bytes] = None, - fab_id: Optional[str] = None, - fab_version: Optional[str] = None, + run_id: int, + stub: Optional[GrpcDriverStub] = None, ) -> None: - self.addr = driver_service_address - self.root_certificates = root_certificates - self.driver_helper: Optional[GrpcDriverHelper] = None - self.run_id: Optional[int] = None - self.fab_id = fab_id if fab_id is not None else "" - self.fab_version = fab_version if fab_version is not None else "" + self._run_id = run_id + self._run: Optional[Run] = None + self.stub = stub if stub is not None else GrpcDriverStub() self.node = Node(node_id=0, anonymous=True) - def _get_grpc_driver_helper_and_run_id(self) -> Tuple[GrpcDriverHelper, int]: - # Check if the GrpcDriverHelper is initialized - if self.driver_helper is None or self.run_id is None: - # Connect and create run - self.driver_helper = GrpcDriverHelper( - driver_service_address=self.addr, - root_certificates=self.root_certificates, + @property + def run(self) -> Run: + """Run information.""" + self._get_stub_and_run_id() + return Run(**vars(cast(Run, self._run))) + + def _get_stub_and_run_id(self) -> Tuple[GrpcDriverStub, int]: + # Check if is initialized + if self._run is None: + # Connect + if not self.stub.is_connected(): + self.stub.connect() + # Get the run info + req = GetRunRequest(run_id=self._run_id) + res = self.stub.get_run(req) + if not res.HasField("run"): + raise RuntimeError(f"Cannot find the run with ID: {self._run_id}") + self._run = Run( + run_id=res.run.run_id, + fab_id=res.run.fab_id, + fab_version=res.run.fab_version, ) - self.driver_helper.connect() - req = CreateRunRequest(fab_id=self.fab_id, fab_version=self.fab_version) - res = self.driver_helper.create_run(req) - self.run_id = res.run_id - return self.driver_helper, self.run_id + + return self.stub, self._run.run_id def _check_message(self, message: Message) -> None: # Check if the message is valid if not ( - message.metadata.run_id == self.run_id + message.metadata.run_id == cast(Run, self._run).run_id and message.metadata.src_node_id == self.node.node_id and message.metadata.message_id == "" and message.metadata.reply_to_message == "" @@ -210,7 +234,7 @@ def create_message( # pylint: disable=too-many-arguments This method constructs a new `Message` with given content and metadata. The `run_id` and `src_node_id` will be set automatically. """ - _, run_id = self._get_grpc_driver_helper_and_run_id() + _, run_id = self._get_stub_and_run_id() if ttl: warnings.warn( "A custom TTL was set, but note that the SuperLink does not enforce " @@ -234,9 +258,9 @@ def create_message( # pylint: disable=too-many-arguments def get_node_ids(self) -> List[int]: """Get node IDs.""" - grpc_driver_helper, run_id = self._get_grpc_driver_helper_and_run_id() - # Call GrpcDriverHelper method - res = grpc_driver_helper.get_nodes(GetNodesRequest(run_id=run_id)) + stub, run_id = self._get_stub_and_run_id() + # Call GrpcDriverStub method + res = stub.get_nodes(GetNodesRequest(run_id=run_id)) return [node.node_id for node in res.nodes] def push_messages(self, messages: Iterable[Message]) -> Iterable[str]: @@ -245,7 +269,7 @@ def push_messages(self, messages: Iterable[Message]) -> Iterable[str]: This method takes an iterable of messages and sends each message to the node specified in `dst_node_id`. """ - grpc_driver_helper, _ = self._get_grpc_driver_helper_and_run_id() + stub, _ = self._get_stub_and_run_id() # Construct TaskIns task_ins_list: List[TaskIns] = [] for msg in messages: @@ -255,10 +279,8 @@ def push_messages(self, messages: Iterable[Message]) -> Iterable[str]: taskins = message_to_taskins(msg) # Add to list task_ins_list.append(taskins) - # Call GrpcDriverHelper method - res = grpc_driver_helper.push_task_ins( - PushTaskInsRequest(task_ins_list=task_ins_list) - ) + # Call GrpcDriverStub method + res = stub.push_task_ins(PushTaskInsRequest(task_ins_list=task_ins_list)) return list(res.task_ids) def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]: @@ -267,9 +289,9 @@ def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]: This method is used to collect messages from the SuperLink that correspond to a set of given message IDs. """ - grpc_driver, _ = self._get_grpc_driver_helper_and_run_id() + stub, _ = self._get_stub_and_run_id() # Pull TaskRes - res = grpc_driver.pull_task_res( + res = stub.pull_task_res( PullTaskResRequest(node=self.node, task_ids=message_ids) ) # Convert TaskRes to Message @@ -308,8 +330,8 @@ def send_and_receive( def close(self) -> None: """Disconnect from the SuperLink if connected.""" - # Check if GrpcDriverHelper is initialized - if self.driver_helper is None: + # Check if `connect` was called before + if not self.stub.is_connected(): return # Disconnect - self.driver_helper.disconnect() + self.stub.disconnect() diff --git a/src/py/flwr/server/driver/grpc_driver_test.py b/src/py/flwr/server/driver/grpc_driver_test.py index fbead0e3043d..60ab79002f85 100644 --- a/src/py/flwr/server/driver/grpc_driver_test.py +++ b/src/py/flwr/server/driver/grpc_driver_test.py @@ -27,6 +27,7 @@ PullTaskResRequest, PushTaskInsRequest, ) +from flwr.proto.run_pb2 import Run # pylint: disable=E0611 from flwr.proto.task_pb2 import Task, TaskRes # pylint: disable=E0611 from .grpc_driver import GrpcDriver @@ -36,58 +37,36 @@ class TestGrpcDriver(unittest.TestCase): """Tests for `GrpcDriver` class.""" def setUp(self) -> None: - """Initialize mock GrpcDriverHelper and Driver instance before each test.""" - mock_response = Mock() - mock_response.run_id = 61016 - self.mock_grpc_driver_helper = Mock() - self.mock_grpc_driver_helper.create_run.return_value = mock_response - self.patcher = patch( - "flwr.server.driver.grpc_driver.GrpcDriverHelper", - return_value=self.mock_grpc_driver_helper, + """Initialize mock GrpcDriverStub and Driver instance before each test.""" + mock_response = Mock( + run=Run(run_id=61016, fab_id="mock/mock", fab_version="v1.0.0") ) - self.patcher.start() - self.driver = GrpcDriver() - - def tearDown(self) -> None: - """Cleanup after each test.""" - self.patcher.stop() - - def test_check_and_init_grpc_driver_already_initialized(self) -> None: - """Test that GrpcDriverHelper doesn't initialize if run is created.""" - # Prepare - self.driver.driver_helper = self.mock_grpc_driver_helper - self.driver.run_id = 61016 - - # Execute - # pylint: disable-next=protected-access - self.driver._get_grpc_driver_helper_and_run_id() + self.mock_grpc_driver_stub = Mock() + self.mock_grpc_driver_stub.get_run.return_value = mock_response + self.mock_grpc_driver_stub.HasField.return_value = True + self.driver = GrpcDriver(run_id=61016, stub=self.mock_grpc_driver_stub) + def test_init_grpc_driver(self) -> None: + """Test GrpcDriverStub initialization.""" # Assert - self.mock_grpc_driver_helper.connect.assert_not_called() - - def test_check_and_init_grpc_driver_needs_initialization(self) -> None: - """Test GrpcDriverHelper initialization when run is not created.""" - # Execute - # pylint: disable-next=protected-access - self.driver._get_grpc_driver_helper_and_run_id() - - # Assert - self.mock_grpc_driver_helper.connect.assert_called_once() - self.assertEqual(self.driver.run_id, 61016) + self.assertEqual(self.driver.run.run_id, 61016) + self.assertEqual(self.driver.run.fab_id, "mock/mock") + self.assertEqual(self.driver.run.fab_version, "v1.0.0") + self.mock_grpc_driver_stub.get_run.assert_called_once() def test_get_nodes(self) -> None: """Test retrieval of nodes.""" # Prepare mock_response = Mock() mock_response.nodes = [Mock(node_id=404), Mock(node_id=200)] - self.mock_grpc_driver_helper.get_nodes.return_value = mock_response + self.mock_grpc_driver_stub.get_nodes.return_value = mock_response # Execute node_ids = self.driver.get_node_ids() - args, kwargs = self.mock_grpc_driver_helper.get_nodes.call_args + args, kwargs = self.mock_grpc_driver_stub.get_nodes.call_args # Assert - self.mock_grpc_driver_helper.connect.assert_called_once() + self.mock_grpc_driver_stub.get_run.assert_called_once() self.assertEqual(len(args), 1) self.assertEqual(len(kwargs), 0) self.assertIsInstance(args[0], GetNodesRequest) @@ -98,7 +77,7 @@ def test_push_messages_valid(self) -> None: """Test pushing valid messages.""" # Prepare mock_response = Mock(task_ids=["id1", "id2"]) - self.mock_grpc_driver_helper.push_task_ins.return_value = mock_response + self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response msgs = [ self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL) for _ in range(2) @@ -106,10 +85,10 @@ def test_push_messages_valid(self) -> None: # Execute msg_ids = self.driver.push_messages(msgs) - args, kwargs = self.mock_grpc_driver_helper.push_task_ins.call_args + args, kwargs = self.mock_grpc_driver_stub.push_task_ins.call_args # Assert - self.mock_grpc_driver_helper.connect.assert_called_once() + self.mock_grpc_driver_stub.get_run.assert_called_once() self.assertEqual(len(args), 1) self.assertEqual(len(kwargs), 0) self.assertIsInstance(args[0], PushTaskInsRequest) @@ -121,7 +100,7 @@ def test_push_messages_invalid(self) -> None: """Test pushing invalid messages.""" # Prepare mock_response = Mock(task_ids=["id1", "id2"]) - self.mock_grpc_driver_helper.push_task_ins.return_value = mock_response + self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response msgs = [ self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL) for _ in range(2) @@ -145,16 +124,16 @@ def test_pull_messages_with_given_message_ids(self) -> None: ), TaskRes(task=Task(ancestry=["id3"], error=error_to_proto(Error(code=0)))), ] - self.mock_grpc_driver_helper.pull_task_res.return_value = mock_response + self.mock_grpc_driver_stub.pull_task_res.return_value = mock_response msg_ids = ["id1", "id2", "id3"] # Execute msgs = self.driver.pull_messages(msg_ids) reply_tos = {msg.metadata.reply_to_message for msg in msgs} - args, kwargs = self.mock_grpc_driver_helper.pull_task_res.call_args + args, kwargs = self.mock_grpc_driver_stub.pull_task_res.call_args # Assert - self.mock_grpc_driver_helper.connect.assert_called_once() + self.mock_grpc_driver_stub.get_run.assert_called_once() self.assertEqual(len(args), 1) self.assertEqual(len(kwargs), 0) self.assertIsInstance(args[0], PullTaskResRequest) @@ -165,14 +144,14 @@ def test_send_and_receive_messages_complete(self) -> None: """Test send and receive all messages successfully.""" # Prepare mock_response = Mock(task_ids=["id1"]) - self.mock_grpc_driver_helper.push_task_ins.return_value = mock_response + self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response # The response message must include either `content` (i.e. a recordset) or # an `Error`. We choose the latter in this case error_proto = error_to_proto(Error(code=0)) mock_response = Mock( task_res_list=[TaskRes(task=Task(ancestry=["id1"], error=error_proto))] ) - self.mock_grpc_driver_helper.pull_task_res.return_value = mock_response + self.mock_grpc_driver_stub.pull_task_res.return_value = mock_response msgs = [self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL)] # Execute @@ -187,9 +166,9 @@ def test_send_and_receive_messages_timeout(self) -> None: # Prepare sleep_fn = time.sleep mock_response = Mock(task_ids=["id1"]) - self.mock_grpc_driver_helper.push_task_ins.return_value = mock_response + self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response mock_response = Mock(task_res_list=[]) - self.mock_grpc_driver_helper.pull_task_res.return_value = mock_response + self.mock_grpc_driver_stub.pull_task_res.return_value = mock_response msgs = [self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL)] # Execute @@ -204,19 +183,21 @@ def test_send_and_receive_messages_timeout(self) -> None: def test_del_with_initialized_driver(self) -> None: """Test cleanup behavior when Driver is initialized.""" # Prepare - # pylint: disable-next=protected-access - self.driver._get_grpc_driver_helper_and_run_id() + self.mock_grpc_driver_stub.is_connected.return_value = True # Execute self.driver.close() # Assert - self.mock_grpc_driver_helper.disconnect.assert_called_once() + self.mock_grpc_driver_stub.disconnect.assert_called_once() def test_del_with_uninitialized_driver(self) -> None: """Test cleanup behavior when Driver is not initialized.""" + # Prepare + self.mock_grpc_driver_stub.is_connected.return_value = False + # Execute self.driver.close() # Assert - self.mock_grpc_driver_helper.disconnect.assert_not_called() + self.mock_grpc_driver_stub.disconnect.assert_not_called() diff --git a/src/py/flwr/server/driver/inmemory_driver.py b/src/py/flwr/server/driver/inmemory_driver.py index 8c71b1067293..53406796750f 100644 --- a/src/py/flwr/server/driver/inmemory_driver.py +++ b/src/py/flwr/server/driver/inmemory_driver.py @@ -17,11 +17,12 @@ import time import warnings -from typing import Iterable, List, Optional +from typing import Iterable, List, Optional, cast from uuid import UUID from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet from flwr.common.serde import message_from_taskres, message_to_taskins +from flwr.common.typing import Run from flwr.proto.node_pb2 import Node # pylint: disable=E0611 from flwr.server.superlink.state import StateFactory @@ -33,30 +34,27 @@ class InMemoryDriver(Driver): Parameters ---------- + run_id : int + The identifier of the run. state_factory : StateFactory A StateFactory embedding a state that this driver can interface with. - fab_id : str (default: None) - The identifier of the FAB used in the run. - fab_version : str (default: None) - The version of the FAB used in the run. """ def __init__( self, + run_id: int, state_factory: StateFactory, - fab_id: Optional[str] = None, - fab_version: Optional[str] = None, ) -> None: - self.run_id: Optional[int] = None - self.fab_id = fab_id if fab_id is not None else "" - self.fab_version = fab_version if fab_version is not None else "" - self.node = Node(node_id=0, anonymous=True) + self._run_id = run_id + self._run: Optional[Run] = None self.state = state_factory.state() + self.node = Node(node_id=0, anonymous=True) def _check_message(self, message: Message) -> None: + self._init_run() # Check if the message is valid if not ( - message.metadata.run_id == self.run_id + message.metadata.run_id == cast(Run, self._run).run_id and message.metadata.src_node_id == self.node.node_id and message.metadata.message_id == "" and message.metadata.reply_to_message == "" @@ -64,16 +62,20 @@ def _check_message(self, message: Message) -> None: ): raise ValueError(f"Invalid message: {message}") - def _get_run_id(self) -> int: - """Return run_id. - - If unset, create a new run. - """ - if self.run_id is None: - self.run_id = self.state.create_run( - fab_id=self.fab_id, fab_version=self.fab_version - ) - return self.run_id + def _init_run(self) -> None: + """Initialize the run.""" + if self._run is not None: + return + run = self.state.get_run(self._run_id) + if run is None: + raise RuntimeError(f"Cannot find the run with ID: {self._run_id}") + self._run = run + + @property + def run(self) -> Run: + """Run ID.""" + self._init_run() + return Run(**vars(cast(Run, self._run))) def create_message( # pylint: disable=too-many-arguments self, @@ -88,7 +90,7 @@ def create_message( # pylint: disable=too-many-arguments This method constructs a new `Message` with given content and metadata. The `run_id` and `src_node_id` will be set automatically. """ - run_id = self._get_run_id() + self._init_run() if ttl: warnings.warn( "A custom TTL was set, but note that the SuperLink does not enforce " @@ -99,7 +101,7 @@ def create_message( # pylint: disable=too-many-arguments ttl_ = DEFAULT_TTL if ttl is None else ttl metadata = Metadata( - run_id=run_id, + run_id=cast(Run, self._run).run_id, message_id="", # Will be set by the server src_node_id=self.node.node_id, dst_node_id=dst_node_id, @@ -112,8 +114,8 @@ def create_message( # pylint: disable=too-many-arguments def get_node_ids(self) -> List[int]: """Get node IDs.""" - run_id = self._get_run_id() - return list(self.state.get_nodes(run_id)) + self._init_run() + return list(self.state.get_nodes(cast(Run, self._run).run_id)) def push_messages(self, messages: Iterable[Message]) -> Iterable[str]: """Push messages to specified node IDs. diff --git a/src/py/flwr/server/driver/inmemory_driver_test.py b/src/py/flwr/server/driver/inmemory_driver_test.py index 95c2a0b277af..55d52d848dfd 100644 --- a/src/py/flwr/server/driver/inmemory_driver_test.py +++ b/src/py/flwr/server/driver/inmemory_driver_test.py @@ -31,8 +31,9 @@ message_to_taskres, recordset_to_proto, ) +from flwr.common.typing import Run from flwr.proto.task_pb2 import Task, TaskRes # pylint: disable=E0611 -from flwr.server.superlink.state import StateFactory +from flwr.server.superlink.state import InMemoryState, SqliteState, StateFactory from .inmemory_driver import InMemoryDriver @@ -79,12 +80,24 @@ def setUp(self) -> None: """ # Create driver self.num_nodes = 42 - self.driver = InMemoryDriver(StateFactory("")) - self.driver.state = MagicMock() - self.driver.state.get_nodes.return_value = [ + self.state = MagicMock() + self.state.get_nodes.return_value = [ int.from_bytes(os.urandom(8), "little", signed=True) for _ in range(self.num_nodes) ] + self.state.get_run.return_value = Run( + run_id=61016, fab_id="mock/mock", fab_version="v1.0.0" + ) + state_factory = MagicMock(state=lambda: self.state) + self.driver = InMemoryDriver(run_id=61016, state_factory=state_factory) + self.driver.state = self.state + + def test_get_run(self) -> None: + """Test the InMemoryDriver starting with run_id.""" + # Assert + self.assertEqual(self.driver.run.run_id, 61016) + self.assertEqual(self.driver.run.fab_id, "mock/mock") + self.assertEqual(self.driver.run.fab_version, "v1.0.0") def test_get_nodes(self) -> None: """Test retrieval of nodes.""" @@ -104,7 +117,7 @@ def test_push_messages_valid(self) -> None: ] taskins_ids = [uuid4() for _ in range(num_messages)] - self.driver.state.store_task_ins.side_effect = taskins_ids # type: ignore + self.state.store_task_ins.side_effect = taskins_ids # Execute msg_ids = list(self.driver.push_messages(msgs)) @@ -141,7 +154,7 @@ def test_pull_messages_with_given_message_ids(self) -> None: task=Task(ancestry=[msg_ids[1]], error=error_to_proto(Error(code=0))) ), ] - self.driver.state.get_task_res.return_value = task_res_list # type: ignore + self.state.get_task_res.return_value = task_res_list # Execute pulled_msgs = list(self.driver.pull_messages(msg_ids)) @@ -167,8 +180,8 @@ def test_send_and_receive_messages_complete(self) -> None: task=Task(ancestry=[msg_ids[1]], error=error_to_proto(Error(code=0))) ), ] - self.driver.state.store_task_ins.side_effect = msg_ids # type: ignore - self.driver.state.get_task_res.return_value = task_res_list # type: ignore + self.state.store_task_ins.side_effect = msg_ids + self.state.get_task_res.return_value = task_res_list # Execute ret_msgs = list(self.driver.send_and_receive(msgs)) @@ -193,8 +206,8 @@ def test_send_and_receive_messages_timeout(self) -> None: task=Task(ancestry=[msg_ids[1]], error=error_to_proto(Error(code=0))) ), ] - self.driver.state.store_task_ins.side_effect = msg_ids # type: ignore - self.driver.state.get_task_res.return_value = task_res_list # type: ignore + self.state.store_task_ins.side_effect = msg_ids + self.state.get_task_res.return_value = task_res_list # Execute with patch("time.sleep", side_effect=lambda t: time.sleep(t * 0.01)): @@ -208,19 +221,23 @@ def test_send_and_receive_messages_timeout(self) -> None: def test_task_store_consistency_after_push_pull_sqlitestate(self) -> None: """Test tasks are deleted in sqlite state once messages are pulled.""" # Prepare - self.driver = InMemoryDriver(StateFactory("")) + state = StateFactory("").state() + self.driver = InMemoryDriver( + state.create_run("", ""), MagicMock(state=lambda: state) + ) msg_ids, node_id = push_messages(self.driver, self.num_nodes) + assert isinstance(state, SqliteState) # Check recorded - task_ins = self.driver.state.query("SELECT * FROM task_ins;") # type: ignore + task_ins = state.query("SELECT * FROM task_ins;") self.assertEqual(len(task_ins), len(list(msg_ids))) # Prepare: create replies reply_tos = get_replies(self.driver, msg_ids, node_id) # Query number of task_ins and task_res in State - task_res = self.driver.state.query("SELECT * FROM task_res;") # type: ignore - task_ins = self.driver.state.query("SELECT * FROM task_ins;") # type: ignore + task_res = state.query("SELECT * FROM task_res;") + task_ins = state.query("SELECT * FROM task_ins;") # Assert self.assertEqual(reply_tos, msg_ids) @@ -230,18 +247,19 @@ def test_task_store_consistency_after_push_pull_sqlitestate(self) -> None: def test_task_store_consistency_after_push_pull_inmemory_state(self) -> None: """Test tasks are deleted in in-memory state once messages are pulled.""" # Prepare - self.driver = InMemoryDriver(StateFactory(":flwr-in-memory-state:")) + state_factory = StateFactory(":flwr-in-memory-state:") + state = state_factory.state() + self.driver = InMemoryDriver(state.create_run("", ""), state_factory) msg_ids, node_id = push_messages(self.driver, self.num_nodes) + assert isinstance(state, InMemoryState) # Check recorded - self.assertEqual( - len(self.driver.state.task_ins_store), len(list(msg_ids)) # type: ignore - ) + self.assertEqual(len(state.task_ins_store), len(list(msg_ids))) # Prepare: create replies reply_tos = get_replies(self.driver, msg_ids, node_id) # Assert self.assertEqual(reply_tos, msg_ids) - self.assertEqual(len(self.driver.state.task_res_store), 0) # type: ignore - self.assertEqual(len(self.driver.state.task_ins_store), 0) # type: ignore + self.assertEqual(len(state.task_res_store), 0) + self.assertEqual(len(state.task_ins_store), 0) diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index efd3f6846264..63ffc4a1caae 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -24,8 +24,10 @@ from flwr.common import Context, EventType, RecordSet, event from flwr.common.logger import log, update_console_handler, warn_deprecated_feature from flwr.common.object_ref import load_app +from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611 -from .driver import Driver, GrpcDriver +from .driver import Driver +from .driver.grpc_driver import GrpcDriver, GrpcDriverStub from .server_app import LoadServerAppError, ServerApp ADDRESS_DRIVER_API = "0.0.0.0:9091" @@ -149,13 +151,16 @@ def run_server_app() -> None: server_app_dir = args.dir server_app_attr = getattr(args, "server-app") - # Initialize GrpcDriver - driver = GrpcDriver( - driver_service_address=args.superlink, - root_certificates=root_certificates, - fab_id=args.fab_id, - fab_version=args.fab_version, + # Create run + stub = GrpcDriverStub( + driver_service_address=args.superlink, root_certificates=root_certificates ) + stub.connect() + req = CreateRunRequest(fab_id=args.fab_id, fab_version=args.fab_version) + res = stub.create_run(req) + + # Initialize GrpcDriver + driver = GrpcDriver(run_id=res.run_id, stub=stub) # Run the ServerApp with the Driver run(driver=driver, server_app_dir=server_app_dir, server_app_attr=server_app_attr) diff --git a/src/py/flwr/server/superlink/driver/driver_servicer.py b/src/py/flwr/server/superlink/driver/driver_servicer.py index e808616af778..30d2e883dc63 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer.py @@ -35,7 +35,11 @@ PushTaskInsResponse, ) from flwr.proto.node_pb2 import Node # pylint: disable=E0611 -from flwr.proto.run_pb2 import GetRunRequest, GetRunResponse # pylint: disable=E0611 +from flwr.proto.run_pb2 import ( # pylint: disable=E0611 + GetRunRequest, + GetRunResponse, + Run, +) from flwr.proto.task_pb2 import TaskRes # pylint: disable=E0611 from flwr.server.superlink.state import State, StateFactory from flwr.server.utils.validator import validate_task_ins_or_res @@ -134,7 +138,15 @@ def GetRun( self, request: GetRunRequest, context: grpc.ServicerContext ) -> GetRunResponse: """Get run information.""" - raise NotImplementedError + log(DEBUG, "DriverServicer.GetRun") + + # Init state + state: State = self.state_factory.state() + + # Retrieve run information + run = state.get_run(request.run_id) + run_proto = None if run is None else Run(**vars(run)) + return GetRunResponse(run=run_proto) def _raise_if(validation_error: bool, detail: str) -> None: diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 3532c5a4e877..a3de1401d252 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -27,7 +27,7 @@ from flwr.client import ClientApp from flwr.common import EventType, event, log from flwr.common.logger import set_logger_propagation, update_console_handler -from flwr.common.typing import ConfigsRecordValues +from flwr.common.typing import ConfigsRecordValues, Run from flwr.server.driver import Driver, InMemoryDriver from flwr.server.run_serverapp import run from flwr.server.server_app import ServerApp @@ -169,11 +169,14 @@ def server_th_with_start_checks( # type: ignore return serverapp_th -def _init_run_id(driver: InMemoryDriver, state: StateFactory, run_id: int) -> None: - """Create a run with a given `run_id`.""" +def _override_run_id(state: StateFactory, run_id_to_replace: int, run_id: int) -> None: + """Override the run_id of an existing Run.""" log(DEBUG, "Pre-registering run with id %s", run_id) - state.state().run_ids[run_id] = ("", "") # type: ignore - driver.run_id = run_id + # Remove run + run_info: Run = state.state().run_ids.pop(run_id_to_replace) # type: ignore + # Update with new run_id and insert back in state + run_info.run_id = run_id + state.state().run_ids[run_id] = run_info # type: ignore # pylint: disable=too-many-locals @@ -201,11 +204,15 @@ def _main_loop( f_stop = asyncio.Event() serverapp_th = None try: - # Initialize Driver - driver = InMemoryDriver(state_factory) + # Create run (with empty fab_id and fab_version) + run_id_ = state_factory.state().create_run("", "") if run_id: - _init_run_id(driver, state_factory, run_id) + _override_run_id(state_factory, run_id_to_replace=run_id_, run_id=run_id) + run_id_ = run_id + + # Initialize Driver + driver = InMemoryDriver(run_id=run_id_, state_factory=state_factory) # Get and run ServerApp thread serverapp_th = run_serverapp_th( From 85918cb3784c76bfbd87eb978d58867f20615fea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 12:03:41 +0000 Subject: [PATCH 061/595] chore(deps): bump scikit-learn from 1.4.2 to 1.5.0 in /examples/fl-tabular (#3656) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/fl-tabular/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/fl-tabular/pyproject.toml b/examples/fl-tabular/pyproject.toml index 07ff226d5d06..21498f73a4f3 100644 --- a/examples/fl-tabular/pyproject.toml +++ b/examples/fl-tabular/pyproject.toml @@ -13,7 +13,7 @@ dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets>=0.1.0,<1.0.0", "torch==2.1.1", - "scikit-learn==1.4.2", + "scikit-learn==1.5.0", ] [tool.hatch.build.targets.wheel] From 7a77ff20b4f071278981d05cb116815dcba0e0e3 Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Thu, 20 Jun 2024 20:17:48 +0800 Subject: [PATCH 062/595] feat(*:skip) Initialize FlowerTune LLM Leaderboard (#3541) Co-authored-by: jafermarq --- benchmarks/flowertune-llm/README.md | 61 ++++++++++++++++++ .../flowertune-llm/_static/flower_llm.jpg | Bin 0 -> 1627444 bytes 2 files changed, 61 insertions(+) create mode 100644 benchmarks/flowertune-llm/README.md create mode 100644 benchmarks/flowertune-llm/_static/flower_llm.jpg diff --git a/benchmarks/flowertune-llm/README.md b/benchmarks/flowertune-llm/README.md new file mode 100644 index 000000000000..0cb69e7ff9c7 --- /dev/null +++ b/benchmarks/flowertune-llm/README.md @@ -0,0 +1,61 @@ +![](_static/flower_llm.jpg) + +# FlowerTune LLM Leaderboard + +This repository guides you through the process of federated LLM instruction tuning with a +pre-trained [Mistral-7B](https://huggingface.co/mistralai/Mistral-7B-v0.3) model across 4 domains --- general NLP, finance, medical and code. + +Please follow the instructions to run and evaluate the federated LLMs. + +## Create a new project + +As the first step, please register a Flower account on [Flower website](https://flower.ai/login). +Assuming `flwr` package is already installed on your system (check [here](https://flower.ai/docs/framework/how-to-install-flower.html) for `flwr` installation). +We provide a single-line command to create a new project directory based on your selected challenge: + +```shell +flwr new --framework=flwrtune --username=your_flower_account +``` + +Then you will see a prompt to ask your project name and the choice of LLM challenges from the set of general NLP, finance, medical and code. +Type your project name and select your preferred challenge, +and then a new project directory will be generated automatically. + +### Structure + +After running `flwr new`, you will see a new directory generated with the following structure: + +```bash + + ├── README.md # <- Instructions + ├── pyproject.toml # <- Environment dependencies + └── + ├── app.py # <- Flower ClientApp/ServerApp build + ├── client.py # <- Flower client constructor + ├── server.py # <- Sever-related functions + ├── models.py # <- Model build + ├── dataset.py # <- Dataset and tokenizer build + ├── conf/config.yaml # <- User configuration + └── conf/static_config.yaml # <- Static configuration +``` + +This can serve as the starting point for you to build up your own federated LLM fine-tuning methods. +Please note that any modification to the content of `conf/static_config.yaml` is strictly prohibited for those who wish to participate in the [LLM Leaderboard](https://flower.ai/benchmarks/llm-leaderboard). +Otherwise, the submission will not be considered. + +## Run FlowerTune LLM challenges + +With a new project directory created, running a baseline challenge can be done by: + +1. Navigate inside the directory that you just created. + + +2. Follow the `Environments setup` section of `README.md` in the project directory to install project dependencies. + + +3. Run the challenge as indicated in the `Running the challenge` section in the `README.md`. + +## Evaluate pre-trained LLMs + +After the LLM fine-tuning finished, evaluate the performance of your pre-trained LLMs +following the `README.md` in `evaluation` directory. diff --git a/benchmarks/flowertune-llm/_static/flower_llm.jpg b/benchmarks/flowertune-llm/_static/flower_llm.jpg new file mode 100644 index 0000000000000000000000000000000000000000..96081d9c2ad1990ae72819f3f5eb69d969ebe971 GIT binary patch literal 1627444 zcmb@sWmFtb+c(&_yKB&47$gj?!3Ng>26q|Uog^d#cXvr}clY2<@DMym2pS+pi0$Mr z_x-%j?m4?3wwUhv)m4{QHC5H8f3N@E14z}C)sz7=G&DdJ^#}akCs|NaRJ797(@|E_ zR6=zC0Le=mFE2NAQUGxG@bl4AQD8APF=fGd51;{<00AHZpxfB_ddchRY67V8Z~X`V zw|rScaihvI-ydE7rT>3IWcCieb^w5;i{iGm_qB6J;WhxkEU@$P@dE(tKXmp$Kd(O+ zbpezop0K^L@I!l0^BT5JH6$%S@ z*gJcm;%7nO2k!2k_9$$P!eA7S7YhH;FK+uk^f>+xY-3~lADuQfj{m`b$AXeX8HPFg zdb!yI|JC^a^XBg6kIL6y1%$d2I{B#Tp=wH0-qu|`HU3~i6h8N`)z(2_aumLGMn(5G zM#p#ZS4E&OiXWZP&Q}qYOMn`M1?+8L8Ys+&!pa_Q+JDCT58W2&tgME@q$nKZ?5Adc z!n7zH@wD>t0pq_(2`N0^qv-!(^R4f<^4j%IVaGuj02hDgbYaY5 z^#8&C*jN5*bRGX=P46#%Tv6lV{2!iw+9dD!vuvIq(Y3Io8O_2mx_0DOf1wQitM&;FMdz6$^{gTH_O?)@)K zeGmYe#ZZrb_kU?%aR4AA0D#GEJAa>mzx84MsjyK0B}hXeOARb5oGJ!ncIZz5z0rfx&&d+|pb4Uhqdh=VLeoUkLo-3MMsq~-K=Vg? zgcgn#kCuj(hgOVMjn<6TjW&cfg|>+H2JJ1{3EB;titTT z9K~G5e2aO7`3s8(ixGB%NWZMD-bIRD+{Xvs~u|$^(-A>eZj`YrpD&N7DGLY zCfF|653v)l3$YuqUt%v}A7FpR!NH-!;lYu?(ZR97@xzJ2$;GL|8N^w_Il{TcCBVIh zD~PL%Yl`cE`wTY=w+43rcM10x_d6ab9w(k8o(`TpUNBxVUKw5w-YdK#yzlsA_+0oh z_y+hc_+j`t_zn1D_*?iN2?z+-2qXyL1kMCc333RU2qp>M5_}^hCxj3x5}FhG6DAQ> z622sSL->)1hzLX^M`TLmN0dZVO*BHZLv%|_LCjCAMr=>~ggBqLgLsMfk_3;0lSH1x zk|cyAi=>t070Ed%9w~@ak<^AXlr*2ToAfp5XEF*hD47nKJ6Qr*4cQdgdvY9d5V;b$ zJ$VFqDftNb0R;vHJB1>J9Yr`r8O0dI5hXSy7o{4d3uQcIJ>@**6%{#^FqI)y5LG@^ zKh+*J1~rIUjoOVmnYx8~jrxv;iAJ8rfhLxwfo6&3D=h=99IXRw9Bm`*D(xK|3!M_3 zD_tsGC*3wZCOw26P9I2LL_bD<&Opf^#bCz}$I!yC!HCWXVbo)M$XLoa%lMgznMsAo ziz%0Bgz21_npuw7g*k(HfcfMe#k~jjobP4a8@zYQLd7D_;>ME0GRktr%E+q9>d#ut zI?wu@jf)Mz_KdBOZJQmRU7Q`sp20rMe#LQ*LyO}PM=i$&CmyFbrxRy3=LF|h5EsY@ z6blIJYZzA@>3V9U=m8g5*P9@u2gF@;LJp z@+|UV@k;P|@Rsnt<|E{j=L_Jg<$KFd&9BM-jK7or@;=9X)BCCSC+`0e5D{<_C==L( zl0(&@&!9cf8$pPmonXG;iV%^Ivd|NuE}?5-9$^RJBH?ur3K1=lXpv!&pQ2)-KB5hx zCt@68)?x)>uf-|Fb;T3JCnc~X6eONV^htb^6qWRoY?1sR#V6$|RU`FYnoHV2x=i{& zhE2vsrdVe00m}pH2QMD%$+F7Y$d<_N%dyMZ%T>r7%X7=S$k)kVDhMd}D6}hlRg_eG ztoRa!0aJm+!RD37l@LmKN;}GI%8tsl%2z5PDi2j&s$!{XsHUpER%24LQ>#|HQWsNy ztUjtisG+Zsr?IaI(e%;m(L&c!*Gkvg(&p6m(C*X$bkuaxb+&c6biH+Z;n;9pcs~4{ z9#rp<-h@7dzLkEB{ucuUgJgqE1Q_9u7&as^G&ig^{AvUk|W zm#?lmu9dF8-HhBC-ErNm-Mc-=JzPCTJ()d&JQuxqyrR7Jyd}Ley{~*Ud@6j=d@X#t z{3uZy`B{Ikf29B0fCmBj0k?sMfvrJgK^{S~!I0qC;NuYGkn)F^5A7ZfKVp3p_UP?n z`NzefXrZ>D!%x_sL_9fqs`9id3_r{@Z0?!Bv$SWQ!i~avBA6ncMjS+{MAk+TNBKm( zj+Tmk9)lU<95Wv)6q_6SE6zS{I$j_?EB;4vbK3O!mCcdC~cfS7NWa79xS)>hm$>2}r*<4(yg_ip2!=wAO@*xT8C{r$}YyMwbs-@_kA5$}lJ zWgXu;u6{50zVAf&WZ~58^ytj%?E87t1=&TxCHS)KgWQMNE90x9YoF`iHwho0iJ9ca4fhNC>rg`d{_$P1L>=wH^6?E$HZIXs90=`oHDB2>p+e|DoXjVEdQ- z|F!=85+K7x_rsOOKqCXt$anHPJBf@Ud`ku?bKNR-~wq zQO;ptV4`9Cu?$5+#~>req+r9sW(9EAaVepKctY|D_#9MlJ!(Z8TW=a+pDdaagIS1j61gFe9_IePnTI`7O!-Guj^yP!9Z`9$;bP;Nsz< z`VGlY41ernpktw-HcV(t=xF2^WNZ|yf&eCzU0wl851Ud5Zfk?1=;3K~(0SpK~M5MrS8l3|bmvcQL4{WOwG$tKL) z4!FR2O=QfObz{fi2$b2FDB&k#DG2E`ul|az{xx^v{Ii|*pL<`@zvdMSK`jI8IFx)hwnp_iN71P(ZpCQ06EY8$t1l!I zjS>v~`#n2KMCcb|S4^Z9(&W9}#tpvy>>o4YzlMZ98N|YNxJ$Cdr9=*cV*?=NN|b%! z)T@ivAAh#9df!ZUpL0E--mCS#*$}wD_DZlzsusxbjFi`X83kcDs))WtgT>}@Ny>uv9<1QDb+%J845EQb ziwwr3xCZ3|Hla<=y{a1_z3#auhr159clG_nCb|Y6t#%fAgJ7E<9}hlQDA1{?3K32C zr7u!#gwSK4pEA=A=kJ^eW(N-L$W>NpSq~-??1VnuCuU7F3c~>B zg)$U=weo=lCe0+(T7mUXNmwpe&`+ryG-^`j0cUDAPUetP#YeY2ovC`tFh<7#X#JPO zAF$a_YWNXPXNI1Q2jy!m*tsdiP2uLxK$A{9eQtwuXN6YDPxzVPVeDV8nuB^ z`YgOTv>gGwR=)vAa+=!E@)T(aiNj!9jm{BH-xFua16inEI->(so``_v&B|yiv`nd1 zV0QL-B(qrJvvRc;rMjsNYg8kUXo#UZd(Mw6TLrL#)$QR=y@H2h&}1JwU#Be|8cD<7 zK--d7bt=<7%bo9JJ9o@YbZ4c?a$_efIWcHz#bRqGY; zyWouuL56T%O)TuZszN|Y_y?rUU8k4nFO;fX`(A!32!b`0a#Fv_m>BD9(5(>@kt|Yp z;7W!lshi5g7hdJfFlqqXUs`~lLOP_VT5q)R1*LuiNRatZP-k5n4bv;WQI(*By#dFc zp8d+th0=u&Ywu!BNuG?$Elzy^daM7j;gETFD%OZ$%2+f%0A7{5F5BYxauj^MH64Xh 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

uYG7s(h)Kq}}$!(Op@jR>4UQ@p(|Jln0WvIY;Cu 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%0A{S1D=bu=GUZukc`_7bEiJ^v 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

WByN7-JQ9khI$HWT5(Fyx23#yG>%PvDF2 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 z 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⁡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®bRa+qB-04B@F~}n6 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¬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

    R#OR54wM^e^&Lu&-GYv{Y!!R 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

    iCGyJ^G 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 *{sjmWybGI{zbsdSB(T2)>_7DhWSg}9Dx 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 T|Z6CSeRgY3%C{o3Z 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

    5;P;_a|2ii*Hqmr)%C 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 literal 0 HcmV?d00001 From 2f888007498542cde99e04af7ea1e6f747a49740 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 20 Jun 2024 13:23:53 +0100 Subject: [PATCH 063/595] refactor(framework:skip) Append the doc string of `StateFactory` (#3648) --- src/py/flwr/server/superlink/state/state_factory.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/server/superlink/state/state_factory.py b/src/py/flwr/server/superlink/state/state_factory.py index 62a00d910828..51f5bf5e2ee2 100644 --- a/src/py/flwr/server/superlink/state/state_factory.py +++ b/src/py/flwr/server/superlink/state/state_factory.py @@ -26,7 +26,16 @@ class StateFactory: - """Factory class that creates State instances.""" + """Factory class that creates State instances. + + Parameters + ---------- + database : str + A string representing the path to the database file that will be opened. + Note that passing ':memory:' will open a connection to a database that is + in RAM, instead of on disk. For more information on special in-memory + databases, please refer to https://sqlite.org/inmemorydb.html. + """ def __init__(self, database: str) -> None: self.database = database From 0fc8e36f329bbc0413ee567ce63cbf26b191be55 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 20 Jun 2024 15:32:12 +0200 Subject: [PATCH 064/595] fix(framework:skip) Display correct folder upon flwr new (#3646) --- src/py/flwr/cli/new/new.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/cli/new/new.py b/src/py/flwr/cli/new/new.py index 9bbc016de1a8..94da99dce36e 100644 --- a/src/py/flwr/cli/new/new.py +++ b/src/py/flwr/cli/new/new.py @@ -190,7 +190,7 @@ def new( ) print( typer.style( - f" cd {project_name}\n" + " pip install -e .\n flwr run\n", + f" cd {package_name}\n" + " pip install -e .\n flwr run\n", fg=typer.colors.BRIGHT_CYAN, bold=True, ) From 6c91366f8fa367322cbfdcfd0de7c9303354a4f1 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Thu, 20 Jun 2024 16:11:25 +0200 Subject: [PATCH 065/595] docs(framework:skip) Update flwr version in Docker docs (#3592) --- ...contributor-how-to-build-docker-images.rst | 8 +-- doc/source/how-to-run-flower-using-docker.rst | 53 +++++++++---------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/doc/source/contributor-how-to-build-docker-images.rst b/doc/source/contributor-how-to-build-docker-images.rst index 2efc739f54f0..4c178f439a07 100644 --- a/doc/source/contributor-how-to-build-docker-images.rst +++ b/doc/source/contributor-how-to-build-docker-images.rst @@ -65,7 +65,7 @@ Building the base image * - ``FLWR_VERSION`` - Version of Flower to be installed. - Yes - - ``1.8.0`` + - ``1.9.0`` * - ``FLWR_PACKAGE`` - The Flower package to be installed. - No @@ -73,14 +73,14 @@ Building the base image The following example creates a base Ubuntu/Alpine image with Python 3.11.0, pip 23.0.1, -setuptools 69.0.2 and Flower 1.8.0: +setuptools 69.0.2 and Flower 1.9.0: .. code-block:: bash $ cd src/docker/base/ $ docker build \ --build-arg PYTHON_VERSION=3.11.0 \ - --build-arg FLWR_VERSION=1.8.0 \ + --build-arg FLWR_VERSION=1.9.0 \ --build-arg PIP_VERSION=23.0.1 \ --build-arg SETUPTOOLS_VERSION=69.0.2 \ -t flwr_base:0.1.0 . @@ -106,7 +106,7 @@ Building the SuperLink/SuperNode or ServerApp image * - ``BASE_IMAGE`` - The Tag of the Flower base image. - Yes - - ``1.8.0-py3.10-ubuntu22.04`` + - ``1.9.0-py3.10-ubuntu22.04`` The following example creates a SuperLink/SuperNode or ServerApp image with the official Flower base image: diff --git a/doc/source/how-to-run-flower-using-docker.rst b/doc/source/how-to-run-flower-using-docker.rst index 54079968d417..7d9ec883960a 100644 --- a/doc/source/how-to-run-flower-using-docker.rst +++ b/doc/source/how-to-run-flower-using-docker.rst @@ -38,10 +38,10 @@ If you're looking to try out Flower, you can use the following command: .. code-block:: bash - $ docker run --rm -p 9091:9091 -p 9092:9092 flwr/superlink:1.8.0 --insecure + $ docker run --rm -p 9091:9091 -p 9092:9092 flwr/superlink:1.9.0 --insecure -The command pulls the Docker image with the tag ``1.8.0`` from Docker Hub. The tag specifies -the Flower version. In this case, Flower 1.8.0. The ``--rm`` flag tells Docker to remove the +The command pulls the Docker image with the tag ``1.9.0`` from Docker Hub. The tag specifies +the Flower version. In this case, Flower 1.9.0. The ``--rm`` flag tells Docker to remove the container after it exits. .. note:: @@ -66,7 +66,7 @@ You can use ``--help`` to view all available flags that the SuperLink supports: .. code-block:: bash - $ docker run --rm flwr/superlink:1.8.0 --help + $ docker run --rm flwr/superlink:1.9.0 --help Mounting a volume to store the state on the host system ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -88,7 +88,7 @@ container. Furthermore, we use the flag ``--database`` to specify the name of th $ mkdir state $ sudo chown -R 49999:49999 state $ docker run --rm \ - -p 9091:9091 -p 9092:9092 --volume ./state/:/app/state flwr/superlink:1.8.0 \ + -p 9091:9091 -p 9092:9092 --volume ./state/:/app/state flwr/superlink:1.9.0 \ --insecure \ --database state.db @@ -118,7 +118,7 @@ with the ``--ssl-ca-certfile``, ``--ssl-certfile`` and ``--ssl-keyfile`` flag. $ docker run --rm \ -p 9091:9091 -p 9092:9092 \ - --volume ./certificates/:/app/certificates/:ro flwr/superlink:nightly \ + --volume ./certificates/:/app/certificates/:ro flwr/superlink:1.9.0 \ --ssl-ca-certfile certificates/ca.crt \ --ssl-certfile certificates/server.pem \ --ssl-keyfile certificates/server.key @@ -136,14 +136,6 @@ Flower SuperNode The SuperNode Docker image comes with a pre-installed version of Flower and serves as a base for building your own SuperNode image. -.. important:: - - The SuperNode Docker image currently works only with the 1.9.0-nightly release. A stable version - will be available when Flower 1.9.0 (stable) gets released (ETA: May). A SuperNode nightly image - must be paired with the corresponding SuperLink and ServerApp nightly images released on the same - day. To ensure the versions are in sync, using the concrete tag, e.g., ``1.9.0.dev20240501`` - instead of ``nightly`` is recommended. - We will use the ``quickstart-pytorch`` example, which you can find in the Flower repository, to illustrate how you can dockerize your ClientApp. @@ -204,7 +196,7 @@ The ``Dockerfile.supernode`` contains the instructions that assemble the SuperNo .. code-block:: dockerfile - FROM flwr/supernode:nightly + FROM flwr/supernode:1.9.0 WORKDIR /app @@ -214,7 +206,7 @@ The ``Dockerfile.supernode`` contains the instructions that assemble the SuperNo COPY client.py ./ ENTRYPOINT ["flower-client-app", "client:app"] -In the first two lines, we instruct Docker to use the SuperNode image tagged ``nightly`` as a base +In the first two lines, we instruct Docker to use the SuperNode image tagged ``1.9.0`` as a base image and set our working directory to ``/app``. The following instructions will now be executed in the ``/app`` directory. Next, we install the ClientApp dependencies by copying the ``requirements.txt`` file into the image and run ``pip install``. In the last two lines, @@ -275,7 +267,7 @@ To see all available flags that the SuperNode supports, run: .. code-block:: bash - $ docker run --rm flwr/supernode:nightly --help + $ docker run --rm flwr/supernode:1.9.0 --help Enabling SSL for secure connections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -325,14 +317,14 @@ The ``Dockerfile.serverapp`` contains the instructions that assemble the ServerA .. code-block:: dockerfile - FROM flwr/serverapp:1.8.0 + FROM flwr/serverapp:1.9.0 WORKDIR /app COPY server.py ./ ENTRYPOINT ["flower-server-app", "server:app"] -In the first two lines, we instruct Docker to use the ServerApp image tagged ``1.8.0`` as a base +In the first two lines, we instruct Docker to use the ServerApp image tagged ``1.9.0`` as a base image and set our working directory to ``/app``. The following instructions will now be executed in the ``/app`` directory. In the last two lines, we copy the ``server.py`` module into the image and set the entry point to ``flower-server-app`` with the argument ``server:app``. @@ -391,7 +383,7 @@ To see all available flags that the ServerApp supports, run: .. code-block:: bash - $ docker run --rm flwr/serverapp:1.8.0 --help + $ docker run --rm flwr/serverapp:1.9.0 --help Enabling SSL for secure connections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -425,7 +417,7 @@ Run the Docker image with the ``-u`` flag and specify ``root`` as the username: .. code-block:: bash - $ docker run --rm -u root flwr/superlink:1.8.0 + $ docker run --rm -u root flwr/superlink:1.9.0 This command will run the Docker container with root user privileges. @@ -436,7 +428,7 @@ missing system dependencies, you can use the ``USER root`` directive within your .. code-block:: dockerfile - FROM flwr/supernode:1.8.0 + FROM flwr/supernode:1.9.0 # Switch to root user USER root @@ -456,6 +448,13 @@ Using a different Flower version If you want to use a different version of Flower, for example Flower nightly, you can do so by changing the tag. All available versions are on `Docker Hub `__. +.. important:: + + When using Flower nightly, the SuperLink nightly image must be paired with the corresponding + SuperNode and ServerApp nightly images released on the same day. To ensure the versions are + in sync, using the concrete tag, e.g., ``1.10.0.dev20240610`` instead of ``nightly`` is + recommended. + Pinning a Docker image to a specific version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -464,19 +463,19 @@ updates of system dependencies that should not change the functionality of Flowe want to ensure that you always use the same image, you can specify the hash of the image instead of the tag. -The following command returns the current image hash referenced by the ``superlink:1.8.0`` tag: +The following command returns the current image hash referenced by the ``superlink:1.9.0`` tag: .. code-block:: bash - $ docker inspect --format='{{index .RepoDigests 0}}' flwr/superlink:1.8.0 - flwr/superlink@sha256:1b855d1fa4e344e4d95db99793f2bb35d8c63f6a1decdd736863bfe4bb0fe46c + $ docker inspect --format='{{index .RepoDigests 0}}' flwr/superlink:1.9.0 + flwr/superlink@sha256:985c24b2b337ab7f15a554fde9d860cede95079bcaa244fda8f12c0805e34c7d Next, we can pin the hash when running a new SuperLink container: .. code-block:: bash $ docker run \ - --rm flwr/superlink@sha256:1b855d1fa4e344e4d95db99793f2bb35d8c63f6a1decdd736863bfe4bb0fe46c \ + --rm flwr/superlink@sha256:985c24b2b337ab7f15a554fde9d860cede95079bcaa244fda8f12c0805e34c7d \ --insecure Setting environment variables @@ -487,4 +486,4 @@ To set a variable inside a Docker container, you can use the ``-e = .. code-block:: bash $ docker run -e FLWR_TELEMETRY_ENABLED=0 \ - --rm flwr/superlink:1.8.0 --insecure + --rm flwr/superlink:1.9.0 --insecure From 540b4c5aa45b00ba9924882f1e51eaa8660f7de2 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 20 Jun 2024 16:18:15 +0200 Subject: [PATCH 066/595] fix(framework:skip) Fix copyright notices (#3649) --- src/py/flwr/client/app_test.py | 2 +- src/py/flwr/client/client_app.py | 2 +- src/py/flwr/client/client_test.py | 2 +- src/py/flwr/client/dpfedavg_numpy_client.py | 2 +- src/py/flwr/client/grpc_rere_client/__init__.py | 2 +- .../grpc_rere_client/client_interceptor_test.py | 2 +- src/py/flwr/client/grpc_rere_client/connection.py | 2 +- src/py/flwr/client/message_handler/__init__.py | 2 +- .../flwr/client/message_handler/message_handler.py | 2 +- .../client/message_handler/message_handler_test.py | 2 +- src/py/flwr/client/mod/__init__.py | 2 +- .../flwr/client/mod/secure_aggregation/__init__.py | 2 +- src/py/flwr/client/mod/utils.py | 2 +- src/py/flwr/client/mod/utils_test.py | 2 +- src/py/flwr/client/numpy_client_test.py | 2 +- src/py/flwr/client/rest_client/__init__.py | 2 +- src/py/flwr/client/rest_client/connection.py | 2 +- src/py/flwr/common/address.py | 2 +- src/py/flwr/common/address_test.py | 2 +- src/py/flwr/common/constant.py | 2 +- src/py/flwr/common/date.py | 2 +- src/py/flwr/common/dp.py | 2 +- src/py/flwr/common/grpc.py | 2 +- src/py/flwr/common/message_test.py | 2 +- src/py/flwr/common/secure_aggregation/__init__.py | 2 +- .../common/secure_aggregation/crypto/__init__.py | 2 +- .../common/secure_aggregation/crypto/shamir.py | 2 +- .../crypto/symmetric_encryption.py | 2 +- .../secure_aggregation/ndarrays_arithmetic.py | 2 +- .../flwr/common/secure_aggregation/quantization.py | 2 +- .../secure_aggregation/secaggplus_constants.py | 2 +- .../common/secure_aggregation/secaggplus_utils.py | 2 +- src/py/flwr/common/serde_test.py | 2 +- src/py/flwr/common/version.py | 14 ++++++++++++++ src/py/flwr/server/client_proxy_test.py | 2 +- src/py/flwr/server/compat/app.py | 2 +- src/py/flwr/server/compat/driver_client_proxy.py | 2 +- .../flwr/server/compat/driver_client_proxy_test.py | 2 +- src/py/flwr/server/driver/grpc_driver.py | 2 +- src/py/flwr/server/driver/grpc_driver_test.py | 2 +- src/py/flwr/server/driver/inmemory_driver_test.py | 2 +- src/py/flwr/server/history_test.py | 2 +- src/py/flwr/server/server_app_test.py | 2 +- src/py/flwr/server/strategy/bulyan.py | 2 +- src/py/flwr/server/strategy/bulyan_test.py | 2 +- src/py/flwr/server/strategy/dpfedavg_adaptive.py | 2 +- src/py/flwr/server/strategy/dpfedavg_fixed.py | 2 +- src/py/flwr/server/strategy/fedadagrad.py | 2 +- src/py/flwr/server/strategy/fedadagrad_test.py | 2 +- src/py/flwr/server/strategy/fedadam.py | 2 +- src/py/flwr/server/strategy/fedavg_android.py | 2 +- src/py/flwr/server/strategy/fedavgm.py | 2 +- src/py/flwr/server/strategy/fedavgm_test.py | 2 +- src/py/flwr/server/strategy/fedmedian.py | 2 +- src/py/flwr/server/strategy/fedmedian_test.py | 2 +- src/py/flwr/server/strategy/fedopt.py | 2 +- src/py/flwr/server/strategy/fedprox.py | 2 +- src/py/flwr/server/strategy/fedxgb_bagging.py | 2 +- src/py/flwr/server/strategy/fedxgb_cyclic.py | 2 +- src/py/flwr/server/strategy/fedxgb_nn_avg.py | 2 +- src/py/flwr/server/strategy/fedyogi.py | 2 +- src/py/flwr/server/strategy/krum.py | 2 +- src/py/flwr/server/strategy/krum_test.py | 2 +- src/py/flwr/server/strategy/multikrum_test.py | 2 +- src/py/flwr/server/strategy/qfedavg.py | 2 +- src/py/flwr/server/superlink/driver/__init__.py | 2 +- src/py/flwr/server/superlink/driver/driver_grpc.py | 2 +- .../server/superlink/driver/driver_servicer.py | 2 +- .../superlink/driver/driver_servicer_test.py | 2 +- src/py/flwr/server/superlink/fleet/__init__.py | 2 +- .../server/superlink/fleet/grpc_bidi/__init__.py | 2 +- .../fleet/grpc_bidi/flower_service_servicer.py | 2 +- .../grpc_bidi/flower_service_servicer_test.py | 2 +- .../superlink/fleet/grpc_bidi/grpc_bridge.py | 2 +- .../superlink/fleet/grpc_bidi/grpc_bridge_test.py | 2 +- .../superlink/fleet/grpc_bidi/grpc_client_proxy.py | 2 +- .../fleet/grpc_bidi/grpc_client_proxy_test.py | 2 +- .../superlink/fleet/grpc_bidi/grpc_server.py | 2 +- .../superlink/fleet/grpc_bidi/grpc_server_test.py | 2 +- .../server/superlink/fleet/grpc_rere/__init__.py | 2 +- .../superlink/fleet/grpc_rere/fleet_servicer.py | 2 +- .../superlink/fleet/message_handler/__init__.py | 2 +- .../fleet/message_handler/message_handler.py | 2 +- .../fleet/message_handler/message_handler_test.py | 2 +- .../server/superlink/fleet/rest_rere/__init__.py | 2 +- .../server/superlink/fleet/rest_rere/rest_api.py | 2 +- src/py/flwr/server/superlink/state/__init__.py | 2 +- .../flwr/server/superlink/state/in_memory_state.py | 2 +- src/py/flwr/server/superlink/state/sqlite_state.py | 2 +- .../server/superlink/state/sqlite_state_test.py | 2 +- src/py/flwr/server/superlink/state/state.py | 2 +- .../flwr/server/superlink/state/state_factory.py | 2 +- src/py/flwr/server/superlink/state/state_test.py | 2 +- src/py/flwr/server/utils/__init__.py | 2 +- src/py/flwr/server/utils/tensorboard.py | 2 +- src/py/flwr/server/utils/tensorboard_test.py | 2 +- src/py/flwr/simulation/__init__.py | 2 +- src/py/flwr/simulation/app.py | 2 +- src/py/flwr/simulation/ray_transport/__init__.py | 2 +- .../simulation/ray_transport/ray_client_proxy.py | 2 +- 100 files changed, 113 insertions(+), 99 deletions(-) diff --git a/src/py/flwr/client/app_test.py b/src/py/flwr/client/app_test.py index 56d6308a0fe2..74ade03f973a 100644 --- a/src/py/flwr/client/app_test.py +++ b/src/py/flwr/client/app_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/client_app.py b/src/py/flwr/client/client_app.py index 82539834eaad..2e810f6560f2 100644 --- a/src/py/flwr/client/client_app.py +++ b/src/py/flwr/client/client_app.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/client_test.py b/src/py/flwr/client/client_test.py index 373c676e5edc..343b6cf093b2 100644 --- a/src/py/flwr/client/client_test.py +++ b/src/py/flwr/client/client_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/dpfedavg_numpy_client.py b/src/py/flwr/client/dpfedavg_numpy_client.py index ab31a289d29b..c592d10936d5 100644 --- a/src/py/flwr/client/dpfedavg_numpy_client.py +++ b/src/py/flwr/client/dpfedavg_numpy_client.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/grpc_rere_client/__init__.py b/src/py/flwr/client/grpc_rere_client/__init__.py index 93903e725776..e7c9408c0047 100644 --- a/src/py/flwr/client/grpc_rere_client/__init__.py +++ b/src/py/flwr/client/grpc_rere_client/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py b/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py index cc35ffef46db..9607dac5679e 100644 --- a/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py +++ b/src/py/flwr/client/grpc_rere_client/client_interceptor_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index b1c268d51d55..34dc0e417383 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/message_handler/__init__.py b/src/py/flwr/client/message_handler/__init__.py index 653563963de5..a345b4af3ef2 100644 --- a/src/py/flwr/client/message_handler/__init__.py +++ b/src/py/flwr/client/message_handler/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/message_handler/message_handler.py b/src/py/flwr/client/message_handler/message_handler.py index e5acbe0cc9d0..68326852970f 100644 --- a/src/py/flwr/client/message_handler/message_handler.py +++ b/src/py/flwr/client/message_handler/message_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/message_handler/message_handler_test.py b/src/py/flwr/client/message_handler/message_handler_test.py index 8a2db1804e4a..40907942513d 100644 --- a/src/py/flwr/client/message_handler/message_handler_test.py +++ b/src/py/flwr/client/message_handler/message_handler_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/mod/__init__.py b/src/py/flwr/client/mod/__init__.py index 0b4cf6488421..35d1fa81805c 100644 --- a/src/py/flwr/client/mod/__init__.py +++ b/src/py/flwr/client/mod/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/mod/secure_aggregation/__init__.py b/src/py/flwr/client/mod/secure_aggregation/__init__.py index 8892d8c03935..a64bc89e62c9 100644 --- a/src/py/flwr/client/mod/secure_aggregation/__init__.py +++ b/src/py/flwr/client/mod/secure_aggregation/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/mod/utils.py b/src/py/flwr/client/mod/utils.py index 4c3c32944f01..c8fb21379783 100644 --- a/src/py/flwr/client/mod/utils.py +++ b/src/py/flwr/client/mod/utils.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/mod/utils_test.py b/src/py/flwr/client/mod/utils_test.py index 4676a2c02c4b..035e41639b10 100644 --- a/src/py/flwr/client/mod/utils_test.py +++ b/src/py/flwr/client/mod/utils_test.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/numpy_client_test.py b/src/py/flwr/client/numpy_client_test.py index 526098798e45..06a0deafe2c9 100644 --- a/src/py/flwr/client/numpy_client_test.py +++ b/src/py/flwr/client/numpy_client_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/rest_client/__init__.py b/src/py/flwr/client/rest_client/__init__.py index c3485483ad35..a24d822a6d75 100644 --- a/src/py/flwr/client/rest_client/__init__.py +++ b/src/py/flwr/client/rest_client/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index 5f5e153f9d8d..db5bd7eb6770 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/address.py b/src/py/flwr/common/address.py index 71b6d684597f..1c6481b80a74 100644 --- a/src/py/flwr/common/address.py +++ b/src/py/flwr/common/address.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/address_test.py b/src/py/flwr/common/address_test.py index 420b89871d69..d5901ed640b1 100644 --- a/src/py/flwr/common/address_test.py +++ b/src/py/flwr/common/address_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index 193f000ca42e..ce29b3edb30e 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/date.py b/src/py/flwr/common/date.py index f47ad5470106..7f30f5e0591a 100644 --- a/src/py/flwr/common/date.py +++ b/src/py/flwr/common/date.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/dp.py b/src/py/flwr/common/dp.py index 83a72b8ce749..527805c8ef42 100644 --- a/src/py/flwr/common/dp.py +++ b/src/py/flwr/common/dp.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/grpc.py b/src/py/flwr/common/grpc.py index ead0329ca79c..ec8fe823a7eb 100644 --- a/src/py/flwr/common/grpc.py +++ b/src/py/flwr/common/grpc.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/message_test.py b/src/py/flwr/common/message_test.py index 19f8aeb1eb63..daee57896903 100644 --- a/src/py/flwr/common/message_test.py +++ b/src/py/flwr/common/message_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/__init__.py b/src/py/flwr/common/secure_aggregation/__init__.py index b4e0acc0c148..77e1ea3842d7 100644 --- a/src/py/flwr/common/secure_aggregation/__init__.py +++ b/src/py/flwr/common/secure_aggregation/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/crypto/__init__.py b/src/py/flwr/common/secure_aggregation/crypto/__init__.py index 2cb34493f7d0..3788dbc0ca15 100644 --- a/src/py/flwr/common/secure_aggregation/crypto/__init__.py +++ b/src/py/flwr/common/secure_aggregation/crypto/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/crypto/shamir.py b/src/py/flwr/common/secure_aggregation/crypto/shamir.py index e56e21b89371..688bfa2153ea 100644 --- a/src/py/flwr/common/secure_aggregation/crypto/shamir.py +++ b/src/py/flwr/common/secure_aggregation/crypto/shamir.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/crypto/symmetric_encryption.py b/src/py/flwr/common/secure_aggregation/crypto/symmetric_encryption.py index 1d004a398ea8..59ca84d604b8 100644 --- a/src/py/flwr/common/secure_aggregation/crypto/symmetric_encryption.py +++ b/src/py/flwr/common/secure_aggregation/crypto/symmetric_encryption.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/ndarrays_arithmetic.py b/src/py/flwr/common/secure_aggregation/ndarrays_arithmetic.py index e926a9531bea..207c15b61518 100644 --- a/src/py/flwr/common/secure_aggregation/ndarrays_arithmetic.py +++ b/src/py/flwr/common/secure_aggregation/ndarrays_arithmetic.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/quantization.py b/src/py/flwr/common/secure_aggregation/quantization.py index 56c25e2bd59c..7946276b6a4f 100644 --- a/src/py/flwr/common/secure_aggregation/quantization.py +++ b/src/py/flwr/common/secure_aggregation/quantization.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/secaggplus_constants.py b/src/py/flwr/common/secure_aggregation/secaggplus_constants.py index 8a15908c13c5..545507eb44ed 100644 --- a/src/py/flwr/common/secure_aggregation/secaggplus_constants.py +++ b/src/py/flwr/common/secure_aggregation/secaggplus_constants.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/secure_aggregation/secaggplus_utils.py b/src/py/flwr/common/secure_aggregation/secaggplus_utils.py index c373573477b9..cf6ac3bfb003 100644 --- a/src/py/flwr/common/secure_aggregation/secaggplus_utils.py +++ b/src/py/flwr/common/secure_aggregation/secaggplus_utils.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/serde_test.py b/src/py/flwr/common/serde_test.py index f9969426fc36..afb11b6956f2 100644 --- a/src/py/flwr/common/serde_test.py +++ b/src/py/flwr/common/serde_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/common/version.py b/src/py/flwr/common/version.py index 6808c66606b1..ac13f70d8a88 100644 --- a/src/py/flwr/common/version.py +++ b/src/py/flwr/common/version.py @@ -1,3 +1,17 @@ +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== """Flower package version helper.""" import importlib.metadata as importlib_metadata diff --git a/src/py/flwr/server/client_proxy_test.py b/src/py/flwr/server/client_proxy_test.py index 685698558e3a..6ca37052a87d 100644 --- a/src/py/flwr/server/client_proxy_test.py +++ b/src/py/flwr/server/client_proxy_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/compat/app.py b/src/py/flwr/server/compat/app.py index 4bb23b846ab7..e978359fa828 100644 --- a/src/py/flwr/server/compat/app.py +++ b/src/py/flwr/server/compat/app.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/compat/driver_client_proxy.py b/src/py/flwr/server/compat/driver_client_proxy.py index 150803786f98..7190786784ec 100644 --- a/src/py/flwr/server/compat/driver_client_proxy.py +++ b/src/py/flwr/server/compat/driver_client_proxy.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/compat/driver_client_proxy_test.py b/src/py/flwr/server/compat/driver_client_proxy_test.py index d9e3d3bc0824..31b917fa869b 100644 --- a/src/py/flwr/server/compat/driver_client_proxy_test.py +++ b/src/py/flwr/server/compat/driver_client_proxy_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/driver/grpc_driver.py b/src/py/flwr/server/driver/grpc_driver.py index 2016d54b655a..e614df659e3f 100644 --- a/src/py/flwr/server/driver/grpc_driver.py +++ b/src/py/flwr/server/driver/grpc_driver.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/driver/grpc_driver_test.py b/src/py/flwr/server/driver/grpc_driver_test.py index 60ab79002f85..72efc5f8b2c6 100644 --- a/src/py/flwr/server/driver/grpc_driver_test.py +++ b/src/py/flwr/server/driver/grpc_driver_test.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/driver/inmemory_driver_test.py b/src/py/flwr/server/driver/inmemory_driver_test.py index 55d52d848dfd..eff38f548826 100644 --- a/src/py/flwr/server/driver/inmemory_driver_test.py +++ b/src/py/flwr/server/driver/inmemory_driver_test.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/history_test.py b/src/py/flwr/server/history_test.py index adb9d697e409..b53357149623 100644 --- a/src/py/flwr/server/history_test.py +++ b/src/py/flwr/server/history_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/server_app_test.py b/src/py/flwr/server/server_app_test.py index 38c0d6240d90..0751a0cb2bc5 100644 --- a/src/py/flwr/server/server_app_test.py +++ b/src/py/flwr/server/server_app_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/bulyan.py b/src/py/flwr/server/strategy/bulyan.py index 1e4f97530ab7..a81406c255ad 100644 --- a/src/py/flwr/server/strategy/bulyan.py +++ b/src/py/flwr/server/strategy/bulyan.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/bulyan_test.py b/src/py/flwr/server/strategy/bulyan_test.py index 299ed49066fb..93a9ebda3783 100644 --- a/src/py/flwr/server/strategy/bulyan_test.py +++ b/src/py/flwr/server/strategy/bulyan_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/dpfedavg_adaptive.py b/src/py/flwr/server/strategy/dpfedavg_adaptive.py index a908679ed668..423ddddeb379 100644 --- a/src/py/flwr/server/strategy/dpfedavg_adaptive.py +++ b/src/py/flwr/server/strategy/dpfedavg_adaptive.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/dpfedavg_fixed.py b/src/py/flwr/server/strategy/dpfedavg_fixed.py index c54379fc7087..d122f0688922 100644 --- a/src/py/flwr/server/strategy/dpfedavg_fixed.py +++ b/src/py/flwr/server/strategy/dpfedavg_fixed.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedadagrad.py b/src/py/flwr/server/strategy/fedadagrad.py index 4a8f52d98e18..f13c5358da25 100644 --- a/src/py/flwr/server/strategy/fedadagrad.py +++ b/src/py/flwr/server/strategy/fedadagrad.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedadagrad_test.py b/src/py/flwr/server/strategy/fedadagrad_test.py index 0c966442ecaf..b43a4c75d123 100644 --- a/src/py/flwr/server/strategy/fedadagrad_test.py +++ b/src/py/flwr/server/strategy/fedadagrad_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedadam.py b/src/py/flwr/server/strategy/fedadam.py index 8a47cf0dd8ac..dc90e90c7568 100644 --- a/src/py/flwr/server/strategy/fedadam.py +++ b/src/py/flwr/server/strategy/fedadam.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedavg_android.py b/src/py/flwr/server/strategy/fedavg_android.py index 6678b7ced114..2f49cf8784c9 100644 --- a/src/py/flwr/server/strategy/fedavg_android.py +++ b/src/py/flwr/server/strategy/fedavg_android.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedavgm.py b/src/py/flwr/server/strategy/fedavgm.py index fb9261abe89d..ab3d37249db6 100644 --- a/src/py/flwr/server/strategy/fedavgm.py +++ b/src/py/flwr/server/strategy/fedavgm.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedavgm_test.py b/src/py/flwr/server/strategy/fedavgm_test.py index a0e942171627..39da5f4b82c4 100644 --- a/src/py/flwr/server/strategy/fedavgm_test.py +++ b/src/py/flwr/server/strategy/fedavgm_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedmedian.py b/src/py/flwr/server/strategy/fedmedian.py index 17e979d92beb..e7cba5324fa8 100644 --- a/src/py/flwr/server/strategy/fedmedian.py +++ b/src/py/flwr/server/strategy/fedmedian.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedmedian_test.py b/src/py/flwr/server/strategy/fedmedian_test.py index 57cf08d8c01d..3960ad70b145 100644 --- a/src/py/flwr/server/strategy/fedmedian_test.py +++ b/src/py/flwr/server/strategy/fedmedian_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedopt.py b/src/py/flwr/server/strategy/fedopt.py index be5f260d96fa..c581d4797123 100644 --- a/src/py/flwr/server/strategy/fedopt.py +++ b/src/py/flwr/server/strategy/fedopt.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedprox.py b/src/py/flwr/server/strategy/fedprox.py index d20f578b193d..f15271e06060 100644 --- a/src/py/flwr/server/strategy/fedprox.py +++ b/src/py/flwr/server/strategy/fedprox.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedxgb_bagging.py b/src/py/flwr/server/strategy/fedxgb_bagging.py index a8e8adddafbb..a74ee81976a6 100644 --- a/src/py/flwr/server/strategy/fedxgb_bagging.py +++ b/src/py/flwr/server/strategy/fedxgb_bagging.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedxgb_cyclic.py b/src/py/flwr/server/strategy/fedxgb_cyclic.py index 2605daab29f4..75025a89728b 100644 --- a/src/py/flwr/server/strategy/fedxgb_cyclic.py +++ b/src/py/flwr/server/strategy/fedxgb_cyclic.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedxgb_nn_avg.py b/src/py/flwr/server/strategy/fedxgb_nn_avg.py index 8dedc925f350..4562663287ae 100644 --- a/src/py/flwr/server/strategy/fedxgb_nn_avg.py +++ b/src/py/flwr/server/strategy/fedxgb_nn_avg.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/fedyogi.py b/src/py/flwr/server/strategy/fedyogi.py index 7c77aab7ae73..c7b2ebb51667 100644 --- a/src/py/flwr/server/strategy/fedyogi.py +++ b/src/py/flwr/server/strategy/fedyogi.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/krum.py b/src/py/flwr/server/strategy/krum.py index 16eb5212940e..074d018c35a3 100644 --- a/src/py/flwr/server/strategy/krum.py +++ b/src/py/flwr/server/strategy/krum.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/krum_test.py b/src/py/flwr/server/strategy/krum_test.py index 653dc9a8475d..b34982325b39 100644 --- a/src/py/flwr/server/strategy/krum_test.py +++ b/src/py/flwr/server/strategy/krum_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/multikrum_test.py b/src/py/flwr/server/strategy/multikrum_test.py index f874dc2f9800..7a1a4c3ecf38 100644 --- a/src/py/flwr/server/strategy/multikrum_test.py +++ b/src/py/flwr/server/strategy/multikrum_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2022 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/strategy/qfedavg.py b/src/py/flwr/server/strategy/qfedavg.py index 758e8e608e9f..26a397d4cf8c 100644 --- a/src/py/flwr/server/strategy/qfedavg.py +++ b/src/py/flwr/server/strategy/qfedavg.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/driver/__init__.py b/src/py/flwr/server/superlink/driver/__init__.py index 2bfe63e6065f..58fbc479478f 100644 --- a/src/py/flwr/server/superlink/driver/__init__.py +++ b/src/py/flwr/server/superlink/driver/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/driver/driver_grpc.py b/src/py/flwr/server/superlink/driver/driver_grpc.py index f74000bc59c4..782935481945 100644 --- a/src/py/flwr/server/superlink/driver/driver_grpc.py +++ b/src/py/flwr/server/superlink/driver/driver_grpc.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/driver/driver_servicer.py b/src/py/flwr/server/superlink/driver/driver_servicer.py index 30d2e883dc63..03128f02158e 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/driver/driver_servicer_test.py b/src/py/flwr/server/superlink/driver/driver_servicer_test.py index 99f7cc007a89..394d6be7ee6a 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer_test.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer_test.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/__init__.py b/src/py/flwr/server/superlink/fleet/__init__.py index d3c3ef90163d..c236ed06ae1c 100644 --- a/src/py/flwr/server/superlink/fleet/__init__.py +++ b/src/py/flwr/server/superlink/fleet/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/__init__.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/__init__.py index bae8bc431edd..6b2c2bf3ffec 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/__init__.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py index 6f94ea844e38..79f1a8f9902b 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer_test.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer_test.py index bd93554a6a32..03e8555f8ecf 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer_test.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/flower_service_servicer_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py index d5b4a915c609..5fe0396696ab 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge_test.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge_test.py index f7c236acd7a1..f9b6b97030f0 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge_test.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_bridge_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py index ac62ad014950..03497743becd 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy_test.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy_test.py index e7077dfd39ae..6d3eb4f67e30 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy_test.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_client_proxy_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py index ae685fda91a7..1b4286c87b92 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server_test.py b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server_test.py index 8afa37515950..7ff730b17afa 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server_test.py +++ b/src/py/flwr/server/superlink/fleet/grpc_bidi/grpc_server_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/__init__.py b/src/py/flwr/server/superlink/fleet/grpc_rere/__init__.py index 61ab71d91400..03c8ded2423a 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/__init__.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py b/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py index 13e024eb31e4..89342a46eb48 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/message_handler/__init__.py b/src/py/flwr/server/superlink/fleet/message_handler/__init__.py index 18b0f11fa6c5..3db0ef5d1611 100644 --- a/src/py/flwr/server/superlink/fleet/message_handler/__init__.py +++ b/src/py/flwr/server/superlink/fleet/message_handler/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py index dceb18cab453..b70cd54035fe 100644 --- a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py +++ b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/message_handler/message_handler_test.py b/src/py/flwr/server/superlink/fleet/message_handler/message_handler_test.py index c135f6fb7b61..ec521b328eb8 100644 --- a/src/py/flwr/server/superlink/fleet/message_handler/message_handler_test.py +++ b/src/py/flwr/server/superlink/fleet/message_handler/message_handler_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/rest_rere/__init__.py b/src/py/flwr/server/superlink/fleet/rest_rere/__init__.py index a926f9ca0bfc..f24db2a2e12f 100644 --- a/src/py/flwr/server/superlink/fleet/rest_rere/__init__.py +++ b/src/py/flwr/server/superlink/fleet/rest_rere/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py b/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py index c7ff496d39bf..1ed67e5eb0aa 100644 --- a/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py +++ b/src/py/flwr/server/superlink/fleet/rest_rere/rest_api.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/state/__init__.py b/src/py/flwr/server/superlink/state/__init__.py index 7f260d733bbe..9d3bd220403b 100644 --- a/src/py/flwr/server/superlink/state/__init__.py +++ b/src/py/flwr/server/superlink/state/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/state/in_memory_state.py b/src/py/flwr/server/superlink/state/in_memory_state.py index e03260355db9..da9c754c3115 100644 --- a/src/py/flwr/server/superlink/state/in_memory_state.py +++ b/src/py/flwr/server/superlink/state/in_memory_state.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/state/sqlite_state.py b/src/py/flwr/server/superlink/state/sqlite_state.py index b9672757b0e6..4df9470ded62 100644 --- a/src/py/flwr/server/superlink/state/sqlite_state.py +++ b/src/py/flwr/server/superlink/state/sqlite_state.py @@ -1,4 +1,4 @@ -# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/state/sqlite_state_test.py b/src/py/flwr/server/superlink/state/sqlite_state_test.py index 20927df1cf12..10e12da96bd5 100644 --- a/src/py/flwr/server/superlink/state/sqlite_state_test.py +++ b/src/py/flwr/server/superlink/state/sqlite_state_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/state/state.py b/src/py/flwr/server/superlink/state/state.py index d1fc9465c9f2..65e2c63cab69 100644 --- a/src/py/flwr/server/superlink/state/state.py +++ b/src/py/flwr/server/superlink/state/state.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/state/state_factory.py b/src/py/flwr/server/superlink/state/state_factory.py index 51f5bf5e2ee2..96c8d445c16e 100644 --- a/src/py/flwr/server/superlink/state/state_factory.py +++ b/src/py/flwr/server/superlink/state/state_factory.py @@ -1,4 +1,4 @@ -# Copyright 2022 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/superlink/state/state_test.py b/src/py/flwr/server/superlink/state/state_test.py index 81307d938400..373202d5cde6 100644 --- a/src/py/flwr/server/superlink/state/state_test.py +++ b/src/py/flwr/server/superlink/state/state_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/utils/__init__.py b/src/py/flwr/server/utils/__init__.py index c370716adaac..8994374c4d08 100644 --- a/src/py/flwr/server/utils/__init__.py +++ b/src/py/flwr/server/utils/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/utils/tensorboard.py b/src/py/flwr/server/utils/tensorboard.py index 3e8d1e62411e..5d38fc159657 100644 --- a/src/py/flwr/server/utils/tensorboard.py +++ b/src/py/flwr/server/utils/tensorboard.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/server/utils/tensorboard_test.py b/src/py/flwr/server/utils/tensorboard_test.py index 1827a42cf6e6..689755c6da16 100644 --- a/src/py/flwr/server/utils/tensorboard_test.py +++ b/src/py/flwr/server/utils/tensorboard_test.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/simulation/__init__.py b/src/py/flwr/simulation/__init__.py index 3d648b14edba..5db90a352e3f 100644 --- a/src/py/flwr/simulation/__init__.py +++ b/src/py/flwr/simulation/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/simulation/app.py b/src/py/flwr/simulation/app.py index 4b4b7249ccd3..856d6fc45e22 100644 --- a/src/py/flwr/simulation/app.py +++ b/src/py/flwr/simulation/app.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/simulation/ray_transport/__init__.py b/src/py/flwr/simulation/ray_transport/__init__.py index 0e82b75bb4b3..ed4971935a15 100644 --- a/src/py/flwr/simulation/ray_transport/__init__.py +++ b/src/py/flwr/simulation/ray_transport/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index 5e344eb087ee..d3d103bb377a 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -1,4 +1,4 @@ -# Copyright 2020 Flower Labs GmbH. All Rights Reserved. +# Copyright 2021 Flower Labs GmbH. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From cd330e2bec026d910a86e146377a64783c3f5e2f Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 20 Jun 2024 16:24:41 +0200 Subject: [PATCH 067/595] ci(framework:skip) Check copyright notices correctness (#3657) --- .github/workflows/framework.yml | 2 + dev/format.sh | 1 + dev/test.sh | 4 ++ src/py/flwr_tool/check_copyright.py | 76 +++++++++++++++++++++++++++++ src/py/flwr_tool/fix_copyright.py | 59 ++++++++++++++++++++++ 5 files changed, 142 insertions(+) create mode 100755 src/py/flwr_tool/check_copyright.py create mode 100755 src/py/flwr_tool/fix_copyright.py diff --git a/.github/workflows/framework.yml b/.github/workflows/framework.yml index 784f04750c5e..feb08229be06 100644 --- a/.github/workflows/framework.yml +++ b/.github/workflows/framework.yml @@ -31,6 +31,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Bootstrap uses: ./.github/actions/bootstrap with: diff --git a/dev/format.sh b/dev/format.sh index b9e3b00dffe1..71edf9c6065a 100755 --- a/dev/format.sh +++ b/dev/format.sh @@ -3,6 +3,7 @@ set -e cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"/../ # Python +python -m flwr_tool.check_copyright src/py/flwr python -m flwr_tool.init_py_fix src/py/flwr python -m isort --skip src/py/flwr/proto src/py python -m black -q --exclude src/py/flwr/proto src/py diff --git a/dev/test.sh b/dev/test.sh index 7cabf35abf41..5b827380bc50 100755 --- a/dev/test.sh +++ b/dev/test.sh @@ -58,6 +58,10 @@ echo "- All Markdown checks passed" echo "- Start license checks" +echo "- copyright: start" +python -m flwr_tool.check_copyright src/py/flwr +echo "- copyright: done" + echo "- licensecheck: start" python -m licensecheck -u poetry --fail-licenses gpl --zero echo "- licensecheck: done" diff --git a/src/py/flwr_tool/check_copyright.py b/src/py/flwr_tool/check_copyright.py new file mode 100755 index 000000000000..96870ba67bd0 --- /dev/null +++ b/src/py/flwr_tool/check_copyright.py @@ -0,0 +1,76 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +"""Check if copyright notices are present in all Python files. + +Example: + python -m flwr_tool.check_copyright src/py/flwr +""" + + +import os +import subprocess +import sys +from pathlib import Path +from typing import List + +from flwr_tool.init_py_check import get_init_dir_list_and_warnings + +COPYRIGHT_FORMAT = """# Copyright {} Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ==============================================================================""" + + +def _get_file_creation_year(filepath: str) -> str: + result = subprocess.run( + ["git", "log", "--diff-filter=A", "--format=%ai", "--", filepath], + stdout=subprocess.PIPE, + text=True, + check=True, + ) + date_str = result.stdout.splitlines()[-1] # Get the first commit date + creation_year = date_str.split("-")[0] # Extract the year + return creation_year + + +def _check_copyright(dir_list: List[str]) -> None: + warning_list = [] + for valid_dir in dir_list: + if "proto" in valid_dir: + continue + + dir_path = Path(valid_dir) + for py_file in dir_path.glob("*.py"): + creation_year = _get_file_creation_year(str(py_file.absolute())) + expected_copyright = COPYRIGHT_FORMAT.format(creation_year) + + if expected_copyright not in py_file.read_text(): + warning_message = "- " + str(py_file) + warning_list.append(warning_message) + + if len(warning_list) > 0: + print("Missing or incorrect copyright notice in the following files:") + for warning in warning_list: + print(warning) + sys.exit(1) + + +if __name__ == "__main__": + if len(sys.argv) == 0: + raise Exception( # pylint: disable=W0719 + "Please provide at least one directory path relative " + "to your current working directory." + ) + for i, _ in enumerate(sys.argv): + abs_path: str = os.path.abspath(os.path.join(os.getcwd(), sys.argv[i])) + __, init_dirs = get_init_dir_list_and_warnings(abs_path) + _check_copyright(init_dirs) diff --git a/src/py/flwr_tool/fix_copyright.py b/src/py/flwr_tool/fix_copyright.py new file mode 100755 index 000000000000..a5bbbdf616f7 --- /dev/null +++ b/src/py/flwr_tool/fix_copyright.py @@ -0,0 +1,59 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +"""Fix copyright notices in all Python files of a given directory. + +Example: + python -m flwr_tool.fix_copyright src/py/flwr +""" + + +import os +import sys +from pathlib import Path +from typing import List + +from flwr_tool.check_copyright import COPYRIGHT_FORMAT, _get_file_creation_year +from flwr_tool.init_py_check import get_init_dir_list_and_warnings + + +def _insert_or_edit_copyright(py_file: Path) -> None: + contents = py_file.read_text() + lines = contents.splitlines() + creation_year = _get_file_creation_year(str(py_file.absolute())) + expected_copyright = COPYRIGHT_FORMAT.format(creation_year) + + if expected_copyright not in contents: + if "Copyright" in lines[0]: + end_index = 0 + for idx, line in enumerate(lines): + if ( + line.strip() + == COPYRIGHT_FORMAT.rsplit("\n", maxsplit=1)[-1].strip() + ): + end_index = idx + 1 + break + lines = lines[end_index:] + + lines.insert(0, expected_copyright) + py_file.write_text("\n".join(lines) + "\n") + + +def _fix_copyright(dir_list: List[str]) -> None: + for valid_dir in dir_list: + if "proto" in valid_dir: + continue + + dir_path = Path(valid_dir) + for py_file in dir_path.glob("*.py"): + _insert_or_edit_copyright(py_file) + + +if __name__ == "__main__": + if len(sys.argv) == 0: + raise Exception( # pylint: disable=W0719 + "Please provide at least one directory path relative " + "to your current working directory." + ) + for i, _ in enumerate(sys.argv): + abs_path: str = os.path.abspath(os.path.join(os.getcwd(), sys.argv[i])) + __, init_dirs = get_init_dir_list_and_warnings(abs_path) + _fix_copyright(init_dirs) From 83fefc1f151f2c741a68473bdacd6ddbd2b8fddb Mon Sep 17 00:00:00 2001 From: Jiahao Tan Date: Thu, 20 Jun 2024 22:36:03 +0800 Subject: [PATCH 068/595] docs(framework) Fix pip command of installing `flwr[simulation]` (#3641) --- doc/source/how-to-install-flower.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/how-to-install-flower.rst b/doc/source/how-to-install-flower.rst index 725a7468090c..b00e2ae803ab 100644 --- a/doc/source/how-to-install-flower.rst +++ b/doc/source/how-to-install-flower.rst @@ -20,7 +20,7 @@ Stable releases are available on `PyPI `_:: For simulations that use the Virtual Client Engine, ``flwr`` should be installed with the ``simulation`` extra:: - python -m pip install flwr[simulation] + python -m pip install "flwr[simulation]" Using conda (or mamba) From f80dfa5cebc8579437d2bd4305668d865920482b Mon Sep 17 00:00:00 2001 From: Gustavo Bertoli Date: Thu, 20 Jun 2024 16:42:47 +0200 Subject: [PATCH 069/595] docs(examples:skip) Fix minor typos (#3636) --- .../contributor-tutorial-get-started-as-a-contributor.rst | 4 ++-- doc/source/how-to-use-differential-privacy.rst | 2 +- doc/source/how-to-use-strategies.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/contributor-tutorial-get-started-as-a-contributor.rst b/doc/source/contributor-tutorial-get-started-as-a-contributor.rst index 43f9739987ac..d7d647996a3d 100644 --- a/doc/source/contributor-tutorial-get-started-as-a-contributor.rst +++ b/doc/source/contributor-tutorial-get-started-as-a-contributor.rst @@ -17,8 +17,8 @@ supports `PEP 517 `_. Developer Machine Setup ----------------------- -Preliminarities -~~~~~~~~~~~~~~~ +Preliminaries +~~~~~~~~~~~~~ Some system-wide dependencies are needed. For macOS diff --git a/doc/source/how-to-use-differential-privacy.rst b/doc/source/how-to-use-differential-privacy.rst index c8901bd906cc..5d4fa3dca1a4 100644 --- a/doc/source/how-to-use-differential-privacy.rst +++ b/doc/source/how-to-use-differential-privacy.rst @@ -9,7 +9,7 @@ This guide explains how you can utilize differential privacy in the Flower frame Central Differential Privacy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This approach consists of two seprate phases: clipping of the updates and adding noise to the aggregated model. +This approach consists of two separate phases: clipping of the updates and adding noise to the aggregated model. For the clipping phase, Flower framework has made it possible to decide whether to perform clipping on the server side or the client side. - **Server-side Clipping**: This approach has the advantage of the server enforcing uniform clipping across all clients' updates and reducing the communication overhead for clipping values. However, it also has the disadvantage of increasing the computational load on the server due to the need to perform the clipping operation for all clients. diff --git a/doc/source/how-to-use-strategies.rst b/doc/source/how-to-use-strategies.rst index d0e2cd63a091..8ac120124951 100644 --- a/doc/source/how-to-use-strategies.rst +++ b/doc/source/how-to-use-strategies.rst @@ -72,7 +72,7 @@ It must return a dictionary of arbitrary configuration values :code:`client.fit ) fl.server.start_server(config=fl.server.ServerConfig(num_rounds=3), strategy=strategy) -The :code:`on_fit_config_fn` can be used to pass arbitrary configuration values from server to client, and poetentially change these values each round, for example, to adjust the learning rate. +The :code:`on_fit_config_fn` can be used to pass arbitrary configuration values from server to client, and potentially change these values each round, for example, to adjust the learning rate. The client will receive the dictionary returned by the :code:`on_fit_config_fn` in its own :code:`client.fit()` function. Similar to :code:`on_fit_config_fn`, there is also :code:`on_evaluate_config_fn` to customize the configuration sent to :code:`client.evaluate()` From e2d51638b1f2b90202e86ef362f5a59dc4acddb0 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 20 Jun 2024 15:53:25 +0100 Subject: [PATCH 070/595] fix(framework:skip) Fix the help message for `flwr-dir` arg in `flower-supernode` (#3660) --- src/py/flwr/client/supernode/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 742281f5c011..c9a16edeaf15 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -267,7 +267,7 @@ def _parse_args_run_supernode() -> argparse.ArgumentParser: "--flwr-dir", default=None, help="""The path containing installed Flower Apps. - By default, this value isequal to: + By default, this value is equal to: - `$FLWR_HOME/` if `$FLWR_HOME` is defined - `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined From 72e88672f84714ac726419eb2b4137f9b54b2230 Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Thu, 20 Jun 2024 23:48:35 +0800 Subject: [PATCH 071/595] feat(framework) Read `backend_config` from config when running simulation via `flwr run` (#3581) --- src/py/flwr/cli/run/run.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 28fa67f9d4f6..4c95a4041c05 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -90,12 +90,16 @@ def run( if engine == Engine.SIMULATION: num_supernodes = config["flower"]["engine"]["simulation"]["supernode"]["num"] + backend_config = config["flower"]["engine"]["simulation"].get( + "backend_config", None + ) typer.secho("Starting run... ", fg=typer.colors.BLUE) _run_simulation( server_app_attr=server_app_ref, client_app_attr=client_app_ref, num_supernodes=num_supernodes, + backend_config=backend_config, ) else: typer.secho( From 9899354002ed9dd27890938708da1151389aa308 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 20 Jun 2024 18:56:14 +0200 Subject: [PATCH 072/595] feat(framework:skip) Add argument to `get_flwr_dir` (#3661) --- src/py/flwr/common/config.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 95bf8ce31c45..20de00a6fba9 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -24,14 +24,16 @@ from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME -def get_flwr_dir() -> Path: +def get_flwr_dir(provided_path: Optional[str] = None) -> Path: """Return the Flower home directory based on env variables.""" - return Path( - os.getenv( - FLWR_HOME, - f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", + if provided_path is None or not Path(provided_path).is_dir(): + return Path( + os.getenv( + FLWR_HOME, + f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", + ) ) - ) + return Path(provided_path).absolute() def get_project_dir( From a730466946ee81c59c7cdbea3398f0e9ee28ac62 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Fri, 21 Jun 2024 12:37:33 +0100 Subject: [PATCH 073/595] feat(framework) Allow `flower-server-app` to start with `run_id` (#3658) Co-authored-by: Daniel J. Beutel --- src/py/flwr/server/run_serverapp.py | 78 +++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 63ffc4a1caae..3505ebfdb0a9 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -22,6 +22,7 @@ from typing import Optional from flwr.common import Context, EventType, RecordSet, event +from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir from flwr.common.logger import log, update_console_handler, warn_deprecated_feature from flwr.common.object_ref import load_app from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611 @@ -43,7 +44,7 @@ def run( if not (server_app_attr is None) ^ (loaded_server_app is None): raise ValueError( "Either `server_app_attr` or `loaded_server_app` should be set " - "but not both. " + "but not both." ) if server_app_dir is not None: @@ -76,7 +77,7 @@ def _load() -> ServerApp: log(DEBUG, "ServerApp finished running.") -def run_server_app() -> None: +def run_server_app() -> None: # pylint: disable=too-many-branches """Run Flower server app.""" event(EventType.RUN_SERVER_APP_ENTER) @@ -136,11 +137,43 @@ def run_server_app() -> None: cert_path, ) - log( - DEBUG, - "Flower will load ServerApp `%s`", - getattr(args, "server-app"), + server_app_attr: Optional[str] = getattr(args, "server-app") + if not (server_app_attr is None) ^ (args.run_id is None): + raise sys.exit( + "Please provide either a ServerApp reference or a Run ID, but not both. " + "For more details, use: ``flower-server-app -h``" + ) + + stub = GrpcDriverStub( + driver_service_address=args.superlink, root_certificates=root_certificates ) + if args.run_id is not None: + # User provided `--run-id`, but not `server-app` + run_id = args.run_id + else: + # User provided `server-app`, but not `--run-id` + # Create run if run_id is not provided + stub.connect() + req = CreateRunRequest(fab_id=args.fab_id, fab_version=args.fab_version) + res = stub.create_run(req) + run_id = res.run_id + + # Initialize GrpcDriver + driver = GrpcDriver(run_id=run_id, stub=stub) + + # Dynamically obtain ServerApp path based on run_id + if args.run_id is not None: + # User provided `--run-id`, but not `server-app` + flwr_dir = get_flwr_dir(args.flwr_dir) + run_ = driver.run + server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir)) + config = get_project_config(server_app_dir) + server_app_attr = config["flower"]["components"]["serverapp"] + else: + # User provided `server-app`, but not `--run-id` + server_app_dir = str(Path(args.dir).absolute()) + + log(DEBUG, "Flower will load ServerApp `%s` in %s", server_app_attr, server_app_dir) log( DEBUG, @@ -148,20 +181,6 @@ def run_server_app() -> None: root_certificates, ) - server_app_dir = args.dir - server_app_attr = getattr(args, "server-app") - - # Create run - stub = GrpcDriverStub( - driver_service_address=args.superlink, root_certificates=root_certificates - ) - stub.connect() - req = CreateRunRequest(fab_id=args.fab_id, fab_version=args.fab_version) - res = stub.create_run(req) - - # Initialize GrpcDriver - driver = GrpcDriver(run_id=res.run_id, stub=stub) - # Run the ServerApp with the Driver run(driver=driver, server_app_dir=server_app_dir, server_app_attr=server_app_attr) @@ -179,6 +198,8 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser: parser.add_argument( "server-app", + nargs="?", + default=None, help="For example: `server:app` or `project.package.module:wrapper.app`", ) parser.add_argument( @@ -228,5 +249,22 @@ def _parse_args_run_server_app() -> argparse.ArgumentParser: type=str, help="The version of the FAB used in the run.", ) + parser.add_argument( + "--run-id", + default=None, + type=int, + help="The identifier of the run.", + ) + parser.add_argument( + "--flwr-dir", + default=None, + help="""The path containing installed Flower Apps. + By default, this value is equal to: + + - `$FLWR_HOME/` if `$FLWR_HOME` is defined + - `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined + - `$HOME/.flwr/` in all other cases + """, + ) return parser From ccfef792e9308c03f91b80f345091b8658b63afc Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:22:06 +0200 Subject: [PATCH 074/595] feat(datasets) Add telemetry (#3479) --- datasets/doc/source/index.rst | 4 + datasets/doc/source/ref-telemetry.md | 66 ++++++ datasets/flwr_datasets/common/__init__.py | 9 + datasets/flwr_datasets/common/telemetry.py | 224 ++++++++++++++++++ .../flwr_datasets/common/telemetry_test.py | 115 +++++++++ datasets/flwr_datasets/federated_dataset.py | 36 ++- .../comparison_label_distribution.py | 8 + .../visualization/label_distribution.py | 7 + 8 files changed, 467 insertions(+), 2 deletions(-) create mode 100644 datasets/doc/source/ref-telemetry.md create mode 100644 datasets/flwr_datasets/common/telemetry.py create mode 100644 datasets/flwr_datasets/common/telemetry_test.py diff --git a/datasets/doc/source/index.rst b/datasets/doc/source/index.rst index 2144c527f8cd..df248969a849 100644 --- a/datasets/doc/source/index.rst +++ b/datasets/doc/source/index.rst @@ -47,7 +47,11 @@ Information-oriented API reference and other reference material. flwr_datasets +.. toctree:: + :maxdepth: 1 + :caption: Reference docs + ref-telemetry Main features ------------- diff --git a/datasets/doc/source/ref-telemetry.md b/datasets/doc/source/ref-telemetry.md new file mode 100644 index 000000000000..a4fc9b6b0061 --- /dev/null +++ b/datasets/doc/source/ref-telemetry.md @@ -0,0 +1,66 @@ +# Telemetry + +The Flower Datasets open-source project collects **anonymous** usage metrics to make well-informed decisions to improve Flower Datasets. Doing this enables the Flower team to understand how Flower Datasets is used and what challenges users might face. + +**Flower is a friendly framework for collaborative AI and data science.** Staying true to this statement, Flower makes it easy to disable telemetry for users that do not want to share anonymous usage metrics. + +## Principles + +We follow strong principles guarding anonymous usage metrics collection: + +- **Optional:** You will always be able to disable telemetry; read on to learn “[How to opt-out](#how-to-opt-out)”. +- **Anonymous:** The reported usage metrics are anonymous and do not contain any personally identifiable information (PII). See “[Collected metrics](#collected-metrics)” to understand what metrics are being reported. +- **Transparent:** You can easily inspect what anonymous metrics are being reported; see the section “[How to inspect what is being reported](#how-to-inspect-what-is-being-reported)” +- **Open for feedback:** You can always reach out to us if you have feedback; see the section “[How to contact us](#how-to-contact-us)” for details. + +## How to opt-out + +When Flower Datasets starts, it will check for an environment variable called `FLWR_TELEMETRY_ENABLED`. Telemetry can easily be disabled by setting `FLWR_TELEMETRY_ENABLED=0`. Assuming you are using Flower Datasets in a Flower server or client, simply do so by prepending your command as in: + +```bash +FLWR_TELEMETRY_ENABLED=0 python server.py # or client.py +``` + +Alternatively, you can export `FLWR_TELEMETRY_ENABLED=0` in, for example, `.bashrc` (or whatever configuration file applies to your environment) to disable Flower Datasets telemetry permanently. + +## Collected metrics + +Flower telemetry collects the following metrics: + +**Flower version.** Understand which versions of Flower Datasets are currently being used. This helps us to decide whether we should invest effort into releasing a patch version for an older version of Flower Datasets or instead use the bandwidth to build new features. + +**Operating system.** Enables us to answer questions such as: *Should we create more guides for Linux, macOS, or Windows?* + +**Python version.** Knowing the Python version helps us, for example, to decide whether we should invest effort into supporting old versions of Python or stop supporting them and start taking advantage of new Python features. + +**Hardware properties.** Understanding the hardware environment that Flower Datasets is being used in helps to decide whether we should, for example, put more effort into supporting low-resource environments. + +**Dataset and Partitioners names.** Knowing what datasets and Partitioners are used enables us to provide more detailed code examples and tutorials and better prioritize work on development and support for them. + +**Cluster.** Flower telemetry assigns a random in-memory cluster ID each time a Flower workload starts. This allows us to understand which device types not only start Flower workloads but also successfully complete them. + +**Source.** Flower telemetry tries to store a random source ID in `~/.flwr/source` the first time a telemetry event is generated. The source ID is important to identify whether an issue is recurring or whether an issue is triggered by multiple clusters running concurrently (which often happens in simulation). For example, if a device runs multiple workloads at the same time, and this results in an issue, then, in order to reproduce the issue, multiple workloads must be started at the same time. + +You may delete the source ID at any time. If you wish for all events logged under a specific source ID to be deleted, you can send a deletion request mentioning the source ID to `telemetry@flower.ai`. All events related to that source ID will then be permanently deleted. + +We will not collect any personally identifiable information. If you think any of the metrics collected could be misused in any way, please [get in touch with us](#how-to-contact-us). We will update this page to reflect any changes to the metrics collected and publish changes in the changelog. + +If you think other metrics would be helpful for us to better guide our decisions, please let us know! We will carefully review them; if we are confident that they do not compromise user privacy, we may add them. + +## How to inspect what is being reported + +We wanted to make it very easy for you to inspect what anonymous usage metrics are reported. You can view all the reported telemetry information by setting the environment variable `FLWR_TELEMETRY_LOGGING=1`. Logging is disabled by default. You may use logging independently from `FLWR_TELEMETRY_ENABLED` so that you can inspect the telemetry feature without sending any metrics. + +```bash +FLWR_TELEMETRY_LOGGING=1 python server.py # or client.py +``` + +The inspect Flower telemetry without sending any anonymous usage metrics, use both environment variables: + +```bash +FLWR_TELEMETRY_ENABLED=0 FLWR_TELEMETRY_LOGGING=1 python server.py # or client.py +``` + +## How to contact us + +We want to hear from you. If you have any feedback or ideas on how to improve the way we handle anonymous usage metrics, reach out to us via [Slack](https://flower.ai/join-slack/) (channel `#telemetry`) or email (`telemetry@flower.ai`). diff --git a/datasets/flwr_datasets/common/__init__.py b/datasets/flwr_datasets/common/__init__.py index b4f12f8641b3..efb4eaf55b70 100644 --- a/datasets/flwr_datasets/common/__init__.py +++ b/datasets/flwr_datasets/common/__init__.py @@ -13,3 +13,12 @@ # limitations under the License. # ============================================================================== """Common components in Flower Datasets.""" + + +from .telemetry import EventType as EventType +from .telemetry import event as event + +__all__ = [ + "EventType", + "event", +] diff --git a/datasets/flwr_datasets/common/telemetry.py b/datasets/flwr_datasets/common/telemetry.py new file mode 100644 index 000000000000..ca484fdda73f --- /dev/null +++ b/datasets/flwr_datasets/common/telemetry.py @@ -0,0 +1,224 @@ +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Flower telemetry.""" + + +import datetime +import json +import logging +import os +import platform +import urllib.request +import uuid +from concurrent.futures import Future, ThreadPoolExecutor +from enum import Enum, auto +from pathlib import Path +from typing import Any, Dict, List, Optional, Union, cast + +from flwr_datasets.common.version import package_name, package_version + +FLWR_TELEMETRY_ENABLED = os.getenv("FLWR_TELEMETRY_ENABLED", "1") +FLWR_TELEMETRY_LOGGING = os.getenv("FLWR_TELEMETRY_LOGGING", "0") + +TELEMETRY_EVENTS_URL = "https://telemetry.flower.ai/api/v1/event" + +LOGGER_NAME = "flwr-datasets-telemetry" +LOGGER_LEVEL = logging.DEBUG + + +def _configure_logger(log_level: int) -> None: + console_handler = logging.StreamHandler() + console_handler.setLevel(log_level) + console_handler.setFormatter( + logging.Formatter( + "%(levelname)s %(name)s %(asctime)s | %(filename)s:%(lineno)d | %(message)s" + ) + ) + + logger = logging.getLogger(LOGGER_NAME) + logger.setLevel(log_level) + logger.addHandler(console_handler) + + +_configure_logger(LOGGER_LEVEL) + + +def log(msg: Union[str, Exception]) -> None: + """Log message using logger at DEBUG level.""" + logging.getLogger(LOGGER_NAME).log(LOGGER_LEVEL, msg) + + +def _get_home() -> Path: + return Path().home() + + +def _get_source_id() -> str: + """Get existing or new source ID.""" + source_id = "unavailable" + # Check if .flwr in home exists + try: + home = _get_home() + except RuntimeError: + # If the home directory can’t be resolved, RuntimeError is raised. + return source_id + + flwr_dir = home.joinpath(".flwr") + # Create .flwr directory if it does not exist yet. + try: + flwr_dir.mkdir(parents=True, exist_ok=True) + except PermissionError: + return source_id + + source_file = flwr_dir.joinpath("source") + + # If no source_file exists create one and write it + if not source_file.exists(): + try: + source_file.touch(exist_ok=True) + source_file.write_text(str(uuid.uuid4()), encoding="utf-8") + except PermissionError: + return source_id + + source_id = source_file.read_text(encoding="utf-8").strip() + + try: + uuid.UUID(source_id) + except ValueError: + source_id = "invalid" + + return source_id + + +# Using str as first base type to make it JSON serializable as +# otherwise the following exception will be thrown when serializing +# the event dict: +# TypeError: Object of type EventType is not JSON serializable +class EventType(str, Enum): + """Types of telemetry events.""" + + # This method combined with auto() will set the property value to + # the property name e.g. + # `START_CLIENT = auto()` becomes `START_CLIENT = "START_CLIENT"` + # The type signature is not compatible with mypy, pylint and flake8 + # so each of those needs to be disabled for this line. + # pylint: disable-next=no-self-argument,arguments-differ,line-too-long + def _generate_next_value_(name: str, start: int, count: int, last_values: List[Any]) -> Any: # type: ignore # noqa: E501 + return name + + PING = auto() + + LOAD_PARTITION_CALLED = auto() + LOAD_SPLIT_CALLED = auto() + PLOT_LABEL_DISTRIBUTION_CALLED = auto() + PLOT_COMPARISON_LABEL_DISTRIBUTION_CALLED = auto() + + +# Use the ThreadPoolExecutor with max_workers=1 to have a queue +# and also ensure that telemetry calls are not blocking. +state: Dict[str, Union[Optional[str], Optional[ThreadPoolExecutor]]] = { + # Will be assigned ThreadPoolExecutor(max_workers=1) + # in event() the first time it's required + "executor": None, + "source": None, + "cluster": None, +} + + +# In Python 3.7 pylint will throw an error stating that +# "Value 'Future' is unsubscriptable". +# This pylint disable line can be remove when dropping support +# for Python 3.7 +# pylint: disable-next=unsubscriptable-object +def event( + event_type: EventType, + event_details: Optional[Dict[str, Any]] = None, +) -> Future: # type: ignore + """Submit create_event to ThreadPoolExecutor to avoid blocking.""" + if state["executor"] is None: + state["executor"] = ThreadPoolExecutor(max_workers=1) + + executor: ThreadPoolExecutor = cast(ThreadPoolExecutor, state["executor"]) + + result = executor.submit(create_event, event_type, event_details) + return result + + +def create_event(event_type: EventType, event_details: Optional[Dict[str, Any]]) -> str: + """Create telemetry event.""" + if state["source"] is None: + state["source"] = _get_source_id() + + if state["cluster"] is None: + state["cluster"] = str(uuid.uuid4()) + + if event_details is None: + event_details = {} + + date = datetime.datetime.now(tz=datetime.timezone.utc).isoformat() + context = { + "source": state["source"], + "cluster": state["cluster"], + "date": date, + "package": { + "package_name": package_name, + "package_version": package_version, + }, + "hw": { + "cpu_count": os.cpu_count(), + }, + "platform": { + "system": platform.system(), + "release": platform.release(), + "platform": platform.platform(), + "python_implementation": platform.python_implementation(), + "python_version": platform.python_version(), + "machine": platform.machine(), + "architecture": platform.architecture(), + "version": platform.uname().version, + }, + } + payload = { + "event_type": event_type, + "event_details": event_details, + "context": context, + } + payload_json = json.dumps(payload) + if FLWR_TELEMETRY_LOGGING == "1": + log(" - ".join([date, "POST", payload_json])) + + # If telemetry is not disabled with setting FLWR_TELEMETRY_ENABLED=0 + # create a request and send it to the telemetry backend + if FLWR_TELEMETRY_ENABLED == "1": + request = urllib.request.Request( + url=TELEMETRY_EVENTS_URL, + data=payload_json.encode("utf-8"), + headers={ + "User-Agent": f"{package_name}/{package_version}", + "Content-Type": "application/json", + }, + method="POST", + ) + try: + with urllib.request.urlopen(request, timeout=60) as response: + result = response.read() + + response_json: str = result.decode("utf-8") + + return response_json + except urllib.error.URLError as ex: + if FLWR_TELEMETRY_LOGGING == "1": + log(ex) + + return "disabled" diff --git a/datasets/flwr_datasets/common/telemetry_test.py b/datasets/flwr_datasets/common/telemetry_test.py new file mode 100644 index 000000000000..f46b7b9a2ddf --- /dev/null +++ b/datasets/flwr_datasets/common/telemetry_test.py @@ -0,0 +1,115 @@ +# Copyright 2023 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Telemetry tests.""" + + +import time +import unittest +from typing import Callable +from unittest import mock + +from flwr_datasets.common.telemetry import EventType, _get_source_id, event + + +class TelemetryTest(unittest.TestCase): + """Tests for the telemetry module.""" + + @mock.patch("flwr_datasets.common.telemetry.FLWR_TELEMETRY_ENABLED", "1") + def test_event(self) -> None: + """Test if sending works against the actual API.""" + # Prepare + expected = '{\n "status": "created"\n}' + + # Execute + future = event(EventType.PING) + actual = future.result() + + # Assert + self.assertEqual(actual, expected) + + @mock.patch("flwr_datasets.common.telemetry.FLWR_TELEMETRY_ENABLED", "1") + def test_not_blocking(self) -> None: + """Test if the code is blocking. + + If the code does not block duration_actual should be less than + 0.001s. + """ + # Prepare + # Use 0.1ms as any blocking networked call would take longer. + duration_max = 0.001 + start = time.time() + + # Execute + event(EventType.PING) + duration_actual = time.time() - start + + # Assert + self.assertLess(duration_actual, duration_max) + + @mock.patch("flwr_datasets.common.telemetry.FLWR_TELEMETRY_ENABLED", "0") + def test_telemetry_disabled(self) -> None: + """Test opt-out.""" + # Prepare + expected = "disabled" + + # Execute + future = event(EventType.PING) + actual = future.result() + + # Assert + self.assertEqual(actual, expected) + + def test_get_source_id(self) -> None: + """Test if _get_source_id returns an ID successfully. + + This test might fail if the UNIX user invoking the test has no home directory. + """ + # Prepare + # nothing to prepare + + # Execute + source_id = _get_source_id() + + # Assert + # source_id should be len 36 as it's a uuid4 in the current + # implementation + self.assertIsNotNone(source_id) + self.assertEqual(len(source_id), 36) + + def test_get_source_id_no_home(self) -> None: + """Test if _get_source_id returns unavailable without a home dir.""" + + # Prepare + def new_callable() -> Callable[[], None]: + def _new_failing_get_home() -> None: + raise RuntimeError + + return _new_failing_get_home + + except_value = "unavailable" + + # Execute + with mock.patch( + "flwr_datasets.common.telemetry._get_home", + new_callable=new_callable, + ): + source_id = _get_source_id() + + # Assert + self.assertEqual(source_id, except_value) + + +if __name__ == "__main__": + unittest.main() diff --git a/datasets/flwr_datasets/federated_dataset.py b/datasets/flwr_datasets/federated_dataset.py index 5d98d01d4941..bbc6e99b651e 100644 --- a/datasets/flwr_datasets/federated_dataset.py +++ b/datasets/flwr_datasets/federated_dataset.py @@ -19,6 +19,7 @@ import datasets from datasets import Dataset, DatasetDict +from flwr_datasets.common import EventType, event from flwr_datasets.partitioner import Partitioner from flwr_datasets.preprocessor import Preprocessor from flwr_datasets.utils import ( @@ -102,6 +103,9 @@ def __init__( self._dataset: Optional[DatasetDict] = None # Indicate if the dataset is prepared for `load_partition` or `load_split` self._dataset_prepared: bool = False + self._event = { + "load_partition": {split: False for split in self._partitioners}, + } def load_partition( self, @@ -141,7 +145,20 @@ def load_partition( self._check_if_split_possible_to_federate(split) partitioner: Partitioner = self._partitioners[split] self._assign_dataset_to_partitioner(split) - return partitioner.load_partition(partition_id) + partition = partitioner.load_partition(partition_id) + if not self._event["load_partition"][split]: + event( + EventType.LOAD_PARTITION_CALLED, + { + "federated_dataset_id": id(self), + "dataset_name": self._dataset_name, + "split": split, + "partitioner": partitioner.__class__.__name__, + "num_partitions": partitioner.num_partitions, + }, + ) + self._event["load_partition"][split] = True + return partition def load_split(self, split: str) -> Dataset: """Load the full split of the dataset. @@ -164,7 +181,20 @@ def load_split(self, split: str) -> Dataset: if self._dataset is None: raise ValueError("Dataset is not loaded yet.") self._check_if_split_present(split) - return self._dataset[split] + dataset_split = self._dataset[split] + + if not self._event["load_split"][split]: + event( + EventType.LOAD_SPLIT_CALLED, + { + "federated_dataset_id": id(self), + "dataset_name": self._dataset_name, + "split": split, + }, + ) + self._event["load_split"][split] = True + + return dataset_split @property def partitioners(self) -> Dict[str, Partitioner]: @@ -246,6 +276,8 @@ def _prepare_dataset(self) -> None: self._dataset = self._dataset.shuffle(seed=self._seed) if self._preprocessor: self._dataset = self._preprocessor(self._dataset) + available_splits = list(self._dataset.keys()) + self._event["load_split"] = {split: False for split in available_splits} self._dataset_prepared = True def _check_if_no_split_keyword_possible(self) -> None: diff --git a/datasets/flwr_datasets/visualization/comparison_label_distribution.py b/datasets/flwr_datasets/visualization/comparison_label_distribution.py index d59f8c47986d..554f6d78d59a 100644 --- a/datasets/flwr_datasets/visualization/comparison_label_distribution.py +++ b/datasets/flwr_datasets/visualization/comparison_label_distribution.py @@ -23,6 +23,7 @@ from matplotlib.axes import Axes from matplotlib.figure import Figure +from flwr_datasets.common import EventType, event from flwr_datasets.partitioner import Partitioner from flwr_datasets.visualization.constants import PLOT_TYPES from flwr_datasets.visualization.label_distribution import plot_label_distributions @@ -132,6 +133,13 @@ def plot_comparison_label_distribution( >>> titles=[f"Concentration = {alpha}" for alpha in alpha_list], >>> ) """ + event( + EventType.PLOT_COMPARISON_LABEL_DISTRIBUTION_CALLED, + { + "num_compare": len(partitioner_list), + "plot_type": plot_type, + }, + ) num_partitioners = len(partitioner_list) if isinstance(label_name, str): label_name = [label_name] * num_partitioners diff --git a/datasets/flwr_datasets/visualization/label_distribution.py b/datasets/flwr_datasets/visualization/label_distribution.py index 940b0e8f91bd..0c47bd204a17 100644 --- a/datasets/flwr_datasets/visualization/label_distribution.py +++ b/datasets/flwr_datasets/visualization/label_distribution.py @@ -22,6 +22,7 @@ from matplotlib.axes import Axes from matplotlib.figure import Figure +from flwr_datasets.common import EventType, event from flwr_datasets.metrics.utils import compute_counts, compute_frequencies from flwr_datasets.partitioner import Partitioner from flwr_datasets.visualization.bar_plot import _plot_bar @@ -191,6 +192,12 @@ def plot_label_distributions( You can also visualize the returned DataFrame in Jupyter Notebook >>> dataframe.style.background_gradient(axis=None) """ + event( + EventType.PLOT_LABEL_DISTRIBUTION_CALLED, + { + "plot_type": plot_type, + }, + ) _validate_parameters(plot_type, size_unit, partition_id_axis) dataframe = pd.DataFrame() From 7424b6264cb12be3c26e2885dcb9ff0c0497fb78 Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Fri, 21 Jun 2024 22:42:31 +0800 Subject: [PATCH 075/595] feat(framework) Add FlowerTune templates to `flwr new` (#3587) Co-authored-by: Javier Co-authored-by: Daniel J. Beutel --- src/py/flwr/cli/new/new.py | 130 ++++++++++++++---- .../templates/app/README.flowertune.md.tpl | 56 ++++++++ .../templates/app/code/flwr_tune/__init__.py | 15 ++ .../templates/app/code/flwr_tune/app.py.tpl | 86 ++++++++++++ .../app/code/flwr_tune/client.py.tpl | 124 +++++++++++++++++ .../app/code/flwr_tune/config.yaml.tpl | 34 +++++ .../app/code/flwr_tune/dataset.py.tpl | 57 ++++++++ .../app/code/flwr_tune/models.py.tpl | 59 ++++++++ .../app/code/flwr_tune/server.py.tpl | 48 +++++++ .../app/code/flwr_tune/static_config.yaml.tpl | 11 ++ .../app/pyproject.flowertune.toml.tpl | 42 ++++++ 11 files changed, 635 insertions(+), 27 deletions(-) create mode 100644 src/py/flwr/cli/new/templates/app/README.flowertune.md.tpl create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/__init__.py create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl create mode 100644 src/py/flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl create mode 100644 src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl diff --git a/src/py/flwr/cli/new/new.py b/src/py/flwr/cli/new/new.py index 94da99dce36e..9367cf6c9ffb 100644 --- a/src/py/flwr/cli/new/new.py +++ b/src/py/flwr/cli/new/new.py @@ -41,6 +41,16 @@ class MlFramework(str, Enum): HUGGINGFACE = "HF" MLX = "MLX" SKLEARN = "sklearn" + FLOWERTUNE = "FlowerTune" + + +class LlmChallengeName(str, Enum): + """Available LLM challenges.""" + + GENERALNLP = "GeneralNLP" + FINANCE = "Finance" + MEDICAL = "Medical" + CODE = "Code" class TemplateNotFound(Exception): @@ -81,6 +91,7 @@ def render_and_create(file_path: str, template: str, context: Dict[str, str]) -> create_file(file_path, content) +# pylint: disable=too-many-locals,too-many-branches,too-many-statements def new( project_name: Annotated[ Optional[str], @@ -125,6 +136,19 @@ def new( framework_str = framework_str.lower() + if framework_str == "flowertune": + llm_challenge_value = prompt_options( + "Please select LLM challenge by typing in the number", + sorted([challenge.value for challenge in LlmChallengeName]), + ) + selected_value = [ + name + for name, value in vars(LlmChallengeName).items() + if value == llm_challenge_value + ] + llm_challenge_str = selected_value[0] + llm_challenge_str = llm_challenge_str.lower() + print( typer.style( f"\n🔨 Creating Flower project {project_name}...", @@ -139,33 +163,6 @@ def new( import_name = package_name.replace("-", "_") project_dir = os.path.join(cwd, package_name) - # List of files to render - files = { - ".gitignore": {"template": "app/.gitignore.tpl"}, - "README.md": {"template": "app/README.md.tpl"}, - "pyproject.toml": {"template": f"app/pyproject.{framework_str}.toml.tpl"}, - f"{import_name}/__init__.py": {"template": "app/code/__init__.py.tpl"}, - f"{import_name}/server.py": { - "template": f"app/code/server.{framework_str}.py.tpl" - }, - f"{import_name}/client.py": { - "template": f"app/code/client.{framework_str}.py.tpl" - }, - } - - # Depending on the framework, generate task.py file - frameworks_with_tasks = [ - MlFramework.PYTORCH.value.lower(), - MlFramework.JAX.value.lower(), - MlFramework.HUGGINGFACE.value.lower(), - MlFramework.MLX.value.lower(), - MlFramework.TENSORFLOW.value.lower(), - ] - if framework_str in frameworks_with_tasks: - files[f"{import_name}/task.py"] = { - "template": f"app/code/task.{framework_str}.py.tpl" - } - context = { "project_name": project_name, "package_name": package_name, @@ -173,6 +170,85 @@ def new( "username": username, } + # List of files to render + if framework_str == "flowertune": + files = { + ".gitignore": {"template": "app/.gitignore.tpl"}, + "pyproject.toml": {"template": f"app/pyproject.{framework_str}.toml.tpl"}, + "README.md": {"template": f"app/README.{framework_str}.md.tpl"}, + f"{import_name}/__init__.py": {"template": "app/code/__init__.py.tpl"}, + f"{import_name}/server.py": { + "template": "app/code/flwr_tune/server.py.tpl" + }, + f"{import_name}/client.py": { + "template": "app/code/flwr_tune/client.py.tpl" + }, + f"{import_name}/app.py": {"template": "app/code/flwr_tune/app.py.tpl"}, + f"{import_name}/models.py": { + "template": "app/code/flwr_tune/models.py.tpl" + }, + f"{import_name}/dataset.py": { + "template": "app/code/flwr_tune/dataset.py.tpl" + }, + f"{import_name}/conf/config.yaml": { + "template": "app/code/flwr_tune/config.yaml.tpl" + }, + f"{import_name}/conf/static_config.yaml": { + "template": "app/code/flwr_tune/static_config.yaml.tpl" + }, + } + + # Challenge specific context + fraction_fit = "0.2" if llm_challenge_str == "code" else "0.1" + if llm_challenge_str == "generalnlp": + challenge_name = "General NLP" + num_clients = "20" + dataset_name = "vicgalle/alpaca-gpt4" + elif llm_challenge_str == "finance": + challenge_name = "Finance" + num_clients = "50" + dataset_name = "FinGPT/fingpt-sentiment-train" + elif llm_challenge_str == "medical": + challenge_name = "Medical" + num_clients = "20" + dataset_name = "medalpaca/medical_meadow_medical_flashcards" + else: + challenge_name = "Code" + num_clients = "10" + dataset_name = "lucasmccabe-lmi/CodeAlpaca-20k" + + context["llm_challenge_str"] = llm_challenge_str + context["fraction_fit"] = fraction_fit + context["challenge_name"] = challenge_name + context["num_clients"] = num_clients + context["dataset_name"] = dataset_name + else: + files = { + ".gitignore": {"template": "app/.gitignore.tpl"}, + "README.md": {"template": "app/README.md.tpl"}, + "pyproject.toml": {"template": f"app/pyproject.{framework_str}.toml.tpl"}, + f"{import_name}/__init__.py": {"template": "app/code/__init__.py.tpl"}, + f"{import_name}/server.py": { + "template": f"app/code/server.{framework_str}.py.tpl" + }, + f"{import_name}/client.py": { + "template": f"app/code/client.{framework_str}.py.tpl" + }, + } + + # Depending on the framework, generate task.py file + frameworks_with_tasks = [ + MlFramework.PYTORCH.value.lower(), + MlFramework.JAX.value.lower(), + MlFramework.HUGGINGFACE.value.lower(), + MlFramework.MLX.value.lower(), + MlFramework.TENSORFLOW.value.lower(), + ] + if framework_str in frameworks_with_tasks: + files[f"{import_name}/task.py"] = { + "template": f"app/code/task.{framework_str}.py.tpl" + } + for file_path, value in files.items(): render_and_create( file_path=os.path.join(project_dir, file_path), diff --git a/src/py/flwr/cli/new/templates/app/README.flowertune.md.tpl b/src/py/flwr/cli/new/templates/app/README.flowertune.md.tpl new file mode 100644 index 000000000000..2b59937e4130 --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/README.flowertune.md.tpl @@ -0,0 +1,56 @@ +# FlowerTune LLM on $challenge_name Dataset + +This directory conducts federated instruction tuning with a pretrained [Mistral-7B](https://huggingface.co/mistralai/Mistral-7B-v0.3) model on a [$challenge_name dataset](https://huggingface.co/datasets/$dataset_name). +We use [Flower Datasets](https://flower.dev/docs/datasets/) to download, partition and preprocess the dataset. +Flower's Simulation Engine is used to simulate the LLM fine-tuning process in federated way, +which allows users to perform the training on a single GPU. + + +## Methodology + +This baseline performs federated LLM fine-tuning with [LoRA](https://arxiv.org/pdf/2106.09685) using the [🤗PEFT](https://huggingface.co/docs/peft/en/index) library. +The clients' models are aggregated with FedAvg strategy. +This provides a baseline performance for the leaderboard of $challenge_name challenge. + + +## Environments setup + +Project dependencies are defined in `pyproject.toml`. Install them in an activated Python environment with: + +```shell +pip install -e . +``` + +## Experimental setup + +The dataset is partitioned into $num_clients shards with IID fashion serving as clients. +We randomly sample $fraction_fit clients to be available for each round, +and the federated fine-tuning lasts for `200` rounds. +All settings are defined in `$project_name/conf/static_config.yaml`, which is not allowed to be modified for fair competition if you plan to participated in the [LLM leaderboard](https://flower.ai/benchmarks/llm-leaderboard). + + +## Running the challenge + +First make sure that you have got the access to [Mistral-7B](https://huggingface.co/mistralai/Mistral-7B-v0.3) model with your Hugging-Face account. You can request access directly from the Hugging-Face website. +Then, follow the instruction [here](https://huggingface.co/docs/huggingface_hub/en/quick-start#login-command) to log in your account. Note you only need to complete this stage once in your development machine: + +```bash +huggingface-cli login +``` + +Run the challenge with default config values. +The configs are in `$project_name/conf/config.yaml` and `$project_name/conf/static_config.yaml`, and are loaded automatically. + +```bash +flwr run +``` + +## VRAM consumption + +We use Mistral-7B model with 4-bit quantization as default. The estimated VRAM consumption per client for each challenge is shown below: + +| Challenges | GeneralNLP | Finance | Medical | Code | +| :--------: | :--------: | :--------: | :--------: | :--------: | +| VRAM | ~25.50 GB | ~17.30 GB | ~22.80 GB | ~17.40 GB | + +You can adjust the CPU/GPU resources you assign to each of the clients based on your device, which is specified with `flower.engine.simulation` in `pyproject.toml`. diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/__init__.py b/src/py/flwr/cli/new/templates/app/code/flwr_tune/__init__.py new file mode 100644 index 000000000000..6d886f216bc9 --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Flower CLI `new` command app / code / flwr_tune templates.""" diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl new file mode 100644 index 000000000000..ecb87bd71e3f --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl @@ -0,0 +1,86 @@ +"""$project_name: A Flower / FlowerTune app.""" + +import os +import warnings +from datetime import datetime + +from flwr_datasets import FederatedDataset +from hydra import compose, initialize +from hydra.utils import instantiate + +from flwr.client import ClientApp +from flwr.common import ndarrays_to_parameters +from flwr.server import ServerApp, ServerConfig + +from $import_name.client import gen_client_fn, get_parameters +from $import_name.dataset import get_tokenizer_and_data_collator_and_propt_formatting +from $import_name.models import get_model +from $import_name.server import fit_weighted_average, get_evaluate_fn, get_on_fit_config + +# Avoid warnings +warnings.filterwarnings("ignore", category=UserWarning) +os.environ["TOKENIZERS_PARALLELISM"] = "true" +os.environ["RAY_DISABLE_DOCKER_CPU_WARNING"] = "1" + +# Initialise regular config +with initialize(config_path="conf", version_base="1.1"): + cfg = compose(config_name="config") + +# Initialise static config +with initialize(config_path="conf", version_base="1.1"): + cfg_static = compose(config_name="static_config") + +cfg.train.num_rounds = cfg_static.num_rounds + +# Create output directory given current timestamp +current_time = datetime.now() +folder_name = current_time.strftime("%Y-%m-%d_%H-%M-%S") +save_path = os.path.join(os.getcwd(), f"results/{folder_name}") +os.makedirs(save_path, exist_ok=True) + +# Partition dataset and get dataloaders +partitioner = instantiate(cfg_static.partitioner) +fds = FederatedDataset( + dataset=cfg_static.dataset.name, partitioners={"train": partitioner} +) +( + tokenizer, + data_collator, + formatting_prompts_func, +) = get_tokenizer_and_data_collator_and_propt_formatting(cfg.model.name) + +# ClientApp for Flower Next +client = ClientApp( + client_fn=gen_client_fn( + fds, + tokenizer, + formatting_prompts_func, + data_collator, + cfg.model, + cfg.train, + save_path, + ), +) + +# Get initial model weights +init_model = get_model(cfg.model) +init_model_parameters = get_parameters(init_model) +init_model_parameters = ndarrays_to_parameters(init_model_parameters) + +# Instantiate strategy according to config. Here we pass other arguments +# that are only defined at runtime. +strategy = instantiate( + cfg.strategy, + on_fit_config_fn=get_on_fit_config(), + fit_metrics_aggregation_fn=fit_weighted_average, + initial_parameters=init_model_parameters, + evaluate_fn=get_evaluate_fn( + cfg.model, cfg.train.save_every_round, cfg_static.num_rounds, save_path + ), +) + +# ServerApp for Flower Next +server = ServerApp( + config=ServerConfig(num_rounds=cfg_static.num_rounds), + strategy=strategy, +) diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl new file mode 100644 index 000000000000..c0d5842964fd --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl @@ -0,0 +1,124 @@ +"""$project_name: A Flower / FlowerTune app.""" + +from collections import OrderedDict +from typing import Callable, Dict, Tuple + +import torch +from omegaconf import DictConfig +from peft import get_peft_model_state_dict, set_peft_model_state_dict +from transformers import TrainingArguments +from trl import SFTTrainer + +from flwr.client import NumPyClient +from flwr.common.typing import NDArrays, Scalar +from $import_name.dataset import reformat +from $import_name.models import cosine_annealing, get_model + + +# pylint: disable=too-many-arguments +# pylint: disable=too-many-instance-attributes +class FlowerClient(NumPyClient): + """Standard Flower client for CNN training.""" + + def __init__( + self, + model_cfg: DictConfig, + train_cfg: DictConfig, + trainset, + tokenizer, + formatting_prompts_func, + data_collator, + save_path, + ): # pylint: disable=too-many-arguments + self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + self.train_cfg = train_cfg + self.training_argumnets = TrainingArguments(**train_cfg.training_arguments) + self.tokenizer = tokenizer + self.formatting_prompts_func = formatting_prompts_func + self.data_collator = data_collator + self.save_path = save_path + + # instantiate model + self.model = get_model(model_cfg) + + self.trainset = trainset + + def fit( + self, parameters: NDArrays, config: Dict[str, Scalar] + ) -> Tuple[NDArrays, int, Dict]: + """Implement distributed fit function for a given client.""" + set_parameters(self.model, parameters) + + new_lr = cosine_annealing( + int(config["current_round"]), + self.train_cfg.num_rounds, + self.train_cfg.learning_rate_max, + self.train_cfg.learning_rate_min, + ) + + self.training_argumnets.learning_rate = new_lr + self.training_argumnets.output_dir = self.save_path + + # Construct trainer + trainer = SFTTrainer( + model=self.model, + tokenizer=self.tokenizer, + args=self.training_argumnets, + max_seq_length=self.train_cfg.seq_length, + train_dataset=self.trainset, + formatting_func=self.formatting_prompts_func, + data_collator=self.data_collator, + ) + + # Do local training + results = trainer.train() + + return ( + get_parameters(self.model), + len(self.trainset), + {"train_loss": results.training_loss}, + ) + + +def set_parameters(model, parameters: NDArrays) -> None: + """Change the parameters of the model using the given ones.""" + peft_state_dict_keys = get_peft_model_state_dict(model).keys() + params_dict = zip(peft_state_dict_keys, parameters) + state_dict = OrderedDict({k: torch.Tensor(v) for k, v in params_dict}) + set_peft_model_state_dict(model, state_dict) + + +def get_parameters(model) -> NDArrays: + """Return the parameters of the current net.""" + state_dict = get_peft_model_state_dict(model) + return [val.cpu().numpy() for _, val in state_dict.items()] + + +def gen_client_fn( + fds, + tokenizer, + formatting_prompts_func, + data_collator, + model_cfg: DictConfig, + train_cfg: DictConfig, + save_path: str, +) -> Callable[[str], FlowerClient]: # pylint: disable=too-many-arguments + """Generate the client function that creates the Flower Clients.""" + + def client_fn(cid: str) -> FlowerClient: + """Create a Flower client representing a single organization.""" + # Let's get the partition corresponding to the i-th client + client_trainset = fds.load_partition(int(cid), "train") + client_trainset = reformat(client_trainset, llm_task="$llm_challenge_str") + + return FlowerClient( + model_cfg, + train_cfg, + client_trainset, + tokenizer, + formatting_prompts_func, + data_collator, + save_path, + ).to_client() + + return client_fn diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl new file mode 100644 index 000000000000..9f700dd5b8da --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/config.yaml.tpl @@ -0,0 +1,34 @@ +# Federated Instruction Tuning +--- +model: + name: "mistralai/Mistral-7B-v0.3" + quantization: 4 # 8 or 4 if you want to do quantization with BitsAndBytes + gradient_checkpointing: True + lora: + peft_lora_r: 32 + peft_lora_alpha: 64 + +train: + num_rounds: null + save_every_round: 5 + learning_rate_max: 5e-5 + learning_rate_min: 1e-6 + seq_length: 512 + training_arguments: + output_dir: null # to be set by hydra + learning_rate: null # to be set by the client + per_device_train_batch_size: 16 + gradient_accumulation_steps: 1 + logging_steps: 10 + num_train_epochs: 3 + max_steps: 10 + report_to: null + save_steps: 1000 + save_total_limit: 10 + gradient_checkpointing: True + lr_scheduler_type: "constant" + +strategy: + _target_: flwr.server.strategy.FedAvg + fraction_fit: $fraction_fit + fraction_evaluate: 0.0 # no client evaluation diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl new file mode 100644 index 000000000000..1b3691d7cf3c --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl @@ -0,0 +1,57 @@ +"""$project_name: A Flower / FlowerTune app.""" + +from transformers import AutoTokenizer +from trl import DataCollatorForCompletionOnlyLM + + +def formatting_prompts_func(example): + """Construct prompts.""" + output_texts = [] + # Constructing a standard Alpaca + # (https://github.com/tatsu-lab/stanford_alpaca#data-release) prompt + mssg = ( + "Below is an instruction that describes a task. " + "Write a response that appropriately completes the request." + ) + for i in range(len(example["instruction"])): + text = ( + f"{mssg}\n### Instruction:\n{example['instruction'][i]}\n" + f"### Response: {example['response'][i]}" + ) + output_texts.append(text) + return output_texts + + +def get_tokenizer_and_data_collator_and_propt_formatting(model_name: str): + """Get tokenizer, data_collator and prompt formatting.""" + # From: https://huggingface.co/docs/trl/en/sft_trainer + tokenizer = AutoTokenizer.from_pretrained( + model_name, use_fast=True, padding_side="right" + ) + tokenizer.pad_token = tokenizer.eos_token + response_template_with_context = "\n### Response:" # alpaca response tag + response_template_ids = tokenizer.encode( + response_template_with_context, add_special_tokens=False + )[2:] + data_collator = DataCollatorForCompletionOnlyLM( + response_template_ids, tokenizer=tokenizer + ) + + return tokenizer, data_collator, formatting_prompts_func + + +def formatting(dataset): + """Format dataset.""" + dataset["instruction"] = dataset["instruction"] + " " + dataset["input"] + return dataset + + +def reformat(dataset, llm_task): + """Reformat datasets.""" + dataset = dataset.rename_column("output", "response") + if llm_task == "finance" or llm_task == "code": + dataset = dataset.map(formatting, remove_columns=["input"]) + if llm_task == "medical": + dataset = dataset.remove_columns(["instruction"]) + dataset = dataset.rename_column("input", "instruction") + return dataset diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl new file mode 100644 index 000000000000..a2794f35518c --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl @@ -0,0 +1,59 @@ +"""$project_name: A Flower / FlowerTune app.""" + +import math + +import torch +from omegaconf import DictConfig +from peft import LoraConfig, get_peft_model +from peft.utils import prepare_model_for_kbit_training +from transformers import AutoModelForCausalLM, BitsAndBytesConfig + + +def cosine_annealing( + current_round: int, + total_round: int, + lrate_max: float = 0.001, + lrate_min: float = 0.0, +) -> float: + """Implement cosine annealing learning rate schedule.""" + cos_inner = math.pi * current_round / total_round + return lrate_min + 0.5 * (lrate_max - lrate_min) * (1 + math.cos(cos_inner)) + + +def get_model(model_cfg: DictConfig): + """Load model with appropriate quantization config and other optimizations. + + Please refer to this example for `peft + BitsAndBytes`: + https://github.com/huggingface/peft/blob/main/examples/fp4_finetuning/finetune_fp4_opt_bnb_peft.py + """ + if model_cfg.quantization == 4: + quantization_config = BitsAndBytesConfig(load_in_4bit=True) + elif model_cfg.quantization == 8: + quantization_config = BitsAndBytesConfig(load_in_8bit=True) + else: + raise ValueError( + f"Use 4-bit or 8-bit quantization. You passed: {model_cfg.quantization}/" + ) + + model = AutoModelForCausalLM.from_pretrained( + model_cfg.name, + quantization_config=quantization_config, + torch_dtype=torch.bfloat16, + low_cpu_mem_usage=True, + ) + + model = prepare_model_for_kbit_training( + model, use_gradient_checkpointing=model_cfg.gradient_checkpointing + ) + + peft_config = LoraConfig( + r=model_cfg.lora.peft_lora_r, + lora_alpha=model_cfg.lora.peft_lora_alpha, + lora_dropout=0.075, + task_type="CAUSAL_LM", + ) + + if model_cfg.gradient_checkpointing: + model.config.use_cache = False + + return get_peft_model(model, peft_config) diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl new file mode 100644 index 000000000000..19223148bca5 --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl @@ -0,0 +1,48 @@ +"""$project_name: A Flower / FlowerTune app.""" + +from $import_name.client import set_parameters +from $import_name.models import get_model + + +# Get function that will be executed by the strategy's evaluate() method +# Here we use it to save global model checkpoints +def get_evaluate_fn(model_cfg, save_every_round, total_round, save_path): + """Return an evaluation function for saving global model.""" + + def evaluate(server_round: int, parameters, config): + # Save model + if server_round != 0 and ( + server_round == total_round or server_round % save_every_round == 0 + ): + # Init model + model = get_model(model_cfg) + set_parameters(model, parameters) + + model.save_pretrained(f"{save_path}/peft_{server_round}") + + return 0.0, {} + + return evaluate + + +def get_on_fit_config(): + """ + Return a function that will be used to construct the config + that the client's fit() method will receive. + """ + + def fit_config_fn(server_round: int): + fit_config = {"current_round": server_round} + return fit_config + + return fit_config_fn + + +def fit_weighted_average(metrics): + """Aggregate (federated) evaluation metrics.""" + # Multiply accuracy of each client by number of examples used + losses = [num_examples * m["train_loss"] for num_examples, m in metrics] + examples = [num_examples for num_examples, _ in metrics] + + # Aggregate and return custom metric (weighted average) + return {"train_loss": sum(losses) / sum(examples)} diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl new file mode 100644 index 000000000000..a8a4039fc831 --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/static_config.yaml.tpl @@ -0,0 +1,11 @@ +# Federated Instruction Tuning (static) +--- +dataset: + name: $dataset_name + +# FL experimental settings +num_clients: $num_clients # total number of clients +num_rounds: 200 +partitioner: + _target_: flwr_datasets.partitioner.IidPartitioner + num_partitions: $num_clients diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl new file mode 100644 index 000000000000..2ed6bd36fd89 --- /dev/null +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -0,0 +1,42 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "$package_name" +version = "1.0.0" +description = "" +authors = [ + { name = "The Flower Authors", email = "hello@flower.ai" }, +] +license = { text = "Apache License (2.0)" } +dependencies = [ + "flwr[simulation]>=1.9.0,<2.0", + "flwr-datasets>=0.1.0,<1.0.0", + "hydra-core==1.3.2", + "trl==0.8.1", + "bitsandbytes==0.43.0", + "scipy==1.13.0", + "peft==0.6.2", + "transformers==4.39.3", + "sentencepiece==0.2.0", +] + +[tool.hatch.build.targets.wheel] +packages = ["."] + +[flower] +publisher = "$username" + +[flower.components] +serverapp = "$import_name.app:server" +clientapp = "$import_name.app:client" + +[flower.engine] +name = "simulation" + +[flower.engine.simulation.supernode] +num = $num_clients + +[flower.engine.simulation] +backend_config = { client_resources = { num_cpus = 8, num_gpus = 1.0 } } From 0c9fe5e3734e876f37d94cbbe2f633353a07d099 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:55:58 +0200 Subject: [PATCH 076/595] Disable telemetry in datasets.yml workflow (#3667) --- .github/workflows/datasets.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/datasets.yml b/.github/workflows/datasets.yml index 47e9f2aed926..80cece262754 100644 --- a/.github/workflows/datasets.yml +++ b/.github/workflows/datasets.yml @@ -16,6 +16,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.event.pull_request.number || github.ref }} cancel-in-progress: true +env: + FLWR_TELEMETRY_ENABLED: 0 + defaults: run: working-directory: datasets From 5480b6ac2020ce8ef150c872da990bdb10806d07 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Fri, 21 Jun 2024 17:52:01 +0200 Subject: [PATCH 077/595] Remove extra space (#3669) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16cb7f1cfaf6..5f61d616775b 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ Other [examples](https://github.com/adap/flower/tree/main/examples): - [Flower with KaplanMeierFitter from the lifelines library](https://github.com/adap/flower/tree/main/examples/federated-kaplan-meier-fitter) - [Sample Level Privacy with Opacus](https://github.com/adap/flower/tree/main/examples/opacus) - [Sample Level Privacy with TensorFlow-Privacy](https://github.com/adap/flower/tree/main/examples/tensorflow-privacy) -- [Flower with a Tabular Dataset] (https://github.com/adap/flower/tree/main/examples/fl-tabular) +- [Flower with a Tabular Dataset](https://github.com/adap/flower/tree/main/examples/fl-tabular) ## Community From 87a305cd68babca4c6c6f0c72798e68a9e52fbf9 Mon Sep 17 00:00:00 2001 From: Sebastian van der Voort Date: Fri, 21 Jun 2024 22:03:39 +0200 Subject: [PATCH 078/595] feat(framework) Parse initialization arguments to `ray` (#3543) Co-authored-by: svdvoort <23049683+Svdvoort@users.noreply.github.com> Co-authored-by: jafermarq --- dev/test.sh | 2 +- .../superlink/fleet/vce/backend/raybackend.py | 69 ++++++++++++------- .../fleet/vce/backend/raybackend_test.py | 33 +++++++++ .../simulation/ray_transport/ray_actor.py | 6 -- src/py/flwr/simulation/run_simulation.py | 54 +++++++++------ 5 files changed, 111 insertions(+), 53 deletions(-) diff --git a/dev/test.sh b/dev/test.sh index 5b827380bc50..8cbe88c9298b 100755 --- a/dev/test.sh +++ b/dev/test.sh @@ -23,7 +23,7 @@ python -m flwr_tool.init_py_check src/py/flwr src/py/flwr_tool echo "- init_py_check: done" echo "- docformatter: start" -python -m docformatter -c -r src/py/flwr e2e -e src/py/flwr/proto +python -m docformatter -c -r src/py/flwr e2e -e src/py/flwr/proto echo "- docformatter: done" echo "- ruff: start" diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py index 93aca583af9c..8a21393db590 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py @@ -15,7 +15,7 @@ """Ray backend for the Fleet API using the Simulation Engine.""" import pathlib -from logging import DEBUG, ERROR, WARNING +from logging import DEBUG, ERROR from typing import Callable, Dict, List, Tuple, Union import ray @@ -24,16 +24,15 @@ from flwr.common.context import Context from flwr.common.logger import log from flwr.common.message import Message -from flwr.simulation.ray_transport.ray_actor import ( - BasicActorPool, - ClientAppActor, - init_ray, -) +from flwr.common.typing import ConfigsRecordValues +from flwr.simulation.ray_transport.ray_actor import BasicActorPool, ClientAppActor from flwr.simulation.ray_transport.utils import enable_tf_gpu_growth from .backend import Backend, BackendConfig ClientResourcesDict = Dict[str, Union[int, float]] +ActorArgsDict = Dict[str, Union[int, float, Callable[[], None]]] +RunTimeEnvDict = Dict[str, Union[str, List[str]]] class RayBackend(Backend): @@ -51,40 +50,29 @@ def __init__( if not pathlib.Path(work_dir).exists(): raise ValueError(f"Specified work_dir {work_dir} does not exist.") - # Init ray and append working dir if needed - runtime_env = ( - self._configure_runtime_env(work_dir=work_dir) if work_dir else None - ) - - if backend_config.get("mute_logging", False): - init_ray( - logging_level=WARNING, log_to_driver=False, runtime_env=runtime_env - ) - elif backend_config.get("silent", False): - init_ray(logging_level=WARNING, log_to_driver=True, runtime_env=runtime_env) - else: - init_ray(runtime_env=runtime_env) + # Initialise ray + self.init_args_key = "init_args" + self.init_ray(backend_config, work_dir) # Validate client resources self.client_resources_key = "client_resources" + client_resources = self._validate_client_resources(config=backend_config) # Create actor pool - use_tf = backend_config.get("tensorflow", False) - actor_kwargs = {"on_actor_init_fn": enable_tf_gpu_growth} if use_tf else {} + actor_kwargs = self._validate_actor_arguments(config=backend_config) - client_resources = self._validate_client_resources(config=backend_config) self.pool = BasicActorPool( actor_type=ClientAppActor, client_resources=client_resources, actor_kwargs=actor_kwargs, ) - def _configure_runtime_env(self, work_dir: str) -> Dict[str, Union[str, List[str]]]: + def _configure_runtime_env(self, work_dir: str) -> RunTimeEnvDict: """Return list of files/subdirectories to exclude relative to work_dir. Without this, Ray will push everything to the Ray Cluster. """ - runtime_env: Dict[str, Union[str, List[str]]] = {"working_dir": work_dir} + runtime_env: RunTimeEnvDict = {"working_dir": work_dir} excludes = [] path = pathlib.Path(work_dir) @@ -125,6 +113,37 @@ def _validate_client_resources(self, config: BackendConfig) -> ClientResourcesDi return client_resources + def _validate_actor_arguments(self, config: BackendConfig) -> ActorArgsDict: + actor_args_config = config.get("actor", False) + actor_args: ActorArgsDict = {} + if actor_args_config: + use_tf = actor_args.get("tensorflow", False) + if use_tf: + actor_args["on_actor_init_fn"] = enable_tf_gpu_growth + return actor_args + + def init_ray(self, backend_config: BackendConfig, work_dir: str) -> None: + """Intialises Ray if not already initialised.""" + if not ray.is_initialized(): + # Init ray and append working dir if needed + runtime_env = ( + self._configure_runtime_env(work_dir=work_dir) if work_dir else None + ) + + ray_init_args: Dict[ + str, + Union[ConfigsRecordValues, RunTimeEnvDict], + ] = {} + + if backend_config.get(self.init_args_key): + for k, v in backend_config[self.init_args_key].items(): + ray_init_args[k] = v + + if runtime_env is not None: + ray_init_args["runtime_env"] = runtime_env + + ray.init(**ray_init_args) + @property def num_workers(self) -> int: """Return number of actors in pool.""" @@ -152,7 +171,7 @@ async def process_message( partition_id = message.metadata.partition_id try: - # Submite a task to the pool + # Submit a task to the pool future = await self.pool.submit( lambda a, a_fn, mssg, cid, state: a.run.remote(a_fn, mssg, cid, state), (app, message, str(partition_id), context), diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index dcac0b81d666..57c952cc9310 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -38,6 +38,7 @@ ) from flwr.common.object_ref import load_app from flwr.common.recordset_compat import getpropertiesins_to_recordset +from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig from flwr.server.superlink.fleet.vce.backend.raybackend import RayBackend @@ -215,3 +216,35 @@ def test_backend_creation_submit_and_termination_existing_client_app_unsetworkdi workdir="/?&%$^#%@$!", ) self.addAsyncCleanup(self.on_cleanup) + + def test_backend_creation_with_init_arguments(self) -> None: + """Testing whether init args are properly parsed to Ray.""" + backend_config_4: BackendConfig = { + "init_args": {"num_cpus": 4}, + "client_resources": {"num_cpus": 1, "num_gpus": 0}, + } + + backend_config_2: BackendConfig = { + "init_args": {"num_cpus": 2}, + "client_resources": {"num_cpus": 1, "num_gpus": 0}, + } + + RayBackend( + backend_config=backend_config_4, + work_dir="", + ) + nodes = ray.nodes() + + assert nodes[0]["Resources"]["CPU"] == backend_config_4["init_args"]["num_cpus"] + + ray.shutdown() + + RayBackend( + backend_config=backend_config_2, + work_dir="", + ) + nodes = ray.nodes() + + assert nodes[0]["Resources"]["CPU"] == backend_config_2["init_args"]["num_cpus"] + + self.addAsyncCleanup(self.on_cleanup) diff --git a/src/py/flwr/simulation/ray_transport/ray_actor.py b/src/py/flwr/simulation/ray_transport/ray_actor.py index 9caf0fc3e6c0..7afffb865334 100644 --- a/src/py/flwr/simulation/ray_transport/ray_actor.py +++ b/src/py/flwr/simulation/ray_transport/ray_actor.py @@ -399,12 +399,6 @@ def get_client_result( return self._fetch_future_result(cid) -def init_ray(*args: Any, **kwargs: Any) -> None: - """Intialises Ray if not already initialised.""" - if not ray.is_initialized(): - ray.init(*args, **kwargs) - - class BasicActorPool: """A basic actor pool.""" diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index a3de1401d252..7c7a412a245b 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -22,16 +22,17 @@ import traceback from logging import DEBUG, ERROR, INFO, WARNING from time import sleep -from typing import Dict, Optional +from typing import Optional from flwr.client import ClientApp from flwr.common import EventType, event, log from flwr.common.logger import set_logger_propagation, update_console_handler -from flwr.common.typing import ConfigsRecordValues, Run +from flwr.common.typing import Run from flwr.server.driver import Driver, InMemoryDriver from flwr.server.run_serverapp import run from flwr.server.server_app import ServerApp from flwr.server.superlink.fleet import vce +from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig from flwr.server.superlink.state import StateFactory from flwr.simulation.ray_transport.utils import ( enable_tf_gpu_growth as enable_gpu_growth, @@ -66,7 +67,7 @@ def run_simulation( client_app: ClientApp, num_supernodes: int, backend_name: str = "ray", - backend_config: Optional[Dict[str, ConfigsRecordValues]] = None, + backend_config: Optional[BackendConfig] = None, enable_tf_gpu_growth: bool = False, verbose_logging: bool = False, ) -> None: @@ -90,9 +91,12 @@ def run_simulation( backend_name : str (default: ray) A simulation backend that runs `ClientApp`s. - backend_config : Optional[Dict[str, ConfigsRecordValues]] - 'A dictionary, e.g {"": , "": } to configure a - backend. Values supported in are those included by + backend_config : Optional[BackendConfig] + 'A dictionary to configure a backend. Separate dictionaries to configure + different elements of backend. Supported top-level keys are `init_args` + for values parsed to initialisation of backend, `client_resources` + to define the resources for clients, and `actor` to define the actor + parameters. Values supported in are those included by `flwr.common.typing.ConfigsRecordValues`. enable_tf_gpu_growth : bool (default: False) @@ -104,7 +108,7 @@ def run_simulation( works in the TensorFlow documentation: https://www.tensorflow.org/api/stable. verbose_logging : bool (default: False) - When diabled, only INFO, WARNING and ERROR log messages will be shown. If + When disabled, only INFO, WARNING and ERROR log messages will be shown. If enabled, DEBUG-level logs will be displayed. """ _run_simulation( @@ -133,7 +137,7 @@ def run_serverapp_th( def server_th_with_start_checks( # type: ignore tf_gpu_growth: bool, stop_event: asyncio.Event, **kwargs ) -> None: - """Run SeverApp, after check if GPU memory grouwth has to be set. + """Run SeverApp, after check if GPU memory growth has to be set. Upon exception, trigger stop event for Simulation Engine. """ @@ -194,7 +198,7 @@ def _main_loop( ) -> None: """Launch SuperLink with Simulation Engine, then ServerApp on a separate thread. - Everything runs on the main thread or a separate one, depening on whether the main + Everything runs on the main thread or a separate one, depending on whether the main thread already contains a running Asyncio event loop. This is the case if running the Simulation Engine on a Jupyter/Colab notebook. """ @@ -259,7 +263,7 @@ def _run_simulation( client_app: Optional[ClientApp] = None, server_app: Optional[ServerApp] = None, backend_name: str = "ray", - backend_config: Optional[Dict[str, ConfigsRecordValues]] = None, + backend_config: Optional[BackendConfig] = None, client_app_attr: Optional[str] = None, server_app_attr: Optional[str] = None, app_dir: str = "", @@ -286,9 +290,12 @@ def _run_simulation( backend_name : str (default: ray) A simulation backend that runs `ClientApp`s. - backend_config : Optional[Dict[str, ConfigsRecordValues]] - 'A dictionary, e.g {"":, "":} to configure a - backend. Values supported in are those included by + backend_config : Optional[BackendConfig] + 'A dictionary to configure a backend. Separate dictionaries to configure + different elements of backend. Supported top-level keys are `init_args` + for values parsed to initialisation of backend, `client_resources` + to define the resources for clients, and `actor` to define the actor + parameters. Values supported in are those included by `flwr.common.typing.ConfigsRecordValues`. client_app_attr : str @@ -310,30 +317,34 @@ def _run_simulation( A boolean to indicate whether to enable GPU growth on the main thread. This is desirable if you make use of a TensorFlow model on your `ServerApp` while having your `ClientApp` running on the same GPU. Without enabling this, you - might encounter an out-of-memory error becasue TensorFlow by default allocates + might encounter an out-of-memory error because TensorFlow by default allocates all GPU memory. Read mor about how `tf.config.experimental.set_memory_growth()` works in the TensorFlow documentation: https://www.tensorflow.org/api/stable. verbose_logging : bool (default: False) - When diabled, only INFO, WARNING and ERROR log messages will be shown. If + When disabled, only INFO, WARNING and ERROR log messages will be shown. If enabled, DEBUG-level logs will be displayed. """ if backend_config is None: backend_config = {} + if "init_args" not in backend_config: + backend_config["init_args"] = {} + # Set logging level logger = logging.getLogger("flwr") if verbose_logging: update_console_handler(level=DEBUG, timestamps=True, colored=True) else: - backend_config["silent"] = True + backend_config["init_args"]["logging_level"] = WARNING + backend_config["init_args"]["log_to_driver"] = True if enable_tf_gpu_growth: # Check that Backend config has also enabled using GPU growth - use_tf = backend_config.get("tensorflow", False) + use_tf = backend_config.get("actor", {}).get("tensorflow", False) if not use_tf: log(WARNING, "Enabling GPU growth for your backend.") - backend_config["tensorflow"] = True + backend_config["actor"]["tensorflow"] = True # Convert config to original JSON-stream format backend_config_stream = json.dumps(backend_config) @@ -352,7 +363,7 @@ def _run_simulation( server_app_attr, ) # Detect if there is an Asyncio event loop already running. - # If yes, run everything on a separate thread. In environmnets + # If yes, run everything on a separate thread. In environments # like Jupyter/Colab notebooks, there is an event loop present. run_in_thread = False try: @@ -364,7 +375,7 @@ def _run_simulation( run_in_thread = True except RuntimeError: - log(DEBUG, "No asyncio event loop runnig") + log(DEBUG, "No asyncio event loop running") finally: if run_in_thread: @@ -409,7 +420,8 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: parser.add_argument( "--backend-config", type=str, - default='{"client_resources": {"num_cpus":2, "num_gpus":0.0}, "tensorflow": 0}', + default='{"client_resources": {"num_cpus":2, "num_gpus":0.0},' + '"actor": {"tensorflow": 0}}', help='A JSON formatted stream, e.g \'{"":, "":}\' to ' "configure a backend. Values supported in are those included by " "`flwr.common.typing.ConfigsRecordValues`. ", From b025d8ec35337f35791f97e5a464f582cdb00f10 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sat, 22 Jun 2024 12:58:01 +0200 Subject: [PATCH 079/595] docs(framework) Add latest Hosted Weblate translation updates (#3671) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 박태현 --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 1993 +++++++++++++----- 1 file changed, 1497 insertions(+), 496 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 9c8d2f5ff19b..74cb5f00589f 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -8,15 +8,16 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-06-17 16:09+0200\n" -"PO-Revision-Date: 2024-06-16 09:09+0000\n" +"PO-Revision-Date: 2024-06-22 10:52+0000\n" "Last-Translator: 박태현 \n" +"Language-Team: Korean \n" "Language: ko\n" -"Language-Team: Korean \n" -"Plural-Forms: nplurals=1; plural=0;\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" +"X-Generator: Weblate 5.6-rc\n" "Generated-By: Babel 2.15.0\n" #: ../../source/contributor-explanation-architecture.rst:2 @@ -58,7 +59,6 @@ msgid "How to build Docker Flower images locally" msgstr "Docker Flower 이미지를 Locally 구축하는 방법" #: ../../source/contributor-how-to-build-docker-images.rst:4 -#, fuzzy msgid "" "Flower provides pre-made docker images on `Docker Hub " "`_ that include all necessary dependencies" @@ -68,11 +68,12 @@ msgid "" " this guide, we will explain what images exist and how to build them " "locally." msgstr "" -"Flower는 'Docker Hub '_에서 미리 만들어진 Docker " -"이미지들을 제공합니다. 해당 이미지들은 SuperLink, ServerNode 또는 ServerApp을 실행하는 데 필요한 모든 " -"dependencies를 포함합니다. 필요한 경우 다른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해" -" 처음부터 사용자 정의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 이미지들과 이들을 로컬에서 빌드하는 " -"방법에 대해 설명하겠습니다." +"Flower는 'Docker Hub '_에서 미리 만들어진 " +"Docker 이미지들을 제공합니다. 해당 이미지들은 SuperLink, ServerNode 또는 " +"ServerApp을 실행하는 데 필요한 모든 dependencies를 포함합니다. 필요한 경우 " +"다른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해 처음부터 사용자 " +"정의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 " +"이미지들과 이들을 로컬에서 빌드하는 방법에 대해 설명하겠습니다." #: ../../source/contributor-how-to-build-docker-images.rst:10 msgid "" @@ -108,7 +109,6 @@ msgstr "" "디렉토리에서 찾을 수 있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:28 -#, fuzzy msgid "" "Flower Docker images are configured via build arguments. Through build " "arguments, we can make the creation of images more flexible. For example," @@ -118,10 +118,12 @@ msgid "" "available build arguments for each image are listed in one of the tables " "below." msgstr "" -"base 이미지와 SuperLink 이미지 둘 다 빌드 argument들을 통해 구성됩니다. 빌드 argument들을 통해, 빌드를" -" 더 유연하게 만들 수 있습니다. 예를 들어, base 이미지에서 \"PYTHON_VERSION\" 빌드 argument를 사용하여" -" Python 버전을 지정할 수 있습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 합니다. 각 " -"이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 있습니다." +"Flower Docker는 빌드 argument를 통해 구성됩니다. 빌드 argument들을 통해, " +"이미지를 보다 유연하게 생성할 수 있습니다. 예를 들어, base 이미지에서 " +"\"PYTHON_VERSION\" 빌드 argument를 사용하여 Python 버전을 지정할 수 " +"있습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 " +"합니다. 각 이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 " +"있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:35 msgid "Building the base image" @@ -149,12 +151,11 @@ msgstr "예시" #: ../../source/contributor-how-to-build-docker-images.rst:45 msgid "``DISTRO``" -msgstr "" +msgstr "``DISTRO``" #: ../../source/contributor-how-to-build-docker-images.rst:46 -#, fuzzy msgid "The Linux distribution to use as the base image." -msgstr "base 이미지의 Ubuntu 버전." +msgstr "base 이미지 사용을 위한 Linux 배포판." #: ../../source/contributor-how-to-build-docker-images.rst:47 #: ../../source/contributor-how-to-build-docker-images.rst:51 @@ -162,26 +163,23 @@ msgstr "base 이미지의 Ubuntu 버전." #: ../../source/contributor-how-to-build-docker-images.rst:71 #: ../../source/contributor-how-to-build-docker-images.rst:104 msgid "No" -msgstr "" +msgstr "아니오" #: ../../source/contributor-how-to-build-docker-images.rst:48 -#, fuzzy msgid "``ubuntu``" -msgstr "``UBUNTU_VERSION``" +msgstr "``ubuntu``" #: ../../source/contributor-how-to-build-docker-images.rst:49 -#, fuzzy msgid "``DISTRO_VERSION``" -msgstr "``PIP_VERSION``" +msgstr "``DISTRO_VERSION``" #: ../../source/contributor-how-to-build-docker-images.rst:50 msgid "Version of the Linux distribution." -msgstr "" +msgstr "Linux 배포판 버전." #: ../../source/contributor-how-to-build-docker-images.rst:52 -#, fuzzy msgid "``22.04``" -msgstr "``23.0.1``" +msgstr "``22.04``" #: ../../source/contributor-how-to-build-docker-images.rst:53 msgid "``PYTHON_VERSION``" @@ -193,7 +191,7 @@ msgstr "설치 된 ``python`` 버전." #: ../../source/contributor-how-to-build-docker-images.rst:56 msgid "``3.11`` or ``3.11.1``" -msgstr "" +msgstr "``3.11`` 또는 ``3.11.1``" #: ../../source/contributor-how-to-build-docker-images.rst:57 msgid "``PIP_VERSION``" @@ -243,20 +241,20 @@ msgid "``FLWR_PACKAGE``" msgstr "``FLWR_PACKAGE``" #: ../../source/contributor-how-to-build-docker-images.rst:70 -#, fuzzy msgid "The Flower package to be installed." -msgstr "설치 할 PyPI 패키지." +msgstr "설치 할 Flower 패키지." #: ../../source/contributor-how-to-build-docker-images.rst:72 msgid "``flwr`` or ``flwr-nightly``" -msgstr "" +msgstr "``flwr`` 또는 ``flwr-nightly``" #: ../../source/contributor-how-to-build-docker-images.rst:75 -#, fuzzy msgid "" "The following example creates a base Ubuntu/Alpine image with Python " "3.11.0, pip 23.0.1, setuptools 69.0.2 and Flower 1.8.0:" -msgstr "다음 예시에서는 Python 3.11.0, pip 23.0.1 그리고 setuptools 69.0.2의 base 이미지를 만듭니다:" +msgstr "" +"다음 예시에서는 Python 3.11.0, pip 23.0.1, setuptools 및 Flower 1.8.0으로 " +"기본 Ubuntu/Alpine 이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:88 msgid "" @@ -268,9 +266,8 @@ msgstr "" " 태그도 정할 수 있습니다. 이 값들은 예시일 뿐입니다." #: ../../source/contributor-how-to-build-docker-images.rst:92 -#, fuzzy msgid "Building the SuperLink/SuperNode or ServerApp image" -msgstr "SuperLink 이미지 빌드" +msgstr "SuperLink/SuperNode 또는 ServerApp 이미지 빌드" #: ../../source/contributor-how-to-build-docker-images.rst:102 msgid "``BASE_REPOSITORY``" @@ -281,42 +278,36 @@ msgid "The repository name of the base image." msgstr "base 이미지의 리포지토리 이름." #: ../../source/contributor-how-to-build-docker-images.rst:105 -#, fuzzy msgid "``flwr/base``" -msgstr "``FLWR_PACKAGE``" +msgstr "``flwr/base``" #: ../../source/contributor-how-to-build-docker-images.rst:106 -#, fuzzy msgid "``BASE_IMAGE``" -msgstr "``BASE_REPOSITORY``" +msgstr "``BASE_IMAGE``" #: ../../source/contributor-how-to-build-docker-images.rst:107 -#, fuzzy msgid "The Tag of the Flower base image." -msgstr "base 이미지의 리포지토리 이름." +msgstr "Flower 기본 이미지의 태그." #: ../../source/contributor-how-to-build-docker-images.rst:109 msgid "``1.8.0-py3.10-ubuntu22.04``" -msgstr "" +msgstr "``1.8.0-py3.10-ubuntu22.04``" #: ../../source/contributor-how-to-build-docker-images.rst:111 -#, fuzzy msgid "" "The following example creates a SuperLink/SuperNode or ServerApp image " "with the official Flower base image:" -msgstr "" -"다음 예시에서는 py3.11-ubuntu22.04 및 Flower 1.8.0의 공식 Flower base 이미지로 SuperLink" -" 이미지를 만듭니다:" +msgstr "다음 예시에서는 공식 Flower 기본 이미지로 SuperLink/SuperNode 또는 " +"ServerApp이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:122 -#, fuzzy msgid "" "If you want to use your own base image instead of the official Flower " "base image, all you need to do is set the ``BASE_REPOSITORY`` build " "argument." msgstr "" -"공식 Flower base 이미지 대신 자체 base 이미지를 사용 하길 원한다면, ``BASE_REPOSITORY``, " -"``PYTHON_VERSION`` 및 ``UBUNTU_VERSION`` 빌드 argument들을 설정해야 합니다." +"공식 Flower 기본 이미지 대신 자체 기본 이미지를 사용 하길 원한다면, " +"``BASE_REPOSITORY`` 빌드 argument들을 설정해야 합니다." #: ../../source/contributor-how-to-build-docker-images.rst:133 msgid "After creating the image, we can test whether the image is working:" @@ -818,15 +809,18 @@ msgid "" "This will create a draft release on GitHub containing the correct " "artifacts and the relevant part of the changelog." msgstr "" +"pull request가 병합되면, PR이 병합되는 즉시 버전 번호로 릴리즈 커밋에 태그를 " +"지정합니다:``git tag v`` (버전 번호 앞에 ``v``가 추가된 것을 " +"확인), 그 다음 ``git push --tags``. 이렇게 하면 올바른 아티팩트와 변경 " +"로그의 관련 부분이 포함된 초안 릴리즈가 GitHub에 생성됩니다." #: ../../source/contributor-how-to-release-flower.rst:14 msgid "Check the draft release on GitHub, and if everything is good, publish it." -msgstr "" +msgstr "GitHub에서 릴리즈 초안을 확인하고, 모든 것이 양호하면 게시하세요." #: ../../source/contributor-how-to-release-flower.rst:15 -#, fuzzy msgid "Trigger the CI for building the Docker images." -msgstr "공식 Ubuntu Docker 이미지 버전." +msgstr "Docker 이미지 빌드를 위해 CI를 트리거합니다." #: ../../source/contributor-how-to-release-flower.rst:17 msgid "" @@ -835,124 +829,136 @@ msgid "" "through the UI or via the GitHub CLI. The event requires only one input, " "the Flower version, to be released." msgstr "" +"워크플로우를 트리거하려면 공동 작업자가 GitHub CI에서 ``workflow_dispatch``" +"를 생성해야 합니다. 이 작업은 UI 또는 GitHub CLI 를 통해 수행할 수 있습니다. " +"이벤트는 Flower 버전 한 가지 입력만 필요합니다." #: ../../source/contributor-how-to-release-flower.rst:21 msgid "**Via the UI**" -msgstr "" +msgstr "**UI를 통해서**" #: ../../source/contributor-how-to-release-flower.rst:23 msgid "" "Go to the ``Build docker images`` workflow `page " "`_." msgstr "" +"``Build docker images`` 워크플로우 `페이지 `_로 이동합니다." #: ../../source/contributor-how-to-release-flower.rst:24 msgid "" "Click on the ``Run workflow`` button and type the new version of Flower " "in the ``Version of Flower`` input field." -msgstr "" +msgstr "``Run workflow`` 버튼을 누르고 ``Version of Flower``에 Flower의 새버전을 " +"입력합니다." #: ../../source/contributor-how-to-release-flower.rst:25 msgid "Click on the **green** ``Run workflow`` button." -msgstr "" +msgstr "**초록색**의 ``Run workflow``버튼을 클릭합니다." #: ../../source/contributor-how-to-release-flower.rst:29 msgid "**Via the GitHub CI**" -msgstr "" +msgstr "**GitHub CI를 통해서**" #: ../../source/contributor-how-to-release-flower.rst:31 msgid "" "Make sure you are logged in via ``gh auth login`` and that the current " "working directory is the root of the Flower repository." -msgstr "" +msgstr "``gh auth login``을 통해 로그인 했는지, 현재 작업 디렉토리가 Flower " +"리포지토리의 root인지 확인하세요." #: ../../source/contributor-how-to-release-flower.rst:32 msgid "" "Trigger the workflow via ``gh workflow run docker-images.yml -f flwr-" "version=``." msgstr "" +"``gh workflow run docker-images.yml -f flwr-version=``을 통해 " +"워크플로우 를 트리거합니다." #: ../../source/contributor-how-to-release-flower.rst:35 msgid "After the release" -msgstr "" +msgstr "릴리즈 후에" #: ../../source/contributor-how-to-release-flower.rst:37 msgid "Create a pull request which contains the following changes:" -msgstr "" +msgstr "다음 변경 사항이 포함된 pull request를 만듭니다:" #: ../../source/contributor-how-to-release-flower.rst:39 msgid "Increase the minor version in ``pyproject.toml`` by one." -msgstr "" +msgstr "``pyproject.toml``의 마이너 버전을 하나씩 늘립니다." #: ../../source/contributor-how-to-release-flower.rst:40 msgid "Update all files which contain the current version number if necessary." -msgstr "" +msgstr "필요한 경우 현재 버전 번호가 포함된 모든 파일을 업데이트합니다." #: ../../source/contributor-how-to-release-flower.rst:41 msgid "Add a new ``Unreleased`` section in ``changelog.md``." -msgstr "" +msgstr "``changelog.md``에 ``Unreleased`` 섹션을 새로 추가합니다." #: ../../source/contributor-how-to-release-flower.rst:43 msgid "" "Merge the pull request on the same day (i.e., before a new nightly " "release gets published to PyPI)." -msgstr "" +msgstr "pull request를 같은 날(즉, 새로운 nightly 릴리즈가 PyPI에 게시되기 전에) " +"병합하세요." #: ../../source/contributor-how-to-release-flower.rst:46 msgid "Publishing a pre-release" -msgstr "" +msgstr "사전 릴리즈 게시" #: ../../source/contributor-how-to-release-flower.rst:49 msgid "Pre-release naming" -msgstr "" +msgstr "사전 릴리즈 이름" #: ../../source/contributor-how-to-release-flower.rst:51 msgid "" "PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases" " MUST use one of the following naming patterns:" msgstr "" +"PyPI는 사전 릴리즈(알파, 베타, 릴리스 후보)를 지원합니다. 사전 릴리즈는 " +"반드시 다음 명명 패턴 중 하나를 사용해야 합니다:" #: ../../source/contributor-how-to-release-flower.rst:53 msgid "Alpha: ``MAJOR.MINOR.PATCHaN``" -msgstr "" +msgstr "Alpha: ``MAJOR.MINOR.PATCHaN``" #: ../../source/contributor-how-to-release-flower.rst:54 msgid "Beta: ``MAJOR.MINOR.PATCHbN``" -msgstr "" +msgstr "Beta: ``MAJOR.MINOR.PATCHbN``" #: ../../source/contributor-how-to-release-flower.rst:55 msgid "Release candidate (RC): ``MAJOR.MINOR.PATCHrcN``" -msgstr "" +msgstr "Release candidate (RC): ``MAJOR.MINOR.PATCHrcN``" #: ../../source/contributor-how-to-release-flower.rst:57 msgid "Examples include:" -msgstr "" +msgstr "예시:" #: ../../source/contributor-how-to-release-flower.rst:59 msgid "``1.0.0a0``" -msgstr "" +msgstr "``1.0.0a0``" #: ../../source/contributor-how-to-release-flower.rst:60 msgid "``1.0.0b0``" -msgstr "" +msgstr "``1.0.0b0``" #: ../../source/contributor-how-to-release-flower.rst:61 msgid "``1.0.0rc0``" -msgstr "" +msgstr "``1.0.0rc0``" #: ../../source/contributor-how-to-release-flower.rst:62 msgid "``1.0.0rc1``" -msgstr "" +msgstr "``1.0.0rc1``" #: ../../source/contributor-how-to-release-flower.rst:64 msgid "" "This is in line with PEP-440 and the recommendations from the Python " "Packaging Authority (PyPA):" -msgstr "" +msgstr "이는 PEP-440 및 Python Packaging Authority (PyPA)의 권장 사항과 일치합니다:" #: ../../source/contributor-how-to-release-flower.rst:67 msgid "`PEP-440 `_" -msgstr "" +msgstr "`PEP-440 `_" #: ../../source/contributor-how-to-release-flower.rst:68 msgid "" @@ -960,6 +966,8 @@ msgid "" "`_" msgstr "" +"`PyPA 버전 관리 체계 선택하기 `_" #: ../../source/contributor-how-to-release-flower.rst:70 msgid "" @@ -968,14 +976,17 @@ msgid "" "`_ (specifically item " "11 on precedence)." msgstr "" +"PyPA에서 정의한 접근 방식은 SemVer 2.0.0 사양과 호환되지 않으며, 자세한 " +"내용은`Semantic Versioning 관리 사양 `_ (특히 항목 11이 우선순위)을 참조하세요." #: ../../source/contributor-how-to-release-flower.rst:73 msgid "Pre-release classification" -msgstr "" +msgstr "사전 릴리즈 분류" #: ../../source/contributor-how-to-release-flower.rst:75 msgid "Should the next pre-release be called alpha, beta, or release candidate?" -msgstr "" +msgstr "다음 사전 릴리를 알파, 베타 또는 릴리스 후보라고 불러야 하나요?" #: ../../source/contributor-how-to-release-flower.rst:77 msgid "" @@ -983,18 +994,20 @@ msgid "" "classified as \"won't fix\" for the next stable release) - if no issues " "surface this will become the next stable release" msgstr "" +"RC: 기능 완료, 알려진 문제 없음(다음 stable 릴리즈에서 \"수정되지 않음\"으로 " +"분류된 문제 제외) - 문제가 나타나지 않으면 다음 stable 릴리즈가 됩니다" #: ../../source/contributor-how-to-release-flower.rst:78 msgid "Beta: feature complete, allowed to have known issues" -msgstr "" +msgstr "베타: 기능 완료, 알려진 문제 발생 가능" #: ../../source/contributor-how-to-release-flower.rst:79 msgid "Alpha: not feature complete, allowed to have known issues" -msgstr "" +msgstr "알파: 기능 미완성, 알려진 문제가 있을 수 있음" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:2 msgid "Set up a virtual env" -msgstr "" +msgstr "가상 환경 설정" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:4 msgid "" @@ -1003,10 +1016,13 @@ msgid "" "environment with pyenv virtualenv, poetry, or Anaconda. You can follow " "the instructions or choose your preferred setup." msgstr "" +"가상 환경 내에서 파이썬 설정을 실행하는 것이 좋습니다. 이 가이드에서는 pyenv " +"virtualenv, poetry 또는 Anaconda를 사용하여 가상 환경을 만드는 세 가지 " +"예제를 보여줍니다. 안내를 따르거나 원하는 설정을 선택할 수 있습니다." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:9 msgid "Python Version" -msgstr "" +msgstr "Python 버전" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:11 #: ../../source/how-to-install-flower.rst:8 @@ -1015,6 +1031,8 @@ msgid "" "but `Python 3.10 `_ or above is " "recommended." msgstr "" +"Flower는 `Python 3.8 `_이상이 필요하지만, `" +"Python 3.10 `_이상을 권장합니다." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:14 msgid "" @@ -1023,10 +1041,13 @@ msgid "" "most `Python 3.11 `_ for running Flower " "simulations." msgstr "" +"`Ray `__와 호환되지 않는 것으로 알려져 " +"있으므로, 현재 Flower 시뮬레이션을 실행할 때는 최대 `Python 3.11 " +"`_을 사용하는 것이 좋습니다." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:19 msgid "Virtualenv with Pyenv/Virtualenv" -msgstr "" +msgstr "Pyenv/Virtualenv를 사용한 가상 환경" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:21 msgid "" @@ -1035,24 +1056,30 @@ msgid "" "/pyenv-virtualenv>`_. Please see `Flower examples " "`_ for details." msgstr "" +"권장 가상 환경 중 하나는 `pyenv `_/`" +"virtualenv `_입니다. 자세한 " +"내용은 `Flower examples `" +"_를 참조하세요." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:23 msgid "" "Once Pyenv is set up, you can use it to install `Python Version 3.10 " "`_ or above:" msgstr "" +"Pyenv가 설정되면 이를 사용하여 'Python 버전 3.10 `_ 이상'을 설치할 수 있습니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:29 msgid "Create the virtualenv with:" -msgstr "" +msgstr "가상 환경을 만듭니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:36 msgid "Activate the virtualenv by running the following command:" -msgstr "" +msgstr "다음 명령을 실행하여 가상 환경을 활성화합니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:44 msgid "Virtualenv with Poetry" -msgstr "" +msgstr "Poetry를 사용한 가상 환경" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:46 msgid "" @@ -1060,16 +1087,20 @@ msgid "" "poetry.org/docs/>`_ to manage dependencies. After installing Poetry you " "simply create a virtual environment with:" msgstr "" +"Flower examples은 dependencies을 관리하기 위해 `Poetry `_를 기반으로 합니다. Poetry를 설치한 후 가상 환경을 생성하기만 " +"하면 됩니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:52 msgid "" "If you open a new terminal you can activate the previously created " "virtual environment with the following command:" -msgstr "" +msgstr "새 터미널을 열면 다음 명령을 사용하여 이전에 생성한 가상 환경을 활성화할 수 " +"있습니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:60 msgid "Virtualenv with Anaconda" -msgstr "" +msgstr "Anaconda를 사용한 가상 환경" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:62 msgid "" @@ -1078,28 +1109,33 @@ msgid "" "/user-guide/install/index.html>`_ package. After setting it up you can " "create a virtual environment with:" msgstr "" +"가상 환경에서 Anaconda를 사용하려면 `conda `_ 패키지를 설치 및 " +"설정하세요. 설정 후 다음을 사용하여 가상 환경을 만들 수 있습니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:68 msgid "and activate the virtual environment with:" -msgstr "" +msgstr "그 후 가상 환경을 활성화합니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:76 msgid "And then?" -msgstr "" +msgstr "그다음은?" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:78 msgid "" "As soon as you created your virtual environment you clone one of the " "`Flower examples `_." msgstr "" +"가상 환경을 생성하자마자 'Flower examples `_ 중 하나를 클론합니다." #: ../../source/contributor-how-to-write-documentation.rst:2 msgid "Write documentation" -msgstr "" +msgstr "문서 작성" #: ../../source/contributor-how-to-write-documentation.rst:6 msgid "Project layout" -msgstr "" +msgstr "프로젝트 레이아웃" #: ../../source/contributor-how-to-write-documentation.rst:8 msgid "" @@ -1107,6 +1143,9 @@ msgid "" " documentation system supports both reStructuredText (``.rst`` files) and" " Markdown (``.md`` files)." msgstr "" +"Flower 문서는 ``doc`` 디렉토리에 있습니다. Sphinx 기반 문서 시스템은 " +"reStructuredText 텍스트(``.rst`` 파일)와 Markdown(``.md`` 파일)을 모두 " +"지원합니다." #: ../../source/contributor-how-to-write-documentation.rst:10 #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:169 @@ -1116,44 +1155,47 @@ msgid "" "`_ needs to be installed on the " "system." msgstr "" +"로컬에서 문서를 작성하려면(아래 설명과 같이 ``poetry run make html``로) `" +"Pandoc `_이 시스템에 설치되어 있어야 " +"합니다." #: ../../source/contributor-how-to-write-documentation.rst:14 msgid "Edit an existing page" -msgstr "" +msgstr "기존 페이지 편집" #: ../../source/contributor-how-to-write-documentation.rst:16 msgid "Edit an existing ``.rst`` (or ``.md``) file under ``doc/source/``" -msgstr "" +msgstr "doc/source/``에서 기존 ``.rst``(또는 ``.md``) 파일을 편집합니다" #: ../../source/contributor-how-to-write-documentation.rst:17 #: ../../source/contributor-how-to-write-documentation.rst:27 msgid "Compile the docs: ``cd doc``, then ``poetry run make html``" -msgstr "" +msgstr "문서를 컴파일합니다: ``cd doc``, ``poetry run make html`` 순으로 컴파일합니다" #: ../../source/contributor-how-to-write-documentation.rst:18 #: ../../source/contributor-how-to-write-documentation.rst:28 msgid "Open ``doc/build/html/index.html`` in the browser to check the result" -msgstr "" +msgstr "브라우저에서 ``doc/build/html/index.html``을 열어 결과를 확인합니다" #: ../../source/contributor-how-to-write-documentation.rst:22 msgid "Create a new page" -msgstr "" +msgstr "새 페이지 만들기" #: ../../source/contributor-how-to-write-documentation.rst:24 msgid "Add new ``.rst`` file under ``doc/source/``" -msgstr "" +msgstr "``doc/source/`에 새 ``.rst`` 을 추가합니다" #: ../../source/contributor-how-to-write-documentation.rst:25 msgid "Add content to the new ``.rst`` file" -msgstr "" +msgstr "새 '.rst' 파일에 내용을 추가합니다" #: ../../source/contributor-how-to-write-documentation.rst:26 msgid "Link to the new rst from ``index.rst``" -msgstr "" +msgstr "``index.rst``에서 새 rst로 연결합니다" #: ../../source/contributor-ref-good-first-contributions.rst:2 msgid "Good first contributions" -msgstr "" +msgstr "좋은 첫 번째 기여" #: ../../source/contributor-ref-good-first-contributions.rst:4 msgid "" @@ -1162,10 +1204,13 @@ msgid "" "where to start to increase your chances of getting your PR accepted into " "the Flower codebase." msgstr "" +"Flower에 대한 기여를 환영합니다! 하지만 어디서부터 시작해야 할지 알기란 쉽지 " +"않습니다. 그래서 저희는 여러분의 PR이 Flower 코드베이스에 채택될 가능성을 " +"높이기 위해 어디서부터 시작해야 하는지 몇 가지 권장 사항을 정리해 보았습니다." #: ../../source/contributor-ref-good-first-contributions.rst:11 msgid "Where to start" -msgstr "" +msgstr "시작 위치" #: ../../source/contributor-ref-good-first-contributions.rst:13 msgid "" @@ -1173,22 +1218,25 @@ msgid "" "accepted if they only touch non-core areas of the codebase. Good " "candidates to get started are:" msgstr "" +"Flower 코어 라이브러리가 완성될 때까지는 코드베이스의 비핵심 영역만 건드리는 " +"것이 PR을 승인받기가 더 쉬울 것입니다. 시작하기에 좋은 후보자는 다음과 " +"같습니다:" #: ../../source/contributor-ref-good-first-contributions.rst:17 msgid "Documentation: What's missing? What could be expressed more clearly?" -msgstr "" +msgstr "문서: 무엇이 누락되었나요? 무엇을 더 명확하게 표현할 수 있을까요?" #: ../../source/contributor-ref-good-first-contributions.rst:18 msgid "Baselines: See below." -msgstr "" +msgstr "Baselines: 아래를 참조하세요." #: ../../source/contributor-ref-good-first-contributions.rst:19 msgid "Examples: See below." -msgstr "" +msgstr "예시: 아래를 참조하세요." #: ../../source/contributor-ref-good-first-contributions.rst:23 msgid "Request for Flower Baselines" -msgstr "" +msgstr "Flower Baselines 요청" #: ../../source/contributor-ref-good-first-contributions.rst:25 msgid "" @@ -1196,6 +1244,8 @@ msgid "" "out our `contributing guide for baselines " "`_." msgstr "" +"Flower Baseline에 익숙하지 않다면 ' Baseline 기여 가이드 `_를 확인해보세요." #: ../../source/contributor-ref-good-first-contributions.rst:27 msgid "" @@ -1205,16 +1255,21 @@ msgid "" " and that has no assignees, feel free to assign it to yourself and start " "working on it!" msgstr "" +"그런 다음 오픈 된 `이슈 `_에서 baseline " +"요청을 확인해야 합니다. 작업하고 싶은 기준선을 찾았지만 담당자가 없는 경우, " +"자유롭게 자신에게 할당하고 작업을 시작하세요!" #: ../../source/contributor-ref-good-first-contributions.rst:31 msgid "" "Otherwise, if you don't find a baseline you'd like to work on, be sure to" " open a new issue with the baseline request template!" -msgstr "" +msgstr "그렇지 않으면 작업하고 싶은 baseline을 찾지 못하면 baseline 요청 템플릿으로 " +"새 이슈를 열어야 합니다!" #: ../../source/contributor-ref-good-first-contributions.rst:34 msgid "Request for examples" -msgstr "" +msgstr "예시 요청" #: ../../source/contributor-ref-good-first-contributions.rst:36 msgid "" @@ -1222,22 +1277,25 @@ msgid "" "help users to get started with building what they want to build. Here are" " a few ideas where we'd be happy to accept a PR:" msgstr "" +"사용 예시는 사용자가 원하는 것을 구축하는 데 도움이 된다고 생각하기 때문에 " +"더 많은 시간을 할애하여 작성할 수 있었으면 합니다. 다음은 저희가 기꺼이 PR을 " +"수락할 수 있는 몇 가지 아이디어입니다:" #: ../../source/contributor-ref-good-first-contributions.rst:40 msgid "Llama 2 fine-tuning, with Hugging Face Transformers and PyTorch" -msgstr "" +msgstr "Llama 2 미세 조정, Hugging Face Transformer와 파이토치 포함" #: ../../source/contributor-ref-good-first-contributions.rst:41 msgid "XGBoost" -msgstr "" +msgstr "XGBoost" #: ../../source/contributor-ref-good-first-contributions.rst:42 msgid "Android ONNX on-device training" -msgstr "" +msgstr "Android ONNX 온디바이스 훈련" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:2 msgid "Secure Aggregation Protocols" -msgstr "" +msgstr "Secure Aggregation 프로토콜" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:4 msgid "" @@ -1246,10 +1304,14 @@ msgid "" " not be accurate in practice. The SecAgg protocol can be considered as a " "special case of the SecAgg+ protocol." msgstr "" +"SecAgg, SecAgg+, LightSecAgg 프로토콜을 포함합니다. LightSecAgg 프로토콜은 " +"아직 구현되지 않았기 때문에 다이어그램과 추상화가 실제로는 정확하지 않을 수 " +"있습니다. SecAgg 프로토콜은 SecAgg+ 프로토콜의 특수한 경우로 간주할 수 " +"있습니다." #: ../../source/contributor-ref-secure-aggregation-protocols.rst:8 msgid "The :code:`SecAgg+` abstraction" -msgstr "" +msgstr "The :code:`SecAgg+` 추상화" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:10 #: ../../source/contributor-ref-secure-aggregation-protocols.rst:161 @@ -1258,31 +1320,35 @@ msgid "" "(int) for secure aggregation, and thus many python dictionaries used have" " keys of int type rather than ClientProxy type." msgstr "" +"구현에서는 각 클라이언트에 secure aggregation를 위한 고유 인덱스(int)가 " +"할당되므로 사용되는 많은 파이썬 dictionaries에는 ClientProxy 타입이 아닌 int " +"타입의 키가 있습니다." #: ../../source/contributor-ref-secure-aggregation-protocols.rst:65 #: ../../source/contributor-ref-secure-aggregation-protocols.rst:198 msgid "" "The Flower server will execute and process received results in the " "following order:" -msgstr "" +msgstr "Flower 서버는 수신된 결과를 다음 순서로 실행하고 처리합니다:" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:159 msgid "The :code:`LightSecAgg` abstraction" -msgstr "" +msgstr "The :code:`LightSecAgg` 추상" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:271 msgid "Types" -msgstr "" +msgstr "타입" #: ../../source/contributor-tutorial-contribute-on-github.rst:2 msgid "Contribute on GitHub" -msgstr "" +msgstr "GitHub에서 기여하기" #: ../../source/contributor-tutorial-contribute-on-github.rst:4 msgid "" "This guide is for people who want to get involved with Flower, but who " "are not used to contributing to GitHub projects." -msgstr "" +msgstr "이 가이드는 Flower에 참여하고 싶지만 GitHub 프로젝트에 기여하는 데 익숙하지 " +"않은 분들을 위한 것입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:6 msgid "" @@ -1290,14 +1356,16 @@ msgid "" "directly checkout our :doc:`getting started guide for contributors " "`." msgstr "" +"깃허브에서 기여하는 방식에 익숙하다면 :doc:`기여자를 위한 시작 가이드" +"`를 직접 확인하세요." #: ../../source/contributor-tutorial-contribute-on-github.rst:10 msgid "Setting up the repository" -msgstr "" +msgstr "레포지토리 설정하기" #: ../../source/contributor-tutorial-contribute-on-github.rst:21 msgid "**Create a GitHub account and setup Git**" -msgstr "" +msgstr "**GitHub 계정을 만들고 Git을 설정합니다**" #: ../../source/contributor-tutorial-contribute-on-github.rst:13 msgid "" @@ -1307,6 +1375,10 @@ msgid "" "follow this `guide `_ to set it up." msgstr "" +"Git은 분산 버전 관리 도구입니다. 이를 통해 전체 코드베이스의 히스토리와 모든 " +"개발자의 컴퓨터를 저장할 수 있습니다. 로컬 컴퓨터에 설치해야 하는 " +"소프트웨어로, 이 `가이드 `_를 따라 설정할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:16 msgid "" @@ -1314,12 +1386,15 @@ msgid "" "collaboration. It allows for everyone to collaborate and work from " "anywhere on remote repositories." msgstr "" +"GitHub는 그 자체로 버전 관리 및 협업을 위한 코드 호스팅 플랫폼입니다. 누구나 " +"원격 레포지토리에서 어디서든 협업하고 작업할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:18 msgid "" "If you haven't already, you will need to create an account on `GitHub " "`_." -msgstr "" +msgstr "아직 계정을 만들지 않았다면 `GitHub `_에서 계정을 " +"만들어야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:20 msgid "" @@ -1328,10 +1403,13 @@ msgid "" "locally and keep track of them using Git and then you upload your new " "history back to GitHub." msgstr "" +"일반적인 Git 및 GitHub 워크플로우의 기본 개념은 다음과 같이 요약됩니다. " +"GitHub의 원격 레포지토리에서 코드를 다운로드하고 로컬에서 변경한 후 Git을 " +"사용하여 추적한 다음 새 기록을 다시 GitHub에 업로드하는 것입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:32 msgid "**Forking the Flower repository**" -msgstr "" +msgstr "**Flower 레포지토리 포크하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:24 msgid "" @@ -1340,6 +1418,9 @@ msgid "" "connected to your GitHub account) and click the ``Fork`` button situated " "on the top right of the page." msgstr "" +"포크는 GitHub 리포지토리의 개인 복사본입니다. Flower용 포크를 만들려면 " +"``_로 이동하여(GitHub 계정에 연결된 상태에서)" +" 페이지 오른쪽 상단에 있는 ``포크`` 버튼을 클릭해야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:29 msgid "" @@ -1348,10 +1429,13 @@ msgid "" "(i.e., in your own list of repositories). Once created, you should see on" " the top left corner that you are looking at your own version of Flower." msgstr "" +"원하는 경우 이름을 변경할 수 있지만, 이 버전의 Flower는 자신의 계정(즉, " +"자신의 리포지토리 목록)에 위치하게 되므로 변경할 필요는 없습니다. 만들기가 " +"완료되면 왼쪽 상단에Flower 버전이 표시되는 것을 볼 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "**Cloning your forked repository**" -msgstr "" +msgstr "**포크된 레포지토리 클론하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:35 msgid "" @@ -1360,26 +1444,31 @@ msgid "" "first click on the ``Code`` button on the right, this will give you the " "ability to copy the HTTPS link of the repository." msgstr "" +"다음 단계는 컴퓨터에서 포크된 레포지토리를 변경할 수 있도록 다운로드하는 " +"것입니다. 포크된 포지토리 페이지에서 먼저 오른쪽의 ``Code`` 버튼을 클릭하면 " +"레포지토리의 HTTPS 링크를 복사할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:41 msgid "" "Once you copied the \\, you can open a terminal on your machine, " "navigate to the place you want to download the repository to and type:" -msgstr "" +msgstr "\\를 복사한 후에는 컴퓨터에서 터미널을 열고 레포지토리를 다운로드할 " +"위치로 이동하여 입력하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "" "This will create a ``flower/`` (or the name of your fork if you renamed " "it) folder in the current working directory." -msgstr "" +msgstr "현재 작업 디렉터리에``flower/``(또는 포크 이름을 변경한 경우 포크 이름) " +"폴더가 생성됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:66 msgid "**Add origin**" -msgstr "" +msgstr "**origin 추가**" #: ../../source/contributor-tutorial-contribute-on-github.rst:50 msgid "You can then go into the repository folder:" -msgstr "" +msgstr "그런 다음 레포지토리 폴더로 이동할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:56 msgid "" @@ -1388,26 +1477,30 @@ msgid "" "previously mentioned by going to our fork repository on our GitHub " "account and copying the link." msgstr "" +"여기에 레포지토리에 origin을 추가해야 합니다. origin은 원격 포크 " +"레포지토리의 \\입니다. origin을 얻으려면 앞서 설명한 대로 GitHub " +"계정의 포크 레포지토리로 이동하여 링크를 복사하면 됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:61 msgid "" "Once the \\ is copied, we can type the following command in our " "terminal:" -msgstr "" +msgstr "\\ 이 복사되면 터미널에 다음 명령을 입력하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:90 msgid "**Add upstream**" -msgstr "" +msgstr "**Upstream 추가하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:69 msgid "" "Now we will add an upstream address to our repository. Still in the same " "directory, we must run the following command:" -msgstr "" +msgstr "이제 레포지토리에 upstream 주소를 추가하겠습니다. 여전히 같은 디렉터리에서 " +"다음 명령을 실행해야 합니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:76 msgid "The following diagram visually explains what we did in the previous steps:" -msgstr "" +msgstr "다음 다이어그램은 이전 단계에서 수행한 작업을 시각적으로 설명합니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:80 msgid "" @@ -1417,16 +1510,21 @@ msgid "" "remote address of the forked repository we created, i.e. the copy (fork) " "in our own account." msgstr "" +"upstream은 부모 레포지토리(이 경우 Flower)의 GitHub 원격 주소, 즉 우리가 " +"최종적으로 기여하고 싶고 따라서 최신 기록이 필요한 레포지토리입니다. " +"origin은 우리가 만든 포크된 레포지토리의 GitHub 원격 주소, 즉 우리 계정에 " +"있는 사본(포크)입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:84 msgid "" "To make sure our local version of the fork is up-to-date with the latest " "changes from the Flower repository, we can execute the following command:" -msgstr "" +msgstr "로컬 버전의 포크가 Flower 레포지토리의 최신 변경 사항으로 최신 상태인지 " +"확인하려면 다음 명령을 실행하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:93 msgid "Setting up the coding environment" -msgstr "" +msgstr "코딩 환경 설정" #: ../../source/contributor-tutorial-contribute-on-github.rst:95 msgid "" @@ -1435,49 +1533,53 @@ msgid "" "that you won't need to clone the repository). Once you are able to write " "code and test it, you can finally start making changes!" msgstr "" +":doc:'기여자를 위한 시작 가이드 '를 참조하세요(리포지토리를 복제할 필요는 없습니다). 코드를 " +"작성하고 테스트할 수 있게 되면 드디어 변경을 시작할 수 있습니다!" #: ../../source/contributor-tutorial-contribute-on-github.rst:100 msgid "Making changes" -msgstr "" +msgstr "변경하기" #: ../../source/contributor-tutorial-contribute-on-github.rst:102 msgid "" "Before making any changes make sure you are up-to-date with your " "repository:" -msgstr "" +msgstr "변경하기 전에 레포지토리를 최신 상태로 유지하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:108 msgid "And with Flower's repository:" -msgstr "" +msgstr "Flower의 레포지토리도 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:122 msgid "**Create a new branch**" -msgstr "" +msgstr "**새 브랜치 만들기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:115 msgid "" "To make the history cleaner and easier to work with, it is good practice " "to create a new branch for each feature/project that needs to be " "implemented." -msgstr "" +msgstr "히스토리를 더 깔끔하고 작업하기 쉽게 만들려면 구현해야 하는 각 기능/" +"프로젝트에 대해 새 브랜치를 만드는 것이 좋습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:118 msgid "" "To do so, just run the following command inside the repository's " "directory:" -msgstr "" +msgstr "이렇게 하려면 레포지토리 디렉토리에서 다음 명령을 실행하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 msgid "**Make changes**" -msgstr "" +msgstr "**변경하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 msgid "Write great code and create wonderful changes using your favorite editor!" -msgstr "" +msgstr "선호하 편집기를 사용하여 멋진 코드를 작성하고 훌륭한 변화를 만들어 보세요!" #: ../../source/contributor-tutorial-contribute-on-github.rst:138 msgid "**Test and format your code**" -msgstr "" +msgstr "**코드 테스트 및 서식 지정**" #: ../../source/contributor-tutorial-contribute-on-github.rst:128 msgid "" @@ -1485,24 +1587,28 @@ msgid "" "able to be merged into the Flower repository. This is done so the " "codebase stays consistent and easy to understand." msgstr "" +"코드를 테스트하고 서식을 지정하는 것을 잊지 마세요! 그렇지 않으면 코드를 " +"Flower 레포지토리에 병합할 수 없습니다. 이는 코드베이스가 일관성을 유지하고 " +"이해하기 쉽도록 하기 위한 것입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:131 msgid "To do so, we have written a few scripts that you can execute:" -msgstr "" +msgstr "이를 위해 실행할 수 있는 몇 가지 스크립트를 작성했습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:150 msgid "**Stage changes**" -msgstr "" +msgstr "**Stage 변경**" #: ../../source/contributor-tutorial-contribute-on-github.rst:141 msgid "" "Before creating a commit that will update your history, you must specify " "to Git which files it needs to take into account." -msgstr "" +msgstr "기록을 업데이트할 커밋을 만들기 전에 어떤 파일을 고려해야 하는지 Git에 " +"지정해야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:143 msgid "This can be done with:" -msgstr "" +msgstr "이 작업을 수행할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:149 msgid "" @@ -1510,16 +1616,20 @@ msgid "" "(last commit) and to see which files are staged for commit, you can use " "the :code:`git status` command." msgstr "" +"마지막 버전(마지막 커밋)과 비교하여 수정된 파일을 확인하고 커밋을 위해 " +"스테이징된 파일을 확인하려면 :code:`git status` 명령을 사용하면 됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:160 msgid "**Commit changes**" -msgstr "" +msgstr "**Commit 변경**" #: ../../source/contributor-tutorial-contribute-on-github.rst:153 msgid "" "Once you have added all the files you wanted to commit using :code:`git " "add`, you can finally create your commit using this command:" msgstr "" +":code:`git add`를 사용하여 커밋하려는 모든 파일을 추가한 후, 마지막으로 이 " +"명령을 사용하여 커밋을 생성할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:159 msgid "" @@ -1527,10 +1637,13 @@ msgid "" "does. It should be written in an imperative style and be concise. An " "example would be :code:`git commit -m \"Add images to README\"`." msgstr "" +"커밋의 내용을 다른 사람에게 설명하기 위해 \\가 있습니다. " +"명령형 스타일로 작성해야 하며 간결해야 합니다. 예를 들면 :code:`git commit -" +"m \"Add images to README\"`." #: ../../source/contributor-tutorial-contribute-on-github.rst:171 msgid "**Push the changes to the fork**" -msgstr "" +msgstr "**변경 사항을 포크에 푸시**" #: ../../source/contributor-tutorial-contribute-on-github.rst:163 msgid "" @@ -1538,40 +1651,44 @@ msgid "" " history, but GitHub has no way of knowing this unless we push our " "changes to our origin's remote address:" msgstr "" +"변경 사항을 커밋하면 로컬 히스토리를 효과적으로 업데이트한 것이지만, 변경 " +"사항을 원본의 원격 주소로 푸시하지 않는 한 GitHub는 이를 알 방법이 없습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:170 msgid "" "Once this is done, you will see on the GitHub that your forked repo was " "updated with the changes you have made." -msgstr "" +msgstr "이 작업이 완료되면 변경한 내용으로 포크된 레포지토리가 업데이트된 것을 " +"GitHub에서 확인할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:174 msgid "Creating and merging a pull request (PR)" -msgstr "" +msgstr "pull request(PR) 만들기 및 병합하기" #: ../../source/contributor-tutorial-contribute-on-github.rst:206 msgid "**Create the PR**" -msgstr "" +msgstr "**PR 만들기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:177 msgid "" "Once you have pushed changes, on the GitHub webpage of your repository " "you should see the following message:" -msgstr "" +msgstr "변경 사항을 푸시하고 나면 레포지토리의 GitHub 웹페이지에 다음 메시지가 " +"표시됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:181 msgid "Otherwise you can always find this option in the ``Branches`` page." -msgstr "" +msgstr "그렇지 않으면 언제든지 ``Branches`` 페이지에서 이 옵션을 찾을 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:183 msgid "" "Once you click the ``Compare & pull request`` button, you should see " "something similar to this:" -msgstr "" +msgstr "``Compare & pull request`` 버튼을 클릭하면 이와 비슷한 화면이 표시됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:187 msgid "At the top you have an explanation of which branch will be merged where:" -msgstr "" +msgstr "상단에는 어느 지점이 어디에 병합될 것인지에 대한 설명이 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:191 msgid "" @@ -1579,6 +1696,8 @@ msgid "" "``doc-fixes`` from my forked repository to branch ``main`` from the " "Flower repository." msgstr "" +"이 예제에서는 내 포크된 레포지토리의 ``doc-fixes`` 브랜치를 Flower " +"레포지토리의 ``main`` 브랜치에 병합하라는 요청을 볼 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:193 msgid "" @@ -1586,6 +1705,9 @@ msgid "" "guidelines, otherwise it won't be possible to merge the PR. So in this " "case, a correct title might be ``docs(framework:skip) Fix typos``." msgstr "" +"제목은 :ref:`pr_title_format` 가이드라인을 준수하도록 변경해야 하며, 그렇지 " +"않으면 PR을 병합할 수 없습니다. 따라서 이 경우 올바른 제목은 " +"``docs(framework:skip) Fix typos``이 될 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:196 msgid "" @@ -1594,10 +1716,13 @@ msgid "" "won't be rendered once the PR is opened) to guide you through the " "process." msgstr "" +"가운데에 있는 입력 상자는 PR의 기능을 설명하고 기존 이슈에 연결할 수 있는 " +"곳입니다. 프로세스를 안내하기 위해 코멘트(PR이 열리면 렌더링되지 않음)를 " +"배치했습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:199 msgid "It is important to follow the instructions described in comments." -msgstr "" +msgstr "코멘트에 설명된 지침을 따르는 것이 중요합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:201 msgid "" @@ -1605,155 +1730,176 @@ msgid "" "reviewers that a new PR has been opened and that they should look over it" " to merge or to request changes." msgstr "" +"하단에는 PR을 여는 버튼이 있습니다. 이렇게 하면 검토자에게 새 PR이 열렸으며 " +"병합하거나 변경을 요청하기 위해 검토해야 함을 알립니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:204 msgid "" "If your PR is not yet ready for review, and you don't want to notify " "anyone, you have the option to create a draft pull request:" msgstr "" +"PR이 아직 검토할 준비가 되지 않았고 다른 사람에게 알리고 싶지 않은 경우 pull " +"request 초안을 만드는 옵션이 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "**Making new changes**" -msgstr "" +msgstr "**new changes 만들기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "" "Once the PR has been opened (as draft or not), you can still push new " "commits to it the same way we did before, by making changes to the branch" " associated with the PR." -msgstr "" +msgstr "PR이 초안으로 열렸든 아니든, PR과 연결된 브랜치를 변경하여 이전과 같은 " +"방식으로 새 커밋을 푸시할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:231 msgid "**Review the PR**" -msgstr "" +msgstr "**PR 검토하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:212 msgid "" "Once the PR has been opened or once the draft PR has been marked as " "ready, a review from code owners will be automatically requested:" -msgstr "" +msgstr "PR이 열리거나 초안 PR이 준비됨으로 표시되면 코드 소유자의 검토가 자동으로 " +"요청됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:216 msgid "" "Code owners will then look into the code, ask questions, request changes " "or validate the PR." -msgstr "" +msgstr "그러면 코드 소유자는 코드를 살펴보고, 질문하고, 변경을 요청하거나 PR의 " +"유효성을 검사합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:218 msgid "Merging will be blocked if there are ongoing requested changes." -msgstr "" +msgstr "진행 중인 변경 요청이 있는 경우 병합이 차단됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:222 msgid "" "To resolve them, just push the necessary changes to the branch associated" " with the PR:" -msgstr "" +msgstr "이를 해결하려면 PR과 연결된 브랜치에 필요한 변경 사항을 푸시하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:226 msgid "And resolve the conversation:" -msgstr "" +msgstr "그리고 소통을 통해 해결하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:230 msgid "" "Once all the conversations have been resolved, you can re-request a " "review." -msgstr "" +msgstr "모든 대화가 해결되면 검토를 다시 요청할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:251 msgid "**Once the PR is merged**" -msgstr "" +msgstr "**PR이 병합되면**" #: ../../source/contributor-tutorial-contribute-on-github.rst:234 msgid "" "If all the automatic tests have passed and reviewers have no more changes" " to request, they can approve the PR and merge it." -msgstr "" +msgstr "모든 자동 테스트가 통과되고 검토자가 더 이상 요청할 변경 사항이 없는 경우 " +"PR을 승인하고 병합할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:238 msgid "" "Once it is merged, you can delete the branch on GitHub (a button should " "appear to do so) and also delete it locally by doing:" -msgstr "" +msgstr "병합이 완료되면 GitHub에서 브랜치를 삭제할 수 있으며(삭제 버튼이 표시되어야 " +"함), 로컬에서도 삭제할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:245 msgid "Then you should update your forked repository by doing:" -msgstr "" +msgstr "그런 다음 다음을 수행하여 포크된 레포지토리를 업데이트해야 합니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:254 msgid "Example of first contribution" -msgstr "" +msgstr "첫 번째 기여의 예" #: ../../source/contributor-tutorial-contribute-on-github.rst:257 msgid "Problem" -msgstr "" +msgstr "문제" #: ../../source/contributor-tutorial-contribute-on-github.rst:259 msgid "" "For our documentation, we've started to use the `Diàtaxis framework " "`_." -msgstr "" +msgstr "저희 문서에는 'Diàtaxis 프레임워크 `_'를 사용하기 " +"시작했습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:261 msgid "" "Our \"How to\" guides should have titles that continue the sentence \"How" " to …\", for example, \"How to upgrade to Flower 1.0\"." msgstr "" +"'How to' 가이드의 제목은 \"How to …\"라는 문장을 이어가는 제목이어야 " +"합니다(예: \"How to upgrade to Flower 1.0\")." #: ../../source/contributor-tutorial-contribute-on-github.rst:263 msgid "" "Most of our guides do not follow this new format yet, and changing their " "title is (unfortunately) more involved than one might think." -msgstr "" +msgstr "대부분의 가이드는 아직 이 새로운 형식을 따르지 않으며, 안타깝게도 제목을 " +"변경하는 작업은 생각보다 복잡합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:265 msgid "" "This issue is about changing the title of a doc from present continuous " "to present simple." -msgstr "" +msgstr "이번 이슈는 문서 제목을 현재 연속형에서 현재 단순형으로 변경하는 것에 관한 " +"것입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:267 msgid "" "Let's take the example of \"Saving Progress\" which we changed to \"Save " "Progress\". Does this pass our check?" msgstr "" +"\"How to saving progress\"을 \"How to save progress\"으로 변경한 예를 들어 " +"보겠습니다. 이것이 우리의 점검을 통과했나요?" #: ../../source/contributor-tutorial-contribute-on-github.rst:269 msgid "Before: \"How to saving progress\" ❌" -msgstr "" +msgstr "Before: \"How to saving progress\" ❌" #: ../../source/contributor-tutorial-contribute-on-github.rst:271 msgid "After: \"How to save progress\" ✅" -msgstr "" +msgstr "After: \"How to save progress\" ✅" #: ../../source/contributor-tutorial-contribute-on-github.rst:274 msgid "Solution" -msgstr "" +msgstr "해결법" #: ../../source/contributor-tutorial-contribute-on-github.rst:276 msgid "" "This is a tiny change, but it'll allow us to test your end-to-end setup. " "After cloning and setting up the Flower repo, here's what you should do:" msgstr "" +"이것은 사소한 변경이지만 end-to-end 설정을 테스트할 수 있습니다. Flower " +"포지토리를 복제하고 설정한 후에는 다음과 같이 하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:278 msgid "Find the source file in ``doc/source``" -msgstr "" +msgstr "``doc/source``에서 소스 파일을 찾습니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:279 msgid "" "Make the change in the ``.rst`` file (beware, the dashes under the title " "should be the same length as the title itself)" -msgstr "" +msgstr "``.rst`` 파일에서 변경합니다(제목 아래의 대시는 제목 자체의 길이와 같아야 " +"합니다)" #: ../../source/contributor-tutorial-contribute-on-github.rst:280 msgid "" "Build the docs and `check the result `_" msgstr "" +"문서를 빌드하고 '결과 확인 `_'합니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:283 msgid "Rename file" -msgstr "" +msgstr "파일 이름 바꾸기" #: ../../source/contributor-tutorial-contribute-on-github.rst:285 msgid "" @@ -1762,28 +1908,33 @@ msgid "" "is **very important** to avoid that, breaking links can harm our search " "engine ranking." msgstr "" +"파일 이름에 여전히 이전 문구가 반영되어 있는 것을 보셨을 것입니다. 파일만 " +"변경하면 파일에 대한 기존 링크가 모두 끊어지는데, 링크를 끊으면 검색 엔진 " +"순위에 영향을 줄 수 있으므로 이를 방지하는 것이 **매우 중요**합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:288 msgid "Here's how to change the file name:" -msgstr "" +msgstr "파일 이름을 변경하는 방법은 다음과 같습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:290 msgid "Change the file name to ``save-progress.rst``" -msgstr "" +msgstr "파일 이름을 ``save-progress.rst``로 변경합니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:291 msgid "Add a redirect rule to ``doc/source/conf.py``" -msgstr "" +msgstr "'doc/source/conf.py'에 리디렉션 규칙을 추가합니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:293 msgid "" "This will cause a redirect from ``saving-progress.html`` to ``save-" "progress.html``, old links will continue to work." msgstr "" +"이렇게 하면 ``saving-progress.html``에서 ``save-progress.html``로 " +"리디렉션되며, 이전 링크는 계속 작동합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:296 msgid "Apply changes in the index file" -msgstr "" +msgstr "인덱스 파일에 변경 사항 적용" #: ../../source/contributor-tutorial-contribute-on-github.rst:298 msgid "" @@ -1791,38 +1942,42 @@ msgid "" "update the ``index.rst`` file as well. This is where we define the whole " "arborescence of the navbar." msgstr "" +"횡방향 내비게이션 바가 제대로 작동하려면 ``index.rst`` 파일도 업데이트하는 " +"것이 매우 중요합니다. 이 파일은 탐색 모음의 전체 배열을 정의하는 곳입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:301 msgid "Find and modify the file name in ``index.rst``" -msgstr "" +msgstr "``index.rst``에서 파일 이름을 찾아 수정합니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:304 msgid "Open PR" -msgstr "" +msgstr "PR 열기" #: ../../source/contributor-tutorial-contribute-on-github.rst:306 msgid "" "Commit the changes (commit messages are always imperative: \"Do " "something\", in this case \"Change …\")" msgstr "" +"변경 사항을 커밋합니다(커밋 메시지는 항상 필수 메시지입니다:\"Do something\"(" +"이 경우 는 \"Change …\" )" #: ../../source/contributor-tutorial-contribute-on-github.rst:307 msgid "Push the changes to your fork" -msgstr "" +msgstr "변경 사항을 포크에 푸시합니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:308 msgid "" "Open a PR (as shown above) with title ``docs(framework) Update how-to " "guide title``" -msgstr "" +msgstr "``docs(framework) Update how-to guide title`` 제목으로 PR(위와 같이)을 엽니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:309 msgid "Wait for it to be approved!" -msgstr "" +msgstr "승인될 때까지 기다리세요!" #: ../../source/contributor-tutorial-contribute-on-github.rst:310 msgid "Congrats! 🥳 You're now officially a Flower contributor!" -msgstr "" +msgstr "축하합니다! 이제 공식적으로 Flower 기여자가 되셨습니다!" #: ../../source/contributor-tutorial-contribute-on-github.rst:314 #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:548 @@ -1831,13 +1986,13 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:713 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:367 msgid "Next steps" -msgstr "" +msgstr "다음 단계" #: ../../source/contributor-tutorial-contribute-on-github.rst:316 msgid "" "Once you have made your first PR, and want to contribute more, be sure to" " check out the following :" -msgstr "" +msgstr "첫 번째 PR을 작성하고 더 많은 기여를 하고 싶다면 다음 을 확인하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:318 msgid "" @@ -1845,25 +2000,28 @@ msgid "" "contributions>`, where you should particularly look into the " ":code:`baselines` contributions." msgstr "" +":doc:`훌륭한 첫 번째 기여 `, 특히 " +":code:`baselines` 기여를 살펴봐야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:322 #: ../../source/fed/0000-20200102-fed-template.md:60 msgid "Appendix" -msgstr "" +msgstr "부록" #: ../../source/contributor-tutorial-contribute-on-github.rst:327 msgid "PR title format" -msgstr "" +msgstr "PR 제목 형식" #: ../../source/contributor-tutorial-contribute-on-github.rst:329 msgid "We enforce the following PR title format:" -msgstr "" +msgstr "다음과 같은 PR 제목 형식을 적용합니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:335 msgid "" "(or ``(:skip) `` to ignore the PR in the " "changelog)" -msgstr "" +msgstr "(또는 ``(:skip) ``를 사용하면 변경 로그에서 PR을 " +"무시합니다.)" #: ../../source/contributor-tutorial-contribute-on-github.rst:337 msgid "" @@ -1873,77 +2031,81 @@ msgid "" "':skip' flag to be used}``, and ```` starts with a capitalised " "verb in the imperative mood." msgstr "" +"여기서 ````은 ``{ci, fix, feat, docs, refactor, break}``, ````" +"는 ``{framework, baselines, datasets, examples, or '*' ':skip' 플래그를 " +"사용해야 하는 여러 프로젝트를 수정하는 경우}``로 입력해야 하며, ````" +"는 대문자로 시작해야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:341 msgid "Valid examples:" -msgstr "" +msgstr "유효한 예시입니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:343 msgid "``feat(framework) Add flwr build CLI command``" -msgstr "" +msgstr "``feat(framework) Add flwr build CLI command``" #: ../../source/contributor-tutorial-contribute-on-github.rst:344 msgid "``refactor(examples:skip) Improve quickstart-pytorch logging``" -msgstr "" +msgstr "``refactor(examples:skip) Improve quickstart-pytorch logging``" #: ../../source/contributor-tutorial-contribute-on-github.rst:345 msgid "``ci(*:skip) Enforce PR title format``" -msgstr "" +msgstr "``ci(*:skip) Enforce PR title format``" #: ../../source/contributor-tutorial-contribute-on-github.rst:347 msgid "Invalid examples:" -msgstr "" +msgstr "잘못된 예제입니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:349 msgid "``feat(framework): Add flwr build CLI command`` (extra ``:``)" -msgstr "" +msgstr "``feat(framework): Add flwr build CLI command`` ( ``:``제외)" #: ../../source/contributor-tutorial-contribute-on-github.rst:350 msgid "" "``feat(*) Add flwr build CLI command`` (missing ``skip`` flag along with " "``*``)" -msgstr "" +msgstr "``feat(*) Add flwr build CLI command`` (``skip`` flag와 함께 ``*``누락)" #: ../../source/contributor-tutorial-contribute-on-github.rst:351 msgid "``feat(skip) Add flwr build CLI command`` (missing ````)" -msgstr "" +msgstr "``feat(skip) Add flwr build CLI command`` (````누락)" #: ../../source/contributor-tutorial-contribute-on-github.rst:352 msgid "``feat(framework) add flwr build CLI command`` (non capitalised verb)" -msgstr "" +msgstr "``feat(framework) add flwr build CLI command`` (대문자로 표기되지 않은 동사)" #: ../../source/contributor-tutorial-contribute-on-github.rst:353 msgid "``feat(framework) Add flwr build CLI command.`` (dot at the end)" -msgstr "" +msgstr "``feat(framework) Add flwr build CLI command.`` (끝에 마침표)" #: ../../source/contributor-tutorial-contribute-on-github.rst:354 msgid "``Add flwr build CLI command.`` (missing ``()``)" -msgstr "" +msgstr "``Add flwr build CLI command.`` ( ``()``누락)" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:2 msgid "Get started as a contributor" -msgstr "" +msgstr "기여자로 시작하기" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 #: ../../source/how-to-run-flower-using-docker.rst:153 msgid "Prerequisites" -msgstr "" +msgstr "전제 조건" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:7 msgid "`Python 3.8 `_ or above" -msgstr "" +msgstr "Python 3.8 `_ 이상" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:8 msgid "`Poetry 1.3 `_ or above" -msgstr "" +msgstr "`Poetry 1.3 `_ _ 이상" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:9 msgid "(Optional) `pyenv `_" -msgstr "" +msgstr "(선택 사항) `pyenv `_" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:10 msgid "(Optional) `pyenv-virtualenv `_" -msgstr "" +msgstr "(선택 사항) `pyenv-virtualenv `_" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:12 msgid "" @@ -1951,54 +2113,62 @@ msgid "" "development tools (the ones which support it). Poetry is a build tool " "which supports `PEP 517 `_." msgstr "" +"Flower는 dependencies을 관리하고 개발 도구(이를 지원하는 도구)를 구성하기 " +"위해 :code:`pyproject.toml`을 사용합니다. Poetry는 `PEP 517 `_을 지원하는 빌드 도구입니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:18 msgid "Developer Machine Setup" -msgstr "" +msgstr "개발자 머신 설정" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:21 msgid "Preliminarities" -msgstr "" +msgstr "사전 준비" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:22 msgid "Some system-wide dependencies are needed." -msgstr "" +msgstr "일부 시스템 전체에 대한 dependencies이 필요합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:25 msgid "For macOS" -msgstr "" +msgstr "macOS의 경우" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:27 msgid "" "Install `homebrew `_. Don't forget the post-" "installation actions to add `brew` to your PATH." msgstr "" +"`homebrew `_를 설치합니다. 설치 후 `brew`를 PATH에 " +"추가하는 작업을 잊지 마세요." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:28 msgid "" "Install `xz` (to install different Python versions) and `pandoc` to build" " the docs::" -msgstr "" +msgstr "xz`(다른 Python 버전을 설치하려면)와 `pandoc`을 설치하여 문서를 빌드합니다::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:34 msgid "For Ubuntu" -msgstr "" +msgstr "Ubuntu의 경우" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:35 msgid "" "Ensure you system (Ubuntu 22.04+) is up-to-date, and you have all " "necessary packages::" -msgstr "" +msgstr "시스템(우분투 22.04 이상)이 최신 상태이고 필요한 패키지가 모두 설치되어 " +"있는지 확인하세요:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:44 msgid "Create Flower Dev Environment" -msgstr "" +msgstr "Flower 개발 환경 만들기" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:46 msgid "" "1. Clone the `Flower repository `_ from " "GitHub::" msgstr "" +"1. GitHub: 에서 ``Flower 레포지토리 `_를 " +"복제합니다::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:52 msgid "" @@ -2008,6 +2178,10 @@ msgid "" "environment, activate and skip to the last point where all packages are " "installed." msgstr "" +"Flower의 모든 것을 위한 파이썬 환경을 만들어 보겠습니다.:code:`pyenv`를 " +"사용하고자 하는 경우 사용할 수 있는 두 가지 편의 스크립트를 " +"제공합니다.:code:`pyenv`가 아닌 다른 것을 사용하려면 새 환경을 생성하고 " +"활성화한 후 모든 패키지가 설치된 마지막 지점으로 건너뛰세요." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:54 msgid "" @@ -2015,6 +2189,8 @@ msgid "" " install it, set it up, and create the virtual environment (with " ":code:`Python 3.8.17` by default)::" msgstr "" +":code:`pyenv`가 설치되어 있지 않은 경우 다음 스크립트를 사용하여 설치, 설정 " +"및 가상 환경을 생성합니다(기본적으로 :code:`Python 3.8.17` 사용):" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:58 msgid "" @@ -2022,16 +2198,21 @@ msgid "" "virtualenv` plugin), you can use the following convenience script (with " ":code:`Python 3.8.17` by default)::" msgstr "" +":code:`pyenv`가 이미 설치되어 있는 경우( :code:`pyenv-virtualenv` 플러그인과 " +"함께) 다음과 같은 편의 스크립트를 사용할 수 있습니다(기본적으로 코드:`Python " +"3.8.17` 사용):" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:62 msgid "" "3. Install the Flower package in development mode (think :code:`pip " "install -e`) along with all necessary dependencies::" msgstr "" +"3. 필요한 모든 dependencies와 함께 개발 모드에서 Flower 패키지를 " +"설치합니다(예:code:`pip install -e`)::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:69 msgid "Convenience Scripts" -msgstr "" +msgstr "편의 스크립트" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:71 msgid "" @@ -2040,26 +2221,29 @@ msgid "" ":code:`/dev` subdirectory for a full list. The following scripts are " "amongst the most important ones:" msgstr "" +"Flower 레포지토리에는 반복적인 개발 작업을 더 쉽고 오류를 줄이기 위한 여러 " +"가지 편의 스크립트가 포함되어 있습니다. 전체 목록은 :code:`/dev` 하위 " +"디렉터리를 참조하세요. 다음 스크립트는 가장 중요한 스크립트 중 하나입니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:77 msgid "Create/Delete Virtual Environment" -msgstr "" +msgstr "가상 환경 생성/삭제" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:85 msgid "Compile ProtoBuf Definitions" -msgstr "" +msgstr "ProtoBuf 정의 컴파일" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:92 msgid "Auto-Format Code" -msgstr "" +msgstr "자동 포맷 코드" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:99 msgid "Run Linters and Tests" -msgstr "" +msgstr "린터 및 테스트 실행" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:106 msgid "Add a pre-commit hook" -msgstr "" +msgstr "사전 커밋 훅 추가" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:108 msgid "" @@ -2068,26 +2252,30 @@ msgid "" "commit hook is configured to execute two primary operations: " "``./dev/format.sh`` and ``./dev/test.sh`` scripts." msgstr "" +"개발자는 `pre-commit `_ 라이브러리를 " +"사용하여 사전 커밋 훅을 워크플로에 통합할 수 있습니다. 사전 커밋 훅은 두 " +"가지 기본 작업을 실행하도록 구성됩니다:``./dev/format.sh`` 및 ``./dev/test." +"sh`` 스크립트." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:110 msgid "There are multiple ways developers can use this:" -msgstr "" +msgstr "개발자가 이것을 사용할 수 있는 여러가지 방법이 있습니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:112 msgid "Install the pre-commit hook to your local git directory by simply running:" -msgstr "" +msgstr "간단하게 실행하여 로컬 git 디렉터리에 사전 커밋 훅을 설치하세요:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:118 msgid "" "Each ``git commit`` will trigger the execution of formatting and " "linting/test scripts." -msgstr "" +msgstr "각 ``git 커밋``은 포맷 및 린팅/테스트 스크립트의 실행을 트리거합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:119 msgid "" "If in a hurry, bypass the hook using ``--no-verify`` with the ``git " "commit`` command. ::" -msgstr "" +msgstr "급한 경우 ``git commit`` 명령과 함께 `--no-verify``를 사용하여 훅을 넘기세요:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:124 msgid "" @@ -2095,16 +2283,19 @@ msgid "" "possible to execute a one-time check prior to committing changes by using" " the following command:" msgstr "" +"훅을 영구적으로 설치하지 않으려는 개발자의 경우 다음 명령을 사용하여 변경 " +"사항을 커밋하기 전에 일회성 검사를 실행할 수 있습니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:130 msgid "" "This executes the formatting and linting checks/tests on all the files " "without modifying the default behavior of ``git commit``." -msgstr "" +msgstr "이렇게 하면 ``git commit``의 기본 동작을 수정하지 않고 모든 파일에 대해 포맷 " +"및 린팅 검사/테스트를 실행합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:133 msgid "Run Github Actions (CI) locally" -msgstr "" +msgstr "로컬에서 Github Action(CI) 실행하기" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:135 msgid "" @@ -2113,32 +2304,38 @@ msgid "" "Please refer to the installation instructions under the linked repository" " and run the next command under Flower main cloned repository folder::" msgstr "" +"개발자는 `Act `_를 사용하여 로컬 환경에서 " +"전체 Github Actions 워크플로우 세트를 실행할 수 있습니다. 링크된 레포지토리 " +"아래의 설치 지침을 참조하여 Flower 메인 클론 레포지토리 폴더 아래에서 다음 " +"명령을 실행하세요::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:142 msgid "" "The Flower default workflow would run by setting up the required Docker " "machines underneath." -msgstr "" +msgstr "Flower 기본 워크플로우는 아래에 필요한 Docker 머신을 설정하여 실행합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:147 msgid "Build Release" -msgstr "" +msgstr "릴리즈 빌드" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:149 msgid "" "Flower uses Poetry to build releases. The necessary command is wrapped in" " a simple script::" -msgstr "" +msgstr "Flower는 Poetry를 사용하여 릴리즈를 빌드합니다. 필요한 명령은 간단한 " +"스크립트로 래핑됩니다::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:154 msgid "" "The resulting :code:`.whl` and :code:`.tar.gz` releases will be stored in" " the :code:`/dist` subdirectory." -msgstr "" +msgstr "결과물인 :code:`.whl` 및 :code:`.tar.gz` 릴리즈는 :code:`/dist` 하위 " +"디렉터리에 저장됩니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:159 msgid "Build Documentation" -msgstr "" +msgstr "문서 빌드" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:161 msgid "" @@ -2146,14 +2343,17 @@ msgid "" "There's no convenience script to re-build the documentation yet, but it's" " pretty easy::" msgstr "" +"Flower의 문서는 `Sphinx `_를 사용합니다. 아직 " +"문서를 다시 작성할 수 있는 편리한 스크립트는 없지만 다음과 같이 쉽게 작성할 " +"수 있습니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:167 msgid "This will generate HTML documentation in ``doc/build/html``." -msgstr "" +msgstr "그러면 ``doc/build/html``에 HTML 문서가 생성됩니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:2 msgid "Example: FedBN in PyTorch - From Centralized To Federated" -msgstr "" +msgstr "예시: PyTorch에서 FedBN - Centralize에서 Federated으로" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:4 msgid "" @@ -2166,11 +2366,18 @@ msgid "" "PyTorch - From Centralized To Federated `." msgstr "" +"이 튜토리얼에서는 non-iid data를 위해 설계된 federated 훈련 전략인 `FedBN " +"`_으로 기존 머신러닝 워크로드의 federated " +"버전을 구축하기 위해 Flower를 사용하는 방법을 보여드립니다. 우리는 PyTorch를 " +"사용하여 CIFAR-10 데이터 세트에서 컨볼루션 신경망(일괄 정규화 레이어 포함)을 " +"훈련하고 있습니다. FedBN을 적용할 때, :doc:`예제: 파이토치 -Centralized에서 " +"Federated으로 ` 와 비교했을 " +"때 몇 가지 사항만 변경 하면 됩니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:9 #: ../../source/example-pytorch-from-centralized-to-federated.rst:10 msgid "Centralized Training" -msgstr "" +msgstr "Centralized 훈련" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:10 msgid "" @@ -2179,17 +2386,21 @@ msgid "" "thing to do is modifying the file called :code:`cifar.py`, revised part " "is shown below:" msgstr "" +"모든 파일은 :doc:`예제: 파이토치 - Centralized에서 Federated으로 `를 기반으로 수정합니다. :code:`cifar." +"py`라는 파일을 수정하기만 하면 되며, 수정된 부분은 아래와 같습니다:" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:13 msgid "" "The model architecture defined in class Net() is added with Batch " "Normalization layers accordingly." -msgstr "" +msgstr "Net() 클래스에 정의된 모델 아키텍처는 그에 따라 배치 정규화 레이어가 " +"추가됩니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:41 #: ../../source/example-pytorch-from-centralized-to-federated.rst:157 msgid "You can now run your machine learning workload:" -msgstr "" +msgstr "이제 머신 러닝 워크로드를 실행할 수 있습니다:" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:47 msgid "" @@ -2198,11 +2409,14 @@ msgid "" "federated learning system within FedBN, the system consists of one server" " and two clients." msgstr "" +"지금까지는 파이토치를 사용해 본 적이 있다면 상당히 익숙하게 보일 것입니다. " +"다음 단계로 넘어가서 우리가 구축한 것을 사용하여 FedBN 내에서 하나의 서버와 " +"두 개의 클라이언트로 구성된 federated 학습 시스템을 만들어 보겠습니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:51 #: ../../source/example-pytorch-from-centralized-to-federated.rst:167 msgid "Federated Training" -msgstr "" +msgstr "Federated 훈련" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:53 msgid "" @@ -2213,12 +2427,21 @@ msgid "" ":doc:`Example: PyTorch - From Centralized To Federated `. first." msgstr "" +":doc:`예제: 파이토치 - Centralized에서 Federated으로 `를 읽었다면, 다음 부분은 쉽게 따라할 수 있으며 " +":code:`client.py`의 :code:`get_parameters`와 :code:`set_parameters` 함수만 " +"수정해야 합니다. 그렇지 않은 경우 :doc:`예제: 파이토치 - Centralized에서 " +"Federated으로 `를 먼저 " +"읽어보세요." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:56 msgid "" "Our example consists of one *server* and two *clients*. In FedBN, " ":code:`server.py` keeps unchanged, we can start the server directly." msgstr "" +"이 예제는 하나의 *서버*와 두 개의 *클라이언트*로 구성됩니다. FedBN에서 " +":code:`server.py`는 변경되지 않고 그대로 유지되므로 서버를 바로 시작할 수 " +"있습니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:62 msgid "" @@ -2227,10 +2450,14 @@ msgid "" "we will exclude batch normalization parameters from model parameter list " "when sending to or receiving from the server." msgstr "" +"마지막으로, :code:`client.py`에서 :code:`get_parameters` 및 " +":code:`set_parameters`를 변경하여 *client* 로직을 수정할 것입니다. 서버로 " +"보내거나 서버에서 받을 때 모델 파라미터 목록에서 배치 정규화 파라미터를 " +"제외할 수 있습니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:85 msgid "Now, you can now open two additional terminal windows and run" -msgstr "" +msgstr "이제 두 개의 터미널 창을 추가로 열고 다음을 실행할 수 있습니다" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:91 msgid "" @@ -2238,13 +2465,16 @@ msgid "" "so) and see your (previously centralized) PyTorch project run federated " "learning with FedBN strategy across two clients. Congratulations!" msgstr "" +"를 입력하고(클릭하기 전에 서버가 계속 실행 중인지 확인하세요), (이전에 " +"centralized된) PyTorch 프로젝트가 두 클라이언트에서 FedBN으로 federated " +"학습을 실행하는 것을 확인합니다. 축하합니다!" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:94 #: ../../source/example-jax-from-centralized-to-federated.rst:277 #: ../../source/example-pytorch-from-centralized-to-federated.rst:310 #: ../../source/tutorial-quickstart-jax.rst:283 msgid "Next Steps" -msgstr "" +msgstr "다음 단계" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:96 msgid "" @@ -2256,10 +2486,16 @@ msgid "" "using different subsets of CIFAR-10 on each client? How about adding more" " clients?" msgstr "" +"이 예제의 전체 소스 코드는 '여기 `_'에서 확인할 수 있습니다. " +"물론 이 예제는 두 클라이언트가 완전히 동일한 데이터 세트를 로드하기 때문에 " +"다소 지나치게 단순화되어 있으며, 이는 현실적이지 않습니다. 이제 이 주제를 더 " +"자세히 살펴볼 준비가 되셨습니다. 각 클라이언트에서 서로 다른 CIFAR-10의 하위 " +"집합을 사용해 보는 것은 어떨까요? 클라이언트를 더 추가하는 것은 어떨까요?" #: ../../source/example-jax-from-centralized-to-federated.rst:2 msgid "Example: JAX - Run JAX Federated" -msgstr "" +msgstr "예시: JAX - JAX Federated 실행" #: ../../source/example-jax-from-centralized-to-federated.rst:4 #: ../../source/tutorial-quickstart-jax.rst:10 @@ -2275,6 +2511,15 @@ msgid "" " tutorial`. Then, we build upon the centralized training code to run the " "training in a federated fashion." msgstr "" +"이 튜토리얼에서는 Flower를 사용하여 기존 JAX 워크로드의 federated 버전을 " +"구축하는 방법을 보여드립니다. JAX를 사용해 scikit-learn 데이터 세트에서 선형 " +"회귀 모델을 훈련하고 있습니다. 예제는 '파이토치 - Centralized에서 " +"Federated으로 `_ 워크스루와 유사하게 구성하겠습니다. 먼저, `" +"JAX를 사용한 선형 회귀 `_ 튜토리얼`을 기반으로 centralized 학습 접근 " +"방식을 구축합니다. 그런 다음 centralized 트레이닝 코드를 기반으로 federated " +"방식으로 트레이닝을 실행합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:10 #: ../../source/tutorial-quickstart-jax.rst:16 @@ -2282,11 +2527,13 @@ msgid "" "Before we start building our JAX example, we need install the packages " ":code:`jax`, :code:`jaxlib`, :code:`scikit-learn`, and :code:`flwr`:" msgstr "" +"JAX 예제 빌드를 시작하기 전에 :code:`jax`, :code:`jaxlib`, :code:`scikit-" +"learn`, :code:`flwr` 패키지를 설치해야 합니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:18 #: ../../source/tutorial-quickstart-jax.rst:24 msgid "Linear Regression with JAX" -msgstr "" +msgstr "JAX를 사용한 선형 회귀" #: ../../source/example-jax-from-centralized-to-federated.rst:20 #: ../../source/tutorial-quickstart-jax.rst:26 @@ -2296,6 +2543,9 @@ msgid "" "explanation of what's going on then have a look at the official `JAX " "documentation `_." msgstr "" +"먼저 :code:`선형 회귀` 모델을 기반으로 하는 centralized 훈련 코드에 대한 " +"간략한 설명부터 시작하겠습니다. 더 자세한 설명을 원하시면 공식 `JAX 문서 " +"`_를 참조하세요." #: ../../source/example-jax-from-centralized-to-federated.rst:23 #: ../../source/tutorial-quickstart-jax.rst:29 @@ -2309,20 +2559,28 @@ msgid "" "not yet import the :code:`flwr` package for federated learning. This will" " be done later." msgstr "" +"전통적인(centralized) 선형 회귀 훈련에 필요한 모든 구성 요소가 포함된 " +":code:`jax_training.py`라는 새 파일을 생성해 보겠습니다. 먼저, JAX 패키지인 " +":code:`jax`와 :code:`jaxlib`를 가져와야 합니다. 또한 데이터 세트에 " +":code:`make_regression`을 사용하고 데이터 세트를 학습 및 테스트 세트로 " +"분할하기 위해 :code:`train_test_split`을 사용하므로 :code:`sklearn`을 " +"가져와야 합니다. 연합 학습을 위해 아직 :code:`flwr` 패키지를 가져오지 않은 " +"것을 볼 수 있습니다. 이 작업은 나중에 수행됩니다." #: ../../source/example-jax-from-centralized-to-federated.rst:37 #: ../../source/tutorial-quickstart-jax.rst:43 msgid "" "The :code:`load_data()` function loads the mentioned training and test " "sets." -msgstr "" +msgstr "code:`load_data()` 함수는 앞서 언급한 트레이닝 및 테스트 세트를 로드합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:47 #: ../../source/tutorial-quickstart-jax.rst:53 msgid "" "The model architecture (a very simple :code:`Linear Regression` model) is" " defined in :code:`load_model()`." -msgstr "" +msgstr "모델 아키텍처(매우 간단한 :code:`선형 회귀` 모델)는 :code:`load_model()`에 " +"정의되어 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:59 #: ../../source/tutorial-quickstart-jax.rst:65 @@ -2333,6 +2591,10 @@ msgid "" " is separate since JAX takes derivatives with a :code:`grad()` function " "(defined in the :code:`main()` function and called in :code:`train()`)." msgstr "" +"이제 훈련 집합을 반복하고 각 훈련 예제 배치에 대해 손실을 측정하는(함수 " +":code:`loss_fn()`) 훈련(함수 :code:`train()`)을 정의해야 합니다. JAX는 " +":code:`grad()` 함수(:code:`main()` 함수에 정의되고 :code:`train()`에서 " +"호출됨)로 파생물을 취하므로 손실 함수는 분리되어 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:77 #: ../../source/tutorial-quickstart-jax.rst:83 @@ -2341,6 +2603,8 @@ msgid "" ":code:`evaluation()`. The function takes all test examples and measures " "the loss of the linear regression model." msgstr "" +"모델의 평가는 :code:`evaluation()` 함수에 정의되어 있습니다. 이 함수는 모든 " +"테스트 예제를 가져와 선형 회귀 모델의 손실을 측정합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:88 #: ../../source/tutorial-quickstart-jax.rst:94 @@ -2350,11 +2614,14 @@ msgid "" "As already mentioned, the :code:`jax.grad()` function is defined in " ":code:`main()` and passed to :code:`train()`." msgstr "" +"데이터 로딩, 모델 아키텍처, 훈련 및 평가를 정의했으므로 이제 모든 것을 " +"종합하여 JAX를 사용 모델을 훈련할 수 있습니다. 이미 언급했듯이 :code:`jax." +"grad()` 함수는 :code:`main()`에 정의되어 :code:`train()`에 전달됩니다." #: ../../source/example-jax-from-centralized-to-federated.rst:105 #: ../../source/tutorial-quickstart-jax.rst:111 msgid "You can now run your (centralized) JAX linear regression workload:" -msgstr "" +msgstr "이제 (centralized) JAX 선형 회귀 워크로드를 실행할 수 있습니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:111 #: ../../source/tutorial-quickstart-jax.rst:117 @@ -2363,11 +2630,14 @@ msgid "" "Let's take the next step and use what we've built to create a simple " "federated learning system consisting of one server and two clients." msgstr "" +"지금까지는 JAX를 사용해 본 적이 있다면 이 모든 것이 상당히 익숙해 보일 " +"것입니다. 다음 단계로 넘어가서 우리가 구축한 것을 사용하여 하나의 서버와 두 " +"개의 클라이언트로 구성된 간단한 연합 학습 시스템을 만들어 보겠습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:115 #: ../../source/tutorial-quickstart-jax.rst:121 msgid "JAX meets Flower" -msgstr "" +msgstr "JAX와 Flower의 만남" #: ../../source/example-jax-from-centralized-to-federated.rst:117 #: ../../source/tutorial-quickstart-jax.rst:123 @@ -2381,6 +2651,13 @@ msgid "" "parameter updates. This describes one round of the federated learning " "process, and we repeat this for multiple rounds." msgstr "" +"기존 워크로드를 federating하는 개념은 항상 동일하고 이해하기 쉽습니다. 서버*" +"를 시작한 다음 *서버*에 연결된 *클라이언트*에 대해 :code:`jax_training.py`의 " +"코드를 사용해야 합니다. *서버*는 모델 파라미터를 클라이언트로 전송합니다. " +"클라이언트는 학습을 실행하고 파라미터를 업데이트합니다. 업데이트된 " +"파라미터는 *서버*로 다시 전송되며, 수신된 모든 파라미터 업데이트의 평균을 " +"구합니다. 이는 federated 학습 프로세스의 한 라운드를 설명하며, 이 과정을 " +"여러 라운드에 걸쳐 반복합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:123 #: ../../source/example-pytorch-from-centralized-to-federated.rst:181 @@ -2391,12 +2668,16 @@ msgid "" ":code:`flwr`. Next, we use the :code:`start_server` function to start a " "server and tell it to perform three rounds of federated learning." msgstr "" +"이 예제는 하나의 *서버*와 두 개의 *클라이언트*로 구성됩니다. 먼저 " +":code:`server.py`를 설정해 보겠습니다. *server*는 Flower 패키지 :code:`flwr`" +"를 가져와야 합니다. 다음으로, :code:`start_server` 함수를 사용하여 서버를 " +"시작하고 세 차례의 federated 학습을 수행하도록 지시합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:133 #: ../../source/example-pytorch-from-centralized-to-federated.rst:191 #: ../../source/tutorial-quickstart-jax.rst:139 msgid "We can already start the *server*:" -msgstr "" +msgstr "이미 *서버*를 시작할 수 있습니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:139 #: ../../source/tutorial-quickstart-jax.rst:145 @@ -2406,6 +2687,10 @@ msgid "" " *client* needs to import :code:`flwr`, but also :code:`jax` and " ":code:`jaxlib` to update the parameters on our JAX model:" msgstr "" +"마지막으로, :code:`client.py`에서 *client* 로직을 정의하고 " +":code:`jax_training.py`에서 이전에 정의한 JAX 교육을 기반으로 빌드합니다. " +"*클라이언트*는 :code:`flwr`을 가져와야 하며, JAX 모델의 파라미터를 " +"업데이트하기 위해 :code:`jax` 및 :code:`jaxlib`도 가져와야 합니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:154 #: ../../source/tutorial-quickstart-jax.rst:160 @@ -2421,11 +2706,20 @@ msgid "" "parameters, one method for training the model, and one method for testing" " the model:" msgstr "" +"Flower *클라이언트*를 구현한다는 것은 기본적으로 :code:`flwr.client.Client` " +"또는 :code:`flwr.client.NumPyClient`의 서브클래스를 구현하는 것을 " +"의미합니다. 구현은 :code:`flwr.client.NumPyClient`를 기반으로 하며, 이를 " +":code:`FlowerClient`라고 부를 것입니다. :code:`NumPyClient`는 필요한 일부 " +"보일러플레이를 피할 수 있기 때문에 NumPy 상호 운용성이 좋은 프레임워크(예: " +"JAX)를 사용하는 경우 :code:`Client`보다 구현하기가 약간 더 쉽습니다. " +"code:`FlowerClient`는 모델 매개변수를 가져오거나 설정하는 메서드 2개, 모델 " +"학습을 위한 메서드 1개, 모델 테스트를 위한 메서드 1개 등 총 4개의 메서드를 " +"구현해야 합니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:161 #: ../../source/tutorial-quickstart-jax.rst:167 msgid ":code:`set_parameters (optional)`" -msgstr "" +msgstr ":code:`set_parameters (선택사항)`" #: ../../source/example-jax-from-centralized-to-federated.rst:160 #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 @@ -2433,12 +2727,12 @@ msgstr "" msgid "" "set the model parameters on the local model that are received from the " "server" -msgstr "" +msgstr "서버에서 수신한 로컬 모델의 모델 파라미터를 설정합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:161 #: ../../source/tutorial-quickstart-jax.rst:167 msgid "transform parameters to NumPy :code:`ndarray`'s" -msgstr "" +msgstr "매개 변수를 NumPy :code:`ndarray`로 변환" #: ../../source/example-jax-from-centralized-to-federated.rst:162 #: ../../source/example-pytorch-from-centralized-to-federated.rst:220 @@ -2446,7 +2740,8 @@ msgstr "" msgid "" "loop over the list of model parameters received as NumPy " ":code:`ndarray`'s (think list of neural network layers)" -msgstr "" +msgstr "(신경망 레이어 목록으로 생각하면 됩니다) NumPy :code:`ndarray`로 받은 모델 " +"파라미터 목록에 대해 반복합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:163 #: ../../source/example-pytorch-from-centralized-to-federated.rst:221 @@ -2454,7 +2749,7 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:155 #: ../../source/tutorial-quickstart-scikitlearn.rst:118 msgid ":code:`get_parameters`" -msgstr "" +msgstr ":code:`get_parameters`" #: ../../source/example-jax-from-centralized-to-federated.rst:164 #: ../../source/example-pytorch-from-centralized-to-federated.rst:222 @@ -2463,6 +2758,8 @@ msgid "" "get the model parameters and return them as a list of NumPy " ":code:`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" msgstr "" +"모델 매개변수를 가져와서 NumPy :code:`ndarray`의 목록으로 반환합니다(이는 " +":code:`flwr.client.NumPyClient`가 기대하는 바와 같습니다)" #: ../../source/example-jax-from-centralized-to-federated.rst:167 #: ../../source/example-pytorch-from-centralized-to-federated.rst:225 @@ -2470,7 +2767,7 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:161 #: ../../source/tutorial-quickstart-scikitlearn.rst:125 msgid ":code:`fit`" -msgstr "" +msgstr ":code:`fit`" #: ../../source/example-jax-from-centralized-to-federated.rst:166 #: ../../source/example-jax-from-centralized-to-federated.rst:170 @@ -2481,18 +2778,18 @@ msgstr "" msgid "" "update the parameters of the local model with the parameters received " "from the server" -msgstr "" +msgstr "서버에서 받은 파라미터로 로컬 모델의 파라미터를 업데이트합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:167 #: ../../source/example-pytorch-from-centralized-to-federated.rst:225 #: ../../source/tutorial-quickstart-jax.rst:173 msgid "train the model on the local training set" -msgstr "" +msgstr "로컬 훈련 세트에서 모델을 훈련합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:168 #: ../../source/tutorial-quickstart-jax.rst:174 msgid "get the updated local model parameters and return them to the server" -msgstr "" +msgstr "업데이트된 로컬 모델 파라미터를 가져와 서버로 반환합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:172 #: ../../source/example-pytorch-from-centralized-to-federated.rst:230 @@ -2500,18 +2797,18 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:164 #: ../../source/tutorial-quickstart-scikitlearn.rst:128 msgid ":code:`evaluate`" -msgstr "" +msgstr ":code:`evaluate`" #: ../../source/example-jax-from-centralized-to-federated.rst:171 #: ../../source/example-pytorch-from-centralized-to-federated.rst:229 #: ../../source/tutorial-quickstart-jax.rst:177 msgid "evaluate the updated model on the local test set" -msgstr "" +msgstr "로컬 테스트 세트에서 업데이트된 모델을 평가합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:172 #: ../../source/tutorial-quickstart-jax.rst:178 msgid "return the local loss to the server" -msgstr "" +msgstr "로컬 손실을 서버로 반환합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:174 #: ../../source/tutorial-quickstart-jax.rst:180 @@ -2520,6 +2817,8 @@ msgid "" ":code:`DeviceArray` to :code:`NumPy ndarray` to make them compatible with" " `NumPyClient`." msgstr "" +"어려운 부분은 JAX 모델 매개변수를 :code:`DeviceArray`에서 :code:`NumPy " +"ndarray`로 변환하여 `NumPyClient`와 호환되도록 하는 것입니다." #: ../../source/example-jax-from-centralized-to-federated.rst:176 #: ../../source/tutorial-quickstart-jax.rst:182 @@ -2532,11 +2831,17 @@ msgid "" "annotations to give you a better understanding of the data types that get" " passed around." msgstr "" +"두 개의 :code:`NumPyClient` 메서드인 :code:`fit`과 :code:`evaluate`는 이전에 " +":code:`jax_training.py`에 정의된 함수 :code:`train()`과 :code:`evaluate()`를 " +"사용합니다. 따라서 여기서 우리가 실제로 하는 일은 이미 정의된 함수 중 훈련과 " +"평가를 위해 호출할 함수를 :code:`NumPyClient` 서브클래스를 통해 Flower에게 " +"알려주는 것입니다. 전달되는 데이터 유형을 더 잘 이해할 수 있도록 유형 type " +"annotation을 포함했습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:245 #: ../../source/tutorial-quickstart-jax.rst:251 msgid "Having defined the federation process, we can run it." -msgstr "" +msgstr "federation 프로세스를 정의했으면 이제 실행할 수 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:268 #: ../../source/example-pytorch-from-centralized-to-federated.rst:301 @@ -2551,6 +2856,8 @@ msgid "" "so) and see your JAX project run federated learning across two clients. " "Congratulations!" msgstr "" +"를 입력하고(그 전에 서버가 계속 실행 중인지 확인하세요) 두 클라이언트에서 " +"federated 학습을 실행하는 JAX 프로젝트를 확인합니다. 축하합니다!" #: ../../source/example-jax-from-centralized-to-federated.rst:279 #: ../../source/tutorial-quickstart-jax.rst:285 @@ -2560,6 +2867,10 @@ msgid "" "/quickstart-jax>`_. Our example is somewhat over-simplified because both " "clients load the same dataset." msgstr "" +"이 예제의 소스 코드는 시간이 지남에 따라 개선되었으며 여기에서 확인할 수 " +"있습니다: 'Quickstart JAX `_. 두 클라이언트가 동일한 데이터 세트를 로드하기 때문에 이 " +"예제는 다소 단순화되어 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:282 #: ../../source/tutorial-quickstart-jax.rst:288 @@ -2568,10 +2879,13 @@ msgid "" " sophisticated model or using a different dataset? How about adding more " "clients?" msgstr "" +"이제 이 주제를 더 자세히 살펴볼 준비가 되었습니다. 더 정교한 모델을 " +"사용하거나 다른 데이터 집합을 사용해 보는 것은 어떨까요? 클라이언트를 더 " +"추가하는 것은 어떨까요?" #: ../../source/example-pytorch-from-centralized-to-federated.rst:2 msgid "Example: PyTorch - From Centralized To Federated" -msgstr "" +msgstr "예제: 파이토치 - 중앙 Centralized에서 Federated으로" #: ../../source/example-pytorch-from-centralized-to-federated.rst:4 msgid "" @@ -2584,6 +2898,13 @@ msgid "" "tutorial. Then, we build upon the centralized training code to run the " "training in a federated fashion." msgstr "" +"이 튜토리얼에서는 Flower를 사용해 기존 머신 러닝 워크로드의 federated 버전을 " +"구축하는 방법을 보여드립니다. 여기서는 PyTorch를 사용해 CIFAR-10 데이터 " +"세트에서 컨볼루션 신경망을 훈련합니다. 먼저, 'PyTorch로 딥 러닝 " +"`_ " +"튜토리얼을 기반으로 centralized 학습 접근 방식을 사용하여 이 머신 러닝 " +"작업을 소개합니다. 그런 다음 centralized 훈련 코드를 기반으로 federated 방식 " +"훈련을 실행합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:12 msgid "" @@ -2592,6 +2913,10 @@ msgid "" "look at the official `PyTorch tutorial " "`_." msgstr "" +"centralized CNN 트레이닝 코드에 대한 간략한 설명부터 시작하겠습니다. 무슨 " +"일이 일어나고 있는지 더 자세히 설명하려면 공식 `PyTorch 튜토리얼 " +"`_을 " +"참조하세요." #: ../../source/example-pytorch-from-centralized-to-federated.rst:15 msgid "" @@ -2602,6 +2927,12 @@ msgid "" "federated learning. You can keep all these imports as they are even when " "we add the federated learning components at a later point." msgstr "" +"CIFAR-10에 대한 기존 (centralized) 교육에 필요한 모든 구성 요소가 포함된 " +":code:`cifar.py`라는 새 파일을 생성해 보겠습니다. 먼저, 필요한 모든 " +"패키지(예: :code:`torch` 및 :code:`torchvision`)를 가져와야 합니다. " +"federated 학습을 위한 패키지를 가져오지 않는 것을 확인 할 수 있습니. 나중에 " +"federated 학습 구성 요소를 추가할 때에도 이러한 모든 가져오기를 그대로 " +"유지할 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:32 msgid "" @@ -2609,12 +2940,17 @@ msgid "" "learning workload. The model architecture (a very simple Convolutional " "Neural Network) is defined in :code:`class Net()`." msgstr "" +"이미 언급했듯이 이 머신 러닝 워크로드에는 CIFAR-10 데이터 세트를 사용합니다. " +"모델 아키텍처(매우 간단한 컨볼루션 신경망)는 :code:`class Net()`에 정의되어 " +"있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:56 msgid "" "The :code:`load_data()` function loads the CIFAR-10 training and test " "sets. The :code:`transform` normalized the data after loading." msgstr "" +":code:`load_data()` 함수는 CIFAR-10 훈련 및 테스트 세트를 로드합니다. " +":code:`transform`은 로드 후 데이터를 정규화합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:74 msgid "" @@ -2622,6 +2958,9 @@ msgid "" " over the training set, measures the loss, backpropagates it, and then " "takes one optimizer step for each batch of training examples." msgstr "" +"이제 학습 집합을 반복하고, 손실을 측정하고, 이를 역전파한 다음 각 학습 예제 " +"배치에 대해 하나의 최적화 단계를 수행하는 학습(함수 :code:`train()`)을 " +"정의해야 합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:76 msgid "" @@ -2629,12 +2968,15 @@ msgid "" "The function loops over all test samples and measures the loss of the " "model based on the test dataset." msgstr "" +"모델 평가는 :code:`test()` 함수에 정의되어 있습니다. 이 함수는 모든 테스트 " +"샘플을 반복하고 테스트 데이터 세트에 따라 모델의 손실을 측정합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:136 msgid "" "Having defined the data loading, model architecture, training, and " "evaluation we can put everything together and train our CNN on CIFAR-10." -msgstr "" +msgstr "데이터 로딩, 모델 아키텍처, 훈련 및 평가를 정의했으면 모든 것을 종합하여 " +"CIFAR-10에서 CNN을 훈련할 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:163 msgid "" @@ -2643,6 +2985,9 @@ msgid "" "simple federated learning system consisting of one server and two " "clients." msgstr "" +"지금까지는 파이토치를 사용해 본 적이 있다면 상당히 익숙하게 보일 것입니다. " +"다음 단계로 넘어가서 구축한 것을 사용하여 하나의 서버와 두 개의 클라이언트로 " +"구성된 간단한 연합 학습 시스템을 만들어 보겠습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:169 msgid "" @@ -2654,12 +2999,19 @@ msgid "" "a federated fashion, then you'd have to change most of your code and set " "everything up from scratch. This can be a considerable effort." msgstr "" +"이전 섹션에서 설명한 간단한 머신 러닝 프로젝트는 단일 데이터 세트(CIFAR-10)" +"로 모델을 학습시키는데, 이를 centralized 학습이라고 부릅니다. 이전 섹션에서 " +"설명한 centralized 학습의 개념은 대부분 알고 계실 것이며, 많은 분들이 이전에 " +"사용해 보셨을 것입니다. 일반적으로 머신 러닝 워크로드를 federated 방식으로 " +"실행하려면 대부분의 코드를 변경하고 모든 것을 처음부터 다시 설정해야 합니다. " +"이는 상당한 노력이 필요할 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:173 msgid "" "However, with Flower you can evolve your pre-existing code into a " "federated learning setup without the need for a major rewrite." -msgstr "" +msgstr "하지만 Flower를 사용하면 대대적인 재작성 없이도 기존 코드를 연합 학습 " +"설정으로 발전시킬 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:175 msgid "" @@ -2671,6 +3023,12 @@ msgid "" "parameter updates. This describes one round of the federated learning " "process and we repeat this for multiple rounds." msgstr "" +"개념은 이해하기 쉽습니다. *서버*를 시작한 다음 *서버*에 연결된 *클라이언트*" +"에 대해 :code:`cifar.py`의 코드를 사용해야 합니다. *서버*는 모델 파라미터를 " +"클라이언트로 전송합니다. *클라이언트*는 학습을 실행하고 파라미터를 " +"업데이트합니다. 업데이트된 파라미터는 *서버*로 다시 전송되며, *서버*는 " +"수신된 모든 파라미터 업데이트의 평균을 구합니다. 이것은 federated 학습 " +"프로세스의 한 라운드를 설명하며 여러 라운드에 걸쳐 이 과정을 반복합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:197 msgid "" @@ -2679,6 +3037,10 @@ msgid "" "Our *client* needs to import :code:`flwr`, but also :code:`torch` to " "update the parameters on our PyTorch model:" msgstr "" +"마지막으로, :code:`client.py`에서 *client* 로직을 정의하고 :code:`cifar.py`" +"에서 이전에 정의한 centralized 학습을 기반으로 구축합니다. *클라이언트*는 " +":code:`flwr`을 가져와야 하며, PyTorch 모델의 파라미터를 업데이트하기 위해 " +":code:`torch`도 가져와야 합니다:" #: ../../source/example-pytorch-from-centralized-to-federated.rst:213 msgid "" @@ -2693,18 +3055,27 @@ msgid "" "getting/setting model parameters, one method for training the model, and " "one method for testing the model:" msgstr "" +"Flower *클라이언트*를 구현한다는 것은 기본적으로 :code:`flwr.client.Client` " +"또는 :code:`flwr.client.NumPyClient`의 서브클래스를 구현하는 것을 " +"의미합니다. 우리의 구현은 :code:`flwr.client.NumPyClient`를 기반으로 하며, " +"이를 :code:`CifarClient`라고 부를 것입니다. :code:`NumPyClient`는 파이토치나 " +"텐서플로우/Keras처럼 NumPy 상호운용성이 좋은 프레임워크를 사용하는 경우 " +"필요한 일부 보일러플레이트를 피하기 때문에 :code:`Client`보다 구현하기가 " +"조금 더 쉽습니다. code:`CifarClient`는 모델 파라미터를 가져오거나 설정하는 " +"메서드 2개, 모델 학습을 위한 메서드 1개, 모델 테스트를 위한 메서드 1개 등 네 " +"가지 메서드를 구현해야 합니다:" #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 msgid ":code:`set_parameters`" -msgstr "" +msgstr ":code:`set_parameters`" #: ../../source/example-pytorch-from-centralized-to-federated.rst:226 msgid "get the updated local model weights and return them to the server" -msgstr "" +msgstr "업데이트된 로컬 모델 가중치를 가져와 서버로 반환합니다" #: ../../source/example-pytorch-from-centralized-to-federated.rst:230 msgid "return the local loss and accuracy to the server" -msgstr "" +msgstr "로컬 손실 및 정확도를 서버에 반환합니다" #: ../../source/example-pytorch-from-centralized-to-federated.rst:232 msgid "" @@ -2716,6 +3087,12 @@ msgid "" "annotations to give you a better understanding of the data types that get" " passed around." msgstr "" +"두 개의 :code:`NumPyClient` 메서드인 :code:`fit`과 :code:`evaluate`는 이전에 " +":code:`cifar.py`에 정의된 함수인 :code:`train()`과 :code:`test()`를 " +"활용합니다. 따라서 여기서 실제로 하는 일은 :code:`NumPyClient` 서브클래스를 " +"통해 이미 정의된 함수 중 훈련과 평가를 위해 호출할 함수를 Flower에 알려주는 " +"것입니다. 전달되는 데이터 유형을 더 잘 이해할 수 있도록 type annotations을 " +"포함했습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:280 msgid "" @@ -2725,6 +3102,11 @@ msgid "" "with the function :code:`fl.client.start_client()` by pointing it at the " "same IP address we used in :code:`server.py`:" msgstr "" +"이제 모델과 데이터를 모두 로드하는 함수를 정의하고, :code:`CifarClient`를 " +"생성하고, 이 클라이언트를 시작하는 작업만 남았습니다. 코드:`cifar.py`를 " +"사용하여 데이터와 모델을 로드합니다. :code:`server.py`에서 사용한 것과 " +"동일한 IP 주소를 지정하여 :code:`fl.client.start_client()` 함수로 " +":code:`CifarClient`를 시작합니다:" #: ../../source/example-pytorch-from-centralized-to-federated.rst:307 msgid "" @@ -2732,6 +3114,9 @@ msgid "" "and see your (previously centralized) PyTorch project run federated " "learning across two clients. Congratulations!" msgstr "" +"를 입력하고(그 전에 서버가 실행 중인지 확인하세요) (이전에는centralized) " +"PyTorch 프로젝트가 두 클라이언트에서 federated 학습을 실행하는 것을 " +"확인합니다. 축하합니다!" #: ../../source/example-pytorch-from-centralized-to-federated.rst:312 msgid "" @@ -2743,12 +3128,19 @@ msgid "" " further. How about using different subsets of CIFAR-10 on each client? " "How about adding more clients?" msgstr "" +"이 예제의 전체 소스 코드: `파이토치: 중앙 Centralized에서 Federated으로 " +"(코드) `_. 물론 이 예제는 두 클라이언트가 완전히 동일한 " +"데이터 세트를 로드하기 때문에 다소 지나치게 단순화되어 있으며, 이는 " +"현실적이지 않습니다. 이제 이 주제를 더 자세히 살펴볼 준비가 되셨습니다. 각 " +"클라이언트에서 서로 다른 CIFAR-10의 하위 집합을 사용해 보는 것은 어떨까요? " +"클라이언트를 더 추가하는 것은 어떨까요?" #: ../../source/explanation-differential-privacy.rst:2 #: ../../source/explanation-differential-privacy.rst:11 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:303 msgid "Differential Privacy" -msgstr "" +msgstr "차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:3 msgid "" @@ -2758,6 +3150,10 @@ msgid "" "data is also sensitive and there is a risk of compromising individual " "privacy." msgstr "" +"의료, 금융 거래, 사용자 선호도 등과 같은 데이터 세트의 정보는 가치 있고 " +"과학적 혁신의 잠재력을 지니고 있으며 중요한 비즈니스 인사이트를 제공합니다. " +"그러나 이러한 데이터는 또한 민감한 정보이며 개인의 프라이버시를 침해할 " +"위험이 있습니다." #: ../../source/explanation-differential-privacy.rst:6 msgid "" @@ -2766,6 +3162,9 @@ msgid "" "differential privacy comes in. It provides the possibility of analyzing " "data while ensuring the privacy of individuals." msgstr "" +"익명화와 같은 기존 방법만으로는 재식별 및 데이터 연결과 같은 공격으로 인해 " +"효과가 없습니다. 그래서 차분 프라이버시가 등장했습니다. 차등 개인정보 보호는 " +"개인의 프라이버시를 보장하면서 데이터를 분석할 수 있는 가능성을 제공합니다." #: ../../source/explanation-differential-privacy.rst:12 msgid "" @@ -2776,10 +3175,15 @@ msgid "" "preserves group patterns while obscuring individual details, ensuring the" " individual's information remains hidden in the crowd." msgstr "" +"하나의 레코드(예: 앨리스의 데이터)를 제외하고는 동일한 두 개의 데이터 세트가 " +"있다고 상상해 보세요. 차분 프라이버(DP)는 평균 소득 계산과 같은 모든 분석(M)" +"이 두 데이터 세트에 대해 거의 동일한 결과를 산출하도록 보장합니다(O와 O' 는 " +"비슷할 것입니다). 이렇게 하면 그룹 패턴은 보존하면서 개별 세부 정보는 가려져 " +"개인의 정보가 군중 속에 숨겨집니다." #: ../../source/explanation-differential-privacy.rst:-1 msgid "DP Intro" -msgstr "" +msgstr "DP 소개" #: ../../source/explanation-differential-privacy.rst:22 msgid "" @@ -2788,10 +3192,13 @@ msgid "" "individual in the data while preserving the overall accuracy of the " "analysis." msgstr "" +"DP를 달성하기 위해 가장 일반적으로 사용되는 메커니즘 중 하나는 분석의 " +"전반적인 정확도를 유지하면서 데이터에서 각 개인의 기여도를 가릴 수 있도록 " +"분석 결과에 충분한 노이즈를 추가하는 것입니다." #: ../../source/explanation-differential-privacy.rst:25 msgid "Formal Definition" -msgstr "" +msgstr "공식 정의" #: ../../source/explanation-differential-privacy.rst:26 msgid "" @@ -2804,12 +3211,20 @@ msgid "" "databases, D :sub:`1` and D :sub:`2`, that differ in only a single " "record, and for all possible outputs S ⊆ Range(A):" msgstr "" +"차분 프라이버시(DP)는 공격자가 무작위 알고리즘의 출력을 통해 유추할 수 있는 " +"정보에 대해 통계적 보장을 제공합니다. 이는 노이즈를 추가하여 알고리즘의 " +"출력에 대한 한 개인의 영향력에 대한 무조건적인 상한선을 제공합니다[1]. " +"무작위 메커니즘 M은 하나의 레코드만 다른 두 개의 인접 데이터베이스인 " +"D:sub:`1`과 D:sub:`2`의 경우, 가능한 모든 출력 S ⊆ Range(A)에 대해 (:math:`" +"\\epsilon`, :math:`\\delta`)-차분 프라이버시를 제공합니다:" #: ../../source/explanation-differential-privacy.rst:32 msgid "" "\\small\n" "P[M(D_{1} \\in A)] \\leq e^{\\delta} P[M(D_{2} \\in A)] + \\delta" msgstr "" +"\\small\n" +"P[M(D_{1} \\in A)] \\leq e^{\\delta} P[M(D_{2} \\in A)] + \\delta" #: ../../source/explanation-differential-privacy.rst:38 msgid "" @@ -2822,10 +3237,17 @@ msgid "" "proportional to the sensitivity of the output, which measures the maximum" " change in the output due to the inclusion or removal of a single record." msgstr "" +"프라이버시 예산이라고도 하는 :math:`\\epsilon` 매개변수는 프라이버시 손실을 " +"측정하는 지표입니다. 이 매개변수는 프라이버시와 효용의 균형을 제어하며, " +":math:`\\epsilon` 값이 낮을수록 프라이버시 수준이 높지만 효용도 감소할 " +"가능성이 높습니다. math:`\\delta` 매개변수는 상한값인 :math:`\\epsilon`이 " +"적용되지 않는 작은 확률을 설명합니다. 차분 프라이버시를 달성하는 데 필요한 " +"노이즈의 양은 출력의 감도에 비례하며, 이는 단일 레코드의 포함 또는 제거로 " +"인한 출력의 최대 변화를 측정합니다." #: ../../source/explanation-differential-privacy.rst:45 msgid "Differential Privacy in Machine Learning" -msgstr "" +msgstr "머신 러닝의 차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:46 msgid "" @@ -2841,10 +3263,18 @@ msgid "" "model training. Additionally, such noise can be incorporated into the " "model's output." msgstr "" +"머신 러닝에서 DP를 활용하여 학습 데이터의 개인정보를 보호할 수 있습니다. " +"차분 비공개 머신 러닝 알고리즘은 알고리즘이 개별 데이터 포인트에 대한 특정 " +"정보를 학습하지 못하도록 하여 모델이 민감한 정보를 노출하지 않도록 하는 " +"방식으로 설계되었습니다. 노이즈가 도입되는 단계에 따라 머신 러닝 알고리즘에 " +"DP를 적용하는 다양한 방법이 존재합니다. 한 가지 방법은 학습 데이터(특징 또는 " +"레이블)에 노이즈를 추가하는 것이고, 다른 방법은 모델 학습 중에 손실 함수의 " +"기울기에 노이즈를 주입하는 것입니다. 또한 이러한 노이즈를 모델의 출력에 " +"통합할 수도 있습니다." #: ../../source/explanation-differential-privacy.rst:53 msgid "Differential Privacy in Federated Learning" -msgstr "" +msgstr "Federated 학습의 차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:54 msgid "" @@ -2856,12 +3286,19 @@ msgid "" "membership inference and property inference attacks, or model inversion " "attacks." msgstr "" +"Federated 학습은 여러 당사자가 원시 데이터를 공유하지 않고도 공동으로 모델을 " +"학습할 수 있는 데이터 최소화 접근 방식입니다. 그러나 연합 학습은 새로운 " +"개인정보 보호 문제를 야기하기도 합니다. 당사자와 중앙 서버 간의 모델 " +"업데이트는 로컬 데이터에 대한 정보를 유출할 수 있습니다. 이러한 유출은 " +"멤버십 추론 및 속성 추론 공격이나 모델 반전 공격과 같은 공격에 악용될 수 " +"있습니다." #: ../../source/explanation-differential-privacy.rst:58 msgid "" "DP can play a crucial role in federated learning to provide privacy for " "the clients' data." -msgstr "" +msgstr "DP는 federated학습에서 클라이언트의 데이터에 대한 개인 정보 보호를 제공하는 " +"데 중요한 역할을 할 수 있습니다." #: ../../source/explanation-differential-privacy.rst:60 msgid "" @@ -2871,6 +3308,10 @@ msgid "" " learning based on where the noise is added: at the server (also known as" " the center) or at the client (also known as the local)." msgstr "" +"개인 정보 제공의 세분성 또는 노이즈 추가 위치에 따라 federated 학습에는 " +"다양한 형태의 DP가 존재합니다. 이 설명에서는 노이즈가 추가되는 위치에 따라 " +"서버(중앙이라고도 함) 또는 클라이언트(로컬이라고도 함)에서의 federated " +"학습에서 DP를 활용하는 두 가지 접근 방식에 중점을 둡니다." #: ../../source/explanation-differential-privacy.rst:63 msgid "" @@ -2878,6 +3319,8 @@ msgid "" "goal is to prevent the aggregated model from leaking information about " "each client's data." msgstr "" +"**중앙 차분 프라이버시**: DP는 서버에서 적용되며 집계된 모델이 각 " +"클라이언트의 데이터에 대한 정보를 유출하는 것을 방지하는 것이 목표입니다." #: ../../source/explanation-differential-privacy.rst:65 msgid "" @@ -2886,12 +3329,15 @@ msgid "" "updates that are sent to the server from leaking any information about " "the client's data." msgstr "" +"**로컬 차분 개인정보 보호**: DP는 정보를 서버로 보내기 전에 클라이언트 " +"측에서 적용되며, 서버로 전송되는 업데이트가 클라이언트 데이터에 대한 정보를 " +"유출하는 것을 방지하는 것이 목표입니다." #: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:68 #: ../../source/how-to-use-differential-privacy.rst:11 msgid "Central Differential Privacy" -msgstr "" +msgstr "중앙 차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:69 msgid "" @@ -2899,6 +3345,9 @@ msgid "" "server is responsible for adding noise to the globally aggregated " "parameters. It should be noted that trust in the server is required." msgstr "" +"사용자 수준 DP라고도 하는 이 접근 방식에서는 중앙 서버가 전역적으로 집계된 " +"매개변수에 노이즈를 추가하는 역할을 담당합니다. 서버에 대한 신뢰가 " +"필요하다는 점에 유의해야 합니다." #: ../../source/explanation-differential-privacy.rst:76 msgid "" @@ -2914,10 +3363,20 @@ msgid "" " by restricting the `L2` norm of the clients' model updates, ensuring " "that larger updates are scaled down to fit within the norm `S`." msgstr "" +"federated 학습에서 중앙 DP를 구현하는 방법은 여러 가지가 있지만, 여기서는 [2]" +"와 [3]에서 제안한 알고리즘에 집중합니다. 전반적인 접근 방식은 클라이언트가 " +"전송한 모델 업데이트를 잘라내고 집계된 모델에 약간의 노이즈를 추가하는 " +"것입니다. 각 반복에서 특정 확률로 훈련할 무작위 클라이언트 세트가 " +"선택됩니다. 각 클라이언트는 자체 데이터에 대해 로컬 학습을 수행합니다. 그런 " +"다음 각 클라이언트의 업데이트는 특정 값 `S`(민감도 `S`)에 의해 잘립니다. " +"이렇게 하면 개별 클라이언트의 영향을 제한할 수 있어 개인정보 보호에 중요하고 " +"견고성에 도움이 되는 경우가 많습니다. 이를 달성하기 위한 일반적인 접근 " +"방식은 클라이언트 모델 업데이트의 `L2` 규범을 제한하여 더 큰 업데이트가 규범 " +"`S`에 맞도록 축소되도록 하는 것입니다." #: ../../source/explanation-differential-privacy.rst:-1 msgid "clipping" -msgstr "" +msgstr "클리핑" #: ../../source/explanation-differential-privacy.rst:89 msgid "" @@ -2927,16 +3386,22 @@ msgid "" "mechanism is used with a noise sampled from `N (0, σ²)` where `σ = ( " "noise_scale * S ) / (number of sampled clients)`." msgstr "" +"그 후 가우시안 메커니즘을 사용하여 모든 클라이언트의 업데이트 합계를 " +"왜곡하기 위해 노이즈를 추가합니다. 노이즈의 양은 감도 값에 따라 조정되어 " +"프라이버시 보장을 얻습니다. 가우시안 메커니즘은 `N (0, σ²)`에서 샘플링된 " +"노이즈와 함께 사용됩니다. 여기서 `σ = (noise_scale * S) / (샘플링된 " +"클라이언트 수)`입니다." #: ../../source/explanation-differential-privacy.rst:94 msgid "Clipping" -msgstr "" +msgstr "클리핑" #: ../../source/explanation-differential-privacy.rst:96 msgid "" "There are two forms of clipping commonly used in Central DP: Fixed " "Clipping and Adaptive Clipping." -msgstr "" +msgstr "Central DP에서 일반적으로 사용되는 클리핑에는 고정 클리핑과 조정 클리핑의 두 " +"가지 형태가 있습니다." #: ../../source/explanation-differential-privacy.rst:98 msgid "" @@ -2944,6 +3409,9 @@ msgid "" "of clients' updates. Any update exceeding this threshold is clipped back " "to the threshold value." msgstr "" +"**고정 클리핑** : 클라이언트의 업데이트 크기에 대해 미리 정의된 고정 " +"임계값이 설정됩니다. 이 임계값을 초과하는 모든 업데이트는 임계값으로 다시 " +"클리핑됩니다." #: ../../source/explanation-differential-privacy.rst:100 msgid "" @@ -2952,19 +3420,23 @@ msgid "" " is tuned during the rounds with respect to the quantile of the update " "norm distribution." msgstr "" +"**조 클리핑** : 클리핑 임계값은 관찰된 업데이트 분포에 따라 동적으로 " +"조정됩니다[4]. 즉, 클리핑 값은 업데이트 표준 분포의 사분위수에 따라 라운드가 " +"진행되는 동안 조정됩니다." #: ../../source/explanation-differential-privacy.rst:102 msgid "" "The choice between fixed and adaptive clipping depends on various factors" " such as privacy requirements, data distribution, model complexity, and " "others." -msgstr "" +msgstr "고정 클리핑과 조정 클리핑 중 선택은 개인정보 보호 요구 사항, 데이터 배포, " +"모델 복잡성 등 다양한 요인에 따라 달라집니다." #: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:105 #: ../../source/how-to-use-differential-privacy.rst:96 msgid "Local Differential Privacy" -msgstr "" +msgstr "로컬 차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:107 msgid "" @@ -2973,10 +3445,13 @@ msgid "" "that local DP leads to a decrease in accuracy but better privacy in " "comparison to central DP." msgstr "" +"이 접근 방식에서는 각 클라이언트가 DP를 수행할 책임이 있습니다. 로컬 DP는 " +"완전히 신뢰할 수 있는 애그리게이터가 필요하지 않지만, 로컬 DP는 중앙 DP에 " +"비해 정확도는 떨어져도 개인 정보 보호는 더 우수하다는 점에 유의해야 합니다." #: ../../source/explanation-differential-privacy.rst:116 msgid "In this explainer, we focus on two forms of achieving Local DP:" -msgstr "" +msgstr "이 설명에서는 로컬 DP를 달성하는 두 가지 형태에 중점을 둡니다:" #: ../../source/explanation-differential-privacy.rst:118 msgid "" @@ -2985,6 +3460,10 @@ msgid "" "the sensitivity of the local model to be ∆, Gaussian noise is applied " "with a noise scale of σ where:" msgstr "" +"각 클라이언트는 로컬 업데이트를 서버로 보내기 전에 로컬 업데이트에 노이즈를 " +"추가합니다. 로컬 모델의 감도를 ∆로 간주하여 가우시안 노이즈가 σ의 노이즈 " +"스케일로 적용되어 (:math:`\\epsilon`, :math:`\\delta`)-DP를 달성하기 위해, " +"여기서 σ는 노이즈 스케일입니다:" #: ../../source/explanation-differential-privacy.rst:120 msgid "" @@ -2993,6 +3472,10 @@ msgid "" "\\log\\left(\\frac{1.25}{\\delta}\\right)}}{\\epsilon}\n" "\n" msgstr "" +"\\small\n" +"\\frac{∆ \\times \\sqrt{2 \\times \\log\\left(\\frac{1.25}{\\delta}\\right" +")}}{\\epsilon}\n" +"\n" #: ../../source/explanation-differential-privacy.rst:125 msgid "" @@ -3000,41 +3483,45 @@ msgid "" "training (DP-SGD). More specifically, in this approach, gradients are " "clipped and an amount of calibrated noise is injected into the gradients." msgstr "" +"각 클라이언트는 로컬 트레이닝(DP-SGD) 중에 모델의 gradient에 노이즈를 " +"추가합니다. 보다 구체적으로, 이 접근 방식에서는 gradient이 클리핑되고 보정된 " +"노이즈가 gradient에 주입됩니다." #: ../../source/explanation-differential-privacy.rst:128 msgid "" "Please note that these two approaches are providing privacy at different " "levels." -msgstr "" +msgstr "이 두 가지 접근 방식은 서로 다른 수준의 개인정보 보호 기능을 제공한다는 점에 " +"유의하세요." #: ../../source/explanation-differential-privacy.rst:131 msgid "**References:**" -msgstr "" +msgstr "**참고:**" #: ../../source/explanation-differential-privacy.rst:133 msgid "[1] Dwork et al. The Algorithmic Foundations of Differential Privacy." -msgstr "" +msgstr "[1] Dwork 외. 차분 프라이버시의 알고리즘적 기초." #: ../../source/explanation-differential-privacy.rst:135 msgid "" "[2] McMahan et al. Learning Differentially Private Recurrent Language " "Models." -msgstr "" +msgstr "[2] McMahan 외. 차분적 개인 반복 언어 모델 학습." #: ../../source/explanation-differential-privacy.rst:137 msgid "" "[3] Geyer et al. Differentially Private Federated Learning: A Client " "Level Perspective." -msgstr "" +msgstr "[3] Geyer 외. 차분적 개인 Federated 학습: 고객 수준의 관점." #: ../../source/explanation-differential-privacy.rst:139 msgid "[4] Galen et al. Differentially Private Learning with Adaptive Clipping." -msgstr "" +msgstr "[4] Galen 외. 조정형 클리핑을 통한 차분적 개인 학습." #: ../../source/explanation-federated-evaluation.rst:2 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:292 msgid "Federated evaluation" -msgstr "" +msgstr "Federated 평가" #: ../../source/explanation-federated-evaluation.rst:4 msgid "" @@ -3042,14 +3529,17 @@ msgid "" "systems: centralized (or server-side) evaluation and federated (or " "client-side) evaluation." msgstr "" +"federated 학습 시스템에서 모델을 평가하는 데는 centralized(또는 서버 측) " +"평가와 federated(또는 클라이언트 측) 평가라는 두 가지 주요 접근 방식이 " +"있습니다." #: ../../source/explanation-federated-evaluation.rst:8 msgid "Centralized Evaluation" -msgstr "" +msgstr "Centralized 평가" #: ../../source/explanation-federated-evaluation.rst:11 msgid "Built-In Strategies" -msgstr "" +msgstr "기본 제공 전략" #: ../../source/explanation-federated-evaluation.rst:13 msgid "" @@ -3058,10 +3548,13 @@ msgid "" "function that can take the current global model parameters as input and " "return evaluation results:" msgstr "" +"모든 기본 제공 전략은 초기화 중에 평가 함수를 제공하여 centralized 평가를 " +"지원합니다. 평가 함수는 현재 글로벌 모델 파라미터를 입력으로 받아 평가 " +"결과를 반환할 수 있는 모든 함수입니다:" #: ../../source/explanation-federated-evaluation.rst:58 msgid "Custom Strategies" -msgstr "" +msgstr "사용자 정의 전략" #: ../../source/explanation-federated-evaluation.rst:60 msgid "" @@ -3071,30 +3564,35 @@ msgid "" ":code:`evaluate` after parameter aggregation and before federated " "evaluation (see next paragraph)." msgstr "" +"코드:`전략` 추상화는 현재 전역 모델 파라미터를 평가하는 데 직접 사용할 수 " +"있는 :코드:`평가`라는 메서드를 제공합니다. 현재 서버 구현에서는 매개변수 " +"집계 후와 연합 평가 전에 :code:`evaluate`를 호출합니다(다음 단락 참조)." #: ../../source/explanation-federated-evaluation.rst:65 msgid "Federated Evaluation" -msgstr "" +msgstr "Federated 평가" #: ../../source/explanation-federated-evaluation.rst:68 msgid "Implementing Federated Evaluation" -msgstr "" +msgstr "Federated 평가 구현" #: ../../source/explanation-federated-evaluation.rst:70 msgid "" "Client-side evaluation happens in the :code:`Client.evaluate` method and " "can be configured from the server side." -msgstr "" +msgstr "클라이언트 측 평가는 :code:`Client.evaluate` 메서드에서 이루어지며 서버 " +"측에서 구성할 수 있습니다." #: ../../source/explanation-federated-evaluation.rst:101 msgid "Configuring Federated Evaluation" -msgstr "" +msgstr "Federated 평가 구성" #: ../../source/explanation-federated-evaluation.rst:103 msgid "" "Federated evaluation can be configured from the server side. Built-in " "strategies support the following arguments:" -msgstr "" +msgstr "Federated 평가는 서버 측에서 구성할 수 있습니다. 기본 제공 전략은 다음 " +"인수를 지원합니다:" #: ../../source/explanation-federated-evaluation.rst:105 msgid "" @@ -3105,6 +3603,11 @@ msgid "" "for evaluation. If :code:`fraction_evaluate` is set to :code:`0.0`, " "federated evaluation will be disabled." msgstr "" +":code:`fraction_evaluate`: 평가를 위해 선택될 클라이언트의 비율을 정의하는 " +":code:`float`입니다. 코드:`fraction_evaluate`가 :code:`0.1`로 설정되어 있고 " +":code:`100` 클라이언트가 서버에 연결되어 있는 경우 :code:`10`이 평가를 위해 " +"무작위로 선택됩니다. code:`fraction_evaluate`가 :code:`0.0`으로 설정된 경우 " +"federated 평가가 비활성화됩니다." #: ../../source/explanation-federated-evaluation.rst:106 msgid "" @@ -3114,6 +3617,10 @@ msgid "" ":code:`100` clients are connected to the server, then :code:`20` clients " "will be selected for evaluation." msgstr "" +":code:`min_evaluate_clients`: 평가를 위해 선택할 최소 클라이언트 수. " +":code:`int`. 코드:`fraction_evaluate`가 :code:`0.1`로 설정되어 있고 " +":code:`fraction_evaluate`가 20으로 설정되어 있으며 :code:`100` 클라이언트가 " +"서버에 연결되어 있는 경우 :code:`20` 클라이언트가 평가를 위해 선택됩니다." #: ../../source/explanation-federated-evaluation.rst:107 msgid "" @@ -3124,6 +3631,11 @@ msgid "" "will wait until more clients are connected before it continues to sample " "clients for evaluation." msgstr "" +":code:`min_available_clients`: federated 평가 단계를 시작하기 전에 서버에 " +"연결해야 하는 최소 클라이언트 수를 정의하는 :code:`int`입니다. 서버에 연결된 " +"클라이언트가 :code:`min_available_clients`보다 적으면 서버는 더 많은 " +"클라이언트가 연결될 때까지 기다렸다가 평가를 위한 클라이언트 샘플링을 " +"계속합니다." #: ../../source/explanation-federated-evaluation.rst:108 msgid "" @@ -3133,10 +3645,14 @@ msgid "" "client-side evaluation from the server side, for example, to configure " "the number of validation steps performed." msgstr "" +"code:`on_evaluate_config_fn`: 선택한 클라이언트로 전송할 구성 사전을 " +"반환하는 함수입니다. 이 함수는 각 단계 중에 호출되며, 서버 측에서 클라이언트 " +"측 평가를 사용자 지정하는 편리한 방법을 제공합니다(예: 수행되는 유효성 검사 " +"단계 수 구성)." #: ../../source/explanation-federated-evaluation.rst:135 msgid "Evaluating Local Model Updates During Training" -msgstr "" +msgstr "훈련 중 로컬 모델 업데이트 평가" #: ../../source/explanation-federated-evaluation.rst:137 msgid "" @@ -3144,10 +3660,12 @@ msgid "" ":code:`Client.fit` can return arbitrary evaluation results as a " "dictionary:" msgstr "" +"모델 파라미터는 훈련 중에도 평가할 수 있습니다. :code:`Client.fit`은 임의의 " +"평가 결과를 dictionary로 반환할 수 있습니다:" #: ../../source/explanation-federated-evaluation.rst:177 msgid "Full Code Example" -msgstr "" +msgstr "전체 코드 예제" #: ../../source/explanation-federated-evaluation.rst:179 msgid "" @@ -3156,79 +3674,83 @@ msgid "" "be applied to workloads implemented in any other framework): " "https://github.com/adap/flower/tree/main/examples/advanced-tensorflow" msgstr "" +"federated 평가와 centralized 평가를 모두 사용하는 전체 코드 예제는 *고급 " +"텐서플로우 예제*(다른 프레임워크에서 구현된 워크로드에도 동일한 접근 방식을 " +"적용할 수 있음)를 참조하세요: https://github.com/adap/flower/tree/main/" +"examples/advanced-tensorflow" #: ../../source/fed/0000-20200102-fed-template.md:10 msgid "FED Template" -msgstr "" +msgstr "FED 템플릿" #: ../../source/fed/0000-20200102-fed-template.md:12 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:12 msgid "Table of Contents" -msgstr "" +msgstr "목차" #: ../../source/fed/0000-20200102-fed-template.md:14 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:14 msgid "[Table of Contents](#table-of-contents)" -msgstr "" +msgstr "[목차](#목차)" #: ../../source/fed/0000-20200102-fed-template.md:15 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:15 msgid "[Summary](#summary)" -msgstr "" +msgstr "[요약](#요약)" #: ../../source/fed/0000-20200102-fed-template.md:16 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:16 msgid "[Motivation](#motivation)" -msgstr "" +msgstr "[동기](#동기)" #: ../../source/fed/0000-20200102-fed-template.md:17 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:17 msgid "[Goals](#goals)" -msgstr "" +msgstr "[목표](#목표)" #: ../../source/fed/0000-20200102-fed-template.md:18 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:18 msgid "[Non-Goals](#non-goals)" -msgstr "" +msgstr "[비목표](#비목표)" #: ../../source/fed/0000-20200102-fed-template.md:19 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:19 msgid "[Proposal](#proposal)" -msgstr "" +msgstr "[제안](#제안)" #: ../../source/fed/0000-20200102-fed-template.md:20 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:23 msgid "[Drawbacks](#drawbacks)" -msgstr "" +msgstr "[단점](#단점)" #: ../../source/fed/0000-20200102-fed-template.md:21 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:24 msgid "[Alternatives Considered](#alternatives-considered)" -msgstr "" +msgstr "[고려되는 대안](#고려되는 대안)" #: ../../source/fed/0000-20200102-fed-template.md:22 msgid "[Appendix](#appendix)" -msgstr "" +msgstr "[부록](#부록)" #: ../../source/fed/0000-20200102-fed-template.md:24 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:28 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:76 msgid "Summary" -msgstr "" +msgstr "요약" #: ../../source/fed/0000-20200102-fed-template.md:26 msgid "\\[TODO - sentence 1: summary of the problem\\]" -msgstr "" +msgstr "\\[TODO - 문장 1: 문제 요약\\]" #: ../../source/fed/0000-20200102-fed-template.md:28 msgid "\\[TODO - sentence 2: summary of the solution\\]" -msgstr "" +msgstr "\\[TODO - 문장 2: 솔루션 요약\\]" #: ../../source/fed/0000-20200102-fed-template.md:30 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:47 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:77 msgid "Motivation" -msgstr "" +msgstr "동기" #: ../../source/fed/0000-20200102-fed-template.md:32 #: ../../source/fed/0000-20200102-fed-template.md:36 @@ -3238,93 +3760,93 @@ msgstr "" #: ../../source/fed/0000-20200102-fed-template.md:54 #: ../../source/fed/0000-20200102-fed-template.md:58 msgid "\\[TODO\\]" -msgstr "" +msgstr "\\[TODO\\]" #: ../../source/fed/0000-20200102-fed-template.md:34 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:53 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:78 msgid "Goals" -msgstr "" +msgstr "목표" #: ../../source/fed/0000-20200102-fed-template.md:38 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:59 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:79 msgid "Non-Goals" -msgstr "" +msgstr "비목표" #: ../../source/fed/0000-20200102-fed-template.md:42 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:65 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:80 msgid "Proposal" -msgstr "" +msgstr "제안" #: ../../source/fed/0000-20200102-fed-template.md:46 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:85 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:129 msgid "Drawbacks" -msgstr "" +msgstr "단점" #: ../../source/fed/0000-20200102-fed-template.md:50 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:86 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:135 msgid "Alternatives Considered" -msgstr "" +msgstr "고려되는 대안" #: ../../source/fed/0000-20200102-fed-template.md:52 msgid "\\[Alternative 1\\]" -msgstr "" +msgstr "\\[대안 1\\]" #: ../../source/fed/0000-20200102-fed-template.md:56 msgid "\\[Alternative 2\\]" -msgstr "" +msgstr "\\[대안 2\\]" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:10 msgid "Flower Enhancement Doc" -msgstr "" +msgstr "Flower Enhancement Doc" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:20 msgid "[Enhancement Doc Template](#enhancement-doc-template)" -msgstr "" +msgstr "[Enhancement Doc 템플릿](#enhancement-doc-템플릿)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:21 msgid "[Metadata](#metadata)" -msgstr "" +msgstr "[Metadata](#metadata)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:22 msgid "[Workflow](#workflow)" -msgstr "" +msgstr "[워크플로우](#워크플로우)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:25 msgid "[GitHub Issues](#github-issues)" -msgstr "" +msgstr "[GitHub Issues](#github-issues)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:26 msgid "[Google Docs](#google-docs)" -msgstr "" +msgstr "[Google Docs](#google-docs)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:30 msgid "A Flower Enhancement is a standardized development process to" -msgstr "" +msgstr "Flower Enhancement는 다음과 같은 표준화된 개발 프로세스입니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:32 msgid "provide a common structure for proposing larger changes" -msgstr "" +msgstr "더 큰 변경 사항을 제안하기 위한 공통 구조를 제공합니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:33 msgid "ensure that the motivation for a change is clear" -msgstr "" +msgstr "변화의 동기가 분명한지 확인합니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:34 msgid "persist project information in a version control system" -msgstr "" +msgstr "버전 관리 시스템에서 프로젝트 정보를 유지합니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:35 msgid "document the motivation for impactful user-facing changes" -msgstr "" +msgstr "사용자에게 영향력 있는 변화에 대한 동기를 문서화합니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:36 msgid "reserve GitHub issues for tracking work in flight" -msgstr "" +msgstr "운행 중 작업 추적을 위한 깃허브 이슈를 예약합니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:37 msgid "" @@ -3332,28 +3854,31 @@ msgid "" "completion across one or more releases while stakeholders are adequately " "represented throughout the process" msgstr "" +"커뮤니티 참여자가 하나 이상의 릴리즈에서 변경 사항을 성공적으로 완료할 수 " +"있도록 하는 동시에 이해 관계자가 프로세스 전반에 걸쳐 적절히 대표되도록 " +"보장합니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:39 msgid "Hence, an Enhancement Doc combines aspects of" -msgstr "" +msgstr "따라서 Enhancement 문서에는 다음과 같은 측면이 결합되어 있습니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:41 msgid "a feature, and effort-tracking document" -msgstr "" +msgstr "기능 및 effort-tracking 문서" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:42 msgid "a product requirements document" -msgstr "" +msgstr "제품 요구 사항 문서" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:43 msgid "a design document" -msgstr "" +msgstr "디자인 문서" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:45 msgid "" "into one file, which is created incrementally in collaboration with the " "community." -msgstr "" +msgstr "를 하나의 파일로 통합하여 커뮤니티와 협력해 점진적으로 생성합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:49 msgid "" @@ -3361,6 +3886,9 @@ msgid "" "beyond a single GitHub issue or pull request is required to understand " "and communicate upcoming changes to the project." msgstr "" +"Flower에 제안된 변경 사항이나 기능을 멀리 가져오는 경우, 프로젝트의 향후 " +"변경 사항을 이해하고 전달하기 위해 단일 GitHub 이슈 또는 pull request를 " +"넘어서는 abstraction이 필요합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:51 msgid "" @@ -3369,6 +3897,10 @@ msgid "" "video calls, and hallway conversations into a well-tracked artifact, this" " process aims to enhance communication and discoverability." msgstr "" +"이 프로세스의 목적은 커뮤니티 내 '부족한 지식'의 양을 줄이는 것입니다. 이 " +"프로세스는 Slack 스레드, 영상 통화, 복도 대화에서 나온 의사 결정을 잘 추적된 " +"아티팩트로 옮김으로써 커뮤니케이션과 검색 가능성을 향상시키는 것을 목표로 " +"합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:55 msgid "" @@ -3377,6 +3909,9 @@ msgid "" "verbal communication to anyone besides the author or developer, then " "consider creating an Enhancement Doc." msgstr "" +"대략적으로 사용자를 대상으로 하는 대규모 개선 사항은 개선 프로세스를 따라야 " +"합니다. 개선 사항을 작성자나 개발자 이외의 다른 사람에게 서면 또는 구두로 " +"설명해야 하는 경우에는 개선 문서 작성을 고려하세요." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:57 msgid "" @@ -3385,6 +3920,9 @@ msgid "" "also be communicated widely. The Enhancement process is suited for this " "even if it will have zero impact on the typical user or operator." msgstr "" +"마찬가지로 개발 커뮤니티의 많은 부분에 영향을 미치는 기술적 노력(리팩토링, " +"주요 아키텍처 변경)도 널리 알려야 합니다. 개선 프로세스는 일반 사용자나 " +"운영자에게 전혀 영향을 미치지 않더라도 이를 위해 적합합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:61 msgid "" @@ -3393,13 +3931,18 @@ msgid "" "adding new Federated Learning algorithms, as these only add features " "without changing how Flower works or is used." msgstr "" +"작은 변경 및 추가의 경우, 개선 프로세스를 거치는 것은 시간이 많이 걸리고 " +"불필요합니다. 예를 들어, 새로운 Federated 학습 알고리즘을 추가하는 것은 " +"Flower의 작동 방식이나 사용 방식을 변경하지 않고 기능만 추가하는 것이기 " +"때문입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:63 msgid "" "Enhancements are different from feature requests, as they are already " "providing a laid-out path for implementation and are championed by " "members of the community." -msgstr "" +msgstr "기능 개선은 이미 구현할 수 있는 경로가 마련되어 있고 커뮤니티 구성원들이 " +"지지하는 것이므로 기능 요청과는 다릅니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:67 msgid "" @@ -3407,105 +3950,110 @@ msgid "" "template and a workflow to review and store enhancement docs for " "reference — the Enhancement Doc." msgstr "" +"개선 사항은 정의된 템플릿과 참조용으로 Enhancement Doc.를 검토하고 저장하는 " +"워크플로우를 따르는 Markdown 파일에 캡처됩니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:69 msgid "Enhancement Doc Template" -msgstr "" +msgstr "Enhancement Doc 템플릿" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:71 msgid "" "Each enhancement doc is provided as a Markdown file having the following " "structure" -msgstr "" +msgstr "각 개선 사항 문서는 다음과 같은 구조의 Markdown 파일로 제공됩니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:73 msgid "Metadata (as [described below](#metadata) in form of a YAML preamble)" -msgstr "" +msgstr "Metadata ([아래 설명](#metadata) YAML preamble 형식)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:74 msgid "Title (same as in metadata)" -msgstr "" +msgstr "Title (metadata와 같게)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:75 msgid "Table of Contents (if needed)" -msgstr "" +msgstr "Table of Contents (필요시)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:81 msgid "Notes/Constraints/Caveats (optional)" -msgstr "" +msgstr "Notes/Constraints/Caveats (선택 사항)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:82 msgid "Design Details (optional)" -msgstr "" +msgstr "Design Details (선택 사항)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:83 msgid "Graduation Criteria" -msgstr "" +msgstr "졸업 기준" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:84 msgid "Upgrade/Downgrade Strategy (if applicable)" -msgstr "" +msgstr "업그레이드/다운그레이드 전략(해당되는 경우)" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:88 msgid "As a reference, this document follows the above structure." -msgstr "" +msgstr "참고로 이 문서는 위의 구조를 따릅니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:90 #: ../../source/ref-api/flwr.common.Metadata.rst:2 msgid "Metadata" -msgstr "" +msgstr "Metadata" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:92 msgid "" "**fed-number** (Required) The `fed-number` of the last Flower Enhancement" " Doc + 1. With this number, it becomes easy to reference other proposals." msgstr "" +"**피드 번호** (필수) 마지막 Flower Enhancement 문서의 `피드 번호` + 1. 이 " +"번호를 사용하면 다른 제안을 쉽게 참조할 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:94 msgid "**title** (Required) The title of the proposal in plain language." -msgstr "" +msgstr "**제목** (필수) 제안서의 제목을 평이한 언어로 입력합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:96 msgid "" "**status** (Required) The current status of the proposal. See " "[workflow](#workflow) for the possible states." -msgstr "" +msgstr "**상태** (필수) 제안의 현재 상태입니다. 가능한 상태는 [워크플로](#워크플로)" +"를 참조하세요." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:98 msgid "" "**authors** (Required) A list of authors of the proposal. This is simply " "the GitHub ID." -msgstr "" +msgstr "**저자** (필수) 제안서의 작성자 목록입니다. 간단히 GitHub ID입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:100 msgid "" "**creation-date** (Required) The date that the proposal was first " "submitted in a PR." -msgstr "" +msgstr "**생성 날짜** (필수) PR에서 제안서를 처음 제출한 날짜입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:102 msgid "" "**last-updated** (Optional) The date that the proposal was last changed " "significantly." -msgstr "" +msgstr "**마지막 업데이트** (선택 사항) 제안서가 마지막으로 크게 변경된 날짜입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:104 msgid "" "**see-also** (Optional) A list of other proposals that are relevant to " "this one." -msgstr "" +msgstr "**함께 보기** (선택 사항) 이 제안과 관련된 다른 제안 목록입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:106 msgid "**replaces** (Optional) A list of proposals that this one replaces." -msgstr "" +msgstr "**대체** (선택 사항) 이 제안이 대체하는 제안 목록입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:108 msgid "**superseded-by** (Optional) A list of proposals that this one supersedes." -msgstr "" +msgstr "**대체됨** (선택 사항) 이 제안이 대체하는 제안의 목록입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:111 msgid "Workflow" -msgstr "" +msgstr "워크플로우" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:113 msgid "" @@ -3514,6 +4062,9 @@ msgid "" "author, who shepherds the enhancement. This person also has to find " "committers to Flower willing to review the proposal." msgstr "" +"개선 사항을 구성하는 아이디어는 이미 커뮤니티에서 논의되었거나 제안된 적이 " +"있어야 합니다. 따라서 개선 사항을 주도하는 사(보통 작성자)이 필요합니다. 이 " +"사람은 또한 제안을 검토할 의향이 있는 Flower 커미터를 찾아야 합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:115 msgid "" @@ -3523,6 +4074,10 @@ msgid "" "state as part of a pull request. Discussions are done as part of the pull" " request review." msgstr "" +"새 개선 사항은 `NNNN-YYYYMMDD-enhancement-title.md` 형식의 파일 이름으로 " +"체크인되며, `NNNN`은 Flower 개선 문서 번호이고 `enhancements`에 해당합니다. " +"모든 개선 사항은 pull request의 일부로 `잠정` 상태에서 시작됩니다. 토론은 " +"pull request 검토의 일부로 이루어집니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:117 msgid "" @@ -3532,12 +4087,16 @@ msgid "" "enhancement as part of their description. After the implementation is " "done, the proposal status is changed to `implemented`." msgstr "" +"개선 사항이 검토 및 승인되면 상태가 '구현 가능'으로 변경됩니다. 그런 다음 " +"실제 구현은 별도의 pull requests를 통해 이루어집니다. 이러한 pull requests는 " +"설명의 일부로 해당 개선 사항을 언급해야 합니다. 구현이 완료되면 제안 상태는 " +"'구현됨'으로 변경됩니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:119 msgid "" "Under certain conditions, other states are possible. An Enhancement has " "the following states:" -msgstr "" +msgstr "특정 조건에서는 다른 상태도 가능합니다. 개선에는 다음과 같은 상태가 있습니다:" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:121 msgid "" @@ -3545,34 +4104,37 @@ msgid "" "defined. This is the starting state while the proposal is being fleshed " "out and actively defined and discussed." msgstr "" +"'잠정적': 개선 사항이 제안되어 활발히 정의되고 있습니다. 제안이 구체화되고 " +"활발하게 정의 및 논의되는 동안의 시작 단계입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:122 msgid "`implementable`: The enhancement has been reviewed and approved." -msgstr "" +msgstr "`구현 가능`: 개선 사항이 검토 및 승인되었습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:123 msgid "" "`implemented`: The enhancement has been implemented and is no longer " "actively changed." -msgstr "" +msgstr "`구현됨`: 개선 사항이 구현되었으며 더 이상 활발히 변경되지 않습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:124 msgid "`deferred`: The enhancement is proposed but not actively being worked on." -msgstr "" +msgstr "'지연됨': 개선 사항이 제안되었지만 아직 활발히 작업 중이 아닙니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:125 msgid "" "`rejected`: The authors and reviewers have decided that this enhancement " "is not moving forward." -msgstr "" +msgstr "`거부됨`: 작성자와 검토자는 이 개선 사항을 더 이상 진행하지 않기로 " +"결정했습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:126 msgid "`withdrawn`: The authors have withdrawn the enhancement." -msgstr "" +msgstr "`철회`: 작성자가 개선 사항을 철회했습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:127 msgid "`replaced`: The enhancement has been replaced by a new enhancement." -msgstr "" +msgstr "'대체됨': 개선 사항이 새로운 개선 사항으로 대체되었습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:131 msgid "" @@ -3580,6 +4142,8 @@ msgid "" "(Issues and Pull Requests) adds more complexity and can be a barrier for " "potential first-time contributors." msgstr "" +"GitHub에서 이미 제공하는 프로세스(이슈 및 Pull Requests)에 추가 프로세스를 " +"추가하면 더 복잡해지고 잠재적인 처음인 기여자에게는 장벽이 될 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:133 msgid "" @@ -3587,10 +4151,12 @@ msgid "" "currently required in the features issue template may be a heavy burden " "for non-native English speakers." msgstr "" +"현재 기능 이슈 템플릿에서 요구되는 한 문장 설명 이상으로 제안서 템플릿을 " +"확장하는 것은 영어가 모국어가 아닌 사용자에게는 큰 부담이 될 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:137 msgid "GitHub Issues" -msgstr "" +msgstr "GitHub 이슈" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:139 msgid "" @@ -3602,10 +4168,16 @@ msgid "" "parts of the doc. Managing these multiple discussions can be confusing " "when using GitHub Issues." msgstr "" +"이러한 종류의 개선을 위해 GitHub 이슈를 사용하면 가능합니다. 예를 들어 " +"태그를 사용하여 다른 이슈와 구별하고 필터링할 수 있습니다. 주요 이슈는 개선 " +"사항에 대해 토론하고 검토하는 것입니다: GitHub 이슈에는 댓글 스레드가 하나만 " +"있습니다. 개선 사항에는 일반적으로 문서의 여러 부분에 대해 동시에 여러 개의 " +"토론 스레드가 있습니다. GitHub 이슈를 사용할 때 이러한 여러 토론을 관리하면 " +"혼란스러울 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:141 msgid "Google Docs" -msgstr "" +msgstr "Google 문서 도구" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:143 msgid "" @@ -3616,24 +4188,29 @@ msgid "" "proposals as part of Flower's repository, the potential for missing links" " is much higher." msgstr "" +"Google 문서는 여러 스레드의 토론을 허용합니다. 하지만 Google 문서는 프로젝트 " +"외부에서 호스팅되므로 커뮤니티에서 검색할 수 있도록 관리해야 합니다. 모든 " +"제안에 대한 링크 목록을 관리하고 커뮤니티에 제공해야 합니다. Flower 저장소의 " +"일부로 제안서를 보낼 때와 비교하면 링크가 누락될 가능성이 훨씬 더 높습니다." #: ../../source/fed/index.md:1 msgid "FED - Flower Enhancement Doc" -msgstr "" +msgstr "FED - Flower 개선 문서" #: ../../source/how-to-aggregate-evaluation-results.rst:2 msgid "Aggregate evaluation results" -msgstr "" +msgstr "종합 평가 결과" #: ../../source/how-to-aggregate-evaluation-results.rst:4 msgid "" "The Flower server does not prescribe a way to aggregate evaluation " "results, but it enables the user to fully customize result aggregation." -msgstr "" +msgstr "Flower 서버는 평가 결과를 집계하는 방법을 규정하고 있지 않지만 사용자가 결과 " +"집계를 완전히 사용자 지정할 수 있습니다." #: ../../source/how-to-aggregate-evaluation-results.rst:8 msgid "Aggregate Custom Evaluation Results" -msgstr "" +msgstr "사용자 지정 평가 결과 집계" #: ../../source/how-to-aggregate-evaluation-results.rst:10 msgid "" @@ -3641,16 +4218,20 @@ msgid "" " custom evaluation results coming from individual clients. Clients can " "return custom metrics to the server by returning a dictionary:" msgstr "" +"동일한 :code:`Strategy`-사용자 지정 방식을 사용하여 개별 클라이언트로부터 " +"오는 사용자 지정 평가 결과를 집계할 수 있습니다. 클라이언트는 dictionary를 " +"반환하여 사용자 지정 지표를 서버에 반환할 수 있습니다:" #: ../../source/how-to-aggregate-evaluation-results.rst:36 msgid "" "The server can then use a customized strategy to aggregate the metrics " "provided in these dictionaries:" -msgstr "" +msgstr "그런 다음 서버는 사용자 지정 전략을 사용하여 이러한 dictionaries에서 " +"제공하는 메트릭을 집계할 수 있습니다:" #: ../../source/how-to-authenticate-supernodes.rst:2 msgid "Authenticate SuperNodes" -msgstr "" +msgstr "SuperNodes 인증하기" #: ../../source/how-to-authenticate-supernodes.rst:4 msgid "" @@ -3659,26 +4240,30 @@ msgid "" "Flower node authentication works similar to how GitHub SSH authentication" " works:" msgstr "" +"Flower는 SuperLink에 연결하는 각 SuperNodes의 신원을 확인하는 데 사용할 수 " +"있는 인증된 SuperNodes에 대한 기본 지원을 제공합니다. Flower 노드 인증은 " +"GitHub SSH 인증 방식과 유사하게 작동합니다:" #: ../../source/how-to-authenticate-supernodes.rst:7 msgid "SuperLink (server) stores a list of known (client) node public keys" -msgstr "" +msgstr "SuperLink(서버)는 알려진 (클라이언트) 노드 공개키 목록을 저장합니다" #: ../../source/how-to-authenticate-supernodes.rst:8 msgid "" "Using ECDH, both SuperNode and SuperLink independently derive a shared " "secret" -msgstr "" +msgstr "SuperNode와 SuperLink는 ECDH를 사용하여 독립적으로 공유된 비밀을 도출합니다" #: ../../source/how-to-authenticate-supernodes.rst:9 msgid "" "Shared secret is used to compute the HMAC value of the message sent from " "SuperNode to SuperLink as a token" -msgstr "" +msgstr "비밀 공유는 SuperNode에서 SuperLink로 토큰으로 전송된 메시지의 HMAC 값을 " +"계산하는 데 사용됩니다" #: ../../source/how-to-authenticate-supernodes.rst:10 msgid "SuperLink verifies the token" -msgstr "" +msgstr "SuperLink가 토큰을 확인합니다" #: ../../source/how-to-authenticate-supernodes.rst:12 msgid "" @@ -3687,22 +4272,27 @@ msgid "" "authentication>`_ demonstrating federated learning with Flower in an " "authenticated setting." msgstr "" +"인증된 환경에서 Flower로 federated 학습을 시연하는 전체 '코드 예제 " +"`" +"_를 확인하는 것이 좋습니다." #: ../../source/how-to-authenticate-supernodes.rst:15 msgid "" "This guide covers a preview feature that might change in future versions " "of Flower." -msgstr "" +msgstr "이 가이드에서는 향후 버전의 Flower에서 변경될 수 있는 미리보기 기능에 대해 " +"설명합니다." #: ../../source/how-to-authenticate-supernodes.rst:18 msgid "" "For increased security, node authentication can only be used when " "encrypted connections (SSL/TLS) are enabled." -msgstr "" +msgstr "보안을 강화하기 위해 노드 인증은 암호화된 연결(SSL/TLS)을 사용하도록 설정한 " +"경우에만 사용할 수 있습니다." #: ../../source/how-to-authenticate-supernodes.rst:21 msgid "Enable node authentication in :code:`SuperLink`" -msgstr "" +msgstr ":code:`SuperLink`에서 노드 인증 활성화" #: ../../source/how-to-authenticate-supernodes.rst:23 msgid "" @@ -3715,10 +4305,16 @@ msgid "" ":code:`SuperNode` that has both secure connections and node " "authentication enabled:" msgstr "" +"노드 인증을 활성화하려면 먼저 SuperLink<>SuperNode 통신을 보호하기 위해 SSL/" +"TLS 연결을 구성해야 합니다. 전체 가이드는 `여기 `_에서 확인할 수 있습니다. 보안 " +"연결을 구성한 후, 장기 실행하는 Flower :code:`SuperLink`에서 클라이언트 " +"인증을 활성화할 수 있습니다. 다음 터미널 명령을 사용하여 보안 연결과 노드 " +"인증이 모두 활성화된 Flower :code:`SuperNode`를 시작하세요:" #: ../../source/how-to-authenticate-supernodes.rst:38 msgid "Let's break down the authentication flags:" -msgstr "" +msgstr "인증 플래그를 세분화해 보겠습니다:" #: ../../source/how-to-authenticate-supernodes.rst:40 msgid "" @@ -3727,6 +4323,9 @@ msgid "" " public keys that are allowed to participate in a federation in one CSV " "file (:code:`.csv`)." msgstr "" +"첫 번째 플래그 :code:`--auth-list-public-keys`는 알려진 모든 노드 공개키를 " +"저장하는 CSV 파일의 경로를 기대합니다. federation에 참여하도록 허용된 모든 " +"알려진 노드 공개 키를 하나의 CSV 파일(:code:`.csv`)에 저장해야 합니다." #: ../../source/how-to-authenticate-supernodes.rst:42 msgid "" @@ -3735,6 +4334,9 @@ msgid "" "example, refer to our code sample, which contains a CSV file with two " "known node public keys." msgstr "" +"알려진 노드 공개키를 저장하는 유효한 CSV 파일은 쉼표로 구분하고 주석 없이 " +"OpenSSH 형식으로 키를 나열해야 합니다. 예를 들어, 두 개의 알려진 노드 " +"공개키가 포함된 CSV 파일이 포함된 코드 샘플을 참조하세요." #: ../../source/how-to-authenticate-supernodes.rst:44 msgid "" @@ -3743,6 +4345,10 @@ msgid "" "public keys. For development purposes, you can generate a private and " "public key pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" +"두 번째 및 세 번째 플래그 :code:`--auth-superlink-private-key` 및 :code" +":`--auth-superlink-public-key`는 서버의 개인 및 공개 키의 경로를 예상합니다. " +"개발 목적으로 :code:`ssh-keygen -t ecdsa -b 384`를 사용하여 개인 및 공개 키 " +"쌍을 생성할 수 있습니다." #: ../../source/how-to-authenticate-supernodes.rst:47 msgid "" @@ -3752,10 +4358,15 @@ msgid "" "start the server again. Support for dynamically changing the set of known" " nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" +"Flower 1.9에서는 알려진 노드 공개키를 SuperLink에 동적으로 제거, 편집 또는 " +"추가하는 기능이 지원되지 않습니다. 알려진 노드 집합을 변경하려면 서버를 " +"종료하고 CSV 파일을 편집한 다음 서버를 다시 시작해야 합니다. 알려진 노드 " +"집합을 동적으로 변경하는 기능은 Flower 1.10(출시 예정일: 6월)에서 로드맵에 " +"포함되어 있습니다." #: ../../source/how-to-authenticate-supernodes.rst:53 msgid "Enable node authentication in :code:`SuperNode`" -msgstr "" +msgstr ":code:`SuperNode`에서 노드 인증을 활성화합니다" #: ../../source/how-to-authenticate-supernodes.rst:55 msgid "" @@ -3764,6 +4375,9 @@ msgid "" "(:code:`SuperNode`). Use the following terminal command to start an " "authenticated :code:`SuperNode`:" msgstr "" +"장기 실행 중인 Flower 서버(:code:`SuperLink`)와 마찬가지로, 장기 실행 중인 " +"Flower 클라이언트(:code:`SuperNode`)에서도 노드 인증을 쉽게 활성화할 수 " +"있습니다. 다음 터미널 명령을 사용하여 인증된 :code:`SuperNode`를 시작하세요:" #: ../../source/how-to-authenticate-supernodes.rst:66 msgid "" @@ -3773,10 +4387,14 @@ msgid "" "you can generate a private and public key pair using :code:`ssh-keygen -t" " ecdsa -b 384`." msgstr "" +":code:`--auth-supernode-private-key` 플래그는 노드의 개인 키 파일 경로를, " +":code:`--auth-supernode-public-key` 플래그는 노드의 공개 키 파일 경로를 " +"예상합니다. 개발 목적으로 :code:`ssh-keygen -t ecdsa -b 384`를 사용하여 개인 " +"및 공개 키 쌍을 생성할 수 있습니다." #: ../../source/how-to-authenticate-supernodes.rst:70 msgid "Security notice" -msgstr "" +msgstr "보안 공지" #: ../../source/how-to-authenticate-supernodes.rst:72 msgid "" @@ -3788,13 +4406,18 @@ msgid "" "communication is done in a secure manner, using trusted communication " "methods." msgstr "" +"시스템의 보안은 SuperLink와 각SuperNode의 자격 증명에 의존합니다. 따라서 " +"공개키 기반구조(PKI) 사칭 공격과 같은 보안 위험을 피하기 위해 자격 증명을 " +"보호하고 안전하게 보관하는 것이 필수적입니다. 노드 인증 메커니즘에는 사람의 " +"상호 작용도 포함되므로 모든 통신이 신뢰할 수 있는 통신 방법을 사용하여 " +"안전한 방식으로 이루어지도록 하세요." #: ../../source/how-to-authenticate-supernodes.rst:77 #: ../../source/how-to-enable-ssl-connections.rst:68 #: ../../source/how-to-use-built-in-mods.rst:85 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:287 msgid "Conclusion" -msgstr "" +msgstr "결론" #: ../../source/how-to-authenticate-supernodes.rst:79 msgid "" @@ -3803,10 +4426,14 @@ msgid "" "authentication enabled. You should also know the significance of the " "private key and store it safely to minimize security risks." msgstr "" +"이제 노드 인증이 활성화된 상태에서 장기간 실행되는 Flower " +"서버(:code:`SuperLink`)와 클라이언트(:code:`SuperNode`)를 시작하는 방법을 " +"배웠을 것입니다. 또한 보안 위험을 최소화하기 위해 개인키의 중요성을 알고 " +"안전하게 보관해야 합니다." #: ../../source/how-to-configure-clients.rst:2 msgid "Configure clients" -msgstr "" +msgstr "클라이언트 구성" #: ../../source/how-to-configure-clients.rst:4 msgid "" @@ -3815,10 +4442,13 @@ msgid "" " for example, a popular way to control client-side hyperparameters from " "the server." msgstr "" +"모델 파라미터와 함께 Flower는 설정 값을 클라이언트에 전송할 수 있습니다. " +"구성 값은 다양한 용도로 사용할 수 있습니다. 예를 들어 서버에서 클라이언트 측 " +"하이퍼파라미터를 제어하는 데 널리 사용되는 방법입니다." #: ../../source/how-to-configure-clients.rst:7 msgid "Configuration values" -msgstr "" +msgstr "구성 값" #: ../../source/how-to-configure-clients.rst:9 msgid "" @@ -3827,6 +4457,9 @@ msgid "" "float), ``int``, or ``str`` (or equivalent types in different languages)." " Here is an example of a configuration dictionary in Python:" msgstr "" +"구성 값은 ``str`` 키와 ``bool``, ``bytes``, ``double``(64비트 정밀도 정수), " +"``int`` 또는 ``str``(또는 다른 언어의 동등한 유형) 유형의 값으로 구성된 " +"사전으로 표현됩니다. 다음은 Python의 구성 사전 예제입니다:" #: ../../source/how-to-configure-clients.rst:20 msgid "" @@ -3834,6 +4467,9 @@ msgid "" "short) to their ProtoBuf representation, transports them to the client " "using gRPC, and then deserializes them back to Python dictionaries." msgstr "" +"Flower는 이러한 구성 dictionaries(또는 줄여서 *config dict*)를 ProtoBuf " +"표현으로 직렬화하고, gRPC를 사용하여 클라이언트로 전송한 다음 다시 Python " +"dictionaries로 역직렬화합니다." #: ../../source/how-to-configure-clients.rst:24 msgid "" @@ -3843,6 +4479,10 @@ msgid "" " by converting them to one of the supported value types (and converting " "them back on the client-side)." msgstr "" +"현재 구성 사전에서 컬렉션 유형(예: ``Set``, ``List``, ``Map``)을 값으로 직접 " +"전송하는 기능은 지원되지 않습니다. 컬렉션을 지원되는 값 유형 중 하나로 " +"변환한 다음 클라이언트 측에서 다시 변환하여 값으로 보내는 몇 가지 해결 " +"방법이 있습니다." #: ../../source/how-to-configure-clients.rst:26 msgid "" @@ -3851,10 +4491,13 @@ msgid "" " then convert the JSON string back to a list of floating-point numbers on" " the client." msgstr "" +"예를 들어 부동 소수점 숫자 목록을 JSON 문자열로 변환한 다음 구성 " +"dictionary을 사용하여 JSON 문자열을 전송한 다음 클라이언트에서 다시 부동 " +"소수점 숫자 목록으로 변환할 수 있습니다." #: ../../source/how-to-configure-clients.rst:30 msgid "Configuration through built-in strategies" -msgstr "" +msgstr "기본 제공 전략을 통한 구성" #: ../../source/how-to-configure-clients.rst:32 msgid "" @@ -3865,6 +4508,11 @@ msgid "" "the current round. It then forwards the configuration dictionary to all " "the clients selected during that round." msgstr "" +"클라이언트에 구성 값을 보내는 가장 쉬운 방법은 :code:`FedAvg`와 같은 기본 " +"제공 전략을 사용하는 것입니다. 기본 제공 전략은 소위 구성 함수를 지원합니다. " +"구성 함수는 내장 전략이 현재 단계의 구성 사전을 가져오기 위해 호출하는 " +"함수입니다. 그런 다음 해당 단계 동안 선택된 모든 클라이언트에 구성 사전을 " +"전달합니다." #: ../../source/how-to-configure-clients.rst:34 msgid "" @@ -3873,6 +4521,9 @@ msgid "" "federated learning, and (c) the number of epochs to train on the client-" "side. Our configuration function could look like this:" msgstr "" +"간단한 예부터 시작하겠습니다. (a) 클라이언트가 사용해야 하는 배치 크기, (b) " +"현재 글로벌 연합 federated 라운드, (c) 클라이언트 측에서 학습할 에포크 수를 " +"전송하고 싶다고 가정해 보겠습니다. 구성 함수는 다음과 같습니다:" #: ../../source/how-to-configure-clients.rst:47 msgid "" @@ -3880,10 +4531,12 @@ msgid "" "``FedAvg`` during initialization using the parameter " ":code:`on_fit_config_fn`:" msgstr "" +"기본 제공 전략이 이 함수를 사용하도록 하려면 초기화 중에 매개 변수 " +":code:`on_fit_config_fn`을 사용하여 ``FedAvg``에 이 함수를 전달하면 됩니다:" #: ../../source/how-to-configure-clients.rst:56 msgid "One the client side, we receive the configuration dictionary in ``fit``:" -msgstr "" +msgstr "클라이언트 측에서는 ``fit``으로 구성 dictionary을 받습니다:" #: ../../source/how-to-configure-clients.rst:67 msgid "" @@ -3892,6 +4545,9 @@ msgid "" " send different configuration values to `evaluate` (for example, to use a" " different batch size)." msgstr "" +"평가를 구성하는 `on_evaluate_config_fn`도 있으며, 같은 방식으로 작동합니다. " +"다른 배치 크기를 사용하기 위해 다른 구성 값을 `evaluate`로 보내려고 할 수 " +"있기 때문에 이 함수는 별도의 함수입니다." #: ../../source/how-to-configure-clients.rst:69 msgid "" @@ -3902,20 +4558,25 @@ msgid "" "hyperparameter schedule, for example, to increase the number of local " "epochs during later rounds, we could do the following:" msgstr "" +"기본 제공 전략은 매 라운드마다 이 함수를 호출합니다(즉, `Strategy." +"configure_fit` 또는 `Strategy.configure_evaluate`가 실행될 때마다). 매 " +"라운드마다 `on_evaluate_config_fn`을 호출하면 연속된 라운드에서 config " +"dict를 변경/변경할 수 있습니다. 예를 들어 이후 라운드에서 로컬 에포크 수를 " +"늘리기 위해 하이퍼파라미터 일정을 구현하려면 다음과 같이 할 수 있습니다:" #: ../../source/how-to-configure-clients.rst:82 msgid "The :code:`FedAvg` strategy will call this function *every round*." -msgstr "" +msgstr ":code:`FedAvg` 전략은 이 함수를 *매 라운드마다* 호출합니다." #: ../../source/how-to-configure-clients.rst:85 msgid "Configuring individual clients" -msgstr "" +msgstr "개별 클라이언트 구성" #: ../../source/how-to-configure-clients.rst:87 msgid "" "In some cases, it is necessary to send different configuration values to " "different clients." -msgstr "" +msgstr "경우에 따라 다른 구성 값을 다른 클라이언트에 보내야 하는 경우도 있습니다." #: ../../source/how-to-configure-clients.rst:89 msgid "" @@ -3927,10 +4588,16 @@ msgid "" "list, the other clients in this round to not receive this \"special\" " "config value):" msgstr "" +"이는 기존 전략을 사용자 지정하거나 :doc:`implementing a custom strategy from " +"scratch `를 통해 수행할 수 있습니다. 다음은 " +"사용자 지정 ``\"hello\"'를 추가하여 :code:`FedAvg`를 사용자 지정하는 " +"무의미한 예입니다: \"world\"`` 구성 키/값 쌍을 *단일 클라이언트*의 config " +"dict에 추가합니다(목록의 첫 번째 클라이언트만, 이 라운드의 다른 클라이언트는 " +"이 \"특별한\" 구성 값을 수신하지 않음):" #: ../../source/how-to-configure-logging.rst:2 msgid "Configure logging" -msgstr "" +msgstr "로깅 구성" #: ../../source/how-to-configure-logging.rst:4 msgid "" @@ -3938,6 +4605,8 @@ msgid "" "federated learning workloads. It presents information by default " "following a standard message format:" msgstr "" +"Flower 로거는 federated 학습 워크로드에서 발생하는 모든 핵심 이벤트를 " +"추적합니다. 기본적으로 표준 메시지 형식에 따라 정보를 표시합니다:" #: ../../source/how-to-configure-logging.rst:13 msgid "" @@ -3946,10 +4615,13 @@ msgid "" "took place from, as well as the log message itself. In this way, the " "logger would typically display information on your terminal as follows:" msgstr "" +"로그 메시지 수준(예: :code:`INFO`, :code:`DEBUG`), 타임스탬프, 로깅이 발생한 " +"줄, 로그 메시지 자체 등 관련 정보를 포함합니다. 이러한 방식으로 로거는 " +"일반적으로 다음과 같은 정보를 터미널에 표시합니다:" #: ../../source/how-to-configure-logging.rst:34 msgid "Saving log to file" -msgstr "" +msgstr "파일에 로그 저장" #: ../../source/how-to-configure-logging.rst:36 msgid "" @@ -3963,6 +4635,14 @@ msgid "" "`_" " function. For example:" msgstr "" +"기본적으로 Flower 로그는 Federated 학습 워크로드를 실행하는 터미널에 " +"출력됩니다. 이는 gRPC 기반 페더레이션(즉,:code:`fl.simulation." +"start_simulation`를 실행하는 경우)과 :code:`VirtualClientEngine`을 사용하는 " +"경우(즉, :코드:`fl.simulation.start_simulation`을 실행하는 경우) 모두에 " +"적용됩니다. 경우에 따라 이 로그를 디스크에 저장하고 싶을 수도 있습니다. 이 " +"경우 `fl.common.logger.configure() `_ 함수를 호출하여 저장할 수 있습니다. 예를 " +"들어:" #: ../../source/how-to-configure-logging.rst:53 msgid "" @@ -3971,10 +4651,14 @@ msgid "" "you are running the code from. If we inspect we see the log above is also" " recorded but prefixing with :code:`identifier` each line:" msgstr "" +"위와 같이 하면 Flower는 터미널에 표시되는 로그를 :code:`log.txt`에 " +"기록합니다. 이 파일은 코드를 실행한 디렉터리와 동일한 디렉터리에 생성됩니다. " +"검사해보면 위의 로그도 기록되지만 각 줄 앞에 :code:`identifier` 접두사가 " +"붙는 것을 확인할 수 있습니다:" #: ../../source/how-to-configure-logging.rst:74 msgid "Log your own messages" -msgstr "" +msgstr "나만의 메시지 기록" #: ../../source/how-to-configure-logging.rst:76 msgid "" @@ -3982,16 +4666,19 @@ msgid "" "by adding more messages relevant to your application. You can achieve " "this easily as follows." msgstr "" +"애플리케이션과 관련된 메시지를 더 추가하여 Flower 로거에 기본적으로 표시되는 " +"정보를 확장할 수 있습니다. 다음과 같이 쉽게 추가할 수 있습니다." #: ../../source/how-to-configure-logging.rst:102 msgid "" "In this way your logger will show, in addition to the default messages, " "the ones introduced by the clients as specified above." -msgstr "" +msgstr "이렇게 하면 로거에 기본 메시지 외에 위에서 지정한 대로 클라이언트가 소개한 " +"메시지가 표시됩니다." #: ../../source/how-to-configure-logging.rst:128 msgid "Log to a remote service" -msgstr "" +msgstr "원격 서비스에 로그인" #: ../../source/how-to-configure-logging.rst:130 msgid "" @@ -4005,10 +4692,17 @@ msgid "" ":code:`HTTPHandler` should you wish to backup or analyze the logs " "somewhere else." msgstr "" +"또한 :code:`fl.common.logger.configure` 함수를 사용하면 네이티브 Python " +":code:`logging.handler.HTTPHandler`를 통해 로그를 푸시할 수 있는 호스트를 " +"지정할 수 있습니다(:code:`POST`를 통해). 이는 모든 엔티티(예: 서버 및 " +"클라이언트)에서 로그를 수집하는 것이 번거로울 수 있는 :code:`gRPC` 기반 " +"Federated 학습 워크로드에서 특히 유용한 기능입니다. Flower 시뮬레이션에서는 " +"서버가 모든 로그를 자동으로 표시합니다. 로그를 다른 곳에 백업하거나 " +"분석하려는 경우 :code:`HTTPHandler`를 지정할 수 있습니다." #: ../../source/how-to-enable-ssl-connections.rst:2 msgid "Enable SSL connections" -msgstr "" +msgstr "SSL 연결 사용" #: ../../source/how-to-enable-ssl-connections.rst:4 msgid "" @@ -4016,6 +4710,9 @@ msgid "" "(:code:`SuperLink`) can be started and how a Flower client " "(:code:`SuperNode`) can establish a secure connections to it." msgstr "" +"이 가이드에서는 SSL을 지원하는 보안 Flower 서버(:코드:`SuperLink`)를 " +"시작하는 방법과 Flower 클라이언트(:코드:`SuperNode`)가 이 서버에 보안 연결을 " +"설정하는 방법을 설명합니다." #: ../../source/how-to-enable-ssl-connections.rst:7 msgid "" @@ -4023,6 +4720,8 @@ msgid "" "`here `_." msgstr "" +"보안 연결을 보여주는 전체 코드 예제는 '여기 `_'에서 확인할 수 있습니다." #: ../../source/how-to-enable-ssl-connections.rst:10 msgid "" @@ -4031,10 +4730,13 @@ msgid "" "descriptive on how it does so. Stick to this guide for a deeper " "introduction to the topic." msgstr "" +"코드 예제에는 시작 방법을 설명하는 :code:`README.md` 파일이 함께 제공됩니다. " +"이미 SSL을 사용하도록 설정되어 있지만 그 방법에 대한 설명이 부족할 수 " +"있습니다. 이 가이드를 참고하여 이 주제에 대해 자세히 알아보세요." #: ../../source/how-to-enable-ssl-connections.rst:16 msgid "Certificates" -msgstr "" +msgstr "인증서" #: ../../source/how-to-enable-ssl-connections.rst:18 msgid "" @@ -4044,12 +4746,18 @@ msgid "" "to ask you to run the script in :code:`examples/advanced-" "tensorflow/certificates/generate.sh` with the following command sequence:" msgstr "" +"SSL 사용 연결을 사용하려면 서버와 클라이언트에 인증서를 전달해야 합니다. 이 " +"가이드에서는 자체 서명된 인증서를 생성하겠습니다. 이 과정은 상당히 복잡할 수 " +"있으므로 다음 명령 시퀀스를 사용하여 :code:`examples/advanced-tensorflow/" +"certificates/generate.sh`에서 스크립트를 실행하도록 요청하겠습니다:" #: ../../source/how-to-enable-ssl-connections.rst:29 msgid "" "This will generate the certificates in :code:`examples/advanced-" "tensorflow/.cache/certificates`." msgstr "" +"이렇게 하면 :code:`examples/advanced-tensorflow/.cache/certificates`에 " +"인증서가 생성됩니다." #: ../../source/how-to-enable-ssl-connections.rst:31 msgid "" @@ -4061,39 +4769,48 @@ msgid "" "projects, it might be sufficient to use the self-signed certificates " "generated using the scripts mentioned in this guide." msgstr "" +"이 예의 맥락에서 SSL 인증서를 생성하는 접근 방식은 영감과 출발점이 될 수 " +"있지만 프로덕션 환경에 대한 참조로 사용해서는 안 됩니다. 프로덕션 환경용 " +"인증서를 올바르게 생성하는 문제에 대해서는 다른 출처를 참조하세요. 중요하지 " +"않은 프로토타이핑 또는 연구 프로젝트의 경우, 이 가이드에 언급된 스크립트를 " +"사용하여 생성한 자체 서명 인증서를 사용하는 것으로 충분할 수 있습니다." #: ../../source/how-to-enable-ssl-connections.rst:39 msgid "Server (SuperLink)" -msgstr "" +msgstr "서버(SuperLink)" #: ../../source/how-to-enable-ssl-connections.rst:41 msgid "" "Use the following terminal command to start a sever (SuperLink) that uses" " the previously generated certificates:" -msgstr "" +msgstr "다음 터미널 명령을 사용하여 이전에 생성한 인증서를 사용하는 서버(SuperLink)" +"를 시작합니다:" #: ../../source/how-to-enable-ssl-connections.rst:50 msgid "" "When providing certificates, the server expects a tuple of three " "certificates paths: CA certificate, server certificate and server private" " key." -msgstr "" +msgstr "인증서를 제공할 때 서버는 세 가지 인증서 경로의 튜플을 기대합니다: CA " +"인증서, 서버 인증서 및 서버 개인 키입니다." #: ../../source/how-to-enable-ssl-connections.rst:54 msgid "Client (SuperNode)" -msgstr "" +msgstr "클라이언트(SuperNode)" #: ../../source/how-to-enable-ssl-connections.rst:56 msgid "" "Use the following terminal command to start a client (SuperNode) that " "uses the previously generated certificates:" -msgstr "" +msgstr "다음 터미널 명령을 사용하여 이전에 생성한 인증서를 사용하는 " +"클라이언트(SuperNode)를 시작합니다:" #: ../../source/how-to-enable-ssl-connections.rst:64 msgid "" "When setting :code:`root_certificates`, the client expects a file path to" " PEM-encoded root certificates." -msgstr "" +msgstr "코드:`root_certificates`를 설정하면 클라이언트는 PEM 인코딩된 루트 인증서의 " +"파일 경로를 예상합니다." #: ../../source/how-to-enable-ssl-connections.rst:70 msgid "" @@ -4101,28 +4818,30 @@ msgid "" "using the given script, start an SSL-enabled server and have a client " "establish a secure connection to it." msgstr "" +"이제 주어진 스크립트를 사용하여 자체 서명 인증서를 생성하고, SSL 사용 서버를 " +"시작하고, 클라이언트가 보안 연결을 설정하는 방법을 배웠을 것입니다." #: ../../source/how-to-enable-ssl-connections.rst:75 msgid "Additional resources" -msgstr "" +msgstr "추가 리소스" #: ../../source/how-to-enable-ssl-connections.rst:77 msgid "" "These additional sources might be relevant if you would like to dive " "deeper into the topic of certificates:" -msgstr "" +msgstr "인증서에 대해 더 자세히 알아보고 싶다면 이러한 추가 자료를 참고하세요:" #: ../../source/how-to-enable-ssl-connections.rst:79 msgid "`Let's Encrypt `_" -msgstr "" +msgstr "'암호화하세요 `_'" #: ../../source/how-to-enable-ssl-connections.rst:80 msgid "`certbot `_" -msgstr "" +msgstr "인증봇 `_" #: ../../source/how-to-implement-strategies.rst:2 msgid "Implement strategies" -msgstr "" +msgstr "전략 구현" #: ../../source/how-to-implement-strategies.rst:4 msgid "" @@ -4133,10 +4852,15 @@ msgid "" "evaluate models. Flower provides a few built-in strategies which are " "based on the same API described below." msgstr "" +"전략 추상화를 통해 완전한 맞춤형 전략을 구현할 수 있습니다. 전략은 " +"기본적으로 서버에서 실행되는 federated 학습 알고리즘입니다. 전략은 " +"클라이언트를 샘플링하는 방법, 학습을 위해 클라이언트를 구성하는 방법, " +"업데이트를 집계하는 방법, 모델을 평가하는 방법을 결정합니다. Flower는 아래에 " +"설명된 것과 동일한 API를 기반으로 하는 몇 가지 기본 제공 전략을 제공합니다." #: ../../source/how-to-implement-strategies.rst:11 msgid "The :code:`Strategy` abstraction" -msgstr "" +msgstr ":code:`Strategy` 추상화" #: ../../source/how-to-implement-strategies.rst:13 msgid "" @@ -4146,12 +4870,15 @@ msgid "" "implementations have the exact same capabilities at their disposal as " "built-in ones." msgstr "" +"모든 전략 구현은 기본 제공 구현과 타사 구현 모두 추상 기본 클래스인 " +":code:`flwr.server.strategy.Strategy`에서 파생됩니다. 즉, 사용자 정의 전략 " +"구현은 기본 제공 구현과 완전히 동일한 기능을 사용할 수 있습니다." #: ../../source/how-to-implement-strategies.rst:18 msgid "" "The strategy abstraction defines a few abstract methods that need to be " "implemented:" -msgstr "" +msgstr "전략 추상화에서는 구현해야 하는 몇 가지 추상적인 메서드를 정의합니다:" #: ../../source/how-to-implement-strategies.rst:75 msgid "" @@ -4159,18 +4886,21 @@ msgid "" "from the abstract base class :code:`Strategy`) that implements for the " "previously shown abstract methods:" msgstr "" +"새 전략을 생성한다는 것은 이전에 표시된 추상 메서드에 대해 구현하는 새로운 " +":code:`class`(추상 기본 클래스 :code:`Strategy`에서 파생됨)를 구현하는 것을 " +"의미합니다:" #: ../../source/how-to-implement-strategies.rst:100 msgid "The Flower server calls these methods in the following order:" -msgstr "" +msgstr "Flower 서버는 다음 순서로 이러한 메서드를 호출합니다:" #: ../../source/how-to-implement-strategies.rst:177 msgid "The following sections describe each of those methods in more detail." -msgstr "" +msgstr "다음 섹션에서는 이러한 각 방법에 대해 자세히 설명합니다." #: ../../source/how-to-implement-strategies.rst:180 msgid "The :code:`initialize_parameters` method" -msgstr "" +msgstr ":code:`initialize_parameters` 메서드" #: ../../source/how-to-implement-strategies.rst:182 msgid "" @@ -4178,6 +4908,9 @@ msgid "" "of an execution. It is responsible for providing the initial global model" " parameters in a serialized form (i.e., as a :code:`Parameters` object)." msgstr "" +"code:`initialize_parameters`는 실행을 처음 시작할 때 한 번만 호출됩니다. 이 " +"함수는 초기 전역 모델 파라미터를 직렬화된 형식(즉, :code:`Parameters` 객체)" +"으로 제공하는 역할을 합니다." #: ../../source/how-to-implement-strategies.rst:184 msgid "" @@ -4185,6 +4918,8 @@ msgid "" "following example shows how initial parameters can be passed to " ":code:`FedAvg`:" msgstr "" +"기본 제공 전략은 사용자가 제공한 초기 매개 변수를 반환합니다. 다음 예는 초기 " +"매개 변수를 :code:`FedAvg`에 전달하는 방법을 보여줍니다:" #: ../../source/how-to-implement-strategies.rst:209 msgid "" @@ -4197,6 +4932,13 @@ msgid "" "useful for prototyping. In practice, it is recommended to always use " "server-side parameter initialization." msgstr "" +"Flower 서버는 :code:`initialize_parameters`를 호출하여 " +":code:`initial_parameters`에 전달된 파라미터를 반환하거나 :code:`None`을 " +"반환합니다. :code:`initial_parameters`에서 반환되는 매개변수가 없는 경우(즉, " +":code:`None`) 서버는 무작위로 클라이언트 하나를 선택하여 해당 클라이언트에 " +"매개변수를 제공하도록 요청합니다. 이는 편의 기능이며 실제로는 권장하지 " +"않지만 프로토타이핑에는 유용할 수 있습니다. 실제로는 항상 서버 측 매개변수 " +"초기화를 사용하는 것이 좋습니다." #: ../../source/how-to-implement-strategies.rst:213 msgid "" @@ -4206,10 +4948,14 @@ msgid "" "approaches, for example, to fine-tune a pre-trained model using federated" " learning." msgstr "" +"서버 측 파라미터 초기화는 강력한 메커니즘입니다. 예를 들어 이전에 저장한 " +"체크포인트에서 학습을 재개하는 데 사용할 수 있습니다. 또한 federated 학습을 " +"사용하여 사전 학습된 모델을 미세 조정하는 등 하이브리드 접근 방식을 구현하는 " +"데 필요한 기본 기능입니다." #: ../../source/how-to-implement-strategies.rst:216 msgid "The :code:`configure_fit` method" -msgstr "" +msgstr ":code:`configure_fit` 메서드" #: ../../source/how-to-implement-strategies.rst:218 msgid "" @@ -4218,6 +4964,10 @@ msgid "" "round means selecting clients and deciding what instructions to send to " "these clients. The signature of :code:`configure_fit` makes this clear:" msgstr "" +":code:`configure_fit`은 다가오는 학 라운드를 구성하는 역할을 합니다. 이 " +"문맥에서 *구성*은 무엇을 의미하나요? 라운드를 구성한다는 것은 클라이언트를 " +"선택하고 이 클라이언트에게 어떤 지침을 보낼지 결정하는 것을 의미합니다. " +"code:`configure_fit`의 시그니처를 보면 이를 명확히 알 수 있습니다:" #: ../../source/how-to-implement-strategies.rst:231 msgid "" @@ -4225,6 +4975,9 @@ msgid "" "that will be sent to a particular client. Strategy implementations " "usually perform the following steps in :code:`configure_fit`:" msgstr "" +"반환 값은 튜플 목록으로, 각 튜플은 특정 클라이언트로 전송될 instruction을 " +"나타냅니다. 전략 구현은 일반적으로 :code:`configure_fit`에서 다음 단계를 " +"수행합니다:" #: ../../source/how-to-implement-strategies.rst:233 #: ../../source/how-to-implement-strategies.rst:280 @@ -4232,12 +4985,16 @@ msgid "" "Use the :code:`client_manager` to randomly sample all (or a subset of) " "available clients (each represented as a :code:`ClientProxy` object)" msgstr "" +":code:`client_manager`를 사용하여 사용 가능한 모든 클라이언트(또는 그 하위 " +"집합)를 무작위로 샘플링합니다(각각 :code:`ClientProxy` 개체로 표시됨)" #: ../../source/how-to-implement-strategies.rst:234 msgid "" "Pair each :code:`ClientProxy` with the same :code:`FitIns` holding the " "current global model :code:`parameters` and :code:`config` dict" msgstr "" +"각 :code:`ClientProxy`를 현재 글로벌 모델 :code:`parameters` 및 " +":code:`config` dict를 보유한 동일한 :code:`FitIns`와 쌍을 이룹니다" #: ../../source/how-to-implement-strategies.rst:236 msgid "" @@ -4246,6 +5003,9 @@ msgid "" "in a round if the corresponding :code:`ClientProxy` is included in the " "list returned from :code:`configure_fit`." msgstr "" +"보다 정교한 구현은 :code:`configure_fit`을 사용하여 사용자 지정 클라이언트 " +"선택 로직을 구현할 수 있습니다. 클라이언트는 :code:`configure_fit`에서 " +"반환된 목록에 해당 :code:`ClientProxy`가 포함된 경우에만 라운드에 참여합니다." #: ../../source/how-to-implement-strategies.rst:240 msgid "" @@ -4256,10 +5016,15 @@ msgid "" "different hyperparameters on different clients (via the :code:`config` " "dict)." msgstr "" +"이 반환 값의 구조는 사용자에게 많은 유연성을 제공합니다. instructions은 " +"클라이언트별로 정의되므로 각 클라이언트에 서로 다른 instructions을 전송할 수 " +"있습니다. 이를 통해 예를 들어 클라이언트마다 다른 모델을 학습시키거나 " +"클라이언트마다 다른 하이퍼파라미터를 사용하는 사용자 지정 전략을 사용할 수 " +"있습니다(:code:`config` dict를 통해)." #: ../../source/how-to-implement-strategies.rst:243 msgid "The :code:`aggregate_fit` method" -msgstr "" +msgstr ":code:`aggregate_fit` 메서드" #: ../../source/how-to-implement-strategies.rst:245 msgid "" @@ -4267,6 +5032,8 @@ msgid "" " by the clients that were selected and asked to train in " ":code:`configure_fit`." msgstr "" +"code:`aggregate_fit`은 :code:`configure_fit`에서 훈련하도록 선택되고 요청된 " +"클라이언트가 반환한 결과를 집계하는 역할을 담당합니다." #: ../../source/how-to-implement-strategies.rst:258 msgid "" @@ -4275,6 +5042,10 @@ msgid "" ":code:`configure_fit`). :code:`aggregate_fit` therefore receives a list " "of :code:`results`, but also a list of :code:`failures`." msgstr "" +"물론 실패가 발생할 수 있으므로 서버가 명령을 보낸 모든 클라이언트로부터 " +"결과를 얻을 수 있다는 보장은 없습니다(:code:`configure_fit`을 통해). 따라서 " +":code:`aggregate_fit`은 :code:`results` 목록뿐만 아니라 :code:`failures` " +"목록도 받습니다." #: ../../source/how-to-implement-strategies.rst:260 msgid "" @@ -4283,10 +5054,14 @@ msgid "" " optional because :code:`aggregate_fit` might decide that the results " "provided are not sufficient for aggregation (e.g., too many failures)." msgstr "" +"code:`aggregate_fit`은 선택적 :code:`Parameters` 개체와 집계된 메트릭의 " +"dictionary를 반환합니다. :code:`Parameters` 반환 값은 :code:`aggregate_fit`" +"이 제공된 결과가 집계에 충분하지 않다고 판단할 수 있으므로(예: 실패 수가 " +"너무 많음) 선택 사항입니다." #: ../../source/how-to-implement-strategies.rst:263 msgid "The :code:`configure_evaluate` method" -msgstr "" +msgstr ":code:`configure_evaluate` 메서드" #: ../../source/how-to-implement-strategies.rst:265 msgid "" @@ -4296,6 +5071,11 @@ msgid "" "instructions to send to these clients. The signature of " ":code:`configure_evaluate` makes this clear:" msgstr "" +":code:`configure_evaluate`는 다가오는 평가 라운드를 구성하는 역할을 합니다. " +"이 문맥에서 *구성*은 무엇을 의미하나요? 라운드를 구성한다는 것은 " +"클라이언트를 선택하고 이러한 클라이언트에 전송할 지침을 결정하는 것을 " +"의미합니다. :code:`configure_evaluate`의 시그니처를 보면 이를 명확히 알 수 " +"있습니다:" #: ../../source/how-to-implement-strategies.rst:278 msgid "" @@ -4303,12 +5083,17 @@ msgid "" "that will be sent to a particular client. Strategy implementations " "usually perform the following steps in :code:`configure_evaluate`:" msgstr "" +"반환 값은 튜플 목록으로, 각 튜플은 특정 클라이언트로 전송될 instructions을 " +"나타냅니다. 전략 구현은 일반적으로 :code:`configure_evaluate`에서 다음 " +"단계를 수행합니다:" #: ../../source/how-to-implement-strategies.rst:281 msgid "" "Pair each :code:`ClientProxy` with the same :code:`EvaluateIns` holding " "the current global model :code:`parameters` and :code:`config` dict" msgstr "" +"각 :code:`ClientProxy`를 현재 글로벌 모델 :code:`parameters` 및 " +":code:`config` dict를 보유한 동일한 :code:`EvaluateIns`와 쌍을 이룹니다" #: ../../source/how-to-implement-strategies.rst:283 msgid "" @@ -4317,6 +5102,10 @@ msgid "" "in a round if the corresponding :code:`ClientProxy` is included in the " "list returned from :code:`configure_evaluate`." msgstr "" +"보다 정교한 구현은 :code:`configure_evaluate`를 사용하여 사용자 지정 " +"클라이언트 선택 로직을 구현할 수 있습니다. 클라이언트는 " +":code:`configure_evaluate`에서 반환된 목록에 해당 :code:`ClientProxy`가 " +"포함된 경우에만 라운드에 참여합니다." #: ../../source/how-to-implement-strategies.rst:287 msgid "" @@ -4327,10 +5116,15 @@ msgid "" "different hyperparameters on different clients (via the :code:`config` " "dict)." msgstr "" +"이 반환 값의 구조는 사용자에게 많은 유연성을 제공합니다. instructions은 " +"클라이언트별로 정의되므로 각 클라이언트에 서로 다른 instructions을 전송할 수 " +"있습니다. 이를 통해 사용자 지정 전략을 통해 예를 들어 클라이언트마다 다른 " +"모델을 평가하거나 클라이언트마다 다른 하이퍼파라미터를 사용할 수 " +"있습니다(:code:`config` dict를 통해)." #: ../../source/how-to-implement-strategies.rst:291 msgid "The :code:`aggregate_evaluate` method" -msgstr "" +msgstr ":code:`aggregate_evaluate` 메서드" #: ../../source/how-to-implement-strategies.rst:293 msgid "" @@ -4338,6 +5132,8 @@ msgid "" "returned by the clients that were selected and asked to evaluate in " ":code:`configure_evaluate`." msgstr "" +"code:`aggregate_evaluate`는 :code:`configure_evaluate`에서 선택되어 평가를 " +"요청한 클라이언트가 반환한 결과를 집계하는 역할을 담당합니다." #: ../../source/how-to-implement-strategies.rst:306 msgid "" @@ -4346,6 +5142,10 @@ msgid "" ":code:`configure_evaluate`). :code:`aggregate_evaluate` therefore " "receives a list of :code:`results`, but also a list of :code:`failures`." msgstr "" +"물론 실패가 발생할 수 있으므로 서버가 명령을 보낸 모든 클라이언트로부터 " +"결과를 얻을 수 있다는 보장은 없습니다(:code:`configure_evaluate`를 통해). " +"따라서 :code:`aggregate_evaluate`는 :code:`results` 목록뿐만 아니라 " +":code:`failures` 목록도 받습니다." #: ../../source/how-to-implement-strategies.rst:308 msgid "" @@ -4354,10 +5154,14 @@ msgid "" "optional because :code:`aggregate_evaluate` might decide that the results" " provided are not sufficient for aggregation (e.g., too many failures)." msgstr "" +"code:`aggregate_evaluate`는 선택적 :code:`float`(손실)와 집계된 메트릭의 " +"dictionary를 반환합니다. code:`float` 반환 값은 :code:`aggregate_evaluate`가 " +"제공된 결과가 집계에 충분하지 않다고 판단할 수 있으므로(예: 실패 수가 너무 " +"많음) 선택 사항입니다." #: ../../source/how-to-implement-strategies.rst:311 msgid "The :code:`evaluate` method" -msgstr "" +msgstr ":code:`evaluate` 메서드" #: ../../source/how-to-implement-strategies.rst:313 msgid "" @@ -4366,6 +5170,10 @@ msgid "" ":code:`configure_evaluate`/:code:`aggregate_evaluate` enables strategies " "to perform both servers-side and client-side (federated) evaluation." msgstr "" +":code:`evaluate`는 서버 측에서 모델 매개변수를 평가하는 역할을 담당합니다. " +"code:`configure_evaluate`/:code:`aggregate_evaluate`와 함께 :code:`evaluate`" +"를 사용하면 서버 측과 클라이언트 측(federated) 평가를 모두 수행할 수 있는 " +"전략을 사용할 수 있습니다." #: ../../source/how-to-implement-strategies.rst:323 msgid "" @@ -4374,63 +5182,69 @@ msgid "" ":code:`evaluate` method might not complete successfully (e.g., it might " "fail to load the server-side evaluation data)." msgstr "" +"반환 값은 전략에서 서버 측 평가를 구현할 필요가 없거나 사용자 정의 " +":code:`evaluate` 메서드가 성공적으로 완료되지 않을 수 있기 때문에(예: 서버 " +"측 평가 데이터를 로드하지 못할 수 있음) 다시 선택 사항으로 설정할 수 " +"있습니다." #: ../../source/how-to-install-flower.rst:2 msgid "Install Flower" -msgstr "" +msgstr "Flower 설치" #: ../../source/how-to-install-flower.rst:6 msgid "Python version" -msgstr "" +msgstr "Python 버전" #: ../../source/how-to-install-flower.rst:12 msgid "Install stable release" -msgstr "" +msgstr "안정적인 릴리즈 설치" #: ../../source/how-to-install-flower.rst:15 #: ../../source/how-to-upgrade-to-flower-next.rst:46 msgid "Using pip" -msgstr "" +msgstr "pip 사용" #: ../../source/how-to-install-flower.rst:17 msgid "" "Stable releases are available on `PyPI " "`_::" -msgstr "" +msgstr "안정적인 릴리즈는 `PyPI `_:: 에서 확인할 수 " +"있습니다::" #: ../../source/how-to-install-flower.rst:21 msgid "" "For simulations that use the Virtual Client Engine, ``flwr`` should be " "installed with the ``simulation`` extra::" -msgstr "" +msgstr "가상 클라이언트 엔진을 사용하는 시뮬레이션의 경우 ``flwr``을 ``simulation``" +"extra와 함께 설치해야 합니다:" #: ../../source/how-to-install-flower.rst:27 msgid "Using conda (or mamba)" -msgstr "" +msgstr "conda(또는 mamba) 사용" #: ../../source/how-to-install-flower.rst:29 msgid "Flower can also be installed from the ``conda-forge`` channel." -msgstr "" +msgstr "Flower은 'conda-forge' 채널에서도 설치할 수 있습니다." #: ../../source/how-to-install-flower.rst:31 msgid "" "If you have not added ``conda-forge`` to your channels, you will first " "need to run the following::" -msgstr "" +msgstr "채널에 'conda-forge'를 추가하지 않은 경우 먼저 다음을 실행해야 합니다:" #: ../../source/how-to-install-flower.rst:36 msgid "" "Once the ``conda-forge`` channel has been enabled, ``flwr`` can be " "installed with ``conda``::" -msgstr "" +msgstr "conda-forge`` 채널이 활성화되면 ``flwr``을 ``conda``로 설치할 수 있습니다::" #: ../../source/how-to-install-flower.rst:40 msgid "or with ``mamba``::" -msgstr "" +msgstr "또는 ``mamba``::" #: ../../source/how-to-install-flower.rst:46 msgid "Verify installation" -msgstr "" +msgstr "설치 확인" #: ../../source/how-to-install-flower.rst:48 msgid "" @@ -4438,22 +5252,25 @@ msgid "" "installed. If everything worked, it should print the version of Flower to" " the command line::" msgstr "" +"다음 명령을 사용하여 Flower가 성공적으로 설치되었는지 확인할 수 있습니다. " +"모든 것이 정상적으로 작동하면 명령줄에 Flower의 버전이 출력됩니다:" #: ../../source/how-to-install-flower.rst:55 msgid "Advanced installation options" -msgstr "" +msgstr "고급 설치 옵션" #: ../../source/how-to-install-flower.rst:58 msgid "Install via Docker" -msgstr "" +msgstr "Docker를 통해 설치" #: ../../source/how-to-install-flower.rst:60 msgid ":doc:`How to run Flower using Docker `" -msgstr "" +msgstr ":doc:`Docker를 사용하여 Flower를 실행하는 방법 `" #: ../../source/how-to-install-flower.rst:63 msgid "Install pre-release" -msgstr "" +msgstr "사전 릴리즈 설치" #: ../../source/how-to-install-flower.rst:65 msgid "" @@ -4461,32 +5278,39 @@ msgid "" "pre-release versions (alpha, beta, release candidate) before the stable " "release happens::" msgstr "" +"새(불안정할 수 있는) 버전의 Flower는 안정 버전이 출시되기 전에 사전 릴리즈 " +"버전(알파, 베타, 릴리즈 후보)으로 제공되는 경우가 있습니다:" #: ../../source/how-to-install-flower.rst:69 msgid "" "For simulations that use the Virtual Client Engine, ``flwr`` pre-releases" " should be installed with the ``simulation`` extra::" msgstr "" +"가상 클라이언트 엔진을 사용하는 시뮬레이션의 경우 ``flwr`` 사전 릴리즈를 " +"``simulation`` extra와 함께 설치해야 합니다:" #: ../../source/how-to-install-flower.rst:74 msgid "Install nightly release" -msgstr "" +msgstr "야간 릴리즈 설치" #: ../../source/how-to-install-flower.rst:76 msgid "" "The latest (potentially unstable) changes in Flower are available as " "nightly releases::" -msgstr "" +msgstr "Flower의 최신 (불안정할 수 있는) 변경 사항은 다음과 같이 야간 릴리즈로 " +"제공됩니다:" #: ../../source/how-to-install-flower.rst:80 msgid "" "For simulations that use the Virtual Client Engine, ``flwr-nightly`` " "should be installed with the ``simulation`` extra::" msgstr "" +"가상 클라이언트 엔진을 사용하는 시뮬레이션의 경우, ``flwr-nightly``를 " +"``simulation`` extr와 함께 설치해야 합니다::" #: ../../source/how-to-monitor-simulation.rst:2 msgid "Monitor simulation" -msgstr "" +msgstr "모니터 시뮬레이션" #: ../../source/how-to-monitor-simulation.rst:4 msgid "" @@ -4496,16 +5320,22 @@ msgid "" "constrain the total usage. Insights from resource consumption can help " "you make smarter decisions and speed up the execution time." msgstr "" +"Flower를 사용하면 시뮬레이션을 실행하는 동안 시스템 리소스를 모니터링할 수 " +"있습니다. 또한 Flower 시뮬레이션 엔진은 강력하며 클라이언트별 리소스 할당 " +"방법을 결정하고 총 사용량을 제한할 수 있습니다. 리소스 소비에 대한 " +"인사이트를 통해 더 현명한 결정을 내리고 실행 시간을 단축할 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:6 msgid "" "The specific instructions assume you are using macOS and have the " "`Homebrew `_ package manager installed." msgstr "" +"구체적인 지침은 macOS를 사용 중이고 'Homebrew `_ 패키지 " +"관리자가 설치되어 있다고 가정합니다." #: ../../source/how-to-monitor-simulation.rst:10 msgid "Downloads" -msgstr "" +msgstr "다운로드" #: ../../source/how-to-monitor-simulation.rst:16 msgid "" @@ -4514,26 +5344,31 @@ msgid "" "collected data. They are both well integrated with `Ray " "`_ which Flower uses under the hood." msgstr "" +"`Prometheus `_는 데이터 수집에 사용되며, `Grafana " +"`_는 수집된 데이터를 시각화할 수 있게 해줍니다. 이 두 " +"도구는 모두 Flower가 내부적으로 사용하는 `Ray `_와 잘 " +"통합되어 있습니다." #: ../../source/how-to-monitor-simulation.rst:18 msgid "" "Overwrite the configuration files (depending on your device, it might be " "installed on a different path)." -msgstr "" +msgstr "구성 파일을 덮어씁니다(장치에 따라 다른 경로에 설치되어 있을 수 있음)." #: ../../source/how-to-monitor-simulation.rst:20 msgid "If you are on an M1 Mac, it should be:" -msgstr "" +msgstr "M1 Mac을 사용 중이라면:" #: ../../source/how-to-monitor-simulation.rst:27 msgid "On the previous generation Intel Mac devices, it should be:" -msgstr "" +msgstr "이전 세대 Intel Mac 장치에서는:" #: ../../source/how-to-monitor-simulation.rst:34 msgid "" "Open the respective configuration files and change them. Depending on " "your device, use one of the two following commands:" -msgstr "" +msgstr "각 구성 파일을 열고 변경합니다. 장치에 따라 다음 두 명령 중 하나를 " +"사용합니다:" #: ../../source/how-to-monitor-simulation.rst:44 msgid "" @@ -4541,6 +5376,8 @@ msgid "" "config you see below. You may adjust the time intervals to your " "requirements:" msgstr "" +"를 입력한 다음 파일의 모든 텍스트를 삭제하고 아래에 표시된 새 Prometheus " +"설정을 붙여넣습니다. 요구 사항에 따라 시간 간격을 조정할 수 있습니다:" #: ../../source/how-to-monitor-simulation.rst:59 msgid "" @@ -4548,54 +5385,59 @@ msgid "" "the Grafana configuration files. Open those using one of the following " "commands as before:" msgstr "" +"이제 Prometheus 구성을 편집한 후 Grafana 구성 파일에 대해서도 동일한 작업을 " +"수행합니다. 이전과 마찬가지로 다음 명령 중 하나를 사용하여 파일을 엽니다:" #: ../../source/how-to-monitor-simulation.rst:69 msgid "" "Your terminal editor should open and allow you to apply the following " "configuration as before." -msgstr "" +msgstr "터미널 편집기가 열리면 이전과 마찬가지로 다음 구성을 적용할 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:84 msgid "" "Congratulations, you just downloaded all the necessary software needed " "for metrics tracking. Now, let’s start it." -msgstr "" +msgstr "축하합니다. 매트릭 트레킹에 필요한 모든 소프트웨어를 다운로드하셨습니다. " +"이제 시작해 보겠습니다." #: ../../source/how-to-monitor-simulation.rst:88 msgid "Tracking metrics" -msgstr "" +msgstr "매트릭 트래킹" #: ../../source/how-to-monitor-simulation.rst:90 msgid "" "Before running your Flower simulation, you have to start the monitoring " "tools you have just installed and configured." -msgstr "" +msgstr "Flower 시뮬레이션을 실행하기 전에 방금 설치 및 구성한 모니터링 도구를 " +"시작해야 합니다." #: ../../source/how-to-monitor-simulation.rst:97 msgid "" "Please include the following argument in your Python code when starting a" " simulation." -msgstr "" +msgstr "시뮬레이션을 시작할 때 Python 코드에 다음 argument를 포함하세요." #: ../../source/how-to-monitor-simulation.rst:108 msgid "Now, you are ready to start your workload." -msgstr "" +msgstr "이제 워크로드를 시작할 준비가 되었습니다." #: ../../source/how-to-monitor-simulation.rst:110 msgid "" "Shortly after the simulation starts, you should see the following logs in" " your terminal:" -msgstr "" +msgstr "시뮬레이션이 시작되고 얼마 지나지 않아 터미널에 다음 로그가 표시됩니다:" #: ../../source/how-to-monitor-simulation.rst:117 msgid "You can look at everything at ``_ ." -msgstr "" +msgstr "``_ 에서 모든 것을 볼 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:119 msgid "" "It's a Ray Dashboard. You can navigate to Metrics (on the left panel, the" " lowest option)." -msgstr "" +msgstr "Ray 대시보드입니다. 메트릭(왼쪽 패널의 가장 아래 옵션)으로 이동할 수 " +"있습니다." #: ../../source/how-to-monitor-simulation.rst:121 msgid "" @@ -4605,6 +5447,11 @@ msgid "" "can only use Grafana to explore the metrics. You can start Grafana by " "going to ``http://localhost:3000/``." msgstr "" +"또는 오른쪽 위 모서리인 \"Grafana에서 보기\"를 클릭하여 Grafana에서 바로 " +"확인할 수도 있습니다. Ray 대시보드는 시뮬레이션 중에만 액세스할 수 있다는 " +"점에 유의하세요. 시뮬레이션이 종료된 후에는 Grafana를 사용하여 메트릭을 " +"탐색할 수만 있습니다. ``http://localhost:3000/``로 이동하여 Grafana를 시작할 " +"수 있습니다." #: ../../source/how-to-monitor-simulation.rst:123 msgid "" @@ -4612,16 +5459,19 @@ msgid "" "important as they will otherwise block, for example port :code:`3000` on " "your machine as long as they are running." msgstr "" +"시각화를 완료한 후에는 Prometheus와 Grafana를 중지합니다. 그렇지 않으면 실행 " +"중인 동안 컴퓨터에서 포트 :code:`3000` 등을 차단하므로 이 작업이 중요합니다." #: ../../source/how-to-monitor-simulation.rst:132 msgid "Resource allocation" -msgstr "" +msgstr "리소스 할당" #: ../../source/how-to-monitor-simulation.rst:134 msgid "" "You must understand how the Ray library works to efficiently allocate " "system resources to simulation clients on your own." -msgstr "" +msgstr "Ray 라이브러리가 어떻게 작동하는지 이해해야 시뮬레이션 클라이언트에 시스템 " +"리소스를 효율적으로 할당할 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:136 msgid "" @@ -4632,20 +5482,27 @@ msgid "" "You will learn more about that in the later part of this blog. You can " "check the system resources by running the following:" msgstr "" +"처음에 시뮬레이션(Ray가 내부에서 처리하는)은 기본적으로 시스템에서 사용 " +"가능한 모든 리소스를 사용하여 시작되며, 이 리소스는 클라이언트 간에 " +"공유됩니다. 그렇다고 해서 모든 클라이언트에게 균등하게 분배하거나 모든 " +"클라이언트에서 동시에 모델 학습이 이루어지는 것은 아닙니다. 이에 대한 자세한 " +"내용은 이 블로그의 뒷부분에서 설명합니다. 다음을 실행하여 시스템 리소스를 " +"확인할 수 있습니다:" #: ../../source/how-to-monitor-simulation.rst:143 msgid "In Google Colab, the result you see might be similar to this:" -msgstr "" +msgstr "Google Colab에서는 이와 유사한 결과가 표시될 수 있습니다:" #: ../../source/how-to-monitor-simulation.rst:155 msgid "" "However, you can overwrite the defaults. When starting a simulation, do " "the following (you don't need to overwrite all of them):" -msgstr "" +msgstr "그러나 기본값을 덮어쓸 수 있습니다. 시뮬레이션을 시작할 때 다음을 수행합니다(" +"모두 덮어쓸 필요는 없음):" #: ../../source/how-to-monitor-simulation.rst:175 msgid "Let’s also specify the resource for a single client." -msgstr "" +msgstr "단일 클라이언트에 대한 리소스도 지정해 보겠습니다." #: ../../source/how-to-monitor-simulation.rst:205 msgid "" @@ -4653,6 +5510,8 @@ msgid "" "all the required resources (such that they run in parallel) when the " "resources allow." msgstr "" +"이제 중요한 부분이 나옵니다. Ray는 리소스가 허용하는 경우에만 필요한 모든 " +"리소스가 있을 때(병렬로 실행되는 등) 새 클라이언트를 시작합니다." #: ../../source/how-to-monitor-simulation.rst:207 msgid "" @@ -4663,14 +5522,20 @@ msgid "" ":code:`client_num_gpus = 2`, the simulation wouldn't start (even if you " "had 2 GPUs but decided to set 1 in :code:`ray_init_args`)." msgstr "" +"위의 예에서는 하나의 클라이언트만 실행되므로 클라이언트가 동시에 실행되지 " +"않습니다. :code:`client_num_gpus = 0.5` 를 설정하면 두 개의 클라이언트를 " +"실행할 수 있으므로 동시에 실행할 수 있습니다. 사용 가능한 리소스보다 더 많은 " +"리소스를 요구하지 않도록 주의하세요. :code:`client_num_gpus = 2`를 지정하면 " +"시뮬레이션이 시작되지 않습니다(GPU가 2개이지만 :code:`ray_init_args`에서 " +"1개를 설정한 경우에도 마찬가지입니다)." #: ../../source/how-to-monitor-simulation.rst:212 ../../source/ref-faq.rst:2 msgid "FAQ" -msgstr "" +msgstr "자주 묻는 질문" #: ../../source/how-to-monitor-simulation.rst:214 msgid "Q: I don't see any metrics logged." -msgstr "" +msgstr "질문: 기록된 메트릭이 보이지 않습니다." #: ../../source/how-to-monitor-simulation.rst:216 msgid "" @@ -4678,6 +5543,9 @@ msgid "" "right corner (\"Last 30 minutes\" by default). Please change the " "timeframe to reflect the period when the simulation was running." msgstr "" +"A: 기간이 제대로 설정되지 않았을 수 있습니다. 설정은 오른쪽 상단에 있습니다(" +"기본값은 '지난 30분'). 시뮬레이션이 실행된 기간을 반영하도록 기간을 변경해 " +"주세요." #: ../../source/how-to-monitor-simulation.rst:218 msgid "" @@ -4685,42 +5553,49 @@ msgid "" "server is running and refresh this page” after going to the Metrics tab " "in Ray Dashboard." msgstr "" +"질문: \"Grafana 서버가 감지되지 않았습니다. Ray 대시보드의 메트릭 탭으로 " +"이동한 후 Grafana 서버가 실행 중인지 확인하고 이 페이지를 새로고침하세요." +"\"라는 메시지가 표시됩니다." #: ../../source/how-to-monitor-simulation.rst:220 msgid "" "A: You probably don't have Grafana running. Please check the running " "services" -msgstr "" +msgstr "A: Grafana가 실행되고 있지 않을 수 있습니다. 실행 중인 서비스를 확인하세요" #: ../../source/how-to-monitor-simulation.rst:226 msgid "" "Q: I see \"This site can't be reached\" when going to " "``_." msgstr "" +"Q: ``_로 이동할 때 \"이 사이트에 연결할 수 없습니다." +"\"라는 메시지가 표시됩니다." #: ../../source/how-to-monitor-simulation.rst:228 msgid "" "A: Either the simulation has already finished, or you still need to start" " Prometheus." -msgstr "" +msgstr "A: 시뮬레이션이 이미 완료되었거나 아직 Prometheus를 시작해야 합니다." #: ../../source/how-to-monitor-simulation.rst:232 msgid "Resources" -msgstr "" +msgstr "리소스" #: ../../source/how-to-monitor-simulation.rst:234 msgid "" "Ray Dashboard: ``_" msgstr "" +"Ray 대시보드: ``_" #: ../../source/how-to-monitor-simulation.rst:236 msgid "Ray Metrics: ``_" -msgstr "" +msgstr "Ray 메트릭: ``_" #: ../../source/how-to-run-flower-using-docker.rst:2 msgid "Run Flower using Docker" -msgstr "" +msgstr "Docker를 사용하여 Flower 실행" #: ../../source/how-to-run-flower-using-docker.rst:4 msgid "" @@ -4729,10 +5604,13 @@ msgid "" "`__. Supported architectures include " "``amd64`` and ``arm64v8``." msgstr "" +"Flower를 시작하는 가장 간단한 방법은 `Docker Hub `__에서 찾을 수 있는 미리 만들어진 Docker 이미지를 사용하는 것입니다. " +"지원되는 아키텍처는 ``amd64`` 및 ``arm64v8``입니다." #: ../../source/how-to-run-flower-using-docker.rst:8 msgid "Before you start, make sure that the Docker daemon is running:" -msgstr "" +msgstr "시작하기 전에 Docker daemon이 실행 중인지 확인하세요:" #: ../../source/how-to-run-flower-using-docker.rst:15 msgid "" @@ -4741,6 +5619,9 @@ msgid "" "You can find installation instruction `here `_." msgstr "" +"전이 표시되지 않고 대신 명령을 찾을 수 없다는 오류가 표시되는 경우 먼저 " +"Docker를 설치해야 합니다. `여기 `_에서 " +"설치 지침을 찾을 수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:21 msgid "" @@ -4749,6 +5630,9 @@ msgid "" "`_ on the " "official Docker website." msgstr "" +"Linux에서 Docker 명령을 실행하려면 ``sudo`` 권한이 필요합니다. sudo``를 " +"사용하지 않으려면 공식 Docker 웹사이트의 `Post-installation steps " +"`_를 따르세요." #: ../../source/how-to-run-flower-using-docker.rst:27 msgid "" @@ -4757,18 +5641,22 @@ msgid "" "This guarantees seamless integration and avoids potential conflicts or " "issues that may arise from using different versions." msgstr "" +"최적의 성능과 호환성을 보장하려면 SuperLink, SuperNode 및 ServerApp 이미지를 " +"함께 실행할 때 버전이 동일해야 합니다. 이렇게 하면 원활한 통합을 보장하고 " +"서로 다른 버전을 사용할 때 발생할 수 있는 잠재적인 충돌이나 문제를 방지할 수 " +"있습니다." #: ../../source/how-to-run-flower-using-docker.rst:32 msgid "Flower SuperLink" -msgstr "" +msgstr "Flower SuperLink" #: ../../source/how-to-run-flower-using-docker.rst:35 msgid "Quickstart" -msgstr "" +msgstr "빠른 시작" #: ../../source/how-to-run-flower-using-docker.rst:37 msgid "If you're looking to try out Flower, you can use the following command:" -msgstr "" +msgstr "Flower를 사용해보고 싶다면 다음 명령을 사용하면 됩니다:" #: ../../source/how-to-run-flower-using-docker.rst:43 msgid "" @@ -4776,6 +5664,9 @@ msgid "" "Hub. The tag specifies the Flower version. In this case, Flower 1.8.0. " "The ``--rm`` flag tells Docker to remove the container after it exits." msgstr "" +"이 명령은 Docker Hub에서 ``1.8.0`` 태그가 있는 Docker 이미지를 가져옵니다. " +"이 태그는 Flower 버전을 지정합니다. 이 경우, Flower 1.8.0입니다. '`--rm`` " +"플래그는 컨테이너가 종료된 후 컨테이너를 제거하도록 Docker에 지시합니다." #: ../../source/how-to-run-flower-using-docker.rst:49 msgid "" @@ -4784,6 +5675,9 @@ msgid "" "starts. We will show below how to save the state in a file on your host " "system." msgstr "" +"기본적으로 Flower SuperLink는 상태를 in-memory에 유지합니다. Docker 플래그 " +"`--rm``을 사용하는 경우 컨테이너 시작 사이에 상태가 유지되지 않습니다. " +"아래에서 호스트 시스템의 파일에 상태를 저장하는 방법을 보여드리겠습니다." #: ../../source/how-to-run-flower-using-docker.rst:53 msgid "" @@ -4794,6 +5688,11 @@ msgid "" "after the tag is passed to the Flower SuperLink. Here, we are passing the" " flag ``--insecure``." msgstr "" +"``-p :`` 플래그는 호스트의 포트 ``9091``/``9092``를 " +"컨테이너의 ``9091``/``9092``에 매핑하여 ``http://localhost:9091``의 드라이버 " +"API와 ``http://localhost:9092``의 Fleet API에 액세스할 수 있도록 Docker에 " +"지시합니다. 마지막으로, 태그 뒤에 오는 모든 플래그는 Flower SuperLink에 " +"전달됩니다. 여기서는 ``--insecure``플래그를 전달합니다." #: ../../source/how-to-run-flower-using-docker.rst:60 #: ../../source/how-to-run-flower-using-docker.rst:259 @@ -4805,16 +5704,20 @@ msgid "" "flower-using-docker.html#enabling-ssl-for-secure-connections>`__ when " "deploying to a production environment." msgstr "" +"``--insecure`` 플래그는 안전하지 않은 통신(HTTPS가 아닌 HTTP 사용)을 " +"활성화하며 테스트 목적으로만 사용해야 합니다. 프로덕션 환경에 배포할 때는 `" +"SSL `__을 활성화할 것을 강력히 권장합니다." #: ../../source/how-to-run-flower-using-docker.rst:65 msgid "" "You can use ``--help`` to view all available flags that the SuperLink " "supports:" -msgstr "" +msgstr "'`--help``을 사용하면 SuperLink가 지원하는 모든 플래그를 볼 수 있습니다:" #: ../../source/how-to-run-flower-using-docker.rst:72 msgid "Mounting a volume to store the state on the host system" -msgstr "" +msgstr "호스트 시스템에 상태를 저장할 볼륨 마운트하기" #: ../../source/how-to-run-flower-using-docker.rst:74 msgid "" @@ -4828,6 +5731,14 @@ msgid "" "you can change the user ID back to the current user ID by running ``sudo " "chown -R $USER:$(id -gn) state``." msgstr "" +"호스트 시스템에서 SuperLink의 상태를 유지하려면 호스트 시스템에서 파일을 " +"저장할 디렉터리와 데이터베이스 파일의 이름을 지정하기만 하면 됩니다. " +"기본적으로 SuperLink 컨테이너는 사용자 ID가 ``49999``인 ``app``이라는 루트가 " +"아닌 사용자로 실행됩니다. 마운트된 디렉터리에 적절한 권한이 있는지 " +"확인하려면 새 디렉터리를 생성하고 디렉터리의 사용자 ID를 ``49999``로 " +"변경하는 것이 좋습니다. 나중에 디렉터리를 삭제하려면 ``sudo chown -R $USER:$(" +"id -gn) state``를 실행하여 사용자 ID를 현재 사용자 ID로 다시 변경할 수 " +"있습니다." #: ../../source/how-to-run-flower-using-docker.rst:82 msgid "" @@ -4837,6 +5748,10 @@ msgid "" "Furthermore, we use the flag ``--database`` to specify the name of the " "database file." msgstr "" +"아래 예에서는 새 디렉터리를 생성하고, 사용자 ID를 변경하고, 플래그 " +"``--volume``을 통해 Docker에게 로컬 ``state`` 디렉터리를 컨테이너의 ``/app/" +"state`` 디렉터리에 마운트하도록 지시합니다. 또한 ``--database`` 플래그를 " +"사용하여 데이터베이스 파일의 이름을 지정합니다." #: ../../source/how-to-run-flower-using-docker.rst:95 msgid "" @@ -4845,18 +5760,24 @@ msgid "" "SuperLink tries to restore the state from the file. To start the " "SuperLink with an empty database, simply remove the ``state.db`` file." msgstr "" +"SuperLink가 시작되자마자 호스트 시스템의 ``state`` 디렉터리에 ``state.db`` " +"파일이 생성됩니다. 파일이 이미 존재하는 경우 SuperLink는 파일에서 상태를 " +"복원하려고 시도합니다. 빈 데이터베이스로 SuperLink를 시작하려면 ``state.db`` " +"파일을 제거하면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:100 #: ../../source/how-to-run-flower-using-docker.rst:281 #: ../../source/how-to-run-flower-using-docker.rst:397 msgid "Enabling SSL for secure connections" -msgstr "" +msgstr "보안 연결을 위한 SSL 사용 설정" #: ../../source/how-to-run-flower-using-docker.rst:102 msgid "" "To enable SSL, you will need a PEM-encoded root certificate, a PEM-" "encoded private key and a PEM-encoded certificate chain." msgstr "" +"SSL을 사용하려면 PEM으로 인코딩된 루트 인증서, PEM으로 인코딩된 개인 키 및 " +"PEM으로 인코딩된 인증서 체인이 필요합니다." #: ../../source/how-to-run-flower-using-docker.rst:106 msgid "" @@ -4865,6 +5786,9 @@ msgid "" "enable-ssl-connections.html#certificates>`__ page contains a section that" " will guide you through the process." msgstr "" +"테스트 목적으로 자체 서명된 인증서를 생성할 수 있습니다. 'SSL 연결 사용 " +"`__ 페이지에 프로세스를 안내하는 섹션이 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:110 msgid "" @@ -4877,6 +5801,14 @@ msgid "" "the names of the certificates and key file to the SuperLink with the " "``--ssl-ca-certfile``, ``--ssl-certfile`` and ``--ssl-keyfile`` flag." msgstr "" +"필요한 모든 파일이 로컬``certificates`` 디렉터리에 있다고 가정하면, " +"``--volume``플래그를 사용하여 로컬 디렉터리를 컨테이너의 ``/app/certificates/" +"`` 디렉터리에 마운트할 수 있습니다. 이렇게 하면 SuperLink 가 컨테이너 내의 " +"파일에 액세스할 수 있습니다. ``ro``는 ``read-only``을 의미합니다. Docker " +"볼륨은 기본적으로 ``read-write``로 설정되어 있는데, 이 옵션을 사용하면 " +"볼륨을 ``read-only``으로 만들 수 있습니다. 마지막으로 인증서 및 키 파일의 " +"이름을 ``--ssl-ca-certfile``, ``--ssl-certfile`` 및 ``--ssl-keyfile`` " +"플래그와 함께 SuperLink에 전달합니다." #: ../../source/how-to-run-flower-using-docker.rst:128 msgid "" @@ -4886,16 +5818,22 @@ msgid "" " the ``certificates/`` directory, you can run ``sudo chown -R 49999:49999" " certificates/*``." msgstr "" +"기본적으로 Flower 컨테이너는 루트가 아닌 사용자 ``app``로 실행되므로 " +"마운트된 파일과 디렉터리에 사용자 ID ``49999``에 대한 적절한 권한이 있어야 " +"합니다. 예를 들어, ``certificates/`` 디렉터리에 있는 모든 파일의 사용자 ID를 " +"변경하려면 ``sudo chown -R 49999:49999 certificates/*``를 실행하면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:134 msgid "Flower SuperNode" -msgstr "" +msgstr "Flower SuperNode" #: ../../source/how-to-run-flower-using-docker.rst:136 msgid "" "The SuperNode Docker image comes with a pre-installed version of Flower " "and serves as a base for building your own SuperNode image." msgstr "" +"SuperNode Docker 이미지는 Flower의 사전 설치된 버전과 함께 제공되며, 자체 " +"SuperNode 이미지를 구축하기 위한 기반 역할을 합니다." #: ../../source/how-to-run-flower-using-docker.rst:141 msgid "" @@ -4906,12 +5844,19 @@ msgid "" "same day. To ensure the versions are in sync, using the concrete tag, " "e.g., ``1.9.0.dev20240501`` instead of ``nightly`` is recommended." msgstr "" +"SuperNode Docker 이미지는 현재 1.9.0 야간 릴리스에서만 작동합니다. 안정 " +"버전은 Flower 1.9.0(안정)이 출시되면 사용할 수 있습니다(예상 출시일: 5월). " +"SuperNode 야간 이미지는 같은 날 릴리스된 해당 SuperLink 및 서버앱 야간 " +"이미지와 페어링되어야 합니다. 버전이 동기화되도록 하려면 ``nightly`` 대신 ``1" +".9.0.dev20240501``과 같은 구체적인 태그를 사용하는 것이 좋습니다." #: ../../source/how-to-run-flower-using-docker.rst:147 msgid "" "We will use the ``quickstart-pytorch`` example, which you can find in the" " Flower repository, to illustrate how you can dockerize your ClientApp." msgstr "" +"Flower 레포지토리에서 찾을 수 있는 ``quickstart-pytorch`` 예제를 사용하여 " +"ClientApp을 도커라이즈하는 방법을 설명하겠습니다." #: ../../source/how-to-run-flower-using-docker.rst:155 msgid "" @@ -4919,19 +5864,22 @@ msgid "" "development environment. You can skip the first part if you want to run " "your ClientApp instead of the ``quickstart-pytorch`` example." msgstr "" +"시작하기 전에 로컬 개발 환경에서 몇 가지 전제 조건을 충족해야 합니다. " +"'quickstart-pytorch' 예제 대신 ClientApp을 실행하려는 경우 첫 번째 부분을 " +"건너뛸 수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:159 msgid "Clone the Flower repository." -msgstr "" +msgstr "플라워 레포지토리를 클론합니다." #: ../../source/how-to-run-flower-using-docker.rst:173 msgid "Creating a SuperNode Dockerfile" -msgstr "" +msgstr "SuperNode Dockerfile 만들기" #: ../../source/how-to-run-flower-using-docker.rst:175 #: ../../source/how-to-run-flower-using-docker.rst:311 msgid "Let's assume the following project layout:" -msgstr "" +msgstr "다음과 같은 프로젝트 레이아웃을 가정해 보겠습니다:" #: ../../source/how-to-run-flower-using-docker.rst:184 msgid "" @@ -4939,6 +5887,9 @@ msgid "" "where the ``ClientApp`` code is located. In the file, we list all the " "dependencies that the ClientApp requires." msgstr "" +"먼저 ``ClientApp`` 코드가 있는 디렉토리에 ``requirements.txt`` 파일을 " +"만들어야 합니다. 이 파일에는 클라이언트 앱에 필요한 모든 dependencies을 " +"나열합니다." #: ../../source/how-to-run-flower-using-docker.rst:196 msgid "" @@ -4947,6 +5898,9 @@ msgid "" "package dependencies in your ``requirements.txt``, such as ``torch``, " "``tensorflow``, etc." msgstr "" +"`flwr `__ 는 이미 ``flwr/supernode`` 기본 " +"이미지에 설치되어 있으므로, ``torch``, ``tensorflow`` 등과 같은 다른 패키지 " +"dependencies만 ``requirements.txt``에 포함시키면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:200 msgid "" @@ -4954,12 +5908,16 @@ msgid "" "example, create a new file called ``Dockerfile.supernode`` in ``examples" "/quickstart-pytorch``." msgstr "" +"다음으로, Dockerfile을 생성합니다.``quickstart-pytorch`` 예제를 사용하는 " +"경우 ``examples/quickstart-pytorch``에 ``Dockerfile.supernode``라는 새 " +"파일을 생성합니다." #: ../../source/how-to-run-flower-using-docker.rst:203 msgid "" "The ``Dockerfile.supernode`` contains the instructions that assemble the " "SuperNode image." -msgstr "" +msgstr "``Dockerfile.supernode``에는 SuperNode 이미지를 조립하는 지침이 포함되어 " +"있습니다." #: ../../source/how-to-run-flower-using-docker.rst:217 msgid "" @@ -4973,16 +5931,26 @@ msgid "" "``client:app``. The argument is the object reference of the ClientApp " "(``:``) that will be run inside the ClientApp." msgstr "" +"처음 두 줄에서는 ``nightly`` 태그가 붙은 SuperNode 이미지를 기본 이미지로 " +"사용하고 작업 디렉터리를 ``/app``로 설정하도록 Docker에 지시합니다. 이제 " +"``/app`` 디렉토리에서 다음 명령이 실행됩니다. 다음으로, ``requirements.txt`` " +"파일을 이미지에 복사하여 ClientApp dependencies 요소를 설치하고 ``pip " +"install``을 실행합니다. 마지막 두 줄에서 ``client.py`` 모듈을 이미지에 " +"복사하고 ``client:app`` 인수를 사용하여 진입점을 ``flower-client-app``로 " +"설정합니다. 인수는 클라이언트앱 내부에서 실행될 클라이언트앱의 객체 참조 " +"(``:``) 입니다." #: ../../source/how-to-run-flower-using-docker.rst:226 msgid "Building the SuperNode Docker image" -msgstr "" +msgstr "SuperNode Docker 이미지 빌드" #: ../../source/how-to-run-flower-using-docker.rst:228 msgid "" "Next, we build the SuperNode Docker image by running the following " "command in the directory where Dockerfile and ClientApp code are located." msgstr "" +"다음으로, Dockerfile 및 ClientApp 코드가 있는 디렉터리에서 다음 명령을 " +"실행하여 SuperNode Docker 이미지를 빌드합니다." #: ../../source/how-to-run-flower-using-docker.rst:235 msgid "" @@ -4990,50 +5958,54 @@ msgid "" "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" +"이미지에 ``flwr_supernode``라는 이름을 붙이고 ``0.0.1`` 태그를 붙였습니다. " +"여기서 선택한 값은 예시일 뿐이라는 점을 기억하세요. 필요에 따라 변경할 수 " +"있습니다." #: ../../source/how-to-run-flower-using-docker.rst:240 msgid "Running the SuperNode Docker image" -msgstr "" +msgstr "SuperNode Docker 이미지 실행" #: ../../source/how-to-run-flower-using-docker.rst:242 msgid "Now that we have built the SuperNode image, we can finally run it." -msgstr "" +msgstr "이제 SuperNode 이미지를 빌드했으니 이제 실행할 수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:250 #: ../../source/how-to-run-flower-using-docker.rst:367 msgid "Let's break down each part of this command:" -msgstr "" +msgstr "이 명령의 각 부분을 자세히 살펴보겠습니다:" #: ../../source/how-to-run-flower-using-docker.rst:252 #: ../../source/how-to-run-flower-using-docker.rst:369 msgid "``docker run``: This is the command to run a new Docker container." -msgstr "" +msgstr "``docker run``: 새 Docker 컨테이너를 실행하는 명령입니다." #: ../../source/how-to-run-flower-using-docker.rst:253 #: ../../source/how-to-run-flower-using-docker.rst:370 msgid "" "``--rm``: This option specifies that the container should be " "automatically removed when it stops." -msgstr "" +msgstr "``--rm``: 이 옵션은 컨테이너가 중지될 때 자동으로 제거되도록 지정합니다." #: ../../source/how-to-run-flower-using-docker.rst:254 msgid "``flwr_supernode:0.0.1``: The name the tag of the Docker image to use." -msgstr "" +msgstr "``flwr_supernode:0.0.1``: 사용할 Docker 이미지의 태그 이름입니다." #: ../../source/how-to-run-flower-using-docker.rst:255 #: ../../source/how-to-run-flower-using-docker.rst:372 msgid "``--insecure``: This option enables insecure communication." -msgstr "" +msgstr "``--insecure``: 이 옵션은 보안되지 않은 통신을 활성화합니다." #: ../../source/how-to-run-flower-using-docker.rst msgid "" "``--superlink 192.168.1.100:9092``: This option specifies the address of " "the SuperLinks Fleet" -msgstr "" +msgstr "``--superlink 192.168.1.100:9092``: 이 옵션은 SuperLinks Fleet의 주소를 " +"지정합니다" #: ../../source/how-to-run-flower-using-docker.rst msgid "API to connect to. Remember to update it with your SuperLink IP." -msgstr "" +msgstr "API에 연결할 수 있습니다. SuperLink IP로 업데이트하는 것을 잊지 마세요." #: ../../source/how-to-run-flower-using-docker.rst:269 msgid "" @@ -5042,18 +6014,25 @@ msgid "" "defined-bridge-networks>`__, use the ``--network`` argument and pass the " "name of the Docker network to run your SuperNodes." msgstr "" +"로컬에서 Flower를 실행하는 것을 테스트하려면 `bridge network `__를 생성하고 ``--network`` argument를 사용하고 SuperNodes를 " +"실행할 Docker 네트워크의 이름을 전달하면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:273 msgid "" "Any argument that comes after the tag is passed to the Flower SuperNode " "binary. To see all available flags that the SuperNode supports, run:" msgstr "" +"태그 뒤에 오는 모든 argument는 Flower SuperNode 바이너리에 전달됩니다. " +"SuperNode가 지원하는 사용 가능한 모든 플래그를 보려면 실행하세요:" #: ../../source/how-to-run-flower-using-docker.rst:283 msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your SuperNode container." -msgstr "" +msgstr "SSL을 사용하려면 PEM 인코딩된 루트 인증서를 SuperNode 컨테이너에 마운트해야 " +"합니다." #: ../../source/how-to-run-flower-using-docker.rst:285 msgid "" @@ -5063,16 +6042,21 @@ msgid "" "within the container. Use the ``--root-certificates`` flag when starting " "the container." msgstr "" +"인증서가 이미 로컬에 존재한다고 가정하면, ``--volume`` 플래그를 사용하여 " +"로컬 인증서를 컨테이너의 ``/app/`` 디렉터리에 마운트할 수 있습니다. 이렇게 " +"하면 SuperNode가 컨테이너 내의 인증서에 액세스할 수 있습니다. 컨테이너를 " +"시작할 때 ``--root-certificates`` 플래그를 사용하세요." #: ../../source/how-to-run-flower-using-docker.rst:297 msgid "Flower ServerApp" -msgstr "" +msgstr "Flower 서버앱" #: ../../source/how-to-run-flower-using-docker.rst:299 msgid "" "The procedure for building and running a ServerApp image is almost " "identical to the SuperNode image." -msgstr "" +msgstr "ServerApp 이미지를 빌드하고 실행하는 절차는 SuperNode 이미지와 거의 " +"동일합니다." #: ../../source/how-to-run-flower-using-docker.rst:301 msgid "" @@ -5080,6 +6064,9 @@ msgid "" "pre-installed version of Flower and serves as a base for building your " "own ServerApp image." msgstr "" +"SuperNode 이미지와 마찬가지로 ServerApp Docker 이미지는 Flower의 사전 설치된 " +"버전과 함께 제공되며, 자체 ServerApp 이미지를 구축하기 위한 기본 역할을 " +"합니다." #: ../../source/how-to-run-flower-using-docker.rst:304 msgid "" @@ -5087,10 +6074,13 @@ msgid "" "Flower SuperNode section. If you have not already done so, please follow " "the `SuperNode Prerequisites`_ before proceeding." msgstr "" +"여기서는 Flower SuperNode 섹션에서와 동일한`quickstart-pytorch`` 예제를 " +"사용하겠습니다. 아직 수행하지 않았다면 계속 진행하기 전에 `SuperNode " +"Prerequisites`_ 을 따르세요." #: ../../source/how-to-run-flower-using-docker.rst:309 msgid "Creating a ServerApp Dockerfile" -msgstr "" +msgstr "ServerApp Dockerfile 만들기" #: ../../source/how-to-run-flower-using-docker.rst:320 msgid "" @@ -5099,12 +6089,16 @@ msgid "" "example, create a new file called ``Dockerfile.serverapp`` in ``examples" "/quickstart-pytorch``." msgstr "" +"먼저, ``ServerApp`` 코드가 있는 디렉토리에 Docker파일을 생성해야 합니다. " +"``quickstart-pytorch`` 예제를 사용하는 경우 ``examples/quickstart-pytorch``" +"에 ``Dockerfile.serverapp``이라는 새 파일을 생성합니다." #: ../../source/how-to-run-flower-using-docker.rst:324 msgid "" "The ``Dockerfile.serverapp`` contains the instructions that assemble the " "ServerApp image." -msgstr "" +msgstr "``Dockerfile.serverapp``에는 ServerApp 이미지를 합치는 지침이 포함되어 " +"있습니다." #: ../../source/how-to-run-flower-using-docker.rst:335 msgid "" @@ -5117,16 +6111,24 @@ msgid "" "ServerApp (``:``) that will be run inside the " "ServerApp container." msgstr "" +"처음 두 줄에서는 ``1.8.0`` 태그가 붙은 ServerApp 이미지를 기본 이미지로 " +"사용하고 작업 디렉터리를 ``/app``로 설정하도록 Docker에 지시합니다. 이제 " +"``/app`` 디렉토리에서 다음 명령이 실행됩니다. 마지막 두 줄에서는 ``server." +"py`` 모듈을 이미지에 복사하고 ``server:app`` argument를 사용하여 진입점을 " +"``flower-server-app``로 설정합니다. 인수는 ServerApp 컨테이너 내에서 실행될 " +"ServerApp의 객체 참조(``:``)입니다." #: ../../source/how-to-run-flower-using-docker.rst:343 msgid "Building the ServerApp Docker image" -msgstr "" +msgstr "ServerApp Docker 이미지 빌드" #: ../../source/how-to-run-flower-using-docker.rst:345 msgid "" "Next, we build the ServerApp Docker image by running the following " "command in the directory where Dockerfile and ServerApp code are located." msgstr "" +"다음으로, Docker파일과 ServerApp 코드가 있는 디렉터리에서 다음 명령을 " +"실행하여 ServerApp Docker 이미지를 빌드합니다." #: ../../source/how-to-run-flower-using-docker.rst:352 msgid "" @@ -21815,4 +22817,3 @@ msgstr "" #~ msgid "|83a8daee45da4a98b8d6f24ae098fc50|" #~ msgstr "" - From 87e247991e00539187c8b86348d26592a1e5397a Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Sun, 23 Jun 2024 17:30:03 +0200 Subject: [PATCH 080/595] docs(framework) Add latest Hosted Weblate translation updates (#3674) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 박태현 Co-authored-by: Sijiaomg Ohoh --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 15259 +++++++++-------- 1 file changed, 7661 insertions(+), 7598 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 74cb5f00589f..3c41a8647c35 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-06-17 16:09+0200\n" -"PO-Revision-Date: 2024-06-22 10:52+0000\n" +"PO-Revision-Date: 2024-06-23 14:41+0000\n" "Last-Translator: 박태현 \n" "Language-Team: Korean \n" @@ -30,9 +30,11 @@ msgstr "엣지 클라이언트 엔진" #: ../../source/contributor-explanation-architecture.rst:7 msgid "" -"`Flower `_ core framework architecture with Edge " -"Client Engine" -msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 엣지 클라이언트 엔진" +"`Flower `_ core framework architecture with Edge Client " +"Engine" +msgstr "" +"`Flower `_의 핵심 프레임워크 아키텍처와 엣지 클라이언트 엔" +"진" #: ../../source/contributor-explanation-architecture.rst:13 msgid "Virtual Client Engine" @@ -42,7 +44,9 @@ msgstr "가상 클라이언트 엔진" msgid "" "`Flower `_ core framework architecture with Virtual " "Client Engine" -msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 가상 클라이언트 엔진" +msgstr "" +"`Flower `_의 핵심 프레임워크 아키텍처와 가상 클라이언트 엔" +"진" #: ../../source/contributor-explanation-architecture.rst:21 msgid "Virtual Client Engine and Edge Client Engine in the same workload" @@ -50,9 +54,11 @@ msgstr "동일 작업에서 가상 클라이언트 엔진과 엣지 클라이언 #: ../../source/contributor-explanation-architecture.rst:23 msgid "" -"`Flower `_ core framework architecture with both " -"Virtual Client Engine and Edge Client Engine" -msgstr "`Flower `_의 핵심 프레임워크 아키텍처와 가상 및 엣지 클라이언트 엔진" +"`Flower `_ core framework architecture with both Virtual " +"Client Engine and Edge Client Engine" +msgstr "" +"`Flower `_의 핵심 프레임워크 아키텍처와 가상 및 엣지 클라" +"이언트 엔진" #: ../../source/contributor-how-to-build-docker-images.rst:2 msgid "How to build Docker Flower images locally" @@ -60,20 +66,19 @@ msgstr "Docker Flower 이미지를 Locally 구축하는 방법" #: ../../source/contributor-how-to-build-docker-images.rst:4 msgid "" -"Flower provides pre-made docker images on `Docker Hub " -"`_ that include all necessary dependencies" -" for running the SuperLink, SuperNode or ServerApp. You can also build " -"your own custom docker images from scratch with a different version of " -"Python or Linux distribution (Ubuntu/Alpine) if that is what you need. In" -" this guide, we will explain what images exist and how to build them " -"locally." +"Flower provides pre-made docker images on `Docker Hub `_ that include all necessary dependencies for running the " +"SuperLink, SuperNode or ServerApp. You can also build your own custom docker " +"images from scratch with a different version of Python or Linux distribution " +"(Ubuntu/Alpine) if that is what you need. In this guide, we will explain " +"what images exist and how to build them locally." msgstr "" "Flower는 'Docker Hub '_에서 미리 만들어진 " "Docker 이미지들을 제공합니다. 해당 이미지들은 SuperLink, ServerNode 또는 " -"ServerApp을 실행하는 데 필요한 모든 dependencies를 포함합니다. 필요한 경우 " -"다른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해 처음부터 사용자 " -"정의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 " -"이미지들과 이들을 로컬에서 빌드하는 방법에 대해 설명하겠습니다." +"ServerApp을 실행하는 데 필요한 모든 dependencies를 포함합니다. 필요한 경우 다" +"른 버전의 Python이나 Linux 배포판(Ubuntu/Alpine)을 사용해 처음부터 사용자 정" +"의 Docker 이미지를 빌드할 수도 있습니다. 이 가이드에서는 존재하는 이미지들과 " +"이들을 로컬에서 빌드하는 방법에 대해 설명하겠습니다." #: ../../source/contributor-how-to-build-docker-images.rst:10 msgid "" @@ -83,7 +88,7 @@ msgstr "시작하기 전에, 로컬 개발 환경에서 몇 가지 전제 조건 #: ../../source/contributor-how-to-build-docker-images.rst:12 msgid "Clone the flower repository." -msgstr "Flower 리포지토리를 복제합니다." +msgstr "Flower 레포지토리를 복제합니다." #: ../../source/contributor-how-to-build-docker-images.rst:18 #: ../../source/how-to-run-flower-using-docker.rst:165 @@ -93,46 +98,45 @@ msgstr "Docker 데몬이 실행 중인지 확인하십시오." #: ../../source/contributor-how-to-build-docker-images.rst:20 #: ../../source/how-to-run-flower-using-docker.rst:167 msgid "" -"Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." +"Please follow the first section on :doc:`Run Flower using Docker ` which covers this step in more detail." msgstr "" -":doc:Run Flower using Docker 의 첫 번째 섹션을 " -"따라 주십시오. 해당 부분을 더 자세히 설명해 줍니다." +":doc:Run Flower using Docker 의 첫 번째 섹션" +"을 따라 주십시오. 해당 부분을 더 자세히 설명해 줍니다." #: ../../source/contributor-how-to-build-docker-images.rst:25 msgid "" "The build instructions that assemble the images are located in the " -"respective Dockerfiles. You can find them in the subdirectories of " -"``src/docker``." +"respective Dockerfiles. You can find them in the subdirectories of ``src/" +"docker``." msgstr "" -"이미지들을 조합하는 빌드 instruction들은 해당 Dockerfile에 있습니다. \"src/docker\" 의 하위 " -"디렉토리에서 찾을 수 있습니다." +"이미지들을 조합하는 빌드 명령어들은 해당 Dockerfile에 있습니다. \"src/" +"docker\" 의 하위 디렉토리에서 찾을 수 있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:28 msgid "" "Flower Docker images are configured via build arguments. Through build " -"arguments, we can make the creation of images more flexible. For example," -" in the base image, we can specify the version of Python to install using" -" the ``PYTHON_VERSION`` build argument. Some of the build arguments have " -"default values, others must be specified when building the image. All " -"available build arguments for each image are listed in one of the tables " -"below." +"arguments, we can make the creation of images more flexible. For example, in " +"the base image, we can specify the version of Python to install using the " +"``PYTHON_VERSION`` build argument. Some of the build arguments have default " +"values, others must be specified when building the image. All available " +"build arguments for each image are listed in one of the tables below." msgstr "" -"Flower Docker는 빌드 argument를 통해 구성됩니다. 빌드 argument들을 통해, " +"Flower Docker는 빌드 전달인자를 통해 구성됩니다. 빌드 argument들을 통해, " "이미지를 보다 유연하게 생성할 수 있습니다. 예를 들어, base 이미지에서 " -"\"PYTHON_VERSION\" 빌드 argument를 사용하여 Python 버전을 지정할 수 " -"있습니다. 일부 빌드 argument들은 기본값이며, 이미지를 빌드할 때 지정해야 " -"합니다. 각 이미지에 사용할 수 있는 모든 빌드 argument는 아래 표 중에 " +"\"PYTHON_VERSION\" 빌드 전달인자를 사용하여 Python 버전을 지정할 수 " +"있습니다. 일부 빌드 전달인자들은 기본값이며, 이미지를 빌드할 때 지정해야 " +"합니다. 각 이미지에 사용할 수 있는 모든 빌드 전달인자는 아래 표 중에 " "있습니다." #: ../../source/contributor-how-to-build-docker-images.rst:35 msgid "Building the base image" -msgstr "base 이미지 빌드" +msgstr "기본 이미지 빌드" #: ../../source/contributor-how-to-build-docker-images.rst:41 #: ../../source/contributor-how-to-build-docker-images.rst:98 msgid "Build argument" -msgstr "빌드 argument" +msgstr "빌드 전달인자" #: ../../source/contributor-how-to-build-docker-images.rst:42 #: ../../source/contributor-how-to-build-docker-images.rst:99 @@ -155,7 +159,7 @@ msgstr "``DISTRO``" #: ../../source/contributor-how-to-build-docker-images.rst:46 msgid "The Linux distribution to use as the base image." -msgstr "base 이미지 사용을 위한 Linux 배포판." +msgstr "기본 이미지 사용을 위한 Linux 배포판." #: ../../source/contributor-how-to-build-docker-images.rst:47 #: ../../source/contributor-how-to-build-docker-images.rst:51 @@ -250,20 +254,21 @@ msgstr "``flwr`` 또는 ``flwr-nightly``" #: ../../source/contributor-how-to-build-docker-images.rst:75 msgid "" -"The following example creates a base Ubuntu/Alpine image with Python " -"3.11.0, pip 23.0.1, setuptools 69.0.2 and Flower 1.8.0:" +"The following example creates a base Ubuntu/Alpine image with Python 3.11.0, " +"pip 23.0.1, setuptools 69.0.2 and Flower 1.8.0:" msgstr "" -"다음 예시에서는 Python 3.11.0, pip 23.0.1, setuptools 및 Flower 1.8.0으로 " -"기본 Ubuntu/Alpine 이미지를 만듭니다:" +"다음 예시에서는 Python 3.11.0, pip 23.0.1, setuptools 및 Flower 1.8.0으로 기" +"본 Ubuntu/Alpine 이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:88 msgid "" -"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that " -"the build arguments as well as the name and tag can be adapted to your " -"needs. These values serve as examples only." +"The name of image is ``flwr_base`` and the tag ``0.1.0``. Remember that the " +"build arguments as well as the name and tag can be adapted to your needs. " +"These values serve as examples only." msgstr "" -"이미지의 이름은 ``flwr_base``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌드 argument들 뿐만 아니라 이름과" -" 태그도 정할 수 있습니다. 이 값들은 예시일 뿐입니다." +"이미지의 이름은 ``flwr_base``이고 태그는 ``0.1.0``입니다. 필요에 따라 빌드 " +"전달인자들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 예시일 " +"뿐입니다." #: ../../source/contributor-how-to-build-docker-images.rst:92 msgid "Building the SuperLink/SuperNode or ServerApp image" @@ -275,7 +280,7 @@ msgstr "``BASE_REPOSITORY``" #: ../../source/contributor-how-to-build-docker-images.rst:103 msgid "The repository name of the base image." -msgstr "base 이미지의 리포지토리 이름." +msgstr "기본 이미지의 레포지토리 이름." #: ../../source/contributor-how-to-build-docker-images.rst:105 msgid "``flwr/base``" @@ -295,19 +300,19 @@ msgstr "``1.8.0-py3.10-ubuntu22.04``" #: ../../source/contributor-how-to-build-docker-images.rst:111 msgid "" -"The following example creates a SuperLink/SuperNode or ServerApp image " -"with the official Flower base image:" -msgstr "다음 예시에서는 공식 Flower 기본 이미지로 SuperLink/SuperNode 또는 " -"ServerApp이미지를 만듭니다:" +"The following example creates a SuperLink/SuperNode or ServerApp image with " +"the official Flower base image:" +msgstr "" +"다음 예시에서는 공식 Flower 기본 이미지로 SuperLink/SuperNode 또는 ServerApp" +"이미지를 만듭니다:" #: ../../source/contributor-how-to-build-docker-images.rst:122 msgid "" -"If you want to use your own base image instead of the official Flower " -"base image, all you need to do is set the ``BASE_REPOSITORY`` build " -"argument." +"If you want to use your own base image instead of the official Flower base " +"image, all you need to do is set the ``BASE_REPOSITORY`` build argument." msgstr "" "공식 Flower 기본 이미지 대신 자체 기본 이미지를 사용 하길 원한다면, " -"``BASE_REPOSITORY`` 빌드 argument들을 설정해야 합니다." +"``BASE_REPOSITORY`` 빌드 전달인자들을 설정해야 합니다." #: ../../source/contributor-how-to-build-docker-images.rst:133 msgid "After creating the image, we can test whether the image is working:" @@ -319,29 +324,30 @@ msgstr "번역 기여" #: ../../source/contributor-how-to-contribute-translations.rst:4 msgid "" -"Since `Flower 1.5 `_ we have introduced translations to " -"our doc pages, but, as you might have noticed, the translations are often" -" imperfect. If you speak languages other than English, you might be able " -"to help us in our effort to make Federated Learning accessible to as many" -" people as possible by contributing to those translations! This might " -"also be a great opportunity for those wanting to become open source " -"contributors with little prerequisites." -msgstr "" -"`Flower 1.5 `_ 부터 문서 페이지에 번역을 도입했지만, 아시다시피 번역이 불안전한 " -"경우가 많습니다. 만일 영어 이외의 언어를 사용한다면, 많은 사람들이 Federated Learning에 접근할 수 있도록 번역 " -"작업에 기여함으로써 저희의 노력에 도움을 주실 수 있습니다! 이는 전제 조건이 거의 없는 오픈 소스 기여자가 되고자 하는 사람들에게" -" 좋은 기회가 될 수도 있습니다." +"Since `Flower 1.5 `_ we have introduced translations to our doc pages, " +"but, as you might have noticed, the translations are often imperfect. If you " +"speak languages other than English, you might be able to help us in our " +"effort to make Federated Learning accessible to as many people as possible " +"by contributing to those translations! This might also be a great " +"opportunity for those wanting to become open source contributors with little " +"prerequisites." +msgstr "" +"`Flower 1.5 `_ 부터 문서 페이지에 번역을 도입했지만, 아시다시피 " +"번역이 불안전한 경우가 많습니다. 만일 영어 이외의 언어를 사용한다면, 많은 " +"사람들이 연합 학습에 접근할 수 있도록 번역 작업에 기여함으로써 저희의 노력에 " +"도움을 주실 수 있습니다! 이는 전제 조건이 거의 없는 오픈 소스 기여자가 " +"되고자 하는 사람들에게 좋은 기회가 될 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:13 msgid "" -"Our translation project is publicly available over on `Weblate " -"`_, this " -"where most of the work will happen." +"Our translation project is publicly available over on `Weblate `_, this where most of " +"the work will happen." msgstr "" -"번역 프로젝트는 `Weblate `_에서 공개적으로 진행되며, 대부분의 작업이 이곳에서 이루어집니다." +"번역 프로젝트는 `Weblate `_에서 공개적으로 진행되며, 대부분의 작업이 이곳에서 이루어집니다." #: ../../source/contributor-how-to-contribute-translations.rst:18 msgid "Contribute to existing languages" @@ -349,43 +355,43 @@ msgstr "기존 언어에 기여하기" #: ../../source/contributor-how-to-contribute-translations.rst:23 msgid "" -"The first thing you will need to do in order to contribute is to create a" -" free Weblate account on this `page " -"`_. More information about" -" profile settings can be found `here " +"The first thing you will need to do in order to contribute is to create a " +"free Weblate account on this `page `_. More information about profile settings can be found `here " "`_." msgstr "" -"기여를 하기 위해 가장 먼저 해야 할 일은 해당 `page " -"`_에서 무료 Weblate 계정을 만드는 " -"것입니다. 프로필 설정에 대한 자세한 정보는 `here " -"`_를 참조하세요." +"기여를 하기 위해 가장 먼저 해야 할 일은 해당 `page `_에서 무료 Weblate 계정을 만드는 것입니다. 프로필 설" +"정에 대한 자세한 정보는 `here `_를 참조하세요." #: ../../source/contributor-how-to-contribute-translations.rst:29 msgid "" -"Once you are signed in to Weblate, you can navigate to the `Flower " -"Framework project `_. Here, you should see the different existing languages" -" that can be found on the website." +"Once you are signed in to Weblate, you can navigate to the `Flower Framework " +"project `_. " +"Here, you should see the different existing languages that can be found on " +"the website." msgstr "" -"Weblate에 로그인한 후, `Flower Framework project " -"`_로 이동할 수 " -"있습니다. 여기에서 웹사이트에 있는 다양한 기존 언어들을 확인할 수 있습니다." +"Weblate에 로그인한 후, `Flower Framework project `_로 이동할 수 있습니다. 여기에서 웹사이트에 " +"있는 다양한 기존 언어들을 확인할 수 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:34 msgid "" -"Once you have selected the language you want to contribute to, you should" -" see a similar interface to this:" +"Once you have selected the language you want to contribute to, you should " +"see a similar interface to this:" msgstr "기여하고자 하는 언어를 선택하면, 다음과 같은 인터페이스가 나타납니다:" #: ../../source/contributor-how-to-contribute-translations.rst:39 msgid "" "The most straight forward option here is to click on the ``Translate`` " -"button on the top right (in the ``Translation status`` section). This " -"will automatically bring you to the translation interface for " -"untranslated strings." +"button on the top right (in the ``Translation status`` section). This will " +"automatically bring you to the translation interface for untranslated " +"strings." msgstr "" -"여기서 가장 간단한 옵션은 오른쪽 상단(``Translation status`` 부분)에 있는 ``Translate`` 버튼을 " -"클릭하는 것 입니다. 번역되지 않은 문장에 대한 번역 인터페이스로 자동으로 이동합니다." +"여기서 가장 간단한 옵션은 오른쪽 상단(``Translation status`` 부분)에 있는 " +"``Translate`` 버튼을 클릭하는 것 입니다. 번역되지 않은 문장에 대한 번역 인터" +"페이스로 자동으로 이동합니다." #: ../../source/contributor-how-to-contribute-translations.rst:43 msgid "This is what the interface looks like:" @@ -393,44 +399,47 @@ msgstr "인터페이스는 다음과 같습니다:" #: ../../source/contributor-how-to-contribute-translations.rst:47 msgid "" -"You input your translation in the text box at the top and then, once you " -"are happy with it, you either press ``Save and continue`` (to save the " -"translation and go to the next untranslated string), ``Save and stay`` " -"(to save the translation and stay on the same page), ``Suggest`` (to add " -"your translation to suggestions for other users to view), or ``Skip`` (to" -" go to the next untranslated string without saving anything)." +"You input your translation in the text box at the top and then, once you are " +"happy with it, you either press ``Save and continue`` (to save the " +"translation and go to the next untranslated string), ``Save and stay`` (to " +"save the translation and stay on the same page), ``Suggest`` (to add your " +"translation to suggestions for other users to view), or ``Skip`` (to go to " +"the next untranslated string without saving anything)." msgstr "" -"번역문을 상단의 텍스트 상자에 입력한 후, 번역이 만족스러우면 ``Save and continue``(번역을 저장하고 다음 미번역 " -"문장으로 이동), ``Save and stay``(번역을 저장하고 해당 페이지에 머무르기), ``Suggest`` (다른 사용자가 " -"볼 수 있도록 번역을 제안 항목에 추가), ``Skip``(아무것도 저장하지 않고 다음 미번역 문장으로 이동) 중 하나를 선택하면 " -"됩니다." +"번역문을 상단의 텍스트 상자에 입력한 후, 번역이 만족스러우면 ``Save and " +"continue``(번역을 저장하고 다음 미번역 문장으로 이동), ``Save and stay``(번역" +"을 저장하고 해당 페이지에 머무르기), ``Suggest`` (다른 사용자가 볼 수 있도록 " +"번역을 제안 항목에 추가), ``Skip``(아무것도 저장하지 않고 다음 미번역 문장으" +"로 이동) 중 하나를 선택하면 됩니다." #: ../../source/contributor-how-to-contribute-translations.rst:54 msgid "" "In order to help with the translations, you can see on the bottom the " "``Nearby strings``, the ``Comments`` (from other contributors), the " "``Automatic suggestions`` (from machine translation engines), the " -"translations in ``Other languages``, and the ``History`` of translations " -"for this string." +"translations in ``Other languages``, and the ``History`` of translations for " +"this string." msgstr "" -"번역에 도움을 주기위해 하단에서 `주변 문자열``, ``의견``(다른 기여자의), ``자동 제안``(기계 번역의), ``다른 " -"언어``의 번역 및 해당 문장의 번역``히스토리``를 볼 수 있습니다." +"번역에 도움을 주기위해 하단에서 `주변 문자열``, ``의견``(다른 기여자의), ``자" +"동 제안``(기계 번역의), ``다른 언어``의 번역 및 해당 문장의 번역``히스토리``" +"를 볼 수 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:59 msgid "" -"On the right, under the ``String information`` section, you can also " -"click the link under ``Source string location`` in order to view the " -"source of the doc file containing the string." -msgstr "오른쪽의 ``문자열 정보``에서 ``원본 문자열 위치``를 클릭하여 해당 문장이 포함된 문서의 파일 소스를 볼 수도 있습니다." +"On the right, under the ``String information`` section, you can also click " +"the link under ``Source string location`` in order to view the source of the " +"doc file containing the string." +msgstr "" +"오른쪽의 ``문자열 정보``에서 ``원본 문자열 위치``를 클릭하여 해당 문장이 포함" +"된 문서의 파일 소스를 볼 수도 있습니다." #: ../../source/contributor-how-to-contribute-translations.rst:63 msgid "" -"For more information about translating using Weblate, you can check out " -"this `in-depth guide " -"`_." +"For more information about translating using Weblate, you can check out this " +"`in-depth guide `_." msgstr "" -"Weblate를 통한 번역에 대한 자세한 정보는 `in-depth guide " -"`_를 확인하세요." +"Weblate를 통한 번역에 대한 자세한 정보는 `in-depth guide `_를 확인하세요." #: ../../source/contributor-how-to-contribute-translations.rst:67 msgid "Add new languages" @@ -438,12 +447,13 @@ msgstr "새 언어 추가" #: ../../source/contributor-how-to-contribute-translations.rst:69 msgid "" -"If you want to add a new language, you will first have to contact us, " -"either on `Slack `_, or by opening an issue" -" on our `GitHub repo `_." +"If you want to add a new language, you will first have to contact us, either " +"on `Slack `_, or by opening an issue on our " +"`GitHub repo `_." msgstr "" -"새 언어를 추가하려면, `Slack `에 문의하거나 `GitHub repo " -"`_에서 issue에 들어가 문의 해야 합니다." +"새 언어를 추가하려면, `Slack `에 문의하거나 " +"`GitHub repo `_에서 issue에 들어가 문의 해야 " +"합니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:2 msgid "Develop in VSCode Dev Containers" @@ -451,49 +461,52 @@ msgstr "VSCode Dev Container에서 개발" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:4 msgid "" -"When working on the Flower framework we want to ensure that all " -"contributors use the same developer environment to format code or run " -"tests. For this purpose we are using the VSCode Remote Containers " -"extension. What is it? Read the following quote:" +"When working on the Flower framework we want to ensure that all contributors " +"use the same developer environment to format code or run tests. For this " +"purpose we are using the VSCode Remote Containers extension. What is it? " +"Read the following quote:" msgstr "" -"Flower 프레임워크 작업시, 모든 기여자들이 코드 포맷팅이나 테스트 실행을 위해 동일한 개발 환경을 사용하길 원합니다. 이를 " -"위해 VSCode Remote Containers 확장을 사용하고 있습니다. 그것이 무엇인지 알아보기 위해 다음 인용문을 " -"읽어보세요:" +"Flower 프레임워크 작업시, 모든 기여자들이 코드 포맷팅이나 테스트 실행을 위해 " +"동일한 개발 환경을 사용하길 원합니다. 이를 위해 VSCode Remote Containers 확장" +"을 사용하고 있습니다. 그것이 무엇인지 알아보기 위해 다음 인용문을 읽어보세요:" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:7 msgid "" -"The Visual Studio Code Remote - Containers extension lets you use a " -"Docker container as a fully-featured development environment. It allows " -"you to open any folder inside (or mounted into) a container and take " -"advantage of Visual Studio Code's full feature set. A " -":code:`devcontainer.json` file in your project tells VS Code how to " -"access (or create) a development container with a well-defined tool and " -"runtime stack. This container can be used to run an application or to " -"separate tools, libraries, or runtimes needed for working with a " -"codebase." -msgstr "" -"Visual Studio Code Remote - 컨테이너 확장을 사용하면 Docker 컨테이너를 모든 기능을 갖춘 개발 환경으로 " -"사용할 수 있습니다. 이 확장 기능을 사용하면 컨테이너 내부(또는 컨테이너에 마운트된)의 모든 폴더를 열고 Visual Studio" -" Code의 모든 기능을 활용할 수 있습니다. 프로젝트에 있는 :code:`devcontainer.json` 파일은 잘 정의된 " -"도구와 런타임 스택을 사용하여 개발 컨테이너에 액세스(또는 생성)하는 방법을 VS Code에 알려줍니다. 이 컨테이너는 " -"애플리케이션을 실행하거나 코드베이스 작업에 필요한 도구, 라이브러리 또는 런타임을 분리하는 데 사용할 수 있습니다." +"The Visual Studio Code Remote - Containers extension lets you use a Docker " +"container as a fully-featured development environment. It allows you to open " +"any folder inside (or mounted into) a container and take advantage of Visual " +"Studio Code's full feature set. A :code:`devcontainer.json` file in your " +"project tells VS Code how to access (or create) a development container with " +"a well-defined tool and runtime stack. This container can be used to run an " +"application or to separate tools, libraries, or runtimes needed for working " +"with a codebase." +msgstr "" +"Visual Studio Code Remote - 컨테이너 확장을 사용하면 Docker 컨테이너를 모든 " +"기능을 갖춘 개발 환경으로 사용할 수 있습니다. 이 확장 기능을 사용하면 컨테이" +"너 내부(또는 컨테이너에 마운트된)의 모든 폴더를 열고 Visual Studio Code의 모" +"든 기능을 활용할 수 있습니다. 프로젝트에 있는 :code:`devcontainer.json` 파일" +"은 잘 정의된 도구와 런타임 스택을 사용하여 개발 컨테이너에 액세스(또는 생성)" +"하는 방법을 VS Code에 알려줍니다. 이 컨테이너는 애플리케이션을 실행하거나 코" +"드베이스 작업에 필요한 도구, 라이브러리 또는 런타임을 분리하는 데 사용할 수 " +"있습니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:9 msgid "" -"Workspace files are mounted from the local file system or copied or " -"cloned into the container. Extensions are installed and run inside the " -"container, where they have full access to the tools, platform, and file " -"system. This means that you can seamlessly switch your entire development" -" environment just by connecting to a different container." +"Workspace files are mounted from the local file system or copied or cloned " +"into the container. Extensions are installed and run inside the container, " +"where they have full access to the tools, platform, and file system. This " +"means that you can seamlessly switch your entire development environment " +"just by connecting to a different container." msgstr "" -"작업 공간 파일은 로컬 파일 시스템에서 마운트되거나 컨테이너에 복사 또는 클론됩니다. 확장 프로그램은 컨테이너 내부에 설치되고 " -"실행되며, 도구, 플랫폼 및 파일 시스템에 완전한 접근 권한을 갖습니다. 이는 다른 컨테이너에 연결하는 것만으로 전체 개발 환경을 " -"원활하게 전환할 수 있음을 의미합니다." +"작업 공간 파일은 로컬 파일 시스템에서 마운트되거나 컨테이너에 복사 또는 클론" +"됩니다. 확장 프로그램은 컨테이너 내부에 설치되고 실행되며, 도구, 플랫폼 및 파" +"일 시스템에 완전한 접근 권한을 갖습니다. 이는 다른 컨테이너에 연결하는 것만으" +"로 전체 개발 환경을 원활하게 전환할 수 있음을 의미합니다." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:11 msgid "" -"Source: `Official VSCode documentation " -"`_" +"Source: `Official VSCode documentation `_" msgstr "출처 : 공식 VSCode 문서" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:15 @@ -502,52 +515,57 @@ msgstr "시작하기" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:17 msgid "" -"Configuring and setting up the :code:`Dockerfile` as well the " -"configuration for the devcontainer can be a bit more involved. The good " -"thing is you don't have to do it. Usually it should be enough to install " -"`Docker `_ on your system and " -"ensure its available on your command line. Additionally, install the " -"`VSCode Containers Extension `_." -msgstr "" -"`Dockerfile`을 설정하고 구성하는 것과 개발 컨테이너 구성은 약간 복잡할 수 있습니다. 다행히도, 이를 직접 할 필요는 " -"없습니다. 일반적으로 시스템에 `Docker `_를 " -"설치하고 커맨드 라인에서 사용할 수 있는지 확인하는 것으로 충분합니다. 추가로 `VSCode Containers Extension " +"Configuring and setting up the :code:`Dockerfile` as well the configuration " +"for the devcontainer can be a bit more involved. The good thing is you don't " +"have to do it. Usually it should be enough to install `Docker `_ on your system and ensure its available on " +"your command line. Additionally, install the `VSCode Containers Extension " +"`_." +msgstr "" +"`Dockerfile`을 설정하고 구성하는 것과 개발 컨테이너 구성은 약간 복잡할 수 있" +"습니다. 다행히도, 이를 직접 할 필요는 없습니다. 일반적으로 시스템에 `Docker " +"`_를 설치하고 커맨드 라인에서 사용" +"할 수 있는지 확인하는 것으로 충분합니다. 추가로 `VSCode Containers Extension " "`_을 설치하세요." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:19 msgid "" -"Now you should be good to go. When starting VSCode, it will ask you to " -"run in the container environment and - if you confirm - automatically " -"build the container and use it. To manually instruct VSCode to use the " -"devcontainer, you can, after installing the extension, click the green " -"area in the bottom left corner of your VSCode window and select the " -"option *(Re)Open Folder in Container*." +"Now you should be good to go. When starting VSCode, it will ask you to run " +"in the container environment and - if you confirm - automatically build the " +"container and use it. To manually instruct VSCode to use the devcontainer, " +"you can, after installing the extension, click the green area in the bottom " +"left corner of your VSCode window and select the option *(Re)Open Folder in " +"Container*." msgstr "" -"이제 준비가 완료되었습니다. VSCode를 시작하면 컨테이너 환경에서 실행할지를 묻고, 확인하면 자동으로 컨테이너를 빌드하고 사용할" -" 것입니다. VSCode에 수동으로 개발 컨테이너를 사용하도록 지시하려면, 확장을 설치한 후, VSCode 창의 왼쪽 하단에 있는 " -"초록색 부을 클릭하고 *(Re)Open Folder in Container* 옵션을 선택하세요." +"이제 준비가 완료되었습니다. VSCode를 시작하면 컨테이너 환경에서 실행할지를 묻" +"고, 확인하면 자동으로 컨테이너를 빌드하고 사용할 것입니다. VSCode에 수동으로 " +"개발 컨테이너를 사용하도록 지시하려면, 확장을 설치한 후, VSCode 창의 왼쪽 하" +"단에 있는 초록색 부을 클릭하고 *(Re)Open Folder in Container* 옵션을 선택하세" +"요." #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:21 msgid "" -"In some cases your setup might be more involved. For those cases consult " -"the following sources:" -msgstr "경우에 따라 설정이 더 복잡할 수도 있습니다. 이러한 경우에는 다음 소스를 참조하세요:" +"In some cases your setup might be more involved. For those cases consult the " +"following sources:" +msgstr "" +"경우에 따라 설정이 더 복잡할 수도 있습니다. 이러한 경우에는 다음 소스를 참조" +"하세요:" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:23 msgid "" -"`Developing inside a Container " -"`_" +"`Developing inside a Container `_" msgstr "" -"`컨테이너 내부 개발`_" +"`컨테이너 내부 개발`_" #: ../../source/contributor-how-to-develop-in-vscode-dev-containers.rst:24 msgid "" -"`Remote development in Containers " -"`_" -msgstr "`컨테이너 원격 개발`_" +"`Remote development in Containers `_" +msgstr "" +"`컨테이너 원격 개발`_" #: ../../source/contributor-how-to-install-development-versions.rst:2 msgid "Install development versions" @@ -563,19 +581,20 @@ msgstr "Poetry 사용하기(권장)" #: ../../source/contributor-how-to-install-development-versions.rst:10 msgid "" -"Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency " -"in ``pyproject.toml`` and then reinstall (don't forget to delete " -"``poetry.lock`` (``rm poetry.lock``) before running ``poetry install``)." +"Install a ``flwr`` pre-release from PyPI: update the ``flwr`` dependency in " +"``pyproject.toml`` and then reinstall (don't forget to delete ``poetry." +"lock`` (``rm poetry.lock``) before running ``poetry install``)." msgstr "" -"PyPI에서 ``flwr`` 사전 릴리스 설치하기: ``pyproject.toml``에서 ``flwr``의 dependency를 " -"업데이트한 다음, 재설치하세요(``poetry 설치``이전에 ``poetry.lock`` (``rm poetry.lock``)를 " -"제거하는 것을 잊지 마세요)." +"PyPI에서 ``flwr`` 사전 릴리스 설치하기: ``pyproject.toml``에서 ``flwr``의 " +"의존성을 업데이트한 다음, 재설치하세요(``poetry 설치``이전에 ``poetry.lock`` " +"(``rm poetry.lock``)를 제거하는 것을 잊지 마세요)." #: ../../source/contributor-how-to-install-development-versions.rst:12 msgid "" "``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (without " "extras)" -msgstr "``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (extras 제외)" +msgstr "" +"``flwr = { version = \"1.0.0a0\", allow-prereleases = true }`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:13 msgid "" @@ -587,9 +606,11 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:15 msgid "" -"Install ``flwr`` from a local copy of the Flower source code via " -"``pyproject.toml``:" -msgstr "``pyproject.toml``을 통해 Flower 소스 코드의 로컬 복사본에서 ``flwr``을 설치하세요:" +"Install ``flwr`` from a local copy of the Flower source code via ``pyproject." +"toml``:" +msgstr "" +"``pyproject.toml``을 통해 Flower 소스 코드의 로컬 복사본에서 ``flwr``을 설치" +"하세요:" #: ../../source/contributor-how-to-install-development-versions.rst:17 msgid "``flwr = { path = \"../../\", develop = true }`` (without extras)" @@ -597,11 +618,11 @@ msgstr "``flwr = { path = \"../../\", develop = true }`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:18 msgid "" -"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] " -"}`` (with extras)" +"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] }`` " +"(with extras)" msgstr "" -"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] " -"}`` (extras 포함)" +"``flwr = { path = \"../../\", develop = true, extras = [\"simulation\"] }`` " +"(extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:20 msgid "Install ``flwr`` from a local wheel file via ``pyproject.toml``:" @@ -609,11 +630,11 @@ msgstr "``pyproject.toml``을 통해 로컬 wheel file에서 ``flwr``을 설치 #: ../../source/contributor-how-to-install-development-versions.rst:22 msgid "" -"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (without" -" extras)" +"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (without " +"extras)" msgstr "" -"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (extras " -"제외)" +"``flwr = { path = \"../../dist/flwr-1.8.0-py3-none-any.whl\" }`` (extras 제" +"외)" #: ../../source/contributor-how-to-install-development-versions.rst:23 msgid "" @@ -629,8 +650,8 @@ msgid "" "Dependency Specification `_" msgstr "" -"자세한 내용은 Poetry 문서를 참고하세요: `Poetry Dependency Specification `_" +"자세한 내용은 Poetry 문서를 참고하세요: `Poetry Dependency Specification " +"`_" #: ../../source/contributor-how-to-install-development-versions.rst:28 msgid "Using pip (recommended on Colab)" @@ -638,7 +659,7 @@ msgstr "pip 사용하기(Colab에서 권장)" #: ../../source/contributor-how-to-install-development-versions.rst:30 msgid "Install a ``flwr`` pre-release from PyPI:" -msgstr "PyPI에서 ``flwr`` 사전 릴리스를 설치하기:" +msgstr "PyPI에서 ``flwr`` 사전 릴리즈를 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:32 msgid "``pip install -U --pre flwr`` (without extras)" @@ -653,8 +674,8 @@ msgid "" "Python packages can be installed from git repositories. Use one of the " "following commands to install the Flower directly from GitHub." msgstr "" -"Python 패키지는 git 저장소에서 설치할 수 있습니다. 다음 명령어 중 하나를 사용하여 GitHub에서 직접 Flower를 " -"설치하세요." +"Python 패키지는 git 저장소에서 설치할 수 있습니다. 다음 명령어 중 하나를 사용" +"하여 GitHub에서 직접 Flower를 설치하세요." #: ../../source/contributor-how-to-install-development-versions.rst:37 msgid "Install ``flwr`` from the default GitHub branch (``main``):" @@ -662,9 +683,9 @@ msgstr "기본 GitHub branch (``main``)에서 ``flwr`` 를 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:39 msgid "" -"``pip install flwr@git+https://github.com/adap/flower.git`` (without " -"extras)" -msgstr "``pip install flwr@git+https://github.com/adap/flower.git`` (extras 제외)" +"``pip install flwr@git+https://github.com/adap/flower.git`` (without extras)" +msgstr "" +"``pip install flwr@git+https://github.com/adap/flower.git`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:40 msgid "" @@ -688,11 +709,11 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" -"@branch-name`` (with extras)" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" +"name`` (with extras)" msgstr "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" -"@branch-name`` (extras 포함)" +"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" +"name`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:49 msgid "Open Jupyter Notebooks on Google Colab" @@ -703,32 +724,32 @@ msgid "" "Open the notebook ``doc/source/tutorial-series-get-started-with-flower-" "pytorch.ipynb``:" msgstr "" -"``doc/source/tutorial-series-get-started-with-flower-" -"pytorch.ipynb``notebook을 엽니다:" +"``doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb``notebook" +"을 엽니다:" #: ../../source/contributor-how-to-install-development-versions.rst:53 msgid "" -"https://colab.research.google.com/github/adap/flower/blob/main/doc/source" -"/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/main/doc/source/" +"tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" -"https://colab.research.google.com/github/adap/flower/blob/main/doc/source" -"/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/main/doc/source/" +"tutorial-series-get-started-with-flower-pytorch.ipynb" #: ../../source/contributor-how-to-install-development-versions.rst:55 msgid "" -"Open a development version of the same notebook from branch `branch-name`" -" by changing ``main`` to ``branch-name`` (right after ``blob``):" +"Open a development version of the same notebook from branch `branch-name` by " +"changing ``main`` to ``branch-name`` (right after ``blob``):" msgstr "" -"``main``을 ``branch-name``(``blob`` 바로 뒤)으로 변경하여 동일한 notebook의 개발 버전을 브랜치 " -"`branch-name`에서 엽니다 :" +"``main``을 ``branch-name``(``blob`` 바로 뒤)으로 변경하여 동일한 notebook의 " +"개발 버전을 브랜치 `branch-name`에서 엽니다 :" #: ../../source/contributor-how-to-install-development-versions.rst:57 msgid "" -"https://colab.research.google.com/github/adap/flower/blob/branch-" -"name/doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/branch-name/doc/" +"source/tutorial-series-get-started-with-flower-pytorch.ipynb" msgstr "" -"https://colab.research.google.com/github/adap/flower/blob/branch-" -"name/doc/source/tutorial-series-get-started-with-flower-pytorch.ipynb" +"https://colab.research.google.com/github/adap/flower/blob/branch-name/doc/" +"source/tutorial-series-get-started-with-flower-pytorch.ipynb" #: ../../source/contributor-how-to-install-development-versions.rst:59 msgid "Install a `whl` on Google Colab:" @@ -736,9 +757,11 @@ msgstr "Google Colab에서 `whl` 설치하기:" #: ../../source/contributor-how-to-install-development-versions.rst:61 msgid "" -"In the vertical icon grid on the left hand side, select ``Files`` > " -"``Upload to session storage``" -msgstr "왼쪽의 수직 아이콘 그리드에서 ``Files`` > ``Upload to session storage``를 선택하세요" +"In the vertical icon grid on the left hand side, select ``Files`` > ``Upload " +"to session storage``" +msgstr "" +"왼쪽의 수직 아이콘 그리드에서 ``Files`` > ``Upload to session storage``를 선" +"택하세요" #: ../../source/contributor-how-to-install-development-versions.rst:62 msgid "Upload the whl (e.g., ``flwr-1.8.0-py3-none-any.whl``)" @@ -746,13 +769,13 @@ msgstr "whl (예:``flwr-1.8.0-py3-none-any.whl``)을 업로드하세요" #: ../../source/contributor-how-to-install-development-versions.rst:63 msgid "" -"Change ``!pip install -q 'flwr[simulation]' torch torchvision " -"matplotlib`` to ``!pip install -q 'flwr-1.8.0-py3-none-" -"any.whl[simulation]' torch torchvision matplotlib``" +"Change ``!pip install -q 'flwr[simulation]' torch torchvision matplotlib`` " +"to ``!pip install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch " +"torchvision matplotlib``" msgstr "" -"``!pip install -q 'flwr[simulation]' torch torchvision matplotlib``를 " -"``!pip install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch " -"torchvision matplotlib``로 바꾸세요" +"``!pip install -q 'flwr[simulation]' torch torchvision matplotlib``를 ``!pip " +"install -q 'flwr-1.8.0-py3-none-any.whl[simulation]' torch torchvision " +"matplotlib``로 바꾸세요" #: ../../source/contributor-how-to-release-flower.rst:2 msgid "Release Flower" @@ -762,7 +785,8 @@ msgstr "Flower 릴리즈 하기" msgid "" "This document describes the current release process. It may or may not " "change in the future." -msgstr "이 문서는 현재 릴리즈 과정을 설명합니다. 이는 앞으로 변경될 수도 있습니다." +msgstr "" +"이 문서는 현재 릴리즈 과정을 설명합니다. 이는 앞으로 변경될 수도 있습니다." #: ../../source/contributor-how-to-release-flower.rst:7 msgid "During the release" @@ -770,12 +794,11 @@ msgstr "릴리즈 동안에" #: ../../source/contributor-how-to-release-flower.rst:9 msgid "" -"The version number of a release is stated in ``pyproject.toml``. To " -"release a new version of Flower, the following things need to happen (in " -"that order):" +"The version number of a release is stated in ``pyproject.toml``. To release " +"a new version of Flower, the following things need to happen (in that order):" msgstr "" -"릴리즈의 버전 번호는 ``pyproject.toml``에 명시되어 있습니다. Flower의 새 버전을 릴리즈하려면 다음 작업이 " -"순서대로 수행되어야 합니다:" +"릴리즈의 버전 번호는 ``pyproject.toml``에 명시되어 있습니다. Flower의 새 버전" +"을 릴리즈하려면 다음 작업이 순서대로 수행되어야 합니다:" #: ../../source/contributor-how-to-release-flower.rst:11 msgid "" @@ -783,39 +806,42 @@ msgid "" "order to add every new change to the changelog (feel free to make manual " "changes to the changelog afterwards until it looks good)." msgstr "" -"모든 새로운 변경 사항을 변경 로그에 추가하기 위해``python3 " -"src/py/flwr_tool/update_changelog.py ``을 실행합니다 (변경 로그가 " -"만족스러워질 때까지 수동으로 변경해도 됩니다)." +"모든 새로운 변경 사항을 변경 로그에 추가하기 위해``python3 src/py/flwr_tool/" +"update_changelog.py ``을 실행합니다 (변경 로그가 만족스러워질 " +"때까지 수동으로 변경해도 됩니다)." #: ../../source/contributor-how-to-release-flower.rst:12 msgid "" -"Once the changelog has been updated with all the changes, run ``./dev" -"/prepare-release-changelog.sh v``, where ```` " -"is the version stated in ``pyproject.toml`` (notice the ``v`` added " -"before it). This will replace the ``Unreleased`` header of the changelog " -"by the version and current date, and it will add a thanking message for " -"the contributors. Open a pull request with those changes." +"Once the changelog has been updated with all the changes, run ``./dev/" +"prepare-release-changelog.sh v``, where ```` is " +"the version stated in ``pyproject.toml`` (notice the ``v`` added before it). " +"This will replace the ``Unreleased`` header of the changelog by the version " +"and current date, and it will add a thanking message for the contributors. " +"Open a pull request with those changes." msgstr "" -"모든 변경 사항으로 변경 로그가 업데이트되면,``./dev/prepare-release-changelog.sh " -"v``을 실행합니다. 여기서 ````은 ``pyproject.toml``에 명시된 " -"버전 번호입니다 (앞에 ``v``가 추가된 것을 주의하세요). 이 명령어는 변경 로그의 ``Unreleased``헤더를 해당 버전과" -" 현재 날짜로 교체하고, 기여자들에게 감사 메시지가 추가됩니다. 이러한 변경 사항으로 pull request합니다." +"모든 변경 사항으로 변경 로그가 업데이트되면,``./dev/prepare-release-" +"changelog.sh v``을 실행합니다. 여기서 ````은 " +"``pyproject.toml``에 명시된 버전 번호입니다 (앞에 ``v``가 추가된 것을 주의하" +"세요). 이 명령어는 변경 로그의 ``Unreleased``헤더를 해당 버전과 현재 날짜로 " +"교체하고, 기여자들에게 감사 메시지가 추가됩니다. 이러한 변경 사항으로 pull " +"request합니다." #: ../../source/contributor-how-to-release-flower.rst:13 msgid "" "Once the pull request is merged, tag the release commit with the version " -"number as soon as the PR is merged: ``git tag v`` (notice " -"the ``v`` added before the version number), then ``git push --tags``. " -"This will create a draft release on GitHub containing the correct " -"artifacts and the relevant part of the changelog." +"number as soon as the PR is merged: ``git tag v`` (notice the " +"``v`` added before the version number), then ``git push --tags``. This will " +"create a draft release on GitHub containing the correct artifacts and the " +"relevant part of the changelog." msgstr "" "pull request가 병합되면, PR이 병합되는 즉시 버전 번호로 릴리즈 커밋에 태그를 " -"지정합니다:``git tag v`` (버전 번호 앞에 ``v``가 추가된 것을 " -"확인), 그 다음 ``git push --tags``. 이렇게 하면 올바른 아티팩트와 변경 " -"로그의 관련 부분이 포함된 초안 릴리즈가 GitHub에 생성됩니다." +"지정합니다:``git tag v`` (버전 번호 앞에 ``v``가 추가된 것을 확" +"인), 그 다음 ``git push --tags``. 이렇게 하면 올바른 아티팩트와 변경 로그의 " +"관련 부분이 포함된 초안 릴리즈가 GitHub에 생성됩니다." #: ../../source/contributor-how-to-release-flower.rst:14 -msgid "Check the draft release on GitHub, and if everything is good, publish it." +msgid "" +"Check the draft release on GitHub, and if everything is good, publish it." msgstr "GitHub에서 릴리즈 초안을 확인하고, 모든 것이 양호하면 게시하세요." #: ../../source/contributor-how-to-release-flower.rst:15 @@ -824,10 +850,10 @@ msgstr "Docker 이미지 빌드를 위해 CI를 트리거합니다." #: ../../source/contributor-how-to-release-flower.rst:17 msgid "" -"To trigger the workflow, a collaborator must create a " -"``workflow_dispatch`` event in the GitHub CI. This can be done either " -"through the UI or via the GitHub CLI. The event requires only one input, " -"the Flower version, to be released." +"To trigger the workflow, a collaborator must create a ``workflow_dispatch`` " +"event in the GitHub CI. This can be done either through the UI or via the " +"GitHub CLI. The event requires only one input, the Flower version, to be " +"released." msgstr "" "워크플로우를 트리거하려면 공동 작업자가 GitHub CI에서 ``workflow_dispatch``" "를 생성해야 합니다. 이 작업은 UI 또는 GitHub CLI 를 통해 수행할 수 있습니다. " @@ -839,18 +865,19 @@ msgstr "**UI를 통해서**" #: ../../source/contributor-how-to-release-flower.rst:23 msgid "" -"Go to the ``Build docker images`` workflow `page " -"`_." +"Go to the ``Build docker images`` workflow `page `_." msgstr "" "``Build docker images`` 워크플로우 `페이지 `_로 이동합니다." #: ../../source/contributor-how-to-release-flower.rst:24 msgid "" -"Click on the ``Run workflow`` button and type the new version of Flower " -"in the ``Version of Flower`` input field." -msgstr "``Run workflow`` 버튼을 누르고 ``Version of Flower``에 Flower의 새버전을 " -"입력합니다." +"Click on the ``Run workflow`` button and type the new version of Flower in " +"the ``Version of Flower`` input field." +msgstr "" +"``Run workflow`` 버튼을 누르고 ``Version of Flower``에 Flower의 새버전을 입력" +"합니다." #: ../../source/contributor-how-to-release-flower.rst:25 msgid "Click on the **green** ``Run workflow`` button." @@ -864,16 +891,17 @@ msgstr "**GitHub CI를 통해서**" msgid "" "Make sure you are logged in via ``gh auth login`` and that the current " "working directory is the root of the Flower repository." -msgstr "``gh auth login``을 통해 로그인 했는지, 현재 작업 디렉토리가 Flower " -"리포지토리의 root인지 확인하세요." +msgstr "" +"``gh auth login``을 통해 로그인 했는지, 현재 작업 디렉토리가 Flower 리포지토" +"리의 root인지 확인하세요." #: ../../source/contributor-how-to-release-flower.rst:32 msgid "" "Trigger the workflow via ``gh workflow run docker-images.yml -f flwr-" "version=``." msgstr "" -"``gh workflow run docker-images.yml -f flwr-version=``을 통해 " -"워크플로우 를 트리거합니다." +"``gh workflow run docker-images.yml -f flwr-version=``을 통해 워" +"크플로우 를 트리거합니다." #: ../../source/contributor-how-to-release-flower.rst:35 msgid "After the release" @@ -897,10 +925,11 @@ msgstr "``changelog.md``에 ``Unreleased`` 섹션을 새로 추가합니다." #: ../../source/contributor-how-to-release-flower.rst:43 msgid "" -"Merge the pull request on the same day (i.e., before a new nightly " -"release gets published to PyPI)." -msgstr "pull request를 같은 날(즉, 새로운 nightly 릴리즈가 PyPI에 게시되기 전에) " -"병합하세요." +"Merge the pull request on the same day (i.e., before a new nightly release " +"gets published to PyPI)." +msgstr "" +"pull request를 같은 날(즉, 새로운 nightly 릴리즈가 PyPI에 게시되기 전에) 병합" +"하세요." #: ../../source/contributor-how-to-release-flower.rst:46 msgid "Publishing a pre-release" @@ -912,11 +941,11 @@ msgstr "사전 릴리즈 이름" #: ../../source/contributor-how-to-release-flower.rst:51 msgid "" -"PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases" -" MUST use one of the following naming patterns:" +"PyPI supports pre-releases (alpha, beta, release candidate). Pre-releases " +"MUST use one of the following naming patterns:" msgstr "" -"PyPI는 사전 릴리즈(알파, 베타, 릴리스 후보)를 지원합니다. 사전 릴리즈는 " -"반드시 다음 명명 패턴 중 하나를 사용해야 합니다:" +"PyPI는 사전 릴리즈(알파, 베타, 릴리스 후보)를 지원합니다. 사전 릴리즈는 반드" +"시 다음 명명 패턴 중 하나를 사용해야 합니다:" #: ../../source/contributor-how-to-release-flower.rst:53 msgid "Alpha: ``MAJOR.MINOR.PATCHaN``" @@ -954,7 +983,8 @@ msgstr "``1.0.0rc1``" msgid "" "This is in line with PEP-440 and the recommendations from the Python " "Packaging Authority (PyPA):" -msgstr "이는 PEP-440 및 Python Packaging Authority (PyPA)의 권장 사항과 일치합니다:" +msgstr "" +"이는 PEP-440 및 Python Packaging Authority (PyPA)의 권장 사항과 일치합니다:" #: ../../source/contributor-how-to-release-flower.rst:67 msgid "`PEP-440 `_" @@ -962,37 +992,38 @@ msgstr "`PEP-440 `_" #: ../../source/contributor-how-to-release-flower.rst:68 msgid "" -"`PyPA Choosing a versioning scheme " -"`_" +"`PyPA Choosing a versioning scheme `_" msgstr "" "`PyPA 버전 관리 체계 선택하기 `_" #: ../../source/contributor-how-to-release-flower.rst:70 msgid "" -"Note that the approach defined by PyPA is not compatible with SemVer " -"2.0.0 spec, for details consult the `Semantic Versioning Specification " -"`_ (specifically item " -"11 on precedence)." +"Note that the approach defined by PyPA is not compatible with SemVer 2.0.0 " +"spec, for details consult the `Semantic Versioning Specification `_ (specifically item 11 on " +"precedence)." msgstr "" -"PyPA에서 정의한 접근 방식은 SemVer 2.0.0 사양과 호환되지 않으며, 자세한 " -"내용은`Semantic Versioning 관리 사양 `_ (특히 항목 11이 우선순위)을 참조하세요." +"PyPA에서 정의한 접근 방식은 SemVer 2.0.0 사양과 호환되지 않으며, 자세한 내용" +"은`Semantic Versioning 관리 사양 `_ (특히 항목 11이 우선순위)을 참조하세요." #: ../../source/contributor-how-to-release-flower.rst:73 msgid "Pre-release classification" msgstr "사전 릴리즈 분류" #: ../../source/contributor-how-to-release-flower.rst:75 -msgid "Should the next pre-release be called alpha, beta, or release candidate?" -msgstr "다음 사전 릴리를 알파, 베타 또는 릴리스 후보라고 불러야 하나요?" +msgid "" +"Should the next pre-release be called alpha, beta, or release candidate?" +msgstr "다음 사전 릴리즈를 알파, 베타 또는 릴리스 후보라고 불러야 하나요?" #: ../../source/contributor-how-to-release-flower.rst:77 msgid "" -"RC: feature complete, no known issues (apart from issues that are " -"classified as \"won't fix\" for the next stable release) - if no issues " -"surface this will become the next stable release" +"RC: feature complete, no known issues (apart from issues that are classified " +"as \"won't fix\" for the next stable release) - if no issues surface this " +"will become the next stable release" msgstr "" "RC: 기능 완료, 알려진 문제 없음(다음 stable 릴리즈에서 \"수정되지 않음\"으로 " "분류된 문제 제외) - 문제가 나타나지 않으면 다음 stable 릴리즈가 됩니다" @@ -1013,12 +1044,12 @@ msgstr "가상 환경 설정" msgid "" "It is recommended to run your Python setup within a virtual environment. " "This guide shows three different examples how to create a virtual " -"environment with pyenv virtualenv, poetry, or Anaconda. You can follow " -"the instructions or choose your preferred setup." +"environment with pyenv virtualenv, poetry, or Anaconda. You can follow the " +"instructions or choose your preferred setup." msgstr "" "가상 환경 내에서 파이썬 설정을 실행하는 것이 좋습니다. 이 가이드에서는 pyenv " -"virtualenv, poetry 또는 Anaconda를 사용하여 가상 환경을 만드는 세 가지 " -"예제를 보여줍니다. 안내를 따르거나 원하는 설정을 선택할 수 있습니다." +"virtualenv, poetry 또는 Anaconda를 사용하여 가상 환경을 만드는 세 가지 예제" +"를 보여줍니다. 안내를 따르거나 원하는 설정을 선택할 수 있습니다." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:9 msgid "Python Version" @@ -1027,23 +1058,21 @@ msgstr "Python 버전" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:11 #: ../../source/how-to-install-flower.rst:8 msgid "" -"Flower requires at least `Python 3.8 `_, " -"but `Python 3.10 `_ or above is " -"recommended." +"Flower requires at least `Python 3.8 `_, but " +"`Python 3.10 `_ or above is recommended." msgstr "" -"Flower는 `Python 3.8 `_이상이 필요하지만, `" -"Python 3.10 `_이상을 권장합니다." +"Flower는 `Python 3.8 `_이상이 필요하지만, " +"`Python 3.10 `_이상을 권장합니다." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:14 msgid "" -"Due to a known incompatibility with `ray " -"`_, we currently recommend utilizing at " -"most `Python 3.11 `_ for running Flower " -"simulations." +"Due to a known incompatibility with `ray `_, " +"we currently recommend utilizing at most `Python 3.11 `_ for running Flower simulations." msgstr "" -"`Ray `__와 호환되지 않는 것으로 알려져 " -"있으므로, 현재 Flower 시뮬레이션을 실행할 때는 최대 `Python 3.11 " -"`_을 사용하는 것이 좋습니다." +"`Ray `__와 호환되지 않는 것으로 알려져 있으므" +"로, 현재 Flower 시뮬레이션을 실행할 때는 최대 `Python 3.11 `_을 사용하는 것이 좋습니다." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:19 msgid "Virtualenv with Pyenv/Virtualenv" @@ -1051,23 +1080,23 @@ msgstr "Pyenv/Virtualenv를 사용한 가상 환경" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:21 msgid "" -"One of the recommended virtual environment is `pyenv " -"`_/`virtualenv `_. Please see `Flower examples " -"`_ for details." +"One of the recommended virtual environment is `pyenv `_/`virtualenv `_. " +"Please see `Flower examples `_ for details." msgstr "" -"권장 가상 환경 중 하나는 `pyenv `_/`" -"virtualenv `_입니다. 자세한 " -"내용은 `Flower examples `" -"_를 참조하세요." +"권장 가상 환경 중 하나는 `pyenv `_/" +"`virtualenv `_입니다. 자세한 내용" +"은 `Flower examples `_를 " +"참조하세요." #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:23 msgid "" "Once Pyenv is set up, you can use it to install `Python Version 3.10 " "`_ or above:" msgstr "" -"Pyenv가 설정되면 이를 사용하여 'Python 버전 3.10 `_ 이상'을 설치할 수 있습니다:" +"Pyenv가 설정되면 이를 사용하여 'Python 버전 3.10 `_ 이상'을 설치할 수 있습니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:29 msgid "Create the virtualenv with:" @@ -1083,19 +1112,20 @@ msgstr "Poetry를 사용한 가상 환경" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:46 msgid "" -"The Flower examples are based on `Poetry `_ to manage dependencies. After installing Poetry you " -"simply create a virtual environment with:" +"The Flower examples are based on `Poetry `_ " +"to manage dependencies. After installing Poetry you simply create a virtual " +"environment with:" msgstr "" -"Flower examples은 dependencies을 관리하기 위해 `Poetry `_를 기반으로 합니다. Poetry를 설치한 후 가상 환경을 생성하기만 " -"하면 됩니다:" +"Flower examples은 의존성을 관리하기 위해 `Poetry `_를 기반으로 합니다. Poetry를 설치한 후 가상 환경을 생성하기만 하면 " +"됩니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:52 msgid "" -"If you open a new terminal you can activate the previously created " -"virtual environment with the following command:" -msgstr "새 터미널을 열면 다음 명령을 사용하여 이전에 생성한 가상 환경을 활성화할 수 " +"If you open a new terminal you can activate the previously created virtual " +"environment with the following command:" +msgstr "" +"새 터미널을 열면 다음 명령을 사용하여 이전에 생성한 가상 환경을 활성화할 수 " "있습니다:" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:60 @@ -1104,12 +1134,12 @@ msgstr "Anaconda를 사용한 가상 환경" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:62 msgid "" -"If you prefer to use Anaconda for your virtual environment then install " -"and setup the `conda `_ package. After setting it up you can " -"create a virtual environment with:" +"If you prefer to use Anaconda for your virtual environment then install and " +"setup the `conda `_ package. After setting it up you can create a virtual " +"environment with:" msgstr "" -"가상 환경에서 Anaconda를 사용하려면 `conda `_ 패키지를 설치 및 " "설정하세요. 설정 후 다음을 사용하여 가상 환경을 만들 수 있습니다:" @@ -1123,8 +1153,8 @@ msgstr "그다음은?" #: ../../source/contributor-how-to-set-up-a-virtual-env.rst:78 msgid "" -"As soon as you created your virtual environment you clone one of the " -"`Flower examples `_." +"As soon as you created your virtual environment you clone one of the `Flower " +"examples `_." msgstr "" "가상 환경을 생성하자마자 'Flower examples `_ 중 하나를 클론합니다." @@ -1139,25 +1169,24 @@ msgstr "프로젝트 레이아웃" #: ../../source/contributor-how-to-write-documentation.rst:8 msgid "" -"The Flower documentation lives in the ``doc`` directory. The Sphinx-based" -" documentation system supports both reStructuredText (``.rst`` files) and" -" Markdown (``.md`` files)." +"The Flower documentation lives in the ``doc`` directory. The Sphinx-based " +"documentation system supports both reStructuredText (``.rst`` files) and " +"Markdown (``.md`` files)." msgstr "" "Flower 문서는 ``doc`` 디렉토리에 있습니다. Sphinx 기반 문서 시스템은 " -"reStructuredText 텍스트(``.rst`` 파일)와 Markdown(``.md`` 파일)을 모두 " -"지원합니다." +"reStructuredText 텍스트(``.rst`` 파일)와 Markdown(``.md`` 파일)을 모두 지원합" +"니다." #: ../../source/contributor-how-to-write-documentation.rst:10 #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:169 msgid "" -"Note that, in order to build the documentation locally (with ``poetry run" -" make html``, like described below), `Pandoc " -"`_ needs to be installed on the " -"system." +"Note that, in order to build the documentation locally (with ``poetry run " +"make html``, like described below), `Pandoc `_ needs to be installed on the system." msgstr "" -"로컬에서 문서를 작성하려면(아래 설명과 같이 ``poetry run make html``로) `" -"Pandoc `_이 시스템에 설치되어 있어야 " -"합니다." +"로컬에서 문서를 작성하려면(아래 설명과 같이 ``poetry run make html``로) " +"`Pandoc `_이 시스템에 설치되어 있어야 합" +"니다." #: ../../source/contributor-how-to-write-documentation.rst:14 msgid "Edit an existing page" @@ -1170,7 +1199,8 @@ msgstr "doc/source/``에서 기존 ``.rst``(또는 ``.md``) 파일을 편집합 #: ../../source/contributor-how-to-write-documentation.rst:17 #: ../../source/contributor-how-to-write-documentation.rst:27 msgid "Compile the docs: ``cd doc``, then ``poetry run make html``" -msgstr "문서를 컴파일합니다: ``cd doc``, ``poetry run make html`` 순으로 컴파일합니다" +msgstr "" +"문서를 컴파일합니다: ``cd doc``, ``poetry run make html`` 순으로 컴파일합니다" #: ../../source/contributor-how-to-write-documentation.rst:18 #: ../../source/contributor-how-to-write-documentation.rst:28 @@ -1195,18 +1225,18 @@ msgstr "``index.rst``에서 새 rst로 연결합니다" #: ../../source/contributor-ref-good-first-contributions.rst:2 msgid "Good first contributions" -msgstr "좋은 첫 번째 기여" +msgstr "훌륭한 첫 번째 기여" #: ../../source/contributor-ref-good-first-contributions.rst:4 msgid "" -"We welcome contributions to Flower! However, it is not always easy to " -"know where to start. We therefore put together a few recommendations on " -"where to start to increase your chances of getting your PR accepted into " -"the Flower codebase." +"We welcome contributions to Flower! However, it is not always easy to know " +"where to start. We therefore put together a few recommendations on where to " +"start to increase your chances of getting your PR accepted into the Flower " +"codebase." msgstr "" "Flower에 대한 기여를 환영합니다! 하지만 어디서부터 시작해야 할지 알기란 쉽지 " -"않습니다. 그래서 저희는 여러분의 PR이 Flower 코드베이스에 채택될 가능성을 " -"높이기 위해 어디서부터 시작해야 하는지 몇 가지 권장 사항을 정리해 보았습니다." +"않습니다. 그래서 저희는 여러분의 PR이 Flower 코드베이스에 채택될 가능성을 높" +"이기 위해 어디서부터 시작해야 하는지 몇 가지 권장 사항을 정리해 보았습니다." #: ../../source/contributor-ref-good-first-contributions.rst:11 msgid "Where to start" @@ -1214,13 +1244,13 @@ msgstr "시작 위치" #: ../../source/contributor-ref-good-first-contributions.rst:13 msgid "" -"Until the Flower core library matures it will be easier to get PR's " -"accepted if they only touch non-core areas of the codebase. Good " -"candidates to get started are:" +"Until the Flower core library matures it will be easier to get PR's accepted " +"if they only touch non-core areas of the codebase. Good candidates to get " +"started are:" msgstr "" "Flower 코어 라이브러리가 완성될 때까지는 코드베이스의 비핵심 영역만 건드리는 " -"것이 PR을 승인받기가 더 쉬울 것입니다. 시작하기에 좋은 후보자는 다음과 " -"같습니다:" +"것이 PR을 승인받기가 더 쉬울 것입니다. 시작하기에 좋은 후보자는 다음과 같습니" +"다:" #: ../../source/contributor-ref-good-first-contributions.rst:17 msgid "Documentation: What's missing? What could be expressed more clearly?" @@ -1240,31 +1270,31 @@ msgstr "Flower Baselines 요청" #: ../../source/contributor-ref-good-first-contributions.rst:25 msgid "" -"If you are not familiar with Flower Baselines, you should probably check-" -"out our `contributing guide for baselines " -"`_." +"If you are not familiar with Flower Baselines, you should probably check-out " +"our `contributing guide for baselines `_." msgstr "" "Flower Baseline에 익숙하지 않다면 ' Baseline 기여 가이드 `_를 확인해보세요." #: ../../source/contributor-ref-good-first-contributions.rst:27 msgid "" -"You should then check out the open `issues " -"`_" -" for baseline requests. If you find a baseline that you'd like to work on" -" and that has no assignees, feel free to assign it to yourself and start " -"working on it!" +"You should then check out the open `issues `_ for baseline " +"requests. If you find a baseline that you'd like to work on and that has no " +"assignees, feel free to assign it to yourself and start working on it!" msgstr "" -"그런 다음 오픈 된 `이슈 `_에서 baseline " "요청을 확인해야 합니다. 작업하고 싶은 기준선을 찾았지만 담당자가 없는 경우, " "자유롭게 자신에게 할당하고 작업을 시작하세요!" #: ../../source/contributor-ref-good-first-contributions.rst:31 msgid "" -"Otherwise, if you don't find a baseline you'd like to work on, be sure to" -" open a new issue with the baseline request template!" -msgstr "그렇지 않으면 작업하고 싶은 baseline을 찾지 못하면 baseline 요청 템플릿으로 " +"Otherwise, if you don't find a baseline you'd like to work on, be sure to " +"open a new issue with the baseline request template!" +msgstr "" +"그렇지 않으면 작업하고 싶은 baseline을 찾지 못하면 baseline 요청 템플릿으로 " "새 이슈를 열어야 합니다!" #: ../../source/contributor-ref-good-first-contributions.rst:34 @@ -1274,8 +1304,8 @@ msgstr "예시 요청" #: ../../source/contributor-ref-good-first-contributions.rst:36 msgid "" "We wish we had more time to write usage examples because we believe they " -"help users to get started with building what they want to build. Here are" -" a few ideas where we'd be happy to accept a PR:" +"help users to get started with building what they want to build. Here are a " +"few ideas where we'd be happy to accept a PR:" msgstr "" "사용 예시는 사용자가 원하는 것을 구축하는 데 도움이 된다고 생각하기 때문에 " "더 많은 시간을 할애하여 작성할 수 있었으면 합니다. 다음은 저희가 기꺼이 PR을 " @@ -1299,15 +1329,15 @@ msgstr "Secure Aggregation 프로토콜" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:4 msgid "" -"Include SecAgg, SecAgg+, and LightSecAgg protocol. The LightSecAgg " -"protocol has not been implemented yet, so its diagram and abstraction may" -" not be accurate in practice. The SecAgg protocol can be considered as a " -"special case of the SecAgg+ protocol." +"Include SecAgg, SecAgg+, and LightSecAgg protocol. The LightSecAgg protocol " +"has not been implemented yet, so its diagram and abstraction may not be " +"accurate in practice. The SecAgg protocol can be considered as a special " +"case of the SecAgg+ protocol." msgstr "" -"SecAgg, SecAgg+, LightSecAgg 프로토콜을 포함합니다. LightSecAgg 프로토콜은 " -"아직 구현되지 않았기 때문에 다이어그램과 추상화가 실제로는 정확하지 않을 수 " -"있습니다. SecAgg 프로토콜은 SecAgg+ 프로토콜의 특수한 경우로 간주할 수 " -"있습니다." +"SecAgg, SecAgg+, LightSecAgg 프로토콜을 포함합니다. LightSecAgg 프로토콜은 아" +"직 구현되지 않았기 때문에 다이어그램과 추상화가 실제로는 정확하지 않을 수 있" +"습니다. SecAgg 프로토콜은 SecAgg+ 프로토콜의 특수한 경우로 간주할 수 있습니" +"다." #: ../../source/contributor-ref-secure-aggregation-protocols.rst:8 msgid "The :code:`SecAgg+` abstraction" @@ -1317,18 +1347,18 @@ msgstr "The :code:`SecAgg+` 추상화" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:161 msgid "" "In this implementation, each client will be assigned with a unique index " -"(int) for secure aggregation, and thus many python dictionaries used have" -" keys of int type rather than ClientProxy type." +"(int) for secure aggregation, and thus many python dictionaries used have " +"keys of int type rather than ClientProxy type." msgstr "" -"구현에서는 각 클라이언트에 secure aggregation를 위한 고유 인덱스(int)가 " -"할당되므로 사용되는 많은 파이썬 dictionaries에는 ClientProxy 타입이 아닌 int " -"타입의 키가 있습니다." +"구현에서는 각 클라이언트에 secure aggregation를 위한 고유 인덱스(int)가 할당" +"되므로 사용되는 많은 파이썬 dictionaries에는 ClientProxy 타입이 아닌 int 타입" +"의 키가 있습니다." #: ../../source/contributor-ref-secure-aggregation-protocols.rst:65 #: ../../source/contributor-ref-secure-aggregation-protocols.rst:198 msgid "" -"The Flower server will execute and process received results in the " -"following order:" +"The Flower server will execute and process received results in the following " +"order:" msgstr "Flower 서버는 수신된 결과를 다음 순서로 실행하고 처리합니다:" #: ../../source/contributor-ref-secure-aggregation-protocols.rst:159 @@ -1345,16 +1375,17 @@ msgstr "GitHub에서 기여하기" #: ../../source/contributor-tutorial-contribute-on-github.rst:4 msgid "" -"This guide is for people who want to get involved with Flower, but who " -"are not used to contributing to GitHub projects." -msgstr "이 가이드는 Flower에 참여하고 싶지만 GitHub 프로젝트에 기여하는 데 익숙하지 " +"This guide is for people who want to get involved with Flower, but who are " +"not used to contributing to GitHub projects." +msgstr "" +"이 가이드는 Flower에 참여하고 싶지만 GitHub 프로젝트에 기여하는 데 익숙하지 " "않은 분들을 위한 것입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:6 msgid "" -"If you're familiar with how contributing on GitHub works, you can " -"directly checkout our :doc:`getting started guide for contributors " -"`." +"If you're familiar with how contributing on GitHub works, you can directly " +"checkout our :doc:`getting started guide for contributors `." msgstr "" "깃허브에서 기여하는 방식에 익숙하다면 :doc:`기여자를 위한 시작 가이드" "`를 직접 확인하세요." @@ -1372,19 +1403,19 @@ msgid "" "Git is a distributed version control tool. This allows for an entire " "codebase's history to be stored and every developer's machine. It is a " "software that will need to be installed on your local machine, you can " -"follow this `guide `_ to set it up." +"follow this `guide `_ to set it up." msgstr "" "Git은 분산 버전 관리 도구입니다. 이를 통해 전체 코드베이스의 히스토리와 모든 " -"개발자의 컴퓨터를 저장할 수 있습니다. 로컬 컴퓨터에 설치해야 하는 " -"소프트웨어로, 이 `가이드 `_를 따라 설정할 수 있습니다." +"개발자의 컴퓨터를 저장할 수 있습니다. 로컬 컴퓨터에 설치해야 하는 소프트웨어" +"로, 이 `가이드 `_를 따라 설정할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:16 msgid "" "GitHub, itself, is a code hosting platform for version control and " -"collaboration. It allows for everyone to collaborate and work from " -"anywhere on remote repositories." +"collaboration. It allows for everyone to collaborate and work from anywhere " +"on remote repositories." msgstr "" "GitHub는 그 자체로 버전 관리 및 협업을 위한 코드 호스팅 플랫폼입니다. 누구나 " "원격 레포지토리에서 어디서든 협업하고 작업할 수 있습니다." @@ -1393,19 +1424,20 @@ msgstr "" msgid "" "If you haven't already, you will need to create an account on `GitHub " "`_." -msgstr "아직 계정을 만들지 않았다면 `GitHub `_에서 계정을 " +msgstr "" +"아직 계정을 만들지 않았다면 `GitHub `_에서 계정을 " "만들어야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:20 msgid "" -"The idea behind the generic Git and GitHub workflow boils down to this: " -"you download code from a remote repository on GitHub, make changes " -"locally and keep track of them using Git and then you upload your new " -"history back to GitHub." +"The idea behind the generic Git and GitHub workflow boils down to this: you " +"download code from a remote repository on GitHub, make changes locally and " +"keep track of them using Git and then you upload your new history back to " +"GitHub." msgstr "" "일반적인 Git 및 GitHub 워크플로우의 기본 개념은 다음과 같이 요약됩니다. " -"GitHub의 원격 레포지토리에서 코드를 다운로드하고 로컬에서 변경한 후 Git을 " -"사용하여 추적한 다음 새 기록을 다시 GitHub에 업로드하는 것입니다." +"GitHub의 원격 레포지토리에서 코드를 다운로드하고 로컬에서 변경한 후 Git을 사" +"용하여 추적한 다음 새 기록을 다시 GitHub에 업로드하는 것입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:32 msgid "**Forking the Flower repository**" @@ -1413,25 +1445,25 @@ msgstr "**Flower 레포지토리 포크하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:24 msgid "" -"A fork is a personal copy of a GitHub repository. To create one for " -"Flower, you must navigate to ``_ (while " -"connected to your GitHub account) and click the ``Fork`` button situated " -"on the top right of the page." +"A fork is a personal copy of a GitHub repository. To create one for Flower, " +"you must navigate to ``_ (while connected to " +"your GitHub account) and click the ``Fork`` button situated on the top right " +"of the page." msgstr "" "포크는 GitHub 리포지토리의 개인 복사본입니다. Flower용 포크를 만들려면 " -"``_로 이동하여(GitHub 계정에 연결된 상태에서)" -" 페이지 오른쪽 상단에 있는 ``포크`` 버튼을 클릭해야 합니다." +"``_로 이동하여(GitHub 계정에 연결된 상태에" +"서) 페이지 오른쪽 상단에 있는 ``포크`` 버튼을 클릭해야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:29 msgid "" "You can change the name if you want, but this is not necessary as this " -"version of Flower will be yours and will sit inside your own account " -"(i.e., in your own list of repositories). Once created, you should see on" -" the top left corner that you are looking at your own version of Flower." +"version of Flower will be yours and will sit inside your own account (i.e., " +"in your own list of repositories). Once created, you should see on the top " +"left corner that you are looking at your own version of Flower." msgstr "" -"원하는 경우 이름을 변경할 수 있지만, 이 버전의 Flower는 자신의 계정(즉, " -"자신의 리포지토리 목록)에 위치하게 되므로 변경할 필요는 없습니다. 만들기가 " -"완료되면 왼쪽 상단에Flower 버전이 표시되는 것을 볼 수 있습니다." +"원하는 경우 이름을 변경할 수 있지만, 이 버전의 Flower는 자신의 계정(즉, 자신" +"의 리포지토리 목록)에 위치하게 되므로 변경할 필요는 없습니다. 만들기가 완료되" +"면 왼쪽 상단에Flower 버전이 표시되는 것을 볼 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "**Cloning your forked repository**" @@ -1440,27 +1472,29 @@ msgstr "**포크된 레포지토리 클론하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:35 msgid "" "The next step is to download the forked repository on your machine to be " -"able to make changes to it. On your forked repository page, you should " -"first click on the ``Code`` button on the right, this will give you the " -"ability to copy the HTTPS link of the repository." +"able to make changes to it. On your forked repository page, you should first " +"click on the ``Code`` button on the right, this will give you the ability to " +"copy the HTTPS link of the repository." msgstr "" -"다음 단계는 컴퓨터에서 포크된 레포지토리를 변경할 수 있도록 다운로드하는 " -"것입니다. 포크된 포지토리 페이지에서 먼저 오른쪽의 ``Code`` 버튼을 클릭하면 " -"레포지토리의 HTTPS 링크를 복사할 수 있습니다." +"다음 단계는 컴퓨터에서 포크된 레포지토리를 변경할 수 있도록 다운로드하는 것입" +"니다. 포크된 포지토리 페이지에서 먼저 오른쪽의 ``Code`` 버튼을 클릭하면 레포" +"지토리의 HTTPS 링크를 복사할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:41 msgid "" "Once you copied the \\, you can open a terminal on your machine, " "navigate to the place you want to download the repository to and type:" -msgstr "\\를 복사한 후에는 컴퓨터에서 터미널을 열고 레포지토리를 다운로드할 " -"위치로 이동하여 입력하면 됩니다:" +msgstr "" +"\\를 복사한 후에는 컴퓨터에서 터미널을 열고 레포지토리를 다운로드할 위" +"치로 이동하여 입력하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "" -"This will create a ``flower/`` (or the name of your fork if you renamed " -"it) folder in the current working directory." -msgstr "현재 작업 디렉터리에``flower/``(또는 포크 이름을 변경한 경우 포크 이름) " -"폴더가 생성됩니다." +"This will create a ``flower/`` (or the name of your fork if you renamed it) " +"folder in the current working directory." +msgstr "" +"현재 작업 디렉터리에``flower/``(또는 포크 이름을 변경한 경우 포크 이름) 폴더" +"가 생성됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:66 msgid "**Add origin**" @@ -1472,14 +1506,14 @@ msgstr "그런 다음 레포지토리 폴더로 이동할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:56 msgid "" -"And here we will need to add an origin to our repository. The origin is " -"the \\ of the remote fork repository. To obtain it, we can do as " -"previously mentioned by going to our fork repository on our GitHub " -"account and copying the link." +"And here we will need to add an origin to our repository. The origin is the " +"\\ of the remote fork repository. To obtain it, we can do as " +"previously mentioned by going to our fork repository on our GitHub account " +"and copying the link." msgstr "" -"여기에 레포지토리에 origin을 추가해야 합니다. origin은 원격 포크 " -"레포지토리의 \\입니다. origin을 얻으려면 앞서 설명한 대로 GitHub " -"계정의 포크 레포지토리로 이동하여 링크를 복사하면 됩니다." +"여기에 레포지토리에 origin을 추가해야 합니다. origin은 원격 포크 레포지토리" +"의 \\입니다. origin을 얻으려면 앞서 설명한 대로 GitHub 계정의 포크 레" +"포지토리로 이동하여 링크를 복사하면 됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:61 msgid "" @@ -1495,32 +1529,34 @@ msgstr "**Upstream 추가하기**" msgid "" "Now we will add an upstream address to our repository. Still in the same " "directory, we must run the following command:" -msgstr "이제 레포지토리에 upstream 주소를 추가하겠습니다. 여전히 같은 디렉터리에서 " -"다음 명령을 실행해야 합니다:" +msgstr "" +"이제 레포지토리에 upstream 주소를 추가하겠습니다. 여전히 같은 디렉터리에서 다" +"음 명령을 실행해야 합니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:76 -msgid "The following diagram visually explains what we did in the previous steps:" +msgid "" +"The following diagram visually explains what we did in the previous steps:" msgstr "다음 다이어그램은 이전 단계에서 수행한 작업을 시각적으로 설명합니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:80 msgid "" -"The upstream is the GitHub remote address of the parent repository (in " -"this case Flower), i.e. the one we eventually want to contribute to and " -"therefore need an up-to-date history of. The origin is just the GitHub " -"remote address of the forked repository we created, i.e. the copy (fork) " -"in our own account." +"The upstream is the GitHub remote address of the parent repository (in this " +"case Flower), i.e. the one we eventually want to contribute to and therefore " +"need an up-to-date history of. The origin is just the GitHub remote address " +"of the forked repository we created, i.e. the copy (fork) in our own account." msgstr "" -"upstream은 부모 레포지토리(이 경우 Flower)의 GitHub 원격 주소, 즉 우리가 " -"최종적으로 기여하고 싶고 따라서 최신 기록이 필요한 레포지토리입니다. " -"origin은 우리가 만든 포크된 레포지토리의 GitHub 원격 주소, 즉 우리 계정에 " -"있는 사본(포크)입니다." +"upstream은 부모 레포지토리(이 경우 Flower)의 GitHub 원격 주소, 즉 우리가 최종" +"적으로 기여하고 싶고 따라서 최신 기록이 필요한 레포지토리입니다. origin은 우" +"리가 만든 포크된 레포지토리의 GitHub 원격 주소, 즉 우리 계정에 있는 사본(포" +"크)입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:84 msgid "" "To make sure our local version of the fork is up-to-date with the latest " "changes from the Flower repository, we can execute the following command:" -msgstr "로컬 버전의 포크가 Flower 레포지토리의 최신 변경 사항으로 최신 상태인지 " -"확인하려면 다음 명령을 실행하면 됩니다:" +msgstr "" +"로컬 버전의 포크가 Flower 레포지토리의 최신 변경 사항으로 최신 상태인지 확인" +"하려면 다음 명령을 실행하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:93 msgid "Setting up the coding environment" @@ -1529,12 +1565,12 @@ msgstr "코딩 환경 설정" #: ../../source/contributor-tutorial-contribute-on-github.rst:95 msgid "" "This can be achieved by following this :doc:`getting started guide for " -"contributors ` (note " -"that you won't need to clone the repository). Once you are able to write " -"code and test it, you can finally start making changes!" +"contributors ` (note that " +"you won't need to clone the repository). Once you are able to write code and " +"test it, you can finally start making changes!" msgstr "" ":doc:'기여자를 위한 시작 가이드 '를 참조하세요(리포지토리를 복제할 필요는 없습니다). 코드를 " +"contributor>'를 참조하세요(레포지토리를 복제할 필요는 없습니다). 코드를 " "작성하고 테스트할 수 있게 되면 드디어 변경을 시작할 수 있습니다!" #: ../../source/contributor-tutorial-contribute-on-github.rst:100 @@ -1543,8 +1579,7 @@ msgstr "변경하기" #: ../../source/contributor-tutorial-contribute-on-github.rst:102 msgid "" -"Before making any changes make sure you are up-to-date with your " -"repository:" +"Before making any changes make sure you are up-to-date with your repository:" msgstr "변경하기 전에 레포지토리를 최신 상태로 유지하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:108 @@ -1557,16 +1592,15 @@ msgstr "**새 브랜치 만들기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:115 msgid "" -"To make the history cleaner and easier to work with, it is good practice " -"to create a new branch for each feature/project that needs to be " -"implemented." -msgstr "히스토리를 더 깔끔하고 작업하기 쉽게 만들려면 구현해야 하는 각 기능/" -"프로젝트에 대해 새 브랜치를 만드는 것이 좋습니다." +"To make the history cleaner and easier to work with, it is good practice to " +"create a new branch for each feature/project that needs to be implemented." +msgstr "" +"히스토리를 더 깔끔하고 작업하기 쉽게 만들려면 구현해야 하는 각 기능/프로젝트" +"에 대해 새 브랜치를 만드는 것이 좋습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:118 msgid "" -"To do so, just run the following command inside the repository's " -"directory:" +"To do so, just run the following command inside the repository's directory:" msgstr "이렇게 하려면 레포지토리 디렉토리에서 다음 명령을 실행하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 @@ -1574,8 +1608,9 @@ msgid "**Make changes**" msgstr "**변경하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:125 -msgid "Write great code and create wonderful changes using your favorite editor!" -msgstr "선호하 편집기를 사용하여 멋진 코드를 작성하고 훌륭한 변화를 만들어 보세요!" +msgid "" +"Write great code and create wonderful changes using your favorite editor!" +msgstr "선호하는 편집기를 사용하여 멋진 코드를 작성하고 훌륭한 변화를 만들어 보세요!" #: ../../source/contributor-tutorial-contribute-on-github.rst:138 msgid "**Test and format your code**" @@ -1583,9 +1618,9 @@ msgstr "**코드 테스트 및 서식 지정**" #: ../../source/contributor-tutorial-contribute-on-github.rst:128 msgid "" -"Don't forget to test and format your code! Otherwise your code won't be " -"able to be merged into the Flower repository. This is done so the " -"codebase stays consistent and easy to understand." +"Don't forget to test and format your code! Otherwise your code won't be able " +"to be merged into the Flower repository. This is done so the codebase stays " +"consistent and easy to understand." msgstr "" "코드를 테스트하고 서식을 지정하는 것을 잊지 마세요! 그렇지 않으면 코드를 " "Flower 레포지토리에 병합할 수 없습니다. 이는 코드베이스가 일관성을 유지하고 " @@ -1601,10 +1636,11 @@ msgstr "**Stage 변경**" #: ../../source/contributor-tutorial-contribute-on-github.rst:141 msgid "" -"Before creating a commit that will update your history, you must specify " -"to Git which files it needs to take into account." -msgstr "기록을 업데이트할 커밋을 만들기 전에 어떤 파일을 고려해야 하는지 Git에 " -"지정해야 합니다." +"Before creating a commit that will update your history, you must specify to " +"Git which files it needs to take into account." +msgstr "" +"기록을 업데이트할 커밋을 만들기 전에 어떤 파일을 고려해야 하는지 Git에 지정해" +"야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:143 msgid "This can be done with:" @@ -1612,12 +1648,12 @@ msgstr "이 작업을 수행할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:149 msgid "" -"To check which files have been modified compared to the last version " -"(last commit) and to see which files are staged for commit, you can use " -"the :code:`git status` command." +"To check which files have been modified compared to the last version (last " +"commit) and to see which files are staged for commit, you can use the :code:" +"`git status` command." msgstr "" -"마지막 버전(마지막 커밋)과 비교하여 수정된 파일을 확인하고 커밋을 위해 " -"스테이징된 파일을 확인하려면 :code:`git status` 명령을 사용하면 됩니다." +"마지막 버전(마지막 커밋)과 비교하여 수정된 파일을 확인하고 커밋을 위해 스테이" +"징된 파일을 확인하려면 :code:`git status` 명령을 사용하면 됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:160 msgid "**Commit changes**" @@ -1628,18 +1664,18 @@ msgid "" "Once you have added all the files you wanted to commit using :code:`git " "add`, you can finally create your commit using this command:" msgstr "" -":code:`git add`를 사용하여 커밋하려는 모든 파일을 추가한 후, 마지막으로 이 " -"명령을 사용하여 커밋을 생성할 수 있습니다:" +":code:`git add`를 사용하여 커밋하려는 모든 파일을 추가한 후, 마지막으로 이 명" +"령을 사용하여 커밋을 생성할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:159 msgid "" -"The \\ is there to explain to others what the commit " -"does. It should be written in an imperative style and be concise. An " -"example would be :code:`git commit -m \"Add images to README\"`." +"The \\ is there to explain to others what the commit does. " +"It should be written in an imperative style and be concise. An example would " +"be :code:`git commit -m \"Add images to README\"`." msgstr "" -"커밋의 내용을 다른 사람에게 설명하기 위해 \\가 있습니다. " -"명령형 스타일로 작성해야 하며 간결해야 합니다. 예를 들면 :code:`git commit -" -"m \"Add images to README\"`." +"커밋의 내용을 다른 사람에게 설명하기 위해 \\가 있습니다. 명" +"령형 스타일로 작성해야 하며 간결해야 합니다. 예를 들면 :code:`git commit -m " +"\"Add images to README\"`." #: ../../source/contributor-tutorial-contribute-on-github.rst:171 msgid "**Push the changes to the fork**" @@ -1647,19 +1683,20 @@ msgstr "**변경 사항을 포크에 푸시**" #: ../../source/contributor-tutorial-contribute-on-github.rst:163 msgid "" -"Once we have committed our changes, we have effectively updated our local" -" history, but GitHub has no way of knowing this unless we push our " -"changes to our origin's remote address:" +"Once we have committed our changes, we have effectively updated our local " +"history, but GitHub has no way of knowing this unless we push our changes to " +"our origin's remote address:" msgstr "" -"변경 사항을 커밋하면 로컬 히스토리를 효과적으로 업데이트한 것이지만, 변경 " -"사항을 원본의 원격 주소로 푸시하지 않는 한 GitHub는 이를 알 방법이 없습니다:" +"변경 사항을 커밋하면 로컬 히스토리를 효과적으로 업데이트한 것이지만, 변경 사" +"항을 원본의 원격 주소로 푸시하지 않는 한 GitHub는 이를 알 방법이 없습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:170 msgid "" "Once this is done, you will see on the GitHub that your forked repo was " "updated with the changes you have made." -msgstr "이 작업이 완료되면 변경한 내용으로 포크된 레포지토리가 업데이트된 것을 " -"GitHub에서 확인할 수 있습니다." +msgstr "" +"이 작업이 완료되면 변경한 내용으로 포크된 레포지토리가 업데이트된 것을 GitHub" +"에서 확인할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:174 msgid "Creating and merging a pull request (PR)" @@ -1671,39 +1708,43 @@ msgstr "**PR 만들기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:177 msgid "" -"Once you have pushed changes, on the GitHub webpage of your repository " -"you should see the following message:" -msgstr "변경 사항을 푸시하고 나면 레포지토리의 GitHub 웹페이지에 다음 메시지가 " -"표시됩니다:" +"Once you have pushed changes, on the GitHub webpage of your repository you " +"should see the following message:" +msgstr "" +"변경 사항을 푸시하고 나면 레포지토리의 GitHub 웹페이지에 다음 메시지가 표시됩" +"니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:181 msgid "Otherwise you can always find this option in the ``Branches`` page." -msgstr "그렇지 않으면 언제든지 ``Branches`` 페이지에서 이 옵션을 찾을 수 있습니다." +msgstr "" +"그렇지 않으면 언제든지 ``Branches`` 페이지에서 이 옵션을 찾을 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:183 msgid "" "Once you click the ``Compare & pull request`` button, you should see " "something similar to this:" -msgstr "``Compare & pull request`` 버튼을 클릭하면 이와 비슷한 화면이 표시됩니다:" +msgstr "" +"``Compare & pull request`` 버튼을 클릭하면 이와 비슷한 화면이 표시됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:187 -msgid "At the top you have an explanation of which branch will be merged where:" +msgid "" +"At the top you have an explanation of which branch will be merged where:" msgstr "상단에는 어느 지점이 어디에 병합될 것인지에 대한 설명이 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:191 msgid "" -"In this example you can see that the request is to merge the branch " -"``doc-fixes`` from my forked repository to branch ``main`` from the " -"Flower repository." +"In this example you can see that the request is to merge the branch ``doc-" +"fixes`` from my forked repository to branch ``main`` from the Flower " +"repository." msgstr "" -"이 예제에서는 내 포크된 레포지토리의 ``doc-fixes`` 브랜치를 Flower " -"레포지토리의 ``main`` 브랜치에 병합하라는 요청을 볼 수 있습니다." +"이 예제에서는 내 포크된 레포지토리의 ``doc-fixes`` 브랜치를 Flower 레포지토리" +"의 ``main`` 브랜치에 병합하라는 요청을 볼 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:193 msgid "" "The title should be changed to adhere to the :ref:`pr_title_format` " -"guidelines, otherwise it won't be possible to merge the PR. So in this " -"case, a correct title might be ``docs(framework:skip) Fix typos``." +"guidelines, otherwise it won't be possible to merge the PR. So in this case, " +"a correct title might be ``docs(framework:skip) Fix typos``." msgstr "" "제목은 :ref:`pr_title_format` 가이드라인을 준수하도록 변경해야 하며, 그렇지 " "않으면 PR을 병합할 수 없습니다. 따라서 이 경우 올바른 제목은 " @@ -1711,14 +1752,13 @@ msgstr "" #: ../../source/contributor-tutorial-contribute-on-github.rst:196 msgid "" -"The input box in the middle is there for you to describe what your PR " -"does and to link it to existing issues. We have placed comments (that " -"won't be rendered once the PR is opened) to guide you through the " -"process." +"The input box in the middle is there for you to describe what your PR does " +"and to link it to existing issues. We have placed comments (that won't be " +"rendered once the PR is opened) to guide you through the process." msgstr "" -"가운데에 있는 입력 상자는 PR의 기능을 설명하고 기존 이슈에 연결할 수 있는 " -"곳입니다. 프로세스를 안내하기 위해 코멘트(PR이 열리면 렌더링되지 않음)를 " -"배치했습니다." +"가운데에 있는 입력 상자는 PR의 기능을 설명하고 기존 이슈에 연결할 수 있는 곳" +"입니다. 프로세스를 안내하기 위해 코멘트(PR이 열리면 렌더링되지 않음)를 배치했" +"습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:199 msgid "It is important to follow the instructions described in comments." @@ -1727,16 +1767,16 @@ msgstr "코멘트에 설명된 지침을 따르는 것이 중요합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:201 msgid "" "At the bottom you will find the button to open the PR. This will notify " -"reviewers that a new PR has been opened and that they should look over it" -" to merge or to request changes." +"reviewers that a new PR has been opened and that they should look over it to " +"merge or to request changes." msgstr "" "하단에는 PR을 여는 버튼이 있습니다. 이렇게 하면 검토자에게 새 PR이 열렸으며 " "병합하거나 변경을 요청하기 위해 검토해야 함을 알립니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:204 msgid "" -"If your PR is not yet ready for review, and you don't want to notify " -"anyone, you have the option to create a draft pull request:" +"If your PR is not yet ready for review, and you don't want to notify anyone, " +"you have the option to create a draft pull request:" msgstr "" "PR이 아직 검토할 준비가 되지 않았고 다른 사람에게 알리고 싶지 않은 경우 pull " "request 초안을 만드는 옵션이 있습니다:" @@ -1748,10 +1788,11 @@ msgstr "**new changes 만들기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "" "Once the PR has been opened (as draft or not), you can still push new " -"commits to it the same way we did before, by making changes to the branch" -" associated with the PR." -msgstr "PR이 초안으로 열렸든 아니든, PR과 연결된 브랜치를 변경하여 이전과 같은 " -"방식으로 새 커밋을 푸시할 수 있습니다." +"commits to it the same way we did before, by making changes to the branch " +"associated with the PR." +msgstr "" +"PR이 초안으로 열렸든 아니든, PR과 연결된 브랜치를 변경하여 이전과 같은 방식으" +"로 새 커밋을 푸시할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:231 msgid "**Review the PR**" @@ -1759,17 +1800,19 @@ msgstr "**PR 검토하기**" #: ../../source/contributor-tutorial-contribute-on-github.rst:212 msgid "" -"Once the PR has been opened or once the draft PR has been marked as " -"ready, a review from code owners will be automatically requested:" -msgstr "PR이 열리거나 초안 PR이 준비됨으로 표시되면 코드 소유자의 검토가 자동으로 " -"요청됩니다:" +"Once the PR has been opened or once the draft PR has been marked as ready, a " +"review from code owners will be automatically requested:" +msgstr "" +"PR이 열리거나 초안 PR이 준비됨으로 표시되면 코드 소유자의 검토가 자동으로 요" +"청됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:216 msgid "" -"Code owners will then look into the code, ask questions, request changes " -"or validate the PR." -msgstr "그러면 코드 소유자는 코드를 살펴보고, 질문하고, 변경을 요청하거나 PR의 " -"유효성을 검사합니다." +"Code owners will then look into the code, ask questions, request changes or " +"validate the PR." +msgstr "" +"그러면 코드 소유자는 코드를 살펴보고, 질문하고, 변경을 요청하거나 PR의 유효성" +"을 검사합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:218 msgid "Merging will be blocked if there are ongoing requested changes." @@ -1777,9 +1820,10 @@ msgstr "진행 중인 변경 요청이 있는 경우 병합이 차단됩니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:222 msgid "" -"To resolve them, just push the necessary changes to the branch associated" -" with the PR:" -msgstr "이를 해결하려면 PR과 연결된 브랜치에 필요한 변경 사항을 푸시하면 됩니다:" +"To resolve them, just push the necessary changes to the branch associated " +"with the PR:" +msgstr "" +"이를 해결하려면 PR과 연결된 브랜치에 필요한 변경 사항을 푸시하면 됩니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:226 msgid "And resolve the conversation:" @@ -1787,8 +1831,7 @@ msgstr "그리고 소통을 통해 해결하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:230 msgid "" -"Once all the conversations have been resolved, you can re-request a " -"review." +"Once all the conversations have been resolved, you can re-request a review." msgstr "모든 대화가 해결되면 검토를 다시 요청할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:251 @@ -1797,16 +1840,18 @@ msgstr "**PR이 병합되면**" #: ../../source/contributor-tutorial-contribute-on-github.rst:234 msgid "" -"If all the automatic tests have passed and reviewers have no more changes" -" to request, they can approve the PR and merge it." -msgstr "모든 자동 테스트가 통과되고 검토자가 더 이상 요청할 변경 사항이 없는 경우 " -"PR을 승인하고 병합할 수 있습니다." +"If all the automatic tests have passed and reviewers have no more changes to " +"request, they can approve the PR and merge it." +msgstr "" +"모든 자동 테스트가 통과되고 검토자가 더 이상 요청할 변경 사항이 없는 경우 PR" +"을 승인하고 병합할 수 있습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:238 msgid "" "Once it is merged, you can delete the branch on GitHub (a button should " "appear to do so) and also delete it locally by doing:" -msgstr "병합이 완료되면 GitHub에서 브랜치를 삭제할 수 있으며(삭제 버튼이 표시되어야 " +msgstr "" +"병합이 완료되면 GitHub에서 브랜치를 삭제할 수 있으며(삭제 버튼이 표시되어야 " "함), 로컬에서도 삭제할 수 있습니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:245 @@ -1823,40 +1868,43 @@ msgstr "문제" #: ../../source/contributor-tutorial-contribute-on-github.rst:259 msgid "" -"For our documentation, we've started to use the `Diàtaxis framework " -"`_." -msgstr "저희 문서에는 'Diàtaxis 프레임워크 `_'를 사용하기 " -"시작했습니다." +"For our documentation, we've started to use the `Diàtaxis framework `_." +msgstr "" +"저희 문서에는 'Diàtaxis 프레임워크 `_'를 사용하기 시작" +"했습니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:261 msgid "" -"Our \"How to\" guides should have titles that continue the sentence \"How" -" to …\", for example, \"How to upgrade to Flower 1.0\"." +"Our \"How to\" guides should have titles that continue the sentence \"How to " +"…\", for example, \"How to upgrade to Flower 1.0\"." msgstr "" -"'How to' 가이드의 제목은 \"How to …\"라는 문장을 이어가는 제목이어야 " -"합니다(예: \"How to upgrade to Flower 1.0\")." +"'How to' 가이드의 제목은 \"How to …\"라는 문장을 이어가는 제목이어야 합니다" +"(예: \"How to upgrade to Flower 1.0\")." #: ../../source/contributor-tutorial-contribute-on-github.rst:263 msgid "" "Most of our guides do not follow this new format yet, and changing their " "title is (unfortunately) more involved than one might think." -msgstr "대부분의 가이드는 아직 이 새로운 형식을 따르지 않으며, 안타깝게도 제목을 " -"변경하는 작업은 생각보다 복잡합니다." +msgstr "" +"대부분의 가이드는 아직 이 새로운 형식을 따르지 않으며, 안타깝게도 제목을 변경" +"하는 작업은 생각보다 복잡합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:265 msgid "" -"This issue is about changing the title of a doc from present continuous " -"to present simple." -msgstr "이번 이슈는 문서 제목을 현재 연속형에서 현재 단순형으로 변경하는 것에 관한 " -"것입니다." +"This issue is about changing the title of a doc from present continuous to " +"present simple." +msgstr "" +"이번 이슈는 문서 제목을 현재 연속형에서 현재 단순형으로 변경하는 것에 관한 것" +"입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:267 msgid "" "Let's take the example of \"Saving Progress\" which we changed to \"Save " "Progress\". Does this pass our check?" msgstr "" -"\"How to saving progress\"을 \"How to save progress\"으로 변경한 예를 들어 " -"보겠습니다. 이것이 우리의 점검을 통과했나요?" +"\"How to saving progress\"을 \"How to save progress\"으로 변경한 예를 들어 보" +"겠습니다. 이것이 우리의 점검을 통과했나요?" #: ../../source/contributor-tutorial-contribute-on-github.rst:269 msgid "Before: \"How to saving progress\" ❌" @@ -1876,7 +1924,7 @@ msgid "" "After cloning and setting up the Flower repo, here's what you should do:" msgstr "" "이것은 사소한 변경이지만 end-to-end 설정을 테스트할 수 있습니다. Flower " -"포지토리를 복제하고 설정한 후에는 다음과 같이 하세요:" +"레포지토리를 복제하고 설정한 후에는 다음과 같이 하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:278 msgid "Find the source file in ``doc/source``" @@ -1886,13 +1934,14 @@ msgstr "``doc/source``에서 소스 파일을 찾습니다" msgid "" "Make the change in the ``.rst`` file (beware, the dashes under the title " "should be the same length as the title itself)" -msgstr "``.rst`` 파일에서 변경합니다(제목 아래의 대시는 제목 자체의 길이와 같아야 " -"합니다)" +msgstr "" +"``.rst`` 파일에서 변경합니다(제목 아래의 대시는 제목 자체의 길이와 같아야 합" +"니다)" #: ../../source/contributor-tutorial-contribute-on-github.rst:280 msgid "" -"Build the docs and `check the result `_" +"Build the docs and `check the result `_" msgstr "" "문서를 빌드하고 '결과 확인 `_'합니다" @@ -1903,14 +1952,14 @@ msgstr "파일 이름 바꾸기" #: ../../source/contributor-tutorial-contribute-on-github.rst:285 msgid "" -"You might have noticed that the file name still reflects the old wording." -" If we just change the file, then we break all existing links to it - it " -"is **very important** to avoid that, breaking links can harm our search " -"engine ranking." +"You might have noticed that the file name still reflects the old wording. If " +"we just change the file, then we break all existing links to it - it is " +"**very important** to avoid that, breaking links can harm our search engine " +"ranking." msgstr "" -"파일 이름에 여전히 이전 문구가 반영되어 있는 것을 보셨을 것입니다. 파일만 " -"변경하면 파일에 대한 기존 링크가 모두 끊어지는데, 링크를 끊으면 검색 엔진 " -"순위에 영향을 줄 수 있으므로 이를 방지하는 것이 **매우 중요**합니다." +"파일 이름에 여전히 이전 문구가 반영되어 있는 것을 보셨을 것입니다. 파일만 변" +"경하면 파일에 대한 기존 링크가 모두 끊어지는데, 링크를 끊으면 검색 엔진 순위" +"에 영향을 줄 수 있으므로 이를 방지하는 것이 **매우 중요**합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:288 msgid "Here's how to change the file name:" @@ -1926,11 +1975,11 @@ msgstr "'doc/source/conf.py'에 리디렉션 규칙을 추가합니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:293 msgid "" -"This will cause a redirect from ``saving-progress.html`` to ``save-" -"progress.html``, old links will continue to work." +"This will cause a redirect from ``saving-progress.html`` to ``save-progress." +"html``, old links will continue to work." msgstr "" -"이렇게 하면 ``saving-progress.html``에서 ``save-progress.html``로 " -"리디렉션되며, 이전 링크는 계속 작동합니다." +"이렇게 하면 ``saving-progress.html``에서 ``save-progress.html``로 리디렉션되" +"며, 이전 링크는 계속 작동합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:296 msgid "Apply changes in the index file" @@ -1942,8 +1991,8 @@ msgid "" "update the ``index.rst`` file as well. This is where we define the whole " "arborescence of the navbar." msgstr "" -"횡방향 내비게이션 바가 제대로 작동하려면 ``index.rst`` 파일도 업데이트하는 " -"것이 매우 중요합니다. 이 파일은 탐색 모음의 전체 배열을 정의하는 곳입니다." +"횡방향 내비게이션 바가 제대로 작동하려면 ``index.rst`` 파일도 업데이트하는 것" +"이 매우 중요합니다. 이 파일은 탐색 모음의 전체 배열을 정의하는 곳입니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:301 msgid "Find and modify the file name in ``index.rst``" @@ -1955,11 +2004,11 @@ msgstr "PR 열기" #: ../../source/contributor-tutorial-contribute-on-github.rst:306 msgid "" -"Commit the changes (commit messages are always imperative: \"Do " -"something\", in this case \"Change …\")" +"Commit the changes (commit messages are always imperative: \"Do something\", " +"in this case \"Change …\")" msgstr "" -"변경 사항을 커밋합니다(커밋 메시지는 항상 필수 메시지입니다:\"Do something\"(" -"이 경우 는 \"Change …\" )" +"변경 사항을 커밋합니다(커밋 메시지는 항상 필수 메시지입니다:\"Do " +"something\"(이 경우 는 \"Change …\" )" #: ../../source/contributor-tutorial-contribute-on-github.rst:307 msgid "Push the changes to your fork" @@ -1967,9 +2016,10 @@ msgstr "변경 사항을 포크에 푸시합니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:308 msgid "" -"Open a PR (as shown above) with title ``docs(framework) Update how-to " -"guide title``" -msgstr "``docs(framework) Update how-to guide title`` 제목으로 PR(위와 같이)을 엽니다" +"Open a PR (as shown above) with title ``docs(framework) Update how-to guide " +"title``" +msgstr "" +"``docs(framework) Update how-to guide title`` 제목으로 PR(위와 같이)을 엽니다" #: ../../source/contributor-tutorial-contribute-on-github.rst:309 msgid "Wait for it to be approved!" @@ -1990,18 +2040,17 @@ msgstr "다음 단계" #: ../../source/contributor-tutorial-contribute-on-github.rst:316 msgid "" -"Once you have made your first PR, and want to contribute more, be sure to" -" check out the following :" -msgstr "첫 번째 PR을 작성하고 더 많은 기여를 하고 싶다면 다음 을 확인하세요:" +"Once you have made your first PR, and want to contribute more, be sure to " +"check out the following :" +msgstr "첫 번째 PR을 작성하고 더 많은 기여를 하고 싶다면 다음을 확인하세요:" #: ../../source/contributor-tutorial-contribute-on-github.rst:318 msgid "" -":doc:`Good first contributions `, where you should particularly look into the " -":code:`baselines` contributions." +":doc:`Good first contributions `, " +"where you should particularly look into the :code:`baselines` contributions." msgstr "" -":doc:`훌륭한 첫 번째 기여 `, 특히 " -":code:`baselines` 기여를 살펴봐야 합니다." +":doc:`훌륭한 첫 번째 기여 `, 특히 :" +"code:`baselines` 기여를 살펴봐야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:322 #: ../../source/fed/0000-20200102-fed-template.md:60 @@ -2018,23 +2067,23 @@ msgstr "다음과 같은 PR 제목 형식을 적용합니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:335 msgid "" -"(or ``(:skip) `` to ignore the PR in the " -"changelog)" -msgstr "(또는 ``(:skip) ``를 사용하면 변경 로그에서 PR을 " -"무시합니다.)" +"(or ``(:skip) `` to ignore the PR in the changelog)" +msgstr "" +"(또는 ``(:skip) ``를 사용하면 변경 로그에서 PR을 무시" +"합니다.)" #: ../../source/contributor-tutorial-contribute-on-github.rst:337 msgid "" -"Where ```` needs to be in ``{ci, fix, feat, docs, refactor, " -"break}``, ```` should be in ``{framework, baselines, datasets, " -"examples, or '*' when modifying multiple projects which requires the " -"':skip' flag to be used}``, and ```` starts with a capitalised " -"verb in the imperative mood." +"Where ```` needs to be in ``{ci, fix, feat, docs, refactor, break}``, " +"```` should be in ``{framework, baselines, datasets, examples, or " +"'*' when modifying multiple projects which requires the ':skip' flag to be " +"used}``, and ```` starts with a capitalised verb in the imperative " +"mood." msgstr "" "여기서 ````은 ``{ci, fix, feat, docs, refactor, break}``, ````" -"는 ``{framework, baselines, datasets, examples, or '*' ':skip' 플래그를 " -"사용해야 하는 여러 프로젝트를 수정하는 경우}``로 입력해야 하며, ````" -"는 대문자로 시작해야 합니다." +"는 ``{framework, baselines, datasets, examples, or '*' ':skip' 플래그를 사용" +"해야 하는 여러 프로젝트를 수정하는 경우}``로 입력해야 하며, ````는 " +"대문자로 시작해야 합니다." #: ../../source/contributor-tutorial-contribute-on-github.rst:341 msgid "Valid examples:" @@ -2054,7 +2103,7 @@ msgstr "``ci(*:skip) Enforce PR title format``" #: ../../source/contributor-tutorial-contribute-on-github.rst:347 msgid "Invalid examples:" -msgstr "잘못된 예제입니다:" +msgstr "잘못된 예시입니다:" #: ../../source/contributor-tutorial-contribute-on-github.rst:349 msgid "``feat(framework): Add flwr build CLI command`` (extra ``:``)" @@ -2064,7 +2113,8 @@ msgstr "``feat(framework): Add flwr build CLI command`` ( ``:``제외)" msgid "" "``feat(*) Add flwr build CLI command`` (missing ``skip`` flag along with " "``*``)" -msgstr "``feat(*) Add flwr build CLI command`` (``skip`` flag와 함께 ``*``누락)" +msgstr "" +"``feat(*) Add flwr build CLI command`` (``skip`` flag와 함께 ``*``누락)" #: ../../source/contributor-tutorial-contribute-on-github.rst:351 msgid "``feat(skip) Add flwr build CLI command`` (missing ````)" @@ -2072,7 +2122,8 @@ msgstr "``feat(skip) Add flwr build CLI command`` (````누락)" #: ../../source/contributor-tutorial-contribute-on-github.rst:352 msgid "``feat(framework) add flwr build CLI command`` (non capitalised verb)" -msgstr "``feat(framework) add flwr build CLI command`` (대문자로 표기되지 않은 동사)" +msgstr "" +"``feat(framework) add flwr build CLI command`` (대문자로 표기되지 않은 동사)" #: ../../source/contributor-tutorial-contribute-on-github.rst:353 msgid "``feat(framework) Add flwr build CLI command.`` (dot at the end)" @@ -2104,17 +2155,19 @@ msgid "(Optional) `pyenv `_" msgstr "(선택 사항) `pyenv `_" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:10 -msgid "(Optional) `pyenv-virtualenv `_" -msgstr "(선택 사항) `pyenv-virtualenv `_" +msgid "" +"(Optional) `pyenv-virtualenv `_" +msgstr "" +"(선택 사항) `pyenv-virtualenv `_" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:12 msgid "" "Flower uses :code:`pyproject.toml` to manage dependencies and configure " -"development tools (the ones which support it). Poetry is a build tool " -"which supports `PEP 517 `_." +"development tools (the ones which support it). Poetry is a build tool which " +"supports `PEP 517 `_." msgstr "" -"Flower는 dependencies을 관리하고 개발 도구(이를 지원하는 도구)를 구성하기 " -"위해 :code:`pyproject.toml`을 사용합니다. Poetry는 `PEP 517 `_을 지원하는 빌드 도구입니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:18 @@ -2127,7 +2180,7 @@ msgstr "사전 준비" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:22 msgid "Some system-wide dependencies are needed." -msgstr "일부 시스템 전체에 대한 dependencies이 필요합니다." +msgstr "일부 시스템 전체에 대한 의존성이 필요합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:25 msgid "For macOS" @@ -2135,17 +2188,18 @@ msgstr "macOS의 경우" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:27 msgid "" -"Install `homebrew `_. Don't forget the post-" -"installation actions to add `brew` to your PATH." +"Install `homebrew `_. Don't forget the post-installation " +"actions to add `brew` to your PATH." msgstr "" -"`homebrew `_를 설치합니다. 설치 후 `brew`를 PATH에 " -"추가하는 작업을 잊지 마세요." +"`homebrew `_를 설치합니다. 설치 후 `brew`를 PATH에 추가하" +"는 작업을 잊지 마세요." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:28 msgid "" -"Install `xz` (to install different Python versions) and `pandoc` to build" -" the docs::" -msgstr "xz`(다른 Python 버전을 설치하려면)와 `pandoc`을 설치하여 문서를 빌드합니다::" +"Install `xz` (to install different Python versions) and `pandoc` to build " +"the docs::" +msgstr "" +"xz`(다른 Python 버전을 설치하려면)와 `pandoc`을 설치하여 문서를 빌드합니다::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:34 msgid "For Ubuntu" @@ -2153,10 +2207,11 @@ msgstr "Ubuntu의 경우" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:35 msgid "" -"Ensure you system (Ubuntu 22.04+) is up-to-date, and you have all " -"necessary packages::" -msgstr "시스템(우분투 22.04 이상)이 최신 상태이고 필요한 패키지가 모두 설치되어 " -"있는지 확인하세요:" +"Ensure you system (Ubuntu 22.04+) is up-to-date, and you have all necessary " +"packages::" +msgstr "" +"시스템(우분투 22.04 이상)이 최신 상태이고 필요한 패키지가 모두 설치되어 있는" +"지 확인하세요:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:44 msgid "Create Flower Dev Environment" @@ -2167,27 +2222,27 @@ msgid "" "1. Clone the `Flower repository `_ from " "GitHub::" msgstr "" -"1. GitHub: 에서 ``Flower 레포지토리 `_를 " -"복제합니다::" +"1. GitHub: 에서 ``Flower 레포지토리 `_를 복제" +"합니다::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:52 msgid "" -"Let's create the Python environment for all-things Flower. If you wish to" -" use :code:`pyenv`, we provide two convenience scripts that you can use. " -"If you prefer using something else than :code:`pyenv`, create a new " +"Let's create the Python environment for all-things Flower. If you wish to " +"use :code:`pyenv`, we provide two convenience scripts that you can use. If " +"you prefer using something else than :code:`pyenv`, create a new " "environment, activate and skip to the last point where all packages are " "installed." msgstr "" -"Flower의 모든 것을 위한 파이썬 환경을 만들어 보겠습니다.:code:`pyenv`를 " -"사용하고자 하는 경우 사용할 수 있는 두 가지 편의 스크립트를 " -"제공합니다.:code:`pyenv`가 아닌 다른 것을 사용하려면 새 환경을 생성하고 " -"활성화한 후 모든 패키지가 설치된 마지막 지점으로 건너뛰세요." +"Flower의 모든 것을 위한 파이썬 환경을 만들어 보겠습니다.:code:`pyenv`를 사용" +"하고자 하는 경우 사용할 수 있는 두 가지 편의 스크립트를 제공합니다.:code:" +"`pyenv`가 아닌 다른 것을 사용하려면 새 환경을 생성하고 활성화한 후 모든 패키" +"지가 설치된 마지막 지점으로 건너뛰세요." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:54 msgid "" -"If you don't have :code:`pyenv` installed, the following script that will" -" install it, set it up, and create the virtual environment (with " -":code:`Python 3.8.17` by default)::" +"If you don't have :code:`pyenv` installed, the following script that will " +"install it, set it up, and create the virtual environment (with :code:" +"`Python 3.8.17` by default)::" msgstr "" ":code:`pyenv`가 설치되어 있지 않은 경우 다음 스크립트를 사용하여 설치, 설정 " "및 가상 환경을 생성합니다(기본적으로 :code:`Python 3.8.17` 사용):" @@ -2195,8 +2250,8 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:58 msgid "" "If you already have :code:`pyenv` installed (along with the :code:`pyenv-" -"virtualenv` plugin), you can use the following convenience script (with " -":code:`Python 3.8.17` by default)::" +"virtualenv` plugin), you can use the following convenience script (with :" +"code:`Python 3.8.17` by default)::" msgstr "" ":code:`pyenv`가 이미 설치되어 있는 경우( :code:`pyenv-virtualenv` 플러그인과 " "함께) 다음과 같은 편의 스크립트를 사용할 수 있습니다(기본적으로 코드:`Python " @@ -2204,11 +2259,11 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:62 msgid "" -"3. Install the Flower package in development mode (think :code:`pip " -"install -e`) along with all necessary dependencies::" +"3. Install the Flower package in development mode (think :code:`pip install -" +"e`) along with all necessary dependencies::" msgstr "" -"3. 필요한 모든 dependencies와 함께 개발 모드에서 Flower 패키지를 " -"설치합니다(예:code:`pip install -e`)::" +"3. 필요한 모든 dependencies와 함께 개발 모드에서 Flower 패키지를 설치합니다" +"(예:code:`pip install -e`)::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:69 msgid "Convenience Scripts" @@ -2217,13 +2272,13 @@ msgstr "편의 스크립트" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:71 msgid "" "The Flower repository contains a number of convenience scripts to make " -"recurring development tasks easier and less error-prone. See the " -":code:`/dev` subdirectory for a full list. The following scripts are " -"amongst the most important ones:" +"recurring development tasks easier and less error-prone. See the :code:`/" +"dev` subdirectory for a full list. The following scripts are amongst the " +"most important ones:" msgstr "" -"Flower 레포지토리에는 반복적인 개발 작업을 더 쉽고 오류를 줄이기 위한 여러 " -"가지 편의 스크립트가 포함되어 있습니다. 전체 목록은 :code:`/dev` 하위 " -"디렉터리를 참조하세요. 다음 스크립트는 가장 중요한 스크립트 중 하나입니다:" +"Flower 레포지토리에는 반복적인 개발 작업을 더 쉽고 오류를 줄이기 위한 여러 가" +"지 편의 스크립트가 포함되어 있습니다. 전체 목록은 :code:`/dev` 하위 디렉터리" +"를 참조하세요. 다음 스크립트는 가장 중요한 스크립트 중 하나입니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:77 msgid "Create/Delete Virtual Environment" @@ -2247,50 +2302,53 @@ msgstr "사전 커밋 훅 추가" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:108 msgid "" -"Developers may integrate a pre-commit hook into their workflow utilizing " -"the `pre-commit `_ library. The pre-" -"commit hook is configured to execute two primary operations: " -"``./dev/format.sh`` and ``./dev/test.sh`` scripts." +"Developers may integrate a pre-commit hook into their workflow utilizing the " +"`pre-commit `_ library. The pre-commit hook " +"is configured to execute two primary operations: ``./dev/format.sh`` and ``./" +"dev/test.sh`` scripts." msgstr "" -"개발자는 `pre-commit `_ 라이브러리를 " -"사용하여 사전 커밋 훅을 워크플로에 통합할 수 있습니다. 사전 커밋 훅은 두 " -"가지 기본 작업을 실행하도록 구성됩니다:``./dev/format.sh`` 및 ``./dev/test." -"sh`` 스크립트." +"개발자는 `pre-commit `_ 라이브러리를 사용하" +"여 사전 커밋 훅을 워크플로에 통합할 수 있습니다. 사전 커밋 훅은 두 가지 기본 " +"작업을 실행하도록 구성됩니다:``./dev/format.sh`` 및 ``./dev/test.sh`` 스크립" +"트." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:110 msgid "There are multiple ways developers can use this:" msgstr "개발자가 이것을 사용할 수 있는 여러가지 방법이 있습니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:112 -msgid "Install the pre-commit hook to your local git directory by simply running:" +msgid "" +"Install the pre-commit hook to your local git directory by simply running:" msgstr "간단하게 실행하여 로컬 git 디렉터리에 사전 커밋 훅을 설치하세요:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:118 msgid "" -"Each ``git commit`` will trigger the execution of formatting and " -"linting/test scripts." +"Each ``git commit`` will trigger the execution of formatting and linting/" +"test scripts." msgstr "각 ``git 커밋``은 포맷 및 린팅/테스트 스크립트의 실행을 트리거합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:119 msgid "" -"If in a hurry, bypass the hook using ``--no-verify`` with the ``git " -"commit`` command. ::" -msgstr "급한 경우 ``git commit`` 명령과 함께 `--no-verify``를 사용하여 훅을 넘기세요:" +"If in a hurry, bypass the hook using ``--no-verify`` with the ``git commit`` " +"command. ::" +msgstr "" +"급한 경우 ``git commit`` 명령과 함께 `--no-verify``를 사용하여 훅을 넘기세요:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:124 msgid "" "For developers who prefer not to install the hook permanently, it is " -"possible to execute a one-time check prior to committing changes by using" -" the following command:" +"possible to execute a one-time check prior to committing changes by using " +"the following command:" msgstr "" -"훅을 영구적으로 설치하지 않으려는 개발자의 경우 다음 명령을 사용하여 변경 " -"사항을 커밋하기 전에 일회성 검사를 실행할 수 있습니다:" +"훅을 영구적으로 설치하지 않으려는 개발자의 경우 다음 명령을 사용하여 변경 사" +"항을 커밋하기 전에 일회성 검사를 실행할 수 있습니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:130 msgid "" "This executes the formatting and linting checks/tests on all the files " "without modifying the default behavior of ``git commit``." -msgstr "이렇게 하면 ``git commit``의 기본 동작을 수정하지 않고 모든 파일에 대해 포맷 " +msgstr "" +"이렇게 하면 ``git commit``의 기본 동작을 수정하지 않고 모든 파일에 대해 포맷 " "및 린팅 검사/테스트를 실행합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:133 @@ -2299,21 +2357,22 @@ msgstr "로컬에서 Github Action(CI) 실행하기" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:135 msgid "" -"Developers could run the full set of Github Actions workflows under their" -" local environment by using `Act `_. " -"Please refer to the installation instructions under the linked repository" -" and run the next command under Flower main cloned repository folder::" +"Developers could run the full set of Github Actions workflows under their " +"local environment by using `Act `_. Please " +"refer to the installation instructions under the linked repository and run " +"the next command under Flower main cloned repository folder::" msgstr "" -"개발자는 `Act `_를 사용하여 로컬 환경에서 " -"전체 Github Actions 워크플로우 세트를 실행할 수 있습니다. 링크된 레포지토리 " -"아래의 설치 지침을 참조하여 Flower 메인 클론 레포지토리 폴더 아래에서 다음 " -"명령을 실행하세요::" +"개발자는 `Act `_를 사용하여 로컬 환경에서 전" +"체 Github Actions 워크플로우 세트를 실행할 수 있습니다. 링크된 레포지토리 아" +"래의 설치 지침을 참조하여 Flower 메인 클론 레포지토리 폴더 아래에서 다음 명령" +"을 실행하세요::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:142 msgid "" "The Flower default workflow would run by setting up the required Docker " "machines underneath." -msgstr "Flower 기본 워크플로우는 아래에 필요한 Docker 머신을 설정하여 실행합니다." +msgstr "" +"Flower 기본 워크플로우는 아래에 필요한 Docker 머신을 설정하여 실행합니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:147 msgid "Build Release" @@ -2321,17 +2380,19 @@ msgstr "릴리즈 빌드" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:149 msgid "" -"Flower uses Poetry to build releases. The necessary command is wrapped in" -" a simple script::" -msgstr "Flower는 Poetry를 사용하여 릴리즈를 빌드합니다. 필요한 명령은 간단한 " -"스크립트로 래핑됩니다::" +"Flower uses Poetry to build releases. The necessary command is wrapped in a " +"simple script::" +msgstr "" +"Flower는 Poetry를 사용하여 릴리즈를 빌드합니다. 필요한 명령은 간단한 스크립트" +"로 래핑됩니다::" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:154 msgid "" -"The resulting :code:`.whl` and :code:`.tar.gz` releases will be stored in" -" the :code:`/dist` subdirectory." -msgstr "결과물인 :code:`.whl` 및 :code:`.tar.gz` 릴리즈는 :code:`/dist` 하위 " -"디렉터리에 저장됩니다." +"The resulting :code:`.whl` and :code:`.tar.gz` releases will be stored in " +"the :code:`/dist` subdirectory." +msgstr "" +"결과물인 :code:`.whl` 및 :code:`.tar.gz` 릴리즈는 :code:`/dist` 하위 디렉터리" +"에 저장됩니다." #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:159 msgid "Build Documentation" @@ -2339,13 +2400,13 @@ msgstr "문서 빌드" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:161 msgid "" -"Flower's documentation uses `Sphinx `_. " -"There's no convenience script to re-build the documentation yet, but it's" -" pretty easy::" +"Flower's documentation uses `Sphinx `_. There's " +"no convenience script to re-build the documentation yet, but it's pretty " +"easy::" msgstr "" -"Flower의 문서는 `Sphinx `_를 사용합니다. 아직 " -"문서를 다시 작성할 수 있는 편리한 스크립트는 없지만 다음과 같이 쉽게 작성할 " -"수 있습니다:" +"Flower의 문서는 `Sphinx `_를 사용합니다. 아직 문" +"서를 다시 작성할 수 있는 편리한 스크립트는 없지만 다음과 같이 쉽게 작성할 수 " +"있습니다:" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:167 msgid "This will generate HTML documentation in ``doc/build/html``." @@ -2353,40 +2414,39 @@ msgstr "그러면 ``doc/build/html``에 HTML 문서가 생성됩니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:2 msgid "Example: FedBN in PyTorch - From Centralized To Federated" -msgstr "예시: PyTorch에서 FedBN - Centralize에서 Federated으로" +msgstr "예시: PyTorch에서 FedBN - 중앙 집중식에서 연합식으로" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:4 msgid "" -"This tutorial will show you how to use Flower to build a federated " -"version of an existing machine learning workload with `FedBN " -"`_, a federated training strategy " -"designed for non-iid data. We are using PyTorch to train a Convolutional " -"Neural Network(with Batch Normalization layers) on the CIFAR-10 dataset. " -"When applying FedBN, only few changes needed compared to :doc:`Example: " -"PyTorch - From Centralized To Federated `." +"This tutorial will show you how to use Flower to build a federated version " +"of an existing machine learning workload with `FedBN `_, a federated training strategy designed for non-iid data. We " +"are using PyTorch to train a Convolutional Neural Network(with Batch " +"Normalization layers) on the CIFAR-10 dataset. When applying FedBN, only few " +"changes needed compared to :doc:`Example: PyTorch - From Centralized To " +"Federated `." msgstr "" "이 튜토리얼에서는 non-iid data를 위해 설계된 federated 훈련 전략인 `FedBN " "`_으로 기존 머신러닝 워크로드의 federated " "버전을 구축하기 위해 Flower를 사용하는 방법을 보여드립니다. 우리는 PyTorch를 " "사용하여 CIFAR-10 데이터 세트에서 컨볼루션 신경망(일괄 정규화 레이어 포함)을 " -"훈련하고 있습니다. FedBN을 적용할 때, :doc:`예제: 파이토치 -Centralized에서 " -"Federated으로 ` 와 비교했을 " -"때 몇 가지 사항만 변경 하면 됩니다." +"훈련하고 있습니다. FedBN을 적용할 때, :doc:`예제: 파이토치 -중앙 집중식에서 " +"연합식으로 ` 와 비교했을 때 " +"몇 가지 사항만 변경 하면 됩니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:9 #: ../../source/example-pytorch-from-centralized-to-federated.rst:10 msgid "Centralized Training" -msgstr "Centralized 훈련" +msgstr "중앙 집중식 훈련" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:10 msgid "" -"All files are revised based on :doc:`Example: PyTorch - From Centralized " -"To Federated `. The only " -"thing to do is modifying the file called :code:`cifar.py`, revised part " -"is shown below:" +"All files are revised based on :doc:`Example: PyTorch - From Centralized To " +"Federated `. The only thing " +"to do is modifying the file called :code:`cifar.py`, revised part is shown " +"below:" msgstr "" -"모든 파일은 :doc:`예제: 파이토치 - Centralized에서 Federated으로 `를 기반으로 수정합니다. :code:`cifar." "py`라는 파일을 수정하기만 하면 되며, 수정된 부분은 아래와 같습니다:" @@ -2394,8 +2454,9 @@ msgstr "" msgid "" "The model architecture defined in class Net() is added with Batch " "Normalization layers accordingly." -msgstr "Net() 클래스에 정의된 모델 아키텍처는 그에 따라 배치 정규화 레이어가 " -"추가됩니다." +msgstr "" +"Net() 클래스에 정의된 모델 아키텍처는 그에 따라 배치 정규화 레이어가 추가됩니" +"다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:41 #: ../../source/example-pytorch-from-centralized-to-federated.rst:157 @@ -2404,56 +2465,55 @@ msgstr "이제 머신 러닝 워크로드를 실행할 수 있습니다:" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:47 msgid "" -"So far this should all look fairly familiar if you've used PyTorch " -"before. Let's take the next step and use what we've built to create a " -"federated learning system within FedBN, the system consists of one server" -" and two clients." +"So far this should all look fairly familiar if you've used PyTorch before. " +"Let's take the next step and use what we've built to create a federated " +"learning system within FedBN, the system consists of one server and two " +"clients." msgstr "" "지금까지는 파이토치를 사용해 본 적이 있다면 상당히 익숙하게 보일 것입니다. " "다음 단계로 넘어가서 우리가 구축한 것을 사용하여 FedBN 내에서 하나의 서버와 " -"두 개의 클라이언트로 구성된 federated 학습 시스템을 만들어 보겠습니다." +"두 개의 클라이언트로 구성된 연합학습 시스템을 만들어 보겠습니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:51 #: ../../source/example-pytorch-from-centralized-to-federated.rst:167 msgid "Federated Training" -msgstr "Federated 훈련" +msgstr "연합 훈련" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:53 msgid "" "If you have read :doc:`Example: PyTorch - From Centralized To Federated " -"`, the following parts are" -" easy to follow, only :code:`get_parameters` and :code:`set_parameters` " -"function in :code:`client.py` needed to revise. If not, please read the " -":doc:`Example: PyTorch - From Centralized To Federated `. first." +"`, the following parts are " +"easy to follow, only :code:`get_parameters` and :code:`set_parameters` " +"function in :code:`client.py` needed to revise. If not, please read the :doc:" +"`Example: PyTorch - From Centralized To Federated `. first." msgstr "" -":doc:`예제: 파이토치 - Centralized에서 Federated으로 `를 읽었다면, 다음 부분은 쉽게 따라할 수 있으며 " ":code:`client.py`의 :code:`get_parameters`와 :code:`set_parameters` 함수만 " -"수정해야 합니다. 그렇지 않은 경우 :doc:`예제: 파이토치 - Centralized에서 " -"Federated으로 `를 먼저 " +"수정해야 합니다. 그렇지 않은 경우 :doc:`예제: 파이토치 - 중앙 집중식에서 " +"연합식으로 `를 먼저 " "읽어보세요." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:56 msgid "" -"Our example consists of one *server* and two *clients*. In FedBN, " -":code:`server.py` keeps unchanged, we can start the server directly." +"Our example consists of one *server* and two *clients*. In FedBN, :code:" +"`server.py` keeps unchanged, we can start the server directly." msgstr "" -"이 예제는 하나의 *서버*와 두 개의 *클라이언트*로 구성됩니다. FedBN에서 " -":code:`server.py`는 변경되지 않고 그대로 유지되므로 서버를 바로 시작할 수 " -"있습니다." +"이 예제는 하나의 *서버*와 두 개의 *클라이언트*로 구성됩니다. FedBN에서 :code:" +"`server.py`는 변경되지 않고 그대로 유지되므로 서버를 바로 시작할 수 있습니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:62 msgid "" -"Finally, we will revise our *client* logic by changing " -":code:`get_parameters` and :code:`set_parameters` in :code:`client.py`, " -"we will exclude batch normalization parameters from model parameter list " -"when sending to or receiving from the server." +"Finally, we will revise our *client* logic by changing :code:" +"`get_parameters` and :code:`set_parameters` in :code:`client.py`, we will " +"exclude batch normalization parameters from model parameter list when " +"sending to or receiving from the server." msgstr "" -"마지막으로, :code:`client.py`에서 :code:`get_parameters` 및 " -":code:`set_parameters`를 변경하여 *client* 로직을 수정할 것입니다. 서버로 " -"보내거나 서버에서 받을 때 모델 파라미터 목록에서 배치 정규화 파라미터를 " -"제외할 수 있습니다." +"마지막으로, :code:`client.py`에서 :code:`get_parameters` 및 :code:" +"`set_parameters`를 변경하여 *client* 로직을 수정할 것입니다. 서버로 보내거나 " +"서버에서 받을 때 모델 파라미터 목록에서 배치 정규화 파라미터를 제외할 수 있습" +"니다." #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:85 msgid "Now, you can now open two additional terminal windows and run" @@ -2461,13 +2521,13 @@ msgstr "이제 두 개의 터미널 창을 추가로 열고 다음을 실행할 #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:91 msgid "" -"in each window (make sure that the server is still running before you do " -"so) and see your (previously centralized) PyTorch project run federated " -"learning with FedBN strategy across two clients. Congratulations!" +"in each window (make sure that the server is still running before you do so) " +"and see your (previously centralized) PyTorch project run federated learning " +"with FedBN strategy across two clients. Congratulations!" msgstr "" -"를 입력하고(클릭하기 전에 서버가 계속 실행 중인지 확인하세요), (이전에 " -"centralized된) PyTorch 프로젝트가 두 클라이언트에서 FedBN으로 federated " -"학습을 실행하는 것을 확인합니다. 축하합니다!" +"를 입력하고(클릭하기 전에 서버가 계속 실행 중인지 확인하세요), (이전에 중앙 " +"집중된) PyTorch 프로젝트가 두 클라이언트에서 FedBN으로 연합 학습을 실행하는 " +"것을 확인합니다. 축하합니다!" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:94 #: ../../source/example-jax-from-centralized-to-federated.rst:277 @@ -2478,20 +2538,19 @@ msgstr "다음 단계" #: ../../source/example-fedbn-pytorch-from-centralized-to-federated.rst:96 msgid "" -"The full source code for this example can be found `here " -"`_. Our example is of course somewhat over-" -"simplified because both clients load the exact same dataset, which isn't " -"realistic. You're now prepared to explore this topic further. How about " -"using different subsets of CIFAR-10 on each client? How about adding more" -" clients?" +"The full source code for this example can be found `here `_. Our " +"example is of course somewhat over-simplified because both clients load the " +"exact same dataset, which isn't realistic. You're now prepared to explore " +"this topic further. How about using different subsets of CIFAR-10 on each " +"client? How about adding more clients?" msgstr "" "이 예제의 전체 소스 코드는 '여기 `_'에서 확인할 수 있습니다. " -"물론 이 예제는 두 클라이언트가 완전히 동일한 데이터 세트를 로드하기 때문에 " -"다소 지나치게 단순화되어 있으며, 이는 현실적이지 않습니다. 이제 이 주제를 더 " -"자세히 살펴볼 준비가 되셨습니다. 각 클라이언트에서 서로 다른 CIFAR-10의 하위 " -"집합을 사용해 보는 것은 어떨까요? 클라이언트를 더 추가하는 것은 어떨까요?" +"examples/pytorch-from-centralized-to-federated>`_'에서 확인할 수 있습니다. 물" +"론 이 예제는 두 클라이언트가 완전히 동일한 데이터 세트를 로드하기 때문에 다" +"소 지나치게 단순화되어 있으며, 이는 현실적이지 않습니다. 이제 이 주제를 더 자" +"세히 살펴볼 준비가 되셨습니다. 각 클라이언트에서 서로 다른 CIFAR-10의 하위 집" +"합을 사용해 보는 것은 어떨까요? 클라이언트를 더 추가하는 것은 어떨까요?" #: ../../source/example-jax-from-centralized-to-federated.rst:2 msgid "Example: JAX - Run JAX Federated" @@ -2500,32 +2559,31 @@ msgstr "예시: JAX - JAX Federated 실행" #: ../../source/example-jax-from-centralized-to-federated.rst:4 #: ../../source/tutorial-quickstart-jax.rst:10 msgid "" -"This tutorial will show you how to use Flower to build a federated " -"version of an existing JAX workload. We are using JAX to train a linear " -"regression model on a scikit-learn dataset. We will structure the example" -" similar to our `PyTorch - From Centralized To Federated " -"`_ walkthrough. First, we build a centralized " -"training approach based on the `Linear Regression with JAX " -"`_" -" tutorial`. Then, we build upon the centralized training code to run the " -"training in a federated fashion." -msgstr "" -"이 튜토리얼에서는 Flower를 사용하여 기존 JAX 워크로드의 federated 버전을 " -"구축하는 방법을 보여드립니다. JAX를 사용해 scikit-learn 데이터 세트에서 선형 " -"회귀 모델을 훈련하고 있습니다. 예제는 '파이토치 - Centralized에서 " -"Federated으로 `_ 워크스루와 유사하게 구성하겠습니다. 먼저, `" -"JAX를 사용한 선형 회귀 `_ 튜토리얼`을 기반으로 centralized 학습 접근 " -"방식을 구축합니다. 그런 다음 centralized 트레이닝 코드를 기반으로 federated " -"방식으로 트레이닝을 실행합니다." +"This tutorial will show you how to use Flower to build a federated version " +"of an existing JAX workload. We are using JAX to train a linear regression " +"model on a scikit-learn dataset. We will structure the example similar to " +"our `PyTorch - From Centralized To Federated `_ walkthrough. " +"First, we build a centralized training approach based on the `Linear " +"Regression with JAX `_ tutorial`. Then, we build upon the centralized " +"training code to run the training in a federated fashion." +msgstr "" +"이 튜토리얼에서는 Flower를 사용하여 기존 JAX 워크로드의 연합 버전을 구축하는 " +"방법을 보여드립니다. JAX를 사용해 scikit-learn 데이터 세트에서 선형 회귀 " +"모델을 훈련하고 있습니다. 예제는 '파이토치 - Centralized에서 Federated으로 " +"`_ 워크스루와 유사하게 구성하겠습니다. 먼저, `JAX를 사용한 선형 " +"회귀 `_ 튜토리얼`을 기반으로 centralized 학습 접근 방식을 구축합니다. 그런 " +"다음 centralized 트레이닝 코드를 기반으로 federated 방식으로 트레이닝을 " +"실행합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:10 #: ../../source/tutorial-quickstart-jax.rst:16 msgid "" -"Before we start building our JAX example, we need install the packages " -":code:`jax`, :code:`jaxlib`, :code:`scikit-learn`, and :code:`flwr`:" +"Before we start building our JAX example, we need install the packages :code:" +"`jax`, :code:`jaxlib`, :code:`scikit-learn`, and :code:`flwr`:" msgstr "" "JAX 예제 빌드를 시작하기 전에 :code:`jax`, :code:`jaxlib`, :code:`scikit-" "learn`, :code:`flwr` 패키지를 설치해야 합니다:" @@ -2538,12 +2596,12 @@ msgstr "JAX를 사용한 선형 회귀" #: ../../source/example-jax-from-centralized-to-federated.rst:20 #: ../../source/tutorial-quickstart-jax.rst:26 msgid "" -"We begin with a brief description of the centralized training code based " -"on a :code:`Linear Regression` model. If you want a more in-depth " -"explanation of what's going on then have a look at the official `JAX " -"documentation `_." +"We begin with a brief description of the centralized training code based on " +"a :code:`Linear Regression` model. If you want a more in-depth explanation " +"of what's going on then have a look at the official `JAX documentation " +"`_." msgstr "" -"먼저 :code:`선형 회귀` 모델을 기반으로 하는 centralized 훈련 코드에 대한 " +"먼저 :code:`선형 회귀` 모델을 기반으로 하는 중앙 집중식 훈련 코드에 대한 " "간략한 설명부터 시작하겠습니다. 더 자세한 설명을 원하시면 공식 `JAX 문서 " "`_를 참조하세요." @@ -2552,14 +2610,13 @@ msgstr "" msgid "" "Let's create a new file called :code:`jax_training.py` with all the " "components required for a traditional (centralized) linear regression " -"training. First, the JAX packages :code:`jax` and :code:`jaxlib` need to " -"be imported. In addition, we need to import :code:`sklearn` since we use " -":code:`make_regression` for the dataset and :code:`train_test_split` to " -"split the dataset into a training and test set. You can see that we do " -"not yet import the :code:`flwr` package for federated learning. This will" -" be done later." -msgstr "" -"전통적인(centralized) 선형 회귀 훈련에 필요한 모든 구성 요소가 포함된 " +"training. First, the JAX packages :code:`jax` and :code:`jaxlib` need to be " +"imported. In addition, we need to import :code:`sklearn` since we use :code:" +"`make_regression` for the dataset and :code:`train_test_split` to split the " +"dataset into a training and test set. You can see that we do not yet import " +"the :code:`flwr` package for federated learning. This will be done later." +msgstr "" +"전통적인(중앙 집중식) 선형 회귀 훈련에 필요한 모든 구성 요소가 포함된 " ":code:`jax_training.py`라는 새 파일을 생성해 보겠습니다. 먼저, JAX 패키지인 " ":code:`jax`와 :code:`jaxlib`를 가져와야 합니다. 또한 데이터 세트에 " ":code:`make_regression`을 사용하고 데이터 세트를 학습 및 테스트 세트로 " @@ -2570,38 +2627,39 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:37 #: ../../source/tutorial-quickstart-jax.rst:43 msgid "" -"The :code:`load_data()` function loads the mentioned training and test " -"sets." -msgstr "code:`load_data()` 함수는 앞서 언급한 트레이닝 및 테스트 세트를 로드합니다." +"The :code:`load_data()` function loads the mentioned training and test sets." +msgstr "" +"code:`load_data()` 함수는 앞서 언급한 트레이닝 및 테스트 세트를 로드합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:47 #: ../../source/tutorial-quickstart-jax.rst:53 msgid "" -"The model architecture (a very simple :code:`Linear Regression` model) is" -" defined in :code:`load_model()`." -msgstr "모델 아키텍처(매우 간단한 :code:`선형 회귀` 모델)는 :code:`load_model()`에 " -"정의되어 있습니다." +"The model architecture (a very simple :code:`Linear Regression` model) is " +"defined in :code:`load_model()`." +msgstr "" +"모델 아키텍처(매우 간단한 :code:`선형 회귀` 모델)는 :code:`load_model()`에 정" +"의되어 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:59 #: ../../source/tutorial-quickstart-jax.rst:65 msgid "" -"We now need to define the training (function :code:`train()`), which " -"loops over the training set and measures the loss (function " -":code:`loss_fn()`) for each batch of training examples. The loss function" -" is separate since JAX takes derivatives with a :code:`grad()` function " -"(defined in the :code:`main()` function and called in :code:`train()`)." +"We now need to define the training (function :code:`train()`), which loops " +"over the training set and measures the loss (function :code:`loss_fn()`) for " +"each batch of training examples. The loss function is separate since JAX " +"takes derivatives with a :code:`grad()` function (defined in the :code:" +"`main()` function and called in :code:`train()`)." msgstr "" -"이제 훈련 집합을 반복하고 각 훈련 예제 배치에 대해 손실을 측정하는(함수 " -":code:`loss_fn()`) 훈련(함수 :code:`train()`)을 정의해야 합니다. JAX는 " -":code:`grad()` 함수(:code:`main()` 함수에 정의되고 :code:`train()`에서 " -"호출됨)로 파생물을 취하므로 손실 함수는 분리되어 있습니다." +"이제 훈련 집합을 반복하고 각 훈련 예제 배치에 대해 손실을 측정하는(함수 :" +"code:`loss_fn()`) 훈련(함수 :code:`train()`)을 정의해야 합니다. JAX는 :code:" +"`grad()` 함수(:code:`main()` 함수에 정의되고 :code:`train()`에서 호출됨)로 파" +"생물을 취하므로 손실 함수는 분리되어 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:77 #: ../../source/tutorial-quickstart-jax.rst:83 msgid "" -"The evaluation of the model is defined in the function " -":code:`evaluation()`. The function takes all test examples and measures " -"the loss of the linear regression model." +"The evaluation of the model is defined in the function :code:`evaluation()`. " +"The function takes all test examples and measures the loss of the linear " +"regression model." msgstr "" "모델의 평가는 :code:`evaluation()` 함수에 정의되어 있습니다. 이 함수는 모든 " "테스트 예제를 가져와 선형 회귀 모델의 손실을 측정합니다." @@ -2610,29 +2668,29 @@ msgstr "" #: ../../source/tutorial-quickstart-jax.rst:94 msgid "" "Having defined the data loading, model architecture, training, and " -"evaluation we can put everything together and train our model using JAX. " -"As already mentioned, the :code:`jax.grad()` function is defined in " -":code:`main()` and passed to :code:`train()`." +"evaluation we can put everything together and train our model using JAX. As " +"already mentioned, the :code:`jax.grad()` function is defined in :code:" +"`main()` and passed to :code:`train()`." msgstr "" -"데이터 로딩, 모델 아키텍처, 훈련 및 평가를 정의했으므로 이제 모든 것을 " -"종합하여 JAX를 사용 모델을 훈련할 수 있습니다. 이미 언급했듯이 :code:`jax." -"grad()` 함수는 :code:`main()`에 정의되어 :code:`train()`에 전달됩니다." +"데이터 로딩, 모델 아키텍처, 훈련 및 평가를 정의했으므로 이제 모든 것을 종합하" +"여 JAX를 사용 모델을 훈련할 수 있습니다. 이미 언급했듯이 :code:`jax.grad()` " +"함수는 :code:`main()`에 정의되어 :code:`train()`에 전달됩니다." #: ../../source/example-jax-from-centralized-to-federated.rst:105 #: ../../source/tutorial-quickstart-jax.rst:111 msgid "You can now run your (centralized) JAX linear regression workload:" -msgstr "이제 (centralized) JAX 선형 회귀 워크로드를 실행할 수 있습니다:" +msgstr "이제 (중앙 집중식) JAX 선형 회귀 워크로드를 실행할 수 있습니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:111 #: ../../source/tutorial-quickstart-jax.rst:117 msgid "" -"So far this should all look fairly familiar if you've used JAX before. " -"Let's take the next step and use what we've built to create a simple " -"federated learning system consisting of one server and two clients." +"So far this should all look fairly familiar if you've used JAX before. Let's " +"take the next step and use what we've built to create a simple federated " +"learning system consisting of one server and two clients." msgstr "" -"지금까지는 JAX를 사용해 본 적이 있다면 이 모든 것이 상당히 익숙해 보일 " -"것입니다. 다음 단계로 넘어가서 우리가 구축한 것을 사용하여 하나의 서버와 두 " -"개의 클라이언트로 구성된 간단한 연합 학습 시스템을 만들어 보겠습니다." +"지금까지는 JAX를 사용해 본 적이 있다면 이 모든 것이 상당히 익숙해 보일 것입니" +"다. 다음 단계로 넘어가서 우리가 구축한 것을 사용하여 하나의 서버와 두 개의 클" +"라이언트로 구성된 간단한 연합 학습 시스템을 만들어 보겠습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:115 #: ../../source/tutorial-quickstart-jax.rst:121 @@ -2642,36 +2700,36 @@ msgstr "JAX와 Flower의 만남" #: ../../source/example-jax-from-centralized-to-federated.rst:117 #: ../../source/tutorial-quickstart-jax.rst:123 msgid "" -"The concept of federating an existing workload is always the same and " -"easy to understand. We have to start a *server* and then use the code in " -":code:`jax_training.py` for the *clients* that are connected to the " -"*server*. The *server* sends model parameters to the clients. The " -"*clients* run the training and update the parameters. The updated " -"parameters are sent back to the *server*, which averages all received " -"parameter updates. This describes one round of the federated learning " -"process, and we repeat this for multiple rounds." -msgstr "" -"기존 워크로드를 federating하는 개념은 항상 동일하고 이해하기 쉽습니다. 서버*" -"를 시작한 다음 *서버*에 연결된 *클라이언트*에 대해 :code:`jax_training.py`의 " +"The concept of federating an existing workload is always the same and easy " +"to understand. We have to start a *server* and then use the code in :code:" +"`jax_training.py` for the *clients* that are connected to the *server*. The " +"*server* sends model parameters to the clients. The *clients* run the " +"training and update the parameters. The updated parameters are sent back to " +"the *server*, which averages all received parameter updates. This describes " +"one round of the federated learning process, and we repeat this for multiple " +"rounds." +msgstr "" +"기존 워크로드를 연합하는 개념은 항상 동일하고 이해하기 쉽습니다. 서버*를 " +"시작한 다음 *서버*에 연결된 *클라이언트*에 대해 :code:`jax_training.py`의 " "코드를 사용해야 합니다. *서버*는 모델 파라미터를 클라이언트로 전송합니다. " "클라이언트는 학습을 실행하고 파라미터를 업데이트합니다. 업데이트된 " "파라미터는 *서버*로 다시 전송되며, 수신된 모든 파라미터 업데이트의 평균을 " -"구합니다. 이는 federated 학습 프로세스의 한 라운드를 설명하며, 이 과정을 " -"여러 라운드에 걸쳐 반복합니다." +"구합니다. 이는 연합 학습 프로세스의 한 라운드를 설명하며, 이 과정을 여러 " +"라운드에 걸쳐 반복합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:123 #: ../../source/example-pytorch-from-centralized-to-federated.rst:181 #: ../../source/tutorial-quickstart-jax.rst:129 msgid "" -"Our example consists of one *server* and two *clients*. Let's set up " -":code:`server.py` first. The *server* needs to import the Flower package " -":code:`flwr`. Next, we use the :code:`start_server` function to start a " -"server and tell it to perform three rounds of federated learning." +"Our example consists of one *server* and two *clients*. Let's set up :code:" +"`server.py` first. The *server* needs to import the Flower package :code:" +"`flwr`. Next, we use the :code:`start_server` function to start a server and " +"tell it to perform three rounds of federated learning." msgstr "" "이 예제는 하나의 *서버*와 두 개의 *클라이언트*로 구성됩니다. 먼저 " ":code:`server.py`를 설정해 보겠습니다. *server*는 Flower 패키지 :code:`flwr`" "를 가져와야 합니다. 다음으로, :code:`start_server` 함수를 사용하여 서버를 " -"시작하고 세 차례의 federated 학습을 수행하도록 지시합니다." +"시작하고 세 차례의 연합 학습을 수행하도록 지시합니다." #: ../../source/example-jax-from-centralized-to-federated.rst:133 #: ../../source/example-pytorch-from-centralized-to-federated.rst:191 @@ -2682,39 +2740,38 @@ msgstr "이미 *서버*를 시작할 수 있습니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:139 #: ../../source/tutorial-quickstart-jax.rst:145 msgid "" -"Finally, we will define our *client* logic in :code:`client.py` and build" -" upon the previously defined JAX training in :code:`jax_training.py`. Our" -" *client* needs to import :code:`flwr`, but also :code:`jax` and " -":code:`jaxlib` to update the parameters on our JAX model:" +"Finally, we will define our *client* logic in :code:`client.py` and build " +"upon the previously defined JAX training in :code:`jax_training.py`. Our " +"*client* needs to import :code:`flwr`, but also :code:`jax` and :code:" +"`jaxlib` to update the parameters on our JAX model:" msgstr "" -"마지막으로, :code:`client.py`에서 *client* 로직을 정의하고 " -":code:`jax_training.py`에서 이전에 정의한 JAX 교육을 기반으로 빌드합니다. " -"*클라이언트*는 :code:`flwr`을 가져와야 하며, JAX 모델의 파라미터를 " -"업데이트하기 위해 :code:`jax` 및 :code:`jaxlib`도 가져와야 합니다:" +"마지막으로, :code:`client.py`에서 *client* 로직을 정의하고 :code:" +"`jax_training.py`에서 이전에 정의한 JAX 교육을 기반으로 빌드합니다. *클라이언" +"트*는 :code:`flwr`을 가져와야 하며, JAX 모델의 파라미터를 업데이트하기 위해 :" +"code:`jax` 및 :code:`jaxlib`도 가져와야 합니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:154 #: ../../source/tutorial-quickstart-jax.rst:160 msgid "" -"Implementing a Flower *client* basically means implementing a subclass of" -" either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. " -"Our implementation will be based on :code:`flwr.client.NumPyClient` and " -"we'll call it :code:`FlowerClient`. :code:`NumPyClient` is slightly " -"easier to implement than :code:`Client` if you use a framework with good " -"NumPy interoperability (like JAX) because it avoids some of the " -"boilerplate that would otherwise be necessary. :code:`FlowerClient` needs" -" to implement four methods, two methods for getting/setting model " -"parameters, one method for training the model, and one method for testing" -" the model:" +"Implementing a Flower *client* basically means implementing a subclass of " +"either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. Our " +"implementation will be based on :code:`flwr.client.NumPyClient` and we'll " +"call it :code:`FlowerClient`. :code:`NumPyClient` is slightly easier to " +"implement than :code:`Client` if you use a framework with good NumPy " +"interoperability (like JAX) because it avoids some of the boilerplate that " +"would otherwise be necessary. :code:`FlowerClient` needs to implement four " +"methods, two methods for getting/setting model parameters, one method for " +"training the model, and one method for testing the model:" msgstr "" "Flower *클라이언트*를 구현한다는 것은 기본적으로 :code:`flwr.client.Client` " -"또는 :code:`flwr.client.NumPyClient`의 서브클래스를 구현하는 것을 " -"의미합니다. 구현은 :code:`flwr.client.NumPyClient`를 기반으로 하며, 이를 " -":code:`FlowerClient`라고 부를 것입니다. :code:`NumPyClient`는 필요한 일부 " -"보일러플레이를 피할 수 있기 때문에 NumPy 상호 운용성이 좋은 프레임워크(예: " -"JAX)를 사용하는 경우 :code:`Client`보다 구현하기가 약간 더 쉽습니다. " -"code:`FlowerClient`는 모델 매개변수를 가져오거나 설정하는 메서드 2개, 모델 " -"학습을 위한 메서드 1개, 모델 테스트를 위한 메서드 1개 등 총 4개의 메서드를 " -"구현해야 합니다:" +"또는 :code:`flwr.client.NumPyClient`의 서브클래스를 구현하는 것을 의미합니" +"다. 구현은 :code:`flwr.client.NumPyClient`를 기반으로 하며, 이를 :code:" +"`FlowerClient`라고 부를 것입니다. :code:`NumPyClient`는 필요한 일부 보일러플" +"레이를 피할 수 있기 때문에 NumPy 상호 운용성이 좋은 프레임워크(예: JAX)를 사" +"용하는 경우 :code:`Client`보다 구현하기가 약간 더 쉽습니다. code:" +"`FlowerClient`는 모델 매개변수를 가져오거나 설정하는 메서드 2개, 모델 학습을 " +"위한 메서드 1개, 모델 테스트를 위한 메서드 1개 등 총 4개의 메서드를 구현해야 " +"합니다:" #: ../../source/example-jax-from-centralized-to-federated.rst:161 #: ../../source/tutorial-quickstart-jax.rst:167 @@ -2725,8 +2782,7 @@ msgstr ":code:`set_parameters (선택사항)`" #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 #: ../../source/tutorial-quickstart-jax.rst:166 msgid "" -"set the model parameters on the local model that are received from the " -"server" +"set the model parameters on the local model that are received from the server" msgstr "서버에서 수신한 로컬 모델의 모델 파라미터를 설정합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:161 @@ -2738,10 +2794,11 @@ msgstr "매개 변수를 NumPy :code:`ndarray`로 변환" #: ../../source/example-pytorch-from-centralized-to-federated.rst:220 #: ../../source/tutorial-quickstart-jax.rst:168 msgid "" -"loop over the list of model parameters received as NumPy " -":code:`ndarray`'s (think list of neural network layers)" -msgstr "(신경망 레이어 목록으로 생각하면 됩니다) NumPy :code:`ndarray`로 받은 모델 " -"파라미터 목록에 대해 반복합니다" +"loop over the list of model parameters received as NumPy :code:`ndarray`'s " +"(think list of neural network layers)" +msgstr "" +"(신경망 레이어 목록으로 생각하면 됩니다) NumPy :code:`ndarray`로 받은 모델 파" +"라미터 목록에 대해 반복합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:163 #: ../../source/example-pytorch-from-centralized-to-federated.rst:221 @@ -2755,11 +2812,11 @@ msgstr ":code:`get_parameters`" #: ../../source/example-pytorch-from-centralized-to-federated.rst:222 #: ../../source/tutorial-quickstart-jax.rst:170 msgid "" -"get the model parameters and return them as a list of NumPy " -":code:`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" +"get the model parameters and return them as a list of NumPy :code:" +"`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" msgstr "" -"모델 매개변수를 가져와서 NumPy :code:`ndarray`의 목록으로 반환합니다(이는 " -":code:`flwr.client.NumPyClient`가 기대하는 바와 같습니다)" +"모델 매개변수를 가져와서 NumPy :code:`ndarray`의 목록으로 반환합니다(이는 :" +"code:`flwr.client.NumPyClient`가 기대하는 바와 같습니다)" #: ../../source/example-jax-from-centralized-to-federated.rst:167 #: ../../source/example-pytorch-from-centralized-to-federated.rst:225 @@ -2776,8 +2833,8 @@ msgstr ":code:`fit`" #: ../../source/tutorial-quickstart-jax.rst:172 #: ../../source/tutorial-quickstart-jax.rst:176 msgid "" -"update the parameters of the local model with the parameters received " -"from the server" +"update the parameters of the local model with the parameters received from " +"the server" msgstr "서버에서 받은 파라미터로 로컬 모델의 파라미터를 업데이트합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:167 @@ -2813,9 +2870,9 @@ msgstr "로컬 손실을 서버로 반환합니다" #: ../../source/example-jax-from-centralized-to-federated.rst:174 #: ../../source/tutorial-quickstart-jax.rst:180 msgid "" -"The challenging part is to transform the JAX model parameters from " -":code:`DeviceArray` to :code:`NumPy ndarray` to make them compatible with" -" `NumPyClient`." +"The challenging part is to transform the JAX model parameters from :code:" +"`DeviceArray` to :code:`NumPy ndarray` to make them compatible with " +"`NumPyClient`." msgstr "" "어려운 부분은 JAX 모델 매개변수를 :code:`DeviceArray`에서 :code:`NumPy " "ndarray`로 변환하여 `NumPyClient`와 호환되도록 하는 것입니다." @@ -2823,97 +2880,96 @@ msgstr "" #: ../../source/example-jax-from-centralized-to-federated.rst:176 #: ../../source/tutorial-quickstart-jax.rst:182 msgid "" -"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make" -" use of the functions :code:`train()` and :code:`evaluate()` previously " +"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make " +"use of the functions :code:`train()` and :code:`evaluate()` previously " "defined in :code:`jax_training.py`. So what we really do here is we tell " -"Flower through our :code:`NumPyClient` subclass which of our already " -"defined functions to call for training and evaluation. We included type " -"annotations to give you a better understanding of the data types that get" -" passed around." -msgstr "" -"두 개의 :code:`NumPyClient` 메서드인 :code:`fit`과 :code:`evaluate`는 이전에 " -":code:`jax_training.py`에 정의된 함수 :code:`train()`과 :code:`evaluate()`를 " -"사용합니다. 따라서 여기서 우리가 실제로 하는 일은 이미 정의된 함수 중 훈련과 " -"평가를 위해 호출할 함수를 :code:`NumPyClient` 서브클래스를 통해 Flower에게 " -"알려주는 것입니다. 전달되는 데이터 유형을 더 잘 이해할 수 있도록 유형 type " -"annotation을 포함했습니다." +"Flower through our :code:`NumPyClient` subclass which of our already defined " +"functions to call for training and evaluation. We included type annotations " +"to give you a better understanding of the data types that get passed around." +msgstr "" +"두 개의 :code:`NumPyClient` 메서드인 :code:`fit`과 :code:`evaluate`는 이전" +"에 :code:`jax_training.py`에 정의된 함수 :code:`train()`과 :code:`evaluate()`" +"를 사용합니다. 따라서 여기서 우리가 실제로 하는 일은 이미 정의된 함수 중 훈련" +"과 평가를 위해 호출할 함수를 :code:`NumPyClient` 서브클래스를 통해 Flower에" +"게 알려주는 것입니다. 전달되는 데이터 유형을 더 잘 이해할 수 있도록 유형 " +"type annotation을 포함했습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:245 #: ../../source/tutorial-quickstart-jax.rst:251 msgid "Having defined the federation process, we can run it." -msgstr "federation 프로세스를 정의했으면 이제 실행할 수 있습니다." +msgstr "연합 프로세스를 정의했으면 이제 실행할 수 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:268 #: ../../source/example-pytorch-from-centralized-to-federated.rst:301 #: ../../source/tutorial-quickstart-jax.rst:274 msgid "And that's it. You can now open two additional terminal windows and run" -msgstr "" +msgstr "여기까지입니다. 이제 두 개의 터미널 창을 추가로 열고 다음을 실행할 수 " +"있습니다" #: ../../source/example-jax-from-centralized-to-federated.rst:274 #: ../../source/tutorial-quickstart-jax.rst:280 msgid "" -"in each window (make sure that the server is still running before you do " -"so) and see your JAX project run federated learning across two clients. " +"in each window (make sure that the server is still running before you do so) " +"and see your JAX project run federated learning across two clients. " "Congratulations!" msgstr "" "를 입력하고(그 전에 서버가 계속 실행 중인지 확인하세요) 두 클라이언트에서 " -"federated 학습을 실행하는 JAX 프로젝트를 확인합니다. 축하합니다!" +"연합 학습을 실행하는 JAX 프로젝트를 확인합니다. 축하합니다!" #: ../../source/example-jax-from-centralized-to-federated.rst:279 #: ../../source/tutorial-quickstart-jax.rst:285 msgid "" "The source code of this example was improved over time and can be found " -"here: `Quickstart JAX `_. Our example is somewhat over-simplified because both " +"here: `Quickstart JAX `_. Our example is somewhat over-simplified because both " "clients load the same dataset." msgstr "" -"이 예제의 소스 코드는 시간이 지남에 따라 개선되었으며 여기에서 확인할 수 " -"있습니다: 'Quickstart JAX `_. 두 클라이언트가 동일한 데이터 세트를 로드하기 때문에 이 " -"예제는 다소 단순화되어 있습니다." +"이 예제의 소스 코드는 시간이 지남에 따라 개선되었으며 여기에서 확인할 수 있습" +"니다: 'Quickstart JAX `_. 두 클라이언트가 동일한 데이터 세트를 로드하기 때문에 이 예" +"제는 다소 단순화되어 있습니다." #: ../../source/example-jax-from-centralized-to-federated.rst:282 #: ../../source/tutorial-quickstart-jax.rst:288 msgid "" -"You're now prepared to explore this topic further. How about using a more" -" sophisticated model or using a different dataset? How about adding more " +"You're now prepared to explore this topic further. How about using a more " +"sophisticated model or using a different dataset? How about adding more " "clients?" msgstr "" -"이제 이 주제를 더 자세히 살펴볼 준비가 되었습니다. 더 정교한 모델을 " -"사용하거나 다른 데이터 집합을 사용해 보는 것은 어떨까요? 클라이언트를 더 " -"추가하는 것은 어떨까요?" +"이제 이 주제를 더 자세히 살펴볼 준비가 되었습니다. 더 정교한 모델을 사용하거" +"나 다른 데이터 집합을 사용해 보는 것은 어떨까요? 클라이언트를 더 추가하는 것" +"은 어떨까요?" #: ../../source/example-pytorch-from-centralized-to-federated.rst:2 msgid "Example: PyTorch - From Centralized To Federated" -msgstr "예제: 파이토치 - 중앙 Centralized에서 Federated으로" +msgstr "예제: 파이토치 - 중앙 집중식에서 연합식으로" #: ../../source/example-pytorch-from-centralized-to-federated.rst:4 msgid "" -"This tutorial will show you how to use Flower to build a federated " -"version of an existing machine learning workload. We are using PyTorch to" -" train a Convolutional Neural Network on the CIFAR-10 dataset. First, we " -"introduce this machine learning task with a centralized training approach" -" based on the `Deep Learning with PyTorch " -"`_ " -"tutorial. Then, we build upon the centralized training code to run the " -"training in a federated fashion." +"This tutorial will show you how to use Flower to build a federated version " +"of an existing machine learning workload. We are using PyTorch to train a " +"Convolutional Neural Network on the CIFAR-10 dataset. First, we introduce " +"this machine learning task with a centralized training approach based on the " +"`Deep Learning with PyTorch `_ tutorial. Then, we build upon the centralized " +"training code to run the training in a federated fashion." msgstr "" -"이 튜토리얼에서는 Flower를 사용해 기존 머신 러닝 워크로드의 federated 버전을 " +"이 튜토리얼에서는 Flower를 사용해 기존 머신 러닝 워크로드의 연합 버전을 " "구축하는 방법을 보여드립니다. 여기서는 PyTorch를 사용해 CIFAR-10 데이터 " "세트에서 컨볼루션 신경망을 훈련합니다. 먼저, 'PyTorch로 딥 러닝 " "`_ " "튜토리얼을 기반으로 centralized 학습 접근 방식을 사용하여 이 머신 러닝 " -"작업을 소개합니다. 그런 다음 centralized 훈련 코드를 기반으로 federated 방식 " +"작업을 소개합니다. 그런 다음 centralized 훈련 코드를 기반으로 연합 방식 " "훈련을 실행합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:12 msgid "" -"We begin with a brief description of the centralized CNN training code. " -"If you want a more in-depth explanation of what's going on then have a " -"look at the official `PyTorch tutorial " -"`_." +"We begin with a brief description of the centralized CNN training code. If " +"you want a more in-depth explanation of what's going on then have a look at " +"the official `PyTorch tutorial `_." msgstr "" -"centralized CNN 트레이닝 코드에 대한 간략한 설명부터 시작하겠습니다. 무슨 " +"중앙 집중식 CNN 트레이닝 코드에 대한 간략한 설명부터 시작하겠습니다. 무슨 " "일이 일어나고 있는지 더 자세히 설명하려면 공식 `PyTorch 튜토리얼 " "`_을 " "참조하세요." @@ -2921,18 +2977,17 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:15 msgid "" "Let's create a new file called :code:`cifar.py` with all the components " -"required for a traditional (centralized) training on CIFAR-10. First, all" -" required packages (such as :code:`torch` and :code:`torchvision`) need " -"to be imported. You can see that we do not import any package for " -"federated learning. You can keep all these imports as they are even when " -"we add the federated learning components at a later point." +"required for a traditional (centralized) training on CIFAR-10. First, all " +"required packages (such as :code:`torch` and :code:`torchvision`) need to be " +"imported. You can see that we do not import any package for federated " +"learning. You can keep all these imports as they are even when we add the " +"federated learning components at a later point." msgstr "" -"CIFAR-10에 대한 기존 (centralized) 교육에 필요한 모든 구성 요소가 포함된 " +"CIFAR-10에 대한 기존 (중앙 집중식) 교육에 필요한 모든 구성 요소가 포함된 " ":code:`cifar.py`라는 새 파일을 생성해 보겠습니다. 먼저, 필요한 모든 " -"패키지(예: :code:`torch` 및 :code:`torchvision`)를 가져와야 합니다. " -"federated 학습을 위한 패키지를 가져오지 않는 것을 확인 할 수 있습니. 나중에 " -"federated 학습 구성 요소를 추가할 때에도 이러한 모든 가져오기를 그대로 " -"유지할 수 있습니다." +"패키지(예: :code:`torch` 및 :code:`torchvision`)를 가져와야 합니다. 연합 " +"학습을 위한 패키지를 가져오지 않는 것을 확인 할 수 있습니. 나중에 연합 학습 " +"구성 요소를 추가할 때에도 이러한 모든 가져오기를 그대로 유지할 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:32 msgid "" @@ -2946,124 +3001,125 @@ msgstr "" #: ../../source/example-pytorch-from-centralized-to-federated.rst:56 msgid "" -"The :code:`load_data()` function loads the CIFAR-10 training and test " -"sets. The :code:`transform` normalized the data after loading." +"The :code:`load_data()` function loads the CIFAR-10 training and test sets. " +"The :code:`transform` normalized the data after loading." msgstr "" -":code:`load_data()` 함수는 CIFAR-10 훈련 및 테스트 세트를 로드합니다. " -":code:`transform`은 로드 후 데이터를 정규화합니다." +":code:`load_data()` 함수는 CIFAR-10 훈련 및 테스트 세트를 로드합니다. :code:" +"`transform`은 로드 후 데이터를 정규화합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:74 msgid "" -"We now need to define the training (function :code:`train()`) which loops" -" over the training set, measures the loss, backpropagates it, and then " -"takes one optimizer step for each batch of training examples." +"We now need to define the training (function :code:`train()`) which loops " +"over the training set, measures the loss, backpropagates it, and then takes " +"one optimizer step for each batch of training examples." msgstr "" "이제 학습 집합을 반복하고, 손실을 측정하고, 이를 역전파한 다음 각 학습 예제 " -"배치에 대해 하나의 최적화 단계를 수행하는 학습(함수 :code:`train()`)을 " -"정의해야 합니다." +"배치에 대해 하나의 최적화 단계를 수행하는 학습(함수 :code:`train()`)을 정의해" +"야 합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:76 msgid "" -"The evaluation of the model is defined in the function :code:`test()`. " -"The function loops over all test samples and measures the loss of the " -"model based on the test dataset." +"The evaluation of the model is defined in the function :code:`test()`. The " +"function loops over all test samples and measures the loss of the model " +"based on the test dataset." msgstr "" -"모델 평가는 :code:`test()` 함수에 정의되어 있습니다. 이 함수는 모든 테스트 " -"샘플을 반복하고 테스트 데이터 세트에 따라 모델의 손실을 측정합니다." +"모델 평가는 :code:`test()` 함수에 정의되어 있습니다. 이 함수는 모든 테스트 샘" +"플을 반복하고 테스트 데이터 세트에 따라 모델의 손실을 측정합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:136 msgid "" "Having defined the data loading, model architecture, training, and " "evaluation we can put everything together and train our CNN on CIFAR-10." -msgstr "데이터 로딩, 모델 아키텍처, 훈련 및 평가를 정의했으면 모든 것을 종합하여 " +msgstr "" +"데이터 로딩, 모델 아키텍처, 훈련 및 평가를 정의했으면 모든 것을 종합하여 " "CIFAR-10에서 CNN을 훈련할 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:163 msgid "" -"So far, this should all look fairly familiar if you've used PyTorch " -"before. Let's take the next step and use what we've built to create a " -"simple federated learning system consisting of one server and two " -"clients." +"So far, this should all look fairly familiar if you've used PyTorch before. " +"Let's take the next step and use what we've built to create a simple " +"federated learning system consisting of one server and two clients." msgstr "" -"지금까지는 파이토치를 사용해 본 적이 있다면 상당히 익숙하게 보일 것입니다. " -"다음 단계로 넘어가서 구축한 것을 사용하여 하나의 서버와 두 개의 클라이언트로 " -"구성된 간단한 연합 학습 시스템을 만들어 보겠습니다." +"지금까지는 파이토치를 사용해 본 적이 있다면 상당히 익숙하게 보일 것입니다. 다" +"음 단계로 넘어가서 구축한 것을 사용하여 하나의 서버와 두 개의 클라이언트로 구" +"성된 간단한 연합 학습 시스템을 만들어 보겠습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:169 msgid "" -"The simple machine learning project discussed in the previous section " -"trains the model on a single dataset (CIFAR-10), we call this centralized" -" learning. This concept of centralized learning, as shown in the previous" -" section, is probably known to most of you, and many of you have used it " -"previously. Normally, if you'd want to run machine learning workloads in " -"a federated fashion, then you'd have to change most of your code and set " -"everything up from scratch. This can be a considerable effort." +"The simple machine learning project discussed in the previous section trains " +"the model on a single dataset (CIFAR-10), we call this centralized learning. " +"This concept of centralized learning, as shown in the previous section, is " +"probably known to most of you, and many of you have used it previously. " +"Normally, if you'd want to run machine learning workloads in a federated " +"fashion, then you'd have to change most of your code and set everything up " +"from scratch. This can be a considerable effort." msgstr "" "이전 섹션에서 설명한 간단한 머신 러닝 프로젝트는 단일 데이터 세트(CIFAR-10)" -"로 모델을 학습시키는데, 이를 centralized 학습이라고 부릅니다. 이전 섹션에서 " -"설명한 centralized 학습의 개념은 대부분 알고 계실 것이며, 많은 분들이 이전에 " -"사용해 보셨을 것입니다. 일반적으로 머신 러닝 워크로드를 federated 방식으로 " +"로 모델을 학습시키는데, 이를 중앙 집중식 학습이라고 부릅니다. 이전 섹션에서 " +"설명한 중앙 집중식 학습의 개념은 대부분 알고 계실 것이며, 많은 분들이 이전에 " +"사용해 보셨을 것입니다. 일반적으로 머신 러닝 워크로드를 연합 방식으로 " "실행하려면 대부분의 코드를 변경하고 모든 것을 처음부터 다시 설정해야 합니다. " "이는 상당한 노력이 필요할 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:173 msgid "" -"However, with Flower you can evolve your pre-existing code into a " -"federated learning setup without the need for a major rewrite." -msgstr "하지만 Flower를 사용하면 대대적인 재작성 없이도 기존 코드를 연합 학습 " -"설정으로 발전시킬 수 있습니다." +"However, with Flower you can evolve your pre-existing code into a federated " +"learning setup without the need for a major rewrite." +msgstr "" +"하지만 Flower를 사용하면 대대적인 재작성 없이도 기존 코드를 연합 학습 설정으" +"로 발전시킬 수 있습니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:175 msgid "" -"The concept is easy to understand. We have to start a *server* and then " -"use the code in :code:`cifar.py` for the *clients* that are connected to " -"the *server*. The *server* sends model parameters to the clients. The " -"*clients* run the training and update the parameters. The updated " -"parameters are sent back to the *server* which averages all received " -"parameter updates. This describes one round of the federated learning " -"process and we repeat this for multiple rounds." +"The concept is easy to understand. We have to start a *server* and then use " +"the code in :code:`cifar.py` for the *clients* that are connected to the " +"*server*. The *server* sends model parameters to the clients. The *clients* " +"run the training and update the parameters. The updated parameters are sent " +"back to the *server* which averages all received parameter updates. This " +"describes one round of the federated learning process and we repeat this for " +"multiple rounds." msgstr "" "개념은 이해하기 쉽습니다. *서버*를 시작한 다음 *서버*에 연결된 *클라이언트*" "에 대해 :code:`cifar.py`의 코드를 사용해야 합니다. *서버*는 모델 파라미터를 " "클라이언트로 전송합니다. *클라이언트*는 학습을 실행하고 파라미터를 " "업데이트합니다. 업데이트된 파라미터는 *서버*로 다시 전송되며, *서버*는 " -"수신된 모든 파라미터 업데이트의 평균을 구합니다. 이것은 federated 학습 " -"프로세스의 한 라운드를 설명하며 여러 라운드에 걸쳐 이 과정을 반복합니다." +"수신된 모든 파라미터 업데이트의 평균을 구합니다. 이것은 연합 학습 프로세스의 " +"한 라운드를 설명하며 여러 라운드에 걸쳐 이 과정을 반복합니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:197 msgid "" -"Finally, we will define our *client* logic in :code:`client.py` and build" -" upon the previously defined centralized training in :code:`cifar.py`. " -"Our *client* needs to import :code:`flwr`, but also :code:`torch` to " -"update the parameters on our PyTorch model:" +"Finally, we will define our *client* logic in :code:`client.py` and build " +"upon the previously defined centralized training in :code:`cifar.py`. Our " +"*client* needs to import :code:`flwr`, but also :code:`torch` to update the " +"parameters on our PyTorch model:" msgstr "" "마지막으로, :code:`client.py`에서 *client* 로직을 정의하고 :code:`cifar.py`" -"에서 이전에 정의한 centralized 학습을 기반으로 구축합니다. *클라이언트*는 " +"에서 이전에 정의한 중앙 집중식 학습을 기반으로 구축합니다. *클라이언트*는 " ":code:`flwr`을 가져와야 하며, PyTorch 모델의 파라미터를 업데이트하기 위해 " ":code:`torch`도 가져와야 합니다:" #: ../../source/example-pytorch-from-centralized-to-federated.rst:213 msgid "" -"Implementing a Flower *client* basically means implementing a subclass of" -" either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. " -"Our implementation will be based on :code:`flwr.client.NumPyClient` and " -"we'll call it :code:`CifarClient`. :code:`NumPyClient` is slightly easier" -" to implement than :code:`Client` if you use a framework with good NumPy " -"interoperability (like PyTorch or TensorFlow/Keras) because it avoids " -"some of the boilerplate that would otherwise be necessary. " -":code:`CifarClient` needs to implement four methods, two methods for " -"getting/setting model parameters, one method for training the model, and " -"one method for testing the model:" +"Implementing a Flower *client* basically means implementing a subclass of " +"either :code:`flwr.client.Client` or :code:`flwr.client.NumPyClient`. Our " +"implementation will be based on :code:`flwr.client.NumPyClient` and we'll " +"call it :code:`CifarClient`. :code:`NumPyClient` is slightly easier to " +"implement than :code:`Client` if you use a framework with good NumPy " +"interoperability (like PyTorch or TensorFlow/Keras) because it avoids some " +"of the boilerplate that would otherwise be necessary. :code:`CifarClient` " +"needs to implement four methods, two methods for getting/setting model " +"parameters, one method for training the model, and one method for testing " +"the model:" msgstr "" "Flower *클라이언트*를 구현한다는 것은 기본적으로 :code:`flwr.client.Client` " -"또는 :code:`flwr.client.NumPyClient`의 서브클래스를 구현하는 것을 " -"의미합니다. 우리의 구현은 :code:`flwr.client.NumPyClient`를 기반으로 하며, " -"이를 :code:`CifarClient`라고 부를 것입니다. :code:`NumPyClient`는 파이토치나 " -"텐서플로우/Keras처럼 NumPy 상호운용성이 좋은 프레임워크를 사용하는 경우 " -"필요한 일부 보일러플레이트를 피하기 때문에 :code:`Client`보다 구현하기가 " -"조금 더 쉽습니다. code:`CifarClient`는 모델 파라미터를 가져오거나 설정하는 " -"메서드 2개, 모델 학습을 위한 메서드 1개, 모델 테스트를 위한 메서드 1개 등 네 " -"가지 메서드를 구현해야 합니다:" +"또는 :code:`flwr.client.NumPyClient`의 서브클래스를 구현하는 것을 의미합니" +"다. 우리의 구현은 :code:`flwr.client.NumPyClient`를 기반으로 하며, 이를 :" +"code:`CifarClient`라고 부를 것입니다. :code:`NumPyClient`는 파이토치나 텐서플" +"로우/Keras처럼 NumPy 상호운용성이 좋은 프레임워크를 사용하는 경우 필요한 일" +"부 보일러플레이트를 피하기 때문에 :code:`Client`보다 구현하기가 조금 더 쉽습" +"니다. code:`CifarClient`는 모델 파라미터를 가져오거나 설정하는 메서드 2개, 모" +"델 학습을 위한 메서드 1개, 모델 테스트를 위한 메서드 1개 등 네 가지 메서드를 " +"구현해야 합니다:" #: ../../source/example-pytorch-from-centralized-to-federated.rst:219 msgid ":code:`set_parameters`" @@ -3079,62 +3135,61 @@ msgstr "로컬 손실 및 정확도를 서버에 반환합니다" #: ../../source/example-pytorch-from-centralized-to-federated.rst:232 msgid "" -"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make" -" use of the functions :code:`train()` and :code:`test()` previously " -"defined in :code:`cifar.py`. So what we really do here is we tell Flower " -"through our :code:`NumPyClient` subclass which of our already defined " -"functions to call for training and evaluation. We included type " -"annotations to give you a better understanding of the data types that get" -" passed around." -msgstr "" -"두 개의 :code:`NumPyClient` 메서드인 :code:`fit`과 :code:`evaluate`는 이전에 " -":code:`cifar.py`에 정의된 함수인 :code:`train()`과 :code:`test()`를 " -"활용합니다. 따라서 여기서 실제로 하는 일은 :code:`NumPyClient` 서브클래스를 " -"통해 이미 정의된 함수 중 훈련과 평가를 위해 호출할 함수를 Flower에 알려주는 " -"것입니다. 전달되는 데이터 유형을 더 잘 이해할 수 있도록 type annotations을 " -"포함했습니다." +"The two :code:`NumPyClient` methods :code:`fit` and :code:`evaluate` make " +"use of the functions :code:`train()` and :code:`test()` previously defined " +"in :code:`cifar.py`. So what we really do here is we tell Flower through " +"our :code:`NumPyClient` subclass which of our already defined functions to " +"call for training and evaluation. We included type annotations to give you a " +"better understanding of the data types that get passed around." +msgstr "" +"두 개의 :code:`NumPyClient` 메서드인 :code:`fit`과 :code:`evaluate`는 이전" +"에 :code:`cifar.py`에 정의된 함수인 :code:`train()`과 :code:`test()`를 활용합" +"니다. 따라서 여기서 실제로 하는 일은 :code:`NumPyClient` 서브클래스를 통해 이" +"미 정의된 함수 중 훈련과 평가를 위해 호출할 함수를 Flower에 알려주는 것입니" +"다. 전달되는 데이터 유형을 더 잘 이해할 수 있도록 type annotations을 포함했습" +"니다." #: ../../source/example-pytorch-from-centralized-to-federated.rst:280 msgid "" "All that's left to do it to define a function that loads both model and " -"data, creates a :code:`CifarClient`, and starts this client. You load " -"your data and model by using :code:`cifar.py`. Start :code:`CifarClient` " -"with the function :code:`fl.client.start_client()` by pointing it at the " -"same IP address we used in :code:`server.py`:" +"data, creates a :code:`CifarClient`, and starts this client. You load your " +"data and model by using :code:`cifar.py`. Start :code:`CifarClient` with the " +"function :code:`fl.client.start_client()` by pointing it at the same IP " +"address we used in :code:`server.py`:" msgstr "" -"이제 모델과 데이터를 모두 로드하는 함수를 정의하고, :code:`CifarClient`를 " -"생성하고, 이 클라이언트를 시작하는 작업만 남았습니다. 코드:`cifar.py`를 " -"사용하여 데이터와 모델을 로드합니다. :code:`server.py`에서 사용한 것과 " -"동일한 IP 주소를 지정하여 :code:`fl.client.start_client()` 함수로 " -":code:`CifarClient`를 시작합니다:" +"이제 모델과 데이터를 모두 로드하는 함수를 정의하고, :code:`CifarClient`를 생" +"성하고, 이 클라이언트를 시작하는 작업만 남았습니다. 코드:`cifar.py`를 사용하" +"여 데이터와 모델을 로드합니다. :code:`server.py`에서 사용한 것과 동일한 IP 주" +"소를 지정하여 :code:`fl.client.start_client()` 함수로 :code:`CifarClient`를 " +"시작합니다:" #: ../../source/example-pytorch-from-centralized-to-federated.rst:307 msgid "" -"in each window (make sure that the server is running before you do so) " -"and see your (previously centralized) PyTorch project run federated " -"learning across two clients. Congratulations!" +"in each window (make sure that the server is running before you do so) and " +"see your (previously centralized) PyTorch project run federated learning " +"across two clients. Congratulations!" msgstr "" -"를 입력하고(그 전에 서버가 실행 중인지 확인하세요) (이전에는centralized) " -"PyTorch 프로젝트가 두 클라이언트에서 federated 학습을 실행하는 것을 " -"확인합니다. 축하합니다!" +"를 입력하고(그 전에 서버가 실행 중인지 확인하세요) (이전에는 중앙 집중식) " +"PyTorch 프로젝트가 두 클라이언트에서 연합 학습을 실행하는 것을 확인합니다. " +"축하합니다!" #: ../../source/example-pytorch-from-centralized-to-federated.rst:312 msgid "" "The full source code for this example: `PyTorch: From Centralized To " -"Federated (Code) `_. Our example is, of course, " -"somewhat over-simplified because both clients load the exact same " -"dataset, which isn't realistic. You're now prepared to explore this topic" -" further. How about using different subsets of CIFAR-10 on each client? " -"How about adding more clients?" -msgstr "" -"이 예제의 전체 소스 코드: `파이토치: 중앙 Centralized에서 Federated으로 " -"(코드) `_. 물론 이 예제는 두 클라이언트가 완전히 동일한 " -"데이터 세트를 로드하기 때문에 다소 지나치게 단순화되어 있으며, 이는 " -"현실적이지 않습니다. 이제 이 주제를 더 자세히 살펴볼 준비가 되셨습니다. 각 " -"클라이언트에서 서로 다른 CIFAR-10의 하위 집합을 사용해 보는 것은 어떨까요? " -"클라이언트를 더 추가하는 것은 어떨까요?" +"Federated (Code) `_. Our example is, of course, somewhat over-" +"simplified because both clients load the exact same dataset, which isn't " +"realistic. You're now prepared to explore this topic further. How about " +"using different subsets of CIFAR-10 on each client? How about adding more " +"clients?" +msgstr "" +"이 예제의 전체 소스 코드: `파이토치: 중앙 Centralized에서 Federated으로 (코" +"드) `_. 물론 이 예제는 두 클라이언트가 완전히 동일한 데" +"이터 세트를 로드하기 때문에 다소 지나치게 단순화되어 있으며, 이는 현실적이지 " +"않습니다. 이제 이 주제를 더 자세히 살펴볼 준비가 되셨습니다. 각 클라이언트에" +"서 서로 다른 CIFAR-10의 하위 집합을 사용해 보는 것은 어떨까요? 클라이언트를 " +"더 추가하는 것은 어떨까요?" #: ../../source/explanation-differential-privacy.rst:2 #: ../../source/explanation-differential-privacy.rst:11 @@ -3144,27 +3199,26 @@ msgstr "차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:3 msgid "" -"The information in datasets like healthcare, financial transactions, user" -" preferences, etc., is valuable and has the potential for scientific " -"breakthroughs and provides important business insights. However, such " -"data is also sensitive and there is a risk of compromising individual " -"privacy." +"The information in datasets like healthcare, financial transactions, user " +"preferences, etc., is valuable and has the potential for scientific " +"breakthroughs and provides important business insights. However, such data " +"is also sensitive and there is a risk of compromising individual privacy." msgstr "" -"의료, 금융 거래, 사용자 선호도 등과 같은 데이터 세트의 정보는 가치 있고 " -"과학적 혁신의 잠재력을 지니고 있으며 중요한 비즈니스 인사이트를 제공합니다. " -"그러나 이러한 데이터는 또한 민감한 정보이며 개인의 프라이버시를 침해할 " -"위험이 있습니다." +"의료, 금융 거래, 사용자 선호도 등과 같은 데이터 세트의 정보는 가치 있고 과학" +"적 혁신의 잠재력을 지니고 있으며 중요한 비즈니스 인사이트를 제공합니다. 그러" +"나 이러한 데이터는 또한 민감한 정보이며 개인의 프라이버시를 침해할 위험이 있" +"습니다." #: ../../source/explanation-differential-privacy.rst:6 msgid "" "Traditional methods like anonymization alone would not work because of " -"attacks like Re-identification and Data Linkage. That's where " -"differential privacy comes in. It provides the possibility of analyzing " -"data while ensuring the privacy of individuals." +"attacks like Re-identification and Data Linkage. That's where differential " +"privacy comes in. It provides the possibility of analyzing data while " +"ensuring the privacy of individuals." msgstr "" -"익명화와 같은 기존 방법만으로는 재식별 및 데이터 연결과 같은 공격으로 인해 " -"효과가 없습니다. 그래서 차분 프라이버시가 등장했습니다. 차등 개인정보 보호는 " -"개인의 프라이버시를 보장하면서 데이터를 분석할 수 있는 가능성을 제공합니다." +"익명화와 같은 기존 방법만으로는 재식별 및 데이터 연결과 같은 공격으로 인해 효" +"과가 없습니다. 그래서 차분 프라이버시가 등장했습니다. 차등 개인정보 보호는 개" +"인의 프라이버시를 보장하면서 데이터를 분석할 수 있는 가능성을 제공합니다." #: ../../source/explanation-differential-privacy.rst:12 msgid "" @@ -3172,8 +3226,8 @@ msgid "" "instance, Alice's data). Differential Privacy (DP) guarantees that any " "analysis (M), like calculating the average income, will produce nearly " "identical results for both datasets (O and O' would be similar). This " -"preserves group patterns while obscuring individual details, ensuring the" -" individual's information remains hidden in the crowd." +"preserves group patterns while obscuring individual details, ensuring the " +"individual's information remains hidden in the crowd." msgstr "" "하나의 레코드(예: 앨리스의 데이터)를 제외하고는 동일한 두 개의 데이터 세트가 " "있다고 상상해 보세요. 차분 프라이버(DP)는 평균 소득 계산과 같은 모든 분석(M)" @@ -3189,12 +3243,11 @@ msgstr "DP 소개" msgid "" "One of the most commonly used mechanisms to achieve DP is adding enough " "noise to the output of the analysis to mask the contribution of each " -"individual in the data while preserving the overall accuracy of the " -"analysis." +"individual in the data while preserving the overall accuracy of the analysis." msgstr "" -"DP를 달성하기 위해 가장 일반적으로 사용되는 메커니즘 중 하나는 분석의 " -"전반적인 정확도를 유지하면서 데이터에서 각 개인의 기여도를 가릴 수 있도록 " -"분석 결과에 충분한 노이즈를 추가하는 것입니다." +"DP를 달성하기 위해 가장 일반적으로 사용되는 메커니즘 중 하나는 분석의 전반적" +"인 정확도를 유지하면서 데이터에서 각 개인의 기여도를 가릴 수 있도록 분석 결과" +"에 충분한 노이즈를 추가하는 것입니다." #: ../../source/explanation-differential-privacy.rst:25 msgid "Formal Definition" @@ -3204,19 +3257,19 @@ msgstr "공식 정의" msgid "" "Differential Privacy (DP) provides statistical guarantees against the " "information an adversary can infer through the output of a randomized " -"algorithm. It provides an unconditional upper bound on the influence of a" -" single individual on the output of the algorithm by adding noise [1]. A " -"randomized mechanism M provides (:math:`\\epsilon`, " -":math:`\\delta`)-differential privacy if for any two neighboring " -"databases, D :sub:`1` and D :sub:`2`, that differ in only a single " -"record, and for all possible outputs S ⊆ Range(A):" +"algorithm. It provides an unconditional upper bound on the influence of a " +"single individual on the output of the algorithm by adding noise [1]. A " +"randomized mechanism M provides (:math:`\\epsilon`, :math:`\\delta`)-" +"differential privacy if for any two neighboring databases, D :sub:`1` and D :" +"sub:`2`, that differ in only a single record, and for all possible outputs S " +"⊆ Range(A):" msgstr "" "차분 프라이버시(DP)는 공격자가 무작위 알고리즘의 출력을 통해 유추할 수 있는 " -"정보에 대해 통계적 보장을 제공합니다. 이는 노이즈를 추가하여 알고리즘의 " -"출력에 대한 한 개인의 영향력에 대한 무조건적인 상한선을 제공합니다[1]. " -"무작위 메커니즘 M은 하나의 레코드만 다른 두 개의 인접 데이터베이스인 " -"D:sub:`1`과 D:sub:`2`의 경우, 가능한 모든 출력 S ⊆ Range(A)에 대해 (:math:`" -"\\epsilon`, :math:`\\delta`)-차분 프라이버시를 제공합니다:" +"정보에 대해 통계적 보장을 제공합니다. 이는 노이즈를 추가하여 알고리즘의 출력" +"에 대한 한 개인의 영향력에 대한 무조건적인 상한선을 제공합니다[1]. 무작위 메" +"커니즘 M은 하나의 레코드만 다른 두 개의 인접 데이터베이스인 D:sub:`1`과 D:" +"sub:`2`의 경우, 가능한 모든 출력 S ⊆ Range(A)에 대해 (:math:`\\epsilon`, :" +"math:`\\delta`)-차분 프라이버시를 제공합니다:" #: ../../source/explanation-differential-privacy.rst:32 msgid "" @@ -3231,19 +3284,19 @@ msgid "" "The :math:`\\epsilon` parameter, also known as the privacy budget, is a " "metric of privacy loss. It also controls the privacy-utility trade-off; " "lower :math:`\\epsilon` values indicate higher levels of privacy but are " -"likely to reduce utility as well. The :math:`\\delta` parameter accounts " -"for a small probability on which the upper bound :math:`\\epsilon` does " -"not hold. The amount of noise needed to achieve differential privacy is " -"proportional to the sensitivity of the output, which measures the maximum" -" change in the output due to the inclusion or removal of a single record." +"likely to reduce utility as well. The :math:`\\delta` parameter accounts for " +"a small probability on which the upper bound :math:`\\epsilon` does not " +"hold. The amount of noise needed to achieve differential privacy is " +"proportional to the sensitivity of the output, which measures the maximum " +"change in the output due to the inclusion or removal of a single record." msgstr "" "프라이버시 예산이라고도 하는 :math:`\\epsilon` 매개변수는 프라이버시 손실을 " -"측정하는 지표입니다. 이 매개변수는 프라이버시와 효용의 균형을 제어하며, " -":math:`\\epsilon` 값이 낮을수록 프라이버시 수준이 높지만 효용도 감소할 " -"가능성이 높습니다. math:`\\delta` 매개변수는 상한값인 :math:`\\epsilon`이 " -"적용되지 않는 작은 확률을 설명합니다. 차분 프라이버시를 달성하는 데 필요한 " -"노이즈의 양은 출력의 감도에 비례하며, 이는 단일 레코드의 포함 또는 제거로 " -"인한 출력의 최대 변화를 측정합니다." +"측정하는 지표입니다. 이 매개변수는 프라이버시와 효용의 균형을 제어하며, :" +"math:`\\epsilon` 값이 낮을수록 프라이버시 수준이 높지만 효용도 감소할 가능성" +"이 높습니다. math:`\\delta` 매개변수는 상한값인 :math:`\\epsilon`이 적용되지 " +"않는 작은 확률을 설명합니다. 차분 프라이버시를 달성하는 데 필요한 노이즈의 양" +"은 출력의 감도에 비례하며, 이는 단일 레코드의 포함 또는 제거로 인한 출력의 최" +"대 변화를 측정합니다." #: ../../source/explanation-differential-privacy.rst:45 msgid "Differential Privacy in Machine Learning" @@ -3253,40 +3306,39 @@ msgstr "머신 러닝의 차분 프라이버시" msgid "" "DP can be utilized in machine learning to preserve the privacy of the " "training data. Differentially private machine learning algorithms are " -"designed in a way to prevent the algorithm to learn any specific " -"information about any individual data points and subsequently prevent the" -" model from revealing sensitive information. Depending on the stage at " -"which noise is introduced, various methods exist for applying DP to " -"machine learning algorithms. One approach involves adding noise to the " -"training data (either to the features or labels), while another method " -"entails injecting noise into the gradients of the loss function during " -"model training. Additionally, such noise can be incorporated into the " -"model's output." -msgstr "" -"머신 러닝에서 DP를 활용하여 학습 데이터의 개인정보를 보호할 수 있습니다. " -"차분 비공개 머신 러닝 알고리즘은 알고리즘이 개별 데이터 포인트에 대한 특정 " -"정보를 학습하지 못하도록 하여 모델이 민감한 정보를 노출하지 않도록 하는 " -"방식으로 설계되었습니다. 노이즈가 도입되는 단계에 따라 머신 러닝 알고리즘에 " -"DP를 적용하는 다양한 방법이 존재합니다. 한 가지 방법은 학습 데이터(특징 또는 " -"레이블)에 노이즈를 추가하는 것이고, 다른 방법은 모델 학습 중에 손실 함수의 " -"기울기에 노이즈를 주입하는 것입니다. 또한 이러한 노이즈를 모델의 출력에 " -"통합할 수도 있습니다." +"designed in a way to prevent the algorithm to learn any specific information " +"about any individual data points and subsequently prevent the model from " +"revealing sensitive information. Depending on the stage at which noise is " +"introduced, various methods exist for applying DP to machine learning " +"algorithms. One approach involves adding noise to the training data (either " +"to the features or labels), while another method entails injecting noise " +"into the gradients of the loss function during model training. Additionally, " +"such noise can be incorporated into the model's output." +msgstr "" +"머신 러닝에서 DP를 활용하여 학습 데이터의 개인정보를 보호할 수 있습니다. 차" +"분 비공개 머신 러닝 알고리즘은 알고리즘이 개별 데이터 포인트에 대한 특정 정보" +"를 학습하지 못하도록 하여 모델이 민감한 정보를 노출하지 않도록 하는 방식으로 " +"설계되었습니다. 노이즈가 도입되는 단계에 따라 머신 러닝 알고리즘에 DP를 적용" +"하는 다양한 방법이 존재합니다. 한 가지 방법은 학습 데이터(특징 또는 레이블)" +"에 노이즈를 추가하는 것이고, 다른 방법은 모델 학습 중에 손실 함수의 기울기에 " +"노이즈를 주입하는 것입니다. 또한 이러한 노이즈를 모델의 출력에 통합할 수도 있" +"습니다." #: ../../source/explanation-differential-privacy.rst:53 msgid "Differential Privacy in Federated Learning" -msgstr "Federated 학습의 차분 프라이버시" +msgstr "연합 학습의 차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:54 msgid "" "Federated learning is a data minimization approach that allows multiple " "parties to collaboratively train a model without sharing their raw data. " "However, federated learning also introduces new privacy challenges. The " -"model updates between parties and the central server can leak information" -" about the local data. These leaks can be exploited by attacks such as " +"model updates between parties and the central server can leak information " +"about the local data. These leaks can be exploited by attacks such as " "membership inference and property inference attacks, or model inversion " "attacks." msgstr "" -"Federated 학습은 여러 당사자가 원시 데이터를 공유하지 않고도 공동으로 모델을 " +"연합 학습은 여러 당사자가 원시 데이터를 공유하지 않고도 공동으로 모델을 " "학습할 수 있는 데이터 최소화 접근 방식입니다. 그러나 연합 학습은 새로운 " "개인정보 보호 문제를 야기하기도 합니다. 당사자와 중앙 서버 간의 모델 " "업데이트는 로컬 데이터에 대한 정보를 유출할 수 있습니다. 이러한 유출은 " @@ -3295,43 +3347,43 @@ msgstr "" #: ../../source/explanation-differential-privacy.rst:58 msgid "" -"DP can play a crucial role in federated learning to provide privacy for " -"the clients' data." -msgstr "DP는 federated학습에서 클라이언트의 데이터에 대한 개인 정보 보호를 제공하는 " -"데 중요한 역할을 할 수 있습니다." +"DP can play a crucial role in federated learning to provide privacy for the " +"clients' data." +msgstr "DP는 연합 학습에서 클라이언트의 데이터에 대한 개인 정보 보호를 제공하는 데 " +"중요한 역할을 할 수 있습니다." #: ../../source/explanation-differential-privacy.rst:60 msgid "" -"Depending on the granularity of privacy provision or the location of " -"noise addition, different forms of DP exist in federated learning. In " -"this explainer, we focus on two approaches of DP utilization in federated" -" learning based on where the noise is added: at the server (also known as" -" the center) or at the client (also known as the local)." +"Depending on the granularity of privacy provision or the location of noise " +"addition, different forms of DP exist in federated learning. In this " +"explainer, we focus on two approaches of DP utilization in federated " +"learning based on where the noise is added: at the server (also known as the " +"center) or at the client (also known as the local)." msgstr "" -"개인 정보 제공의 세분성 또는 노이즈 추가 위치에 따라 federated 학습에는 " -"다양한 형태의 DP가 존재합니다. 이 설명에서는 노이즈가 추가되는 위치에 따라 " -"서버(중앙이라고도 함) 또는 클라이언트(로컬이라고도 함)에서의 federated " -"학습에서 DP를 활용하는 두 가지 접근 방식에 중점을 둡니다." +"개인 정보 제공의 세분성 또는 노이즈 추가 위치에 따라 연합 학습에는 다양한 " +"형태의 DP가 존재합니다. 이 설명에서는 노이즈가 추가되는 위치에 따라 서버(" +"중앙이라고도 함) 또는 클라이언트(로컬이라고도 함)에서의 연합 학습에서 DP를 " +"활용하는 두 가지 접근 방식에 중점을 둡니다." #: ../../source/explanation-differential-privacy.rst:63 msgid "" -"**Central Differential Privacy**: DP is applied by the server and the " -"goal is to prevent the aggregated model from leaking information about " -"each client's data." +"**Central Differential Privacy**: DP is applied by the server and the goal " +"is to prevent the aggregated model from leaking information about each " +"client's data." msgstr "" -"**중앙 차분 프라이버시**: DP는 서버에서 적용되며 집계된 모델이 각 " -"클라이언트의 데이터에 대한 정보를 유출하는 것을 방지하는 것이 목표입니다." +"**중앙 차분 프라이버시**: DP는 서버에서 적용되며 집계된 모델이 각 클라이언트" +"의 데이터에 대한 정보를 유출하는 것을 방지하는 것이 목표입니다." #: ../../source/explanation-differential-privacy.rst:65 msgid "" "**Local Differential Privacy**: DP is applied on the client side before " -"sending any information to the server and the goal is to prevent the " -"updates that are sent to the server from leaking any information about " -"the client's data." +"sending any information to the server and the goal is to prevent the updates " +"that are sent to the server from leaking any information about the client's " +"data." msgstr "" -"**로컬 차분 개인정보 보호**: DP는 정보를 서버로 보내기 전에 클라이언트 " -"측에서 적용되며, 서버로 전송되는 업데이트가 클라이언트 데이터에 대한 정보를 " -"유출하는 것을 방지하는 것이 목표입니다." +"**로컬 차분 개인정보 보호**: DP는 정보를 서버로 보내기 전에 클라이언트 측에" +"서 적용되며, 서버로 전송되는 업데이트가 클라이언트 데이터에 대한 정보를 유출" +"하는 것을 방지하는 것이 목표입니다." #: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:68 @@ -3341,30 +3393,30 @@ msgstr "중앙 차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:69 msgid "" -"In this approach, which is also known as user-level DP, the central " -"server is responsible for adding noise to the globally aggregated " -"parameters. It should be noted that trust in the server is required." +"In this approach, which is also known as user-level DP, the central server " +"is responsible for adding noise to the globally aggregated parameters. It " +"should be noted that trust in the server is required." msgstr "" -"사용자 수준 DP라고도 하는 이 접근 방식에서는 중앙 서버가 전역적으로 집계된 " -"매개변수에 노이즈를 추가하는 역할을 담당합니다. 서버에 대한 신뢰가 " -"필요하다는 점에 유의해야 합니다." +"사용자 수준 DP라고도 하는 이 접근 방식에서는 중앙 서버가 전역적으로 집계된 매" +"개변수에 노이즈를 추가하는 역할을 담당합니다. 서버에 대한 신뢰가 필요하다는 " +"점에 유의해야 합니다." #: ../../source/explanation-differential-privacy.rst:76 msgid "" -"While there are various ways to implement central DP in federated " -"learning, we concentrate on the algorithms proposed by [2] and [3]. The " -"overall approach is to clip the model updates sent by the clients and add" -" some amount of noise to the aggregated model. In each iteration, a " -"random set of clients is chosen with a specific probability for training." -" Each client performs local training on its own data. The update of each " -"client is then clipped by some value `S` (sensitivity `S`). This would " -"limit the impact of any individual client which is crucial for privacy " -"and often beneficial for robustness. A common approach to achieve this is" -" by restricting the `L2` norm of the clients' model updates, ensuring " -"that larger updates are scaled down to fit within the norm `S`." -msgstr "" -"federated 학습에서 중앙 DP를 구현하는 방법은 여러 가지가 있지만, 여기서는 [2]" -"와 [3]에서 제안한 알고리즘에 집중합니다. 전반적인 접근 방식은 클라이언트가 " +"While there are various ways to implement central DP in federated learning, " +"we concentrate on the algorithms proposed by [2] and [3]. The overall " +"approach is to clip the model updates sent by the clients and add some " +"amount of noise to the aggregated model. In each iteration, a random set of " +"clients is chosen with a specific probability for training. Each client " +"performs local training on its own data. The update of each client is then " +"clipped by some value `S` (sensitivity `S`). This would limit the impact of " +"any individual client which is crucial for privacy and often beneficial for " +"robustness. A common approach to achieve this is by restricting the `L2` " +"norm of the clients' model updates, ensuring that larger updates are scaled " +"down to fit within the norm `S`." +msgstr "" +"연합 학습에서 중앙 DP를 구현하는 방법은 여러 가지가 있지만, 여기서는 [2]와 " +"[3]에서 제안한 알고리즘에 집중합니다. 전반적인 접근 방식은 클라이언트가 " "전송한 모델 업데이트를 잘라내고 집계된 모델에 약간의 노이즈를 추가하는 " "것입니다. 각 반복에서 특정 확률로 훈련할 무작위 클라이언트 세트가 " "선택됩니다. 각 클라이언트는 자체 데이터에 대해 로컬 학습을 수행합니다. 그런 " @@ -3380,17 +3432,17 @@ msgstr "클리핑" #: ../../source/explanation-differential-privacy.rst:89 msgid "" -"Afterwards, the Gaussian mechanism is used to add noise in order to " -"distort the sum of all clients' updates. The amount of noise is scaled to" -" the sensitivity value to obtain a privacy guarantee. The Gaussian " -"mechanism is used with a noise sampled from `N (0, σ²)` where `σ = ( " -"noise_scale * S ) / (number of sampled clients)`." +"Afterwards, the Gaussian mechanism is used to add noise in order to distort " +"the sum of all clients' updates. The amount of noise is scaled to the " +"sensitivity value to obtain a privacy guarantee. The Gaussian mechanism is " +"used with a noise sampled from `N (0, σ²)` where `σ = ( noise_scale * S ) / " +"(number of sampled clients)`." msgstr "" -"그 후 가우시안 메커니즘을 사용하여 모든 클라이언트의 업데이트 합계를 " -"왜곡하기 위해 노이즈를 추가합니다. 노이즈의 양은 감도 값에 따라 조정되어 " -"프라이버시 보장을 얻습니다. 가우시안 메커니즘은 `N (0, σ²)`에서 샘플링된 " -"노이즈와 함께 사용됩니다. 여기서 `σ = (noise_scale * S) / (샘플링된 " -"클라이언트 수)`입니다." +"그 후 가우시안 메커니즘을 사용하여 모든 클라이언트의 업데이트 합계를 왜곡하" +"기 위해 노이즈를 추가합니다. 노이즈의 양은 감도 값에 따라 조정되어 프라이버" +"시 보장을 얻습니다. 가우시안 메커니즘은 `N (0, σ²)`에서 샘플링된 노이즈와 함" +"께 사용됩니다. 여기서 `σ = (noise_scale * S) / (샘플링된 클라이언트 수)`입니" +"다." #: ../../source/explanation-differential-privacy.rst:94 msgid "Clipping" @@ -3398,39 +3450,40 @@ msgstr "클리핑" #: ../../source/explanation-differential-privacy.rst:96 msgid "" -"There are two forms of clipping commonly used in Central DP: Fixed " -"Clipping and Adaptive Clipping." -msgstr "Central DP에서 일반적으로 사용되는 클리핑에는 고정 클리핑과 조정 클리핑의 두 " +"There are two forms of clipping commonly used in Central DP: Fixed Clipping " +"and Adaptive Clipping." +msgstr "중앙 DP에서 일반적으로 사용되는 클리핑에는 고정 클리핑과 조정 클리핑의 두 " "가지 형태가 있습니다." #: ../../source/explanation-differential-privacy.rst:98 msgid "" -"**Fixed Clipping** : A predefined fix threshold is set for the magnitude " -"of clients' updates. Any update exceeding this threshold is clipped back " -"to the threshold value." +"**Fixed Clipping** : A predefined fix threshold is set for the magnitude of " +"clients' updates. Any update exceeding this threshold is clipped back to the " +"threshold value." msgstr "" -"**고정 클리핑** : 클라이언트의 업데이트 크기에 대해 미리 정의된 고정 " -"임계값이 설정됩니다. 이 임계값을 초과하는 모든 업데이트는 임계값으로 다시 " -"클리핑됩니다." +"**고정 클리핑** : 클라이언트의 업데이트 크기에 대해 미리 정의된 고정 임계값" +"이 설정됩니다. 이 임계값을 초과하는 모든 업데이트는 임계값으로 다시 클리핑됩" +"니다." #: ../../source/explanation-differential-privacy.rst:100 msgid "" -"**Adaptive Clipping** : The clipping threshold dynamically adjusts based " -"on the observed update distribution [4]. It means that the clipping value" -" is tuned during the rounds with respect to the quantile of the update " -"norm distribution." +"**Adaptive Clipping** : The clipping threshold dynamically adjusts based on " +"the observed update distribution [4]. It means that the clipping value is " +"tuned during the rounds with respect to the quantile of the update norm " +"distribution." msgstr "" -"**조 클리핑** : 클리핑 임계값은 관찰된 업데이트 분포에 따라 동적으로 " +"**조정 클리핑** : 클리핑 임계값은 관찰된 업데이트 분포에 따라 동적으로 " "조정됩니다[4]. 즉, 클리핑 값은 업데이트 표준 분포의 사분위수에 따라 라운드가 " "진행되는 동안 조정됩니다." #: ../../source/explanation-differential-privacy.rst:102 msgid "" -"The choice between fixed and adaptive clipping depends on various factors" -" such as privacy requirements, data distribution, model complexity, and " +"The choice between fixed and adaptive clipping depends on various factors " +"such as privacy requirements, data distribution, model complexity, and " "others." -msgstr "고정 클리핑과 조정 클리핑 중 선택은 개인정보 보호 요구 사항, 데이터 배포, " -"모델 복잡성 등 다양한 요인에 따라 달라집니다." +msgstr "" +"고정 클리핑과 조정 클리핑 중 선택은 개인정보 보호 요구 사항, 데이터 배포, 모" +"델 복잡성 등 다양한 요인에 따라 달라집니다." #: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:105 @@ -3441,13 +3494,13 @@ msgstr "로컬 차분 프라이버시" #: ../../source/explanation-differential-privacy.rst:107 msgid "" "In this approach, each client is responsible for performing DP. Local DP " -"avoids the need for a fully trusted aggregator, but it should be noted " -"that local DP leads to a decrease in accuracy but better privacy in " -"comparison to central DP." +"avoids the need for a fully trusted aggregator, but it should be noted that " +"local DP leads to a decrease in accuracy but better privacy in comparison to " +"central DP." msgstr "" -"이 접근 방식에서는 각 클라이언트가 DP를 수행할 책임이 있습니다. 로컬 DP는 " -"완전히 신뢰할 수 있는 애그리게이터가 필요하지 않지만, 로컬 DP는 중앙 DP에 " -"비해 정확도는 떨어져도 개인 정보 보호는 더 우수하다는 점에 유의해야 합니다." +"이 접근 방식에서는 각 클라이언트가 DP를 수행할 책임이 있습니다. 로컬 DP는 완" +"전히 신뢰할 수 있는 애그리게이터가 필요하지 않지만, 로컬 DP는 중앙 DP에 비해 " +"정확도는 떨어져도 개인 정보 보호는 더 우수하다는 점에 유의해야 합니다." #: ../../source/explanation-differential-privacy.rst:116 msgid "In this explainer, we focus on two forms of achieving Local DP:" @@ -3456,25 +3509,25 @@ msgstr "이 설명에서는 로컬 DP를 달성하는 두 가지 형태에 중 #: ../../source/explanation-differential-privacy.rst:118 msgid "" "Each client adds noise to the local updates before sending them to the " -"server. To achieve (:math:`\\epsilon`, :math:`\\delta`)-DP, considering " -"the sensitivity of the local model to be ∆, Gaussian noise is applied " -"with a noise scale of σ where:" +"server. To achieve (:math:`\\epsilon`, :math:`\\delta`)-DP, considering the " +"sensitivity of the local model to be ∆, Gaussian noise is applied with a " +"noise scale of σ where:" msgstr "" "각 클라이언트는 로컬 업데이트를 서버로 보내기 전에 로컬 업데이트에 노이즈를 " -"추가합니다. 로컬 모델의 감도를 ∆로 간주하여 가우시안 노이즈가 σ의 노이즈 " -"스케일로 적용되어 (:math:`\\epsilon`, :math:`\\delta`)-DP를 달성하기 위해, " -"여기서 σ는 노이즈 스케일입니다:" +"추가합니다. 로컬 모델의 감도를 ∆로 간주하여 가우시안 노이즈가 σ의 노이즈 스케" +"일로 적용되어 (:math:`\\epsilon`, :math:`\\delta`)-DP를 달성하기 위해, 여기" +"서 σ는 노이즈 스케일입니다:" #: ../../source/explanation-differential-privacy.rst:120 msgid "" "\\small\n" -"\\frac{∆ \\times \\sqrt{2 \\times " -"\\log\\left(\\frac{1.25}{\\delta}\\right)}}{\\epsilon}\n" +"\\frac{∆ \\times \\sqrt{2 \\times \\log\\left(\\frac{1.25}{\\delta}\\right)}}" +"{\\epsilon}\n" "\n" msgstr "" "\\small\n" -"\\frac{∆ \\times \\sqrt{2 \\times \\log\\left(\\frac{1.25}{\\delta}\\right" -")}}{\\epsilon}\n" +"\\frac{∆ \\times \\sqrt{2 \\times \\log\\left(\\frac{1.25}{\\delta}\\right)}}" +"{\\epsilon}\n" "\n" #: ../../source/explanation-differential-privacy.rst:125 @@ -3483,15 +3536,16 @@ msgid "" "training (DP-SGD). More specifically, in this approach, gradients are " "clipped and an amount of calibrated noise is injected into the gradients." msgstr "" -"각 클라이언트는 로컬 트레이닝(DP-SGD) 중에 모델의 gradient에 노이즈를 " -"추가합니다. 보다 구체적으로, 이 접근 방식에서는 gradient이 클리핑되고 보정된 " -"노이즈가 gradient에 주입됩니다." +"각 클라이언트는 로컬 트레이닝(DP-SGD) 중에 모델의 gradient에 노이즈를 추가합" +"니다. 보다 구체적으로, 이 접근 방식에서는 gradient이 클리핑되고 보정된 노이즈" +"가 gradient에 주입됩니다." #: ../../source/explanation-differential-privacy.rst:128 msgid "" "Please note that these two approaches are providing privacy at different " "levels." -msgstr "이 두 가지 접근 방식은 서로 다른 수준의 개인정보 보호 기능을 제공한다는 점에 " +msgstr "" +"이 두 가지 접근 방식은 서로 다른 수준의 개인정보 보호 기능을 제공한다는 점에 " "유의하세요." #: ../../source/explanation-differential-privacy.rst:131 @@ -3504,38 +3558,37 @@ msgstr "[1] Dwork 외. 차분 프라이버시의 알고리즘적 기초." #: ../../source/explanation-differential-privacy.rst:135 msgid "" -"[2] McMahan et al. Learning Differentially Private Recurrent Language " -"Models." +"[2] McMahan et al. Learning Differentially Private Recurrent Language Models." msgstr "[2] McMahan 외. 차분적 개인 반복 언어 모델 학습." #: ../../source/explanation-differential-privacy.rst:137 msgid "" -"[3] Geyer et al. Differentially Private Federated Learning: A Client " -"Level Perspective." -msgstr "[3] Geyer 외. 차분적 개인 Federated 학습: 고객 수준의 관점." +"[3] Geyer et al. Differentially Private Federated Learning: A Client Level " +"Perspective." +msgstr "[3] Geyer 외. 차분적 개인 연합 학습: 고객 수준의 관점." #: ../../source/explanation-differential-privacy.rst:139 -msgid "[4] Galen et al. Differentially Private Learning with Adaptive Clipping." +msgid "" +"[4] Galen et al. Differentially Private Learning with Adaptive Clipping." msgstr "[4] Galen 외. 조정형 클리핑을 통한 차분적 개인 학습." #: ../../source/explanation-federated-evaluation.rst:2 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:292 msgid "Federated evaluation" -msgstr "Federated 평가" +msgstr "연합 평가" #: ../../source/explanation-federated-evaluation.rst:4 msgid "" "There are two main approaches to evaluating models in federated learning " -"systems: centralized (or server-side) evaluation and federated (or " -"client-side) evaluation." +"systems: centralized (or server-side) evaluation and federated (or client-" +"side) evaluation." msgstr "" -"federated 학습 시스템에서 모델을 평가하는 데는 centralized(또는 서버 측) " -"평가와 federated(또는 클라이언트 측) 평가라는 두 가지 주요 접근 방식이 " -"있습니다." +"연합 학습 시스템에서 모델을 평가하는 데는 중앙 집중식(또는 서버 측) 평가와 " +"연합(또는 클라이언트 측) 평가라는 두 가지 주요 접근 방식이 있습니다." #: ../../source/explanation-federated-evaluation.rst:8 msgid "Centralized Evaluation" -msgstr "Centralized 평가" +msgstr "중앙 집중식 평가" #: ../../source/explanation-federated-evaluation.rst:11 msgid "Built-In Strategies" @@ -3548,7 +3601,7 @@ msgid "" "function that can take the current global model parameters as input and " "return evaluation results:" msgstr "" -"모든 기본 제공 전략은 초기화 중에 평가 함수를 제공하여 centralized 평가를 " +"모든 기본 제공 전략은 초기화 중에 평가 함수를 제공하여 중앙 집중식 평가를 " "지원합니다. 평가 함수는 현재 글로벌 모델 파라미터를 입력으로 받아 평가 " "결과를 반환할 수 있는 모든 함수입니다:" @@ -3558,97 +3611,95 @@ msgstr "사용자 정의 전략" #: ../../source/explanation-federated-evaluation.rst:60 msgid "" -"The :code:`Strategy` abstraction provides a method called " -":code:`evaluate` that can directly be used to evaluate the current global" -" model parameters. The current server implementation calls " -":code:`evaluate` after parameter aggregation and before federated " -"evaluation (see next paragraph)." +"The :code:`Strategy` abstraction provides a method called :code:`evaluate` " +"that can directly be used to evaluate the current global model parameters. " +"The current server implementation calls :code:`evaluate` after parameter " +"aggregation and before federated evaluation (see next paragraph)." msgstr "" -"코드:`전략` 추상화는 현재 전역 모델 파라미터를 평가하는 데 직접 사용할 수 " -"있는 :코드:`평가`라는 메서드를 제공합니다. 현재 서버 구현에서는 매개변수 " -"집계 후와 연합 평가 전에 :code:`evaluate`를 호출합니다(다음 단락 참조)." +"코드:`전략` 추상화는 현재 전역 모델 파라미터를 평가하는 데 직접 사용할 수 있" +"는 :코드:`평가`라는 메서드를 제공합니다. 현재 서버 구현에서는 매개변수 집계 " +"후와 연합 평가 전에 :code:`evaluate`를 호출합니다(다음 단락 참조)." #: ../../source/explanation-federated-evaluation.rst:65 msgid "Federated Evaluation" -msgstr "Federated 평가" +msgstr "연합 평가" #: ../../source/explanation-federated-evaluation.rst:68 msgid "Implementing Federated Evaluation" -msgstr "Federated 평가 구현" +msgstr "연합 평가 구현" #: ../../source/explanation-federated-evaluation.rst:70 msgid "" -"Client-side evaluation happens in the :code:`Client.evaluate` method and " -"can be configured from the server side." -msgstr "클라이언트 측 평가는 :code:`Client.evaluate` 메서드에서 이루어지며 서버 " -"측에서 구성할 수 있습니다." +"Client-side evaluation happens in the :code:`Client.evaluate` method and can " +"be configured from the server side." +msgstr "" +"클라이언트 측 평가는 :code:`Client.evaluate` 메서드에서 이루어지며 서버 측에" +"서 구성할 수 있습니다." #: ../../source/explanation-federated-evaluation.rst:101 msgid "Configuring Federated Evaluation" -msgstr "Federated 평가 구성" +msgstr "연합 평가 구성" #: ../../source/explanation-federated-evaluation.rst:103 msgid "" "Federated evaluation can be configured from the server side. Built-in " "strategies support the following arguments:" -msgstr "Federated 평가는 서버 측에서 구성할 수 있습니다. 기본 제공 전략은 다음 " -"인수를 지원합니다:" +msgstr "연합 평가는 서버 측에서 구성할 수 있습니다. 기본 제공 전략은 다음 인수를 " +"지원합니다:" #: ../../source/explanation-federated-evaluation.rst:105 msgid "" -":code:`fraction_evaluate`: a :code:`float` defining the fraction of " -"clients that will be selected for evaluation. If " -":code:`fraction_evaluate` is set to :code:`0.1` and :code:`100` clients " -"are connected to the server, then :code:`10` will be randomly selected " -"for evaluation. If :code:`fraction_evaluate` is set to :code:`0.0`, " -"federated evaluation will be disabled." +":code:`fraction_evaluate`: a :code:`float` defining the fraction of clients " +"that will be selected for evaluation. If :code:`fraction_evaluate` is set " +"to :code:`0.1` and :code:`100` clients are connected to the server, then :" +"code:`10` will be randomly selected for evaluation. If :code:" +"`fraction_evaluate` is set to :code:`0.0`, federated evaluation will be " +"disabled." msgstr "" ":code:`fraction_evaluate`: 평가를 위해 선택될 클라이언트의 비율을 정의하는 " ":code:`float`입니다. 코드:`fraction_evaluate`가 :code:`0.1`로 설정되어 있고 " ":code:`100` 클라이언트가 서버에 연결되어 있는 경우 :code:`10`이 평가를 위해 " "무작위로 선택됩니다. code:`fraction_evaluate`가 :code:`0.0`으로 설정된 경우 " -"federated 평가가 비활성화됩니다." +"연합 평가가 비활성화됩니다." #: ../../source/explanation-federated-evaluation.rst:106 msgid "" -":code:`min_evaluate_clients`: an :code:`int`: the minimum number of " -"clients to be selected for evaluation. If :code:`fraction_evaluate` is " -"set to :code:`0.1`, :code:`min_evaluate_clients` is set to 20, and " -":code:`100` clients are connected to the server, then :code:`20` clients " -"will be selected for evaluation." +":code:`min_evaluate_clients`: an :code:`int`: the minimum number of clients " +"to be selected for evaluation. If :code:`fraction_evaluate` is set to :code:" +"`0.1`, :code:`min_evaluate_clients` is set to 20, and :code:`100` clients " +"are connected to the server, then :code:`20` clients will be selected for " +"evaluation." msgstr "" -":code:`min_evaluate_clients`: 평가를 위해 선택할 최소 클라이언트 수. " -":code:`int`. 코드:`fraction_evaluate`가 :code:`0.1`로 설정되어 있고 " -":code:`fraction_evaluate`가 20으로 설정되어 있으며 :code:`100` 클라이언트가 " -"서버에 연결되어 있는 경우 :code:`20` 클라이언트가 평가를 위해 선택됩니다." +":code:`min_evaluate_clients`: 평가를 위해 선택할 최소 클라이언트 수. :code:" +"`int`. 코드:`fraction_evaluate`가 :code:`0.1`로 설정되어 있고 :code:" +"`fraction_evaluate`가 20으로 설정되어 있으며 :code:`100` 클라이언트가 서버에 " +"연결되어 있는 경우 :code:`20` 클라이언트가 평가를 위해 선택됩니다." #: ../../source/explanation-federated-evaluation.rst:107 msgid "" ":code:`min_available_clients`: an :code:`int` that defines the minimum " -"number of clients which need to be connected to the server before a round" -" of federated evaluation can start. If fewer than " -":code:`min_available_clients` are connected to the server, the server " -"will wait until more clients are connected before it continues to sample " -"clients for evaluation." -msgstr "" -":code:`min_available_clients`: federated 평가 단계를 시작하기 전에 서버에 " -"연결해야 하는 최소 클라이언트 수를 정의하는 :code:`int`입니다. 서버에 연결된 " -"클라이언트가 :code:`min_available_clients`보다 적으면 서버는 더 많은 " -"클라이언트가 연결될 때까지 기다렸다가 평가를 위한 클라이언트 샘플링을 " -"계속합니다." +"number of clients which need to be connected to the server before a round of " +"federated evaluation can start. If fewer than :code:`min_available_clients` " +"are connected to the server, the server will wait until more clients are " +"connected before it continues to sample clients for evaluation." +msgstr "" +":code:`min_available_clients`: federated 평가 단계를 시작하기 전에 서버에 연" +"결해야 하는 최소 클라이언트 수를 정의하는 :code:`int`입니다. 서버에 연결된 클" +"라이언트가 :code:`min_available_clients`보다 적으면 서버는 더 많은 클라이언트" +"가 연결될 때까지 기다렸다가 평가를 위한 클라이언트 샘플링을 계속합니다." #: ../../source/explanation-federated-evaluation.rst:108 msgid "" ":code:`on_evaluate_config_fn`: a function that returns a configuration " -"dictionary which will be sent to the selected clients. The function will " -"be called during each round and provides a convenient way to customize " -"client-side evaluation from the server side, for example, to configure " -"the number of validation steps performed." +"dictionary which will be sent to the selected clients. The function will be " +"called during each round and provides a convenient way to customize client-" +"side evaluation from the server side, for example, to configure the number " +"of validation steps performed." msgstr "" -"code:`on_evaluate_config_fn`: 선택한 클라이언트로 전송할 구성 사전을 " -"반환하는 함수입니다. 이 함수는 각 단계 중에 호출되며, 서버 측에서 클라이언트 " -"측 평가를 사용자 지정하는 편리한 방법을 제공합니다(예: 수행되는 유효성 검사 " -"단계 수 구성)." +"code:`on_evaluate_config_fn`: 선택한 클라이언트로 전송할 구성 사전을 반환하" +"는 함수입니다. 이 함수는 각 단계 중에 호출되며, 서버 측에서 클라이언트 측 평" +"가를 사용자 지정하는 편리한 방법을 제공합니다(예: 수행되는 유효성 검사 단계 " +"수 구성)." #: ../../source/explanation-federated-evaluation.rst:135 msgid "Evaluating Local Model Updates During Training" @@ -3656,9 +3707,8 @@ msgstr "훈련 중 로컬 모델 업데이트 평가" #: ../../source/explanation-federated-evaluation.rst:137 msgid "" -"Model parameters can also be evaluated during training. " -":code:`Client.fit` can return arbitrary evaluation results as a " -"dictionary:" +"Model parameters can also be evaluated during training. :code:`Client.fit` " +"can return arbitrary evaluation results as a dictionary:" msgstr "" "모델 파라미터는 훈련 중에도 평가할 수 있습니다. :code:`Client.fit`은 임의의 " "평가 결과를 dictionary로 반환할 수 있습니다:" @@ -3669,12 +3719,12 @@ msgstr "전체 코드 예제" #: ../../source/explanation-federated-evaluation.rst:179 msgid "" -"For a full code example that uses both centralized and federated " -"evaluation, see the *Advanced TensorFlow Example* (the same approach can " -"be applied to workloads implemented in any other framework): " -"https://github.com/adap/flower/tree/main/examples/advanced-tensorflow" +"For a full code example that uses both centralized and federated evaluation, " +"see the *Advanced TensorFlow Example* (the same approach can be applied to " +"workloads implemented in any other framework): https://github.com/adap/" +"flower/tree/main/examples/advanced-tensorflow" msgstr "" -"federated 평가와 centralized 평가를 모두 사용하는 전체 코드 예제는 *고급 " +"연합 평가와 중앙 집중식 평가를 모두 사용하는 전체 코드 예제는 *고급 " "텐서플로우 예제*(다른 프레임워크에서 구현된 워크로드에도 동일한 접근 방식을 " "적용할 수 있음)를 참조하세요: https://github.com/adap/flower/tree/main/" "examples/advanced-tensorflow" @@ -3850,13 +3900,13 @@ msgstr "운행 중 작업 추적을 위한 깃허브 이슈를 예약합니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:37 msgid "" -"ensure community participants can successfully drive changes to " -"completion across one or more releases while stakeholders are adequately " -"represented throughout the process" +"ensure community participants can successfully drive changes to completion " +"across one or more releases while stakeholders are adequately represented " +"throughout the process" msgstr "" -"커뮤니티 참여자가 하나 이상의 릴리즈에서 변경 사항을 성공적으로 완료할 수 " -"있도록 하는 동시에 이해 관계자가 프로세스 전반에 걸쳐 적절히 대표되도록 " -"보장합니다" +"커뮤니티 참여자가 하나 이상의 릴리즈에서 변경 사항을 성공적으로 완료할 수 있" +"도록 하는 동시에 이해 관계자가 프로세스 전반에 걸쳐 적절히 대표되도록 보장합" +"니다" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:39 msgid "Hence, an Enhancement Doc combines aspects of" @@ -3883,72 +3933,72 @@ msgstr "를 하나의 파일로 통합하여 커뮤니티와 협력해 점진적 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:49 msgid "" "For far-fetching changes or features proposed to Flower, an abstraction " -"beyond a single GitHub issue or pull request is required to understand " -"and communicate upcoming changes to the project." +"beyond a single GitHub issue or pull request is required to understand and " +"communicate upcoming changes to the project." msgstr "" -"Flower에 제안된 변경 사항이나 기능을 멀리 가져오는 경우, 프로젝트의 향후 " -"변경 사항을 이해하고 전달하기 위해 단일 GitHub 이슈 또는 pull request를 " -"넘어서는 abstraction이 필요합니다." +"Flower에 제안된 변경 사항이나 기능을 멀리 가져오는 경우, 프로젝트의 향후 변" +"경 사항을 이해하고 전달하기 위해 단일 GitHub 이슈 또는 pull request를 넘어서" +"는 abstraction이 필요합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:51 msgid "" -"The purpose of this process is to reduce the amount of \"tribal " -"knowledge\" in our community. By moving decisions from Slack threads, " -"video calls, and hallway conversations into a well-tracked artifact, this" -" process aims to enhance communication and discoverability." +"The purpose of this process is to reduce the amount of \"tribal knowledge\" " +"in our community. By moving decisions from Slack threads, video calls, and " +"hallway conversations into a well-tracked artifact, this process aims to " +"enhance communication and discoverability." msgstr "" -"이 프로세스의 목적은 커뮤니티 내 '부족한 지식'의 양을 줄이는 것입니다. 이 " -"프로세스는 Slack 스레드, 영상 통화, 복도 대화에서 나온 의사 결정을 잘 추적된 " -"아티팩트로 옮김으로써 커뮤니케이션과 검색 가능성을 향상시키는 것을 목표로 " -"합니다." +"이 프로세스의 목적은 커뮤니티 내 '부족한 지식'의 양을 줄이는 것입니다. 이 프" +"로세스는 Slack 스레드, 영상 통화, 복도 대화에서 나온 의사 결정을 잘 추적된 아" +"티팩트로 옮김으로써 커뮤니케이션과 검색 가능성을 향상시키는 것을 목표로 합니" +"다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:55 msgid "" -"Roughly any larger, user-facing enhancement should follow the Enhancement" -" process. If an enhancement would be described in either written or " -"verbal communication to anyone besides the author or developer, then " -"consider creating an Enhancement Doc." +"Roughly any larger, user-facing enhancement should follow the Enhancement " +"process. If an enhancement would be described in either written or verbal " +"communication to anyone besides the author or developer, then consider " +"creating an Enhancement Doc." msgstr "" "대략적으로 사용자를 대상으로 하는 대규모 개선 사항은 개선 프로세스를 따라야 " -"합니다. 개선 사항을 작성자나 개발자 이외의 다른 사람에게 서면 또는 구두로 " -"설명해야 하는 경우에는 개선 문서 작성을 고려하세요." +"합니다. 개선 사항을 작성자나 개발자 이외의 다른 사람에게 서면 또는 구두로 설" +"명해야 하는 경우에는 개선 문서 작성을 고려하세요." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:57 msgid "" -"Similarly, any technical effort (refactoring, major architectural change)" -" that will impact a large section of the development community should " -"also be communicated widely. The Enhancement process is suited for this " -"even if it will have zero impact on the typical user or operator." +"Similarly, any technical effort (refactoring, major architectural change) " +"that will impact a large section of the development community should also be " +"communicated widely. The Enhancement process is suited for this even if it " +"will have zero impact on the typical user or operator." msgstr "" -"마찬가지로 개발 커뮤니티의 많은 부분에 영향을 미치는 기술적 노력(리팩토링, " -"주요 아키텍처 변경)도 널리 알려야 합니다. 개선 프로세스는 일반 사용자나 " -"운영자에게 전혀 영향을 미치지 않더라도 이를 위해 적합합니다." +"마찬가지로 개발 커뮤니티의 많은 부분에 영향을 미치는 기술적 노력(리팩토링, 주" +"요 아키텍처 변경)도 널리 알려야 합니다. 개선 프로세스는 일반 사용자나 운영자" +"에게 전혀 영향을 미치지 않더라도 이를 위해 적합합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:61 msgid "" -"For small changes and additions, going through the Enhancement process " -"would be time-consuming and unnecessary. This includes, for example, " -"adding new Federated Learning algorithms, as these only add features " -"without changing how Flower works or is used." +"For small changes and additions, going through the Enhancement process would " +"be time-consuming and unnecessary. This includes, for example, adding new " +"Federated Learning algorithms, as these only add features without changing " +"how Flower works or is used." msgstr "" "작은 변경 및 추가의 경우, 개선 프로세스를 거치는 것은 시간이 많이 걸리고 " -"불필요합니다. 예를 들어, 새로운 Federated 학습 알고리즘을 추가하는 것은 " -"Flower의 작동 방식이나 사용 방식을 변경하지 않고 기능만 추가하는 것이기 " -"때문입니다." +"불필요합니다. 예를 들어, 새로운 연합 학습 알고리즘을 추가하는 것은 Flower의 " +"작동 방식이나 사용 방식을 변경하지 않고 기능만 추가하는 것이기 때문입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:63 msgid "" "Enhancements are different from feature requests, as they are already " -"providing a laid-out path for implementation and are championed by " -"members of the community." -msgstr "기능 개선은 이미 구현할 수 있는 경로가 마련되어 있고 커뮤니티 구성원들이 " -"지지하는 것이므로 기능 요청과는 다릅니다." +"providing a laid-out path for implementation and are championed by members " +"of the community." +msgstr "" +"기능 개선은 이미 구현할 수 있는 경로가 마련되어 있고 커뮤니티 구성원들이 지지" +"하는 것이므로 기능 요청과는 다릅니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:67 msgid "" "An Enhancement is captured in a Markdown file that follows a defined " -"template and a workflow to review and store enhancement docs for " -"reference — the Enhancement Doc." +"template and a workflow to review and store enhancement docs for reference " +"— the Enhancement Doc." msgstr "" "개선 사항은 정의된 템플릿과 참조용으로 Enhancement Doc.를 검토하고 저장하는 " "워크플로우를 따르는 Markdown 파일에 캡처됩니다." @@ -4002,11 +4052,11 @@ msgstr "Metadata" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:92 msgid "" -"**fed-number** (Required) The `fed-number` of the last Flower Enhancement" -" Doc + 1. With this number, it becomes easy to reference other proposals." +"**fed-number** (Required) The `fed-number` of the last Flower Enhancement " +"Doc + 1. With this number, it becomes easy to reference other proposals." msgstr "" -"**피드 번호** (필수) 마지막 Flower Enhancement 문서의 `피드 번호` + 1. 이 " -"번호를 사용하면 다른 제안을 쉽게 참조할 수 있습니다." +"**피드 번호** (필수) 마지막 Flower Enhancement 문서의 `피드 번호` + 1. 이 번" +"호를 사용하면 다른 제안을 쉽게 참조할 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:94 msgid "**title** (Required) The title of the proposal in plain language." @@ -4014,33 +4064,35 @@ msgstr "**제목** (필수) 제안서의 제목을 평이한 언어로 입력합 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:96 msgid "" -"**status** (Required) The current status of the proposal. See " -"[workflow](#workflow) for the possible states." -msgstr "**상태** (필수) 제안의 현재 상태입니다. 가능한 상태는 [워크플로](#워크플로)" +"**status** (Required) The current status of the proposal. See [workflow]" +"(#workflow) for the possible states." +msgstr "" +"**상태** (필수) 제안의 현재 상태입니다. 가능한 상태는 [워크플로](#워크플로)" "를 참조하세요." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:98 msgid "" -"**authors** (Required) A list of authors of the proposal. This is simply " -"the GitHub ID." +"**authors** (Required) A list of authors of the proposal. This is simply the " +"GitHub ID." msgstr "**저자** (필수) 제안서의 작성자 목록입니다. 간단히 GitHub ID입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:100 msgid "" -"**creation-date** (Required) The date that the proposal was first " -"submitted in a PR." +"**creation-date** (Required) The date that the proposal was first submitted " +"in a PR." msgstr "**생성 날짜** (필수) PR에서 제안서를 처음 제출한 날짜입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:102 msgid "" "**last-updated** (Optional) The date that the proposal was last changed " "significantly." -msgstr "**마지막 업데이트** (선택 사항) 제안서가 마지막으로 크게 변경된 날짜입니다." +msgstr "" +"**마지막 업데이트** (선택 사항) 제안서가 마지막으로 크게 변경된 날짜입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:104 msgid "" -"**see-also** (Optional) A list of other proposals that are relevant to " -"this one." +"**see-also** (Optional) A list of other proposals that are relevant to this " +"one." msgstr "**함께 보기** (선택 사항) 이 제안과 관련된 다른 제안 목록입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:106 @@ -4048,7 +4100,8 @@ msgid "**replaces** (Optional) A list of proposals that this one replaces." msgstr "**대체** (선택 사항) 이 제안이 대체하는 제안 목록입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:108 -msgid "**superseded-by** (Optional) A list of proposals that this one supersedes." +msgid "" +"**superseded-by** (Optional) A list of proposals that this one supersedes." msgstr "**대체됨** (선택 사항) 이 제안이 대체하는 제안의 목록입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:111 @@ -4058,54 +4111,55 @@ msgstr "워크플로우" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:113 msgid "" "The idea forming the enhancement should already have been discussed or " -"pitched in the community. As such, it needs a champion, usually the " -"author, who shepherds the enhancement. This person also has to find " -"committers to Flower willing to review the proposal." +"pitched in the community. As such, it needs a champion, usually the author, " +"who shepherds the enhancement. This person also has to find committers to " +"Flower willing to review the proposal." msgstr "" -"개선 사항을 구성하는 아이디어는 이미 커뮤니티에서 논의되었거나 제안된 적이 " -"있어야 합니다. 따라서 개선 사항을 주도하는 사(보통 작성자)이 필요합니다. 이 " -"사람은 또한 제안을 검토할 의향이 있는 Flower 커미터를 찾아야 합니다." +"개선 사항을 구성하는 아이디어는 이미 커뮤니티에서 논의되었거나 제안된 적이 있" +"어야 합니다. 따라서 개선 사항을 주도하는 사(보통 작성자)이 필요합니다. 이 사" +"람은 또한 제안을 검토할 의향이 있는 Flower 커미터를 찾아야 합니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:115 msgid "" "New enhancements are checked in with a file name in the form of `NNNN-" -"YYYYMMDD-enhancement-title.md`, with `NNNN` being the Flower Enhancement " -"Doc number, to `enhancements`. All enhancements start in `provisional` " -"state as part of a pull request. Discussions are done as part of the pull" -" request review." +"YYYYMMDD-enhancement-title.md`, with `NNNN` being the Flower Enhancement Doc " +"number, to `enhancements`. All enhancements start in `provisional` state as " +"part of a pull request. Discussions are done as part of the pull request " +"review." msgstr "" -"새 개선 사항은 `NNNN-YYYYMMDD-enhancement-title.md` 형식의 파일 이름으로 " -"체크인되며, `NNNN`은 Flower 개선 문서 번호이고 `enhancements`에 해당합니다. " -"모든 개선 사항은 pull request의 일부로 `잠정` 상태에서 시작됩니다. 토론은 " -"pull request 검토의 일부로 이루어집니다." +"새 개선 사항은 `NNNN-YYYYMMDD-enhancement-title.md` 형식의 파일 이름으로 체크" +"인되며, `NNNN`은 Flower 개선 문서 번호이고 `enhancements`에 해당합니다. 모든 " +"개선 사항은 pull request의 일부로 `잠정` 상태에서 시작됩니다. 토론은 pull " +"request 검토의 일부로 이루어집니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:117 msgid "" -"Once an enhancement has been reviewed and approved, its status is changed" -" to `implementable`. The actual implementation is then done in separate " -"pull requests. These pull requests should mention the respective " -"enhancement as part of their description. After the implementation is " -"done, the proposal status is changed to `implemented`." +"Once an enhancement has been reviewed and approved, its status is changed to " +"`implementable`. The actual implementation is then done in separate pull " +"requests. These pull requests should mention the respective enhancement as " +"part of their description. After the implementation is done, the proposal " +"status is changed to `implemented`." msgstr "" -"개선 사항이 검토 및 승인되면 상태가 '구현 가능'으로 변경됩니다. 그런 다음 " -"실제 구현은 별도의 pull requests를 통해 이루어집니다. 이러한 pull requests는 " -"설명의 일부로 해당 개선 사항을 언급해야 합니다. 구현이 완료되면 제안 상태는 " -"'구현됨'으로 변경됩니다." +"개선 사항이 검토 및 승인되면 상태가 '구현 가능'으로 변경됩니다. 그런 다음 실" +"제 구현은 별도의 pull requests를 통해 이루어집니다. 이러한 pull requests는 설" +"명의 일부로 해당 개선 사항을 언급해야 합니다. 구현이 완료되면 제안 상태는 '구" +"현됨'으로 변경됩니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:119 msgid "" -"Under certain conditions, other states are possible. An Enhancement has " -"the following states:" -msgstr "특정 조건에서는 다른 상태도 가능합니다. 개선에는 다음과 같은 상태가 있습니다:" +"Under certain conditions, other states are possible. An Enhancement has the " +"following states:" +msgstr "" +"특정 조건에서는 다른 상태도 가능합니다. 개선에는 다음과 같은 상태가 있습니다:" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:121 msgid "" "`provisional`: The enhancement has been proposed and is actively being " -"defined. This is the starting state while the proposal is being fleshed " -"out and actively defined and discussed." +"defined. This is the starting state while the proposal is being fleshed out " +"and actively defined and discussed." msgstr "" -"'잠정적': 개선 사항이 제안되어 활발히 정의되고 있습니다. 제안이 구체화되고 " -"활발하게 정의 및 논의되는 동안의 시작 단계입니다." +"'잠정적': 개선 사항이 제안되어 활발히 정의되고 있습니다. 제안이 구체화되고 활" +"발하게 정의 및 논의되는 동안의 시작 단계입니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:122 msgid "`implementable`: The enhancement has been reviewed and approved." @@ -4118,15 +4172,17 @@ msgid "" msgstr "`구현됨`: 개선 사항이 구현되었으며 더 이상 활발히 변경되지 않습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:124 -msgid "`deferred`: The enhancement is proposed but not actively being worked on." +msgid "" +"`deferred`: The enhancement is proposed but not actively being worked on." msgstr "'지연됨': 개선 사항이 제안되었지만 아직 활발히 작업 중이 아닙니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:125 msgid "" -"`rejected`: The authors and reviewers have decided that this enhancement " -"is not moving forward." -msgstr "`거부됨`: 작성자와 검토자는 이 개선 사항을 더 이상 진행하지 않기로 " -"결정했습니다." +"`rejected`: The authors and reviewers have decided that this enhancement is " +"not moving forward." +msgstr "" +"`거부됨`: 작성자와 검토자는 이 개선 사항을 더 이상 진행하지 않기로 결정했습니" +"다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:126 msgid "`withdrawn`: The authors have withdrawn the enhancement." @@ -4138,21 +4194,21 @@ msgstr "'대체됨': 개선 사항이 새로운 개선 사항으로 대체되었 #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:131 msgid "" -"Adding an additional process to the ones already provided by GitHub " -"(Issues and Pull Requests) adds more complexity and can be a barrier for " -"potential first-time contributors." +"Adding an additional process to the ones already provided by GitHub (Issues " +"and Pull Requests) adds more complexity and can be a barrier for potential " +"first-time contributors." msgstr "" -"GitHub에서 이미 제공하는 프로세스(이슈 및 Pull Requests)에 추가 프로세스를 " -"추가하면 더 복잡해지고 잠재적인 처음인 기여자에게는 장벽이 될 수 있습니다." +"GitHub에서 이미 제공하는 프로세스(이슈 및 Pull Requests)에 추가 프로세스를 추" +"가하면 더 복잡해지고 잠재적인 처음인 기여자에게는 장벽이 될 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:133 msgid "" "Expanding the proposal template beyond the single-sentence description " -"currently required in the features issue template may be a heavy burden " -"for non-native English speakers." +"currently required in the features issue template may be a heavy burden for " +"non-native English speakers." msgstr "" -"현재 기능 이슈 템플릿에서 요구되는 한 문장 설명 이상으로 제안서 템플릿을 " -"확장하는 것은 영어가 모국어가 아닌 사용자에게는 큰 부담이 될 수 있습니다." +"현재 기능 이슈 템플릿에서 요구되는 한 문장 설명 이상으로 제안서 템플릿을 확장" +"하는 것은 영어가 모국어가 아닌 사용자에게는 큰 부담이 될 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:137 msgid "GitHub Issues" @@ -4161,19 +4217,19 @@ msgstr "GitHub 이슈" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:139 msgid "" "Using GitHub Issues for these kinds of enhancements is doable. One could " -"use, for example, tags, to differentiate and filter them from other " -"issues. The main issue is in discussing and reviewing an enhancement: " -"GitHub issues only have a single thread for comments. Enhancements " -"usually have multiple threads of discussion at the same time for various " -"parts of the doc. Managing these multiple discussions can be confusing " -"when using GitHub Issues." -msgstr "" -"이러한 종류의 개선을 위해 GitHub 이슈를 사용하면 가능합니다. 예를 들어 " -"태그를 사용하여 다른 이슈와 구별하고 필터링할 수 있습니다. 주요 이슈는 개선 " -"사항에 대해 토론하고 검토하는 것입니다: GitHub 이슈에는 댓글 스레드가 하나만 " -"있습니다. 개선 사항에는 일반적으로 문서의 여러 부분에 대해 동시에 여러 개의 " -"토론 스레드가 있습니다. GitHub 이슈를 사용할 때 이러한 여러 토론을 관리하면 " -"혼란스러울 수 있습니다." +"use, for example, tags, to differentiate and filter them from other issues. " +"The main issue is in discussing and reviewing an enhancement: GitHub issues " +"only have a single thread for comments. Enhancements usually have multiple " +"threads of discussion at the same time for various parts of the doc. " +"Managing these multiple discussions can be confusing when using GitHub " +"Issues." +msgstr "" +"이러한 종류의 개선을 위해 GitHub 이슈를 사용하면 가능합니다. 예를 들어 태그" +"를 사용하여 다른 이슈와 구별하고 필터링할 수 있습니다. 주요 이슈는 개선 사항" +"에 대해 토론하고 검토하는 것입니다: GitHub 이슈에는 댓글 스레드가 하나만 있습" +"니다. 개선 사항에는 일반적으로 문서의 여러 부분에 대해 동시에 여러 개의 토론 " +"스레드가 있습니다. GitHub 이슈를 사용할 때 이러한 여러 토론을 관리하면 혼란스" +"러울 수 있습니다." #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:141 msgid "Google Docs" @@ -4181,17 +4237,16 @@ msgstr "Google 문서 도구" #: ../../source/fed/0001-20220311-flower-enhancement-doc.md:143 msgid "" -"Google Docs allow for multiple threads of discussions. But as Google Docs" -" are hosted outside the project, their discoverability by the community " -"needs to be taken care of. A list of links to all proposals has to be " -"managed and made available for the community. Compared to shipping " -"proposals as part of Flower's repository, the potential for missing links" -" is much higher." +"Google Docs allow for multiple threads of discussions. But as Google Docs " +"are hosted outside the project, their discoverability by the community needs " +"to be taken care of. A list of links to all proposals has to be managed and " +"made available for the community. Compared to shipping proposals as part of " +"Flower's repository, the potential for missing links is much higher." msgstr "" "Google 문서는 여러 스레드의 토론을 허용합니다. 하지만 Google 문서는 프로젝트 " -"외부에서 호스팅되므로 커뮤니티에서 검색할 수 있도록 관리해야 합니다. 모든 " -"제안에 대한 링크 목록을 관리하고 커뮤니티에 제공해야 합니다. Flower 저장소의 " -"일부로 제안서를 보낼 때와 비교하면 링크가 누락될 가능성이 훨씬 더 높습니다." +"외부에서 호스팅되므로 커뮤니티에서 검색할 수 있도록 관리해야 합니다. 모든 제" +"안에 대한 링크 목록을 관리하고 커뮤니티에 제공해야 합니다. Flower 저장소의 일" +"부로 제안서를 보낼 때와 비교하면 링크가 누락될 가능성이 훨씬 더 높습니다." #: ../../source/fed/index.md:1 msgid "FED - Flower Enhancement Doc" @@ -4203,9 +4258,10 @@ msgstr "종합 평가 결과" #: ../../source/how-to-aggregate-evaluation-results.rst:4 msgid "" -"The Flower server does not prescribe a way to aggregate evaluation " -"results, but it enables the user to fully customize result aggregation." -msgstr "Flower 서버는 평가 결과를 집계하는 방법을 규정하고 있지 않지만 사용자가 결과 " +"The Flower server does not prescribe a way to aggregate evaluation results, " +"but it enables the user to fully customize result aggregation." +msgstr "" +"Flower 서버는 평가 결과를 집계하는 방법을 규정하고 있지 않지만 사용자가 결과 " "집계를 완전히 사용자 지정할 수 있습니다." #: ../../source/how-to-aggregate-evaluation-results.rst:8 @@ -4214,20 +4270,21 @@ msgstr "사용자 지정 평가 결과 집계" #: ../../source/how-to-aggregate-evaluation-results.rst:10 msgid "" -"The same :code:`Strategy`-customization approach can be used to aggregate" -" custom evaluation results coming from individual clients. Clients can " -"return custom metrics to the server by returning a dictionary:" +"The same :code:`Strategy`-customization approach can be used to aggregate " +"custom evaluation results coming from individual clients. Clients can return " +"custom metrics to the server by returning a dictionary:" msgstr "" -"동일한 :code:`Strategy`-사용자 지정 방식을 사용하여 개별 클라이언트로부터 " -"오는 사용자 지정 평가 결과를 집계할 수 있습니다. 클라이언트는 dictionary를 " -"반환하여 사용자 지정 지표를 서버에 반환할 수 있습니다:" +"동일한 :code:`Strategy`-사용자 지정 방식을 사용하여 개별 클라이언트로부터 오" +"는 사용자 지정 평가 결과를 집계할 수 있습니다. 클라이언트는 dictionary를 반환" +"하여 사용자 지정 지표를 서버에 반환할 수 있습니다:" #: ../../source/how-to-aggregate-evaluation-results.rst:36 msgid "" "The server can then use a customized strategy to aggregate the metrics " "provided in these dictionaries:" -msgstr "그런 다음 서버는 사용자 지정 전략을 사용하여 이러한 dictionaries에서 " -"제공하는 메트릭을 집계할 수 있습니다:" +msgstr "" +"그런 다음 서버는 사용자 지정 전략을 사용하여 이러한 dictionaries에서 제공하" +"는 메트릭을 집계할 수 있습니다:" #: ../../source/how-to-authenticate-supernodes.rst:2 msgid "Authenticate SuperNodes" @@ -4235,13 +4292,12 @@ msgstr "SuperNodes 인증하기" #: ../../source/how-to-authenticate-supernodes.rst:4 msgid "" -"Flower has built-in support for authenticated SuperNodes that you can use" -" to verify the identities of each SuperNode connecting to a SuperLink. " -"Flower node authentication works similar to how GitHub SSH authentication" -" works:" +"Flower has built-in support for authenticated SuperNodes that you can use to " +"verify the identities of each SuperNode connecting to a SuperLink. Flower " +"node authentication works similar to how GitHub SSH authentication works:" msgstr "" -"Flower는 SuperLink에 연결하는 각 SuperNodes의 신원을 확인하는 데 사용할 수 " -"있는 인증된 SuperNodes에 대한 기본 지원을 제공합니다. Flower 노드 인증은 " +"Flower는 SuperLink에 연결하는 각 SuperNodes의 신원을 확인하는 데 사용할 수 있" +"는 인증된 SuperNodes에 대한 기본 지원을 제공합니다. Flower 노드 인증은 " "GitHub SSH 인증 방식과 유사하게 작동합니다:" #: ../../source/how-to-authenticate-supernodes.rst:7 @@ -4250,16 +4306,17 @@ msgstr "SuperLink(서버)는 알려진 (클라이언트) 노드 공개키 목록 #: ../../source/how-to-authenticate-supernodes.rst:8 msgid "" -"Using ECDH, both SuperNode and SuperLink independently derive a shared " -"secret" -msgstr "SuperNode와 SuperLink는 ECDH를 사용하여 독립적으로 공유된 비밀을 도출합니다" +"Using ECDH, both SuperNode and SuperLink independently derive a shared secret" +msgstr "" +"SuperNode와 SuperLink는 ECDH를 사용하여 독립적으로 공유된 비밀을 도출합니다" #: ../../source/how-to-authenticate-supernodes.rst:9 msgid "" "Shared secret is used to compute the HMAC value of the message sent from " "SuperNode to SuperLink as a token" -msgstr "비밀 공유는 SuperNode에서 SuperLink로 토큰으로 전송된 메시지의 HMAC 값을 " -"계산하는 데 사용됩니다" +msgstr "" +"비밀 공유는 SuperNode에서 SuperLink로 토큰으로 전송된 메시지의 HMAC 값을 계산" +"하는 데 사용됩니다" #: ../../source/how-to-authenticate-supernodes.rst:10 msgid "SuperLink verifies the token" @@ -4267,27 +4324,28 @@ msgstr "SuperLink가 토큰을 확인합니다" #: ../../source/how-to-authenticate-supernodes.rst:12 msgid "" -"We recommend you to check out the complete `code example " -"`_ demonstrating federated learning with Flower in an " -"authenticated setting." +"We recommend you to check out the complete `code example `_ demonstrating " +"federated learning with Flower in an authenticated setting." msgstr "" -"인증된 환경에서 Flower로 federated 학습을 시연하는 전체 '코드 예제 " -"`" -"_를 확인하는 것이 좋습니다." +"인증된 환경에서 Flower로 연합 학습을 시연하는 전체 '코드 예제 `_를 확인하는 것이 " +"좋습니다." #: ../../source/how-to-authenticate-supernodes.rst:15 msgid "" -"This guide covers a preview feature that might change in future versions " -"of Flower." -msgstr "이 가이드에서는 향후 버전의 Flower에서 변경될 수 있는 미리보기 기능에 대해 " -"설명합니다." +"This guide covers a preview feature that might change in future versions of " +"Flower." +msgstr "" +"이 가이드에서는 향후 버전의 Flower에서 변경될 수 있는 미리보기 기능에 대해 설" +"명합니다." #: ../../source/how-to-authenticate-supernodes.rst:18 msgid "" -"For increased security, node authentication can only be used when " -"encrypted connections (SSL/TLS) are enabled." -msgstr "보안을 강화하기 위해 노드 인증은 암호화된 연결(SSL/TLS)을 사용하도록 설정한 " +"For increased security, node authentication can only be used when encrypted " +"connections (SSL/TLS) are enabled." +msgstr "" +"보안을 강화하기 위해 노드 인증은 암호화된 연결(SSL/TLS)을 사용하도록 설정한 " "경우에만 사용할 수 있습니다." #: ../../source/how-to-authenticate-supernodes.rst:21 @@ -4297,20 +4355,19 @@ msgstr ":code:`SuperLink`에서 노드 인증 활성화" #: ../../source/how-to-authenticate-supernodes.rst:23 msgid "" "To enable node authentication, first you need to configure SSL/TLS " -"connections to secure the SuperLink<>SuperNode communication. You can " -"find the complete guide `here `_. After configuring secure connections, you" -" can enable client authentication in a long-running Flower " -":code:`SuperLink`. Use the following terminal command to start a Flower " -":code:`SuperNode` that has both secure connections and node " -"authentication enabled:" +"connections to secure the SuperLink<>SuperNode communication. You can find " +"the complete guide `here `_. After configuring secure connections, you can enable " +"client authentication in a long-running Flower :code:`SuperLink`. Use the " +"following terminal command to start a Flower :code:`SuperNode` that has both " +"secure connections and node authentication enabled:" msgstr "" "노드 인증을 활성화하려면 먼저 SuperLink<>SuperNode 통신을 보호하기 위해 SSL/" "TLS 연결을 구성해야 합니다. 전체 가이드는 `여기 `_에서 확인할 수 있습니다. 보안 " -"연결을 구성한 후, 장기 실행하는 Flower :code:`SuperLink`에서 클라이언트 " -"인증을 활성화할 수 있습니다. 다음 터미널 명령을 사용하여 보안 연결과 노드 " -"인증이 모두 활성화된 Flower :code:`SuperNode`를 시작하세요:" +"연결을 구성한 후, 장기 실행하는 Flower :code:`SuperLink`에서 클라이언트 인증" +"을 활성화할 수 있습니다. 다음 터미널 명령을 사용하여 보안 연결과 노드 인증이 " +"모두 활성화된 Flower :code:`SuperNode`를 시작하세요:" #: ../../source/how-to-authenticate-supernodes.rst:38 msgid "Let's break down the authentication flags:" @@ -4318,51 +4375,51 @@ msgstr "인증 플래그를 세분화해 보겠습니다:" #: ../../source/how-to-authenticate-supernodes.rst:40 msgid "" -"The first flag :code:`--auth-list-public-keys` expects a path to a CSV " -"file storing all known node public keys. You need to store all known node" -" public keys that are allowed to participate in a federation in one CSV " -"file (:code:`.csv`)." +"The first flag :code:`--auth-list-public-keys` expects a path to a CSV file " +"storing all known node public keys. You need to store all known node public " +"keys that are allowed to participate in a federation in one CSV file (:code:" +"`.csv`)." msgstr "" -"첫 번째 플래그 :code:`--auth-list-public-keys`는 알려진 모든 노드 공개키를 " -"저장하는 CSV 파일의 경로를 기대합니다. federation에 참여하도록 허용된 모든 " -"알려진 노드 공개 키를 하나의 CSV 파일(:code:`.csv`)에 저장해야 합니다." +"첫 번째 플래그 :code:`--auth-list-public-keys`는 알려진 모든 노드 공개키를 저" +"장하는 CSV 파일의 경로를 기대합니다. federation에 참여하도록 허용된 모든 알려" +"진 노드 공개 키를 하나의 CSV 파일(:code:`.csv`)에 저장해야 합니다." #: ../../source/how-to-authenticate-supernodes.rst:42 msgid "" "A valid CSV file storing known node public keys should list the keys in " "OpenSSH format, separated by commas and without any comments. For an " -"example, refer to our code sample, which contains a CSV file with two " -"known node public keys." +"example, refer to our code sample, which contains a CSV file with two known " +"node public keys." msgstr "" "알려진 노드 공개키를 저장하는 유효한 CSV 파일은 쉼표로 구분하고 주석 없이 " -"OpenSSH 형식으로 키를 나열해야 합니다. 예를 들어, 두 개의 알려진 노드 " -"공개키가 포함된 CSV 파일이 포함된 코드 샘플을 참조하세요." +"OpenSSH 형식으로 키를 나열해야 합니다. 예를 들어, 두 개의 알려진 노드 공개키" +"가 포함된 CSV 파일이 포함된 코드 샘플을 참조하세요." #: ../../source/how-to-authenticate-supernodes.rst:44 msgid "" -"The second and third flags :code:`--auth-superlink-private-key` and :code" -":`--auth-superlink-public-key` expect paths to the server's private and " -"public keys. For development purposes, you can generate a private and " -"public key pair using :code:`ssh-keygen -t ecdsa -b 384`." +"The second and third flags :code:`--auth-superlink-private-key` and :code:`--" +"auth-superlink-public-key` expect paths to the server's private and public " +"keys. For development purposes, you can generate a private and public key " +"pair using :code:`ssh-keygen -t ecdsa -b 384`." msgstr "" -"두 번째 및 세 번째 플래그 :code:`--auth-superlink-private-key` 및 :code" -":`--auth-superlink-public-key`는 서버의 개인 및 공개 키의 경로를 예상합니다. " -"개발 목적으로 :code:`ssh-keygen -t ecdsa -b 384`를 사용하여 개인 및 공개 키 " -"쌍을 생성할 수 있습니다." +"두 번째 및 세 번째 플래그 :code:`--auth-superlink-private-key` 및 :code:`--" +"auth-superlink-public-key`는 서버의 개인 및 공개 키의 경로를 예상합니다. 개" +"발 목적으로 :code:`ssh-keygen -t ecdsa -b 384`를 사용하여 개인 및 공개 키 쌍" +"을 생성할 수 있습니다." #: ../../source/how-to-authenticate-supernodes.rst:47 msgid "" "In Flower 1.9, there is no support for dynamically removing, editing, or " -"adding known node public keys to the SuperLink. To change the set of " -"known nodes, you need to shut the server down, edit the CSV file, and " -"start the server again. Support for dynamically changing the set of known" -" nodes is on the roadmap to be released in Flower 1.10 (ETA: June)." +"adding known node public keys to the SuperLink. To change the set of known " +"nodes, you need to shut the server down, edit the CSV file, and start the " +"server again. Support for dynamically changing the set of known nodes is on " +"the roadmap to be released in Flower 1.10 (ETA: June)." msgstr "" -"Flower 1.9에서는 알려진 노드 공개키를 SuperLink에 동적으로 제거, 편집 또는 " -"추가하는 기능이 지원되지 않습니다. 알려진 노드 집합을 변경하려면 서버를 " -"종료하고 CSV 파일을 편집한 다음 서버를 다시 시작해야 합니다. 알려진 노드 " -"집합을 동적으로 변경하는 기능은 Flower 1.10(출시 예정일: 6월)에서 로드맵에 " -"포함되어 있습니다." +"Flower 1.9에서는 알려진 노드 공개키를 SuperLink에 동적으로 제거, 편집 또는 추" +"가하는 기능이 지원되지 않습니다. 알려진 노드 집합을 변경하려면 서버를 종료하" +"고 CSV 파일을 편집한 다음 서버를 다시 시작해야 합니다. 알려진 노드 집합을 동" +"적으로 변경하는 기능은 Flower 1.10(출시 예정일: 6월)에서 로드맵에 포함되어 있" +"습니다." #: ../../source/how-to-authenticate-supernodes.rst:53 msgid "Enable node authentication in :code:`SuperNode`" @@ -4371,26 +4428,26 @@ msgstr ":code:`SuperNode`에서 노드 인증을 활성화합니다" #: ../../source/how-to-authenticate-supernodes.rst:55 msgid "" "Similar to the long-running Flower server (:code:`SuperLink`), you can " -"easily enable node authentication in the long-running Flower client " -"(:code:`SuperNode`). Use the following terminal command to start an " -"authenticated :code:`SuperNode`:" +"easily enable node authentication in the long-running Flower client (:code:" +"`SuperNode`). Use the following terminal command to start an authenticated :" +"code:`SuperNode`:" msgstr "" "장기 실행 중인 Flower 서버(:code:`SuperLink`)와 마찬가지로, 장기 실행 중인 " -"Flower 클라이언트(:code:`SuperNode`)에서도 노드 인증을 쉽게 활성화할 수 " -"있습니다. 다음 터미널 명령을 사용하여 인증된 :code:`SuperNode`를 시작하세요:" +"Flower 클라이언트(:code:`SuperNode`)에서도 노드 인증을 쉽게 활성화할 수 있습" +"니다. 다음 터미널 명령을 사용하여 인증된 :code:`SuperNode`를 시작하세요:" #: ../../source/how-to-authenticate-supernodes.rst:66 msgid "" -"The :code:`--auth-supernode-private-key` flag expects a path to the " -"node's private key file and the :code:`--auth-supernode-public-key` flag " -"expects a path to the node's public key file. For development purposes, " -"you can generate a private and public key pair using :code:`ssh-keygen -t" -" ecdsa -b 384`." +"The :code:`--auth-supernode-private-key` flag expects a path to the node's " +"private key file and the :code:`--auth-supernode-public-key` flag expects a " +"path to the node's public key file. For development purposes, you can " +"generate a private and public key pair using :code:`ssh-keygen -t ecdsa -b " +"384`." msgstr "" -":code:`--auth-supernode-private-key` 플래그는 노드의 개인 키 파일 경로를, " -":code:`--auth-supernode-public-key` 플래그는 노드의 공개 키 파일 경로를 " -"예상합니다. 개발 목적으로 :code:`ssh-keygen -t ecdsa -b 384`를 사용하여 개인 " -"및 공개 키 쌍을 생성할 수 있습니다." +":code:`--auth-supernode-private-key` 플래그는 노드의 개인 키 파일 경로를, :" +"code:`--auth-supernode-public-key` 플래그는 노드의 공개 키 파일 경로를 예상합" +"니다. 개발 목적으로 :code:`ssh-keygen -t ecdsa -b 384`를 사용하여 개인 및 공" +"개 키 쌍을 생성할 수 있습니다." #: ../../source/how-to-authenticate-supernodes.rst:70 msgid "Security notice" @@ -4398,19 +4455,18 @@ msgstr "보안 공지" #: ../../source/how-to-authenticate-supernodes.rst:72 msgid "" -"The system's security relies on the credentials of the SuperLink and each" -" SuperNode. Therefore, it is imperative to safeguard and safely store the" -" credentials to avoid security risks such as Public Key Infrastructure " -"(PKI) impersonation attacks. The node authentication mechanism also " -"involves human interaction, so please ensure that all of the " -"communication is done in a secure manner, using trusted communication " -"methods." +"The system's security relies on the credentials of the SuperLink and each " +"SuperNode. Therefore, it is imperative to safeguard and safely store the " +"credentials to avoid security risks such as Public Key Infrastructure (PKI) " +"impersonation attacks. The node authentication mechanism also involves human " +"interaction, so please ensure that all of the communication is done in a " +"secure manner, using trusted communication methods." msgstr "" -"시스템의 보안은 SuperLink와 각SuperNode의 자격 증명에 의존합니다. 따라서 " -"공개키 기반구조(PKI) 사칭 공격과 같은 보안 위험을 피하기 위해 자격 증명을 " -"보호하고 안전하게 보관하는 것이 필수적입니다. 노드 인증 메커니즘에는 사람의 " -"상호 작용도 포함되므로 모든 통신이 신뢰할 수 있는 통신 방법을 사용하여 " -"안전한 방식으로 이루어지도록 하세요." +"시스템의 보안은 SuperLink와 각SuperNode의 자격 증명에 의존합니다. 따라서 공개" +"키 기반구조(PKI) 사칭 공격과 같은 보안 위험을 피하기 위해 자격 증명을 보호하" +"고 안전하게 보관하는 것이 필수적입니다. 노드 인증 메커니즘에는 사람의 상호 작" +"용도 포함되므로 모든 통신이 신뢰할 수 있는 통신 방법을 사용하여 안전한 방식으" +"로 이루어지도록 하세요." #: ../../source/how-to-authenticate-supernodes.rst:77 #: ../../source/how-to-enable-ssl-connections.rst:68 @@ -4421,15 +4477,15 @@ msgstr "결론" #: ../../source/how-to-authenticate-supernodes.rst:79 msgid "" -"You should now have learned how to start a long-running Flower server " -"(:code:`SuperLink`) and client (:code:`SuperNode`) with node " -"authentication enabled. You should also know the significance of the " -"private key and store it safely to minimize security risks." +"You should now have learned how to start a long-running Flower server (:code:" +"`SuperLink`) and client (:code:`SuperNode`) with node authentication " +"enabled. You should also know the significance of the private key and store " +"it safely to minimize security risks." msgstr "" -"이제 노드 인증이 활성화된 상태에서 장기간 실행되는 Flower " -"서버(:code:`SuperLink`)와 클라이언트(:code:`SuperNode`)를 시작하는 방법을 " -"배웠을 것입니다. 또한 보안 위험을 최소화하기 위해 개인키의 중요성을 알고 " -"안전하게 보관해야 합니다." +"이제 노드 인증이 활성화된 상태에서 장기간 실행되는 Flower 서버(:code:" +"`SuperLink`)와 클라이언트(:code:`SuperNode`)를 시작하는 방법을 배웠을 것입니" +"다. 또한 보안 위험을 최소화하기 위해 개인키의 중요성을 알고 안전하게 보관해" +"야 합니다." #: ../../source/how-to-configure-clients.rst:2 msgid "Configure clients" @@ -4438,13 +4494,13 @@ msgstr "클라이언트 구성" #: ../../source/how-to-configure-clients.rst:4 msgid "" "Along with model parameters, Flower can send configuration values to " -"clients. Configuration values can be used for various purposes. They are," -" for example, a popular way to control client-side hyperparameters from " -"the server." +"clients. Configuration values can be used for various purposes. They are, " +"for example, a popular way to control client-side hyperparameters from the " +"server." msgstr "" -"모델 파라미터와 함께 Flower는 설정 값을 클라이언트에 전송할 수 있습니다. " -"구성 값은 다양한 용도로 사용할 수 있습니다. 예를 들어 서버에서 클라이언트 측 " -"하이퍼파라미터를 제어하는 데 널리 사용되는 방법입니다." +"모델 파라미터와 함께 Flower는 설정 값을 클라이언트에 전송할 수 있습니다. 구" +"성 값은 다양한 용도로 사용할 수 있습니다. 예를 들어 서버에서 클라이언트 측 하" +"이퍼파라미터를 제어하는 데 널리 사용되는 방법입니다." #: ../../source/how-to-configure-clients.rst:7 msgid "Configuration values" @@ -4452,48 +4508,48 @@ msgstr "구성 값" #: ../../source/how-to-configure-clients.rst:9 msgid "" -"Configuration values are represented as a dictionary with ``str`` keys " -"and values of type ``bool``, ``bytes``, ``double`` (64-bit precision " -"float), ``int``, or ``str`` (or equivalent types in different languages)." -" Here is an example of a configuration dictionary in Python:" +"Configuration values are represented as a dictionary with ``str`` keys and " +"values of type ``bool``, ``bytes``, ``double`` (64-bit precision float), " +"``int``, or ``str`` (or equivalent types in different languages). Here is an " +"example of a configuration dictionary in Python:" msgstr "" "구성 값은 ``str`` 키와 ``bool``, ``bytes``, ``double``(64비트 정밀도 정수), " -"``int`` 또는 ``str``(또는 다른 언어의 동등한 유형) 유형의 값으로 구성된 " -"사전으로 표현됩니다. 다음은 Python의 구성 사전 예제입니다:" +"``int`` 또는 ``str``(또는 다른 언어의 동등한 유형) 유형의 값으로 구성된 사전" +"으로 표현됩니다. 다음은 Python의 구성 사전 예제입니다:" #: ../../source/how-to-configure-clients.rst:20 msgid "" "Flower serializes these configuration dictionaries (or *config dict* for " -"short) to their ProtoBuf representation, transports them to the client " -"using gRPC, and then deserializes them back to Python dictionaries." +"short) to their ProtoBuf representation, transports them to the client using " +"gRPC, and then deserializes them back to Python dictionaries." msgstr "" -"Flower는 이러한 구성 dictionaries(또는 줄여서 *config dict*)를 ProtoBuf " -"표현으로 직렬화하고, gRPC를 사용하여 클라이언트로 전송한 다음 다시 Python " +"Flower는 이러한 구성 dictionaries(또는 줄여서 *config dict*)를 ProtoBuf 표현" +"으로 직렬화하고, gRPC를 사용하여 클라이언트로 전송한 다음 다시 Python " "dictionaries로 역직렬화합니다." #: ../../source/how-to-configure-clients.rst:24 msgid "" -"Currently, there is no support for directly sending collection types " -"(e.g., ``Set``, ``List``, ``Map``) as values in configuration " -"dictionaries. There are several workarounds to send collections as values" -" by converting them to one of the supported value types (and converting " -"them back on the client-side)." +"Currently, there is no support for directly sending collection types (e.g., " +"``Set``, ``List``, ``Map``) as values in configuration dictionaries. There " +"are several workarounds to send collections as values by converting them to " +"one of the supported value types (and converting them back on the client-" +"side)." msgstr "" "현재 구성 사전에서 컬렉션 유형(예: ``Set``, ``List``, ``Map``)을 값으로 직접 " -"전송하는 기능은 지원되지 않습니다. 컬렉션을 지원되는 값 유형 중 하나로 " -"변환한 다음 클라이언트 측에서 다시 변환하여 값으로 보내는 몇 가지 해결 " -"방법이 있습니다." +"전송하는 기능은 지원되지 않습니다. 컬렉션을 지원되는 값 유형 중 하나로 변환" +"한 다음 클라이언트 측에서 다시 변환하여 값으로 보내는 몇 가지 해결 방법이 있" +"습니다." #: ../../source/how-to-configure-clients.rst:26 msgid "" "One can, for example, convert a list of floating-point numbers to a JSON " -"string, then send the JSON string using the configuration dictionary, and" -" then convert the JSON string back to a list of floating-point numbers on" -" the client." +"string, then send the JSON string using the configuration dictionary, and " +"then convert the JSON string back to a list of floating-point numbers on the " +"client." msgstr "" -"예를 들어 부동 소수점 숫자 목록을 JSON 문자열로 변환한 다음 구성 " -"dictionary을 사용하여 JSON 문자열을 전송한 다음 클라이언트에서 다시 부동 " -"소수점 숫자 목록으로 변환할 수 있습니다." +"예를 들어 부동 소수점 숫자 목록을 JSON 문자열로 변환한 다음 구성 dictionary" +"을 사용하여 JSON 문자열을 전송한 다음 클라이언트에서 다시 부동 소수점 숫자 목" +"록으로 변환할 수 있습니다." #: ../../source/how-to-configure-clients.rst:30 msgid "Configuration through built-in strategies" @@ -4501,68 +4557,68 @@ msgstr "기본 제공 전략을 통한 구성" #: ../../source/how-to-configure-clients.rst:32 msgid "" -"The easiest way to send configuration values to clients is to use a " -"built-in strategy like :code:`FedAvg`. Built-in strategies support so-" -"called configuration functions. A configuration function is a function " -"that the built-in strategy calls to get the configuration dictionary for " -"the current round. It then forwards the configuration dictionary to all " -"the clients selected during that round." +"The easiest way to send configuration values to clients is to use a built-in " +"strategy like :code:`FedAvg`. Built-in strategies support so-called " +"configuration functions. A configuration function is a function that the " +"built-in strategy calls to get the configuration dictionary for the current " +"round. It then forwards the configuration dictionary to all the clients " +"selected during that round." msgstr "" -"클라이언트에 구성 값을 보내는 가장 쉬운 방법은 :code:`FedAvg`와 같은 기본 " -"제공 전략을 사용하는 것입니다. 기본 제공 전략은 소위 구성 함수를 지원합니다. " -"구성 함수는 내장 전략이 현재 단계의 구성 사전을 가져오기 위해 호출하는 " -"함수입니다. 그런 다음 해당 단계 동안 선택된 모든 클라이언트에 구성 사전을 " -"전달합니다." +"클라이언트에 구성 값을 보내는 가장 쉬운 방법은 :code:`FedAvg`와 같은 기본 제" +"공 전략을 사용하는 것입니다. 기본 제공 전략은 소위 구성 함수를 지원합니다. 구" +"성 함수는 내장 전략이 현재 단계의 구성 사전을 가져오기 위해 호출하는 함수입니" +"다. 그런 다음 해당 단계 동안 선택된 모든 클라이언트에 구성 사전을 전달합니다." #: ../../source/how-to-configure-clients.rst:34 msgid "" "Let's start with a simple example. Imagine we want to send (a) the batch " -"size that the client should use, (b) the current global round of " -"federated learning, and (c) the number of epochs to train on the client-" -"side. Our configuration function could look like this:" +"size that the client should use, (b) the current global round of federated " +"learning, and (c) the number of epochs to train on the client-side. Our " +"configuration function could look like this:" msgstr "" "간단한 예부터 시작하겠습니다. (a) 클라이언트가 사용해야 하는 배치 크기, (b) " -"현재 글로벌 연합 federated 라운드, (c) 클라이언트 측에서 학습할 에포크 수를 " -"전송하고 싶다고 가정해 보겠습니다. 구성 함수는 다음과 같습니다:" +"현재 글로벌 연합 라운드, (c) 클라이언트 측에서 학습할 에포크 수를 전송하고 " +"싶다고 가정해 보겠습니다. 구성 함수는 다음과 같습니다:" #: ../../source/how-to-configure-clients.rst:47 msgid "" "To make the built-in strategies use this function, we can pass it to " -"``FedAvg`` during initialization using the parameter " -":code:`on_fit_config_fn`:" +"``FedAvg`` during initialization using the parameter :code:" +"`on_fit_config_fn`:" msgstr "" -"기본 제공 전략이 이 함수를 사용하도록 하려면 초기화 중에 매개 변수 " -":code:`on_fit_config_fn`을 사용하여 ``FedAvg``에 이 함수를 전달하면 됩니다:" +"기본 제공 전략이 이 함수를 사용하도록 하려면 초기화 중에 매개 변수 :code:" +"`on_fit_config_fn`을 사용하여 ``FedAvg``에 이 함수를 전달하면 됩니다:" #: ../../source/how-to-configure-clients.rst:56 -msgid "One the client side, we receive the configuration dictionary in ``fit``:" +msgid "" +"One the client side, we receive the configuration dictionary in ``fit``:" msgstr "클라이언트 측에서는 ``fit``으로 구성 dictionary을 받습니다:" #: ../../source/how-to-configure-clients.rst:67 msgid "" "There is also an `on_evaluate_config_fn` to configure evaluation, which " -"works the same way. They are separate functions because one might want to" -" send different configuration values to `evaluate` (for example, to use a" -" different batch size)." +"works the same way. They are separate functions because one might want to " +"send different configuration values to `evaluate` (for example, to use a " +"different batch size)." msgstr "" "평가를 구성하는 `on_evaluate_config_fn`도 있으며, 같은 방식으로 작동합니다. " -"다른 배치 크기를 사용하기 위해 다른 구성 값을 `evaluate`로 보내려고 할 수 " -"있기 때문에 이 함수는 별도의 함수입니다." +"다른 배치 크기를 사용하기 위해 다른 구성 값을 `evaluate`로 보내려고 할 수 있" +"기 때문에 이 함수는 별도의 함수입니다." #: ../../source/how-to-configure-clients.rst:69 msgid "" -"The built-in strategies call this function every round (that is, every " -"time `Strategy.configure_fit` or `Strategy.configure_evaluate` runs). " -"Calling `on_evaluate_config_fn` every round allows us to vary/change the " -"config dict over consecutive rounds. If we wanted to implement a " -"hyperparameter schedule, for example, to increase the number of local " -"epochs during later rounds, we could do the following:" +"The built-in strategies call this function every round (that is, every time " +"`Strategy.configure_fit` or `Strategy.configure_evaluate` runs). Calling " +"`on_evaluate_config_fn` every round allows us to vary/change the config dict " +"over consecutive rounds. If we wanted to implement a hyperparameter " +"schedule, for example, to increase the number of local epochs during later " +"rounds, we could do the following:" msgstr "" "기본 제공 전략은 매 라운드마다 이 함수를 호출합니다(즉, `Strategy." -"configure_fit` 또는 `Strategy.configure_evaluate`가 실행될 때마다). 매 " -"라운드마다 `on_evaluate_config_fn`을 호출하면 연속된 라운드에서 config " -"dict를 변경/변경할 수 있습니다. 예를 들어 이후 라운드에서 로컬 에포크 수를 " -"늘리기 위해 하이퍼파라미터 일정을 구현하려면 다음과 같이 할 수 있습니다:" +"configure_fit` 또는 `Strategy.configure_evaluate`가 실행될 때마다). 매 라운드" +"마다 `on_evaluate_config_fn`을 호출하면 연속된 라운드에서 config dict를 변경/" +"변경할 수 있습니다. 예를 들어 이후 라운드에서 로컬 에포크 수를 늘리기 위해 하" +"이퍼파라미터 일정을 구현하려면 다음과 같이 할 수 있습니다:" #: ../../source/how-to-configure-clients.rst:82 msgid "The :code:`FedAvg` strategy will call this function *every round*." @@ -4576,24 +4632,24 @@ msgstr "개별 클라이언트 구성" msgid "" "In some cases, it is necessary to send different configuration values to " "different clients." -msgstr "경우에 따라 다른 구성 값을 다른 클라이언트에 보내야 하는 경우도 있습니다." +msgstr "" +"경우에 따라 다른 구성 값을 다른 클라이언트에 보내야 하는 경우도 있습니다." #: ../../source/how-to-configure-clients.rst:89 msgid "" -"This can be achieved by customizing an existing strategy or by " -":doc:`implementing a custom strategy from scratch `. Here's a nonsensical example that customizes :code:`FedAvg`" -" by adding a custom ``\"hello\": \"world\"`` configuration key/value pair" -" to the config dict of a *single client* (only the first client in the " -"list, the other clients in this round to not receive this \"special\" " -"config value):" +"This can be achieved by customizing an existing strategy or by :doc:" +"`implementing a custom strategy from scratch `. " +"Here's a nonsensical example that customizes :code:`FedAvg` by adding a " +"custom ``\"hello\": \"world\"`` configuration key/value pair to the config " +"dict of a *single client* (only the first client in the list, the other " +"clients in this round to not receive this \"special\" config value):" msgstr "" "이는 기존 전략을 사용자 지정하거나 :doc:`implementing a custom strategy from " -"scratch `를 통해 수행할 수 있습니다. 다음은 " -"사용자 지정 ``\"hello\"'를 추가하여 :code:`FedAvg`를 사용자 지정하는 " -"무의미한 예입니다: \"world\"`` 구성 키/값 쌍을 *단일 클라이언트*의 config " -"dict에 추가합니다(목록의 첫 번째 클라이언트만, 이 라운드의 다른 클라이언트는 " -"이 \"특별한\" 구성 값을 수신하지 않음):" +"scratch `를 통해 수행할 수 있습니다. 다음은 사용" +"자 지정 ``\"hello\"'를 추가하여 :code:`FedAvg`를 사용자 지정하는 무의미한 예" +"입니다: \"world\"`` 구성 키/값 쌍을 *단일 클라이언트*의 config dict에 추가합" +"니다(목록의 첫 번째 클라이언트만, 이 라운드의 다른 클라이언트는 이 \"특별한" +"\" 구성 값을 수신하지 않음):" #: ../../source/how-to-configure-logging.rst:2 msgid "Configure logging" @@ -4602,22 +4658,22 @@ msgstr "로깅 구성" #: ../../source/how-to-configure-logging.rst:4 msgid "" "The Flower logger keeps track of all core events that take place in " -"federated learning workloads. It presents information by default " -"following a standard message format:" +"federated learning workloads. It presents information by default following a " +"standard message format:" msgstr "" -"Flower 로거는 federated 학습 워크로드에서 발생하는 모든 핵심 이벤트를 " -"추적합니다. 기본적으로 표준 메시지 형식에 따라 정보를 표시합니다:" +"Flower 로거는 federated 학습 워크로드에서 발생하는 모든 핵심 이벤트를 추적합" +"니다. 기본적으로 표준 메시지 형식에 따라 정보를 표시합니다:" #: ../../source/how-to-configure-logging.rst:13 msgid "" -"containing relevant information including: log message level (e.g. " -":code:`INFO`, :code:`DEBUG`), a timestamp, the line where the logging " -"took place from, as well as the log message itself. In this way, the " -"logger would typically display information on your terminal as follows:" +"containing relevant information including: log message level (e.g. :code:" +"`INFO`, :code:`DEBUG`), a timestamp, the line where the logging took place " +"from, as well as the log message itself. In this way, the logger would " +"typically display information on your terminal as follows:" msgstr "" "로그 메시지 수준(예: :code:`INFO`, :code:`DEBUG`), 타임스탬프, 로깅이 발생한 " -"줄, 로그 메시지 자체 등 관련 정보를 포함합니다. 이러한 방식으로 로거는 " -"일반적으로 다음과 같은 정보를 터미널에 표시합니다:" +"줄, 로그 메시지 자체 등 관련 정보를 포함합니다. 이러한 방식으로 로거는 일반적" +"으로 다음과 같은 정보를 터미널에 표시합니다:" #: ../../source/how-to-configure-logging.rst:34 msgid "Saving log to file" @@ -4627,34 +4683,32 @@ msgstr "파일에 로그 저장" msgid "" "By default, the Flower log is outputted to the terminal where you launch " "your Federated Learning workload from. This applies for both gRPC-based " -"federation (i.e. when you do :code:`fl.server.start_server`) and when " -"using the :code:`VirtualClientEngine` (i.e. when you do " -":code:`fl.simulation.start_simulation`). In some situations you might " -"want to save this log to disk. You can do so by calling the " -"`fl.common.logger.configure() " -"`_" -" function. For example:" -msgstr "" -"기본적으로 Flower 로그는 Federated 학습 워크로드를 실행하는 터미널에 " -"출력됩니다. 이는 gRPC 기반 페더레이션(즉,:code:`fl.simulation." -"start_simulation`를 실행하는 경우)과 :code:`VirtualClientEngine`을 사용하는 " -"경우(즉, :코드:`fl.simulation.start_simulation`을 실행하는 경우) 모두에 " -"적용됩니다. 경우에 따라 이 로그를 디스크에 저장하고 싶을 수도 있습니다. 이 " -"경우 `fl.common.logger.configure() `_ 함수를 호출하여 저장할 수 있습니다. 예를 " -"들어:" +"federation (i.e. when you do :code:`fl.server.start_server`) and when using " +"the :code:`VirtualClientEngine` (i.e. when you do :code:`fl.simulation." +"start_simulation`). In some situations you might want to save this log to " +"disk. You can do so by calling the `fl.common.logger.configure() `_ function. " +"For example:" +msgstr "" +"기본적으로 Flower 로그는 Federated 학습 워크로드를 실행하는 터미널에 출력됩니" +"다. 이는 gRPC 기반 페더레이션(즉,:code:`fl.simulation.start_simulation`를 실" +"행하는 경우)과 :code:`VirtualClientEngine`을 사용하는 경우(즉, :코드:`fl." +"simulation.start_simulation`을 실행하는 경우) 모두에 적용됩니다. 경우에 따라 " +"이 로그를 디스크에 저장하고 싶을 수도 있습니다. 이 경우 `fl.common.logger." +"configure() `_ 함수를 호출하여 저장할 수 있습니다. 예를 들어:" #: ../../source/how-to-configure-logging.rst:53 msgid "" -"With the above, Flower will record the log you see on your terminal to " -":code:`log.txt`. This file will be created in the same directory as were " -"you are running the code from. If we inspect we see the log above is also" -" recorded but prefixing with :code:`identifier` each line:" +"With the above, Flower will record the log you see on your terminal to :code:" +"`log.txt`. This file will be created in the same directory as were you are " +"running the code from. If we inspect we see the log above is also recorded " +"but prefixing with :code:`identifier` each line:" msgstr "" -"위와 같이 하면 Flower는 터미널에 표시되는 로그를 :code:`log.txt`에 " -"기록합니다. 이 파일은 코드를 실행한 디렉터리와 동일한 디렉터리에 생성됩니다. " -"검사해보면 위의 로그도 기록되지만 각 줄 앞에 :code:`identifier` 접두사가 " -"붙는 것을 확인할 수 있습니다:" +"위와 같이 하면 Flower는 터미널에 표시되는 로그를 :code:`log.txt`에 기록합니" +"다. 이 파일은 코드를 실행한 디렉터리와 동일한 디렉터리에 생성됩니다. 검사해보" +"면 위의 로그도 기록되지만 각 줄 앞에 :code:`identifier` 접두사가 붙는 것을 확" +"인할 수 있습니다:" #: ../../source/how-to-configure-logging.rst:74 msgid "Log your own messages" @@ -4662,19 +4716,20 @@ msgstr "나만의 메시지 기록" #: ../../source/how-to-configure-logging.rst:76 msgid "" -"You might expand the information shown by default with the Flower logger " -"by adding more messages relevant to your application. You can achieve " -"this easily as follows." +"You might expand the information shown by default with the Flower logger by " +"adding more messages relevant to your application. You can achieve this " +"easily as follows." msgstr "" "애플리케이션과 관련된 메시지를 더 추가하여 Flower 로거에 기본적으로 표시되는 " "정보를 확장할 수 있습니다. 다음과 같이 쉽게 추가할 수 있습니다." #: ../../source/how-to-configure-logging.rst:102 msgid "" -"In this way your logger will show, in addition to the default messages, " -"the ones introduced by the clients as specified above." -msgstr "이렇게 하면 로거에 기본 메시지 외에 위에서 지정한 대로 클라이언트가 소개한 " -"메시지가 표시됩니다." +"In this way your logger will show, in addition to the default messages, the " +"ones introduced by the clients as specified above." +msgstr "" +"이렇게 하면 로거에 기본 메시지 외에 위에서 지정한 대로 클라이언트가 소개한 메" +"시지가 표시됩니다." #: ../../source/how-to-configure-logging.rst:128 msgid "Log to a remote service" @@ -4682,23 +4737,22 @@ msgstr "원격 서비스에 로그인" #: ../../source/how-to-configure-logging.rst:130 msgid "" -"The :code:`fl.common.logger.configure` function, also allows specifying a" -" host to which logs can be pushed (via :code:`POST`) through a native " -"Python :code:`logging.handler.HTTPHandler`. This is a particularly useful" -" feature in :code:`gRPC`-based Federated Learning workloads where " -"otherwise gathering logs from all entities (i.e. the server and the " -"clients) might be cumbersome. Note that in Flower simulation, the server " -"automatically displays all logs. You can still specify a " -":code:`HTTPHandler` should you wish to backup or analyze the logs " -"somewhere else." -msgstr "" -"또한 :code:`fl.common.logger.configure` 함수를 사용하면 네이티브 Python " -":code:`logging.handler.HTTPHandler`를 통해 로그를 푸시할 수 있는 호스트를 " -"지정할 수 있습니다(:code:`POST`를 통해). 이는 모든 엔티티(예: 서버 및 " -"클라이언트)에서 로그를 수집하는 것이 번거로울 수 있는 :code:`gRPC` 기반 " -"Federated 학습 워크로드에서 특히 유용한 기능입니다. Flower 시뮬레이션에서는 " -"서버가 모든 로그를 자동으로 표시합니다. 로그를 다른 곳에 백업하거나 " -"분석하려는 경우 :code:`HTTPHandler`를 지정할 수 있습니다." +"The :code:`fl.common.logger.configure` function, also allows specifying a " +"host to which logs can be pushed (via :code:`POST`) through a native Python :" +"code:`logging.handler.HTTPHandler`. This is a particularly useful feature " +"in :code:`gRPC`-based Federated Learning workloads where otherwise gathering " +"logs from all entities (i.e. the server and the clients) might be " +"cumbersome. Note that in Flower simulation, the server automatically " +"displays all logs. You can still specify a :code:`HTTPHandler` should you " +"wish to backup or analyze the logs somewhere else." +msgstr "" +"또한 :code:`fl.common.logger.configure` 함수를 사용하면 네이티브 Python :" +"code:`logging.handler.HTTPHandler`를 통해 로그를 푸시할 수 있는 호스트를 지정" +"할 수 있습니다(:code:`POST`를 통해). 이는 모든 엔티티(예: 서버 및 클라이언트)" +"에서 로그를 수집하는 것이 번거로울 수 있는 :code:`gRPC` 기반 Federated 학습 " +"워크로드에서 특히 유용한 기능입니다. Flower 시뮬레이션에서는 서버가 모든 로그" +"를 자동으로 표시합니다. 로그를 다른 곳에 백업하거나 분석하려는 경우 :code:" +"`HTTPHandler`를 지정할 수 있습니다." #: ../../source/how-to-enable-ssl-connections.rst:2 msgid "Enable SSL connections" @@ -4706,33 +4760,32 @@ msgstr "SSL 연결 사용" #: ../../source/how-to-enable-ssl-connections.rst:4 msgid "" -"This guide describes how to a SSL-enabled secure Flower server " -"(:code:`SuperLink`) can be started and how a Flower client " -"(:code:`SuperNode`) can establish a secure connections to it." +"This guide describes how to a SSL-enabled secure Flower server (:code:" +"`SuperLink`) can be started and how a Flower client (:code:`SuperNode`) can " +"establish a secure connections to it." msgstr "" -"이 가이드에서는 SSL을 지원하는 보안 Flower 서버(:코드:`SuperLink`)를 " -"시작하는 방법과 Flower 클라이언트(:코드:`SuperNode`)가 이 서버에 보안 연결을 " -"설정하는 방법을 설명합니다." +"이 가이드에서는 SSL을 지원하는 보안 Flower 서버(:코드:`SuperLink`)를 시작하" +"는 방법과 Flower 클라이언트(:코드:`SuperNode`)가 이 서버에 보안 연결을 설정하" +"는 방법을 설명합니다." #: ../../source/how-to-enable-ssl-connections.rst:7 msgid "" -"A complete code example demonstrating a secure connection can be found " -"`here `_." +"A complete code example demonstrating a secure connection can be found `here " +"`_." msgstr "" "보안 연결을 보여주는 전체 코드 예제는 '여기 `_'에서 확인할 수 있습니다." #: ../../source/how-to-enable-ssl-connections.rst:10 msgid "" -"The code example comes with a :code:`README.md` file which explains how " -"to start it. Although it is already SSL-enabled, it might be less " -"descriptive on how it does so. Stick to this guide for a deeper " -"introduction to the topic." +"The code example comes with a :code:`README.md` file which explains how to " +"start it. Although it is already SSL-enabled, it might be less descriptive " +"on how it does so. Stick to this guide for a deeper introduction to the " +"topic." msgstr "" "코드 예제에는 시작 방법을 설명하는 :code:`README.md` 파일이 함께 제공됩니다. " -"이미 SSL을 사용하도록 설정되어 있지만 그 방법에 대한 설명이 부족할 수 " -"있습니다. 이 가이드를 참고하여 이 주제에 대해 자세히 알아보세요." +"이미 SSL을 사용하도록 설정되어 있지만 그 방법에 대한 설명이 부족할 수 있습니" +"다. 이 가이드를 참고하여 이 주제에 대해 자세히 알아보세요." #: ../../source/how-to-enable-ssl-connections.rst:16 msgid "Certificates" @@ -4741,10 +4794,10 @@ msgstr "인증서" #: ../../source/how-to-enable-ssl-connections.rst:18 msgid "" "Using SSL-enabled connections requires certificates to be passed to the " -"server and client. For the purpose of this guide we are going to generate" -" self-signed certificates. As this can become quite complex we are going " -"to ask you to run the script in :code:`examples/advanced-" -"tensorflow/certificates/generate.sh` with the following command sequence:" +"server and client. For the purpose of this guide we are going to generate " +"self-signed certificates. As this can become quite complex we are going to " +"ask you to run the script in :code:`examples/advanced-tensorflow/" +"certificates/generate.sh` with the following command sequence:" msgstr "" "SSL 사용 연결을 사용하려면 서버와 클라이언트에 인증서를 전달해야 합니다. 이 " "가이드에서는 자체 서명된 인증서를 생성하겠습니다. 이 과정은 상당히 복잡할 수 " @@ -4753,27 +4806,27 @@ msgstr "" #: ../../source/how-to-enable-ssl-connections.rst:29 msgid "" -"This will generate the certificates in :code:`examples/advanced-" -"tensorflow/.cache/certificates`." +"This will generate the certificates in :code:`examples/advanced-tensorflow/." +"cache/certificates`." msgstr "" -"이렇게 하면 :code:`examples/advanced-tensorflow/.cache/certificates`에 " -"인증서가 생성됩니다." +"이렇게 하면 :code:`examples/advanced-tensorflow/.cache/certificates`에 인증서" +"가 생성됩니다." #: ../../source/how-to-enable-ssl-connections.rst:31 msgid "" -"The approach for generating SSL certificates in the context of this " -"example can serve as an inspiration and starting point, but it should not" -" be used as a reference for production environments. Please refer to " -"other sources regarding the issue of correctly generating certificates " -"for production environments. For non-critical prototyping or research " -"projects, it might be sufficient to use the self-signed certificates " -"generated using the scripts mentioned in this guide." +"The approach for generating SSL certificates in the context of this example " +"can serve as an inspiration and starting point, but it should not be used as " +"a reference for production environments. Please refer to other sources " +"regarding the issue of correctly generating certificates for production " +"environments. For non-critical prototyping or research projects, it might be " +"sufficient to use the self-signed certificates generated using the scripts " +"mentioned in this guide." msgstr "" -"이 예의 맥락에서 SSL 인증서를 생성하는 접근 방식은 영감과 출발점이 될 수 " -"있지만 프로덕션 환경에 대한 참조로 사용해서는 안 됩니다. 프로덕션 환경용 " -"인증서를 올바르게 생성하는 문제에 대해서는 다른 출처를 참조하세요. 중요하지 " -"않은 프로토타이핑 또는 연구 프로젝트의 경우, 이 가이드에 언급된 스크립트를 " -"사용하여 생성한 자체 서명 인증서를 사용하는 것으로 충분할 수 있습니다." +"이 예의 맥락에서 SSL 인증서를 생성하는 접근 방식은 영감과 출발점이 될 수 있지" +"만 프로덕션 환경에 대한 참조로 사용해서는 안 됩니다. 프로덕션 환경용 인증서" +"를 올바르게 생성하는 문제에 대해서는 다른 출처를 참조하세요. 중요하지 않은 프" +"로토타이핑 또는 연구 프로젝트의 경우, 이 가이드에 언급된 스크립트를 사용하여 " +"생성한 자체 서명 인증서를 사용하는 것으로 충분할 수 있습니다." #: ../../source/how-to-enable-ssl-connections.rst:39 msgid "Server (SuperLink)" @@ -4781,18 +4834,20 @@ msgstr "서버(SuperLink)" #: ../../source/how-to-enable-ssl-connections.rst:41 msgid "" -"Use the following terminal command to start a sever (SuperLink) that uses" -" the previously generated certificates:" -msgstr "다음 터미널 명령을 사용하여 이전에 생성한 인증서를 사용하는 서버(SuperLink)" +"Use the following terminal command to start a sever (SuperLink) that uses " +"the previously generated certificates:" +msgstr "" +"다음 터미널 명령을 사용하여 이전에 생성한 인증서를 사용하는 서버(SuperLink)" "를 시작합니다:" #: ../../source/how-to-enable-ssl-connections.rst:50 msgid "" "When providing certificates, the server expects a tuple of three " -"certificates paths: CA certificate, server certificate and server private" -" key." -msgstr "인증서를 제공할 때 서버는 세 가지 인증서 경로의 튜플을 기대합니다: CA " -"인증서, 서버 인증서 및 서버 개인 키입니다." +"certificates paths: CA certificate, server certificate and server private " +"key." +msgstr "" +"인증서를 제공할 때 서버는 세 가지 인증서 경로의 튜플을 기대합니다: CA 인증" +"서, 서버 인증서 및 서버 개인 키입니다." #: ../../source/how-to-enable-ssl-connections.rst:54 msgid "Client (SuperNode)" @@ -4800,23 +4855,25 @@ msgstr "클라이언트(SuperNode)" #: ../../source/how-to-enable-ssl-connections.rst:56 msgid "" -"Use the following terminal command to start a client (SuperNode) that " -"uses the previously generated certificates:" -msgstr "다음 터미널 명령을 사용하여 이전에 생성한 인증서를 사용하는 " -"클라이언트(SuperNode)를 시작합니다:" +"Use the following terminal command to start a client (SuperNode) that uses " +"the previously generated certificates:" +msgstr "" +"다음 터미널 명령을 사용하여 이전에 생성한 인증서를 사용하는 클라이언트" +"(SuperNode)를 시작합니다:" #: ../../source/how-to-enable-ssl-connections.rst:64 msgid "" -"When setting :code:`root_certificates`, the client expects a file path to" -" PEM-encoded root certificates." -msgstr "코드:`root_certificates`를 설정하면 클라이언트는 PEM 인코딩된 루트 인증서의 " +"When setting :code:`root_certificates`, the client expects a file path to " +"PEM-encoded root certificates." +msgstr "" +"코드:`root_certificates`를 설정하면 클라이언트는 PEM 인코딩된 루트 인증서의 " "파일 경로를 예상합니다." #: ../../source/how-to-enable-ssl-connections.rst:70 msgid "" -"You should now have learned how to generate self-signed certificates " -"using the given script, start an SSL-enabled server and have a client " -"establish a secure connection to it." +"You should now have learned how to generate self-signed certificates using " +"the given script, start an SSL-enabled server and have a client establish a " +"secure connection to it." msgstr "" "이제 주어진 스크립트를 사용하여 자체 서명 인증서를 생성하고, SSL 사용 서버를 " "시작하고, 클라이언트가 보안 연결을 설정하는 방법을 배웠을 것입니다." @@ -4827,8 +4884,8 @@ msgstr "추가 리소스" #: ../../source/how-to-enable-ssl-connections.rst:77 msgid "" -"These additional sources might be relevant if you would like to dive " -"deeper into the topic of certificates:" +"These additional sources might be relevant if you would like to dive deeper " +"into the topic of certificates:" msgstr "인증서에 대해 더 자세히 알아보고 싶다면 이러한 추가 자료를 참고하세요:" #: ../../source/how-to-enable-ssl-connections.rst:79 @@ -4837,7 +4894,7 @@ msgstr "'암호화하세요 `_'" #: ../../source/how-to-enable-ssl-connections.rst:80 msgid "`certbot `_" -msgstr "인증봇 `_" +msgstr "`인증봇 `_" #: ../../source/how-to-implement-strategies.rst:2 msgid "Implement strategies" @@ -4845,18 +4902,18 @@ msgstr "전략 구현" #: ../../source/how-to-implement-strategies.rst:4 msgid "" -"The strategy abstraction enables implementation of fully custom " -"strategies. A strategy is basically the federated learning algorithm that" -" runs on the server. Strategies decide how to sample clients, how to " -"configure clients for training, how to aggregate updates, and how to " -"evaluate models. Flower provides a few built-in strategies which are " -"based on the same API described below." +"The strategy abstraction enables implementation of fully custom strategies. " +"A strategy is basically the federated learning algorithm that runs on the " +"server. Strategies decide how to sample clients, how to configure clients " +"for training, how to aggregate updates, and how to evaluate models. Flower " +"provides a few built-in strategies which are based on the same API described " +"below." msgstr "" "전략 추상화를 통해 완전한 맞춤형 전략을 구현할 수 있습니다. 전략은 " -"기본적으로 서버에서 실행되는 federated 학습 알고리즘입니다. 전략은 " -"클라이언트를 샘플링하는 방법, 학습을 위해 클라이언트를 구성하는 방법, " -"업데이트를 집계하는 방법, 모델을 평가하는 방법을 결정합니다. Flower는 아래에 " -"설명된 것과 동일한 API를 기반으로 하는 몇 가지 기본 제공 전략을 제공합니다." +"기본적으로 서버에서 실행되는 연합 학습 알고리즘입니다. 전략은 클라이언트를 " +"샘플링하는 방법, 학습을 위해 클라이언트를 구성하는 방법, 업데이트를 집계하는 " +"방법, 모델을 평가하는 방법을 결정합니다. Flower는 아래에 설명된 것과 동일한 " +"API를 기반으로 하는 몇 가지 기본 제공 전략을 제공합니다." #: ../../source/how-to-implement-strategies.rst:11 msgid "The :code:`Strategy` abstraction" @@ -4864,15 +4921,14 @@ msgstr ":code:`Strategy` 추상화" #: ../../source/how-to-implement-strategies.rst:13 msgid "" -"All strategy implementation are derived from the abstract base class " -":code:`flwr.server.strategy.Strategy`, both built-in implementations and " -"third party implementations. This means that custom strategy " -"implementations have the exact same capabilities at their disposal as " -"built-in ones." +"All strategy implementation are derived from the abstract base class :code:" +"`flwr.server.strategy.Strategy`, both built-in implementations and third " +"party implementations. This means that custom strategy implementations have " +"the exact same capabilities at their disposal as built-in ones." msgstr "" -"모든 전략 구현은 기본 제공 구현과 타사 구현 모두 추상 기본 클래스인 " -":code:`flwr.server.strategy.Strategy`에서 파생됩니다. 즉, 사용자 정의 전략 " -"구현은 기본 제공 구현과 완전히 동일한 기능을 사용할 수 있습니다." +"모든 전략 구현은 기본 제공 구현과 타사 구현 모두 추상 기본 클래스인 :code:" +"`flwr.server.strategy.Strategy`에서 파생됩니다. 즉, 사용자 정의 전략 구현은 " +"기본 제공 구현과 완전히 동일한 기능을 사용할 수 있습니다." #: ../../source/how-to-implement-strategies.rst:18 msgid "" @@ -4882,13 +4938,13 @@ msgstr "전략 추상화에서는 구현해야 하는 몇 가지 추상적인 #: ../../source/how-to-implement-strategies.rst:75 msgid "" -"Creating a new strategy means implementing a new :code:`class` (derived " -"from the abstract base class :code:`Strategy`) that implements for the " -"previously shown abstract methods:" +"Creating a new strategy means implementing a new :code:`class` (derived from " +"the abstract base class :code:`Strategy`) that implements for the previously " +"shown abstract methods:" msgstr "" -"새 전략을 생성한다는 것은 이전에 표시된 추상 메서드에 대해 구현하는 새로운 " -":code:`class`(추상 기본 클래스 :code:`Strategy`에서 파생됨)를 구현하는 것을 " -"의미합니다:" +"새 전략을 생성한다는 것은 이전에 표시된 추상 메서드에 대해 구현하는 새로운 :" +"code:`class`(추상 기본 클래스 :code:`Strategy`에서 파생됨)를 구현하는 것을 의" +"미합니다:" #: ../../source/how-to-implement-strategies.rst:100 msgid "The Flower server calls these methods in the following order:" @@ -4904,19 +4960,18 @@ msgstr ":code:`initialize_parameters` 메서드" #: ../../source/how-to-implement-strategies.rst:182 msgid "" -":code:`initialize_parameters` is called only once, at the very beginning " -"of an execution. It is responsible for providing the initial global model" -" parameters in a serialized form (i.e., as a :code:`Parameters` object)." +":code:`initialize_parameters` is called only once, at the very beginning of " +"an execution. It is responsible for providing the initial global model " +"parameters in a serialized form (i.e., as a :code:`Parameters` object)." msgstr "" "code:`initialize_parameters`는 실행을 처음 시작할 때 한 번만 호출됩니다. 이 " -"함수는 초기 전역 모델 파라미터를 직렬화된 형식(즉, :code:`Parameters` 객체)" -"으로 제공하는 역할을 합니다." +"함수는 초기 전역 모델 파라미터를 직렬화된 형식(즉, :code:`Parameters` 객체)으" +"로 제공하는 역할을 합니다." #: ../../source/how-to-implement-strategies.rst:184 msgid "" -"Built-in strategies return user-provided initial parameters. The " -"following example shows how initial parameters can be passed to " -":code:`FedAvg`:" +"Built-in strategies return user-provided initial parameters. The following " +"example shows how initial parameters can be passed to :code:`FedAvg`:" msgstr "" "기본 제공 전략은 사용자가 제공한 초기 매개 변수를 반환합니다. 다음 예는 초기 " "매개 변수를 :code:`FedAvg`에 전달하는 방법을 보여줍니다:" @@ -4924,32 +4979,31 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:209 msgid "" "The Flower server will call :code:`initialize_parameters`, which either " -"returns the parameters that were passed to :code:`initial_parameters`, or" -" :code:`None`. If no parameters are returned from " -":code:`initialize_parameters` (i.e., :code:`None`), the server will " -"randomly select one client and ask it to provide its parameters. This is " -"a convenience feature and not recommended in practice, but it can be " -"useful for prototyping. In practice, it is recommended to always use " -"server-side parameter initialization." -msgstr "" -"Flower 서버는 :code:`initialize_parameters`를 호출하여 " -":code:`initial_parameters`에 전달된 파라미터를 반환하거나 :code:`None`을 " -"반환합니다. :code:`initial_parameters`에서 반환되는 매개변수가 없는 경우(즉, " -":code:`None`) 서버는 무작위로 클라이언트 하나를 선택하여 해당 클라이언트에 " -"매개변수를 제공하도록 요청합니다. 이는 편의 기능이며 실제로는 권장하지 " -"않지만 프로토타이핑에는 유용할 수 있습니다. 실제로는 항상 서버 측 매개변수 " -"초기화를 사용하는 것이 좋습니다." +"returns the parameters that were passed to :code:`initial_parameters`, or :" +"code:`None`. If no parameters are returned from :code:" +"`initialize_parameters` (i.e., :code:`None`), the server will randomly " +"select one client and ask it to provide its parameters. This is a " +"convenience feature and not recommended in practice, but it can be useful " +"for prototyping. In practice, it is recommended to always use server-side " +"parameter initialization." +msgstr "" +"Flower 서버는 :code:`initialize_parameters`를 호출하여 :code:" +"`initial_parameters`에 전달된 파라미터를 반환하거나 :code:`None`을 반환합니" +"다. :code:`initial_parameters`에서 반환되는 매개변수가 없는 경우(즉, :code:" +"`None`) 서버는 무작위로 클라이언트 하나를 선택하여 해당 클라이언트에 매개변수" +"를 제공하도록 요청합니다. 이는 편의 기능이며 실제로는 권장하지 않지만 프로토" +"타이핑에는 유용할 수 있습니다. 실제로는 항상 서버 측 매개변수 초기화를 사용하" +"는 것이 좋습니다." #: ../../source/how-to-implement-strategies.rst:213 msgid "" "Server-side parameter initialization is a powerful mechanism. It can be " -"used, for example, to resume training from a previously saved checkpoint." -" It is also the fundamental capability needed to implement hybrid " -"approaches, for example, to fine-tune a pre-trained model using federated" -" learning." +"used, for example, to resume training from a previously saved checkpoint. It " +"is also the fundamental capability needed to implement hybrid approaches, " +"for example, to fine-tune a pre-trained model using federated learning." msgstr "" "서버 측 파라미터 초기화는 강력한 메커니즘입니다. 예를 들어 이전에 저장한 " -"체크포인트에서 학습을 재개하는 데 사용할 수 있습니다. 또한 federated 학습을 " +"체크포인트에서 학습을 재개하는 데 사용할 수 있습니다. 또한 연합 학습을 " "사용하여 사전 학습된 모델을 미세 조정하는 등 하이브리드 접근 방식을 구현하는 " "데 필요한 기본 기능입니다." @@ -4959,23 +5013,23 @@ msgstr ":code:`configure_fit` 메서드" #: ../../source/how-to-implement-strategies.rst:218 msgid "" -":code:`configure_fit` is responsible for configuring the upcoming round " -"of training. What does *configure* mean in this context? Configuring a " -"round means selecting clients and deciding what instructions to send to " -"these clients. The signature of :code:`configure_fit` makes this clear:" +":code:`configure_fit` is responsible for configuring the upcoming round of " +"training. What does *configure* mean in this context? Configuring a round " +"means selecting clients and deciding what instructions to send to these " +"clients. The signature of :code:`configure_fit` makes this clear:" msgstr "" -":code:`configure_fit`은 다가오는 학 라운드를 구성하는 역할을 합니다. 이 " -"문맥에서 *구성*은 무엇을 의미하나요? 라운드를 구성한다는 것은 클라이언트를 " -"선택하고 이 클라이언트에게 어떤 지침을 보낼지 결정하는 것을 의미합니다. " -"code:`configure_fit`의 시그니처를 보면 이를 명확히 알 수 있습니다:" +":code:`configure_fit`은 다가오는 학 라운드를 구성하는 역할을 합니다. 이 문맥" +"에서 *구성*은 무엇을 의미하나요? 라운드를 구성한다는 것은 클라이언트를 선택하" +"고 이 클라이언트에게 어떤 지침을 보낼지 결정하는 것을 의미합니다. code:" +"`configure_fit`의 시그니처를 보면 이를 명확히 알 수 있습니다:" #: ../../source/how-to-implement-strategies.rst:231 msgid "" "The return value is a list of tuples, each representing the instructions " -"that will be sent to a particular client. Strategy implementations " -"usually perform the following steps in :code:`configure_fit`:" +"that will be sent to a particular client. Strategy implementations usually " +"perform the following steps in :code:`configure_fit`:" msgstr "" -"반환 값은 튜플 목록으로, 각 튜플은 특정 클라이언트로 전송될 instruction을 " +"반환 값은 튜플 목록으로, 각 튜플은 특정 클라이언트로 전송될 명령어를 " "나타냅니다. 전략 구현은 일반적으로 :code:`configure_fit`에서 다음 단계를 " "수행합니다:" @@ -4985,39 +5039,38 @@ msgid "" "Use the :code:`client_manager` to randomly sample all (or a subset of) " "available clients (each represented as a :code:`ClientProxy` object)" msgstr "" -":code:`client_manager`를 사용하여 사용 가능한 모든 클라이언트(또는 그 하위 " -"집합)를 무작위로 샘플링합니다(각각 :code:`ClientProxy` 개체로 표시됨)" +":code:`client_manager`를 사용하여 사용 가능한 모든 클라이언트(또는 그 하위 집" +"합)를 무작위로 샘플링합니다(각각 :code:`ClientProxy` 개체로 표시됨)" #: ../../source/how-to-implement-strategies.rst:234 msgid "" "Pair each :code:`ClientProxy` with the same :code:`FitIns` holding the " "current global model :code:`parameters` and :code:`config` dict" msgstr "" -"각 :code:`ClientProxy`를 현재 글로벌 모델 :code:`parameters` 및 " -":code:`config` dict를 보유한 동일한 :code:`FitIns`와 쌍을 이룹니다" +"각 :code:`ClientProxy`를 현재 글로벌 모델 :code:`parameters` 및 :code:" +"`config` dict를 보유한 동일한 :code:`FitIns`와 쌍을 이룹니다" #: ../../source/how-to-implement-strategies.rst:236 msgid "" "More sophisticated implementations can use :code:`configure_fit` to " -"implement custom client selection logic. A client will only participate " -"in a round if the corresponding :code:`ClientProxy` is included in the " -"list returned from :code:`configure_fit`." +"implement custom client selection logic. A client will only participate in a " +"round if the corresponding :code:`ClientProxy` is included in the list " +"returned from :code:`configure_fit`." msgstr "" -"보다 정교한 구현은 :code:`configure_fit`을 사용하여 사용자 지정 클라이언트 " -"선택 로직을 구현할 수 있습니다. 클라이언트는 :code:`configure_fit`에서 " -"반환된 목록에 해당 :code:`ClientProxy`가 포함된 경우에만 라운드에 참여합니다." +"보다 정교한 구현은 :code:`configure_fit`을 사용하여 사용자 지정 클라이언트 선" +"택 로직을 구현할 수 있습니다. 클라이언트는 :code:`configure_fit`에서 반환된 " +"목록에 해당 :code:`ClientProxy`가 포함된 경우에만 라운드에 참여합니다." #: ../../source/how-to-implement-strategies.rst:240 msgid "" "The structure of this return value provides a lot of flexibility to the " "user. Since instructions are defined on a per-client basis, different " -"instructions can be sent to each client. This enables custom strategies " -"to train, for example, different models on different clients, or use " -"different hyperparameters on different clients (via the :code:`config` " -"dict)." +"instructions can be sent to each client. This enables custom strategies to " +"train, for example, different models on different clients, or use different " +"hyperparameters on different clients (via the :code:`config` dict)." msgstr "" "이 반환 값의 구조는 사용자에게 많은 유연성을 제공합니다. instructions은 " -"클라이언트별로 정의되므로 각 클라이언트에 서로 다른 instructions을 전송할 수 " +"클라이언트별로 정의되므로 각 클라이언트에 서로 다른 명령어를 전송할 수 " "있습니다. 이를 통해 예를 들어 클라이언트마다 다른 모델을 학습시키거나 " "클라이언트마다 다른 하이퍼파라미터를 사용하는 사용자 지정 전략을 사용할 수 " "있습니다(:code:`config` dict를 통해)." @@ -5028,9 +5081,8 @@ msgstr ":code:`aggregate_fit` 메서드" #: ../../source/how-to-implement-strategies.rst:245 msgid "" -":code:`aggregate_fit` is responsible for aggregating the results returned" -" by the clients that were selected and asked to train in " -":code:`configure_fit`." +":code:`aggregate_fit` is responsible for aggregating the results returned by " +"the clients that were selected and asked to train in :code:`configure_fit`." msgstr "" "code:`aggregate_fit`은 :code:`configure_fit`에서 훈련하도록 선택되고 요청된 " "클라이언트가 반환한 결과를 집계하는 역할을 담당합니다." @@ -5038,26 +5090,26 @@ msgstr "" #: ../../source/how-to-implement-strategies.rst:258 msgid "" "Of course, failures can happen, so there is no guarantee that the server " -"will get results from all the clients it sent instructions to (via " -":code:`configure_fit`). :code:`aggregate_fit` therefore receives a list " -"of :code:`results`, but also a list of :code:`failures`." +"will get results from all the clients it sent instructions to (via :code:" +"`configure_fit`). :code:`aggregate_fit` therefore receives a list of :code:" +"`results`, but also a list of :code:`failures`." msgstr "" -"물론 실패가 발생할 수 있으므로 서버가 명령을 보낸 모든 클라이언트로부터 " -"결과를 얻을 수 있다는 보장은 없습니다(:code:`configure_fit`을 통해). 따라서 " -":code:`aggregate_fit`은 :code:`results` 목록뿐만 아니라 :code:`failures` " -"목록도 받습니다." +"물론 실패가 발생할 수 있으므로 서버가 명령을 보낸 모든 클라이언트로부터 결과" +"를 얻을 수 있다는 보장은 없습니다(:code:`configure_fit`을 통해). 따라서 :" +"code:`aggregate_fit`은 :code:`results` 목록뿐만 아니라 :code:`failures` 목록" +"도 받습니다." #: ../../source/how-to-implement-strategies.rst:260 msgid "" -":code:`aggregate_fit` returns an optional :code:`Parameters` object and a" -" dictionary of aggregated metrics. The :code:`Parameters` return value is" -" optional because :code:`aggregate_fit` might decide that the results " +":code:`aggregate_fit` returns an optional :code:`Parameters` object and a " +"dictionary of aggregated metrics. The :code:`Parameters` return value is " +"optional because :code:`aggregate_fit` might decide that the results " "provided are not sufficient for aggregation (e.g., too many failures)." msgstr "" "code:`aggregate_fit`은 선택적 :code:`Parameters` 개체와 집계된 메트릭의 " "dictionary를 반환합니다. :code:`Parameters` 반환 값은 :code:`aggregate_fit`" -"이 제공된 결과가 집계에 충분하지 않다고 판단할 수 있으므로(예: 실패 수가 " -"너무 많음) 선택 사항입니다." +"이 제공된 결과가 집계에 충분하지 않다고 판단할 수 있으므로(예: 실패 수가 너" +"무 많음) 선택 사항입니다." #: ../../source/how-to-implement-strategies.rst:263 msgid "The :code:`configure_evaluate` method" @@ -5065,59 +5117,56 @@ msgstr ":code:`configure_evaluate` 메서드" #: ../../source/how-to-implement-strategies.rst:265 msgid "" -":code:`configure_evaluate` is responsible for configuring the upcoming " -"round of evaluation. What does *configure* mean in this context? " -"Configuring a round means selecting clients and deciding what " -"instructions to send to these clients. The signature of " -":code:`configure_evaluate` makes this clear:" +":code:`configure_evaluate` is responsible for configuring the upcoming round " +"of evaluation. What does *configure* mean in this context? Configuring a " +"round means selecting clients and deciding what instructions to send to " +"these clients. The signature of :code:`configure_evaluate` makes this clear:" msgstr "" ":code:`configure_evaluate`는 다가오는 평가 라운드를 구성하는 역할을 합니다. " -"이 문맥에서 *구성*은 무엇을 의미하나요? 라운드를 구성한다는 것은 " -"클라이언트를 선택하고 이러한 클라이언트에 전송할 지침을 결정하는 것을 " -"의미합니다. :code:`configure_evaluate`의 시그니처를 보면 이를 명확히 알 수 " -"있습니다:" +"이 문맥에서 *구성*은 무엇을 의미하나요? 라운드를 구성한다는 것은 클라이언트" +"를 선택하고 이러한 클라이언트에 전송할 지침을 결정하는 것을 의미합니다. :" +"code:`configure_evaluate`의 시그니처를 보면 이를 명확히 알 수 있습니다:" #: ../../source/how-to-implement-strategies.rst:278 msgid "" "The return value is a list of tuples, each representing the instructions " -"that will be sent to a particular client. Strategy implementations " -"usually perform the following steps in :code:`configure_evaluate`:" +"that will be sent to a particular client. Strategy implementations usually " +"perform the following steps in :code:`configure_evaluate`:" msgstr "" -"반환 값은 튜플 목록으로, 각 튜플은 특정 클라이언트로 전송될 instructions을 " +"반환 값은 튜플 목록으로, 각 튜플은 특정 클라이언트로 전송될 명령어를 " "나타냅니다. 전략 구현은 일반적으로 :code:`configure_evaluate`에서 다음 " "단계를 수행합니다:" #: ../../source/how-to-implement-strategies.rst:281 msgid "" -"Pair each :code:`ClientProxy` with the same :code:`EvaluateIns` holding " -"the current global model :code:`parameters` and :code:`config` dict" +"Pair each :code:`ClientProxy` with the same :code:`EvaluateIns` holding the " +"current global model :code:`parameters` and :code:`config` dict" msgstr "" -"각 :code:`ClientProxy`를 현재 글로벌 모델 :code:`parameters` 및 " -":code:`config` dict를 보유한 동일한 :code:`EvaluateIns`와 쌍을 이룹니다" +"각 :code:`ClientProxy`를 현재 글로벌 모델 :code:`parameters` 및 :code:" +"`config` dict를 보유한 동일한 :code:`EvaluateIns`와 쌍을 이룹니다" #: ../../source/how-to-implement-strategies.rst:283 msgid "" "More sophisticated implementations can use :code:`configure_evaluate` to " -"implement custom client selection logic. A client will only participate " -"in a round if the corresponding :code:`ClientProxy` is included in the " -"list returned from :code:`configure_evaluate`." +"implement custom client selection logic. A client will only participate in a " +"round if the corresponding :code:`ClientProxy` is included in the list " +"returned from :code:`configure_evaluate`." msgstr "" -"보다 정교한 구현은 :code:`configure_evaluate`를 사용하여 사용자 지정 " -"클라이언트 선택 로직을 구현할 수 있습니다. 클라이언트는 " -":code:`configure_evaluate`에서 반환된 목록에 해당 :code:`ClientProxy`가 " -"포함된 경우에만 라운드에 참여합니다." +"보다 정교한 구현은 :code:`configure_evaluate`를 사용하여 사용자 지정 클라이언" +"트 선택 로직을 구현할 수 있습니다. 클라이언트는 :code:`configure_evaluate`에" +"서 반환된 목록에 해당 :code:`ClientProxy`가 포함된 경우에만 라운드에 참여합니" +"다." #: ../../source/how-to-implement-strategies.rst:287 msgid "" "The structure of this return value provides a lot of flexibility to the " "user. Since instructions are defined on a per-client basis, different " -"instructions can be sent to each client. This enables custom strategies " -"to evaluate, for example, different models on different clients, or use " -"different hyperparameters on different clients (via the :code:`config` " -"dict)." +"instructions can be sent to each client. This enables custom strategies to " +"evaluate, for example, different models on different clients, or use " +"different hyperparameters on different clients (via the :code:`config` dict)." msgstr "" -"이 반환 값의 구조는 사용자에게 많은 유연성을 제공합니다. instructions은 " -"클라이언트별로 정의되므로 각 클라이언트에 서로 다른 instructions을 전송할 수 " +"이 반환 값의 구조는 사용자에게 많은 유연성을 제공합니다. 명령어는 " +"클라이언트별로 정의되므로 각 클라이언트에 서로 다른 명령어를 전송할 수 " "있습니다. 이를 통해 사용자 지정 전략을 통해 예를 들어 클라이언트마다 다른 " "모델을 평가하거나 클라이언트마다 다른 하이퍼파라미터를 사용할 수 " "있습니다(:code:`config` dict를 통해)." @@ -5129,35 +5178,35 @@ msgstr ":code:`aggregate_evaluate` 메서드" #: ../../source/how-to-implement-strategies.rst:293 msgid "" ":code:`aggregate_evaluate` is responsible for aggregating the results " -"returned by the clients that were selected and asked to evaluate in " -":code:`configure_evaluate`." +"returned by the clients that were selected and asked to evaluate in :code:" +"`configure_evaluate`." msgstr "" -"code:`aggregate_evaluate`는 :code:`configure_evaluate`에서 선택되어 평가를 " -"요청한 클라이언트가 반환한 결과를 집계하는 역할을 담당합니다." +"code:`aggregate_evaluate`는 :code:`configure_evaluate`에서 선택되어 평가를 요" +"청한 클라이언트가 반환한 결과를 집계하는 역할을 담당합니다." #: ../../source/how-to-implement-strategies.rst:306 msgid "" "Of course, failures can happen, so there is no guarantee that the server " -"will get results from all the clients it sent instructions to (via " -":code:`configure_evaluate`). :code:`aggregate_evaluate` therefore " -"receives a list of :code:`results`, but also a list of :code:`failures`." +"will get results from all the clients it sent instructions to (via :code:" +"`configure_evaluate`). :code:`aggregate_evaluate` therefore receives a list " +"of :code:`results`, but also a list of :code:`failures`." msgstr "" -"물론 실패가 발생할 수 있으므로 서버가 명령을 보낸 모든 클라이언트로부터 " -"결과를 얻을 수 있다는 보장은 없습니다(:code:`configure_evaluate`를 통해). " -"따라서 :code:`aggregate_evaluate`는 :code:`results` 목록뿐만 아니라 " -":code:`failures` 목록도 받습니다." +"물론 실패가 발생할 수 있으므로 서버가 명령을 보낸 모든 클라이언트로부터 결과" +"를 얻을 수 있다는 보장은 없습니다(:code:`configure_evaluate`를 통해). 따라" +"서 :code:`aggregate_evaluate`는 :code:`results` 목록뿐만 아니라 :code:" +"`failures` 목록도 받습니다." #: ../../source/how-to-implement-strategies.rst:308 msgid "" -":code:`aggregate_evaluate` returns an optional :code:`float` (loss) and a" -" dictionary of aggregated metrics. The :code:`float` return value is " -"optional because :code:`aggregate_evaluate` might decide that the results" -" provided are not sufficient for aggregation (e.g., too many failures)." +":code:`aggregate_evaluate` returns an optional :code:`float` (loss) and a " +"dictionary of aggregated metrics. The :code:`float` return value is optional " +"because :code:`aggregate_evaluate` might decide that the results provided " +"are not sufficient for aggregation (e.g., too many failures)." msgstr "" "code:`aggregate_evaluate`는 선택적 :code:`float`(손실)와 집계된 메트릭의 " "dictionary를 반환합니다. code:`float` 반환 값은 :code:`aggregate_evaluate`가 " -"제공된 결과가 집계에 충분하지 않다고 판단할 수 있으므로(예: 실패 수가 너무 " -"많음) 선택 사항입니다." +"제공된 결과가 집계에 충분하지 않다고 판단할 수 있으므로(예: 실패 수가 너무 많" +"음) 선택 사항입니다." #: ../../source/how-to-implement-strategies.rst:311 msgid "The :code:`evaluate` method" @@ -5166,26 +5215,25 @@ msgstr ":code:`evaluate` 메서드" #: ../../source/how-to-implement-strategies.rst:313 msgid "" ":code:`evaluate` is responsible for evaluating model parameters on the " -"server-side. Having :code:`evaluate` in addition to " -":code:`configure_evaluate`/:code:`aggregate_evaluate` enables strategies " -"to perform both servers-side and client-side (federated) evaluation." +"server-side. Having :code:`evaluate` in addition to :code:" +"`configure_evaluate`/:code:`aggregate_evaluate` enables strategies to " +"perform both servers-side and client-side (federated) evaluation." msgstr "" ":code:`evaluate`는 서버 측에서 모델 매개변수를 평가하는 역할을 담당합니다. " "code:`configure_evaluate`/:code:`aggregate_evaluate`와 함께 :code:`evaluate`" -"를 사용하면 서버 측과 클라이언트 측(federated) 평가를 모두 수행할 수 있는 " -"전략을 사용할 수 있습니다." +"를 사용하면 서버 측과 클라이언트 측(federated) 평가를 모두 수행할 수 있는 전" +"략을 사용할 수 있습니다." #: ../../source/how-to-implement-strategies.rst:323 msgid "" -"The return value is again optional because the strategy might not need to" -" implement server-side evaluation or because the user-defined " -":code:`evaluate` method might not complete successfully (e.g., it might " -"fail to load the server-side evaluation data)." +"The return value is again optional because the strategy might not need to " +"implement server-side evaluation or because the user-defined :code:" +"`evaluate` method might not complete successfully (e.g., it might fail to " +"load the server-side evaluation data)." msgstr "" -"반환 값은 전략에서 서버 측 평가를 구현할 필요가 없거나 사용자 정의 " -":code:`evaluate` 메서드가 성공적으로 완료되지 않을 수 있기 때문에(예: 서버 " -"측 평가 데이터를 로드하지 못할 수 있음) 다시 선택 사항으로 설정할 수 " -"있습니다." +"반환 값은 전략에서 서버 측 평가를 구현할 필요가 없거나 사용자 정의 :code:" +"`evaluate` 메서드가 성공적으로 완료되지 않을 수 있기 때문에(예: 서버 측 평가 " +"데이터를 로드하지 못할 수 있음) 다시 선택 사항으로 설정할 수 있습니다." #: ../../source/how-to-install-flower.rst:2 msgid "Install Flower" @@ -5206,17 +5254,18 @@ msgstr "pip 사용" #: ../../source/how-to-install-flower.rst:17 msgid "" -"Stable releases are available on `PyPI " -"`_::" -msgstr "안정적인 릴리즈는 `PyPI `_:: 에서 확인할 수 " +"Stable releases are available on `PyPI `_::" +msgstr "" +"안정적인 릴리즈는 `PyPI `_:: 에서 확인할 수 " "있습니다::" #: ../../source/how-to-install-flower.rst:21 msgid "" "For simulations that use the Virtual Client Engine, ``flwr`` should be " "installed with the ``simulation`` extra::" -msgstr "가상 클라이언트 엔진을 사용하는 시뮬레이션의 경우 ``flwr``을 ``simulation``" -"extra와 함께 설치해야 합니다:" +msgstr "" +"가상 클라이언트 엔진을 사용하는 시뮬레이션의 경우 ``flwr``을 " +"``simulation``extra와 함께 설치해야 합니다:" #: ../../source/how-to-install-flower.rst:27 msgid "Using conda (or mamba)" @@ -5228,15 +5277,16 @@ msgstr "Flower은 'conda-forge' 채널에서도 설치할 수 있습니다." #: ../../source/how-to-install-flower.rst:31 msgid "" -"If you have not added ``conda-forge`` to your channels, you will first " -"need to run the following::" +"If you have not added ``conda-forge`` to your channels, you will first need " +"to run the following::" msgstr "채널에 'conda-forge'를 추가하지 않은 경우 먼저 다음을 실행해야 합니다:" #: ../../source/how-to-install-flower.rst:36 msgid "" -"Once the ``conda-forge`` channel has been enabled, ``flwr`` can be " -"installed with ``conda``::" -msgstr "conda-forge`` 채널이 활성화되면 ``flwr``을 ``conda``로 설치할 수 있습니다::" +"Once the ``conda-forge`` channel has been enabled, ``flwr`` can be installed " +"with ``conda``::" +msgstr "" +"conda-forge`` 채널이 활성화되면 ``flwr``을 ``conda``로 설치할 수 있습니다::" #: ../../source/how-to-install-flower.rst:40 msgid "or with ``mamba``::" @@ -5249,11 +5299,11 @@ msgstr "설치 확인" #: ../../source/how-to-install-flower.rst:48 msgid "" "The following command can be used to verify if Flower was successfully " -"installed. If everything worked, it should print the version of Flower to" -" the command line::" +"installed. If everything worked, it should print the version of Flower to " +"the command line::" msgstr "" -"다음 명령을 사용하여 Flower가 성공적으로 설치되었는지 확인할 수 있습니다. " -"모든 것이 정상적으로 작동하면 명령줄에 Flower의 버전이 출력됩니다:" +"다음 명령을 사용하여 Flower가 성공적으로 설치되었는지 확인할 수 있습니다. 모" +"든 것이 정상적으로 작동하면 명령줄에 Flower의 버전이 출력됩니다:" #: ../../source/how-to-install-flower.rst:55 msgid "Advanced installation options" @@ -5265,7 +5315,8 @@ msgstr "Docker를 통해 설치" #: ../../source/how-to-install-flower.rst:60 msgid ":doc:`How to run Flower using Docker `" -msgstr ":doc:`Docker를 사용하여 Flower를 실행하는 방법 `" #: ../../source/how-to-install-flower.rst:63 @@ -5274,17 +5325,17 @@ msgstr "사전 릴리즈 설치" #: ../../source/how-to-install-flower.rst:65 msgid "" -"New (possibly unstable) versions of Flower are sometimes available as " -"pre-release versions (alpha, beta, release candidate) before the stable " -"release happens::" +"New (possibly unstable) versions of Flower are sometimes available as pre-" +"release versions (alpha, beta, release candidate) before the stable release " +"happens::" msgstr "" -"새(불안정할 수 있는) 버전의 Flower는 안정 버전이 출시되기 전에 사전 릴리즈 " -"버전(알파, 베타, 릴리즈 후보)으로 제공되는 경우가 있습니다:" +"새(불안정할 수 있는) 버전의 Flower는 안정 버전이 출시되기 전에 사전 릴리즈 버" +"전(알파, 베타, 릴리즈 후보)으로 제공되는 경우가 있습니다:" #: ../../source/how-to-install-flower.rst:69 msgid "" -"For simulations that use the Virtual Client Engine, ``flwr`` pre-releases" -" should be installed with the ``simulation`` extra::" +"For simulations that use the Virtual Client Engine, ``flwr`` pre-releases " +"should be installed with the ``simulation`` extra::" msgstr "" "가상 클라이언트 엔진을 사용하는 시뮬레이션의 경우 ``flwr`` 사전 릴리즈를 " "``simulation`` extra와 함께 설치해야 합니다:" @@ -5295,15 +5346,16 @@ msgstr "야간 릴리즈 설치" #: ../../source/how-to-install-flower.rst:76 msgid "" -"The latest (potentially unstable) changes in Flower are available as " -"nightly releases::" -msgstr "Flower의 최신 (불안정할 수 있는) 변경 사항은 다음과 같이 야간 릴리즈로 " -"제공됩니다:" +"The latest (potentially unstable) changes in Flower are available as nightly " +"releases::" +msgstr "" +"Flower의 최신 (불안정할 수 있는) 변경 사항은 다음과 같이 야간 릴리즈로 제공됩" +"니다:" #: ../../source/how-to-install-flower.rst:80 msgid "" -"For simulations that use the Virtual Client Engine, ``flwr-nightly`` " -"should be installed with the ``simulation`` extra::" +"For simulations that use the Virtual Client Engine, ``flwr-nightly`` should " +"be installed with the ``simulation`` extra::" msgstr "" "가상 클라이언트 엔진을 사용하는 시뮬레이션의 경우, ``flwr-nightly``를 " "``simulation`` extr와 함께 설치해야 합니다::" @@ -5314,24 +5366,24 @@ msgstr "모니터 시뮬레이션" #: ../../source/how-to-monitor-simulation.rst:4 msgid "" -"Flower allows you to monitor system resources while running your " -"simulation. Moreover, the Flower simulation engine is powerful and " -"enables you to decide how to allocate resources per client manner and " -"constrain the total usage. Insights from resource consumption can help " -"you make smarter decisions and speed up the execution time." +"Flower allows you to monitor system resources while running your simulation. " +"Moreover, the Flower simulation engine is powerful and enables you to decide " +"how to allocate resources per client manner and constrain the total usage. " +"Insights from resource consumption can help you make smarter decisions and " +"speed up the execution time." msgstr "" -"Flower를 사용하면 시뮬레이션을 실행하는 동안 시스템 리소스를 모니터링할 수 " -"있습니다. 또한 Flower 시뮬레이션 엔진은 강력하며 클라이언트별 리소스 할당 " -"방법을 결정하고 총 사용량을 제한할 수 있습니다. 리소스 소비에 대한 " -"인사이트를 통해 더 현명한 결정을 내리고 실행 시간을 단축할 수 있습니다." +"Flower를 사용하면 시뮬레이션을 실행하는 동안 시스템 리소스를 모니터링할 수 있" +"습니다. 또한 Flower 시뮬레이션 엔진은 강력하며 클라이언트별 리소스 할당 방법" +"을 결정하고 총 사용량을 제한할 수 있습니다. 리소스 소비에 대한 인사이트를 통" +"해 더 현명한 결정을 내리고 실행 시간을 단축할 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:6 msgid "" -"The specific instructions assume you are using macOS and have the " -"`Homebrew `_ package manager installed." +"The specific instructions assume you are using macOS and have the `Homebrew " +"`_ package manager installed." msgstr "" -"구체적인 지침은 macOS를 사용 중이고 'Homebrew `_ 패키지 " -"관리자가 설치되어 있다고 가정합니다." +"구체적인 지침은 macOS를 사용 중이고 'Homebrew `_ 패키지 관" +"리자가 설치되어 있다고 가정합니다." #: ../../source/how-to-monitor-simulation.rst:10 msgid "Downloads" @@ -5339,15 +5391,15 @@ msgstr "다운로드" #: ../../source/how-to-monitor-simulation.rst:16 msgid "" -"`Prometheus `_ is used for data collection, while" -" `Grafana `_ will enable you to visualize the " -"collected data. They are both well integrated with `Ray " -"`_ which Flower uses under the hood." +"`Prometheus `_ is used for data collection, while " +"`Grafana `_ will enable you to visualize the collected " +"data. They are both well integrated with `Ray `_ which " +"Flower uses under the hood." msgstr "" "`Prometheus `_는 데이터 수집에 사용되며, `Grafana " "`_는 수집된 데이터를 시각화할 수 있게 해줍니다. 이 두 " -"도구는 모두 Flower가 내부적으로 사용하는 `Ray `_와 잘 " -"통합되어 있습니다." +"도구는 모두 Flower가 내부적으로 사용하는 `Ray `_와 잘 통" +"합되어 있습니다." #: ../../source/how-to-monitor-simulation.rst:18 msgid "" @@ -5365,25 +5417,25 @@ msgstr "이전 세대 Intel Mac 장치에서는:" #: ../../source/how-to-monitor-simulation.rst:34 msgid "" -"Open the respective configuration files and change them. Depending on " -"your device, use one of the two following commands:" -msgstr "각 구성 파일을 열고 변경합니다. 장치에 따라 다음 두 명령 중 하나를 " -"사용합니다:" +"Open the respective configuration files and change them. Depending on your " +"device, use one of the two following commands:" +msgstr "" +"각 구성 파일을 열고 변경합니다. 장치에 따라 다음 두 명령 중 하나를 사용합니" +"다:" #: ../../source/how-to-monitor-simulation.rst:44 msgid "" -"and then delete all the text in the file and paste a new Prometheus " -"config you see below. You may adjust the time intervals to your " -"requirements:" +"and then delete all the text in the file and paste a new Prometheus config " +"you see below. You may adjust the time intervals to your requirements:" msgstr "" -"를 입력한 다음 파일의 모든 텍스트를 삭제하고 아래에 표시된 새 Prometheus " -"설정을 붙여넣습니다. 요구 사항에 따라 시간 간격을 조정할 수 있습니다:" +"를 입력한 다음 파일의 모든 텍스트를 삭제하고 아래에 표시된 새 Prometheus 설정" +"을 붙여넣습니다. 요구 사항에 따라 시간 간격을 조정할 수 있습니다:" #: ../../source/how-to-monitor-simulation.rst:59 msgid "" -"Now after you have edited the Prometheus configuration, do the same with " -"the Grafana configuration files. Open those using one of the following " -"commands as before:" +"Now after you have edited the Prometheus configuration, do the same with the " +"Grafana configuration files. Open those using one of the following commands " +"as before:" msgstr "" "이제 Prometheus 구성을 편집한 후 Grafana 구성 파일에 대해서도 동일한 작업을 " "수행합니다. 이전과 마찬가지로 다음 명령 중 하나를 사용하여 파일을 엽니다:" @@ -5392,14 +5444,16 @@ msgstr "" msgid "" "Your terminal editor should open and allow you to apply the following " "configuration as before." -msgstr "터미널 편집기가 열리면 이전과 마찬가지로 다음 구성을 적용할 수 있습니다." +msgstr "" +"터미널 편집기가 열리면 이전과 마찬가지로 다음 구성을 적용할 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:84 msgid "" -"Congratulations, you just downloaded all the necessary software needed " -"for metrics tracking. Now, let’s start it." -msgstr "축하합니다. 매트릭 트레킹에 필요한 모든 소프트웨어를 다운로드하셨습니다. " -"이제 시작해 보겠습니다." +"Congratulations, you just downloaded all the necessary software needed for " +"metrics tracking. Now, let’s start it." +msgstr "" +"축하합니다. 매트릭 트레킹에 필요한 모든 소프트웨어를 다운로드하셨습니다. 이" +"제 시작해 보겠습니다." #: ../../source/how-to-monitor-simulation.rst:88 msgid "Tracking metrics" @@ -5409,14 +5463,15 @@ msgstr "매트릭 트래킹" msgid "" "Before running your Flower simulation, you have to start the monitoring " "tools you have just installed and configured." -msgstr "Flower 시뮬레이션을 실행하기 전에 방금 설치 및 구성한 모니터링 도구를 " -"시작해야 합니다." +msgstr "" +"Flower 시뮬레이션을 실행하기 전에 방금 설치 및 구성한 모니터링 도구를 시작해" +"야 합니다." #: ../../source/how-to-monitor-simulation.rst:97 msgid "" -"Please include the following argument in your Python code when starting a" -" simulation." -msgstr "시뮬레이션을 시작할 때 Python 코드에 다음 argument를 포함하세요." +"Please include the following argument in your Python code when starting a " +"simulation." +msgstr "시뮬레이션을 시작할 때 Python 코드에 다음 전달인자를 포함하세요." #: ../../source/how-to-monitor-simulation.rst:108 msgid "Now, you are ready to start your workload." @@ -5424,9 +5479,10 @@ msgstr "이제 워크로드를 시작할 준비가 되었습니다." #: ../../source/how-to-monitor-simulation.rst:110 msgid "" -"Shortly after the simulation starts, you should see the following logs in" -" your terminal:" -msgstr "시뮬레이션이 시작되고 얼마 지나지 않아 터미널에 다음 로그가 표시됩니다:" +"Shortly after the simulation starts, you should see the following logs in " +"your terminal:" +msgstr "" +"시뮬레이션이 시작되고 얼마 지나지 않아 터미널에 다음 로그가 표시됩니다:" #: ../../source/how-to-monitor-simulation.rst:117 msgid "You can look at everything at ``_ ." @@ -5434,24 +5490,24 @@ msgstr "``_ 에서 모든 것을 볼 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:119 msgid "" -"It's a Ray Dashboard. You can navigate to Metrics (on the left panel, the" -" lowest option)." -msgstr "Ray 대시보드입니다. 메트릭(왼쪽 패널의 가장 아래 옵션)으로 이동할 수 " -"있습니다." +"It's a Ray Dashboard. You can navigate to Metrics (on the left panel, the " +"lowest option)." +msgstr "" +"Ray 대시보드입니다. 메트릭(왼쪽 패널의 가장 아래 옵션)으로 이동할 수 있습니" +"다." #: ../../source/how-to-monitor-simulation.rst:121 msgid "" -"Or alternatively, you can just see them in Grafana by clicking on the " -"right-up corner, “View in Grafana”. Please note that the Ray dashboard is" -" only accessible during the simulation. After the simulation ends, you " -"can only use Grafana to explore the metrics. You can start Grafana by " -"going to ``http://localhost:3000/``." +"Or alternatively, you can just see them in Grafana by clicking on the right-" +"up corner, “View in Grafana”. Please note that the Ray dashboard is only " +"accessible during the simulation. After the simulation ends, you can only " +"use Grafana to explore the metrics. You can start Grafana by going to " +"``http://localhost:3000/``." msgstr "" -"또는 오른쪽 위 모서리인 \"Grafana에서 보기\"를 클릭하여 Grafana에서 바로 " -"확인할 수도 있습니다. Ray 대시보드는 시뮬레이션 중에만 액세스할 수 있다는 " -"점에 유의하세요. 시뮬레이션이 종료된 후에는 Grafana를 사용하여 메트릭을 " -"탐색할 수만 있습니다. ``http://localhost:3000/``로 이동하여 Grafana를 시작할 " -"수 있습니다." +"또는 오른쪽 위 모서리인 \"Grafana에서 보기\"를 클릭하여 Grafana에서 바로 확인" +"할 수도 있습니다. Ray 대시보드는 시뮬레이션 중에만 액세스할 수 있다는 점에 유" +"의하세요. 시뮬레이션이 종료된 후에는 Grafana를 사용하여 메트릭을 탐색할 수만 " +"있습니다. ``http://localhost:3000/``로 이동하여 Grafana를 시작할 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:123 msgid "" @@ -5468,26 +5524,26 @@ msgstr "리소스 할당" #: ../../source/how-to-monitor-simulation.rst:134 msgid "" -"You must understand how the Ray library works to efficiently allocate " -"system resources to simulation clients on your own." -msgstr "Ray 라이브러리가 어떻게 작동하는지 이해해야 시뮬레이션 클라이언트에 시스템 " -"리소스를 효율적으로 할당할 수 있습니다." +"You must understand how the Ray library works to efficiently allocate system " +"resources to simulation clients on your own." +msgstr "" +"Ray 라이브러리가 어떻게 작동하는지 이해해야 시뮬레이션 클라이언트에 시스템 리" +"소스를 효율적으로 할당할 수 있습니다." #: ../../source/how-to-monitor-simulation.rst:136 msgid "" "Initially, the simulation (which Ray handles under the hood) starts by " "default with all the available resources on the system, which it shares " -"among the clients. It doesn't mean it divides it equally among all of " -"them, nor that the model training happens at all of them simultaneously. " -"You will learn more about that in the later part of this blog. You can " -"check the system resources by running the following:" -msgstr "" -"처음에 시뮬레이션(Ray가 내부에서 처리하는)은 기본적으로 시스템에서 사용 " -"가능한 모든 리소스를 사용하여 시작되며, 이 리소스는 클라이언트 간에 " -"공유됩니다. 그렇다고 해서 모든 클라이언트에게 균등하게 분배하거나 모든 " -"클라이언트에서 동시에 모델 학습이 이루어지는 것은 아닙니다. 이에 대한 자세한 " -"내용은 이 블로그의 뒷부분에서 설명합니다. 다음을 실행하여 시스템 리소스를 " -"확인할 수 있습니다:" +"among the clients. It doesn't mean it divides it equally among all of them, " +"nor that the model training happens at all of them simultaneously. You will " +"learn more about that in the later part of this blog. You can check the " +"system resources by running the following:" +msgstr "" +"처음에 시뮬레이션(Ray가 내부에서 처리하는)은 기본적으로 시스템에서 사용 가능" +"한 모든 리소스를 사용하여 시작되며, 이 리소스는 클라이언트 간에 공유됩니다. " +"그렇다고 해서 모든 클라이언트에게 균등하게 분배하거나 모든 클라이언트에서 동" +"시에 모델 학습이 이루어지는 것은 아닙니다. 이에 대한 자세한 내용은 이 블로그" +"의 뒷부분에서 설명합니다. 다음을 실행하여 시스템 리소스를 확인할 수 있습니다:" #: ../../source/how-to-monitor-simulation.rst:143 msgid "In Google Colab, the result you see might be similar to this:" @@ -5495,10 +5551,11 @@ msgstr "Google Colab에서는 이와 유사한 결과가 표시될 수 있습니 #: ../../source/how-to-monitor-simulation.rst:155 msgid "" -"However, you can overwrite the defaults. When starting a simulation, do " -"the following (you don't need to overwrite all of them):" -msgstr "그러나 기본값을 덮어쓸 수 있습니다. 시뮬레이션을 시작할 때 다음을 수행합니다(" -"모두 덮어쓸 필요는 없음):" +"However, you can overwrite the defaults. When starting a simulation, do the " +"following (you don't need to overwrite all of them):" +msgstr "" +"그러나 기본값을 덮어쓸 수 있습니다. 시뮬레이션을 시작할 때 다음을 수행합니다" +"(모두 덮어쓸 필요는 없음):" #: ../../source/how-to-monitor-simulation.rst:175 msgid "Let’s also specify the resource for a single client." @@ -5506,28 +5563,28 @@ msgstr "단일 클라이언트에 대한 리소스도 지정해 보겠습니다. #: ../../source/how-to-monitor-simulation.rst:205 msgid "" -"Now comes the crucial part. Ray will start a new client only when it has " -"all the required resources (such that they run in parallel) when the " -"resources allow." +"Now comes the crucial part. Ray will start a new client only when it has all " +"the required resources (such that they run in parallel) when the resources " +"allow." msgstr "" -"이제 중요한 부분이 나옵니다. Ray는 리소스가 허용하는 경우에만 필요한 모든 " -"리소스가 있을 때(병렬로 실행되는 등) 새 클라이언트를 시작합니다." +"이제 중요한 부분이 나옵니다. Ray는 리소스가 허용하는 경우에만 필요한 모든 리" +"소스가 있을 때(병렬로 실행되는 등) 새 클라이언트를 시작합니다." #: ../../source/how-to-monitor-simulation.rst:207 msgid "" -"In the example above, only one client will be run, so your clients won't " -"run concurrently. Setting :code:`client_num_gpus = 0.5` would allow " -"running two clients and therefore enable them to run concurrently. Be " -"careful not to require more resources than available. If you specified " -":code:`client_num_gpus = 2`, the simulation wouldn't start (even if you " -"had 2 GPUs but decided to set 1 in :code:`ray_init_args`)." +"In the example above, only one client will be run, so your clients won't run " +"concurrently. Setting :code:`client_num_gpus = 0.5` would allow running two " +"clients and therefore enable them to run concurrently. Be careful not to " +"require more resources than available. If you specified :code:" +"`client_num_gpus = 2`, the simulation wouldn't start (even if you had 2 GPUs " +"but decided to set 1 in :code:`ray_init_args`)." msgstr "" -"위의 예에서는 하나의 클라이언트만 실행되므로 클라이언트가 동시에 실행되지 " -"않습니다. :code:`client_num_gpus = 0.5` 를 설정하면 두 개의 클라이언트를 " -"실행할 수 있으므로 동시에 실행할 수 있습니다. 사용 가능한 리소스보다 더 많은 " -"리소스를 요구하지 않도록 주의하세요. :code:`client_num_gpus = 2`를 지정하면 " -"시뮬레이션이 시작되지 않습니다(GPU가 2개이지만 :code:`ray_init_args`에서 " -"1개를 설정한 경우에도 마찬가지입니다)." +"위의 예에서는 하나의 클라이언트만 실행되므로 클라이언트가 동시에 실행되지 않" +"습니다. :code:`client_num_gpus = 0.5` 를 설정하면 두 개의 클라이언트를 실행" +"할 수 있으므로 동시에 실행할 수 있습니다. 사용 가능한 리소스보다 더 많은 리소" +"스를 요구하지 않도록 주의하세요. :code:`client_num_gpus = 2`를 지정하면 시뮬" +"레이션이 시작되지 않습니다(GPU가 2개이지만 :code:`ray_init_args`에서 1개를 설" +"정한 경우에도 마찬가지입니다)." #: ../../source/how-to-monitor-simulation.rst:212 ../../source/ref-faq.rst:2 msgid "FAQ" @@ -5539,29 +5596,29 @@ msgstr "질문: 기록된 메트릭이 보이지 않습니다." #: ../../source/how-to-monitor-simulation.rst:216 msgid "" -"A: The timeframe might not be properly set. The setting is in the top " -"right corner (\"Last 30 minutes\" by default). Please change the " -"timeframe to reflect the period when the simulation was running." +"A: The timeframe might not be properly set. The setting is in the top right " +"corner (\"Last 30 minutes\" by default). Please change the timeframe to " +"reflect the period when the simulation was running." msgstr "" -"A: 기간이 제대로 설정되지 않았을 수 있습니다. 설정은 오른쪽 상단에 있습니다(" -"기본값은 '지난 30분'). 시뮬레이션이 실행된 기간을 반영하도록 기간을 변경해 " +"A: 기간이 제대로 설정되지 않았을 수 있습니다. 설정은 오른쪽 상단에 있습니다" +"(기본값은 '지난 30분'). 시뮬레이션이 실행된 기간을 반영하도록 기간을 변경해 " "주세요." #: ../../source/how-to-monitor-simulation.rst:218 msgid "" -"Q: I see “Grafana server not detected. Please make sure the Grafana " -"server is running and refresh this page” after going to the Metrics tab " -"in Ray Dashboard." +"Q: I see “Grafana server not detected. Please make sure the Grafana server " +"is running and refresh this page” after going to the Metrics tab in Ray " +"Dashboard." msgstr "" -"질문: \"Grafana 서버가 감지되지 않았습니다. Ray 대시보드의 메트릭 탭으로 " -"이동한 후 Grafana 서버가 실행 중인지 확인하고 이 페이지를 새로고침하세요." -"\"라는 메시지가 표시됩니다." +"질문: \"Grafana 서버가 감지되지 않았습니다. Ray 대시보드의 메트릭 탭으로 이동" +"한 후 Grafana 서버가 실행 중인지 확인하고 이 페이지를 새로고침하세요.\"라는 " +"메시지가 표시됩니다." #: ../../source/how-to-monitor-simulation.rst:220 msgid "" -"A: You probably don't have Grafana running. Please check the running " -"services" -msgstr "A: Grafana가 실행되고 있지 않을 수 있습니다. 실행 중인 서비스를 확인하세요" +"A: You probably don't have Grafana running. Please check the running services" +msgstr "" +"A: Grafana가 실행되고 있지 않을 수 있습니다. 실행 중인 서비스를 확인하세요" #: ../../source/how-to-monitor-simulation.rst:226 msgid "" @@ -5573,8 +5630,8 @@ msgstr "" #: ../../source/how-to-monitor-simulation.rst:228 msgid "" -"A: Either the simulation has already finished, or you still need to start" -" Prometheus." +"A: Either the simulation has already finished, or you still need to start " +"Prometheus." msgstr "A: 시뮬레이션이 이미 완료되었거나 아직 Prometheus를 시작해야 합니다." #: ../../source/how-to-monitor-simulation.rst:232 @@ -5599,14 +5656,13 @@ msgstr "Docker를 사용하여 Flower 실행" #: ../../source/how-to-run-flower-using-docker.rst:4 msgid "" -"The simplest way to get started with Flower is by using the pre-made " -"Docker images, which you can find on `Docker Hub " -"`__. Supported architectures include " -"``amd64`` and ``arm64v8``." +"The simplest way to get started with Flower is by using the pre-made Docker " +"images, which you can find on `Docker Hub `__. Supported architectures include ``amd64`` and ``arm64v8``." msgstr "" "Flower를 시작하는 가장 간단한 방법은 `Docker Hub `__에서 찾을 수 있는 미리 만들어진 Docker 이미지를 사용하는 것입니다. " -"지원되는 아키텍처는 ``amd64`` 및 ``arm64v8``입니다." +"flwr>`__에서 찾을 수 있는 미리 만들어진 Docker 이미지를 사용하는 것입니다. 지" +"원되는 아키텍처는 ``amd64`` 및 ``arm64v8``입니다." #: ../../source/how-to-run-flower-using-docker.rst:8 msgid "Before you start, make sure that the Docker daemon is running:" @@ -5614,10 +5670,9 @@ msgstr "시작하기 전에 Docker daemon이 실행 중인지 확인하세요:" #: ../../source/how-to-run-flower-using-docker.rst:15 msgid "" -"If you do not see the version of Docker but instead get an error saying " -"that the command was not found, you will need to install Docker first. " -"You can find installation instruction `here `_." +"If you do not see the version of Docker but instead get an error saying that " +"the command was not found, you will need to install Docker first. You can " +"find installation instruction `here `_." msgstr "" "전이 표시되지 않고 대신 명령을 찾을 수 없다는 오류가 표시되는 경우 먼저 " "Docker를 설치해야 합니다. `여기 `_에서 " @@ -5625,26 +5680,26 @@ msgstr "" #: ../../source/how-to-run-flower-using-docker.rst:21 msgid "" -"On Linux, Docker commands require ``sudo`` privilege. If you want to " -"avoid using ``sudo``, you can follow the `Post-installation steps " -"`_ on the " -"official Docker website." +"On Linux, Docker commands require ``sudo`` privilege. If you want to avoid " +"using ``sudo``, you can follow the `Post-installation steps `_ on the official Docker " +"website." msgstr "" -"Linux에서 Docker 명령을 실행하려면 ``sudo`` 권한이 필요합니다. sudo``를 " +"Linux에서 Docker 명령을 실행하려면 ``sudo`` 권한이 필요합니다. ``sudo`` 를 " "사용하지 않으려면 공식 Docker 웹사이트의 `Post-installation steps " "`_를 따르세요." #: ../../source/how-to-run-flower-using-docker.rst:27 msgid "" -"To ensure optimal performance and compatibility, the SuperLink, SuperNode" -" and ServerApp image must have the same version when running together. " -"This guarantees seamless integration and avoids potential conflicts or " -"issues that may arise from using different versions." +"To ensure optimal performance and compatibility, the SuperLink, SuperNode " +"and ServerApp image must have the same version when running together. This " +"guarantees seamless integration and avoids potential conflicts or issues " +"that may arise from using different versions." msgstr "" "최적의 성능과 호환성을 보장하려면 SuperLink, SuperNode 및 ServerApp 이미지를 " -"함께 실행할 때 버전이 동일해야 합니다. 이렇게 하면 원활한 통합을 보장하고 " -"서로 다른 버전을 사용할 때 발생할 수 있는 잠재적인 충돌이나 문제를 방지할 수 " -"있습니다." +"함께 실행할 때 버전이 동일해야 합니다. 이렇게 하면 원활한 통합을 보장하고 서" +"로 다른 버전을 사용할 때 발생할 수 있는 잠재적인 충돌이나 문제를 방지할 수 있" +"습니다." #: ../../source/how-to-run-flower-using-docker.rst:32 msgid "Flower SuperLink" @@ -5660,60 +5715,60 @@ msgstr "Flower를 사용해보고 싶다면 다음 명령을 사용하면 됩니 #: ../../source/how-to-run-flower-using-docker.rst:43 msgid "" -"The command pulls the Docker image with the tag ``1.8.0`` from Docker " -"Hub. The tag specifies the Flower version. In this case, Flower 1.8.0. " -"The ``--rm`` flag tells Docker to remove the container after it exits." +"The command pulls the Docker image with the tag ``1.8.0`` from Docker Hub. " +"The tag specifies the Flower version. In this case, Flower 1.8.0. The ``--" +"rm`` flag tells Docker to remove the container after it exits." msgstr "" "이 명령은 Docker Hub에서 ``1.8.0`` 태그가 있는 Docker 이미지를 가져옵니다. " -"이 태그는 Flower 버전을 지정합니다. 이 경우, Flower 1.8.0입니다. '`--rm`` " -"플래그는 컨테이너가 종료된 후 컨테이너를 제거하도록 Docker에 지시합니다." +"이 태그는 Flower 버전을 지정합니다. 이 경우, Flower 1.8.0입니다. '`--rm`` 플" +"래그는 컨테이너가 종료된 후 컨테이너를 제거하도록 Docker에 지시합니다." #: ../../source/how-to-run-flower-using-docker.rst:49 msgid "" "By default, the Flower SuperLink keeps state in-memory. When using the " -"Docker flag ``--rm``, the state is not persisted between container " -"starts. We will show below how to save the state in a file on your host " -"system." +"Docker flag ``--rm``, the state is not persisted between container starts. " +"We will show below how to save the state in a file on your host system." msgstr "" "기본적으로 Flower SuperLink는 상태를 in-memory에 유지합니다. Docker 플래그 " -"`--rm``을 사용하는 경우 컨테이너 시작 사이에 상태가 유지되지 않습니다. " -"아래에서 호스트 시스템의 파일에 상태를 저장하는 방법을 보여드리겠습니다." +"`--rm``을 사용하는 경우 컨테이너 시작 사이에 상태가 유지되지 않습니다. 아래에" +"서 호스트 시스템의 파일에 상태를 저장하는 방법을 보여드리겠습니다." #: ../../source/how-to-run-flower-using-docker.rst:53 msgid "" -"The ``-p :`` flag tells Docker to map the ports " -"``9091``/``9092`` of the host to ``9091``/``9092`` of the container, " -"allowing you to access the Driver API on ``http://localhost:9091`` and " -"the Fleet API on ``http://localhost:9092``. Lastly, any flag that comes " -"after the tag is passed to the Flower SuperLink. Here, we are passing the" -" flag ``--insecure``." +"The ``-p :`` flag tells Docker to map the ports ``9091``/" +"``9092`` of the host to ``9091``/``9092`` of the container, allowing you to " +"access the Driver API on ``http://localhost:9091`` and the Fleet API on " +"``http://localhost:9092``. Lastly, any flag that comes after the tag is " +"passed to the Flower SuperLink. Here, we are passing the flag ``--insecure``." msgstr "" -"``-p :`` 플래그는 호스트의 포트 ``9091``/``9092``를 " -"컨테이너의 ``9091``/``9092``에 매핑하여 ``http://localhost:9091``의 드라이버 " -"API와 ``http://localhost:9092``의 Fleet API에 액세스할 수 있도록 Docker에 " -"지시합니다. 마지막으로, 태그 뒤에 오는 모든 플래그는 Flower SuperLink에 " -"전달됩니다. 여기서는 ``--insecure``플래그를 전달합니다." +"``-p :`` 플래그는 호스트의 포트 ``9091``/``9092``를 컨테이너" +"의 ``9091``/``9092``에 매핑하여 ``http://localhost:9091``의 드라이버 API와 " +"``http://localhost:9092``의 Fleet API에 액세스할 수 있도록 Docker에 지시합니" +"다. 마지막으로, 태그 뒤에 오는 모든 플래그는 Flower SuperLink에 전달됩니다. " +"여기서는 ``--insecure``플래그를 전달합니다." #: ../../source/how-to-run-flower-using-docker.rst:60 #: ../../source/how-to-run-flower-using-docker.rst:259 #: ../../source/how-to-run-flower-using-docker.rst:376 msgid "" "The ``--insecure`` flag enables insecure communication (using HTTP, not " -"HTTPS) and should only be used for testing purposes. We strongly " -"recommend enabling `SSL `__ when " -"deploying to a production environment." +"HTTPS) and should only be used for testing purposes. We strongly recommend " +"enabling `SSL `__ when deploying to a " +"production environment." msgstr "" -"``--insecure`` 플래그는 안전하지 않은 통신(HTTPS가 아닌 HTTP 사용)을 " -"활성화하며 테스트 목적으로만 사용해야 합니다. 프로덕션 환경에 배포할 때는 `" -"SSL `__을 활성화할 것을 강력히 권장합니다." +"``--insecure`` 플래그는 안전하지 않은 통신(HTTPS가 아닌 HTTP 사용)을 활성화하" +"며 테스트 목적으로만 사용해야 합니다. 프로덕션 환경에 배포할 때는 `SSL " +"`__을 활성화할 것을 강력히 권장합니" +"다." #: ../../source/how-to-run-flower-using-docker.rst:65 msgid "" "You can use ``--help`` to view all available flags that the SuperLink " "supports:" -msgstr "'`--help``을 사용하면 SuperLink가 지원하는 모든 플래그를 볼 수 있습니다:" +msgstr "" +"'`--help``을 사용하면 SuperLink가 지원하는 모든 플래그를 볼 수 있습니다:" #: ../../source/how-to-run-flower-using-docker.rst:72 msgid "Mounting a volume to store the state on the host system" @@ -5721,49 +5776,47 @@ msgstr "호스트 시스템에 상태를 저장할 볼륨 마운트하기" #: ../../source/how-to-run-flower-using-docker.rst:74 msgid "" -"If you want to persist the state of the SuperLink on your host system, " -"all you need to do is specify a directory where you want to save the file" -" on your host system and a name for the database file. By default, the " -"SuperLink container runs with a non-root user called ``app`` with the " -"user ID ``49999``. It is recommended to create new directory and change " -"the user ID of the directory to ``49999`` to ensure the mounted directory" -" has the proper permissions. If you later want to delete the directory, " -"you can change the user ID back to the current user ID by running ``sudo " -"chown -R $USER:$(id -gn) state``." -msgstr "" -"호스트 시스템에서 SuperLink의 상태를 유지하려면 호스트 시스템에서 파일을 " -"저장할 디렉터리와 데이터베이스 파일의 이름을 지정하기만 하면 됩니다. " -"기본적으로 SuperLink 컨테이너는 사용자 ID가 ``49999``인 ``app``이라는 루트가 " -"아닌 사용자로 실행됩니다. 마운트된 디렉터리에 적절한 권한이 있는지 " -"확인하려면 새 디렉터리를 생성하고 디렉터리의 사용자 ID를 ``49999``로 " -"변경하는 것이 좋습니다. 나중에 디렉터리를 삭제하려면 ``sudo chown -R $USER:$(" -"id -gn) state``를 실행하여 사용자 ID를 현재 사용자 ID로 다시 변경할 수 " -"있습니다." +"If you want to persist the state of the SuperLink on your host system, all " +"you need to do is specify a directory where you want to save the file on " +"your host system and a name for the database file. By default, the SuperLink " +"container runs with a non-root user called ``app`` with the user ID " +"``49999``. It is recommended to create new directory and change the user ID " +"of the directory to ``49999`` to ensure the mounted directory has the proper " +"permissions. If you later want to delete the directory, you can change the " +"user ID back to the current user ID by running ``sudo chown -R $USER:$(id -" +"gn) state``." +msgstr "" +"호스트 시스템에서 SuperLink의 상태를 유지하려면 호스트 시스템에서 파일을 저장" +"할 디렉터리와 데이터베이스 파일의 이름을 지정하기만 하면 됩니다. 기본적으로 " +"SuperLink 컨테이너는 사용자 ID가 ``49999``인 ``app``이라는 루트가 아닌 사용자" +"로 실행됩니다. 마운트된 디렉터리에 적절한 권한이 있는지 확인하려면 새 디렉터" +"리를 생성하고 디렉터리의 사용자 ID를 ``49999``로 변경하는 것이 좋습니다. 나중" +"에 디렉터리를 삭제하려면 ``sudo chown -R $USER:$(id -gn) state``를 실행하여 " +"사용자 ID를 현재 사용자 ID로 다시 변경할 수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:82 msgid "" -"In the example below, we create a new directory, change the user ID and " -"tell Docker via the flag ``--volume`` to mount the local ``state`` " -"directory into the ``/app/state`` directory of the container. " -"Furthermore, we use the flag ``--database`` to specify the name of the " -"database file." +"In the example below, we create a new directory, change the user ID and tell " +"Docker via the flag ``--volume`` to mount the local ``state`` directory into " +"the ``/app/state`` directory of the container. Furthermore, we use the flag " +"``--database`` to specify the name of the database file." msgstr "" -"아래 예에서는 새 디렉터리를 생성하고, 사용자 ID를 변경하고, 플래그 " -"``--volume``을 통해 Docker에게 로컬 ``state`` 디렉터리를 컨테이너의 ``/app/" -"state`` 디렉터리에 마운트하도록 지시합니다. 또한 ``--database`` 플래그를 " -"사용하여 데이터베이스 파일의 이름을 지정합니다." +"아래 예에서는 새 디렉터리를 생성하고, 사용자 ID를 변경하고, 플래그 ``--" +"volume``을 통해 Docker에게 로컬 ``state`` 디렉터리를 컨테이너의 ``/app/" +"state`` 디렉터리에 마운트하도록 지시합니다. 또한 ``--database`` 플래그를 사용" +"하여 데이터베이스 파일의 이름을 지정합니다." #: ../../source/how-to-run-flower-using-docker.rst:95 msgid "" "As soon as the SuperLink starts, the file ``state.db`` is created in the " "``state`` directory on your host system. If the file already exists, the " -"SuperLink tries to restore the state from the file. To start the " -"SuperLink with an empty database, simply remove the ``state.db`` file." +"SuperLink tries to restore the state from the file. To start the SuperLink " +"with an empty database, simply remove the ``state.db`` file." msgstr "" -"SuperLink가 시작되자마자 호스트 시스템의 ``state`` 디렉터리에 ``state.db`` " -"파일이 생성됩니다. 파일이 이미 존재하는 경우 SuperLink는 파일에서 상태를 " -"복원하려고 시도합니다. 빈 데이터베이스로 SuperLink를 시작하려면 ``state.db`` " -"파일을 제거하면 됩니다." +"SuperLink가 시작되자마자 호스트 시스템의 ``state`` 디렉터리에 ``state.db`` 파" +"일이 생성됩니다. 파일이 이미 존재하는 경우 SuperLink는 파일에서 상태를 복원하" +"려고 시도합니다. 빈 데이터베이스로 SuperLink를 시작하려면 ``state.db`` 파일" +"을 제거하면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:100 #: ../../source/how-to-run-flower-using-docker.rst:281 @@ -5773,18 +5826,18 @@ msgstr "보안 연결을 위한 SSL 사용 설정" #: ../../source/how-to-run-flower-using-docker.rst:102 msgid "" -"To enable SSL, you will need a PEM-encoded root certificate, a PEM-" -"encoded private key and a PEM-encoded certificate chain." +"To enable SSL, you will need a PEM-encoded root certificate, a PEM-encoded " +"private key and a PEM-encoded certificate chain." msgstr "" "SSL을 사용하려면 PEM으로 인코딩된 루트 인증서, PEM으로 인코딩된 개인 키 및 " "PEM으로 인코딩된 인증서 체인이 필요합니다." #: ../../source/how-to-run-flower-using-docker.rst:106 msgid "" -"For testing purposes, you can generate your own self-signed certificates." -" The `Enable SSL connections `__ page contains a section that" -" will guide you through the process." +"For testing purposes, you can generate your own self-signed certificates. " +"The `Enable SSL connections `__ page contains a section that will " +"guide you through the process." msgstr "" "테스트 목적으로 자체 서명된 인증서를 생성할 수 있습니다. 'SSL 연결 사용 " "`__ is already installed " -"in the ``flwr/supernode`` base image, so you only need to include other " -"package dependencies in your ``requirements.txt``, such as ``torch``, " +"Note that `flwr `__ is already installed in " +"the ``flwr/supernode`` base image, so you only need to include other package " +"dependencies in your ``requirements.txt``, such as ``torch``, " "``tensorflow``, etc." msgstr "" -"`flwr `__ 는 이미 ``flwr/supernode`` 기본 " -"이미지에 설치되어 있으므로, ``torch``, ``tensorflow`` 등과 같은 다른 패키지 " +"`flwr `__ 는 이미 ``flwr/supernode`` 기본 이" +"미지에 설치되어 있으므로, ``torch``, ``tensorflow`` 등과 같은 다른 패키지 " "dependencies만 ``requirements.txt``에 포함시키면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:200 msgid "" -"Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` " -"example, create a new file called ``Dockerfile.supernode`` in ``examples" -"/quickstart-pytorch``." +"Next, we create a Dockerfile. If you use the ``quickstart-pytorch`` example, " +"create a new file called ``Dockerfile.supernode`` in ``examples/quickstart-" +"pytorch``." msgstr "" -"다음으로, Dockerfile을 생성합니다.``quickstart-pytorch`` 예제를 사용하는 " -"경우 ``examples/quickstart-pytorch``에 ``Dockerfile.supernode``라는 새 " -"파일을 생성합니다." +"다음으로, Dockerfile을 생성합니다.``quickstart-pytorch`` 예제를 사용하는 경" +"우 ``examples/quickstart-pytorch``에 ``Dockerfile.supernode``라는 새 파일을 " +"생성합니다." #: ../../source/how-to-run-flower-using-docker.rst:203 msgid "" "The ``Dockerfile.supernode`` contains the instructions that assemble the " "SuperNode image." -msgstr "``Dockerfile.supernode``에는 SuperNode 이미지를 조립하는 지침이 포함되어 " -"있습니다." +msgstr "" +"``Dockerfile.supernode``에는 SuperNode 이미지를 조립하는 지침이 포함되어 있습" +"니다." #: ../../source/how-to-run-flower-using-docker.rst:217 msgid "" -"In the first two lines, we instruct Docker to use the SuperNode image " -"tagged ``nightly`` as a base image and set our working directory to " -"``/app``. The following instructions will now be executed in the ``/app``" -" directory. Next, we install the ClientApp dependencies by copying the " -"``requirements.txt`` file into the image and run ``pip install``. In the " -"last two lines, we copy the ``client.py`` module into the image and set " -"the entry point to ``flower-client-app`` with the argument " -"``client:app``. The argument is the object reference of the ClientApp " -"(``:``) that will be run inside the ClientApp." -msgstr "" -"처음 두 줄에서는 ``nightly`` 태그가 붙은 SuperNode 이미지를 기본 이미지로 " -"사용하고 작업 디렉터리를 ``/app``로 설정하도록 Docker에 지시합니다. 이제 " -"``/app`` 디렉토리에서 다음 명령이 실행됩니다. 다음으로, ``requirements.txt`` " -"파일을 이미지에 복사하여 ClientApp dependencies 요소를 설치하고 ``pip " -"install``을 실행합니다. 마지막 두 줄에서 ``client.py`` 모듈을 이미지에 " -"복사하고 ``client:app`` 인수를 사용하여 진입점을 ``flower-client-app``로 " -"설정합니다. 인수는 클라이언트앱 내부에서 실행될 클라이언트앱의 객체 참조 " -"(``:``) 입니다." +"In the first two lines, we instruct Docker to use the SuperNode image tagged " +"``nightly`` as a base image and set our working directory to ``/app``. The " +"following instructions will now be executed in the ``/app`` directory. Next, " +"we install the ClientApp dependencies by copying the ``requirements.txt`` " +"file into the image and run ``pip install``. In the last two lines, we copy " +"the ``client.py`` module into the image and set the entry point to ``flower-" +"client-app`` with the argument ``client:app``. The argument is the object " +"reference of the ClientApp (``:``) that will be run " +"inside the ClientApp." +msgstr "" +"처음 두 줄에서는 ``nightly`` 태그가 붙은 SuperNode 이미지를 기본 이미지로 사" +"용하고 작업 디렉터리를 ``/app``로 설정하도록 Docker에 지시합니다. 이제 ``/" +"app`` 디렉토리에서 다음 명령이 실행됩니다. 다음으로, ``requirements.txt`` 파" +"일을 이미지에 복사하여 ClientApp dependencies 요소를 설치하고 ``pip install``" +"을 실행합니다. 마지막 두 줄에서 ``client.py`` 모듈을 이미지에 복사하고 " +"``client:app`` 인수를 사용하여 진입점을 ``flower-client-app``로 설정합니다. " +"인수는 클라이언트앱 내부에서 실행될 클라이언트앱의 객체 참조 (``:" +"``) 입니다." #: ../../source/how-to-run-flower-using-docker.rst:226 msgid "Building the SuperNode Docker image" @@ -5946,11 +5999,11 @@ msgstr "SuperNode Docker 이미지 빌드" #: ../../source/how-to-run-flower-using-docker.rst:228 msgid "" -"Next, we build the SuperNode Docker image by running the following " -"command in the directory where Dockerfile and ClientApp code are located." +"Next, we build the SuperNode Docker image by running the following command " +"in the directory where Dockerfile and ClientApp code are located." msgstr "" -"다음으로, Dockerfile 및 ClientApp 코드가 있는 디렉터리에서 다음 명령을 " -"실행하여 SuperNode Docker 이미지를 빌드합니다." +"다음으로, Dockerfile 및 ClientApp 코드가 있는 디렉터리에서 다음 명령을 실행하" +"여 SuperNode Docker 이미지를 빌드합니다." #: ../../source/how-to-run-flower-using-docker.rst:235 msgid "" @@ -5958,9 +6011,9 @@ msgid "" "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" -"이미지에 ``flwr_supernode``라는 이름을 붙이고 ``0.0.1`` 태그를 붙였습니다. " -"여기서 선택한 값은 예시일 뿐이라는 점을 기억하세요. 필요에 따라 변경할 수 " -"있습니다." +"이미지에 ``flwr_supernode``라는 이름을 붙이고 ``0.0.1`` 태그를 붙였습니다. 여" +"기서 선택한 값은 예시일 뿐이라는 점을 기억하세요. 필요에 따라 변경할 수 있습" +"니다." #: ../../source/how-to-run-flower-using-docker.rst:240 msgid "Running the SuperNode Docker image" @@ -5983,9 +6036,10 @@ msgstr "``docker run``: 새 Docker 컨테이너를 실행하는 명령입니다. #: ../../source/how-to-run-flower-using-docker.rst:253 #: ../../source/how-to-run-flower-using-docker.rst:370 msgid "" -"``--rm``: This option specifies that the container should be " -"automatically removed when it stops." -msgstr "``--rm``: 이 옵션은 컨테이너가 중지될 때 자동으로 제거되도록 지정합니다." +"``--rm``: This option specifies that the container should be automatically " +"removed when it stops." +msgstr "" +"``--rm``: 이 옵션은 컨테이너가 중지될 때 자동으로 제거되도록 지정합니다." #: ../../source/how-to-run-flower-using-docker.rst:254 msgid "``flwr_supernode:0.0.1``: The name the tag of the Docker image to use." @@ -5998,26 +6052,28 @@ msgstr "``--insecure``: 이 옵션은 보안되지 않은 통신을 활성화합 #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--superlink 192.168.1.100:9092``: This option specifies the address of " -"the SuperLinks Fleet" -msgstr "``--superlink 192.168.1.100:9092``: 이 옵션은 SuperLinks Fleet의 주소를 " -"지정합니다" +"``--superlink 192.168.1.100:9092``: This option specifies the address of the " +"SuperLinks Fleet" +msgstr "" +"``--superlink 192.168.1.100:9092``: 이 옵션은 SuperLinks Fleet의 주소를 지정" +"합니다" #: ../../source/how-to-run-flower-using-docker.rst msgid "API to connect to. Remember to update it with your SuperLink IP." -msgstr "API에 연결할 수 있습니다. SuperLink IP로 업데이트하는 것을 잊지 마세요." +msgstr "" +"API에 연결할 수 있습니다. SuperLink IP로 업데이트하는 것을 잊지 마세요." #: ../../source/how-to-run-flower-using-docker.rst:269 msgid "" -"To test running Flower locally, you can create a `bridge network " -"`__, use the ``--network`` argument and pass the " -"name of the Docker network to run your SuperNodes." +"To test running Flower locally, you can create a `bridge network `__, use the ``--network`` argument and pass the name of the Docker " +"network to run your SuperNodes." msgstr "" "로컬에서 Flower를 실행하는 것을 테스트하려면 `bridge network `__를 생성하고 ``--network`` argument를 사용하고 SuperNodes를 " -"실행할 Docker 네트워크의 이름을 전달하면 됩니다." +"networks>`__를 생성하고 ``--network`` argument를 사용하고 SuperNodes를 실행" +"할 Docker 네트워크의 이름을 전달하면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:273 msgid "" @@ -6031,21 +6087,21 @@ msgstr "" msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your SuperNode container." -msgstr "SSL을 사용하려면 PEM 인코딩된 루트 인증서를 SuperNode 컨테이너에 마운트해야 " +msgstr "" +"SSL을 사용하려면 PEM 인코딩된 루트 인증서를 SuperNode 컨테이너에 마운트해야 " "합니다." #: ../../source/how-to-run-flower-using-docker.rst:285 msgid "" -"Assuming the certificate already exists locally, we can use the flag " -"``--volume`` to mount the local certificate into the container's " -"``/app/`` directory. This allows the SuperNode to access the certificate " -"within the container. Use the ``--root-certificates`` flag when starting " -"the container." +"Assuming the certificate already exists locally, we can use the flag ``--" +"volume`` to mount the local certificate into the container's ``/app/`` " +"directory. This allows the SuperNode to access the certificate within the " +"container. Use the ``--root-certificates`` flag when starting the container." msgstr "" -"인증서가 이미 로컬에 존재한다고 가정하면, ``--volume`` 플래그를 사용하여 " -"로컬 인증서를 컨테이너의 ``/app/`` 디렉터리에 마운트할 수 있습니다. 이렇게 " -"하면 SuperNode가 컨테이너 내의 인증서에 액세스할 수 있습니다. 컨테이너를 " -"시작할 때 ``--root-certificates`` 플래그를 사용하세요." +"인증서가 이미 로컬에 존재한다고 가정하면, ``--volume`` 플래그를 사용하여 로" +"컬 인증서를 컨테이너의 ``/app/`` 디렉터리에 마운트할 수 있습니다. 이렇게 하" +"면 SuperNode가 컨테이너 내의 인증서에 액세스할 수 있습니다. 컨테이너를 시작" +"할 때 ``--root-certificates`` 플래그를 사용하세요." #: ../../source/how-to-run-flower-using-docker.rst:297 msgid "Flower ServerApp" @@ -6053,29 +6109,30 @@ msgstr "Flower 서버앱" #: ../../source/how-to-run-flower-using-docker.rst:299 msgid "" -"The procedure for building and running a ServerApp image is almost " -"identical to the SuperNode image." -msgstr "ServerApp 이미지를 빌드하고 실행하는 절차는 SuperNode 이미지와 거의 " -"동일합니다." +"The procedure for building and running a ServerApp image is almost identical " +"to the SuperNode image." +msgstr "" +"ServerApp 이미지를 빌드하고 실행하는 절차는 SuperNode 이미지와 거의 동일합니" +"다." #: ../../source/how-to-run-flower-using-docker.rst:301 msgid "" -"Similar to the SuperNode image, the ServerApp Docker image comes with a " -"pre-installed version of Flower and serves as a base for building your " -"own ServerApp image." +"Similar to the SuperNode image, the ServerApp Docker image comes with a pre-" +"installed version of Flower and serves as a base for building your own " +"ServerApp image." msgstr "" "SuperNode 이미지와 마찬가지로 ServerApp Docker 이미지는 Flower의 사전 설치된 " -"버전과 함께 제공되며, 자체 ServerApp 이미지를 구축하기 위한 기본 역할을 " -"합니다." +"버전과 함께 제공되며, 자체 ServerApp 이미지를 구축하기 위한 기본 역할을 합니" +"다." #: ../../source/how-to-run-flower-using-docker.rst:304 msgid "" -"We will use the same ``quickstart-pytorch`` example as we do in the " -"Flower SuperNode section. If you have not already done so, please follow " -"the `SuperNode Prerequisites`_ before proceeding." +"We will use the same ``quickstart-pytorch`` example as we do in the Flower " +"SuperNode section. If you have not already done so, please follow the " +"`SuperNode Prerequisites`_ before proceeding." msgstr "" -"여기서는 Flower SuperNode 섹션에서와 동일한`quickstart-pytorch`` 예제를 " -"사용하겠습니다. 아직 수행하지 않았다면 계속 진행하기 전에 `SuperNode " +"여기서는 Flower SuperNode 섹션에서와 동일한`quickstart-pytorch`` 예제를 사용" +"하겠습니다. 아직 수행하지 않았다면 계속 진행하기 전에 `SuperNode " "Prerequisites`_ 을 따르세요." #: ../../source/how-to-run-flower-using-docker.rst:309 @@ -6086,8 +6143,8 @@ msgstr "ServerApp Dockerfile 만들기" msgid "" "First, we need to create a Dockerfile in the directory where the " "``ServerApp`` code is located. If you use the ``quickstart-pytorch`` " -"example, create a new file called ``Dockerfile.serverapp`` in ``examples" -"/quickstart-pytorch``." +"example, create a new file called ``Dockerfile.serverapp`` in ``examples/" +"quickstart-pytorch``." msgstr "" "먼저, ``ServerApp`` 코드가 있는 디렉토리에 Docker파일을 생성해야 합니다. " "``quickstart-pytorch`` 예제를 사용하는 경우 ``examples/quickstart-pytorch``" @@ -6097,24 +6154,24 @@ msgstr "" msgid "" "The ``Dockerfile.serverapp`` contains the instructions that assemble the " "ServerApp image." -msgstr "``Dockerfile.serverapp``에는 ServerApp 이미지를 합치는 지침이 포함되어 " -"있습니다." +msgstr "" +"``Dockerfile.serverapp``에는 ServerApp 이미지를 합치는 지침이 포함되어 있습니" +"다." #: ../../source/how-to-run-flower-using-docker.rst:335 msgid "" -"In the first two lines, we instruct Docker to use the ServerApp image " -"tagged ``1.8.0`` as a base image and set our working directory to " -"``/app``. The following instructions will now be executed in the ``/app``" -" directory. In the last two lines, we copy the ``server.py`` module into " -"the image and set the entry point to ``flower-server-app`` with the " -"argument ``server:app``. The argument is the object reference of the " -"ServerApp (``:``) that will be run inside the " -"ServerApp container." -msgstr "" -"처음 두 줄에서는 ``1.8.0`` 태그가 붙은 ServerApp 이미지를 기본 이미지로 " -"사용하고 작업 디렉터리를 ``/app``로 설정하도록 Docker에 지시합니다. 이제 " -"``/app`` 디렉토리에서 다음 명령이 실행됩니다. 마지막 두 줄에서는 ``server." -"py`` 모듈을 이미지에 복사하고 ``server:app`` argument를 사용하여 진입점을 " +"In the first two lines, we instruct Docker to use the ServerApp image tagged " +"``1.8.0`` as a base image and set our working directory to ``/app``. The " +"following instructions will now be executed in the ``/app`` directory. In " +"the last two lines, we copy the ``server.py`` module into the image and set " +"the entry point to ``flower-server-app`` with the argument ``server:app``. " +"The argument is the object reference of the ServerApp (``:" +"``) that will be run inside the ServerApp container." +msgstr "" +"처음 두 줄에서는 ``1.8.0`` 태그가 붙은 ServerApp 이미지를 기본 이미지로 사용" +"하고 작업 디렉터리를 ``/app``로 설정하도록 Docker에 지시합니다. 이제 ``/" +"app`` 디렉토리에서 다음 명령이 실행됩니다. 마지막 두 줄에서는 ``server.py`` " +"모듈을 이미지에 복사하고 ``server:app`` argument를 사용하여 진입점을 " "``flower-server-app``로 설정합니다. 인수는 ServerApp 컨테이너 내에서 실행될 " "ServerApp의 객체 참조(``:``)입니다." @@ -6124,11 +6181,11 @@ msgstr "ServerApp Docker 이미지 빌드" #: ../../source/how-to-run-flower-using-docker.rst:345 msgid "" -"Next, we build the ServerApp Docker image by running the following " -"command in the directory where Dockerfile and ServerApp code are located." +"Next, we build the ServerApp Docker image by running the following command " +"in the directory where Dockerfile and ServerApp code are located." msgstr "" -"다음으로, Docker파일과 ServerApp 코드가 있는 디렉터리에서 다음 명령을 " -"실행하여 ServerApp Docker 이미지를 빌드합니다." +"다음으로, Docker파일과 ServerApp 코드가 있는 디렉터리에서 다음 명령을 실행하" +"여 ServerApp Docker 이미지를 빌드합니다." #: ../../source/how-to-run-flower-using-docker.rst:352 msgid "" @@ -6136,453 +6193,600 @@ msgid "" "Remember that the here chosen values only serve as an example. You can " "change them to your needs." msgstr "" +"이미지에``flwr_serverapp``이라는 이름을 붙이고 ``0.0.1``이라는 태그를 붙였습" +"니다. 여기서 선택한 값은 예시일 뿐이라는 점을 기억하세요. 필요에 따라 변경할 " +"수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:357 msgid "Running the ServerApp Docker image" -msgstr "" +msgstr "ServerApp Docker 이미지 실행" #: ../../source/how-to-run-flower-using-docker.rst:359 msgid "Now that we have built the ServerApp image, we can finally run it." -msgstr "" +msgstr "이제 ServerApp 이미지를 빌드했으니 이제 실행할 수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:371 msgid "``flwr_serverapp:0.0.1``: The name the tag of the Docker image to use." -msgstr "" +msgstr "``flwr_serverapp:0.0.1``: 사용할 Docker 이미지의 태그 이름입니다." #: ../../source/how-to-run-flower-using-docker.rst msgid "" -"``--superlink 192.168.1.100:9091``: This option specifies the address of " -"the SuperLinks Driver" +"``--superlink 192.168.1.100:9091``: This option specifies the address of the " +"SuperLinks Driver" msgstr "" +"``--superlink 192.168.1.100:9091``: 이 옵션은 SuperLinks 드라이버의 주소를 지" +"정합니다" #: ../../source/how-to-run-flower-using-docker.rst:385 msgid "" -"To test running Flower locally, you can create a `bridge network " -"`__, use the ``--network`` argument and pass the " -"name of the Docker network to run your ServerApps." +"To test running Flower locally, you can create a `bridge network `__, use the ``--network`` argument and pass the name of the Docker " +"network to run your ServerApps." msgstr "" +"로컬에서 Flower를 실행하는 것을 테스트하려면 `bridge network `__,를 생성하고 ``--network`` argument를 사용하여 ServerApp을 실행" +"할 Docker 네트워크의 이름을 전달하면 됩니다." #: ../../source/how-to-run-flower-using-docker.rst:389 msgid "" "Any argument that comes after the tag is passed to the Flower ServerApp " "binary. To see all available flags that the ServerApp supports, run:" msgstr "" +"태그 뒤에 오는 모든 argument는 Flower ServerApp 바이너리에 전달됩니다. " +"ServerApp에서 지원하는 사용 가능한 모든 플래그를 보려면 실행하세요:" #: ../../source/how-to-run-flower-using-docker.rst:399 msgid "" "To enable SSL, we will need to mount a PEM-encoded root certificate into " "your ServerApp container." msgstr "" +"SSL을 사용하려면 PEM 인코딩된 루트 인증서를 ServerApp 컨테이너에 마운트해야 " +"합니다." #: ../../source/how-to-run-flower-using-docker.rst:401 msgid "" -"Assuming the certificate already exists locally, we can use the flag " -"``--volume`` to mount the local certificate into the container's " -"``/app/`` directory. This allows the ServerApp to access the certificate " -"within the container. Use the ``--root-certificates`` flags when starting" -" the container." +"Assuming the certificate already exists locally, we can use the flag ``--" +"volume`` to mount the local certificate into the container's ``/app/`` " +"directory. This allows the ServerApp to access the certificate within the " +"container. Use the ``--root-certificates`` flags when starting the container." msgstr "" +"인증서가 이미 로컬에 존재한다고 가정하면, ``--volume`` 플래그를 사용하여 로" +"컬 인증서를 컨테이너의 ``/app/`` 디렉터리에 마운트할 수 있습니다. 이렇게 하" +"면 ServerApp이 컨테이너 내의 인증서에 액세스할 수 있습니다. 컨테이너를 시작" +"할 때 ``--root-certificates`` 플래그를 사용하세요." #: ../../source/how-to-run-flower-using-docker.rst:412 msgid "Advanced Docker options" -msgstr "" +msgstr "고급 Docker 옵션" #: ../../source/how-to-run-flower-using-docker.rst:415 msgid "Run with root user privileges" -msgstr "" +msgstr "루트 사용자 권한으로 실행" #: ../../source/how-to-run-flower-using-docker.rst:417 msgid "" -"Flower Docker images, by default, run with a non-root user " -"(username/groupname: ``app``, UID/GID: ``49999``). Using root user is not" -" recommended unless it is necessary for specific tasks during the build " -"process. Always make sure to run the container as a non-root user in " -"production to maintain security best practices." +"Flower Docker images, by default, run with a non-root user (username/" +"groupname: ``app``, UID/GID: ``49999``). Using root user is not recommended " +"unless it is necessary for specific tasks during the build process. Always " +"make sure to run the container as a non-root user in production to maintain " +"security best practices." msgstr "" +"기본적으로 Flower Docker 이미지는 루트 사용자가 아닌 사용자(사용자명/그룹명:" +"``app``, UID/GID: ``49999``)로 실행됩니다. 빌드 프로세스 중 특정 작업에 필요" +"한 경우가 아니라면 루트 사용자를 사용하지 않는 것이 좋습니다. 보안 모범 사례" +"를 유지하려면 항상 프로덕션 환경에서 루트 사용자가 아닌 사용자로 컨테이너를 " +"실행해야 합니다." #: ../../source/how-to-run-flower-using-docker.rst:422 msgid "**Run a container with root user privileges**" -msgstr "" +msgstr "**루트 사용자 권한으로 컨테이너 실행하기**" #: ../../source/how-to-run-flower-using-docker.rst:424 msgid "" "Run the Docker image with the ``-u`` flag and specify ``root`` as the " "username:" msgstr "" +"``-u`` 플래그를 사용하여 Docker 이미지를 실행하고 사용자 이름으로 ``root``를 " +"지정합니다:" #: ../../source/how-to-run-flower-using-docker.rst:430 msgid "This command will run the Docker container with root user privileges." -msgstr "" +msgstr "이 명령은 루트 사용자 권한으로 Docker 컨테이너를 실행합니다." #: ../../source/how-to-run-flower-using-docker.rst:432 msgid "**Run the build process with root user privileges**" -msgstr "" +msgstr "**루트 사용자 권한으로 빌드 프로세스를 실행합니다**" #: ../../source/how-to-run-flower-using-docker.rst:434 msgid "" "If you want to switch to the root user during the build process of the " -"Docker image to install missing system dependencies, you can use the " -"``USER root`` directive within your Dockerfile." +"Docker image to install missing system dependencies, you can use the ``USER " +"root`` directive within your Dockerfile." msgstr "" +"Docker 이미지 빌드 과정에서 루트 사용자로 전환하여 누락된 시스템 의존성을 " +"설치하려면 Dockerfile 내에서 ``USER root`` 지시어를 사용할 수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:454 msgid "Using a different Flower version" -msgstr "" +msgstr "다른 Flower 버전 사용" #: ../../source/how-to-run-flower-using-docker.rst:456 msgid "" "If you want to use a different version of Flower, for example Flower " -"nightly, you can do so by changing the tag. All available versions are on" -" `Docker Hub `__." +"nightly, you can do so by changing the tag. All available versions are on " +"`Docker Hub `__." msgstr "" +"다른 버전의 Flower를 사용하려면 태그를 변경하여 사용할 수 있습니다(예: " +"Flower nightly). 사용 가능한 모든 버전은 `Docker Hub `__에 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:460 msgid "Pinning a Docker image to a specific version" -msgstr "" +msgstr "특정 버전에 Docker 이미지 고정하기" #: ../../source/how-to-run-flower-using-docker.rst:462 msgid "" "It may happen that we update the images behind the tags. Such updates " "usually include security updates of system dependencies that should not " -"change the functionality of Flower. However, if you want to ensure that " -"you always use the same image, you can specify the hash of the image " -"instead of the tag." +"change the functionality of Flower. However, if you want to ensure that you " +"always use the same image, you can specify the hash of the image instead of " +"the tag." msgstr "" +"태그 뒤에 있는 이미지가 업데이트될 수 있습니다. 이러한 업데이트에는 " +"일반적으로 Flower의 기능을 변경해서는 안 되는 시스템 의존성에 대한 보안 " +"업데이트가 포함됩니다. 그러나 항상 동일한 이미지를 사용하려면 태그 대신 " +"이미지의 해시를 지정할 수 있습니다." #: ../../source/how-to-run-flower-using-docker.rst:467 msgid "" "The following command returns the current image hash referenced by the " "``superlink:1.8.0`` tag:" msgstr "" +"다음 명령은 ``superlink:1.8.0`` 태그가 참조하는 현재 이미지 해시를 반환합니" +"다:" #: ../../source/how-to-run-flower-using-docker.rst:474 msgid "Next, we can pin the hash when running a new SuperLink container:" -msgstr "" +msgstr "다음으로, 새 SuperLink 컨테이너를 실행할 때 해시를 고정할 수 있습니다:" #: ../../source/how-to-run-flower-using-docker.rst:483 msgid "Setting environment variables" -msgstr "" +msgstr "환경 변수 설정" #: ../../source/how-to-run-flower-using-docker.rst:485 msgid "" "To set a variable inside a Docker container, you can use the ``-e " "=`` flag." msgstr "" +"Docker 컨테이너 내에서 변수를 설정하려면 ``-e =`` 플래그를 사용" +"하면 됩니다." #: ../../source/how-to-run-simulations.rst:2 msgid "Run simulations" -msgstr "" +msgstr "시뮬레이션 실행" #: ../../source/how-to-run-simulations.rst:8 msgid "" "Simulating Federated Learning workloads is useful for a multitude of use-" -"cases: you might want to run your workload on a large cohort of clients " -"but without having to source, configure and mange a large number of " -"physical devices; you might want to run your FL workloads as fast as " -"possible on the compute systems you have access to without having to go " -"through a complex setup process; you might want to validate your " -"algorithm on different scenarios at varying levels of data and system " -"heterogeneity, client availability, privacy budgets, etc. These are among" -" some of the use-cases where simulating FL workloads makes sense. Flower " -"can accommodate these scenarios by means of its `VirtualClientEngine " -"`_ or " -"VCE." -msgstr "" +"cases: you might want to run your workload on a large cohort of clients but " +"without having to source, configure and mange a large number of physical " +"devices; you might want to run your FL workloads as fast as possible on the " +"compute systems you have access to without having to go through a complex " +"setup process; you might want to validate your algorithm on different " +"scenarios at varying levels of data and system heterogeneity, client " +"availability, privacy budgets, etc. These are among some of the use-cases " +"where simulating FL workloads makes sense. Flower can accommodate these " +"scenarios by means of its `VirtualClientEngine `_ or VCE." +msgstr "" +"Federated 학습 워크로드 시뮬레이션은 다양한 사용 사례에 유용합니다. 대규모 클" +"라이언트 집단에서 워크로드를 실행하되 많은 수의 물리적 장치를 소싱, 구성 및 " +"관리할 필요가 없는 경우, 복잡한 설정 과정을 거치지 않고도 액세스 가능한 컴퓨" +"팅 시스템에서 최대한 빠르게 FL 워크로드를 실행하려는 경우, 다양한 수준의 데이" +"터 및 시스템 이질성, 클라이언트 가용성, 개인정보 예산 등의 다양한 시나리오에" +"서 알고리즘을 검증하려는 경우 등 여러 가지 사용 사례에 유용합니다. 이러한 사" +"례는 FL 워크로드 시뮬레이션이 적합한 사용 사례 중 일부입니다. Flower는 " +"`VirtualClientEngine `_ 또는 VCE를 통해 이러한 시나리오를 수용할 수 있습니다." #: ../../source/how-to-run-simulations.rst:10 msgid "" -"The :code:`VirtualClientEngine` schedules, launches and manages `virtual`" -" clients. These clients are identical to `non-virtual` clients (i.e. the " -"ones you launch via the command `flwr.client.start_client `_) in the sense that they can be configure by " -"creating a class inheriting, for example, from `flwr.client.NumPyClient " -"`_ and therefore behave in an " -"identical way. In addition to that, clients managed by the " -":code:`VirtualClientEngine` are:" -msgstr "" +"The :code:`VirtualClientEngine` schedules, launches and manages `virtual` " +"clients. These clients are identical to `non-virtual` clients (i.e. the ones " +"you launch via the command `flwr.client.start_client `_) in the sense that they can be configure by creating a " +"class inheriting, for example, from `flwr.client.NumPyClient `_ and therefore behave in an identical way. In " +"addition to that, clients managed by the :code:`VirtualClientEngine` are:" +msgstr "" +":code:`VirtualClientEngine`은 `virtual` 클라이언트를 예약, 실행 및 관리합니" +"다. 이러한 클라이언트는 `non-virtual` 클라이언트(예: `flwr.client." +"start_client `_ 명령을 통해 실행하는 클라이언" +"트)와 동일하며, `flwr.client.NumPyClient `_에서 상속하는 클래스 생성으로 구성될 수 있으므로 동일한 방식으" +"로 동작합니다. 그 외에도 :code:`VirtualClientEngine`에 의해 관리되는 클라이언" +"트는 다음과 같습니다:" #: ../../source/how-to-run-simulations.rst:12 msgid "" -"resource-aware: this means that each client gets assigned a portion of " -"the compute and memory on your system. You as a user can control this at " -"the beginning of the simulation and allows you to control the degree of " +"resource-aware: this means that each client gets assigned a portion of the " +"compute and memory on your system. You as a user can control this at the " +"beginning of the simulation and allows you to control the degree of " "parallelism of your Flower FL simulation. The fewer the resources per " "client, the more clients can run concurrently on the same hardware." msgstr "" +"resource-aware: 이는 각 클라이언트가 시스템에서 컴퓨팅 및 메모리의 일부를 할" +"당받는다는 것을 의미합니다. 사용자는 시뮬레이션을 시작할 때 이를 제어할 수 있" +"으며, 이를 통해 Flower FL 시뮬레이션의 병렬 처리 정도를 제어할 수 있습니다. " +"클라이언트당 리소스가 적을수록 동일한 하드웨어에서 더 많은 클라이언트를 동시" +"에 실행할 수 있습니다." #: ../../source/how-to-run-simulations.rst:13 msgid "" -"self-managed: this means that you as a user do not need to launch clients" -" manually, instead this gets delegated to :code:`VirtualClientEngine`'s " +"self-managed: this means that you as a user do not need to launch clients " +"manually, instead this gets delegated to :code:`VirtualClientEngine`'s " "internals." msgstr "" +"self-managed: 이는 사용자가 클라이언트를 수동으로 실행할 필요가 없으며, 대" +"신 :code:`VirtualClientEngine`의 내부에 위임된다는 의미입니다." #: ../../source/how-to-run-simulations.rst:14 msgid "" -"ephemeral: this means that a client is only materialized when it is " -"required in the FL process (e.g. to do `fit() `_). The object is destroyed afterwards," -" releasing the resources it was assigned and allowing in this way other " -"clients to participate." +"ephemeral: this means that a client is only materialized when it is required " +"in the FL process (e.g. to do `fit() `_). The object is destroyed afterwards, releasing the resources it was " +"assigned and allowing in this way other clients to participate." msgstr "" +"ephemeral: 이는 클라이언트가 FL 프로세스에서 필요할 때만 구체화됨을 의미합니" +"다(예: `fit() `_을 수행하기 위해). " +"객체는 나중에 소멸되어 할당된 리소스를 해제하고 다른 클라이언트가 참여할 수 " +"있도록 허용합니다." #: ../../source/how-to-run-simulations.rst:16 msgid "" "The :code:`VirtualClientEngine` implements `virtual` clients using `Ray " "`_, an open-source framework for scalable Python " -"workloads. In particular, Flower's :code:`VirtualClientEngine` makes use " -"of `Actors `_ to " -"spawn `virtual` clients and run their workload." +"workloads. In particular, Flower's :code:`VirtualClientEngine` makes use of " +"`Actors `_ to spawn " +"`virtual` clients and run their workload." msgstr "" +":code:`VirtualClientEngine`은 확장 가능한 파이썬 워크로드를 위한 오픈 소스 프" +"레임워크인 `Ray `_를 사용하여 `virtual` 클라이언트를 구" +"현합니다. 특히 Flower의 :code:`VirtualClientEngine`은 `Actors `_를 사용하여 `virtual` 클라이언트를 생" +"성하고 해당 워크로드를 실행합니다." #: ../../source/how-to-run-simulations.rst:20 msgid "Launch your Flower simulation" -msgstr "" +msgstr "Flower 시뮬레이션 시작" #: ../../source/how-to-run-simulations.rst:22 msgid "" -"Running Flower simulations still require you to define your client class," -" a strategy, and utility functions to download and load (and potentially " -"partition) your dataset. With that out of the way, launching your " -"simulation is done with `start_simulation `_ and a minimal example looks" -" as follows:" +"Running Flower simulations still require you to define your client class, a " +"strategy, and utility functions to download and load (and potentially " +"partition) your dataset. With that out of the way, launching your simulation " +"is done with `start_simulation `_ and a minimal example looks as follows:" msgstr "" +"Flower 시뮬레이션을 실행하려면 여전히 클라이언트 클래스, 전략 및 유틸리티 함" +"수를 정의하여 데이터 세트를 다운로드하고 로드(및 파티션)해야 합니다. 이 작업" +"을 마친 후 시뮬레이션을 시작하려면 `start_simulation `_을 사용하면 되며, 최소한의 예시는 다음과 같습니" +"다:" #: ../../source/how-to-run-simulations.rst:44 msgid "VirtualClientEngine resources" -msgstr "" +msgstr "VirtualClientEngine 리소스" #: ../../source/how-to-run-simulations.rst:45 msgid "" -"By default the VCE has access to all system resources (i.e. all CPUs, all" -" GPUs, etc) since that is also the default behavior when starting Ray. " -"However, in some settings you might want to limit how many of your system" -" resources are used for simulation. You can do this via the " -":code:`ray_init_args` input argument to :code:`start_simulation` which " -"the VCE internally passes to Ray's :code:`ray.init` command. For a " -"complete list of settings you can configure check the `ray.init " -"`_" -" documentation. Do not set :code:`ray_init_args` if you want the VCE to " -"use all your system's CPUs and GPUs." -msgstr "" +"By default the VCE has access to all system resources (i.e. all CPUs, all " +"GPUs, etc) since that is also the default behavior when starting Ray. " +"However, in some settings you might want to limit how many of your system " +"resources are used for simulation. You can do this via the :code:" +"`ray_init_args` input argument to :code:`start_simulation` which the VCE " +"internally passes to Ray's :code:`ray.init` command. For a complete list of " +"settings you can configure check the `ray.init `_ documentation. Do not set :" +"code:`ray_init_args` if you want the VCE to use all your system's CPUs and " +"GPUs." +msgstr "" +"기본적으로 VCE는 모든 시스템 리소스(예: 모든 CPU, 모든 GPU 등)에 액세스할 수 " +"있으며, 이는 Ray를 시작할 때의 기본 동작이기도 합니다. 그러나 일부 설정에서" +"는 시뮬레이션에 사용되는 시스템 리소스의 수를 제한하고 싶을 수 있습니다. 이 " +"설정은 VCE가 내부적으로 Ray의 :code:`ray.init` 명령에 전달하는 :code:" +"`start_simulation`에 대한 :code:`ray_init_args` 입력 인수를 통해 수행할 수 있" +"습니다. 구성할 수 있는 전체 설정 목록은 `ray.init `_ 설명서를 확인하세요. VCE가 " +"시스템의 모든 CPU와 GPU를 사용하도록 하려면 :code:`ray_init_args`를 설정하지 " +"마세요." #: ../../source/how-to-run-simulations.rst:62 msgid "Assigning client resources" -msgstr "" +msgstr "클라이언트 리소스 할당" #: ../../source/how-to-run-simulations.rst:63 msgid "" -"By default the :code:`VirtualClientEngine` assigns a single CPU core (and" -" nothing else) to each virtual client. This means that if your system has" -" 10 cores, that many virtual clients can be concurrently running." +"By default the :code:`VirtualClientEngine` assigns a single CPU core (and " +"nothing else) to each virtual client. This means that if your system has 10 " +"cores, that many virtual clients can be concurrently running." msgstr "" +"기본적으로 :code:`VirtualClientEngine`은 각 가상 클라이언트에 단일 CPU 코어" +"를 할당합니다(그 외에는 아무것도 할당하지 않음). 즉, 시스템에 코어가 10개인 " +"경우 그만큼 많은 가상 클라이언트를 동시에 실행할 수 있습니다." #: ../../source/how-to-run-simulations.rst:65 msgid "" -"More often than not, you would probably like to adjust the resources your" -" clients get assigned based on the complexity (i.e. compute and memory " -"footprint) of your FL workload. You can do so when starting your " -"simulation by setting the argument `client_resources` to " -"`start_simulation `_." -" Two keys are internally used by Ray to schedule and spawn workloads (in " -"our case Flower clients):" +"More often than not, you would probably like to adjust the resources your " +"clients get assigned based on the complexity (i.e. compute and memory " +"footprint) of your FL workload. You can do so when starting your simulation " +"by setting the argument `client_resources` to `start_simulation `_. Two keys are internally used " +"by Ray to schedule and spawn workloads (in our case Flower clients):" msgstr "" +"대부분의 경우 FL 워크로드의 복잡성(즉, 컴퓨팅 및 메모리 사용량)에 따라 클라이" +"언트에 할당되는 리소스를 조정하고 싶을 것입니다. 시뮬레이션을 시작할 때 " +"`client_resources` argument를 `start_simulation `_로 설정하여 이를 수행할 수 있습니다. Ray는 내부" +"적으로 두 개의 키를 사용하여 워크로드(이 경우 Flower 클라이언트)를 스케줄링하" +"고 스폰합니다:" #: ../../source/how-to-run-simulations.rst:67 msgid ":code:`num_cpus` indicates the number of CPU cores a client would get." msgstr "" +":code:`num_cpus`는 클라이언트에서 사용할 수 있는 CPU 코어 수를 나타냅니다." #: ../../source/how-to-run-simulations.rst:68 msgid "" ":code:`num_gpus` indicates the **ratio** of GPU memory a client gets " "assigned." msgstr "" +":code:`num_gpus`는 클라이언트에 할당되는 GPU 메모리의 **비율**을 나타냅니다." #: ../../source/how-to-run-simulations.rst:70 msgid "Let's see a few examples:" -msgstr "" +msgstr "몇 가지 예를 살펴보겠습니다:" #: ../../source/how-to-run-simulations.rst:89 msgid "" "While the :code:`client_resources` can be used to control the degree of " "concurrency in your FL simulation, this does not stop you from running " -"dozens, hundreds or even thousands of clients in the same round and " -"having orders of magnitude more `dormant` (i.e. not participating in a " -"round) clients. Let's say you want to have 100 clients per round but your" -" system can only accommodate 8 clients concurrently. The " -":code:`VirtualClientEngine` will schedule 100 jobs to run (each " -"simulating a client sampled by the strategy) and then will execute them " -"in a resource-aware manner in batches of 8." -msgstr "" +"dozens, hundreds or even thousands of clients in the same round and having " +"orders of magnitude more `dormant` (i.e. not participating in a round) " +"clients. Let's say you want to have 100 clients per round but your system " +"can only accommodate 8 clients concurrently. The :code:`VirtualClientEngine` " +"will schedule 100 jobs to run (each simulating a client sampled by the " +"strategy) and then will execute them in a resource-aware manner in batches " +"of 8." +msgstr "" +"code:`client_resources`를 사용하여 FL 시뮬레이션의 동시성 정도를 제어할 수 있" +"지만, 동일한 라운드에서 수십, 수백 또는 수천 개의 클라이언트를 실행하고 훨씬 " +"더 많은 '휴면'(즉, 라운드에 참여하지 않는) 클라이언트를 보유하는 것을 막을 수" +"는 없습니다. 라운드당 100명의 클라이언트를 받고 싶지만 시스템이 동시에 8명의 " +"클라이언트만 수용할 수 있다고 가정해 봅시다. code:`VirtualClientEngine`은 실" +"행할 100개의 작업(각각 전략에서 샘플링한 클라이언트를 시뮬레이션)을 예약한 다" +"음 리소스 인식 방식으로 8개씩 일괄적으로 실행합니다." #: ../../source/how-to-run-simulations.rst:91 msgid "" "To understand all the intricate details on how resources are used to " -"schedule FL clients and how to define custom resources, please take a " -"look at the `Ray documentation `_." +"schedule FL clients and how to define custom resources, please take a look " +"at the `Ray documentation `_." msgstr "" +"리소스가 FL 클라이언트를 예약하는 데 사용되는 방법과 사용자 지정 리소스를 정" +"의하는 방법에 대한 모든 복잡한 세부 사항을 이해하려면 'Ray 문서 '를 참조하세요." #: ../../source/how-to-run-simulations.rst:94 msgid "Simulation examples" -msgstr "" +msgstr "시뮬레이션 예제" #: ../../source/how-to-run-simulations.rst:96 msgid "" -"A few ready-to-run complete examples for Flower simulation in " -"Tensorflow/Keras and PyTorch are provided in the `Flower repository " -"`_. You can run them on Google Colab too:" +"A few ready-to-run complete examples for Flower simulation in Tensorflow/" +"Keras and PyTorch are provided in the `Flower repository `_. You can run them on Google Colab too:" msgstr "" +"Tensorflow/Keras와 파이토치에서 바로 실행할 수 있는 몇 가지 Flower 시뮬레이" +"션 예제는 `Flower 레포지토리 `_에서 제공됩니" +"다. Google Colab에서도 실행할 수 있습니다:" #: ../../source/how-to-run-simulations.rst:98 msgid "" -"`Tensorflow/Keras Simulation " -"`_: 100 clients collaboratively train a MLP model on MNIST." +"`Tensorflow/Keras Simulation `_: 100 clients collaboratively train a MLP " +"model on MNIST." msgstr "" +"`Tensorflow/Keras 시뮬레이션 `_: 100개의 클라이언트가 공동으로 MNIST에서 " +"MLP 모델을 훈련합니다." #: ../../source/how-to-run-simulations.rst:99 msgid "" -"`PyTorch Simulation `_: 100 clients collaboratively train a CNN model on " +"`PyTorch Simulation `_: 100 clients collaboratively train a CNN model on " "MNIST." msgstr "" +"파이토치 시뮬레이션 `_: 100개의 클라이언트가 공동으로 MNIST에서 CNN 모델을 훈" +"련합니다." #: ../../source/how-to-run-simulations.rst:104 msgid "Multi-node Flower simulations" -msgstr "" +msgstr "멀티 노드 Flower 시뮬레이션" #: ../../source/how-to-run-simulations.rst:106 msgid "" -"Flower's :code:`VirtualClientEngine` allows you to run FL simulations " -"across multiple compute nodes. Before starting your multi-node simulation" -" ensure that you:" +"Flower's :code:`VirtualClientEngine` allows you to run FL simulations across " +"multiple compute nodes. Before starting your multi-node simulation ensure " +"that you:" msgstr "" +"Flower의 :code:`VirtualClientEngine`을 사용하면 여러 컴퓨팅 노드에서 FL 시뮬" +"레이션을 실행할 수 있습니다. 멀티 노드 시뮬레이션을 시작하기 전에 다음 사항" +"을 확인하세요:" #: ../../source/how-to-run-simulations.rst:108 msgid "Have the same Python environment in all nodes." -msgstr "" +msgstr "모든 노드에서 동일한 Python 환경을 유지합니다." #: ../../source/how-to-run-simulations.rst:109 msgid "Have a copy of your code (e.g. your entire repo) in all nodes." -msgstr "" +msgstr "모든 노드에 코드 사본(예: 전체 레포지토리)을 보관하세요." #: ../../source/how-to-run-simulations.rst:110 msgid "" -"Have a copy of your dataset in all nodes (more about this in " -":ref:`simulation considerations `)" +"Have a copy of your dataset in all nodes (more about this in :ref:" +"`simulation considerations `)" msgstr "" +"모든 노드에 데이터 세트의 사본을 보유하세요(자세한 내용은 :ref:`simulation " +"considerations `에서 확인하세요)" #: ../../source/how-to-run-simulations.rst:111 msgid "" -"Pass :code:`ray_init_args={\"address\"=\"auto\"}` to `start_simulation " -"`_ so the " -":code:`VirtualClientEngine` attaches to a running Ray instance." +"Pass :code:`ray_init_args={\"address\"=\"auto\"}` to `start_simulation `_ so the :code:" +"`VirtualClientEngine` attaches to a running Ray instance." msgstr "" +":code:`ray_init_args={\"address\"=\"auto\"}`를 `start_simulation `_에 전달하여 :code:" +"`VirtualClientEngine`이 실행 중인 Ray 인스턴스에 연결되도록 합니다." #: ../../source/how-to-run-simulations.rst:112 msgid "" -"Start Ray on you head node: on the terminal type :code:`ray start " -"--head`. This command will print a few lines, one of which indicates how " -"to attach other nodes to the head node." +"Start Ray on you head node: on the terminal type :code:`ray start --head`. " +"This command will print a few lines, one of which indicates how to attach " +"other nodes to the head node." msgstr "" +"헤드 노드에서 Ray 시작: 터미널에서 :code:`ray start --head`를 입력합니다. 이 " +"명령은 몇 줄을 출력하며, 그 중 하나는 다른 노드를 헤드 노드에 연결하는 방법" +"을 나타냅니다." #: ../../source/how-to-run-simulations.rst:113 msgid "" -"Attach other nodes to the head node: copy the command shown after " -"starting the head and execute it on terminal of a new node: for example " -":code:`ray start --address='192.168.1.132:6379'`" +"Attach other nodes to the head node: copy the command shown after starting " +"the head and execute it on terminal of a new node: for example :code:`ray " +"start --address='192.168.1.132:6379'`" msgstr "" +"헤드 노드에 다른 노드 연결: 헤드를 시작한 후 표시된 명령어을 복사하여 새 노드" +"의 터미널에서 실행합니다: 예: :code:`ray start --" +"address='192.168.1.132:6379'`" #: ../../source/how-to-run-simulations.rst:115 msgid "" "With all the above done, you can run your code from the head node as you " "would if the simulation was running on a single node." msgstr "" +"위의 모든 작업이 완료되면 단일 노드에서 시뮬레이션을 실행할 때와 마찬가지로 " +"헤드 노드에서 코드를 실행할 수 있습니다." #: ../../source/how-to-run-simulations.rst:117 msgid "" -"Once your simulation is finished, if you'd like to dismantle your cluster" -" you simply need to run the command :code:`ray stop` in each node's " -"terminal (including the head node)." +"Once your simulation is finished, if you'd like to dismantle your cluster " +"you simply need to run the command :code:`ray stop` in each node's terminal " +"(including the head node)." msgstr "" +"시뮬레이션이 완료되면 클러스터를 해체하려면 각 노드(헤드 노드 포함)의 터미널" +"에서 :code:`ray stop` 명령을 실행하기만 하면 됩니다." #: ../../source/how-to-run-simulations.rst:120 msgid "Multi-node simulation good-to-know" -msgstr "" +msgstr "멀티 노드 시뮬레이션에 대해 알아두면 좋은 사항" #: ../../source/how-to-run-simulations.rst:122 msgid "" "Here we list a few interesting functionality when running multi-node FL " "simulations:" msgstr "" +"여기에서는 멀티 노드 FL 시뮬레이션을 실행할 때 흥미로운 몇 가지 기능을 나열합" +"니다:" #: ../../source/how-to-run-simulations.rst:124 msgid "" -"User :code:`ray status` to check all nodes connected to your head node as" -" well as the total resources available to the " -":code:`VirtualClientEngine`." +"User :code:`ray status` to check all nodes connected to your head node as " +"well as the total resources available to the :code:`VirtualClientEngine`." msgstr "" +"사용자는 :code:`ray status`를 통해 헤드 노드에 연결된 모든 노드와 :code:" +"`VirtualClientEngine`에 사용 가능한 총 리소스를 확인할 수 있습니다." #: ../../source/how-to-run-simulations.rst:126 msgid "" -"When attaching a new node to the head, all its resources (i.e. all CPUs, " -"all GPUs) will be visible by the head node. This means that the " -":code:`VirtualClientEngine` can schedule as many `virtual` clients as " -"that node can possible run. In some settings you might want to exclude " -"certain resources from the simulation. You can do this by appending " -"`--num-cpus=` and/or `--num-" -"gpus=` in any :code:`ray start` command (including " -"when starting the head)" -msgstr "" +"When attaching a new node to the head, all its resources (i.e. all CPUs, all " +"GPUs) will be visible by the head node. This means that the :code:" +"`VirtualClientEngine` can schedule as many `virtual` clients as that node " +"can possible run. In some settings you might want to exclude certain " +"resources from the simulation. You can do this by appending `--num-" +"cpus=` and/or `--num-gpus=` in any :" +"code:`ray start` command (including when starting the head)" +msgstr "" +"새 노드를 헤드에 연결하면 해당 노드의 모든 리소스(즉, 모든 CPU, 모든 GPU)가 " +"헤드 노드에 표시됩니다. 즉, :code:`VirtualClientEngine`은 해당 노드가 실행할 " +"수 있는 만큼의 `가상` 클라이언트를 예약할 수 있습니다. 일부 설정에서는 시뮬레" +"이션에서 특정 리소스를 제외하고 싶을 수 있습니다. 모든 :code:`ray start` 명령" +"(헤드 시작 시 포함)에 `--num-cpus=` 및/또는 `--num-" +"gpus=`를 추가하여 이 작업을 수행하면 됩니다" #: ../../source/how-to-run-simulations.rst:132 msgid "Considerations for simulations" -msgstr "" +msgstr "시뮬레이션 시 고려 사항" #: ../../source/how-to-run-simulations.rst:135 msgid "" -"We are actively working on these fronts so to make it trivial to run any " -"FL workload with Flower simulation." +"We are actively working on these fronts so to make it trivial to run any FL " +"workload with Flower simulation." msgstr "" +"Flower 시뮬레이션으로 모든 FL 워크로드를 간편하게 실행할 수 있도록 이러한 측" +"면에서 적극적으로 노력하고 있습니다." #: ../../source/how-to-run-simulations.rst:138 msgid "" -"The current VCE allows you to run Federated Learning workloads in " -"simulation mode whether you are prototyping simple scenarios on your " -"personal laptop or you want to train a complex FL pipeline across " -"multiple high-performance GPU nodes. While we add more capabilities to " -"the VCE, the points below highlight some of the considerations to keep in" -" mind when designing your FL pipeline with Flower. We also highlight a " -"couple of current limitations in our implementation." +"The current VCE allows you to run Federated Learning workloads in simulation " +"mode whether you are prototyping simple scenarios on your personal laptop or " +"you want to train a complex FL pipeline across multiple high-performance GPU " +"nodes. While we add more capabilities to the VCE, the points below highlight " +"some of the considerations to keep in mind when designing your FL pipeline " +"with Flower. We also highlight a couple of current limitations in our " +"implementation." msgstr "" +"현재 VCE를 사용하면 개인 노트북에서 간단한 시나리오를 프로토타이핑하든, 여러 " +"고성능 GPU 노드에서 복잡한 FL 파이프라인을 훈련하든 상관없이 시뮬레이션 모드" +"에서 Federated 학습 워크로드를 실행할 수 있습니다. VCE에 더 많은 기능을 추가" +"하는 동안, 아래에서는 Flower로 FL 파이프라인을 설계할 때 염두에 두어야 할 몇 " +"가지 사항을 강조합니다. 또한 현재 구현에서 몇 가지 제한 사항을 강조합니다." #: ../../source/how-to-run-simulations.rst:141 msgid "GPU resources" -msgstr "" +msgstr "GPU 리소스" #: ../../source/how-to-run-simulations.rst:143 msgid "" -"The VCE assigns a share of GPU memory to a client that specifies the key " -":code:`num_gpus` in :code:`client_resources`. This being said, Ray (used " +"The VCE assigns a share of GPU memory to a client that specifies the key :" +"code:`num_gpus` in :code:`client_resources`. This being said, Ray (used " "internally by the VCE) is by default:" msgstr "" +"VCE는 :code:`client_resources`에서 :code:`num_gpus` 키를 지정하는 클라이언트" +"에 GPU 메모리 공유를 할당합니다. 즉, (VCE에서 내부적으로 사용하는) Ray가 기본" +"적으로 사용됩니다:" #: ../../source/how-to-run-simulations.rst:146 msgid "" -"not aware of the total VRAM available on the GPUs. This means that if you" -" set :code:`num_gpus=0.5` and you have two GPUs in your system with " -"different (e.g. 32GB and 8GB) VRAM amounts, they both would run 2 clients" -" concurrently." +"not aware of the total VRAM available on the GPUs. This means that if you " +"set :code:`num_gpus=0.5` and you have two GPUs in your system with different " +"(e.g. 32GB and 8GB) VRAM amounts, they both would run 2 clients concurrently." msgstr "" +"GPU에서 사용 가능한 총 VRAM을 인식하지 못합니다. 즉, 시스템에 서로 다른(예: " +"32GB와 8GB) VRAM 용량을 가진 두 개의 GPU가 있고 :code:`num_gpus=0.5`를 설정하" +"면 둘 다 동시에 2개의 클라이언트를 실행하게 됩니다." #: ../../source/how-to-run-simulations.rst:147 msgid "" "not aware of other unrelated (i.e. not created by the VCE) workloads are " "running on the GPU. Two takeaways from this are:" msgstr "" +"관련 없는(즉, VCE에 의해 생성되지 않은) 다른 워크로드가 GPU에서 실행되고 있는" +"지 알지 못합니다. 여기서 두 가지 시사점을 얻을 수 있습니다:" #: ../../source/how-to-run-simulations.rst:149 msgid "" @@ -6590,178 +6794,237 @@ msgid "" "aggregation (by instance when making use of the `evaluate method `_)" msgstr "" +"집계 후 '글로벌 모델'을 평가하려면 Flower 서버에 GPU가 필요할 수 있습니다" +"(예: `evaluate method `_를 사용할 때)" #: ../../source/how-to-run-simulations.rst:150 msgid "" "If you want to run several independent Flower simulations on the same " -"machine you need to mask-out your GPUs with " -":code:`CUDA_VISIBLE_DEVICES=\"\"` when launching your " -"experiment." +"machine you need to mask-out your GPUs with :code:" +"`CUDA_VISIBLE_DEVICES=\"\"` when launching your experiment." msgstr "" +"동일한 머신에서 여러 개의 독립적인 Flower 시뮬레이션을 실행하려면, 실험을 시" +"작할 때 :code:`CUDA_VISIBLE_DEVICES=\"\"`로 GPU를 마스킹해야 합니다." #: ../../source/how-to-run-simulations.rst:153 msgid "" -"In addition, the GPU resource limits passed to :code:`client_resources` " -"are not `enforced` (i.e. they can be exceeded) which can result in the " -"situation of client using more VRAM than the ratio specified when " -"starting the simulation." +"In addition, the GPU resource limits passed to :code:`client_resources` are " +"not `enforced` (i.e. they can be exceeded) which can result in the situation " +"of client using more VRAM than the ratio specified when starting the " +"simulation." msgstr "" +"또한 :code:`client_resources`에 전달된 GPU 리소스 제한이 '강제'되지 않아(즉, " +"초과할 수 있음) 클라이언트가 시뮬레이션을 시작할 때 지정된 비율보다 더 많은 " +"VRAM을 사용하는 상황이 발생할 수 있습니다." #: ../../source/how-to-run-simulations.rst:156 msgid "TensorFlow with GPUs" -msgstr "" +msgstr "GPU를 사용한 TensorFlow" #: ../../source/how-to-run-simulations.rst:158 msgid "" -"When `using a GPU with TensorFlow " -"`_ nearly your entire GPU memory of" -" all your GPUs visible to the process will be mapped. This is done by " -"TensorFlow for optimization purposes. However, in settings such as FL " -"simulations where we want to split the GPU into multiple `virtual` " -"clients, this is not a desirable mechanism. Luckily we can disable this " -"default behavior by `enabling memory growth " -"`_." -msgstr "" +"When `using a GPU with TensorFlow `_ " +"nearly your entire GPU memory of all your GPUs visible to the process will " +"be mapped. This is done by TensorFlow for optimization purposes. However, in " +"settings such as FL simulations where we want to split the GPU into multiple " +"`virtual` clients, this is not a desirable mechanism. Luckily we can disable " +"this default behavior by `enabling memory growth `_." +msgstr "" +"`TensorFlow와 함께 GPU를 사용 `_하면 프" +"로세스에 보이는 모든 GPU의 거의 전체 GPU 메모리가 매핑됩니다. 이는 최적화 목" +"적으로 TensorFlow에서 수행됩니다. 그러나 GPU를 여러 개의 '가상' 클라이언트로 " +"분할하려는 FL 시뮬레이션과 같은 설정에서는 이는 바람직한 메커니즘이 아닙니" +"다. 다행히도 '메모리 증가 활성화 `_'를 통해 이 기본 동작을 비활성화할 수 있습니" +"다." #: ../../source/how-to-run-simulations.rst:160 msgid "" -"This would need to be done in the main process (which is where the server" -" would run) and in each Actor created by the VCE. By means of " -":code:`actor_kwargs` we can pass the reserved key `\"on_actor_init_fn\"` " -"in order to specify a function to be executed upon actor initialization. " -"In this case, to enable GPU growth for TF workloads. It would look as " -"follows:" +"This would need to be done in the main process (which is where the server " +"would run) and in each Actor created by the VCE. By means of :code:" +"`actor_kwargs` we can pass the reserved key `\"on_actor_init_fn\"` in order " +"to specify a function to be executed upon actor initialization. In this " +"case, to enable GPU growth for TF workloads. It would look as follows:" msgstr "" +"이 작업은 메인 프로세스(서버가 실행되는 곳)와 VCE에서 생성한 각 액터에서 수행" +"해야 합니다. :code:`actor_kwargs`를 통해 예약 키 `\"on_actor_init_fn\"`을 전" +"달하여 액터 초기화 시 실행할 함수를 지정할 수 있습니다. 이 경우 TF 워크로드" +"에 대한 GPU 증가를 활성화합니다. 다음과 같이 보입니다:" #: ../../source/how-to-run-simulations.rst:179 msgid "" "This is precisely the mechanism used in `Tensorflow/Keras Simulation " -"`_ example." +"`_ " +"example." msgstr "" +"이것이 바로`Tensorflow/Keras Simulation `_ 예제에서 사용된 메커니즘입니다." #: ../../source/how-to-run-simulations.rst:183 msgid "Multi-node setups" -msgstr "" +msgstr "멀티 노드 설정" #: ../../source/how-to-run-simulations.rst:185 msgid "" -"The VCE does not currently offer a way to control on which node a " -"particular `virtual` client is executed. In other words, if more than a " -"single node have the resources needed by a client to run, then any of " -"those nodes could get the client workload scheduled onto. Later in the FL" -" process (i.e. in a different round) the same client could be executed by" -" a different node. Depending on how your clients access their datasets, " -"this might require either having a copy of all dataset partitions on all " -"nodes or a dataset serving mechanism (e.g. using nfs, a database) to " -"circumvent data duplication." -msgstr "" +"The VCE does not currently offer a way to control on which node a particular " +"`virtual` client is executed. In other words, if more than a single node " +"have the resources needed by a client to run, then any of those nodes could " +"get the client workload scheduled onto. Later in the FL process (i.e. in a " +"different round) the same client could be executed by a different node. " +"Depending on how your clients access their datasets, this might require " +"either having a copy of all dataset partitions on all nodes or a dataset " +"serving mechanism (e.g. using nfs, a database) to circumvent data " +"duplication." +msgstr "" +"VCE는 현재 특정 '가상' 클라이언트를 어느 노드에서 실행할지 제어하는 방법을 제" +"공하지 않습니다. 즉, 클라이언트가 실행하는 데 필요한 리소스가 하나 이상의 노" +"드에 있는 경우 해당 노드 중 어느 노드에나 클라이언트 워크로드가 예약될 수 있" +"습니다. FL 프로세스 후반부(즉, 다른 라운드에서)에는 동일한 클라이언트가 다른 " +"노드에서 실행될 수 있습니다. 클라이언트가 데이터 세트에 액세스하는 방식에 따" +"라 모든 노드에 모든 데이터 세트 파티션의 복사본을 보유하거나 데이터 중복을 피" +"하기 위해 데이터 세트 제공 메커니즘(예: nfs, 데이터베이스 사용)을 사용해야 " +"할 수 있습니다." #: ../../source/how-to-run-simulations.rst:187 msgid "" -"By definition virtual clients are `stateless` due to their ephemeral " -"nature. A client state can be implemented as part of the Flower client " -"class but users need to ensure this saved to persistent storage (e.g. a " -"database, disk) and that can be retrieve later by the same client " -"regardless on which node it is running from. This is related to the point" -" above also since, in some way, the client's dataset could be seen as a " -"type of `state`." +"By definition virtual clients are `stateless` due to their ephemeral nature. " +"A client state can be implemented as part of the Flower client class but " +"users need to ensure this saved to persistent storage (e.g. a database, " +"disk) and that can be retrieve later by the same client regardless on which " +"node it is running from. This is related to the point above also since, in " +"some way, the client's dataset could be seen as a type of `state`." msgstr "" +"정의상 가상 클라이언트는 임시적 특성으로 인해 '상태 없음'입니다. 클라이언트 " +"상태는 Flower 클라이언트 클래스의 일부로 구현할 수 있지만, 사용자는 이를 영" +"구 저장소(예: 데이터베이스, 디스크)에 저장하여 나중에 실행 중인 노드와 관계없" +"이 동일한 클라이언트가 검색할 수 있도록 해야 합니다. 이는 어떤 식으로든 클라" +"이언트의 데이터 세트가 일종의 '상태'로 볼 수 있기 때문에 위의 요점과도 관련" +"이 있습니다." #: ../../source/how-to-save-and-load-model-checkpoints.rst:2 msgid "Save and load model checkpoints" -msgstr "" +msgstr "모델 체크포인트 저장 및 로드" #: ../../source/how-to-save-and-load-model-checkpoints.rst:4 msgid "" -"Flower does not automatically save model updates on the server-side. This" -" how-to guide describes the steps to save (and load) model checkpoints in" -" Flower." +"Flower does not automatically save model updates on the server-side. This " +"how-to guide describes the steps to save (and load) model checkpoints in " +"Flower." msgstr "" +"Flower는 서버 측에서 모델 업데이트를 자동으로 저장하지 않습니다. 이 사용법 가" +"이드에서는 Flower에서 모델 체크포인트를 저장(및 로드)하는 단계에 대해 설명합" +"니다." #: ../../source/how-to-save-and-load-model-checkpoints.rst:8 msgid "Model checkpointing" -msgstr "" +msgstr "모델 체크포인트" #: ../../source/how-to-save-and-load-model-checkpoints.rst:10 msgid "" -"Model updates can be persisted on the server-side by customizing " -":code:`Strategy` methods. Implementing custom strategies is always an " -"option, but for many cases it may be more convenient to simply customize " -"an existing strategy. The following code example defines a new " -":code:`SaveModelStrategy` which customized the existing built-in " -":code:`FedAvg` strategy. In particular, it customizes " -":code:`aggregate_fit` by calling :code:`aggregate_fit` in the base class " -"(:code:`FedAvg`). It then continues to save returned (aggregated) weights" -" before it returns those aggregated weights to the caller (i.e., the " -"server):" -msgstr "" +"Model updates can be persisted on the server-side by customizing :code:" +"`Strategy` methods. Implementing custom strategies is always an option, but " +"for many cases it may be more convenient to simply customize an existing " +"strategy. The following code example defines a new :code:`SaveModelStrategy` " +"which customized the existing built-in :code:`FedAvg` strategy. In " +"particular, it customizes :code:`aggregate_fit` by calling :code:" +"`aggregate_fit` in the base class (:code:`FedAvg`). It then continues to " +"save returned (aggregated) weights before it returns those aggregated " +"weights to the caller (i.e., the server):" +msgstr "" +":code:`Strategy` 메소드를 사용자 지정하여 서버 측에서 모델 업데이트를 지속할 " +"수 있습니다. 사용자 지정 전략을 구현하는 것은 항상 옵션이지만 대부분의 경우 " +"기존 전략을 간단히 사용자 지정하는 것이 더 편리할 수 있습니다. 다음 코드 예시" +"는 기존의 기본 제공 :code:`FedAvg` 전략을 사용자 지정한 새로운 :code:" +"`SaveModelStrategy`를 정의합니다. 특히, 기본 클래스(:code:`FedAvg`)에서 :" +"code:`aggregate_fit`을 호출하여 :code:`aggregate_fit`을 사용자 지정합니다. 그" +"런 다음 호출자(즉, 서버)에게 집계된 가중치를 반환하기 전에 반환된(집계된) 가" +"중치를 계속 저장합니다:" #: ../../source/how-to-save-and-load-model-checkpoints.rst:47 msgid "Save and load PyTorch checkpoints" -msgstr "" +msgstr "파이토치 체크포인트 저장 및 로드" #: ../../source/how-to-save-and-load-model-checkpoints.rst:49 msgid "" -"Similar to the previous example but with a few extra steps, we'll show " -"how to store a PyTorch checkpoint we'll use the ``torch.save`` function. " -"Firstly, ``aggregate_fit`` returns a ``Parameters`` object that has to be" -" transformed into a list of NumPy ``ndarray``'s, then those are " -"transformed into the PyTorch ``state_dict`` following the ``OrderedDict``" -" class structure." +"Similar to the previous example but with a few extra steps, we'll show how " +"to store a PyTorch checkpoint we'll use the ``torch.save`` function. " +"Firstly, ``aggregate_fit`` returns a ``Parameters`` object that has to be " +"transformed into a list of NumPy ``ndarray``'s, then those are transformed " +"into the PyTorch ``state_dict`` following the ``OrderedDict`` class " +"structure." msgstr "" +"이전 예제와 비슷하지만 몇 가지 단계가 추가되어 ``torch.save`` 함수를 사용하" +"여 파이토치 체크포인트를 저장하는 방법을 보여드리겠습니다. 먼저, " +"``aggregate_fit``은 ``Parameters`` 객체를 반환하는데, 이 객체는 NumPy " +"``ndarray``의 목록으로 변환되어야 하며, ``OrderedDict`` 클래스 구조에 따라 파" +"이토치 ``state_dict``로 변환됩니다." #: ../../source/how-to-save-and-load-model-checkpoints.rst:85 msgid "" -"To load your progress, you simply append the following lines to your " -"code. Note that this will iterate over all saved checkpoints and load the" -" latest one:" +"To load your progress, you simply append the following lines to your code. " +"Note that this will iterate over all saved checkpoints and load the latest " +"one:" msgstr "" +"진행 상황을 로드하려면 코드에 다음 줄을 추가하기만 하면 됩니다. 이렇게 하면 " +"저장된 모든 체크포인트를 반복하고 최신 체크포인트를 로드합니다:" #: ../../source/how-to-save-and-load-model-checkpoints.rst:97 msgid "" -"Return/use this object of type ``Parameters`` wherever necessary, such as" -" in the ``initial_parameters`` when defining a ``Strategy``." +"Return/use this object of type ``Parameters`` wherever necessary, such as in " +"the ``initial_parameters`` when defining a ``Strategy``." msgstr "" +"``전략``을 정의할 때 ``초기_파라미터``와 같이 필요한 경우 ``파라미터`` 유형" +"의 이 객체를 반환/사용합니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:2 msgid "Upgrade to Flower 1.0" -msgstr "" +msgstr "Flower 1.0으로 업그레이드" #: ../../source/how-to-upgrade-to-flower-1.0.rst:4 msgid "" -"Flower 1.0 is here. Along with new features, Flower 1.0 provides a stable" -" foundation for future growth. Compared to Flower 0.19 (and other 0.x " -"series releases), there are a few breaking changes that make it necessary" -" to change the code of existing 0.x-series projects." +"Flower 1.0 is here. Along with new features, Flower 1.0 provides a stable " +"foundation for future growth. Compared to Flower 0.19 (and other 0.x series " +"releases), there are a few breaking changes that make it necessary to change " +"the code of existing 0.x-series projects." msgstr "" +"Flower 1.0이 출시되었습니다. 새로운 기능과 함께 Flower 1.0은 향후 성장을 위" +"한 안정적인 기반을 제공합니다. Flower 0.19(및 다른 0.x 시리즈 릴리스)와 비교" +"했을 때 기존 0.x 시리즈 프로젝트의 코드를 변경해야 하는 몇 가지 획기적인 변" +"경 사항이 있습니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:8 #: ../../source/how-to-upgrade-to-flower-next.rst:43 msgid "Install update" -msgstr "" +msgstr "업데이트 설치" #: ../../source/how-to-upgrade-to-flower-1.0.rst:10 msgid "" -"Here's how to update an existing installation to Flower 1.0 using either " -"pip or Poetry:" +"Here's how to update an existing installation to Flower 1.0 using either pip " +"or Poetry:" msgstr "" +"다음은 pip 또는 Poetry를 사용하여 기존 설치를 Flower 1.0으로 업데이트하는 방" +"법입니다:" #: ../../source/how-to-upgrade-to-flower-1.0.rst:12 msgid "pip: add ``-U`` when installing." -msgstr "" +msgstr "pip: 설치할 때 ``-U``를 추가합니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:14 msgid "" "``python -m pip install -U flwr`` (when using ``start_server`` and " "``start_client``)" msgstr "" +"``python -m pip install -U flwr``(``start_server`` 및 ``start_client``를 사용" +"하는 경우)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:15 msgid "" "``python -m pip install -U flwr[simulation]`` (when using " "``start_simulation``)" msgstr "" +"``python -m pip install -U flwr[simulation]``(``start_simulation`` 사용 시)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:17 msgid "" @@ -6769,140 +7032,170 @@ msgid "" "reinstall (don't forget to delete ``poetry.lock`` via ``rm poetry.lock`` " "before running ``poetry install``)." msgstr "" +"Poetry: ``pyproject.toml``에서 ``flwr`` dependency을 업데이트한 다음 다시 설" +"치하세요(``poetry 설치``를 실행하기 전에 ``rm poetry.lock``을 통해 ``poetry." +"lock``을 삭제하는 것을 잊지 마세요)." #: ../../source/how-to-upgrade-to-flower-1.0.rst:19 -msgid "``flwr = \"^1.0.0\"`` (when using ``start_server`` and ``start_client``)" -msgstr "" +msgid "" +"``flwr = \"^1.0.0\"`` (when using ``start_server`` and ``start_client``)" +msgstr "``flwr = \"^1.0.0\"``(``start_server`` 및 ``start_client`` 사용 시)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:20 msgid "" -"``flwr = { version = \"^1.0.0\", extras = [\"simulation\"] }`` (when " -"using ``start_simulation``)" +"``flwr = { version = \"^1.0.0\", extras = [\"simulation\"] }`` (when using " +"``start_simulation``)" msgstr "" +"``flwr = { version = \"^1.0.0\", extras = [\"simulation\"] }`` " +"(``start_simulation`` 사용 시)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:24 #: ../../source/how-to-upgrade-to-flower-next.rst:100 msgid "Required changes" -msgstr "" +msgstr "필수 변경 사항" #: ../../source/how-to-upgrade-to-flower-1.0.rst:26 msgid "The following breaking changes require manual updates." -msgstr "" +msgstr "다음과 같은 주요 변경 사항에는 수동 업데이트가 필요합니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:29 msgid "General" -msgstr "" +msgstr "일반" #: ../../source/how-to-upgrade-to-flower-1.0.rst:31 msgid "" "Pass all arguments as keyword arguments (not as positional arguments). " "Here's an example:" -msgstr "" +msgstr "모든 전달인자를 위치 전달인자가 아닌 키워드 전달인자로 전달합니다. 다음은 " +"예시입니다:" #: ../../source/how-to-upgrade-to-flower-1.0.rst:33 msgid "" "Flower 0.19 (positional arguments): ``start_client(\"127.0.0.1:8080\", " "FlowerClient())``" msgstr "" +"Flower 0.19 (위치 전달인자): ``start_client(\"127.0.0.1:8080\", " +"FlowerClient())``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:34 msgid "" "Flower 1.0 (keyword arguments): " -"``start_client(server_address=\"127.0.0.1:8080\", " -"client=FlowerClient())``" +"``start_client(server_address=\"127.0.0.1:8080\", client=FlowerClient())``" msgstr "" +"Flower 1.0 (키워드 전달인자): ``start_client(server_address=\"127.0.0.1:" +"8080\", client=FlowerClient())``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:37 #: ../../source/ref-api/flwr.client.Client.rst:2 msgid "Client" -msgstr "" +msgstr "클라이언트" #: ../../source/how-to-upgrade-to-flower-1.0.rst:39 msgid "" "Subclasses of ``NumPyClient``: change ``def get_parameters(self):``` to " "``def get_parameters(self, config):``" msgstr "" +"``NumPyClient``의 서브클래스: ``def get_parameters(self):``를 ``def " +"get_parameters(self, config):``로 변경합니다" #: ../../source/how-to-upgrade-to-flower-1.0.rst:40 msgid "" "Subclasses of ``Client``: change ``def get_parameters(self):``` to ``def " "get_parameters(self, ins: GetParametersIns):``" msgstr "" +"``클라이언트``의 서브클래스: ``def get_parameters(self):``를 ``def " +"get_parameters(self, ins: GetParametersIns):``로 변경합니다" #: ../../source/how-to-upgrade-to-flower-1.0.rst:43 msgid "Strategies / ``start_server`` / ``start_simulation``" -msgstr "" +msgstr "전략 / ``start_server`` / ``start_simulation``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:45 msgid "" "Pass ``ServerConfig`` (instead of a dictionary) to ``start_server`` and " "``start_simulation``. Here's an example:" msgstr "" +"Dictionary 대신 ``ServerConfig``를 ``start_server`` 및 ``start_simulation``" +"에 전달합니다. 다음은 예제입니다:" #: ../../source/how-to-upgrade-to-flower-1.0.rst:47 msgid "" "Flower 0.19: ``start_server(..., config={\"num_rounds\": 3, " "\"round_timeout\": 600.0}, ...)``" msgstr "" +"Flower 0.19: ``start_server(..., config={\"num_rounds\": 3, " +"\"round_timeout\": 600.0}, ...)``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:48 msgid "" -"Flower 1.0: ``start_server(..., " -"config=flwr.server.ServerConfig(num_rounds=3, round_timeout=600.0), " -"...)``" +"Flower 1.0: ``start_server(..., config=flwr.server." +"ServerConfig(num_rounds=3, round_timeout=600.0), ...)``" msgstr "" +"Flower 1.0: ``start_server(..., config=flwr.server." +"ServerConfig(num_rounds=3, round_timeout=600.0), ...)``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:50 msgid "" "Replace ``num_rounds=1`` in ``start_simulation`` with the new " "``config=ServerConfig(...)`` (see previous item)" msgstr "" +"``start_simulation``의 ``num_rounds=1``을 새로운 ``config=ServerConfig(...)``" +"로 바꿉니다(이전 항목 참조)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:51 msgid "" "Remove ``force_final_distributed_eval`` parameter from calls to " -"``start_server``. Distributed evaluation on all clients can be enabled by" -" configuring the strategy to sample all clients for evaluation after the " -"last round of training." +"``start_server``. Distributed evaluation on all clients can be enabled by " +"configuring the strategy to sample all clients for evaluation after the last " +"round of training." msgstr "" +"'start_server`` 호출에서 ``force_final_distributed_eval`` 매개변수를 제거합니" +"다. 모든 클라이언트에 대한 분산 평가는 마지막 훈련 라운드 후 평가를 위해 모" +"든 클라이언트를 샘플링하도록 전략을 구성하여 활성화할 수 있습니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:52 msgid "Rename parameter/ndarray conversion functions:" -msgstr "" +msgstr "매개변수/ndarray 변환 함수의 이름을 바꿉니다:" #: ../../source/how-to-upgrade-to-flower-1.0.rst:54 msgid "``parameters_to_weights`` --> ``parameters_to_ndarrays``" -msgstr "" +msgstr "``parameters_to_weights`` --> ``parameters_to_ndarrays``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:55 msgid "``weights_to_parameters`` --> ``ndarrays_to_parameters``" -msgstr "" +msgstr "``weights_to_parameters`` --> ``ndarrays_to_parameters``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:57 msgid "" -"Strategy initialization: if the strategy relies on the default values for" -" ``fraction_fit`` and ``fraction_evaluate``, set ``fraction_fit`` and " +"Strategy initialization: if the strategy relies on the default values for " +"``fraction_fit`` and ``fraction_evaluate``, set ``fraction_fit`` and " "``fraction_evaluate`` manually to ``0.1``. Projects that do not manually " "create a strategy (by calling ``start_server`` or ``start_simulation`` " -"without passing a strategy instance) should now manually initialize " -"FedAvg with ``fraction_fit`` and ``fraction_evaluate`` set to ``0.1``." +"without passing a strategy instance) should now manually initialize FedAvg " +"with ``fraction_fit`` and ``fraction_evaluate`` set to ``0.1``." msgstr "" +"전략 초기화: 전략이 ``fraction_fit`` 및 ``fraction_evaluate``의 기본값에 의존" +"하는 경우 ``fraction_fit`` 및 ``fraction_evaluate``를 ``0.1``로 수동 설정합니" +"다. 전략을 수동으로 생성하지 않는 프로젝트(전략 인스턴스를 전달하지 않고 " +"``start_server`` 또는 ``start_simulation``을 호출하여)는 이제 " +"``fraction_fit`` 및 ``fraction_evaluate``를 ``0.1``로 설정하여 FedAvg를 수동" +"으로 초기화해야 합니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:58 msgid "Rename built-in strategy parameters (e.g., ``FedAvg``):" -msgstr "" +msgstr "기본 제공 전략 매개변수의 이름을 바꿉니다(예: ``FedAvg``):" #: ../../source/how-to-upgrade-to-flower-1.0.rst:60 msgid "``fraction_eval`` --> ``fraction_evaluate``" -msgstr "" +msgstr "``fraction_eval`` --> ``fraction_evaluate``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:61 msgid "``min_eval_clients`` --> ``min_evaluate_clients``" -msgstr "" +msgstr "``min_eval_clients`` --> ``min_evaluate_clients``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:62 msgid "``eval_fn`` --> ``evaluate_fn``" -msgstr "" +msgstr "``eval_fn`` --> ``evaluate_fn``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:64 msgid "" @@ -6910,72 +7203,90 @@ msgid "" "functions, for example, ``configure_fit``, ``aggregate_fit``, " "``configure_evaluate``, ``aggregate_evaluate``, and ``evaluate_fn``." msgstr "" +"``rnd``의 이름을 ``server_round``로 바꿉니다. 이는 여러 메서드 및 함수(예: " +"``configure_fit``, ``aggregate_fit``, ``configure_evaluate``, " +"``aggregate_evaluate`` 및 ``evaluate_fn``)에 영향을 미칩니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:65 msgid "Add ``server_round`` and ``config`` to ``evaluate_fn``:" -msgstr "" +msgstr "``server_round`` 및 ``config``를 ``evaluate_fn``에 추가합니다:" #: ../../source/how-to-upgrade-to-flower-1.0.rst:67 msgid "" -"Flower 0.19: ``def evaluate(parameters: NDArrays) -> " -"Optional[Tuple[float, Dict[str, Scalar]]]:``" +"Flower 0.19: ``def evaluate(parameters: NDArrays) -> Optional[Tuple[float, " +"Dict[str, Scalar]]]:``" msgstr "" +"Flower 0.19: ``def evaluate(parameters: NDArrays) -> Optional[Tuple[float, " +"Dict[str, Scalar]]]:``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:68 msgid "" -"Flower 1.0: ``def evaluate(server_round: int, parameters: NDArrays, " -"config: Dict[str, Scalar]) -> Optional[Tuple[float, Dict[str, " -"Scalar]]]:``" +"Flower 1.0: ``def evaluate(server_round: int, parameters: NDArrays, config: " +"Dict[str, Scalar]) -> Optional[Tuple[float, Dict[str, Scalar]]]:``" msgstr "" +"Flower 1.0: ``def evaluate(server_round: int, parameters: NDArrays, config: " +"Dict[str, Scalar]) -> Optional[Tuple[float, Dict[str, Scalar]]]:``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:71 msgid "Custom strategies" -msgstr "" +msgstr "사용자 정의 전략" #: ../../source/how-to-upgrade-to-flower-1.0.rst:73 msgid "" -"The type of parameter ``failures`` has changed from " -"``List[BaseException]`` to ``List[Union[Tuple[ClientProxy, FitRes], " -"BaseException]]`` (in ``aggregate_fit``) and " -"``List[Union[Tuple[ClientProxy, EvaluateRes], BaseException]]`` (in " -"``aggregate_evaluate``)" +"The type of parameter ``failures`` has changed from ``List[BaseException]`` " +"to ``List[Union[Tuple[ClientProxy, FitRes], BaseException]]`` (in " +"``aggregate_fit``) and ``List[Union[Tuple[ClientProxy, EvaluateRes], " +"BaseException]]`` (in ``aggregate_evaluate``)" msgstr "" +"매개변수 ``failures``의 유형이 ``List[BaseException]``에서 " +"``List[Union[Tuple[ClientProxy], FitRes], BaseException]]``(``aggregate_fit``" +"에서) 및 ``List[Union[Tuple[ClientProxy], EvaluateRes], " +"BaseException]]``(``aggregate_evaluate``)로 변경되었습니다" #: ../../source/how-to-upgrade-to-flower-1.0.rst:74 msgid "" "The ``Strategy`` method ``evaluate`` now receives the current round of " "federated learning/evaluation as the first parameter:" msgstr "" +"이제 ``Strategy`` 메서드 ``evaluate``는 현재 federated 학습/평가 라운드를 첫 " +"번째 파라미터로 받습니다:" #: ../../source/how-to-upgrade-to-flower-1.0.rst:76 msgid "" "Flower 0.19: ``def evaluate(self, parameters: Parameters) -> " "Optional[Tuple[float, Dict[str, Scalar]]]:``" msgstr "" +"Flower 0.19: ``def evaluate(self, parameters: Parameters) -> " +"Optional[Tuple[float, Dict[str, Scalar]]]:``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:77 msgid "" -"Flower 1.0: ``def evaluate(self, server_round: int, parameters: " -"Parameters) -> Optional[Tuple[float, Dict[str, Scalar]]]:``" +"Flower 1.0: ``def evaluate(self, server_round: int, parameters: Parameters) -" +"> Optional[Tuple[float, Dict[str, Scalar]]]:``" msgstr "" +"Flower 1.0: ``def evaluate(self, server_round: int, parameters: Parameters) -" +"> Optional[Tuple[float, Dict[str, Scalar]]]:``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:80 msgid "Optional improvements" -msgstr "" +msgstr "선택적 개선 사항" #: ../../source/how-to-upgrade-to-flower-1.0.rst:82 msgid "" "Along with the necessary changes above, there are a number of potential " "improvements that just became possible:" msgstr "" +"위의 필수 변경 사항과 함께 방금 가능한 여러 가지 잠재적 개선 사항이 있습니다:" #: ../../source/how-to-upgrade-to-flower-1.0.rst:84 msgid "" "Remove \"placeholder\" methods from subclasses of ``Client`` or " -"``NumPyClient``. If you, for example, use server-side evaluation, then " -"empty placeholder implementations of ``evaluate`` are no longer " -"necessary." +"``NumPyClient``. If you, for example, use server-side evaluation, then empty " +"placeholder implementations of ``evaluate`` are no longer necessary." msgstr "" +"``Client`` 또는 ``NumPyClient``의 서브 클래스에서 \"placeholder\" 메서드를 제" +"거합니다. 예를 들어 서버 측 평가를 사용하는 경우 ``evaluate``의 빈 자리 표시" +"자 구현은 더 이상 필요하지 않습니다." #: ../../source/how-to-upgrade-to-flower-1.0.rst:85 msgid "" @@ -6983,69 +7294,84 @@ msgid "" "``start_simulation(..., config=flwr.server.ServerConfig(num_rounds=3, " "round_timeout=600.0), ...)``" msgstr "" +"``start_simulation``을 통해 라운드 타임아웃을 구성합니다: " +"``start_simulation(..., config=flwr.server.ServerConfig(num_rounds=3, " +"round_timeout=600.0), ...)``" #: ../../source/how-to-upgrade-to-flower-1.0.rst:89 #: ../../source/how-to-upgrade-to-flower-next.rst:317 msgid "Further help" -msgstr "" +msgstr "추가 도움말" #: ../../source/how-to-upgrade-to-flower-1.0.rst:91 msgid "" -"Most official `Flower code examples " -"`_ are already updated" -" to Flower 1.0, they can serve as a reference for using the Flower 1.0 " -"API. If there are further questions, `join the Flower Slack " -"`_ and use the channel ``#questions``." +"Most official `Flower code examples `_ are already updated to Flower 1.0, they can serve as a " +"reference for using the Flower 1.0 API. If there are further questions, " +"`join the Flower Slack `_ and use the channel " +"``#questions``." msgstr "" +"대부분의 공식 ``Flower code 예제 `_는 이미 Flower 1.0으로 업데이트되어 있으며, Flower 1.0 API를 사용" +"하기 위한 참고 자료로 사용할 수 있습니다. 더 궁금한 점이 있다면 ``플라워 슬" +"랙 `_에 가입하여 ``#questions`` 채널을 이용하" +"세요." #: ../../source/how-to-upgrade-to-flower-next.rst:2 msgid "Upgrade to Flower Next" -msgstr "" +msgstr "Flower Next 업그레이드" #: ../../source/how-to-upgrade-to-flower-next.rst:4 msgid "" -"Welcome to the migration guide for updating Flower to Flower Next! " -"Whether you're a seasoned user or just getting started, this guide will " -"help you smoothly transition your existing setup to take advantage of the" -" latest features and improvements in Flower Next, starting from version " -"1.8." +"Welcome to the migration guide for updating Flower to Flower Next! Whether " +"you're a seasoned user or just getting started, this guide will help you " +"smoothly transition your existing setup to take advantage of the latest " +"features and improvements in Flower Next, starting from version 1.8." msgstr "" +"Flower에서 Flower Next로의 업데이트를 위한 이동 가이드에 오신 것을 환영합니" +"다! 이 가이드는 숙련된 사용자든 이제 막 시작한 사용자든 상관없이 기존 설정을 " +"원활하게 전환하여 버전 1.8부터 Flower Next의 최신 기능 및 개선 사항을 활용할 " +"수 있도록 도와드립니다." #: ../../source/how-to-upgrade-to-flower-next.rst:9 msgid "" "This guide shows how to reuse pre-``1.8`` Flower code with minimum code " -"changes by using the *compatibility layer* in Flower Next. In another " -"guide, we will show how to run Flower Next end-to-end with pure Flower " -"Next APIs." +"changes by using the *compatibility layer* in Flower Next. In another guide, " +"we will show how to run Flower Next end-to-end with pure Flower Next APIs." msgstr "" +"이 가이드에서는 Flower Next의 *호환성 레이어*를 사용하여 최소한의 코드 변경으" +"로 ``1.8`` 이전의 Flower 코드를 재사용하는 방법을 보여줍니다. 다른 가이드에서" +"는 순수한 Flower Next API로 Flower Next를 end-to-end로 실행하는 방법을 보여드" +"리겠습니다." #: ../../source/how-to-upgrade-to-flower-next.rst:13 msgid "Let's dive in!" -msgstr "" +msgstr "자세히 알아봅시다!" #: ../../source/how-to-upgrade-to-flower-next.rst:48 msgid "" -"Here's how to update an existing installation of Flower to Flower Next " -"with ``pip``:" +"Here's how to update an existing installation of Flower to Flower Next with " +"``pip``:" msgstr "" +"기존에 설치된 Flower to Flower Next를 ``pip``으로 업데이트하는 방법은 다음과 " +"같습니다:" #: ../../source/how-to-upgrade-to-flower-next.rst:54 msgid "or if you need Flower Next with simulation:" -msgstr "" +msgstr "또는 시뮬레이션이 포함된 Flower Next가 필요한 경우:" #: ../../source/how-to-upgrade-to-flower-next.rst:61 msgid "" -"Ensure you set the following version constraint in your " -"``requirements.txt``" -msgstr "" +"Ensure you set the following version constraint in your ``requirements.txt``" +msgstr "``requirements.txt``에서 다음 버전 제약 조건을 설정했는지 확인하세요" #: ../../source/how-to-upgrade-to-flower-next.rst:71 msgid "or ``pyproject.toml``:" -msgstr "" +msgstr "또는 ``pyproject.toml``:" #: ../../source/how-to-upgrade-to-flower-next.rst:82 msgid "Using Poetry" -msgstr "" +msgstr "Poetry 사용" #: ../../source/how-to-upgrade-to-flower-next.rst:84 msgid "" @@ -7053,122 +7379,155 @@ msgid "" "(don't forget to delete ``poetry.lock`` via ``rm poetry.lock`` before " "running ``poetry install``)." msgstr "" +"``pyproject.toml``에서 ``flwr`` 의존성를 업데이트한 다음 다시 설치하세요(``" +"poetry install``을 실행하기 전에 ``rm poetry.lock``을 통해 ``poetry.lock``을 " +"삭제하는 것을 잊지 마세요)." #: ../../source/how-to-upgrade-to-flower-next.rst:86 msgid "" -"Ensure you set the following version constraint in your " -"``pyproject.toml``:" -msgstr "" +"Ensure you set the following version constraint in your ``pyproject.toml``:" +msgstr "``pyproject.toml``에 다음 버전 제약 조건을 설정했는지 확인하세요:" #: ../../source/how-to-upgrade-to-flower-next.rst:102 msgid "" "In Flower Next, the *infrastructure* and *application layers* have been " -"decoupled. Instead of starting a client in code via ``start_client()``, " -"you create a |clientapp_link|_ and start it via the command line. Instead" -" of starting a server in code via ``start_server()``, you create a " -"|serverapp_link|_ and start it via the command line. The long-running " +"decoupled. Instead of starting a client in code via ``start_client()``, you " +"create a |clientapp_link|_ and start it via the command line. Instead of " +"starting a server in code via ``start_server()``, you create a |" +"serverapp_link|_ and start it via the command line. The long-running " "components of server and client are called SuperLink and SuperNode. The " -"following non-breaking changes that require manual updates and allow you " -"to run your project both in the traditional way and in the Flower Next " -"way:" +"following non-breaking changes that require manual updates and allow you to " +"run your project both in the traditional way and in the Flower Next way:" msgstr "" +"Flower Next에서는 *infrastructure*와 *application layers*가 분리되었습니다. " +"코드에서 ``start_client()``를 통해 클라이언트를 시작하는 대신, 명령줄을 통해 " +"|clientapp_link|_를 생성하여 시작합니다. 코드에서 ``start_server()``를 통해 " +"서버를 시작하는 대신 |serverapp_link|_를 생성하고 명령줄을 통해 서버를 시작합" +"니다. 서버와 클라이언트의 장기 실행 컴포넌트를 SuperLink와 SuperNode라고 합니" +"다. 수동 업데이트가 필요하지 않고 기존 방식과 Flower Next 방식 모두에서 프로" +"젝트를 실행할 수 있는 non-breaking 변경 사항은 다음과 같습니다:" #: ../../source/how-to-upgrade-to-flower-next.rst:109 msgid "|clientapp_link|_" -msgstr "" +msgstr "|clientapp_link|_" #: ../../source/how-to-upgrade-to-flower-next.rst:110 msgid "" -"Wrap your existing client with |clientapp_link|_ instead of launching it " -"via |startclient_link|_. Here's an example:" +"Wrap your existing client with |clientapp_link|_ instead of launching it via " +"|startclient_link|_. Here's an example:" msgstr "" +"|clientapp_link|_를 통해 실행하는 대신 기존 클라이언트를 |clientapp_link|_로 " +"래핑하세요. 다음은 예시입니다:" #: ../../source/how-to-upgrade-to-flower-next.rst:132 msgid "|serverapp_link|_" -msgstr "" +msgstr "|serverapp_link|_" #: ../../source/how-to-upgrade-to-flower-next.rst:133 msgid "" -"Wrap your existing strategy with |serverapp_link|_ instead of starting " -"the server via |startserver_link|_. Here's an example:" +"Wrap your existing strategy with |serverapp_link|_ instead of starting the " +"server via |startserver_link|_. Here's an example:" msgstr "" +"서버를 시작하려면 |startserver_link|_를 통해 서버를 시작하는 대신 기존 전략" +"을 |serverapp_link|_로 래핑하세요. 다음은 예시입니다:" #: ../../source/how-to-upgrade-to-flower-next.rst:154 msgid "Deployment" -msgstr "" +msgstr "배포" #: ../../source/how-to-upgrade-to-flower-next.rst:155 msgid "" -"Run the ``SuperLink`` using |flowernext_superlink_link|_ before running, " -"in sequence, |flowernext_clientapp_link|_ (2x) and " -"|flowernext_serverapp_link|_. There is no need to execute `client.py` and" -" `server.py` as Python scripts." +"Run the ``SuperLink`` using |flowernext_superlink_link|_ before running, in " +"sequence, |flowernext_clientapp_link|_ (2x) and |flowernext_serverapp_link|" +"_. There is no need to execute `client.py` and `server.py` as Python scripts." msgstr "" +"실행하기 전에 |flowernext_superlink_link|_를 사용하여 ``SuperLink``를 실행한 " +"후 |flowernext_clientapp_link|_(2회) 및 |flowernext_serverapp_link|_를 " +"순서대로 실행합니다. 'client.py'와 'server.py'를 Python 스크립트로 실행할 " +"필요는 없습니다." #: ../../source/how-to-upgrade-to-flower-next.rst:158 msgid "" -"Here's an example to start the server without HTTPS (only for " -"prototyping):" +"Here's an example to start the server without HTTPS (only for prototyping):" msgstr "" +"다음은 HTTPS 없이 서버를 시작하는 예제입니다(프로토타이핑용으로만 사용):" #: ../../source/how-to-upgrade-to-flower-next.rst:174 msgid "" -"Here's another example to start with HTTPS. Use the ``--ssl-ca-" -"certfile``, ``--ssl-certfile``, and ``--ssl-keyfile`` command line " -"options to pass paths to (CA certificate, server certificate, and server " -"private key)." +"Here's another example to start with HTTPS. Use the ``--ssl-ca-certfile``, " +"``--ssl-certfile``, and ``--ssl-keyfile`` command line options to pass paths " +"to (CA certificate, server certificate, and server private key)." msgstr "" +"다음은 HTTPS로 시작하는 또 다른 예제입니다. '`--ssl-ca-certfile``, '`--ssl-" +"certfile``, '`--ssl-keyfile`` 명령줄 옵션을 사용하여 (CA 인증서, 서버 인증서 " +"및 서버 개인 키)의 경로를 전달합니다." #: ../../source/how-to-upgrade-to-flower-next.rst:201 msgid "Simulation in CLI" -msgstr "" +msgstr "CLI 시뮬레이션" #: ../../source/how-to-upgrade-to-flower-next.rst:202 msgid "" -"Wrap your existing client and strategy with |clientapp_link|_ and " -"|serverapp_link|_, respectively. There is no need to use |startsim_link|_" -" anymore. Here's an example:" +"Wrap your existing client and strategy with |clientapp_link|_ and |" +"serverapp_link|_, respectively. There is no need to use |startsim_link|_ " +"anymore. Here's an example:" msgstr "" +"기존 클라이언트와 전략을 각각 |clientapp_link|_와 |serverapp_link|_로 래핑하" +"세요. 더 이상 |startsim_link|_를 사용할 필요가 없습니다. 다음은 예시입니다:" #: ../../source/how-to-upgrade-to-flower-next.rst:232 msgid "" "Run |flower_simulation_link|_ in CLI and point to the ``server_app`` / " -"``client_app`` object in the code instead of executing the Python script." -" Here's an example (assuming the ``server_app`` and ``client_app`` " -"objects are in a ``sim.py`` module):" +"``client_app`` object in the code instead of executing the Python script. " +"Here's an example (assuming the ``server_app`` and ``client_app`` objects " +"are in a ``sim.py`` module):" msgstr "" +"CLI에서 |flower_simulation_link|_를 실행하고 Python 스크립트를 실행하는 대신 " +"코드에서 ``server_app`` / ``client_app`` 개체를 가리키세요. 다음은 예제입니다" +"(``server_app`` 및 ``client_app`` 객체가 ``sim.py`` 모듈에 있다고 가정):" #: ../../source/how-to-upgrade-to-flower-next.rst:249 msgid "" "Set default resources for each |clientapp_link|_ using the ``--backend-" -"config`` command line argument instead of setting the " -"``client_resources`` argument in |startsim_link|_. Here's an example:" +"config`` command line argument instead of setting the ``client_resources`` " +"argument in |startsim_link|_. Here's an example:" msgstr "" +"|startsim_link|_에서 ``client_resources`` 인수를 설정하는 대신 ``--backend-" +"config`` 명령줄 인수를 사용하여 각 |clientapp_link|_에 대한 기본 리소스를 설" +"정하세요. 다음은 예시입니다:" #: ../../source/how-to-upgrade-to-flower-next.rst:275 msgid "Simulation in a Notebook" -msgstr "" +msgstr "Notebook에서 시뮬레이션" #: ../../source/how-to-upgrade-to-flower-next.rst:276 msgid "" -"Run |runsim_link|_ in your notebook instead of |startsim_link|_. Here's " -"an example:" +"Run |runsim_link|_ in your notebook instead of |startsim_link|_. Here's an " +"example:" msgstr "" +"notebook에서 |startsim_link|_ 대신 |runsim_link|_를 실행하세요. 다음은 예시입" +"니다:" #: ../../source/how-to-upgrade-to-flower-next.rst:319 msgid "" -"Some official `Flower code examples `_ " -"are already updated to Flower Next so they can serve as a reference for " -"using the Flower Next API. If there are further questions, `join the " -"Flower Slack `_ and use the channel " -"``#questions``. You can also `participate in Flower Discuss " -"`_ where you can find us answering questions," -" or share and learn from others about migrating to Flower Next." -msgstr "" +"Some official `Flower code examples `_ are " +"already updated to Flower Next so they can serve as a reference for using " +"the Flower Next API. If there are further questions, `join the Flower Slack " +"`_ and use the channel ``#questions``. You " +"can also `participate in Flower Discuss `_ where " +"you can find us answering questions, or share and learn from others about " +"migrating to Flower Next." +msgstr "" +"일부 공식 ``Flower 코드 예제 `_는 이미 플라" +"워 넥스트에 업데이트되어 있으므로 플라워 넥스트 API를 사용하는 데 참고할 수 " +"있습니다. 더 궁금한 점이 있다면 ``플라워 슬랙 `_에 가입하고 ``#questions`` 채널을 이용하세요. 또한, ``Flower Discuss " +"`_에 참여하여 질문에 대한 답변을 확인하거나 다른 " +"사람들과 Flower Next로의 이동에 대해 공유하고 배울 수 있습니다." #: ../../source/how-to-upgrade-to-flower-next.rst:325 msgid "Important" -msgstr "" +msgstr "중요" #: ../../source/how-to-upgrade-to-flower-next.rst:328 msgid "" @@ -7176,322 +7535,399 @@ msgid "" "periodically updating this guide. Please feel free to share any feedback " "with us!" msgstr "" +"Flower Next는 빠른 속도로 지속적으로 개선되고 있으므로 이 가이드는 주기적으" +"로 업데이트될 예정입니다. 피드백이 있으면 언제든지 공유해 주세요!" #: ../../source/how-to-upgrade-to-flower-next.rst:334 msgid "Happy migrating! 🚀" -msgstr "" +msgstr "행복한 마이그레이션! 🚀" #: ../../source/how-to-use-built-in-mods.rst:2 msgid "Use Built-in Mods" -msgstr "" +msgstr "기본 제공 모드 사용" #: ../../source/how-to-use-built-in-mods.rst:4 msgid "" -"**Note: This tutorial covers experimental features. The functionality and" -" interfaces may change in future versions.**" +"**Note: This tutorial covers experimental features. The functionality and " +"interfaces may change in future versions.**" msgstr "" +"**참고: 이 튜토리얼은 실험적인 기능을 다룹니다. 기능 및 인터페이스는 향후 버" +"전에서 변경될 수 있습니다.**" #: ../../source/how-to-use-built-in-mods.rst:6 msgid "" -"In this tutorial, we will learn how to utilize built-in mods to augment " -"the behavior of a ``ClientApp``. Mods (sometimes also called Modifiers) " -"allow us to perform operations before and after a task is processed in " -"the ``ClientApp``." +"In this tutorial, we will learn how to utilize built-in mods to augment the " +"behavior of a ``ClientApp``. Mods (sometimes also called Modifiers) allow us " +"to perform operations before and after a task is processed in the " +"``ClientApp``." msgstr "" +"이 튜토리얼에서는 내장 모드를 활용하여 ``ClientApp``의 동작을 보강하는 방법" +"을 배우겠습니다. Mods(Modifiers라고도 함)를 사용하면 ``ClientApp``에서 작업" +"이 처리되기 전과 후에 작업을 수행할 수 있습니다." #: ../../source/how-to-use-built-in-mods.rst:9 msgid "What are Mods?" -msgstr "" +msgstr "Mods란 무엇인가요?" #: ../../source/how-to-use-built-in-mods.rst:11 msgid "" -"A Mod is a callable that wraps around a ``ClientApp``. It can manipulate " -"or inspect the incoming ``Message`` and the resulting outgoing " -"``Message``. The signature for a ``Mod`` is as follows:" +"A Mod is a callable that wraps around a ``ClientApp``. It can manipulate or " +"inspect the incoming ``Message`` and the resulting outgoing ``Message``. The " +"signature for a ``Mod`` is as follows:" msgstr "" +"Mod는 ``ClientApp``을 감싸는 콜러블입니다. 들어오는 ``Message``와 그 결과로 " +"나가는 ``Message``를 조작하거나 검사할 수 있습니다. ``Mod``의 시그니처는 다음" +"과 같습니다:" #: ../../source/how-to-use-built-in-mods.rst:18 msgid "A typical mod function might look something like this:" -msgstr "" +msgstr "일반적인 mod 함수는 다음과 같은 모습일 수 있습니다:" #: ../../source/how-to-use-built-in-mods.rst:31 msgid "Using Mods" -msgstr "" +msgstr "Mods 사용" #: ../../source/how-to-use-built-in-mods.rst:33 msgid "To use mods in your ``ClientApp``, you can follow these steps:" -msgstr "" +msgstr "``ClientApp``에서 mods를 사용하려면 다음 단계를 따르세요:" #: ../../source/how-to-use-built-in-mods.rst:36 msgid "1. Import the required mods" -msgstr "" +msgstr "1. 필요한 mods를 가져옵니다" #: ../../source/how-to-use-built-in-mods.rst:38 msgid "First, import the built-in mod you intend to use:" -msgstr "" +msgstr "먼저 사용하려는 기본 제공 mod를 가져옵니다:" #: ../../source/how-to-use-built-in-mods.rst:46 msgid "2. Define your client function" -msgstr "" +msgstr "2. 클라이언트 기능 정의" #: ../../source/how-to-use-built-in-mods.rst:48 msgid "" "Define your client function (``client_fn``) that will be wrapped by the " "mod(s):" -msgstr "" +msgstr "mod(s)로 래핑할 클라이언트 함수('``client_fn``)를 정의합니다:" #: ../../source/how-to-use-built-in-mods.rst:57 msgid "3. Create the ``ClientApp`` with mods" -msgstr "" +msgstr "3. mods로 ``ClientApp``을 생성합니다" #: ../../source/how-to-use-built-in-mods.rst:59 msgid "" "Create your ``ClientApp`` and pass the mods as a list to the ``mods`` " "argument. The order in which you provide the mods matters:" msgstr "" +"``ClientApp``을 생성하고 mods를 ``mods`` argument에 목록으로 전달합니다. mods" +"를 제공하는 순서가 중요합니다:" #: ../../source/how-to-use-built-in-mods.rst:72 msgid "Order of execution" -msgstr "" +msgstr "실행 순서" #: ../../source/how-to-use-built-in-mods.rst:74 msgid "" "When the ``ClientApp`` runs, the mods are executed in the order they are " "provided in the list:" -msgstr "" +msgstr "``ClientApp``이 실행되면 목록에 제공된 순서대로 모드가 실행됩니다:" #: ../../source/how-to-use-built-in-mods.rst:76 msgid "``example_mod_1`` (outermost mod)" -msgstr "" +msgstr "``example_mod_1``(가장 바깥쪽 mod)" #: ../../source/how-to-use-built-in-mods.rst:77 msgid "``example_mod_2`` (next mod)" -msgstr "" +msgstr "``example_mod_2`` (다음 mod)" #: ../../source/how-to-use-built-in-mods.rst:78 msgid "" "Message handler (core function that handles the incoming ``Message`` and " "returns the outgoing ``Message``)" msgstr "" +"Message handler(들어오는 ``Message``를 처리하고 나가는 ``Message``를 반환하" +"는 핵심 함수)" #: ../../source/how-to-use-built-in-mods.rst:79 msgid "``example_mod_2`` (on the way back)" -msgstr "" +msgstr "``example_mod_2``(돌아가는 방법)" #: ../../source/how-to-use-built-in-mods.rst:80 msgid "``example_mod_1`` (outermost mod on the way back)" -msgstr "" +msgstr "``example_mod_1``(돌아가는 방법에 가장 바깥쪽 모드)" #: ../../source/how-to-use-built-in-mods.rst:82 msgid "" -"Each mod has a chance to inspect and modify the incoming ``Message`` " -"before passing it to the next mod, and likewise with the outgoing " -"``Message`` before returning it up the stack." +"Each mod has a chance to inspect and modify the incoming ``Message`` before " +"passing it to the next mod, and likewise with the outgoing ``Message`` " +"before returning it up the stack." msgstr "" +"각 mod는 다음 mod로 전달하기 전에 들어오는 ``Message``를 검사하고 수정할 기회" +"가 있으며, 스택 위로 반환하기 전에 나가는 ``Message``도 마찬가지로 검사하고 " +"수정할 수 있습니다." #: ../../source/how-to-use-built-in-mods.rst:87 msgid "" "By following this guide, you have learned how to effectively use mods to " -"enhance your ``ClientApp``'s functionality. Remember that the order of " -"mods is crucial and affects how the input and output are processed." +"enhance your ``ClientApp``'s functionality. Remember that the order of mods " +"is crucial and affects how the input and output are processed." msgstr "" +"이 가이드를 따라 mods를 효과적으로 사용하여 ``ClientApp``의 기능을 향상시키" +"는 방법을 배웠습니다. mods 순서는 매우 중요하며 입력과 출력이 처리되는 방식" +"에 영향을 미친다는 점을 기억하세요." #: ../../source/how-to-use-built-in-mods.rst:89 msgid "Enjoy building a more robust and flexible ``ClientApp`` with mods!" -msgstr "" +msgstr "Mods를 통해 더욱 강력하고 유연한 ``ClientApp``을 구축해 보세요!" #: ../../source/how-to-use-differential-privacy.rst:2 msgid "Use Differential Privacy" -msgstr "" +msgstr "차분 개인정보 보호 사용" #: ../../source/how-to-use-differential-privacy.rst:3 msgid "" -"This guide explains how you can utilize differential privacy in the " -"Flower framework. If you are not yet familiar with differential privacy, " -"you can refer to :doc:`explanation-differential-privacy`." +"This guide explains how you can utilize differential privacy in the Flower " +"framework. If you are not yet familiar with differential privacy, you can " +"refer to :doc:`explanation-differential-privacy`." msgstr "" +"이 가이드에서는 Flower 프레임워크에서 차분 개인정보 보호 기능을 활용하는 방법" +"을 설명합니다. 차분 개인정보 보호에 대해 아직 익숙하지 않은 경우 :doc:" +"`explanation-differential-privacy`를 참조하세요." #: ../../source/how-to-use-differential-privacy.rst:7 msgid "" "Differential Privacy in Flower is in a preview phase. If you plan to use " -"these features in a production environment with sensitive data, feel free" -" contact us to discuss your requirements and to receive guidance on how " -"to best use these features." +"these features in a production environment with sensitive data, feel free " +"contact us to discuss your requirements and to receive guidance on how to " +"best use these features." msgstr "" +"Flower의 차분 개인정보 보호는 현재 프리뷰 단계에 있습니다. 민감한 데이터가 있" +"는 프로덕션 환경에서 이러한 기능을 사용할 계획이라면 언제든지 문의하여 요구 " +"사항을 논의하고 이러한 기능을 가장 잘 사용하는 방법에 대한 안내를 받으세요." #: ../../source/how-to-use-differential-privacy.rst:12 msgid "" -"This approach consists of two seprate phases: clipping of the updates and" -" adding noise to the aggregated model. For the clipping phase, Flower " -"framework has made it possible to decide whether to perform clipping on " -"the server side or the client side." +"This approach consists of two seprate phases: clipping of the updates and " +"adding noise to the aggregated model. For the clipping phase, Flower " +"framework has made it possible to decide whether to perform clipping on the " +"server side or the client side." msgstr "" +"이 접근 방식은 업데이트 클리핑과 집계된 모델에 노이즈 추가라는 두 가지 단계" +"로 구성됩니다. 클리핑 단계의 경우, Flower 프레임워크는 클리핑을 서버 측에서 " +"수행할지 클라이언트 측에서 수행할지 결정할 수 있도록 했습니다." #: ../../source/how-to-use-differential-privacy.rst:15 msgid "" "**Server-side Clipping**: This approach has the advantage of the server " "enforcing uniform clipping across all clients' updates and reducing the " "communication overhead for clipping values. However, it also has the " -"disadvantage of increasing the computational load on the server due to " -"the need to perform the clipping operation for all clients." +"disadvantage of increasing the computational load on the server due to the " +"need to perform the clipping operation for all clients." msgstr "" +"**Server-side Clipping**: 이 방식은 서버가 모든 클라이언트의 업데이트에 대해 " +"균일한 클리핑을 적용하고 클리핑 값에 대한 통신 오버헤드를 줄일 수 있다는 장점" +"이 있습니다. 하지만 모든 클라이언트에 대해 클리핑 작업을 수행해야 하기 때문" +"에 서버의 계산 부하가 증가한다는 단점도 있습니다." #: ../../source/how-to-use-differential-privacy.rst:16 msgid "" -"**Client-side Clipping**: This approach has the advantage of reducing the" -" computational overhead on the server. However, it also has the " -"disadvantage of lacking centralized control, as the server has less " -"control over the clipping process." +"**Client-side Clipping**: This approach has the advantage of reducing the " +"computational overhead on the server. However, it also has the disadvantage " +"of lacking centralized control, as the server has less control over the " +"clipping process." msgstr "" +"**Client-side Clipping**: 이 방식은 서버의 계산 오버헤드를 줄일 수 있다는 장" +"점이 있습니다. 하지만 서버가 클리핑 프로세스에 대한 통제력이 떨어지기 때문에 " +"centralized 제어가 부족하다는 단점도 있습니다." #: ../../source/how-to-use-differential-privacy.rst:21 msgid "Server-side Clipping" -msgstr "" +msgstr "서버 측 클리핑" #: ../../source/how-to-use-differential-privacy.rst:22 msgid "" "For central DP with server-side clipping, there are two :code:`Strategy` " "classes that act as wrappers around the actual :code:`Strategy` instance " -"(for example, :code:`FedAvg`). The two wrapper classes are " -":code:`DifferentialPrivacyServerSideFixedClipping` and " -":code:`DifferentialPrivacyServerSideAdaptiveClipping` for fixed and " -"adaptive clipping." +"(for example, :code:`FedAvg`). The two wrapper classes are :code:" +"`DifferentialPrivacyServerSideFixedClipping` and :code:" +"`DifferentialPrivacyServerSideAdaptiveClipping` for fixed and adaptive " +"clipping." msgstr "" +"서버 측 클리핑이 있는 중앙 DP의 경우, 실제 :code:`Strategy` 인스턴스를 감싸" +"는 래퍼 역할을 하는 두 개의 :code:`Strategy` 클래스가 있습니다(예: :code:" +"`FedAvg`). 두 개의 래퍼 클래스는 고정 및 적응형 클리핑을 위한 :code:" +"`DifferentialPrivacyServerSideFixedClipping`과 :code:" +"`DifferentialPrivacyServerSideAdaptiveClipping`입니다." #: ../../source/how-to-use-differential-privacy.rst:-1 msgid "server side clipping" -msgstr "" +msgstr "서버 측 클리핑" #: ../../source/how-to-use-differential-privacy.rst:31 msgid "" -"The code sample below enables the :code:`FedAvg` strategy to use server-" -"side fixed clipping using the " -":code:`DifferentialPrivacyServerSideFixedClipping` wrapper class. The " -"same approach can be used with " -":code:`DifferentialPrivacyServerSideAdaptiveClipping` by adjusting the " +"The code sample below enables the :code:`FedAvg` strategy to use server-side " +"fixed clipping using the :code:`DifferentialPrivacyServerSideFixedClipping` " +"wrapper class. The same approach can be used with :code:" +"`DifferentialPrivacyServerSideAdaptiveClipping` by adjusting the " "corresponding input parameters." msgstr "" +"아래 코드 샘플은 :code:`FedAvg` 전략이 :code:" +"`DifferentialPrivacyServerSideFixedClipping` 래퍼 클래스를 사용하여 서버 측 " +"고정 클리핑을 사용할 수 있도록 합니다. 해당 입력 매개변수를 조정하여 :code:" +"`DifferentialPrivacyServerSideAdaptiveClipping`과 동일한 접근 방식을 사용할 " +"수 있습니다." #: ../../source/how-to-use-differential-privacy.rst:52 msgid "Client-side Clipping" -msgstr "" +msgstr "클라이언트 측 클리핑" #: ../../source/how-to-use-differential-privacy.rst:53 msgid "" "For central DP with client-side clipping, the server sends the clipping " -"value to selected clients on each round. Clients can use existing Flower " -":code:`Mods` to perform the clipping. Two mods are available for fixed " -"and adaptive client-side clipping: :code:`fixedclipping_mod` and " -":code:`adaptiveclipping_mod` with corresponding server-side wrappers " -":code:`DifferentialPrivacyClientSideFixedClipping` and " -":code:`DifferentialPrivacyClientSideAdaptiveClipping`." -msgstr "" +"value to selected clients on each round. Clients can use existing Flower :" +"code:`Mods` to perform the clipping. Two mods are available for fixed and " +"adaptive client-side clipping: :code:`fixedclipping_mod` and :code:" +"`adaptiveclipping_mod` with corresponding server-side wrappers :code:" +"`DifferentialPrivacyClientSideFixedClipping` and :code:" +"`DifferentialPrivacyClientSideAdaptiveClipping`." +msgstr "" +"클라이언트 측 클리핑이 있는 중앙 DP의 경우 서버는 각 라운드마다 선택한 클라이" +"언트에 클리핑 값을 보냅니다. 클라이언트는 기존 Flower :code:`Mods`를 사용하" +"여 클리핑을 수행할 수 있습니다. 고정 및 적응형 클라이언트 측 클리핑에는 두 가" +"지 모드를 사용할 수 있습니다: :code:`fixedclipping_mod` 및 :code:" +"`adaptiveclipping_mod`와 해당 서버 측 래퍼 :code:" +"`DifferentialPrivacyClientSideFixedClipping` 및 :code:" +"`DifferentialPrivacyClientSideAdaptiveClipping`이 있습니다." #: ../../source/how-to-use-differential-privacy.rst:-1 msgid "client side clipping" -msgstr "" +msgstr "클라이언트 측 클리핑" #: ../../source/how-to-use-differential-privacy.rst:63 msgid "" "The code sample below enables the :code:`FedAvg` strategy to use " -"differential privacy with client-side fixed clipping using both the " -":code:`DifferentialPrivacyClientSideFixedClipping` wrapper class and, on " -"the client, :code:`fixedclipping_mod`:" +"differential privacy with client-side fixed clipping using both the :code:" +"`DifferentialPrivacyClientSideFixedClipping` wrapper class and, on the " +"client, :code:`fixedclipping_mod`:" msgstr "" +"아래 코드 샘플은 :code:`FedAvg` 전략이 클라이언트 측 고정 클리핑과 함께 차분 " +"프라이버시를 사용할 수 있도록 :code:" +"`DifferentialPrivacyClientSideFixedClipping` 래퍼 클래스와 클라이언트에서 :" +"code:`fixedclipping_mod`를 모두 사용하도록 합니다:" #: ../../source/how-to-use-differential-privacy.rst:80 msgid "" -"In addition to the server-side strategy wrapper, the :code:`ClientApp` " -"needs to configure the matching :code:`fixedclipping_mod` to perform the " -"client-side clipping:" +"In addition to the server-side strategy wrapper, the :code:`ClientApp` needs " +"to configure the matching :code:`fixedclipping_mod` to perform the client-" +"side clipping:" msgstr "" +"서버 측 전략 래퍼 외에도 클라이언트 측 클리핑을 수행하려면 :code:`ClientApp`" +"이 일치하는 :code:`fixedclipping_mod`를 구성해야 합니다:" #: ../../source/how-to-use-differential-privacy.rst:97 msgid "" -"To utilize local differential privacy (DP) and add noise to the client " -"model parameters before transmitting them to the server in Flower, you " -"can use the `LocalDpMod`. The following hyperparameters need to be set: " -"clipping norm value, sensitivity, epsilon, and delta." +"To utilize local differential privacy (DP) and add noise to the client model " +"parameters before transmitting them to the server in Flower, you can use the " +"`LocalDpMod`. The following hyperparameters need to be set: clipping norm " +"value, sensitivity, epsilon, and delta." msgstr "" +"로컬 차분 프라이버시(DP)를 활용하고 클라이언트 모델 파라미터를 서버로 전송하" +"기 전에 노이즈를 추가하려면 `LocalDpMod`를 사용하면 됩니다. 클리핑 노멀 값, " +"감도, 엡실론, 델타 등의 하이퍼파라미터를 설정해야 합니다." #: ../../source/how-to-use-differential-privacy.rst:-1 msgid "local DP mod" -msgstr "" +msgstr "로컬 DP mod" #: ../../source/how-to-use-differential-privacy.rst:104 msgid "Below is a code example that shows how to use :code:`LocalDpMod`:" -msgstr "" +msgstr "다음은 :code:`LocalDpMod`를 사용하는 방법을 보여주는 코드 예시입니다:" #: ../../source/how-to-use-differential-privacy.rst:122 msgid "" -"Please note that the order of mods, especially those that modify " -"parameters, is important when using multiple modifiers. Typically, " -"differential privacy (DP) modifiers should be the last to operate on " -"parameters." +"Please note that the order of mods, especially those that modify parameters, " +"is important when using multiple modifiers. Typically, differential privacy " +"(DP) modifiers should be the last to operate on parameters." msgstr "" +"여러 개의 수정자를 사용할 때는 수정자, 특히 매개변수를 수정하는 수정자의 순서" +"가 중요하다는 점에 유의하세요. 일반적으로 차분 프라이버시(DP) 수정자는 매개변" +"수에서 가장 마지막에 작동해야 합니다." #: ../../source/how-to-use-differential-privacy.rst:125 msgid "Local Training using Privacy Engines" -msgstr "" +msgstr "Privacy Engines을 사용한 로컬 훈련" #: ../../source/how-to-use-differential-privacy.rst:126 msgid "" -"For ensuring data instance-level privacy during local model training on " -"the client side, consider leveraging privacy engines such as Opacus and " -"TensorFlow Privacy. For examples of using Flower with these engines, " -"please refer to the Flower examples directory (`Opacus " -"`_, `Tensorflow" -" Privacy `_)." +"For ensuring data instance-level privacy during local model training on the " +"client side, consider leveraging privacy engines such as Opacus and " +"TensorFlow Privacy. For examples of using Flower with these engines, please " +"refer to the Flower examples directory (`Opacus `_, `Tensorflow Privacy `_)." msgstr "" +"클라이언트 측에서 로컬 모델을 훈련하는 동안 데이터 인스턴스 수준의 개인 정보 " +"보호를 보장하려면 Opacus 및 TensorFlow Privacy와 같은 개인 정보 보호 엔진을 " +"활용하는 것을 고려하세요. 이러한 엔진과 함께 Flower를 사용하는 예제는 Flower " +"examples directory (`Opacus `_, `Tensorflow Privacy `_)를 참조하세요." #: ../../source/how-to-use-strategies.rst:2 msgid "Use strategies" -msgstr "" +msgstr "전략 사용하기" #: ../../source/how-to-use-strategies.rst:4 msgid "" -"Flower allows full customization of the learning process through the " -":code:`Strategy` abstraction. A number of built-in strategies are " -"provided in the core framework." +"Flower allows full customization of the learning process through the :code:" +"`Strategy` abstraction. A number of built-in strategies are provided in the " +"core framework." msgstr "" +"Flower는 :code:`Strategy` abstraction를 통해 학습 과정을 완전히 사용자 정의" +"할 수 있습니다. 핵심 프레임워크에는 여러 가지 기본 제공 전략이 제공됩니다." #: ../../source/how-to-use-strategies.rst:6 msgid "" -"There are three ways to customize the way Flower orchestrates the " -"learning process on the server side:" +"There are three ways to customize the way Flower orchestrates the learning " +"process on the server side:" msgstr "" +"서버 측에서 Flower가 학습 과정을 조율하는 방식을 사용자 지정하는 방법에는 세 " +"가지가 있습니다:" #: ../../source/how-to-use-strategies.rst:8 msgid "Use an existing strategy, for example, :code:`FedAvg`" -msgstr "" +msgstr "기존 전략(예: :code:`FedAvg`)을 사용합니다" #: ../../source/how-to-use-strategies.rst:9 #: ../../source/how-to-use-strategies.rst:40 msgid "Customize an existing strategy with callback functions" -msgstr "" +msgstr "콜백 함수로 기존 전략 사용자 지정" #: ../../source/how-to-use-strategies.rst:10 #: ../../source/how-to-use-strategies.rst:87 msgid "Implement a novel strategy" -msgstr "" +msgstr "새로운 전략 구현" #: ../../source/how-to-use-strategies.rst:14 msgid "Use an existing strategy" -msgstr "" +msgstr "기존 전략 사용" #: ../../source/how-to-use-strategies.rst:16 msgid "" -"Flower comes with a number of popular federated learning strategies " -"built-in. A built-in strategy can be instantiated as follows:" +"Flower comes with a number of popular federated learning strategies built-" +"in. A built-in strategy can be instantiated as follows:" msgstr "" +"Flower에는 여러 가지 인기 있는 연합 학습 전략이 기본으로 제공됩니다. 기본 " +"제공 전략은 다음과 같이 인스턴스화할 수 있습니다:" #: ../../source/how-to-use-strategies.rst:25 msgid "" -"This creates a strategy with all parameters left at their default values " -"and passes it to the :code:`start_server` function. It is usually " -"recommended to adjust a few parameters during instantiation:" +"This creates a strategy with all parameters left at their default values and " +"passes it to the :code:`start_server` function. It is usually recommended to " +"adjust a few parameters during instantiation:" msgstr "" +"이렇게 하면 모든 매개변수가 기본값으로 유지된 전략이 생성되어 :code:" +"`start_server` 함수에 전달됩니다. 일반적으로 인스턴스화 중에 몇 가지 매개변수" +"를 조정하는 것이 좋습니다:" #: ../../source/how-to-use-strategies.rst:42 msgid "" @@ -7499,118 +7935,138 @@ msgid "" "Callback functions allow strategies to call user-provided code during " "execution." msgstr "" +"기존 전략은 동작을 사용자 지정하는 여러 가지 방법을 제공합니다. 콜백 함수를 " +"사용하면 전략이 실행 중에 사용자가 제공한 코드를 호출할 수 있습니다." #: ../../source/how-to-use-strategies.rst:45 msgid "Configuring client fit and client evaluate" -msgstr "" +msgstr "클라이언트 적합성 및 클라이언트 평가 구성" #: ../../source/how-to-use-strategies.rst:47 msgid "" "The server can pass new configuration values to the client each round by " -"providing a function to :code:`on_fit_config_fn`. The provided function " -"will be called by the strategy and must return a dictionary of " -"configuration key values pairs that will be sent to the client. It must " -"return a dictionary of arbitrary configuration values :code:`client.fit`" -" and :code:`client.evaluate` functions during each round of federated " -"learning." -msgstr "" +"providing a function to :code:`on_fit_config_fn`. The provided function will " +"be called by the strategy and must return a dictionary of configuration key " +"values pairs that will be sent to the client. It must return a dictionary of " +"arbitrary configuration values :code:`client.fit` and :code:`client." +"evaluate` functions during each round of federated learning." +msgstr "" +"서버는 매 라운드마다 새로운 설정 값을 클라이언트에 전달하기 위해 " +":code:`on_fit_config_fn`에 함수를 제공할 수 있습니다. 제공된 함수는 전략에 " +"의해 호출되며 클라이언트에 전송될 구성 키 값 쌍의 dictionary를 반환해야 " +"합니다. 연합 학습의 각 라운드 동안 임의의 구성 값 dictionary인 :code:`client." +"fit` 및 :code:`client.evaluate` 함수를 반환해야 합니다." #: ../../source/how-to-use-strategies.rst:75 msgid "" "The :code:`on_fit_config_fn` can be used to pass arbitrary configuration " "values from server to client, and poetentially change these values each " -"round, for example, to adjust the learning rate. The client will receive " -"the dictionary returned by the :code:`on_fit_config_fn` in its own " -":code:`client.fit()` function." +"round, for example, to adjust the learning rate. The client will receive the " +"dictionary returned by the :code:`on_fit_config_fn` in its own :code:`client." +"fit()` function." msgstr "" +":code:`on_fit_config_fn`은 서버에서 클라이언트로 임의의 구성 값을 전달하고, " +"예를 들어 학습 속도를 조정하기 위해 매 라운드마다 이 값을 잠재적으로 변경하" +"는 데 사용할 수 있습니다. 클라이언트는 자체 :code:`client.fit()` 함수에서 :" +"code:`on_fit_config_fn`이 반환한 dictionary를 받습니다." #: ../../source/how-to-use-strategies.rst:78 msgid "" -"Similar to :code:`on_fit_config_fn`, there is also " -":code:`on_evaluate_config_fn` to customize the configuration sent to " -":code:`client.evaluate()`" +"Similar to :code:`on_fit_config_fn`, there is also :code:" +"`on_evaluate_config_fn` to customize the configuration sent to :code:`client." +"evaluate()`" msgstr "" +":code:`on_fit_config_fn`과 유사하게, :code:`client.evaluate()`로 전송되는 구" +"성을 사용자 지정하는 :code:`on_evaluate_config_fn`도 있습니다" #: ../../source/how-to-use-strategies.rst:81 msgid "Configuring server-side evaluation" -msgstr "" +msgstr "서버 측 평가 구성" #: ../../source/how-to-use-strategies.rst:83 msgid "" -"Server-side evaluation can be enabled by passing an evaluation function " -"to :code:`evaluate_fn`." +"Server-side evaluation can be enabled by passing an evaluation function to :" +"code:`evaluate_fn`." msgstr "" +"서버 측 평가는 :code:`evaluate_fn`에 평가 함수를 전달하여 활성화할 수 있습니" +"다." #: ../../source/how-to-use-strategies.rst:89 msgid "" -"Writing a fully custom strategy is a bit more involved, but it provides " -"the most flexibility. Read the `Implementing Strategies `_ guide to learn more." +"Writing a fully custom strategy is a bit more involved, but it provides the " +"most flexibility. Read the `Implementing Strategies `_ guide to learn more." msgstr "" +"완전한 사용자 지정 전략을 작성하는 것은 조금 더 복잡하지만 유연성이 가장 뛰어" +"납니다. 자세한 내용은 `Implementing Strategies `_ 가이드를 참조하세요." #: ../../source/index.rst:34 msgid "Tutorial" -msgstr "" +msgstr "튜토리얼" #: ../../source/index.rst:44 msgid "Quickstart tutorials" -msgstr "" +msgstr "빠른 시작 튜토리얼" #: ../../source/index.rst:74 ../../source/index.rst:78 msgid "How-to guides" -msgstr "" +msgstr "사용 방법 가이드" #: ../../source/index.rst:99 msgid "Legacy example guides" -msgstr "" +msgstr "레거시 예제 가이드" #: ../../source/index.rst:108 ../../source/index.rst:112 msgid "Explanations" -msgstr "" +msgstr "설명" #: None:-1 msgid "API reference" -msgstr "" +msgstr "API 참조" #: ../../source/index.rst:137 msgid "Reference docs" -msgstr "" +msgstr "참조 문서" #: ../../source/index.rst:153 msgid "Contributor tutorials" -msgstr "" +msgstr "기여자 튜토리얼" #: ../../source/index.rst:160 msgid "Contributor how-to guides" -msgstr "" +msgstr "기여자 사용법 가이드" #: ../../source/index.rst:172 msgid "Contributor explanations" -msgstr "" +msgstr "기여자 설명" #: ../../source/index.rst:178 msgid "Contributor references" -msgstr "" +msgstr "기여자 참조" #: ../../source/index.rst:-1 msgid "" "Check out the documentation of the main Flower Framework enabling easy " "Python development for Federated Learning." -msgstr "" +msgstr "연합 학습을 위한 Python 개발을 쉽게 할 수 있는 주요 Flower 프레임워크의 " +"설명서를 확인하세요." #: ../../source/index.rst:2 msgid "Flower Framework Documentation" -msgstr "" +msgstr "플라워 프레임워크 문서" #: ../../source/index.rst:7 msgid "" "Welcome to Flower's documentation. `Flower `_ is a " "friendly federated learning framework." msgstr "" +"Flower 문서에 오신 것을 환영합니다. Flower `_는 편한 연합 " +"학습 프레임워크입니다." #: ../../source/index.rst:11 msgid "Join the Flower Community" -msgstr "" +msgstr "Flower 커뮤니티 가입하기" #: ../../source/index.rst:13 msgid "" @@ -7618,158 +8074,175 @@ msgid "" "researchers, engineers, students, professionals, academics, and other " "enthusiasts." msgstr "" +"Flower 커뮤니티는 연구원, 엔지니어, 학생, 전문가, 학자 및 기타 애호가들로 구" +"성된 편한 그룹으로 빠르게 성장하고 있습니다." #: ../../source/index.rst:15 msgid "Join us on Slack" -msgstr "" +msgstr "Slack에 가입하세요" #: ../../source/index.rst:23 msgid "Flower Framework" -msgstr "" +msgstr "Flower 프레임워크" #: ../../source/index.rst:25 msgid "" "The user guide is targeted at researchers and developers who want to use " "Flower to bring existing machine learning workloads into a federated " -"setting. One of Flower's design goals was to make this simple. Read on to" -" learn more." +"setting. One of Flower's design goals was to make this simple. Read on to " +"learn more." msgstr "" +"이 사용자 가이드는 Flower를 사용해 기존 머신 러닝 워크로드를 연합된 환경으로 " +"가져오고자 하는 연구자와 개발자를 대상으로 합니다. Flower의 설계 목표 중 하나" +"는 이를 간단하게 만드는 것이었습니다. 자세히 알아보려면 계속 읽어보세요." #: ../../source/index.rst:30 msgid "Tutorials" -msgstr "" +msgstr "튜토리얼" #: ../../source/index.rst:32 msgid "" -"A learning-oriented series of federated learning tutorials, the best " -"place to start." -msgstr "" +"A learning-oriented series of federated learning tutorials, the best place " +"to start." +msgstr "학습 중심의 연합 학습 튜토리얼 시리즈로, 시작하기에 가장 좋은 곳입니다." #: ../../source/index.rst:61 msgid "" -"QUICKSTART TUTORIALS: :doc:`PyTorch ` | " -":doc:`TensorFlow ` | :doc:`🤗 Transformers" -" ` | :doc:`JAX ` | :doc:`Pandas ` | :doc:`fastai " -"` | :doc:`PyTorch Lightning ` | :doc:`scikit-learn ` | :doc:`XGBoost ` | " -":doc:`Android ` | :doc:`iOS `" -msgstr "" +"QUICKSTART TUTORIALS: :doc:`PyTorch ` | :doc:" +"`TensorFlow ` | :doc:`🤗 Transformers " +"` | :doc:`JAX ` | :" +"doc:`Pandas ` | :doc:`fastai ` | :doc:`PyTorch Lightning ` | :doc:`scikit-learn ` | :doc:" +"`XGBoost ` | :doc:`Android ` | :doc:`iOS `" +msgstr "" +"QUICKSTART TUTORIALS: :doc:`PyTorch ` | :doc:" +"`TensorFlow ` | :doc:`🤗 Transformers " +"` | :doc:`JAX ` | :" +"doc:`Pandas ` | :doc:`fastai ` | :doc:`PyTorch Lightning ` | :doc:`scikit-learn ` | :doc:" +"`XGBoost ` | :doc:`Android ` | :doc:`iOS `" #: ../../source/index.rst:63 msgid "We also made video tutorials for PyTorch:" -msgstr "" +msgstr "파이토치용 동영상 튜토리얼도 만들었습니다:" #: ../../source/index.rst:68 msgid "And TensorFlow:" -msgstr "" +msgstr "그리고 TensorFlow도:" #: ../../source/index.rst:76 msgid "" -"Problem-oriented how-to guides show step-by-step how to achieve a " -"specific goal." +"Problem-oriented how-to guides show step-by-step how to achieve a specific " +"goal." msgstr "" +"문제 중심의 방법 가이드는 특정 목표를 달성하는 방법을 단계별로 보여줍니다." #: ../../source/index.rst:110 msgid "" "Understanding-oriented concept guides explain and discuss key topics and " "underlying ideas behind Flower and collaborative AI." msgstr "" +"이해 중심의 개념 가이드에서는 Flower와 협업 AI의 주요 주제와 기본 아이디어를 " +"설명하고 토론합니다." #: ../../source/index.rst:120 msgid "References" -msgstr "" +msgstr "참조" #: ../../source/index.rst:122 msgid "Information-oriented API reference and other reference material." -msgstr "" +msgstr "정보 지향 API 참조 및 기타 참고 자료." #: ../../source/index.rst:131::1 msgid ":py:obj:`flwr `\\" -msgstr "" +msgstr ":py:obj:`flwr `\\" #: ../../source/index.rst:131::1 flwr:1 of msgid "Flower main package." -msgstr "" +msgstr "Flower 메인 패키지." #: ../../source/index.rst:148 msgid "Contributor docs" -msgstr "" +msgstr "기여자 문서" #: ../../source/index.rst:150 msgid "" -"The Flower community welcomes contributions. The following docs are " -"intended to help along the way." +"The Flower community welcomes contributions. The following docs are intended " +"to help along the way." msgstr "" +"Flower 커뮤니티는 여러분의 기여를 환영합니다. 다음 문서는 그 과정에서 도움을 " +"드리기 위한 문서입니다." #: ../../source/ref-api-cli.rst:2 msgid "Flower CLI reference" -msgstr "" +msgstr "Flower CLI 참조" #: ../../source/ref-api-cli.rst:7 msgid "flower-simulation" -msgstr "" +msgstr "flower 시뮬레이션" #: ../../source/ref-api-cli.rst:17 msgid "flower-superlink" -msgstr "" +msgstr "flower 초연결" #: ../../source/ref-api-cli.rst:27 msgid "flower-client-app" -msgstr "" +msgstr "flower 클라이언트 앱" #: ../../source/ref-api-cli.rst:37 msgid "flower-server-app" -msgstr "" +msgstr "flower 서버 프로그램" #: ../../source/ref-api/flwr.rst:2 msgid "flwr" -msgstr "" +msgstr "flwr" #: ../../source/ref-api/flwr.client.rst:45 ../../source/ref-api/flwr.rst:25 #: ../../source/ref-api/flwr.server.rst:49 msgid "Modules" -msgstr "" +msgstr "Modules" #: ../../source/ref-api/flwr.rst:35::1 msgid ":py:obj:`flwr.client `\\" -msgstr "" +msgstr ":py:obj:`flwr.client `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.client:1 of msgid "Flower client." -msgstr "" +msgstr "Flower 클라이언트." #: ../../source/ref-api/flwr.rst:35::1 msgid ":py:obj:`flwr.common `\\" -msgstr "" +msgstr ":py:obj:`flwr.common `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.common:1 of msgid "Common components shared between server and client." -msgstr "" +msgstr "서버와 클라이언트 간에 공유되는 공통 구성 요소입니다." #: ../../source/ref-api/flwr.rst:35::1 msgid ":py:obj:`flwr.server `\\" -msgstr "" +msgstr ":py:obj:`flwr.server `\\" #: ../../source/ref-api/flwr.rst:35::1 #: ../../source/ref-api/flwr.server.rst:38::1 flwr.server:1 #: flwr.server.server.Server:1 of msgid "Flower server." -msgstr "" +msgstr "Flower 서버." #: ../../source/ref-api/flwr.rst:35::1 msgid ":py:obj:`flwr.simulation `\\" -msgstr "" +msgstr ":py:obj:`flwr.simulation `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.simulation:1 of msgid "Flower simulation." -msgstr "" +msgstr "Flower 시뮬레이션." #: ../../source/ref-api/flwr.client.rst:2 msgid "client" -msgstr "" +msgstr "클라이언트" #: ../../source/ref-api/flwr.client.mod.rst:13 #: ../../source/ref-api/flwr.client.rst:13 @@ -7777,47 +8250,51 @@ msgstr "" #: ../../source/ref-api/flwr.server.rst:13 #: ../../source/ref-api/flwr.simulation.rst:13 msgid "Functions" -msgstr "" +msgstr "함수" #: ../../source/ref-api/flwr.client.rst:25::1 msgid ":py:obj:`run_client_app `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`run_client_app `\\ \\(\\)" #: ../../source/ref-api/flwr.client.rst:25::1 #: flwr.client.supernode.app.run_client_app:1 of msgid "Run Flower client app." -msgstr "" +msgstr "Flower 클라이언트 앱을 실행합니다." #: ../../source/ref-api/flwr.client.rst:25::1 msgid ":py:obj:`run_supernode `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`run_supernode `\\ \\(\\)" #: ../../source/ref-api/flwr.client.rst:25::1 #: flwr.client.supernode.app.run_supernode:1 of msgid "Run Flower SuperNode." -msgstr "" +msgstr "Flower SuperNode를 실행합니다." #: ../../source/ref-api/flwr.client.rst:25::1 msgid "" ":py:obj:`start_client `\\ \\(\\*\\, " "server\\_address\\[\\, client\\_fn\\, ...\\]\\)" msgstr "" +":py:obj:`start_client `\\ \\(\\*\\, " +"server\\_address\\[\\, client\\_fn\\, ...\\]\\)" #: ../../source/ref-api/flwr.client.rst:25::1 #: flwr.client.app.start_client:1 of msgid "Start a Flower client node which connects to a Flower server." -msgstr "" +msgstr "Flower 서버에 연결되는 Flower 클라이언트 노드를 시작합니다." #: ../../source/ref-api/flwr.client.rst:25::1 msgid "" -":py:obj:`start_numpy_client `\\ \\(\\*\\," -" server\\_address\\, client\\)" +":py:obj:`start_numpy_client `\\ \\(\\*\\, " +"server\\_address\\, client\\)" msgstr "" +":py:obj:`start_numpy_client `\\ \\(\\*\\, " +"server\\_address\\, client\\)" #: ../../source/ref-api/flwr.client.rst:25::1 #: flwr.client.app.start_numpy_client:1 of msgid "Start a Flower NumPyClient which connects to a gRPC server." -msgstr "" +msgstr "gRPC 서버에 연결되는 Flower NumPyClient를 시작합니다." #: ../../source/ref-api/flwr.client.mod.rst:30 #: ../../source/ref-api/flwr.client.rst:27 @@ -7826,51 +8303,51 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:17 #: ../../source/ref-api/flwr.server.workflow.rst:17 msgid "Classes" -msgstr "" +msgstr "클래스" #: ../../source/ref-api/flwr.client.rst:34::1 msgid ":py:obj:`Client `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`Client `\\ \\(\\)" #: ../../source/ref-api/flwr.client.rst:34::1 #: flwr.client.client.Client:1 of msgid "Abstract base class for Flower clients." -msgstr "" +msgstr "Flower 클라이언트를 위한 추상 베이스 클래스입니다." #: ../../source/ref-api/flwr.client.rst:34::1 msgid "" -":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, " -"mods\\]\\)" +":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, mods\\]\\)" msgstr "" +":py:obj:`ClientApp `\\ \\(\\[client\\_fn\\, mods\\]\\)" #: ../../source/ref-api/flwr.client.rst:34::1 #: flwr.client.client_app.ClientApp:1 of msgid "Flower ClientApp." -msgstr "" +msgstr "Flower ClientApp." #: ../../source/ref-api/flwr.client.rst:34::1 msgid ":py:obj:`NumPyClient `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`NumPyClient `\\ \\(\\)" #: ../../source/ref-api/flwr.client.rst:34::1 #: flwr.client.numpy_client.NumPyClient:1 of msgid "Abstract base class for Flower clients using NumPy." -msgstr "" +msgstr "NumPy를 사용하는 Flower 클라이언트를 위한 추상 베이스 클래스입니다." #: ../../source/ref-api/flwr.client.rst:52::1 msgid ":py:obj:`flwr.client.mod `\\" -msgstr "" +msgstr ":py:obj:`flwr.client.mod `\\" #: ../../source/ref-api/flwr.client.rst:52::1 flwr.client.mod:1 of msgid "Flower Built-in Mods." -msgstr "" +msgstr "Flower 내장 모드." #: flwr.client.client.Client:1 flwr.client.numpy_client.NumPyClient:1 #: flwr.server.client_manager.ClientManager:1 #: flwr.server.driver.driver.Driver:1 flwr.server.strategy.strategy.Strategy:1 #: of msgid "Bases: :py:class:`~abc.ABC`" -msgstr "" +msgstr "Bases: :py:class:`~abc.ABC`" #: ../../source/ref-api/flwr.client.Client.rst:15 #: ../../source/ref-api/flwr.client.ClientApp.rst:15 @@ -7938,78 +8415,82 @@ msgstr "" #: ../../source/ref-api/flwr.server.workflow.SecAggPlusWorkflow.rst:15 #: ../../source/ref-api/flwr.server.workflow.SecAggWorkflow.rst:15 msgid "Methods" -msgstr "" +msgstr "메소드" #: ../../source/ref-api/flwr.client.Client.rst:44::1 msgid ":py:obj:`evaluate `\\ \\(ins\\)" -msgstr "" +msgstr ":py:obj:`evaluate `\\ \\(ins\\)" #: ../../source/ref-api/flwr.client.Client.rst:44::1 #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 #: flwr.client.client.Client.evaluate:1 #: flwr.client.numpy_client.NumPyClient.evaluate:1 of msgid "Evaluate the provided parameters using the locally held dataset." -msgstr "" +msgstr "로컬로 보유한 데이터 세트를 사용하여 제공된 매개변수를 평가합니다." #: ../../source/ref-api/flwr.client.Client.rst:44::1 msgid ":py:obj:`fit `\\ \\(ins\\)" -msgstr "" +msgstr ":py:obj:`fit `\\ \\(ins\\)" #: ../../source/ref-api/flwr.client.Client.rst:44::1 #: flwr.client.client.Client.fit:1 of msgid "Refine the provided parameters using the locally held dataset." -msgstr "" +msgstr "로컬로 보유한 데이터 세트를 사용하여 제공된 매개변수를 구체화합니다." #: ../../source/ref-api/flwr.client.Client.rst:44::1 msgid ":py:obj:`get_context `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`get_context `\\ \\(\\)" #: ../../source/ref-api/flwr.client.Client.rst:44::1 #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 #: flwr.client.client.Client.get_context:1 #: flwr.client.numpy_client.NumPyClient.get_context:1 of msgid "Get the run context from this client." -msgstr "" +msgstr "이 클라이언트에서 실행 컨텍스트를 가져옵니다." #: ../../source/ref-api/flwr.client.Client.rst:44::1 -msgid ":py:obj:`get_parameters `\\ \\(ins\\)" +msgid "" +":py:obj:`get_parameters `\\ \\(ins\\)" msgstr "" +":py:obj:`get_parameters `\\ \\(ins\\)" #: ../../source/ref-api/flwr.client.Client.rst:44::1 #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 #: flwr.client.client.Client.get_parameters:1 #: flwr.client.numpy_client.NumPyClient.get_parameters:1 of msgid "Return the current local model parameters." -msgstr "" +msgstr "현재 로컬 모델 파라미터를 반환합니다." #: ../../source/ref-api/flwr.client.Client.rst:44::1 -msgid ":py:obj:`get_properties `\\ \\(ins\\)" +msgid "" +":py:obj:`get_properties `\\ \\(ins\\)" msgstr "" +":py:obj:`get_properties `\\ \\(ins\\)" #: ../../source/ref-api/flwr.client.Client.rst:44::1 #: flwr.client.client.Client.get_properties:1 of msgid "Return set of client's properties." -msgstr "" +msgstr "클라이언트의 속성 집합을 반환합니다." #: ../../source/ref-api/flwr.client.Client.rst:44::1 msgid ":py:obj:`set_context `\\ \\(context\\)" -msgstr "" +msgstr ":py:obj:`set_context `\\ \\(context\\)" #: ../../source/ref-api/flwr.client.Client.rst:44::1 #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 #: flwr.client.client.Client.set_context:1 #: flwr.client.numpy_client.NumPyClient.set_context:1 of msgid "Apply a run context to this client." -msgstr "" +msgstr "이 클라이언트에 실행 컨텍스트를 적용합니다." #: ../../source/ref-api/flwr.client.Client.rst:44::1 msgid ":py:obj:`to_client `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`to_client `\\ \\(\\)" #: ../../source/ref-api/flwr.client.Client.rst:44::1 #: flwr.client.client.Client.to_client:1 of msgid "Return client (itself)." -msgstr "" +msgstr "클라이언트(자체)를 반환합니다." #: ../../source/ref-api/flwr.client.Client.rst:46 #: ../../source/ref-api/flwr.client.NumPyClient.rst:46 @@ -8040,11 +8521,11 @@ msgstr "" #: ../../source/ref-api/flwr.server.LegacyContext.rst:25 #: ../../source/ref-api/flwr.server.ServerConfig.rst:25 msgid "Attributes" -msgstr "" +msgstr "속성" #: flwr.client.client.Client.evaluate:1::1 of msgid ":py:obj:`context `\\" -msgstr "" +msgstr ":py:obj:`context `\\" #: ../../source/ref-api/flwr.common.Parameters.rst:2 #: flwr.client.app.start_client flwr.client.app.start_numpy_client @@ -8094,14 +8575,16 @@ msgstr "" #: flwr.simulation.app.start_simulation #: flwr.simulation.run_simulation.run_simulation of msgid "Parameters" -msgstr "" +msgstr "매개변수" #: flwr.client.client.Client.evaluate:3 of msgid "" -"The evaluation instructions containing (global) model parameters received" -" from the server and a dictionary of configuration values used to " -"customize the local evaluation process." +"The evaluation instructions containing (global) model parameters received " +"from the server and a dictionary of configuration values used to customize " +"the local evaluation process." msgstr "" +"서버에서 받은 (전역) 모델 파라미터와 로컬 평가 프로세스를 사용자 지정하는 데 " +"사용되는 구성 값 사전이 포함된 평가 지침입니다." #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit #: flwr.client.client.Client.get_parameters @@ -8130,13 +8613,15 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.initialize_parameters #: flwr.simulation.app.start_simulation of msgid "Returns" -msgstr "" +msgstr "반환" #: flwr.client.client.Client.evaluate:8 of msgid "" "The evaluation result containing the loss on the local dataset and other " "details such as the number of local data examples used for evaluation." msgstr "" +"로컬 데이터 세트의 손실 및 평가에 사용된 로컬 데이터 예제 수와 같은 기타 세" +"부 정보가 포함된 평가 결과입니다." #: flwr.client.client.Client.evaluate flwr.client.client.Client.fit #: flwr.client.client.Client.get_parameters @@ -8163,44 +8648,49 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.initialize_parameters #: flwr.simulation.app.start_simulation of msgid "Return type" -msgstr "" +msgstr "반환 타입" #: flwr.client.client.Client.fit:3 of msgid "" -"The training instructions containing (global) model parameters received " -"from the server and a dictionary of configuration values used to " -"customize the local training process." +"The training instructions containing (global) model parameters received from " +"the server and a dictionary of configuration values used to customize the " +"local training process." msgstr "" +"서버에서 받은 (전역) 모델 파라미터와 로컬 학습 프로세스를 사용자 지정하는 데 " +"사용되는 구성 값 사전이 포함된 학습 지침입니다." #: flwr.client.client.Client.fit:8 of msgid "" -"The training result containing updated parameters and other details such " -"as the number of local training examples used for training." +"The training result containing updated parameters and other details such as " +"the number of local training examples used for training." msgstr "" +"업데이트된 매개변수와 훈련에 사용된 로컬 훈련 예제 수와 같은 기타 세부 정보" +"가 포함된 훈련 결과입니다." #: flwr.client.client.Client.get_parameters:3 of msgid "" "The get parameters instructions received from the server containing a " "dictionary of configuration values." msgstr "" +"구성 값 dictionary이 포함된 서버에서 받은 매개변수 가져오기 명령어입니다." #: flwr.client.client.Client.get_parameters:7 of msgid "The current local model parameters." -msgstr "" +msgstr "현재 로컬 모델 파라미터입니다." #: flwr.client.client.Client.get_properties:3 of msgid "" "The get properties instructions received from the server containing a " "dictionary of configuration values." -msgstr "" +msgstr "구성 값 dictionary이 포함된 서버로부터 받은 속성 가져오기 명령입니다." #: flwr.client.client.Client.get_properties:7 of msgid "The current client properties." -msgstr "" +msgstr "현재 클라이언트 속성입니다." #: ../../source/ref-api/flwr.client.ClientApp.rst:2 msgid "ClientApp" -msgstr "" +msgstr "클라이언트앱" #: flwr.client.client_app.ClientApp:1 flwr.client.mod.localdp_mod.LocalDpMod:1 #: flwr.common.constant.MessageType:1 flwr.common.constant.MessageTypeLegacy:1 @@ -8221,7 +8711,7 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:1 #: of msgid "Bases: :py:class:`object`" -msgstr "" +msgstr "Bases: :py:class:`object`" #: flwr.client.app.start_client:41 flwr.client.app.start_numpy_client:36 #: flwr.client.client_app.ClientApp:4 @@ -8236,112 +8726,127 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:14 #: of msgid "Examples" -msgstr "" +msgstr "예시" #: flwr.client.client_app.ClientApp:5 of msgid "" "Assuming a typical `Client` implementation named `FlowerClient`, you can " "wrap it in a `ClientApp` as follows:" msgstr "" +"일반적인 `Client` 구현의 이름이 `FlowerClient`라고 가정하면, 다음과 같이 " +"`ClientApp`으로 래핑할 수 있습니다:" #: flwr.client.client_app.ClientApp:16 of msgid "" -"If the above code is in a Python module called `client`, it can be " -"started as follows:" +"If the above code is in a Python module called `client`, it can be started " +"as follows:" msgstr "" +"위의 코드가 'client'라는 Python 모듈에 있는 경우 다음과 같이 시작할 수 있습니" +"다:" #: flwr.client.client_app.ClientApp:21 of msgid "" -"In this `client:app` example, `client` refers to the Python module " -"`client.py` in which the previous code lives in and `app` refers to the " -"global attribute `app` that points to an object of type `ClientApp`." +"In this `client:app` example, `client` refers to the Python module `client." +"py` in which the previous code lives in and `app` refers to the global " +"attribute `app` that points to an object of type `ClientApp`." msgstr "" +"이 `client:app` 예제에서 `client`는 이전 코드가 있는 Python 모듈 `client.py`" +"를 가리키고 `app`는 `ClientApp` 유형의 객체를 가리키는 전역 속성 `app`을 가리" +"킵니다." #: flwr.client.client_app.ClientApp.evaluate:1::1 of msgid ":py:obj:`evaluate `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`evaluate `\\ \\(\\)" #: flwr.client.client_app.ClientApp.evaluate:1 #: flwr.client.client_app.ClientApp.evaluate:1::1 of msgid "Return a decorator that registers the evaluate fn with the client app." -msgstr "" +msgstr "클라이언트 앱에 평가함수를 등록하는 데코레이터를 반환합니다." #: flwr.client.client_app.ClientApp.evaluate:1::1 of msgid ":py:obj:`query `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`query `\\ \\(\\)" #: flwr.client.client_app.ClientApp.evaluate:1::1 #: flwr.client.client_app.ClientApp.query:1 of msgid "Return a decorator that registers the query fn with the client app." -msgstr "" +msgstr "클라이언트 앱에 query fn을 등록하는 데코레이터를 반환합니다." #: flwr.client.client_app.ClientApp.evaluate:1::1 of msgid ":py:obj:`train `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`train `\\ \\(\\)" #: flwr.client.client_app.ClientApp.evaluate:1::1 #: flwr.client.client_app.ClientApp.train:1 of msgid "Return a decorator that registers the train fn with the client app." -msgstr "" +msgstr "클라이언트 앱에 train fn을 등록하는 데코레이터를 반환합니다." #: ../../source/ref-api/flwr.client.NumPyClient.rst:2 msgid "NumPyClient" -msgstr "" +msgstr "NumPyClient" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid "" ":py:obj:`evaluate `\\ \\(parameters\\, " "config\\)" msgstr "" +":py:obj:`evaluate `\\ \\(parameters\\, " +"config\\)" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 -msgid ":py:obj:`fit `\\ \\(parameters\\, config\\)" +msgid "" +":py:obj:`fit `\\ \\(parameters\\, config\\)" msgstr "" +":py:obj:`fit `\\ \\(parameters\\, config\\)" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 #: flwr.client.numpy_client.NumPyClient.fit:1 of msgid "Train the provided parameters using the locally held dataset." -msgstr "" +msgstr "로컬로 보유한 데이터 세트를 사용하여 제공된 파라미터를 학습합니다." #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid ":py:obj:`get_context `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`get_context `\\ \\(\\)" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid "" ":py:obj:`get_parameters `\\ " "\\(config\\)" msgstr "" +":py:obj:`get_parameters `\\ " +"\\(config\\)" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid "" ":py:obj:`get_properties `\\ " "\\(config\\)" msgstr "" +":py:obj:`get_properties `\\ " +"\\(config\\)" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 #: flwr.client.numpy_client.NumPyClient.get_properties:1 of msgid "Return a client's set of properties." -msgstr "" +msgstr "클라이언트의 속성 집합을 반환합니다." #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid "" -":py:obj:`set_context `\\ " -"\\(context\\)" +":py:obj:`set_context `\\ \\(context\\)" msgstr "" +":py:obj:`set_context `\\ \\(context\\)" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 msgid ":py:obj:`to_client `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`to_client `\\ \\(\\)" #: ../../source/ref-api/flwr.client.NumPyClient.rst:44::1 #: flwr.client.numpy_client.NumPyClient.to_client:1 of msgid "Convert to object to Client type and return it." -msgstr "" +msgstr "객체를 클라이언트 유형으로 변환하고 반환합니다." #: flwr.client.numpy_client.NumPyClient.evaluate:1::1 of msgid ":py:obj:`context `\\" -msgstr "" +msgstr ":py:obj:`context `\\" #: flwr.client.numpy_client.NumPyClient.evaluate:3 #: flwr.client.numpy_client.NumPyClient.fit:3 @@ -8351,262 +8856,311 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.configure_fit:5 #: flwr.server.strategy.strategy.Strategy.evaluate:8 of msgid "The current (global) model parameters." -msgstr "" +msgstr "현재(전역) 모델 매개변수입니다." #: flwr.client.numpy_client.NumPyClient.evaluate:5 of msgid "" -"Configuration parameters which allow the server to influence evaluation " -"on the client. It can be used to communicate arbitrary values from the " -"server to the client, for example, to influence the number of examples " -"used for evaluation." +"Configuration parameters which allow the server to influence evaluation on " +"the client. It can be used to communicate arbitrary values from the server " +"to the client, for example, to influence the number of examples used for " +"evaluation." msgstr "" +"서버가 클라이언트의 평가에 영향을 줄 수 있는 구성 매개변수입니다. 예를 들어 " +"평가에 사용되는 예제 수에 영향을 주기 위해 서버에서 클라이언트로 임의의 값을 " +"전달하는 데 사용할 수 있습니다." #: flwr.client.numpy_client.NumPyClient.evaluate:11 of msgid "" "* **loss** (*float*) -- The evaluation loss of the model on the local " "dataset. * **num_examples** (*int*) -- The number of examples used for " "evaluation. * **metrics** (*Dict[str, Scalar]*) -- A dictionary mapping " -"arbitrary string keys to values of type bool, bytes, float, int, or " -"str. It can be used to communicate arbitrary values back to the server." +"arbitrary string keys to values of type bool, bytes, float, int, or str. " +"It can be used to communicate arbitrary values back to the server." msgstr "" +"* **loss** (*float*) - 로컬 데이터 세트에서 모델의 평가 손실입니다. * " +"**num_examples** (*int*) -- 평가에 사용된 예제 수입니다. * **metrics** " +"(*Dict[str, Scalar]*) -- 임의의 문자열 키를 부울, 바이트, float, int 또는 " +"str 유형의 값에 매핑하는 dictionary입니다. 임의의 값을 서버에 다시 전달하는 " +"데 사용할 수 있습니다." #: flwr.client.numpy_client.NumPyClient.evaluate:11 of msgid "" -"**loss** (*float*) -- The evaluation loss of the model on the local " -"dataset." -msgstr "" +"**loss** (*float*) -- The evaluation loss of the model on the local dataset." +msgstr "**loss** (*float*) -- 로컬 데이터 세트에서 모델의 평가 손실입니다." #: flwr.client.numpy_client.NumPyClient.evaluate:12 of msgid "**num_examples** (*int*) -- The number of examples used for evaluation." -msgstr "" +msgstr "**num_examples** (*int*) - 평가에 사용된 예제 수입니다." #: flwr.client.numpy_client.NumPyClient.evaluate:13 #: flwr.client.numpy_client.NumPyClient.fit:13 of msgid "" -"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary " -"string keys to values of type bool, bytes, float, int, or str. It can be " -"used to communicate arbitrary values back to the server." +"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary string " +"keys to values of type bool, bytes, float, int, or str. It can be used to " +"communicate arbitrary values back to the server." msgstr "" +"**metrics** (*Dict[str, Scalar]*) - 임의의 문자열 키를 bool, bytes, float, " +"int 또는 str 타입의 값에 매핑하는 dictionary입니다. 임의의 값을 서버에 다시 " +"전달하는 데 사용할 수 있습니다." #: flwr.client.numpy_client.NumPyClient.evaluate:19 of msgid "" -"The previous return type format (int, float, float) and the extended " -"format (int, float, float, Dict[str, Scalar]) have been deprecated and " -"removed since Flower 0.19." +"The previous return type format (int, float, float) and the extended format " +"(int, float, float, Dict[str, Scalar]) have been deprecated and removed " +"since Flower 0.19." msgstr "" +"이전 반환 유형 형식(int, float, float)과 확장 형식(int, float, float, " +"Dict[str, Scalar])은 Flower 0.19부터 더 이상 사용되지 않으며 제거되었습니다." #: flwr.client.numpy_client.NumPyClient.fit:5 of msgid "" -"Configuration parameters which allow the server to influence training on " -"the client. It can be used to communicate arbitrary values from the " -"server to the client, for example, to set the number of (local) training " -"epochs." +"Configuration parameters which allow the server to influence training on the " +"client. It can be used to communicate arbitrary values from the server to " +"the client, for example, to set the number of (local) training epochs." msgstr "" +"서버가 클라이언트의 훈련에 영향을 줄 수 있는 구성 매개변수입니다. 예를 들어 " +"(로컬) 트레이닝 에포크 수를 설정하는 등 서버에서 클라이언트로 임의의 값을 전" +"달하는 데 사용할 수 있습니다." #: flwr.client.numpy_client.NumPyClient.fit:11 of msgid "" "* **parameters** (*NDArrays*) -- The locally updated model parameters. * " "**num_examples** (*int*) -- The number of examples used for training. * " -"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary " -"string keys to values of type bool, bytes, float, int, or str. It can " -"be used to communicate arbitrary values back to the server." +"**metrics** (*Dict[str, Scalar]*) -- A dictionary mapping arbitrary string " +"keys to values of type bool, bytes, float, int, or str. It can be used to " +"communicate arbitrary values back to the server." msgstr "" +"* **parameters** (*NDArrays*) - 로컬로 업데이트된 모델 파라미터입니다. * " +"**num_examples** (*int*) -- 학습에 사용된 예제 수입니다. * **metrics** " +"(*Dict[str, Scalar]*) - 임의의 문자열 키를 bool, bytes, float, int,또는 str " +"타입의 값에 매핑하는 dictionary입니다. 임의의 값을 서버에 다시 전달하는 데 사" +"용할 수 있습니다." #: flwr.client.numpy_client.NumPyClient.fit:11 of msgid "**parameters** (*NDArrays*) -- The locally updated model parameters." -msgstr "" +msgstr "**parameters** (*NDArrays*) - 로컬로 업데이트된 모델 파라미터입니다." #: flwr.client.numpy_client.NumPyClient.fit:12 of msgid "**num_examples** (*int*) -- The number of examples used for training." -msgstr "" +msgstr "**num_examples** (*int*) - 트레이닝에 사용된 예제 수입니다." #: flwr.client.numpy_client.NumPyClient.get_parameters:3 of msgid "" -"Configuration parameters requested by the server. This can be used to " -"tell the client which parameters are needed along with some Scalar " -"attributes." +"Configuration parameters requested by the server. This can be used to tell " +"the client which parameters are needed along with some Scalar attributes." msgstr "" +"서버에서 요청한 구성 매개변수입니다. 이는 일부 스칼라 속성과 함께 어떤 매개변" +"수가 필요한지 클라이언트에게 알려주는 데 사용할 수 있습니다." #: flwr.client.numpy_client.NumPyClient.get_parameters:8 of -msgid "**parameters** -- The local model parameters as a list of NumPy ndarrays." -msgstr "" +msgid "" +"**parameters** -- The local model parameters as a list of NumPy ndarrays." +msgstr "**parameters** -- 로컬 모델 파라미터를 NumPy 배열 목록으로 표시합니다." #: flwr.client.numpy_client.NumPyClient.get_properties:3 of msgid "" -"Configuration parameters requested by the server. This can be used to " -"tell the client which properties are needed along with some Scalar " -"attributes." +"Configuration parameters requested by the server. This can be used to tell " +"the client which properties are needed along with some Scalar attributes." msgstr "" +"서버에서 요청하는 구성 매개변수입니다. 이는 일부 스칼라 속성과 함께 어떤 속성" +"이 필요한지 클라이언트에게 알려주는 데 사용할 수 있습니다." #: flwr.client.numpy_client.NumPyClient.get_properties:8 of msgid "" -"**properties** -- A dictionary mapping arbitrary string keys to values of" -" type bool, bytes, float, int, or str. It can be used to communicate " +"**properties** -- A dictionary mapping arbitrary string keys to values of " +"type bool, bytes, float, int, or str. It can be used to communicate " "arbitrary property values back to the server." msgstr "" +"**properties** -- 임의의 문자열 키를 bool, bytes, float, int 또는 str 타입의 " +"값에 매핑하는 dictionary입니다. 임의의 속성 값을 서버에 다시 전달하는 데 사용" +"할 수 있습니다." #: ../../source/ref-api/flwr.client.mod.rst:2 msgid "mod" -msgstr "" +msgstr "mod" #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid "" ":py:obj:`adaptiveclipping_mod `\\ " "\\(msg\\, ctxt\\, call\\_next\\)" msgstr "" +":py:obj:`adaptiveclipping_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" #: ../../source/ref-api/flwr.client.mod.rst:28::1 #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:1 of msgid "Client-side adaptive clipping modifier." -msgstr "" +msgstr "클라이언트 측 적응형 클리핑 수정자." #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid "" -":py:obj:`fixedclipping_mod `\\ " -"\\(msg\\, ctxt\\, call\\_next\\)" +":py:obj:`fixedclipping_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" msgstr "" +":py:obj:`fixedclipping_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" #: ../../source/ref-api/flwr.client.mod.rst:28::1 #: flwr.client.mod.centraldp_mods.fixedclipping_mod:1 of msgid "Client-side fixed clipping modifier." -msgstr "" +msgstr "클라이언트 측 고정 클리핑 수정자." #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid ":py:obj:`make_ffn `\\ \\(ffn\\, mods\\)" -msgstr "" +msgstr ":py:obj:`make_ffn `\\ \\(ffn\\, mods\\)" #: ../../source/ref-api/flwr.client.mod.rst:28::1 #: flwr.client.mod.utils.make_ffn:1 of msgid "." -msgstr "" +msgstr "." #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid "" ":py:obj:`secagg_mod `\\ \\(msg\\, ctxt\\, " "call\\_next\\)" msgstr "" +":py:obj:`secagg_mod `\\ \\(msg\\, ctxt\\, " +"call\\_next\\)" #: ../../source/ref-api/flwr.client.mod.rst:28::1 #: flwr.client.mod.secure_aggregation.secagg_mod.secagg_mod:1 of -msgid "Handle incoming message and return results, following the SecAgg protocol." -msgstr "" +msgid "" +"Handle incoming message and return results, following the SecAgg protocol." +msgstr "SecAgg 프로토콜에 따라 수신 메시지를 처리하고 결과를 반환합니다." #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid "" ":py:obj:`secaggplus_mod `\\ \\(msg\\, " "ctxt\\, call\\_next\\)" msgstr "" +":py:obj:`secaggplus_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" #: ../../source/ref-api/flwr.client.mod.rst:28::1 #: flwr.client.mod.secure_aggregation.secaggplus_mod.secaggplus_mod:1 of msgid "" -"Handle incoming message and return results, following the SecAgg+ " -"protocol." -msgstr "" +"Handle incoming message and return results, following the SecAgg+ protocol." +msgstr "SecAgg+ 프로토콜에 따라 수신 메시지를 처리하고 결과를 반환합니다." #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid "" -":py:obj:`message_size_mod `\\ \\(msg\\," -" ctxt\\, call\\_next\\)" +":py:obj:`message_size_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" msgstr "" +":py:obj:`message_size_mod `\\ \\(msg\\, " +"ctxt\\, call\\_next\\)" #: ../../source/ref-api/flwr.client.mod.rst:28::1 #: flwr.client.mod.comms_mods.message_size_mod:1 of msgid "Message size mod." -msgstr "" +msgstr "메시지 크기 수정." #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid "" ":py:obj:`parameters_size_mod `\\ " "\\(msg\\, ctxt\\, call\\_next\\)" msgstr "" +":py:obj:`parameters_size_mod `\\ " +"\\(msg\\, ctxt\\, call\\_next\\)" #: ../../source/ref-api/flwr.client.mod.rst:28::1 #: flwr.client.mod.comms_mods.parameters_size_mod:1 of msgid "Parameters size mod." -msgstr "" +msgstr "매개변수 크기 mod." #: ../../source/ref-api/flwr.client.mod.rst:35::1 msgid "" -":py:obj:`LocalDpMod `\\ \\(clipping\\_norm\\," -" sensitivity\\, ...\\)" +":py:obj:`LocalDpMod `\\ \\(clipping\\_norm\\, " +"sensitivity\\, ...\\)" msgstr "" +":py:obj:`LocalDpMod `\\ \\(clipping\\_norm\\, " +"sensitivity\\, ...\\)" #: ../../source/ref-api/flwr.client.mod.rst:35::1 #: flwr.client.mod.localdp_mod.LocalDpMod:1 of msgid "Modifier for local differential privacy." -msgstr "" +msgstr "로컬 차분 프라이버시를 위한 수정자." #: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst:2 msgid "LocalDpMod" -msgstr "" +msgstr "LocalDpMod" #: flwr.client.mod.localdp_mod.LocalDpMod:3 of msgid "" -"This mod clips the client model updates and adds noise to the params " -"before sending them to the server." +"This mod clips the client model updates and adds noise to the params before " +"sending them to the server." msgstr "" +"이 모드는 클라이언트 모델 업데이트를 클립하고 서버로 보내기 전에 파라미터에 " +"노이즈를 추가합니다." #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:12 #: flwr.client.mod.centraldp_mods.fixedclipping_mod:10 #: flwr.client.mod.localdp_mod.LocalDpMod:6 of msgid "It operates on messages of type `MessageType.TRAIN`." -msgstr "" +msgstr "이 함수는 `MessageType.TRAIN` 유형의 메시지에 대해 작동합니다." #: flwr.client.mod.localdp_mod.LocalDpMod:8 #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:15 #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:8 #: of msgid "The value of the clipping norm." -msgstr "" +msgstr "클리핑 기준값입니다." #: flwr.client.mod.localdp_mod.LocalDpMod:10 of msgid "The sensitivity of the client model." -msgstr "" +msgstr "클라이언트 모델의 민감도입니다." #: flwr.client.mod.localdp_mod.LocalDpMod:12 of msgid "" "The privacy budget. Smaller value of epsilon indicates a higher level of " "privacy protection." msgstr "" +"개인정보 보호 예산. 엡실론 값이 작을수록 개인정보 보호 수준이 높음을 나타냅니" +"다." #: flwr.client.mod.localdp_mod.LocalDpMod:15 of msgid "" -"The failure probability. The probability that the privacy mechanism fails" -" to provide the desired level of privacy. A smaller value of delta " -"indicates a stricter privacy guarantee." +"The failure probability. The probability that the privacy mechanism fails to " +"provide the desired level of privacy. A smaller value of delta indicates a " +"stricter privacy guarantee." msgstr "" +"실패 확률입니다. 프라이버시 메커니즘이 원하는 수준의 프라이버시를 제공하지 못" +"할 확률입니다. 델타 값이 작을수록 프라이버시가 더 엄격하게 보장된다는 의미입" +"니다." #: flwr.client.mod.localdp_mod.LocalDpMod:23 of -msgid "Create an instance of the local DP mod and add it to the client-side mods:" -msgstr "" +msgid "" +"Create an instance of the local DP mod and add it to the client-side mods:" +msgstr "로컬 DP 모드의 인스턴스를 생성하고 클라이언트 측 모드에 추가합니다:" #: ../../source/ref-api/flwr.client.mod.adaptiveclipping_mod.rst:2 msgid "adaptiveclipping\\_mod" -msgstr "" +msgstr "adaptiveclipping\\_mod" #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:3 of msgid "" "This mod needs to be used with the " -"DifferentialPrivacyClientSideAdaptiveClipping server-side strategy " -"wrapper." +"DifferentialPrivacyClientSideAdaptiveClipping server-side strategy wrapper." msgstr "" +"이 모드는 서버 측 전략 래퍼인 차분 프라이버시 클라이언트 측 적응형 클리핑과 " +"함께 사용해야 합니다." #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:6 #: flwr.client.mod.centraldp_mods.fixedclipping_mod:6 of msgid "The wrapper sends the clipping_norm value to the client." -msgstr "" +msgstr "래퍼는 클라이언트에 clipping_norm 값을 전송합니다." #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:8 #: flwr.client.mod.centraldp_mods.fixedclipping_mod:8 of -msgid "This mod clips the client model updates before sending them to the server." -msgstr "" +msgid "" +"This mod clips the client model updates before sending them to the server." +msgstr "이 모드는 클라이언트 모델 업데이트를 서버로 보내기 전에 클립합니다." #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:10 of msgid "" "It also sends KEY_NORM_BIT to the server for computing the new clipping " "value." -msgstr "" +msgstr "또한 새 클리핑 값을 계산하기 위해 서버로 KEY_NORM_BIT을 전송합니다." #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:15 #: flwr.client.mod.centraldp_mods.fixedclipping_mod:13 @@ -8615,240 +9169,280 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:60 #: of msgid "Notes" -msgstr "" +msgstr "참고" #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:16 #: flwr.client.mod.centraldp_mods.fixedclipping_mod:14 of msgid "Consider the order of mods when using multiple." -msgstr "" +msgstr "여러 개를 사용할 때는 모드의 순서를 고려하세요." #: flwr.client.mod.centraldp_mods.adaptiveclipping_mod:18 of -msgid "Typically, adaptiveclipping_mod should be the last to operate on params." +msgid "" +"Typically, adaptiveclipping_mod should be the last to operate on params." msgstr "" +"일반적으로 adaptiveclipping_mod는 매개변수에서 가장 마지막으로 작동해야 합니" +"다." #: ../../source/ref-api/flwr.client.mod.fixedclipping_mod.rst:2 msgid "fixedclipping\\_mod" -msgstr "" +msgstr "fixedclipping\\_mod" #: flwr.client.mod.centraldp_mods.fixedclipping_mod:3 of msgid "" "This mod needs to be used with the " "DifferentialPrivacyClientSideFixedClipping server-side strategy wrapper." msgstr "" +"이 모드는 서버 측 전략 래퍼인 DifferentialPrivacyClientSideFixedClipping과 함" +"께 사용해야 합니다." #: flwr.client.mod.centraldp_mods.fixedclipping_mod:16 of msgid "Typically, fixedclipping_mod should be the last to operate on params." msgstr "" +"일반적으로 fixedclipping_mod는 매개변수에서 가장 마지막으로 작동해야 합니다." #: ../../source/ref-api/flwr.client.mod.make_ffn.rst:2 msgid "make\\_ffn" -msgstr "" +msgstr "make\\_ffn" #: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 msgid "message\\_size\\_mod" -msgstr "" +msgstr "message\\_size\\_mod" #: flwr.client.mod.comms_mods.message_size_mod:3 of msgid "This mod logs the size in bytes of the message being transmited." -msgstr "" +msgstr "이 모드는 전송되는 메시지의 크기를 바이트 단위로 기록합니다." #: ../../source/ref-api/flwr.client.mod.parameters_size_mod.rst:2 msgid "parameters\\_size\\_mod" -msgstr "" +msgstr "parameters\\_size\\_mod" #: flwr.client.mod.comms_mods.parameters_size_mod:3 of msgid "" -"This mod logs the number of parameters transmitted in the message as well" -" as their size in bytes." +"This mod logs the number of parameters transmitted in the message as well as " +"their size in bytes." msgstr "" +"이 모드는 메시지에서 전송된 매개변수의 수와 그 크기를 바이트 단위로 기록합니" +"다." #: ../../source/ref-api/flwr.client.mod.secagg_mod.rst:2 msgid "secagg\\_mod" -msgstr "" +msgstr "secagg\\_mod" #: ../../source/ref-api/flwr.client.mod.secaggplus_mod.rst:2 msgid "secaggplus\\_mod" -msgstr "" +msgstr "secaggplus\\_mod" #: ../../source/ref-api/flwr.client.run_client_app.rst:2 msgid "run\\_client\\_app" -msgstr "" +msgstr "run\\_client\\_app" #: ../../source/ref-api/flwr.client.run_supernode.rst:2 msgid "run\\_supernode" -msgstr "" +msgstr "run\\_supernode" #: ../../source/ref-api/flwr.client.start_client.rst:2 msgid "start\\_client" -msgstr "" +msgstr "start\\_client" #: flwr.client.app.start_client:3 flwr.client.app.start_numpy_client:9 of msgid "" "The IPv4 or IPv6 address of the server. If the Flower server runs on the " -"same machine on port 8080, then `server_address` would be " -"`\"[::]:8080\"`." +"same machine on port 8080, then `server_address` would be `\"[::]:8080\"`." msgstr "" +"서버의 IPv4 또는 IPv6 주소입니다. Flower 서버가 포트 8080의 동일한 컴퓨터에" +"서 실행되는 경우 `서버_주소`는 `\"[::]:8080\"`이 됩니다." #: flwr.client.app.start_client:7 of msgid "A callable that instantiates a Client. (default: None)" -msgstr "" +msgstr "클라이언트를 인스턴스화하는 호출 가능 항목입니다. (기본값: None)" #: flwr.client.app.start_client:9 of msgid "" -"An implementation of the abstract base class `flwr.client.Client` " -"(default: None)" -msgstr "" +"An implementation of the abstract base class `flwr.client.Client` (default: " +"None)" +msgstr "추상 베이스 클래스 `flwr.client.Client`의 구현(기본값: None)" #: flwr.client.app.start_client:12 flwr.client.app.start_numpy_client:15 of msgid "" -"The maximum length of gRPC messages that can be exchanged with the Flower" -" server. The default should be sufficient for most models. Users who " -"train very large models might need to increase this value. Note that the " -"Flower server needs to be started with the same value (see " -"`flwr.server.start_server`), otherwise it will not know about the " -"increased limit and block larger messages." +"The maximum length of gRPC messages that can be exchanged with the Flower " +"server. The default should be sufficient for most models. Users who train " +"very large models might need to increase this value. Note that the Flower " +"server needs to be started with the same value (see `flwr.server." +"start_server`), otherwise it will not know about the increased limit and " +"block larger messages." msgstr "" +"Flower 서버와 교환할 수 있는 gRPC 메시지의 최대 길이입니다. 기본값은 대부분" +"의 모델에 충분합니다. 매우 큰 모델을 훈련하는 사용자는 이 값을 늘려야 할 수" +"도 있습니다. Flower 서버는 동일한 값으로 시작해야 하며(`flwr.server." +"start_server` 참조), 그렇지 않으면 증가된 제한을 알지 못해 더 큰 메시지를 차" +"단합니다." #: flwr.client.app.start_client:19 flwr.client.app.start_numpy_client:22 of msgid "" "The PEM-encoded root certificates as a byte string or a path string. If " -"provided, a secure connection using the certificates will be established " -"to an SSL-enabled Flower server." +"provided, a secure connection using the certificates will be established to " +"an SSL-enabled Flower server." msgstr "" +"바이트 문자열 또는 경로 문자열로 PEM 인코딩된 루트 인증서. 제공하면 인증서를 " +"사용하여 SSL이 활성화된 Flower 서버에 보안 연결이 설정됩니다." #: flwr.client.app.start_client:23 flwr.client.app.start_numpy_client:26 of msgid "" -"Starts an insecure gRPC connection when True. Enables HTTPS connection " -"when False, using system certificates if `root_certificates` is None." +"Starts an insecure gRPC connection when True. Enables HTTPS connection when " +"False, using system certificates if `root_certificates` is None." msgstr "" +"True일 경우 안전하지 않은 gRPC 연결을 시작합니다. root_certificates`가 None" +"인 경우 시스템 인증서를 사용하여 False일 때 HTTPS 연결을 활성화합니다." #: flwr.client.app.start_client:26 flwr.client.app.start_numpy_client:29 of msgid "" "Configure the transport layer. Allowed values: - 'grpc-bidi': gRPC, " -"bidirectional streaming - 'grpc-rere': gRPC, request-response " -"(experimental) - 'rest': HTTP (experimental)" +"bidirectional streaming - 'grpc-rere': gRPC, request-response (experimental) " +"- 'rest': HTTP (experimental)" msgstr "" +"전송 계층을 구성합니다. 허용되는 값입니다: - 'grpc-bidi': gRPC, 양방향 스트리" +"밍 - 'grpc-rere': gRPC, 요청-응답(실험적) - 'rest': HTTP(실험적)" #: flwr.client.app.start_client:31 of msgid "" "The maximum number of times the client will try to connect to the server " -"before giving up in case of a connection error. If set to None, there is " -"no limit to the number of tries." +"before giving up in case of a connection error. If set to None, there is no " +"limit to the number of tries." msgstr "" +"연결 오류 발생 시 클라이언트가 서버 연결을 포기하기 전에 시도하는 최대 횟수입" +"니다. None으로 설정하면 시도 횟수에 제한이 없습니다." #: flwr.client.app.start_client:35 of msgid "" -"The maximum duration before the client stops trying to connect to the " -"server in case of connection error. If set to None, there is no limit to " -"the total time." +"The maximum duration before the client stops trying to connect to the server " +"in case of connection error. If set to None, there is no limit to the total " +"time." msgstr "" +"연결 오류 발생 시 클라이언트가 서버에 대한 연결을 시도하지 않는 최대 기간입니" +"다. None으로 설정하면 총 시간에는 제한이 없습니다." #: flwr.client.app.start_client:42 flwr.client.app.start_numpy_client:37 of msgid "Starting a gRPC client with an insecure server connection:" -msgstr "" +msgstr "안전하지 않은 서버 연결로 gRPC 클라이언트 시작하기:" #: flwr.client.app.start_client:49 flwr.client.app.start_numpy_client:44 of msgid "Starting an SSL-enabled gRPC client using system certificates:" -msgstr "" +msgstr "시스템 인증서를 사용하여 SSL 사용 gRPC 클라이언트를 시작합니다:" #: flwr.client.app.start_client:60 flwr.client.app.start_numpy_client:52 of msgid "Starting an SSL-enabled gRPC client using provided certificates:" -msgstr "" +msgstr "제공된 인증서를 사용하여 SSL 지원 gRPC 클라이언트를 시작합니다:" #: ../../source/ref-api/flwr.client.start_numpy_client.rst:2 msgid "start\\_numpy\\_client" -msgstr "" +msgstr "start\\_numpy\\_client" #: flwr.client.app.start_numpy_client:5 of msgid "" -"This function is deprecated since 1.7.0. Use " -":code:`flwr.client.start_client` instead and first convert your " -":code:`NumPyClient` to type :code:`flwr.client.Client` by executing its " -":code:`to_client()` method." +"This function is deprecated since 1.7.0. Use :code:`flwr.client." +"start_client` instead and first convert your :code:`NumPyClient` to type :" +"code:`flwr.client.Client` by executing its :code:`to_client()` method." msgstr "" +"이 함수는 1.7.0부터 더 이상 사용되지 않습니다. 대신 :code:`flwr.client." +"start_client`를 사용하고 먼저 :code:`to_client()` 메서드를 실행하여 :code:" +"`NumPyClient`를 :code:`flwr.client.Client` 유형으로 변환합니다." #: flwr.client.app.start_numpy_client:13 of msgid "An implementation of the abstract base class `flwr.client.NumPyClient`." -msgstr "" +msgstr "추상 베이스 클래스 `flwr.client.NumPyClient`의 구현입니다." #: ../../source/ref-api/flwr.common.rst:2 msgid "common" -msgstr "" +msgstr "공통" #: ../../source/ref-api/flwr.common.rst:30::1 -msgid ":py:obj:`array_from_numpy `\\ \\(ndarray\\)" +msgid "" +":py:obj:`array_from_numpy `\\ \\(ndarray\\)" msgstr "" +":py:obj:`array_from_numpy `\\ \\(ndarray\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.record.conversion_utils.array_from_numpy:1 of msgid "Create Array from NumPy ndarray." -msgstr "" +msgstr "NumPy에서 배열을 만듭니다." #: ../../source/ref-api/flwr.common.rst:30::1 -msgid ":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" +msgid "" +":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" msgstr "" +":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.parameter.bytes_to_ndarray:1 of msgid "Deserialize NumPy ndarray from bytes." -msgstr "" +msgstr "바이트에서 NumPy를 역직렬화합니다." #: ../../source/ref-api/flwr.common.rst:30::1 msgid "" ":py:obj:`configure `\\ \\(identifier\\[\\, " "filename\\, host\\]\\)" msgstr "" +":py:obj:`configure `\\ \\(identifier\\[\\, " +"filename\\, host\\]\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.logger.configure:1 of msgid "Configure logging to file and/or remote log server." -msgstr "" +msgstr "파일 및/또는 원격 로그 서버에 로깅을 구성합니다." #: ../../source/ref-api/flwr.common.rst:30::1 msgid "" ":py:obj:`event `\\ \\(event\\_type\\[\\, " "event\\_details\\]\\)" msgstr "" +":py:obj:`event `\\ \\(event\\_type\\[\\, " +"event\\_details\\]\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.telemetry.event:1 of msgid "Submit create_event to ThreadPoolExecutor to avoid blocking." -msgstr "" +msgstr "차단을 피하기 위해 create_event를 ThreadPoolExecutor에 제출합니다." #: ../../source/ref-api/flwr.common.rst:30::1 msgid "" ":py:obj:`log `\\ \\(level\\, msg\\, \\*args\\, " "\\*\\*kwargs\\)" msgstr "" +":py:obj:`log `\\ \\(level\\, msg\\, \\*args\\, " +"\\*\\*kwargs\\)" #: ../../source/ref-api/flwr.common.rst:30::1 logging.Logger.log:1 #: of msgid "Log 'msg % args' with the integer severity 'level'." -msgstr "" +msgstr "정수 심각도 'level'과 함께 'msg % args'를 기록합니다." #: ../../source/ref-api/flwr.common.rst:30::1 -msgid ":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" +msgid "" +":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" msgstr "" +":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.parameter.ndarray_to_bytes:1 of msgid "Serialize NumPy ndarray to bytes." -msgstr "" +msgstr "NumPy와 배열을 바이트열로 직렬화합니다." #: ../../source/ref-api/flwr.common.rst:30::1 msgid ":py:obj:`now `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`now `\\ \\(\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.date.now:1 of msgid "Construct a datetime from time.time() with time zone set to UTC." -msgstr "" +msgstr "표준 시간대를 UTC로 설정하여 time.time()에서 날짜 시간을 생성합니다." #: ../../source/ref-api/flwr.common.rst:30::1 msgid "" ":py:obj:`ndarrays_to_parameters `\\ " "\\(ndarrays\\)" msgstr "" +":py:obj:`ndarrays_to_parameters `\\ " +"\\(ndarrays\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.parameter.ndarrays_to_parameters:1 @@ -8856,1333 +9450,1485 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.ndarrays_to_parameters:1 #: of msgid "Convert NumPy ndarrays to parameters object." -msgstr "" +msgstr "NumPy 배열을 매개변수 객체로 변환합니다." #: ../../source/ref-api/flwr.common.rst:30::1 msgid "" ":py:obj:`parameters_to_ndarrays `\\ " "\\(parameters\\)" msgstr "" +":py:obj:`parameters_to_ndarrays `\\ " +"\\(parameters\\)" #: ../../source/ref-api/flwr.common.rst:30::1 #: flwr.common.parameter.parameters_to_ndarrays:1 of msgid "Convert parameters object to NumPy ndarrays." -msgstr "" +msgstr "매개변수 객체를 NumPy 배열로 변환합니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`Array `\\ \\(dtype\\, shape\\, stype\\, " -"data\\)" +":py:obj:`Array `\\ \\(dtype\\, shape\\, stype\\, data\\)" msgstr "" +":py:obj:`Array `\\ \\(dtype\\, shape\\, stype\\, data\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.record.parametersrecord.Array:1 of msgid "Array type." -msgstr "" +msgstr "배열 유형." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`ClientMessage `\\ " "\\(\\[get\\_properties\\_res\\, ...\\]\\)" msgstr "" +":py:obj:`ClientMessage `\\ " +"\\(\\[get\\_properties\\_res\\, ...\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.ClientMessage:1 of msgid "ClientMessage is a container used to hold one result message." msgstr "" +"ClientMessage는 하나의 결과 메시지를 저장하는 데 사용되는 컨테이너입니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`Code `\\ \\(value\\)" -msgstr "" +msgstr ":py:obj:`Code `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.Code:1 of msgid "Client status codes." -msgstr "" +msgstr "클라이언트 상태 코드." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`ConfigsRecord `\\ " "\\(\\[configs\\_dict\\, keep\\_input\\]\\)" msgstr "" +":py:obj:`ConfigsRecord `\\ " +"\\(\\[configs\\_dict\\, keep\\_input\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.record.configsrecord.ConfigsRecord:1 of msgid "Configs record." -msgstr "" +msgstr "레코드를 설정합니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`Context `\\ \\(state\\)" -msgstr "" +msgstr ":py:obj:`Context `\\ \\(state\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.context.Context:1 of msgid "State of your run." -msgstr "" +msgstr "실행 상태." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`DisconnectRes `\\ \\(reason\\)" -msgstr "" +msgstr ":py:obj:`DisconnectRes `\\ \\(reason\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.DisconnectRes:1 of msgid "DisconnectRes message from client to server." -msgstr "" +msgstr "클라이언트에서 서버로 연결 해제 메시지를 보냅니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`EvaluateIns `\\ \\(parameters\\, " -"config\\)" +":py:obj:`EvaluateIns `\\ \\(parameters\\, config\\)" msgstr "" +":py:obj:`EvaluateIns `\\ \\(parameters\\, config\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.EvaluateIns:1 of msgid "Evaluate instructions for a client." -msgstr "" +msgstr "클라이언트에 대한 지침을 평가합니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`EvaluateRes `\\ \\(status\\, loss\\, " "num\\_examples\\, metrics\\)" msgstr "" +":py:obj:`EvaluateRes `\\ \\(status\\, loss\\, " +"num\\_examples\\, metrics\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.EvaluateRes:1 of msgid "Evaluate response from a client." -msgstr "" +msgstr "클라이언트의 응답을 평가합니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`EventType `\\ \\(value\\)" -msgstr "" +msgstr ":py:obj:`EventType `\\ \\(value\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.telemetry.EventType:1 of msgid "Types of telemetry events." -msgstr "" +msgstr "원격 분석 이벤트의 유형." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`FitIns `\\ \\(parameters\\, config\\)" -msgstr "" +msgstr ":py:obj:`FitIns `\\ \\(parameters\\, config\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.FitIns:1 of msgid "Fit instructions for a client." -msgstr "" +msgstr "고객을 위한 맞춤 지침." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`FitRes `\\ \\(status\\, parameters\\, " "num\\_examples\\, metrics\\)" msgstr "" +":py:obj:`FitRes `\\ \\(status\\, parameters\\, " +"num\\_examples\\, metrics\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.FitRes:1 of msgid "Fit response from a client." -msgstr "" +msgstr "클라이언트의 적합성 응답." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`Error `\\ \\(code\\[\\, reason\\]\\)" -msgstr "" +msgstr ":py:obj:`Error `\\ \\(code\\[\\, reason\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.message.Error:1 of msgid "A dataclass that stores information about an error that occurred." -msgstr "" +msgstr "발생한 오류에 대한 정보를 저장하는 데이터 클래스입니다." #: ../../source/ref-api/flwr.common.rst:64::1 -msgid ":py:obj:`GetParametersIns `\\ \\(config\\)" +msgid "" +":py:obj:`GetParametersIns `\\ \\(config\\)" msgstr "" +":py:obj:`GetParametersIns `\\ \\(config\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.GetParametersIns:1 of msgid "Parameters request for a client." -msgstr "" +msgstr "클라이언트에 대한 매개변수 요청입니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`GetParametersRes `\\ \\(status\\, " "parameters\\)" msgstr "" +":py:obj:`GetParametersRes `\\ \\(status\\, " +"parameters\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.GetParametersRes:1 of msgid "Response when asked to return parameters." -msgstr "" +msgstr "매개변수 반환 요청 시 응답합니다." #: ../../source/ref-api/flwr.common.rst:64::1 -msgid ":py:obj:`GetPropertiesIns `\\ \\(config\\)" +msgid "" +":py:obj:`GetPropertiesIns `\\ \\(config\\)" msgstr "" +":py:obj:`GetPropertiesIns `\\ \\(config\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.GetPropertiesIns:1 of msgid "Properties request for a client." -msgstr "" +msgstr "클라이언트에 대한 속성 요청." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`GetPropertiesRes `\\ \\(status\\, " "properties\\)" msgstr "" +":py:obj:`GetPropertiesRes `\\ \\(status\\, " +"properties\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.GetPropertiesRes:1 of msgid "Properties response from a client." -msgstr "" +msgstr "클라이언트의 속성 응답을 확인합니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`Message `\\ \\(metadata\\[\\, content\\, " "error\\]\\)" msgstr "" +":py:obj:`Message `\\ \\(metadata\\[\\, content\\, " +"error\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.message.Message:1 of msgid "State of your application from the viewpoint of the entity using it." -msgstr "" +msgstr "애플리케이션을 사용하는 엔티티의 관점에서 애플리케이션의 상태입니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`MessageType `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`MessageType `\\ \\(\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.constant.MessageType:1 of msgid "Message type." -msgstr "" +msgstr "메시지 타입." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`MessageTypeLegacy `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`MessageTypeLegacy `\\ \\(\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.constant.MessageTypeLegacy:1 of msgid "Legacy message type." -msgstr "" +msgstr "레거시 메시지 타입." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -":py:obj:`Metadata `\\ \\(run\\_id\\, " -"message\\_id\\, src\\_node\\_id\\, ...\\)" +":py:obj:`Metadata `\\ \\(run\\_id\\, message\\_id\\, " +"src\\_node\\_id\\, ...\\)" msgstr "" +":py:obj:`Metadata `\\ \\(run\\_id\\, message\\_id\\, " +"src\\_node\\_id\\, ...\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.message.Metadata:1 of msgid "A dataclass holding metadata associated with the current message." -msgstr "" +msgstr "현재 메시지와 관련된 메타데이터를 보유한 데이터 클래스입니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`MetricsRecord `\\ " "\\(\\[metrics\\_dict\\, keep\\_input\\]\\)" msgstr "" +":py:obj:`MetricsRecord `\\ " +"\\(\\[metrics\\_dict\\, keep\\_input\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.record.metricsrecord.MetricsRecord:1 of msgid "Metrics record." -msgstr "" +msgstr "메트릭 기록." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`NDArray `\\" -msgstr "" +msgstr ":py:obj:`NDArray `\\" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" -"alias of :py:class:`~numpy.ndarray`\\ [:py:obj:`~typing.Any`, " -":py:class:`~numpy.dtype`\\ [:py:obj:`~typing.Any`]]" +"alias of :py:class:`~numpy.ndarray`\\ [:py:obj:`~typing.Any`, :py:class:" +"`~numpy.dtype`\\ [:py:obj:`~typing.Any`]]" msgstr "" +"alias of :py:class:`~numpy.ndarray`\\ [:py:obj:`~typing.Any`, :py:class:" +"`~numpy.dtype`\\ [:py:obj:`~typing.Any`]]" #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`Parameters `\\ \\(tensors\\, " "tensor\\_type\\)" msgstr "" +":py:obj:`Parameters `\\ \\(tensors\\, " +"tensor\\_type\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.Parameters:1 of msgid "Model parameters." -msgstr "" +msgstr "모델 매개변수." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`ParametersRecord `\\ " "\\(\\[array\\_dict\\, keep\\_input\\]\\)" msgstr "" +":py:obj:`ParametersRecord `\\ " +"\\(\\[array\\_dict\\, keep\\_input\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.record.parametersrecord.ParametersRecord:1 of msgid "Parameters record." -msgstr "" +msgstr "매개변수 기록." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`ReconnectIns `\\ \\(seconds\\)" -msgstr "" +msgstr ":py:obj:`ReconnectIns `\\ \\(seconds\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.ReconnectIns:1 of msgid "ReconnectIns message from server to client." -msgstr "" +msgstr "서버에서 클라이언트로 메시지를 다시 연결합니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`RecordSet `\\ " "\\(\\[parameters\\_records\\, ...\\]\\)" msgstr "" +":py:obj:`RecordSet `\\ " +"\\(\\[parameters\\_records\\, ...\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.record.recordset.RecordSet:1 of msgid "RecordSet stores groups of parameters, metrics and configs." -msgstr "" +msgstr "RecordSet은 매개변수, 메트릭 및 설정 그룹을 저장합니다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid "" ":py:obj:`ServerMessage `\\ " "\\(\\[get\\_properties\\_ins\\, ...\\]\\)" msgstr "" +":py:obj:`ServerMessage `\\ " +"\\(\\[get\\_properties\\_ins\\, ...\\]\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.ServerMessage:1 of msgid "ServerMessage is a container used to hold one instruction message." msgstr "" +"ServerMessage는 하나의 instruction 메시지를 저장하는 데 사용되는 컨테이너입니" +"다." #: ../../source/ref-api/flwr.common.rst:64::1 msgid ":py:obj:`Status `\\ \\(code\\, message\\)" -msgstr "" +msgstr ":py:obj:`Status `\\ \\(code\\, message\\)" #: ../../source/ref-api/flwr.common.rst:64::1 #: flwr.common.typing.Status:1 of msgid "Client status." -msgstr "" +msgstr "클라이언트 상태." #: ../../source/ref-api/flwr.common.Array.rst:2 msgid "Array" -msgstr "" +msgstr "배열" #: flwr.common.record.parametersrecord.Array:3 of msgid "" "A dataclass containing serialized data from an array-like or tensor-like " "object along with some metadata about it." msgstr "" +"배열형 또는 텐서형 객체의 직렬화된 데이터와 그에 대한 일부 메타데이터를 포함" +"하는 데이터 클래스입니다." #: flwr.common.record.parametersrecord.Array:6 of msgid "" -"A string representing the data type of the serialised object (e.g. " -"`np.float32`)" -msgstr "" +"A string representing the data type of the serialised object (e.g. `np." +"float32`)" +msgstr "직렬화된 객체의 데이터 유형을 나타내는 문자열(예: `np.float32`)" #: flwr.common.record.parametersrecord.Array:8 of msgid "" -"A list representing the shape of the unserialized array-like object. This" -" is used to deserialize the data (depending on the serialization method) " -"or simply as a metadata field." +"A list representing the shape of the unserialized array-like object. This is " +"used to deserialize the data (depending on the serialization method) or " +"simply as a metadata field." msgstr "" +"직렬화되지 않은 배열과 같은 객체의 모양을 나타내는 목록입니다. 직렬화 방법에 " +"따라 데이터를 역직렬화하는 데 사용되거나 단순히 메타데이터 필드로 사용됩니다." #: flwr.common.record.parametersrecord.Array:12 of msgid "" -"A string indicating the type of serialisation mechanism used to generate " -"the bytes in `data` from an array-like or tensor-like object." +"A string indicating the type of serialisation mechanism used to generate the " +"bytes in `data` from an array-like or tensor-like object." msgstr "" +"배열형 또는 텐서형 객체에서 `데이터`의 바이트를 생성하는 데 사용되는 직렬화 " +"메커니즘의 유형을 나타내는 문자열입니다." #: flwr.common.record.parametersrecord.Array:15 of msgid "A buffer of bytes containing the data." -msgstr "" +msgstr "데이터를 포함하는 바이트 버퍼입니다." #: ../../source/ref-api/flwr.common.Array.rst:26::1 msgid ":py:obj:`numpy `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`numpy `\\ \\(\\)" #: ../../source/ref-api/flwr.common.Array.rst:26::1 #: flwr.common.record.parametersrecord.Array.numpy:1 of msgid "Return the array as a NumPy array." -msgstr "" +msgstr "배열을 NumPy 배열로 반환합니다." #: flwr.common.record.parametersrecord.Array.numpy:1::1 of msgid ":py:obj:`dtype `\\" -msgstr "" +msgstr ":py:obj:`dtype `\\" #: flwr.common.record.parametersrecord.Array.numpy:1::1 of msgid ":py:obj:`shape `\\" -msgstr "" +msgstr ":py:obj:`shape `\\" #: flwr.common.record.parametersrecord.Array.numpy:1::1 of msgid ":py:obj:`stype `\\" -msgstr "" +msgstr ":py:obj:`stype `\\" #: flwr.common.record.parametersrecord.Array.numpy:1::1 of msgid ":py:obj:`data `\\" -msgstr "" +msgstr ":py:obj:`data `\\" #: ../../source/ref-api/flwr.common.ClientMessage.rst:2 msgid "ClientMessage" -msgstr "" +msgstr "클라이언트 메시지" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid ":py:obj:`evaluate_res `\\" -msgstr "" +msgstr ":py:obj:`evaluate_res `\\" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid ":py:obj:`fit_res `\\" -msgstr "" +msgstr ":py:obj:`fit_res `\\" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid "" -":py:obj:`get_parameters_res " -"`\\" +":py:obj:`get_parameters_res `\\" msgstr "" +":py:obj:`get_parameters_res `\\" #: ../../source/ref-api/flwr.common.ClientMessage.rst:31::1 msgid "" -":py:obj:`get_properties_res " -"`\\" +":py:obj:`get_properties_res `\\" msgstr "" +":py:obj:`get_properties_res `\\" #: ../../source/ref-api/flwr.common.Code.rst:2 msgid "Code" -msgstr "" +msgstr "코드" #: flwr.common.typing.Code:1 of msgid "Bases: :py:class:`~enum.Enum`" -msgstr "" +msgstr "Bases: :py:class:`~enum.Enum`" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid ":py:obj:`OK `\\" -msgstr "" +msgstr ":py:obj:`OK `\\" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`GET_PROPERTIES_NOT_IMPLEMENTED " -"`\\" +":py:obj:`GET_PROPERTIES_NOT_IMPLEMENTED `\\" msgstr "" +":py:obj:`GET_PROPERTIES_NOT_IMPLEMENTED `\\" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`GET_PARAMETERS_NOT_IMPLEMENTED " -"`\\" +":py:obj:`GET_PARAMETERS_NOT_IMPLEMENTED `\\" msgstr "" +":py:obj:`GET_PARAMETERS_NOT_IMPLEMENTED `\\" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid ":py:obj:`FIT_NOT_IMPLEMENTED `\\" -msgstr "" +msgstr ":py:obj:`FIT_NOT_IMPLEMENTED `\\" #: ../../source/ref-api/flwr.common.Code.rst:26::1 msgid "" -":py:obj:`EVALUATE_NOT_IMPLEMENTED " -"`\\" +":py:obj:`EVALUATE_NOT_IMPLEMENTED `\\" msgstr "" +":py:obj:`EVALUATE_NOT_IMPLEMENTED `\\" #: ../../source/ref-api/flwr.common.ConfigsRecord.rst:2 msgid "ConfigsRecord" -msgstr "" +msgstr "컨피그 레코드" #: flwr.common.record.configsrecord.ConfigsRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " -"[:py:class:`str`, :py:class:`int` | :py:class:`float` | :py:class:`str` |" -" :py:class:`bytes` | :py:class:`bool` | :py:class:`~typing.List`\\ " -"[:py:class:`int`] | :py:class:`~typing.List`\\ [:py:class:`float`] | " -":py:class:`~typing.List`\\ [:py:class:`str`] | :py:class:`~typing.List`\\" -" [:py:class:`bytes`] | :py:class:`~typing.List`\\ [:py:class:`bool`]]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" +"`str`, :py:class:`int` | :py:class:`float` | :py:class:`str` | :py:class:" +"`bytes` | :py:class:`bool` | :py:class:`~typing.List`\\ [:py:class:`int`] | :" +"py:class:`~typing.List`\\ [:py:class:`float`] | :py:class:`~typing.List`\\ [:" +"py:class:`str`] | :py:class:`~typing.List`\\ [:py:class:`bytes`] | :py:class:" +"`~typing.List`\\ [:py:class:`bool`]]" msgstr "" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:`str`" +", :py:class:`int` | :py:class:`float` | :py:class:`str` | :py:class:`bytes` |" +" :py:class:`bool` | :py:class:`~typing.List`\\ [:py:class:`int`] | " +":py:class:`~typing.List`\\ [:py:class:`float`] | :py:class:`~typing.List`\\ " +"[:py:class:`str`] | :py:class:`~typing.List`\\ [:py:class:`bytes`] | " +":py:class:`~typing.List`\\ [:py:class:`bool`]]" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`clear `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`clear `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1 #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid "Remove all items from R." -msgstr "" +msgstr "R에서 모든 항목을 제거합니다." #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`count_bytes `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`count_bytes `\\ \\(\\)" #: flwr.common.record.configsrecord.ConfigsRecord.count_bytes:1 #: flwr.common.record.metricsrecord.MetricsRecord.count_bytes:1 #: flwr.common.record.parametersrecord.ParametersRecord.count_bytes:1 #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid "Return number of Bytes stored in this object." -msgstr "" +msgstr "이 객체에 저장된 바이트 수를 반환합니다." #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" -msgstr "" +msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 #: flwr.common.record.typeddict.TypedDict.get:1 of msgid "d defaults to None." -msgstr "" +msgstr "d는 기본값이 None입니다." #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`items `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`items `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`keys `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`keys `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`pop `\\ \\(k\\[\\,d\\]\\)" -msgstr "" +msgstr ":py:obj:`pop `\\ \\(k\\[\\,d\\]\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 #: flwr.common.record.typeddict.TypedDict.pop:1 of -msgid "If key is not found, d is returned if given, otherwise KeyError is raised." -msgstr "" +msgid "" +"If key is not found, d is returned if given, otherwise KeyError is raised." +msgstr "키를 찾을 수 없으면 주어진 경우 d가 반환되고, 그렇지 않으면 KeyError가 " +"발생합니다." #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid "" ":py:obj:`update `\\ \\(\\[E\\, " "\\]\\*\\*F\\)" msgstr "" +":py:obj:`update `\\ \\(\\[E\\, \\]\\*\\*F\\" +")" #: flwr.common.record.typeddict.TypedDict.clear:1::1 #: flwr.common.record.typeddict.TypedDict.update:1 of msgid "Update R from dict/iterable E and F." -msgstr "" +msgstr "dict/iterable E 및 F에서 R을 업데이트합니다." #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`values `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`values `\\ \\(\\)" #: flwr.common.record.configsrecord.ConfigsRecord.count_bytes:3 of msgid "This function counts booleans as occupying 1 Byte." -msgstr "" +msgstr "이 함수는 booleans을 1바이트를 차지하는 것으로 계산합니다." #: ../../source/ref-api/flwr.common.Context.rst:2 msgid "Context" -msgstr "" +msgstr "컨텍스트" #: flwr.common.context.Context:3 of msgid "" -"Holds records added by the entity in a given run and that will stay " -"local. This means that the data it holds will never leave the system it's" -" running from. This can be used as an intermediate storage or scratchpad " -"when executing mods. It can also be used as a memory to access at " -"different points during the lifecycle of this entity (e.g. across " -"multiple rounds)" +"Holds records added by the entity in a given run and that will stay local. " +"This means that the data it holds will never leave the system it's running " +"from. This can be used as an intermediate storage or scratchpad when " +"executing mods. It can also be used as a memory to access at different " +"points during the lifecycle of this entity (e.g. across multiple rounds)" msgstr "" +"특정 실행에서 엔티티가 추가한 레코드를 보유하며 로컬에 유지됩니다. 즉, " +"저장된 데이터는 실행 중인 시스템을 벗어나지 않습니다. 모드를 실행할 때 중간 " +"저장소나 스크래치 패드로 사용할 수 있습니다. 또한 이 엔티티의 수명 주기 동안 " +"다른 시점에서 액세스하기 위한 메모리로도 사용할 수 있습니다(예: 여러 " +"라운드에 걸쳐)" #: ../../source/ref-api/flwr.common.Context.rst:28::1 msgid ":py:obj:`state `\\" -msgstr "" +msgstr ":py:obj:`state `\\" #: ../../source/ref-api/flwr.common.DisconnectRes.rst:2 msgid "DisconnectRes" -msgstr "" +msgstr "연결 해제" #: ../../source/ref-api/flwr.common.DisconnectRes.rst:28::1 msgid ":py:obj:`reason `\\" -msgstr "" +msgstr ":py:obj:`reason `\\" #: ../../source/ref-api/flwr.common.Error.rst:2 msgid "Error" -msgstr "" +msgstr "오류" #: flwr.common.message.Error:3 of msgid "An identifier for the error." -msgstr "" +msgstr "오류 식별자입니다." #: flwr.common.message.Error:5 of msgid "A reason for why the error arose (e.g. an exception stack-trace)" -msgstr "" +msgstr "오류가 발생한 이유(예: 예외 스택 추적)" #: flwr.common.Error.code:1::1 of msgid ":py:obj:`code `\\" -msgstr "" +msgstr ":py:obj:`code `\\" #: flwr.common.Error.code:1 flwr.common.Error.code:1::1 of msgid "Error code." -msgstr "" +msgstr "오류 코드." #: flwr.common.Error.code:1::1 of msgid ":py:obj:`reason `\\" -msgstr "" +msgstr ":py:obj:`reason `\\" #: flwr.common.Error.code:1::1 flwr.common.Error.reason:1 of msgid "Reason reported about the error." -msgstr "" +msgstr "오류에 대해 보고된 사유입니다." #: ../../source/ref-api/flwr.common.EvaluateIns.rst:2 msgid "EvaluateIns" -msgstr "" +msgstr "평가" #: ../../source/ref-api/flwr.common.EvaluateIns.rst:29::1 msgid ":py:obj:`parameters `\\" -msgstr "" +msgstr ":py:obj:`parameters `\\" #: ../../source/ref-api/flwr.common.EvaluateIns.rst:29::1 msgid ":py:obj:`config `\\" -msgstr "" +msgstr ":py:obj:`config `\\" #: ../../source/ref-api/flwr.common.EvaluateRes.rst:2 msgid "EvaluateRes" -msgstr "" +msgstr "EvaluateRes" #: ../../source/ref-api/flwr.common.EvaluateRes.rst:31::1 msgid ":py:obj:`status `\\" -msgstr "" +msgstr ":py:obj:`status `\\" #: ../../source/ref-api/flwr.common.EvaluateRes.rst:31::1 msgid ":py:obj:`loss `\\" -msgstr "" +msgstr ":py:obj:`loss `\\" #: ../../source/ref-api/flwr.common.EvaluateRes.rst:31::1 msgid ":py:obj:`num_examples `\\" -msgstr "" +msgstr ":py:obj:`num_examples `\\" #: ../../source/ref-api/flwr.common.EvaluateRes.rst:31::1 msgid ":py:obj:`metrics `\\" -msgstr "" +msgstr ":py:obj:`metrics `\\" #: ../../source/ref-api/flwr.common.EventType.rst:2 msgid "EventType" -msgstr "" +msgstr "이벤트 타입" #: flwr.common.telemetry.EventType:1 of msgid "Bases: :py:class:`str`, :py:class:`~enum.Enum`" -msgstr "" +msgstr "Bases: :py:class:`str`, :py:class:`~enum.Enum`" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`encode `\\ \\(\\[encoding\\, " "errors\\]\\)" msgstr "" +":py:obj:`encode `\\ \\(\\[encoding\\, errors\\]" +"\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.encode:1 of msgid "Encode the string using the codec registered for encoding." -msgstr "" +msgstr "인코딩용으로 등록된 코덱을 사용하여 문자열을 인코딩합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`replace `\\ \\(old\\, new\\[\\, " "count\\]\\)" msgstr "" +":py:obj:`replace `\\ \\(old\\, new\\[\\, " +"count\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.replace:1 of msgid "Return a copy with all occurrences of substring old replaced by new." -msgstr "" +msgstr "이전 하위 문자열이 모두 새 하위 문자열로 바뀐 사본을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`split `\\ \\(\\[sep\\, " -"maxsplit\\]\\)" +":py:obj:`split `\\ \\(\\[sep\\, maxsplit\\]\\)" msgstr "" +":py:obj:`split `\\ \\(\\[sep\\, maxsplit\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rsplit:1 flwr.common.EventType.split:1 of msgid "" -"Return a list of the substrings in the string, using sep as the separator" -" string." -msgstr "" +"Return a list of the substrings in the string, using sep as the separator " +"string." +msgstr "sep를 구분 문자열로 사용하여 문자열의 하위 문자열 목록을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rsplit `\\ \\(\\[sep\\, " -"maxsplit\\]\\)" +":py:obj:`rsplit `\\ \\(\\[sep\\, maxsplit\\]\\)" msgstr "" +":py:obj:`rsplit `\\ \\(\\[sep\\, maxsplit\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`join `\\ \\(iterable\\, \\/\\)" -msgstr "" +msgstr ":py:obj:`join `\\ \\(iterable\\, \\/\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.join:1 of msgid "Concatenate any number of strings." -msgstr "" +msgstr "원하는 수의 문자열을 연결합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`capitalize `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`capitalize `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.capitalize:1 of msgid "Return a capitalized version of the string." -msgstr "" +msgstr "대문자로 된 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`casefold `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`casefold `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.casefold:1 of msgid "Return a version of the string suitable for caseless comparisons." -msgstr "" +msgstr "대소문자 구분 없는 비교에 적합한 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`title `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`title `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.title:1 of msgid "Return a version of the string where each word is titlecased." -msgstr "" +msgstr "각 단어의 제목이 대소문자로 구분된 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`center `\\ \\(width\\[\\, " "fillchar\\]\\)" msgstr "" +":py:obj:`center `\\ \\(width\\[\\, fillchar\\]" +"\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.center:1 of msgid "Return a centered string of length width." -msgstr "" +msgstr "길이 너비의 가운데 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`count `\\ \\(sub\\[\\, start\\[\\, " "end\\]\\]\\)" msgstr "" +":py:obj:`count `\\ \\(sub\\[\\, start\\[\\, " +"end\\]\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the number of non-overlapping occurrences of substring sub in " -"string S[start:end]." -msgstr "" +"Return the number of non-overlapping occurrences of substring sub in string " +"S[start:end]." +msgstr "문자열 S[start:end]에서 하위 문자열 sub이 겹치지 않는 횟수를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`expandtabs `\\ " -"\\(\\[tabsize\\]\\)" +":py:obj:`expandtabs `\\ \\(\\[tabsize\\]\\)" msgstr "" +":py:obj:`expandtabs `\\ \\(\\[tabsize\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.expandtabs:1 of msgid "Return a copy where all tab characters are expanded using spaces." -msgstr "" +msgstr "모든 탭 문자가 공백을 사용하여 확장된 사본을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`find `\\ \\(sub\\[\\, start\\[\\, " "end\\]\\]\\)" msgstr "" +":py:obj:`find `\\ \\(sub\\[\\, start\\[\\, end\\]" +"\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the lowest index in S where substring sub is found, such that sub " -"is contained within S[start:end]." -msgstr "" +"Return the lowest index in S where substring sub is found, such that sub is " +"contained within S[start:end]." +msgstr "하위 문자열 sub이 발견되는 S에서 하위가 S[start:end] 내에 포함되는 가장 낮은 " +"인덱스를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid ":py:obj:`partition `\\ \\(sep\\, \\/\\)" +msgid "" +":py:obj:`partition `\\ \\(sep\\, \\/\\)" msgstr "" +":py:obj:`partition `\\ \\(sep\\, \\/\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.partition:1 flwr.common.EventType.rpartition:1 of msgid "Partition the string into three parts using the given separator." -msgstr "" +msgstr "지정된 구분 기호를 사용하여 문자열을 세 부분으로 분할합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`index `\\ \\(sub\\[\\, start\\[\\, " "end\\]\\]\\)" msgstr "" +":py:obj:`index `\\ \\(sub\\[\\, start\\[\\, " +"end\\]\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`ljust `\\ \\(width\\[\\, " -"fillchar\\]\\)" +":py:obj:`ljust `\\ \\(width\\[\\, fillchar\\]\\)" msgstr "" +":py:obj:`ljust `\\ \\(width\\[\\, fillchar\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.ljust:1 of msgid "Return a left-justified string of length width." -msgstr "" +msgstr "왼쪽으로 정렬된 길이의 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`lower `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`lower `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.lower:1 of msgid "Return a copy of the string converted to lowercase." -msgstr "" +msgstr "소문자로 변환된 문자열 사본을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`lstrip `\\ \\(\\[chars\\]\\)" -msgstr "" +msgstr ":py:obj:`lstrip `\\ \\(\\[chars\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.lstrip:1 of msgid "Return a copy of the string with leading whitespace removed." -msgstr "" +msgstr "선행 공백이 제거된 문자열의 복사본을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`rfind `\\ \\(sub\\[\\, start\\[\\, " "end\\]\\]\\)" msgstr "" +":py:obj:`rfind `\\ \\(sub\\[\\, start\\[\\, " +"end\\]\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -"Return the highest index in S where substring sub is found, such that sub" -" is contained within S[start:end]." -msgstr "" +"Return the highest index in S where substring sub is found, such that sub is " +"contained within S[start:end]." +msgstr "부분 문자열 sub이 발견되는 곳에서 sub이 S[start:end] 내에 포함되도록 S에서 " +"가장 높은 인덱스를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rindex `\\ \\(sub\\[\\, " -"start\\[\\, end\\]\\]\\)" +":py:obj:`rindex `\\ \\(sub\\[\\, start\\[\\, " +"end\\]\\]\\)" msgstr "" +":py:obj:`rindex `\\ \\(sub\\[\\, start\\[\\, " +"end\\]\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`rjust `\\ \\(width\\[\\, " -"fillchar\\]\\)" +":py:obj:`rjust `\\ \\(width\\[\\, fillchar\\]\\)" msgstr "" +":py:obj:`rjust `\\ \\(width\\[\\, fillchar\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rjust:1 of msgid "Return a right-justified string of length width." -msgstr "" +msgstr "길이 너비의 오른쪽 정렬된 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`rstrip `\\ \\(\\[chars\\]\\)" -msgstr "" +msgstr ":py:obj:`rstrip `\\ \\(\\[chars\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.rstrip:1 of msgid "Return a copy of the string with trailing whitespace removed." -msgstr "" +msgstr "후행 공백이 제거된 문자열의 복사본을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid ":py:obj:`rpartition `\\ \\(sep\\, \\/\\)" +msgid "" +":py:obj:`rpartition `\\ \\(sep\\, \\/\\)" msgstr "" +":py:obj:`rpartition `\\ \\(sep\\, \\/\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`splitlines `\\ " "\\(\\[keepends\\]\\)" msgstr "" +":py:obj:`splitlines `\\ \\(\\[keepends\\]\\" +")" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.splitlines:1 of msgid "Return a list of the lines in the string, breaking at line boundaries." -msgstr "" +msgstr "문자열의 줄 목록을 줄 경계에서 구분하여 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`strip `\\ \\(\\[chars\\]\\)" -msgstr "" +msgstr ":py:obj:`strip `\\ \\(\\[chars\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.strip:1 of -msgid "Return a copy of the string with leading and trailing whitespace removed." -msgstr "" +msgid "" +"Return a copy of the string with leading and trailing whitespace removed." +msgstr "선행 및 후행 공백이 제거된 문자열 사본을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`swapcase `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`swapcase `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.swapcase:1 of msgid "" "Convert uppercase characters to lowercase and lowercase characters to " "uppercase." -msgstr "" +msgstr "대문자를 소문자로, 소문자를 대문자로 변환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid ":py:obj:`translate `\\ \\(table\\, \\/\\)" +msgid "" +":py:obj:`translate `\\ \\(table\\, \\/\\)" msgstr "" +":py:obj:`translate `\\ \\(table\\, \\/\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.translate:1 of msgid "Replace each character in the string using the given translation table." -msgstr "" +msgstr "주어진 번역 테이블을 사용하여 문자열의 각 문자를 바꿉니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`upper `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`upper `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.upper:1 of msgid "Return a copy of the string converted to uppercase." -msgstr "" +msgstr "Return a copy of the string converted to uppercase." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`startswith `\\ \\(prefix\\[\\," -" start\\[\\, end\\]\\]\\)" +":py:obj:`startswith `\\ \\(prefix\\[\\, " +"start\\[\\, end\\]\\]\\)" msgstr "" +":py:obj:`startswith `\\ \\(prefix\\[\\, " +"start\\[\\, end\\]\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "Return True if S starts with the specified prefix, False otherwise." -msgstr "" +msgstr "S가 지정된 접두사로 시작하면 True를 반환하고, 그렇지 않으면 False를 " +"반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`endswith `\\ \\(suffix\\[\\, " "start\\[\\, end\\]\\]\\)" msgstr "" +":py:obj:`endswith `\\ \\(suffix\\[\\, start\\" +"[\\, end\\]\\]\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "Return True if S ends with the specified suffix, False otherwise." -msgstr "" +msgstr "S가 지정된 접미사로 끝나면 True를 반환하고 그렇지 않으면 False을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`removeprefix `\\ " -"\\(prefix\\, \\/\\)" +":py:obj:`removeprefix `\\ \\(prefix\\, " +"\\/\\)" msgstr "" +":py:obj:`removeprefix `\\ \\(prefix\\, \\" +"/\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.removeprefix:1 of msgid "Return a str with the given prefix string removed if present." -msgstr "" +msgstr "주어진 접두사 문자열이 있는 경우 제거된 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" -":py:obj:`removesuffix `\\ " -"\\(suffix\\, \\/\\)" +":py:obj:`removesuffix `\\ \\(suffix\\, " +"\\/\\)" msgstr "" +":py:obj:`removesuffix `\\ \\(suffix\\, \\" +"/\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.removesuffix:1 of msgid "Return a str with the given suffix string removed if present." -msgstr "" +msgstr "주어진 접미사 문자열이 있는 경우 제거된 문자열을 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isascii `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isascii `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isascii:1 of msgid "Return True if all characters in the string are ASCII, False otherwise." -msgstr "" +msgstr "문자열의 모든 문자가 ASCII인 경우 True를 반환하고, 그렇지 않으면 False를 " +"반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`islower `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`islower `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.islower:1 of msgid "Return True if the string is a lowercase string, False otherwise." -msgstr "" +msgstr "문자열이 소문자 문자열이면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isupper `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isupper `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isupper:1 of msgid "Return True if the string is an uppercase string, False otherwise." -msgstr "" +msgstr "문자열이 대문자 문자열이면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`istitle `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`istitle `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.istitle:1 of msgid "Return True if the string is a title-cased string, False otherwise." -msgstr "" +msgstr "문자열이 제목 대/소문자가 구분된 문자열이면 True를 반환하고, 그렇지 않으면 " +"False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isspace `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isspace `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isspace:1 of msgid "Return True if the string is a whitespace string, False otherwise." -msgstr "" +msgstr "문자열이 공백 문자열이면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isdecimal `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isdecimal `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isdecimal:1 of msgid "Return True if the string is a decimal string, False otherwise." -msgstr "" +msgstr "문자열이 10진수 문자열이면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isdigit `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isdigit `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isdigit:1 of msgid "Return True if the string is a digit string, False otherwise." -msgstr "" +msgstr "문자열이 숫자 문자열이면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isnumeric `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isnumeric `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isnumeric:1 of msgid "Return True if the string is a numeric string, False otherwise." -msgstr "" +msgstr "문자열이 숫자 문자열이면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isalpha `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isalpha `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isalpha:1 of msgid "Return True if the string is an alphabetic string, False otherwise." -msgstr "" +msgstr "문자열이 알파벳 문자열이면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isalnum `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isalnum `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isalnum:1 of msgid "Return True if the string is an alpha-numeric string, False otherwise." -msgstr "" +msgstr "문자열이 영-숫자 문자열이면 True를 반환하고, 그렇지 않으면 False를 " +"반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isidentifier `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isidentifier `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isidentifier:1 of -msgid "Return True if the string is a valid Python identifier, False otherwise." -msgstr "" +msgid "" +"Return True if the string is a valid Python identifier, False otherwise." +msgstr "문자열이 유효한 파이썬 식별자인 경우 True를 반환하고, 그렇지 않으면 False를 " +"반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`isprintable `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`isprintable `\\ \\(\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.isprintable:1 of msgid "Return True if the string is printable, False otherwise." -msgstr "" +msgstr "문자열을 인쇄할 수 있으면 True를 반환하고, 그렇지 않으면 False를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`zfill `\\ \\(width\\, \\/\\)" -msgstr "" +msgstr ":py:obj:`zfill `\\ \\(width\\, \\/\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.zfill:1 of msgid "" -"Pad a numeric string with zeros on the left, to fill a field of the given" -" width." -msgstr "" +"Pad a numeric string with zeros on the left, to fill a field of the given " +"width." +msgstr "숫자 문자열을 왼쪽에 0으로 채워서 지정된 너비의 필드를 채웁니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "" ":py:obj:`format `\\ \\(\\*args\\, " "\\*\\*kwargs\\)" msgstr "" +":py:obj:`format `\\ \\(\\*args\\, \\*\\*" +"kwargs\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 -msgid "Return a formatted version of S, using substitutions from args and kwargs." -msgstr "" +msgid "" +"Return a formatted version of S, using substitutions from args and kwargs." +msgstr "args와 kwarg의 치환을 사용하여 형식이 지정된 S를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`format_map `\\ \\(mapping\\)" -msgstr "" +msgstr ":py:obj:`format_map `\\ \\(mapping\\)" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid "Return a formatted version of S, using substitutions from mapping." -msgstr "" +msgstr "매핑의 치환을 사용하여 형식이 지정된 S를 반환합니다." #: ../../source/ref-api/flwr.common.EventType.rst:163::1 msgid ":py:obj:`maketrans `\\" -msgstr "" +msgstr ":py:obj:`maketrans `\\" #: ../../source/ref-api/flwr.common.EventType.rst:163::1 #: flwr.common.EventType.maketrans:1 of msgid "Return a translation table usable for str.translate()." -msgstr "" +msgstr "str.translate()에 사용할 수 있는 번역 테이블을 반환합니다." #: flwr.common.EventType.capitalize:1::1 of msgid ":py:obj:`PING `\\" -msgstr "" +msgstr ":py:obj:`PING `\\" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_CLIENT_ENTER `\\" +msgid "" +":py:obj:`START_CLIENT_ENTER `\\" msgstr "" +":py:obj:`START_CLIENT_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_CLIENT_LEAVE `\\" +msgid "" +":py:obj:`START_CLIENT_LEAVE `\\" msgstr "" +":py:obj:`START_CLIENT_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_SERVER_ENTER `\\" +msgid "" +":py:obj:`START_SERVER_ENTER `\\" msgstr "" +":py:obj:`START_SERVER_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_SERVER_LEAVE `\\" +msgid "" +":py:obj:`START_SERVER_LEAVE `\\" msgstr "" +":py:obj:`START_SERVER_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_DRIVER_API_ENTER " -"`\\" +":py:obj:`RUN_DRIVER_API_ENTER `\\" msgstr "" +":py:obj:`RUN_DRIVER_API_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_DRIVER_API_LEAVE " -"`\\" +":py:obj:`RUN_DRIVER_API_LEAVE `\\" msgstr "" +":py:obj:`RUN_DRIVER_API_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_FLEET_API_ENTER " -"`\\" +":py:obj:`RUN_FLEET_API_ENTER `\\" msgstr "" +":py:obj:`RUN_FLEET_API_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_FLEET_API_LEAVE " -"`\\" +":py:obj:`RUN_FLEET_API_LEAVE `\\" msgstr "" +":py:obj:`RUN_FLEET_API_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERLINK_ENTER " -"`\\" +":py:obj:`RUN_SUPERLINK_ENTER `\\" msgstr "" +":py:obj:`RUN_SUPERLINK_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERLINK_LEAVE " -"`\\" +":py:obj:`RUN_SUPERLINK_LEAVE `\\" msgstr "" +":py:obj:`RUN_SUPERLINK_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_SIMULATION_ENTER " -"`\\" +":py:obj:`START_SIMULATION_ENTER `\\" msgstr "" +":py:obj:`START_SIMULATION_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`START_SIMULATION_LEAVE " -"`\\" +":py:obj:`START_SIMULATION_LEAVE `\\" msgstr "" +":py:obj:`START_SIMULATION_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid ":py:obj:`DRIVER_CONNECT `\\" -msgstr "" +msgstr ":py:obj:`DRIVER_CONNECT `\\" #: flwr.common.EventType.capitalize:1::1 of msgid ":py:obj:`DRIVER_DISCONNECT `\\" -msgstr "" +msgstr ":py:obj:`DRIVER_DISCONNECT `\\" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_DRIVER_ENTER `\\" +msgid "" +":py:obj:`START_DRIVER_ENTER `\\" msgstr "" +":py:obj:`START_DRIVER_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of -msgid ":py:obj:`START_DRIVER_LEAVE `\\" +msgid "" +":py:obj:`START_DRIVER_LEAVE `\\" msgstr "" +":py:obj:`START_DRIVER_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_CLIENT_APP_ENTER " -"`\\" +":py:obj:`RUN_CLIENT_APP_ENTER `\\" msgstr "" +":py:obj:`RUN_CLIENT_APP_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_CLIENT_APP_LEAVE " -"`\\" +":py:obj:`RUN_CLIENT_APP_LEAVE `\\" msgstr "" +":py:obj:`RUN_CLIENT_APP_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SERVER_APP_ENTER " -"`\\" +":py:obj:`RUN_SERVER_APP_ENTER `\\" msgstr "" +":py:obj:`RUN_SERVER_APP_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SERVER_APP_LEAVE " -"`\\" +":py:obj:`RUN_SERVER_APP_LEAVE `\\" msgstr "" +":py:obj:`RUN_SERVER_APP_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERNODE_ENTER " -"`\\" +":py:obj:`RUN_SUPERNODE_ENTER `\\" msgstr "" +":py:obj:`RUN_SUPERNODE_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPERNODE_LEAVE " -"`\\" +":py:obj:`RUN_SUPERNODE_LEAVE `\\" msgstr "" +":py:obj:`RUN_SUPERNODE_LEAVE `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPEREXEC_ENTER " -"`\\" +":py:obj:`RUN_SUPEREXEC_ENTER `\\" msgstr "" +":py:obj:`RUN_SUPEREXEC_ENTER `\\" #: flwr.common.EventType.capitalize:1::1 of msgid "" -":py:obj:`RUN_SUPEREXEC_LEAVE " -"`\\" +":py:obj:`RUN_SUPEREXEC_LEAVE `\\" msgstr "" +":py:obj:`RUN_SUPEREXEC_LEAVE `\\" #: flwr.common.EventType.capitalize:3 of msgid "" "More specifically, make the first character have upper case and the rest " "lower case." -msgstr "" +msgstr "보다 구체적으로, 첫 번째 문자는 대문자로, 나머지는 소문자로 만듭니다." #: flwr.common.EventType.center:3 flwr.common.EventType.ljust:3 #: flwr.common.EventType.rjust:3 of -msgid "Padding is done using the specified fill character (default is a space)." -msgstr "" +msgid "" +"Padding is done using the specified fill character (default is a space)." +msgstr "패딩은 지정된 채우기 문자를 사용하여 수행됩니다(기본값은 공백)." #: flwr.common.EventType.count:1 of msgid "" -"Return the number of non-overlapping occurrences of substring sub in " -"string S[start:end]. Optional arguments start and end are interpreted as" -" in slice notation." +"Return the number of non-overlapping occurrences of substring sub in string " +"S[start:end]. Optional arguments start and end are interpreted as in slice " +"notation." msgstr "" +"문자열 S[start:end]에서 부분 문자열 sub의 겹치지 않는 횟수를 반환합니다. " +"선택적 인자 start와 end는 슬라이스 표기법과 같이 해석됩니다." #: flwr.common.EventType.encode:3 of msgid "encoding" -msgstr "" +msgstr "인코딩" #: flwr.common.EventType.encode:4 of msgid "The encoding in which to encode the string." -msgstr "" +msgstr "문자열을 인코딩합니다." #: flwr.common.EventType.encode:9 of msgid "errors" -msgstr "" +msgstr "오류" #: flwr.common.EventType.encode:6 of msgid "" "The error handling scheme to use for encoding errors. The default is " "'strict' meaning that encoding errors raise a UnicodeEncodeError. Other " -"possible values are 'ignore', 'replace' and 'xmlcharrefreplace' as well " -"as any other name registered with codecs.register_error that can handle " +"possible values are 'ignore', 'replace' and 'xmlcharrefreplace' as well as " +"any other name registered with codecs.register_error that can handle " "UnicodeEncodeErrors." msgstr "" +"인코딩 오류에 사용할 오류 처리 방식입니다. 기본값은 'strict'로, 인코딩 " +"오류가 발생하면 UnicodeEncodeError를 발생시킵니다. 다른 가능한 값으로는 " +"'ignore', 'replace', 'xmlcharrefreplace', 그리고 UnicodeEncodeError를 처리할 " +"수 있는 codecs.register_error에 등록된 다른 이름도 사용할 수 있습니다." #: flwr.common.EventType.endswith:1 of msgid "" "Return True if S ends with the specified suffix, False otherwise. With " -"optional start, test S beginning at that position. With optional end, " -"stop comparing S at that position. suffix can also be a tuple of strings " -"to try." +"optional start, test S beginning at that position. With optional end, stop " +"comparing S at that position. suffix can also be a tuple of strings to try." msgstr "" +"S가 지정된 접미사로 끝나면 True를 반환하고, 그렇지 않으면 False를 " +"반환합니다. 시작 옵션을 사용하면 해당 위치부터 S를 테스트합니다. end 옵션을 " +"사용하면 해당 위치에서 S 비교를 중지합니다. 접미사는 시도할 문자열의 튜플일 " +"수도 있습니다." #: flwr.common.EventType.expandtabs:3 of msgid "If tabsize is not given, a tab size of 8 characters is assumed." -msgstr "" +msgstr "탭 크기를 지정하지 않으면 크기가 8로 지정됩니다." #: flwr.common.EventType.find:1 flwr.common.EventType.index:1 of msgid "" -"Return the lowest index in S where substring sub is found, such that sub " -"is contained within S[start:end]. Optional arguments start and end are " +"Return the lowest index in S where substring sub is found, such that sub is " +"contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" +"부분 문자열 sub가 발견되는 곳의 가장 낮은 인덱스를 반환하며, sub는 " +"S[start:end] 내에 포함되어야 합니다. 선택적 인자 start와 end는 슬라이스 " +"표기법과 같이 해석됩니다." #: flwr.common.EventType.find:5 flwr.common.EventType.rfind:5 of msgid "Return -1 on failure." -msgstr "" +msgstr "실패 시 -1을 반환합니다." #: flwr.common.EventType.format:1 of msgid "" -"Return a formatted version of S, using substitutions from args and " -"kwargs. The substitutions are identified by braces ('{' and '}')." -msgstr "" +"Return a formatted version of S, using substitutions from args and kwargs. " +"The substitutions are identified by braces ('{' and '}')." +msgstr "args와 kwargs의 치환을 사용하여 형식이 지정된 S를 반환합니다. 치환은 " +"중괄호('{' 및 '}')로 식별됩니다." #: flwr.common.EventType.format_map:1 of msgid "" "Return a formatted version of S, using substitutions from mapping. The " "substitutions are identified by braces ('{' and '}')." -msgstr "" +msgstr "매핑의 치환을 사용하여 형식이 지정된 S를 반환합니다. 치환은 중괄호('{' 및 " +"'}')로 식별됩니다." #: flwr.common.EventType.index:5 flwr.common.EventType.rindex:5 of msgid "Raises ValueError when the substring is not found." -msgstr "" +msgstr "부분 문자열을 찾을 수 없을 때 ValueError를 발생시킵니다." #: flwr.common.EventType.isalnum:3 of msgid "" -"A string is alpha-numeric if all characters in the string are alpha-" -"numeric and there is at least one character in the string." -msgstr "" +"A string is alpha-numeric if all characters in the string are alpha-numeric " +"and there is at least one character in the string." +msgstr "문자열의 모든 문자가 영숫자이고 문자열에 하나 이상의 문자가 있는 경우 " +"문자열은 영-숫자입니다." #: flwr.common.EventType.isalpha:3 of msgid "" -"A string is alphabetic if all characters in the string are alphabetic and" -" there is at least one character in the string." -msgstr "" +"A string is alphabetic if all characters in the string are alphabetic and " +"there is at least one character in the string." +msgstr "문자열의 모든 문자가 알파벳이고 문자열에 하나 이상의 문자가 있는 경우 " +"문자열은 알파벳입니다." #: flwr.common.EventType.isascii:3 of msgid "" -"ASCII characters have code points in the range U+0000-U+007F. Empty " -"string is ASCII too." -msgstr "" +"ASCII characters have code points in the range U+0000-U+007F. Empty string " +"is ASCII too." +msgstr "ASCII 문자는 U+0000-U+007F 범위의 코드 포인트가 있습니다. 빈 문자열도 " +"ASCII입니다." #: flwr.common.EventType.isdecimal:3 of msgid "" -"A string is a decimal string if all characters in the string are decimal " -"and there is at least one character in the string." -msgstr "" +"A string is a decimal string if all characters in the string are decimal and " +"there is at least one character in the string." +msgstr "문자열의 모든 문자가 10진수이고 문자열에 하나 이상의 문자가 있는 경우 " +"문자열은 10진수 문자열입니다." #: flwr.common.EventType.isdigit:3 of msgid "" -"A string is a digit string if all characters in the string are digits and" -" there is at least one character in the string." -msgstr "" +"A string is a digit string if all characters in the string are digits and " +"there is at least one character in the string." +msgstr "문자열의 모든 문자가 숫자이고 문자열에 하나 이상의 문자가 있는 경우 문자열은 " +"숫자 문자열입니다." #: flwr.common.EventType.isidentifier:3 of msgid "" -"Call keyword.iskeyword(s) to test whether string s is a reserved " -"identifier, such as \"def\" or \"class\"." +"Call keyword.iskeyword(s) to test whether string s is a reserved identifier, " +"such as \"def\" or \"class\"." msgstr "" +"keyword.iskeyword(s)를 호출하여 문자열 s가 \"def\" 또는 \"class\"와 같은 " +"예약 식별자인지 테스트합니다." #: flwr.common.EventType.islower:3 of msgid "" -"A string is lowercase if all cased characters in the string are lowercase" -" and there is at least one cased character in the string." -msgstr "" +"A string is lowercase if all cased characters in the string are lowercase " +"and there is at least one cased character in the string." +msgstr "문자열이 모두 소문자이고 문자열에 문자가 하나 이상 있는 경우 문자열은 " +"소문자입니다." #: flwr.common.EventType.isnumeric:3 of msgid "" -"A string is numeric if all characters in the string are numeric and there" -" is at least one character in the string." -msgstr "" +"A string is numeric if all characters in the string are numeric and there is " +"at least one character in the string." +msgstr "문자열의 모든 문자가 숫자이고 문자열에 하나 이상의 문자가 있는 경우 문자열은 " +"숫자입니다." #: flwr.common.EventType.isprintable:3 of msgid "" -"A string is printable if all of its characters are considered printable " -"in repr() or if it is empty." -msgstr "" +"A string is printable if all of its characters are considered printable in " +"repr() or if it is empty." +msgstr "문자열은 repr()에서 모든 문자가 인쇄 가능한 것으로 간주되거나 비어 있는 경우 " +"인쇄할 수 있습니다." #: flwr.common.EventType.isspace:3 of msgid "" -"A string is whitespace if all characters in the string are whitespace and" -" there is at least one character in the string." -msgstr "" +"A string is whitespace if all characters in the string are whitespace and " +"there is at least one character in the string." +msgstr "문자열의 모든 문자가 공백이고 문자열에 하나 이상의 문자가 있는 경우 문자열은 " +"공백입니다." #: flwr.common.EventType.istitle:3 of msgid "" -"In a title-cased string, upper- and title-case characters may only follow" -" uncased characters and lowercase characters only cased ones." -msgstr "" +"In a title-cased string, upper- and title-case characters may only follow " +"uncased characters and lowercase characters only cased ones." +msgstr "제목 대/소문자 문자열에서 대문자와 제목 대문자는 대소문자만, 소문자는 " +"대문자만 뒤에 올 수 있습니다." #: flwr.common.EventType.isupper:3 of msgid "" -"A string is uppercase if all cased characters in the string are uppercase" -" and there is at least one cased character in the string." -msgstr "" +"A string is uppercase if all cased characters in the string are uppercase " +"and there is at least one cased character in the string." +msgstr "문자열의 모든 문자가 대문자이고 문자열에 문자가 하나 이상 있는 경우 문자열은 " +"대문자입니다." #: flwr.common.EventType.join:3 of msgid "" -"The string whose method is called is inserted in between each given " -"string. The result is returned as a new string." -msgstr "" +"The string whose method is called is inserted in between each given string. " +"The result is returned as a new string." +msgstr "메서드가 호출되는 문자열은 주어진 각 문자열 사이에 삽입됩니다. 결과는 새 " +"문자열로 반환됩니다." #: flwr.common.EventType.join:6 of msgid "Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'" -msgstr "" +msgstr "Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'" #: flwr.common.EventType.lstrip:3 flwr.common.EventType.rstrip:3 #: flwr.common.EventType.strip:3 of msgid "If chars is given and not None, remove characters in chars instead." -msgstr "" +msgstr "None이 아닌 문자가 지정되면 대신 문자열에서 문자를 제거합니다." #: flwr.common.EventType.maketrans:3 of msgid "" @@ -10190,10 +10936,15 @@ msgid "" "ordinals (integers) or characters to Unicode ordinals, strings or None. " "Character keys will be then converted to ordinals. If there are two " "arguments, they must be strings of equal length, and in the resulting " -"dictionary, each character in x will be mapped to the character at the " -"same position in y. If there is a third argument, it must be a string, " -"whose characters will be mapped to None in the result." +"dictionary, each character in x will be mapped to the character at the same " +"position in y. If there is a third argument, it must be a string, whose " +"characters will be mapped to None in the result." msgstr "" +"argument이 하나만 있는 경우, 유니코드 서수(정수) 또는 문자를 유니코드 서수, " +"문자열 또는 None에 매핑하는 dictionary이어야 합니다. 그러면 문자 키가 서수로 " +"변환됩니다. 인수가 두 개이면 길이가 같은 문자열이어야 하며, 결과 " +"dictionary에서 x의 각 문자는 y의 같은 위치에 있는 문자에 매핑됩니다. 세 번째 " +"인수가 있으면 문자열이어야 하며, 그 문자는 결과에서 None에 매핑됩니다." #: flwr.common.EventType.partition:3 of msgid "" @@ -10201,18 +10952,23 @@ msgid "" "found, returns a 3-tuple containing the part before the separator, the " "separator itself, and the part after it." msgstr "" +"문자열에서 구분 기호를 검색합니다. 구분 기호가 발견되면 구분 기호 앞 부분, " +"구분 기호 자체, 구분 기호 뒤 부분을 포함하는 3-tuple을 반환합니다." #: flwr.common.EventType.partition:7 of msgid "" "If the separator is not found, returns a 3-tuple containing the original " "string and two empty strings." -msgstr "" +msgstr "구분 기호를 찾을 수 없으면 원래 문자열과 빈 문자열 2개를 포함하는 3-튜플을 " +"반환합니다." #: flwr.common.EventType.removeprefix:3 of msgid "" -"If the string starts with the prefix string, return string[len(prefix):]." -" Otherwise, return a copy of the original string." +"If the string starts with the prefix string, return string[len(prefix):]. " +"Otherwise, return a copy of the original string." msgstr "" +"문자열이 접두사 문자열로 시작하면 문자열[len(prefix):]을 반환합니다. 그렇지 " +"않으면 원본 문자열의 복사본을 반환합니다." #: flwr.common.EventType.removesuffix:3 of msgid "" @@ -10220,667 +10976,727 @@ msgid "" "return string[:-len(suffix)]. Otherwise, return a copy of the original " "string." msgstr "" +"문자열이 접미사 문자열로 끝나고 해당 접미사가 비어 있지 않으면 " +"문자열[:-len(suffix)]을 반환합니다. 그렇지 않으면 원본 문자열의 복사본을 " +"반환합니다." #: flwr.common.EventType.replace:5 of msgid "count" -msgstr "" +msgstr "카운트" #: flwr.common.EventType.replace:4 of msgid "" "Maximum number of occurrences to replace. -1 (the default value) means " "replace all occurrences." -msgstr "" +msgstr "대체할 최대 발생 횟수입니다. -1(기본값)은 모든 항목을 교체한다는 의미입니다." #: flwr.common.EventType.replace:7 of msgid "" -"If the optional argument count is given, only the first count occurrences" -" are replaced." -msgstr "" +"If the optional argument count is given, only the first count occurrences " +"are replaced." +msgstr "선택적 argument 개수를 지정하면 첫 번째 개수만 바뀝니다." #: flwr.common.EventType.rfind:1 flwr.common.EventType.rindex:1 of msgid "" -"Return the highest index in S where substring sub is found, such that sub" -" is contained within S[start:end]. Optional arguments start and end are " +"Return the highest index in S where substring sub is found, such that sub is " +"contained within S[start:end]. Optional arguments start and end are " "interpreted as in slice notation." msgstr "" +"부분 문자열 sub가 발견되는 곳의 가장 높은 인덱스를 반환하며, sub는 " +"S[start:end] 내에 포함되어야 합니다. 선택적 인자 start와 end는 슬라이스 " +"표기법과 같이 해석됩니다." #: flwr.common.EventType.rpartition:3 of msgid "" -"This will search for the separator in the string, starting at the end. If" -" the separator is found, returns a 3-tuple containing the part before the" -" separator, the separator itself, and the part after it." +"This will search for the separator in the string, starting at the end. If " +"the separator is found, returns a 3-tuple containing the part before the " +"separator, the separator itself, and the part after it." msgstr "" +"그러면 문자열에서 끝 부분부터 시작하여 구분 기호를 검색합니다. 구분 기호가 " +"발견되면 구분 기호 앞 부분, 구분 기호 자체, 구분 기호 뒤 부분을 포함하는 3-" +"tuple을 반환합니다." #: flwr.common.EventType.rpartition:7 of msgid "" "If the separator is not found, returns a 3-tuple containing two empty " "strings and the original string." -msgstr "" +msgstr "구분 기호를 찾을 수 없는 경우 빈 문자열 2개와 원래 문자열을 포함하는 3-" +"tuple을 반환합니다." #: flwr.common.EventType.rsplit:7 flwr.common.EventType.split:7 of msgid "sep" -msgstr "" +msgstr "sep" #: flwr.common.EventType.rsplit:4 flwr.common.EventType.split:4 of msgid "The separator used to split the string." -msgstr "" +msgstr "문자열을 분할하는 데 사용되는 구분 기호입니다." #: flwr.common.EventType.rsplit:6 flwr.common.EventType.split:6 of msgid "" -"When set to None (the default value), will split on any whitespace " -"character (including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard" -" empty strings from the result." +"When set to None (the default value), will split on any whitespace character " +"(including \\\\n \\\\r \\\\t \\\\f and spaces) and will discard empty " +"strings from the result." msgstr "" +"None(기본값)으로 설정하면 모든 공백 문자(\\\\n" +" \\\\r \\\\t \\\\f 및 공백 포함)를 분할하고 결과에서 빈 문자열을 삭제합니다." #: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of msgid "maxsplit" -msgstr "" +msgstr "maxsplit" #: flwr.common.EventType.rsplit:10 flwr.common.EventType.split:10 of msgid "" -"Maximum number of splits (starting from the left). -1 (the default value)" -" means no limit." -msgstr "" +"Maximum number of splits (starting from the left). -1 (the default value) " +"means no limit." +msgstr "최대 분할 횟수(왼쪽부터 시작). -1(기본값)은 제한이 없음을 의미합니다." #: flwr.common.EventType.rsplit:13 of msgid "Splitting starts at the end of the string and works to the front." -msgstr "" +msgstr "분할은 문자열 끝에서 시작하여 앞쪽으로 진행됩니다." #: flwr.common.EventType.split:13 of msgid "" "Note, str.split() is mainly useful for data that has been intentionally " -"delimited. With natural text that includes punctuation, consider using " -"the regular expression module." +"delimited. With natural text that includes punctuation, consider using the " +"regular expression module." msgstr "" +"참고로 str.split()은 주로 의도적으로 구분된 데이터에 유용합니다. 구두점이 " +"포함된 자연 텍스트의 경우 정규식 모듈을 사용하는 것이 좋습니다." #: flwr.common.EventType.splitlines:3 of msgid "" -"Line breaks are not included in the resulting list unless keepends is " -"given and true." -msgstr "" +"Line breaks are not included in the resulting list unless keepends is given " +"and true." +msgstr "줄 바꿈은 keepends가 주어지고 참이 아니면 결과 목록에 포함되지 않습니다." #: flwr.common.EventType.startswith:1 of msgid "" "Return True if S starts with the specified prefix, False otherwise. With " -"optional start, test S beginning at that position. With optional end, " -"stop comparing S at that position. prefix can also be a tuple of strings " -"to try." +"optional start, test S beginning at that position. With optional end, stop " +"comparing S at that position. prefix can also be a tuple of strings to try." msgstr "" +"S가 지정된 접두사로 시작하면 True를 반환하고, 그렇지 않으면 False를 " +"반환합니다. 시작 옵션을 사용하면 해당 위치에서 시작되는 S를 테스트합니다. " +"선택적 end를 사용하면 해당 위치에서 S 비교를 중지합니다. 접두사는 시도할 " +"문자열의 튜플일 수도 있습니다." #: flwr.common.EventType.title:3 of msgid "" -"More specifically, words start with uppercased characters and all " -"remaining cased characters have lower case." -msgstr "" +"More specifically, words start with uppercased characters and all remaining " +"cased characters have lower case." +msgstr "보다 구체적으로, 단어는 대문자로 시작하고 나머지 모든 대소문자는 소문자로 " +"표기합니다." #: flwr.common.EventType.translate:5 of msgid "table" -msgstr "" +msgstr "table" #: flwr.common.EventType.translate:4 of msgid "" -"Translation table, which must be a mapping of Unicode ordinals to Unicode" -" ordinals, strings, or None." -msgstr "" +"Translation table, which must be a mapping of Unicode ordinals to Unicode " +"ordinals, strings, or None." +msgstr "유니코드 서수를 유니코드 서수, 문자열 또는 없음으로 매핑하는 번역 " +"테이블이어야 합니다." #: flwr.common.EventType.translate:7 of msgid "" "The table must implement lookup/indexing via __getitem__, for instance a " -"dictionary or list. If this operation raises LookupError, the character " -"is left untouched. Characters mapped to None are deleted." +"dictionary or list. If this operation raises LookupError, the character is " +"left untouched. Characters mapped to None are deleted." msgstr "" +"테이블은 사전이나 목록과 같이 __getitem__을 통해 조회/색인을 구현해야 " +"합니다. 이 작업에서 LookupError가 발생하면 문자는 그대로 유지됩니다. " +"없음으로 매핑된 문자는 삭제됩니다." #: flwr.common.EventType.zfill:3 of msgid "The string is never truncated." -msgstr "" +msgstr "문자열은 잘리지 않습니다." #: ../../source/ref-api/flwr.common.FitIns.rst:2 msgid "FitIns" -msgstr "" +msgstr "FitIns" #: ../../source/ref-api/flwr.common.FitIns.rst:29::1 msgid ":py:obj:`parameters `\\" -msgstr "" +msgstr ":py:obj:`parameters `\\" #: ../../source/ref-api/flwr.common.FitIns.rst:29::1 msgid ":py:obj:`config `\\" -msgstr "" +msgstr ":py:obj:`config `\\" #: ../../source/ref-api/flwr.common.FitRes.rst:2 msgid "FitRes" -msgstr "" +msgstr "FitRes" #: ../../source/ref-api/flwr.common.FitRes.rst:31::1 msgid ":py:obj:`status `\\" -msgstr "" +msgstr ":py:obj:`status `\\" #: ../../source/ref-api/flwr.common.FitRes.rst:31::1 msgid ":py:obj:`parameters `\\" -msgstr "" +msgstr ":py:obj:`parameters `\\" #: ../../source/ref-api/flwr.common.FitRes.rst:31::1 msgid ":py:obj:`num_examples `\\" -msgstr "" +msgstr ":py:obj:`num_examples `\\" #: ../../source/ref-api/flwr.common.FitRes.rst:31::1 msgid ":py:obj:`metrics `\\" -msgstr "" +msgstr ":py:obj:`metrics `\\" #: ../../source/ref-api/flwr.common.GetParametersIns.rst:2 msgid "GetParametersIns" -msgstr "" +msgstr "GetParametersIns" #: ../../source/ref-api/flwr.common.GetParametersIns.rst:28::1 msgid ":py:obj:`config `\\" -msgstr "" +msgstr ":py:obj:`config `\\" #: ../../source/ref-api/flwr.common.GetParametersRes.rst:2 msgid "GetParametersRes" -msgstr "" +msgstr "GetParametersRes" #: ../../source/ref-api/flwr.common.GetParametersRes.rst:29::1 msgid ":py:obj:`status `\\" -msgstr "" +msgstr ":py:obj:`status `\\" #: ../../source/ref-api/flwr.common.GetParametersRes.rst:29::1 msgid ":py:obj:`parameters `\\" -msgstr "" +msgstr ":py:obj:`parameters `\\" #: ../../source/ref-api/flwr.common.GetPropertiesIns.rst:2 msgid "GetPropertiesIns" -msgstr "" +msgstr "GetPropertiesIns" #: ../../source/ref-api/flwr.common.GetPropertiesIns.rst:28::1 msgid ":py:obj:`config `\\" -msgstr "" +msgstr ":py:obj:`config `\\" #: ../../source/ref-api/flwr.common.GetPropertiesRes.rst:2 msgid "GetPropertiesRes" -msgstr "" +msgstr "GetPropertiesRes" #: ../../source/ref-api/flwr.common.GetPropertiesRes.rst:29::1 msgid ":py:obj:`status `\\" -msgstr "" +msgstr ":py:obj:`status `\\" #: ../../source/ref-api/flwr.common.GetPropertiesRes.rst:29::1 msgid ":py:obj:`properties `\\" -msgstr "" +msgstr ":py:obj:`properties `\\" #: ../../source/ref-api/flwr.common.Message.rst:2 msgid "Message" -msgstr "" +msgstr "Message" #: flwr.common.Message.content:1::1 flwr.common.Message.metadata:1 #: flwr.common.message.Message:3 of msgid "A dataclass including information about the message to be executed." -msgstr "" +msgstr "실행할 메시지에 대한 정보를 포함한 데이터 클래스입니다." #: flwr.common.message.Message:5 of msgid "" -"Holds records either sent by another entity (e.g. sent by the server-side" -" logic to a client, or vice-versa) or that will be sent to it." -msgstr "" +"Holds records either sent by another entity (e.g. sent by the server-side " +"logic to a client, or vice-versa) or that will be sent to it." +msgstr "다른 엔터티(예: 서버 측 로직이 클라이언트로 전송하거나 그 반대로 전송하는 등)" +"가 전송했거나 전송할 레코드를 보유합니다." #: flwr.common.message.Message:8 of msgid "" -"A dataclass that captures information about an error that took place when" -" processing another message." -msgstr "" +"A dataclass that captures information about an error that took place when " +"processing another message." +msgstr "다른 메시지를 처리할 때 발생한 오류에 대한 정보를 캡처하는 데이터 " +"클래스입니다." #: ../../source/ref-api/flwr.common.Message.rst:35::1 msgid "" ":py:obj:`create_error_reply `\\ " "\\(error\\[\\, ttl\\]\\)" msgstr "" +":py:obj:`create_error_reply `\\ \\(" +"error\\[\\, ttl\\]\\)" #: ../../source/ref-api/flwr.common.Message.rst:35::1 #: flwr.common.message.Message.create_error_reply:1 of msgid "Construct a reply message indicating an error happened." -msgstr "" +msgstr "오류가 발생했음을 나타내는 답장 메시지를 작성합니다." #: ../../source/ref-api/flwr.common.Message.rst:35::1 msgid "" -":py:obj:`create_reply `\\ " -"\\(content\\[\\, ttl\\]\\)" +":py:obj:`create_reply `\\ \\(content\\[\\, " +"ttl\\]\\)" msgstr "" +":py:obj:`create_reply `\\ \\(content\\[\\, " +"ttl\\]\\)" #: ../../source/ref-api/flwr.common.Message.rst:35::1 #: flwr.common.message.Message.create_reply:1 of msgid "Create a reply to this message with specified content and TTL." -msgstr "" +msgstr "지정된 콘텐츠와 TTL을 사용하여 이 메시지에 대한 답글을 작성합니다." #: ../../source/ref-api/flwr.common.Message.rst:35::1 msgid ":py:obj:`has_content `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`has_content `\\ \\(\\)" #: ../../source/ref-api/flwr.common.Message.rst:35::1 #: flwr.common.message.Message.has_content:1 of msgid "Return True if message has content, else False." -msgstr "" +msgstr "메시지에 콘텐츠가 있으면 True을 반환하고, 그렇지 않으면 False을 반환합니다." #: ../../source/ref-api/flwr.common.Message.rst:35::1 msgid ":py:obj:`has_error `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`has_error `\\ \\(\\)" #: ../../source/ref-api/flwr.common.Message.rst:35::1 #: flwr.common.message.Message.has_error:1 of msgid "Return True if message has an error, else False." -msgstr "" +msgstr "메시지에 오류가 있으면 True을 반환하고, 그렇지 않으면 False을 반환합니다." #: flwr.common.Message.content:1::1 of msgid ":py:obj:`content `\\" -msgstr "" +msgstr ":py:obj:`content `\\" #: flwr.common.Message.content:1 flwr.common.Message.content:1::1 #: of msgid "The content of this message." -msgstr "" +msgstr "이 메시지의 내용입니다." #: flwr.common.Message.content:1::1 of msgid ":py:obj:`error `\\" -msgstr "" +msgstr ":py:obj:`error `\\" #: flwr.common.Message.content:1::1 flwr.common.Message.error:1 of msgid "Error captured by this message." -msgstr "" +msgstr "이 메시지가 캡처한 오류입니다." #: flwr.common.Message.content:1::1 of msgid ":py:obj:`metadata `\\" -msgstr "" +msgstr ":py:obj:`metadata `\\" #: flwr.common.message.Message.create_error_reply:3 of msgid "The error that was encountered." -msgstr "" +msgstr "오류가 발생했습니다." #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of msgid "" -"Time-to-live for this message in seconds. If unset, it will be set based " -"on the remaining time for the received message before it expires. This " -"follows the equation: ttl = msg.meta.ttl - (reply.meta.created_at - " -"msg.meta.created_at)" +"Time-to-live for this message in seconds. If unset, it will be set based on " +"the remaining time for the received message before it expires. This follows " +"the equation: ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta." +"created_at)" msgstr "" +"이 메시지의 남은 시간(초)입니다. 설정하지 않으면 수신된 메시지가 만료되기 " +"전까지 남은 시간을 기준으로 설정됩니다. 이는 다음과 같은 공식을 따릅니다: " +"ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)" #: flwr.common.message.Message.create_error_reply:5 #: flwr.common.message.Message.create_reply:9 of msgid "" -"Time-to-live for this message in seconds. If unset, it will be set based " -"on the remaining time for the received message before it expires. This " -"follows the equation:" +"Time-to-live for this message in seconds. If unset, it will be set based on " +"the remaining time for the received message before it expires. This follows " +"the equation:" msgstr "" +"이 메시지의 남은 시간(초)입니다. 설정하지 않으면 수신된 메시지가 만료되기 " +"전까지 남은 시간을 기준으로 설정됩니다. 이는 다음 공식을 따릅니다:" #: flwr.common.message.Message.create_error_reply:9 #: flwr.common.message.Message.create_reply:13 of msgid "ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)" -msgstr "" +msgstr "ttl = msg.meta.ttl - (reply.meta.created_at - msg.meta.created_at)" #: flwr.common.message.Message.create_reply:3 of msgid "" -"The method generates a new `Message` as a reply to this message. It " -"inherits 'run_id', 'src_node_id', 'dst_node_id', and 'message_type' from " -"this message and sets 'reply_to_message' to the ID of this message." +"The method generates a new `Message` as a reply to this message. It inherits " +"'run_id', 'src_node_id', 'dst_node_id', and 'message_type' from this message " +"and sets 'reply_to_message' to the ID of this message." msgstr "" +"이 메서드는 이 메시지에 대한 응답으로 새로운 '메시지'를 생성합니다. 이 " +"메시지에서 'run_id', 'src_node_id', 'dst_node_id', 'message_type'을 상속하고 " +"'reply_to_message'를 이 메시지의 ID로 설정합니다." #: flwr.common.message.Message.create_reply:7 of msgid "The content for the reply message." -msgstr "" +msgstr "답장 메시지의 콘텐츠입니다." #: flwr.common.message.Message.create_reply:16 of msgid "A new `Message` instance representing the reply." -msgstr "" +msgstr "답장을 나타내는 새로운 `메시지` 인스턴스입니다." #: ../../source/ref-api/flwr.common.MessageType.rst:2 msgid "MessageType" -msgstr "" +msgstr "MessageType" #: ../../source/ref-api/flwr.common.MessageType.rst:30::1 msgid ":py:obj:`EVALUATE `\\" -msgstr "" +msgstr ":py:obj:`EVALUATE `\\" #: ../../source/ref-api/flwr.common.MessageType.rst:30::1 msgid ":py:obj:`QUERY `\\" -msgstr "" +msgstr ":py:obj:`QUERY `\\" #: ../../source/ref-api/flwr.common.MessageType.rst:30::1 msgid ":py:obj:`TRAIN `\\" -msgstr "" +msgstr ":py:obj:`TRAIN `\\" #: ../../source/ref-api/flwr.common.MessageTypeLegacy.rst:2 msgid "MessageTypeLegacy" -msgstr "" +msgstr "MessageTypeLegacy" #: ../../source/ref-api/flwr.common.MessageTypeLegacy.rst:29::1 -msgid ":py:obj:`GET_PARAMETERS `\\" +msgid "" +":py:obj:`GET_PARAMETERS `\\" msgstr "" +":py:obj:`GET_PARAMETERS `\\" #: ../../source/ref-api/flwr.common.MessageTypeLegacy.rst:29::1 -msgid ":py:obj:`GET_PROPERTIES `\\" +msgid "" +":py:obj:`GET_PROPERTIES `\\" msgstr "" +":py:obj:`GET_PROPERTIES `\\" #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.run_id:1 flwr.common.message.Metadata:3 of msgid "An identifier for the current run." -msgstr "" +msgstr "현재 실행에 대한 식별자입니다." #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.message_id:1 flwr.common.message.Metadata:5 of msgid "An identifier for the current message." -msgstr "" +msgstr "현재 메시지의 식별자입니다." #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.src_node_id:1 flwr.common.message.Metadata:7 of msgid "An identifier for the node sending this message." -msgstr "" +msgstr "이 메시지를 보내는 노드의 식별자입니다." #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.dst_node_id:1 flwr.common.message.Metadata:9 of msgid "An identifier for the node receiving this message." -msgstr "" +msgstr "이 메시지를 수신하는 노드의 식별자입니다." #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.reply_to_message:1 flwr.common.message.Metadata:11 of msgid "An identifier for the message this message replies to." -msgstr "" +msgstr "이 메시지가 회신하는 메시지의 식별자입니다." #: flwr.common.message.Metadata:13 of msgid "" -"An identifier for grouping messages. In some settings, this is used as " -"the FL round." -msgstr "" +"An identifier for grouping messages. In some settings, this is used as the " +"FL round." +msgstr "메시지를 그룹화하기 위한 식별자입니다. 일부 설정에서는 FL 라운드로 " +"사용됩니다." #: flwr.common.message.Metadata:16 of msgid "Time-to-live for this message in seconds." -msgstr "" +msgstr "이 메시지의 유효 시간(초)입니다." #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.message_type:1 flwr.common.message.Metadata:18 of msgid "A string that encodes the action to be executed on the receiving end." -msgstr "" +msgstr "수신 측에서 실행할 작업을 인코딩하는 문자열입니다." #: flwr.common.message.Metadata:21 of msgid "" -"An identifier that can be used when loading a particular data partition " -"for a ClientApp. Making use of this identifier is more relevant when " -"conducting simulations." +"An identifier that can be used when loading a particular data partition for " +"a ClientApp. Making use of this identifier is more relevant when conducting " +"simulations." msgstr "" +"클라이언트 앱의 특정 데이터 파티션을 로드할 때 사용할 수 있는 식별자입니다. " +"시뮬레이션을 수행할 때 이 식별자를 사용하는 것이 더 적절합니다." #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`created_at `\\" -msgstr "" +msgstr ":py:obj:`created_at `\\" #: flwr.common.Metadata.created_at:1 #: flwr.common.Metadata.created_at:1::1 of msgid "Unix timestamp when the message was created." -msgstr "" +msgstr "메시지가 생성된 때의 Unix timestamp입니다." #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`dst_node_id `\\" -msgstr "" +msgstr ":py:obj:`dst_node_id `\\" #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`group_id `\\" -msgstr "" +msgstr ":py:obj:`group_id `\\" #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.group_id:1 of msgid "An identifier for grouping messages." -msgstr "" +msgstr "메시지를 그룹화하기 위한 식별자입니다." #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`message_id `\\" -msgstr "" +msgstr ":py:obj:`message_id `\\" #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`message_type `\\" -msgstr "" +msgstr ":py:obj:`message_type `\\" #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`partition_id `\\" -msgstr "" +msgstr ":py:obj:`partition_id `\\" #: flwr.common.Metadata.created_at:1::1 #: flwr.common.Metadata.partition_id:1 of msgid "An identifier telling which data partition a ClientApp should use." -msgstr "" +msgstr "클라이언트앱이 사용해야 하는 데이터 파티션을 알려주는 식별자입니다." #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`reply_to_message `\\" -msgstr "" +msgstr ":py:obj:`reply_to_message `\\" #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`run_id `\\" -msgstr "" +msgstr ":py:obj:`run_id `\\" #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`src_node_id `\\" -msgstr "" +msgstr ":py:obj:`src_node_id `\\" #: flwr.common.Metadata.created_at:1::1 of msgid ":py:obj:`ttl `\\" -msgstr "" +msgstr ":py:obj:`ttl `\\" #: flwr.common.Metadata.created_at:1::1 flwr.common.Metadata.ttl:1 #: of msgid "Time-to-live for this message." -msgstr "" +msgstr "이 메시지를 기다리는 시간입니다." #: ../../source/ref-api/flwr.common.MetricsRecord.rst:2 msgid "MetricsRecord" -msgstr "" +msgstr "MetricsRecord" #: flwr.common.record.metricsrecord.MetricsRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " -"[:py:class:`str`, :py:class:`int` | :py:class:`float` | " -":py:class:`~typing.List`\\ [:py:class:`int`] | :py:class:`~typing.List`\\" -" [:py:class:`float`]]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" +"`str`, :py:class:`int` | :py:class:`float` | :py:class:`~typing.List`\\ [:py:" +"class:`int`] | :py:class:`~typing.List`\\ [:py:class:`float`]]" msgstr "" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:`str`" +", :py:class:`int` | :py:class:`float` | :py:class:`~typing.List`\\ " +"[:py:class:`int`] | :py:class:`~typing.List`\\ [:py:class:`float`]]" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`clear `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`clear `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`count_bytes `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`count_bytes `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" -msgstr "" +msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`items `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`items `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`keys `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`keys `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`pop `\\ \\(k\\[\\,d\\]\\)" -msgstr "" +msgstr ":py:obj:`pop `\\ \\(k\\[\\,d\\]\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid "" ":py:obj:`update `\\ \\(\\[E\\, " "\\]\\*\\*F\\)" msgstr "" +":py:obj:`update `\\ \\(\\[E\\, \\]\\*\\*F\\" +")" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`values `\\ \\(\\)" msgstr "" +":py:obj:`update `\\ \\(\\[E\\, \\]\\*\\*F\\" +")" #: ../../source/ref-api/flwr.common.NDArray.rst:2 msgid "NDArray" -msgstr "" +msgstr "NDArray" #: ../../source/ref-api/flwr.common.Parameters.rst:29::1 msgid ":py:obj:`tensors `\\" -msgstr "" +msgstr ":py:obj:`tensors `\\" #: ../../source/ref-api/flwr.common.Parameters.rst:29::1 msgid ":py:obj:`tensor_type `\\" -msgstr "" +msgstr ":py:obj:`tensor_type `\\" #: ../../source/ref-api/flwr.common.ParametersRecord.rst:2 msgid "ParametersRecord" -msgstr "" +msgstr "ParametersRecord" #: flwr.common.record.parametersrecord.ParametersRecord:1 of msgid "" -"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ " -"[:py:class:`str`, :py:class:`~flwr.common.record.parametersrecord.Array`]" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:" +"`str`, :py:class:`~flwr.common.record.parametersrecord.Array`]" msgstr "" +"Bases: :py:class:`~flwr.common.record.typeddict.TypedDict`\\ [:py:class:`str`" +", :py:class:`~flwr.common.record.parametersrecord.Array`]" #: flwr.common.record.parametersrecord.ParametersRecord:3 of msgid "" -"A dataclass storing named Arrays in order. This means that it holds " -"entries as an OrderedDict[str, Array]. ParametersRecord objects can be " -"viewed as an equivalent to PyTorch's state_dict, but holding serialised " -"tensors instead." +"A dataclass storing named Arrays in order. This means that it holds entries " +"as an OrderedDict[str, Array]. ParametersRecord objects can be viewed as an " +"equivalent to PyTorch's state_dict, but holding serialised tensors instead." msgstr "" +"Arrays라는 이름의 데이터 클래스를 순서대로 저장합니다. 즉, OrderedDict[str, " +"Array]로 항목을 보유합니다. ParametersRecord 객체는 파이토치의 state_dict와 " +"동등한 것으로 볼 수 있지만, 대신 직렬화된 텐서를 보유합니다." #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`clear `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`clear `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of -msgid ":py:obj:`count_bytes `\\ \\(\\)" +msgid "" +":py:obj:`count_bytes `\\ \\(\\)" msgstr "" +":py:obj:`count_bytes `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" -msgstr "" +msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`items `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`items `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`keys `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`keys `\\ \\(\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`pop `\\ \\(k\\[\\,d\\]\\)" -msgstr "" +msgstr ":py:obj:`pop `\\ \\(k\\[\\,d\\]\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid "" ":py:obj:`update `\\ \\(\\[E\\, " "\\]\\*\\*F\\)" msgstr "" +":py:obj:`update `\\ \\(\\[E\\, \\]\\*\\*" +"F\\)" #: flwr.common.record.typeddict.TypedDict.clear:1::1 of msgid ":py:obj:`values `\\ \\(\\)" -msgstr "" +msgstr ":py:obj:`values `\\ \\(\\)" #: flwr.common.record.parametersrecord.ParametersRecord.count_bytes:3 of msgid "" -"Note that a small amount of Bytes might also be included in this counting" -" that correspond to metadata of the serialized object (e.g. of NumPy " -"array) needed for deseralization." -msgstr "" +"Note that a small amount of Bytes might also be included in this counting " +"that correspond to metadata of the serialized object (e.g. of NumPy array) " +"needed for deseralization." +msgstr "역직렬화에 필요한 직렬화된 객체의 메타데이터(예: NumPy 배열)에 해당하는 " +"소량의 바이트도 이 카운팅에 포함될 수 있습니다." #: ../../source/ref-api/flwr.common.ReconnectIns.rst:2 msgid "ReconnectIns" -msgstr "" +msgstr "ReconnectIns" #: ../../source/ref-api/flwr.common.ReconnectIns.rst:28::1 msgid ":py:obj:`seconds `\\" -msgstr "" +msgstr ":py:obj:`seconds `\\" #: ../../source/ref-api/flwr.common.RecordSet.rst:2 msgid "RecordSet" -msgstr "" +msgstr "RecordSet" #: flwr.common.RecordSet.configs_records:1::1 of msgid ":py:obj:`configs_records `\\" -msgstr "" +msgstr ":py:obj:`configs_records `\\" #: flwr.common.RecordSet.configs_records:1 #: flwr.common.RecordSet.configs_records:1::1 of msgid "Dictionary holding ConfigsRecord instances." -msgstr "" +msgstr "Dictionary holding ConfigsRecord instances." #: flwr.common.RecordSet.configs_records:1::1 of msgid ":py:obj:`metrics_records `\\" -msgstr "" +msgstr ":py:obj:`metrics_records `\\" #: flwr.common.RecordSet.configs_records:1::1 #: flwr.common.RecordSet.metrics_records:1 of msgid "Dictionary holding MetricsRecord instances." -msgstr "" +msgstr "Dictionary holding MetricsRecord instances." #: flwr.common.RecordSet.configs_records:1::1 of -msgid ":py:obj:`parameters_records `\\" +msgid "" +":py:obj:`parameters_records `\\" msgstr "" +":py:obj:`parameters_records `\\" #: flwr.common.RecordSet.configs_records:1::1 #: flwr.common.RecordSet.parameters_records:1 of msgid "Dictionary holding ParametersRecord instances." -msgstr "" +msgstr "Dictionary holding ParametersRecord instances." #: ../../source/ref-api/flwr.common.ServerMessage.rst:2 msgid "ServerMessage" -msgstr "" +msgstr "ServerMessage" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid ":py:obj:`evaluate_ins `\\" -msgstr "" +msgstr ":py:obj:`evaluate_ins `\\" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid ":py:obj:`fit_ins `\\" -msgstr "" +msgstr ":py:obj:`fit_ins `\\" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid "" -":py:obj:`get_parameters_ins " -"`\\" +":py:obj:`get_parameters_ins `\\" msgstr "" +":py:obj:`get_parameters_ins `\\" #: ../../source/ref-api/flwr.common.ServerMessage.rst:31::1 msgid "" -":py:obj:`get_properties_ins " -"`\\" +":py:obj:`get_properties_ins `\\" msgstr "" +":py:obj:`get_properties_ins `\\" #: ../../source/ref-api/flwr.common.Status.rst:2 msgid "Status" -msgstr "" +msgstr "Status" #: ../../source/ref-api/flwr.common.Status.rst:29::1 msgid ":py:obj:`code `\\" -msgstr "" +msgstr ":py:obj:`code `\\" #: ../../source/ref-api/flwr.common.Status.rst:29::1 msgid ":py:obj:`message `\\" -msgstr "" +msgstr ":py:obj:`message `\\" #: ../../source/ref-api/flwr.common.array_from_numpy.rst:2 msgid "array\\_from\\_numpy" -msgstr "" +msgstr "array\\_from\\_numpy" #: ../../source/ref-api/flwr.common.bytes_to_ndarray.rst:2 msgid "bytes\\_to\\_ndarray" -msgstr "" +msgstr "bytes\\_to\\_ndarray" #: ../../source/ref-api/flwr.common.configure.rst:2 msgid "configure" -msgstr "" +msgstr "구성" #: ../../source/ref-api/flwr.common.event.rst:2 msgid "event" -msgstr "" +msgstr "이벤트" #: ../../source/ref-api/flwr.common.log.rst:2 msgid "log" -msgstr "" +msgstr "로그" #: logging.Logger.log:3 of msgid "" -"To pass exception information, use the keyword argument exc_info with a " -"true value, e.g." -msgstr "" +"To pass exception information, use the keyword argument exc_info with a true " +"value, e.g." +msgstr "예외 정보를 전달하려면 키워드 argument exc_info를 참 값과 함께 사용합니다." #: logging.Logger.log:6 of #, python-format @@ -10993,8 +11809,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.rst:38::1 msgid "" -":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\," -" round\\_timeout\\]\\)" +":py:obj:`ServerConfig `\\ \\(\\[num\\_rounds\\, " +"round\\_timeout\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.rst:38::1 @@ -11003,7 +11819,8 @@ msgid "Flower server config." msgstr "" #: ../../source/ref-api/flwr.server.rst:38::1 -msgid ":py:obj:`SimpleClientManager `\\ \\(\\)" +msgid "" +":py:obj:`SimpleClientManager `\\ \\(\\)" msgstr "" #: ../../source/ref-api/flwr.server.rst:38::1 @@ -11045,7 +11862,8 @@ msgid "Return all available clients." msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of -msgid ":py:obj:`num_available `\\ \\(\\)" +msgid "" +":py:obj:`num_available `\\ \\(\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -11068,8 +11886,8 @@ msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of msgid "" -":py:obj:`sample `\\ " -"\\(num\\_clients\\[\\, min\\_num\\_clients\\, criterion\\]\\)" +":py:obj:`sample `\\ \\(num\\_clients\\[\\, " +"min\\_num\\_clients\\, criterion\\]\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -11080,7 +11898,8 @@ msgid "Sample a number of Flower ClientProxy instances." msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 of -msgid ":py:obj:`unregister `\\ \\(client\\)" +msgid "" +":py:obj:`unregister `\\ \\(client\\)" msgstr "" #: flwr.server.client_manager.ClientManager.all:1::1 @@ -11112,8 +11931,7 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.register:6 of msgid "" "**success** -- Indicating if registration was successful. False if " -"ClientProxy is already registered or can not be registered for any " -"reason." +"ClientProxy is already registered or can not be registered for any reason." msgstr "" #: flwr.server.client_manager.ClientManager.unregister:3 @@ -11127,8 +11945,8 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 of msgid "" -":py:obj:`create_message `\\ " -"\\(content\\, message\\_type\\, ...\\[\\, ttl\\]\\)" +":py:obj:`create_message `\\ \\(content\\, " +"message\\_type\\, ...\\[\\, ttl\\]\\)" msgstr "" #: flwr.server.driver.driver.Driver.create_message:1 @@ -11158,8 +11976,7 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 of msgid "" -":py:obj:`push_messages `\\ " -"\\(messages\\)" +":py:obj:`push_messages `\\ \\(messages\\)" msgstr "" #: flwr.server.driver.driver.Driver.create_message:1::1 @@ -11180,20 +11997,20 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:3 of msgid "" -"This method constructs a new `Message` with given content and metadata. " -"The `run_id` and `src_node_id` will be set automatically." +"This method constructs a new `Message` with given content and metadata. The " +"`run_id` and `src_node_id` will be set automatically." msgstr "" #: flwr.server.driver.driver.Driver.create_message:6 of msgid "" -"The content for the new message. This holds records that are to be sent " -"to the destination node." +"The content for the new message. This holds records that are to be sent to " +"the destination node." msgstr "" #: flwr.server.driver.driver.Driver.create_message:9 of msgid "" -"The type of the message, defining the action to be executed on the " -"receiving end." +"The type of the message, defining the action to be executed on the receiving " +"end." msgstr "" #: flwr.server.driver.driver.Driver.create_message:12 of @@ -11202,17 +12019,16 @@ msgstr "" #: flwr.server.driver.driver.Driver.create_message:14 of msgid "" -"The ID of the group to which this message is associated. In some " -"settings, this is used as the FL round." +"The ID of the group to which this message is associated. In some settings, " +"this is used as the FL round." msgstr "" #: flwr.server.driver.driver.Driver.create_message:17 of msgid "" -"Time-to-live for the round trip of this message, i.e., the time from " -"sending this message to receiving a reply. It specifies in seconds the " -"duration for which the message and its potential reply are considered " -"valid. If unset, the default TTL (i.e., `common.DEFAULT_TTL`) will be " -"used." +"Time-to-live for the round trip of this message, i.e., the time from sending " +"this message to receiving a reply. It specifies in seconds the duration for " +"which the message and its potential reply are considered valid. If unset, " +"the default TTL (i.e., `common.DEFAULT_TTL`) will be used." msgstr "" #: flwr.server.driver.driver.Driver.create_message:23 of @@ -11223,12 +12039,13 @@ msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:3 of msgid "" -"This method is used to collect messages from the SuperLink that " -"correspond to a set of given message IDs." +"This method is used to collect messages from the SuperLink that correspond " +"to a set of given message IDs." msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:6 of -msgid "An iterable of message IDs for which reply messages are to be retrieved." +msgid "" +"An iterable of message IDs for which reply messages are to be retrieved." msgstr "" #: flwr.server.driver.driver.Driver.pull_messages:9 of @@ -11237,8 +12054,8 @@ msgstr "" #: flwr.server.driver.driver.Driver.push_messages:3 of msgid "" -"This method takes an iterable of messages and sends each message to the " -"node specified in `dst_node_id`." +"This method takes an iterable of messages and sends each message to the node " +"specified in `dst_node_id`." msgstr "" #: flwr.server.driver.driver.Driver.push_messages:6 @@ -11248,34 +12065,35 @@ msgstr "" #: flwr.server.driver.driver.Driver.push_messages:9 of msgid "" -"**message_ids** -- An iterable of IDs for the messages that were sent, " -"which can be used to pull replies." +"**message_ids** -- An iterable of IDs for the messages that were sent, which " +"can be used to pull replies." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:3 of msgid "" -"This method sends a list of messages to their destination node IDs and " -"then waits for the replies. It continues to pull replies until either all" -" replies are received or the specified timeout duration is exceeded." +"This method sends a list of messages to their destination node IDs and then " +"waits for the replies. It continues to pull replies until either all replies " +"are received or the specified timeout duration is exceeded." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:9 of msgid "" "The timeout duration in seconds. If specified, the method will wait for " -"replies for this duration. If `None`, there is no time limit and the " -"method will wait until replies for all messages are received." +"replies for this duration. If `None`, there is no time limit and the method " +"will wait until replies for all messages are received." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:14 of -msgid "**replies** -- An iterable of reply messages received from the SuperLink." +msgid "" +"**replies** -- An iterable of reply messages received from the SuperLink." msgstr "" #: flwr.server.driver.driver.Driver.send_and_receive:19 of msgid "" -"This method uses `push_messages` to send the messages and `pull_messages`" -" to collect the replies. If `timeout` is set, the method may not return " -"replies for all sent messages. A message remains valid until its TTL, " -"which is not affected by `timeout`." +"This method uses `push_messages` to send the messages and `pull_messages` to " +"collect the replies. If `timeout` is set, the method may not return replies " +"for all sent messages. A message remains valid until its TTL, which is not " +"affected by `timeout`." msgstr "" #: ../../source/ref-api/flwr.server.History.rst:2 @@ -11284,9 +12102,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_loss_centralized " -"`\\ \\(server\\_round\\, " -"loss\\)" +":py:obj:`add_loss_centralized `\\ " +"\\(server\\_round\\, loss\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1 @@ -11296,9 +12113,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_loss_distributed " -"`\\ \\(server\\_round\\, " -"loss\\)" +":py:obj:`add_loss_distributed `\\ " +"\\(server\\_round\\, loss\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -11308,9 +12124,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_centralized " -"`\\ \\(server\\_round\\, " -"metrics\\)" +":py:obj:`add_metrics_centralized `\\ \\(server\\_round\\, metrics\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -11320,9 +12135,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_distributed " -"`\\ \\(server\\_round\\, " -"metrics\\)" +":py:obj:`add_metrics_distributed `\\ \\(server\\_round\\, metrics\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -11332,9 +12146,8 @@ msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 of msgid "" -":py:obj:`add_metrics_distributed_fit " -"`\\ \\(server\\_round\\," -" ...\\)" +":py:obj:`add_metrics_distributed_fit `\\ \\(server\\_round\\, ...\\)" msgstr "" #: flwr.server.history.History.add_loss_centralized:1::1 @@ -11385,8 +12198,8 @@ msgstr "" #: flwr.server.server.Server.client_manager:1::1 of msgid "" -":py:obj:`disconnect_all_clients " -"`\\ \\(timeout\\)" +":py:obj:`disconnect_all_clients `\\ \\(timeout\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -11416,8 +12229,8 @@ msgstr "" #: flwr.server.server.Server.client_manager:1::1 of msgid "" -":py:obj:`fit_round `\\ \\(server\\_round\\," -" timeout\\)" +":py:obj:`fit_round `\\ \\(server\\_round\\, " +"timeout\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -11437,7 +12250,8 @@ msgid "Set the max_workers used by ThreadPoolExecutor." msgstr "" #: flwr.server.server.Server.client_manager:1::1 of -msgid ":py:obj:`set_strategy `\\ \\(strategy\\)" +msgid "" +":py:obj:`set_strategy `\\ \\(strategy\\)" msgstr "" #: flwr.server.server.Server.client_manager:1::1 @@ -11472,8 +12286,8 @@ msgstr "" #: flwr.server.server_config.ServerConfig:3 of msgid "" -"All attributes have default values which allows users to configure just " -"the ones they care about." +"All attributes have default values which allows users to configure just the " +"ones they care about." msgstr "" #: ../../source/ref-api/flwr.server.ServerConfig.rst:29::1 @@ -11498,14 +12312,13 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of msgid "" -":py:obj:`num_available `\\" -" \\(\\)" +":py:obj:`num_available `\\ " +"\\(\\)" msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of msgid "" -":py:obj:`register `\\ " -"\\(client\\)" +":py:obj:`register `\\ \\(client\\)" msgstr "" #: flwr.server.client_manager.SimpleClientManager.all:1::1 of @@ -11528,8 +12341,8 @@ msgstr "" #: flwr.server.client_manager.SimpleClientManager.wait_for:3 of msgid "" -"Blocks until the requested number of clients is available or until a " -"timeout is reached. Current timeout default: 1 day." +"Blocks until the requested number of clients is available or until a timeout " +"is reached. Current timeout default: 1 day." msgstr "" #: flwr.server.client_manager.SimpleClientManager.wait_for:6 of @@ -11570,8 +12383,8 @@ msgstr "" #: flwr.server.app.start_server:5 of msgid "" -"A server implementation, either `flwr.server.Server` or a subclass " -"thereof. If no instance is provided, then `start_server` will create one." +"A server implementation, either `flwr.server.Server` or a subclass thereof. " +"If no instance is provided, then `start_server` will create one." msgstr "" #: flwr.server.app.start_server:9 flwr.simulation.app.start_simulation:28 of @@ -11582,41 +12395,41 @@ msgstr "" #: flwr.server.app.start_server:12 of msgid "" -"An implementation of the abstract base class " -"`flwr.server.strategy.Strategy`. If no strategy is provided, then " -"`start_server` will use `flwr.server.strategy.FedAvg`." +"An implementation of the abstract base class `flwr.server.strategy." +"Strategy`. If no strategy is provided, then `start_server` will use `flwr." +"server.strategy.FedAvg`." msgstr "" #: flwr.server.app.start_server:16 of msgid "" -"An implementation of the abstract base class `flwr.server.ClientManager`." -" If no implementation is provided, then `start_server` will use " -"`flwr.server.client_manager.SimpleClientManager`." +"An implementation of the abstract base class `flwr.server.ClientManager`. If " +"no implementation is provided, then `start_server` will use `flwr.server." +"client_manager.SimpleClientManager`." msgstr "" #: flwr.server.app.start_server:21 of msgid "" -"The maximum length of gRPC messages that can be exchanged with the Flower" -" clients. The default should be sufficient for most models. Users who " -"train very large models might need to increase this value. Note that the " -"Flower clients need to be started with the same value (see " -"`flwr.client.start_client`), otherwise clients will not know about the " -"increased limit and block larger messages." +"The maximum length of gRPC messages that can be exchanged with the Flower " +"clients. The default should be sufficient for most models. Users who train " +"very large models might need to increase this value. Note that the Flower " +"clients need to be started with the same value (see `flwr.client." +"start_client`), otherwise clients will not know about the increased limit " +"and block larger messages." msgstr "" #: flwr.server.app.start_server:28 of msgid "" -"Tuple containing root certificate, server certificate, and private key to" -" start a secure SSL-enabled server. The tuple is expected to have three " -"bytes elements in the following order: * CA certificate. * " -"server certificate. * server private key." +"Tuple containing root certificate, server certificate, and private key to " +"start a secure SSL-enabled server. The tuple is expected to have three bytes " +"elements in the following order: * CA certificate. * server " +"certificate. * server private key." msgstr "" #: flwr.server.app.start_server:28 of msgid "" -"Tuple containing root certificate, server certificate, and private key to" -" start a secure SSL-enabled server. The tuple is expected to have three " -"bytes elements in the following order:" +"Tuple containing root certificate, server certificate, and private key to " +"start a secure SSL-enabled server. The tuple is expected to have three bytes " +"elements in the following order:" msgstr "" #: flwr.server.app.start_server:32 of @@ -11649,8 +12462,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`Bulyan `\\ \\(\\*\\, " -"fraction\\_fit\\, fraction\\_evaluate\\, ...\\)" +":py:obj:`Bulyan `\\ \\(\\*\\, fraction\\_fit\\, " +"fraction\\_evaluate\\, ...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11682,9 +12495,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyClientSideAdaptiveClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyClientSideAdaptiveClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11695,9 +12507,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyServerSideAdaptiveClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyServerSideAdaptiveClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11708,9 +12519,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyClientSideFixedClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyClientSideFixedClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11721,9 +12531,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`DifferentialPrivacyServerSideFixedClipping " -"`\\ " -"\\(...\\)" +":py:obj:`DifferentialPrivacyServerSideFixedClipping `\\ \\(...\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11768,8 +12577,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FedAvgAndroid `\\ " -"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" +":py:obj:`FedAvgAndroid `\\ \\(\\*\\[\\, " +"fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11818,8 +12627,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FedTrimmedAvg `\\ " -"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" +":py:obj:`FedTrimmedAvg `\\ \\(\\*\\[\\, " +"fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11873,9 +12682,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`FaultTolerantFedAvg " -"`\\ \\(\\*\\[\\, " -"fraction\\_fit\\, ...\\]\\)" +":py:obj:`FaultTolerantFedAvg `\\ " +"\\(\\*\\[\\, fraction\\_fit\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11885,8 +12693,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`Krum `\\ \\(\\*\\[\\, " -"fraction\\_fit\\, fraction\\_evaluate\\, ...\\]\\)" +":py:obj:`Krum `\\ \\(\\*\\[\\, fraction\\_fit\\, " +"fraction\\_evaluate\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -11896,8 +12704,8 @@ msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 msgid "" -":py:obj:`QFedAvg `\\ \\(\\*\\[\\, " -"q\\_param\\, qffl\\_learning\\_rate\\, ...\\]\\)" +":py:obj:`QFedAvg `\\ \\(\\*\\[\\, q\\_param\\, " +"qffl\\_learning\\_rate\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.rst:45::1 @@ -12065,8 +12873,8 @@ msgstr "" #: flwr.server.strategy.bulyan.Bulyan:27 of msgid "" -"Byzantine resilient aggregation rule that is used as the first step of " -"the Bulyan (e.g., Krum)" +"Byzantine resilient aggregation rule that is used as the first step of the " +"Bulyan (e.g., Krum)" msgstr "" #: flwr.server.strategy.bulyan.Bulyan:29 of @@ -12075,9 +12883,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1 @@ -12104,9 +12911,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -12185,9 +12991,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -12204,9 +13009,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -12223,8 +13027,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\" -" \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -12255,9 +13059,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1 @@ -12277,9 +13080,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dpfedavg_adaptive.DPFedAvgAdaptive.aggregate_fit:1 @@ -12291,9 +13093,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -12304,9 +13105,8 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -12326,15 +13126,15 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.evaluate:1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.evaluate:1 of -msgid "Evaluate model parameters using an evaluation function from the strategy." +msgid "" +"Evaluate model parameters using an evaluation function from the strategy." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -12373,9 +13173,9 @@ msgstr "" msgid "" "**evaluate_configuration** -- A list of tuples. Each tuple in the list " "identifies a `ClientProxy` and the `EvaluateIns` for this particular " -"`ClientProxy`. If a particular `ClientProxy` is not included in this " -"list, it means that this `ClientProxy` will not participate in the next " -"round of federated evaluation." +"`ClientProxy`. If a particular `ClientProxy` is not included in this list, " +"it means that this `ClientProxy` will not participate in the next round of " +"federated evaluation." msgstr "" #: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst:2 @@ -12395,16 +13195,14 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -12416,24 +13214,21 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:1 of msgid "" -"Configure the next round of training incorporating Differential Privacy " -"(DP)." +"Configure the next round of training incorporating Differential Privacy (DP)." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 @@ -12446,25 +13241,23 @@ msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:3 of msgid "" -"Configuration of the next training round includes information related to " -"DP, such as clip norm and noise stddev." +"Configuration of the next training round includes information related to DP, " +"such as clip norm and noise stddev." msgstr "" #: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit:13 #: flwr.server.strategy.strategy.Strategy.configure_fit:10 of msgid "" -"**fit_configuration** -- A list of tuples. Each tuple in the list " -"identifies a `ClientProxy` and the `FitIns` for this particular " -"`ClientProxy`. If a particular `ClientProxy` is not included in this " -"list, it means that this `ClientProxy` will not participate in the next " -"round of federated learning." +"**fit_configuration** -- A list of tuples. Each tuple in the list identifies " +"a `ClientProxy` and the `FitIns` for this particular `ClientProxy`. If a " +"particular `ClientProxy` is not included in this list, it means that this " +"`ClientProxy` will not participate in the next round of federated learning." msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideAdaptiveClipping.rst:2 @@ -12481,9 +13274,8 @@ msgstr "" msgid "" "In comparison to `DifferentialPrivacyServerSideAdaptiveClipping`, which " "performs clipping on the server-side, " -"`DifferentialPrivacyClientSideAdaptiveClipping` expects clipping to " -"happen on the client-side, usually by using the built-in " -"`adaptiveclipping_mod`." +"`DifferentialPrivacyClientSideAdaptiveClipping` expects clipping to happen " +"on the client-side, usually by using the built-in `adaptiveclipping_mod`." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:10 @@ -12519,22 +13311,23 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:19 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:12 #: of -msgid "The desired quantile of updates which should be clipped. Defaults to 0.5." +msgid "" +"The desired quantile of updates which should be clipped. Defaults to 0.5." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:21 #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:14 #: of msgid "" -"The learning rate for the clipping norm adaptation. Defaults to 0.2. " -"Andrew et al. recommends to set to 0.2." +"The learning rate for the clipping norm adaptation. Defaults to 0.2. Andrew " +"et al. recommends to set to 0.2." msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:24 #: of msgid "" -"The stddev of the noise added to the count of updates currently below the" -" estimate. Andrew et al. recommends to set to `expected_num_records/20`" +"The stddev of the noise added to the count of updates currently below the " +"estimate. Andrew et al. recommends to set to `expected_num_records/20`" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:30 @@ -12548,8 +13341,8 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:34 #: of msgid "" -"Wrap the strategy with the " -"`DifferentialPrivacyClientSideAdaptiveClipping` wrapper:" +"Wrap the strategy with the `DifferentialPrivacyClientSideAdaptiveClipping` " +"wrapper:" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping:40 @@ -12560,17 +13353,17 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 @@ -12584,33 +13377,33 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ " +"\\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideFixedClipping.rst:2 @@ -12627,16 +13420,16 @@ msgstr "" msgid "" "In comparison to `DifferentialPrivacyServerSideFixedClipping`, which " "performs clipping on the server-side, " -"`DifferentialPrivacyClientSideFixedClipping` expects clipping to happen " -"on the client-side, usually by using the built-in `fixedclipping_mod`." +"`DifferentialPrivacyClientSideFixedClipping` expects clipping to happen on " +"the client-side, usually by using the built-in `fixedclipping_mod`." msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:12 #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:5 #: of msgid "" -"The noise multiplier for the Gaussian mechanism for model updates. A " -"value of 1.0 or higher is recommended for strong privacy." +"The noise multiplier for the Gaussian mechanism for model updates. A value " +"of 1.0 or higher is recommended for strong privacy." msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping:26 @@ -12654,17 +13447,17 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 @@ -12676,33 +13469,33 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ \\(server\\_round\\, " +"parameters\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideAdaptiveClipping.rst:2 @@ -12712,9 +13505,8 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:17 #: of msgid "" -"The standard deviation of the noise added to the count of updates below " -"the estimate. Andrew et al. recommends to set to " -"`expected_num_records/20`" +"The standard deviation of the noise added to the count of updates below the " +"estimate. Andrew et al. recommends to set to `expected_num_records/20`" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping:27 @@ -12727,49 +13519,49 @@ msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ " +"\\(server\\_round\\, parameters\\)" msgstr "" #: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideFixedClipping.rst:2 @@ -12779,24 +13571,23 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping:19 #: of msgid "" -"Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping " -"wrapper" +"Wrap the strategy with the DifferentialPrivacyServerSideFixedClipping wrapper" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\" -" \\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ " +"\\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 @@ -12808,33 +13599,33 @@ msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`evaluate " -"`\\" -" \\(server\\_round\\, parameters\\)" +":py:obj:`evaluate `\\ \\(server\\_round\\, " +"parameters\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\" -" \\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ " +"\\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping.aggregate_fit:3 @@ -12849,17 +13640,15 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " -"\\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ \\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -12881,17 +13670,15 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 @@ -12904,25 +13691,22 @@ msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fault_tolerant_fedavg.FaultTolerantFedAvg.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst:2 @@ -12975,28 +13759,26 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit `\\" -" \\(server\\_round\\, results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit `\\" -" \\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13007,23 +13789,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAdam.rst:2 @@ -13042,9 +13821,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13055,9 +13833,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13074,22 +13851,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -13106,17 +13880,16 @@ msgstr "" #: of msgid "" "Fraction of clients used during training. In case `min_fit_clients` is " -"larger than `fraction_fit * available_clients`, `min_fit_clients` will " -"still be sampled. Defaults to 1.0." +"larger than `fraction_fit * available_clients`, `min_fit_clients` will still " +"be sampled. Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavg.FedAvg:9 flwr.server.strategy.fedprox.FedProx:41 #: of msgid "" -"Fraction of clients used during validation. In case " -"`min_evaluate_clients` is larger than `fraction_evaluate * " -"available_clients`, `min_evaluate_clients` will still be sampled. " -"Defaults to 1.0." +"Fraction of clients used during validation. In case `min_evaluate_clients` " +"is larger than `fraction_evaluate * available_clients`, " +"`min_evaluate_clients` will still be sampled. Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavg.FedAvg:33 of @@ -13125,9 +13898,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13138,9 +13910,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13157,22 +13928,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\" -" \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedAvgAndroid.rst:2 @@ -13182,24 +13951,22 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`bytes_to_ndarray " -"`\\ \\(tensor\\)" +":py:obj:`bytes_to_ndarray `\\ \\(tensor\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -13210,16 +13977,14 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -13233,16 +13998,15 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`ndarray_to_bytes " -"`\\ \\(ndarray\\)" +":py:obj:`ndarray_to_bytes `\\ \\(ndarray\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -13253,33 +14017,29 @@ msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`ndarrays_to_parameters " -"`\\ " -"\\(ndarrays\\)" +":py:obj:`ndarrays_to_parameters `\\ \\(ndarrays\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`parameters_to_ndarrays " -"`\\ " -"\\(parameters\\)" +":py:obj:`parameters_to_ndarrays `\\ \\(parameters\\)" msgstr "" #: flwr.server.strategy.fedavg_android.FedAvgAndroid.aggregate_evaluate:1::1 @@ -13298,8 +14058,7 @@ msgstr "" #: flwr.server.strategy.fedavgm.FedAvgM:25 of msgid "" -"Server-side learning rate used in server-side optimization. Defaults to " -"1.0." +"Server-side learning rate used in server-side optimization. Defaults to 1.0." msgstr "" #: flwr.server.strategy.fedavgm.FedAvgM:28 of @@ -13308,9 +14067,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13321,9 +14079,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13340,22 +14097,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -13365,9 +14119,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13383,9 +14136,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13402,22 +14154,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -13435,9 +14184,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13448,9 +14196,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13467,22 +14214,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients `\\" -" \\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ " +"\\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedProx.rst:2 @@ -13495,9 +14240,9 @@ msgstr "" #: flwr.server.strategy.fedprox.FedProx:5 of msgid "" -"The strategy in itself will not be different than FedAvg, the client " -"needs to be adjusted. A proximal term needs to be added to the loss " -"function during the training:" +"The strategy in itself will not be different than FedAvg, the client needs " +"to be adjusted. A proximal term needs to be added to the loss function " +"during the training:" msgstr "" #: flwr.server.strategy.fedprox.FedProx:9 of @@ -13530,15 +14275,14 @@ msgstr "" msgid "" "The weight of the proximal term used in the optimization. 0.0 makes this " "strategy equivalent to FedAvg, and the higher the coefficient, the more " -"regularization will be used (that is, the client parameters will need to " -"be closer to the server parameters during training)." +"regularization will be used (that is, the client parameters will need to be " +"closer to the server parameters during training)." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13549,9 +14293,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13568,22 +14311,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -13605,15 +14345,13 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -13624,15 +14362,13 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -13644,23 +14380,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbBagging.rst:2 @@ -13670,9 +14403,8 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1 @@ -13686,8 +14418,7 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ " +":py:obj:`aggregate_fit `\\ " "\\(server\\_round\\, results\\, failures\\)" msgstr "" @@ -13701,16 +14432,14 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ " +":py:obj:`configure_fit `\\ " "\\(server\\_round\\, parameters\\, ...\\)" msgstr "" @@ -13724,25 +14453,22 @@ msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedxgb_bagging.FedXgbBagging.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbCyclic.rst:2 @@ -13752,33 +14478,29 @@ msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_fit " -"`\\ \\(server\\_round\\," -" results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_fit " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 @@ -13791,25 +14513,22 @@ msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedxgb_cyclic.FedXgbCyclic.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedXgbNnAvg.rst:2 @@ -13819,36 +14538,31 @@ msgstr "" #: flwr.server.strategy.fedxgb_nn_avg.FedXgbNnAvg:5 of msgid "" "This strategy is deprecated, but a copy of it is available in Flower " -"Baselines: " -"https://github.com/adap/flower/tree/main/baselines/hfedxgboost." +"Baselines: https://github.com/adap/flower/tree/main/baselines/hfedxgboost." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_fit " -"`\\ \\(server\\_round\\, " -"results\\, failures\\)" +":py:obj:`aggregate_fit `\\ " +"\\(server\\_round\\, results\\, failures\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_fit " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_fit `\\ " +"\\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13859,23 +14573,20 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_fit_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: ../../source/ref-api/flwr.server.strategy.FedYogi.rst:2 @@ -13896,9 +14607,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13909,9 +14619,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13928,22 +14637,19 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -13957,15 +14663,14 @@ msgstr "" #: flwr.server.strategy.krum.Krum:17 of msgid "" -"Number of clients to keep before averaging (MultiKrum). Defaults to 0, in" -" that case classical Krum is applied." +"Number of clients to keep before averaging (MultiKrum). Defaults to 0, in " +"that case classical Krum is applied." msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\, " -"results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -13981,9 +14686,8 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\, " -"parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -14000,16 +14704,14 @@ msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.fedavg.FedAvg.aggregate_evaluate:1::1 of @@ -14024,9 +14726,8 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ \\(server\\_round\\," -" results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of @@ -14037,9 +14738,8 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`configure_evaluate " -"`\\ \\(server\\_round\\," -" parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of @@ -14056,22 +14756,19 @@ msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_evaluation_clients " -"`\\ " -"\\(num\\_available\\_clients\\)" +":py:obj:`num_evaluation_clients `\\ \\(num\\_available\\_clients\\)" msgstr "" #: flwr.server.strategy.qfedavg.QFedAvg.aggregate_evaluate:1::1 of msgid "" -":py:obj:`num_fit_clients " -"`\\ " +":py:obj:`num_fit_clients `\\ " "\\(num\\_available\\_clients\\)" msgstr "" @@ -14082,9 +14779,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`aggregate_evaluate " -"`\\ " -"\\(server\\_round\\, results\\, ...\\)" +":py:obj:`aggregate_evaluate `\\ \\(server\\_round\\, results\\, ...\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1 @@ -14108,9 +14804,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`configure_evaluate " -"`\\ " -"\\(server\\_round\\, parameters\\, ...\\)" +":py:obj:`configure_evaluate `\\ \\(server\\_round\\, parameters\\, ...\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 @@ -14135,9 +14830,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 #: of msgid "" -":py:obj:`initialize_parameters " -"`\\ " -"\\(client\\_manager\\)" +":py:obj:`initialize_parameters `\\ \\(client\\_manager\\)" msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:1::1 @@ -14147,17 +14841,18 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:5 of msgid "" -"Successful updates from the previously selected and configured clients. " -"Each pair of `(ClientProxy, FitRes` constitutes a successful update from " -"one of the previously selected clients. Not that not all previously " -"selected clients are necessarily included in this list: a client might " -"drop out and not submit a result. For each client that did not submit an " -"update, there should be an `Exception` in `failures`." +"Successful updates from the previously selected and configured clients. Each " +"pair of `(ClientProxy, FitRes` constitutes a successful update from one of " +"the previously selected clients. Not that not all previously selected " +"clients are necessarily included in this list: a client might drop out and " +"not submit a result. For each client that did not submit an update, there " +"should be an `Exception` in `failures`." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:13 #: flwr.server.strategy.strategy.Strategy.aggregate_fit:13 of -msgid "Exceptions that occurred while the server was waiting for client updates." +msgid "" +"Exceptions that occurred while the server was waiting for client updates." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_evaluate:16 of @@ -14168,23 +14863,22 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_fit:5 of msgid "" -"Successful updates from the previously selected and configured clients. " -"Each pair of `(ClientProxy, FitRes)` constitutes a successful update from" -" one of the previously selected clients. Not that not all previously " -"selected clients are necessarily included in this list: a client might " -"drop out and not submit a result. For each client that did not submit an " -"update, there should be an `Exception` in `failures`." +"Successful updates from the previously selected and configured clients. Each " +"pair of `(ClientProxy, FitRes)` constitutes a successful update from one of " +"the previously selected clients. Not that not all previously selected " +"clients are necessarily included in this list: a client might drop out and " +"not submit a result. For each client that did not submit an update, there " +"should be an `Exception` in `failures`." msgstr "" #: flwr.server.strategy.strategy.Strategy.aggregate_fit:17 of msgid "" "**parameters** -- If parameters are returned, then the server will treat " -"these as the new global model parameters (i.e., it will replace the " -"previous parameters with the ones returned from this method). If `None` " -"is returned (e.g., because there were only failures and no viable " -"results) then the server will no update the previous model parameters, " -"the updates received in this round are discarded, and the global model " -"parameters remain the same." +"these as the new global model parameters (i.e., it will replace the previous " +"parameters with the ones returned from this method). If `None` is returned " +"(e.g., because there were only failures and no viable results) then the " +"server will no update the previous model parameters, the updates received in " +"this round are discarded, and the global model parameters remain the same." msgstr "" #: flwr.server.strategy.strategy.Strategy.evaluate:3 of @@ -14195,9 +14889,8 @@ msgstr "" #: flwr.server.strategy.strategy.Strategy.evaluate:11 of msgid "" -"**evaluation_result** -- The evaluation result, usually a Tuple " -"containing loss and a dictionary containing task-specific metrics (e.g., " -"accuracy)." +"**evaluation_result** -- The evaluation result, usually a Tuple containing " +"loss and a dictionary containing task-specific metrics (e.g., accuracy)." msgstr "" #: flwr.server.strategy.strategy.Strategy.initialize_parameters:6 of @@ -14255,17 +14948,17 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:3 #: of msgid "" -"The SecAgg+ protocol ensures the secure summation of integer vectors " -"owned by multiple parties, without accessing any individual integer " -"vector. This workflow allows the server to compute the weighted average " -"of model parameters across all clients, ensuring individual contributions" -" remain private. This is achieved by clients sending both, a weighting " -"factor and a weighted version of the locally updated parameters, both of " -"which are masked for privacy. Specifically, each client uploads \"[w, w *" -" params]\" with masks, where weighting factor 'w' is the number of " -"examples ('num_examples') and 'params' represents the model parameters " -"('parameters') from the client's `FitRes`. The server then aggregates " -"these contributions to compute the weighted average of model parameters." +"The SecAgg+ protocol ensures the secure summation of integer vectors owned " +"by multiple parties, without accessing any individual integer vector. This " +"workflow allows the server to compute the weighted average of model " +"parameters across all clients, ensuring individual contributions remain " +"private. This is achieved by clients sending both, a weighting factor and a " +"weighted version of the locally updated parameters, both of which are masked " +"for privacy. Specifically, each client uploads \"[w, w * params]\" with " +"masks, where weighting factor 'w' is the number of examples ('num_examples') " +"and 'params' represents the model parameters ('parameters') from the " +"client's `FitRes`. The server then aggregates these contributions to compute " +"the weighted average of model parameters." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:14 @@ -14302,39 +14995,39 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:22 #: of msgid "" -"Only the aggregated model parameters are exposed and passed to " -"`Strategy.aggregate_fit`, ensuring individual data privacy." +"Only the aggregated model parameters are exposed and passed to `Strategy." +"aggregate_fit`, ensuring individual data privacy." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:25 #: of msgid "" -"The number of shares into which each client's private key is split under " -"the SecAgg+ protocol. If specified as a float, it represents the " -"proportion of all selected clients, and the number of shares will be set " -"dynamically in the run time. A private key can be reconstructed from " -"these shares, allowing for the secure aggregation of model updates. Each " -"client sends one share to each of its neighbors while retaining one." +"The number of shares into which each client's private key is split under the " +"SecAgg+ protocol. If specified as a float, it represents the proportion of " +"all selected clients, and the number of shares will be set dynamically in " +"the run time. A private key can be reconstructed from these shares, allowing " +"for the secure aggregation of model updates. Each client sends one share to " +"each of its neighbors while retaining one." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:25 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:32 #: of msgid "" -"The minimum number of shares required to reconstruct a client's private " -"key, or, if specified as a float, it represents the proportion of the " -"total number of shares needed for reconstruction. This threshold ensures " -"privacy by allowing for the recovery of contributions from dropped " -"clients during aggregation, without compromising individual client data." +"The minimum number of shares required to reconstruct a client's private key, " +"or, if specified as a float, it represents the proportion of the total " +"number of shares needed for reconstruction. This threshold ensures privacy " +"by allowing for the recovery of contributions from dropped clients during " +"aggregation, without compromising individual client data." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:31 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:38 #: of msgid "" -"The maximum value of the weight that can be assigned to any single " -"client's update during the weighted average calculation on the server " -"side, e.g., in the FedAvg algorithm." +"The maximum value of the weight that can be assigned to any single client's " +"update during the weighted average calculation on the server side, e.g., in " +"the FedAvg algorithm." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:35 @@ -14342,8 +15035,8 @@ msgstr "" #: of msgid "" "The range within which model parameters are clipped before quantization. " -"This parameter ensures each model parameter is bounded within " -"[-clipping_range, clipping_range], facilitating quantization." +"This parameter ensures each model parameter is bounded within [-" +"clipping_range, clipping_range], facilitating quantization." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:39 @@ -14361,31 +15054,32 @@ msgstr "" #: of msgid "" "The range of values from which random mask entries are uniformly sampled " -"([0, modulus_range-1]). `modulus_range` must be less than 4294967296. " -"Please use 2**n values for `modulus_range` to prevent overflow issues." +"([0, modulus_range-1]). `modulus_range` must be less than 4294967296. Please " +"use 2**n values for `modulus_range` to prevent overflow issues." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:47 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:54 #: of msgid "" -"The timeout duration in seconds. If specified, the workflow will wait for" -" replies for this duration each time. If `None`, there is no time limit " -"and the workflow will wait until replies for all messages are received." +"The timeout duration in seconds. If specified, the workflow will wait for " +"replies for this duration each time. If `None`, there is no time limit and " +"the workflow will wait until replies for all messages are received." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:61 #: of msgid "" "Generally, higher `num_shares` means more robust to dropouts while " -"increasing the computational costs; higher `reconstruction_threshold` " -"means better privacy guarantees but less tolerance to dropouts." +"increasing the computational costs; higher `reconstruction_threshold` means " +"better privacy guarantees but less tolerance to dropouts." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:58 #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:64 #: of -msgid "Too large `max_weight` may compromise the precision of the quantization." +msgid "" +"Too large `max_weight` may compromise the precision of the quantization." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:59 @@ -14398,35 +15092,34 @@ msgstr "" #: of msgid "" "When `num_shares` is a float, it is interpreted as the proportion of all " -"selected clients, and hence the number of shares will be determined in " -"the runtime. This allows for dynamic adjustment based on the total number" -" of participating clients." +"selected clients, and hence the number of shares will be determined in the " +"runtime. This allows for dynamic adjustment based on the total number of " +"participating clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:69 #: of msgid "" -"Similarly, when `reconstruction_threshold` is a float, it is interpreted " -"as the proportion of the number of shares needed for the reconstruction " -"of a private key. This feature enables flexibility in setting the " -"security threshold relative to the number of distributed shares." +"Similarly, when `reconstruction_threshold` is a float, it is interpreted as " +"the proportion of the number of shares needed for the reconstruction of a " +"private key. This feature enables flexibility in setting the security " +"threshold relative to the number of distributed shares." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow:73 #: of msgid "" -"`num_shares`, `reconstruction_threshold`, and the quantization parameters" -" (`clipping_range`, `quantization_range`, `modulus_range`) play critical " -"roles in balancing privacy, robustness, and efficiency within the SecAgg+" -" protocol." +"`num_shares`, `reconstruction_threshold`, and the quantization parameters " +"(`clipping_range`, `quantization_range`, `modulus_range`) play critical " +"roles in balancing privacy, robustness, and efficiency within the SecAgg+ " +"protocol." msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`collect_masked_vectors_stage " -"`\\" -" \\(driver\\, ...\\)" +":py:obj:`collect_masked_vectors_stage `\\ \\(driver\\, ...\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1 @@ -14438,9 +15131,8 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`setup_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`setup_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -14452,9 +15144,8 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`share_keys_stage " -"`\\ " -"\\(driver\\, context\\, state\\)" +":py:obj:`share_keys_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -14466,9 +15157,8 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`unmask_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`unmask_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 @@ -14483,51 +15173,50 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:1 of msgid "" -"Bases: " -":py:class:`~flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow`" +"Bases: :py:class:`~flwr.server.workflow.secure_aggregation." +"secaggplus_workflow.SecAggPlusWorkflow`" msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:3 of msgid "" -"The SecAgg protocol ensures the secure summation of integer vectors owned" -" by multiple parties, without accessing any individual integer vector. " -"This workflow allows the server to compute the weighted average of model " +"The SecAgg protocol ensures the secure summation of integer vectors owned by " +"multiple parties, without accessing any individual integer vector. This " +"workflow allows the server to compute the weighted average of model " "parameters across all clients, ensuring individual contributions remain " -"private. This is achieved by clients sending both, a weighting factor and" -" a weighted version of the locally updated parameters, both of which are " -"masked for privacy. Specifically, each client uploads \"[w, w * params]\"" -" with masks, where weighting factor 'w' is the number of examples " -"('num_examples') and 'params' represents the model parameters " -"('parameters') from the client's `FitRes`. The server then aggregates " -"these contributions to compute the weighted average of model parameters." +"private. This is achieved by clients sending both, a weighting factor and a " +"weighted version of the locally updated parameters, both of which are masked " +"for privacy. Specifically, each client uploads \"[w, w * params]\" with " +"masks, where weighting factor 'w' is the number of examples ('num_examples') " +"and 'params' represents the model parameters ('parameters') from the " +"client's `FitRes`. The server then aggregates these contributions to compute " +"the weighted average of model parameters." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:14 of msgid "" -"The protocol involves four main stages: - 'setup': Send SecAgg " -"configuration to clients and collect their public keys. - 'share keys': " -"Broadcast public keys among clients and collect encrypted secret" +"The protocol involves four main stages: - 'setup': Send SecAgg configuration " +"to clients and collect their public keys. - 'share keys': Broadcast public " +"keys among clients and collect encrypted secret" msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:54 of msgid "" -"Each client's private key is split into N shares under the SecAgg " -"protocol, where N is the number of selected clients." +"Each client's private key is split into N shares under the SecAgg protocol, " +"where N is the number of selected clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:56 of msgid "" -"Generally, higher `reconstruction_threshold` means better privacy " -"guarantees but less tolerance to dropouts." +"Generally, higher `reconstruction_threshold` means better privacy guarantees " +"but less tolerance to dropouts." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:60 of msgid "" "When `reconstruction_threshold` is a float, it is interpreted as the " "proportion of the number of all selected clients needed for the " -"reconstruction of a private key. This feature enables flexibility in " -"setting the security threshold relative to the number of selected " -"clients." +"reconstruction of a private key. This feature enables flexibility in setting " +"the security threshold relative to the number of selected clients." msgstr "" #: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow:64 of @@ -14541,32 +15230,29 @@ msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`collect_masked_vectors_stage " -"`\\ " -"\\(driver\\, ...\\)" +":py:obj:`collect_masked_vectors_stage `\\ \\(driver\\, ...\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`setup_stage `\\" -" \\(driver\\, context\\, state\\)" +":py:obj:`setup_stage `\\ " +"\\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`share_keys_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`share_keys_stage `\\ \\(driver\\, context\\, state\\)" msgstr "" #: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow.collect_masked_vectors_stage:1::1 #: of msgid "" -":py:obj:`unmask_stage " -"`\\ \\(driver\\, " -"context\\, state\\)" +":py:obj:`unmask_stage `\\ " +"\\(driver\\, context\\, state\\)" msgstr "" #: ../../source/ref-api/flwr.simulation.rst:2 @@ -14575,8 +15261,8 @@ msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 msgid "" -":py:obj:`start_simulation `\\ \\(\\*\\," -" client\\_fn\\[\\, ...\\]\\)" +":py:obj:`start_simulation `\\ \\(\\*\\, " +"client\\_fn\\[\\, ...\\]\\)" msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 @@ -14607,15 +15293,14 @@ msgstr "" #: flwr.simulation.run_simulation.run_simulation:6 of msgid "" -"The `ClientApp` to be executed by each of the SuperNodes. It will receive" -" messages sent by the `ServerApp`." +"The `ClientApp` to be executed by each of the SuperNodes. It will receive " +"messages sent by the `ServerApp`." msgstr "" #: flwr.simulation.run_simulation.run_simulation:9 of msgid "" -"Number of nodes that run a ClientApp. They can be sampled by a Driver in " -"the ServerApp and receive a Message describing what the ClientApp should " -"perform." +"Number of nodes that run a ClientApp. They can be sampled by a Driver in the " +"ServerApp and receive a Message describing what the ClientApp should perform." msgstr "" #: flwr.simulation.run_simulation.run_simulation:13 of @@ -14624,26 +15309,26 @@ msgstr "" #: flwr.simulation.run_simulation.run_simulation:15 of msgid "" -"'A dictionary, e.g {\"\": , \"\": } to " -"configure a backend. Values supported in are those included by " -"`flwr.common.typing.ConfigsRecordValues`." +"'A dictionary, e.g {\"\": , \"\": } to configure a " +"backend. Values supported in are those included by `flwr.common." +"typing.ConfigsRecordValues`." msgstr "" #: flwr.simulation.run_simulation.run_simulation:19 of msgid "" -"A boolean to indicate whether to enable GPU growth on the main thread. " -"This is desirable if you make use of a TensorFlow model on your " -"`ServerApp` while having your `ClientApp` running on the same GPU. " -"Without enabling this, you might encounter an out-of-memory error because" -" TensorFlow, by default, allocates all GPU memory. Read more about how " -"`tf.config.experimental.set_memory_growth()` works in the TensorFlow " -"documentation: https://www.tensorflow.org/api/stable." +"A boolean to indicate whether to enable GPU growth on the main thread. This " +"is desirable if you make use of a TensorFlow model on your `ServerApp` while " +"having your `ClientApp` running on the same GPU. Without enabling this, you " +"might encounter an out-of-memory error because TensorFlow, by default, " +"allocates all GPU memory. Read more about how `tf.config.experimental." +"set_memory_growth()` works in the TensorFlow documentation: https://www." +"tensorflow.org/api/stable." msgstr "" #: flwr.simulation.run_simulation.run_simulation:26 of msgid "" -"When diabled, only INFO, WARNING and ERROR log messages will be shown. If" -" enabled, DEBUG-level logs will be displayed." +"When diabled, only INFO, WARNING and ERROR log messages will be shown. If " +"enabled, DEBUG-level logs will be displayed." msgstr "" #: ../../source/ref-api/flwr.simulation.start_simulation.rst:2 @@ -14652,15 +15337,15 @@ msgstr "" #: flwr.simulation.app.start_simulation:3 of msgid "" -"A function creating client instances. The function must take a single " -"`str` argument called `cid`. It should return a single client instance of" -" type Client. Note that the created client instances are ephemeral and " -"will often be destroyed after a single method invocation. Since client " -"instances are not long-lived, they should not attempt to carry state over" -" method invocations. Any state required by the instance (model, dataset, " +"A function creating client instances. The function must take a single `str` " +"argument called `cid`. It should return a single client instance of type " +"Client. Note that the created client instances are ephemeral and will often " +"be destroyed after a single method invocation. Since client instances are " +"not long-lived, they should not attempt to carry state over method " +"invocations. Any state required by the instance (model, dataset, " "hyperparameters, ...) should be (re-)created in either the call to " -"`client_fn` or the call to any of the client methods (e.g., load " -"evaluation data in the `evaluate` method itself)." +"`client_fn` or the call to any of the client methods (e.g., load evaluation " +"data in the `evaluate` method itself)." msgstr "" #: flwr.simulation.app.start_simulation:13 of @@ -14671,16 +15356,16 @@ msgstr "" #: flwr.simulation.app.start_simulation:16 of msgid "" -"List `client_id`s for each client. This is only required if `num_clients`" -" is not set. Setting both `num_clients` and `clients_ids` with " +"List `client_id`s for each client. This is only required if `num_clients` is " +"not set. Setting both `num_clients` and `clients_ids` with " "`len(clients_ids)` not equal to `num_clients` generates an error." msgstr "" #: flwr.simulation.app.start_simulation:20 of msgid "" -"CPU and GPU resources for a single client. Supported keys are `num_cpus` " -"and `num_gpus`. To understand the GPU utilization caused by `num_gpus`, " -"as well as using custom resources, please consult the Ray documentation." +"CPU and GPU resources for a single client. Supported keys are `num_cpus` and " +"`num_gpus`. To understand the GPU utilization caused by `num_gpus`, as well " +"as using custom resources, please consult the Ray documentation." msgstr "" #: flwr.simulation.app.start_simulation:25 of @@ -14691,16 +15376,16 @@ msgstr "" #: flwr.simulation.app.start_simulation:31 of msgid "" -"An implementation of the abstract base class `flwr.server.Strategy`. If " -"no strategy is provided, then `start_server` will use " -"`flwr.server.strategy.FedAvg`." +"An implementation of the abstract base class `flwr.server.Strategy`. If no " +"strategy is provided, then `start_server` will use `flwr.server.strategy." +"FedAvg`." msgstr "" #: flwr.simulation.app.start_simulation:35 of msgid "" -"An implementation of the abstract base class `flwr.server.ClientManager`." -" If no implementation is provided, then `start_simulation` will use " -"`flwr.server.client_manager.SimpleClientManager`." +"An implementation of the abstract base class `flwr.server.ClientManager`. If " +"no implementation is provided, then `start_simulation` will use `flwr.server." +"client_manager.SimpleClientManager`." msgstr "" #: flwr.simulation.app.start_simulation:39 of @@ -14709,8 +15394,7 @@ msgid "" "ray_init_args is None (the default), Ray will be initialized with the " "following default args: { \"ignore_reinit_error\": True, " "\"include_dashboard\": False } An empty dictionary can be used " -"(ray_init_args={}) to prevent any arguments from being passed to " -"ray.init." +"(ray_init_args={}) to prevent any arguments from being passed to ray.init." msgstr "" #: flwr.simulation.app.start_simulation:39 of @@ -14726,14 +15410,13 @@ msgstr "" #: flwr.simulation.app.start_simulation:45 of msgid "" -"An empty dictionary can be used (ray_init_args={}) to prevent any " -"arguments from being passed to ray.init." +"An empty dictionary can be used (ray_init_args={}) to prevent any arguments " +"from being passed to ray.init." msgstr "" #: flwr.simulation.app.start_simulation:48 of msgid "" -"Set to True to prevent `ray.shutdown()` in case " -"`ray.is_initialized()=True`." +"Set to True to prevent `ray.shutdown()` in case `ray.is_initialized()=True`." msgstr "" #: flwr.simulation.app.start_simulation:50 of @@ -14745,19 +15428,19 @@ msgstr "" #: flwr.simulation.app.start_simulation:54 of msgid "" -"If you want to create your own Actor classes, you might need to pass some" -" input argument. You can use this dictionary for such purpose." +"If you want to create your own Actor classes, you might need to pass some " +"input argument. You can use this dictionary for such purpose." msgstr "" #: flwr.simulation.app.start_simulation:57 of msgid "" -"(default: \"DEFAULT\") Optional string (\"DEFAULT\" or \"SPREAD\") for " -"the VCE to choose in which node the actor is placed. If you are an " -"advanced user needed more control you can use lower-level scheduling " -"strategies to pin actors to specific compute nodes (e.g. via " -"NodeAffinitySchedulingStrategy). Please note this is an advanced feature." -" For all details, please refer to the Ray documentation: " -"https://docs.ray.io/en/latest/ray-core/scheduling/index.html" +"(default: \"DEFAULT\") Optional string (\"DEFAULT\" or \"SPREAD\") for the " +"VCE to choose in which node the actor is placed. If you are an advanced user " +"needed more control you can use lower-level scheduling strategies to pin " +"actors to specific compute nodes (e.g. via NodeAffinitySchedulingStrategy). " +"Please note this is an advanced feature. For all details, please refer to " +"the Ray documentation: https://docs.ray.io/en/latest/ray-core/scheduling/" +"index.html" msgstr "" #: flwr.simulation.app.start_simulation:66 of @@ -14785,16 +15468,16 @@ msgstr "" #: ../../source/ref-changelog.md:364 ../../source/ref-changelog.md:448 #: ../../source/ref-changelog.md:512 ../../source/ref-changelog.md:570 msgid "" -"We would like to give our special thanks to all the contributors who made" -" the new version of Flower possible (in `git shortlog` order):" +"We would like to give our special thanks to all the contributors who made " +"the new version of Flower possible (in `git shortlog` order):" msgstr "" #: ../../source/ref-changelog.md:9 msgid "" -"`Adam Narozniak`, `Charles Beauville`, `Chong Shen Ng`, `Daniel J. " -"Beutel`, `Daniel Nata Nugraha`, `Heng Pan`, `Javier`, `Mahdi Beitollahi`," -" `Robert Steiner`, `Taner Topal`, `Yan Gao`, `bapic`, `mohammadnaseri` " +"`Adam Narozniak`, `Charles Beauville`, `Chong Shen Ng`, `Daniel J. Beutel`, " +"`Daniel Nata Nugraha`, `Heng Pan`, `Javier`, `Mahdi Beitollahi`, `Robert " +"Steiner`, `Taner Topal`, `Yan Gao`, `bapic`, `mohammadnaseri` " msgstr "" #: ../../source/ref-changelog.md:11 ../../source/ref-changelog.md:111 @@ -14811,33 +15494,28 @@ msgstr "" #: ../../source/ref-changelog.md:13 msgid "" -"**Introduce built-in authentication (preview)** " -"([#2946](https://github.com/adap/flower/pull/2946), " -"[#3388](https://github.com/adap/flower/pull/3388), " -"[#2948](https://github.com/adap/flower/pull/2948), " -"[#2917](https://github.com/adap/flower/pull/2917), " -"[#3386](https://github.com/adap/flower/pull/3386), " -"[#3308](https://github.com/adap/flower/pull/3308), " -"[#3001](https://github.com/adap/flower/pull/3001), " -"[#3409](https://github.com/adap/flower/pull/3409), " -"[#2999](https://github.com/adap/flower/pull/2999), " -"[#2979](https://github.com/adap/flower/pull/2979), " -"[#3389](https://github.com/adap/flower/pull/3389), " -"[#3503](https://github.com/adap/flower/pull/3503), " -"[#3366](https://github.com/adap/flower/pull/3366), " -"[#3357](https://github.com/adap/flower/pull/3357))" +"**Introduce built-in authentication (preview)** ([#2946](https://github.com/" +"adap/flower/pull/2946), [#3388](https://github.com/adap/flower/pull/3388), " +"[#2948](https://github.com/adap/flower/pull/2948), [#2917](https://github." +"com/adap/flower/pull/2917), [#3386](https://github.com/adap/flower/" +"pull/3386), [#3308](https://github.com/adap/flower/pull/3308), [#3001]" +"(https://github.com/adap/flower/pull/3001), [#3409](https://github.com/adap/" +"flower/pull/3409), [#2999](https://github.com/adap/flower/pull/2999), [#2979]" +"(https://github.com/adap/flower/pull/2979), [#3389](https://github.com/adap/" +"flower/pull/3389), [#3503](https://github.com/adap/flower/pull/3503), [#3366]" +"(https://github.com/adap/flower/pull/3366), [#3357](https://github.com/adap/" +"flower/pull/3357))" msgstr "" #: ../../source/ref-changelog.md:15 msgid "" "Flower 1.9 introduces the first build-in version of client node " -"authentication. In previous releases, users often wrote glue code to " -"connect Flower to external authentication systems. With this release, the" -" SuperLink can authenticate SuperNodes using a built-in authentication " -"system. A new [how-to guide](https://flower.ai/docs/framework/how-to-" -"authenticate-supernodes.html) and a new [code " -"example](https://github.com/adap/flower/tree/main/examples/flower-" -"authentication) help you to get started." +"authentication. In previous releases, users often wrote glue code to connect " +"Flower to external authentication systems. With this release, the SuperLink " +"can authenticate SuperNodes using a built-in authentication system. A new " +"[how-to guide](https://flower.ai/docs/framework/how-to-authenticate-" +"supernodes.html) and a new [code example](https://github.com/adap/flower/" +"tree/main/examples/flower-authentication) help you to get started." msgstr "" #: ../../source/ref-changelog.md:17 @@ -14849,118 +15527,104 @@ msgstr "" #: ../../source/ref-changelog.md:19 msgid "" -"**Introduce end-to-end Docker support** " -"([#3483](https://github.com/adap/flower/pull/3483), " -"[#3266](https://github.com/adap/flower/pull/3266), " -"[#3390](https://github.com/adap/flower/pull/3390), " -"[#3283](https://github.com/adap/flower/pull/3283), " -"[#3285](https://github.com/adap/flower/pull/3285), " -"[#3391](https://github.com/adap/flower/pull/3391), " -"[#3403](https://github.com/adap/flower/pull/3403), " -"[#3458](https://github.com/adap/flower/pull/3458), " -"[#3533](https://github.com/adap/flower/pull/3533), " -"[#3453](https://github.com/adap/flower/pull/3453), " -"[#3486](https://github.com/adap/flower/pull/3486), " -"[#3290](https://github.com/adap/flower/pull/3290))" +"**Introduce end-to-end Docker support** ([#3483](https://github.com/adap/" +"flower/pull/3483), [#3266](https://github.com/adap/flower/pull/3266), [#3390]" +"(https://github.com/adap/flower/pull/3390), [#3283](https://github.com/adap/" +"flower/pull/3283), [#3285](https://github.com/adap/flower/pull/3285), [#3391]" +"(https://github.com/adap/flower/pull/3391), [#3403](https://github.com/adap/" +"flower/pull/3403), [#3458](https://github.com/adap/flower/pull/3458), [#3533]" +"(https://github.com/adap/flower/pull/3533), [#3453](https://github.com/adap/" +"flower/pull/3453), [#3486](https://github.com/adap/flower/pull/3486), [#3290]" +"(https://github.com/adap/flower/pull/3290))" msgstr "" #: ../../source/ref-changelog.md:21 msgid "" "Full Flower Next Docker support is here! With the release of Flower 1.9, " -"Flower provides stable Docker images for the Flower SuperLink, the Flower" -" SuperNode, and the Flower `ServerApp`. This set of images enables you to" -" run all Flower components in Docker. Check out the new [how-to " -"guide](https://flower.ai/docs/framework/how-to-run-flower-using-" -"docker.html) to get stated." +"Flower provides stable Docker images for the Flower SuperLink, the Flower " +"SuperNode, and the Flower `ServerApp`. This set of images enables you to run " +"all Flower components in Docker. Check out the new [how-to guide](https://" +"flower.ai/docs/framework/how-to-run-flower-using-docker.html) to get stated." msgstr "" #: ../../source/ref-changelog.md:23 msgid "" -"**Re-architect Flower Next simulation engine** " -"([#3307](https://github.com/adap/flower/pull/3307), " -"[#3355](https://github.com/adap/flower/pull/3355), " -"[#3272](https://github.com/adap/flower/pull/3272), " -"[#3273](https://github.com/adap/flower/pull/3273), " -"[#3417](https://github.com/adap/flower/pull/3417), " -"[#3281](https://github.com/adap/flower/pull/3281), " -"[#3343](https://github.com/adap/flower/pull/3343), " -"[#3326](https://github.com/adap/flower/pull/3326))" +"**Re-architect Flower Next simulation engine** ([#3307](https://github.com/" +"adap/flower/pull/3307), [#3355](https://github.com/adap/flower/pull/3355), " +"[#3272](https://github.com/adap/flower/pull/3272), [#3273](https://github." +"com/adap/flower/pull/3273), [#3417](https://github.com/adap/flower/" +"pull/3417), [#3281](https://github.com/adap/flower/pull/3281), [#3343]" +"(https://github.com/adap/flower/pull/3343), [#3326](https://github.com/adap/" +"flower/pull/3326))" msgstr "" #: ../../source/ref-changelog.md:25 msgid "" -"Flower Next simulations now use a new in-memory `Driver` that improves " -"the reliability of simulations, especially in notebook environments. This" -" is a significant step towards a complete overhaul of the Flower Next " -"simulation architecture." +"Flower Next simulations now use a new in-memory `Driver` that improves the " +"reliability of simulations, especially in notebook environments. This is a " +"significant step towards a complete overhaul of the Flower Next simulation " +"architecture." msgstr "" #: ../../source/ref-changelog.md:27 msgid "" -"**Upgrade simulation engine** " -"([#3354](https://github.com/adap/flower/pull/3354), " -"[#3378](https://github.com/adap/flower/pull/3378), " -"[#3262](https://github.com/adap/flower/pull/3262), " -"[#3435](https://github.com/adap/flower/pull/3435), " -"[#3501](https://github.com/adap/flower/pull/3501), " -"[#3482](https://github.com/adap/flower/pull/3482), " -"[#3494](https://github.com/adap/flower/pull/3494))" +"**Upgrade simulation engine** ([#3354](https://github.com/adap/flower/" +"pull/3354), [#3378](https://github.com/adap/flower/pull/3378), [#3262]" +"(https://github.com/adap/flower/pull/3262), [#3435](https://github.com/adap/" +"flower/pull/3435), [#3501](https://github.com/adap/flower/pull/3501), [#3482]" +"(https://github.com/adap/flower/pull/3482), [#3494](https://github.com/adap/" +"flower/pull/3494))" msgstr "" #: ../../source/ref-changelog.md:29 msgid "" "The Flower Next simulation engine comes with improved and configurable " -"logging. The Ray-based simulation backend in Flower 1.9 was updated to " -"use Ray 2.10." +"logging. The Ray-based simulation backend in Flower 1.9 was updated to use " +"Ray 2.10." msgstr "" #: ../../source/ref-changelog.md:31 msgid "" -"**Introduce FedPFT baseline** " -"([#3268](https://github.com/adap/flower/pull/3268))" +"**Introduce FedPFT baseline** ([#3268](https://github.com/adap/flower/" +"pull/3268))" msgstr "" #: ../../source/ref-changelog.md:33 msgid "" "FedPFT allows you to perform one-shot Federated Learning by leveraging " -"widely available foundational models, dramatically reducing communication" -" costs while delivering high performing models. This is work led by Mahdi" -" Beitollahi from Huawei Noah's Ark Lab (Montreal, Canada). Read all the " -"details in their paper: \"Parametric Feature Transfer: One-shot Federated" -" Learning with Foundation Models\" " -"([arxiv](https://arxiv.org/abs/2402.01862))" +"widely available foundational models, dramatically reducing communication " +"costs while delivering high performing models. This is work led by Mahdi " +"Beitollahi from Huawei Noah's Ark Lab (Montreal, Canada). Read all the " +"details in their paper: \"Parametric Feature Transfer: One-shot Federated " +"Learning with Foundation Models\" ([arxiv](https://arxiv.org/abs/2402.01862))" msgstr "" #: ../../source/ref-changelog.md:35 msgid "" "**Launch additional** `flwr new` **templates for Apple MLX, Hugging Face " -"Transformers, scikit-learn and TensorFlow** " -"([#3291](https://github.com/adap/flower/pull/3291), " -"[#3139](https://github.com/adap/flower/pull/3139), " -"[#3284](https://github.com/adap/flower/pull/3284), " -"[#3251](https://github.com/adap/flower/pull/3251), " -"[#3376](https://github.com/adap/flower/pull/3376), " -"[#3287](https://github.com/adap/flower/pull/3287))" +"Transformers, scikit-learn and TensorFlow** ([#3291](https://github.com/adap/" +"flower/pull/3291), [#3139](https://github.com/adap/flower/pull/3139), [#3284]" +"(https://github.com/adap/flower/pull/3284), [#3251](https://github.com/adap/" +"flower/pull/3251), [#3376](https://github.com/adap/flower/pull/3376), [#3287]" +"(https://github.com/adap/flower/pull/3287))" msgstr "" #: ../../source/ref-changelog.md:37 msgid "" -"The `flwr` CLI's `flwr new` command is starting to become everone's " -"favorite way of creating new Flower projects. This release introduces " -"additional `flwr new` templates for Apple MLX, Hugging Face Transformers," -" scikit-learn and TensorFlow. In addition to that, existing templates " -"also received updates." +"The `flwr` CLI's `flwr new` command is starting to become everone's favorite " +"way of creating new Flower projects. This release introduces additional " +"`flwr new` templates for Apple MLX, Hugging Face Transformers, scikit-learn " +"and TensorFlow. In addition to that, existing templates also received " +"updates." msgstr "" #: ../../source/ref-changelog.md:39 msgid "" -"**Refine** `RecordSet` **API** " -"([#3209](https://github.com/adap/flower/pull/3209), " -"[#3331](https://github.com/adap/flower/pull/3331), " -"[#3334](https://github.com/adap/flower/pull/3334), " -"[#3335](https://github.com/adap/flower/pull/3335), " -"[#3375](https://github.com/adap/flower/pull/3375), " -"[#3368](https://github.com/adap/flower/pull/3368))" +"**Refine** `RecordSet` **API** ([#3209](https://github.com/adap/flower/" +"pull/3209), [#3331](https://github.com/adap/flower/pull/3331), [#3334]" +"(https://github.com/adap/flower/pull/3334), [#3335](https://github.com/adap/" +"flower/pull/3335), [#3375](https://github.com/adap/flower/pull/3375), [#3368]" +"(https://github.com/adap/flower/pull/3368))" msgstr "" #: ../../source/ref-changelog.md:41 @@ -14973,29 +15637,25 @@ msgstr "" #: ../../source/ref-changelog.md:43 msgid "" "**Beautify logging** ([#3379](https://github.com/adap/flower/pull/3379), " -"[#3430](https://github.com/adap/flower/pull/3430), " -"[#3461](https://github.com/adap/flower/pull/3461), " -"[#3360](https://github.com/adap/flower/pull/3360), " -"[#3433](https://github.com/adap/flower/pull/3433))" +"[#3430](https://github.com/adap/flower/pull/3430), [#3461](https://github." +"com/adap/flower/pull/3461), [#3360](https://github.com/adap/flower/" +"pull/3360), [#3433](https://github.com/adap/flower/pull/3433))" msgstr "" #: ../../source/ref-changelog.md:45 msgid "" -"Logs received a substantial update. Not only are logs now much nicer to " -"look at, but they are also more configurable." +"Logs received a substantial update. Not only are logs now much nicer to look " +"at, but they are also more configurable." msgstr "" #: ../../source/ref-changelog.md:47 msgid "" -"**Improve reliability** " -"([#3564](https://github.com/adap/flower/pull/3564), " -"[#3561](https://github.com/adap/flower/pull/3561), " -"[#3566](https://github.com/adap/flower/pull/3566), " -"[#3462](https://github.com/adap/flower/pull/3462), " -"[#3225](https://github.com/adap/flower/pull/3225), " -"[#3514](https://github.com/adap/flower/pull/3514), " -"[#3535](https://github.com/adap/flower/pull/3535), " -"[#3372](https://github.com/adap/flower/pull/3372))" +"**Improve reliability** ([#3564](https://github.com/adap/flower/pull/3564), " +"[#3561](https://github.com/adap/flower/pull/3561), [#3566](https://github." +"com/adap/flower/pull/3566), [#3462](https://github.com/adap/flower/" +"pull/3462), [#3225](https://github.com/adap/flower/pull/3225), [#3514]" +"(https://github.com/adap/flower/pull/3514), [#3535](https://github.com/adap/" +"flower/pull/3535), [#3372](https://github.com/adap/flower/pull/3372))" msgstr "" #: ../../source/ref-changelog.md:49 @@ -15006,50 +15666,40 @@ msgstr "" #: ../../source/ref-changelog.md:51 msgid "" -"**Update Swift and C++ SDKs** " -"([#3321](https://github.com/adap/flower/pull/3321), " -"[#2763](https://github.com/adap/flower/pull/2763))" +"**Update Swift and C++ SDKs** ([#3321](https://github.com/adap/flower/" +"pull/3321), [#2763](https://github.com/adap/flower/pull/2763))" msgstr "" #: ../../source/ref-changelog.md:53 msgid "" -"In the C++ SDK, communication-related code is now separate from main " -"client logic. A new abstract class `Communicator` has been introduced " -"alongside a gRPC implementation of it." +"In the C++ SDK, communication-related code is now separate from main client " +"logic. A new abstract class `Communicator` has been introduced alongside a " +"gRPC implementation of it." msgstr "" #: ../../source/ref-changelog.md:55 msgid "" -"**Improve testing, tooling and CI/CD infrastructure** " -"([#3294](https://github.com/adap/flower/pull/3294), " -"[#3282](https://github.com/adap/flower/pull/3282), " -"[#3311](https://github.com/adap/flower/pull/3311), " -"[#2878](https://github.com/adap/flower/pull/2878), " -"[#3333](https://github.com/adap/flower/pull/3333), " -"[#3255](https://github.com/adap/flower/pull/3255), " -"[#3349](https://github.com/adap/flower/pull/3349), " -"[#3400](https://github.com/adap/flower/pull/3400), " -"[#3401](https://github.com/adap/flower/pull/3401), " -"[#3399](https://github.com/adap/flower/pull/3399), " -"[#3346](https://github.com/adap/flower/pull/3346), " -"[#3398](https://github.com/adap/flower/pull/3398), " -"[#3397](https://github.com/adap/flower/pull/3397), " -"[#3347](https://github.com/adap/flower/pull/3347), " -"[#3502](https://github.com/adap/flower/pull/3502), " -"[#3387](https://github.com/adap/flower/pull/3387), " -"[#3542](https://github.com/adap/flower/pull/3542), " -"[#3396](https://github.com/adap/flower/pull/3396), " -"[#3496](https://github.com/adap/flower/pull/3496), " -"[#3465](https://github.com/adap/flower/pull/3465), " -"[#3473](https://github.com/adap/flower/pull/3473), " -"[#3484](https://github.com/adap/flower/pull/3484), " -"[#3521](https://github.com/adap/flower/pull/3521), " -"[#3363](https://github.com/adap/flower/pull/3363), " -"[#3497](https://github.com/adap/flower/pull/3497), " -"[#3464](https://github.com/adap/flower/pull/3464), " -"[#3495](https://github.com/adap/flower/pull/3495), " -"[#3478](https://github.com/adap/flower/pull/3478), " -"[#3271](https://github.com/adap/flower/pull/3271))" +"**Improve testing, tooling and CI/CD infrastructure** ([#3294](https://" +"github.com/adap/flower/pull/3294), [#3282](https://github.com/adap/flower/" +"pull/3282), [#3311](https://github.com/adap/flower/pull/3311), [#2878]" +"(https://github.com/adap/flower/pull/2878), [#3333](https://github.com/adap/" +"flower/pull/3333), [#3255](https://github.com/adap/flower/pull/3255), [#3349]" +"(https://github.com/adap/flower/pull/3349), [#3400](https://github.com/adap/" +"flower/pull/3400), [#3401](https://github.com/adap/flower/pull/3401), [#3399]" +"(https://github.com/adap/flower/pull/3399), [#3346](https://github.com/adap/" +"flower/pull/3346), [#3398](https://github.com/adap/flower/pull/3398), [#3397]" +"(https://github.com/adap/flower/pull/3397), [#3347](https://github.com/adap/" +"flower/pull/3347), [#3502](https://github.com/adap/flower/pull/3502), [#3387]" +"(https://github.com/adap/flower/pull/3387), [#3542](https://github.com/adap/" +"flower/pull/3542), [#3396](https://github.com/adap/flower/pull/3396), [#3496]" +"(https://github.com/adap/flower/pull/3496), [#3465](https://github.com/adap/" +"flower/pull/3465), [#3473](https://github.com/adap/flower/pull/3473), [#3484]" +"(https://github.com/adap/flower/pull/3484), [#3521](https://github.com/adap/" +"flower/pull/3521), [#3363](https://github.com/adap/flower/pull/3363), [#3497]" +"(https://github.com/adap/flower/pull/3497), [#3464](https://github.com/adap/" +"flower/pull/3464), [#3495](https://github.com/adap/flower/pull/3495), [#3478]" +"(https://github.com/adap/flower/pull/3478), [#3271](https://github.com/adap/" +"flower/pull/3271))" msgstr "" #: ../../source/ref-changelog.md:57 @@ -15060,75 +15710,61 @@ msgstr "" #: ../../source/ref-changelog.md:59 msgid "" -"**Improve documentation** " -"([#3530](https://github.com/adap/flower/pull/3530), " -"[#3539](https://github.com/adap/flower/pull/3539), " -"[#3425](https://github.com/adap/flower/pull/3425), " -"[#3520](https://github.com/adap/flower/pull/3520), " -"[#3286](https://github.com/adap/flower/pull/3286), " -"[#3516](https://github.com/adap/flower/pull/3516), " -"[#3523](https://github.com/adap/flower/pull/3523), " -"[#3545](https://github.com/adap/flower/pull/3545), " -"[#3498](https://github.com/adap/flower/pull/3498), " -"[#3439](https://github.com/adap/flower/pull/3439), " -"[#3440](https://github.com/adap/flower/pull/3440), " -"[#3382](https://github.com/adap/flower/pull/3382), " -"[#3559](https://github.com/adap/flower/pull/3559), " -"[#3432](https://github.com/adap/flower/pull/3432), " -"[#3278](https://github.com/adap/flower/pull/3278), " -"[#3371](https://github.com/adap/flower/pull/3371), " -"[#3519](https://github.com/adap/flower/pull/3519), " -"[#3267](https://github.com/adap/flower/pull/3267), " -"[#3204](https://github.com/adap/flower/pull/3204), " -"[#3274](https://github.com/adap/flower/pull/3274))" +"**Improve documentation** ([#3530](https://github.com/adap/flower/" +"pull/3530), [#3539](https://github.com/adap/flower/pull/3539), [#3425]" +"(https://github.com/adap/flower/pull/3425), [#3520](https://github.com/adap/" +"flower/pull/3520), [#3286](https://github.com/adap/flower/pull/3286), [#3516]" +"(https://github.com/adap/flower/pull/3516), [#3523](https://github.com/adap/" +"flower/pull/3523), [#3545](https://github.com/adap/flower/pull/3545), [#3498]" +"(https://github.com/adap/flower/pull/3498), [#3439](https://github.com/adap/" +"flower/pull/3439), [#3440](https://github.com/adap/flower/pull/3440), [#3382]" +"(https://github.com/adap/flower/pull/3382), [#3559](https://github.com/adap/" +"flower/pull/3559), [#3432](https://github.com/adap/flower/pull/3432), [#3278]" +"(https://github.com/adap/flower/pull/3278), [#3371](https://github.com/adap/" +"flower/pull/3371), [#3519](https://github.com/adap/flower/pull/3519), [#3267]" +"(https://github.com/adap/flower/pull/3267), [#3204](https://github.com/adap/" +"flower/pull/3204), [#3274](https://github.com/adap/flower/pull/3274))" msgstr "" #: ../../source/ref-changelog.md:61 msgid "" -"As always, the Flower documentation has received many updates. Notable " -"new pages include:" +"As always, the Flower documentation has received many updates. Notable new " +"pages include:" msgstr "" #: ../../source/ref-changelog.md:63 msgid "" -"[How-to upgrate to Flower Next (Flower Next migration " -"guide)](https://flower.ai/docs/framework/how-to-upgrade-to-flower-" -"next.html)" +"[How-to upgrate to Flower Next (Flower Next migration guide)](https://flower." +"ai/docs/framework/how-to-upgrade-to-flower-next.html)" msgstr "" #: ../../source/ref-changelog.md:65 msgid "" -"[How-to run Flower using Docker](https://flower.ai/docs/framework/how-to-" -"run-flower-using-docker.html)" +"[How-to run Flower using Docker](https://flower.ai/docs/framework/how-to-run-" +"flower-using-docker.html)" msgstr "" #: ../../source/ref-changelog.md:67 msgid "" -"[Flower Mods reference](https://flower.ai/docs/framework/ref-" -"api/flwr.client.mod.html#module-flwr.client.mod)" +"[Flower Mods reference](https://flower.ai/docs/framework/ref-api/flwr.client." +"mod.html#module-flwr.client.mod)" msgstr "" #: ../../source/ref-changelog.md:69 msgid "" -"**General updates to Flower Examples** " -"([#3205](https://github.com/adap/flower/pull/3205), " -"[#3226](https://github.com/adap/flower/pull/3226), " -"[#3211](https://github.com/adap/flower/pull/3211), " -"[#3252](https://github.com/adap/flower/pull/3252), " -"[#3427](https://github.com/adap/flower/pull/3427), " -"[#3410](https://github.com/adap/flower/pull/3410), " -"[#3426](https://github.com/adap/flower/pull/3426), " -"[#3228](https://github.com/adap/flower/pull/3228), " -"[#3342](https://github.com/adap/flower/pull/3342), " -"[#3200](https://github.com/adap/flower/pull/3200), " -"[#3202](https://github.com/adap/flower/pull/3202), " -"[#3394](https://github.com/adap/flower/pull/3394), " -"[#3488](https://github.com/adap/flower/pull/3488), " -"[#3329](https://github.com/adap/flower/pull/3329), " -"[#3526](https://github.com/adap/flower/pull/3526), " -"[#3392](https://github.com/adap/flower/pull/3392), " -"[#3474](https://github.com/adap/flower/pull/3474), " -"[#3269](https://github.com/adap/flower/pull/3269))" +"**General updates to Flower Examples** ([#3205](https://github.com/adap/" +"flower/pull/3205), [#3226](https://github.com/adap/flower/pull/3226), [#3211]" +"(https://github.com/adap/flower/pull/3211), [#3252](https://github.com/adap/" +"flower/pull/3252), [#3427](https://github.com/adap/flower/pull/3427), [#3410]" +"(https://github.com/adap/flower/pull/3410), [#3426](https://github.com/adap/" +"flower/pull/3426), [#3228](https://github.com/adap/flower/pull/3228), [#3342]" +"(https://github.com/adap/flower/pull/3342), [#3200](https://github.com/adap/" +"flower/pull/3200), [#3202](https://github.com/adap/flower/pull/3202), [#3394]" +"(https://github.com/adap/flower/pull/3394), [#3488](https://github.com/adap/" +"flower/pull/3488), [#3329](https://github.com/adap/flower/pull/3329), [#3526]" +"(https://github.com/adap/flower/pull/3526), [#3392](https://github.com/adap/" +"flower/pull/3392), [#3474](https://github.com/adap/flower/pull/3474), [#3269]" +"(https://github.com/adap/flower/pull/3269))" msgstr "" #: ../../source/ref-changelog.md:71 @@ -15137,41 +15773,30 @@ msgstr "" #: ../../source/ref-changelog.md:73 msgid "" -"**General improvements** " -"([#3532](https://github.com/adap/flower/pull/3532), " -"[#3318](https://github.com/adap/flower/pull/3318), " -"[#3565](https://github.com/adap/flower/pull/3565), " -"[#3296](https://github.com/adap/flower/pull/3296), " -"[#3305](https://github.com/adap/flower/pull/3305), " -"[#3246](https://github.com/adap/flower/pull/3246), " -"[#3224](https://github.com/adap/flower/pull/3224), " -"[#3475](https://github.com/adap/flower/pull/3475), " -"[#3297](https://github.com/adap/flower/pull/3297), " -"[#3317](https://github.com/adap/flower/pull/3317), " -"[#3429](https://github.com/adap/flower/pull/3429), " -"[#3196](https://github.com/adap/flower/pull/3196), " -"[#3534](https://github.com/adap/flower/pull/3534), " -"[#3240](https://github.com/adap/flower/pull/3240), " -"[#3365](https://github.com/adap/flower/pull/3365), " -"[#3407](https://github.com/adap/flower/pull/3407), " -"[#3563](https://github.com/adap/flower/pull/3563), " -"[#3344](https://github.com/adap/flower/pull/3344), " -"[#3330](https://github.com/adap/flower/pull/3330), " -"[#3436](https://github.com/adap/flower/pull/3436), " -"[#3300](https://github.com/adap/flower/pull/3300), " -"[#3327](https://github.com/adap/flower/pull/3327), " -"[#3254](https://github.com/adap/flower/pull/3254), " -"[#3253](https://github.com/adap/flower/pull/3253), " -"[#3419](https://github.com/adap/flower/pull/3419), " -"[#3289](https://github.com/adap/flower/pull/3289), " -"[#3208](https://github.com/adap/flower/pull/3208), " -"[#3245](https://github.com/adap/flower/pull/3245), " -"[#3319](https://github.com/adap/flower/pull/3319), " -"[#3203](https://github.com/adap/flower/pull/3203), " -"[#3423](https://github.com/adap/flower/pull/3423), " -"[#3352](https://github.com/adap/flower/pull/3352), " -"[#3292](https://github.com/adap/flower/pull/3292), " -"[#3261](https://github.com/adap/flower/pull/3261))" +"**General improvements** ([#3532](https://github.com/adap/flower/pull/3532), " +"[#3318](https://github.com/adap/flower/pull/3318), [#3565](https://github." +"com/adap/flower/pull/3565), [#3296](https://github.com/adap/flower/" +"pull/3296), [#3305](https://github.com/adap/flower/pull/3305), [#3246]" +"(https://github.com/adap/flower/pull/3246), [#3224](https://github.com/adap/" +"flower/pull/3224), [#3475](https://github.com/adap/flower/pull/3475), [#3297]" +"(https://github.com/adap/flower/pull/3297), [#3317](https://github.com/adap/" +"flower/pull/3317), [#3429](https://github.com/adap/flower/pull/3429), [#3196]" +"(https://github.com/adap/flower/pull/3196), [#3534](https://github.com/adap/" +"flower/pull/3534), [#3240](https://github.com/adap/flower/pull/3240), [#3365]" +"(https://github.com/adap/flower/pull/3365), [#3407](https://github.com/adap/" +"flower/pull/3407), [#3563](https://github.com/adap/flower/pull/3563), [#3344]" +"(https://github.com/adap/flower/pull/3344), [#3330](https://github.com/adap/" +"flower/pull/3330), [#3436](https://github.com/adap/flower/pull/3436), [#3300]" +"(https://github.com/adap/flower/pull/3300), [#3327](https://github.com/adap/" +"flower/pull/3327), [#3254](https://github.com/adap/flower/pull/3254), [#3253]" +"(https://github.com/adap/flower/pull/3253), [#3419](https://github.com/adap/" +"flower/pull/3419), [#3289](https://github.com/adap/flower/pull/3289), [#3208]" +"(https://github.com/adap/flower/pull/3208), [#3245](https://github.com/adap/" +"flower/pull/3245), [#3319](https://github.com/adap/flower/pull/3319), [#3203]" +"(https://github.com/adap/flower/pull/3203), [#3423](https://github.com/adap/" +"flower/pull/3423), [#3352](https://github.com/adap/flower/pull/3352), [#3292]" +"(https://github.com/adap/flower/pull/3292), [#3261](https://github.com/adap/" +"flower/pull/3261))" msgstr "" #: ../../source/ref-changelog.md:75 ../../source/ref-changelog.md:1058 @@ -15184,36 +15809,36 @@ msgstr "" #: ../../source/ref-changelog.md:79 msgid "" -"Python 3.8 will stop receiving security fixes in [October " -"2024](https://devguide.python.org/versions/). Support for Python 3.8 is " -"now deprecated and will be removed in an upcoming release." +"Python 3.8 will stop receiving security fixes in [October 2024](https://" +"devguide.python.org/versions/). Support for Python 3.8 is now deprecated and " +"will be removed in an upcoming release." msgstr "" #: ../../source/ref-changelog.md:81 msgid "" -"**Deprecate (experimental)** `flower-driver-api` **and** `flower-fleet-" -"api` ([#3416](https://github.com/adap/flower/pull/3416), " -"[#3420](https://github.com/adap/flower/pull/3420))" +"**Deprecate (experimental)** `flower-driver-api` **and** `flower-fleet-api` " +"([#3416](https://github.com/adap/flower/pull/3416), [#3420](https://github." +"com/adap/flower/pull/3420))" msgstr "" #: ../../source/ref-changelog.md:83 msgid "" -"Flower 1.9 deprecates the two (experimental) commands `flower-driver-api`" -" and `flower-fleet-api`. Both commands will be removed in an upcoming " +"Flower 1.9 deprecates the two (experimental) commands `flower-driver-api` " +"and `flower-fleet-api`. Both commands will be removed in an upcoming " "release. Use `flower-superlink` instead." msgstr "" #: ../../source/ref-changelog.md:85 msgid "" -"**Deprecate** `--server` **in favor of** `--superlink` " -"([#3518](https://github.com/adap/flower/pull/3518))" +"**Deprecate** `--server` **in favor of** `--superlink` ([#3518](https://" +"github.com/adap/flower/pull/3518))" msgstr "" #: ../../source/ref-changelog.md:87 msgid "" -"The commands `flower-server-app` and `flower-client-app` should use " -"`--superlink` instead of the now deprecated `--server`. Support for " -"`--server` will be removed in a future release." +"The commands `flower-server-app` and `flower-client-app` should use `--" +"superlink` instead of the now deprecated `--server`. Support for `--server` " +"will be removed in a future release." msgstr "" #: ../../source/ref-changelog.md:89 ../../source/ref-changelog.md:163 @@ -15228,48 +15853,46 @@ msgstr "" #: ../../source/ref-changelog.md:91 msgid "" -"**Replace** `flower-superlink` **CLI option** `--certificates` **with** " -"`--ssl-ca-certfile` **,** `--ssl-certfile` **and** `--ssl-keyfile` " -"([#3512](https://github.com/adap/flower/pull/3512), " -"[#3408](https://github.com/adap/flower/pull/3408))" +"**Replace** `flower-superlink` **CLI option** `--certificates` **with** `--" +"ssl-ca-certfile` **,** `--ssl-certfile` **and** `--ssl-keyfile` ([#3512]" +"(https://github.com/adap/flower/pull/3512), [#3408](https://github.com/adap/" +"flower/pull/3408))" msgstr "" #: ../../source/ref-changelog.md:93 msgid "" "SSL-related `flower-superlink` CLI arguments were restructured in an " "incompatible way. Instead of passing a single `--certificates` flag with " -"three values, you now need to pass three flags (`--ssl-ca-certfile`, " -"`--ssl-certfile` and `--ssl-keyfile`) with one value each. Check out the " -"[SSL connections](https://flower.ai/docs/framework/how-to-enable-ssl-" -"connections.html) documentation page for details." +"three values, you now need to pass three flags (`--ssl-ca-certfile`, `--ssl-" +"certfile` and `--ssl-keyfile`) with one value each. Check out the [SSL " +"connections](https://flower.ai/docs/framework/how-to-enable-ssl-connections." +"html) documentation page for details." msgstr "" #: ../../source/ref-changelog.md:95 msgid "" -"**Remove SuperLink** `--vce` **option** " -"([#3513](https://github.com/adap/flower/pull/3513))" +"**Remove SuperLink** `--vce` **option** ([#3513](https://github.com/adap/" +"flower/pull/3513))" msgstr "" #: ../../source/ref-changelog.md:97 msgid "" -"Instead of separately starting a SuperLink and a `ServerApp` for " -"simulation, simulations must now be started using the single `flower-" -"simulation` command." +"Instead of separately starting a SuperLink and a `ServerApp` for simulation, " +"simulations must now be started using the single `flower-simulation` command." msgstr "" #: ../../source/ref-changelog.md:99 msgid "" -"**Merge** `--grpc-rere` **and** `--rest` **SuperLink options** " -"([#3527](https://github.com/adap/flower/pull/3527))" +"**Merge** `--grpc-rere` **and** `--rest` **SuperLink options** ([#3527]" +"(https://github.com/adap/flower/pull/3527))" msgstr "" #: ../../source/ref-changelog.md:101 msgid "" -"To simplify the usage of `flower-superlink`, previously separate sets of " -"CLI options for gRPC and REST were merged into one unified set of " -"options. Consult the [Flower CLI reference " -"documentation](https://flower.ai/docs/framework/ref-api-cli.html) for " -"details." +"To simplify the usage of `flower-superlink`, previously separate sets of CLI " +"options for gRPC and REST were merged into one unified set of options. " +"Consult the [Flower CLI reference documentation](https://flower.ai/docs/" +"framework/ref-api-cli.html) for details." msgstr "" #: ../../source/ref-changelog.md:103 @@ -15279,237 +15902,212 @@ msgstr "" #: ../../source/ref-changelog.md:109 msgid "" "`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata " -"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear " -"Ashimine`, `Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, " -"`Sebastian van der Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, " -"`tabdar-khan` " +"Nugraha`, `Danny`, `Gustavo Bertoli`, `Heng Pan`, `Ikko Eltociear Ashimine`, " +"`Jack Cook`, `Javier`, `Raj Parekh`, `Robert Steiner`, `Sebastian van der " +"Voort`, `Taner Topal`, `Yan Gao`, `mohammadnaseri`, `tabdar-khan` " msgstr "" #: ../../source/ref-changelog.md:113 msgid "" -"**Introduce Flower Next high-level API (stable)** " -"([#3002](https://github.com/adap/flower/pull/3002), " -"[#2934](https://github.com/adap/flower/pull/2934), " -"[#2958](https://github.com/adap/flower/pull/2958), " -"[#3173](https://github.com/adap/flower/pull/3173), " -"[#3174](https://github.com/adap/flower/pull/3174), " -"[#2923](https://github.com/adap/flower/pull/2923), " -"[#2691](https://github.com/adap/flower/pull/2691), " -"[#3079](https://github.com/adap/flower/pull/3079), " -"[#2961](https://github.com/adap/flower/pull/2961), " -"[#2924](https://github.com/adap/flower/pull/2924), " -"[#3166](https://github.com/adap/flower/pull/3166), " -"[#3031](https://github.com/adap/flower/pull/3031), " -"[#3057](https://github.com/adap/flower/pull/3057), " -"[#3000](https://github.com/adap/flower/pull/3000), " -"[#3113](https://github.com/adap/flower/pull/3113), " -"[#2957](https://github.com/adap/flower/pull/2957), " -"[#3183](https://github.com/adap/flower/pull/3183), " -"[#3180](https://github.com/adap/flower/pull/3180), " -"[#3035](https://github.com/adap/flower/pull/3035), " -"[#3189](https://github.com/adap/flower/pull/3189), " -"[#3185](https://github.com/adap/flower/pull/3185), " -"[#3190](https://github.com/adap/flower/pull/3190), " -"[#3191](https://github.com/adap/flower/pull/3191), " -"[#3195](https://github.com/adap/flower/pull/3195), " -"[#3197](https://github.com/adap/flower/pull/3197))" +"**Introduce Flower Next high-level API (stable)** ([#3002](https://github." +"com/adap/flower/pull/3002), [#2934](https://github.com/adap/flower/" +"pull/2934), [#2958](https://github.com/adap/flower/pull/2958), [#3173]" +"(https://github.com/adap/flower/pull/3173), [#3174](https://github.com/adap/" +"flower/pull/3174), [#2923](https://github.com/adap/flower/pull/2923), [#2691]" +"(https://github.com/adap/flower/pull/2691), [#3079](https://github.com/adap/" +"flower/pull/3079), [#2961](https://github.com/adap/flower/pull/2961), [#2924]" +"(https://github.com/adap/flower/pull/2924), [#3166](https://github.com/adap/" +"flower/pull/3166), [#3031](https://github.com/adap/flower/pull/3031), [#3057]" +"(https://github.com/adap/flower/pull/3057), [#3000](https://github.com/adap/" +"flower/pull/3000), [#3113](https://github.com/adap/flower/pull/3113), [#2957]" +"(https://github.com/adap/flower/pull/2957), [#3183](https://github.com/adap/" +"flower/pull/3183), [#3180](https://github.com/adap/flower/pull/3180), [#3035]" +"(https://github.com/adap/flower/pull/3035), [#3189](https://github.com/adap/" +"flower/pull/3189), [#3185](https://github.com/adap/flower/pull/3185), [#3190]" +"(https://github.com/adap/flower/pull/3190), [#3191](https://github.com/adap/" +"flower/pull/3191), [#3195](https://github.com/adap/flower/pull/3195), [#3197]" +"(https://github.com/adap/flower/pull/3197))" msgstr "" #: ../../source/ref-changelog.md:115 msgid "" "The Flower Next high-level API is stable! Flower Next is the future of " -"Flower - all new features (like Flower Mods) will be built on top of it. " -"You can start to migrate your existing projects to Flower Next by using " -"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or " -"`quickstart-tensorflow`, a detailed migration guide will follow shortly)." -" Flower Next allows you to run multiple projects concurrently (we call " -"this multi-run) and execute the same project in either simulation " -"environments or deployment environments without having to change a single" -" line of code. The best part? It's fully compatible with existing Flower " -"projects that use `Strategy`, `NumPyClient` & co." +"Flower - all new features (like Flower Mods) will be built on top of it. You " +"can start to migrate your existing projects to Flower Next by using " +"`ServerApp` and `ClientApp` (check out `quickstart-pytorch` or `quickstart-" +"tensorflow`, a detailed migration guide will follow shortly). Flower Next " +"allows you to run multiple projects concurrently (we call this multi-run) " +"and execute the same project in either simulation environments or deployment " +"environments without having to change a single line of code. The best part? " +"It's fully compatible with existing Flower projects that use `Strategy`, " +"`NumPyClient` & co." msgstr "" #: ../../source/ref-changelog.md:117 msgid "" -"**Introduce Flower Next low-level API (preview)** " -"([#3062](https://github.com/adap/flower/pull/3062), " -"[#3034](https://github.com/adap/flower/pull/3034), " -"[#3069](https://github.com/adap/flower/pull/3069))" +"**Introduce Flower Next low-level API (preview)** ([#3062](https://github." +"com/adap/flower/pull/3062), [#3034](https://github.com/adap/flower/" +"pull/3034), [#3069](https://github.com/adap/flower/pull/3069))" msgstr "" #: ../../source/ref-changelog.md:119 msgid "" "In addition to the Flower Next *high-level* API that uses `Strategy`, " -"`NumPyClient` & co, Flower 1.8 also comes with a preview version of the " -"new Flower Next *low-level* API. The low-level API allows for granular " -"control of every aspect of the learning process by sending/receiving " -"individual messages to/from client nodes. The new `ServerApp` supports " -"registering a custom `main` function that allows writing custom training " -"loops for methods like async FL, cyclic training, or federated analytics." -" The new `ClientApp` supports registering `train`, `evaluate` and `query`" -" functions that can access the raw message received from the `ServerApp`." -" New abstractions like `RecordSet`, `Message` and `Context` further " -"enable sending multiple models, multiple sets of config values and " -"metrics, stateful computations on the client node and implementations of " -"custom SMPC protocols, to name just a few." +"`NumPyClient` & co, Flower 1.8 also comes with a preview version of the new " +"Flower Next *low-level* API. The low-level API allows for granular control " +"of every aspect of the learning process by sending/receiving individual " +"messages to/from client nodes. The new `ServerApp` supports registering a " +"custom `main` function that allows writing custom training loops for methods " +"like async FL, cyclic training, or federated analytics. The new `ClientApp` " +"supports registering `train`, `evaluate` and `query` functions that can " +"access the raw message received from the `ServerApp`. New abstractions like " +"`RecordSet`, `Message` and `Context` further enable sending multiple models, " +"multiple sets of config values and metrics, stateful computations on the " +"client node and implementations of custom SMPC protocols, to name just a few." msgstr "" #: ../../source/ref-changelog.md:121 msgid "" -"**Introduce Flower Mods (preview)** " -"([#3054](https://github.com/adap/flower/pull/3054), " -"[#2911](https://github.com/adap/flower/pull/2911), " -"[#3083](https://github.com/adap/flower/pull/3083))" +"**Introduce Flower Mods (preview)** ([#3054](https://github.com/adap/flower/" +"pull/3054), [#2911](https://github.com/adap/flower/pull/2911), [#3083]" +"(https://github.com/adap/flower/pull/3083))" msgstr "" #: ../../source/ref-changelog.md:123 msgid "" "Flower Modifiers (we call them Mods) can intercept messages and analyze, " -"edit or handle them directly. Mods can be used to develop pluggable " -"modules that work across different projects. Flower 1.8 already includes " -"mods to log the size of a message, the number of parameters sent over the" -" network, differential privacy with fixed clipping and adaptive clipping," -" local differential privacy and secure aggregation protocols SecAgg and " -"SecAgg+. The Flower Mods API is released as a preview, but researchers " -"can already use it to experiment with arbirtrary SMPC protocols." +"edit or handle them directly. Mods can be used to develop pluggable modules " +"that work across different projects. Flower 1.8 already includes mods to log " +"the size of a message, the number of parameters sent over the network, " +"differential privacy with fixed clipping and adaptive clipping, local " +"differential privacy and secure aggregation protocols SecAgg and SecAgg+. " +"The Flower Mods API is released as a preview, but researchers can already " +"use it to experiment with arbirtrary SMPC protocols." msgstr "" #: ../../source/ref-changelog.md:125 msgid "" -"**Fine-tune LLMs with LLM FlowerTune** " -"([#3029](https://github.com/adap/flower/pull/3029), " -"[#3089](https://github.com/adap/flower/pull/3089), " -"[#3092](https://github.com/adap/flower/pull/3092), " -"[#3100](https://github.com/adap/flower/pull/3100), " -"[#3114](https://github.com/adap/flower/pull/3114), " -"[#3162](https://github.com/adap/flower/pull/3162), " -"[#3172](https://github.com/adap/flower/pull/3172))" +"**Fine-tune LLMs with LLM FlowerTune** ([#3029](https://github.com/adap/" +"flower/pull/3029), [#3089](https://github.com/adap/flower/pull/3089), [#3092]" +"(https://github.com/adap/flower/pull/3092), [#3100](https://github.com/adap/" +"flower/pull/3100), [#3114](https://github.com/adap/flower/pull/3114), [#3162]" +"(https://github.com/adap/flower/pull/3162), [#3172](https://github.com/adap/" +"flower/pull/3172))" msgstr "" #: ../../source/ref-changelog.md:127 msgid "" -"We are introducing LLM FlowerTune, an introductory example that " -"demonstrates federated LLM fine-tuning of pre-trained Llama2 models on " -"the Alpaca-GPT4 dataset. The example is built to be easily adapted to use" -" different models and/or datasets. Read our blog post [LLM FlowerTune: " -"Federated LLM Fine-tuning with Flower](https://flower.ai/blog/2024-03-14" -"-llm-flowertune-federated-llm-finetuning-with-flower/) for more details." +"We are introducing LLM FlowerTune, an introductory example that demonstrates " +"federated LLM fine-tuning of pre-trained Llama2 models on the Alpaca-GPT4 " +"dataset. The example is built to be easily adapted to use different models " +"and/or datasets. Read our blog post [LLM FlowerTune: Federated LLM Fine-" +"tuning with Flower](https://flower.ai/blog/2024-03-14-llm-flowertune-" +"federated-llm-finetuning-with-flower/) for more details." msgstr "" #: ../../source/ref-changelog.md:129 msgid "" -"**Introduce built-in Differential Privacy (preview)** " -"([#2798](https://github.com/adap/flower/pull/2798), " -"[#2959](https://github.com/adap/flower/pull/2959), " -"[#3038](https://github.com/adap/flower/pull/3038), " -"[#3147](https://github.com/adap/flower/pull/3147), " -"[#2909](https://github.com/adap/flower/pull/2909), " -"[#2893](https://github.com/adap/flower/pull/2893), " -"[#2892](https://github.com/adap/flower/pull/2892), " -"[#3039](https://github.com/adap/flower/pull/3039), " -"[#3074](https://github.com/adap/flower/pull/3074))" +"**Introduce built-in Differential Privacy (preview)** ([#2798](https://" +"github.com/adap/flower/pull/2798), [#2959](https://github.com/adap/flower/" +"pull/2959), [#3038](https://github.com/adap/flower/pull/3038), [#3147]" +"(https://github.com/adap/flower/pull/3147), [#2909](https://github.com/adap/" +"flower/pull/2909), [#2893](https://github.com/adap/flower/pull/2893), [#2892]" +"(https://github.com/adap/flower/pull/2892), [#3039](https://github.com/adap/" +"flower/pull/3039), [#3074](https://github.com/adap/flower/pull/3074))" msgstr "" #: ../../source/ref-changelog.md:131 msgid "" "Built-in Differential Privacy is here! Flower supports both central and " -"local differential privacy (DP). Central DP can be configured with either" -" fixed or adaptive clipping. The clipping can happen either on the " -"server-side or the client-side. Local DP does both clipping and noising " -"on the client-side. A new documentation page [explains Differential " -"Privacy approaches](https://flower.ai/docs/framework/explanation-" -"differential-privacy.html) and a new how-to guide describes [how to use " -"the new Differential Privacy components](https://flower.ai/docs/framework" -"/how-to-use-differential-privacy.html) in Flower." +"local differential privacy (DP). Central DP can be configured with either " +"fixed or adaptive clipping. The clipping can happen either on the server-" +"side or the client-side. Local DP does both clipping and noising on the " +"client-side. A new documentation page [explains Differential Privacy " +"approaches](https://flower.ai/docs/framework/explanation-differential-" +"privacy.html) and a new how-to guide describes [how to use the new " +"Differential Privacy components](https://flower.ai/docs/framework/how-to-use-" +"differential-privacy.html) in Flower." msgstr "" #: ../../source/ref-changelog.md:133 msgid "" -"**Introduce built-in Secure Aggregation (preview)** " -"([#3120](https://github.com/adap/flower/pull/3120), " -"[#3110](https://github.com/adap/flower/pull/3110), " -"[#3108](https://github.com/adap/flower/pull/3108))" +"**Introduce built-in Secure Aggregation (preview)** ([#3120](https://github." +"com/adap/flower/pull/3120), [#3110](https://github.com/adap/flower/" +"pull/3110), [#3108](https://github.com/adap/flower/pull/3108))" msgstr "" #: ../../source/ref-changelog.md:135 msgid "" -"Built-in Secure Aggregation is here! Flower now supports different secure" -" aggregation protocols out-of-the-box. The best part? You can add secure " -"aggregation to your Flower projects with only a few lines of code. In " -"this initial release, we inlcude support for SecAgg and SecAgg+, but more" -" protocols will be implemented shortly. We'll also add detailed docs that" -" explain secure aggregation and how to use it in Flower. You can already " +"Built-in Secure Aggregation is here! Flower now supports different secure " +"aggregation protocols out-of-the-box. The best part? You can add secure " +"aggregation to your Flower projects with only a few lines of code. In this " +"initial release, we inlcude support for SecAgg and SecAgg+, but more " +"protocols will be implemented shortly. We'll also add detailed docs that " +"explain secure aggregation and how to use it in Flower. You can already " "check out the new code example that shows how to use Flower to easily " -"combine Federated Learning, Differential Privacy and Secure Aggregation " -"in the same project." +"combine Federated Learning, Differential Privacy and Secure Aggregation in " +"the same project." msgstr "" #: ../../source/ref-changelog.md:137 msgid "" -"**Introduce** `flwr` **CLI (preview)** " -"([#2942](https://github.com/adap/flower/pull/2942), " -"[#3055](https://github.com/adap/flower/pull/3055), " -"[#3111](https://github.com/adap/flower/pull/3111), " -"[#3130](https://github.com/adap/flower/pull/3130), " -"[#3136](https://github.com/adap/flower/pull/3136), " -"[#3094](https://github.com/adap/flower/pull/3094), " -"[#3059](https://github.com/adap/flower/pull/3059), " -"[#3049](https://github.com/adap/flower/pull/3049), " -"[#3142](https://github.com/adap/flower/pull/3142))" +"**Introduce** `flwr` **CLI (preview)** ([#2942](https://github.com/adap/" +"flower/pull/2942), [#3055](https://github.com/adap/flower/pull/3055), [#3111]" +"(https://github.com/adap/flower/pull/3111), [#3130](https://github.com/adap/" +"flower/pull/3130), [#3136](https://github.com/adap/flower/pull/3136), [#3094]" +"(https://github.com/adap/flower/pull/3094), [#3059](https://github.com/adap/" +"flower/pull/3059), [#3049](https://github.com/adap/flower/pull/3049), [#3142]" +"(https://github.com/adap/flower/pull/3142))" msgstr "" #: ../../source/ref-changelog.md:139 msgid "" -"A new `flwr` CLI command allows creating new Flower projects (`flwr new`)" -" and then running them using the Simulation Engine (`flwr run`)." +"A new `flwr` CLI command allows creating new Flower projects (`flwr new`) " +"and then running them using the Simulation Engine (`flwr run`)." msgstr "" #: ../../source/ref-changelog.md:141 msgid "" -"**Introduce Flower Next Simulation Engine** " -"([#3024](https://github.com/adap/flower/pull/3024), " -"[#3061](https://github.com/adap/flower/pull/3061), " -"[#2997](https://github.com/adap/flower/pull/2997), " -"[#2783](https://github.com/adap/flower/pull/2783), " -"[#3184](https://github.com/adap/flower/pull/3184), " -"[#3075](https://github.com/adap/flower/pull/3075), " -"[#3047](https://github.com/adap/flower/pull/3047), " -"[#2998](https://github.com/adap/flower/pull/2998), " -"[#3009](https://github.com/adap/flower/pull/3009), " -"[#3008](https://github.com/adap/flower/pull/3008))" +"**Introduce Flower Next Simulation Engine** ([#3024](https://github.com/adap/" +"flower/pull/3024), [#3061](https://github.com/adap/flower/pull/3061), [#2997]" +"(https://github.com/adap/flower/pull/2997), [#2783](https://github.com/adap/" +"flower/pull/2783), [#3184](https://github.com/adap/flower/pull/3184), [#3075]" +"(https://github.com/adap/flower/pull/3075), [#3047](https://github.com/adap/" +"flower/pull/3047), [#2998](https://github.com/adap/flower/pull/2998), [#3009]" +"(https://github.com/adap/flower/pull/3009), [#3008](https://github.com/adap/" +"flower/pull/3008))" msgstr "" #: ../../source/ref-changelog.md:143 msgid "" -"The Flower Simulation Engine can now run Flower Next projects. For " -"notebook environments, there's also a new `run_simulation` function that " -"can run `ServerApp` and `ClientApp`." +"The Flower Simulation Engine can now run Flower Next projects. For notebook " +"environments, there's also a new `run_simulation` function that can run " +"`ServerApp` and `ClientApp`." msgstr "" #: ../../source/ref-changelog.md:145 msgid "" -"**Handle SuperNode connection errors** " -"([#2969](https://github.com/adap/flower/pull/2969))" +"**Handle SuperNode connection errors** ([#2969](https://github.com/adap/" +"flower/pull/2969))" msgstr "" #: ../../source/ref-changelog.md:147 msgid "" -"A SuperNode will now try to reconnect indefinitely to the SuperLink in " -"case of connection errors. The arguments `--max-retries` and `--max-wait-" -"time` can now be passed to the `flower-client-app` command. `--max-" -"retries` will define the number of tentatives the client should make " -"before it gives up trying to reconnect to the SuperLink, and, `--max-" -"wait-time` defines the time before the SuperNode gives up trying to " -"reconnect to the SuperLink." +"A SuperNode will now try to reconnect indefinitely to the SuperLink in case " +"of connection errors. The arguments `--max-retries` and `--max-wait-time` " +"can now be passed to the `flower-client-app` command. `--max-retries` will " +"define the number of tentatives the client should make before it gives up " +"trying to reconnect to the SuperLink, and, `--max-wait-time` defines the " +"time before the SuperNode gives up trying to reconnect to the SuperLink." msgstr "" #: ../../source/ref-changelog.md:149 msgid "" -"**General updates to Flower Baselines** " -"([#2904](https://github.com/adap/flower/pull/2904), " -"[#2482](https://github.com/adap/flower/pull/2482), " -"[#2985](https://github.com/adap/flower/pull/2985), " -"[#2968](https://github.com/adap/flower/pull/2968))" +"**General updates to Flower Baselines** ([#2904](https://github.com/adap/" +"flower/pull/2904), [#2482](https://github.com/adap/flower/pull/2482), [#2985]" +"(https://github.com/adap/flower/pull/2985), [#2968](https://github.com/adap/" +"flower/pull/2968))" msgstr "" #: ../../source/ref-changelog.md:151 @@ -15520,133 +16118,100 @@ msgstr "" #: ../../source/ref-changelog.md:153 msgid "" -"**Improve documentation and translations** " -"([#3050](https://github.com/adap/flower/pull/3050), " -"[#3044](https://github.com/adap/flower/pull/3044), " -"[#3043](https://github.com/adap/flower/pull/3043), " -"[#2986](https://github.com/adap/flower/pull/2986), " -"[#3041](https://github.com/adap/flower/pull/3041), " -"[#3046](https://github.com/adap/flower/pull/3046), " -"[#3042](https://github.com/adap/flower/pull/3042), " -"[#2978](https://github.com/adap/flower/pull/2978), " -"[#2952](https://github.com/adap/flower/pull/2952), " -"[#3167](https://github.com/adap/flower/pull/3167), " -"[#2953](https://github.com/adap/flower/pull/2953), " -"[#3045](https://github.com/adap/flower/pull/3045), " -"[#2654](https://github.com/adap/flower/pull/2654), " -"[#3082](https://github.com/adap/flower/pull/3082), " -"[#2990](https://github.com/adap/flower/pull/2990), " -"[#2989](https://github.com/adap/flower/pull/2989))" +"**Improve documentation and translations** ([#3050](https://github.com/adap/" +"flower/pull/3050), [#3044](https://github.com/adap/flower/pull/3044), [#3043]" +"(https://github.com/adap/flower/pull/3043), [#2986](https://github.com/adap/" +"flower/pull/2986), [#3041](https://github.com/adap/flower/pull/3041), [#3046]" +"(https://github.com/adap/flower/pull/3046), [#3042](https://github.com/adap/" +"flower/pull/3042), [#2978](https://github.com/adap/flower/pull/2978), [#2952]" +"(https://github.com/adap/flower/pull/2952), [#3167](https://github.com/adap/" +"flower/pull/3167), [#2953](https://github.com/adap/flower/pull/2953), [#3045]" +"(https://github.com/adap/flower/pull/3045), [#2654](https://github.com/adap/" +"flower/pull/2654), [#3082](https://github.com/adap/flower/pull/3082), [#2990]" +"(https://github.com/adap/flower/pull/2990), [#2989](https://github.com/adap/" +"flower/pull/2989))" msgstr "" #: ../../source/ref-changelog.md:155 msgid "" "As usual, we merged many smaller and larger improvements to the " -"documentation. A special thank you goes to [Sebastian van der " -"Voort](https://github.com/svdvoort) for landing a big documentation PR!" +"documentation. A special thank you goes to [Sebastian van der Voort](https://" +"github.com/svdvoort) for landing a big documentation PR!" msgstr "" #: ../../source/ref-changelog.md:157 msgid "" -"**General updates to Flower Examples** " -"([3134](https://github.com/adap/flower/pull/3134), " -"[2996](https://github.com/adap/flower/pull/2996), " -"[2930](https://github.com/adap/flower/pull/2930), " -"[2967](https://github.com/adap/flower/pull/2967), " -"[2467](https://github.com/adap/flower/pull/2467), " -"[2910](https://github.com/adap/flower/pull/2910), " -"[#2918](https://github.com/adap/flower/pull/2918), " -"[#2773](https://github.com/adap/flower/pull/2773), " -"[#3063](https://github.com/adap/flower/pull/3063), " -"[#3116](https://github.com/adap/flower/pull/3116), " -"[#3117](https://github.com/adap/flower/pull/3117))" +"**General updates to Flower Examples** ([3134](https://github.com/adap/" +"flower/pull/3134), [2996](https://github.com/adap/flower/pull/2996), [2930]" +"(https://github.com/adap/flower/pull/2930), [2967](https://github.com/adap/" +"flower/pull/2967), [2467](https://github.com/adap/flower/pull/2467), [2910]" +"(https://github.com/adap/flower/pull/2910), [#2918](https://github.com/adap/" +"flower/pull/2918), [#2773](https://github.com/adap/flower/pull/2773), [#3063]" +"(https://github.com/adap/flower/pull/3063), [#3116](https://github.com/adap/" +"flower/pull/3116), [#3117](https://github.com/adap/flower/pull/3117))" msgstr "" #: ../../source/ref-changelog.md:159 msgid "" -"Two new examples show federated training of a Vision Transformer (ViT) " -"and federated learning in a medical context using the popular MONAI " -"library. `quickstart-pytorch` and `quickstart-tensorflow` demonstrate the" -" new Flower Next `ServerApp` and `ClientApp`. Many other examples " -"received considerable updates as well." +"Two new examples show federated training of a Vision Transformer (ViT) and " +"federated learning in a medical context using the popular MONAI library. " +"`quickstart-pytorch` and `quickstart-tensorflow` demonstrate the new Flower " +"Next `ServerApp` and `ClientApp`. Many other examples received considerable " +"updates as well." msgstr "" #: ../../source/ref-changelog.md:161 msgid "" -"**General improvements** " -"([#3171](https://github.com/adap/flower/pull/3171), " -"[3099](https://github.com/adap/flower/pull/3099), " -"[3003](https://github.com/adap/flower/pull/3003), " -"[3145](https://github.com/adap/flower/pull/3145), " -"[3017](https://github.com/adap/flower/pull/3017), " -"[3085](https://github.com/adap/flower/pull/3085), " -"[3012](https://github.com/adap/flower/pull/3012), " -"[3119](https://github.com/adap/flower/pull/3119), " -"[2991](https://github.com/adap/flower/pull/2991), " -"[2970](https://github.com/adap/flower/pull/2970), " -"[2980](https://github.com/adap/flower/pull/2980), " -"[3086](https://github.com/adap/flower/pull/3086), " -"[2932](https://github.com/adap/flower/pull/2932), " -"[2928](https://github.com/adap/flower/pull/2928), " -"[2941](https://github.com/adap/flower/pull/2941), " -"[2933](https://github.com/adap/flower/pull/2933), " -"[3181](https://github.com/adap/flower/pull/3181), " -"[2973](https://github.com/adap/flower/pull/2973), " -"[2992](https://github.com/adap/flower/pull/2992), " -"[2915](https://github.com/adap/flower/pull/2915), " -"[3040](https://github.com/adap/flower/pull/3040), " -"[3022](https://github.com/adap/flower/pull/3022), " -"[3032](https://github.com/adap/flower/pull/3032), " -"[2902](https://github.com/adap/flower/pull/2902), " -"[2931](https://github.com/adap/flower/pull/2931), " -"[3005](https://github.com/adap/flower/pull/3005), " -"[3132](https://github.com/adap/flower/pull/3132), " -"[3115](https://github.com/adap/flower/pull/3115), " -"[2944](https://github.com/adap/flower/pull/2944), " -"[3064](https://github.com/adap/flower/pull/3064), " -"[3106](https://github.com/adap/flower/pull/3106), " -"[2974](https://github.com/adap/flower/pull/2974), " -"[3178](https://github.com/adap/flower/pull/3178), " -"[2993](https://github.com/adap/flower/pull/2993), " -"[3186](https://github.com/adap/flower/pull/3186), " -"[3091](https://github.com/adap/flower/pull/3091), " -"[3125](https://github.com/adap/flower/pull/3125), " -"[3093](https://github.com/adap/flower/pull/3093), " -"[3013](https://github.com/adap/flower/pull/3013), " -"[3033](https://github.com/adap/flower/pull/3033), " -"[3133](https://github.com/adap/flower/pull/3133), " -"[3068](https://github.com/adap/flower/pull/3068), " -"[2916](https://github.com/adap/flower/pull/2916), " -"[2975](https://github.com/adap/flower/pull/2975), " -"[2984](https://github.com/adap/flower/pull/2984), " -"[2846](https://github.com/adap/flower/pull/2846), " -"[3077](https://github.com/adap/flower/pull/3077), " -"[3143](https://github.com/adap/flower/pull/3143), " -"[2921](https://github.com/adap/flower/pull/2921), " -"[3101](https://github.com/adap/flower/pull/3101), " -"[2927](https://github.com/adap/flower/pull/2927), " -"[2995](https://github.com/adap/flower/pull/2995), " -"[2972](https://github.com/adap/flower/pull/2972), " -"[2912](https://github.com/adap/flower/pull/2912), " -"[3065](https://github.com/adap/flower/pull/3065), " -"[3028](https://github.com/adap/flower/pull/3028), " -"[2922](https://github.com/adap/flower/pull/2922), " -"[2982](https://github.com/adap/flower/pull/2982), " -"[2914](https://github.com/adap/flower/pull/2914), " -"[3179](https://github.com/adap/flower/pull/3179), " -"[3080](https://github.com/adap/flower/pull/3080), " -"[2994](https://github.com/adap/flower/pull/2994), " -"[3187](https://github.com/adap/flower/pull/3187), " -"[2926](https://github.com/adap/flower/pull/2926), " -"[3018](https://github.com/adap/flower/pull/3018), " -"[3144](https://github.com/adap/flower/pull/3144), " -"[3011](https://github.com/adap/flower/pull/3011), " -"[#3152](https://github.com/adap/flower/pull/3152), " -"[#2836](https://github.com/adap/flower/pull/2836), " -"[#2929](https://github.com/adap/flower/pull/2929), " -"[#2943](https://github.com/adap/flower/pull/2943), " -"[#2955](https://github.com/adap/flower/pull/2955), " -"[#2954](https://github.com/adap/flower/pull/2954))" +"**General improvements** ([#3171](https://github.com/adap/flower/pull/3171), " +"[3099](https://github.com/adap/flower/pull/3099), [3003](https://github.com/" +"adap/flower/pull/3003), [3145](https://github.com/adap/flower/pull/3145), " +"[3017](https://github.com/adap/flower/pull/3017), [3085](https://github.com/" +"adap/flower/pull/3085), [3012](https://github.com/adap/flower/pull/3012), " +"[3119](https://github.com/adap/flower/pull/3119), [2991](https://github.com/" +"adap/flower/pull/2991), [2970](https://github.com/adap/flower/pull/2970), " +"[2980](https://github.com/adap/flower/pull/2980), [3086](https://github.com/" +"adap/flower/pull/3086), [2932](https://github.com/adap/flower/pull/2932), " +"[2928](https://github.com/adap/flower/pull/2928), [2941](https://github.com/" +"adap/flower/pull/2941), [2933](https://github.com/adap/flower/pull/2933), " +"[3181](https://github.com/adap/flower/pull/3181), [2973](https://github.com/" +"adap/flower/pull/2973), [2992](https://github.com/adap/flower/pull/2992), " +"[2915](https://github.com/adap/flower/pull/2915), [3040](https://github.com/" +"adap/flower/pull/3040), [3022](https://github.com/adap/flower/pull/3022), " +"[3032](https://github.com/adap/flower/pull/3032), [2902](https://github.com/" +"adap/flower/pull/2902), [2931](https://github.com/adap/flower/pull/2931), " +"[3005](https://github.com/adap/flower/pull/3005), [3132](https://github.com/" +"adap/flower/pull/3132), [3115](https://github.com/adap/flower/pull/3115), " +"[2944](https://github.com/adap/flower/pull/2944), [3064](https://github.com/" +"adap/flower/pull/3064), [3106](https://github.com/adap/flower/pull/3106), " +"[2974](https://github.com/adap/flower/pull/2974), [3178](https://github.com/" +"adap/flower/pull/3178), [2993](https://github.com/adap/flower/pull/2993), " +"[3186](https://github.com/adap/flower/pull/3186), [3091](https://github.com/" +"adap/flower/pull/3091), [3125](https://github.com/adap/flower/pull/3125), " +"[3093](https://github.com/adap/flower/pull/3093), [3013](https://github.com/" +"adap/flower/pull/3013), [3033](https://github.com/adap/flower/pull/3033), " +"[3133](https://github.com/adap/flower/pull/3133), [3068](https://github.com/" +"adap/flower/pull/3068), [2916](https://github.com/adap/flower/pull/2916), " +"[2975](https://github.com/adap/flower/pull/2975), [2984](https://github.com/" +"adap/flower/pull/2984), [2846](https://github.com/adap/flower/pull/2846), " +"[3077](https://github.com/adap/flower/pull/3077), [3143](https://github.com/" +"adap/flower/pull/3143), [2921](https://github.com/adap/flower/pull/2921), " +"[3101](https://github.com/adap/flower/pull/3101), [2927](https://github.com/" +"adap/flower/pull/2927), [2995](https://github.com/adap/flower/pull/2995), " +"[2972](https://github.com/adap/flower/pull/2972), [2912](https://github.com/" +"adap/flower/pull/2912), [3065](https://github.com/adap/flower/pull/3065), " +"[3028](https://github.com/adap/flower/pull/3028), [2922](https://github.com/" +"adap/flower/pull/2922), [2982](https://github.com/adap/flower/pull/2982), " +"[2914](https://github.com/adap/flower/pull/2914), [3179](https://github.com/" +"adap/flower/pull/3179), [3080](https://github.com/adap/flower/pull/3080), " +"[2994](https://github.com/adap/flower/pull/2994), [3187](https://github.com/" +"adap/flower/pull/3187), [2926](https://github.com/adap/flower/pull/2926), " +"[3018](https://github.com/adap/flower/pull/3018), [3144](https://github.com/" +"adap/flower/pull/3144), [3011](https://github.com/adap/flower/pull/3011), " +"[#3152](https://github.com/adap/flower/pull/3152), [#2836](https://github." +"com/adap/flower/pull/2836), [#2929](https://github.com/adap/flower/" +"pull/2929), [#2943](https://github.com/adap/flower/pull/2943), [#2955]" +"(https://github.com/adap/flower/pull/2955), [#2954](https://github.com/adap/" +"flower/pull/2954))" msgstr "" #: ../../source/ref-changelog.md:165 ../../source/ref-changelog.md:442 @@ -15661,97 +16226,91 @@ msgstr "" #: ../../source/ref-changelog.md:173 msgid "" -"`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles " -"Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo " -"Gabrielli`, `Gustavo Bertoli`, `HelinLin`, `Heng Pan`, `Javier`, `M S " -"Chaitanya Kumar`, `Mohammad Naseri`, `Nikos Vlachakis`, `Pritam Neog`, " -"`Robert Kuska`, `Robert Steiner`, `Taner Topal`, `Yahia Salaheldin " -"Shaaban`, `Yan Gao`, `Yasar Abbas` " +"`Aasheesh Singh`, `Adam Narozniak`, `Aml Hassan Esmil`, `Charles Beauville`, " +"`Daniel J. Beutel`, `Daniel Nata Nugraha`, `Edoardo Gabrielli`, `Gustavo " +"Bertoli`, `HelinLin`, `Heng Pan`, `Javier`, `M S Chaitanya Kumar`, `Mohammad " +"Naseri`, `Nikos Vlachakis`, `Pritam Neog`, `Robert Kuska`, `Robert Steiner`, " +"`Taner Topal`, `Yahia Salaheldin Shaaban`, `Yan Gao`, `Yasar Abbas` " msgstr "" #: ../../source/ref-changelog.md:177 msgid "" -"**Introduce stateful clients (experimental)** " -"([#2770](https://github.com/adap/flower/pull/2770), " -"[#2686](https://github.com/adap/flower/pull/2686), " -"[#2696](https://github.com/adap/flower/pull/2696), " -"[#2643](https://github.com/adap/flower/pull/2643), " -"[#2769](https://github.com/adap/flower/pull/2769))" +"**Introduce stateful clients (experimental)** ([#2770](https://github.com/" +"adap/flower/pull/2770), [#2686](https://github.com/adap/flower/pull/2686), " +"[#2696](https://github.com/adap/flower/pull/2696), [#2643](https://github." +"com/adap/flower/pull/2643), [#2769](https://github.com/adap/flower/" +"pull/2769))" msgstr "" #: ../../source/ref-changelog.md:179 msgid "" "Subclasses of `Client` and `NumPyClient` can now store local state that " "remains on the client. Let's start with the highlight first: this new " -"feature is compatible with both simulated clients (via " -"`start_simulation`) and networked clients (via `start_client`). It's also" -" the first preview of new abstractions like `Context` and `RecordSet`. " -"Clients can access state of type `RecordSet` via `state: RecordSet = " -"self.context.state`. Changes to this `RecordSet` are preserved across " -"different rounds of execution to enable stateful computations in a " -"unified way across simulation and deployment." +"feature is compatible with both simulated clients (via `start_simulation`) " +"and networked clients (via `start_client`). It's also the first preview of " +"new abstractions like `Context` and `RecordSet`. Clients can access state of " +"type `RecordSet` via `state: RecordSet = self.context.state`. Changes to " +"this `RecordSet` are preserved across different rounds of execution to " +"enable stateful computations in a unified way across simulation and " +"deployment." msgstr "" #: ../../source/ref-changelog.md:181 msgid "" -"**Improve performance** " -"([#2293](https://github.com/adap/flower/pull/2293))" +"**Improve performance** ([#2293](https://github.com/adap/flower/pull/2293))" msgstr "" #: ../../source/ref-changelog.md:183 msgid "" -"Flower is faster than ever. All `FedAvg`-derived strategies now use in-" -"place aggregation to reduce memory consumption. The Flower client " -"serialization/deserialization has been rewritten from the ground up, " -"which results in significant speedups, especially when the client-side " -"training time is short." +"Flower is faster than ever. All `FedAvg`-derived strategies now use in-place " +"aggregation to reduce memory consumption. The Flower client serialization/" +"deserialization has been rewritten from the ground up, which results in " +"significant speedups, especially when the client-side training time is short." msgstr "" #: ../../source/ref-changelog.md:185 msgid "" -"**Support Federated Learning with Apple MLX and Flower** " -"([#2693](https://github.com/adap/flower/pull/2693))" +"**Support Federated Learning with Apple MLX and Flower** ([#2693](https://" +"github.com/adap/flower/pull/2693))" msgstr "" #: ../../source/ref-changelog.md:187 msgid "" -"Flower has official support for federated learning using [Apple " -"MLX](https://ml-explore.github.io/mlx) via the new `quickstart-mlx` code " -"example." +"Flower has official support for federated learning using [Apple MLX](https://" +"ml-explore.github.io/mlx) via the new `quickstart-mlx` code example." msgstr "" #: ../../source/ref-changelog.md:189 msgid "" -"**Introduce new XGBoost cyclic strategy** " -"([#2666](https://github.com/adap/flower/pull/2666), " -"[#2668](https://github.com/adap/flower/pull/2668))" +"**Introduce new XGBoost cyclic strategy** ([#2666](https://github.com/adap/" +"flower/pull/2666), [#2668](https://github.com/adap/flower/pull/2668))" msgstr "" #: ../../source/ref-changelog.md:191 msgid "" -"A new strategy called `FedXgbCyclic` supports a client-by-client style of" -" training (often called cyclic). The `xgboost-comprehensive` code example" -" shows how to use it in a full project. In addition to that, `xgboost-" -"comprehensive` now also supports simulation mode. With this, Flower " -"offers best-in-class XGBoost support." +"A new strategy called `FedXgbCyclic` supports a client-by-client style of " +"training (often called cyclic). The `xgboost-comprehensive` code example " +"shows how to use it in a full project. In addition to that, `xgboost-" +"comprehensive` now also supports simulation mode. With this, Flower offers " +"best-in-class XGBoost support." msgstr "" #: ../../source/ref-changelog.md:193 msgid "" -"**Support Python 3.11** " -"([#2394](https://github.com/adap/flower/pull/2394))" +"**Support Python 3.11** ([#2394](https://github.com/adap/flower/pull/2394))" msgstr "" #: ../../source/ref-changelog.md:195 msgid "" -"Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will " -"ensure better support for users using more recent Python versions." +"Framework tests now run on Python 3.8, 3.9, 3.10, and 3.11. This will ensure " +"better support for users using more recent Python versions." msgstr "" #: ../../source/ref-changelog.md:197 msgid "" -"**Update gRPC and ProtoBuf dependencies** " -"([#2814](https://github.com/adap/flower/pull/2814))" +"**Update gRPC and ProtoBuf dependencies** ([#2814](https://github.com/adap/" +"flower/pull/2814))" msgstr "" #: ../../source/ref-changelog.md:199 @@ -15762,72 +16321,65 @@ msgstr "" #: ../../source/ref-changelog.md:201 msgid "" -"**Introduce Docker image for Flower server** " -"([#2700](https://github.com/adap/flower/pull/2700), " -"[#2688](https://github.com/adap/flower/pull/2688), " -"[#2705](https://github.com/adap/flower/pull/2705), " -"[#2695](https://github.com/adap/flower/pull/2695), " -"[#2747](https://github.com/adap/flower/pull/2747), " -"[#2746](https://github.com/adap/flower/pull/2746), " -"[#2680](https://github.com/adap/flower/pull/2680), " -"[#2682](https://github.com/adap/flower/pull/2682), " -"[#2701](https://github.com/adap/flower/pull/2701))" +"**Introduce Docker image for Flower server** ([#2700](https://github.com/" +"adap/flower/pull/2700), [#2688](https://github.com/adap/flower/pull/2688), " +"[#2705](https://github.com/adap/flower/pull/2705), [#2695](https://github." +"com/adap/flower/pull/2695), [#2747](https://github.com/adap/flower/" +"pull/2747), [#2746](https://github.com/adap/flower/pull/2746), [#2680]" +"(https://github.com/adap/flower/pull/2680), [#2682](https://github.com/adap/" +"flower/pull/2682), [#2701](https://github.com/adap/flower/pull/2701))" msgstr "" #: ../../source/ref-changelog.md:203 msgid "" -"The Flower server can now be run using an official Docker image. A new " -"how-to guide explains [how to run Flower using " -"Docker](https://flower.ai/docs/framework/how-to-run-flower-using-" -"docker.html). An official Flower client Docker image will follow." +"The Flower server can now be run using an official Docker image. A new how-" +"to guide explains [how to run Flower using Docker](https://flower.ai/docs/" +"framework/how-to-run-flower-using-docker.html). An official Flower client " +"Docker image will follow." msgstr "" #: ../../source/ref-changelog.md:205 msgid "" -"**Introduce** `flower-via-docker-compose` **example** " -"([#2626](https://github.com/adap/flower/pull/2626))" +"**Introduce** `flower-via-docker-compose` **example** ([#2626](https://" +"github.com/adap/flower/pull/2626))" msgstr "" #: ../../source/ref-changelog.md:207 msgid "" -"**Introduce** `quickstart-sklearn-tabular` **example** " -"([#2719](https://github.com/adap/flower/pull/2719))" +"**Introduce** `quickstart-sklearn-tabular` **example** ([#2719](https://" +"github.com/adap/flower/pull/2719))" msgstr "" #: ../../source/ref-changelog.md:209 msgid "" -"**Introduce** `custom-metrics` **example** " -"([#1958](https://github.com/adap/flower/pull/1958))" +"**Introduce** `custom-metrics` **example** ([#1958](https://github.com/adap/" +"flower/pull/1958))" msgstr "" #: ../../source/ref-changelog.md:211 msgid "" -"**Update code examples to use Flower Datasets** " -"([#2450](https://github.com/adap/flower/pull/2450), " -"[#2456](https://github.com/adap/flower/pull/2456), " -"[#2318](https://github.com/adap/flower/pull/2318), " -"[#2712](https://github.com/adap/flower/pull/2712))" +"**Update code examples to use Flower Datasets** ([#2450](https://github.com/" +"adap/flower/pull/2450), [#2456](https://github.com/adap/flower/pull/2456), " +"[#2318](https://github.com/adap/flower/pull/2318), [#2712](https://github." +"com/adap/flower/pull/2712))" msgstr "" #: ../../source/ref-changelog.md:213 msgid "" -"Several code examples were updated to use [Flower " -"Datasets](https://flower.ai/docs/datasets/)." +"Several code examples were updated to use [Flower Datasets](https://flower." +"ai/docs/datasets/)." msgstr "" #: ../../source/ref-changelog.md:215 msgid "" -"**General updates to Flower Examples** " -"([#2381](https://github.com/adap/flower/pull/2381), " -"[#2805](https://github.com/adap/flower/pull/2805), " -"[#2782](https://github.com/adap/flower/pull/2782), " -"[#2806](https://github.com/adap/flower/pull/2806), " -"[#2829](https://github.com/adap/flower/pull/2829), " -"[#2825](https://github.com/adap/flower/pull/2825), " -"[#2816](https://github.com/adap/flower/pull/2816), " -"[#2726](https://github.com/adap/flower/pull/2726), " -"[#2659](https://github.com/adap/flower/pull/2659), " -"[#2655](https://github.com/adap/flower/pull/2655))" +"**General updates to Flower Examples** ([#2381](https://github.com/adap/" +"flower/pull/2381), [#2805](https://github.com/adap/flower/pull/2805), [#2782]" +"(https://github.com/adap/flower/pull/2782), [#2806](https://github.com/adap/" +"flower/pull/2806), [#2829](https://github.com/adap/flower/pull/2829), [#2825]" +"(https://github.com/adap/flower/pull/2825), [#2816](https://github.com/adap/" +"flower/pull/2816), [#2726](https://github.com/adap/flower/pull/2726), [#2659]" +"(https://github.com/adap/flower/pull/2659), [#2655](https://github.com/adap/" +"flower/pull/2655))" msgstr "" #: ../../source/ref-changelog.md:217 @@ -15840,8 +16392,8 @@ msgstr "" #: ../../source/ref-changelog.md:221 msgid "" -"HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), " -"[#2771](https://github.com/adap/flower/pull/2771))" +"HFedXGBoost ([#2226](https://github.com/adap/flower/pull/2226), [#2771]" +"(https://github.com/adap/flower/pull/2771))" msgstr "" #: ../../source/ref-changelog.md:222 @@ -15866,149 +16418,119 @@ msgstr "" #: ../../source/ref-changelog.md:228 msgid "" -"**Improve documentation** " -"([#2674](https://github.com/adap/flower/pull/2674), " -"[#2480](https://github.com/adap/flower/pull/2480), " -"[#2826](https://github.com/adap/flower/pull/2826), " -"[#2727](https://github.com/adap/flower/pull/2727), " -"[#2761](https://github.com/adap/flower/pull/2761), " -"[#2900](https://github.com/adap/flower/pull/2900))" +"**Improve documentation** ([#2674](https://github.com/adap/flower/" +"pull/2674), [#2480](https://github.com/adap/flower/pull/2480), [#2826]" +"(https://github.com/adap/flower/pull/2826), [#2727](https://github.com/adap/" +"flower/pull/2727), [#2761](https://github.com/adap/flower/pull/2761), [#2900]" +"(https://github.com/adap/flower/pull/2900))" msgstr "" #: ../../source/ref-changelog.md:230 msgid "" -"**Improved testing and development infrastructure** " -"([#2797](https://github.com/adap/flower/pull/2797), " -"[#2676](https://github.com/adap/flower/pull/2676), " -"[#2644](https://github.com/adap/flower/pull/2644), " -"[#2656](https://github.com/adap/flower/pull/2656), " -"[#2848](https://github.com/adap/flower/pull/2848), " -"[#2675](https://github.com/adap/flower/pull/2675), " -"[#2735](https://github.com/adap/flower/pull/2735), " -"[#2767](https://github.com/adap/flower/pull/2767), " -"[#2732](https://github.com/adap/flower/pull/2732), " -"[#2744](https://github.com/adap/flower/pull/2744), " -"[#2681](https://github.com/adap/flower/pull/2681), " -"[#2699](https://github.com/adap/flower/pull/2699), " -"[#2745](https://github.com/adap/flower/pull/2745), " -"[#2734](https://github.com/adap/flower/pull/2734), " -"[#2731](https://github.com/adap/flower/pull/2731), " -"[#2652](https://github.com/adap/flower/pull/2652), " -"[#2720](https://github.com/adap/flower/pull/2720), " -"[#2721](https://github.com/adap/flower/pull/2721), " -"[#2717](https://github.com/adap/flower/pull/2717), " -"[#2864](https://github.com/adap/flower/pull/2864), " -"[#2694](https://github.com/adap/flower/pull/2694), " -"[#2709](https://github.com/adap/flower/pull/2709), " -"[#2658](https://github.com/adap/flower/pull/2658), " -"[#2796](https://github.com/adap/flower/pull/2796), " -"[#2692](https://github.com/adap/flower/pull/2692), " -"[#2657](https://github.com/adap/flower/pull/2657), " -"[#2813](https://github.com/adap/flower/pull/2813), " -"[#2661](https://github.com/adap/flower/pull/2661), " -"[#2398](https://github.com/adap/flower/pull/2398))" +"**Improved testing and development infrastructure** ([#2797](https://github." +"com/adap/flower/pull/2797), [#2676](https://github.com/adap/flower/" +"pull/2676), [#2644](https://github.com/adap/flower/pull/2644), [#2656]" +"(https://github.com/adap/flower/pull/2656), [#2848](https://github.com/adap/" +"flower/pull/2848), [#2675](https://github.com/adap/flower/pull/2675), [#2735]" +"(https://github.com/adap/flower/pull/2735), [#2767](https://github.com/adap/" +"flower/pull/2767), [#2732](https://github.com/adap/flower/pull/2732), [#2744]" +"(https://github.com/adap/flower/pull/2744), [#2681](https://github.com/adap/" +"flower/pull/2681), [#2699](https://github.com/adap/flower/pull/2699), [#2745]" +"(https://github.com/adap/flower/pull/2745), [#2734](https://github.com/adap/" +"flower/pull/2734), [#2731](https://github.com/adap/flower/pull/2731), [#2652]" +"(https://github.com/adap/flower/pull/2652), [#2720](https://github.com/adap/" +"flower/pull/2720), [#2721](https://github.com/adap/flower/pull/2721), [#2717]" +"(https://github.com/adap/flower/pull/2717), [#2864](https://github.com/adap/" +"flower/pull/2864), [#2694](https://github.com/adap/flower/pull/2694), [#2709]" +"(https://github.com/adap/flower/pull/2709), [#2658](https://github.com/adap/" +"flower/pull/2658), [#2796](https://github.com/adap/flower/pull/2796), [#2692]" +"(https://github.com/adap/flower/pull/2692), [#2657](https://github.com/adap/" +"flower/pull/2657), [#2813](https://github.com/adap/flower/pull/2813), [#2661]" +"(https://github.com/adap/flower/pull/2661), [#2398](https://github.com/adap/" +"flower/pull/2398))" msgstr "" #: ../../source/ref-changelog.md:232 msgid "" -"The Flower testing and development infrastructure has received " -"substantial updates. This makes Flower 1.7 the most tested release ever." +"The Flower testing and development infrastructure has received substantial " +"updates. This makes Flower 1.7 the most tested release ever." msgstr "" #: ../../source/ref-changelog.md:234 msgid "" -"**Update dependencies** " -"([#2753](https://github.com/adap/flower/pull/2753), " -"[#2651](https://github.com/adap/flower/pull/2651), " -"[#2739](https://github.com/adap/flower/pull/2739), " -"[#2837](https://github.com/adap/flower/pull/2837), " -"[#2788](https://github.com/adap/flower/pull/2788), " -"[#2811](https://github.com/adap/flower/pull/2811), " -"[#2774](https://github.com/adap/flower/pull/2774), " -"[#2790](https://github.com/adap/flower/pull/2790), " -"[#2751](https://github.com/adap/flower/pull/2751), " -"[#2850](https://github.com/adap/flower/pull/2850), " -"[#2812](https://github.com/adap/flower/pull/2812), " -"[#2872](https://github.com/adap/flower/pull/2872), " -"[#2736](https://github.com/adap/flower/pull/2736), " -"[#2756](https://github.com/adap/flower/pull/2756), " -"[#2857](https://github.com/adap/flower/pull/2857), " -"[#2757](https://github.com/adap/flower/pull/2757), " -"[#2810](https://github.com/adap/flower/pull/2810), " -"[#2740](https://github.com/adap/flower/pull/2740), " -"[#2789](https://github.com/adap/flower/pull/2789))" +"**Update dependencies** ([#2753](https://github.com/adap/flower/pull/2753), " +"[#2651](https://github.com/adap/flower/pull/2651), [#2739](https://github." +"com/adap/flower/pull/2739), [#2837](https://github.com/adap/flower/" +"pull/2837), [#2788](https://github.com/adap/flower/pull/2788), [#2811]" +"(https://github.com/adap/flower/pull/2811), [#2774](https://github.com/adap/" +"flower/pull/2774), [#2790](https://github.com/adap/flower/pull/2790), [#2751]" +"(https://github.com/adap/flower/pull/2751), [#2850](https://github.com/adap/" +"flower/pull/2850), [#2812](https://github.com/adap/flower/pull/2812), [#2872]" +"(https://github.com/adap/flower/pull/2872), [#2736](https://github.com/adap/" +"flower/pull/2736), [#2756](https://github.com/adap/flower/pull/2756), [#2857]" +"(https://github.com/adap/flower/pull/2857), [#2757](https://github.com/adap/" +"flower/pull/2757), [#2810](https://github.com/adap/flower/pull/2810), [#2740]" +"(https://github.com/adap/flower/pull/2740), [#2789](https://github.com/adap/" +"flower/pull/2789))" msgstr "" #: ../../source/ref-changelog.md:236 msgid "" -"**General improvements** " -"([#2803](https://github.com/adap/flower/pull/2803), " -"[#2847](https://github.com/adap/flower/pull/2847), " -"[#2877](https://github.com/adap/flower/pull/2877), " -"[#2690](https://github.com/adap/flower/pull/2690), " -"[#2889](https://github.com/adap/flower/pull/2889), " -"[#2874](https://github.com/adap/flower/pull/2874), " -"[#2819](https://github.com/adap/flower/pull/2819), " -"[#2689](https://github.com/adap/flower/pull/2689), " -"[#2457](https://github.com/adap/flower/pull/2457), " -"[#2870](https://github.com/adap/flower/pull/2870), " -"[#2669](https://github.com/adap/flower/pull/2669), " -"[#2876](https://github.com/adap/flower/pull/2876), " -"[#2885](https://github.com/adap/flower/pull/2885), " -"[#2858](https://github.com/adap/flower/pull/2858), " -"[#2867](https://github.com/adap/flower/pull/2867), " -"[#2351](https://github.com/adap/flower/pull/2351), " -"[#2886](https://github.com/adap/flower/pull/2886), " -"[#2860](https://github.com/adap/flower/pull/2860), " -"[#2828](https://github.com/adap/flower/pull/2828), " -"[#2869](https://github.com/adap/flower/pull/2869), " -"[#2875](https://github.com/adap/flower/pull/2875), " -"[#2733](https://github.com/adap/flower/pull/2733), " -"[#2488](https://github.com/adap/flower/pull/2488), " -"[#2646](https://github.com/adap/flower/pull/2646), " -"[#2879](https://github.com/adap/flower/pull/2879), " -"[#2821](https://github.com/adap/flower/pull/2821), " -"[#2855](https://github.com/adap/flower/pull/2855), " -"[#2800](https://github.com/adap/flower/pull/2800), " -"[#2807](https://github.com/adap/flower/pull/2807), " -"[#2801](https://github.com/adap/flower/pull/2801), " -"[#2804](https://github.com/adap/flower/pull/2804), " -"[#2851](https://github.com/adap/flower/pull/2851), " -"[#2787](https://github.com/adap/flower/pull/2787), " -"[#2852](https://github.com/adap/flower/pull/2852), " -"[#2672](https://github.com/adap/flower/pull/2672), " -"[#2759](https://github.com/adap/flower/pull/2759))" +"**General improvements** ([#2803](https://github.com/adap/flower/pull/2803), " +"[#2847](https://github.com/adap/flower/pull/2847), [#2877](https://github." +"com/adap/flower/pull/2877), [#2690](https://github.com/adap/flower/" +"pull/2690), [#2889](https://github.com/adap/flower/pull/2889), [#2874]" +"(https://github.com/adap/flower/pull/2874), [#2819](https://github.com/adap/" +"flower/pull/2819), [#2689](https://github.com/adap/flower/pull/2689), [#2457]" +"(https://github.com/adap/flower/pull/2457), [#2870](https://github.com/adap/" +"flower/pull/2870), [#2669](https://github.com/adap/flower/pull/2669), [#2876]" +"(https://github.com/adap/flower/pull/2876), [#2885](https://github.com/adap/" +"flower/pull/2885), [#2858](https://github.com/adap/flower/pull/2858), [#2867]" +"(https://github.com/adap/flower/pull/2867), [#2351](https://github.com/adap/" +"flower/pull/2351), [#2886](https://github.com/adap/flower/pull/2886), [#2860]" +"(https://github.com/adap/flower/pull/2860), [#2828](https://github.com/adap/" +"flower/pull/2828), [#2869](https://github.com/adap/flower/pull/2869), [#2875]" +"(https://github.com/adap/flower/pull/2875), [#2733](https://github.com/adap/" +"flower/pull/2733), [#2488](https://github.com/adap/flower/pull/2488), [#2646]" +"(https://github.com/adap/flower/pull/2646), [#2879](https://github.com/adap/" +"flower/pull/2879), [#2821](https://github.com/adap/flower/pull/2821), [#2855]" +"(https://github.com/adap/flower/pull/2855), [#2800](https://github.com/adap/" +"flower/pull/2800), [#2807](https://github.com/adap/flower/pull/2807), [#2801]" +"(https://github.com/adap/flower/pull/2801), [#2804](https://github.com/adap/" +"flower/pull/2804), [#2851](https://github.com/adap/flower/pull/2851), [#2787]" +"(https://github.com/adap/flower/pull/2787), [#2852](https://github.com/adap/" +"flower/pull/2852), [#2672](https://github.com/adap/flower/pull/2672), [#2759]" +"(https://github.com/adap/flower/pull/2759))" msgstr "" #: ../../source/ref-changelog.md:240 msgid "" -"**Deprecate** `start_numpy_client` " -"([#2563](https://github.com/adap/flower/pull/2563), " -"[#2718](https://github.com/adap/flower/pull/2718))" +"**Deprecate** `start_numpy_client` ([#2563](https://github.com/adap/flower/" +"pull/2563), [#2718](https://github.com/adap/flower/pull/2718))" msgstr "" #: ../../source/ref-changelog.md:242 msgid "" "Until now, clients of type `NumPyClient` needed to be started via " -"`start_numpy_client`. In our efforts to consolidate framework APIs, we " -"have introduced changes, and now all client types should start via " -"`start_client`. To continue using `NumPyClient` clients, you simply need " -"to first call the `.to_client()` method and then pass returned `Client` " -"object to `start_client`. The examples and the documentation have been " -"updated accordingly." +"`start_numpy_client`. In our efforts to consolidate framework APIs, we have " +"introduced changes, and now all client types should start via " +"`start_client`. To continue using `NumPyClient` clients, you simply need to " +"first call the `.to_client()` method and then pass returned `Client` object " +"to `start_client`. The examples and the documentation have been updated " +"accordingly." msgstr "" #: ../../source/ref-changelog.md:244 msgid "" -"**Deprecate legacy DP wrappers** " -"([#2749](https://github.com/adap/flower/pull/2749))" +"**Deprecate legacy DP wrappers** ([#2749](https://github.com/adap/flower/" +"pull/2749))" msgstr "" #: ../../source/ref-changelog.md:246 msgid "" -"Legacy DP wrapper classes are deprecated, but still functional. This is " -"in preparation for an all-new pluggable version of differential privacy " -"support in Flower." +"Legacy DP wrapper classes are deprecated, but still functional. This is in " +"preparation for an all-new pluggable version of differential privacy support " +"in Flower." msgstr "" #: ../../source/ref-changelog.md:248 @@ -16019,28 +16541,26 @@ msgstr "" #: ../../source/ref-changelog.md:250 msgid "" -"**Rename** `certificates` **to** `root_certificates` **in** `Driver` " -"([#2890](https://github.com/adap/flower/pull/2890))" +"**Rename** `certificates` **to** `root_certificates` **in** `Driver` ([#2890]" +"(https://github.com/adap/flower/pull/2890))" msgstr "" #: ../../source/ref-changelog.md:252 msgid "" -"**Drop experimental** `Task` **fields** " -"([#2866](https://github.com/adap/flower/pull/2866), " -"[#2865](https://github.com/adap/flower/pull/2865))" +"**Drop experimental** `Task` **fields** ([#2866](https://github.com/adap/" +"flower/pull/2866), [#2865](https://github.com/adap/flower/pull/2865))" msgstr "" #: ../../source/ref-changelog.md:254 msgid "" "Experimental fields `sa`, `legacy_server_message` and " -"`legacy_client_message` were removed from `Task` message. The removed " -"fields are superseded by the new `RecordSet` abstraction." +"`legacy_client_message` were removed from `Task` message. The removed fields " +"are superseded by the new `RecordSet` abstraction." msgstr "" #: ../../source/ref-changelog.md:256 msgid "" -"**Retire MXNet examples** " -"([#2724](https://github.com/adap/flower/pull/2724))" +"**Retire MXNet examples** ([#2724](https://github.com/adap/flower/pull/2724))" msgstr "" #: ../../source/ref-changelog.md:258 @@ -16058,65 +16578,62 @@ msgstr "" msgid "" "`Aashish Kolluri`, `Adam Narozniak`, `Alessio Mora`, `Barathwaja S`, " "`Charles Beauville`, `Daniel J. Beutel`, `Daniel Nata Nugraha`, `Gabriel " -"Mota`, `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`," -" `Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, " -"`Steve Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, " -"`cnxdeveloper`, `k3nfalt` " +"Mota`, `Heng Pan`, `Ivan Agarský`, `JS.KIM`, `Javier`, `Marius Schlegel`, " +"`Navin Chandra`, `Nic Lane`, `Peterpan828`, `Qinbin Li`, `Shaz-hash`, `Steve " +"Laskaridis`, `Taner Topal`, `William Lindskog`, `Yan Gao`, `cnxdeveloper`, " +"`k3nfalt` " msgstr "" #: ../../source/ref-changelog.md:270 msgid "" -"**Add experimental support for Python 3.12** " -"([#2565](https://github.com/adap/flower/pull/2565))" +"**Add experimental support for Python 3.12** ([#2565](https://github.com/" +"adap/flower/pull/2565))" msgstr "" #: ../../source/ref-changelog.md:272 msgid "" -"**Add new XGBoost examples** " -"([#2612](https://github.com/adap/flower/pull/2612), " -"[#2554](https://github.com/adap/flower/pull/2554), " -"[#2617](https://github.com/adap/flower/pull/2617), " -"[#2618](https://github.com/adap/flower/pull/2618), " -"[#2619](https://github.com/adap/flower/pull/2619), " -"[#2567](https://github.com/adap/flower/pull/2567))" +"**Add new XGBoost examples** ([#2612](https://github.com/adap/flower/" +"pull/2612), [#2554](https://github.com/adap/flower/pull/2554), [#2617]" +"(https://github.com/adap/flower/pull/2617), [#2618](https://github.com/adap/" +"flower/pull/2618), [#2619](https://github.com/adap/flower/pull/2619), [#2567]" +"(https://github.com/adap/flower/pull/2567))" msgstr "" #: ../../source/ref-changelog.md:274 msgid "" -"We have added a new `xgboost-quickstart` example alongside a new " -"`xgboost-comprehensive` example that goes more in-depth." +"We have added a new `xgboost-quickstart` example alongside a new `xgboost-" +"comprehensive` example that goes more in-depth." msgstr "" #: ../../source/ref-changelog.md:276 msgid "" -"**Add Vertical FL example** " -"([#2598](https://github.com/adap/flower/pull/2598))" +"**Add Vertical FL example** ([#2598](https://github.com/adap/flower/" +"pull/2598))" msgstr "" #: ../../source/ref-changelog.md:278 msgid "" -"We had many questions about Vertical Federated Learning using Flower, so " -"we decided to add an simple example for it on the [Titanic " -"dataset](https://www.kaggle.com/competitions/titanic/data) alongside a " -"tutorial (in the README)." +"We had many questions about Vertical Federated Learning using Flower, so we " +"decided to add an simple example for it on the [Titanic dataset](https://www." +"kaggle.com/competitions/titanic/data) alongside a tutorial (in the README)." msgstr "" #: ../../source/ref-changelog.md:280 msgid "" -"**Support custom** `ClientManager` **in** `start_driver()` " -"([#2292](https://github.com/adap/flower/pull/2292))" +"**Support custom** `ClientManager` **in** `start_driver()` ([#2292](https://" +"github.com/adap/flower/pull/2292))" msgstr "" #: ../../source/ref-changelog.md:282 msgid "" -"**Update REST API to support create and delete nodes** " -"([#2283](https://github.com/adap/flower/pull/2283))" +"**Update REST API to support create and delete nodes** ([#2283](https://" +"github.com/adap/flower/pull/2283))" msgstr "" #: ../../source/ref-changelog.md:284 msgid "" -"**Update the Android SDK** " -"([#2187](https://github.com/adap/flower/pull/2187))" +"**Update the Android SDK** ([#2187](https://github.com/adap/flower/" +"pull/2187))" msgstr "" #: ../../source/ref-changelog.md:286 @@ -16125,11 +16642,10 @@ msgstr "" #: ../../source/ref-changelog.md:288 msgid "" -"**Update the C++ SDK** " -"([#2537](https://github.com/adap/flower/pull/2537), " -"[#2528](https://github.com/adap/flower/pull/2528), " -"[#2523](https://github.com/adap/flower/pull/2523), " -"[#2522](https://github.com/adap/flower/pull/2522))" +"**Update the C++ SDK** ([#2537](https://github.com/adap/flower/pull/2537), " +"[#2528](https://github.com/adap/flower/pull/2528), [#2523](https://github." +"com/adap/flower/pull/2523), [#2522](https://github.com/adap/flower/" +"pull/2522))" msgstr "" #: ../../source/ref-changelog.md:290 @@ -16138,93 +16654,90 @@ msgstr "" #: ../../source/ref-changelog.md:292 msgid "" -"**Make HTTPS the new default** " -"([#2591](https://github.com/adap/flower/pull/2591), " -"[#2636](https://github.com/adap/flower/pull/2636))" +"**Make HTTPS the new default** ([#2591](https://github.com/adap/flower/" +"pull/2591), [#2636](https://github.com/adap/flower/pull/2636))" msgstr "" #: ../../source/ref-changelog.md:294 msgid "" "Flower is moving to HTTPS by default. The new `flower-server` requires " -"passing `--certificates`, but users can enable `--insecure` to use HTTP " -"for prototyping. The same applies to `flower-client`, which can either " -"use user-provided credentials or gRPC-bundled certificates to connect to " -"an HTTPS-enabled server or requires opt-out via passing `--insecure` to " -"enable insecure HTTP connections." +"passing `--certificates`, but users can enable `--insecure` to use HTTP for " +"prototyping. The same applies to `flower-client`, which can either use user-" +"provided credentials or gRPC-bundled certificates to connect to an HTTPS-" +"enabled server or requires opt-out via passing `--insecure` to enable " +"insecure HTTP connections." msgstr "" #: ../../source/ref-changelog.md:296 msgid "" -"For backward compatibility, `start_client()` and `start_numpy_client()` " -"will still start in insecure mode by default. In a future release, " -"insecure connections will require user opt-in by passing `insecure=True`." +"For backward compatibility, `start_client()` and `start_numpy_client()` will " +"still start in insecure mode by default. In a future release, insecure " +"connections will require user opt-in by passing `insecure=True`." msgstr "" #: ../../source/ref-changelog.md:298 msgid "" "**Unify client API** ([#2303](https://github.com/adap/flower/pull/2303), " -"[#2390](https://github.com/adap/flower/pull/2390), " -"[#2493](https://github.com/adap/flower/pull/2493))" +"[#2390](https://github.com/adap/flower/pull/2390), [#2493](https://github." +"com/adap/flower/pull/2493))" msgstr "" #: ../../source/ref-changelog.md:300 msgid "" -"Using the `client_fn`, Flower clients can interchangeably run as " -"standalone processes (i.e. via `start_client`) or in simulation (i.e. via" -" `start_simulation`) without requiring changes to how the client class is" -" defined and instantiated. The `to_client()` function is introduced to " +"Using the `client_fn`, Flower clients can interchangeably run as standalone " +"processes (i.e. via `start_client`) or in simulation (i.e. via " +"`start_simulation`) without requiring changes to how the client class is " +"defined and instantiated. The `to_client()` function is introduced to " "convert a `NumPyClient` to a `Client`." msgstr "" #: ../../source/ref-changelog.md:302 msgid "" -"**Add new** `Bulyan` **strategy** " -"([#1817](https://github.com/adap/flower/pull/1817), " -"[#1891](https://github.com/adap/flower/pull/1891))" +"**Add new** `Bulyan` **strategy** ([#1817](https://github.com/adap/flower/" +"pull/1817), [#1891](https://github.com/adap/flower/pull/1891))" msgstr "" #: ../../source/ref-changelog.md:304 msgid "" -"The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., " -"2018](https://arxiv.org/abs/1802.07927)" +"The new `Bulyan` strategy implements Bulyan by [El Mhamdi et al., 2018]" +"(https://arxiv.org/abs/1802.07927)" msgstr "" #: ../../source/ref-changelog.md:306 msgid "" -"**Add new** `XGB Bagging` **strategy** " -"([#2611](https://github.com/adap/flower/pull/2611))" +"**Add new** `XGB Bagging` **strategy** ([#2611](https://github.com/adap/" +"flower/pull/2611))" msgstr "" #: ../../source/ref-changelog.md:308 ../../source/ref-changelog.md:310 msgid "" -"**Introduce `WorkloadState`** " -"([#2564](https://github.com/adap/flower/pull/2564), " -"[#2632](https://github.com/adap/flower/pull/2632))" +"**Introduce `WorkloadState`** ([#2564](https://github.com/adap/flower/" +"pull/2564), [#2632](https://github.com/adap/flower/pull/2632))" msgstr "" #: ../../source/ref-changelog.md:314 msgid "" -"FedProx ([#2210](https://github.com/adap/flower/pull/2210), " -"[#2286](https://github.com/adap/flower/pull/2286), " -"[#2509](https://github.com/adap/flower/pull/2509))" +"FedProx ([#2210](https://github.com/adap/flower/pull/2210), [#2286](https://" +"github.com/adap/flower/pull/2286), [#2509](https://github.com/adap/flower/" +"pull/2509))" msgstr "" #: ../../source/ref-changelog.md:316 msgid "" -"Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), " -"[#2400](https://github.com/adap/flower/pull/2400))" +"Baselines Docs ([#2290](https://github.com/adap/flower/pull/2290), [#2400]" +"(https://github.com/adap/flower/pull/2400))" msgstr "" #: ../../source/ref-changelog.md:318 msgid "" -"FedMLB ([#2340](https://github.com/adap/flower/pull/2340), " -"[#2507](https://github.com/adap/flower/pull/2507))" +"FedMLB ([#2340](https://github.com/adap/flower/pull/2340), [#2507](https://" +"github.com/adap/flower/pull/2507))" msgstr "" #: ../../source/ref-changelog.md:320 msgid "" -"TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), " -"[#2508](https://github.com/adap/flower/pull/2508))" +"TAMUNA ([#2254](https://github.com/adap/flower/pull/2254), [#2508](https://" +"github.com/adap/flower/pull/2508))" msgstr "" #: ../../source/ref-changelog.md:322 @@ -16257,125 +16770,106 @@ msgstr "" #: ../../source/ref-changelog.md:336 msgid "" -"FedBN ([#2608](https://github.com/adap/flower/pull/2608), " -"[#2615](https://github.com/adap/flower/pull/2615))" +"FedBN ([#2608](https://github.com/adap/flower/pull/2608), [#2615](https://" +"github.com/adap/flower/pull/2615))" msgstr "" #: ../../source/ref-changelog.md:338 msgid "" -"**General updates to Flower Examples** " -"([#2384](https://github.com/adap/flower/pull/2384), " -"[#2425](https://github.com/adap/flower/pull/2425), " -"[#2526](https://github.com/adap/flower/pull/2526), " -"[#2302](https://github.com/adap/flower/pull/2302), " -"[#2545](https://github.com/adap/flower/pull/2545))" +"**General updates to Flower Examples** ([#2384](https://github.com/adap/" +"flower/pull/2384), [#2425](https://github.com/adap/flower/pull/2425), [#2526]" +"(https://github.com/adap/flower/pull/2526), [#2302](https://github.com/adap/" +"flower/pull/2302), [#2545](https://github.com/adap/flower/pull/2545))" msgstr "" #: ../../source/ref-changelog.md:340 msgid "" -"**General updates to Flower Baselines** " -"([#2301](https://github.com/adap/flower/pull/2301), " -"[#2305](https://github.com/adap/flower/pull/2305), " -"[#2307](https://github.com/adap/flower/pull/2307), " -"[#2327](https://github.com/adap/flower/pull/2327), " -"[#2435](https://github.com/adap/flower/pull/2435), " -"[#2462](https://github.com/adap/flower/pull/2462), " -"[#2463](https://github.com/adap/flower/pull/2463), " -"[#2461](https://github.com/adap/flower/pull/2461), " -"[#2469](https://github.com/adap/flower/pull/2469), " -"[#2466](https://github.com/adap/flower/pull/2466), " -"[#2471](https://github.com/adap/flower/pull/2471), " -"[#2472](https://github.com/adap/flower/pull/2472), " -"[#2470](https://github.com/adap/flower/pull/2470))" +"**General updates to Flower Baselines** ([#2301](https://github.com/adap/" +"flower/pull/2301), [#2305](https://github.com/adap/flower/pull/2305), [#2307]" +"(https://github.com/adap/flower/pull/2307), [#2327](https://github.com/adap/" +"flower/pull/2327), [#2435](https://github.com/adap/flower/pull/2435), [#2462]" +"(https://github.com/adap/flower/pull/2462), [#2463](https://github.com/adap/" +"flower/pull/2463), [#2461](https://github.com/adap/flower/pull/2461), [#2469]" +"(https://github.com/adap/flower/pull/2469), [#2466](https://github.com/adap/" +"flower/pull/2466), [#2471](https://github.com/adap/flower/pull/2471), [#2472]" +"(https://github.com/adap/flower/pull/2472), [#2470](https://github.com/adap/" +"flower/pull/2470))" msgstr "" #: ../../source/ref-changelog.md:342 msgid "" -"**General updates to the simulation engine** " -"([#2331](https://github.com/adap/flower/pull/2331), " -"[#2447](https://github.com/adap/flower/pull/2447), " -"[#2448](https://github.com/adap/flower/pull/2448), " -"[#2294](https://github.com/adap/flower/pull/2294))" +"**General updates to the simulation engine** ([#2331](https://github.com/" +"adap/flower/pull/2331), [#2447](https://github.com/adap/flower/pull/2447), " +"[#2448](https://github.com/adap/flower/pull/2448), [#2294](https://github." +"com/adap/flower/pull/2294))" msgstr "" #: ../../source/ref-changelog.md:344 msgid "" -"**General updates to Flower SDKs** " -"([#2288](https://github.com/adap/flower/pull/2288), " -"[#2429](https://github.com/adap/flower/pull/2429), " -"[#2555](https://github.com/adap/flower/pull/2555), " -"[#2543](https://github.com/adap/flower/pull/2543), " -"[#2544](https://github.com/adap/flower/pull/2544), " -"[#2597](https://github.com/adap/flower/pull/2597), " -"[#2623](https://github.com/adap/flower/pull/2623))" +"**General updates to Flower SDKs** ([#2288](https://github.com/adap/flower/" +"pull/2288), [#2429](https://github.com/adap/flower/pull/2429), [#2555]" +"(https://github.com/adap/flower/pull/2555), [#2543](https://github.com/adap/" +"flower/pull/2543), [#2544](https://github.com/adap/flower/pull/2544), [#2597]" +"(https://github.com/adap/flower/pull/2597), [#2623](https://github.com/adap/" +"flower/pull/2623))" msgstr "" #: ../../source/ref-changelog.md:346 msgid "" -"**General improvements** " -"([#2309](https://github.com/adap/flower/pull/2309), " -"[#2310](https://github.com/adap/flower/pull/2310), " -"[#2313](https://github.com/adap/flower/pull/2313), " -"[#2316](https://github.com/adap/flower/pull/2316), " -"[#2317](https://github.com/adap/flower/pull/2317), " -"[#2349](https://github.com/adap/flower/pull/2349), " -"[#2360](https://github.com/adap/flower/pull/2360), " -"[#2402](https://github.com/adap/flower/pull/2402), " -"[#2446](https://github.com/adap/flower/pull/2446), " -"[#2561](https://github.com/adap/flower/pull/2561), " -"[#2273](https://github.com/adap/flower/pull/2273), " -"[#2267](https://github.com/adap/flower/pull/2267), " -"[#2274](https://github.com/adap/flower/pull/2274), " -"[#2275](https://github.com/adap/flower/pull/2275), " -"[#2432](https://github.com/adap/flower/pull/2432), " -"[#2251](https://github.com/adap/flower/pull/2251), " -"[#2321](https://github.com/adap/flower/pull/2321), " -"[#1936](https://github.com/adap/flower/pull/1936), " -"[#2408](https://github.com/adap/flower/pull/2408), " -"[#2413](https://github.com/adap/flower/pull/2413), " -"[#2401](https://github.com/adap/flower/pull/2401), " -"[#2531](https://github.com/adap/flower/pull/2531), " -"[#2534](https://github.com/adap/flower/pull/2534), " -"[#2535](https://github.com/adap/flower/pull/2535), " -"[#2521](https://github.com/adap/flower/pull/2521), " -"[#2553](https://github.com/adap/flower/pull/2553), " -"[#2596](https://github.com/adap/flower/pull/2596))" +"**General improvements** ([#2309](https://github.com/adap/flower/pull/2309), " +"[#2310](https://github.com/adap/flower/pull/2310), [#2313](https://github." +"com/adap/flower/pull/2313), [#2316](https://github.com/adap/flower/" +"pull/2316), [#2317](https://github.com/adap/flower/pull/2317), [#2349]" +"(https://github.com/adap/flower/pull/2349), [#2360](https://github.com/adap/" +"flower/pull/2360), [#2402](https://github.com/adap/flower/pull/2402), [#2446]" +"(https://github.com/adap/flower/pull/2446), [#2561](https://github.com/adap/" +"flower/pull/2561), [#2273](https://github.com/adap/flower/pull/2273), [#2267]" +"(https://github.com/adap/flower/pull/2267), [#2274](https://github.com/adap/" +"flower/pull/2274), [#2275](https://github.com/adap/flower/pull/2275), [#2432]" +"(https://github.com/adap/flower/pull/2432), [#2251](https://github.com/adap/" +"flower/pull/2251), [#2321](https://github.com/adap/flower/pull/2321), [#1936]" +"(https://github.com/adap/flower/pull/1936), [#2408](https://github.com/adap/" +"flower/pull/2408), [#2413](https://github.com/adap/flower/pull/2413), [#2401]" +"(https://github.com/adap/flower/pull/2401), [#2531](https://github.com/adap/" +"flower/pull/2531), [#2534](https://github.com/adap/flower/pull/2534), [#2535]" +"(https://github.com/adap/flower/pull/2535), [#2521](https://github.com/adap/" +"flower/pull/2521), [#2553](https://github.com/adap/flower/pull/2553), [#2596]" +"(https://github.com/adap/flower/pull/2596))" msgstr "" #: ../../source/ref-changelog.md:348 ../../source/ref-changelog.md:438 #: ../../source/ref-changelog.md:502 ../../source/ref-changelog.md:556 #: ../../source/ref-changelog.md:623 -msgid "Flower received many improvements under the hood, too many to list here." +msgid "" +"Flower received many improvements under the hood, too many to list here." msgstr "" #: ../../source/ref-changelog.md:352 msgid "" -"**Remove support for Python 3.7** " -"([#2280](https://github.com/adap/flower/pull/2280), " -"[#2299](https://github.com/adap/flower/pull/2299), " -"[#2304](https://github.com/adap/flower/pull/2304), " -"[#2306](https://github.com/adap/flower/pull/2306), " -"[#2355](https://github.com/adap/flower/pull/2355), " -"[#2356](https://github.com/adap/flower/pull/2356))" +"**Remove support for Python 3.7** ([#2280](https://github.com/adap/flower/" +"pull/2280), [#2299](https://github.com/adap/flower/pull/2299), [#2304]" +"(https://github.com/adap/flower/pull/2304), [#2306](https://github.com/adap/" +"flower/pull/2306), [#2355](https://github.com/adap/flower/pull/2355), [#2356]" +"(https://github.com/adap/flower/pull/2356))" msgstr "" #: ../../source/ref-changelog.md:354 msgid "" -"Python 3.7 support was deprecated in Flower 1.5, and this release removes" -" support. Flower now requires Python 3.8." +"Python 3.7 support was deprecated in Flower 1.5, and this release removes " +"support. Flower now requires Python 3.8." msgstr "" #: ../../source/ref-changelog.md:356 msgid "" -"**Remove experimental argument** `rest` **from** `start_client` " -"([#2324](https://github.com/adap/flower/pull/2324))" +"**Remove experimental argument** `rest` **from** `start_client` ([#2324]" +"(https://github.com/adap/flower/pull/2324))" msgstr "" #: ../../source/ref-changelog.md:358 msgid "" -"The (still experimental) argument `rest` was removed from `start_client` " -"and `start_numpy_client`. Use `transport=\"rest\"` to opt into the " -"experimental REST API instead." +"The (still experimental) argument `rest` was removed from `start_client` and " +"`start_numpy_client`. Use `transport=\"rest\"` to opt into the experimental " +"REST API instead." msgstr "" #: ../../source/ref-changelog.md:360 @@ -16393,125 +16887,108 @@ msgstr "" #: ../../source/ref-changelog.md:370 msgid "" -"**Introduce new simulation engine** " -"([#1969](https://github.com/adap/flower/pull/1969), " -"[#2221](https://github.com/adap/flower/pull/2221), " -"[#2248](https://github.com/adap/flower/pull/2248))" +"**Introduce new simulation engine** ([#1969](https://github.com/adap/flower/" +"pull/1969), [#2221](https://github.com/adap/flower/pull/2221), [#2248]" +"(https://github.com/adap/flower/pull/2248))" msgstr "" #: ../../source/ref-changelog.md:372 msgid "" "The new simulation engine has been rewritten from the ground up, yet it " -"remains fully backwards compatible. It offers much improved stability and" -" memory handling, especially when working with GPUs. Simulations " -"transparently adapt to different settings to scale simulation in CPU-" -"only, CPU+GPU, multi-GPU, or multi-node multi-GPU environments." +"remains fully backwards compatible. It offers much improved stability and " +"memory handling, especially when working with GPUs. Simulations " +"transparently adapt to different settings to scale simulation in CPU-only, " +"CPU+GPU, multi-GPU, or multi-node multi-GPU environments." msgstr "" #: ../../source/ref-changelog.md:374 msgid "" -"Comprehensive documentation includes a new [how-to run " -"simulations](https://flower.ai/docs/framework/how-to-run-" -"simulations.html) guide, new [simulation-" +"Comprehensive documentation includes a new [how-to run simulations](https://" +"flower.ai/docs/framework/how-to-run-simulations.html) guide, new [simulation-" "pytorch](https://flower.ai/docs/examples/simulation-pytorch.html) and " "[simulation-tensorflow](https://flower.ai/docs/examples/simulation-" -"tensorflow.html) notebooks, and a new [YouTube tutorial " -"series](https://www.youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." +"tensorflow.html) notebooks, and a new [YouTube tutorial series](https://www." +"youtube.com/watch?v=cRebUIGB5RU&list=PLNG4feLHqCWlnj8a_E1A_n5zr2-8pafTB)." msgstr "" #: ../../source/ref-changelog.md:376 msgid "" -"**Restructure Flower Docs** " -"([#1824](https://github.com/adap/flower/pull/1824), " -"[#1865](https://github.com/adap/flower/pull/1865), " -"[#1884](https://github.com/adap/flower/pull/1884), " -"[#1887](https://github.com/adap/flower/pull/1887), " -"[#1919](https://github.com/adap/flower/pull/1919), " -"[#1922](https://github.com/adap/flower/pull/1922), " -"[#1920](https://github.com/adap/flower/pull/1920), " -"[#1923](https://github.com/adap/flower/pull/1923), " -"[#1924](https://github.com/adap/flower/pull/1924), " -"[#1962](https://github.com/adap/flower/pull/1962), " -"[#2006](https://github.com/adap/flower/pull/2006), " -"[#2133](https://github.com/adap/flower/pull/2133), " -"[#2203](https://github.com/adap/flower/pull/2203), " -"[#2215](https://github.com/adap/flower/pull/2215), " -"[#2122](https://github.com/adap/flower/pull/2122), " -"[#2223](https://github.com/adap/flower/pull/2223), " -"[#2219](https://github.com/adap/flower/pull/2219), " -"[#2232](https://github.com/adap/flower/pull/2232), " -"[#2233](https://github.com/adap/flower/pull/2233), " -"[#2234](https://github.com/adap/flower/pull/2234), " -"[#2235](https://github.com/adap/flower/pull/2235), " -"[#2237](https://github.com/adap/flower/pull/2237), " -"[#2238](https://github.com/adap/flower/pull/2238), " -"[#2242](https://github.com/adap/flower/pull/2242), " -"[#2231](https://github.com/adap/flower/pull/2231), " -"[#2243](https://github.com/adap/flower/pull/2243), " -"[#2227](https://github.com/adap/flower/pull/2227))" +"**Restructure Flower Docs** ([#1824](https://github.com/adap/flower/" +"pull/1824), [#1865](https://github.com/adap/flower/pull/1865), [#1884]" +"(https://github.com/adap/flower/pull/1884), [#1887](https://github.com/adap/" +"flower/pull/1887), [#1919](https://github.com/adap/flower/pull/1919), [#1922]" +"(https://github.com/adap/flower/pull/1922), [#1920](https://github.com/adap/" +"flower/pull/1920), [#1923](https://github.com/adap/flower/pull/1923), [#1924]" +"(https://github.com/adap/flower/pull/1924), [#1962](https://github.com/adap/" +"flower/pull/1962), [#2006](https://github.com/adap/flower/pull/2006), [#2133]" +"(https://github.com/adap/flower/pull/2133), [#2203](https://github.com/adap/" +"flower/pull/2203), [#2215](https://github.com/adap/flower/pull/2215), [#2122]" +"(https://github.com/adap/flower/pull/2122), [#2223](https://github.com/adap/" +"flower/pull/2223), [#2219](https://github.com/adap/flower/pull/2219), [#2232]" +"(https://github.com/adap/flower/pull/2232), [#2233](https://github.com/adap/" +"flower/pull/2233), [#2234](https://github.com/adap/flower/pull/2234), [#2235]" +"(https://github.com/adap/flower/pull/2235), [#2237](https://github.com/adap/" +"flower/pull/2237), [#2238](https://github.com/adap/flower/pull/2238), [#2242]" +"(https://github.com/adap/flower/pull/2242), [#2231](https://github.com/adap/" +"flower/pull/2231), [#2243](https://github.com/adap/flower/pull/2243), [#2227]" +"(https://github.com/adap/flower/pull/2227))" msgstr "" #: ../../source/ref-changelog.md:378 msgid "" -"Much effort went into a completely restructured Flower docs experience. " -"The documentation on [flower.ai/docs](https://flower.ai/docs) is now " -"divided into Flower Framework, Flower Baselines, Flower Android SDK, " -"Flower iOS SDK, and code example projects." +"Much effort went into a completely restructured Flower docs experience. The " +"documentation on [flower.ai/docs](https://flower.ai/docs) is now divided " +"into Flower Framework, Flower Baselines, Flower Android SDK, Flower iOS SDK, " +"and code example projects." msgstr "" #: ../../source/ref-changelog.md:380 msgid "" -"**Introduce Flower Swift SDK** " -"([#1858](https://github.com/adap/flower/pull/1858), " -"[#1897](https://github.com/adap/flower/pull/1897))" +"**Introduce Flower Swift SDK** ([#1858](https://github.com/adap/flower/" +"pull/1858), [#1897](https://github.com/adap/flower/pull/1897))" msgstr "" #: ../../source/ref-changelog.md:382 msgid "" -"This is the first preview release of the Flower Swift SDK. Flower support" -" on iOS is improving, and alongside the Swift SDK and code example, there" -" is now also an iOS quickstart tutorial." +"This is the first preview release of the Flower Swift SDK. Flower support on " +"iOS is improving, and alongside the Swift SDK and code example, there is now " +"also an iOS quickstart tutorial." msgstr "" #: ../../source/ref-changelog.md:384 msgid "" -"**Introduce Flower Android SDK** " -"([#2131](https://github.com/adap/flower/pull/2131))" +"**Introduce Flower Android SDK** ([#2131](https://github.com/adap/flower/" +"pull/2131))" msgstr "" #: ../../source/ref-changelog.md:386 msgid "" -"This is the first preview release of the Flower Kotlin SDK. Flower " -"support on Android is improving, and alongside the Kotlin SDK and code " -"example, there is now also an Android quickstart tutorial." +"This is the first preview release of the Flower Kotlin SDK. Flower support " +"on Android is improving, and alongside the Kotlin SDK and code example, " +"there is now also an Android quickstart tutorial." msgstr "" #: ../../source/ref-changelog.md:388 msgid "" -"**Introduce new end-to-end testing infrastructure** " -"([#1842](https://github.com/adap/flower/pull/1842), " -"[#2071](https://github.com/adap/flower/pull/2071), " -"[#2072](https://github.com/adap/flower/pull/2072), " -"[#2068](https://github.com/adap/flower/pull/2068), " -"[#2067](https://github.com/adap/flower/pull/2067), " -"[#2069](https://github.com/adap/flower/pull/2069), " -"[#2073](https://github.com/adap/flower/pull/2073), " -"[#2070](https://github.com/adap/flower/pull/2070), " -"[#2074](https://github.com/adap/flower/pull/2074), " -"[#2082](https://github.com/adap/flower/pull/2082), " -"[#2084](https://github.com/adap/flower/pull/2084), " -"[#2093](https://github.com/adap/flower/pull/2093), " -"[#2109](https://github.com/adap/flower/pull/2109), " -"[#2095](https://github.com/adap/flower/pull/2095), " -"[#2140](https://github.com/adap/flower/pull/2140), " -"[#2137](https://github.com/adap/flower/pull/2137), " -"[#2165](https://github.com/adap/flower/pull/2165))" +"**Introduce new end-to-end testing infrastructure** ([#1842](https://github." +"com/adap/flower/pull/1842), [#2071](https://github.com/adap/flower/" +"pull/2071), [#2072](https://github.com/adap/flower/pull/2072), [#2068]" +"(https://github.com/adap/flower/pull/2068), [#2067](https://github.com/adap/" +"flower/pull/2067), [#2069](https://github.com/adap/flower/pull/2069), [#2073]" +"(https://github.com/adap/flower/pull/2073), [#2070](https://github.com/adap/" +"flower/pull/2070), [#2074](https://github.com/adap/flower/pull/2074), [#2082]" +"(https://github.com/adap/flower/pull/2082), [#2084](https://github.com/adap/" +"flower/pull/2084), [#2093](https://github.com/adap/flower/pull/2093), [#2109]" +"(https://github.com/adap/flower/pull/2109), [#2095](https://github.com/adap/" +"flower/pull/2095), [#2140](https://github.com/adap/flower/pull/2140), [#2137]" +"(https://github.com/adap/flower/pull/2137), [#2165](https://github.com/adap/" +"flower/pull/2165))" msgstr "" #: ../../source/ref-changelog.md:390 msgid "" -"A new testing infrastructure ensures that new changes stay compatible " -"with existing framework integrations or strategies." +"A new testing infrastructure ensures that new changes stay compatible with " +"existing framework integrations or strategies." msgstr "" #: ../../source/ref-changelog.md:392 @@ -16520,124 +16997,121 @@ msgstr "" #: ../../source/ref-changelog.md:394 msgid "" -"Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for" -" Python 3.7 is now deprecated and will be removed in an upcoming release." +"Since Python 3.7 reached its end of life (EOL) on 2023-06-27, support for " +"Python 3.7 is now deprecated and will be removed in an upcoming release." msgstr "" #: ../../source/ref-changelog.md:396 msgid "" -"**Add new** `FedTrimmedAvg` **strategy** " -"([#1769](https://github.com/adap/flower/pull/1769), " -"[#1853](https://github.com/adap/flower/pull/1853))" +"**Add new** `FedTrimmedAvg` **strategy** ([#1769](https://github.com/adap/" +"flower/pull/1769), [#1853](https://github.com/adap/flower/pull/1853))" msgstr "" #: ../../source/ref-changelog.md:398 msgid "" -"The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, " -"2018](https://arxiv.org/abs/1803.01498)." +"The new `FedTrimmedAvg` strategy implements Trimmed Mean by [Dong Yin, 2018]" +"(https://arxiv.org/abs/1803.01498)." msgstr "" #: ../../source/ref-changelog.md:400 msgid "" -"**Introduce start_driver** " -"([#1697](https://github.com/adap/flower/pull/1697))" +"**Introduce start_driver** ([#1697](https://github.com/adap/flower/" +"pull/1697))" msgstr "" #: ../../source/ref-changelog.md:402 msgid "" -"In addition to `start_server` and using the raw Driver API, there is a " -"new `start_driver` function that allows for running `start_server` " -"scripts as a Flower driver with only a single-line code change. Check out" -" the `mt-pytorch` code example to see a working example using " -"`start_driver`." +"In addition to `start_server` and using the raw Driver API, there is a new " +"`start_driver` function that allows for running `start_server` scripts as a " +"Flower driver with only a single-line code change. Check out the `mt-" +"pytorch` code example to see a working example using `start_driver`." msgstr "" #: ../../source/ref-changelog.md:404 msgid "" -"**Add parameter aggregation to** `mt-pytorch` **code example** " -"([#1785](https://github.com/adap/flower/pull/1785))" +"**Add parameter aggregation to** `mt-pytorch` **code example** ([#1785]" +"(https://github.com/adap/flower/pull/1785))" msgstr "" #: ../../source/ref-changelog.md:406 msgid "" -"The `mt-pytorch` example shows how to aggregate parameters when writing a" -" driver script. The included `driver.py` and `server.py` have been " -"aligned to demonstrate both the low-level way and the high-level way of " -"building server-side logic." +"The `mt-pytorch` example shows how to aggregate parameters when writing a " +"driver script. The included `driver.py` and `server.py` have been aligned to " +"demonstrate both the low-level way and the high-level way of building server-" +"side logic." msgstr "" #: ../../source/ref-changelog.md:408 msgid "" -"**Migrate experimental REST API to Starlette** " -"([2171](https://github.com/adap/flower/pull/2171))" +"**Migrate experimental REST API to Starlette** ([2171](https://github.com/" +"adap/flower/pull/2171))" msgstr "" #: ../../source/ref-changelog.md:410 msgid "" -"The (experimental) REST API used to be implemented in " -"[FastAPI](https://fastapi.tiangolo.com/), but it has now been migrated to" -" use [Starlette](https://www.starlette.io/) directly." +"The (experimental) REST API used to be implemented in [FastAPI](https://" +"fastapi.tiangolo.com/), but it has now been migrated to use [Starlette]" +"(https://www.starlette.io/) directly." msgstr "" #: ../../source/ref-changelog.md:412 msgid "" -"Please note: The REST request-response API is still experimental and will" -" likely change significantly over time." +"Please note: The REST request-response API is still experimental and will " +"likely change significantly over time." msgstr "" #: ../../source/ref-changelog.md:414 msgid "" -"**Introduce experimental gRPC request-response API** " -"([#1867](https://github.com/adap/flower/pull/1867), " -"[#1901](https://github.com/adap/flower/pull/1901))" +"**Introduce experimental gRPC request-response API** ([#1867](https://github." +"com/adap/flower/pull/1867), [#1901](https://github.com/adap/flower/" +"pull/1901))" msgstr "" #: ../../source/ref-changelog.md:416 msgid "" -"In addition to the existing gRPC API (based on bidirectional streaming) " -"and the experimental REST API, there is now a new gRPC API that uses a " -"request-response model to communicate with client nodes." +"In addition to the existing gRPC API (based on bidirectional streaming) and " +"the experimental REST API, there is now a new gRPC API that uses a request-" +"response model to communicate with client nodes." msgstr "" #: ../../source/ref-changelog.md:418 msgid "" -"Please note: The gRPC request-response API is still experimental and will" -" likely change significantly over time." +"Please note: The gRPC request-response API is still experimental and will " +"likely change significantly over time." msgstr "" #: ../../source/ref-changelog.md:420 msgid "" "**Replace the experimental** `start_client(rest=True)` **with the new** " -"`start_client(transport=\"rest\")` " -"([#1880](https://github.com/adap/flower/pull/1880))" +"`start_client(transport=\"rest\")` ([#1880](https://github.com/adap/flower/" +"pull/1880))" msgstr "" #: ../../source/ref-changelog.md:422 msgid "" -"The (experimental) `start_client` argument `rest` was deprecated in " -"favour of a new argument `transport`. `start_client(transport=\"rest\")` " -"will yield the same behaviour as `start_client(rest=True)` did before. " -"All code should migrate to the new argument `transport`. The deprecated " -"argument `rest` will be removed in a future release." +"The (experimental) `start_client` argument `rest` was deprecated in favour " +"of a new argument `transport`. `start_client(transport=\"rest\")` will yield " +"the same behaviour as `start_client(rest=True)` did before. All code should " +"migrate to the new argument `transport`. The deprecated argument `rest` will " +"be removed in a future release." msgstr "" #: ../../source/ref-changelog.md:424 msgid "" -"**Add a new gRPC option** " -"([#2197](https://github.com/adap/flower/pull/2197))" +"**Add a new gRPC option** ([#2197](https://github.com/adap/flower/pull/2197))" msgstr "" #: ../../source/ref-changelog.md:426 msgid "" -"We now start a gRPC server with the `grpc.keepalive_permit_without_calls`" -" option set to 0 by default. This prevents the clients from sending " -"keepalive pings when there is no outstanding stream." +"We now start a gRPC server with the `grpc.keepalive_permit_without_calls` " +"option set to 0 by default. This prevents the clients from sending keepalive " +"pings when there is no outstanding stream." msgstr "" #: ../../source/ref-changelog.md:428 msgid "" -"**Improve example notebooks** " -"([#2005](https://github.com/adap/flower/pull/2005))" +"**Improve example notebooks** ([#2005](https://github.com/adap/flower/" +"pull/2005))" msgstr "" #: ../../source/ref-changelog.md:430 @@ -16647,36 +17121,31 @@ msgstr "" #: ../../source/ref-changelog.md:432 msgid "" "**Example updates** ([#1772](https://github.com/adap/flower/pull/1772), " -"[#1873](https://github.com/adap/flower/pull/1873), " -"[#1981](https://github.com/adap/flower/pull/1981), " -"[#1988](https://github.com/adap/flower/pull/1988), " -"[#1984](https://github.com/adap/flower/pull/1984), " -"[#1982](https://github.com/adap/flower/pull/1982), " -"[#2112](https://github.com/adap/flower/pull/2112), " -"[#2144](https://github.com/adap/flower/pull/2144), " -"[#2174](https://github.com/adap/flower/pull/2174), " -"[#2225](https://github.com/adap/flower/pull/2225), " -"[#2183](https://github.com/adap/flower/pull/2183))" +"[#1873](https://github.com/adap/flower/pull/1873), [#1981](https://github." +"com/adap/flower/pull/1981), [#1988](https://github.com/adap/flower/" +"pull/1988), [#1984](https://github.com/adap/flower/pull/1984), [#1982]" +"(https://github.com/adap/flower/pull/1982), [#2112](https://github.com/adap/" +"flower/pull/2112), [#2144](https://github.com/adap/flower/pull/2144), [#2174]" +"(https://github.com/adap/flower/pull/2174), [#2225](https://github.com/adap/" +"flower/pull/2225), [#2183](https://github.com/adap/flower/pull/2183))" msgstr "" #: ../../source/ref-changelog.md:434 msgid "" "Many examples have received significant updates, including simplified " "advanced-tensorflow and advanced-pytorch examples, improved macOS " -"compatibility of TensorFlow examples, and code examples for simulation. A" -" major upgrade is that all code examples now have a `requirements.txt` " -"(in addition to `pyproject.toml`)." +"compatibility of TensorFlow examples, and code examples for simulation. A " +"major upgrade is that all code examples now have a `requirements.txt` (in " +"addition to `pyproject.toml`)." msgstr "" #: ../../source/ref-changelog.md:436 msgid "" -"**General improvements** " -"([#1872](https://github.com/adap/flower/pull/1872), " -"[#1866](https://github.com/adap/flower/pull/1866), " -"[#1884](https://github.com/adap/flower/pull/1884), " -"[#1837](https://github.com/adap/flower/pull/1837), " -"[#1477](https://github.com/adap/flower/pull/1477), " -"[#2171](https://github.com/adap/flower/pull/2171))" +"**General improvements** ([#1872](https://github.com/adap/flower/pull/1872), " +"[#1866](https://github.com/adap/flower/pull/1866), [#1884](https://github." +"com/adap/flower/pull/1884), [#1837](https://github.com/adap/flower/" +"pull/1837), [#1477](https://github.com/adap/flower/pull/1477), [#2171]" +"(https://github.com/adap/flower/pull/2171))" msgstr "" #: ../../source/ref-changelog.md:444 @@ -16686,110 +17155,103 @@ msgstr "" #: ../../source/ref-changelog.md:450 msgid "" "`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " -"`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, " -"`Iacob-Alexandru-Andrei`, `JDRanpariya`, `Jean Charle Yaacoub`, `Kunal " -"Sarkhel`, `L. Jiang`, `Lennart Behme`, `Max Kapsecker`, `Michał`, `Nic " -"Lane`, `Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, " -"`Steve Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" +"`Chenyang Ma (Danny)`, `Daniel J. Beutel`, `Edoardo`, `Gautam Jajoo`, `Iacob-" +"Alexandru-Andrei`, `JDRanpariya`, `Jean Charle Yaacoub`, `Kunal Sarkhel`, " +"`L. Jiang`, `Lennart Behme`, `Max Kapsecker`, `Michał`, `Nic Lane`, " +"`Nikolaos Episkopos`, `Ragy`, `Saurav Maheshkar`, `Semo Yang`, `Steve " +"Laskaridis`, `Steven Hé (Sīchàng)`, `Taner Topal`" msgstr "" #: ../../source/ref-changelog.md:454 msgid "" -"**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and " -"example)** ([#1694](https://github.com/adap/flower/pull/1694), " -"[#1709](https://github.com/adap/flower/pull/1709), " -"[#1715](https://github.com/adap/flower/pull/1715), " -"[#1717](https://github.com/adap/flower/pull/1717), " -"[#1763](https://github.com/adap/flower/pull/1763), " -"[#1795](https://github.com/adap/flower/pull/1795))" +"**Introduce support for XGBoost (**`FedXgbNnAvg` **strategy and example)** " +"([#1694](https://github.com/adap/flower/pull/1694), [#1709](https://github." +"com/adap/flower/pull/1709), [#1715](https://github.com/adap/flower/" +"pull/1715), [#1717](https://github.com/adap/flower/pull/1717), [#1763]" +"(https://github.com/adap/flower/pull/1763), [#1795](https://github.com/adap/" +"flower/pull/1795))" msgstr "" #: ../../source/ref-changelog.md:456 msgid "" "XGBoost is a tree-based ensemble machine learning algorithm that uses " -"gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg`" -" " -"[strategy](https://github.com/adap/flower/tree/main/src/py/flwr/server/strategy/fedxgb_nn_avg.py)," -" and a [code example](https://github.com/adap/flower/tree/main/examples" -"/xgboost-quickstart) that demonstrates the usage of this new strategy in " -"an XGBoost project." +"gradient boosting to improve model accuracy. We added a new `FedXgbNnAvg` " +"[strategy](https://github.com/adap/flower/tree/main/src/py/flwr/server/" +"strategy/fedxgb_nn_avg.py), and a [code example](https://github.com/adap/" +"flower/tree/main/examples/xgboost-quickstart) that demonstrates the usage of " +"this new strategy in an XGBoost project." msgstr "" #: ../../source/ref-changelog.md:458 msgid "" -"**Introduce iOS SDK (preview)** " -"([#1621](https://github.com/adap/flower/pull/1621), " -"[#1764](https://github.com/adap/flower/pull/1764))" +"**Introduce iOS SDK (preview)** ([#1621](https://github.com/adap/flower/" +"pull/1621), [#1764](https://github.com/adap/flower/pull/1764))" msgstr "" #: ../../source/ref-changelog.md:460 msgid "" -"This is a major update for anyone wanting to implement Federated Learning" -" on iOS mobile devices. We now have a swift iOS SDK present under " -"[src/swift/flwr](https://github.com/adap/flower/tree/main/src/swift/flwr)" -" that will facilitate greatly the app creating process. To showcase its " -"use, the [iOS " +"This is a major update for anyone wanting to implement Federated Learning on " +"iOS mobile devices. We now have a swift iOS SDK present under [src/swift/" +"flwr](https://github.com/adap/flower/tree/main/src/swift/flwr) that will " +"facilitate greatly the app creating process. To showcase its use, the [iOS " "example](https://github.com/adap/flower/tree/main/examples/ios) has also " "been updated!" msgstr "" #: ../../source/ref-changelog.md:462 msgid "" -"**Introduce new \"What is Federated Learning?\" tutorial** " -"([#1657](https://github.com/adap/flower/pull/1657), " -"[#1721](https://github.com/adap/flower/pull/1721))" +"**Introduce new \"What is Federated Learning?\" tutorial** ([#1657](https://" +"github.com/adap/flower/pull/1657), [#1721](https://github.com/adap/flower/" +"pull/1721))" msgstr "" #: ../../source/ref-changelog.md:464 msgid "" -"A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-" -"what-is-federated-learning.html) in our documentation explains the basics" -" of Fedetated Learning. It enables anyone who's unfamiliar with Federated" -" Learning to start their journey with Flower. Forward it to anyone who's " +"A new [entry-level tutorial](https://flower.ai/docs/framework/tutorial-what-" +"is-federated-learning.html) in our documentation explains the basics of " +"Fedetated Learning. It enables anyone who's unfamiliar with Federated " +"Learning to start their journey with Flower. Forward it to anyone who's " "interested in Federated Learning!" msgstr "" #: ../../source/ref-changelog.md:466 msgid "" -"**Introduce new Flower Baseline: FedProx MNIST** " -"([#1513](https://github.com/adap/flower/pull/1513), " -"[#1680](https://github.com/adap/flower/pull/1680), " -"[#1681](https://github.com/adap/flower/pull/1681), " -"[#1679](https://github.com/adap/flower/pull/1679))" +"**Introduce new Flower Baseline: FedProx MNIST** ([#1513](https://github.com/" +"adap/flower/pull/1513), [#1680](https://github.com/adap/flower/pull/1680), " +"[#1681](https://github.com/adap/flower/pull/1681), [#1679](https://github." +"com/adap/flower/pull/1679))" msgstr "" #: ../../source/ref-changelog.md:468 msgid "" -"This new baseline replicates the MNIST+CNN task from the paper [Federated" -" Optimization in Heterogeneous Networks (Li et al., " -"2018)](https://arxiv.org/abs/1812.06127). It uses the `FedProx` strategy," -" which aims at making convergence more robust in heterogeneous settings." +"This new baseline replicates the MNIST+CNN task from the paper [Federated " +"Optimization in Heterogeneous Networks (Li et al., 2018)](https://arxiv.org/" +"abs/1812.06127). It uses the `FedProx` strategy, which aims at making " +"convergence more robust in heterogeneous settings." msgstr "" #: ../../source/ref-changelog.md:470 msgid "" -"**Introduce new Flower Baseline: FedAvg FEMNIST** " -"([#1655](https://github.com/adap/flower/pull/1655))" +"**Introduce new Flower Baseline: FedAvg FEMNIST** ([#1655](https://github." +"com/adap/flower/pull/1655))" msgstr "" #: ../../source/ref-changelog.md:472 msgid "" -"This new baseline replicates an experiment evaluating the performance of " -"the FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A " -"Benchmark for Federated Settings (Caldas et al., " -"2018)](https://arxiv.org/abs/1812.01097)." +"This new baseline replicates an experiment evaluating the performance of the " +"FedAvg algorithm on the FEMNIST dataset from the paper [LEAF: A Benchmark " +"for Federated Settings (Caldas et al., 2018)](https://arxiv.org/" +"abs/1812.01097)." msgstr "" #: ../../source/ref-changelog.md:474 msgid "" -"**Introduce (experimental) REST API** " -"([#1594](https://github.com/adap/flower/pull/1594), " -"[#1690](https://github.com/adap/flower/pull/1690), " -"[#1695](https://github.com/adap/flower/pull/1695), " -"[#1712](https://github.com/adap/flower/pull/1712), " -"[#1802](https://github.com/adap/flower/pull/1802), " -"[#1770](https://github.com/adap/flower/pull/1770), " -"[#1733](https://github.com/adap/flower/pull/1733))" +"**Introduce (experimental) REST API** ([#1594](https://github.com/adap/" +"flower/pull/1594), [#1690](https://github.com/adap/flower/pull/1690), [#1695]" +"(https://github.com/adap/flower/pull/1695), [#1712](https://github.com/adap/" +"flower/pull/1712), [#1802](https://github.com/adap/flower/pull/1802), [#1770]" +"(https://github.com/adap/flower/pull/1770), [#1733](https://github.com/adap/" +"flower/pull/1733))" msgstr "" #: ../../source/ref-changelog.md:476 @@ -16807,132 +17269,112 @@ msgstr "" #: ../../source/ref-changelog.md:480 msgid "" -"**Improve the (experimental) Driver API** " -"([#1663](https://github.com/adap/flower/pull/1663), " -"[#1666](https://github.com/adap/flower/pull/1666), " -"[#1667](https://github.com/adap/flower/pull/1667), " -"[#1664](https://github.com/adap/flower/pull/1664), " -"[#1675](https://github.com/adap/flower/pull/1675), " -"[#1676](https://github.com/adap/flower/pull/1676), " -"[#1693](https://github.com/adap/flower/pull/1693), " -"[#1662](https://github.com/adap/flower/pull/1662), " -"[#1794](https://github.com/adap/flower/pull/1794))" +"**Improve the (experimental) Driver API** ([#1663](https://github.com/adap/" +"flower/pull/1663), [#1666](https://github.com/adap/flower/pull/1666), [#1667]" +"(https://github.com/adap/flower/pull/1667), [#1664](https://github.com/adap/" +"flower/pull/1664), [#1675](https://github.com/adap/flower/pull/1675), [#1676]" +"(https://github.com/adap/flower/pull/1676), [#1693](https://github.com/adap/" +"flower/pull/1693), [#1662](https://github.com/adap/flower/pull/1662), [#1794]" +"(https://github.com/adap/flower/pull/1794))" msgstr "" #: ../../source/ref-changelog.md:482 msgid "" -"The Driver API is still an experimental feature, but this release " -"introduces some major upgrades. One of the main improvements is the " -"introduction of an SQLite database to store server state on disk (instead" -" of in-memory). Another improvement is that tasks (instructions or " -"results) that have been delivered will now be deleted. This greatly " -"improves the memory efficiency of a long-running Flower server." +"The Driver API is still an experimental feature, but this release introduces " +"some major upgrades. One of the main improvements is the introduction of an " +"SQLite database to store server state on disk (instead of in-memory). " +"Another improvement is that tasks (instructions or results) that have been " +"delivered will now be deleted. This greatly improves the memory efficiency " +"of a long-running Flower server." msgstr "" #: ../../source/ref-changelog.md:484 msgid "" -"**Fix spilling issues related to Ray during simulations** " -"([#1698](https://github.com/adap/flower/pull/1698))" +"**Fix spilling issues related to Ray during simulations** ([#1698](https://" +"github.com/adap/flower/pull/1698))" msgstr "" #: ../../source/ref-changelog.md:486 msgid "" -"While running long simulations, `ray` was sometimes spilling huge amounts" -" of data that would make the training unable to continue. This is now " -"fixed! 🎉" +"While running long simulations, `ray` was sometimes spilling huge amounts of " +"data that would make the training unable to continue. This is now fixed! 🎉" msgstr "" #: ../../source/ref-changelog.md:488 msgid "" -"**Add new example using** `TabNet` **and Flower** " -"([#1725](https://github.com/adap/flower/pull/1725))" +"**Add new example using** `TabNet` **and Flower** ([#1725](https://github." +"com/adap/flower/pull/1725))" msgstr "" #: ../../source/ref-changelog.md:490 msgid "" -"TabNet is a powerful and flexible framework for training machine learning" -" models on tabular data. We now have a federated example using Flower: " -"[quickstart-tabnet](https://github.com/adap/flower/tree/main/examples" -"/quickstart-tabnet)." +"TabNet is a powerful and flexible framework for training machine learning " +"models on tabular data. We now have a federated example using Flower: " +"[quickstart-tabnet](https://github.com/adap/flower/tree/main/examples/" +"quickstart-tabnet)." msgstr "" #: ../../source/ref-changelog.md:492 msgid "" -"**Add new how-to guide for monitoring simulations** " -"([#1649](https://github.com/adap/flower/pull/1649))" +"**Add new how-to guide for monitoring simulations** ([#1649](https://github." +"com/adap/flower/pull/1649))" msgstr "" #: ../../source/ref-changelog.md:494 msgid "" -"We now have a documentation guide to help users monitor their performance" -" during simulations." +"We now have a documentation guide to help users monitor their performance " +"during simulations." msgstr "" #: ../../source/ref-changelog.md:496 msgid "" -"**Add training metrics to** `History` **object during simulations** " -"([#1696](https://github.com/adap/flower/pull/1696))" +"**Add training metrics to** `History` **object during simulations** ([#1696]" +"(https://github.com/adap/flower/pull/1696))" msgstr "" #: ../../source/ref-changelog.md:498 msgid "" -"The `fit_metrics_aggregation_fn` can be used to aggregate training " -"metrics, but previous releases did not save the results in the `History` " -"object. This is now the case!" +"The `fit_metrics_aggregation_fn` can be used to aggregate training metrics, " +"but previous releases did not save the results in the `History` object. This " +"is now the case!" msgstr "" #: ../../source/ref-changelog.md:500 msgid "" -"**General improvements** " -"([#1659](https://github.com/adap/flower/pull/1659), " -"[#1646](https://github.com/adap/flower/pull/1646), " -"[#1647](https://github.com/adap/flower/pull/1647), " -"[#1471](https://github.com/adap/flower/pull/1471), " -"[#1648](https://github.com/adap/flower/pull/1648), " -"[#1651](https://github.com/adap/flower/pull/1651), " -"[#1652](https://github.com/adap/flower/pull/1652), " -"[#1653](https://github.com/adap/flower/pull/1653), " -"[#1659](https://github.com/adap/flower/pull/1659), " -"[#1665](https://github.com/adap/flower/pull/1665), " -"[#1670](https://github.com/adap/flower/pull/1670), " -"[#1672](https://github.com/adap/flower/pull/1672), " -"[#1677](https://github.com/adap/flower/pull/1677), " -"[#1684](https://github.com/adap/flower/pull/1684), " -"[#1683](https://github.com/adap/flower/pull/1683), " -"[#1686](https://github.com/adap/flower/pull/1686), " -"[#1682](https://github.com/adap/flower/pull/1682), " -"[#1685](https://github.com/adap/flower/pull/1685), " -"[#1692](https://github.com/adap/flower/pull/1692), " -"[#1705](https://github.com/adap/flower/pull/1705), " -"[#1708](https://github.com/adap/flower/pull/1708), " -"[#1711](https://github.com/adap/flower/pull/1711), " -"[#1713](https://github.com/adap/flower/pull/1713), " -"[#1714](https://github.com/adap/flower/pull/1714), " -"[#1718](https://github.com/adap/flower/pull/1718), " -"[#1716](https://github.com/adap/flower/pull/1716), " -"[#1723](https://github.com/adap/flower/pull/1723), " -"[#1735](https://github.com/adap/flower/pull/1735), " -"[#1678](https://github.com/adap/flower/pull/1678), " -"[#1750](https://github.com/adap/flower/pull/1750), " -"[#1753](https://github.com/adap/flower/pull/1753), " -"[#1736](https://github.com/adap/flower/pull/1736), " -"[#1766](https://github.com/adap/flower/pull/1766), " -"[#1760](https://github.com/adap/flower/pull/1760), " -"[#1775](https://github.com/adap/flower/pull/1775), " -"[#1776](https://github.com/adap/flower/pull/1776), " -"[#1777](https://github.com/adap/flower/pull/1777), " -"[#1779](https://github.com/adap/flower/pull/1779), " -"[#1784](https://github.com/adap/flower/pull/1784), " -"[#1773](https://github.com/adap/flower/pull/1773), " -"[#1755](https://github.com/adap/flower/pull/1755), " -"[#1789](https://github.com/adap/flower/pull/1789), " -"[#1788](https://github.com/adap/flower/pull/1788), " -"[#1798](https://github.com/adap/flower/pull/1798), " -"[#1799](https://github.com/adap/flower/pull/1799), " -"[#1739](https://github.com/adap/flower/pull/1739), " -"[#1800](https://github.com/adap/flower/pull/1800), " -"[#1804](https://github.com/adap/flower/pull/1804), " -"[#1805](https://github.com/adap/flower/pull/1805))" +"**General improvements** ([#1659](https://github.com/adap/flower/pull/1659), " +"[#1646](https://github.com/adap/flower/pull/1646), [#1647](https://github." +"com/adap/flower/pull/1647), [#1471](https://github.com/adap/flower/" +"pull/1471), [#1648](https://github.com/adap/flower/pull/1648), [#1651]" +"(https://github.com/adap/flower/pull/1651), [#1652](https://github.com/adap/" +"flower/pull/1652), [#1653](https://github.com/adap/flower/pull/1653), [#1659]" +"(https://github.com/adap/flower/pull/1659), [#1665](https://github.com/adap/" +"flower/pull/1665), [#1670](https://github.com/adap/flower/pull/1670), [#1672]" +"(https://github.com/adap/flower/pull/1672), [#1677](https://github.com/adap/" +"flower/pull/1677), [#1684](https://github.com/adap/flower/pull/1684), [#1683]" +"(https://github.com/adap/flower/pull/1683), [#1686](https://github.com/adap/" +"flower/pull/1686), [#1682](https://github.com/adap/flower/pull/1682), [#1685]" +"(https://github.com/adap/flower/pull/1685), [#1692](https://github.com/adap/" +"flower/pull/1692), [#1705](https://github.com/adap/flower/pull/1705), [#1708]" +"(https://github.com/adap/flower/pull/1708), [#1711](https://github.com/adap/" +"flower/pull/1711), [#1713](https://github.com/adap/flower/pull/1713), [#1714]" +"(https://github.com/adap/flower/pull/1714), [#1718](https://github.com/adap/" +"flower/pull/1718), [#1716](https://github.com/adap/flower/pull/1716), [#1723]" +"(https://github.com/adap/flower/pull/1723), [#1735](https://github.com/adap/" +"flower/pull/1735), [#1678](https://github.com/adap/flower/pull/1678), [#1750]" +"(https://github.com/adap/flower/pull/1750), [#1753](https://github.com/adap/" +"flower/pull/1753), [#1736](https://github.com/adap/flower/pull/1736), [#1766]" +"(https://github.com/adap/flower/pull/1766), [#1760](https://github.com/adap/" +"flower/pull/1760), [#1775](https://github.com/adap/flower/pull/1775), [#1776]" +"(https://github.com/adap/flower/pull/1776), [#1777](https://github.com/adap/" +"flower/pull/1777), [#1779](https://github.com/adap/flower/pull/1779), [#1784]" +"(https://github.com/adap/flower/pull/1784), [#1773](https://github.com/adap/" +"flower/pull/1773), [#1755](https://github.com/adap/flower/pull/1755), [#1789]" +"(https://github.com/adap/flower/pull/1789), [#1788](https://github.com/adap/" +"flower/pull/1788), [#1798](https://github.com/adap/flower/pull/1798), [#1799]" +"(https://github.com/adap/flower/pull/1799), [#1739](https://github.com/adap/" +"flower/pull/1739), [#1800](https://github.com/adap/flower/pull/1800), [#1804]" +"(https://github.com/adap/flower/pull/1804), [#1805](https://github.com/adap/" +"flower/pull/1805))" msgstr "" #: ../../source/ref-changelog.md:508 @@ -16941,8 +17383,8 @@ msgstr "" #: ../../source/ref-changelog.md:514 msgid "" -"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, " -"`Daniel J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" +"`Adam Narozniak`, `Alexander Viala Bellander`, `Charles Beauville`, `Daniel " +"J. Beutel`, `JDRanpariya`, `Lennart Behme`, `Taner Topal`" msgstr "" #: ../../source/ref-changelog.md:518 @@ -16953,24 +17395,24 @@ msgstr "" #: ../../source/ref-changelog.md:520 msgid "" -"The (experimental) Driver API now supports a `workload_id` that can be " -"used to identify which workload a task belongs to. It also supports a new" -" `group_id` that can be used, for example, to indicate the current " -"training round. Both the `workload_id` and `group_id` enable client nodes" -" to decide whether they want to handle a task or not." +"The (experimental) Driver API now supports a `workload_id` that can be used " +"to identify which workload a task belongs to. It also supports a new " +"`group_id` that can be used, for example, to indicate the current training " +"round. Both the `workload_id` and `group_id` enable client nodes to decide " +"whether they want to handle a task or not." msgstr "" #: ../../source/ref-changelog.md:522 msgid "" -"**Make Driver API and Fleet API address configurable** " -"([#1637](https://github.com/adap/flower/pull/1637))" +"**Make Driver API and Fleet API address configurable** ([#1637](https://" +"github.com/adap/flower/pull/1637))" msgstr "" #: ../../source/ref-changelog.md:524 msgid "" -"The (experimental) long-running Flower server (Driver API and Fleet API) " -"can now configure the server address of both Driver API (via `--driver-" -"api-address`) and Fleet API (via `--fleet-api-address`) when starting:" +"The (experimental) long-running Flower server (Driver API and Fleet API) can " +"now configure the server address of both Driver API (via `--driver-api-" +"address`) and Fleet API (via `--fleet-api-address`) when starting:" msgstr "" #: ../../source/ref-changelog.md:526 @@ -16985,55 +17427,51 @@ msgstr "" #: ../../source/ref-changelog.md:530 msgid "" -"**Add new example of Federated Learning using fastai and Flower** " -"([#1598](https://github.com/adap/flower/pull/1598))" +"**Add new example of Federated Learning using fastai and Flower** ([#1598]" +"(https://github.com/adap/flower/pull/1598))" msgstr "" #: ../../source/ref-changelog.md:532 msgid "" "A new code example (`quickstart-fastai`) demonstrates federated learning " "with [fastai](https://www.fast.ai/) and Flower. You can find it here: " -"[quickstart-fastai](https://github.com/adap/flower/tree/main/examples" -"/quickstart-fastai)." +"[quickstart-fastai](https://github.com/adap/flower/tree/main/examples/" +"quickstart-fastai)." msgstr "" #: ../../source/ref-changelog.md:534 msgid "" -"**Make Android example compatible with** `flwr >= 1.0.0` **and the latest" -" versions of Android** " -"([#1603](https://github.com/adap/flower/pull/1603))" +"**Make Android example compatible with** `flwr >= 1.0.0` **and the latest " +"versions of Android** ([#1603](https://github.com/adap/flower/pull/1603))" msgstr "" #: ../../source/ref-changelog.md:536 msgid "" -"The Android code example has received a substantial update: the project " -"is compatible with Flower 1.0 (and later), the UI received a full " -"refresh, and the project is updated to be compatible with newer Android " -"tooling." +"The Android code example has received a substantial update: the project is " +"compatible with Flower 1.0 (and later), the UI received a full refresh, and " +"the project is updated to be compatible with newer Android tooling." msgstr "" #: ../../source/ref-changelog.md:538 msgid "" -"**Add new `FedProx` strategy** " -"([#1619](https://github.com/adap/flower/pull/1619))" +"**Add new `FedProx` strategy** ([#1619](https://github.com/adap/flower/" +"pull/1619))" msgstr "" #: ../../source/ref-changelog.md:540 msgid "" -"This " -"[strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedprox.py)" -" is almost identical to " -"[`FedAvg`](https://github.com/adap/flower/blob/main/src/py/flwr/server/strategy/fedavg.py)," -" but helps users replicate what is described in this " -"[paper](https://arxiv.org/abs/1812.06127). It essentially adds a " -"parameter called `proximal_mu` to regularize the local models with " -"respect to the global models." +"This [strategy](https://github.com/adap/flower/blob/main/src/py/flwr/server/" +"strategy/fedprox.py) is almost identical to [`FedAvg`](https://github.com/" +"adap/flower/blob/main/src/py/flwr/server/strategy/fedavg.py), but helps " +"users replicate what is described in this [paper](https://arxiv.org/" +"abs/1812.06127). It essentially adds a parameter called `proximal_mu` to " +"regularize the local models with respect to the global models." msgstr "" #: ../../source/ref-changelog.md:542 msgid "" -"**Add new metrics to telemetry events** " -"([#1640](https://github.com/adap/flower/pull/1640))" +"**Add new metrics to telemetry events** ([#1640](https://github.com/adap/" +"flower/pull/1640))" msgstr "" #: ../../source/ref-changelog.md:544 @@ -17044,87 +17482,73 @@ msgstr "" #: ../../source/ref-changelog.md:546 msgid "" -"**Add new custom strategy tutorial section** " -"[#1623](https://github.com/adap/flower/pull/1623)" +"**Add new custom strategy tutorial section** [#1623](https://github.com/adap/" +"flower/pull/1623)" msgstr "" #: ../../source/ref-changelog.md:548 msgid "" -"The Flower tutorial now has a new section that covers implementing a " -"custom strategy from scratch: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" -"/tutorial-build-a-strategy-from-scratch-pytorch.ipynb)" +"The Flower tutorial now has a new section that covers implementing a custom " +"strategy from scratch: [Open in Colab](https://colab.research.google.com/" +"github/adap/flower/blob/main/doc/source/tutorial-build-a-strategy-from-" +"scratch-pytorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:550 msgid "" -"**Add new custom serialization tutorial section** " -"([#1622](https://github.com/adap/flower/pull/1622))" +"**Add new custom serialization tutorial section** ([#1622](https://github." +"com/adap/flower/pull/1622))" msgstr "" #: ../../source/ref-changelog.md:552 msgid "" -"The Flower tutorial now has a new section that covers custom " -"serialization: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/doc/source" -"/tutorial-customize-the-client-pytorch.ipynb)" +"The Flower tutorial now has a new section that covers custom serialization: " +"[Open in Colab](https://colab.research.google.com/github/adap/flower/blob/" +"main/doc/source/tutorial-customize-the-client-pytorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:554 msgid "" -"**General improvements** " -"([#1638](https://github.com/adap/flower/pull/1638), " -"[#1634](https://github.com/adap/flower/pull/1634), " -"[#1636](https://github.com/adap/flower/pull/1636), " -"[#1635](https://github.com/adap/flower/pull/1635), " -"[#1633](https://github.com/adap/flower/pull/1633), " -"[#1632](https://github.com/adap/flower/pull/1632), " -"[#1631](https://github.com/adap/flower/pull/1631), " -"[#1630](https://github.com/adap/flower/pull/1630), " -"[#1627](https://github.com/adap/flower/pull/1627), " -"[#1593](https://github.com/adap/flower/pull/1593), " -"[#1616](https://github.com/adap/flower/pull/1616), " -"[#1615](https://github.com/adap/flower/pull/1615), " -"[#1607](https://github.com/adap/flower/pull/1607), " -"[#1609](https://github.com/adap/flower/pull/1609), " -"[#1608](https://github.com/adap/flower/pull/1608), " -"[#1603](https://github.com/adap/flower/pull/1603), " -"[#1590](https://github.com/adap/flower/pull/1590), " -"[#1580](https://github.com/adap/flower/pull/1580), " -"[#1599](https://github.com/adap/flower/pull/1599), " -"[#1600](https://github.com/adap/flower/pull/1600), " -"[#1601](https://github.com/adap/flower/pull/1601), " -"[#1597](https://github.com/adap/flower/pull/1597), " -"[#1595](https://github.com/adap/flower/pull/1595), " -"[#1591](https://github.com/adap/flower/pull/1591), " -"[#1588](https://github.com/adap/flower/pull/1588), " -"[#1589](https://github.com/adap/flower/pull/1589), " -"[#1587](https://github.com/adap/flower/pull/1587), " -"[#1573](https://github.com/adap/flower/pull/1573), " -"[#1581](https://github.com/adap/flower/pull/1581), " -"[#1578](https://github.com/adap/flower/pull/1578), " -"[#1574](https://github.com/adap/flower/pull/1574), " -"[#1572](https://github.com/adap/flower/pull/1572), " -"[#1586](https://github.com/adap/flower/pull/1586))" +"**General improvements** ([#1638](https://github.com/adap/flower/pull/1638), " +"[#1634](https://github.com/adap/flower/pull/1634), [#1636](https://github." +"com/adap/flower/pull/1636), [#1635](https://github.com/adap/flower/" +"pull/1635), [#1633](https://github.com/adap/flower/pull/1633), [#1632]" +"(https://github.com/adap/flower/pull/1632), [#1631](https://github.com/adap/" +"flower/pull/1631), [#1630](https://github.com/adap/flower/pull/1630), [#1627]" +"(https://github.com/adap/flower/pull/1627), [#1593](https://github.com/adap/" +"flower/pull/1593), [#1616](https://github.com/adap/flower/pull/1616), [#1615]" +"(https://github.com/adap/flower/pull/1615), [#1607](https://github.com/adap/" +"flower/pull/1607), [#1609](https://github.com/adap/flower/pull/1609), [#1608]" +"(https://github.com/adap/flower/pull/1608), [#1603](https://github.com/adap/" +"flower/pull/1603), [#1590](https://github.com/adap/flower/pull/1590), [#1580]" +"(https://github.com/adap/flower/pull/1580), [#1599](https://github.com/adap/" +"flower/pull/1599), [#1600](https://github.com/adap/flower/pull/1600), [#1601]" +"(https://github.com/adap/flower/pull/1601), [#1597](https://github.com/adap/" +"flower/pull/1597), [#1595](https://github.com/adap/flower/pull/1595), [#1591]" +"(https://github.com/adap/flower/pull/1591), [#1588](https://github.com/adap/" +"flower/pull/1588), [#1589](https://github.com/adap/flower/pull/1589), [#1587]" +"(https://github.com/adap/flower/pull/1587), [#1573](https://github.com/adap/" +"flower/pull/1573), [#1581](https://github.com/adap/flower/pull/1581), [#1578]" +"(https://github.com/adap/flower/pull/1578), [#1574](https://github.com/adap/" +"flower/pull/1574), [#1572](https://github.com/adap/flower/pull/1572), [#1586]" +"(https://github.com/adap/flower/pull/1586))" msgstr "" #: ../../source/ref-changelog.md:558 msgid "" -"**Updated documentation** " -"([#1629](https://github.com/adap/flower/pull/1629), " -"[#1628](https://github.com/adap/flower/pull/1628), " -"[#1620](https://github.com/adap/flower/pull/1620), " -"[#1618](https://github.com/adap/flower/pull/1618), " -"[#1617](https://github.com/adap/flower/pull/1617), " -"[#1613](https://github.com/adap/flower/pull/1613), " -"[#1614](https://github.com/adap/flower/pull/1614))" +"**Updated documentation** ([#1629](https://github.com/adap/flower/" +"pull/1629), [#1628](https://github.com/adap/flower/pull/1628), [#1620]" +"(https://github.com/adap/flower/pull/1620), [#1618](https://github.com/adap/" +"flower/pull/1618), [#1617](https://github.com/adap/flower/pull/1617), [#1613]" +"(https://github.com/adap/flower/pull/1613), [#1614](https://github.com/adap/" +"flower/pull/1614))" msgstr "" #: ../../source/ref-changelog.md:560 ../../source/ref-changelog.md:627 msgid "" -"As usual, the documentation has improved quite a bit. It is another step " -"in our effort to make the Flower documentation the best documentation of " -"any project. Stay tuned and as always, feel free to provide feedback!" +"As usual, the documentation has improved quite a bit. It is another step in " +"our effort to make the Flower documentation the best documentation of any " +"project. Stay tuned and as always, feel free to provide feedback!" msgstr "" #: ../../source/ref-changelog.md:566 @@ -17133,15 +17557,14 @@ msgstr "" #: ../../source/ref-changelog.md:572 msgid "" -"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L." -" Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" +"`Adam Narozniak`, `Charles Beauville`, `Daniel J. Beutel`, `Edoardo`, `L. " +"Jiang`, `Ragy`, `Taner Topal`, `dannymcy`" msgstr "" #: ../../source/ref-changelog.md:576 msgid "" -"**Introduce new Flower Baseline: FedAvg MNIST** " -"([#1497](https://github.com/adap/flower/pull/1497), " -"[#1552](https://github.com/adap/flower/pull/1552))" +"**Introduce new Flower Baseline: FedAvg MNIST** ([#1497](https://github.com/" +"adap/flower/pull/1497), [#1552](https://github.com/adap/flower/pull/1552))" msgstr "" #: ../../source/ref-changelog.md:578 @@ -17150,207 +17573,195 @@ msgid "" "implementations useful especially to FL newcomers. They will typically " "revisit well known papers from the literature, and be suitable for " "integration in your own application or for experimentation, in order to " -"deepen your knowledge of FL in general. Today's release is the first in " -"this series. [Read more.](https://flower.ai/blog/2023-01-12-fl-starter-" -"pack-fedavg-mnist-cnn/)" +"deepen your knowledge of FL in general. Today's release is the first in this " +"series. [Read more.](https://flower.ai/blog/2023-01-12-fl-starter-pack-" +"fedavg-mnist-cnn/)" msgstr "" #: ../../source/ref-changelog.md:580 msgid "" -"**Improve GPU support in simulations** " -"([#1555](https://github.com/adap/flower/pull/1555))" +"**Improve GPU support in simulations** ([#1555](https://github.com/adap/" +"flower/pull/1555))" msgstr "" #: ../../source/ref-changelog.md:582 msgid "" -"The Ray-based Virtual Client Engine (`start_simulation`) has been updated" -" to improve GPU support. The update includes some of the hard-earned " -"lessons from scaling simulations in GPU cluster environments. New " -"defaults make running GPU-based simulations substantially more robust." +"The Ray-based Virtual Client Engine (`start_simulation`) has been updated to " +"improve GPU support. The update includes some of the hard-earned lessons " +"from scaling simulations in GPU cluster environments. New defaults make " +"running GPU-based simulations substantially more robust." msgstr "" #: ../../source/ref-changelog.md:584 msgid "" -"**Improve GPU support in Jupyter Notebook tutorials** " -"([#1527](https://github.com/adap/flower/pull/1527), " -"[#1558](https://github.com/adap/flower/pull/1558))" +"**Improve GPU support in Jupyter Notebook tutorials** ([#1527](https://" +"github.com/adap/flower/pull/1527), [#1558](https://github.com/adap/flower/" +"pull/1558))" msgstr "" #: ../../source/ref-changelog.md:586 msgid "" -"Some users reported that Jupyter Notebooks have not always been easy to " -"use on GPU instances. We listened and made improvements to all of our " -"Jupyter notebooks! Check out the updated notebooks here:" +"Some users reported that Jupyter Notebooks have not always been easy to use " +"on GPU instances. We listened and made improvements to all of our Jupyter " +"notebooks! Check out the updated notebooks here:" msgstr "" #: ../../source/ref-changelog.md:588 msgid "" -"[An Introduction to Federated Learning](https://flower.ai/docs/framework" -"/tutorial-get-started-with-flower-pytorch.html)" +"[An Introduction to Federated Learning](https://flower.ai/docs/framework/" +"tutorial-get-started-with-flower-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:589 msgid "" -"[Strategies in Federated Learning](https://flower.ai/docs/framework" -"/tutorial-use-a-federated-learning-strategy-pytorch.html)" +"[Strategies in Federated Learning](https://flower.ai/docs/framework/tutorial-" +"use-a-federated-learning-strategy-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:590 msgid "" -"[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a" -"-strategy-from-scratch-pytorch.html)" +"[Building a Strategy](https://flower.ai/docs/framework/tutorial-build-a-" +"strategy-from-scratch-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:591 msgid "" -"[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-" -"customize-the-client-pytorch.html)" +"[Client and NumPyClient](https://flower.ai/docs/framework/tutorial-customize-" +"the-client-pytorch.html)" msgstr "" #: ../../source/ref-changelog.md:593 msgid "" -"**Introduce optional telemetry** " -"([#1533](https://github.com/adap/flower/pull/1533), " -"[#1544](https://github.com/adap/flower/pull/1544), " -"[#1584](https://github.com/adap/flower/pull/1584))" +"**Introduce optional telemetry** ([#1533](https://github.com/adap/flower/" +"pull/1533), [#1544](https://github.com/adap/flower/pull/1544), [#1584]" +"(https://github.com/adap/flower/pull/1584))" msgstr "" #: ../../source/ref-changelog.md:595 msgid "" -"After a [request for " -"feedback](https://github.com/adap/flower/issues/1534) from the community," -" the Flower open-source project introduces optional collection of " -"*anonymous* usage metrics to make well-informed decisions to improve " -"Flower. Doing this enables the Flower team to understand how Flower is " -"used and what challenges users might face." +"After a [request for feedback](https://github.com/adap/flower/issues/1534) " +"from the community, the Flower open-source project introduces optional " +"collection of *anonymous* usage metrics to make well-informed decisions to " +"improve Flower. Doing this enables the Flower team to understand how Flower " +"is used and what challenges users might face." msgstr "" #: ../../source/ref-changelog.md:597 msgid "" -"**Flower is a friendly framework for collaborative AI and data science.**" -" Staying true to this statement, Flower makes it easy to disable " -"telemetry for users who do not want to share anonymous usage metrics. " -"[Read more.](https://flower.ai/docs/telemetry.html)." +"**Flower is a friendly framework for collaborative AI and data science.** " +"Staying true to this statement, Flower makes it easy to disable telemetry " +"for users who do not want to share anonymous usage metrics. [Read more.]" +"(https://flower.ai/docs/telemetry.html)." msgstr "" #: ../../source/ref-changelog.md:599 msgid "" -"**Introduce (experimental) Driver API** " -"([#1520](https://github.com/adap/flower/pull/1520), " -"[#1525](https://github.com/adap/flower/pull/1525), " -"[#1545](https://github.com/adap/flower/pull/1545), " -"[#1546](https://github.com/adap/flower/pull/1546), " -"[#1550](https://github.com/adap/flower/pull/1550), " -"[#1551](https://github.com/adap/flower/pull/1551), " -"[#1567](https://github.com/adap/flower/pull/1567))" +"**Introduce (experimental) Driver API** ([#1520](https://github.com/adap/" +"flower/pull/1520), [#1525](https://github.com/adap/flower/pull/1525), [#1545]" +"(https://github.com/adap/flower/pull/1545), [#1546](https://github.com/adap/" +"flower/pull/1546), [#1550](https://github.com/adap/flower/pull/1550), [#1551]" +"(https://github.com/adap/flower/pull/1551), [#1567](https://github.com/adap/" +"flower/pull/1567))" msgstr "" #: ../../source/ref-changelog.md:601 msgid "" "Flower now has a new (experimental) Driver API which will enable fully " "programmable, async, and multi-tenant Federated Learning and Federated " -"Analytics applications. Phew, that's a lot! Going forward, the Driver API" -" will be the abstraction that many upcoming features will be built on - " -"and you can start building those things now, too." +"Analytics applications. Phew, that's a lot! Going forward, the Driver API " +"will be the abstraction that many upcoming features will be built on - and " +"you can start building those things now, too." msgstr "" #: ../../source/ref-changelog.md:603 msgid "" -"The Driver API also enables a new execution mode in which the server runs" -" indefinitely. Multiple individual workloads can run concurrently and " -"start and stop their execution independent of the server. This is " -"especially useful for users who want to deploy Flower in production." +"The Driver API also enables a new execution mode in which the server runs " +"indefinitely. Multiple individual workloads can run concurrently and start " +"and stop their execution independent of the server. This is especially " +"useful for users who want to deploy Flower in production." msgstr "" #: ../../source/ref-changelog.md:605 msgid "" -"To learn more, check out the `mt-pytorch` code example. We look forward " -"to you feedback!" +"To learn more, check out the `mt-pytorch` code example. We look forward to " +"you feedback!" msgstr "" #: ../../source/ref-changelog.md:607 msgid "" -"Please note: *The Driver API is still experimental and will likely change" -" significantly over time.*" +"Please note: *The Driver API is still experimental and will likely change " +"significantly over time.*" msgstr "" #: ../../source/ref-changelog.md:609 msgid "" -"**Add new Federated Analytics with Pandas example** " -"([#1469](https://github.com/adap/flower/pull/1469), " -"[#1535](https://github.com/adap/flower/pull/1535))" +"**Add new Federated Analytics with Pandas example** ([#1469](https://github." +"com/adap/flower/pull/1469), [#1535](https://github.com/adap/flower/" +"pull/1535))" msgstr "" #: ../../source/ref-changelog.md:611 msgid "" -"A new code example (`quickstart-pandas`) demonstrates federated analytics" -" with Pandas and Flower. You can find it here: [quickstart-" -"pandas](https://github.com/adap/flower/tree/main/examples/quickstart-" -"pandas)." +"A new code example (`quickstart-pandas`) demonstrates federated analytics " +"with Pandas and Flower. You can find it here: [quickstart-pandas](https://" +"github.com/adap/flower/tree/main/examples/quickstart-pandas)." msgstr "" #: ../../source/ref-changelog.md:613 msgid "" -"**Add new strategies: Krum and MultiKrum** " -"([#1481](https://github.com/adap/flower/pull/1481))" +"**Add new strategies: Krum and MultiKrum** ([#1481](https://github.com/adap/" +"flower/pull/1481))" msgstr "" #: ../../source/ref-changelog.md:615 msgid "" "Edoardo, a computer science student at the Sapienza University of Rome, " -"contributed a new `Krum` strategy that enables users to easily use Krum " -"and MultiKrum in their workloads." +"contributed a new `Krum` strategy that enables users to easily use Krum and " +"MultiKrum in their workloads." msgstr "" #: ../../source/ref-changelog.md:617 msgid "" -"**Update C++ example to be compatible with Flower v1.2.0** " -"([#1495](https://github.com/adap/flower/pull/1495))" +"**Update C++ example to be compatible with Flower v1.2.0** ([#1495](https://" +"github.com/adap/flower/pull/1495))" msgstr "" #: ../../source/ref-changelog.md:619 msgid "" -"The C++ code example has received a substantial update to make it " -"compatible with the latest version of Flower." +"The C++ code example has received a substantial update to make it compatible " +"with the latest version of Flower." msgstr "" #: ../../source/ref-changelog.md:621 msgid "" -"**General improvements** " -"([#1491](https://github.com/adap/flower/pull/1491), " -"[#1504](https://github.com/adap/flower/pull/1504), " -"[#1506](https://github.com/adap/flower/pull/1506), " -"[#1514](https://github.com/adap/flower/pull/1514), " -"[#1522](https://github.com/adap/flower/pull/1522), " -"[#1523](https://github.com/adap/flower/pull/1523), " -"[#1526](https://github.com/adap/flower/pull/1526), " -"[#1528](https://github.com/adap/flower/pull/1528), " -"[#1547](https://github.com/adap/flower/pull/1547), " -"[#1549](https://github.com/adap/flower/pull/1549), " -"[#1560](https://github.com/adap/flower/pull/1560), " -"[#1564](https://github.com/adap/flower/pull/1564), " -"[#1566](https://github.com/adap/flower/pull/1566))" +"**General improvements** ([#1491](https://github.com/adap/flower/pull/1491), " +"[#1504](https://github.com/adap/flower/pull/1504), [#1506](https://github." +"com/adap/flower/pull/1506), [#1514](https://github.com/adap/flower/" +"pull/1514), [#1522](https://github.com/adap/flower/pull/1522), [#1523]" +"(https://github.com/adap/flower/pull/1523), [#1526](https://github.com/adap/" +"flower/pull/1526), [#1528](https://github.com/adap/flower/pull/1528), [#1547]" +"(https://github.com/adap/flower/pull/1547), [#1549](https://github.com/adap/" +"flower/pull/1549), [#1560](https://github.com/adap/flower/pull/1560), [#1564]" +"(https://github.com/adap/flower/pull/1564), [#1566](https://github.com/adap/" +"flower/pull/1566))" msgstr "" #: ../../source/ref-changelog.md:625 msgid "" -"**Updated documentation** " -"([#1494](https://github.com/adap/flower/pull/1494), " -"[#1496](https://github.com/adap/flower/pull/1496), " -"[#1500](https://github.com/adap/flower/pull/1500), " -"[#1503](https://github.com/adap/flower/pull/1503), " -"[#1505](https://github.com/adap/flower/pull/1505), " -"[#1524](https://github.com/adap/flower/pull/1524), " -"[#1518](https://github.com/adap/flower/pull/1518), " -"[#1519](https://github.com/adap/flower/pull/1519), " -"[#1515](https://github.com/adap/flower/pull/1515))" +"**Updated documentation** ([#1494](https://github.com/adap/flower/" +"pull/1494), [#1496](https://github.com/adap/flower/pull/1496), [#1500]" +"(https://github.com/adap/flower/pull/1500), [#1503](https://github.com/adap/" +"flower/pull/1503), [#1505](https://github.com/adap/flower/pull/1505), [#1524]" +"(https://github.com/adap/flower/pull/1524), [#1518](https://github.com/adap/" +"flower/pull/1518), [#1519](https://github.com/adap/flower/pull/1519), [#1515]" +"(https://github.com/adap/flower/pull/1515))" msgstr "" #: ../../source/ref-changelog.md:629 msgid "" -"One highlight is the new [first time contributor " -"guide](https://flower.ai/docs/first-time-contributors.html): if you've " -"never contributed on GitHub before, this is the perfect place to start!" +"One highlight is the new [first time contributor guide](https://flower.ai/" +"docs/first-time-contributors.html): if you've never contributed on GitHub " +"before, this is the perfect place to start!" msgstr "" #: ../../source/ref-changelog.md:635 @@ -17367,110 +17778,106 @@ msgstr "" msgid "" "`Akis Linardos`, `Christopher S`, `Daniel J. Beutel`, `George`, `Jan " "Schlicht`, `Mohammad Fares`, `Pedro Porto Buarque de Gusmão`, `Philipp " -"Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, " -"`danielnugraha`, `edogab33`" +"Wiesner`, `Rob Luke`, `Taner Topal`, `VasundharaAgarwal`, `danielnugraha`, " +"`edogab33`" msgstr "" #: ../../source/ref-changelog.md:645 msgid "" -"**Introduce Differential Privacy wrappers (preview)** " -"([#1357](https://github.com/adap/flower/pull/1357), " -"[#1460](https://github.com/adap/flower/pull/1460))" +"**Introduce Differential Privacy wrappers (preview)** ([#1357](https://" +"github.com/adap/flower/pull/1357), [#1460](https://github.com/adap/flower/" +"pull/1460))" msgstr "" #: ../../source/ref-changelog.md:647 msgid "" -"The first (experimental) preview of pluggable Differential Privacy " -"wrappers enables easy configuration and usage of differential privacy " -"(DP). The pluggable DP wrappers enable framework-agnostic **and** " -"strategy-agnostic usage of both client-side DP and server-side DP. Head " -"over to the Flower docs, a new explainer goes into more detail." +"The first (experimental) preview of pluggable Differential Privacy wrappers " +"enables easy configuration and usage of differential privacy (DP). The " +"pluggable DP wrappers enable framework-agnostic **and** strategy-agnostic " +"usage of both client-side DP and server-side DP. Head over to the Flower " +"docs, a new explainer goes into more detail." msgstr "" #: ../../source/ref-changelog.md:649 msgid "" -"**New iOS CoreML code example** " -"([#1289](https://github.com/adap/flower/pull/1289))" +"**New iOS CoreML code example** ([#1289](https://github.com/adap/flower/" +"pull/1289))" msgstr "" #: ../../source/ref-changelog.md:651 msgid "" -"Flower goes iOS! A massive new code example shows how Flower clients can " -"be built for iOS. The code example contains both Flower iOS SDK " -"components that can be used for many tasks, and one task example running " -"on CoreML." +"Flower goes iOS! A massive new code example shows how Flower clients can be " +"built for iOS. The code example contains both Flower iOS SDK components that " +"can be used for many tasks, and one task example running on CoreML." msgstr "" #: ../../source/ref-changelog.md:653 msgid "" -"**New FedMedian strategy** " -"([#1461](https://github.com/adap/flower/pull/1461))" +"**New FedMedian strategy** ([#1461](https://github.com/adap/flower/" +"pull/1461))" msgstr "" #: ../../source/ref-changelog.md:655 msgid "" -"The new `FedMedian` strategy implements Federated Median (FedMedian) by " -"[Yin et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." +"The new `FedMedian` strategy implements Federated Median (FedMedian) by [Yin " +"et al., 2018](https://arxiv.org/pdf/1803.01498v1.pdf)." msgstr "" #: ../../source/ref-changelog.md:657 msgid "" -"**Log** `Client` **exceptions in Virtual Client Engine** " -"([#1493](https://github.com/adap/flower/pull/1493))" +"**Log** `Client` **exceptions in Virtual Client Engine** ([#1493](https://" +"github.com/adap/flower/pull/1493))" msgstr "" #: ../../source/ref-changelog.md:659 msgid "" -"All `Client` exceptions happening in the VCE are now logged by default " -"and not just exposed to the configured `Strategy` (via the `failures` " -"argument)." +"All `Client` exceptions happening in the VCE are now logged by default and " +"not just exposed to the configured `Strategy` (via the `failures` argument)." msgstr "" #: ../../source/ref-changelog.md:661 msgid "" -"**Improve Virtual Client Engine internals** " -"([#1401](https://github.com/adap/flower/pull/1401), " -"[#1453](https://github.com/adap/flower/pull/1453))" +"**Improve Virtual Client Engine internals** ([#1401](https://github.com/adap/" +"flower/pull/1401), [#1453](https://github.com/adap/flower/pull/1453))" msgstr "" #: ../../source/ref-changelog.md:663 msgid "" -"Some internals of the Virtual Client Engine have been revamped. The VCE " -"now uses Ray 2.0 under the hood, the value type of the `client_resources`" -" dictionary changed to `float` to allow fractions of resources to be " +"Some internals of the Virtual Client Engine have been revamped. The VCE now " +"uses Ray 2.0 under the hood, the value type of the `client_resources` " +"dictionary changed to `float` to allow fractions of resources to be " "allocated." msgstr "" #: ../../source/ref-changelog.md:665 msgid "" -"**Support optional** `Client`**/**`NumPyClient` **methods in Virtual " -"Client Engine**" +"**Support optional** `Client`**/**`NumPyClient` **methods in Virtual Client " +"Engine**" msgstr "" #: ../../source/ref-changelog.md:667 msgid "" -"The Virtual Client Engine now has full support for optional `Client` (and" -" `NumPyClient`) methods." +"The Virtual Client Engine now has full support for optional `Client` (and " +"`NumPyClient`) methods." msgstr "" #: ../../source/ref-changelog.md:669 msgid "" -"**Provide type information to packages using** `flwr` " -"([#1377](https://github.com/adap/flower/pull/1377))" +"**Provide type information to packages using** `flwr` ([#1377](https://" +"github.com/adap/flower/pull/1377))" msgstr "" #: ../../source/ref-changelog.md:671 msgid "" -"The package `flwr` is now bundled with a `py.typed` file indicating that " -"the package is typed. This enables typing support for projects or " -"packages that use `flwr` by enabling them to improve their code using " -"static type checkers like `mypy`." +"The package `flwr` is now bundled with a `py.typed` file indicating that the " +"package is typed. This enables typing support for projects or packages that " +"use `flwr` by enabling them to improve their code using static type checkers " +"like `mypy`." msgstr "" #: ../../source/ref-changelog.md:673 msgid "" -"**Updated code example** " -"([#1344](https://github.com/adap/flower/pull/1344), " +"**Updated code example** ([#1344](https://github.com/adap/flower/pull/1344), " "[#1347](https://github.com/adap/flower/pull/1347))" msgstr "" @@ -17482,24 +17889,18 @@ msgstr "" #: ../../source/ref-changelog.md:677 msgid "" -"**Updated documentation** " -"([#1355](https://github.com/adap/flower/pull/1355), " -"[#1558](https://github.com/adap/flower/pull/1558), " -"[#1379](https://github.com/adap/flower/pull/1379), " -"[#1380](https://github.com/adap/flower/pull/1380), " -"[#1381](https://github.com/adap/flower/pull/1381), " -"[#1332](https://github.com/adap/flower/pull/1332), " -"[#1391](https://github.com/adap/flower/pull/1391), " -"[#1403](https://github.com/adap/flower/pull/1403), " -"[#1364](https://github.com/adap/flower/pull/1364), " -"[#1409](https://github.com/adap/flower/pull/1409), " -"[#1419](https://github.com/adap/flower/pull/1419), " -"[#1444](https://github.com/adap/flower/pull/1444), " -"[#1448](https://github.com/adap/flower/pull/1448), " -"[#1417](https://github.com/adap/flower/pull/1417), " -"[#1449](https://github.com/adap/flower/pull/1449), " -"[#1465](https://github.com/adap/flower/pull/1465), " -"[#1467](https://github.com/adap/flower/pull/1467))" +"**Updated documentation** ([#1355](https://github.com/adap/flower/" +"pull/1355), [#1558](https://github.com/adap/flower/pull/1558), [#1379]" +"(https://github.com/adap/flower/pull/1379), [#1380](https://github.com/adap/" +"flower/pull/1380), [#1381](https://github.com/adap/flower/pull/1381), [#1332]" +"(https://github.com/adap/flower/pull/1332), [#1391](https://github.com/adap/" +"flower/pull/1391), [#1403](https://github.com/adap/flower/pull/1403), [#1364]" +"(https://github.com/adap/flower/pull/1364), [#1409](https://github.com/adap/" +"flower/pull/1409), [#1419](https://github.com/adap/flower/pull/1419), [#1444]" +"(https://github.com/adap/flower/pull/1444), [#1448](https://github.com/adap/" +"flower/pull/1448), [#1417](https://github.com/adap/flower/pull/1417), [#1449]" +"(https://github.com/adap/flower/pull/1449), [#1465](https://github.com/adap/" +"flower/pull/1465), [#1467](https://github.com/adap/flower/pull/1467))" msgstr "" #: ../../source/ref-changelog.md:679 @@ -17510,47 +17911,45 @@ msgstr "" #: ../../source/ref-changelog.md:681 msgid "" -"**Restructured documentation** " -"([#1387](https://github.com/adap/flower/pull/1387))" +"**Restructured documentation** ([#1387](https://github.com/adap/flower/" +"pull/1387))" msgstr "" #: ../../source/ref-changelog.md:683 msgid "" -"The documentation has been restructured to make it easier to navigate. " -"This is just the first step in a larger effort to make the Flower " -"documentation the best documentation of any project ever. Stay tuned!" +"The documentation has been restructured to make it easier to navigate. This " +"is just the first step in a larger effort to make the Flower documentation " +"the best documentation of any project ever. Stay tuned!" msgstr "" #: ../../source/ref-changelog.md:685 msgid "" -"**Open in Colab button** " -"([#1389](https://github.com/adap/flower/pull/1389))" +"**Open in Colab button** ([#1389](https://github.com/adap/flower/pull/1389))" msgstr "" #: ../../source/ref-changelog.md:687 msgid "" -"The four parts of the Flower Federated Learning Tutorial now come with a " -"new `Open in Colab` button. No need to install anything on your local " -"machine, you can now use and learn about Flower in your browser, it's " -"only a single click away." +"The four parts of the Flower Federated Learning Tutorial now come with a new " +"`Open in Colab` button. No need to install anything on your local machine, " +"you can now use and learn about Flower in your browser, it's only a single " +"click away." msgstr "" #: ../../source/ref-changelog.md:689 msgid "" -"**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468)," -" [#1470](https://github.com/adap/flower/pull/1470), " -"[#1472](https://github.com/adap/flower/pull/1472), " -"[#1473](https://github.com/adap/flower/pull/1473), " -"[#1474](https://github.com/adap/flower/pull/1474), " -"[#1475](https://github.com/adap/flower/pull/1475))" +"**Improved tutorial** ([#1468](https://github.com/adap/flower/pull/1468), " +"[#1470](https://github.com/adap/flower/pull/1470), [#1472](https://github." +"com/adap/flower/pull/1472), [#1473](https://github.com/adap/flower/" +"pull/1473), [#1474](https://github.com/adap/flower/pull/1474), [#1475]" +"(https://github.com/adap/flower/pull/1475))" msgstr "" #: ../../source/ref-changelog.md:691 msgid "" "The Flower Federated Learning Tutorial has two brand-new parts covering " "custom strategies (still WIP) and the distinction between `Client` and " -"`NumPyClient`. The existing parts one and two have also been improved " -"(many small changes and fixes)." +"`NumPyClient`. The existing parts one and two have also been improved (many " +"small changes and fixes)." msgstr "" #: ../../source/ref-changelog.md:697 @@ -17575,93 +17974,79 @@ msgstr "" #: ../../source/ref-changelog.md:704 msgid "" -"Tons of small API cleanups resulting in a more coherent developer " -"experience" +"Tons of small API cleanups resulting in a more coherent developer experience" msgstr "" #: ../../source/ref-changelog.md:708 msgid "" "We would like to give our **special thanks** to all the contributors who " -"made Flower 1.0 possible (in reverse [GitHub " -"Contributors](https://github.com/adap/flower/graphs/contributors) order):" +"made Flower 1.0 possible (in reverse [GitHub Contributors](https://github." +"com/adap/flower/graphs/contributors) order):" msgstr "" #: ../../source/ref-changelog.md:710 msgid "" -"[@rtaiello](https://github.com/rtaiello), " -"[@g-pichler](https://github.com/g-pichler), [@rob-" -"luke](https://github.com/rob-luke), [@andreea-zaharia](https://github.com" -"/andreea-zaharia), [@kinshukdua](https://github.com/kinshukdua), " -"[@nfnt](https://github.com/nfnt), " -"[@tatiana-s](https://github.com/tatiana-s), " -"[@TParcollet](https://github.com/TParcollet), " -"[@vballoli](https://github.com/vballoli), " -"[@negedng](https://github.com/negedng), " -"[@RISHIKESHAVAN](https://github.com/RISHIKESHAVAN), " -"[@hei411](https://github.com/hei411), " -"[@SebastianSpeitel](https://github.com/SebastianSpeitel), " -"[@AmitChaulwar](https://github.com/AmitChaulwar), " -"[@Rubiel1](https://github.com/Rubiel1), [@FANTOME-PAN](https://github.com" -"/FANTOME-PAN), [@Rono-BC](https://github.com/Rono-BC), " -"[@lbhm](https://github.com/lbhm), " -"[@sishtiaq](https://github.com/sishtiaq), " -"[@remde](https://github.com/remde), [@Jueun-Park](https://github.com" -"/Jueun-Park), [@architjen](https://github.com/architjen), " -"[@PratikGarai](https://github.com/PratikGarai), " -"[@mrinaald](https://github.com/mrinaald), " -"[@zliel](https://github.com/zliel), " -"[@MeiruiJiang](https://github.com/MeiruiJiang), " -"[@sancarlim](https://github.com/sancarlim), " -"[@gubertoli](https://github.com/gubertoli), " -"[@Vingt100](https://github.com/Vingt100), " -"[@MakGulati](https://github.com/MakGulati), " -"[@cozek](https://github.com/cozek), " -"[@jafermarq](https://github.com/jafermarq), " -"[@sisco0](https://github.com/sisco0), " -"[@akhilmathurs](https://github.com/akhilmathurs), " -"[@CanTuerk](https://github.com/CanTuerk), " -"[@mariaboerner1987](https://github.com/mariaboerner1987), " -"[@pedropgusmao](https://github.com/pedropgusmao), " -"[@tanertopal](https://github.com/tanertopal), " -"[@danieljanes](https://github.com/danieljanes)." +"[@rtaiello](https://github.com/rtaiello), [@g-pichler](https://github.com/g-" +"pichler), [@rob-luke](https://github.com/rob-luke), [@andreea-zaharia]" +"(https://github.com/andreea-zaharia), [@kinshukdua](https://github.com/" +"kinshukdua), [@nfnt](https://github.com/nfnt), [@tatiana-s](https://github." +"com/tatiana-s), [@TParcollet](https://github.com/TParcollet), [@vballoli]" +"(https://github.com/vballoli), [@negedng](https://github.com/negedng), " +"[@RISHIKESHAVAN](https://github.com/RISHIKESHAVAN), [@hei411](https://github." +"com/hei411), [@SebastianSpeitel](https://github.com/SebastianSpeitel), " +"[@AmitChaulwar](https://github.com/AmitChaulwar), [@Rubiel1](https://github." +"com/Rubiel1), [@FANTOME-PAN](https://github.com/FANTOME-PAN), [@Rono-BC]" +"(https://github.com/Rono-BC), [@lbhm](https://github.com/lbhm), [@sishtiaq]" +"(https://github.com/sishtiaq), [@remde](https://github.com/remde), [@Jueun-" +"Park](https://github.com/Jueun-Park), [@architjen](https://github.com/" +"architjen), [@PratikGarai](https://github.com/PratikGarai), [@mrinaald]" +"(https://github.com/mrinaald), [@zliel](https://github.com/zliel), " +"[@MeiruiJiang](https://github.com/MeiruiJiang), [@sancarlim](https://github." +"com/sancarlim), [@gubertoli](https://github.com/gubertoli), [@Vingt100]" +"(https://github.com/Vingt100), [@MakGulati](https://github.com/MakGulati), " +"[@cozek](https://github.com/cozek), [@jafermarq](https://github.com/" +"jafermarq), [@sisco0](https://github.com/sisco0), [@akhilmathurs](https://" +"github.com/akhilmathurs), [@CanTuerk](https://github.com/CanTuerk), " +"[@mariaboerner1987](https://github.com/mariaboerner1987), [@pedropgusmao]" +"(https://github.com/pedropgusmao), [@tanertopal](https://github.com/" +"tanertopal), [@danieljanes](https://github.com/danieljanes)." msgstr "" #: ../../source/ref-changelog.md:714 msgid "" -"**All arguments must be passed as keyword arguments** " -"([#1338](https://github.com/adap/flower/pull/1338))" +"**All arguments must be passed as keyword arguments** ([#1338](https://" +"github.com/adap/flower/pull/1338))" msgstr "" #: ../../source/ref-changelog.md:716 msgid "" -"Pass all arguments as keyword arguments, positional arguments are not " -"longer supported. Code that uses positional arguments (e.g., " -"`start_client(\"127.0.0.1:8080\", FlowerClient())`) must add the keyword " -"for each positional argument (e.g., " -"`start_client(server_address=\"127.0.0.1:8080\", " -"client=FlowerClient())`)." +"Pass all arguments as keyword arguments, positional arguments are not longer " +"supported. Code that uses positional arguments (e.g., " +"`start_client(\"127.0.0.1:8080\", FlowerClient())`) must add the keyword for " +"each positional argument (e.g., " +"`start_client(server_address=\"127.0.0.1:8080\", client=FlowerClient())`)." msgstr "" #: ../../source/ref-changelog.md:718 msgid "" "**Introduce configuration object** `ServerConfig` **in** `start_server` " -"**and** `start_simulation` " -"([#1317](https://github.com/adap/flower/pull/1317))" +"**and** `start_simulation` ([#1317](https://github.com/adap/flower/" +"pull/1317))" msgstr "" #: ../../source/ref-changelog.md:720 msgid "" -"Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": " -"600.0}`, `start_server` and `start_simulation` now expect a configuration" -" object of type `flwr.server.ServerConfig`. `ServerConfig` takes the same" -" arguments that as the previous config dict, but it makes writing type-" -"safe code easier and the default parameters values more transparent." +"Instead of a config dictionary `{\"num_rounds\": 3, \"round_timeout\": 600.0}" +"`, `start_server` and `start_simulation` now expect a configuration object " +"of type `flwr.server.ServerConfig`. `ServerConfig` takes the same arguments " +"that as the previous config dict, but it makes writing type-safe code easier " +"and the default parameters values more transparent." msgstr "" #: ../../source/ref-changelog.md:722 msgid "" -"**Rename built-in strategy parameters for clarity** " -"([#1334](https://github.com/adap/flower/pull/1334))" +"**Rename built-in strategy parameters for clarity** ([#1334](https://github." +"com/adap/flower/pull/1334))" msgstr "" #: ../../source/ref-changelog.md:724 @@ -17684,17 +18069,17 @@ msgstr "" #: ../../source/ref-changelog.md:730 msgid "" -"**Update default arguments of built-in strategies** " -"([#1278](https://github.com/adap/flower/pull/1278))" +"**Update default arguments of built-in strategies** ([#1278](https://github." +"com/adap/flower/pull/1278))" msgstr "" #: ../../source/ref-changelog.md:732 msgid "" "All built-in strategies now use `fraction_fit=1.0` and " -"`fraction_evaluate=1.0`, which means they select *all* currently " -"available clients for training and evaluation. Projects that relied on " -"the previous default values can get the previous behaviour by " -"initializing the strategy in the following way:" +"`fraction_evaluate=1.0`, which means they select *all* currently available " +"clients for training and evaluation. Projects that relied on the previous " +"default values can get the previous behaviour by initializing the strategy " +"in the following way:" msgstr "" #: ../../source/ref-changelog.md:734 @@ -17703,14 +18088,14 @@ msgstr "" #: ../../source/ref-changelog.md:736 msgid "" -"**Add** `server_round` **to** `Strategy.evaluate` " -"([#1334](https://github.com/adap/flower/pull/1334))" +"**Add** `server_round` **to** `Strategy.evaluate` ([#1334](https://github." +"com/adap/flower/pull/1334))" msgstr "" #: ../../source/ref-changelog.md:738 msgid "" -"The `Strategy` method `evaluate` now receives the current round of " -"federated learning/evaluation as the first parameter." +"The `Strategy` method `evaluate` now receives the current round of federated " +"learning/evaluation as the first parameter." msgstr "" #: ../../source/ref-changelog.md:740 @@ -17723,39 +18108,40 @@ msgstr "" msgid "" "The `evaluate_fn` passed to built-in strategies like `FedAvg` now takes " "three parameters: (1) The current round of federated learning/evaluation " -"(`server_round`), (2) the model parameters to evaluate (`parameters`), " -"and (3) a config dictionary (`config`)." +"(`server_round`), (2) the model parameters to evaluate (`parameters`), and " +"(3) a config dictionary (`config`)." msgstr "" #: ../../source/ref-changelog.md:744 msgid "" -"**Rename** `rnd` **to** `server_round` " -"([#1321](https://github.com/adap/flower/pull/1321))" +"**Rename** `rnd` **to** `server_round` ([#1321](https://github.com/adap/" +"flower/pull/1321))" msgstr "" #: ../../source/ref-changelog.md:746 msgid "" "Several Flower methods and functions (`evaluate_fn`, `configure_fit`, " "`aggregate_fit`, `configure_evaluate`, `aggregate_evaluate`) receive the " -"current round of federated learning/evaluation as their first parameter. " -"To improve reaability and avoid confusion with *random*, this parameter " -"has been renamed from `rnd` to `server_round`." +"current round of federated learning/evaluation as their first parameter. To " +"improve reaability and avoid confusion with *random*, this parameter has " +"been renamed from `rnd` to `server_round`." msgstr "" #: ../../source/ref-changelog.md:748 msgid "" -"**Move** `flwr.dataset` **to** `flwr_baselines` " -"([#1273](https://github.com/adap/flower/pull/1273))" +"**Move** `flwr.dataset` **to** `flwr_baselines` ([#1273](https://github.com/" +"adap/flower/pull/1273))" msgstr "" #: ../../source/ref-changelog.md:750 -msgid "The experimental package `flwr.dataset` was migrated to Flower Baselines." +msgid "" +"The experimental package `flwr.dataset` was migrated to Flower Baselines." msgstr "" #: ../../source/ref-changelog.md:752 msgid "" -"**Remove experimental strategies** " -"([#1280](https://github.com/adap/flower/pull/1280))" +"**Remove experimental strategies** ([#1280](https://github.com/adap/flower/" +"pull/1280))" msgstr "" #: ../../source/ref-changelog.md:754 @@ -17766,9 +18152,8 @@ msgstr "" #: ../../source/ref-changelog.md:756 msgid "" -"**Rename** `Weights` **to** `NDArrays` " -"([#1258](https://github.com/adap/flower/pull/1258), " -"[#1259](https://github.com/adap/flower/pull/1259))" +"**Rename** `Weights` **to** `NDArrays` ([#1258](https://github.com/adap/" +"flower/pull/1258), [#1259](https://github.com/adap/flower/pull/1259))" msgstr "" #: ../../source/ref-changelog.md:758 @@ -17779,21 +18164,21 @@ msgstr "" #: ../../source/ref-changelog.md:760 msgid "" -"**Remove antiquated** `force_final_distributed_eval` **from** " -"`start_server` ([#1258](https://github.com/adap/flower/pull/1258), " -"[#1259](https://github.com/adap/flower/pull/1259))" +"**Remove antiquated** `force_final_distributed_eval` **from** `start_server` " +"([#1258](https://github.com/adap/flower/pull/1258), [#1259](https://github." +"com/adap/flower/pull/1259))" msgstr "" #: ../../source/ref-changelog.md:762 msgid "" -"The `start_server` parameter `force_final_distributed_eval` has long been" -" a historic artefact, in this release it is finally gone for good." +"The `start_server` parameter `force_final_distributed_eval` has long been a " +"historic artefact, in this release it is finally gone for good." msgstr "" #: ../../source/ref-changelog.md:764 msgid "" -"**Make** `get_parameters` **configurable** " -"([#1242](https://github.com/adap/flower/pull/1242))" +"**Make** `get_parameters` **configurable** ([#1242](https://github.com/adap/" +"flower/pull/1242))" msgstr "" #: ../../source/ref-changelog.md:766 @@ -17811,64 +18196,62 @@ msgstr "" #: ../../source/ref-changelog.md:770 msgid "" "The `start_simulation` function now accepts a configuration dictionary " -"`config` instead of the `num_rounds` integer. This improves the " -"consistency between `start_simulation` and `start_server` and makes " -"transitioning between the two easier." +"`config` instead of the `num_rounds` integer. This improves the consistency " +"between `start_simulation` and `start_server` and makes transitioning " +"between the two easier." msgstr "" #: ../../source/ref-changelog.md:774 msgid "" -"**Support Python 3.10** " -"([#1320](https://github.com/adap/flower/pull/1320))" +"**Support Python 3.10** ([#1320](https://github.com/adap/flower/pull/1320))" msgstr "" #: ../../source/ref-changelog.md:776 msgid "" -"The previous Flower release introduced experimental support for Python " -"3.10, this release declares Python 3.10 support as stable." +"The previous Flower release introduced experimental support for Python 3.10, " +"this release declares Python 3.10 support as stable." msgstr "" #: ../../source/ref-changelog.md:778 msgid "" -"**Make all** `Client` **and** `NumPyClient` **methods optional** " -"([#1260](https://github.com/adap/flower/pull/1260), " -"[#1277](https://github.com/adap/flower/pull/1277))" +"**Make all** `Client` **and** `NumPyClient` **methods optional** ([#1260]" +"(https://github.com/adap/flower/pull/1260), [#1277](https://github.com/adap/" +"flower/pull/1277))" msgstr "" #: ../../source/ref-changelog.md:780 msgid "" "The `Client`/`NumPyClient` methods `get_properties`, `get_parameters`, " -"`fit`, and `evaluate` are all optional. This enables writing clients that" -" implement, for example, only `fit`, but no other method. No need to " +"`fit`, and `evaluate` are all optional. This enables writing clients that " +"implement, for example, only `fit`, but no other method. No need to " "implement `evaluate` when using centralized evaluation!" msgstr "" #: ../../source/ref-changelog.md:782 msgid "" -"**Enable passing a** `Server` **instance to** `start_simulation` " -"([#1281](https://github.com/adap/flower/pull/1281))" +"**Enable passing a** `Server` **instance to** `start_simulation` ([#1281]" +"(https://github.com/adap/flower/pull/1281))" msgstr "" #: ../../source/ref-changelog.md:784 msgid "" -"Similar to `start_server`, `start_simulation` now accepts a full `Server`" -" instance. This enables users to heavily customize the execution of " -"eperiments and opens the door to running, for example, async FL using the" -" Virtual Client Engine." +"Similar to `start_server`, `start_simulation` now accepts a full `Server` " +"instance. This enables users to heavily customize the execution of " +"eperiments and opens the door to running, for example, async FL using the " +"Virtual Client Engine." msgstr "" #: ../../source/ref-changelog.md:786 msgid "" -"**Update code examples** " -"([#1291](https://github.com/adap/flower/pull/1291), " -"[#1286](https://github.com/adap/flower/pull/1286), " -"[#1282](https://github.com/adap/flower/pull/1282))" +"**Update code examples** ([#1291](https://github.com/adap/flower/pull/1291), " +"[#1286](https://github.com/adap/flower/pull/1286), [#1282](https://github." +"com/adap/flower/pull/1282))" msgstr "" #: ../../source/ref-changelog.md:788 msgid "" -"Many code examples received small or even large maintenance updates, " -"among them are" +"Many code examples received small or even large maintenance updates, among " +"them are" msgstr "" #: ../../source/ref-changelog.md:790 @@ -17897,8 +18280,8 @@ msgstr "" #: ../../source/ref-changelog.md:797 msgid "" -"**Remove the obsolete simulation example** " -"([#1328](https://github.com/adap/flower/pull/1328))" +"**Remove the obsolete simulation example** ([#1328](https://github.com/adap/" +"flower/pull/1328))" msgstr "" #: ../../source/ref-changelog.md:799 @@ -17910,27 +18293,24 @@ msgstr "" #: ../../source/ref-changelog.md:801 msgid "" -"**Update documentation** " -"([#1223](https://github.com/adap/flower/pull/1223), " -"[#1209](https://github.com/adap/flower/pull/1209), " -"[#1251](https://github.com/adap/flower/pull/1251), " -"[#1257](https://github.com/adap/flower/pull/1257), " -"[#1267](https://github.com/adap/flower/pull/1267), " -"[#1268](https://github.com/adap/flower/pull/1268), " -"[#1300](https://github.com/adap/flower/pull/1300), " -"[#1304](https://github.com/adap/flower/pull/1304), " -"[#1305](https://github.com/adap/flower/pull/1305), " -"[#1307](https://github.com/adap/flower/pull/1307))" +"**Update documentation** ([#1223](https://github.com/adap/flower/pull/1223), " +"[#1209](https://github.com/adap/flower/pull/1209), [#1251](https://github." +"com/adap/flower/pull/1251), [#1257](https://github.com/adap/flower/" +"pull/1257), [#1267](https://github.com/adap/flower/pull/1267), [#1268]" +"(https://github.com/adap/flower/pull/1268), [#1300](https://github.com/adap/" +"flower/pull/1300), [#1304](https://github.com/adap/flower/pull/1304), [#1305]" +"(https://github.com/adap/flower/pull/1305), [#1307](https://github.com/adap/" +"flower/pull/1307))" msgstr "" #: ../../source/ref-changelog.md:803 msgid "" "One substantial documentation update fixes multiple smaller rendering " "issues, makes titles more succinct to improve navigation, removes a " -"deprecated library, updates documentation dependencies, includes the " -"`flwr.common` module in the API reference, includes support for markdown-" -"based documentation, migrates the changelog from `.rst` to `.md`, and " -"fixes a number of smaller details!" +"deprecated library, updates documentation dependencies, includes the `flwr." +"common` module in the API reference, includes support for markdown-based " +"documentation, migrates the changelog from `.rst` to `.md`, and fixes a " +"number of smaller details!" msgstr "" #: ../../source/ref-changelog.md:805 ../../source/ref-changelog.md:860 @@ -17940,30 +18320,28 @@ msgstr "" #: ../../source/ref-changelog.md:807 msgid "" -"Add round number to fit and evaluate log messages " -"([#1266](https://github.com/adap/flower/pull/1266))" +"Add round number to fit and evaluate log messages ([#1266](https://github." +"com/adap/flower/pull/1266))" msgstr "" #: ../../source/ref-changelog.md:808 msgid "" -"Add secure gRPC connection to the `advanced_tensorflow` code example " -"([#847](https://github.com/adap/flower/pull/847))" +"Add secure gRPC connection to the `advanced_tensorflow` code example ([#847]" +"(https://github.com/adap/flower/pull/847))" msgstr "" #: ../../source/ref-changelog.md:809 msgid "" -"Update developer tooling " -"([#1231](https://github.com/adap/flower/pull/1231), " -"[#1276](https://github.com/adap/flower/pull/1276), " -"[#1301](https://github.com/adap/flower/pull/1301), " -"[#1310](https://github.com/adap/flower/pull/1310))" +"Update developer tooling ([#1231](https://github.com/adap/flower/pull/1231), " +"[#1276](https://github.com/adap/flower/pull/1276), [#1301](https://github." +"com/adap/flower/pull/1301), [#1310](https://github.com/adap/flower/" +"pull/1310))" msgstr "" #: ../../source/ref-changelog.md:810 msgid "" -"Rename ProtoBuf messages to improve consistency " -"([#1214](https://github.com/adap/flower/pull/1214), " -"[#1258](https://github.com/adap/flower/pull/1258), " +"Rename ProtoBuf messages to improve consistency ([#1214](https://github.com/" +"adap/flower/pull/1214), [#1258](https://github.com/adap/flower/pull/1258), " "[#1259](https://github.com/adap/flower/pull/1259))" msgstr "" @@ -17973,123 +18351,120 @@ msgstr "" #: ../../source/ref-changelog.md:816 msgid "" -"**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** " -"([#919](https://github.com/adap/flower/pull/919), " -"[#1127](https://github.com/adap/flower/pull/1127), " -"[#914](https://github.com/adap/flower/pull/914))" +"**Flower Baselines (preview): FedOpt, FedBN, FedAvgM** ([#919](https://" +"github.com/adap/flower/pull/919), [#1127](https://github.com/adap/flower/" +"pull/1127), [#914](https://github.com/adap/flower/pull/914))" msgstr "" #: ../../source/ref-changelog.md:818 msgid "" "The first preview release of Flower Baselines has arrived! We're " "kickstarting Flower Baselines with implementations of FedOpt (FedYogi, " -"FedAdam, FedAdagrad), FedBN, and FedAvgM. Check the documentation on how " -"to use [Flower Baselines](https://flower.ai/docs/using-baselines.html). " -"With this first preview release we're also inviting the community to " -"[contribute their own baselines](https://flower.ai/docs/baselines/how-to-" -"contribute-baselines.html)." +"FedAdam, FedAdagrad), FedBN, and FedAvgM. Check the documentation on how to " +"use [Flower Baselines](https://flower.ai/docs/using-baselines.html). With " +"this first preview release we're also inviting the community to [contribute " +"their own baselines](https://flower.ai/docs/baselines/how-to-contribute-" +"baselines.html)." msgstr "" #: ../../source/ref-changelog.md:820 msgid "" -"**C++ client SDK (preview) and code example** " -"([#1111](https://github.com/adap/flower/pull/1111))" +"**C++ client SDK (preview) and code example** ([#1111](https://github.com/" +"adap/flower/pull/1111))" msgstr "" #: ../../source/ref-changelog.md:822 msgid "" -"Preview support for Flower clients written in C++. The C++ preview " -"includes a Flower client SDK and a quickstart code example that " -"demonstrates a simple C++ client using the SDK." +"Preview support for Flower clients written in C++. The C++ preview includes " +"a Flower client SDK and a quickstart code example that demonstrates a simple " +"C++ client using the SDK." msgstr "" #: ../../source/ref-changelog.md:824 msgid "" -"**Add experimental support for Python 3.10 and Python 3.11** " -"([#1135](https://github.com/adap/flower/pull/1135))" +"**Add experimental support for Python 3.10 and Python 3.11** ([#1135]" +"(https://github.com/adap/flower/pull/1135))" msgstr "" #: ../../source/ref-changelog.md:826 msgid "" -"Python 3.10 is the latest stable release of Python and Python 3.11 is due" -" to be released in October. This Flower release adds experimental support" -" for both Python versions." +"Python 3.10 is the latest stable release of Python and Python 3.11 is due to " +"be released in October. This Flower release adds experimental support for " +"both Python versions." msgstr "" #: ../../source/ref-changelog.md:828 msgid "" -"**Aggregate custom metrics through user-provided functions** " -"([#1144](https://github.com/adap/flower/pull/1144))" +"**Aggregate custom metrics through user-provided functions** ([#1144]" +"(https://github.com/adap/flower/pull/1144))" msgstr "" #: ../../source/ref-changelog.md:830 msgid "" -"Custom metrics (e.g., `accuracy`) can now be aggregated without having to" -" customize the strategy. Built-in strategies support two new arguments, " +"Custom metrics (e.g., `accuracy`) can now be aggregated without having to " +"customize the strategy. Built-in strategies support two new arguments, " "`fit_metrics_aggregation_fn` and `evaluate_metrics_aggregation_fn`, that " "allow passing custom metric aggregation functions." msgstr "" #: ../../source/ref-changelog.md:832 msgid "" -"**User-configurable round timeout** " -"([#1162](https://github.com/adap/flower/pull/1162))" +"**User-configurable round timeout** ([#1162](https://github.com/adap/flower/" +"pull/1162))" msgstr "" #: ../../source/ref-changelog.md:834 msgid "" "A new configuration value allows the round timeout to be set for " -"`start_server` and `start_simulation`. If the `config` dictionary " -"contains a `round_timeout` key (with a `float` value in seconds), the " -"server will wait *at least* `round_timeout` seconds before it closes the " -"connection." +"`start_server` and `start_simulation`. If the `config` dictionary contains a " +"`round_timeout` key (with a `float` value in seconds), the server will wait " +"*at least* `round_timeout` seconds before it closes the connection." msgstr "" #: ../../source/ref-changelog.md:836 msgid "" -"**Enable both federated evaluation and centralized evaluation to be used " -"at the same time in all built-in strategies** " -"([#1091](https://github.com/adap/flower/pull/1091))" +"**Enable both federated evaluation and centralized evaluation to be used at " +"the same time in all built-in strategies** ([#1091](https://github.com/adap/" +"flower/pull/1091))" msgstr "" #: ../../source/ref-changelog.md:838 msgid "" -"Built-in strategies can now perform both federated evaluation (i.e., " -"client-side) and centralized evaluation (i.e., server-side) in the same " -"round. Federated evaluation can be disabled by setting `fraction_eval` to" -" `0.0`." +"Built-in strategies can now perform both federated evaluation (i.e., client-" +"side) and centralized evaluation (i.e., server-side) in the same round. " +"Federated evaluation can be disabled by setting `fraction_eval` to `0.0`." msgstr "" #: ../../source/ref-changelog.md:840 msgid "" -"**Two new Jupyter Notebook tutorials** " -"([#1141](https://github.com/adap/flower/pull/1141))" +"**Two new Jupyter Notebook tutorials** ([#1141](https://github.com/adap/" +"flower/pull/1141))" msgstr "" #: ../../source/ref-changelog.md:842 msgid "" -"Two Jupyter Notebook tutorials (compatible with Google Colab) explain " -"basic and intermediate Flower features:" +"Two Jupyter Notebook tutorials (compatible with Google Colab) explain basic " +"and intermediate Flower features:" msgstr "" #: ../../source/ref-changelog.md:844 msgid "" -"*An Introduction to Federated Learning*: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-1" -"-Intro-to-FL-PyTorch.ipynb)" +"*An Introduction to Federated Learning*: [Open in Colab](https://colab." +"research.google.com/github/adap/flower/blob/main/tutorials/Flower-1-Intro-to-" +"FL-PyTorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:846 msgid "" -"*Using Strategies in Federated Learning*: [Open in " -"Colab](https://colab.research.google.com/github/adap/flower/blob/main/tutorials/Flower-2" -"-Strategies-in-FL-PyTorch.ipynb)" +"*Using Strategies in Federated Learning*: [Open in Colab](https://colab." +"research.google.com/github/adap/flower/blob/main/tutorials/Flower-2-" +"Strategies-in-FL-PyTorch.ipynb)" msgstr "" #: ../../source/ref-changelog.md:848 msgid "" -"**New FedAvgM strategy (Federated Averaging with Server Momentum)** " -"([#1076](https://github.com/adap/flower/pull/1076))" +"**New FedAvgM strategy (Federated Averaging with Server Momentum)** ([#1076]" +"(https://github.com/adap/flower/pull/1076))" msgstr "" #: ../../source/ref-changelog.md:850 @@ -18100,8 +18475,8 @@ msgstr "" #: ../../source/ref-changelog.md:852 msgid "" -"**New advanced PyTorch code example** " -"([#1007](https://github.com/adap/flower/pull/1007))" +"**New advanced PyTorch code example** ([#1007](https://github.com/adap/" +"flower/pull/1007))" msgstr "" #: ../../source/ref-changelog.md:854 @@ -18112,8 +18487,7 @@ msgstr "" #: ../../source/ref-changelog.md:856 msgid "" -"**New JAX code example** " -"([#906](https://github.com/adap/flower/pull/906), " +"**New JAX code example** ([#906](https://github.com/adap/flower/pull/906), " "[#1143](https://github.com/adap/flower/pull/1143))" msgstr "" @@ -18137,41 +18511,40 @@ msgstr "" #: ../../source/ref-changelog.md:864 msgid "" -"New documentation for [implementing " -"strategies](https://flower.ai/docs/framework/how-to-implement-" -"strategies.html) ([#1097](https://github.com/adap/flower/pull/1097), " -"[#1175](https://github.com/adap/flower/pull/1175))" +"New documentation for [implementing strategies](https://flower.ai/docs/" +"framework/how-to-implement-strategies.html) ([#1097](https://github.com/adap/" +"flower/pull/1097), [#1175](https://github.com/adap/flower/pull/1175))" msgstr "" #: ../../source/ref-changelog.md:865 msgid "" -"New mobile-friendly documentation theme " -"([#1174](https://github.com/adap/flower/pull/1174))" +"New mobile-friendly documentation theme ([#1174](https://github.com/adap/" +"flower/pull/1174))" msgstr "" #: ../../source/ref-changelog.md:866 msgid "" "Limit version range for (optional) `ray` dependency to include only " -"compatible releases (`>=1.9.2,<1.12.0`) " -"([#1205](https://github.com/adap/flower/pull/1205))" +"compatible releases (`>=1.9.2,<1.12.0`) ([#1205](https://github.com/adap/" +"flower/pull/1205))" msgstr "" #: ../../source/ref-changelog.md:870 msgid "" -"**Remove deprecated support for Python 3.6** " -"([#871](https://github.com/adap/flower/pull/871))" +"**Remove deprecated support for Python 3.6** ([#871](https://github.com/adap/" +"flower/pull/871))" msgstr "" #: ../../source/ref-changelog.md:871 msgid "" -"**Remove deprecated KerasClient** " -"([#857](https://github.com/adap/flower/pull/857))" +"**Remove deprecated KerasClient** ([#857](https://github.com/adap/flower/" +"pull/857))" msgstr "" #: ../../source/ref-changelog.md:872 msgid "" -"**Remove deprecated no-op extra installs** " -"([#973](https://github.com/adap/flower/pull/973))" +"**Remove deprecated no-op extra installs** ([#973](https://github.com/adap/" +"flower/pull/973))" msgstr "" #: ../../source/ref-changelog.md:873 @@ -18182,20 +18555,20 @@ msgstr "" #: ../../source/ref-changelog.md:874 msgid "" -"**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** " -"([#1107](https://github.com/adap/flower/pull/1107))" +"**Remove deprecated QffedAvg strategy (replaced by QFedAvg)** ([#1107]" +"(https://github.com/adap/flower/pull/1107))" msgstr "" #: ../../source/ref-changelog.md:875 msgid "" -"**Remove deprecated DefaultStrategy strategy** " -"([#1142](https://github.com/adap/flower/pull/1142))" +"**Remove deprecated DefaultStrategy strategy** ([#1142](https://github.com/" +"adap/flower/pull/1142))" msgstr "" #: ../../source/ref-changelog.md:876 msgid "" -"**Remove deprecated support for eval_fn accuracy return value** " -"([#1142](https://github.com/adap/flower/pull/1142))" +"**Remove deprecated support for eval_fn accuracy return value** ([#1142]" +"(https://github.com/adap/flower/pull/1142))" msgstr "" #: ../../source/ref-changelog.md:877 @@ -18211,156 +18584,152 @@ msgstr "" #: ../../source/ref-changelog.md:883 msgid "" "**Improved Virtual Client Engine compatibility with Jupyter Notebook / " -"Google Colab** ([#866](https://github.com/adap/flower/pull/866), " -"[#872](https://github.com/adap/flower/pull/872), " -"[#833](https://github.com/adap/flower/pull/833), " -"[#1036](https://github.com/adap/flower/pull/1036))" +"Google Colab** ([#866](https://github.com/adap/flower/pull/866), [#872]" +"(https://github.com/adap/flower/pull/872), [#833](https://github.com/adap/" +"flower/pull/833), [#1036](https://github.com/adap/flower/pull/1036))" msgstr "" #: ../../source/ref-changelog.md:885 msgid "" -"Simulations (using the Virtual Client Engine through `start_simulation`) " -"now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " +"Simulations (using the Virtual Client Engine through `start_simulation`) now " +"work more smoothly on Jupyter Notebooks (incl. Google Colab) after " "installing Flower with the `simulation` extra (`pip install " "flwr[simulation]`)." msgstr "" #: ../../source/ref-changelog.md:887 msgid "" -"**New Jupyter Notebook code example** " -"([#833](https://github.com/adap/flower/pull/833))" +"**New Jupyter Notebook code example** ([#833](https://github.com/adap/flower/" +"pull/833))" msgstr "" #: ../../source/ref-changelog.md:889 msgid "" -"A new code example (`quickstart_simulation`) demonstrates Flower " -"simulations using the Virtual Client Engine through Jupyter Notebook " -"(incl. Google Colab)." +"A new code example (`quickstart_simulation`) demonstrates Flower simulations " +"using the Virtual Client Engine through Jupyter Notebook (incl. Google " +"Colab)." msgstr "" #: ../../source/ref-changelog.md:891 msgid "" -"**Client properties (feature preview)** " -"([#795](https://github.com/adap/flower/pull/795))" +"**Client properties (feature preview)** ([#795](https://github.com/adap/" +"flower/pull/795))" msgstr "" #: ../../source/ref-changelog.md:893 msgid "" -"Clients can implement a new method `get_properties` to enable server-side" -" strategies to query client properties." +"Clients can implement a new method `get_properties` to enable server-side " +"strategies to query client properties." msgstr "" #: ../../source/ref-changelog.md:895 msgid "" -"**Experimental Android support with TFLite** " -"([#865](https://github.com/adap/flower/pull/865))" +"**Experimental Android support with TFLite** ([#865](https://github.com/adap/" +"flower/pull/865))" msgstr "" #: ../../source/ref-changelog.md:897 msgid "" "Android support has finally arrived in `main`! Flower is both client-" "agnostic and framework-agnostic by design. One can integrate arbitrary " -"client platforms and with this release, using Flower on Android has " -"become a lot easier." +"client platforms and with this release, using Flower on Android has become a " +"lot easier." msgstr "" #: ../../source/ref-changelog.md:899 msgid "" -"The example uses TFLite on the client side, along with a new " -"`FedAvgAndroid` strategy. The Android client and `FedAvgAndroid` are " -"still experimental, but they are a first step towards a fully-fledged " -"Android SDK and a unified `FedAvg` implementation that integrated the new" -" functionality from `FedAvgAndroid`." +"The example uses TFLite on the client side, along with a new `FedAvgAndroid` " +"strategy. The Android client and `FedAvgAndroid` are still experimental, but " +"they are a first step towards a fully-fledged Android SDK and a unified " +"`FedAvg` implementation that integrated the new functionality from " +"`FedAvgAndroid`." msgstr "" #: ../../source/ref-changelog.md:901 msgid "" -"**Make gRPC keepalive time user-configurable and decrease default " -"keepalive time** ([#1069](https://github.com/adap/flower/pull/1069))" +"**Make gRPC keepalive time user-configurable and decrease default keepalive " +"time** ([#1069](https://github.com/adap/flower/pull/1069))" msgstr "" #: ../../source/ref-changelog.md:903 msgid "" "The default gRPC keepalive time has been reduced to increase the " -"compatibility of Flower with more cloud environments (for example, " -"Microsoft Azure). Users can configure the keepalive time to customize the" -" gRPC stack based on specific requirements." +"compatibility of Flower with more cloud environments (for example, Microsoft " +"Azure). Users can configure the keepalive time to customize the gRPC stack " +"based on specific requirements." msgstr "" #: ../../source/ref-changelog.md:905 msgid "" -"**New differential privacy example using Opacus and PyTorch** " -"([#805](https://github.com/adap/flower/pull/805))" +"**New differential privacy example using Opacus and PyTorch** ([#805]" +"(https://github.com/adap/flower/pull/805))" msgstr "" #: ../../source/ref-changelog.md:907 msgid "" -"A new code example (`opacus`) demonstrates differentially-private " -"federated learning with Opacus, PyTorch, and Flower." +"A new code example (`opacus`) demonstrates differentially-private federated " +"learning with Opacus, PyTorch, and Flower." msgstr "" #: ../../source/ref-changelog.md:909 msgid "" -"**New Hugging Face Transformers code example** " -"([#863](https://github.com/adap/flower/pull/863))" +"**New Hugging Face Transformers code example** ([#863](https://github.com/" +"adap/flower/pull/863))" msgstr "" #: ../../source/ref-changelog.md:911 msgid "" -"A new code example (`quickstart_huggingface`) demonstrates usage of " -"Hugging Face Transformers with Flower." +"A new code example (`quickstart_huggingface`) demonstrates usage of Hugging " +"Face Transformers with Flower." msgstr "" #: ../../source/ref-changelog.md:913 msgid "" -"**New MLCube code example** " -"([#779](https://github.com/adap/flower/pull/779), " -"[#1034](https://github.com/adap/flower/pull/1034), " -"[#1065](https://github.com/adap/flower/pull/1065), " -"[#1090](https://github.com/adap/flower/pull/1090))" +"**New MLCube code example** ([#779](https://github.com/adap/flower/" +"pull/779), [#1034](https://github.com/adap/flower/pull/1034), [#1065]" +"(https://github.com/adap/flower/pull/1065), [#1090](https://github.com/adap/" +"flower/pull/1090))" msgstr "" #: ../../source/ref-changelog.md:915 msgid "" -"A new code example (`quickstart_mlcube`) demonstrates usage of MLCube " -"with Flower." +"A new code example (`quickstart_mlcube`) demonstrates usage of MLCube with " +"Flower." msgstr "" #: ../../source/ref-changelog.md:917 msgid "" -"**SSL-enabled server and client** " -"([#842](https://github.com/adap/flower/pull/842), " -"[#844](https://github.com/adap/flower/pull/844), " -"[#845](https://github.com/adap/flower/pull/845), " -"[#847](https://github.com/adap/flower/pull/847), " -"[#993](https://github.com/adap/flower/pull/993), " -"[#994](https://github.com/adap/flower/pull/994))" +"**SSL-enabled server and client** ([#842](https://github.com/adap/flower/" +"pull/842), [#844](https://github.com/adap/flower/pull/844), [#845](https://" +"github.com/adap/flower/pull/845), [#847](https://github.com/adap/flower/" +"pull/847), [#993](https://github.com/adap/flower/pull/993), [#994](https://" +"github.com/adap/flower/pull/994))" msgstr "" #: ../../source/ref-changelog.md:919 msgid "" -"SSL enables secure encrypted connections between clients and servers. " -"This release open-sources the Flower secure gRPC implementation to make " -"encrypted communication channels accessible to all Flower users." +"SSL enables secure encrypted connections between clients and servers. This " +"release open-sources the Flower secure gRPC implementation to make encrypted " +"communication channels accessible to all Flower users." msgstr "" #: ../../source/ref-changelog.md:921 msgid "" -"**Updated** `FedAdam` **and** `FedYogi` **strategies** " -"([#885](https://github.com/adap/flower/pull/885), " -"[#895](https://github.com/adap/flower/pull/895))" +"**Updated** `FedAdam` **and** `FedYogi` **strategies** ([#885](https://" +"github.com/adap/flower/pull/885), [#895](https://github.com/adap/flower/" +"pull/895))" msgstr "" #: ../../source/ref-changelog.md:923 msgid "" -"`FedAdam` and `FedAdam` match the latest version of the Adaptive " -"Federated Optimization paper." +"`FedAdam` and `FedAdam` match the latest version of the Adaptive Federated " +"Optimization paper." msgstr "" #: ../../source/ref-changelog.md:925 msgid "" -"**Initialize** `start_simulation` **with a list of client IDs** " -"([#860](https://github.com/adap/flower/pull/860))" +"**Initialize** `start_simulation` **with a list of client IDs** ([#860]" +"(https://github.com/adap/flower/pull/860))" msgstr "" #: ../../source/ref-changelog.md:927 @@ -18374,38 +18743,38 @@ msgstr "" #: ../../source/ref-changelog.md:931 msgid "" -"Update `num_examples` calculation in PyTorch code examples in " -"([#909](https://github.com/adap/flower/pull/909))" +"Update `num_examples` calculation in PyTorch code examples in ([#909]" +"(https://github.com/adap/flower/pull/909))" msgstr "" #: ../../source/ref-changelog.md:932 msgid "" -"Expose Flower version through `flwr.__version__` " -"([#952](https://github.com/adap/flower/pull/952))" +"Expose Flower version through `flwr.__version__` ([#952](https://github.com/" +"adap/flower/pull/952))" msgstr "" #: ../../source/ref-changelog.md:933 msgid "" -"`start_server` in `app.py` now returns a `History` object containing " -"metrics from training ([#974](https://github.com/adap/flower/pull/974))" +"`start_server` in `app.py` now returns a `History` object containing metrics " +"from training ([#974](https://github.com/adap/flower/pull/974))" msgstr "" #: ../../source/ref-changelog.md:934 msgid "" -"Make `max_workers` (used by `ThreadPoolExecutor`) configurable " -"([#978](https://github.com/adap/flower/pull/978))" +"Make `max_workers` (used by `ThreadPoolExecutor`) configurable ([#978]" +"(https://github.com/adap/flower/pull/978))" msgstr "" #: ../../source/ref-changelog.md:935 msgid "" -"Increase sleep time after server start to three seconds in all code " -"examples ([#1086](https://github.com/adap/flower/pull/1086))" +"Increase sleep time after server start to three seconds in all code examples " +"([#1086](https://github.com/adap/flower/pull/1086))" msgstr "" #: ../../source/ref-changelog.md:936 msgid "" -"Added a new FAQ section to the documentation " -"([#948](https://github.com/adap/flower/pull/948))" +"Added a new FAQ section to the documentation ([#948](https://github.com/adap/" +"flower/pull/948))" msgstr "" #: ../../source/ref-changelog.md:937 @@ -18425,8 +18794,8 @@ msgid "" "The packages `flwr_example` and `flwr_experimental` have been deprecated " "since Flower 0.12.0 and they are not longer included in Flower release " "builds. The associated extras (`baseline`, `examples-pytorch`, `examples-" -"tensorflow`, `http-logger`, `ops`) are now no-op and will be removed in " -"an upcoming release." +"tensorflow`, `http-logger`, `ops`) are now no-op and will be removed in an " +"upcoming release." msgstr "" #: ../../source/ref-changelog.md:945 @@ -18435,34 +18804,32 @@ msgstr "" #: ../../source/ref-changelog.md:949 msgid "" -"**Experimental virtual client engine** " -"([#781](https://github.com/adap/flower/pull/781) " -"[#790](https://github.com/adap/flower/pull/790) " -"[#791](https://github.com/adap/flower/pull/791))" +"**Experimental virtual client engine** ([#781](https://github.com/adap/" +"flower/pull/781) [#790](https://github.com/adap/flower/pull/790) [#791]" +"(https://github.com/adap/flower/pull/791))" msgstr "" #: ../../source/ref-changelog.md:951 msgid "" -"One of Flower's goals is to enable research at scale. This release " -"enables a first (experimental) peek at a major new feature, codenamed the" -" virtual client engine. Virtual clients enable simulations that scale to " -"a (very) large number of clients on a single machine or compute cluster. " -"The easiest way to test the new functionality is to look at the two new " -"code examples called `quickstart_simulation` and `simulation_pytorch`." +"One of Flower's goals is to enable research at scale. This release enables a " +"first (experimental) peek at a major new feature, codenamed the virtual " +"client engine. Virtual clients enable simulations that scale to a (very) " +"large number of clients on a single machine or compute cluster. The easiest " +"way to test the new functionality is to look at the two new code examples " +"called `quickstart_simulation` and `simulation_pytorch`." msgstr "" #: ../../source/ref-changelog.md:953 msgid "" -"The feature is still experimental, so there's no stability guarantee for " -"the API. It's also not quite ready for prime time and comes with a few " -"known caveats. However, those who are curious are encouraged to try it " -"out and share their thoughts." +"The feature is still experimental, so there's no stability guarantee for the " +"API. It's also not quite ready for prime time and comes with a few known " +"caveats. However, those who are curious are encouraged to try it out and " +"share their thoughts." msgstr "" #: ../../source/ref-changelog.md:955 msgid "" -"**New built-in strategies** " -"([#828](https://github.com/adap/flower/pull/828) " +"**New built-in strategies** ([#828](https://github.com/adap/flower/pull/828) " "[#822](https://github.com/adap/flower/pull/822))" msgstr "" @@ -18480,101 +18847,99 @@ msgstr "" #: ../../source/ref-changelog.md:960 msgid "" -"**New PyTorch Lightning code example** " -"([#617](https://github.com/adap/flower/pull/617))" +"**New PyTorch Lightning code example** ([#617](https://github.com/adap/" +"flower/pull/617))" msgstr "" #: ../../source/ref-changelog.md:962 msgid "" -"**New Variational Auto-Encoder code example** " -"([#752](https://github.com/adap/flower/pull/752))" +"**New Variational Auto-Encoder code example** ([#752](https://github.com/" +"adap/flower/pull/752))" msgstr "" #: ../../source/ref-changelog.md:964 msgid "" -"**New scikit-learn code example** " -"([#748](https://github.com/adap/flower/pull/748))" +"**New scikit-learn code example** ([#748](https://github.com/adap/flower/" +"pull/748))" msgstr "" #: ../../source/ref-changelog.md:966 msgid "" -"**New experimental TensorBoard strategy** " -"([#789](https://github.com/adap/flower/pull/789))" +"**New experimental TensorBoard strategy** ([#789](https://github.com/adap/" +"flower/pull/789))" msgstr "" #: ../../source/ref-changelog.md:970 msgid "" -"Improved advanced TensorFlow code example " -"([#769](https://github.com/adap/flower/pull/769))" +"Improved advanced TensorFlow code example ([#769](https://github.com/adap/" +"flower/pull/769))" msgstr "" #: ../../source/ref-changelog.md:971 msgid "" -"Warning when `min_available_clients` is misconfigured " -"([#830](https://github.com/adap/flower/pull/830))" +"Warning when `min_available_clients` is misconfigured ([#830](https://github." +"com/adap/flower/pull/830))" msgstr "" #: ../../source/ref-changelog.md:972 msgid "" -"Improved gRPC server docs " -"([#841](https://github.com/adap/flower/pull/841))" +"Improved gRPC server docs ([#841](https://github.com/adap/flower/pull/841))" msgstr "" #: ../../source/ref-changelog.md:973 msgid "" -"Improved error message in `NumPyClient` " -"([#851](https://github.com/adap/flower/pull/851))" +"Improved error message in `NumPyClient` ([#851](https://github.com/adap/" +"flower/pull/851))" msgstr "" #: ../../source/ref-changelog.md:974 msgid "" -"Improved PyTorch quickstart code example " -"([#852](https://github.com/adap/flower/pull/852))" +"Improved PyTorch quickstart code example ([#852](https://github.com/adap/" +"flower/pull/852))" msgstr "" #: ../../source/ref-changelog.md:978 msgid "" -"**Disabled final distributed evaluation** " -"([#800](https://github.com/adap/flower/pull/800))" +"**Disabled final distributed evaluation** ([#800](https://github.com/adap/" +"flower/pull/800))" msgstr "" #: ../../source/ref-changelog.md:980 msgid "" -"Prior behaviour was to perform a final round of distributed evaluation on" -" all connected clients, which is often not required (e.g., when using " -"server-side evaluation). The prior behaviour can be enabled by passing " +"Prior behaviour was to perform a final round of distributed evaluation on " +"all connected clients, which is often not required (e.g., when using server-" +"side evaluation). The prior behaviour can be enabled by passing " "`force_final_distributed_eval=True` to `start_server`." msgstr "" #: ../../source/ref-changelog.md:982 msgid "" -"**Renamed q-FedAvg strategy** " -"([#802](https://github.com/adap/flower/pull/802))" +"**Renamed q-FedAvg strategy** ([#802](https://github.com/adap/flower/" +"pull/802))" msgstr "" #: ../../source/ref-changelog.md:984 msgid "" -"The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect " -"the notation given in the original paper (q-FFL is the optimization " -"objective, q-FedAvg is the proposed solver). Note the original (now " -"deprecated) `QffedAvg` class is still available for compatibility reasons" -" (it will be removed in a future release)." +"The strategy named `QffedAvg` was renamed to `QFedAvg` to better reflect the " +"notation given in the original paper (q-FFL is the optimization objective, q-" +"FedAvg is the proposed solver). Note the original (now deprecated) " +"`QffedAvg` class is still available for compatibility reasons (it will be " +"removed in a future release)." msgstr "" #: ../../source/ref-changelog.md:986 msgid "" "**Deprecated and renamed code example** `simulation_pytorch` **to** " -"`simulation_pytorch_legacy` " -"([#791](https://github.com/adap/flower/pull/791))" +"`simulation_pytorch_legacy` ([#791](https://github.com/adap/flower/pull/791))" msgstr "" #: ../../source/ref-changelog.md:988 msgid "" -"This example has been replaced by a new example. The new example is based" -" on the experimental virtual client engine, which will become the new " -"default way of doing most types of large-scale simulations in Flower. The" -" existing example was kept for reference purposes, but it might be " -"removed in the future." +"This example has been replaced by a new example. The new example is based on " +"the experimental virtual client engine, which will become the new default " +"way of doing most types of large-scale simulations in Flower. The existing " +"example was kept for reference purposes, but it might be removed in the " +"future." msgstr "" #: ../../source/ref-changelog.md:990 @@ -18583,8 +18948,7 @@ msgstr "" #: ../../source/ref-changelog.md:994 msgid "" -"**New built-in strategies** " -"([#549](https://github.com/adap/flower/pull/549))" +"**New built-in strategies** ([#549](https://github.com/adap/flower/pull/549))" msgstr "" #: ../../source/ref-changelog.md:996 @@ -18593,8 +18957,8 @@ msgstr "" #: ../../source/ref-changelog.md:999 msgid "" -"**Custom metrics for server and strategies** " -"([#717](https://github.com/adap/flower/pull/717))" +"**Custom metrics for server and strategies** ([#717](https://github.com/adap/" +"flower/pull/717))" msgstr "" #: ../../source/ref-changelog.md:1001 @@ -18608,20 +18972,19 @@ msgstr "" #: ../../source/ref-changelog.md:1003 msgid "" -"Custom metric dictionaries are now used in two user-facing APIs: they are" -" returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and " -"they enable evaluation functions passed to built-in strategies (via " -"`eval_fn`) to return more than two evaluation metrics. Strategies can " -"even return *aggregated* metrics dictionaries for the server to keep " -"track of." +"Custom metric dictionaries are now used in two user-facing APIs: they are " +"returned from Strategy methods `aggregate_fit`/`aggregate_evaluate` and they " +"enable evaluation functions passed to built-in strategies (via `eval_fn`) to " +"return more than two evaluation metrics. Strategies can even return " +"*aggregated* metrics dictionaries for the server to keep track of." msgstr "" #: ../../source/ref-changelog.md:1005 msgid "" "Strategy implementations should migrate their `aggregate_fit` and " "`aggregate_evaluate` methods to the new return type (e.g., by simply " -"returning an empty `{}`), server-side evaluation functions should migrate" -" from `return loss, accuracy` to `return loss, {\"accuracy\": accuracy}`." +"returning an empty `{}`), server-side evaluation functions should migrate " +"from `return loss, accuracy` to `return loss, {\"accuracy\": accuracy}`." msgstr "" #: ../../source/ref-changelog.md:1007 @@ -18632,25 +18995,24 @@ msgstr "" #: ../../source/ref-changelog.md:1009 msgid "" -"**Migration warnings for deprecated functionality** " -"([#690](https://github.com/adap/flower/pull/690))" +"**Migration warnings for deprecated functionality** ([#690](https://github." +"com/adap/flower/pull/690))" msgstr "" #: ../../source/ref-changelog.md:1011 msgid "" "Earlier versions of Flower were often migrated to new APIs, while " -"maintaining compatibility with legacy APIs. This release introduces " -"detailed warning messages if usage of deprecated APIs is detected. The " -"new warning messages often provide details on how to migrate to more " -"recent APIs, thus easing the transition from one release to another." +"maintaining compatibility with legacy APIs. This release introduces detailed " +"warning messages if usage of deprecated APIs is detected. The new warning " +"messages often provide details on how to migrate to more recent APIs, thus " +"easing the transition from one release to another." msgstr "" #: ../../source/ref-changelog.md:1013 msgid "" -"Improved docs and docstrings " -"([#691](https://github.com/adap/flower/pull/691) " -"[#692](https://github.com/adap/flower/pull/692) " -"[#713](https://github.com/adap/flower/pull/713))" +"Improved docs and docstrings ([#691](https://github.com/adap/flower/" +"pull/691) [#692](https://github.com/adap/flower/pull/692) [#713](https://" +"github.com/adap/flower/pull/713))" msgstr "" #: ../../source/ref-changelog.md:1015 @@ -18660,43 +19022,39 @@ msgstr "" #: ../../source/ref-changelog.md:1017 msgid "" "FedBN implementation in example PyTorch: From Centralized To Federated " -"([#696](https://github.com/adap/flower/pull/696) " -"[#702](https://github.com/adap/flower/pull/702) " -"[#705](https://github.com/adap/flower/pull/705))" +"([#696](https://github.com/adap/flower/pull/696) [#702](https://github.com/" +"adap/flower/pull/702) [#705](https://github.com/adap/flower/pull/705))" msgstr "" #: ../../source/ref-changelog.md:1021 msgid "" -"**Serialization-agnostic server** " -"([#721](https://github.com/adap/flower/pull/721))" +"**Serialization-agnostic server** ([#721](https://github.com/adap/flower/" +"pull/721))" msgstr "" #: ../../source/ref-changelog.md:1023 msgid "" -"The Flower server is now fully serialization-agnostic. Prior usage of " -"class `Weights` (which represents parameters as deserialized NumPy " -"ndarrays) was replaced by class `Parameters` (e.g., in `Strategy`). " -"`Parameters` objects are fully serialization-agnostic and represents " -"parameters as byte arrays, the `tensor_type` attributes indicates how " -"these byte arrays should be interpreted (e.g., for " -"serialization/deserialization)." +"The Flower server is now fully serialization-agnostic. Prior usage of class " +"`Weights` (which represents parameters as deserialized NumPy ndarrays) was " +"replaced by class `Parameters` (e.g., in `Strategy`). `Parameters` objects " +"are fully serialization-agnostic and represents parameters as byte arrays, " +"the `tensor_type` attributes indicates how these byte arrays should be " +"interpreted (e.g., for serialization/deserialization)." msgstr "" #: ../../source/ref-changelog.md:1025 msgid "" -"Built-in strategies implement this approach by handling serialization and" -" deserialization to/from `Weights` internally. Custom/3rd-party Strategy " +"Built-in strategies implement this approach by handling serialization and " +"deserialization to/from `Weights` internally. Custom/3rd-party Strategy " "implementations should update to the slightly changed Strategy method " -"definitions. Strategy authors can consult PR " -"[#721](https://github.com/adap/flower/pull/721) to see how strategies can" -" easily migrate to the new format." +"definitions. Strategy authors can consult PR [#721](https://github.com/adap/" +"flower/pull/721) to see how strategies can easily migrate to the new format." msgstr "" #: ../../source/ref-changelog.md:1027 msgid "" -"Deprecated `flwr.server.Server.evaluate`, use " -"`flwr.server.Server.evaluate_round` instead " -"([#717](https://github.com/adap/flower/pull/717))" +"Deprecated `flwr.server.Server.evaluate`, use `flwr.server.Server." +"evaluate_round` instead ([#717](https://github.com/adap/flower/pull/717))" msgstr "" #: ../../source/ref-changelog.md:1029 @@ -18705,8 +19063,8 @@ msgstr "" #: ../../source/ref-changelog.md:1033 msgid "" -"**Server-side parameter initialization** " -"([#658](https://github.com/adap/flower/pull/658))" +"**Server-side parameter initialization** ([#658](https://github.com/adap/" +"flower/pull/658))" msgstr "" #: ../../source/ref-changelog.md:1035 @@ -18719,9 +19077,9 @@ msgstr "" #: ../../source/ref-changelog.md:1037 msgid "" "Built-in strategies support a new constructor argument called " -"`initial_parameters` to set the initial parameters. Built-in strategies " -"will provide these initial parameters to the server on startup and then " -"delete them to free the memory afterwards." +"`initial_parameters` to set the initial parameters. Built-in strategies will " +"provide these initial parameters to the server on startup and then delete " +"them to free the memory afterwards." msgstr "" #: ../../source/ref-changelog.md:1056 @@ -18734,8 +19092,8 @@ msgstr "" #: ../../source/ref-changelog.md:1060 msgid "" -"Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to " -"`flwr.server.strategy.FedAvg`, which is equivalent)" +"Deprecate `flwr.server.strategy.DefaultStrategy` (migrate to `flwr.server." +"strategy.FedAvg`, which is equivalent)" msgstr "" #: ../../source/ref-changelog.md:1062 @@ -18745,35 +19103,33 @@ msgstr "" #: ../../source/ref-changelog.md:1066 msgid "" "**Generalized** `Client.fit` **and** `Client.evaluate` **return values** " -"([#610](https://github.com/adap/flower/pull/610) " -"[#572](https://github.com/adap/flower/pull/572) " -"[#633](https://github.com/adap/flower/pull/633))" +"([#610](https://github.com/adap/flower/pull/610) [#572](https://github.com/" +"adap/flower/pull/572) [#633](https://github.com/adap/flower/pull/633))" msgstr "" #: ../../source/ref-changelog.md:1068 msgid "" -"Clients can now return an additional dictionary mapping `str` keys to " -"values of the following types: `bool`, `bytes`, `float`, `int`, `str`. " -"This means one can return almost arbitrary values from `fit`/`evaluate` " -"and make use of them on the server side!" +"Clients can now return an additional dictionary mapping `str` keys to values " +"of the following types: `bool`, `bytes`, `float`, `int`, `str`. This means " +"one can return almost arbitrary values from `fit`/`evaluate` and make use of " +"them on the server side!" msgstr "" #: ../../source/ref-changelog.md:1070 msgid "" -"This improvement also allowed for more consistent return types between " -"`fit` and `evaluate`: `evaluate` should now return a tuple `(float, int, " -"dict)` representing the loss, number of examples, and a dictionary " -"holding arbitrary problem-specific values like accuracy." +"This improvement also allowed for more consistent return types between `fit` " +"and `evaluate`: `evaluate` should now return a tuple `(float, int, dict)` " +"representing the loss, number of examples, and a dictionary holding " +"arbitrary problem-specific values like accuracy." msgstr "" #: ../../source/ref-changelog.md:1072 msgid "" -"In case you wondered: this feature is compatible with existing projects, " -"the additional dictionary return value is optional. New code should " -"however migrate to the new return types to be compatible with upcoming " -"Flower releases (`fit`: `List[np.ndarray], int, Dict[str, Scalar]`, " -"`evaluate`: `float, int, Dict[str, Scalar]`). See the example below for " -"details." +"In case you wondered: this feature is compatible with existing projects, the " +"additional dictionary return value is optional. New code should however " +"migrate to the new return types to be compatible with upcoming Flower " +"releases (`fit`: `List[np.ndarray], int, Dict[str, Scalar]`, `evaluate`: " +"`float, int, Dict[str, Scalar]`). See the example below for details." msgstr "" #: ../../source/ref-changelog.md:1074 @@ -18784,23 +19140,23 @@ msgstr "" #: ../../source/ref-changelog.md:1089 msgid "" -"**Generalized** `config` **argument in** `Client.fit` **and** " -"`Client.evaluate` ([#595](https://github.com/adap/flower/pull/595))" +"**Generalized** `config` **argument in** `Client.fit` **and** `Client." +"evaluate` ([#595](https://github.com/adap/flower/pull/595))" msgstr "" #: ../../source/ref-changelog.md:1091 msgid "" -"The `config` argument used to be of type `Dict[str, str]`, which means " -"that dictionary values were expected to be strings. The new release " -"generalizes this to enable values of the following types: `bool`, " -"`bytes`, `float`, `int`, `str`." +"The `config` argument used to be of type `Dict[str, str]`, which means that " +"dictionary values were expected to be strings. The new release generalizes " +"this to enable values of the following types: `bool`, `bytes`, `float`, " +"`int`, `str`." msgstr "" #: ../../source/ref-changelog.md:1093 msgid "" "This means one can now pass almost arbitrary values to `fit`/`evaluate` " -"using the `config` dictionary. Yay, no more `str(epochs)` on the server-" -"side and `int(config[\"epochs\"])` on the client side!" +"using the `config` dictionary. Yay, no more `str(epochs)` on the server-side " +"and `int(config[\"epochs\"])` on the client side!" msgstr "" #: ../../source/ref-changelog.md:1095 @@ -18815,8 +19171,8 @@ msgstr "" #: ../../source/ref-changelog.md:1116 msgid "" -"New example: PyTorch From Centralized To Federated " -"([#549](https://github.com/adap/flower/pull/549))" +"New example: PyTorch From Centralized To Federated ([#549](https://github." +"com/adap/flower/pull/549))" msgstr "" #: ../../source/ref-changelog.md:1117 @@ -18824,7 +19180,8 @@ msgid "Improved documentation" msgstr "" #: ../../source/ref-changelog.md:1118 -msgid "New documentation theme ([#551](https://github.com/adap/flower/pull/551))" +msgid "" +"New documentation theme ([#551](https://github.com/adap/flower/pull/551))" msgstr "" #: ../../source/ref-changelog.md:1119 @@ -18833,14 +19190,14 @@ msgstr "" #: ../../source/ref-changelog.md:1120 msgid "" -"Updated examples documentation " -"([#549](https://github.com/adap/flower/pull/549))" +"Updated examples documentation ([#549](https://github.com/adap/flower/" +"pull/549))" msgstr "" #: ../../source/ref-changelog.md:1121 msgid "" -"Removed obsolete documentation " -"([#548](https://github.com/adap/flower/pull/548))" +"Removed obsolete documentation ([#548](https://github.com/adap/flower/" +"pull/548))" msgstr "" #: ../../source/ref-changelog.md:1123 @@ -18849,10 +19206,9 @@ msgstr "" #: ../../source/ref-changelog.md:1125 msgid "" -"`Server.fit` does not disconnect clients when finished, disconnecting the" -" clients is now handled in `flwr.server.start_server` " -"([#553](https://github.com/adap/flower/pull/553) " -"[#540](https://github.com/adap/flower/issues/540))." +"`Server.fit` does not disconnect clients when finished, disconnecting the " +"clients is now handled in `flwr.server.start_server` ([#553](https://github." +"com/adap/flower/pull/553) [#540](https://github.com/adap/flower/issues/540))." msgstr "" #: ../../source/ref-changelog.md:1127 @@ -18865,23 +19221,22 @@ msgstr "" #: ../../source/ref-changelog.md:1131 msgid "" -"Added an example for embedded devices " -"([#507](https://github.com/adap/flower/pull/507))" +"Added an example for embedded devices ([#507](https://github.com/adap/flower/" +"pull/507))" msgstr "" #: ../../source/ref-changelog.md:1132 msgid "" -"Added a new NumPyClient (in addition to the existing KerasClient) " -"([#504](https://github.com/adap/flower/pull/504) " -"[#508](https://github.com/adap/flower/pull/508))" +"Added a new NumPyClient (in addition to the existing KerasClient) ([#504]" +"(https://github.com/adap/flower/pull/504) [#508](https://github.com/adap/" +"flower/pull/508))" msgstr "" #: ../../source/ref-changelog.md:1133 msgid "" -"Deprecated `flwr_example` package and started to migrate examples into " -"the top-level `examples` directory " -"([#494](https://github.com/adap/flower/pull/494) " -"[#512](https://github.com/adap/flower/pull/512))" +"Deprecated `flwr_example` package and started to migrate examples into the " +"top-level `examples` directory ([#494](https://github.com/adap/flower/" +"pull/494) [#512](https://github.com/adap/flower/pull/512))" msgstr "" #: ../../source/ref-changelog.md:1135 @@ -18894,12 +19249,11 @@ msgstr "" #: ../../source/ref-changelog.md:1139 msgid "" -"Renamed strategy methods " -"([#486](https://github.com/adap/flower/pull/486)) to unify the naming of " -"Flower's public APIs. Other public methods/functions (e.g., every method " -"in `Client`, but also `Strategy.evaluate`) do not use the `on_` prefix, " -"which is why we're removing it from the four methods in Strategy. To " -"migrate rename the following `Strategy` methods accordingly:" +"Renamed strategy methods ([#486](https://github.com/adap/flower/pull/486)) " +"to unify the naming of Flower's public APIs. Other public methods/functions " +"(e.g., every method in `Client`, but also `Strategy.evaluate`) do not use " +"the `on_` prefix, which is why we're removing it from the four methods in " +"Strategy. To migrate rename the following `Strategy` methods accordingly:" msgstr "" #: ../../source/ref-changelog.md:1140 @@ -18920,33 +19274,32 @@ msgstr "" #: ../../source/ref-changelog.md:1147 msgid "" -"Deprecated `DefaultStrategy` " -"([#479](https://github.com/adap/flower/pull/479)). To migrate use " -"`FedAvg` instead." +"Deprecated `DefaultStrategy` ([#479](https://github.com/adap/flower/" +"pull/479)). To migrate use `FedAvg` instead." msgstr "" #: ../../source/ref-changelog.md:1148 msgid "" -"Simplified examples and baselines " -"([#484](https://github.com/adap/flower/pull/484))." +"Simplified examples and baselines ([#484](https://github.com/adap/flower/" +"pull/484))." msgstr "" #: ../../source/ref-changelog.md:1149 msgid "" -"Removed presently unused `on_conclude_round` from strategy interface " -"([#483](https://github.com/adap/flower/pull/483))." +"Removed presently unused `on_conclude_round` from strategy interface ([#483]" +"(https://github.com/adap/flower/pull/483))." msgstr "" #: ../../source/ref-changelog.md:1150 msgid "" -"Set minimal Python version to 3.6.1 instead of 3.6.9 " -"([#471](https://github.com/adap/flower/pull/471))." +"Set minimal Python version to 3.6.1 instead of 3.6.9 ([#471](https://github." +"com/adap/flower/pull/471))." msgstr "" #: ../../source/ref-changelog.md:1151 msgid "" -"Improved `Strategy` docstrings " -"([#470](https://github.com/adap/flower/pull/470))." +"Improved `Strategy` docstrings ([#470](https://github.com/adap/flower/" +"pull/470))." msgstr "" #: ../../source/ref-example-projects.rst:2 @@ -18955,11 +19308,11 @@ msgstr "" #: ../../source/ref-example-projects.rst:4 msgid "" -"Flower comes with a number of usage examples. The examples demonstrate " -"how Flower can be used to federate different kinds of existing machine " -"learning pipelines, usually leveraging popular machine learning " -"frameworks such as `PyTorch `_ or `TensorFlow " -"`_." +"Flower comes with a number of usage examples. The examples demonstrate how " +"Flower can be used to federate different kinds of existing machine learning " +"pipelines, usually leveraging popular machine learning frameworks such as " +"`PyTorch `_ or `TensorFlow `_." msgstr "" #: ../../source/ref-example-projects.rst:10 @@ -18970,25 +19323,25 @@ msgstr "" #: ../../source/ref-example-projects.rst:14 msgid "" -"The TensorFlow/Keras quickstart example shows CIFAR-10 image " -"classification with MobileNetV2:" +"The TensorFlow/Keras quickstart example shows CIFAR-10 image classification " +"with MobileNetV2:" msgstr "" #: ../../source/ref-example-projects.rst:17 msgid "" -"`Quickstart TensorFlow (Code) " -"`_" +"`Quickstart TensorFlow (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:18 -msgid ":doc:`Quickstart TensorFlow (Tutorial) `" +msgid "" +":doc:`Quickstart TensorFlow (Tutorial) `" msgstr "" #: ../../source/ref-example-projects.rst:19 msgid "" -"`Quickstart TensorFlow (Blog Post) `_" +"`Quickstart TensorFlow (Blog Post) `_" msgstr "" #: ../../source/ref-example-projects.rst:23 @@ -18998,14 +19351,14 @@ msgstr "" #: ../../source/ref-example-projects.rst:25 msgid "" -"The PyTorch quickstart example shows CIFAR-10 image classification with a" -" simple Convolutional Neural Network:" +"The PyTorch quickstart example shows CIFAR-10 image classification with a " +"simple Convolutional Neural Network:" msgstr "" #: ../../source/ref-example-projects.rst:28 msgid "" -"`Quickstart PyTorch (Code) " -"`_" +"`Quickstart PyTorch (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:29 @@ -19024,9 +19377,8 @@ msgstr "" #: ../../source/ref-example-projects.rst:37 msgid "" -"`PyTorch: From Centralized To Federated (Code) " -"`_" +"`PyTorch: From Centralized To Federated (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:38 @@ -19047,14 +19399,15 @@ msgstr "" #: ../../source/ref-example-projects.rst:46 msgid "" -"`Federated Learning on Raspberry Pi and Nvidia Jetson (Code) " -"`_" +"`Federated Learning on Raspberry Pi and Nvidia Jetson (Code) `_" msgstr "" #: ../../source/ref-example-projects.rst:47 msgid "" -"`Federated Learning on Raspberry Pi and Nvidia Jetson (Blog Post) " -"`_" +"`Federated Learning on Raspberry Pi and Nvidia Jetson (Blog Post) `_" msgstr "" #: ../../source/ref-faq.rst:4 @@ -19069,22 +19422,20 @@ msgstr "" #: ../../source/ref-faq.rst:8 msgid "" -"Yes, it can! Flower even comes with a few under-the-hood optimizations to" -" make it work even better on Colab. Here's a quickstart example:" +"Yes, it can! Flower even comes with a few under-the-hood optimizations to " +"make it work even better on Colab. Here's a quickstart example:" msgstr "" #: ../../source/ref-faq.rst:10 msgid "" -"`Flower simulation PyTorch " -"`_" +"`Flower simulation PyTorch `_" msgstr "" #: ../../source/ref-faq.rst:11 msgid "" -"`Flower simulation TensorFlow/Keras " -"`_" +"`Flower simulation TensorFlow/Keras `_" msgstr "" #: ../../source/ref-faq.rst @@ -19094,26 +19445,28 @@ msgstr "" #: ../../source/ref-faq.rst:15 msgid "" "Find the `blog post about federated learning on embedded device here " -"`_" -" and the corresponding `GitHub code example " -"`_." +"`_ " +"and the corresponding `GitHub code example `_." msgstr "" #: ../../source/ref-faq.rst -msgid ":fa:`eye,mr-1` Does Flower support federated learning on Android devices?" +msgid "" +":fa:`eye,mr-1` Does Flower support federated learning on Android devices?" msgstr "" #: ../../source/ref-faq.rst:19 msgid "" -"Yes, it does. Please take a look at our `blog post " -"`_ or check out the code examples:" +"Yes, it does. Please take a look at our `blog post `_ or " +"check out the code examples:" msgstr "" #: ../../source/ref-faq.rst:21 msgid "" -"`Android Kotlin example `_" +"`Android Kotlin example `_" msgstr "" #: ../../source/ref-faq.rst:22 @@ -19132,33 +19485,33 @@ msgstr "" #: ../../source/ref-faq.rst:28 msgid "" -"`Flower meets Nevermined GitHub Repository `_." +"`Flower meets Nevermined GitHub Repository `_." msgstr "" #: ../../source/ref-faq.rst:29 msgid "" -"`Flower meets Nevermined YouTube video " -"`_." +"`Flower meets Nevermined YouTube video `_." msgstr "" #: ../../source/ref-faq.rst:30 msgid "" -"`Flower meets KOSMoS `_." +"`Flower meets KOSMoS `_." msgstr "" #: ../../source/ref-faq.rst:31 msgid "" "`Flower meets Talan blog post `_ ." +"learning-same-mask-different-faces-imen-ayari/?" +"trackingId=971oIlxLQ9%2BA9RB0IQ73XQ%3D%3D>`_ ." msgstr "" #: ../../source/ref-faq.rst:32 msgid "" -"`Flower meets Talan GitHub Repository " -"`_ ." +"`Flower meets Talan GitHub Repository `_ ." msgstr "" #: ../../source/ref-telemetry.md:1 @@ -19167,17 +19520,16 @@ msgstr "" #: ../../source/ref-telemetry.md:3 msgid "" -"The Flower open-source project collects **anonymous** usage metrics to " -"make well-informed decisions to improve Flower. Doing this enables the " -"Flower team to understand how Flower is used and what challenges users " -"might face." +"The Flower open-source project collects **anonymous** usage metrics to make " +"well-informed decisions to improve Flower. Doing this enables the Flower " +"team to understand how Flower is used and what challenges users might face." msgstr "" #: ../../source/ref-telemetry.md:5 msgid "" -"**Flower is a friendly framework for collaborative AI and data science.**" -" Staying true to this statement, Flower makes it easy to disable " -"telemetry for users that do not want to share anonymous usage metrics." +"**Flower is a friendly framework for collaborative AI and data science.** " +"Staying true to this statement, Flower makes it easy to disable telemetry " +"for users that do not want to share anonymous usage metrics." msgstr "" #: ../../source/ref-telemetry.md:7 @@ -19185,35 +19537,34 @@ msgid "Principles" msgstr "" #: ../../source/ref-telemetry.md:9 -msgid "We follow strong principles guarding anonymous usage metrics collection:" +msgid "" +"We follow strong principles guarding anonymous usage metrics collection:" msgstr "" #: ../../source/ref-telemetry.md:11 msgid "" -"**Optional:** You will always be able to disable telemetry; read on to " -"learn “[How to opt-out](#how-to-opt-out)”." +"**Optional:** You will always be able to disable telemetry; read on to learn " +"“[How to opt-out](#how-to-opt-out)”." msgstr "" #: ../../source/ref-telemetry.md:12 msgid "" -"**Anonymous:** The reported usage metrics are anonymous and do not " -"contain any personally identifiable information (PII). See “[Collected " -"metrics](#collected-metrics)” to understand what metrics are being " -"reported." +"**Anonymous:** The reported usage metrics are anonymous and do not contain " +"any personally identifiable information (PII). See “[Collected metrics]" +"(#collected-metrics)” to understand what metrics are being reported." msgstr "" #: ../../source/ref-telemetry.md:13 msgid "" "**Transparent:** You can easily inspect what anonymous metrics are being " -"reported; see the section “[How to inspect what is being reported](#how-" -"to-inspect-what-is-being-reported)”" +"reported; see the section “[How to inspect what is being reported](#how-to-" +"inspect-what-is-being-reported)”" msgstr "" #: ../../source/ref-telemetry.md:14 msgid "" -"**Open for feedback:** You can always reach out to us if you have " -"feedback; see the section “[How to contact us](#how-to-contact-us)” for " -"details." +"**Open for feedback:** You can always reach out to us if you have feedback; " +"see the section “[How to contact us](#how-to-contact-us)” for details." msgstr "" #: ../../source/ref-telemetry.md:16 @@ -19230,9 +19581,9 @@ msgstr "" #: ../../source/ref-telemetry.md:24 msgid "" -"Alternatively, you can export `FLWR_TELEMETRY_ENABLED=0` in, for example," -" `.bashrc` (or whatever configuration file applies to your environment) " -"to disable Flower telemetry permanently." +"Alternatively, you can export `FLWR_TELEMETRY_ENABLED=0` in, for example, `." +"bashrc` (or whatever configuration file applies to your environment) to " +"disable Flower telemetry permanently." msgstr "" #: ../../source/ref-telemetry.md:26 @@ -19245,10 +19596,10 @@ msgstr "" #: ../../source/ref-telemetry.md:30 msgid "" -"**Flower version.** Understand which versions of Flower are currently " -"being used. This helps us to decide whether we should invest effort into " -"releasing a patch version for an older version of Flower or instead use " -"the bandwidth to build new features." +"**Flower version.** Understand which versions of Flower are currently being " +"used. This helps us to decide whether we should invest effort into releasing " +"a patch version for an older version of Flower or instead use the bandwidth " +"to build new features." msgstr "" #: ../../source/ref-telemetry.md:32 @@ -19267,15 +19618,15 @@ msgstr "" #: ../../source/ref-telemetry.md:36 msgid "" -"**Hardware properties.** Understanding the hardware environment that " -"Flower is being used in helps to decide whether we should, for example, " -"put more effort into supporting low-resource environments." +"**Hardware properties.** Understanding the hardware environment that Flower " +"is being used in helps to decide whether we should, for example, put more " +"effort into supporting low-resource environments." msgstr "" #: ../../source/ref-telemetry.md:38 msgid "" -"**Execution mode.** Knowing what execution mode Flower starts in enables " -"us to understand how heavily certain features are being used and better " +"**Execution mode.** Knowing what execution mode Flower starts in enables us " +"to understand how heavily certain features are being used and better " "prioritize based on that." msgstr "" @@ -19283,37 +19634,34 @@ msgstr "" msgid "" "**Cluster.** Flower telemetry assigns a random in-memory cluster ID each " "time a Flower workload starts. This allows us to understand which device " -"types not only start Flower workloads but also successfully complete " -"them." +"types not only start Flower workloads but also successfully complete them." msgstr "" #: ../../source/ref-telemetry.md:42 msgid "" -"**Source.** Flower telemetry tries to store a random source ID in " -"`~/.flwr/source` the first time a telemetry event is generated. The " -"source ID is important to identify whether an issue is recurring or " -"whether an issue is triggered by multiple clusters running concurrently " -"(which often happens in simulation). For example, if a device runs " -"multiple workloads at the same time, and this results in an issue, then, " -"in order to reproduce the issue, multiple workloads must be started at " -"the same time." +"**Source.** Flower telemetry tries to store a random source ID in `~/.flwr/" +"source` the first time a telemetry event is generated. The source ID is " +"important to identify whether an issue is recurring or whether an issue is " +"triggered by multiple clusters running concurrently (which often happens in " +"simulation). For example, if a device runs multiple workloads at the same " +"time, and this results in an issue, then, in order to reproduce the issue, " +"multiple workloads must be started at the same time." msgstr "" #: ../../source/ref-telemetry.md:44 msgid "" -"You may delete the source ID at any time. If you wish for all events " -"logged under a specific source ID to be deleted, you can send a deletion " -"request mentioning the source ID to `telemetry@flower.ai`. All events " -"related to that source ID will then be permanently deleted." +"You may delete the source ID at any time. If you wish for all events logged " +"under a specific source ID to be deleted, you can send a deletion request " +"mentioning the source ID to `telemetry@flower.ai`. All events related to " +"that source ID will then be permanently deleted." msgstr "" #: ../../source/ref-telemetry.md:46 msgid "" -"We will not collect any personally identifiable information. If you think" -" any of the metrics collected could be misused in any way, please [get in" -" touch with us](#how-to-contact-us). We will update this page to reflect " -"any changes to the metrics collected and publish changes in the " -"changelog." +"We will not collect any personally identifiable information. If you think " +"any of the metrics collected could be misused in any way, please [get in " +"touch with us](#how-to-contact-us). We will update this page to reflect any " +"changes to the metrics collected and publish changes in the changelog." msgstr "" #: ../../source/ref-telemetry.md:48 @@ -19330,17 +19678,17 @@ msgstr "" #: ../../source/ref-telemetry.md:52 msgid "" "We wanted to make it very easy for you to inspect what anonymous usage " -"metrics are reported. You can view all the reported telemetry information" -" by setting the environment variable `FLWR_TELEMETRY_LOGGING=1`. Logging " -"is disabled by default. You may use logging independently from " +"metrics are reported. You can view all the reported telemetry information by " +"setting the environment variable `FLWR_TELEMETRY_LOGGING=1`. Logging is " +"disabled by default. You may use logging independently from " "`FLWR_TELEMETRY_ENABLED` so that you can inspect the telemetry feature " "without sending any metrics." msgstr "" #: ../../source/ref-telemetry.md:58 msgid "" -"The inspect Flower telemetry without sending any anonymous usage metrics," -" use both environment variables:" +"The inspect Flower telemetry without sending any anonymous usage metrics, " +"use both environment variables:" msgstr "" #: ../../source/ref-telemetry.md:64 @@ -19357,8 +19705,8 @@ msgstr "" #: ../../source/tutorial-quickstart-android.rst:-1 msgid "" -"Read this Federated Learning quickstart tutorial for creating an Android " -"app using Flower." +"Read this Federated Learning quickstart tutorial for creating an Android app " +"using Flower." msgstr "" #: ../../source/tutorial-quickstart-android.rst:5 @@ -19367,21 +19715,19 @@ msgstr "" #: ../../source/tutorial-quickstart-android.rst:10 msgid "" -"Let's build a federated learning system using TFLite and Flower on " -"Android!" +"Let's build a federated learning system using TFLite and Flower on Android!" msgstr "" #: ../../source/tutorial-quickstart-android.rst:12 msgid "" -"Please refer to the `full code example " -"`_ to learn " -"more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with FastAI to train a vision model on CIFAR-10." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"FastAI to train a vision model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:5 @@ -19394,15 +19740,14 @@ msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:12 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:-1 msgid "" -"Check out this Federating Learning quickstart tutorial for using Flower " -"with HuggingFace Transformers in order to fine-tune an LLM." +"Check out this Federating Learning quickstart tutorial for using Flower with " +"HuggingFace Transformers in order to fine-tune an LLM." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:5 @@ -19411,17 +19756,17 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:10 msgid "" -"Let's build a federated learning system using Hugging Face Transformers " -"and Flower!" +"Let's build a federated learning system using Hugging Face Transformers and " +"Flower!" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:12 msgid "" -"We will leverage Hugging Face to federate the training of language models" -" over multiple clients using Flower. More specifically, we will fine-tune" -" a pre-trained Transformer model (distilBERT) for sequence classification" -" over a dataset of IMDB ratings. The end goal is to detect if a movie " -"rating is positive or negative." +"We will leverage Hugging Face to federate the training of language models " +"over multiple clients using Flower. More specifically, we will fine-tune a " +"pre-trained Transformer model (distilBERT) for sequence classification over " +"a dataset of IMDB ratings. The end goal is to detect if a movie rating is " +"positive or negative." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:18 @@ -19431,9 +19776,8 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:20 msgid "" "To follow along this tutorial you will need to install the following " -"packages: :code:`datasets`, :code:`evaluate`, :code:`flwr`, " -":code:`torch`, and :code:`transformers`. This can be done using " -":code:`pip`:" +"packages: :code:`datasets`, :code:`evaluate`, :code:`flwr`, :code:`torch`, " +"and :code:`transformers`. This can be done using :code:`pip`:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:30 @@ -19457,9 +19801,9 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:83 msgid "" -"Once we have a way of creating our trainloader and testloader, we can " -"take care of the training and testing. This is very similar to any " -":code:`PyTorch` training or testing loop:" +"Once we have a way of creating our trainloader and testloader, we can take " +"care of the training and testing. This is very similar to any :code:" +"`PyTorch` training or testing loop:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:121 @@ -19468,8 +19812,8 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:123 msgid "" -"To create the model itself, we will just load the pre-trained distillBERT" -" model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" +"To create the model itself, we will just load the pre-trained distillBERT " +"model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:136 @@ -19483,18 +19827,17 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:141 msgid "" "To federate our example to multiple clients, we first need to write our " -"Flower client class (inheriting from :code:`flwr.client.NumPyClient`). " -"This is very easy, as our model is a standard :code:`PyTorch` model:" +"Flower client class (inheriting from :code:`flwr.client.NumPyClient`). This " +"is very easy, as our model is a standard :code:`PyTorch` model:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:169 msgid "" "The :code:`get_parameters` function lets the server get the client's " -"parameters. Inversely, the :code:`set_parameters` function allows the " -"server to send its parameters to the client. Finally, the :code:`fit` " -"function trains the model locally for the client, and the " -":code:`evaluate` function tests the model locally and returns the " -"relevant metrics." +"parameters. Inversely, the :code:`set_parameters` function allows the server " +"to send its parameters to the client. Finally, the :code:`fit` function " +"trains the model locally for the client, and the :code:`evaluate` function " +"tests the model locally and returns the relevant metrics." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:175 @@ -19503,19 +19846,19 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:177 msgid "" -"Now that we have a way to instantiate clients, we need to create our " -"server in order to aggregate the results. Using Flower, this can be done " -"very easily by first choosing a strategy (here, we are using " -":code:`FedAvg`, which will define the global weights as the average of " -"all the clients' weights at each round) and then using the " -":code:`flwr.server.start_server` function:" +"Now that we have a way to instantiate clients, we need to create our server " +"in order to aggregate the results. Using Flower, this can be done very " +"easily by first choosing a strategy (here, we are using :code:`FedAvg`, " +"which will define the global weights as the average of all the clients' " +"weights at each round) and then using the :code:`flwr.server.start_server` " +"function:" msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:205 msgid "" -"The :code:`weighted_average` function is there to provide a way to " -"aggregate the metrics distributed amongst the clients (basically this " -"allows us to display a nice average accuracy and loss for every round)." +"The :code:`weighted_average` function is there to provide a way to aggregate " +"the metrics distributed amongst the clients (basically this allows us to " +"display a nice average accuracy and loss for every round)." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:209 @@ -19534,22 +19877,22 @@ msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:223 msgid "" -"If you want to check out everything put together, you should check out " -"the `full code example `_ ." +"If you want to check out everything put together, you should check out the " +"`full code example `_ ." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:226 msgid "" -"Of course, this is a very basic example, and a lot can be added or " -"modified, it was just to showcase how simply we could federate a Hugging " -"Face workflow using Flower." +"Of course, this is a very basic example, and a lot can be added or modified, " +"it was just to showcase how simply we could federate a Hugging Face workflow " +"using Flower." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:229 msgid "" -"Note that in this example we used :code:`PyTorch`, but we could have very" -" well used :code:`TensorFlow`." +"Note that in this example we used :code:`PyTorch`, but we could have very " +"well used :code:`TensorFlow`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:-1 @@ -19564,38 +19907,38 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:10 msgid "" -"In this tutorial we will learn how to train a Neural Network on MNIST " -"using Flower and CoreML on iOS devices." +"In this tutorial we will learn how to train a Neural Network on MNIST using " +"Flower and CoreML on iOS devices." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:12 msgid "" "First of all, for running the Flower Python server, it is recommended to " -"create a virtual environment and run everything within a :doc:`virtualenv" -" `. For the Flower client " +"create a virtual environment and run everything within a :doc:`virtualenv " +"`. For the Flower client " "implementation in iOS, it is recommended to use Xcode as our IDE." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:15 msgid "" -"Our example consists of one Python *server* and two iPhone *clients* that" -" all have the same model." +"Our example consists of one Python *server* and two iPhone *clients* that " +"all have the same model." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:17 msgid "" -"*Clients* are responsible for generating individual weight updates for " -"the model based on their local datasets. These updates are then sent to " -"the *server* which will aggregate them to produce a better model. " -"Finally, the *server* sends this improved version of the model back to " -"each *client*. A complete cycle of weight updates is called a *round*." +"*Clients* are responsible for generating individual weight updates for the " +"model based on their local datasets. These updates are then sent to the " +"*server* which will aggregate them to produce a better model. Finally, the " +"*server* sends this improved version of the model back to each *client*. A " +"complete cycle of weight updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:21 msgid "" "Now that we have a rough idea of what is going on, let's get started to " -"setup our Flower server environment. We first need to install Flower. You" -" can do this by using pip:" +"setup our Flower server environment. We first need to install Flower. You " +"can do this by using pip:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:27 @@ -19613,21 +19956,20 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:36 msgid "" "Now that we have all our dependencies installed, let's run a simple " -"distributed training using CoreML as our local training pipeline and " -"MNIST as our dataset. For simplicity reasons we will use the complete " -"Flower client with CoreML, that has been implemented and stored inside " -"the Swift SDK. The client implementation can be seen below:" +"distributed training using CoreML as our local training pipeline and MNIST " +"as our dataset. For simplicity reasons we will use the complete Flower " +"client with CoreML, that has been implemented and stored inside the Swift " +"SDK. The client implementation can be seen below:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:72 msgid "" -"Let's create a new application project in Xcode and add :code:`flwr` as a" -" dependency in your project. For our application, we will store the logic" -" of our app in :code:`FLiOSModel.swift` and the UI elements in " -":code:`ContentView.swift`. We will focus more on :code:`FLiOSModel.swift`" -" in this quickstart. Please refer to the `full code example " -"`_ to learn more " -"about the app." +"Let's create a new application project in Xcode and add :code:`flwr` as a " +"dependency in your project. For our application, we will store the logic of " +"our app in :code:`FLiOSModel.swift` and the UI elements in :code:" +"`ContentView.swift`. We will focus more on :code:`FLiOSModel.swift` in this " +"quickstart. Please refer to the `full code example `_ to learn more about the app." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:75 @@ -19637,22 +19979,21 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:83 msgid "" "Then add the mlmodel to the project simply by drag-and-drop, the mlmodel " -"will be bundled inside the application during deployment to your iOS " -"device. We need to pass the url to access mlmodel and run CoreML machine " -"learning processes, it can be retrieved by calling the function " -":code:`Bundle.main.url`. For the MNIST dataset, we need to preprocess it " -"into :code:`MLBatchProvider` object. The preprocessing is done inside " -":code:`DataLoader.swift`." +"will be bundled inside the application during deployment to your iOS device. " +"We need to pass the url to access mlmodel and run CoreML machine learning " +"processes, it can be retrieved by calling the function :code:`Bundle.main." +"url`. For the MNIST dataset, we need to preprocess it into :code:" +"`MLBatchProvider` object. The preprocessing is done inside :code:`DataLoader." +"swift`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:99 msgid "" -"Since CoreML does not allow the model parameters to be seen before " -"training, and accessing the model parameters during or after the training" -" can only be done by specifying the layer name, we need to know this " -"information beforehand, through looking at the model specification, which" -" are written as proto files. The implementation can be seen in " -":code:`MLModelInspect`." +"Since CoreML does not allow the model parameters to be seen before training, " +"and accessing the model parameters during or after the training can only be " +"done by specifying the layer name, we need to know this information " +"beforehand, through looking at the model specification, which are written as " +"proto files. The implementation can be seen in :code:`MLModelInspect`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:102 @@ -19663,18 +20004,18 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:117 msgid "" -"Then start the Flower gRPC client and start communicating to the server " -"by passing our Flower client to the function :code:`startFlwrGRPC`." +"Then start the Flower gRPC client and start communicating to the server by " +"passing our Flower client to the function :code:`startFlwrGRPC`." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:124 msgid "" -"That's it for the client. We only have to implement :code:`Client` or " -"call the provided :code:`MLFlwrClient` and call :code:`startFlwrGRPC()`. " -"The attribute :code:`hostname` and :code:`port` tells the client which " -"server to connect to. This can be done by entering the hostname and port " -"in the application before clicking the start button to start the " -"federated learning process." +"That's it for the client. We only have to implement :code:`Client` or call " +"the provided :code:`MLFlwrClient` and call :code:`startFlwrGRPC()`. The " +"attribute :code:`hostname` and :code:`port` tells the client which server to " +"connect to. This can be done by entering the hostname and port in the " +"application before clicking the start button to start the federated learning " +"process." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:129 @@ -19690,8 +20031,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:100 msgid "" "For simple workloads we can start a Flower server and leave all the " -"configuration possibilities at their default values. In a file named " -":code:`server.py`, import Flower and start the server:" +"configuration possibilities at their default values. In a file named :code:" +"`server.py`, import Flower and start the server:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:142 @@ -19707,32 +20048,31 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:525 msgid "" "With both client and server ready, we can now run everything and see " -"federated learning in action. FL systems usually have a server and " -"multiple clients. We therefore have to start the server first:" +"federated learning in action. FL systems usually have a server and multiple " +"clients. We therefore have to start the server first:" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:152 msgid "" -"Once the server is running we can start the clients in different " -"terminals. Build and run the client through your Xcode, one through Xcode" -" Simulator and the other by deploying it to your iPhone. To see more " -"about how to deploy your app to iPhone or Simulator visit `here " -"`_." +"Once the server is running we can start the clients in different terminals. " +"Build and run the client through your Xcode, one through Xcode Simulator and " +"the other by deploying it to your iPhone. To see more about how to deploy " +"your app to iPhone or Simulator visit `here `_." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:156 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system in your ios device. The full `source code " -"`_ for this " -"example can be found in :code:`examples/ios`." +"learning system in your ios device. The full `source code `_ for this example can be found in :" +"code:`examples/ios`." msgstr "" #: ../../source/tutorial-quickstart-jax.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with Jax to train a linear regression model on a scikit-learn dataset." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"Jax to train a linear regression model on a scikit-learn dataset." msgstr "" #: ../../source/tutorial-quickstart-jax.rst:5 @@ -19741,8 +20081,8 @@ msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with Pandas to perform Federated Analytics." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"Pandas to perform Federated Analytics." msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:5 @@ -19755,45 +20095,44 @@ msgstr "" #: ../../source/tutorial-quickstart-pandas.rst:12 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with PyTorch to train a CNN model on MNIST." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"PyTorch to train a CNN model on MNIST." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:13 msgid "" -"In this tutorial we will learn how to train a Convolutional Neural " -"Network on CIFAR10 using Flower and PyTorch." +"In this tutorial we will learn how to train a Convolutional Neural Network " +"on CIFAR10 using Flower and PyTorch." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:15 #: ../../source/tutorial-quickstart-xgboost.rst:39 msgid "" "First of all, it is recommended to create a virtual environment and run " -"everything within a :doc:`virtualenv `." +"everything within a :doc:`virtualenv `." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:17 #: ../../source/tutorial-quickstart-scikitlearn.rst:14 msgid "" -"Our example consists of one *server* and two *clients* all having the " -"same model." +"Our example consists of one *server* and two *clients* all having the same " +"model." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:19 msgid "" -"*Clients* are responsible for generating individual weight-updates for " -"the model based on their local datasets. These updates are then sent to " -"the *server* which will aggregate them to produce a better model. " -"Finally, the *server* sends this improved version of the model back to " -"each *client*. A complete cycle of weight updates is called a *round*." +"*Clients* are responsible for generating individual weight-updates for the " +"model based on their local datasets. These updates are then sent to the " +"*server* which will aggregate them to produce a better model. Finally, the " +"*server* sends this improved version of the model back to each *client*. A " +"complete cycle of weight updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:23 @@ -19804,16 +20143,15 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:29 msgid "" -"Since we want to use PyTorch to solve a computer vision task, let's go " -"ahead and install PyTorch and the **torchvision** library:" +"Since we want to use PyTorch to solve a computer vision task, let's go ahead " +"and install PyTorch and the **torchvision** library:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:39 msgid "" "Now that we have all our dependencies installed, let's run a simple " -"distributed training with two clients and one server. Our training " -"procedure and network architecture are based on PyTorch's `Deep Learning " -"with PyTorch " +"distributed training with two clients and one server. Our training procedure " +"and network architecture are based on PyTorch's `Deep Learning with PyTorch " "`_." msgstr "" @@ -19830,33 +20168,33 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:62 msgid "" "We use PyTorch to load CIFAR10, a popular colored image classification " -"dataset for machine learning. The PyTorch :code:`DataLoader()` downloads " -"the training and test data that are then normalized." +"dataset for machine learning. The PyTorch :code:`DataLoader()` downloads the " +"training and test data that are then normalized." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:78 msgid "" -"Define the loss and optimizer with PyTorch. The training of the dataset " -"is done by looping over the dataset, measure the corresponding loss and " +"Define the loss and optimizer with PyTorch. The training of the dataset is " +"done by looping over the dataset, measure the corresponding loss and " "optimize it." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:94 msgid "" -"Define then the validation of the machine learning network. We loop over" -" the test set and measure the loss and accuracy of the test set." +"Define then the validation of the machine learning network. We loop over " +"the test set and measure the loss and accuracy of the test set." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:113 msgid "" -"After defining the training and testing of a PyTorch machine learning " -"model, we use the functions for the Flower clients." +"After defining the training and testing of a PyTorch machine learning model, " +"we use the functions for the Flower clients." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:115 msgid "" -"The Flower clients will use a simple CNN adapted from 'PyTorch: A 60 " -"Minute Blitz':" +"The Flower clients will use a simple CNN adapted from 'PyTorch: A 60 Minute " +"Blitz':" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:142 @@ -19868,20 +20206,19 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:144 #: ../../source/tutorial-quickstart-tensorflow.rst:54 msgid "" -"The Flower server interacts with clients through an interface called " -":code:`Client`. When the server selects a particular client for training," -" it sends training instructions over the network. The client receives " -"those instructions and calls one of the :code:`Client` methods to run " -"your code (i.e., to train the neural network we defined earlier)." +"The Flower server interacts with clients through an interface called :code:" +"`Client`. When the server selects a particular client for training, it sends " +"training instructions over the network. The client receives those " +"instructions and calls one of the :code:`Client` methods to run your code (i." +"e., to train the neural network we defined earlier)." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:150 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which " -"makes it easier to implement the :code:`Client` interface when your " -"workload uses PyTorch. Implementing :code:`NumPyClient` usually means " -"defining the following methods (:code:`set_parameters` is optional " -"though):" +"Flower provides a convenience class called :code:`NumPyClient` which makes " +"it easier to implement the :code:`Client` interface when your workload uses " +"PyTorch. Implementing :code:`NumPyClient` usually means defining the " +"following methods (:code:`set_parameters` is optional though):" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:156 @@ -19897,8 +20234,7 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:158 #: ../../source/tutorial-quickstart-scikitlearn.rst:121 msgid "" -"update the local model weights with the parameters received from the " -"server" +"update the local model weights with the parameters received from the server" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:160 @@ -19928,22 +20264,22 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:189 #: ../../source/tutorial-quickstart-tensorflow.rst:82 msgid "" -"We can now create an instance of our class :code:`CifarClient` and add " -"one line to actually run this client:" +"We can now create an instance of our class :code:`CifarClient` and add one " +"line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:196 #: ../../source/tutorial-quickstart-tensorflow.rst:90 msgid "" -"That's it for the client. We only have to implement :code:`Client` or " -":code:`NumPyClient` and call :code:`fl.client.start_client()`. If you " -"implement a client of type :code:`NumPyClient` you'll need to first call " -"its :code:`to_client()` method. The string :code:`\"[::]:8080\"` tells " -"the client which server to connect to. In our case we can run the server " -"and the client on the same machine, therefore we use " -":code:`\"[::]:8080\"`. If we run a truly federated workload with the " -"server and clients running on different machines, all that needs to " -"change is the :code:`server_address` we point the client at." +"That's it for the client. We only have to implement :code:`Client` or :code:" +"`NumPyClient` and call :code:`fl.client.start_client()`. If you implement a " +"client of type :code:`NumPyClient` you'll need to first call its :code:" +"`to_client()` method. The string :code:`\"[::]:8080\"` tells the client " +"which server to connect to. In our case we can run the server and the client " +"on the same machine, therefore we use :code:`\"[::]:8080\"`. If we run a " +"truly federated workload with the server and clients running on different " +"machines, all that needs to change is the :code:`server_address` we point " +"the client at." msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:226 @@ -19951,8 +20287,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:122 #: ../../source/tutorial-quickstart-xgboost.rst:533 msgid "" -"Once the server is running we can start the clients in different " -"terminals. Open a new terminal and start the first client:" +"Once the server is running we can start the clients in different terminals. " +"Open a new terminal and start the first client:" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:233 @@ -19966,24 +20302,22 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:252 #: ../../source/tutorial-quickstart-xgboost.rst:546 msgid "" -"Each client will have its own dataset. You should now see how the " -"training does in the very first terminal (the one that started the " -"server):" +"Each client will have its own dataset. You should now see how the training " +"does in the very first terminal (the one that started the server):" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:271 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code " -"`_ for this example can be found in :code:`examples" -"/quickstart-pytorch`." +"learning system. The full `source code `_ for this example can be found " +"in :code:`examples/quickstart-pytorch`." msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with PyTorch Lightning to train an Auto Encoder model on MNIST." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"PyTorch Lightning to train an Auto Encoder model on MNIST." msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 @@ -19992,21 +20326,20 @@ msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:10 msgid "" -"Let's build a horizontal federated learning system using PyTorch " -"Lightning and Flower!" +"Let's build a horizontal federated learning system using PyTorch Lightning " +"and Flower!" msgstr "" #: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 msgid "" -"Please refer to the `full code example " -"`_ to learn more." +"Please refer to the `full code example `_ to learn more." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with scikit-learn to train a linear regression model." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"scikit-learn to train a linear regression model." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:5 @@ -20015,24 +20348,23 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:10 msgid "" -"In this tutorial, we will learn how to train a :code:`Logistic " -"Regression` model on MNIST using Flower and scikit-learn." +"In this tutorial, we will learn how to train a :code:`Logistic Regression` " +"model on MNIST using Flower and scikit-learn." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:12 msgid "" -"It is recommended to create a virtual environment and run everything " -"within this :doc:`virtualenv `." +"It is recommended to create a virtual environment and run everything within " +"this :doc:`virtualenv `." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:16 msgid "" -"*Clients* are responsible for generating individual model parameter " -"updates for the model based on their local datasets. These updates are " -"then sent to the *server* which will aggregate them to produce an updated" -" global model. Finally, the *server* sends this improved version of the " -"model back to each *client*. A complete cycle of parameters updates is " -"called a *round*." +"*Clients* are responsible for generating individual model parameter updates " +"for the model based on their local datasets. These updates are then sent to " +"the *server* which will aggregate them to produce an updated global model. " +"Finally, the *server* sends this improved version of the model back to each " +"*client*. A complete cycle of parameters updates is called a *round*." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:20 @@ -20053,10 +20385,10 @@ msgstr "" msgid "" "Now that we have all our dependencies installed, let's run a simple " "distributed training with two clients and one server. However, before " -"setting up the client and server, we will define all functionalities that" -" we need for our federated learning setup within :code:`utils.py`. The " -":code:`utils.py` contains different functions defining all the machine " -"learning basics:" +"setting up the client and server, we will define all functionalities that we " +"need for our federated learning setup within :code:`utils.py`. The :code:" +"`utils.py` contains different functions defining all the machine learning " +"basics:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:45 @@ -20085,46 +20417,44 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:52 msgid "" -"Please check out :code:`utils.py` `here " -"`_ for more details. The pre-defined functions are used in" -" the :code:`client.py` and imported. The :code:`client.py` also requires " -"to import several packages such as Flower and scikit-learn:" +"Please check out :code:`utils.py` `here `_ for more details. The pre-" +"defined functions are used in the :code:`client.py` and imported. The :code:" +"`client.py` also requires to import several packages such as Flower and " +"scikit-learn:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:67 msgid "" -"Prior to local training, we need to load the MNIST dataset, a popular " -"image classification dataset of handwritten digits for machine learning, " -"and partition the dataset for FL. This can be conveniently achieved using" -" `Flower Datasets `_. The " -":code:`FederatedDataset.load_partition()` method loads the partitioned " -"training set for each partition ID defined in the :code:`--partition-id` " -"argument." +"Prior to local training, we need to load the MNIST dataset, a popular image " +"classification dataset of handwritten digits for machine learning, and " +"partition the dataset for FL. This can be conveniently achieved using " +"`Flower Datasets `_. The :code:" +"`FederatedDataset.load_partition()` method loads the partitioned training " +"set for each partition ID defined in the :code:`--partition-id` argument." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:95 msgid "" -"Next, the logistic regression model is defined and initialized with " -":code:`utils.set_initial_params()`." +"Next, the logistic regression model is defined and initialized with :code:" +"`utils.set_initial_params()`." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:107 msgid "" -"The Flower server interacts with clients through an interface called " -":code:`Client`. When the server selects a particular client for training," -" it sends training instructions over the network. The client receives " -"those instructions and calls one of the :code:`Client` methods to run " -"your code (i.e., to fit the logistic regression we defined earlier)." +"The Flower server interacts with clients through an interface called :code:" +"`Client`. When the server selects a particular client for training, it sends " +"training instructions over the network. The client receives those " +"instructions and calls one of the :code:`Client` methods to run your code (i." +"e., to fit the logistic regression we defined earlier)." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:113 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which " -"makes it easier to implement the :code:`Client` interface when your " -"workload uses scikit-learn. Implementing :code:`NumPyClient` usually " -"means defining the following methods (:code:`set_parameters` is optional " -"though):" +"Flower provides a convenience class called :code:`NumPyClient` which makes " +"it easier to implement the :code:`Client` interface when your workload uses " +"scikit-learn. Implementing :code:`NumPyClient` usually means defining the " +"following methods (:code:`set_parameters` is optional though):" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:122 @@ -20137,28 +20467,28 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:153 msgid "" -"We can now create an instance of our class :code:`MnistClient` and add " -"one line to actually run this client:" +"We can now create an instance of our class :code:`MnistClient` and add one " +"line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:160 msgid "" -"That's it for the client. We only have to implement :code:`Client` or " -":code:`NumPyClient` and call :code:`fl.client.start_client()`. If you " -"implement a client of type :code:`NumPyClient` you'll need to first call " -"its :code:`to_client()` method. The string :code:`\"0.0.0.0:8080\"` tells" -" the client which server to connect to. In our case we can run the server" -" and the client on the same machine, therefore we use " -":code:`\"0.0.0.0:8080\"`. If we run a truly federated workload with the " -"server and clients running on different machines, all that needs to " -"change is the :code:`server_address` we pass to the client." +"That's it for the client. We only have to implement :code:`Client` or :code:" +"`NumPyClient` and call :code:`fl.client.start_client()`. If you implement a " +"client of type :code:`NumPyClient` you'll need to first call its :code:" +"`to_client()` method. The string :code:`\"0.0.0.0:8080\"` tells the client " +"which server to connect to. In our case we can run the server and the client " +"on the same machine, therefore we use :code:`\"0.0.0.0:8080\"`. If we run a " +"truly federated workload with the server and clients running on different " +"machines, all that needs to change is the :code:`server_address` we pass to " +"the client." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:169 msgid "" "The following Flower server is a little bit more advanced and returns an " -"evaluation function for the server-side evaluation. First, we import " -"again all required libraries such as Flower and scikit-learn." +"evaluation function for the server-side evaluation. First, we import again " +"all required libraries such as Flower and scikit-learn." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:172 @@ -20167,46 +20497,44 @@ msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:185 msgid "" -"The number of federated learning rounds is set in :code:`fit_round()` and" -" the evaluation is defined in :code:`get_evaluate_fn()`. The evaluation " +"The number of federated learning rounds is set in :code:`fit_round()` and " +"the evaluation is defined in :code:`get_evaluate_fn()`. The evaluation " "function is called after each federated learning round and gives you " -"information about loss and accuracy. Note that we also make use of Flower" -" Datasets here to load the test split of the MNIST dataset for server-" -"side evaluation." +"information about loss and accuracy. Note that we also make use of Flower " +"Datasets here to load the test split of the MNIST dataset for server-side " +"evaluation." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:213 msgid "" -"The :code:`main` contains the server-side parameter initialization " -":code:`utils.set_initial_params()` as well as the aggregation strategy " -":code:`fl.server.strategy:FedAvg()`. The strategy is the default one, " -"federated averaging (or FedAvg), with two clients and evaluation after " -"each federated learning round. The server can be started with the command" -" :code:`fl.server.start_server(server_address=\"0.0.0.0:8080\", " -"strategy=strategy, config=fl.server.ServerConfig(num_rounds=3))`." +"The :code:`main` contains the server-side parameter initialization :code:" +"`utils.set_initial_params()` as well as the aggregation strategy :code:`fl." +"server.strategy:FedAvg()`. The strategy is the default one, federated " +"averaging (or FedAvg), with two clients and evaluation after each federated " +"learning round. The server can be started with the command :code:`fl.server." +"start_server(server_address=\"0.0.0.0:8080\", strategy=strategy, config=fl." +"server.ServerConfig(num_rounds=3))`." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:232 msgid "" "With both client and server ready, we can now run everything and see " "federated learning in action. Federated learning systems usually have a " -"server and multiple clients. We, therefore, have to start the server " -"first:" +"server and multiple clients. We, therefore, have to start the server first:" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:286 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code " -"`_ for this example can be found in :code:`examples/sklearn-logreg-" -"mnist`." +"learning system. The full `source code `_ for this example can be found in :code:" +"`examples/sklearn-logreg-mnist`." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with TensorFlow to train a MobilNetV2 model on CIFAR-10." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"TensorFlow to train a MobilNetV2 model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:5 @@ -20223,8 +20551,8 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:21 msgid "" -"Since we want to use the Keras API of TensorFlow (TF), we have to install" -" TF as well:" +"Since we want to use the Keras API of TensorFlow (TF), we have to install TF " +"as well:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:31 @@ -20233,25 +20561,24 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:38 msgid "" -"We use the Keras utilities of TF to load CIFAR10, a popular colored image" -" classification dataset for machine learning. The call to " -":code:`tf.keras.datasets.cifar10.load_data()` downloads CIFAR10, caches " -"it locally, and then returns the entire training and test set as NumPy " -"ndarrays." +"We use the Keras utilities of TF to load CIFAR10, a popular colored image " +"classification dataset for machine learning. The call to :code:`tf.keras." +"datasets.cifar10.load_data()` downloads CIFAR10, caches it locally, and then " +"returns the entire training and test set as NumPy ndarrays." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:47 msgid "" -"Next, we need a model. For the purpose of this tutorial, we use " -"MobilNetV2 with 10 output classes:" +"Next, we need a model. For the purpose of this tutorial, we use MobilNetV2 " +"with 10 output classes:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:60 msgid "" -"Flower provides a convenience class called :code:`NumPyClient` which " -"makes it easier to implement the :code:`Client` interface when your " -"workload uses Keras. The :code:`NumPyClient` interface defines three " -"methods which can be implemented in the following way:" +"Flower provides a convenience class called :code:`NumPyClient` which makes " +"it easier to implement the :code:`Client` interface when your workload uses " +"Keras. The :code:`NumPyClient` interface defines three methods which can be " +"implemented in the following way:" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:135 @@ -20260,23 +20587,22 @@ msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:137 msgid "" -"You should now see how the training does in the very first terminal (the " -"one that started the server):" +"You should now see how the training does in the very first terminal (the one " +"that started the server):" msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:169 msgid "" "Congratulations! You've successfully built and run your first federated " -"learning system. The full `source code " -"`_ for this can be found in :code:`examples" -"/quickstart-tensorflow/client.py`." +"learning system. The full `source code `_ for this can be found in :" +"code:`examples/quickstart-tensorflow/client.py`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:-1 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with XGBoost to train classification models on trees." +"Check out this Federated Learning quickstart tutorial for using Flower with " +"XGBoost to train classification models on trees." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:5 @@ -20290,18 +20616,17 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:16 msgid "" "EXtreme Gradient Boosting (**XGBoost**) is a robust and efficient " -"implementation of gradient-boosted decision tree (**GBDT**), that " -"maximises the computational boundaries for boosted tree methods. It's " -"primarily designed to enhance both the performance and computational " -"speed of machine learning models. In XGBoost, trees are constructed " -"concurrently, unlike the sequential approach taken by GBDT." +"implementation of gradient-boosted decision tree (**GBDT**), that maximises " +"the computational boundaries for boosted tree methods. It's primarily " +"designed to enhance both the performance and computational speed of machine " +"learning models. In XGBoost, trees are constructed concurrently, unlike the " +"sequential approach taken by GBDT." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:20 msgid "" "Often, for tabular data on medium-sized datasets with fewer than 10k " -"training examples, XGBoost surpasses the results of deep learning " -"techniques." +"training examples, XGBoost surpasses the results of deep learning techniques." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:23 @@ -20311,30 +20636,30 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:25 msgid "" "Indeed, as the demand for data privacy and decentralized learning grows, " -"there's an increasing requirement to implement federated XGBoost systems " -"for specialised applications, like survival analysis and financial fraud " +"there's an increasing requirement to implement federated XGBoost systems for " +"specialised applications, like survival analysis and financial fraud " "detection." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:27 msgid "" -"Federated learning ensures that raw data remains on the local device, " -"making it an attractive approach for sensitive domains where data " -"security and privacy are paramount. Given the robustness and efficiency " -"of XGBoost, combining it with federated learning offers a promising " -"solution for these specific challenges." +"Federated learning ensures that raw data remains on the local device, making " +"it an attractive approach for sensitive domains where data security and " +"privacy are paramount. Given the robustness and efficiency of XGBoost, " +"combining it with federated learning offers a promising solution for these " +"specific challenges." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:30 msgid "" "In this tutorial we will learn how to train a federated XGBoost model on " "HIGGS dataset using Flower and :code:`xgboost` package. We use a simple " -"example (`full code xgboost-quickstart " -"`_)" -" with two *clients* and one *server* to demonstrate how federated XGBoost" -" works, and then we dive into a more complex example (`full code xgboost-" -"comprehensive `_) to run various experiments." +"example (`full code xgboost-quickstart `_) with two *clients* and one *server* to " +"demonstrate how federated XGBoost works, and then we dive into a more " +"complex example (`full code xgboost-comprehensive `_) to run various " +"experiments." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:37 @@ -20355,16 +20680,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:57 msgid "" -"*Clients* are responsible for generating individual weight-updates for " -"the model based on their local datasets. Now that we have all our " -"dependencies installed, let's run a simple distributed training with two " -"clients and one server." +"*Clients* are responsible for generating individual weight-updates for the " +"model based on their local datasets. Now that we have all our dependencies " +"installed, let's run a simple distributed training with two clients and one " +"server." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:60 msgid "" -"In a file called :code:`client.py`, import xgboost, Flower, Flower " -"Datasets and other related functions:" +"In a file called :code:`client.py`, import xgboost, Flower, Flower Datasets " +"and other related functions:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:87 @@ -20373,15 +20698,15 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:89 msgid "" -"Prior to local training, we require loading the HIGGS dataset from Flower" -" Datasets and conduct data partitioning for FL:" +"Prior to local training, we require loading the HIGGS dataset from Flower " +"Datasets and conduct data partitioning for FL:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:102 msgid "" "In this example, we split the dataset into two partitions with uniform " -"distribution (:code:`IidPartitioner(num_partitions=2)`). Then, we load " -"the partition for the given client based on :code:`node_id`:" +"distribution (:code:`IidPartitioner(num_partitions=2)`). Then, we load the " +"partition for the given client based on :code:`node_id`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:121 @@ -20392,8 +20717,8 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:134 msgid "" -"The functions of :code:`train_test_split` and " -":code:`transform_dataset_to_dmatrix` are defined as below:" +"The functions of :code:`train_test_split` and :code:" +"`transform_dataset_to_dmatrix` are defined as below:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:158 @@ -20402,10 +20727,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:174 msgid "" -"The :code:`num_local_round` represents the number of iterations for local" -" tree boost. We use CPU for the training in default. One can shift it to " -"GPU by setting :code:`tree_method` to :code:`gpu_hist`. We use AUC as " -"evaluation metric." +"The :code:`num_local_round` represents the number of iterations for local " +"tree boost. We use CPU for the training in default. One can shift it to GPU " +"by setting :code:`tree_method` to :code:`gpu_hist`. We use AUC as evaluation " +"metric." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:181 @@ -20414,86 +20739,85 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:183 msgid "" -"After loading the dataset we define the Flower client. We follow the " -"general rule to define :code:`XgbClient` class inherited from " -":code:`fl.client.Client`." +"After loading the dataset we define the Flower client. We follow the general " +"rule to define :code:`XgbClient` class inherited from :code:`fl.client." +"Client`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:193 msgid "" "The :code:`self.bst` is used to keep the Booster objects that remain " "consistent across rounds, allowing them to store predictions from trees " -"integrated in earlier rounds and maintain other essential data structures" -" for training." +"integrated in earlier rounds and maintain other essential data structures " +"for training." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:196 msgid "" -"Then, we override :code:`get_parameters`, :code:`fit` and " -":code:`evaluate` methods insides :code:`XgbClient` class as follows." +"Then, we override :code:`get_parameters`, :code:`fit` and :code:`evaluate` " +"methods insides :code:`XgbClient` class as follows." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:210 msgid "" "Unlike neural network training, XGBoost trees are not started from a " -"specified random weights. In this case, we do not use " -":code:`get_parameters` and :code:`set_parameters` to initialise model " -"parameters for XGBoost. As a result, let's return an empty tensor in " -":code:`get_parameters` when it is called by the server at the first " -"round." +"specified random weights. In this case, we do not use :code:`get_parameters` " +"and :code:`set_parameters` to initialise model parameters for XGBoost. As a " +"result, let's return an empty tensor in :code:`get_parameters` when it is " +"called by the server at the first round." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:251 msgid "" -"In :code:`fit`, at the first round, we call :code:`xgb.train()` to build " -"up the first set of trees. the returned Booster object and config are " -"stored in :code:`self.bst` and :code:`self.config`, respectively. From " -"the second round, we load the global model sent from server to " -":code:`self.bst`, and then update model weights on local training data " -"with function :code:`local_boost` as follows:" +"In :code:`fit`, at the first round, we call :code:`xgb.train()` to build up " +"the first set of trees. the returned Booster object and config are stored " +"in :code:`self.bst` and :code:`self.config`, respectively. From the second " +"round, we load the global model sent from server to :code:`self.bst`, and " +"then update model weights on local training data with function :code:" +"`local_boost` as follows:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:269 msgid "" -"Given :code:`num_local_round`, we update trees by calling " -":code:`self.bst.update` method. After training, the last " -":code:`N=num_local_round` trees will be extracted to send to the server." +"Given :code:`num_local_round`, we update trees by calling :code:`self.bst." +"update` method. After training, the last :code:`N=num_local_round` trees " +"will be extracted to send to the server." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:291 msgid "" -"In :code:`evaluate`, we call :code:`self.bst.eval_set` function to " -"conduct evaluation on valid set. The AUC value will be returned." +"In :code:`evaluate`, we call :code:`self.bst.eval_set` function to conduct " +"evaluation on valid set. The AUC value will be returned." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:294 msgid "" -"Now, we can create an instance of our class :code:`XgbClient` and add one" -" line to actually run this client:" +"Now, we can create an instance of our class :code:`XgbClient` and add one " +"line to actually run this client:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:300 msgid "" -"That's it for the client. We only have to implement :code:`Client`and " -"call :code:`fl.client.start_client()`. The string :code:`\"[::]:8080\"` " -"tells the client which server to connect to. In our case we can run the " -"server and the client on the same machine, therefore we use " -":code:`\"[::]:8080\"`. If we run a truly federated workload with the " -"server and clients running on different machines, all that needs to " -"change is the :code:`server_address` we point the client at." +"That's it for the client. We only have to implement :code:`Client`and call :" +"code:`fl.client.start_client()`. The string :code:`\"[::]:8080\"` tells the " +"client which server to connect to. In our case we can run the server and the " +"client on the same machine, therefore we use :code:`\"[::]:8080\"`. If we " +"run a truly federated workload with the server and clients running on " +"different machines, all that needs to change is the :code:`server_address` " +"we point the client at." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:311 msgid "" "These updates are then sent to the *server* which will aggregate them to " -"produce a better model. Finally, the *server* sends this improved version" -" of the model back to each *client* to finish a complete FL round." +"produce a better model. Finally, the *server* sends this improved version of " +"the model back to each *client* to finish a complete FL round." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:314 msgid "" -"In a file named :code:`server.py`, import Flower and FedXgbBagging from " -":code:`flwr.server.strategy`." +"In a file named :code:`server.py`, import Flower and FedXgbBagging from :" +"code:`flwr.server.strategy`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:316 @@ -20502,9 +20826,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:339 msgid "" -"We use two clients for this example. An " -":code:`evaluate_metrics_aggregation` function is defined to collect and " -"wighted average the AUC values from clients." +"We use two clients for this example. An :code:`evaluate_metrics_aggregation` " +"function is defined to collect and wighted average the AUC values from " +"clients." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:342 @@ -20517,16 +20841,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:356 msgid "" -"You must be curious about how bagging aggregation works. Let's look into " -"the details." +"You must be curious about how bagging aggregation works. Let's look into the " +"details." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:358 msgid "" -"In file :code:`flwr.server.strategy.fedxgb_bagging.py`, we define " -":code:`FedXgbBagging` inherited from :code:`flwr.server.strategy.FedAvg`." -" Then, we override the :code:`aggregate_fit`, :code:`aggregate_evaluate` " -"and :code:`evaluate` methods as follows:" +"In file :code:`flwr.server.strategy.fedxgb_bagging.py`, we define :code:" +"`FedXgbBagging` inherited from :code:`flwr.server.strategy.FedAvg`. Then, we " +"override the :code:`aggregate_fit`, :code:`aggregate_evaluate` and :code:" +"`evaluate` methods as follows:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:454 @@ -20538,10 +20862,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:513 msgid "" "In this function, we first fetch the number of trees and the number of " -"parallel trees for the current and previous model by calling " -":code:`_get_tree_nums`. Then, the fetched information will be aggregated." -" After that, the trees (containing model weights) are aggregated to " -"generate a new tree model." +"parallel trees for the current and previous model by calling :code:" +"`_get_tree_nums`. Then, the fetched information will be aggregated. After " +"that, the trees (containing model weights) are aggregated to generate a new " +"tree model." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:518 @@ -20557,16 +20881,16 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:585 msgid "" "Congratulations! You've successfully built and run your first federated " -"XGBoost system. The AUC values can be checked in " -":code:`metrics_distributed`. One can see that the average AUC increases " -"over FL rounds." +"XGBoost system. The AUC values can be checked in :code:" +"`metrics_distributed`. One can see that the average AUC increases over FL " +"rounds." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:590 msgid "" -"The full `source code `_ for this example can be found in :code:`examples" -"/xgboost-quickstart`." +"The full `source code `_ for this example can be found in :code:`examples/" +"xgboost-quickstart`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:594 @@ -20575,15 +20899,15 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:596 msgid "" -"Now that you have known how federated XGBoost work with Flower, it's time" -" to run some more comprehensive experiments by customising the " -"experimental settings. In the xgboost-comprehensive example (`full code " -"`_), we provide more options to define various experimental" -" setups, including aggregation strategies, data partitioning and " -"centralised/distributed evaluation. We also support :doc:`Flower " -"simulation ` making it easy to simulate large " -"client cohorts in a resource-aware manner. Let's take a look!" +"Now that you have known how federated XGBoost work with Flower, it's time to " +"run some more comprehensive experiments by customising the experimental " +"settings. In the xgboost-comprehensive example (`full code `_), we provide " +"more options to define various experimental setups, including aggregation " +"strategies, data partitioning and centralised/distributed evaluation. We " +"also support :doc:`Flower simulation ` making it " +"easy to simulate large client cohorts in a resource-aware manner. Let's take " +"a look!" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:603 @@ -20592,41 +20916,40 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:605 msgid "" -"In addition to bagging aggregation, we offer a cyclic training scheme, " -"which performs FL in a client-by-client fashion. Instead of aggregating " -"multiple clients, there is only one single client participating in the " -"training per round in the cyclic training scenario. The trained local " -"XGBoost trees will be passed to the next client as an initialised model " -"for next round's boosting." +"In addition to bagging aggregation, we offer a cyclic training scheme, which " +"performs FL in a client-by-client fashion. Instead of aggregating multiple " +"clients, there is only one single client participating in the training per " +"round in the cyclic training scenario. The trained local XGBoost trees will " +"be passed to the next client as an initialised model for next round's " +"boosting." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:609 msgid "" -"To do this, we first customise a :code:`ClientManager` in " -":code:`server_utils.py`:" +"To do this, we first customise a :code:`ClientManager` in :code:" +"`server_utils.py`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:649 msgid "" -"The customised :code:`ClientManager` samples all available clients in " -"each FL round based on the order of connection to the server. Then, we " -"define a new strategy :code:`FedXgbCyclic` in " -":code:`flwr.server.strategy.fedxgb_cyclic.py`, in order to sequentially " -"select only one client in given round and pass the received model to next" -" client." +"The customised :code:`ClientManager` samples all available clients in each " +"FL round based on the order of connection to the server. Then, we define a " +"new strategy :code:`FedXgbCyclic` in :code:`flwr.server.strategy." +"fedxgb_cyclic.py`, in order to sequentially select only one client in given " +"round and pass the received model to next client." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:690 msgid "" "Unlike the original :code:`FedAvg`, we don't perform aggregation here. " -"Instead, we just make a copy of the received client model as global model" -" by overriding :code:`aggregate_fit`." +"Instead, we just make a copy of the received client model as global model by " +"overriding :code:`aggregate_fit`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:693 msgid "" -"Also, the customised :code:`configure_fit` and :code:`configure_evaluate`" -" methods ensure the clients to be sequentially selected given FL round:" +"Also, the customised :code:`configure_fit` and :code:`configure_evaluate` " +"methods ensure the clients to be sequentially selected given FL round:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:757 @@ -20635,11 +20958,11 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:759 msgid "" -"In :code:`dataset.py`, we have a function :code:`instantiate_partitioner`" -" to instantiate the data partitioner based on the given " -":code:`num_partitions` and :code:`partitioner_type`. Currently, we " -"provide four supported partitioner type to simulate the uniformity/non-" -"uniformity in data quantity (uniform, linear, square, exponential)." +"In :code:`dataset.py`, we have a function :code:`instantiate_partitioner` to " +"instantiate the data partitioner based on the given :code:`num_partitions` " +"and :code:`partitioner_type`. Currently, we provide four supported " +"partitioner type to simulate the uniformity/non-uniformity in data quantity " +"(uniform, linear, square, exponential)." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:790 @@ -20648,23 +20971,23 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:792 msgid "" -"To facilitate centralised evaluation, we define a function in " -":code:`server_utils.py`:" +"To facilitate centralised evaluation, we define a function in :code:" +"`server_utils.py`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:824 msgid "" -"This function returns a evaluation function which instantiates a " -":code:`Booster` object and loads the global model weights to it. The " -"evaluation is conducted by calling :code:`eval_set()` method, and the " -"tested AUC value is reported." +"This function returns a evaluation function which instantiates a :code:" +"`Booster` object and loads the global model weights to it. The evaluation is " +"conducted by calling :code:`eval_set()` method, and the tested AUC value is " +"reported." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:827 msgid "" -"As for distributed evaluation on the clients, it's same as the quick-" -"start example by overriding the :code:`evaluate()` method insides the " -":code:`XgbClient` class in :code:`client_utils.py`." +"As for distributed evaluation on the clients, it's same as the quick-start " +"example by overriding the :code:`evaluate()` method insides the :code:" +"`XgbClient` class in :code:`client_utils.py`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:831 @@ -20674,21 +20997,21 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:832 msgid "" "We also provide an example code (:code:`sim.py`) to use the simulation " -"capabilities of Flower to simulate federated XGBoost training on either a" -" single machine or a cluster of machines." +"capabilities of Flower to simulate federated XGBoost training on either a " +"single machine or a cluster of machines." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:866 msgid "" -"After importing all required packages, we define a :code:`main()` " -"function to perform the simulation process:" +"After importing all required packages, we define a :code:`main()` function " +"to perform the simulation process:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:921 msgid "" "We first load the dataset and perform data partitioning, and the pre-" -"processed data is stored in a :code:`list`. After the simulation begins, " -"the clients won't need to pre-process their partitions again." +"processed data is stored in a :code:`list`. After the simulation begins, the " +"clients won't need to pre-process their partitions again." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:924 @@ -20697,8 +21020,8 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:975 msgid "" -"After that, we start the simulation by calling " -":code:`fl.simulation.start_simulation`:" +"After that, we start the simulation by calling :code:`fl.simulation." +"start_simulation`:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:995 @@ -20713,18 +21036,18 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1040 msgid "" -"In :code:`utils.py`, we define the arguments parsers for clients, server " -"and simulation, allowing users to specify different experimental " -"settings. Let's first see the sever side:" +"In :code:`utils.py`, we define the arguments parsers for clients, server and " +"simulation, allowing users to specify different experimental settings. Let's " +"first see the sever side:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1086 msgid "" "This allows user to specify training strategies / the number of total " -"clients / FL rounds / participating clients / clients for evaluation, and" -" evaluation fashion. Note that with :code:`--centralised-eval`, the sever" -" will do centralised evaluation and all functionalities for client " -"evaluation will be disabled." +"clients / FL rounds / participating clients / clients for evaluation, and " +"evaluation fashion. Note that with :code:`--centralised-eval`, the sever " +"will do centralised evaluation and all functionalities for client evaluation " +"will be disabled." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1090 @@ -20733,11 +21056,10 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1144 msgid "" -"This defines various options for client data partitioning. Besides, " -"clients also have an option to conduct evaluation on centralised test set" -" by setting :code:`--centralised-eval`, as well as an option to perform " -"scaled learning rate based on the number of clients by setting :code" -":`--scaled-lr`." +"This defines various options for client data partitioning. Besides, clients " +"also have an option to conduct evaluation on centralised test set by " +"setting :code:`--centralised-eval`, as well as an option to perform scaled " +"learning rate based on the number of clients by setting :code:`--scaled-lr`." msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1148 @@ -20754,9 +21076,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1231 msgid "" -"To run a centralised evaluated experiment with bagging strategy on 5 " -"clients with exponential distribution for 50 rounds, we first start the " -"server as below:" +"To run a centralised evaluated experiment with bagging strategy on 5 clients " +"with exponential distribution for 50 rounds, we first start the server as " +"below:" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1238 @@ -20769,9 +21091,9 @@ msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:1250 msgid "" -"The full `code `_ for this comprehensive example can be found in" -" :code:`examples/xgboost-comprehensive`." +"The full `code `_ for this comprehensive example can be found in :code:" +"`examples/xgboost-comprehensive`." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:9 @@ -20782,19 +21104,18 @@ msgstr "" msgid "" "Welcome to the third part of the Flower federated learning tutorial. In " "previous parts of this tutorial, we introduced federated learning with " -"PyTorch and Flower (`part 1 `__) and we learned how strategies " -"can be used to customize the execution on both the server and the clients" -" (`part 2 `__)." +"PyTorch and Flower (`part 1 `__) and we learned how strategies can be " +"used to customize the execution on both the server and the clients (`part 2 " +"`__)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:13 msgid "" -"In this notebook, we'll continue to customize the federated learning " -"system we built previously by creating a custom version of FedAvg (again," -" using `Flower `__ and `PyTorch " -"`__)." +"In this notebook, we'll continue to customize the federated learning system " +"we built previously by creating a custom version of FedAvg (again, using " +"`Flower `__ and `PyTorch `__)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:15 @@ -20802,11 +21123,11 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:15 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:15 msgid "" -"`Star Flower on GitHub `__ ⭐️ and join " -"the Flower community on Slack to connect, ask questions, and get help: " -"`Join Slack `__ 🌼 We'd love to hear from " -"you in the ``#introductions`` channel! And if anything is unclear, head " -"over to the ``#questions`` channel." +"`Star Flower on GitHub `__ ⭐️ and join the " +"Flower community on Slack to connect, ask questions, and get help: `Join " +"Slack `__ 🌼 We'd love to hear from you in the " +"``#introductions`` channel! And if anything is unclear, head over to the " +"``#questions`` channel." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:17 @@ -20852,14 +21173,14 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:102 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:101 msgid "" -"It is possible to switch to a runtime that has GPU acceleration enabled " -"(on Google Colab: ``Runtime > Change runtime type > Hardware acclerator: " -"GPU > Save``). Note, however, that Google Colab is not always able to " -"offer GPU acceleration. If you see an error related to GPU availability " -"in one of the following sections, consider switching back to CPU-based " -"execution by setting ``DEVICE = torch.device(\"cpu\")``. If the runtime " -"has GPU acceleration enabled, you should see the output ``Training on " -"cuda``, otherwise it'll say ``Training on cpu``." +"It is possible to switch to a runtime that has GPU acceleration enabled (on " +"Google Colab: ``Runtime > Change runtime type > Hardware acclerator: GPU > " +"Save``). Note, however, that Google Colab is not always able to offer GPU " +"acceleration. If you see an error related to GPU availability in one of the " +"following sections, consider switching back to CPU-based execution by " +"setting ``DEVICE = torch.device(\"cpu\")``. If the runtime has GPU " +"acceleration enabled, you should see the output ``Training on cuda``, " +"otherwise it'll say ``Training on cpu``." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:114 @@ -20871,11 +21192,11 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:116 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:116 msgid "" -"Let's now load the CIFAR-10 training and test set, partition them into " -"ten smaller datasets (each split into training and validation set), and " -"wrap everything in their own ``DataLoader``. We introduce a new parameter" -" ``num_clients`` which allows us to call ``load_datasets`` with different" -" numbers of clients." +"Let's now load the CIFAR-10 training and test set, partition them into ten " +"smaller datasets (each split into training and validation set), and wrap " +"everything in their own ``DataLoader``. We introduce a new parameter " +"``num_clients`` which allows us to call ``load_datasets`` with different " +"numbers of clients." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:167 @@ -20888,8 +21209,8 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:170 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:169 msgid "" -"Let's continue with the usual model definition (including " -"``set_parameters`` and ``get_parameters``), training and test functions:" +"Let's continue with the usual model definition (including ``set_parameters`` " +"and ``get_parameters``), training and test functions:" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:258 @@ -20900,10 +21221,10 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:260 #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:260 msgid "" -"To implement the Flower client, we (again) create a subclass of " -"``flwr.client.NumPyClient`` and implement the three methods " -"``get_parameters``, ``fit``, and ``evaluate``. Here, we also pass the " -"``cid`` to the client and use it log additional details:" +"To implement the Flower client, we (again) create a subclass of ``flwr." +"client.NumPyClient`` and implement the three methods ``get_parameters``, " +"``fit``, and ``evaluate``. Here, we also pass the ``cid`` to the client and " +"use it log additional details:" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:308 @@ -20916,11 +21237,11 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:341 msgid "" -"Let’s overwrite the ``configure_fit`` method such that it passes a higher" -" learning rate (potentially also other hyperparameters) to the optimizer " -"of a fraction of the clients. We will keep the sampling of the clients as" -" it is in ``FedAvg`` and then change the configuration dictionary (one of" -" the ``FitIns`` attributes)." +"Let’s overwrite the ``configure_fit`` method such that it passes a higher " +"learning rate (potentially also other hyperparameters) to the optimizer of a " +"fraction of the clients. We will keep the sampling of the clients as it is " +"in ``FedAvg`` and then change the configuration dictionary (one of the " +"``FitIns`` attributes)." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:507 @@ -20937,13 +21258,13 @@ msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:536 msgid "" -"In this notebook, we’ve seen how to implement a custom strategy. A custom" -" strategy enables granular control over client node configuration, result" -" aggregation, and more. To define a custom strategy, you only have to " -"overwrite the abstract methods of the (abstract) base class ``Strategy``." -" To make custom strategies even more powerful, you can pass custom " -"functions to the constructor of your new class (``__init__``) and then " -"call these functions whenever needed." +"In this notebook, we’ve seen how to implement a custom strategy. A custom " +"strategy enables granular control over client node configuration, result " +"aggregation, and more. To define a custom strategy, you only have to " +"overwrite the abstract methods of the (abstract) base class ``Strategy``. To " +"make custom strategies even more powerful, you can pass custom functions to " +"the constructor of your new class (``__init__``) and then call these " +"functions whenever needed." msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:550 @@ -20952,8 +21273,8 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:715 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:369 msgid "" -"Before you continue, make sure to join the Flower community on Slack: " -"`Join Slack `__" +"Before you continue, make sure to join the Flower community on Slack: `Join " +"Slack `__" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:552 @@ -20962,16 +21283,15 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:717 #: ../../source/tutorial-series-what-is-federated-learning.ipynb:371 msgid "" -"There's a dedicated ``#questions`` channel if you need help, but we'd " -"also love to hear who you are in ``#introductions``!" +"There's a dedicated ``#questions`` channel if you need help, but we'd also " +"love to hear who you are in ``#introductions``!" msgstr "" #: ../../source/tutorial-series-build-a-strategy-from-scratch-pytorch.ipynb:554 msgid "" -"The `Flower Federated Learning Tutorial - Part 4 " -"`__ introduces ``Client``, the flexible API underlying " -"``NumPyClient``." +"The `Flower Federated Learning Tutorial - Part 4 `__ introduces " +"``Client``, the flexible API underlying ``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:9 @@ -20980,26 +21300,26 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:11 msgid "" -"Welcome to the fourth part of the Flower federated learning tutorial. In " -"the previous parts of this tutorial, we introduced federated learning " -"with PyTorch and Flower (`part 1 `__), we learned how " -"strategies can be used to customize the execution on both the server and " -"the clients (`part 2 `__), and we built our own " -"custom strategy from scratch (`part 3 `__)." +"Welcome to the fourth part of the Flower federated learning tutorial. In the " +"previous parts of this tutorial, we introduced federated learning with " +"PyTorch and Flower (`part 1 `__), we learned how strategies can be used " +"to customize the execution on both the server and the clients (`part 2 " +"`__), and we built our own custom strategy from scratch (`part " +"3 `__)." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:14 msgid "" -"In this notebook, we revisit ``NumPyClient`` and introduce a new " -"baseclass for building clients, simply named ``Client``. In previous " -"parts of this tutorial, we've based our client on ``NumPyClient``, a " -"convenience class which makes it easy to work with machine learning " -"libraries that have good NumPy interoperability. With ``Client``, we gain" -" a lot of flexibility that we didn't have before, but we'll also have to " -"do a few things the we didn't have to do before." +"In this notebook, we revisit ``NumPyClient`` and introduce a new baseclass " +"for building clients, simply named ``Client``. In previous parts of this " +"tutorial, we've based our client on ``NumPyClient``, a convenience class " +"which makes it easy to work with machine learning libraries that have good " +"NumPy interoperability. With ``Client``, we gain a lot of flexibility that " +"we didn't have before, but we'll also have to do a few things the we didn't " +"have to do before." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:18 @@ -21015,9 +21335,9 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:117 msgid "" -"Let's now load the CIFAR-10 training and test set, partition them into " -"ten smaller datasets (each split into training and validation set), and " -"wrap everything in their own ``DataLoader``." +"Let's now load the CIFAR-10 training and test set, partition them into ten " +"smaller datasets (each split into training and validation set), and wrap " +"everything in their own ``DataLoader``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:259 @@ -21026,10 +21346,10 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:261 msgid "" -"So far, we've implemented our client by subclassing " -"``flwr.client.NumPyClient``. The three methods we implemented are " -"``get_parameters``, ``fit``, and ``evaluate``. Finally, we wrap the " -"creation of instances of this class in a function called ``client_fn``:" +"So far, we've implemented our client by subclassing ``flwr.client." +"NumPyClient``. The three methods we implemented are ``get_parameters``, " +"``fit``, and ``evaluate``. Finally, we wrap the creation of instances of " +"this class in a function called ``client_fn``:" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:309 @@ -21051,25 +21371,24 @@ msgid "" "Let's dive a little bit deeper and discuss how Flower executes this " "simulation. Whenever a client is selected to do some work, " "``start_simulation`` calls the function ``numpyclient_fn`` to create an " -"instance of our ``FlowerNumPyClient`` (along with loading the model and " -"the data)." +"instance of our ``FlowerNumPyClient`` (along with loading the model and the " +"data)." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:343 msgid "" "But here's the perhaps surprising part: Flower doesn't actually use the " -"``FlowerNumPyClient`` object directly. Instead, it wraps the object to " -"makes it look like a subclass of ``flwr.client.Client``, not " -"``flwr.client.NumPyClient``. In fact, the Flower core framework doesn't " -"know how to handle ``NumPyClient``'s, it only knows how to handle " -"``Client``'s. ``NumPyClient`` is just a convenience abstraction built on " -"top of ``Client``." +"``FlowerNumPyClient`` object directly. Instead, it wraps the object to makes " +"it look like a subclass of ``flwr.client.Client``, not ``flwr.client." +"NumPyClient``. In fact, the Flower core framework doesn't know how to handle " +"``NumPyClient``'s, it only knows how to handle ``Client``'s. ``NumPyClient`` " +"is just a convenience abstraction built on top of ``Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:345 msgid "" -"Instead of building on top of ``NumPyClient``, we can directly build on " -"top of ``Client``." +"Instead of building on top of ``NumPyClient``, we can directly build on top " +"of ``Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:357 @@ -21078,14 +21397,13 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:359 msgid "" -"Let's try to do the same thing using ``Client`` instead of " -"``NumPyClient``." +"Let's try to do the same thing using ``Client`` instead of ``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:465 msgid "" -"Before we discuss the code in more detail, let's try to run it! Gotta " -"make sure our new ``Client``-based client works, right?" +"Before we discuss the code in more detail, let's try to run it! Gotta make " +"sure our new ``Client``-based client works, right?" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:490 @@ -21096,40 +21414,40 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:492 msgid "" -"First of all, it's more code. But why? The difference comes from the fact" -" that ``Client`` expects us to take care of parameter serialization and " -"deserialization. For Flower to be able to send parameters over the " -"network, it eventually needs to turn these parameters into ``bytes``. " -"Turning parameters (e.g., NumPy ``ndarray``'s) into raw bytes is called " +"First of all, it's more code. But why? The difference comes from the fact " +"that ``Client`` expects us to take care of parameter serialization and " +"deserialization. For Flower to be able to send parameters over the network, " +"it eventually needs to turn these parameters into ``bytes``. Turning " +"parameters (e.g., NumPy ``ndarray``'s) into raw bytes is called " "serialization. Turning raw bytes into something more useful (like NumPy " -"``ndarray``'s) is called deserialization. Flower needs to do both: it " -"needs to serialize parameters on the server-side and send them to the " -"client, the client needs to deserialize them to use them for local " -"training, and then serialize the updated parameters again to send them " -"back to the server, which (finally!) deserializes them again in order to " -"aggregate them with the updates received from other clients." +"``ndarray``'s) is called deserialization. Flower needs to do both: it needs " +"to serialize parameters on the server-side and send them to the client, the " +"client needs to deserialize them to use them for local training, and then " +"serialize the updated parameters again to send them back to the server, " +"which (finally!) deserializes them again in order to aggregate them with the " +"updates received from other clients." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:495 msgid "" "The only *real* difference between Client and NumPyClient is that " -"NumPyClient takes care of serialization and deserialization for you. It " -"can do so because it expects you to return parameters as NumPy ndarray's," -" and it knows how to handle these. This makes working with machine " -"learning libraries that have good NumPy support (most of them) a breeze." +"NumPyClient takes care of serialization and deserialization for you. It can " +"do so because it expects you to return parameters as NumPy ndarray's, and it " +"knows how to handle these. This makes working with machine learning " +"libraries that have good NumPy support (most of them) a breeze." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:497 msgid "" -"In terms of API, there's one major difference: all methods in Client take" -" exactly one argument (e.g., ``FitIns`` in ``Client.fit``) and return " -"exactly one value (e.g., ``FitRes`` in ``Client.fit``). The methods in " +"In terms of API, there's one major difference: all methods in Client take " +"exactly one argument (e.g., ``FitIns`` in ``Client.fit``) and return exactly " +"one value (e.g., ``FitRes`` in ``Client.fit``). The methods in " "``NumPyClient`` on the other hand have multiple arguments (e.g., " -"``parameters`` and ``config`` in ``NumPyClient.fit``) and multiple return" -" values (e.g., ``parameters``, ``num_example``, and ``metrics`` in " -"``NumPyClient.fit``) if there are multiple things to handle. These " -"``*Ins`` and ``*Res`` objects in ``Client`` wrap all the individual " -"values you're used to from ``NumPyClient``." +"``parameters`` and ``config`` in ``NumPyClient.fit``) and multiple return " +"values (e.g., ``parameters``, ``num_example``, and ``metrics`` in " +"``NumPyClient.fit``) if there are multiple things to handle. These ``*Ins`` " +"and ``*Res`` objects in ``Client`` wrap all the individual values you're " +"used to from ``NumPyClient``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:510 @@ -21146,17 +21464,16 @@ msgstr "" msgid "" "But first what is serialization? Serialization is just the process of " "converting an object into raw bytes, and equally as important, " -"deserialization is the process of converting raw bytes back into an " -"object. This is very useful for network communication. Indeed, without " +"deserialization is the process of converting raw bytes back into an object. " +"This is very useful for network communication. Indeed, without " "serialization, you could not just a Python object through the internet." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:516 msgid "" -"Federated Learning relies heavily on internet communication for training " -"by sending Python objects back and forth between the clients and the " -"server. This means that serialization is an essential part of Federated " -"Learning." +"Federated Learning relies heavily on internet communication for training by " +"sending Python objects back and forth between the clients and the server. " +"This means that serialization is an essential part of Federated Learning." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:518 @@ -21176,15 +21493,15 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:523 msgid "" -"This is where the real serialization/deserialization will happen, " -"especially in ``ndarray_to_sparse_bytes`` for serialization and " +"This is where the real serialization/deserialization will happen, especially " +"in ``ndarray_to_sparse_bytes`` for serialization and " "``sparse_bytes_to_ndarray`` for deserialization." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:525 msgid "" -"Note that we imported the ``scipy.sparse`` library in order to convert " -"our arrays." +"Note that we imported the ``scipy.sparse`` library in order to convert our " +"arrays." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:613 @@ -21193,30 +21510,28 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:615 msgid "" -"To be able to serialize our ``ndarray``\\ s into sparse parameters, we " -"will just have to call our custom functions in our " -"``flwr.client.Client``." +"To be able to serialize our ``ndarray``\\ s into sparse parameters, we will " +"just have to call our custom functions in our ``flwr.client.Client``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:617 msgid "" "Indeed, in ``get_parameters`` we need to serialize the parameters we got " -"from our network using our custom ``ndarrays_to_sparse_parameters`` " -"defined above." +"from our network using our custom ``ndarrays_to_sparse_parameters`` defined " +"above." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:619 msgid "" "In ``fit``, we first need to deserialize the parameters coming from the " -"server using our custom ``sparse_parameters_to_ndarrays`` and then we " -"need to serialize our local results with " -"``ndarrays_to_sparse_parameters``." +"server using our custom ``sparse_parameters_to_ndarrays`` and then we need " +"to serialize our local results with ``ndarrays_to_sparse_parameters``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:621 msgid "" -"In ``evaluate``, we will only need to deserialize the global parameters " -"with our custom function." +"In ``evaluate``, we will only need to deserialize the global parameters with " +"our custom function." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:725 @@ -21225,11 +21540,10 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:727 msgid "" -"For this example, we will just use ``FedAvg`` as a strategy. To change " -"the serialization and deserialization here, we only need to reimplement " -"the ``evaluate`` and ``aggregate_fit`` functions of ``FedAvg``. The other" -" functions of the strategy will be inherited from the super class " -"``FedAvg``." +"For this example, we will just use ``FedAvg`` as a strategy. To change the " +"serialization and deserialization here, we only need to reimplement the " +"``evaluate`` and ``aggregate_fit`` functions of ``FedAvg``. The other " +"functions of the strategy will be inherited from the super class ``FedAvg``." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:729 @@ -21254,19 +21568,19 @@ msgstr "" msgid "" "In this part of the tutorial, we've seen how we can build clients by " "subclassing either ``NumPyClient`` or ``Client``. ``NumPyClient`` is a " -"convenience abstraction that makes it easier to work with machine " -"learning libraries that have good NumPy interoperability. ``Client`` is a" -" more flexible abstraction that allows us to do things that are not " -"possible in ``NumPyClient``. In order to do so, it requires us to handle " -"parameter serialization and deserialization ourselves." +"convenience abstraction that makes it easier to work with machine learning " +"libraries that have good NumPy interoperability. ``Client`` is a more " +"flexible abstraction that allows us to do things that are not possible in " +"``NumPyClient``. In order to do so, it requires us to handle parameter " +"serialization and deserialization ourselves." msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:952 msgid "" -"This is the final part of the Flower tutorial (for now!), " -"congratulations! You're now well equipped to understand the rest of the " -"documentation. There are many topics we didn't cover in the tutorial, we " -"recommend the following resources:" +"This is the final part of the Flower tutorial (for now!), congratulations! " +"You're now well equipped to understand the rest of the documentation. There " +"are many topics we didn't cover in the tutorial, we recommend the following " +"resources:" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:954 @@ -21275,20 +21589,20 @@ msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:955 msgid "" -"`Check out Flower Code Examples " -"`__" +"`Check out Flower Code Examples `__" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:956 msgid "" -"`Use Flower Baselines for your research " -"`__" +"`Use Flower Baselines for your research `__" msgstr "" #: ../../source/tutorial-series-customize-the-client-pytorch.ipynb:957 msgid "" -"`Watch Flower Summit 2023 videos `__" +"`Watch Flower Summit 2023 videos `__" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:9 @@ -21303,10 +21617,9 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:13 msgid "" "In this notebook, we'll build a federated learning system using Flower, " -"`Flower Datasets `__ and PyTorch. In " -"part 1, we use PyTorch for the model training pipeline and data loading. " -"In part 2, we continue to federate the PyTorch-based pipeline using " -"Flower." +"`Flower Datasets `__ and PyTorch. In part " +"1, we use PyTorch for the model training pipeline and data loading. In part " +"2, we continue to federate the PyTorch-based pipeline using Flower." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:17 @@ -21323,20 +21636,19 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:45 msgid "" "Next, we install the necessary packages for PyTorch (``torch`` and " -"``torchvision``), Flower Datasets (``flwr-datasets``) and Flower " -"(``flwr``):" +"``torchvision``), Flower Datasets (``flwr-datasets``) and Flower (``flwr``):" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:105 msgid "" -"It is possible to switch to a runtime that has GPU acceleration enabled " -"(on Google Colab: ``Runtime > Change runtime type > Hardware accelerator:" -" GPU > Save``). Note, however, that Google Colab is not always able to " -"offer GPU acceleration. If you see an error related to GPU availability " -"in one of the following sections, consider switching back to CPU-based " -"execution by setting ``DEVICE = torch.device(\"cpu\")``. If the runtime " -"has GPU acceleration enabled, you should see the output ``Training on " -"cuda``, otherwise it'll say ``Training on cpu``." +"It is possible to switch to a runtime that has GPU acceleration enabled (on " +"Google Colab: ``Runtime > Change runtime type > Hardware accelerator: GPU > " +"Save``). Note, however, that Google Colab is not always able to offer GPU " +"acceleration. If you see an error related to GPU availability in one of the " +"following sections, consider switching back to CPU-based execution by " +"setting ``DEVICE = torch.device(\"cpu\")``. If the runtime has GPU " +"acceleration enabled, you should see the output ``Training on cuda``, " +"otherwise it'll say ``Training on cpu``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:118 @@ -21345,51 +21657,50 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:120 msgid "" -"Federated learning can be applied to many different types of tasks across" -" different domains. In this tutorial, we introduce federated learning by " -"training a simple convolutional neural network (CNN) on the popular " -"CIFAR-10 dataset. CIFAR-10 can be used to train image classifiers that " -"distinguish between images from ten different classes: 'airplane', " -"'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', and " -"'truck'." +"Federated learning can be applied to many different types of tasks across " +"different domains. In this tutorial, we introduce federated learning by " +"training a simple convolutional neural network (CNN) on the popular CIFAR-10 " +"dataset. CIFAR-10 can be used to train image classifiers that distinguish " +"between images from ten different classes: 'airplane', 'automobile', 'bird', " +"'cat', 'deer', 'dog', 'frog', 'horse', 'ship', and 'truck'." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:131 msgid "" "We simulate having multiple datasets from multiple organizations (also " -"called the \"cross-silo\" setting in federated learning) by splitting the" -" original CIFAR-10 dataset into multiple partitions. Each partition will " -"represent the data from a single organization. We're doing this purely " -"for experimentation purposes, in the real world there's no need for data " -"splitting because each organization already has their own data (so the " -"data is naturally partitioned)." +"called the \"cross-silo\" setting in federated learning) by splitting the " +"original CIFAR-10 dataset into multiple partitions. Each partition will " +"represent the data from a single organization. We're doing this purely for " +"experimentation purposes, in the real world there's no need for data " +"splitting because each organization already has their own data (so the data " +"is naturally partitioned)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:133 msgid "" -"Each organization will act as a client in the federated learning system. " -"So having ten organizations participate in a federation means having ten " +"Each organization will act as a client in the federated learning system. So " +"having ten organizations participate in a federation means having ten " "clients connected to the federated learning server." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:144 msgid "" "Let's now create the Federated Dataset abstraction that from ``flwr-" -"datasets`` that partitions the CIFAR-10. We will create small training " -"and test set for each edge device and wrap each of them into a PyTorch " +"datasets`` that partitions the CIFAR-10. We will create small training and " +"test set for each edge device and wrap each of them into a PyTorch " "``DataLoader``:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:198 msgid "" "We now have a list of ten training sets and ten validation sets " -"(``trainloaders`` and ``valloaders``) representing the data of ten " -"different organizations. Each ``trainloader``/``valloader`` pair contains" -" 4000 training examples and 1000 validation examples. There's also a " -"single ``testloader`` (we did not split the test set). Again, this is " -"only necessary for building research or educational systems, actual " -"federated learning systems have their data naturally distributed across " -"multiple partitions." +"(``trainloaders`` and ``valloaders``) representing the data of ten different " +"organizations. Each ``trainloader``/``valloader`` pair contains 4000 " +"training examples and 1000 validation examples. There's also a single " +"``testloader`` (we did not split the test set). Again, this is only " +"necessary for building research or educational systems, actual federated " +"learning systems have their data naturally distributed across multiple " +"partitions." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:201 @@ -21403,8 +21714,8 @@ msgid "" "The output above shows a random batch of images from the first " "``trainloader`` in our list of ten ``trainloaders``. It also prints the " "labels associated with each image (i.e., one of the ten possible labels " -"we've seen above). If you run the cell again, you should see another " -"batch of images." +"we've seen above). If you run the cell again, you should see another batch " +"of images." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:252 @@ -21417,8 +21728,8 @@ msgid "" "network. This introduction assumes basic familiarity with PyTorch, so it " "doesn't cover the PyTorch-related aspects in full detail. If you want to " "dive deeper into PyTorch, we recommend `DEEP LEARNING WITH PYTORCH: A 60 " -"MINUTE BLITZ " -"`__." +"MINUTE BLITZ `__." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:275 @@ -21427,9 +21738,9 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:277 msgid "" -"We use the simple CNN described in the `PyTorch tutorial " -"`__:" +"We use the simple CNN described in the `PyTorch tutorial `__:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:314 @@ -21443,20 +21754,19 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:376 msgid "" "We now have all the basic building blocks we need: a dataset, a model, a " -"training function, and a test function. Let's put them together to train " -"the model on the dataset of one of our organizations " -"(``trainloaders[0]``). This simulates the reality of most machine " -"learning projects today: each organization has their own data and trains " -"models only on this internal data:" +"training function, and a test function. Let's put them together to train the " +"model on the dataset of one of our organizations (``trainloaders[0]``). This " +"simulates the reality of most machine learning projects today: each " +"organization has their own data and trains models only on this internal data:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:406 msgid "" -"Training the simple CNN on our CIFAR-10 split for 5 epochs should result " -"in a test set accuracy of about 41%, which is not good, but at the same " -"time, it doesn't really matter for the purposes of this tutorial. The " -"intent was just to show a simplistic centralized training pipeline that " -"sets the stage for what comes next - federated learning!" +"Training the simple CNN on our CIFAR-10 split for 5 epochs should result in " +"a test set accuracy of about 41%, which is not good, but at the same time, " +"it doesn't really matter for the purposes of this tutorial. The intent was " +"just to show a simplistic centralized training pipeline that sets the stage " +"for what comes next - federated learning!" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:418 @@ -21465,11 +21775,11 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:420 msgid "" -"Step 1 demonstrated a simple centralized training pipeline. All data was " -"in one place (i.e., a single ``trainloader`` and a single ``valloader``)." -" Next, we'll simulate a situation where we have multiple datasets in " -"multiple organizations and where we train a model over these " -"organizations using federated learning." +"Step 1 demonstrated a simple centralized training pipeline. All data was in " +"one place (i.e., a single ``trainloader`` and a single ``valloader``). Next, " +"we'll simulate a situation where we have multiple datasets in multiple " +"organizations and where we train a model over these organizations using " +"federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:432 @@ -21478,30 +21788,29 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:434 msgid "" -"In federated learning, the server sends the global model parameters to " -"the client, and the client updates the local model with the parameters " -"received from the server. It then trains the model on the local data " -"(which changes the model parameters locally) and sends the " -"updated/changed model parameters back to the server (or, alternatively, " -"it sends just the gradients back to the server, not the full model " -"parameters)." +"In federated learning, the server sends the global model parameters to the " +"client, and the client updates the local model with the parameters received " +"from the server. It then trains the model on the local data (which changes " +"the model parameters locally) and sends the updated/changed model parameters " +"back to the server (or, alternatively, it sends just the gradients back to " +"the server, not the full model parameters)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:436 msgid "" "We need two helper functions to update the local model with parameters " -"received from the server and to get the updated model parameters from the" -" local model: ``set_parameters`` and ``get_parameters``. The following " -"two functions do just that for the PyTorch model above." +"received from the server and to get the updated model parameters from the " +"local model: ``set_parameters`` and ``get_parameters``. The following two " +"functions do just that for the PyTorch model above." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:438 msgid "" -"The details of how this works are not really important here (feel free to" -" consult the PyTorch documentation if you want to learn more). In " -"essence, we use ``state_dict`` to access PyTorch model parameter tensors." -" The parameter tensors are then converted to/from a list of NumPy " -"ndarray's (which Flower knows how to serialize/deserialize):" +"The details of how this works are not really important here (feel free to " +"consult the PyTorch documentation if you want to learn more). In essence, we " +"use ``state_dict`` to access PyTorch model parameter tensors. The parameter " +"tensors are then converted to/from a list of NumPy ndarray's (which Flower " +"knows how to serialize/deserialize):" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:466 @@ -21510,19 +21819,18 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:468 msgid "" -"With that out of the way, let's move on to the interesting part. " -"Federated learning systems consist of a server and multiple clients. In " -"Flower, we create clients by implementing subclasses of " -"``flwr.client.Client`` or ``flwr.client.NumPyClient``. We use " -"``NumPyClient`` in this tutorial because it is easier to implement and " -"requires us to write less boilerplate." +"With that out of the way, let's move on to the interesting part. Federated " +"learning systems consist of a server and multiple clients. In Flower, we " +"create clients by implementing subclasses of ``flwr.client.Client`` or " +"``flwr.client.NumPyClient``. We use ``NumPyClient`` in this tutorial because " +"it is easier to implement and requires us to write less boilerplate." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:470 msgid "" -"To implement the Flower client, we create a subclass of " -"``flwr.client.NumPyClient`` and implement the three methods " -"``get_parameters``, ``fit``, and ``evaluate``:" +"To implement the Flower client, we create a subclass of ``flwr.client." +"NumPyClient`` and implement the three methods ``get_parameters``, ``fit``, " +"and ``evaluate``:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:472 @@ -21532,15 +21840,14 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:473 msgid "" "``fit``: Receive model parameters from the server, train the model " -"parameters on the local data, and return the (updated) model parameters " -"to the server" +"parameters on the local data, and return the (updated) model parameters to " +"the server" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:474 msgid "" -"``evaluate``: Receive model parameters from the server, evaluate the " -"model parameters on the local data, and return the evaluation result to " -"the server" +"``evaluate``: Receive model parameters from the server, evaluate the model " +"parameters on the local data, and return the evaluation result to the server" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:476 @@ -21553,16 +21860,15 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:513 msgid "" "Our class ``FlowerClient`` defines how local training/evaluation will be " -"performed and allows Flower to call the local training/evaluation through" -" ``fit`` and ``evaluate``. Each instance of ``FlowerClient`` represents a" -" *single client* in our federated learning system. Federated learning " -"systems have multiple clients (otherwise, there's not much to federate), " -"so each client will be represented by its own instance of " -"``FlowerClient``. If we have, for example, three clients in our workload," -" then we'd have three instances of ``FlowerClient``. Flower calls " -"``FlowerClient.fit`` on the respective instance when the server selects a" -" particular client for training (and ``FlowerClient.evaluate`` for " -"evaluation)." +"performed and allows Flower to call the local training/evaluation through " +"``fit`` and ``evaluate``. Each instance of ``FlowerClient`` represents a " +"*single client* in our federated learning system. Federated learning systems " +"have multiple clients (otherwise, there's not much to federate), so each " +"client will be represented by its own instance of ``FlowerClient``. If we " +"have, for example, three clients in our workload, then we'd have three " +"instances of ``FlowerClient``. Flower calls ``FlowerClient.fit`` on the " +"respective instance when the server selects a particular client for training " +"(and ``FlowerClient.evaluate`` for evaluation)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:517 @@ -21571,13 +21877,13 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:519 msgid "" -"In this notebook, we want to simulate a federated learning system with 10" -" clients on a single machine. This means that the server and all 10 " -"clients will live on a single machine and share resources such as CPU, " -"GPU, and memory. Having 10 clients would mean having 10 instances of " -"``FlowerClient`` in memory. Doing this on a single machine can quickly " -"exhaust the available memory resources, even if only a subset of these " -"clients participates in a single round of federated learning." +"In this notebook, we want to simulate a federated learning system with 10 " +"clients on a single machine. This means that the server and all 10 clients " +"will live on a single machine and share resources such as CPU, GPU, and " +"memory. Having 10 clients would mean having 10 instances of ``FlowerClient`` " +"in memory. Doing this on a single machine can quickly exhaust the available " +"memory resources, even if only a subset of these clients participates in a " +"single round of federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:521 @@ -21586,14 +21892,14 @@ msgid "" "multiple machines, Flower, therefore, provides special simulation " "capabilities that create ``FlowerClient`` instances only when they are " "actually necessary for training or evaluation. To enable the Flower " -"framework to create clients when necessary, we need to implement a " -"function called ``client_fn`` that creates a ``FlowerClient`` instance on" -" demand. Flower calls ``client_fn`` whenever it needs an instance of one " -"particular client to call ``fit`` or ``evaluate`` (those instances are " -"usually discarded after use, so they should not keep any local state). " -"Clients are identified by a client ID, or short ``cid``. The ``cid`` can " -"be used, for example, to load different local data partitions for " -"different clients, as can be seen below:" +"framework to create clients when necessary, we need to implement a function " +"called ``client_fn`` that creates a ``FlowerClient`` instance on demand. " +"Flower calls ``client_fn`` whenever it needs an instance of one particular " +"client to call ``fit`` or ``evaluate`` (those instances are usually " +"discarded after use, so they should not keep any local state). Clients are " +"identified by a client ID, or short ``cid``. The ``cid`` can be used, for " +"example, to load different local data partitions for different clients, as " +"can be seen below:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:556 @@ -21602,31 +21908,31 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:558 msgid "" -"We now have the class ``FlowerClient`` which defines client-side " -"training/evaluation and ``client_fn`` which allows Flower to create " -"``FlowerClient`` instances whenever it needs to call ``fit`` or " -"``evaluate`` on one particular client. The last step is to start the " -"actual simulation using ``flwr.simulation.start_simulation``." +"We now have the class ``FlowerClient`` which defines client-side training/" +"evaluation and ``client_fn`` which allows Flower to create ``FlowerClient`` " +"instances whenever it needs to call ``fit`` or ``evaluate`` on one " +"particular client. The last step is to start the actual simulation using " +"``flwr.simulation.start_simulation``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:560 msgid "" "The function ``start_simulation`` accepts a number of arguments, amongst " -"them the ``client_fn`` used to create ``FlowerClient`` instances, the " -"number of clients to simulate (``num_clients``), the number of federated " -"learning rounds (``num_rounds``), and the strategy. The strategy " -"encapsulates the federated learning approach/algorithm, for example, " -"*Federated Averaging* (FedAvg)." +"them the ``client_fn`` used to create ``FlowerClient`` instances, the number " +"of clients to simulate (``num_clients``), the number of federated learning " +"rounds (``num_rounds``), and the strategy. The strategy encapsulates the " +"federated learning approach/algorithm, for example, *Federated Averaging* " +"(FedAvg)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:562 msgid "" "Flower has a number of built-in strategies, but we can also use our own " -"strategy implementations to customize nearly all aspects of the federated" -" learning approach. For this example, we use the built-in ``FedAvg`` " -"implementation and customize it using a few basic parameters. The last " -"step is the actual call to ``start_simulation`` which - you guessed it - " -"starts the simulation:" +"strategy implementations to customize nearly all aspects of the federated " +"learning approach. For this example, we use the built-in ``FedAvg`` " +"implementation and customize it using a few basic parameters. The last step " +"is the actual call to ``start_simulation`` which - you guessed it - starts " +"the simulation:" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:608 @@ -21640,20 +21946,20 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:612 #, python-format msgid "" -"When we call ``start_simulation``, we tell Flower that there are 10 " -"clients (``num_clients=10``). Flower then goes ahead an asks the " -"``FedAvg`` strategy to select clients. ``FedAvg`` knows that it should " -"select 100% of the available clients (``fraction_fit=1.0``), so it goes " -"ahead and selects 10 random clients (i.e., 100% of 10)." +"When we call ``start_simulation``, we tell Flower that there are 10 clients " +"(``num_clients=10``). Flower then goes ahead an asks the ``FedAvg`` strategy " +"to select clients. ``FedAvg`` knows that it should select 100% of the " +"available clients (``fraction_fit=1.0``), so it goes ahead and selects 10 " +"random clients (i.e., 100% of 10)." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:614 msgid "" -"Flower then asks the selected 10 clients to train the model. When the " -"server receives the model parameter updates from the clients, it hands " -"those updates over to the strategy (*FedAvg*) for aggregation. The " -"strategy aggregates those updates and returns the new global model, which" -" then gets used in the next round of federated learning." +"Flower then asks the selected 10 clients to train the model. When the server " +"receives the model parameter updates from the clients, it hands those " +"updates over to the strategy (*FedAvg*) for aggregation. The strategy " +"aggregates those updates and returns the new global model, which then gets " +"used in the next round of federated learning." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:626 @@ -21662,28 +21968,27 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:628 msgid "" -"You may have noticed that all metrics except for ``losses_distributed`` " -"are empty. Where did the ``{\"accuracy\": float(accuracy)}`` go?" +"You may have noticed that all metrics except for ``losses_distributed`` are " +"empty. Where did the ``{\"accuracy\": float(accuracy)}`` go?" msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:630 msgid "" -"Flower can automatically aggregate losses returned by individual clients," -" but it cannot do the same for metrics in the generic metrics dictionary " -"(the one with the ``accuracy`` key). Metrics dictionaries can contain " -"very different kinds of metrics and even key/value pairs that are not " -"metrics at all, so the framework does not (and can not) know how to " -"handle these automatically." +"Flower can automatically aggregate losses returned by individual clients, " +"but it cannot do the same for metrics in the generic metrics dictionary (the " +"one with the ``accuracy`` key). Metrics dictionaries can contain very " +"different kinds of metrics and even key/value pairs that are not metrics at " +"all, so the framework does not (and can not) know how to handle these " +"automatically." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:632 msgid "" -"As users, we need to tell the framework how to handle/aggregate these " -"custom metrics, and we do so by passing metric aggregation functions to " -"the strategy. The strategy will then call these functions whenever it " -"receives fit or evaluate metrics from clients. The two possible functions" -" are ``fit_metrics_aggregation_fn`` and " -"``evaluate_metrics_aggregation_fn``." +"As users, we need to tell the framework how to handle/aggregate these custom " +"metrics, and we do so by passing metric aggregation functions to the " +"strategy. The strategy will then call these functions whenever it receives " +"fit or evaluate metrics from clients. The two possible functions are " +"``fit_metrics_aggregation_fn`` and ``evaluate_metrics_aggregation_fn``." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:634 @@ -21701,17 +22006,17 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:697 msgid "" "We now have a full system that performs federated training and federated " -"evaluation. It uses the ``weighted_average`` function to aggregate custom" -" evaluation metrics and calculates a single ``accuracy`` metric across " -"all clients on the server side." +"evaluation. It uses the ``weighted_average`` function to aggregate custom " +"evaluation metrics and calculates a single ``accuracy`` metric across all " +"clients on the server side." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:699 msgid "" "The other two categories of metrics (``losses_centralized`` and " "``metrics_centralized``) are still empty because they only apply when " -"centralized evaluation is being used. Part two of the Flower tutorial " -"will cover centralized evaluation." +"centralized evaluation is being used. Part two of the Flower tutorial will " +"cover centralized evaluation." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:711 @@ -21721,28 +22026,28 @@ msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:713 msgid "" -"Congratulations, you just trained a convolutional neural network, " -"federated over 10 clients! With that, you understand the basics of " -"federated learning with Flower. The same approach you've seen can be used" -" with other machine learning frameworks (not just PyTorch) and tasks (not" -" just CIFAR-10 images classification), for example NLP with Hugging Face " -"Transformers or speech with SpeechBrain." +"Congratulations, you just trained a convolutional neural network, federated " +"over 10 clients! With that, you understand the basics of federated learning " +"with Flower. The same approach you've seen can be used with other machine " +"learning frameworks (not just PyTorch) and tasks (not just CIFAR-10 images " +"classification), for example NLP with Hugging Face Transformers or speech " +"with SpeechBrain." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:715 msgid "" -"In the next notebook, we're going to cover some more advanced concepts. " -"Want to customize your strategy? Initialize parameters on the server " -"side? Or evaluate the aggregated model on the server side? We'll cover " -"all this and more in the next tutorial." +"In the next notebook, we're going to cover some more advanced concepts. Want " +"to customize your strategy? Initialize parameters on the server side? Or " +"evaluate the aggregated model on the server side? We'll cover all this and " +"more in the next tutorial." msgstr "" #: ../../source/tutorial-series-get-started-with-flower-pytorch.ipynb:733 msgid "" -"The `Flower Federated Learning Tutorial - Part 2 " -"`__ goes into more depth about strategies and all " -"the advanced things you can build with them." +"The `Flower Federated Learning Tutorial - Part 2 `__ goes " +"into more depth about strategies and all the advanced things you can build " +"with them." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:9 @@ -21752,16 +22057,16 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:11 msgid "" "Welcome to the next part of the federated learning tutorial. In previous " -"parts of this tutorial, we introduced federated learning with PyTorch and" -" Flower (`part 1 `__)." +"parts of this tutorial, we introduced federated learning with PyTorch and " +"Flower (`part 1 `__)." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:13 msgid "" -"In this notebook, we'll begin to customize the federated learning system " -"we built in the introductory notebook (again, using `Flower " -"`__ and `PyTorch `__)." +"In this notebook, we'll begin to customize the federated learning system we " +"built in the introductory notebook (again, using `Flower `__ and `PyTorch `__)." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:17 @@ -21775,8 +22080,8 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:311 msgid "" "So far, everything should look familiar if you've worked through the " -"introductory notebook. With that, we're ready to introduce a number of " -"new features." +"introductory notebook. With that, we're ready to introduce a number of new " +"features." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:323 @@ -21785,16 +22090,16 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:325 msgid "" -"Flower, by default, initializes the global model by asking one random " -"client for the initial parameters. In many cases, we want more control " -"over parameter initialization though. Flower therefore allows you to " -"directly pass the initial parameters to the Strategy:" +"Flower, by default, initializes the global model by asking one random client " +"for the initial parameters. In many cases, we want more control over " +"parameter initialization though. Flower therefore allows you to directly " +"pass the initial parameters to the Strategy:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:370 msgid "" -"Passing ``initial_parameters`` to the ``FedAvg`` strategy prevents Flower" -" from asking one of the clients for the initial parameters. If we look " +"Passing ``initial_parameters`` to the ``FedAvg`` strategy prevents Flower " +"from asking one of the clients for the initial parameters. If we look " "closely, we can see that the logs do not show any calls to the " "``FlowerClient.get_parameters`` method." msgstr "" @@ -21805,17 +22110,17 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:384 msgid "" -"We've seen the function ``start_simulation`` before. It accepts a number " -"of arguments, amongst them the ``client_fn`` used to create " -"``FlowerClient`` instances, the number of clients to simulate " -"``num_clients``, the number of rounds ``num_rounds``, and the strategy." +"We've seen the function ``start_simulation`` before. It accepts a number of " +"arguments, amongst them the ``client_fn`` used to create ``FlowerClient`` " +"instances, the number of clients to simulate ``num_clients``, the number of " +"rounds ``num_rounds``, and the strategy." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:386 msgid "" "The strategy encapsulates the federated learning approach/algorithm, for " -"example, ``FedAvg`` or ``FedAdagrad``. Let's try to use a different " -"strategy this time:" +"example, ``FedAvg`` or ``FedAdagrad``. Let's try to use a different strategy " +"this time:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:424 @@ -21824,9 +22129,9 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:426 msgid "" -"Flower can evaluate the aggregated model on the server-side or on the " -"client-side. Client-side and server-side evaluation are similar in some " -"ways, but different in others." +"Flower can evaluate the aggregated model on the server-side or on the client-" +"side. Client-side and server-side evaluation are similar in some ways, but " +"different in others." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:428 @@ -21834,33 +22139,33 @@ msgid "" "**Centralized Evaluation** (or *server-side evaluation*) is conceptually " "simple: it works the same way that evaluation in centralized machine " "learning does. If there is a server-side dataset that can be used for " -"evaluation purposes, then that's great. We can evaluate the newly " -"aggregated model after each round of training without having to send the " -"model to clients. We're also fortunate in the sense that our entire " -"evaluation dataset is available at all times." +"evaluation purposes, then that's great. We can evaluate the newly aggregated " +"model after each round of training without having to send the model to " +"clients. We're also fortunate in the sense that our entire evaluation " +"dataset is available at all times." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:430 msgid "" -"**Federated Evaluation** (or *client-side evaluation*) is more complex, " -"but also more powerful: it doesn't require a centralized dataset and " -"allows us to evaluate models over a larger set of data, which often " -"yields more realistic evaluation results. In fact, many scenarios require" -" us to use **Federated Evaluation** if we want to get representative " -"evaluation results at all. But this power comes at a cost: once we start " -"to evaluate on the client side, we should be aware that our evaluation " -"dataset can change over consecutive rounds of learning if those clients " -"are not always available. Moreover, the dataset held by each client can " -"also change over consecutive rounds. This can lead to evaluation results " -"that are not stable, so even if we would not change the model, we'd see " -"our evaluation results fluctuate over consecutive rounds." +"**Federated Evaluation** (or *client-side evaluation*) is more complex, but " +"also more powerful: it doesn't require a centralized dataset and allows us " +"to evaluate models over a larger set of data, which often yields more " +"realistic evaluation results. In fact, many scenarios require us to use " +"**Federated Evaluation** if we want to get representative evaluation results " +"at all. But this power comes at a cost: once we start to evaluate on the " +"client side, we should be aware that our evaluation dataset can change over " +"consecutive rounds of learning if those clients are not always available. " +"Moreover, the dataset held by each client can also change over consecutive " +"rounds. This can lead to evaluation results that are not stable, so even if " +"we would not change the model, we'd see our evaluation results fluctuate " +"over consecutive rounds." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:433 msgid "" "We've seen how federated evaluation works on the client side (i.e., by " -"implementing the ``evaluate`` method in ``FlowerClient``). Now let's see " -"how we can evaluate aggregated model parameters on the server-side:" +"implementing the ``evaluate`` method in ``FlowerClient``). Now let's see how " +"we can evaluate aggregated model parameters on the server-side:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:490 @@ -21869,50 +22174,48 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:492 msgid "" -"In some situations, we want to configure client-side execution (training," -" evaluation) from the server-side. One example for that is the server " -"asking the clients to train for a certain number of local epochs. Flower " -"provides a way to send configuration values from the server to the " -"clients using a dictionary. Let's look at an example where the clients " -"receive values from the server through the ``config`` parameter in " -"``fit`` (``config`` is also available in ``evaluate``). The ``fit`` " -"method receives the configuration dictionary through the ``config`` " -"parameter and can then read values from this dictionary. In this example," -" it reads ``server_round`` and ``local_epochs`` and uses those values to " -"improve the logging and configure the number of local training epochs:" +"In some situations, we want to configure client-side execution (training, " +"evaluation) from the server-side. One example for that is the server asking " +"the clients to train for a certain number of local epochs. Flower provides a " +"way to send configuration values from the server to the clients using a " +"dictionary. Let's look at an example where the clients receive values from " +"the server through the ``config`` parameter in ``fit`` (``config`` is also " +"available in ``evaluate``). The ``fit`` method receives the configuration " +"dictionary through the ``config`` parameter and can then read values from " +"this dictionary. In this example, it reads ``server_round`` and " +"``local_epochs`` and uses those values to improve the logging and configure " +"the number of local training epochs:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:546 msgid "" -"So how can we send this config dictionary from server to clients? The " -"built-in Flower Strategies provide way to do this, and it works similarly" -" to the way server-side evaluation works. We provide a function to the " -"strategy, and the strategy calls this function for every round of " -"federated learning:" +"So how can we send this config dictionary from server to clients? The built-" +"in Flower Strategies provide way to do this, and it works similarly to the " +"way server-side evaluation works. We provide a function to the strategy, and " +"the strategy calls this function for every round of federated learning:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:576 msgid "" -"Next, we'll just pass this function to the FedAvg strategy before " -"starting the simulation:" +"Next, we'll just pass this function to the FedAvg strategy before starting " +"the simulation:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:613 msgid "" -"As we can see, the client logs now include the current round of federated" -" learning (which they read from the ``config`` dictionary). We can also " -"configure local training to run for one epoch during the first and second" -" round of federated learning, and then for two epochs during the third " -"round." +"As we can see, the client logs now include the current round of federated " +"learning (which they read from the ``config`` dictionary). We can also " +"configure local training to run for one epoch during the first and second " +"round of federated learning, and then for two epochs during the third round." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:615 msgid "" "Clients can also return arbitrary values to the server. To do so, they " -"return a dictionary from ``fit`` and/or ``evaluate``. We have seen and " -"used this concept throughout this notebook without mentioning it " -"explicitly: our ``FlowerClient`` returns a dictionary containing a custom" -" key/value pair as the third return value in ``evaluate``." +"return a dictionary from ``fit`` and/or ``evaluate``. We have seen and used " +"this concept throughout this notebook without mentioning it explicitly: our " +"``FlowerClient`` returns a dictionary containing a custom key/value pair as " +"the third return value in ``evaluate``." msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:627 @@ -21929,14 +22232,13 @@ msgstr "" #, python-format msgid "" "We now have 1000 partitions, each holding 45 training and 5 validation " -"examples. Given that the number of training examples on each client is " -"quite small, we should probably train the model a bit longer, so we " -"configure the clients to perform 3 local training epochs. We should also " -"adjust the fraction of clients selected for training during each round " -"(we don't want all 1000 clients participating in every round), so we " -"adjust ``fraction_fit`` to ``0.05``, which means that only 5% of " -"available clients (so 50 clients) will be selected for training each " -"round:" +"examples. Given that the number of training examples on each client is quite " +"small, we should probably train the model a bit longer, so we configure the " +"clients to perform 3 local training epochs. We should also adjust the " +"fraction of clients selected for training during each round (we don't want " +"all 1000 clients participating in every round), so we adjust " +"``fraction_fit`` to ``0.05``, which means that only 5% of available clients " +"(so 50 clients) will be selected for training each round:" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:699 @@ -21949,94 +22251,111 @@ msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:701 msgid "" -"In the later sections, we've seen how we can communicate arbitrary values" -" between server and clients to fully customize client-side execution. " -"With that capability, we built a large-scale Federated Learning " -"simulation using the Flower Virtual Client Engine and ran an experiment " -"involving 1000 clients in the same workload - all in a Jupyter Notebook!" +"In the later sections, we've seen how we can communicate arbitrary values " +"between server and clients to fully customize client-side execution. With " +"that capability, we built a large-scale Federated Learning simulation using " +"the Flower Virtual Client Engine and ran an experiment involving 1000 " +"clients in the same workload - all in a Jupyter Notebook!" msgstr "" #: ../../source/tutorial-series-use-a-federated-learning-strategy-pytorch.ipynb:719 msgid "" -"The `Flower Federated Learning Tutorial - Part 3 " -"`__ shows how to build a fully custom ``Strategy`` from " -"scratch." +"The `Flower Federated Learning Tutorial - Part 3 `__ shows how " +"to build a fully custom ``Strategy`` from scratch." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:9 msgid "What is Federated Learning?" -msgstr "" +msgstr "연합 학습이란 무엇입니까?" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:13 msgid "" "In this tutorial, you will learn what federated learning is, build your " "first system in Flower, and gradually extend it. If you work through all " -"parts of the tutorial, you will be able to build advanced federated " -"learning systems that approach the current state of the art in the field." +"parts of the tutorial, you will be able to build advanced federated learning " +"systems that approach the current state of the art in the field." msgstr "" +"이 튜토리얼에서 연합 학습이 무엇인지 배우고 Flower로 첫 번째 시스템을 " +"구축하고 점진적으로 확장해 나갈 것입니다. 본 튜토리얼의 모든 부분을 완성할 " +"수 있다면, 당신은 고급 연방 학습 시스템을 구축하여 그 분야의 현재 기술 " +"수준에 접근할 수 있을 것입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:15 msgid "" -"🧑‍🏫 This tutorial starts at zero and expects no familiarity with " -"federated learning. Only a basic understanding of data science and Python" -" programming is assumed." +"🧑‍🏫 This tutorial starts at zero and expects no familiarity with federated " +"learning. Only a basic understanding of data science and Python programming " +"is assumed." msgstr "" +"🧑‍🏫이 튜토리얼은 제로베이부터 시작되며 연방 학습에 상세히 아는 필요가 " +"없습니다. 데이터 과학과 파이썬 프로그래밍에 대한 기본적인 이해만 가정합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:17 msgid "" -"`Star Flower on GitHub `__ ⭐️ and join " -"the open-source Flower community on Slack to connect, ask questions, and " -"get help: `Join Slack `__ 🌼 We'd love to " -"hear from you in the ``#introductions`` channel! And if anything is " -"unclear, head over to the ``#questions`` channel." +"`Star Flower on GitHub `__ ⭐️ and join the " +"open-source Flower community on Slack to connect, ask questions, and get " +"help: `Join Slack `__ 🌼 We'd love to hear " +"from you in the ``#introductions`` channel! And if anything is unclear, head " +"over to the ``#questions`` channel." msgstr "" +"`Star Flower on GitHub `__ ⭐️ Slack의 " +"오픈소스 Flower 커뮤니티에 가입하여 소통하고 질문하고 도움을 받을 수 " +"있습니다: `Slack 가입`__ 🌼 ``#introductions``" +"채널에서 당신의 목소리를 듣고 싶습니다! 궁금한 점이 있으시면``#questions`` " +"채널로 방문해 주시기 바랍니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:31 msgid "Classic machine learning" -msgstr "" +msgstr "클래식 머신러닝" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:33 msgid "" -"Before we begin to discuss federated learning, let us quickly recap how " -"most machine learning works today." -msgstr "" +"Before we begin to discuss federated learning, let us quickly recap how most " +"machine learning works today." +msgstr "연방 학습에 대해 논의하기 전에 현재 대부분의 머신러닝이 어떻게 작동하는지 " +"간략히 요약하겠습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:35 msgid "" -"In machine learning, we have a model, and we have data. The model could " -"be a neural network (as depicted here), or something else, like classical" -" linear regression." +"In machine learning, we have a model, and we have data. The model could be a " +"neural network (as depicted here), or something else, like classical linear " +"regression." msgstr "" +"기계 학습에서 우리는 모델과 데이터를 가지고 있습니다.모델은 신경망((그림과 " +"같이))일 수도 있고 고전적인 선형 회귀와 같은 다른 것일 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 msgid "|93b02017c78049bbbd5ae456dcb2c91b|" -msgstr "" +msgstr "|93b02017c78049bbbd5ae456dcb2c91b|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 msgid "Model and data" -msgstr "" +msgstr "모델과 데이터" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:47 msgid "" -"We train the model using the data to perform a useful task. A task could " -"be to detect objects in images, transcribe an audio recording, or play a " -"game like Go." +"We train the model using the data to perform a useful task. A task could be " +"to detect objects in images, transcribe an audio recording, or play a game " +"like Go." msgstr "" +"우리는 유용한 작업을 수행하기 위해 데이터를 사용하여 모델을 훈련합니다. " +"작업은 이미지 속 물체를 감지하거나 음성 녹음을 기록하거나 바둑과 같은 게임을 " +"하는 것일 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 msgid "|01471150fd5144c080a176b43e92a3ff|" -msgstr "" +msgstr "|01471150fd5144c080a176b43e92a3ff|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 msgid "Train model using data" -msgstr "" +msgstr "데이터를 이용한 모델 훈련" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:59 msgid "" -"Now, in practice, the training data we work with doesn't originate on the" -" machine we train the model on. It gets created somewhere else." -msgstr "" +"Now, in practice, the training data we work with doesn't originate on the " +"machine we train the model on. It gets created somewhere else." +msgstr "실제로 우리가 사용하는 훈련 데이터는 모델을 훈련시키는 기계에서 비롯된 것이 " +"아닙니다. 그 데이터는 다른 곳에서 만들어졌습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:61 msgid "" @@ -22044,46 +22363,54 @@ msgid "" "collecting sensor data, a laptop receiving input via the keyboard, or a " "smart speaker listening to someone trying to sing a song." msgstr "" +"스마트폰에서 사용자와 앱의 상호 작용, 센서 데이터를 수집하는 자동차, " +"키보드를 통해 입력을 받는 노트북 또는 누군가 노래를 부르리는 것을 듣는 " +"스마트 스피커에서 비롯됩니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 msgid "|9bc21c7dbd17444a8f070c60786e3484|" -msgstr "" +msgstr "|9bc21c7dbd17444a8f070c60786e3484|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 msgid "Data on a phone" -msgstr "" +msgstr "핸드푼에 있는 데이터" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:73 msgid "" "What's also important to mention, this \"somewhere else\" is usually not " -"just one place, it's many places. It could be several devices all running" -" the same app. But it could also be several organizations, all generating" -" data for the same task." +"just one place, it's many places. It could be several devices all running " +"the same app. But it could also be several organizations, all generating " +"data for the same task." msgstr "" +"또한 중요한 것은 이 \"다른 곳\"이 보통 한 곳만 아니라 여러 곳이라는 " +"것입니다. 같은 앱을 실행하는 여러 기기일 수도 있습니다. 하지만 여러 조직이 " +"모두 같은 작업을 위해 데이터를 생성하는 것일 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 msgid "|3047bbce54b34099ae559963d0420d79|" -msgstr "" +msgstr "|3047bbce54b34099ae559963d0420d79|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 msgid "Data is on many devices" -msgstr "" +msgstr "데이터가 여러 장치에 있습니다" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:85 msgid "" -"So to use machine learning, or any kind of data analysis, the approach " -"that has been used in the past was to collect all data on a central " -"server. This server can be somewhere in a data center, or somewhere in " -"the cloud." +"So to use machine learning, or any kind of data analysis, the approach that " +"has been used in the past was to collect all data on a central server. This " +"server can be somewhere in a data center, or somewhere in the cloud." msgstr "" +"따라서 머신러닝이나 어떤 종류의 데이터 분석을 이용하려면 과거에는 중앙 " +"서버에서 모든 데이터를 수집하는 방법이 사용되었습니다.이 서버는 데이터 센터 " +"어딘가에 있을 수도 있고 클라우드 어딘가에 있을 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 msgid "|e9f8ce948593444fb838d2f354c7ec5d|" -msgstr "" +msgstr "|e9f8ce948593444fb838d2f354c7ec5d|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 msgid "Central data collection" -msgstr "" +msgstr "중앙 데이터 수집" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:97 msgid "" @@ -22091,56 +22418,68 @@ msgid "" "learning algorithms to train our model on the data. This is the machine " "learning approach that we've basically always relied on." msgstr "" +"모든 데이터가 한 곳에 모이면, 우리는 궁극적으로 머신러닝 알고리즘을 사용하여 " +"데이터에서 모델을 훈련시킬 수 있습니다.이것이 바로 우리가 기본적으로 " +"의지해왔던 머신러닝 방법입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 msgid "|c24c1478b30e4f74839208628a842d1e|" -msgstr "" +msgstr "|c24c1478b30e4f74839208628a842d1e|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 msgid "Central model training" -msgstr "" +msgstr "중앙 데이터 훈련" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:130 msgid "Challenges of classical machine learning" -msgstr "" +msgstr "클래식 머신러닝이 만난 도전" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:132 msgid "" -"The classic machine learning approach we've just seen can be used in some" -" cases. Great examples include categorizing holiday photos, or analyzing " -"web traffic. Cases, where all the data is naturally available on a " -"centralized server." +"The classic machine learning approach we've just seen can be used in some " +"cases. Great examples include categorizing holiday photos, or analyzing web " +"traffic. Cases, where all the data is naturally available on a centralized " +"server." msgstr "" +"우리가 방금 본 클래식 머신러닝 접근 방식은 경우에 따라 사용될 수 있습니다. " +"좋은 예로는 휴일 사진을 분류하거나 웹 트래픽을 분석하는 것이 있습니다. " +"이러한 사례에서 모든 데이터는 자연스럽게 중앙 서버에서 사용할 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 msgid "|1b3613d7a58847b59e1d3180802dbc09|" -msgstr "" +msgstr "|1b3613d7a58847b59e1d3180802dbc09|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 msgid "Centralized possible" -msgstr "" +msgstr "집중화 가능" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:144 msgid "" -"But the approach can not be used in many other cases. Cases, where the " -"data is not available on a centralized server, or cases where the data " -"available on one server is not enough to train a good model." +"But the approach can not be used in many other cases. Cases, where the data " +"is not available on a centralized server, or cases where the data available " +"on one server is not enough to train a good model." msgstr "" +"그러나 이 방법은 다른 많은 경우에 적용되지 않습니다.예를 들어, 중앙 집중식 " +"서버에 데이터가 없거나 서버의 데이터가 좋은 모델을 훈련하기에 충분하지 " +"않습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 msgid "|9980b5213db547d0b8024a50992b9e3f|" -msgstr "" +msgstr "|9980b5213db547d0b8024a50992b9e3f|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 msgid "Centralized impossible" -msgstr "" +msgstr "집중화 가능" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:156 msgid "" -"There are many reasons why the classic centralized machine learning " -"approach does not work for a large number of highly important real-world " -"use cases. Those reasons include:" +"There are many reasons why the classic centralized machine learning approach " +"does not work for a large number of highly important real-world use cases. " +"Those reasons include:" msgstr "" +"클래식 중앙 집중식 머신러닝 방법이 현실 세계에서 매우 중요한 수많은 사용 " +"사례를 충족시킬 수 없는 이유가 있습니다.이유는 다음과 같은 여러 가지가 " +"있습니다:" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:158 msgid "" @@ -22149,32 +22488,39 @@ msgid "" "(Russia), CDPR (China), PDPB (India), PIPA (Korea), APPI (Japan), PDP " "(Indonesia), PDPA (Singapore), APP (Australia), and other regulations " "protect sensitive data from being moved. In fact, those regulations " -"sometimes even prevent single organizations from combining their own " -"users' data for artificial intelligence training because those users live" -" in different parts of the world, and their data is governed by different" -" data protection regulations." -msgstr "" +"sometimes even prevent single organizations from combining their own users' " +"data for artificial intelligence training because those users live in " +"different parts of the world, and their data is governed by different data " +"protection regulations." +msgstr "" +"**규정**: GDPR (유럽), CCPA (캘리포니아), PIPEDA (캐나다), LGPD (브라질), " +"PDPL (아르헨티나), KVKK (터키), POPI (남아프리카공화국), FSS (러시아), CDPR " +"(중국), PDPB (인도), PIPA (한국), APPI (일본), PDP (인도네시아), PDPA " +"(싱가포르), APP (호주)등의 법규로 민감한 데이터가 이동하지 않도록 보호하고 " +"있습니 다. 실제 로이러한 규정은 사용자가 세계의 다른 지역에 살고 데이터가 " +"다른 데이터 보호 규정에 의해 통제되기 때문에 단일 조직이 자체 사용자 " +"데이터를 인공 지능 교육에 사용하는 것을 방지하기도 합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:160 msgid "" -"**User preference**: In addition to regulation, there are use cases where" -" users just expect that no data leaves their device, ever. If you type " -"your passwords and credit card info into the digital keyboard of your " -"phone, you don't expect those passwords to end up on the server of the " -"company that developed that keyboard, do you? In fact, that use case was " -"the reason federated learning was invented in the first place." +"**User preference**: In addition to regulation, there are use cases where " +"users just expect that no data leaves their device, ever. If you type your " +"passwords and credit card info into the digital keyboard of your phone, you " +"don't expect those passwords to end up on the server of the company that " +"developed that keyboard, do you? In fact, that use case was the reason " +"federated learning was invented in the first place." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:161 msgid "" -"**Data volume**: Some sensors, like cameras, produce such a high data " -"volume that it is neither feasible nor economic to collect all the data " -"(due to, for example, bandwidth or communication efficiency). Think about" -" a national rail service with hundreds of train stations across the " -"country. If each of these train stations is outfitted with a number of " -"security cameras, the volume of raw on-device data they produce requires " -"incredibly powerful and exceedingly expensive infrastructure to process " -"and store. And most of the data isn't even useful." +"**Data volume**: Some sensors, like cameras, produce such a high data volume " +"that it is neither feasible nor economic to collect all the data (due to, " +"for example, bandwidth or communication efficiency). Think about a national " +"rail service with hundreds of train stations across the country. If each of " +"these train stations is outfitted with a number of security cameras, the " +"volume of raw on-device data they produce requires incredibly powerful and " +"exceedingly expensive infrastructure to process and store. And most of the " +"data isn't even useful." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:164 @@ -22189,8 +22535,7 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:167 msgid "" -"Financial information from different organizations to detect financial " -"fraud" +"Financial information from different organizations to detect financial fraud" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:168 @@ -22203,13 +22548,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:171 msgid "" -"The popularity of privacy-enhancing systems like the `Brave " -"`__ browser or the `Signal `__ " -"messenger shows that users care about privacy. In fact, they choose the " -"privacy-enhancing version over other alternatives, if such an alternative" -" exists. But what can we do to apply machine learning and data science to" -" these cases to utilize private data? After all, these are all areas that" -" would benefit significantly from recent advances in AI." +"The popularity of privacy-enhancing systems like the `Brave `__ browser or the `Signal `__ messenger shows " +"that users care about privacy. In fact, they choose the privacy-enhancing " +"version over other alternatives, if such an alternative exists. But what can " +"we do to apply machine learning and data science to these cases to utilize " +"private data? After all, these are all areas that would benefit " +"significantly from recent advances in AI." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:186 @@ -22219,9 +22564,8 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:188 msgid "" "Federated learning simply reverses this approach. It enables machine " -"learning on distributed data by moving the training to the data, instead " -"of moving the data to the training. Here's the single-sentence " -"explanation:" +"learning on distributed data by moving the training to the data, instead of " +"moving the data to the training. Here's the single-sentence explanation:" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:190 @@ -22234,22 +22578,22 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:193 msgid "" -"By doing so, it enables us to use machine learning (and other data " -"science approaches) in areas where it wasn't possible before. We can now " -"train excellent medical AI models by enabling different hospitals to work" -" together. We can solve financial fraud by training AI models on the data" -" of different financial institutions. We can build novel privacy-" -"enhancing applications (such as secure messaging) that have better built-" -"in AI than their non-privacy-enhancing alternatives. And those are just a" -" few of the examples that come to mind. As we deploy federated learning, " -"we discover more and more areas that can suddenly be reinvented because " -"they now have access to vast amounts of previously inaccessible data." +"By doing so, it enables us to use machine learning (and other data science " +"approaches) in areas where it wasn't possible before. We can now train " +"excellent medical AI models by enabling different hospitals to work " +"together. We can solve financial fraud by training AI models on the data of " +"different financial institutions. We can build novel privacy-enhancing " +"applications (such as secure messaging) that have better built-in AI than " +"their non-privacy-enhancing alternatives. And those are just a few of the " +"examples that come to mind. As we deploy federated learning, we discover " +"more and more areas that can suddenly be reinvented because they now have " +"access to vast amounts of previously inaccessible data." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:196 msgid "" -"So how does federated learning work, exactly? Let's start with an " -"intuitive explanation." +"So how does federated learning work, exactly? Let's start with an intuitive " +"explanation." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:199 @@ -22262,9 +22606,9 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:204 msgid "" -"We start by initializing the model on the server. This is exactly the " -"same in classic centralized learning: we initialize the model parameters," -" either randomly or from a previously saved checkpoint." +"We start by initializing the model on the server. This is exactly the same " +"in classic centralized learning: we initialize the model parameters, either " +"randomly or from a previously saved checkpoint." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 @@ -22277,18 +22621,18 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:217 msgid "" -"Step 1: Send model to a number of connected organizations/devices (client" -" nodes)" +"Step 1: Send model to a number of connected organizations/devices (client " +"nodes)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:219 msgid "" "Next, we send the parameters of the global model to the connected client " "nodes (think: edge devices like smartphones or servers belonging to " -"organizations). This is to ensure that each participating node starts " -"their local training using the same model parameters. We often use only a" -" few of the connected nodes instead of all nodes. The reason for this is " -"that selecting more and more client nodes has diminishing returns." +"organizations). This is to ensure that each participating node starts their " +"local training using the same model parameters. We often use only a few of " +"the connected nodes instead of all nodes. The reason for this is that " +"selecting more and more client nodes has diminishing returns." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 @@ -22301,18 +22645,18 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:232 msgid "" -"Step 2: Train model locally on the data of each organization/device " -"(client node)" +"Step 2: Train model locally on the data of each organization/device (client " +"node)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:234 msgid "" -"Now that all (selected) client nodes have the latest version of the " -"global model parameters, they start the local training. They use their " -"own local dataset to train their own local model. They don't train the " -"model until full convergence, but they only train for a little while. " -"This could be as little as one epoch on the local data, or even just a " -"few steps (mini-batches)." +"Now that all (selected) client nodes have the latest version of the global " +"model parameters, they start the local training. They use their own local " +"dataset to train their own local model. They don't train the model until " +"full convergence, but they only train for a little while. This could be as " +"little as one epoch on the local data, or even just a few steps (mini-" +"batches)." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 @@ -22329,13 +22673,12 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:249 msgid "" -"After local training, each client node has a slightly different version " -"of the model parameters they originally received. The parameters are all " +"After local training, each client node has a slightly different version of " +"the model parameters they originally received. The parameters are all " "different because each client node has different examples in its local " -"dataset. The client nodes then send those model updates back to the " -"server. The model updates they send can either be the full model " -"parameters or just the gradients that were accumulated during local " -"training." +"dataset. The client nodes then send those model updates back to the server. " +"The model updates they send can either be the full model parameters or just " +"the gradients that were accumulated during local training." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 @@ -22353,27 +22696,26 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:264 msgid "" "The server receives model updates from the selected client nodes. If it " -"selected 100 client nodes, it now has 100 slightly different versions of " -"the original global model, each trained on the local data of one client. " -"But didn't we want to have one model that contains the learnings from the" -" data of all 100 client nodes?" +"selected 100 client nodes, it now has 100 slightly different versions of the " +"original global model, each trained on the local data of one client. But " +"didn't we want to have one model that contains the learnings from the data " +"of all 100 client nodes?" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:266 msgid "" -"In order to get one single model, we have to combine all the model " -"updates we received from the client nodes. This process is called " -"*aggregation*, and there are many different ways to do it. The most basic" -" way to do it is called *Federated Averaging* (`McMahan et al., 2016 " -"`__), often abbreviated as *FedAvg*. " -"*FedAvg* takes the 100 model updates and, as the name suggests, averages " -"them. To be more precise, it takes the *weighted average* of the model " -"updates, weighted by the number of examples each client used for " -"training. The weighting is important to make sure that each data example " -"has the same \"influence\" on the resulting global model. If one client " -"has 10 examples, and another client has 100 examples, then - without " -"weighting - each of the 10 examples would influence the global model ten " -"times as much as each of the 100 examples." +"In order to get one single model, we have to combine all the model updates " +"we received from the client nodes. This process is called *aggregation*, and " +"there are many different ways to do it. The most basic way to do it is " +"called *Federated Averaging* (`McMahan et al., 2016 `__), often abbreviated as *FedAvg*. *FedAvg* takes the 100 " +"model updates and, as the name suggests, averages them. To be more precise, " +"it takes the *weighted average* of the model updates, weighted by the number " +"of examples each client used for training. The weighting is important to " +"make sure that each data example has the same \"influence\" on the resulting " +"global model. If one client has 10 examples, and another client has 100 " +"examples, then - without weighting - each of the 10 examples would influence " +"the global model ten times as much as each of the 100 examples." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 @@ -22391,41 +22733,39 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:282 msgid "" "Steps 1 to 4 are what we call a single round of federated learning. The " -"global model parameters get sent to the participating client nodes (step " -"1), the client nodes train on their local data (step 2), they send their " -"updated models to the server (step 3), and the server then aggregates the" -" model updates to get a new version of the global model (step 4)." +"global model parameters get sent to the participating client nodes (step 1), " +"the client nodes train on their local data (step 2), they send their updated " +"models to the server (step 3), and the server then aggregates the model " +"updates to get a new version of the global model (step 4)." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:284 msgid "" -"During a single round, each client node that participates in that " -"iteration only trains for a little while. This means that after the " -"aggregation step (step 4), we have a model that has been trained on all " -"the data of all participating client nodes, but only for a little while. " -"We then have to repeat this training process over and over again to " -"eventually arrive at a fully trained model that performs well across the " -"data of all client nodes." +"During a single round, each client node that participates in that iteration " +"only trains for a little while. This means that after the aggregation step " +"(step 4), we have a model that has been trained on all the data of all " +"participating client nodes, but only for a little while. We then have to " +"repeat this training process over and over again to eventually arrive at a " +"fully trained model that performs well across the data of all client nodes." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:289 msgid "" "Congratulations, you now understand the basics of federated learning. " -"There's a lot more to discuss, of course, but that was federated learning" -" in a nutshell. In later parts of this tutorial, we will go into more " -"detail. Interesting questions include: How can we select the best client " -"nodes that should participate in the next round? What's the best way to " -"aggregate model updates? How can we handle failing client nodes " -"(stragglers)?" +"There's a lot more to discuss, of course, but that was federated learning in " +"a nutshell. In later parts of this tutorial, we will go into more detail. " +"Interesting questions include: How can we select the best client nodes that " +"should participate in the next round? What's the best way to aggregate model " +"updates? How can we handle failing client nodes (stragglers)?" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:294 msgid "" -"Just like we can train a model on the decentralized data of different " -"client nodes, we can also evaluate the model on that data to receive " -"valuable metrics. This is called federated evaluation, sometimes " -"abbreviated as FE. In fact, federated evaluation is an integral part of " -"most federated learning systems." +"Just like we can train a model on the decentralized data of different client " +"nodes, we can also evaluate the model on that data to receive valuable " +"metrics. This is called federated evaluation, sometimes abbreviated as FE. " +"In fact, federated evaluation is an integral part of most federated learning " +"systems." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:297 @@ -22434,25 +22774,24 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:299 msgid "" -"In many cases, machine learning isn't necessary to derive value from " -"data. Data analysis can yield valuable insights, but again, there's often" -" not enough data to get a clear answer. What's the average age at which " -"people develop a certain type of health condition? Federated analytics " -"enables such queries over multiple client nodes. It is usually used in " -"conjunction with other privacy-enhancing technologies like secure " -"aggregation to prevent the server from seeing the results submitted by " -"individual client nodes." +"In many cases, machine learning isn't necessary to derive value from data. " +"Data analysis can yield valuable insights, but again, there's often not " +"enough data to get a clear answer. What's the average age at which people " +"develop a certain type of health condition? Federated analytics enables such " +"queries over multiple client nodes. It is usually used in conjunction with " +"other privacy-enhancing technologies like secure aggregation to prevent the " +"server from seeing the results submitted by individual client nodes." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:305 msgid "" "Differential privacy (DP) is often mentioned in the context of Federated " -"Learning. It is a privacy-preserving method used when analyzing and " -"sharing statistical data, ensuring the privacy of individual " -"participants. DP achieves this by adding statistical noise to the model " -"updates, ensuring any individual participants’ information cannot be " -"distinguished or re-identified. This technique can be considered an " -"optimization that provides a quantifiable privacy protection measure." +"Learning. It is a privacy-preserving method used when analyzing and sharing " +"statistical data, ensuring the privacy of individual participants. DP " +"achieves this by adding statistical noise to the model updates, ensuring any " +"individual participants’ information cannot be distinguished or re-" +"identified. This technique can be considered an optimization that provides a " +"quantifiable privacy protection measure." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:326 @@ -22461,13 +22800,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:328 msgid "" -"Federated learning, federated evaluation, and federated analytics require" -" infrastructure to move machine learning models back and forth, train and" -" evaluate them on local data, and then aggregate the updated models. " -"Flower provides the infrastructure to do exactly that in an easy, " -"scalable, and secure way. In short, Flower presents a unified approach to" -" federated learning, analytics, and evaluation. It allows the user to " -"federate any workload, any ML framework, and any programming language." +"Federated learning, federated evaluation, and federated analytics require " +"infrastructure to move machine learning models back and forth, train and " +"evaluate them on local data, and then aggregate the updated models. Flower " +"provides the infrastructure to do exactly that in an easy, scalable, and " +"secure way. In short, Flower presents a unified approach to federated " +"learning, analytics, and evaluation. It allows the user to federate any " +"workload, any ML framework, and any programming language." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 @@ -22476,49 +22815,41 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 msgid "" -"Flower federated learning server and client nodes (car, scooter, personal" -" computer, roomba, and phone)" +"Flower federated learning server and client nodes (car, scooter, personal " +"computer, roomba, and phone)" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:353 msgid "" -"Congratulations, you just learned the basics of federated learning and " -"how it relates to the classic (centralized) machine learning!" +"Congratulations, you just learned the basics of federated learning and how " +"it relates to the classic (centralized) machine learning!" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:355 msgid "" -"In the next part of this tutorial, we are going to build a first " -"federated learning system with Flower." +"In the next part of this tutorial, we are going to build a first federated " +"learning system with Flower." msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:373 msgid "" -"The `Flower Federated Learning Tutorial - Part 1 " -"`__ shows how to build a simple federated learning system " -"with PyTorch and Flower." +"The `Flower Federated Learning Tutorial - Part 1 `__ shows how to " +"build a simple federated learning system with PyTorch and Flower." msgstr "" #~ msgid "" -#~ "Currently, Flower provides two images, a" -#~ " ``base`` image and a ``superlink`` " -#~ "image. The base image, as the name" -#~ " suggests, contains basic dependencies that" -#~ " the SuperLink needs. This includes " -#~ "system dependencies, Python and Python " -#~ "tools. The SuperLink image is based " -#~ "on the base image, but it " -#~ "additionally installs the SuperLink using " -#~ "``pip``." +#~ "Currently, Flower provides two images, a ``base`` image and a " +#~ "``superlink`` image. The base image, as the name suggests, contains basic " +#~ "dependencies that the SuperLink needs. This includes system dependencies, " +#~ "Python and Python tools. The SuperLink image is based on the base image, " +#~ "but it additionally installs the SuperLink using ``pip``." #~ msgstr "" -#~ "현재, Flower는 \"base\" 이미지 그리고 " -#~ "\"superlink\" 이미지를 제공합니다. base 이미지는 이름에서" -#~ " 알 수 있듯이 SuperLink가 필요로 하는 기본" -#~ " dependencies를 포함하고 있습니다. 여기에는 시스템 " -#~ "dependencies, Python 및 Python 도구가 포함됩니다." -#~ " SuperLink 이미지는 base 이미지를 기반으로 하지만" -#~ " \"pip\"을 사용하여 SuperLink를 추가로 설치합니다." +#~ "현재, Flower는 \"base\" 이미지 그리고 \"superlink\" 이미지를 제공합니다. " +#~ "base 이미지는 이름에서 알 수 있듯이 SuperLink가 필요로 하는 기본 " +#~ "dependencies를 포함하고 있습니다. 여기에는 시스템 dependencies, Python 및 " +#~ "Python 도구가 포함됩니다. SuperLink 이미지는 base 이미지를 기반으로 하지" +#~ "만 \"pip\"을 사용하여 SuperLink를 추가로 설치합니다." #~ msgid "``3.11``" #~ msgstr "``3.11``" @@ -22542,278 +22873,10 @@ msgstr "" #~ msgstr "``flwr``이 기본값." #~ msgid "" -#~ "The name of image is ``flwr_superlink``" -#~ " and the tag ``0.1.0``. Remember that" -#~ " the build arguments as well as " -#~ "the name and tag can be adapted" -#~ " to your needs. These values serve" -#~ " as examples only." -#~ msgstr "" -#~ "이미지의 이름은 ``flwr_superlink``이고 태그는 " -#~ "``0.1.0``입니다. 필요에 따라 빌드 argument들 뿐만 " -#~ "아니라 이름과 태그도 정할 수 있습니다. 이 값들은" -#~ " 예시일 뿐입니다." - -#~ msgid "Creating New Messages" -#~ msgstr "" - -#~ msgid "" -#~ "This is a simple guide for " -#~ "creating a new type of message " -#~ "between the server and clients in " -#~ "Flower." -#~ msgstr "" - -#~ msgid "" -#~ "Let's suppose we have the following " -#~ "example functions in :code:`server.py` and " -#~ ":code:`numpy_client.py`..." -#~ msgstr "" - -#~ msgid "Server's side:" -#~ msgstr "" - -#~ msgid "Client's side:" -#~ msgstr "" - -#~ msgid "" -#~ "Let's now see what we need to " -#~ "implement in order to get this " -#~ "simple function between the server and" -#~ " client to work!" -#~ msgstr "" - -#~ msgid "Message Types for Protocol Buffers" -#~ msgstr "" - -#~ msgid "" -#~ "The first thing we need to do " -#~ "is to define a message type for" -#~ " the RPC system in :code:`transport.proto`." -#~ " Note that we have to do it " -#~ "for both the request and response " -#~ "messages. For more details on the " -#~ "syntax of proto3, please see the " -#~ "`official documentation `_." -#~ msgstr "" - -#~ msgid "Within the :code:`ServerMessage` block:" -#~ msgstr "" - -#~ msgid "Within the ClientMessage block:" -#~ msgstr "" - -#~ msgid "" -#~ "Make sure to also add a field " -#~ "of the newly created message type " -#~ "in :code:`oneof msg`." -#~ msgstr "" - -#~ msgid "Once that is done, we will compile the file with:" -#~ msgstr "" - -#~ msgid "If it compiles successfully, you should see the following message:" -#~ msgstr "" - -#~ msgid "Serialization and Deserialization Functions" -#~ msgstr "" - -#~ msgid "" -#~ "Our next step is to add functions" -#~ " to serialize and deserialize Python " -#~ "datatypes to or from our defined " -#~ "RPC message types. You should add " -#~ "these functions in :code:`serde.py`." -#~ msgstr "" - -#~ msgid "The four functions:" -#~ msgstr "" - -#~ msgid "Sending the Message from the Server" -#~ msgstr "" - -#~ msgid "" -#~ "Now write the request function in " -#~ "your Client Proxy class (e.g., " -#~ ":code:`grpc_client_proxy.py`) using the serde " -#~ "functions you just created:" -#~ msgstr "" - -#~ msgid "Receiving the Message by the Client" -#~ msgstr "" - -#~ msgid "" -#~ "Last step! Modify the code in " -#~ ":code:`message_handler.py` to check the field" -#~ " of your message and call the " -#~ ":code:`example_response` function. Remember to " -#~ "use the serde functions!" -#~ msgstr "" - -#~ msgid "Within the handle function:" -#~ msgstr "" - -#~ msgid "And add a new function:" -#~ msgstr "" - -#~ msgid "Hopefully, when you run your program you will get the intended result!" -#~ msgstr "" - -#~ msgid "" -#~ "The simplest way to get started " -#~ "with Flower is by using the " -#~ "pre-made Docker images, which you can" -#~ " find on `Docker Hub " -#~ "`__." -#~ msgstr "" - -#~ msgid "" -#~ "If you want to persist the state" -#~ " of the SuperLink on your host " -#~ "system, all you need to do is " -#~ "specify a path where you want to" -#~ " save the file on your host " -#~ "system and a name for the database" -#~ " file. In the example below, we " -#~ "tell Docker via the flag ``--volume``" -#~ " to mount the user's home directory" -#~ " (``~/`` on your host) into the " -#~ "``/app/`` directory of the container. " -#~ "Furthermore, we use the flag " -#~ "``--database`` to specify the name of" -#~ " the database file." -#~ msgstr "" - -#~ msgid "" -#~ "As soon as the SuperLink starts, " -#~ "the file ``state.db`` is created in " -#~ "the user's home directory on your " -#~ "host system. If the file already " -#~ "exists, the SuperLink tries to restore" -#~ " the state from the file. To " -#~ "start the SuperLink with an empty " -#~ "database, simply remove the ``state.db`` " -#~ "file." -#~ msgstr "" - -#~ msgid "" -#~ "Assuming all files we need are in" -#~ " the local ``certificates`` directory, we" -#~ " can use the flag ``--volume`` to " -#~ "mount the local directory into the " -#~ "``/app/`` directory of the container. " -#~ "This allows the SuperLink to access " -#~ "the files within the container. Finally," -#~ " we pass the names of the " -#~ "certificates to the SuperLink with the" -#~ " ``--certificates`` flag." -#~ msgstr "" - -#~ msgid "" -#~ "``--server 192.168.1.100:9092``: This option " -#~ "specifies the address of the SuperLinks" -#~ " Fleet" -#~ msgstr "" - -#~ msgid "" -#~ "Assuming the certificate already exists " -#~ "locally, we can use the flag " -#~ "``--volume`` to mount the local " -#~ "certificate into the container's ``/app/`` " -#~ "directory. This allows the SuperNode to" -#~ " access the certificate within the " -#~ "container. Use the ``--certificates`` flag " -#~ "when starting the container." -#~ msgstr "" - -#~ msgid "" -#~ "``--server 192.168.1.100:9091``: This option " -#~ "specifies the address of the SuperLinks" -#~ " Driver" -#~ msgstr "" - -#~ msgid "" -#~ "Assuming the certificate already exists " -#~ "locally, we can use the flag " -#~ "``--volume`` to mount the local " -#~ "certificate into the container's ``/app/`` " -#~ "directory. This allows the ServerApp to" -#~ " access the certificate within the " -#~ "container. Use the ``--certificates`` flag " -#~ "when starting the container." -#~ msgstr "" - -#~ msgid "" -#~ "If you want to use a different " -#~ "version of Flower, for example Flower" -#~ " nightly, you can do so by " -#~ "changing the tag. All available versions" -#~ " are on `Docker Hub " -#~ "`__." -#~ msgstr "" - -#~ msgid "" -#~ "Here's another example to start with " -#~ "HTTPS. Use the ``--certificates`` command " -#~ "line argument to pass paths to (CA" -#~ " certificate, server certificate, and " -#~ "server private key)." -#~ msgstr "" - -#~ msgid ":py:obj:`run_driver_api `\\ \\(\\)" -#~ msgstr "" - -#~ msgid "Run Flower server (Driver API)." -#~ msgstr "" - -#~ msgid ":py:obj:`run_fleet_api `\\ \\(\\)" -#~ msgstr "" - -#~ msgid "Run Flower server (Fleet API)." -#~ msgstr "" - -#~ msgid "Unreleased" -#~ msgstr "" - -#~ msgid "|d8bf04f23d9b46d8a23cc6f4887d7873|" -#~ msgstr "" - -#~ msgid "|5aa1711387d74d0f8b9c499e1a51627e|" -#~ msgstr "" - -#~ msgid "|2bc8e069228d4873804061ff4a95048c|" -#~ msgstr "" - -#~ msgid "|c258488766324dc9a6807f0e7c4fd5f4|" -#~ msgstr "" - -#~ msgid "|d5f962c3f4ec48529efda980868c14b0|" -#~ msgstr "" - -#~ msgid "|a5eccea18d4c43a68b54b65043cabef8|" -#~ msgstr "" - -#~ msgid "|f17662f7df2d42f68cac70a1fdeda8a7|" -#~ msgstr "" - -#~ msgid "|241fc906441a4f038c625a19d30d01b2|" -#~ msgstr "" - -#~ msgid "|0aa5aa05810b44b6a835cecce28f3137|" -#~ msgstr "" - -#~ msgid "|c742940dd4bf4de09d8d0d5e8d179638|" -#~ msgstr "" - -#~ msgid "|1f169ab4601a47e1a226f1628f4ebddb|" -#~ msgstr "" - -#~ msgid "|12cfa9cde14440ecb8c8f6c1d7185bec|" -#~ msgstr "" - -#~ msgid "|72939caf6e294b0986fee6dde96614d7|" -#~ msgstr "" - -#~ msgid "|83a8daee45da4a98b8d6f24ae098fc50|" +#~ "The name of image is ``flwr_superlink`` and the tag ``0.1.0``. Remember " +#~ "that the build arguments as well as the name and tag can be adapted to " +#~ "your needs. These values serve as examples only." #~ msgstr "" +#~ "이미지의 이름은 ``flwr_superlink``이고 태그는 ``0.1.0``입니다. 필요에 따" +#~ "라 빌드 argument들 뿐만 아니라 이름과 태그도 정할 수 있습니다. 이 값들은 " +#~ "예시일 뿐입니다." From 68d46ca2a96361fae1dea226bab333b239dfb523 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:08:42 +0200 Subject: [PATCH 081/595] docs(datasets) Update Flower Datasets docs (#3585) Co-authored-by: jafermarq --- datasets/README.md | 32 +++++++---- .../comparison_of_partitioning_schemes.png | Bin 0 -> 36019 bytes datasets/doc/source/index.rst | 51 ++++++++++-------- datasets/flwr_datasets/federated_dataset.py | 41 ++++++++++---- 4 files changed, 82 insertions(+), 42 deletions(-) create mode 100644 datasets/doc/source/_static/readme/comparison_of_partitioning_schemes.png diff --git a/datasets/README.md b/datasets/README.md index cf5caac3e1cd..883fb69d607e 100644 --- a/datasets/README.md +++ b/datasets/README.md @@ -7,6 +7,9 @@ [![Slack](https://img.shields.io/badge/Chat-Slack-red)](https://flower.ai/join-slack) Flower Datasets (`flwr-datasets`) is a library to quickly and easily create datasets for federated learning, federated evaluation, and federated analytics. It was created by the `Flower Labs` team that also created Flower: A Friendly Federated Learning Framework. +For complete documentation that includes API docs, how-to guides and tutorials please visit https://flower.ai/docs/datasets/ and for full FL example see https://github.com/adap/flower/tree/main/examples. +Below you'll find a brief overview of the library. + Flower Datasets library supports: * **downloading datasets** - choose the dataset from Hugging Face's `datasets`, * **partitioning datasets** - customize the partitioning scheme, @@ -21,15 +24,24 @@ Thanks to using Hugging Face's `datasets` used under the hood, Flower Datasets i * Jax, * Arrow. -Create **custom partitioning schemes** or choose from the **implemented partitioning schemes**: +Create **custom partitioning schemes** or choose from the **implemented [partitioning schemes](https://flower.ai/docs/datasets/ref-api/flwr_datasets.partitioner.html#module-flwr_datasets.partitioner)**: + * Partitioner (the abstract base class) `Partitioner` * IID partitioning `IidPartitioner(num_partitions)` -* Natural ID partitioner `NaturalIdPartitioner` +* Dirichlet partitioning `DirichletPartitioner(num_partitions, partition_by, alpha)` +* InnerDirichlet partitioning `InnerDirichletPartitioner(partition_sizes, partition_by, alpha)` +* Natural ID partitioner `NaturalIdPartitioner(partition_by)` * Size partitioner (the abstract base class for the partitioners dictating the division based the number of samples) `SizePartitioner` -* Linear partitioner `LinearPartitioner` -* Square partitioner `SquarePartitioner` -* Exponential partitioner `ExponentialPartitioner` -* more to come in future releases. +* Linear partitioner `LinearPartitioner(num_partitions)` +* Square partitioner `SquarePartitioner(num_partitions)` +* Exponential partitioner `ExponentialPartitioner(num_partitions)` +* more to come in the future releases (contributions are welcome). +

    + Comparison of partitioning schemes. +
    + Comparison of Partitioning Schemes on CIFAR10 +

    +PS: This plot was generated using a library function (see [flwr_datasets.visualization](https://flower.ai/docs/datasets/ref-api/flwr_datasets.visualization.html) package for more). # Installation @@ -67,11 +79,11 @@ Here's a basic quickstart example of how to partition the MNIST dataset: from flwr_datasets import FederatedDataset # The train split of the MNIST dataset will be partitioned into 100 partitions -mnist_fds = FederatedDataset("mnist", partitioners={"train": 100} +fds = FederatedDataset("mnist", partitioners={"train": 100}) -mnist_partition_0 = mnist_fds.load_partition(0, "train") +partition = fds.load_partition(0) -centralized_data = mnist_fds.load_split("test") +centralized_data = fds.load_split("test") ``` For more details, please refer to the specific how-to guides or tutorial. They showcase customization and more advanced features. @@ -85,6 +97,6 @@ Here are a few of the things that we will work on in future releases: * ✅ More out-of-the-box `Partitioner`s. * ✅ Passing `Partitioner`s via `FederatedDataset`'s `partitioners` argument. * ✅ Customization of the dataset splitting before the partitioning. -* Simplification of the dataset transformation to the popular frameworks/types. +* ✅ Simplification of the dataset transformation to the popular frameworks/types. * Creation of the synthetic data, * Support for Vertical FL. diff --git a/datasets/doc/source/_static/readme/comparison_of_partitioning_schemes.png b/datasets/doc/source/_static/readme/comparison_of_partitioning_schemes.png new file mode 100644 index 0000000000000000000000000000000000000000..ed2e323ef649ae1f4c72b2390b71a817ae16ce28 GIT binary patch literal 36019 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 EKdp(oGynhq literal 0 HcmV?d00001 diff --git a/datasets/doc/source/index.rst b/datasets/doc/source/index.rst index df248969a849..263aa4908d6d 100644 --- a/datasets/doc/source/index.rst +++ b/datasets/doc/source/index.rst @@ -7,6 +7,15 @@ learning/analytics/evaluation. It is created by the ``Flower Labs`` team that al Flower Datasets Framework ------------------------- +Install +~~~~~~~ + +.. code-block:: bash + + python -m pip install "flwr-datasets[vision]" + +Check out all the details on how to install Flower Datasets in :doc:`how-to-install-flwr-datasets`. + Tutorials ~~~~~~~~~ @@ -57,9 +66,16 @@ Main features ------------- Flower Datasets library supports: -- **downloading datasets** - choose the dataset from Hugging Face's ``dataset`` -- **partitioning datasets** - customize the partitioning scheme +- **downloading datasets** - choose the dataset from Hugging Face's ``dataset`` (`link `_) +- **partitioning datasets** - choose one of the implemented partitioning scheme or create your own. - **creating centralized datasets** - leave parts of the dataset unpartitioned (e.g. for centralized evaluation) +- **visualization of the partitioned datasets** - visualize the label distribution of the partitioned dataset (and compare the results on different parameters of the same partitioning schemes, different datasets, different partitioning schemes, or any mix of them) + + +.. image:: ./_static/readme/comparison_of_partitioning_schemes.png + :align: center + :alt: Comparison of Partitioning Schemes on CIFAR10 + Thanks to using Hugging Face's ``datasets`` used under the hood, Flower Datasets integrates with the following popular formats/frameworks: @@ -71,28 +87,19 @@ Thanks to using Hugging Face's ``datasets`` used under the hood, Flower Datasets - Jax - Arrow -Install -------- - -The simplest install is - -.. code-block:: bash - - python -m pip install flwr-datasets - -If you plan to use the image datasets - -.. code-block:: bash - - python -m pip install flwr-datasets[vision] - -If you plan to use the audio datasets - -.. code-block:: bash +Here are a few of the ``Partitioner`` s that are available: (for a full list see `link `_ ) - python -m pip install flwr-datasets[audio] +* Partitioner (the abstract base class) ``Partitioner`` +* IID partitioning ``IidPartitioner(num_partitions)`` +* Dirichlet partitioning ``DirichletPartitioner(num_partitions, partition_by, alpha)`` +* InnerDirichlet partitioning ``InnerDirichletPartitioner(partition_sizes, partition_by, alpha)`` +* Natural ID partitioner ``NaturalIdPartitioner(partition_by)`` +* Size partitioner (the abstract base class for the partitioners dictating the division based the number of samples) ``SizePartitioner`` +* Linear partitioner ``LinearPartitioner(num_partitions)`` +* Square partitioner ``SquarePartitioner(num_partitions)`` +* Exponential partitioner ``ExponentialPartitioner(num_partitions)`` +* more to come in the future releases (contributions are welcome). -Check out the full details on the download in :doc:`how-to-install-flwr-datasets`. How To Use the library ---------------------- diff --git a/datasets/flwr_datasets/federated_dataset.py b/datasets/flwr_datasets/federated_dataset.py index bbc6e99b651e..accfb783f368 100644 --- a/datasets/flwr_datasets/federated_dataset.py +++ b/datasets/flwr_datasets/federated_dataset.py @@ -36,8 +36,9 @@ class FederatedDataset: Download, partition data among clients (edge devices), or load full dataset. - Partitions are created using IidPartitioner. Support for different partitioners - specification and types will come in future releases. + Partitions are created per-split-basis using Partitioners from + `flwr_datasets.partitioner` specified in `partitioners` (see `partitioners` + parameter for more information). Parameters ---------- @@ -57,23 +58,43 @@ class FederatedDataset: into). One or multiple `Partitioner` objects can be specified in that manner, but at most, one per split. shuffle : bool - Whether to randomize the order of samples. Applied prior to resplitting, - speratelly to each of the present splits in the dataset. It uses the `seed` - argument. Defaults to True. + Whether to randomize the order of samples. Applied prior to preprocessing + operations, speratelly to each of the present splits in the dataset. It uses + the `seed` argument. Defaults to True. seed : Optional[int] Seed used for dataset shuffling. It has no effect if `shuffle` is False. The - seed cannot be set in the later stages. If `None`, then fresh, unpredictable entropy - will be pulled from the OS. Defaults to 42. + seed cannot be set in the later stages. If `None`, then fresh, unpredictable + entropy will be pulled from the OS. Defaults to 42. Examples -------- Use MNIST dataset for Federated Learning with 100 clients (edge devices): - >>> mnist_fds = FederatedDataset(dataset="mnist", partitioners={"train": 100}) + >>> from flwr_datasets import FederatedDataset + >>> + >>> fds = FederatedDataset(dataset="mnist", partitioners={"train": 100}) >>> # Load partition for client with ID 10. - >>> partition = mnist_fds.load_partition(10, "train") + >>> partition = fds.load_partition(10) >>> # Use test split for centralized evaluation. - >>> centralized = mnist_fds.load_split("test") + >>> centralized = fds.load_split("test") + + Use CIFAR10 dataset for Federated Laerning with 100 clients: + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import DirichletPartitioner + >>> + >>> partitioner = DirichletPartitioner(num_partitions=10, partition_by="label", + >>> alpha=0.5, min_partition_size=10) + >>> fds = FederatedDataset(dataset="cifar10", partitioners={"train": partitioner}) + >>> partition = fds.load_partition(partition_id=0) + + Visualize the partitioned datasets + >>> from flwr_datasets.visualization import plot_label_distributions + >>> + >>> _ = plot_label_distributions( + >>> partitioner=fds.partitioners["train"], + >>> label_name="label", + >>> legend=True, + >>> ) """ # pylint: disable=too-many-instance-attributes From 7fa6fb7b32b82199cf7f5e4b0ade769f53876785 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:56:31 +0200 Subject: [PATCH 082/595] feat(datasets) Add notebooks formatting (#3673) --- datasets/dev/format.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/datasets/dev/format.sh b/datasets/dev/format.sh index 8292e0b3ed79..c6977982dd6c 100755 --- a/datasets/dev/format.sh +++ b/datasets/dev/format.sh @@ -3,9 +3,16 @@ set -e cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"/../ # Python -echo "Formatting started" +echo "Formatting started: Python" python -m isort flwr_datasets/ python -m black -q flwr_datasets/ python -m docformatter -i -r flwr_datasets/ python -m ruff check --fix flwr_datasets/ -echo "Formatting done" +echo "Formatting done: Python" + +# Notebooks +echo "Formatting started: Notebooks" +python -m black --ipynb -q doc/source/*.ipynb +KEYS="metadata.celltoolbar metadata.language_info metadata.toc metadata.notify_time metadata.varInspector metadata.accelerator metadata.vscode cell.metadata.id cell.metadata.heading_collapsed cell.metadata.hidden cell.metadata.code_folding cell.metadata.tags cell.metadata.init_cell cell.metadata.vscode cell.metadata.pycharm" +python -m nbstripout --keep-output doc/source/*.ipynb --extra-keys "$KEYS" +echo "Formatting done: Notebooks" From 90d7aba648e055685d0e034154f79fbfa83d44ad Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 24 Jun 2024 12:56:27 +0200 Subject: [PATCH 083/595] docs(datasets) Update readme proposal (#3677) Co-authored-by: jafermarq --- datasets/README.md | 46 +++++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 29 deletions(-) diff --git a/datasets/README.md b/datasets/README.md index 883fb69d607e..1d8014d57ea3 100644 --- a/datasets/README.md +++ b/datasets/README.md @@ -7,8 +7,20 @@ [![Slack](https://img.shields.io/badge/Chat-Slack-red)](https://flower.ai/join-slack) Flower Datasets (`flwr-datasets`) is a library to quickly and easily create datasets for federated learning, federated evaluation, and federated analytics. It was created by the `Flower Labs` team that also created Flower: A Friendly Federated Learning Framework. -For complete documentation that includes API docs, how-to guides and tutorials please visit https://flower.ai/docs/datasets/ and for full FL example see https://github.com/adap/flower/tree/main/examples. -Below you'll find a brief overview of the library. + + +> [!TIP] +> For complete documentation that includes API docs, how-to guides and tutorials please visit the [Flower Datasets Documentation](https://flower.ai/docs/datasets/) and for full FL example see the [Flower Examples page](https://github.com/adap/flower/tree/main/examples). + +## Installation + +For a complete installation guide visit the [Flower Datasets Documenation](https://flower.ai/docs/datasets/) + +```bash +pip install flwr-datasets[vision] +``` + +## Overview Flower Datasets library supports: * **downloading datasets** - choose the dataset from Hugging Face's `datasets`, @@ -41,35 +53,11 @@ Create **custom partitioning schemes** or choose from the **implemented [partiti
    Comparison of Partitioning Schemes on CIFAR10

    -PS: This plot was generated using a library function (see [flwr_datasets.visualization](https://flower.ai/docs/datasets/ref-api/flwr_datasets.visualization.html) package for more). - -# Installation - -## With pip - -Flower Datasets can be installed from PyPi -```bash -pip install flwr-datasets -``` - -Install with an extension: - -* for image datasets: - -```bash -pip install flwr-datasets[vision] -``` - -* for audio datasets: - -```bash -pip install flwr-datasets[audio] -``` +PS: This plot was generated using a library function (see [flwr_datasets.visualization](https://flower.ai/docs/datasets/ref-api/flwr_datasets.visualization.html) package for more). -If you plan to change the type of the dataset to run the code with your ML framework, make sure to have it installed too. -# Usage +## Usage Flower Datasets exposes the `FederatedDataset` abstraction to represent the dataset needed for federated learning/evaluation/analytics. It has two powerful methods that let you handle the dataset preprocessing: `load_partition(partition_id, split)` and `load_split(split)`. @@ -88,7 +76,7 @@ centralized_data = fds.load_split("test") For more details, please refer to the specific how-to guides or tutorial. They showcase customization and more advanced features. -# Future release +## Future release Here are a few of the things that we will work on in future releases: From d42cc2e84a5835ccac33911fc12b973857c97089 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 24 Jun 2024 13:19:52 +0200 Subject: [PATCH 084/595] docs(datasets) Update Flower Datasets version to 0.2.0 (#3678) --- datasets/doc/source/conf.py | 2 +- datasets/doc/source/how-to-install-flwr-datasets.rst | 2 +- datasets/pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/datasets/doc/source/conf.py b/datasets/doc/source/conf.py index 755147bc9e1d..11285c375f96 100644 --- a/datasets/doc/source/conf.py +++ b/datasets/doc/source/conf.py @@ -38,7 +38,7 @@ author = "The Flower Authors" # The full version, including alpha/beta/rc tags -release = "0.1.0" +release = "0.2.0" # -- General configuration --------------------------------------------------- diff --git a/datasets/doc/source/how-to-install-flwr-datasets.rst b/datasets/doc/source/how-to-install-flwr-datasets.rst index d2fd7923a817..c690ffb1b09c 100644 --- a/datasets/doc/source/how-to-install-flwr-datasets.rst +++ b/datasets/doc/source/how-to-install-flwr-datasets.rst @@ -42,5 +42,5 @@ If everything worked, it should print the version of Flower Datasets to the comm .. code-block:: none - 0.0.1 + 0.2.0 diff --git a/datasets/pyproject.toml b/datasets/pyproject.toml index 56cf3038f4ef..017374181f59 100644 --- a/datasets/pyproject.toml +++ b/datasets/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "flwr-datasets" -version = "0.1.0" +version = "0.2.0" description = "Flower Datasets" license = "Apache-2.0" authors = ["The Flower Authors "] From febaccc20d276f505cb4fea635a5bef3567e870f Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 24 Jun 2024 17:22:37 +0200 Subject: [PATCH 085/595] docs(datasets) Add how to visualization guide (#3672) Co-authored-by: jafermarq --- .../how-to-visualize-label-distribution.ipynb | 1122 +++++++++++++++++ datasets/doc/source/index.rst | 1 + 2 files changed, 1123 insertions(+) create mode 100644 datasets/doc/source/how-to-visualize-label-distribution.ipynb diff --git a/datasets/doc/source/how-to-visualize-label-distribution.ipynb b/datasets/doc/source/how-to-visualize-label-distribution.ipynb new file mode 100644 index 000000000000..26db72047cff --- /dev/null +++ b/datasets/doc/source/how-to-visualize-label-distribution.ipynb @@ -0,0 +1,1122 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "fb7e89caa9e6d772", + "metadata": {}, + "source": [ + "# Visualize Label Distribution" + ] + }, + { + "cell_type": "markdown", + "id": "67c54a8d7c872547", + "metadata": {}, + "source": [ + "If you partition datasets to simulate heterogeneity through label skew and/or size skew, you can now effortlessly visualize the partitioned dataset using `flwr-datasets`.\n", + "\n", + "In this how-to guide, you'll learn how to visualize and compare partitioned datasets when applying different methods or parameters.\n", + "\n", + "All the described visualization functions are compatible with all ``Partitioner`` you can find in\n", + "[flwr_datasets.partitioner](https://flower.ai/docs/datasets/ref-api/flwr_datasets.partitioner.html#module-flwr_datasets.partitioner)\n" + ] + }, + { + "cell_type": "markdown", + "id": "7220467f2c6ba432", + "metadata": {}, + "source": [ + "## Common Setup" + ] + }, + { + "cell_type": "markdown", + "id": "4e2ad2f0a0f7174d", + "metadata": {}, + "source": [ + "Install Flower Datasets library:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c46514b679f394ce", + "metadata": {}, + "outputs": [], + "source": [ + "! pip install -q \"flwr-datasets[vision]\"" + ] + }, + { + "cell_type": "markdown", + "id": "d7ffd5b6836a5ee0", + "metadata": {}, + "source": [ + "## Plot Label Distribution" + ] + }, + { + "cell_type": "markdown", + "id": "38fbbdfe6b930916", + "metadata": {}, + "source": [ + "### Bar plot" + ] + }, + { + "cell_type": "markdown", + "id": "a5778edf97a7ee04", + "metadata": {}, + "source": [ + "Let's visualize the result of `DirichletPartitioner`.\n", + "We will create a `FederatedDataset` and assign `DirichletPartitioner` to the `train` split:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42397afaaf50529e", + "metadata": {}, + "outputs": [], + "source": [ + "from flwr_datasets import FederatedDataset\n", + "from flwr_datasets.partitioner import DirichletPartitioner\n", + "from flwr_datasets.visualization import plot_label_distributions\n", + "\n", + "\n", + "fds = FederatedDataset(\n", + " dataset=\"cifar10\",\n", + " partitioners={\n", + " \"train\": DirichletPartitioner(\n", + " num_partitions=10,\n", + " partition_by=\"label\",\n", + " alpha=0.3,\n", + " seed=42,\n", + " min_partition_size=0,\n", + " ),\n", + " },\n", + ")\n", + "\n", + "partitioner = fds.partitioners[\"train\"]" + ] + }, + { + "cell_type": "markdown", + "id": "c4d5855ee8a605d3", + "metadata": {}, + "source": [ + "Once we have the partitioner with the dataset assigned, we are ready to pass it to the plotting function:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f75b48256ed68897", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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ACBkZGSU7kkgIiIileHvb5ISyexjXtVxH1QiIqKqh7+/SUoku8aciIiIiOh9wsSciIiIiEgCmJgTEREREUkAE3MiIiIiIglgYk5EREREJAFMzImIiIiIJEBD1QEQEREpW50N/ZTex59+vyq9DyKq3jhjTkREREQkAUzMiYiIiIgkgIk5EREREZEEMDEnIiIiIpIAJuZERERERBLAxJyIiIiISAKYmBMRERERSQATcyIiIiIiCWBiTkREREQkAUzMiYiIiIgkgIk5EREREZEEMDEnIiIiIpIAJuZERERERBLAxJyIiIiISAKYmBMRERERSQATcyIiIiIiCWBiTkREREQkAUzMiYiIiIgkQEPVARARESlb33pmqg6BiOhfccaciIiIiEgCmJgTEREREUkAE3MiIiIiIglgYk5EREREJAFMzImIiIiIJICJORERERGRBDAxJyIiIiKSACbmREREREQSwMSciIiIiEgCmJgTEREREUkAE3MiIiIiIglgYk5EREREJAFMzImIiIiIJICJORERERGRBDAxJyIiIiKSACbmREREREQSwMSciIiIiEgCmJgTEREREUkAE3MiIiIiIglgYk5EREREJAFMzImIiIiIJICJORERERGRBDAxJyIiIiKSAA1VB0BERKRsk9w7qDoEIqJ/xRlzIiIiIiIJYGJORERERCQBXMpCREREVAnkcjlyc3NVHQZVIk1NTairq5e4PhNzIiIiIiXLzc1FcnIy5HK5qkOhSmZiYgJra2vIZLJ/rcvEnIiIiEiJBEHAgwcPoK6uDjs7O6ipcSXx+0AQBDx//hxpaWkAABsbm3+9hok5ERERkRLl5+fj+fPnsLW1hZ6enqrDoUqkq6sLAEhLS4OlpeW/LmvhRzYiIiIiJSooKAAAaGlpqTgSUoXCD2N5eXn/WpeJOREREVElKMkaY6p+SvO6MzEnIiIiIpIAJuZEREREVCE2btwIExOTcrcjk8mwZ8+ecrdT1TAxJyIiIiKRr68vevfureow3ktMzImIiIiIJEDSiXlBQQGmT58OR0dH6OrqwsnJCbNnz4YgCGIdQRAwY8YM2NjYQFdXF15eXrh165ZCO0+ePMGgQYNgZGQEExMT+Pn5ISsrS6HO5cuX0aFDB+jo6MDOzg7z58+vlDESERERVRWLFy9Go0aNoK+vDzs7O3z99ddFcioA2LNn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i4uLg76+fqX2WdUxMSciIiKiCmdhYaHqEKocLmUhIiIiomLt3LkTjRo1gq6uLszNzeHl5YXs7Gzx/MKFC2FjYwNzc3P4+/sjLy9PPPfmUhaZTIY1a9agW7du0NXVRZ06dbBz587KHI7kMTEnIiIioiIePHiAgQMHYujQoUhMTER0dDT69u0LQRAAACdOnMCdO3dw4sQJREREYOPGjdi4ceM725w+fTr69euHS5cuYdCgQRgwYAASExMrYTRVA5eyEBEREVERDx48QH5+Pvr27Qt7e3sAQKNGjcTzpqamWLlyJdTV1VG/fn306NEDUVFRGD58+Fvb/PTTTzFs2DAAwOzZsxEZGYkVK1Zg9erVyh1MFcEZcyIiIiIqokmTJujcuTMaNWqETz/9FOvXr8fTp0/F8w0aNIC6urp4bGNjg7S0tHe26eHhUeSYM+b/j4k5ERERERWhrq6OyMhIHDp0CG5ublixYgVcXFyQnJwMANDU1FSoL5PJIJfLVRFqtcHEnIiIiIiKJZPJ0K5dOwQHB+PixYvQ0tLC7t27y9ze2bNnixy7urqWN8xqg2vMiYiIiKiI2NhYREVFoUuXLrC0tERsbCwePXoEV1dXXL58uUxt/vLLL3B3d0f79u2xZcsWnDt3Dhs2bKjgyKsuJuZEREREVISRkRFOnTqFpUuXIjMzE/b29li0aBG6deuG7du3l6nN4OBgbNu2DV9//TVsbGzw888/w83NrYIjr7qYmBMRUbVn8aISOtGthD6oWqnsO3GWlqurKw4fPlzsueK2RXx9z3IASElJKVLH1tYWR48erYDoqieuMSciIiIikgAm5kREREREEsClLERERESkdIV3DKW344w5EREREZEEMDEnIiIiIpIAJuZERERERBLAxJyIiIiISAKYmBMRERERSQB3ZVGxCb8PV2r7CzusV2r7RERERFQxOGNOREREREV4enoiMDBQ1WG8VzhjTkRERKQCyv6r+Zv4V3Tp44w5ERERESldbm6uqkOQPCbmRERERFQsuVyOSZMmwczMDNbW1ggKChLP3b17F7169YKBgQGMjIzw2WefITU1VTwfFBSEpk2b4ocffoCjoyN0dHQAADt37kSjRo2gq6sLc3NzeHl5ITs7W7zuhx9+gKurK3R0dFC/fn2sXr260saralzKQkRERETFioiIwPjx4xEbG4uYmBj4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", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax, df = plot_label_distributions(\n", + " partitioner,\n", + " label_name=\"label\",\n", + " plot_type=\"bar\",\n", + " size_unit=\"absolute\",\n", + " partition_id_axis=\"x\",\n", + " legend=True,\n", + " verbose_labels=True,\n", + " title=\"Per Partition Labels Distribution\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "be05badab744d9f7", + "metadata": {}, + "source": [ + "You can configure many details directly using the function parameters. The ones that can interest you the most are:\n", + "\n", + "* `size_unit` to have the sizes normalized such that they sum up to 1 and express the fraction of the data in each partition,\n", + "* `legend` and `verbose_labels` in case the dataset has more descriptive names and not numbers,\n", + "* `cmap` to change the values of the bars (for an overview of the available colors, have a look at [link](https://matplotlib.org/stable/users/explain/colors/colormaps.html); check out `cmap=\"tab20b\"`) \n", + "\n", + " And for even greater control, you can specify `plot_kwargs` and `legend_kwargs` as `Dict`, which will be further passed to the `plot` and `legend` functions." + ] + }, + { + "cell_type": "markdown", + "id": "3dbf6dc4ede79f05", + "metadata": {}, + "source": [ + "You can also inspect the exact numbers that were used to create this plot. Three objects are returned (see reference [here](https://flower.ai/docs/datasets/ref-api/flwr_datasets.visualization.plot_label_distributions.html#flwr_datasets.visualization.plot_label_distributions)). Let's inspect the returned DataFrame." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6edd14d8b260e9e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
    \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
    airplaneautomobilebirdcatdeerdogfroghorseshiptruck
    Partition ID
    08177941462212343225456384149
    11416697530340903868
    2041124543511158421
    37621591100511201662198213512175
    424371421924004251151477
    5677917025255247727445900
    6422244863809290380506
    7122281159721741038172716825154
    825629342751848151122401417
    91136107350357126711223
    \n", + "
    " + ], + "text/plain": [ + " airplane automobile bird cat deer dog frog horse ship \\\n", + "Partition ID \n", + "0 817 794 1462 2123 432 25 456 384 14 \n", + "1 1416 6 97 5 3 0 3409 0 3 \n", + "2 0 4 11 2 454 3 511 15 84 \n", + "3 762 159 1100 51 120 166 2 1982 1351 \n", + "4 2 43 714 2 19 2400 425 1 151 \n", + "5 67 79 170 25 2552 477 27 44 590 \n", + "6 422 2 4 486 380 92 90 380 50 \n", + "7 122 2811 597 2174 1038 1727 1 682 515 \n", + "8 256 29 342 75 1 84 8 1511 2240 \n", + "9 1136 1073 503 57 1 26 71 1 2 \n", + "\n", + " truck \n", + "Partition ID \n", + "0 9 \n", + "1 868 \n", + "2 21 \n", + "3 2175 \n", + "4 477 \n", + "5 0 \n", + "6 6 \n", + "7 4 \n", + "8 1417 \n", + "9 23 " + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "markdown", + "id": "2902213a", + "metadata": {}, + "source": [ + "Each row represents a unique partition ID, and the columns represent unique labels (either in the verbose version if `verbose_labels=True` or typically `int` values otherwise, representing the partition IDs).\n", + "That you can index the DataFrame `df[partition_id, label_id]` to get the number of samples in `partition_id` for the specified `label_id.`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8ffe4039", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "714" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.loc[4, \"bird\"]" + ] + }, + { + "cell_type": "markdown", + "id": "2e6c17af529a668f", + "metadata": {}, + "source": [ + "Let's see a plot with `size_unit=\"percent\"`, which is another excellent way to understand the partitions. In this mode, the number of datapoints for each class in a given partition are normalized, so they sum up to 100." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a241894a47f3cc9f", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtQAAAHHCAYAAACfh89YAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAABgdklEQVR4nO3deVxU5f///+ew76KkLIqIior7brikJe/MpVxLzRJyKwOX3MtccE1Tc0stK7dssXJr00zT0gy3MEtzS9NPuWUqggkC5/eHP+fbCBowDAP4uN9uc7txrnPOdb0OTPnk4ppzTIZhGAIAAACQKw72LgAAAAAozAjUAAAAgBUI1AAAAIAVCNQAAACAFQjUAAAAgBUI1AAAAIAVCNQAAACAFQjUAAAAgBUI1AAAAIAVCNRAERcdHa1y5cpl69jx48fLZDLZtqB80KJFC1WvXj1P+yxXrpyio6PztM/sWrp0qUwmk06ePGnzsW5/v5w8eVImk0kzZsyw+dhS0XkPAri3EKhRpN0KIrdebm5uqlSpkmJjY3Xu3Dmbj38rHNx6eXh4qGrVqnr55ZeVmJiYZ+P8+eefGj9+vBISEv7z2GvXrmn8+PHaunVrno2fF0wmk2JjY+1dhs1t3brV4j3h6uoqf39/tWjRQlOmTNGFCxfyZJyC+nOWCnZtAJAbBGrcEyZMmKAVK1Zo/vz5aty4sRYuXKiIiAhdu3YtX8ZfuHChVqxYoVmzZqlKlSqaPHmyHnnkERmGkSf9//nnn4qLi8syUC9evFiHDx82b1+7dk1xcXFZhpmXX35Z//zzT57UhLsbOHCgVqxYoTfffFPDhw9XiRIlNG7cOIWHh2vLli0Wxz799NP6559/FBISku3+7/Zzvpvb3y+2wHsQQFHjZO8CgPzQunVr1a9fX5LUp08f+fn5adasWVq3bp26d+9uVd/Xrl2Th4fHXY/p0qWL7rvvPknSc889p86dO2v16tX64YcfFBERkeux09LSlJGRcddjnJ2ds92fk5OTnJz430J+aNasmbp06WLRtn//fj388MPq3LmzDh48qMDAQEmSo6OjHB0dbVpPcnKyPD09c/R+sQXegwAKI2aocU966KGHJEknTpwwt7377ruqV6+e3N3dVaJECXXr1k2nT5+2OO/W2ty9e/fqgQcekIeHh1566SWrxk9NTdXYsWNVr149FStWTJ6enmrWrJm++eYbi3P+vZZ19uzZqlChglxdXbVgwQI1aNBAkvTMM8+YlxIsXbpUkuWa2JMnT6pkyZKSpLi4OPOx48ePl5T1+tW0tDRNnDjRPF65cuX00ksvKSUlxeK4cuXKqV27dtq+fbsaNmwoNzc3lS9fXsuXL8/x9+dO1q1bp7Zt2yooKEiurq6qUKGCJk6cqPT09CyP37t3rxo3bix3d3eFhoZq0aJFmY5JSUnRuHHjVLFiRbm6uio4OFgjRozIdH23u3HjhuLi4hQWFiY3Nzf5+fmpadOm2rRpU66vr1atWpo9e7YuX76s+fPnm9uzWkO9Z88etWrVSvfdd5/5+nr16iXpv3/O0dHR8vLy0vHjx9WmTRt5e3urR48e5n13WnP/2muvKSQkRO7u7mrevLl+/vlni/0tWrRQixYtMp1XlN6DAJAVpgFwTzp+/Lgkyc/PT5I0efJkjRkzRk888YT69OmjCxcuaN68eXrggQf0448/ytfX13zuxYsX1bp1a3Xr1k1PPfWU/P39rRo/MTFRb731lrp3766+ffvq6tWrevvtt9WqVSvt2rVLtWvXtjh3yZIlun79uvr16ydXV1d17NhRV69e1dixY9WvXz81a9ZMktS4ceNM45YsWVILFy5U//791bFjR3Xq1EmSVLNmzTvW2qdPHy1btkxdunTR0KFDFR8fr6lTp+rQoUNas2aNxbHHjh1Tly5d1Lt3b0VFRemdd95RdHS06tWrp2rVquX4+3S7pUuXysvLS0OGDJGXl5e2bNmisWPHKjExUa+++qrFsZcuXVKbNm30xBNPqHv37lq1apX69+8vFxcXc/DMyMjQY489pu3bt6tfv34KDw/XgQMH9Nprr+nIkSNau3btHWsZP368pk6dqj59+qhhw4ZKTEzUnj17tG/fPv3vf//L9TXe+v599dVXmjx5cpbHnD9/Xg8//LBKliypUaNGydfXVydPntTq1aslZe/nnJaWplatWqlp06aaMWPGf/6VZfny5bp69apiYmJ0/fp1zZkzRw899JAOHDiQo/8GCvt7EACyZABF2JIlSwxJxtdff21cuHDBOH36tPHBBx8Yfn5+hru7u/F///d/xsmTJw1HR0dj8uTJFuceOHDAcHJysmhv3ry5IclYtGhRtsYfN26cIck4fPiwceHCBePEiRPGG2+8Ybi6uhr+/v5GcnKykZaWZqSkpFicd+nSJcPf39/o1auXue3EiROGJMPHx8c4f/68xfG7d+82JBlLlizJVENUVJQREhJi3r5w4YIhyRg3btwd670lISHBkGT06dPH4rhhw4YZkowtW7aY20JCQgxJxrfffmtuO3/+vOHq6moMHTr0rt8nwzAMSUZMTMxdj7l27Vqmtmeffdbw8PAwrl+/bm679XOaOXOmuS0lJcWoXbu2UapUKSM1NdUwDMNYsWKF4eDgYHz33XcWfS5atMiQZOzYscPi+qKioszbtWrVMtq2bfuf13W7b775xpBkfPTRR3c8platWkbx4sXN27fexydOnDAMwzDWrFljSDJ27959xz7u9nOOiooyJBmjRo3Kct+/3y+33ne3/nu5JT4+3pBkvPDCC+a25s2bG82bN//PPgvqexAAcoslH7gnREZGqmTJkgoODla3bt3k5eWlNWvWqHTp0lq9erUyMjL0xBNP6K+//jK/AgICFBYWlmnphaurq5555pkcjV+5cmWVLFlSoaGhevbZZ1WxYkV9/vnn8vDwkKOjo1xcXCTdnDH9+++/lZaWpvr162vfvn2Z+urcubP5T+a29sUXX0iShgwZYtE+dOhQSdLnn39u0V61alXzDLl0czaycuXK+u233/KkHnd3d/PXV69e1V9//aVmzZrp2rVr+vXXXy2OdXJy0rPPPmvednFx0bPPPqvz589r7969kqSPPvpI4eHhqlKlisXP/taSnNt/9v/m6+urX375RUePHs2Ta/s3Ly8vXb169a5jS9Jnn32mGzdu5Hqc/v37Z/vYDh06qHTp0ubthg0bqlGjRub3iK0UtPcgAGSFJR+4J7z++uuqVKmSnJyc5O/vr8qVK8vB4ebvk0ePHpVhGAoLC8vy3Ns/pFW6dGlzAM6uTz75RD4+PnJ2dlaZMmVUoUIFi/3Lli3TzJkz9euvv1oEpNDQ0Ex9ZdVmK7///rscHBxUsWJFi/aAgAD5+vrq999/t2gvW7Zspj6KFy+uS5cu5Uk9v/zyi15++WVt2bIl020Hr1y5YrEdFBQkT09Pi7ZKlSpJurmO9/7779fRo0d16NChO/6Ccv78+TvWMmHCBLVv316VKlVS9erV9cgjj+jpp5++69KF7EpKSpK3t/cd9zdv3lydO3dWXFycXnvtNbVo0UIdOnTQk08+KVdX12yN4eTkpDJlymS7pqz++6hUqZJWrVqV7T5yo6C9BwEgKwRq3BMaNmxovsvH7TIyMmQymfTll19meScFLy8vi+1/z5Jm1wMPPGC+y8ft3n33XUVHR6tDhw4aPny4SpUqJUdHR02dOtW81tra8a2V3Qdt3OlOFEYe3B7w8uXLat68uXx8fDRhwgRVqFBBbm5u2rdvn0aOHPmfdzvJSkZGhmrUqKFZs2ZluT84OPiO5z7wwAM6fvy41q1bp6+++kpvvfWWXnvtNS1atEh9+vTJcS233LhxQ0eOHLnrg2lMJpM+/vhj/fDDD/r000+1ceNG9erVSzNnztQPP/yQ6T2bFVdXV/MvlXnFZDJl+bO+04dGc9p3dtjyPQgAd0Kgxj2vQoUKMgxDoaGh5hnM/PTxxx+rfPnyWr16tUVoGDduXLb7yMmT5XJybEhIiDIyMnT06FGFh4eb28+dO6fLly/n6L7I1tq6dasuXryo1atX64EHHjC3//tOLf/2559/mm8Fd8uRI0ckyXzHiQoVKmj//v1q2bJlrp7OV6JECT3zzDN65plnlJSUpAceeEDjx4+3KlB//PHH+ueff9SqVav/PPb+++/X/fffr8mTJ+u9995Tjx499MEHH6hPnz55/rTBrJa2HDlyxOKOIMWLF89yacXts8iF9T0IAHfCGmrc8zp16iRHR0fFxcVlmsUyDEMXL1606fi3ZtT+PXZ8fLx27tyZ7T5uhcbLly//57G37uaQnWPbtGkjSZo9e7ZF+60Z3bZt22a7Rmtl9X1KTU3VggULsjw+LS1Nb7zxhsWxb7zxhkqWLKl69epJkp544gn98ccfWrx4cabz//nnHyUnJ9+xntvfF15eXqpYseJ/3m7vbvbv36/BgwerePHiiomJueNxly5dyvRevXU3mFvj5+TnnB1r167VH3/8Yd7etWuX4uPj1bp1a3NbhQoV9Ouvv1o87XH//v3asWOHRV+F9T0IAHfCDDXueRUqVNCkSZP04osv6uTJk+rQoYO8vb114sQJrVmzRv369dOwYcNsNn67du20evVqdezYUW3bttWJEye0aNEiVa1aVUlJSdm+Bl9fXy1atEje3t7y9PRUo0aNslxv7e7urqpVq+rDDz9UpUqVVKJECVWvXj3LJQa1atVSVFSU3nzzTfOSi127dmnZsmXq0KGDHnzwQauv/9/27NmjSZMmZWpv0aKFGjdurOLFiysqKkoDBw6UyWTSihUr7vin/KCgIE2bNk0nT55UpUqV9OGHHyohIUFvvvmmeV38008/rVWrVum5557TN998oyZNmig9PV2//vqrVq1apY0bN95xqVDVqlXVokUL1atXTyVKlNCePXv08ccfZ/vx6d99952uX7+u9PR0Xbx4UTt27ND69etVrFgxrVmzRgEBAXc8d9myZVqwYIE6duyoChUq6OrVq1q8eLF8fHzMATQnP+fsqFixopo2bar+/fsrJSVFs2fPlp+fn0aMGGE+plevXpo1a5ZatWql3r176/z581q0aJGqVatmsea9IL8HASBX7HR3ESBf3Lrd2N1uL3bLJ598YjRt2tTw9PQ0PD09jSpVqhgxMTHG4cOHzcc0b97cqFatWrbHv3ULsAsXLtzxmIyMDGPKlClGSEiI4erqatSpU8f47LPP7nj7sldffTXLftatW2dUrVrVcHJysriF3u39GIZhfP/990a9evUMFxcXi9uX3X7LMsMwjBs3bhhxcXFGaGio4ezsbAQHBxsvvviixW3qDOPmLcuyuo3cnW6ldjtJd3xNnDjRMAzD2LFjh3H//fcb7u7uRlBQkDFixAhj48aNhiTjm2++sRizWrVqxp49e4yIiAjDzc3NCAkJMebPn59p3NTUVGPatGlGtWrVDFdXV6N48eJGvXr1jLi4OOPKlSsW1/fv2+ZNmjTJaNiwoeHr62u4u7sbVapUMSZPnmy+Jd+d3Lpt3q2Xs7OzUbJkSeOBBx4wJk+enOmWiIaR+bZ5+/btM7p3726ULVvWcHV1NUqVKmW0a9fO2LNnj8V5d/o5R0VFGZ6enlnWd7f33cyZM43g4GDD1dXVaNasmbF///5M57/77rtG+fLlDRcXF6N27drGxo0bC817EAByy2QYfFIDAAAAyC3WUAMAAABWIFADAAAAViBQAwAAAFYgUAMAAABWIFADAAAAViBQAwAAAFbgwS6SMjIy9Oeff8rb2zvPH9cLAABswzAMXb16VUFBQXJwYI4Q9kOglvTnn38qODjY3mUAAIBcOH36tMqUKWPvMnAPI1BL8vb2lnTzP0gfHx87VwMAALIjMTFRwcHB5n/HAXshUEvmZR4+Pj4EagAAChmWa8LeWHAEAAAAWIFADQAAAFiBQA0AAABYgUANAAAAWIFADQAAAFiBQA0AAABYgUANAAAAWIFADQAAAFiBQA0AAABYgUANAAAAWMGugfrbb7/Vo48+qqCgIJlMJq1du9Ziv2EYGjt2rAIDA+Xu7q7IyEgdPXrU4pi///5bPXr0kI+Pj3x9fdW7d28lJSXl41UAAADgXmbXQJ2cnKxatWrp9ddfz3L/9OnTNXfuXC1atEjx8fHy9PRUq1atdP36dfMxPXr00C+//KJNmzbps88+07fffqt+/frl1yUAAADgHmcyDMOwdxGSZDKZtGbNGnXo0EHSzdnpoKAgDR06VMOGDZMkXblyRf7+/lq6dKm6deumQ4cOqWrVqtq9e7fq168vSdqwYYPatGmj//u//1NQUFC2xk5MTFSxYsV05coV+fj42OT6AABA3uLfbxQUBXYN9YkTJ3T27FlFRkaa24oVK6ZGjRpp586dkqSdO3fK19fXHKYlKTIyUg4ODoqPj8/3mgEAAHDvcbJ3AXdy9uxZSZK/v79Fu7+/v3nf2bNnVapUKYv9Tk5OKlGihPmYrKSkpCglJcW8nZiYmFdlAwAA4B5TYAO1LU2dOlVxcXFW9fFep6zXfeelJ1fH2HyME0sO2bT/0GfCbdq/ZPtrkLiO7CoK1yDx33d28bPIvqLw30ZRuAbAVgpsoA4ICJAknTt3ToGBgeb2c+fOqXbt2uZjzp8/b3FeWlqa/v77b/P5WXnxxRc1ZMgQ83ZiYqKCg4NzVN9D/3PL0fEAkN92frrFpv0TfgDgpgK7hjo0NFQBAQHavHmzuS0xMVHx8fGKiIiQJEVEROjy5cvau3ev+ZgtW7YoIyNDjRo1umPfrq6u8vHxsXgBAAAAuWHXGeqkpCQdO3bMvH3ixAklJCSoRIkSKlu2rAYPHqxJkyYpLCxMoaGhGjNmjIKCgsx3AgkPD9cjjzyivn37atGiRbpx44ZiY2PVrVu3bN/hAwAA/LcfPxpm8zFCn/nc5mMAtmDXQL1nzx49+OCD5u1byzCioqK0dOlSjRgxQsnJyerXr58uX76spk2basOGDXJz+3/LLVauXKnY2Fi1bNlSDg4O6ty5s+bOnZvv1wIAAIB7k10DdYsWLXS322CbTCZNmDBBEyZMuOMxJUqU0HvvvWeL8gAAAID/VGA/lAhkl60/eCXx4SsAAHBnBfZDiQAAAEBhQKAGAAAArECgBgAAAKzAGmoAKKJ4ABUA5A9mqAEAAAArMEMNAAD+U+MJbexdAlBgMUMNAAAAWIFADQAAAFiBJR8AAOA/7XM6ZPMxWFSCwooZagAAAMAKBGoAAADACgRqAAAAwAqsoQYAFFg8nAZAYcAMNQAAAGAFAjUAAABgBZZ85NL3n662+Rid+ve2+RgAAGTH6lX7bD5Gm9o2HwKwCWaoAQAAACsQqAEAAAArEKgBAAAAKxCoAQAAACsQqAEAAAArEKgBAAAAKxCoAQAAACsQqAEAAAArEKgBAAAAKxCoAQAAACvw6HEUeg/9z83eJQAAgHsYM9QAAACAFQjUAAAAgBUI1AAAAIAVCNQAAACAFfhQIgq97z9dbfMxOvXvbfMxAABA4USgBpBndn66xeZjhD4TbvMxAADICZZ8AAAAAFYgUAMAAABWIFADAAAAVmANNQCg4Gpw3d4VAMB/YoYaAAAAsAKBGgAAALACSz5yqfGENvYuAQAA5JP09HTduHHD3mUgHzk7O8vR0TFbxxKoAQAA7sAwDJ09e1aXL1+2dymwA19fXwUEBMhkMt31OAJ1Lq1c/rbNxxhaP8bmYwAAgDu7FaZLlSolDw+P/wxWKBoMw9C1a9d0/vx5SVJgYOBdjydQAwAAZCE9Pd0cpv38/OxdDvKZu7u7JOn8+fMqVarUXZd/8KFEAACALNxaM+3h4WHnSmAvt372/7V+nhlqALjNQ/9zs3cJAAoQlnncu7L7sydQ3+N2frrFpv2HPhNu0/5RsLhd/yIfRuGzBQCAgoVAfY+zfQAi/AAAUNAtXbpUgwcPtvpuJiaTSWvWrFGHDh3ypK7CgjXUAAAARUB0dPQ9F2QLCmaoAeA233+62uZjdOrf2+ZjAADyBzPUAAAARdysWbNUo0YNeXp6Kjg4WM8//7ySkpIyHbd27VqFhYXJzc1NrVq10unTpy32r1u3TnXr1pWbm5vKly+vuLg4paWlZTlmamqqYmNjFRgYKDc3N4WEhGjq1Kk2uT57Y4YaAG7TeEIbe5cAAHnKwcFBc+fOVWhoqH777Tc9//zzGjFihBYsWGA+5tq1a5o8ebKWL18uFxcXPf/88+rWrZt27NghSfruu+/Us2dPzZ07V82aNdPx48fVr18/SdK4ceMyjTl37lytX79eq1atUtmyZXX69OlMAb2oIFADAAAUcYMHDzZ/Xa5cOU2aNEnPPfecRaC+ceOG5s+fr0aNGkmSli1bpvDwcO3atUsNGzZUXFycRo0apaioKElS+fLlNXHiRI0YMSLLQH3q1CmFhYWpadOmMplMCgkJse1F2hFLPgAAAIq4r7/+Wi1btlTp0qXl7e2tp59+WhcvXtS1a9fMxzg5OalBgwbm7SpVqsjX11eHDh2SJO3fv18TJkyQl5eX+dW3b1+dOXPGop9boqOjlZCQoMqVK2vgwIH66quvbH+hdkKgBgAAKMJOnjypdu3aqWbNmvrkk0+0d+9evf7665JurnPOrqSkJMXFxSkhIcH8OnDggI4ePSo3t8wPxKpbt65OnDihiRMn6p9//tETTzyhLl265Nl1FSQs+QAAACjC9u7dq4yMDM2cOVMODjfnUletWpXpuLS0NO3Zs0cNGzaUJB0+fFiXL19WePjNh7TVrVtXhw8fVsWKFbM9to+Pj7p27aquXbuqS5cueuSRR/T333+rRIkSeXBlBQeBGgAAoIi4cuWKEhISLNruu+8+3bhxQ/PmzdOjjz6qHTt2aNGiRZnOdXZ21oABAzR37lw5OTkpNjZW999/vzlgjx07Vu3atVPZsmXVpUsXOTg4aP/+/fr55581adKkTP3NmjVLgYGBqlOnjhwcHPTRRx8pICBAvr6+trh0u2LJBwAAQBGxdetW1alTx+K1YsUKzZo1S9OmTVP16tW1cuXKLG9f5+HhoZEjR+rJJ59UkyZN5OXlpQ8//NC8v1WrVvrss8/01VdfqUGDBrr//vv12muv3fHDht7e3po+fbrq16+vBg0a6OTJk/riiy/Ms+RFCTPUAAAARcDSpUu1dOnSO+5/4YUXLLaffvpp89fR0dGKjo6WJHXq1OmOfbRq1UqtWrW6437DMMxf9+3bV3379v2PqouGovcrAgAAAJCPCnSgTk9P15gxYxQaGip3d3dVqFBBEydOtPjtxzAMjR07VoGBgXJ3d1dkZKSOHj1qx6oBAABwLynQgXratGlauHCh5s+fr0OHDmnatGmaPn265s2bZz5m+vTpmjt3rhYtWqT4+Hh5enqqVatWun79uh0rBwAAwL2iQK+h/v7779W+fXu1bdtW0s0n+7z//vvatWuXpJuz07Nnz9bLL7+s9u3bS5KWL18uf39/rV27Vt26dbNb7QAAALg3FOgZ6saNG2vz5s06cuSIpJtP6Nm+fbtat24tSTpx4oTOnj2ryMhI8znFihVTo0aNtHPnzjv2m5KSosTERIsXAAAAkBsFeoZ61KhRSkxMVJUqVeTo6Kj09HRNnjxZPXr0kCSdPXtWkuTv729xnr+/v3lfVqZOnaq4uDjbFQ4AAIB7RoEO1KtWrdLKlSv13nvvqVq1akpISNDgwYMVFBSkqKioXPf74osvasiQIebtxMREBQcH56iP8F6Ncz0+AAAAio4CHaiHDx+uUaNGmddC16hRQ7///rumTp2qqKgoBQQESJLOnTunwMBA83nnzp1T7dq179ivq6urXF1dbVo7AAAA7g0Feg31tWvXMj1Nx9HRURkZGZKk0NBQBQQEaPPmzeb9iYmJio+PV0RERL7WCgAAgHtTgQ7Ujz76qCZPnqzPP/9cJ0+e1Jo1azRr1ix17NhRkmQymTR48GBNmjRJ69ev14EDB9SzZ08FBQWpQ4cO9i0eAACgiDt58qRMJpMSEhLsXYpdFeglH/PmzdOYMWP0/PPP6/z58woKCtKzzz6rsWPHmo8ZMWKEkpOT1a9fP12+fFlNmzbVhg0b5ObmZsfKAQBAUXZiyaF8HS/0mfAcHd+iRQvVrl1bs2fPtk1BsFCgA7W3t7dmz5591zeDyWTShAkTNGHChPwrDAAAoBAzDEPp6elycirQUbDQKNBLPgAAAJAz0dHR2rZtm+bMmSOTySSTyaSlS5fKZDLpyy+/VL169eTq6qrt27crOjo60zLZwYMHq0WLFubtjIwMTZ8+XRUrVpSrq6vKli2ryZMnZzl2enq6evXqpSpVqujUqVM2vMqChV9LAAAAipA5c+boyJEjql69uvkv+L/88oukm8/4mDFjhsqXL6/ixYtnq78XX3xRixcv1muvvaamTZvqzJkz+vXXXzMdl5KSou7du+vkyZP67rvvVLJkyby7qAKOQA0AAFCEFCtWTC4uLvLw8DDfYvhWAJ4wYYL+97//Zbuvq1evas6cOZo/f775GSAVKlRQ06ZNLY5LSkpS27ZtlZKSom+++UbFihXLo6spHAjUAFBEff/papv236l/b5v2DyDv1a9fP0fHHzp0SCkpKWrZsuVdj+vevbvKlCmjLVu2yN3d3ZoSCyXWUAMAANwjPD09LbYdHBxkGIZF240bN8xfZzcct2nTRj/99JN27txpfZGFEIEaAACgiHFxcVF6evp/HleyZEmdOXPGou3f95QOCwuTu7u7xUP0stK/f3+98soreuyxx7Rt27Zc1VyYseQDAAAb2/npFpuPkdP7FOeUW/ICm/aPvFWuXDnFx8fr5MmT8vLyMj9l+nYPPfSQXn31VS1fvlwRERF699139fPPP6tOnTqSJDc3N40cOVIjRoyQi4uLmjRpogsXLuiXX35R796Wy74GDBig9PR0tWvXTl9++WWmddZFGTPUAAAARcywYcPk6OioqlWrqmTJkne8hV2rVq00ZswYjRgxQg0aNNDVq1fVs2dPi2PGjBmjoUOHauzYsQoPD1fXrl11/vz5LPsbPHiw4uLi1KZNG33//fd5fl0FFTPUAAAAOWTrvwhYq1KlSpnWM0dHR2d5bFxcnOLi4u7Yl4ODg0aPHq3Ro0dn2leuXLlMa7CHDBmiIUOG5LzoQowZagAAAMAKzFADyDONJ7SxdwkAAOQ7ZqgBAAAAKxCoAQAAACsQqAEAAAArEKgBAAAAKxCoAQAAACsQqAEAAAArEKgBAAAAKxCoAQAA7hHR0dHq0KHDXY8pV66cZs+enS/1FBU82AUAACCH3uv0er6O9+TqmHwba/fu3fL09My38YoCAjVQQOz8dIvNxwh9JtzmY6Dg4MmVAHKjZMmS9i6h0GHJBwAAQBHz8ccfq0aNGnJ3d5efn58iIyOVnJxs3j9jxgwFBgbKz89PMTExunHjhnnf7Us+TCaTFi5cqNatW8vd3V3ly5fXxx9/nJ+XU+AxQw0At1m5/G2bjzG0fv79+RbAveXMmTPq3r27pk+fro4dO+rq1av67rvvZBiGJOmbb75RYGCgvvnmGx07dkxdu3ZV7dq11bdv3zv2OWbMGL3yyiuaM2eOVqxYoW7duunAgQMKD+cvnxKBGgAAoEg5c+aM0tLS1KlTJ4WEhEiSatSoYd5fvHhxzZ8/X46OjqpSpYratm2rzZs33zVQP/744+rTp48kaeLEidq0aZPmzZunBQsW2PZiCgmWfAAAABQhtWrVUsuWLVWjRg09/vjjWrx4sS5dumTeX61aNTk6Opq3AwMDdf78+bv2GRERkWn70KFDeVt4IUagBgAAKEIcHR21adMmffnll6patarmzZunypUr68SJE5IkZ2dni+NNJpMyMjLsUWqRQaAGAAAoYkwmk5o0aaK4uDj9+OOPcnFx0Zo1a3Ld3w8//JBpm/XT/w9rqAEAAIqQ+Ph4bd68WQ8//LBKlSql+Ph4XbhwQeHh4frpp59y1edHH32k+vXrq2nTplq5cqV27dqlt9+2/Qe4CwsCNQAAQBHi4+Ojb7/9VrNnz1ZiYqJCQkI0c+ZMtW7dWh9++GGu+oyLi9MHH3yg559/XoGBgXr//fdVtWrVPK688CJQAwAKLG5hiIIqP59cmFPh4eHasGFDlvuWLl2aqe32x4yfPHky0zFBQUH66quv8qC6ook11AAAAIAVCNQAAACAFVjyAQAAgDu69YRF3Bkz1AAAAIAVCNQAAACAFQjUAAAAgBVYQw0ARZStbznH7eYA4CZmqAEAAAArEKgBAAAAKxCoAQAAipgWLVpo8ODB9i7jnsEaagAAgBw6u9C2n1G4XUD/3vk6HnKGGWoAAADcVWpqqr1LKNAI1AAAAEVQRkaGRowYoRIlSiggIEDjx4837zt16pTat28vLy8v+fj46IknntC5c+fM+8ePH6/atWvrrbfeUmhoqNzc3CRJH3/8sWrUqCF3d3f5+fkpMjJSycnJ5vPeeusthYeHy83NTVWqVNGCBQvy7XrtiSUfAAAARdCyZcs0ZMgQxcfHa+fOnYqOjlaTJk3UsmVLc5jetm2b0tLSFBMTo65du2rr1q3m848dO6ZPPvlEq1evlqOjo86cOaPu3btr+vTp6tixo65evarvvvvO/GjylStXauzYsZo/f77q1KmjH3/8UX379pWnp6eioqLs9F3IHwRqAACAIqhmzZoaN26cJCksLEzz58/X5s2bJUkHDhzQiRMnFBwcLElavny5qlWrpt27d6tBgwaSbi7zWL58uUqWLClJ2rdvn9LS0tSpUyeFhIRIkmrUqGEeb9y4cZo5c6Y6deokSQoNDdXBgwf1xhtvFPlAzZIPAACAIqhmzZoW24GBgTp//rwOHTqk4OBgc5iWpKpVq8rX11eHDh0yt4WEhJjDtCTVqlVLLVu2VI0aNfT4449r8eLFunTpkiQpOTlZx48fV+/eveXl5WV+TZo0ScePH7fxldofM9QAAABFkLOzs8W2yWRSRkZGts/39PS02HZ0dNSmTZv0/fff66uvvtK8efM0evRoxcfHy8PDQ5K0ePFiNWrUKNN5RR0z1AAAAPeQ8PBwnT59WqdPnza3HTx4UJcvX1bVqlXveq7JZFKTJk0UFxenH3/8US4uLlqzZo38/f0VFBSk3377TRUrVrR4hYaG2vqS7I4ZagAAgHtIZGSkatSooR49emj27NlKS0vT888/r+bNm6t+/fp3PC8+Pl6bN2/Www8/rFKlSik+Pl4XLlxQeHi4JCkuLk4DBw5UsWLF9MgjjyglJUV79uzRpUuXNGTIkPy6PLsgUAMAANxDTCaT1q1bpwEDBuiBBx6Qg4ODHnnkEc2bN++u5/n4+Ojbb7/V7NmzlZiYqJCQEM2cOVOtW7eWJPXp00ceHh569dVXNXz4cHl6eqpGjRr3xBMbCdQAAAA5VNCfXPjv29/dsnbtWvPXZcuW1bp16+54/vjx4y3uWy3dXCqyYcOGu4775JNP6sknn8xJqUUCa6gBAAAAKxCoAQAAACsQqAEAAAArEKgBAAAAKxCoAQAAACsQqAEAAAAr5DpQnzlzRl26dFHJkiVVokQJPfroo/rtt9/ysjYAAACgwMt1oO7Vq5eqV6+ubdu2acuWLfL3978n7zsIAACAe1u2A/WgQYOUnJxs3j527JhGjhypqlWrqnbt2ho0aJAOHz6c5wX+8ccfeuqpp+Tn5yd3d3fVqFFDe/bsMe83DENjx45VYGCg3N3dFRkZqaNHj+Z5HQAAAEBWsh2oy5Qpo3r16mn9+vWSpK5du6pRo0YaNWqUhg4dqscee0w9evTI0+IuXbqkJk2ayNnZWV9++aUOHjyomTNnqnjx4uZjpk+frrlz52rRokWKj4+Xp6enWrVqpevXr+dpLQAAAIWFYRjq16+fSpQoIZPJpISEBHuXVKRl+9Hjw4cPV5cuXfT8889r6dKlmjdvnho1aqStW7cqPT1d06dPV5cuXfK0uGnTpik4OFhLliwxt4WGhpq/NgxDs2fP1ssvv6z27dtLkpYvXy5/f3+tXbtW3bp1y9N6AAAAJGl1m7b5Ol6nLz7P0fEbNmzQ0qVLtXXrVpUvX1733XefjSqDlMM11KGhofryyy/VuXNnNW/eXCdPntSMGTM0e/ZsPf744zKZTHla3Pr161W/fn09/vjjKlWqlOrUqaPFixeb9584cUJnz55VZGSkua1YsWJq1KiRdu7cmae1AAAAFBbHjx9XYGCgGjdurICAADk5Wc6hpqam2qmyoinHH0q8ePGievTood27d+vHH39URESEfvrpJ1vUpt9++00LFy5UWFiYNm7cqP79+2vgwIFatmyZJOns2bOSJH9/f4vz/P39zfuykpKSosTERIsXAABAURAdHa0BAwbo1KlTMplMKleunFq0aKHY2FgNHjxY9913n1q1aiVJ2rZtmxo2bChXV1cFBgZq1KhRSktLM/d19epV9ejRQ56engoMDNRrr72mFi1aaPDgwXa6uoIp24F68+bN8vf3V8mSJVWmTBn9+uuveueddzR16lR1795dI0aM0D///JOnxWVkZKhu3bqaMmWK6tSpo379+qlv375atGiRVf1OnTpVxYoVM7+Cg4PzqGIAAAD7mjNnjiZMmKAyZcrozJkz2r17tyRp2bJlcnFx0Y4dO7Ro0SL98ccfatOmjRo0aKD9+/dr4cKFevvttzVp0iRzX0OGDNGOHTu0fv16bdq0Sd9995327dtnr0srsLIdqGNiYjRixAhdu3ZN8+fPN/9m8uCDD2rfvn1ydnZW7dq187S4wMBAVa1a1aItPDxcp06dkiQFBARIks6dO2dxzLlz58z7svLiiy/qypUr5tfp06fztG4AAAB7KVasmLy9veXo6KiAgACVLFlSkhQWFqbp06ercuXKqly5shYsWKDg4GDNnz9fVapUUYcOHRQXF6eZM2cqIyNDV69e1bJlyzRjxgy1bNlS1atX15IlS5Senm7nKyx4sh2oz5w5o7Zt28rNzU2PPPKILly4YN7n6uqqyZMna/Xq1XlaXJMmTTLdiu/IkSMKCQmRdHNNd0BAgDZv3mzen5iYqPj4eEVERNyxX1dXV/n4+Fi8AAAAirJ69epZbB86dEgREREWn4Fr0qSJkpKS9H//93/67bffdOPGDTVs2NC8v1ixYqpcuXK+1VxYZPsuH4899pi6dOmixx57TNu3b1ebNm0yHVOtWrU8Le6FF15Q48aNNWXKFD3xxBPatWuX3nzzTb355puSJJPJpMGDB2vSpEkKCwtTaGioxowZo6CgIHXo0CFPawEA5L/wXo3tXQJQZHh6etq7hCIr2zPUb7/9tp599llduXJFTz31lGbPnm3Dsm5q0KCB1qxZo/fff1/Vq1fXxIkTNXv2bIv7XY8YMUIDBgxQv3791KBBAyUlJWnDhg1yc3OzeX0AAACFVXh4uHbu3CnDMMxtO3bskLe3t8qUKaPy5cvL2dnZvAZbkq5cuaIjR47Yo9wCLdsz1C4uLhowYIAta8lSu3bt1K5duzvuN5lMmjBhgiZMmJCPVQEAABRuzz//vGbPnq0BAwYoNjZWhw8f1rhx4zRkyBA5ODjI29tbUVFRGj58uEqUKKFSpUpp3LhxcnBwyPNbJRd2Ob5tHgAAAAq/0qVL64svvtCuXbtUq1YtPffcc+rdu7defvll8zGzZs1SRESE2rVrp8jISDVp0kTh4eGsBLhNtmeoAQAAcFNOn1yY3wYPHmxxr+itW7dmeVzz5s21a9euO/bj7e2tlStXmreTk5MVFxenfv365VWpRQKBGgAAAFn68ccf9euvv6phw4a6cuWKeYlt+/bt7VxZwUKgBgAAwB3NmDFDhw8flouLi+rVq6fvvvtO9913n73LKlByHKjLly+v3bt3y8/Pz6L98uXLqlu3rn777bc8Kw4AAAD2U6dOHe3du9feZRR4Of5Q4smTJ7N8Qk5KSor++OOPPCkKAAAAKCyyPUO9fv1689cbN25UsWLFzNvp6enavHmzypUrl6fFAQAAAAVdtgP1rScPmkwmRUVFWexzdnZWuXLlNHPmzDwtDgAAACjosh2oMzIyJEmhoaHavXs3i9EBAAAA5eJDiSdOnLBFHQAAAEChlKvb5m3evFmbN2/W+fPnzTPXt7zzzjt5Uhhwr3nofzx1CgCAwijHd/mIi4vTww8/rM2bN+uvv/7SpUuXLF4AAAAoeFq0aGHx9ETknRzPUC9atEhLly7V008/bYt6AAAACryze17P1/EC6sfk63jImRzPUKempqpx48a2qAUAAAAodHIcqPv06aP33nvPFrUAAAAgDyQnJ6tnz57y8vJSYGBgplsbX7p0ST179lTx4sXl4eGh1q1b6+jRoxbHLF68WMHBwfLw8FDHjh01a9Ys+fr65uNVFB45XvJx/fp1vfnmm/r6669Vs2ZNOTs7W+yfNWtWnhUHAACAnBs+fLi2bdumdevWqVSpUnrppZe0b98+1a5dW5IUHR2to0ePav369fLx8dHIkSPVpk0bHTx4UM7OztqxY4eee+45TZs2TY899pi+/vprjRkzxr4XVYDlOFD/9NNP5h/Gzz//bLHPZDLlSVEAAADInaSkJL399tt699131bJlS0nSsmXLVKZMGUkyB+kdO3aYl/GuXLlSwcHBWrt2rR5//HHNmzdPrVu31rBhwyRJlSpV0vfff6/PPvvMPhdVwOU4UH/zzTe2qAMAAAB54Pjx40pNTVWjRo3MbSVKlFDlypUlSYcOHZKTk5PFfj8/P1WuXFmHDh2SJB0+fFgdO3a06Ldhw4YE6jvI8RrqW44dO6aNGzfqn3/+kSQZhpFnRQEAAACFRY4D9cWLF9WyZUtVqlRJbdq00ZkzZyRJvXv31tChQ/O8QAAAAGRfhQoV5OzsrPj4eHPbpUuXdOTIEUlSeHi40tLSLPZfvHhRhw8fVtWqVSVJlStX1u7duy36vX0b/0+OA/ULL7wgZ2dnnTp1Sh4eHub2rl27asOGDXlaHAAAAHLGy8tLvXv31vDhw7Vlyxb9/PPPio6OloPDzdgXFham9u3bq2/fvtq+fbv279+vp556SqVLl1b79u0lSQMGDNAXX3yhWbNm6ejRo3rjjTf05Zdf8nm5O8hxoP7qq680bdo088L2W8LCwvT777/nWWEAAADInVdffVXNmjXTo48+qsjISDVt2lT16tUz71+yZInq1aundu3aKSIiQoZh6IsvvjDfva1JkyZatGiRZs2apVq1amnDhg164YUX5ObmZq9LKtBy/KHE5ORki5npW/7++2+5urrmSVEAAAAFWUF/cqGXl5dWrFihFStWmNuGDx9u/rp48eJavnz5Xfvo27ev+vbta7FdsWLFvC+2CMjxDHWzZs0sfgAmk0kZGRmaPn26HnzwwTwtDgAAAPYxY8YM7d+/X8eOHdO8efO0bNkyRUVF2busAinHM9TTp09Xy5YttWfPHqWmpmrEiBH65Zdf9Pfff2vHjh22qBEAAAD5bNeuXZo+fbquXr2q8uXLa+7cuerTp4+9yyqQchyoq1evriNHjmj+/Pny9vZWUlKSOnXqpJiYGAUGBtqiRgAAAOSzVatW2buEQiPHgVqSihUrptGjR+d1LQAAAEChk+M11EuWLNFHH32Uqf2jjz7SsmXL8qQoAAAAoLDIcaCeOnWq7rvvvkztpUqV0pQpU/KkKAAAAKCwyHGgPnXqlEJDQzO1h4SE6NSpU3lSFAAAAFBY5DhQlypVSj/99FOm9v3798vPzy9PigIAAAAKixwH6u7du2vgwIH65ptvlJ6ervT0dG3ZskWDBg1St27dbFEjAAAAUGDl+C4fEydO1MmTJ9WyZUs5Od08PSMjQz179mQNNQAAQAHVokUL1a5dW7Nnz7Z3KUVOjgK1YRg6e/asli5dqkmTJikhIUHu7u6qUaOGQkJCbFUjAABAgTJzYN18HW/o3H35Oh5yJseBumLFivrll18UFhamsLAwW9UFAACAQiQ1NVUuLi72LsMucrSG2sHBQWFhYbp48aKt6gEAAICVkpOT1bNnT3l5eSkwMFAzZ8602J+SkqJhw4apdOnS8vT0VKNGjbR161aLY7Zv365mzZrJ3d1dwcHBGjhwoJKTk837y5Urp4kTJ6pnz57y8fFRv3798uPSCqQcr6F+5ZVXNHz4cC1cuFDVq1e3RU0AgDwQ3quxvUsAYCfDhw/Xtm3btG7dOpUqVUovvfSS9u3bp9q1a0uSYmNjdfDgQX3wwQcKCgrSmjVr9Mgjj+jAgQMKCwvT8ePH9cgjj2jSpEl65513dOHCBcXGxio2NlZLliwxjzNjxgyNHTtW48aNs9OVFgw5DtQ9e/bUtWvXVKtWLbm4uMjd3d1i/99//51nxQEAACBnkpKS9Pbbb+vdd99Vy5YtJUnLli1TmTJlJN18psiSJUt06tQpBQUFSZKGDRumDRs2aMmSJZoyZYqmTp2qHj16aPDgwZKksLAwzZ07V82bN9fChQvl5uYmSXrooYc0dOjQ/L/IAibHgZpPhgIAABRcx48fV2pqqho1amRuK1GihCpXrixJOnDggNLT01WpUiWL81JSUszPFNm/f79++uknrVy50rzfMAxlZGToxIkTCg8PlyTVr1/f1pdTKOQ4UEdFRdmijkJn9Srbf9q2TW2bDwEAAO4xSUlJcnR01N69e+Xo6Gixz8vLy3zMs88+q4EDB2Y6v2zZsuavPT09bVtsIZHjQC3d/M1nyZIlOn78uObMmaNSpUrpyy+/VNmyZVWtWrW8rhEAAADZVKFCBTk7Oys+Pt4cfi9duqQjR46oefPmqlOnjtLT03X+/Hk1a9Ysyz7q1q2rgwcPqmLFivlZeqGV4yclbtu2TTVq1FB8fLxWr16tpKQkSTf/NHCvL0gHAACwNy8vL/Xu3VvDhw/Xli1b9PPPPys6OloODjdjX6VKldSjRw/17NlTq1ev1okTJ7Rr1y5NnTpVn3/+uSRp5MiR+v777xUbG6uEhAQdPXpU69atU2xsrD0vrcDK8Qz1qFGjNGnSJA0ZMkTe3t7m9oceekjz58/P0+IAAAAKooL+oJVXX31VSUlJevTRR+Xt7a2hQ4fqypUr5v1LlizRpEmTNHToUP3xxx+67777dP/996tdu3aSpJo1a2rbtm0aPXq0mjVrJsMwVKFCBXXt2tVel1Sg5ThQHzhwQO+9916m9lKlSumvv/7Kk6IAAACQe15eXlqxYoVWrFhhbhs+fLj5a2dnZ8XFxSkuLu6OfTRo0EBfffXVHfefPHkyT2otCnK85MPX11dnzpzJ1P7jjz+qdOnSeVIUAAAAUFjkOFB369ZNI0eO1NmzZ2UymZSRkaEdO3Zo2LBh6tmzpy1qBAAAAAqsHAfqKVOmqEqVKgoODlZSUpKqVq2qBx54QI0bN9bLL79sixoBAACAAivHa6hdXFy0ePFijR07VgcOHFBSUpLq1KmjsLAwW9QHAAAAFGjZDtQZGRl69dVXtX79eqWmpqply5YaN25cpkePAwAAAPeSbC/5mDx5sl566SV5eXmpdOnSmjNnjmJiYmxZGwAAAFDgZTtQL1++XAsWLNDGjRu1du1affrpp1q5cqUyMjJsWR8AAABQoGU7UJ86dUpt2rQxb0dGRspkMunPP/+0SWEAAABAYZDtQJ2WliY3NzeLNmdnZ924cSPPiwIAAAAKi2x/KNEwDEVHR8vV1dXcdv36dT333HPy9PQ0t61evTpvKwQAAAAKsGwH6qioqExtTz31VJ4WA+RG4wlt/vsgAADy0BcJsfk6Xpva8/NtrPHjx2vt2rVKSEjItzELu2wH6iVLltiyDiDXVi5/2+ZjDK3PHW0AAEDWcvykRAAAABRsGRkZmj59uipWrChXV1eVLVtWkydPliSNHDlSlSpVkoeHh8qXL68xY8aYPxO3dOlSxcXFaf/+/TKZTDKZTFq6dKkdr6RwyPGTEgEAAFCwvfjii1q8eLFee+01NW3aVGfOnNGvv/4qSfL29tbSpUsVFBSkAwcOqG/fvvL29taIESPUtWtX/fzzz9qwYYO+/vprSVKxYsXseSmFAoEaAACgCLl69armzJmj+fPnmz8DV6FCBTVt2lSS9PLLL5uPLVeunIYNG6YPPvhAI0aMkLu7u7y8vOTk5KSAgAC71F8YEagBAACKkEOHDiklJUUtW7bMcv+HH36ouXPn6vjx40pKSlJaWpp8fHzyucqipVCtoX7llVdkMpk0ePBgc9v169cVExMjPz8/eXl5qXPnzjp37pz9igQAALAjd3f3O+7buXOnevTooTZt2uizzz7Tjz/+qNGjRys1NTUfKyx6Ck2g3r17t9544w3VrFnTov2FF17Qp59+qo8++kjbtm3Tn3/+qU6dOtmpSgAAAPsKCwuTu7u7Nm/enGnf999/r5CQEI0ePVr169dXWFiYfv/9d4tjXFxclJ6enl/lFgmFYslHUlKSevToocWLF2vSpEnm9itXrujtt9/We++9p4ceekjSzdv7hYeH64cfftD9999vr5IBAADsws3NTSNHjtSIESPk4uKiJk2a6MKFC/rll18UFhamU6dO6YMPPlCDBg30+eefa82aNRbnlytXTidOnFBCQoLKlCkjb29viwf7IbNCEahjYmLUtm1bRUZGWgTqvXv36saNG4qMjDS3ValSRWXLltXOnTvvGKhTUlKUkpJi3k5MTLRd8QAAoMjJzwet5MaYMWPk5OSksWPH6s8//1RgYKCee+459e7dWy+88IJiY2OVkpKitm3basyYMRo/frz53M6dO2v16tV68MEHdfnyZS1ZskTR0dF2u5bCoMAH6g8++ED79u3T7t27M+07e/asXFxc5Ovra9Hu7++vs2fP3rHPqVOnKi4uLq9LBQAAKBAcHBw0evRojR49OtO+6dOna/r06RZt//58mqurqz7++GNbl1ikFOg11KdPn9agQYO0cuVKubm55Vm/L774oq5cuWJ+nT59Os/6BgAAwL2lQAfqvXv36vz586pbt66cnJzk5OSkbdu2ae7cuXJycpK/v79SU1N1+fJli/POnTt313snurq6ysfHx+IFAAAA5EaBXvLRsmVLHThwwKLtmWeeUZUqVTRy5EgFBwfL2dlZmzdvVufOnSVJhw8f1qlTpxQREWGPkgEAKJL27V2VD6PUzocxgLxXoAO1t7e3qlevbtHm6ekpPz8/c3vv3r01ZMgQlShRQj4+PhowYIAiIiK4wwcAAADyRYEO1Nnx2muvycHBQZ07d1ZKSopatWqlBQsW2LssAADMHvpf3n0OCEDBU+gC9datWy223dzc9Prrr+v111+3T0EAAAC4pxXoDyUCAAAABR2BGgAAALACgRoAAACwAoEaAACgiGnRooXF0w9vV65cOc2ePTvH/Y4fP161a9fOdV1FVaH7UCIAAIC99Xmpcb6O99aU7/O0v927d8vT0zNP+7yXEagBAAXW6lX7bD5Gm9o2HwIocEqWLHnX/Tdu3JCzs3M+VVP4seQDAACgCEpLS1NsbKyKFSum++67T2PGjJFhGJIyL/kwmUxauHChHnvsMXl6emry5MmSpFdeeUX+/v7y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SBkZGapevbpSU1PveI6np2e+jevs7Gz+2mQySZIyMjIkSUlJSXr22Wc1cODATP2XLVvW6hrzGoEaAACgiLl48aIOHz6sxYsXq1mzZpJufsgvN06dOqU///xTQUFBkqQffvhBDg4Oqly5ss3GrVu3rg4ePKiKFSvmqub8RqAGAAAoYooXLy4/Pz+9+eabCgwM1KlTpzRq1Khc9eXm5qaoqCjNmDFDiYmJGjhwoJ544oks10/n1bgjR47U/fffr9jYWPXp00eenp46ePCgNm3apPnz5+fqOmyJNdQAAABFjIODgz744APt3btX1atX1wsvvKBXX301V31VrFhRnTp1Ups2bfTwww+rZs2aWrAg69v75tW4NWvW1LZt23TkyBE1a9ZMderU0dixY82z5AUNM9QAUEStXrXPpv23qW3T7oECLa8ftGILkZGROnjwoEWbYRhZft2iRQuL7dv1799f/fv3z3Lf0qVLczRuuXLlMo3l6+ubqa1Bgwb66quv7lhTQUKgBoAiivvMA0D+YMkHAAAAYAUCNQAAALI0fvz4uz5ZETcV6EA9depUNWjQQN7e3ipVqpQ6dOiQ6Sbi169fV0xMjPz8/OTl5aXOnTvr3LlzdqoYAAAA95oCHai3bdummJgY/fDDD9q0aZNu3Lihhx9+WMnJyeZjXnjhBX366af66KOPtG3bNv3555/q1KmTHasGAADAvaRAfyhxw4YNFttLly5VqVKltHfvXj3wwAO6cuWK3n77bb333nt66KGHJElLlixReHi4fvjhB91///32KBsAABQhd7v7BYq27P7sC/QM9e2uXLkiSSpRooQkae/evbpx44YiIyPNx1SpUkVly5bVzp0779hPSkqKEhMTLV4AAAD/duvR2NeuXbNzJbCXWz/7fz8mPSsFeob63zIyMjR48GA1adJE1atXlySdPXtWLi4u8vX1tTjW399fZ8+evWNfU6dOVVxcnC3LBQAAhZyjo6N8fX11/vx5SZKHh4dMJpOdq0J+MAxD165d0/nz5+Xr6ytHR8e7Hl9oAnVMTIx+/vnnXD+H/t9efPFFDRkyxLydmJio4OBgq/sFAABFy63Ha98K1bi3+Pr6ZvmI9dsVikAdGxurzz77TN9++63KlCljbg8ICFBqaqouX75sMUt97ty5u168q6urXF1dbVkyAAAoAkwmkwIDA1WqVCnd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", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax, df = plot_label_distributions(\n", + " partitioner,\n", + " label_name=\"label\",\n", + " plot_type=\"bar\",\n", + " size_unit=\"percent\",\n", + " partition_id_axis=\"x\",\n", + " legend=True,\n", + " verbose_labels=True,\n", + " cmap=\"tab20b\",\n", + " title=\"Per Partition Labels Distribution\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "e7d1fe2e1c0f14c", + "metadata": {}, + "source": [ + "### Heatmap" + ] + }, + { + "cell_type": "markdown", + "id": "ad6a2e53de3cc084", + "metadata": {}, + "source": [ + "You might want to visualize the results of partitioning as a heatmap, which can be especially useful for binary labels. Here's how:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14a4b4e574866120", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, ax, df = plot_label_distributions(\n", + " partitioner,\n", + " label_name=\"label\",\n", + " plot_type=\"heatmap\",\n", + " size_unit=\"absolute\",\n", + " partition_id_axis=\"x\",\n", + " legend=True,\n", + " verbose_labels=True,\n", + " title=\"Per Partition Labels Distribution\",\n", + " plot_kwargs={\"annot\": True},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5167593e67fa3dbb", + "metadata": {}, + "source": [ + "Note: we used the `plot_kwargs={\"annot\": True}` to add the number directly to the plot." + ] + }, + { + "cell_type": "markdown", + "id": "e2e41273551ac32a", + "metadata": {}, + "source": [ + "If you are a `pandas` fan, then you might be interested that a similar heatmap can be created with the DataFrame object for visualization in jupyter notebook:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fcc90b52bfd650cf", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
     airplaneautomobilebirdcatdeerdogfroghorseshiptruck
    Partition ID          
    08177941462212343225456384149
    11416697530340903868
    2041124543511158421
    37621591100511201662198213512175
    424371421924004251151477
    5677917025255247727445900
    6422244863809290380506
    7122281159721741038172716825154
    825629342751848151122401417
    91136107350357126711223
    \n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.style.background_gradient(axis=None, cmap=\"Greens\", vmin=0)" + ] + }, + { + "cell_type": "markdown", + "id": "37d85e1b40d54918", + "metadata": {}, + "source": [ + "## Plot Comparison of Label Distributions" + ] + }, + { + "cell_type": "markdown", + "id": "4f49259a3de7dd17", + "metadata": {}, + "source": [ + "Now, once you know how to visualize a single partitioned dataset, you'll learn how to compare a few of them on a single plot.\n", + "\n", + "Let's compare:\n", + "\n", + "- IidPartitioner,\n", + "- DirichletPartitioner,\n", + "- ShardPartitioner\n", + "still using the `cifar10` dataset.\n", + "\n", + "We need to create a list of partitioners. Each partitioner needs to have a dataset assigned to it (it does not have to be the same dataset so you can also compare the same partitioning on different datasets)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9e84a9192a266f3e", + "metadata": {}, + "outputs": [], + "source": [ + "from flwr_datasets import FederatedDataset\n", + "from flwr_datasets.partitioner import (\n", + " IidPartitioner,\n", + " DirichletPartitioner,\n", + " ShardPartitioner,\n", + ")\n", + "\n", + "partitioner_list = []\n", + "title_list = [\"IidPartitioner\", \"DirichletPartitioner\", \"ShardPartitioner\"]\n", + "\n", + "## IidPartitioner\n", + "fds = FederatedDataset(\n", + " dataset=\"cifar10\",\n", + " partitioners={\n", + " \"train\": IidPartitioner(num_partitions=10),\n", + " },\n", + ")\n", + "partitioner_list.append(fds.partitioners[\"train\"])\n", + "\n", + "## DirichletPartitioner\n", + "fds = FederatedDataset(\n", + " dataset=\"cifar10\",\n", + " partitioners={\n", + " \"train\": DirichletPartitioner(\n", + " num_partitions=10,\n", + " partition_by=\"label\",\n", + " alpha=1.0,\n", + " min_partition_size=0,\n", + " ),\n", + " },\n", + ")\n", + "partitioner_list.append(fds.partitioners[\"train\"])\n", + "\n", + "## ShardPartitioner\n", + "fds = FederatedDataset(\n", + " dataset=\"cifar10\",\n", + " partitioners={\n", + " \"train\": ShardPartitioner(\n", + " num_partitions=10, partition_by=\"label\", num_shards_per_partition=2\n", + " )\n", + " },\n", + ")\n", + "partitioner_list.append(fds.partitioners[\"train\"])" + ] + }, + { + "cell_type": "markdown", + "id": "d18bae80", + "metadata": {}, + "source": [ + "Now let's visualize them side by side" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2ee2864", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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+7wm9ml10URu6LKSlNfiv58hsjTwS4HU5vALzf1+JfVp5zRf2yFQKcczlURERERERKQyFpVERERERESkMhaVREREREREpDKdKypfvHiB4OBgODk5wcTEBM2aNcPFixe1HRYREREREVGZpHNF5dChQ3H06FFs3LgRN27cQNu2beHn54fHj/mgBSIiIiIiouKmU0Vleno6du7cifnz56Nly5Zwd3dHSEgI3N3dER4eru3wiIiIiIiIyhydeqVIVlYWsrOzYWxsrDDexMQEp0+fznOejIwMZGRkyIdTUlI0GiMREREREVFZolNnKi0sLNC0aVPMnj0bT548QXZ2NjZt2oRz587h6dOnec4TFhYGKysr+cfR0bGYoyYiIiIiIiq9dKqoBICNGzdCCIHKlStDKpVi+fLl6Nu3L/T08l6VqVOnIjk5Wf559OhRMUdMRERERERUeunU5a8A4ObmhlOnTiEtLQ0pKSmwt7dH79694erqmmd7qVQKqVRazFESERERERGVDTp3pjKHmZkZ7O3tkZiYiMOHD6Nz587aDomIiIiIiKjM0bkzlYcPH4YQAtWrV8eDBw8wadIkeHp6YtCgQdoOjYiIiIiIqMzRuTOVycnJGDNmDDw9PTFgwAC0aNEChw8fhqGhobZDIyIiIiIiKnN07kxlr1690KtXL22HQURERERERNDBM5VERERERERUckiEEELbQRSnlJQUWFlZITk5GZaWltoOh6hM0HTeMa+Jip8m8445TVT8mHf0PnimkoiIiIiIiFTGopKIiIiIiIhUxqKSiIiIiIiIVMaikoiIiIiIiFTGopKIiIiIiIhUxqKSiIiIiIiIVMaikoiIiIiIiFTGopKIiIiIiIhUxqKSiIiIiIiIVMaikoiIiIiIiFTGopKIiIiIiIhUxqKSiIiIiIiIVMaikoiIiIiIiFTGopKIiIiIiIhUxqKSiIiIiIiIVMaikoiIiIiIiFRmoO0AtGWbVQOYQl+tfTb2Vv/XWcXPSe19AoC0TTW19iep00Ct/SWYqLU7uSvxN9XaX2Rcolr7A4Bd9/5Te585Yi49Vmt/Ivy8Wvt7X9EfecNcX7m8Liy31J0j2qZKjiaYqD9nNEETeahp6s5zdeZ2ScprTfyuJvX66nsvbYdASvhryE5th0ClHM9UEhERERERkcpYVBIREREREZHKWFQSERERERGRylhUEhERERERkcp0qqjMzs7G9OnT4eLiAhMTE7i5uWH27NkQQmg7NCIiIiIiojJJp57+Om/ePISHh2PDhg2oVasWLl26hEGDBsHKygrjxo3TdnhERERERERljk4VlWfPnkXnzp3Rvn17AICzszN+/vln/PHHH1qOjIiIiIiIqGzSqctfmzVrhmPHjuHevXsAgOvXr+P06dMICAjId56MjAykpKQofIiIiIiIiEg9dOpM5RdffIGUlBR4enpCX18f2dnZmDt3Lvr375/vPGFhYQgNDS3GKImIiIiIiMoOnSoqt23bhs2bN+Onn35CrVq1cO3aNQQHB8PBwQEDBw7Mc56pU6diwoQJ8uGUlBQ4OjoWV8hERERERDotOzsbr1+/1nYYVIz09fVhYGAAiUSiVHudKionTZqEL774An369AEA1K5dGw8fPkRYWFi+RaVUKoVUKi3OMImIiIiISoXU1FT8/ffffNtCGWRqagp7e3sYGRkV2lanisqXL19CT0/xNlB9fX3IZDItRUREREREVDplZ2fj77//hqmpKWxtbZU+a0W6TQiBzMxMJCQkICYmBh4eHrlqsHfpVFHZsWNHzJ07F1WrVkWtWrVw9epVLF68GIMHD9Z2aEREREREpcrr168hhICtrS1MTEy0HQ4VIxMTExgaGuLhw4fIzMyEsbFxge11qqhcsWIFpk+fjtGjRyM+Ph4ODg4YMWIEZsyYoe3QiIiIiIhKJZ6hLJsKOzv5Np0qKi0sLLB06VIsXbpU26EQERERERERdOw9lURERERERFSySEQZe5RTSkoKrKyskJycDEtLS22HQ1QmaDrvmNdExU+TececJip+eeXdq1evEBMTAxcXl0LvqdN169evR3BwMJKSkt6rH4lEgt27d6NLly5qiUubirL9eaaSiIiIiIh0XmBgYKko5nQRi0oiIiIiIiJSGYtKIiIiIiIq1RYvXozatWvDzMwMjo6OGD16NFJTU3O127NnDzw8PGBsbAx/f388evRIYfrevXvRoEEDGBsbw9XVFaGhocjKyspzmZmZmQgKCoK9vT2MjY3h5OSEsLAwjayftrGoJCIiIiKiUk1PTw/Lly/HrVu3sGHDBhw/fhyTJ09WaPPy5UvMnTsXP/74I86cOYOkpCT06dNHPv3333/HgAED8Nlnn+H27dtYs2YN1q9fj7lz5+a5zOXL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uqKw40BJyuuE9J/xylADvwhIbSpO4P1kuqAk5XVxuHDhAo4dO4a2bdvCzs4OFy5cQEJCAmrUqIE///xTpT63b9+ORo0aoUWLFti8eTP++OMPrFu3Ts2R664yW1QSEREREVHR/TVkp7ZDKJClpSV+++03LF26FCkpKXBycsKiRYsQEBCArVu3qtRnaGgotmzZgtGjR8Pe3h4///wzatasqebIdReLSiIiIiIiKjVq1KiBQ4cO5Tktr1eHvP1OSgCIjY3N1cbBwQFHjhxRQ3SlE++pJCIiIiIiIpWxqCQiIiIiIiKV6VxR+fjxY3zyySewsbGBiYkJateujUuXLmk7LCIiIiIiKoWEEOjSpYu2wyjRdOqeysTERDRv3hwffvghDh48CFtbW9y/fx/lypXTdmhERERERERlkk4VlfPmzYOjoyMiIiLk41xcXAqcJyMjAxkZGfLhlJQUjcVHRERERERU1ujU5a+//PILGjVqhJ49e8LOzg7169fH2rVrC5wnLCwMVlZW8o+jo2MxRUtERERERFT66VRR+ddffyE8PBweHh44fPgwRo0ahXHjxmHDhg35zjN16lQkJyfLP48ePSrGiImIiIiIiEo3nbr8VSaToVGjRvj6668BAPXr18fNmzexevVqDBw4MM95pFIppFJpcYZJRERERERUZujUmUp7e3vUrFlTYVyNGjUQFxenpYiIiIiIiIjKNp06U9m8eXNERUUpjLt37x6cnJy0FBERERERUdky8fdhxbq8hT4FP0PlXa1atUK9evWwdOlSzQREuejUmcrx48fj/Pnz+Prrr/HgwQP89NNP+O677zBmzBhth0ZERERERFQm6VRR2bhxY+zevRs///wzvLy8MHv2bCxduhT9+/fXdmhERERERFQKZWZmajuEEk+nikoA6NChA27cuIFXr17hzp07GDaseE+/ExERERFRySaTyTB58mSUL18elSpVQkhIiHxaXFwcOnfuDHNzc1haWqJXr1549uyZfHpISAjq1auH77//Hi4uLjA2NgYA7NixA7Vr14aJiQlsbGzg5+eHtLQ0+Xzff/89atSoAWNjY3h6emLVqlXFtr7apvI9lampqYiNjcWLFy9gYWEBFxcXmJmZqTM2IiIiIiKiItuwYQMmTJiACxcu4Ny5cwgMDETz5s3RunVreUF56tQpZGVlYcyYMejduzdO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VBIREREREZHKWFQSERERERGRylhUEhERERERkcpYVBIREREREZHKWFQSERERERGRylhUEhERERERkcpYVBIREREREZHKWFQSERERERGRylhUEhERERERkcpYVBIREREREZHKWFQSERERERGRylhUEhERERERkcpYVBIREREREZHKWFQSERERERGRylhUEhERERERkcpYVBIREREREZHKDLQdgLZss2oAU+irpa/G3ur/Gqv4Oam1P2mbamrpR1KngVr6eVeCiUa6xZX4mxrpNzIuUSP9AsCue/+pvc+YS4/V3icAiPDzGulXVerMa1Vp4nhQVOo+fqhDUY9Byhxr3ue4kXNsUDWXNZGn2pDXsaEk5bX472eILA39giC1mHTrd22HQEpY6LNW2yFQKcczlURERERERKQyFpVERERERESkMhaVREREREREpDKdLCq//fZbODs7w9jYGE2aNMEff/yh7ZCIiIiIiIjKJJ0rKrdu3YoJEyZg5syZuHLlCurWrQt/f3/Ex8drOzQiIiIiIqIyR+eKysWLF2PYsGEYNGgQatasidWrV8PU1BQ//PCDtkMjIiIiIiIqc3SqqMzMzMTly5fh5+cnH6enpwc/Pz+cO3cuz3kyMjKQkpKi8CEiIiIiIiL10Kmi8t9//0V2djYqVqyoML5ixYr4559/8pwnLCwMVlZW8o+jo2NxhEpERERERFQm6FRRqYqpU6ciOTlZ/nn06JG2QyIiIiIiIio1DLQdQFFUqFAB+vr6ePbsmcL4Z8+eoVKlSnnOI5VKIZVKiyM8IiIiIiKiMkenzlQaGRmhYcOGOHbsmHycTCbDsWPH0LRpUy1GRkREREREVDbp1JlKAJgwYQIGDhyIRo0awdvbG0uXLkVaWhoGDRqk7dCIiIiIiIjKHJ0rKnv37o2EhATMmDED//zzD+rVq4dDhw7lengPERERERERaZ7OFZUAEBQUhKCgIG2HQUREREREVObp1D2VREREREREVLKwqCQiIiIiIiKVSYQQQttBFKeUlBRYWVkhOTkZlpaW2g6HqEzQdN4xr4mKnybzjjlNVPyYd/Q+eKaSiIiIiIiIVMaikoiIiIiIiFTGopKIiIiIiIhUxqKSiIiIiIiIVMaikoiIiIiIiFTGopKIiIiIiIhUZqDtAIpbzhtUUlJStBwJUdmRk2+aeoMR85qo+Gkyr5nTRMVP07+rqXQrc0Xl8+fPAQCOjo5ajoSo7Hnx4gWsrKzU3i/zmkh7NJHXzGki7dHU72oq3cpcUVm+fHkAQFxcXKlImJSUFDg6OuLRo0el5kW1XCfdUJR1EkLgxYsXcHBw0EgszOuSj+ukG0pKXpe2nAa4v+iKsrxOmv5dTaVbmSsq9fTe3EZqZWVVag4WAGBpaVmq1gfgOukKZddJk38YMq91B9dJN2g7r0trTgNle3/RJWV1nUrLP3Go+PFBPURERERERKQyFpVERERERESksjJXVEqlUsycORNSqVTboahFaVsfgOukK0rSOpWkWNShtK0PwHXSFSVlnUpKHOrEddINXCci1UgEnxtMREREREREKipzZyqJiIiIiIhIfVhUEhERERERkcpYVBIREREREZHKWFQSERERERGRyspUUfntt9/C2dkZxsbGaNKkCf744w9th6S0sLAwNG7cGBYWFrCzs0OXLl0QFRWl0KZVq1aQSCQKn5EjR2op4sKFhITkitfT01M+/dWrVxgzZgxsbGxgbm6O7t2749mzZ1qMuHDOzs651kkikWDMmDEASv42+u2339CxY0c4ODhAIpFgz549CtOFEJgxYwbs7e1hYmICPz8/3L9/X6HNf//9h/79+8PS0hLW1tYYMmQIUlNTNRYz87pkYV6XvG3EvC5epS2vmdMlc/voYl5T6VZmisqtW7diwoQJmDlzJq5cuYK6devC398f8fHx2g5NKadOncKYMWNw/vx5HD16FK9fv0bbtm2Rlpam0G7YsGF4+vSp/DN//nwtRaycWrVqKcR7+vRp+bTx48fj119/xfbt23Hq1Ck8efIE3bp102K0hbt48aLC+hw9ehQA0LNnT3mbkryN0tLSULduXXz77bd5Tp8/fz6WL1+O1atX48KFCzAzM4O/vz9evXolb9O/f3/cunULR48exb59+/Dbb79h+PDhGomXeV0yMa9L1jZiXhev0pjXzOmSt310La+pDBBlhLe3txgzZox8ODs7Wzg4OIiwsDAtRqW6+Ph4AUCcOnVKPs7X11d89tln2guqiGbOnCnq1q2b57SkpCRhaGgotm/fLh93584dAUCcO3eumCJ8f5999plwc3MTMplMCKFb2wiA2L17t3xYJpOJSpUqiQULFsjHJSUlCalUKn7++WchhBC3b98WAMTFixflbQ4ePCgkEol4/Pix2mNkXpc8zOuSjXld/HQ9r5nTJZ8u5DWVfmXiTGVmZiYuX74MPz8/+Tg9PT34+fnh3LlzWoxMdcnJyQCA8uXLK4zfvHkzKlSoAC8vL0ydOhUvX77URnhKu3//PhwcHODq6or+/fsjLi4OAHD58mW8fv1aYZt5enqiatWqOrPNMjMzsWnTJgwePBgSiUQ+Xte2UY6YmBj8888/CtvEysoKTZo0kW+Tc+fOwdraGo0aNZK38fPzg56eHi5cuKDWeJjXJRfzuuRvoxzMa80rDXnNnC7Z2+ddJS2vqWww0HYAxeHff/9FdnY2KlasqDC+YsWKuHv3rpaiUp1MJkNwcDCaN28OLy8v+fh+/frByckJDg4O+PPPPzFlyhRERUVh165dWow2f02aNMH69etRvXp1PH36FKGhofDx8cHNmzfxzz//wMjICNbW1grzVKxYEf/88492Ai6iPXv2ICkpCYGBgfJxuraN3pbzveeVRznT/vnnH9jZ2SlMNzAwQPny5dW+3ZjXJXOfYV6X/G30Nua1ZpWGvGZOl+ztk5eSltdUNpSJorK0GTNmDG7evKlwTwMAhevga9euDXt7e7Ru3RrR0dFwc3Mr7jALFRAQIP+5Tp06aNKkCZycnLBt2zaYmJhoMTL1WLduHQICAuDg4CAfp2vbiIoP81o3MK+pKEpDXjOnS/b2ISopysTlrxUqVIC+vn6up5E9e/YMlSpV0lJUqgkKCsK+fftw4sQJVKlSpcC2TZo0AQA8ePCgOEJ7b9bW1qhWrRoePHiASpUqITMzE0lJSQptdGWbPXz4EJGRkRg6dGiB7XRpG+V87wXlUaVKlXI9TCMrKwv//fef2rcb87rk7zMA87qkY15rTmnNa+Z0yVfS8prKhjJRVBoZGaFhw4Y4duyYfJxMJsOxY8fQtGlTLUamPCEEgoKCsHv3bhw/fhwuLi6FznPt2jUAgL29vYajU4/U1FRER0fD3t4eDRs2hKGhocI2i4qKQlxcnE5ss4iICNjZ2aF9+/YFttOlbeTi4oJKlSopbJOUlBRcuHBBvk2aNm2KpKQkXL58Wd7m+PHjkMlk8l/K6sK8Lvn7DMC8LumY1+pX2vOaOV3ylbS8pjJCyw8KKjZbtmwRUqlUrF+/Xty+fVsMHz5cWFtbi3/++UfboSll1KhRwsrKSpw8eVI8ffpU/nn58qUQQogHDx6IWbNmiUuXLomYmBixd+9e4erqKlq2bKnlyPP3+eefi5MnT4qYmBhx5swZ4efnJypUqCDi4+OFEEKMHDlSVK1aVRw/flxcunRJNG3aVDRt2lTLURcuOztbVK1aVUyZMkVhvC5soxcvXoirV6+Kq1evCgBi8eLF4urVq+Lhw4dCCCG++eYbYW1tLfbu3Sv+/PNP0blzZ+Hi4iLS09PlfbRr107Ur19fXLhwQZw+fVp4eHiIvn37aiRe5nXJw7wueduIeV28SlteM6dL5vbRtbym0q/MFJVCCLFixQpRtWpVYWRkJLy9vcX58+e1HZLSAOT5iYiIEEIIERcXJ1q2bCnKly8vpFKpcHd3F5MmTRLJycnaDbwAvXv3Fvb29sLIyEhUrlxZ9O7dWzx48EA+PT09XYwePVqUK1dOmJqaiq5du4qnT59qMWLlHD58WAAQUVFRCuN1YRudOHEiz/1s4MCBQog3jymfPn26qFixopBKpaJ169a51vP58+eib9++wtzcXFhaWopBgwaJFy9eaCxm5nXJwrwueduIeV28SlteM6dL5vbRxbym0k0ihBAaPBFKREREREREpViZuKeSiIiIiIiININFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJREREREREamMRSURERERERGpjEUlERERERERqYxFJRGRCpydnREYGKjtMJQSEhICiUSiMK644o+NjYVEIsH69evl4wIDA2Fubq7xZeeQSCQICQkptuURERGVNSwqiYjeEh0djREjRsDV1RXGxsawtLRE8+bNsWzZMqSnp2s7PK06cOBAiS3OSnJsREREpZ2BtgMgIiop9u/fj549e0IqlWLAgAHw8vJCZmYmTp8+jUmTJuHWrVv47rvvtB2mWkRFRUFPr2j/Vzxw4AC+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", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from flwr_datasets.visualization import plot_comparison_label_distribution\n", + "\n", + "fig, axes, df_list = plot_comparison_label_distribution(\n", + " partitioner_list=partitioner_list,\n", + " label_name=\"label\",\n", + " subtitle=\"Comparison of Partitioning Schemes on CIFAR10\",\n", + " titles=title_list,\n", + " legend=True,\n", + " verbose_labels=True,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "862899eb04695380", + "metadata": {}, + "source": [ + "## Bonus: Natural Id Dataset" + ] + }, + { + "cell_type": "markdown", + "id": "4f3f8aaf", + "metadata": {}, + "source": [ + "Nothing stops you from using the `NaturalIdPartitioner` to visualize a dataset with the `id` in it and does not need the artificial partitioning but has the pre-existing partitions. For that dataset, we use `NaturalIdPartitioner`. Let's look at the `speech-commands` dataset that has `speaker_id`, and there are quite a few speakers; therefore, we will show only the first 20 partitions. And since we have quite a few different labels, let's specify `legend_kwargs={\"ncols\": 2}` to display them in two columns (we will also shift the legend slightly to the right)." + ] + }, + { + "cell_type": "markdown", + "id": "f016d21a", + "metadata": {}, + "source": [ + "You'll be using the [Google SpeechCommands](https://huggingface.co/datasets/google/speech_commands) dataset, which is a speech-based dataset. For this, you'll need to install the `\"audio\"` extension for Flower Datasets. It can be easily done like this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd5ca8f4", + "metadata": {}, + "outputs": [], + "source": [ + "! pip install -q \"flwr-datasets[audio]\"" + ] + }, + { + "cell_type": "markdown", + "id": "90ea3642", + "metadata": {}, + "source": [ + "With everything ready, let's visualize the partitions for a naturally partitioned dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4fe70116", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
    " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from flwr_datasets import FederatedDataset\n", + "from flwr_datasets.partitioner import NaturalIdPartitioner\n", + "from flwr_datasets.visualization import plot_label_distributions\n", + "\n", + "\n", + "fds = FederatedDataset(\n", + " dataset=\"google/speech_commands\",\n", + " subset=\"v0.01\",\n", + " partitioners={\n", + " \"train\": NaturalIdPartitioner(\n", + " partition_by=\"speaker_id\",\n", + " ),\n", + " },\n", + ")\n", + "\n", + "partitioner = fds.partitioners[\"train\"]\n", + "\n", + "fix, ax, df = plot_label_distributions(\n", + " partitioner=partitioner,\n", + " label_name=\"label\",\n", + " max_num_partitions=20,\n", + " plot_type=\"bar\",\n", + " size_unit=\"percent\",\n", + " partition_id_axis=\"x\",\n", + " legend=True,\n", + " title=\"Per Partition Labels Distribution\",\n", + " verbose_labels=True,\n", + " legend_kwargs={\"ncols\": 2, \"bbox_to_anchor\": (1.25, 0.5)},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "4442c99c", + "metadata": {}, + "source": [ + "## More resources\n", + "\n", + "If you are looking for more resorces, feel free to check:\n", + "\n", + "* `flwr-dataset` documentation\n", + " * [plot_label_distributions](https://flower.ai/docs/datasets/ref-api/flwr_datasets.visualization.plot_label_distributions.html#flwr_datasets.visualization.plot_label_distributions)\n", + " * [plot_comparison_label_distribution](https://flower.ai/docs/datasets/ref-api/flwr_datasets.visualization.plot_comparison_label_distribution.html#flwr_datasets.visualization.plot_comparison_label_distribution)\n", + "* if you want to do any custom modification of the returned plots\n", + " * [matplotlib](https://matplotlib.org/)\n", + " * [seaborn](https://seaborn.pydata.org/)\n", + " * or plot directly using pandas object [pd.DataFrame.plot](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html)" + ] + }, + { + "cell_type": "markdown", + "id": "52655972", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "flwr", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/datasets/doc/source/index.rst b/datasets/doc/source/index.rst index 263aa4908d6d..bdcea7650bbc 100644 --- a/datasets/doc/source/index.rst +++ b/datasets/doc/source/index.rst @@ -41,6 +41,7 @@ Problem-oriented how-to guides show step-by-step how to achieve a specific goal. how-to-use-with-tensorflow how-to-use-with-numpy how-to-use-with-local-data + how-to-visualize-label-distribution how-to-disable-enable-progress-bar References From 01522cfbefefc38d4a138b18ed71eed19f54f508 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 24 Jun 2024 22:20:48 +0200 Subject: [PATCH 086/595] break(examples) Rename resplitter in examples (#3485) Co-authored-by: jafermarq --- examples/xgboost-comprehensive/client.py | 2 +- examples/xgboost-comprehensive/pyproject.toml | 2 +- examples/xgboost-comprehensive/requirements.txt | 2 +- examples/xgboost-comprehensive/server.py | 2 +- examples/xgboost-comprehensive/sim.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/xgboost-comprehensive/client.py b/examples/xgboost-comprehensive/client.py index 2d54c3fd63c7..08dd548a386b 100644 --- a/examples/xgboost-comprehensive/client.py +++ b/examples/xgboost-comprehensive/client.py @@ -32,7 +32,7 @@ fds = FederatedDataset( dataset="jxie/higgs", partitioners={"train": partitioner}, - resplitter=resplit, + preprocessor=resplit, ) # Load the partition for this `partition_id` diff --git a/examples/xgboost-comprehensive/pyproject.toml b/examples/xgboost-comprehensive/pyproject.toml index 2d44c06d6e3f..c9259ffa1db4 100644 --- a/examples/xgboost-comprehensive/pyproject.toml +++ b/examples/xgboost-comprehensive/pyproject.toml @@ -11,5 +11,5 @@ authors = ["The Flower Authors "] [tool.poetry.dependencies] python = ">=3.8,<3.11" flwr = { extras = ["simulation"], version = ">=1.7.0,<2.0" } -flwr-datasets = ">=0.1.0,<1.0.0" +flwr-datasets = ">=0.2.0,<1.0.0" xgboost = ">=2.0.0,<3.0.0" diff --git a/examples/xgboost-comprehensive/requirements.txt b/examples/xgboost-comprehensive/requirements.txt index 16eb78f484e3..840e19529953 100644 --- a/examples/xgboost-comprehensive/requirements.txt +++ b/examples/xgboost-comprehensive/requirements.txt @@ -1,3 +1,3 @@ flwr[simulation]>=1.7.0, <2.0 -flwr-datasets>=0.1.0, <1.0.0 +flwr-datasets>=0.2.0, <1.0.0 xgboost>=2.0.0, <3.0.0 diff --git a/examples/xgboost-comprehensive/server.py b/examples/xgboost-comprehensive/server.py index 939819641438..07dc4bed6db4 100644 --- a/examples/xgboost-comprehensive/server.py +++ b/examples/xgboost-comprehensive/server.py @@ -32,7 +32,7 @@ # Load centralised test set if centralised_eval: fds = FederatedDataset( - dataset="jxie/higgs", partitioners={"train": 20}, resplitter=resplit + dataset="jxie/higgs", partitioners={"train": 20}, preprocessor=resplit ) log(INFO, "Loading centralised test set...") test_set = fds.load_split("test") diff --git a/examples/xgboost-comprehensive/sim.py b/examples/xgboost-comprehensive/sim.py index c9481f1cdd5d..09ebbb81fcb4 100644 --- a/examples/xgboost-comprehensive/sim.py +++ b/examples/xgboost-comprehensive/sim.py @@ -80,7 +80,7 @@ def main(): fds = FederatedDataset( dataset="jxie/higgs", partitioners={"train": partitioner}, - resplitter=resplit, + preprocessor=resplit, ) # Load centralised test set From 6f5d92f0603098b9fcc9ac6b98c603cd2df3d759 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Tue, 25 Jun 2024 09:28:56 +0200 Subject: [PATCH 087/595] docs(framework) Add latest Hosted Weblate translation updates (#3679) Co-authored-by: Sijiaomg Ohoh --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 177 +++++++++++++++---- 1 file changed, 142 insertions(+), 35 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 3c41a8647c35..89b89de22be4 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-06-17 16:09+0200\n" -"PO-Revision-Date: 2024-06-23 14:41+0000\n" -"Last-Translator: 박태현 \n" +"PO-Revision-Date: 2024-06-25 02:09+0000\n" +"Last-Translator: Sijiaomg Ohoh \n" "Language-Team: Korean \n" "Language: ko\n" @@ -8575,7 +8575,7 @@ msgstr ":py:obj:`context `\\" #: flwr.simulation.app.start_simulation #: flwr.simulation.run_simulation.run_simulation of msgid "Parameters" -msgstr "매개변수" +msgstr "파라미터" #: flwr.client.client.Client.evaluate:3 of msgid "" @@ -22510,6 +22510,10 @@ msgid "" "developed that keyboard, do you? In fact, that use case was the reason " "federated learning was invented in the first place." msgstr "" +"**사용자 선호도**:규정 외에도 일부 사용 사례에서 사용자는 데이터가 자기 " +"장치를 떠나지 않기를 예상합니다. 휴대폰의 디지털 키보드에 비밀번호와 " +"신용카드 정보를 입력하면 비밀번호가 해당 키보드를 개발한 회사의 서버에 뜨길 " +"원하지는 않겠죠? 사실, 이 사용 사례가 애당초 연합 학습이 발명된 이유였습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:161 msgid "" @@ -22522,29 +22526,36 @@ msgid "" "exceedingly expensive infrastructure to process and store. And most of the " "data isn't even useful." msgstr "" +"**데이터 볼륨**:일부 센서(예:카메라)는 너무 많은 데이터 볼륨을 생성하여 모든 " +"데이터를 수집하는 것이 실현 가능하지도 않고 경제적이지도 않습니다(예: 대역폭 " +"또는 통신 효율로 인해). 전국에 수백 개 기차역이 있는 국가 철도 서비스를 " +"생각해 보세요. 각 기차역에 수 많은 보안 카메라가 설치되어 있다면,그들이 " +"생산하는 대량의 원시 온디바이스 데이터는 처리 및 저장을 위해 엄청나게 " +"강력하고 매우 비싼 인프라를 필요로 합니다.그런데 대부분의 데이터는 " +"유용하지도 않습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:164 msgid "Examples where centralized machine learning does not work include:" -msgstr "" +msgstr "중앙 집중식 머신러닝이 작동하지 않는 예는 다음과 같습니다:" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:166 msgid "" "Sensitive healthcare records from multiple hospitals to train cancer " "detection models" -msgstr "" +msgstr "여러 병원의 민감한 의료기록으로 암 검진 모델 훈련" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:167 msgid "" "Financial information from different organizations to detect financial fraud" -msgstr "" +msgstr "금융 사기를 탐지하기 위한 다양한 조직의 금융 정보" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:168 msgid "Location data from your electric car to make better range prediction" -msgstr "" +msgstr "더 나은 범위 예측을 위해 전기 자동차의 위치 데이터" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:169 msgid "End-to-end encrypted messages to train better auto-complete models" -msgstr "" +msgstr "더 나은 자동 완성 모델을 훈련시키기 위한 엔드 투 엔드 암호화된 메시지" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:171 msgid "" @@ -22556,10 +22567,17 @@ msgid "" "private data? After all, these are all areas that would benefit " "significantly from recent advances in AI." msgstr "" +"`Brave `__ 브라우저나 `Signal `__ " +"메신저와 같은 개인 정보 보호 시스템의 인기는 사용자들이 개인 정보 보호에 " +"신경 쓴다는 것을 보여줍니다. 실제로 그러한 대안이 존재하는 경우 다른 " +"대안보다 개인 정보 보호 강화 버전을 선택합니다. 그런데 이러한 사례에 머신 " +"러닝 및 데이터 과학을 적용하여 프라이버시 데이터를 활용하려면 어떻게 해야 " +"합니까? 이 모든 분야는 최근 AI의 발전으로 상당한 이익을 얻을 수 있는 " +"분야입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:186 msgid "Federated learning" -msgstr "" +msgstr "연방 학습" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:188 msgid "" @@ -22567,14 +22585,17 @@ msgid "" "learning on distributed data by moving the training to the data, instead of " "moving the data to the training. Here's the single-sentence explanation:" msgstr "" +"연방 학습은 이 방법을 쉽게 뒤집었습니다. 데이터를 훈련으로 옮기는 대신 " +"데이터로 훈련을 전환함으로써 분산된 데이터에서 머신러닝을 실현합니다.다음은 " +"한마디로 설명입니다:" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:190 msgid "Central machine learning: move the data to the computation" -msgstr "" +msgstr "중앙 집중식 머신러닝: 데이터를 컴퓨팅 센터로 이동" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:191 msgid "Federated (machine) learning: move the computation to the data" -msgstr "" +msgstr "연방(기계)학습: 컴퓨팅을 데이터로 옮김" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:193 msgid "" @@ -22589,20 +22610,28 @@ msgid "" "more and more areas that can suddenly be reinvented because they now have " "access to vast amounts of previously inaccessible data." msgstr "" +"이를 통해 이전에는 불가능했던 분야에서 머신 러닝(및 기타 데이터 과학 방법)을 " +"사용할 수 있습니다. 이제 다양한 병원이 협력할 수 있도록 함으로써 우수한 의료 " +"AI 모델을 훈련할 수 있습니다. 다양한 금융 기관의 데이터에 대한 AI 모델을 " +"훈련하여 금융 사기를 해결할 수 있습니다. 개인 정보 보호를 강화하지 않는 " +"대안보다 더 나은 AI가 내장된 새로운 개인 정보 보호 강화 애플리케이션(예: " +"보안 메시징)을 구축할 수 있습니다.그것들은 떠오르는 몇 가지 예에 불과합니다. " +"연합 학습을 구축함에 따라 이전에 액세스할 수 없었던 많은 데이터에 액세스할 " +"수 있게 되었기 때문에 갑자기 재생될 수 있는 영역이 점점 더 많아지고 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:196 msgid "" "So how does federated learning work, exactly? Let's start with an intuitive " "explanation." -msgstr "" +msgstr "그렇다면 연방 학습은어떻게 작동합니까?직관적인 설명부터 시작하겠습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:199 msgid "Federated learning in five steps" -msgstr "" +msgstr "연방 학습의 5단계" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:202 msgid "Step 0: Initialize global model" -msgstr "" +msgstr "0단계: 전역 모델 초기화" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:204 msgid "" @@ -22610,20 +22639,23 @@ msgid "" "in classic centralized learning: we initialize the model parameters, either " "randomly or from a previously saved checkpoint." msgstr "" +"서버에서 모델을 초기화하는 것으로 시작합니다. 클래식 중앙 집중식 학습에서도 " +"완전히 동일합니다: 임의로 또는 이전에 저장된 체크포인트에서 모델 매개변수를 " +"초기화합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 msgid "|c7afb4c92d154bfaa5e8cb9a150e17f1|" -msgstr "" +msgstr "|c7afb4c92d154bfaa5e8cb9a150e17f1|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 msgid "Initialize global model" -msgstr "" +msgstr "전역 모델 초기화" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:217 msgid "" "Step 1: Send model to a number of connected organizations/devices (client " "nodes)" -msgstr "" +msgstr "1단계: 연결된 여러 조직/장치(클라이언트 노드)에 모델 전송" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:219 msgid "" @@ -22634,20 +22666,25 @@ msgid "" "the connected nodes instead of all nodes. The reason for this is that " "selecting more and more client nodes has diminishing returns." msgstr "" +"다음으로 글로벌 모델의 파라미터를 연결된 클라이언트 노드(예: 스마트폰과 같은 " +"에지 디바이스 또는 조직에 속한 서버)로 보냅니다. 이것은 각 참여 노드가 " +"동일한 모델 매개변수를 사용하여 로컬 훈련을 시작하도록 하기 위함입니다. " +"일반적으로 모든 노드가 아닌 몇 개의 연결 노드만 사용합니다. 그 이유는 점점 " +"더 많은 클라이언트 노드를 선택하면 수익률이 감소하는 것입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 msgid "|032eb6fed6924ac387b9f13854919196|" -msgstr "" +msgstr "|032eb6fed6924ac387b9f13854919196|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 msgid "Send global model" -msgstr "" +msgstr "전역 모델 전송" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:232 msgid "" "Step 2: Train model locally on the data of each organization/device (client " "node)" -msgstr "" +msgstr "2단계: 각 조직/장치(클라이언트 노드)의 데이터에 대해 로컬로 모델 훈련" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:234 msgid "" @@ -22658,18 +22695,23 @@ msgid "" "little as one epoch on the local data, or even just a few steps (mini-" "batches)." msgstr "" +"이제 모든(선택된) 클라이언트 노드에는 최신 버전의 글로벌 모델 파라미터가 " +"있으며 로컬 훈련을 시작합니다. 그들은 자신의 로컬 데이터 세트를 사용하여 " +"자신의 로컬 모델을 훈련합니다. 모델이 완전히 수렴할 때까지 훈련하지 않고 " +"잠시만 훈련합니다. 이는 로컬 데이터에서 한 단계 정도로 짧거나 몇 단계(mini-" +"batches)에 불과할 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 msgid "|fbf225add7fd4df5a9bf25a95597d954|" -msgstr "" +msgstr "|fbf225add7fd4df5a9bf25a95597d954|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 msgid "Train on local data" -msgstr "" +msgstr "로컬 데이터에 대한 훈련" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:247 msgid "Step 3: Return model updates back to the server" -msgstr "" +msgstr "3단계: 모델 파라미터를 업데이트하여 서버로 되돌리기" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:249 msgid "" @@ -22680,18 +22722,23 @@ msgid "" "The model updates they send can either be the full model parameters or just " "the gradients that were accumulated during local training." msgstr "" +"로컬 훈련 후에는 클라이언트 노드마다 원래 받은 모델 파라미터의 버전이 조금씩 " +"다릅니다. 파라미터가 다른 이유는 각 클라이언트 노드의 로컬 데이터 세트에 " +"다른 데이터가 있기 때문입니다. 그런 다음 클라이언트 노드는 이러한 모델 " +"업데이트를 서버로 다시 보냅니다. 보내는 모델 업데이트는 전체 모델 " +"파라미터거나 로컬 교육 중에 누적된 그래디언트일 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 msgid "|7efbe3d29d8349b89594e8947e910525|" -msgstr "" +msgstr "|7efbe3d29d8349b89594e8947e910525|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 msgid "Send model updates" -msgstr "" +msgstr "모델 업데이트 전송" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:262 msgid "Step 4: Aggregate model updates into a new global model" -msgstr "" +msgstr "4단계: 모델 업데이트를 새 글로벌 모델로 집계" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:264 msgid "" @@ -22701,6 +22748,11 @@ msgid "" "didn't we want to have one model that contains the learnings from the data " "of all 100 client nodes?" msgstr "" +"서버는 선택된 클라이언트 노드들로부터 모델 업데이트들을 수신합니다. 서버가 " +"100개의 클라이언트 노드를 선택했다면 이제 각각 클라이언트의 로컬 데이터를 " +"기반으로 훈련된 100개의 약간 다른 원래 글로벌 모델 버전을 갖게 됩니다. " +"하지만 우리는 100개의 모든 클라이언트 노드의 데이터에서 학습한 내용을 " +"포함하는 모델을 하나 갖고 싶지 않았습니까?" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:266 msgid "" @@ -22717,18 +22769,29 @@ msgid "" "examples, then - without weighting - each of the 10 examples would influence " "the global model ten times as much as each of the 100 examples." msgstr "" +"단일 모델 하나를 얻으려면 클라이언트 노드에서 받은 모든 모델 업데이트를 " +"결합해야 합니다. 이 과정이 *집합*라고 하며 여러 가지 방법이 있습니다. 가장 " +"기본적인 방법은*Federated Averaging* (`McMahan et al., 2016 `__)이라고 하고 보통 줄여서 *FedAvg*로 표기합니다. " +"*FedAvg* 는 100개의 모델 업데이트를 받아 이름에서 알 수 있듯이 모델 " +"업데이트를 평균화합니다. 더 정확히 말하면, 모델 업데이트의 *가중 평균* 을 각 " +"클라이언트가 훈련에 사용한 예제 수에 따라 가중치를 부여합니다. 가중치는 각 " +"데이터 예제가 결과 글로벌 모델에 동일한 \"영향\" 을 미치는지 확인하는 데 " +"중요합니다. 한 클라이언트에 10개의 데이터 포인트가 있고 다른 클라이언트에 " +"100개의 데이터 포인트가 있다면 가중치를 부여하지 않고 10개의 예가 100개의 " +"사례보다 글로벌 모델에 10배 더 많은 영향을 미칩니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 msgid "|329fb3c04c744eda83bb51fa444c2266|" -msgstr "" +msgstr "|329fb3c04c744eda83bb51fa444c2266|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 msgid "Aggregate model updates" -msgstr "" +msgstr "모델 업데이트 집계" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:280 msgid "Step 5: Repeat steps 1 to 4 until the model converges" -msgstr "" +msgstr "5단계: 모델이 수렴할 때까지 1~4단계를 반복합니다" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:282 msgid "" @@ -22738,6 +22801,11 @@ msgid "" "models to the server (step 3), and the server then aggregates the model " "updates to get a new version of the global model (step 4)." msgstr "" +"단계 1에서 4는 우리가 말하는 단일 라운드 연방 학습입니다. 글로벌 모델 " +"파라미터는 참여하는 클라이언트 노드에 전송되고(1단계), 클라이언트 노드는 " +"로컬 데이터에 대한 훈련을 받고(2단계), 업데이트된 모델을 서버에 " +"전송하고(3단계), 서버는 모델 업데이트를 집계하여 글로벌 모델의 새로운 버전을 " +"얻습니다(4단계)." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:284 msgid "" @@ -22748,6 +22816,11 @@ msgid "" "repeat this training process over and over again to eventually arrive at a " "fully trained model that performs well across the data of all client nodes." msgstr "" +"한 라운드의 반복에서 해당 반복에 참여하는 각 클라이언트 노드는 짧은 시간 " +"동안만 훈련합니다. 집계 단계(4단계) 이후 우리 모델이 관련된 모든 클라이언트 " +"노드의 모든 데이터에 대해 잠시 동안만 훈련되었음을 의미합니다. 그런 다음 " +"모든 클라이언트 노드의 데이터에서 잘 작동하는 완전히 훈련된 모델에 " +"도달하려면 이 훈련 과정을 계속 반복해야 합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:289 msgid "" @@ -22758,6 +22831,12 @@ msgid "" "should participate in the next round? What's the best way to aggregate model " "updates? How can we handle failing client nodes (stragglers)?" msgstr "" +"축하합니다, 이제 연방 학습의 기초에 대해 알게 되었습니다. 물론 아직 논의해야 " +"할 내용이 많지만 이는 연방 학습의 축소판일 뿐입니다. 본 튜토리얼의 " +"후반부에는 좀 더 자세히 설명하겠습니다. 흥미로운 질문은 다음과 같습니다: " +"다음 라운드에 참여해야 할 가장 좋은 클라이언트 노드를 어떻게 선택할 수 " +"있을까요? 모델 업데이트를 집계하는 가장 좋은 방법은 무엇일까요? 실패한 " +"클라이언트 노드(낙오자)를 어떻게 처리할 수 있을까요?" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:294 msgid "" @@ -22767,10 +22846,14 @@ msgid "" "In fact, federated evaluation is an integral part of most federated learning " "systems." msgstr "" +"다양한 클라이언트 노드의 분산된 데이터에 대해 모델을 훈련할 수 있는 것처럼 " +"해당 데이터에 대한 모델을 평가하여 가치 있는 메트릭을 받을 수도 있습니다. " +"이를 연합 평가라고 하며 FE라고 약칭하기도 합니다. 사실 연합 평가는 대부분의 " +"연합 학습 시스템에서 필수적인 부분입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:297 msgid "Federated analytics" -msgstr "" +msgstr "연방분석" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:299 msgid "" @@ -22782,6 +22865,13 @@ msgid "" "other privacy-enhancing technologies like secure aggregation to prevent the " "server from seeing the results submitted by individual client nodes." msgstr "" +"많은 경우 머신러닝은 데이터로부터 가치를 얻기 위한 필수 조건이 아닙니다. " +"데이터 분석을 통해 귀중한 통찰력을 얻을 수 있지만, 명확한 답변을 얻기에는 " +"데이터가 충분하지 않은 경우가 많습니다. 특정 유형의 건강 상태가 발생하는 " +"평균 연령은 몇 살입니까? 연합 분석을 사용하면 여러 클라이언트 노드에서 " +"이러한 쿼리를 실행할 수 있습니다. 서버가 단일 클라이언트 노드에서 제출한 " +"결과를 보지 못하도록 보안 애그리게이션과 같은 다른 프라이버시 향상 기술과 " +"함께 자주 사용됩니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:305 msgid "" @@ -22793,10 +22883,15 @@ msgid "" "identified. This technique can be considered an optimization that provides a " "quantifiable privacy protection measure." msgstr "" +"차등 프라이버시(DP)는 연합 학습의 맥락에서 종종 언급됩니다. 통계 데이터를 " +"분석하고 공유할 때 사용하는 프라이버시 보호 방식으로, 참가자 개인의 " +"프라이버시를 보장합니다. DP는 모델 업데이트에 통계적 노이즈를 추가하여 개별 " +"참가자의 정보를 구별하거나 재식별할 수 없도록 함으로써 이를 달성합니다. 이 " +"기술은 정량적 개인 정보 보호 조치를 제공하는 최적화라고 볼 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:326 msgid "Flower" -msgstr "" +msgstr "Flower" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:328 msgid "" @@ -22808,28 +22903,37 @@ msgid "" "learning, analytics, and evaluation. It allows the user to federate any " "workload, any ML framework, and any programming language." msgstr "" +"연방 학습, 연방 평가 및 연방 분석은 머신 러닝 모델을 앞뒤로 이동하고 로컬 " +"데이터에 대해 훈련 및 평가한 다음 업데이트된 모델을 통합하기 위한 기본 " +"프레임워크가 필요합니다. Flower가 제공하는 인프라는 간단하고 확장 가능하며 " +"안전한 방식으로 이러한 목표를 달성합니다. 간단히 말해서, Flower는 연방 학습, " +"분석 및 평가를 위한 통합 접근 방식을 제공합니다. 이를 통해 사용자는 모든 " +"워크로드, ML 프레임워크 및 모든 프로그래밍 언어를 통합할 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 msgid "|c00bf2750bc24d229737a0fe1395f0fc|" -msgstr "" +msgstr "|c00bf2750bc24d229737a0fe1395f0fc|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 msgid "" "Flower federated learning server and client nodes (car, scooter, personal " "computer, roomba, and phone)" -msgstr "" +msgstr "Flower 연합 학습 서버 및 클라이언트 노드(자동차, 스쿠터, 개인용 컴퓨터, " +"룸바, 전화)" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:353 msgid "" "Congratulations, you just learned the basics of federated learning and how " "it relates to the classic (centralized) machine learning!" -msgstr "" +msgstr "축하합니다, 연방 학습의 기본 지식과 클래식 (중앙 집중식) 머신러닝과 어떻게 " +"관련되는지 배웠습니다!" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:355 msgid "" "In the next part of this tutorial, we are going to build a first federated " "learning system with Flower." -msgstr "" +msgstr "이 튜토리얼의 다음 부분에서는 Flower와 함께 첫 번째 연방 학습 시스템을 " +"구축할 것입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:373 msgid "" @@ -22837,6 +22941,9 @@ msgid "" "framework/tutorial-get-started-with-flower-pytorch.html>`__ shows how to " "build a simple federated learning system with PyTorch and Flower." msgstr "" +"`Flower 연방 학습 튜토리얼- 1부 `__ PyTorch와 Flower를 사용하여 간단한 " +"연방 학습 시스템을 구축하는 방법을 보여줍니다." #~ msgid "" #~ "Currently, Flower provides two images, a ``base`` image and a " From e8683ae14618959654e1a53f9e8f5155a9264d6e Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Tue, 25 Jun 2024 12:51:46 +0200 Subject: [PATCH 088/595] docs(framework) Add latest Hosted Weblate translation updates (#3681) Co-authored-by: Young D. Kwon --- doc/locales/ko/LC_MESSAGES/framework-docs.po | 170 ++++++++++--------- 1 file changed, 87 insertions(+), 83 deletions(-) diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index 89b89de22be4..f01f9eaf7bd9 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -8,8 +8,8 @@ msgstr "" "Project-Id-Version: Flower main\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-06-17 16:09+0200\n" -"PO-Revision-Date: 2024-06-25 02:09+0000\n" -"Last-Translator: Sijiaomg Ohoh \n" +"PO-Revision-Date: 2024-06-25 10:43+0000\n" +"Last-Translator: \"Young D. Kwon\" \n" "Language-Team: Korean \n" "Language: ko\n" @@ -22278,7 +22278,7 @@ msgid "" msgstr "" "이 튜토리얼에서 연합 학습이 무엇인지 배우고 Flower로 첫 번째 시스템을 " "구축하고 점진적으로 확장해 나갈 것입니다. 본 튜토리얼의 모든 부분을 완성할 " -"수 있다면, 당신은 고급 연방 학습 시스템을 구축하여 그 분야의 현재 기술 " +"수 있다면, 당신은 고급 연합 학습 시스템을 구축하여 그 분야의 현재 최고 기술 " "수준에 접근할 수 있을 것입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:15 @@ -22287,8 +22287,9 @@ msgid "" "learning. Only a basic understanding of data science and Python programming " "is assumed." msgstr "" -"🧑‍🏫이 튜토리얼은 제로베이부터 시작되며 연방 학습에 상세히 아는 필요가 " -"없습니다. 데이터 과학과 파이썬 프로그래밍에 대한 기본적인 이해만 가정합니다." +"🧑‍🏫이 튜토리얼은 사전 지식을 많이 필요로 하지 않으며 연합 학습에 대해 " +"상세히알 필요는 없습니다. 데이터 과학과 파이썬 프로그래밍에 대한 기본적인 " +"이해만 가정합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:17 msgid "" @@ -22306,13 +22307,13 @@ msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:31 msgid "Classic machine learning" -msgstr "클래식 머신러닝" +msgstr "전통적인 머신러닝(기계학습)" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:33 msgid "" "Before we begin to discuss federated learning, let us quickly recap how most " "machine learning works today." -msgstr "연방 학습에 대해 논의하기 전에 현재 대부분의 머신러닝이 어떻게 작동하는지 " +msgstr "연합 학습에 대해 논의하기 전에 현재 대부분의 머신러닝이 어떻게 작동하는지 " "간략히 요약하겠습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:35 @@ -22321,8 +22322,8 @@ msgid "" "neural network (as depicted here), or something else, like classical linear " "regression." msgstr "" -"기계 학습에서 우리는 모델과 데이터를 가지고 있습니다.모델은 신경망((그림과 " -"같이))일 수도 있고 고전적인 선형 회귀와 같은 다른 것일 수도 있습니다." +"머신러닝에서 우리는 모델과 데이터를 가지고 있습니다. 모델은 신경망(그림과 " +"같이)일 수도 있고 고전적인 선형 회귀와 같은 다른 것일 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 msgid "|93b02017c78049bbbd5ae456dcb2c91b|" @@ -22373,7 +22374,7 @@ msgstr "|9bc21c7dbd17444a8f070c60786e3484|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 msgid "Data on a phone" -msgstr "핸드푼에 있는 데이터" +msgstr "핸드폰에 있는 데이터" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:73 msgid "" @@ -22401,7 +22402,7 @@ msgid "" "server can be somewhere in a data center, or somewhere in the cloud." msgstr "" "따라서 머신러닝이나 어떤 종류의 데이터 분석을 이용하려면 과거에는 중앙 " -"서버에서 모든 데이터를 수집하는 방법이 사용되었습니다.이 서버는 데이터 센터 " +"서버에서 모든 데이터를 수집하는 방법이 사용되었습니다. 이 서버는 데이터 센터 " "어딘가에 있을 수도 있고 클라우드 어딘가에 있을 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 @@ -22419,8 +22420,8 @@ msgid "" "learning approach that we've basically always relied on." msgstr "" "모든 데이터가 한 곳에 모이면, 우리는 궁극적으로 머신러닝 알고리즘을 사용하여 " -"데이터에서 모델을 훈련시킬 수 있습니다.이것이 바로 우리가 기본적으로 " -"의지해왔던 머신러닝 방법입니다." +"데이터에서 모델을 훈련시킬 수 있습니다. 이것이 바로 우리가 기본적으로 사용해 " +"온 머신러닝 방법입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 msgid "|c24c1478b30e4f74839208628a842d1e|" @@ -22432,7 +22433,7 @@ msgstr "중앙 데이터 훈련" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:130 msgid "Challenges of classical machine learning" -msgstr "클래식 머신러닝이 만난 도전" +msgstr "클래식 머신러닝의 어려움" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:132 msgid "" @@ -22441,9 +22442,9 @@ msgid "" "traffic. Cases, where all the data is naturally available on a centralized " "server." msgstr "" -"우리가 방금 본 클래식 머신러닝 접근 방식은 경우에 따라 사용될 수 있습니다. " -"좋은 예로는 휴일 사진을 분류하거나 웹 트래픽을 분석하는 것이 있습니다. " -"이러한 사례에서 모든 데이터는 자연스럽게 중앙 서버에서 사용할 수 있습니다." +"우리가 방금 본 전통적 머신러닝의 접근 방식은 경우에 따라 다르게 사용될 수 " +"있습니다. 좋은 예로는 휴일 사진을 분류하거나 웹 트래픽을 분석하는 것이 " +"있습니다. 이러한 사례에서 모든 데이터는 자연스럽게 중앙 서버에 존재합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 msgid "|1b3613d7a58847b59e1d3180802dbc09|" @@ -22459,9 +22460,9 @@ msgid "" "is not available on a centralized server, or cases where the data available " "on one server is not enough to train a good model." msgstr "" -"그러나 이 방법은 다른 많은 경우에 적용되지 않습니다.예를 들어, 중앙 집중식 " -"서버에 데이터가 없거나 서버의 데이터가 좋은 모델을 훈련하기에 충분하지 " -"않습니다." +"그러나 이 방법은 다른 많은 경우에 적용되지 않을 수 있습니다. 예를 들어, 중앙 " +"집중식 서버에 데이터가 없거나 서버의 데이터가 좋은 모델을 훈련하기에 " +"충분하지 않을 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 msgid "|9980b5213db547d0b8024a50992b9e3f|" @@ -22469,7 +22470,7 @@ msgstr "|9980b5213db547d0b8024a50992b9e3f|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 msgid "Centralized impossible" -msgstr "집중화 가능" +msgstr "집중화 불가능" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:156 msgid "" @@ -22477,8 +22478,8 @@ msgid "" "does not work for a large number of highly important real-world use cases. " "Those reasons include:" msgstr "" -"클래식 중앙 집중식 머신러닝 방법이 현실 세계에서 매우 중요한 수많은 사용 " -"사례를 충족시킬 수 없는 이유가 있습니다.이유는 다음과 같은 여러 가지가 " +"전통적인 중앙 집중식 머신러닝 방법이 현실 세계에서 매우 중요한 수많은 사용 " +"사례를 충족시킬 수 없는 이유가 있습니다. 이유는 다음과 같은 여러 가지가 " "있습니다:" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:158 @@ -22497,9 +22498,9 @@ msgstr "" "PDPL (아르헨티나), KVKK (터키), POPI (남아프리카공화국), FSS (러시아), CDPR " "(중국), PDPB (인도), PIPA (한국), APPI (일본), PDP (인도네시아), PDPA " "(싱가포르), APP (호주)등의 법규로 민감한 데이터가 이동하지 않도록 보호하고 " -"있습니 다. 실제 로이러한 규정은 사용자가 세계의 다른 지역에 살고 데이터가 " +"있습니다. 실제로 이러한 규정은 사용자가 세계의 다른 지역에 살고 데이터가 " "다른 데이터 보호 규정에 의해 통제되기 때문에 단일 조직이 자체 사용자 " -"데이터를 인공 지능 교육에 사용하는 것을 방지하기도 합니다." +"데이터를 인공 지능 학습에 사용하는 것을 방지하기도 합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:160 msgid "" @@ -22510,7 +22511,7 @@ msgid "" "developed that keyboard, do you? In fact, that use case was the reason " "federated learning was invented in the first place." msgstr "" -"**사용자 선호도**:규정 외에도 일부 사용 사례에서 사용자는 데이터가 자기 " +"**사용자 선호도**: 규정 외에도 일부 사용 사례에서 사용자는 데이터가 자기 " "장치를 떠나지 않기를 예상합니다. 휴대폰의 디지털 키보드에 비밀번호와 " "신용카드 정보를 입력하면 비밀번호가 해당 키보드를 개발한 회사의 서버에 뜨길 " "원하지는 않겠죠? 사실, 이 사용 사례가 애당초 연합 학습이 발명된 이유였습니다." @@ -22526,13 +22527,13 @@ msgid "" "exceedingly expensive infrastructure to process and store. And most of the " "data isn't even useful." msgstr "" -"**데이터 볼륨**:일부 센서(예:카메라)는 너무 많은 데이터 볼륨을 생성하여 모든 " -"데이터를 수집하는 것이 실현 가능하지도 않고 경제적이지도 않습니다(예: 대역폭 " -"또는 통신 효율로 인해). 전국에 수백 개 기차역이 있는 국가 철도 서비스를 " -"생각해 보세요. 각 기차역에 수 많은 보안 카메라가 설치되어 있다면,그들이 " -"생산하는 대량의 원시 온디바이스 데이터는 처리 및 저장을 위해 엄청나게 " -"강력하고 매우 비싼 인프라를 필요로 합니다.그런데 대부분의 데이터는 " -"유용하지도 않습니다." +"**데이터 볼륨**: 일부 센서(예:카메라)는 너무 많은 데이터 볼륨을 생성하여 " +"모든 데이터를 수집하는 것이 실현 가능하지도 않고 경제적이지도 않습니다(예: " +"대역폭 또는 통신 효율로 인해). 전국에 수백 개 기차역이 있는 국가 철도 " +"서비스를 생각해 보세요. 각 기차역에 수 많은 보안 카메라가 설치되어 있다면, " +"그들이 생산하는 대량의 미가공 된 온디바이스 데이터는 처리 및 저장을 위해 " +"엄청나게 강력하고 매우 비싼기반 구조를 필요로 합니다. 그런데 대부분의 " +"데이터는 유용하지도 않습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:164 msgid "Examples where centralized machine learning does not work include:" @@ -22555,7 +22556,7 @@ msgstr "더 나은 범위 예측을 위해 전기 자동차의 위치 데이터" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:169 msgid "End-to-end encrypted messages to train better auto-complete models" -msgstr "더 나은 자동 완성 모델을 훈련시키기 위한 엔드 투 엔드 암호화된 메시지" +msgstr "더 나은 자동 완성 모델을 훈련시키기 위한 엔드 투 엔드 암호화 된 메시지" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:171 msgid "" @@ -22570,14 +22571,14 @@ msgstr "" "`Brave `__ 브라우저나 `Signal `__ " "메신저와 같은 개인 정보 보호 시스템의 인기는 사용자들이 개인 정보 보호에 " "신경 쓴다는 것을 보여줍니다. 실제로 그러한 대안이 존재하는 경우 다른 " -"대안보다 개인 정보 보호 강화 버전을 선택합니다. 그런데 이러한 사례에 머신 " -"러닝 및 데이터 과학을 적용하여 프라이버시 데이터를 활용하려면 어떻게 해야 " -"합니까? 이 모든 분야는 최근 AI의 발전으로 상당한 이익을 얻을 수 있는 " +"대안보다 개인 정보 보호 강화 버전을 선택합니다. 그런데 이러한 사례에 " +"머신러닝 및 데이터 과학을 적용하여 프라이버시 데이터를 활용하려면 어떻게 " +"해야 합니까? 이 모든 분야는 최근 AI의 발전으로 상당한 이익을 얻을 수 있는 " "분야입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:186 msgid "Federated learning" -msgstr "연방 학습" +msgstr "연합 학습" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:188 msgid "" @@ -22585,9 +22586,9 @@ msgid "" "learning on distributed data by moving the training to the data, instead of " "moving the data to the training. Here's the single-sentence explanation:" msgstr "" -"연방 학습은 이 방법을 쉽게 뒤집었습니다. 데이터를 훈련으로 옮기는 대신 " -"데이터로 훈련을 전환함으로써 분산된 데이터에서 머신러닝을 실현합니다.다음은 " -"한마디로 설명입니다:" +"연합 학습은 이 방법을 쉽게 뒤집었습니다. 데이터를 컴퓨팅 센터로 옮기는 대신 " +"컴퓨팅 능력을 데이터가 생성되는 장소로 이동 시킴으로써 분산된 데이터에서 " +"머신러닝을 실현합니다. 요약하자면:" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:190 msgid "Central machine learning: move the data to the computation" @@ -22595,7 +22596,7 @@ msgstr "중앙 집중식 머신러닝: 데이터를 컴퓨팅 센터로 이동" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:191 msgid "Federated (machine) learning: move the computation to the data" -msgstr "연방(기계)학습: 컴퓨팅을 데이터로 옮김" +msgstr "연합(기계)학습: 컴퓨팅을 데이터로 옮김" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:193 msgid "" @@ -22610,28 +22611,29 @@ msgid "" "more and more areas that can suddenly be reinvented because they now have " "access to vast amounts of previously inaccessible data." msgstr "" -"이를 통해 이전에는 불가능했던 분야에서 머신 러닝(및 기타 데이터 과학 방법)을 " +"이를 통해 이전에는 불가능했던 분야에서 머신러닝(및 기타 데이터 과학 방법)을 " "사용할 수 있습니다. 이제 다양한 병원이 협력할 수 있도록 함으로써 우수한 의료 " "AI 모델을 훈련할 수 있습니다. 다양한 금융 기관의 데이터에 대한 AI 모델을 " "훈련하여 금융 사기를 해결할 수 있습니다. 개인 정보 보호를 강화하지 않는 " "대안보다 더 나은 AI가 내장된 새로운 개인 정보 보호 강화 애플리케이션(예: " -"보안 메시징)을 구축할 수 있습니다.그것들은 떠오르는 몇 가지 예에 불과합니다. " -"연합 학습을 구축함에 따라 이전에 액세스할 수 없었던 많은 데이터에 액세스할 " -"수 있게 되었기 때문에 갑자기 재생될 수 있는 영역이 점점 더 많아지고 있습니다." +"보안 메시징)을 구축할 수 있습니다. 그것들은 떠오르는 몇 가지 예에 " +"불과합니다. 연합 학습을 구축함에 따라 이전에 액세스할 수 없었던 많은 " +"데이터에 액세스할 수 있게 되었기 때문에 갑자기 재생될 수 있는 영역이 점점 더 " +"많아지고 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:196 msgid "" "So how does federated learning work, exactly? Let's start with an intuitive " "explanation." -msgstr "그렇다면 연방 학습은어떻게 작동합니까?직관적인 설명부터 시작하겠습니다." +msgstr "그렇다면 연합 학습은 어떻게 작동합니까? 직관적인 설명부터 시작하겠습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:199 msgid "Federated learning in five steps" -msgstr "연방 학습의 5단계" +msgstr "연합 학습의 5단계" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:202 msgid "Step 0: Initialize global model" -msgstr "0단계: 전역 모델 초기화" +msgstr "0단계: 글로벌 모델 초기화" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:204 msgid "" @@ -22639,9 +22641,9 @@ msgid "" "in classic centralized learning: we initialize the model parameters, either " "randomly or from a previously saved checkpoint." msgstr "" -"서버에서 모델을 초기화하는 것으로 시작합니다. 클래식 중앙 집중식 학습에서도 " -"완전히 동일합니다: 임의로 또는 이전에 저장된 체크포인트에서 모델 매개변수를 " -"초기화합니다." +"서버에서 모델을 초기화하는 것으로 시작합니다. 이것은 전통적인 중앙 집중식 " +"학습과도 동일합니다: 임의로 또는 이전에 저장된 체크포인트에서 모델 " +"매개변수를 초기화합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 msgid "|c7afb4c92d154bfaa5e8cb9a150e17f1|" @@ -22649,7 +22651,7 @@ msgstr "|c7afb4c92d154bfaa5e8cb9a150e17f1|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 msgid "Initialize global model" -msgstr "전역 모델 초기화" +msgstr "글로벌 모델 초기화" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:217 msgid "" @@ -22670,7 +22672,7 @@ msgstr "" "에지 디바이스 또는 조직에 속한 서버)로 보냅니다. 이것은 각 참여 노드가 " "동일한 모델 매개변수를 사용하여 로컬 훈련을 시작하도록 하기 위함입니다. " "일반적으로 모든 노드가 아닌 몇 개의 연결 노드만 사용합니다. 그 이유는 점점 " -"더 많은 클라이언트 노드를 선택하면 수익률이 감소하는 것입니다." +"더 많은 클라이언트 노드를 선택하면 학습의 효율성이 감소하기 때문입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 msgid "|032eb6fed6924ac387b9f13854919196|" @@ -22678,7 +22680,7 @@ msgstr "|032eb6fed6924ac387b9f13854919196|" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 msgid "Send global model" -msgstr "전역 모델 전송" +msgstr "글로벌 모델 전송" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:232 msgid "" @@ -22726,7 +22728,7 @@ msgstr "" "다릅니다. 파라미터가 다른 이유는 각 클라이언트 노드의 로컬 데이터 세트에 " "다른 데이터가 있기 때문입니다. 그런 다음 클라이언트 노드는 이러한 모델 " "업데이트를 서버로 다시 보냅니다. 보내는 모델 업데이트는 전체 모델 " -"파라미터거나 로컬 교육 중에 누적된 그래디언트일 수 있습니다." +"파라미터거나 로컬 교육 중에 누적된 그레디언트(gradient)일 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 msgid "|7efbe3d29d8349b89594e8947e910525|" @@ -22750,9 +22752,9 @@ msgid "" msgstr "" "서버는 선택된 클라이언트 노드들로부터 모델 업데이트들을 수신합니다. 서버가 " "100개의 클라이언트 노드를 선택했다면 이제 각각 클라이언트의 로컬 데이터를 " -"기반으로 훈련된 100개의 약간 다른 원래 글로벌 모델 버전을 갖게 됩니다. " +"기반으로 훈련된 100개의 조금씩 다른 원래 글로벌 모델 버전을 갖게 됩니다. " "하지만 우리는 100개의 모든 클라이언트 노드의 데이터에서 학습한 내용을 " -"포함하는 모델을 하나 갖고 싶지 않았습니까?" +"포함하는 모델을 하나만 갖고 싶지 않았습니까?" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:266 msgid "" @@ -22801,7 +22803,7 @@ msgid "" "models to the server (step 3), and the server then aggregates the model " "updates to get a new version of the global model (step 4)." msgstr "" -"단계 1에서 4는 우리가 말하는 단일 라운드 연방 학습입니다. 글로벌 모델 " +"단계 1에서 4는 우리가 말하는 단일 라운드 연합 학습입니다. 글로벌 모델 " "파라미터는 참여하는 클라이언트 노드에 전송되고(1단계), 클라이언트 노드는 " "로컬 데이터에 대한 훈련을 받고(2단계), 업데이트된 모델을 서버에 " "전송하고(3단계), 서버는 모델 업데이트를 집계하여 글로벌 모델의 새로운 버전을 " @@ -22831,8 +22833,8 @@ msgid "" "should participate in the next round? What's the best way to aggregate model " "updates? How can we handle failing client nodes (stragglers)?" msgstr "" -"축하합니다, 이제 연방 학습의 기초에 대해 알게 되었습니다. 물론 아직 논의해야 " -"할 내용이 많지만 이는 연방 학습의 축소판일 뿐입니다. 본 튜토리얼의 " +"축하합니다, 이제 연합 학습의 기초에 대해 알게 되었습니다. 물론 아직 논의해야 " +"할 내용이 많지만 이는 연합 학습의 축소판일 뿐입니다. 본 튜토리얼의 " "후반부에는 좀 더 자세히 설명하겠습니다. 흥미로운 질문은 다음과 같습니다: " "다음 라운드에 참여해야 할 가장 좋은 클라이언트 노드를 어떻게 선택할 수 " "있을까요? 모델 업데이트를 집계하는 가장 좋은 방법은 무엇일까요? 실패한 " @@ -22847,13 +22849,13 @@ msgid "" "systems." msgstr "" "다양한 클라이언트 노드의 분산된 데이터에 대해 모델을 훈련할 수 있는 것처럼 " -"해당 데이터에 대한 모델을 평가하여 가치 있는 메트릭을 받을 수도 있습니다. " -"이를 연합 평가라고 하며 FE라고 약칭하기도 합니다. 사실 연합 평가는 대부분의 " -"연합 학습 시스템에서 필수적인 부분입니다." +"해당 데이터에 대한 모델을 평가하여 가치 있는 메트릭(metrics)을 받을 수도 " +"있습니다. 이를 연합 평가라고 하며 FE라고 약칭하기도 합니다. 사실 연합 평가는 " +"대부분의 연합 학습 시스템에서 필수적인 부분입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:297 msgid "Federated analytics" -msgstr "연방분석" +msgstr "연합 분석" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:299 msgid "" @@ -22869,9 +22871,9 @@ msgstr "" "데이터 분석을 통해 귀중한 통찰력을 얻을 수 있지만, 명확한 답변을 얻기에는 " "데이터가 충분하지 않은 경우가 많습니다. 특정 유형의 건강 상태가 발생하는 " "평균 연령은 몇 살입니까? 연합 분석을 사용하면 여러 클라이언트 노드에서 " -"이러한 쿼리를 실행할 수 있습니다. 서버가 단일 클라이언트 노드에서 제출한 " -"결과를 보지 못하도록 보안 애그리게이션과 같은 다른 프라이버시 향상 기술과 " -"함께 자주 사용됩니다." +"이러한 쿼리(query)를 실행할 수 있습니다. 서버가 단일 클라이언트 노드에서 " +"제출한 결과를 보지 못하도록 보안을 강화한 집합 방식과 같은 다른 프라이버시 " +"향상 기술과 함께 자주 사용됩니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:305 msgid "" @@ -22883,11 +22885,12 @@ msgid "" "identified. This technique can be considered an optimization that provides a " "quantifiable privacy protection measure." msgstr "" -"차등 프라이버시(DP)는 연합 학습의 맥락에서 종종 언급됩니다. 통계 데이터를 " -"분석하고 공유할 때 사용하는 프라이버시 보호 방식으로, 참가자 개인의 " -"프라이버시를 보장합니다. DP는 모델 업데이트에 통계적 노이즈를 추가하여 개별 " -"참가자의 정보를 구별하거나 재식별할 수 없도록 함으로써 이를 달성합니다. 이 " -"기술은 정량적 개인 정보 보호 조치를 제공하는 최적화라고 볼 수 있습니다." +"차분 프라이버시(Differential Privacy)는 연합 학습의 맥락에서 종종 " +"언급됩니다. 통계 데이터를 분석하고 공유할 때 사용하는 프라이버시 보호 " +"방식으로, 참가자 개인의 프라이버시를 보장합니다. 차분 프라이버시는 모델 " +"업데이트에 통계적 잡음(noise)를 추가하여 개별 참가자의 정보를 구별하거나 " +"재식별할 수 없도록 함으로써 이를 달성합니다. 이 기술은 정량적 개인 정보 보호 " +"조치를 제공하는 최적화라고 볼 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:326 msgid "Flower" @@ -22903,12 +22906,13 @@ msgid "" "learning, analytics, and evaluation. It allows the user to federate any " "workload, any ML framework, and any programming language." msgstr "" -"연방 학습, 연방 평가 및 연방 분석은 머신 러닝 모델을 앞뒤로 이동하고 로컬 " +"연합 학습, 연합 평가 및 연합 분석은 머신러닝 모델을 앞뒤로 이동하고 로컬 " "데이터에 대해 훈련 및 평가한 다음 업데이트된 모델을 통합하기 위한 기본 " -"프레임워크가 필요합니다. Flower가 제공하는 인프라는 간단하고 확장 가능하며 " -"안전한 방식으로 이러한 목표를 달성합니다. 간단히 말해서, Flower는 연방 학습, " -"분석 및 평가를 위한 통합 접근 방식을 제공합니다. 이를 통해 사용자는 모든 " -"워크로드, ML 프레임워크 및 모든 프로그래밍 언어를 통합할 수 있습니다." +"프레임워크가 필요합니다. Flower가 제공하는 기반 구조는 간단하고 확장 " +"가능하며 안전한 방식으로 이러한 목표를 달성합니다. 간단히 말해서, Flower는 " +"연합 학습, 분석 및 평가를 위한 통합 접근 방식을 제공합니다. 이를 통해 " +"사용자는 모든 워크로드, 머신러닝 프레임워크 및 모든 프로그래밍 언어를 통합할 " +"수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 msgid "|c00bf2750bc24d229737a0fe1395f0fc|" @@ -22925,14 +22929,14 @@ msgstr "Flower 연합 학습 서버 및 클라이언트 노드(자동차, 스쿠 msgid "" "Congratulations, you just learned the basics of federated learning and how " "it relates to the classic (centralized) machine learning!" -msgstr "축하합니다, 연방 학습의 기본 지식과 클래식 (중앙 집중식) 머신러닝과 어떻게 " -"관련되는지 배웠습니다!" +msgstr "축하합니다, 지금까지 당신은 연합 학습의 기본 지식과 그것이 어떻게 전통적 (" +"중앙 집중식) 머신러닝과 관련되는지 배웠습니다!" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:355 msgid "" "In the next part of this tutorial, we are going to build a first federated " "learning system with Flower." -msgstr "이 튜토리얼의 다음 부분에서는 Flower와 함께 첫 번째 연방 학습 시스템을 " +msgstr "이 튜토리얼의 다음 부분에서는 Flower와 함께 첫 번째 연합 학습 시스템을 " "구축할 것입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:373 @@ -22941,9 +22945,9 @@ msgid "" "framework/tutorial-get-started-with-flower-pytorch.html>`__ shows how to " "build a simple federated learning system with PyTorch and Flower." msgstr "" -"`Flower 연방 학습 튜토리얼- 1부 `__ PyTorch와 Flower를 사용하여 간단한 " -"연방 학습 시스템을 구축하는 방법을 보여줍니다." +"연합 학습 시스템을 구축하는 방법을 보여줍니다." #~ msgid "" #~ "Currently, Flower provides two images, a ``base`` image and a " From 7e80b912a8aabefd5236f54e35c097fd59b42bc6 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 25 Jun 2024 13:02:45 +0200 Subject: [PATCH 089/595] docs(framework) Add Korean docs (#3680) Co-authored-by: Taner Topal --- doc/source/_templates/sidebar/lang.html | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/doc/source/_templates/sidebar/lang.html b/doc/source/_templates/sidebar/lang.html index b377a53f9c40..bbea57571838 100644 --- a/doc/source/_templates/sidebar/lang.html +++ b/doc/source/_templates/sidebar/lang.html @@ -1,9 +1,14 @@ {% if versions or lang %} - + - + {% endif %} From 36033b5afa576b40e7aeb07aaf7adcd73ff4d7c3 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 25 Jun 2024 17:39:22 +0200 Subject: [PATCH 090/595] docs(framework:skip) Fix versioned docs script (#3683) --- doc/build-versioned-docs.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/build-versioned-docs.sh b/doc/build-versioned-docs.sh index 6c1b6dd9c5fc..4d3462718385 100755 --- a/doc/build-versioned-docs.sh +++ b/doc/build-versioned-docs.sh @@ -82,9 +82,9 @@ done # Build the main version (main for GH CI, local branch for local) if [ $GITHUB_ACTIONS ] then - git switch main + git checkout --force main else - git switch $current_branch + git checkout --force $current_branch fi current_version=main From f4ce64c20d96798ce762146c0174a8b1c9cefeeb Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 25 Jun 2024 18:10:51 +0200 Subject: [PATCH 091/595] ci(framework:skip) Add reconnection tests (#3404) --- .github/workflows/e2e.yml | 3 ++ e2e/test_reconnection.sh | 88 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100755 e2e/test_reconnection.sh diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d66362de9d32..4cbee6f770d6 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -164,6 +164,9 @@ jobs: - name: Run driver test with client authentication if: ${{ matrix.directory == 'bare-client-auth' }} run: ./../test_driver.sh bare client-auth + - name: Run reconnection test with SQLite database + if: ${{ matrix.directory == 'bare' }} + run: ./../test_reconnection.sh sqlite - name: Cache save Python location id: cache-save-python uses: actions/cache/save@v4 diff --git a/e2e/test_reconnection.sh b/e2e/test_reconnection.sh new file mode 100755 index 000000000000..7f8eaa94bf27 --- /dev/null +++ b/e2e/test_reconnection.sh @@ -0,0 +1,88 @@ +#!/bin/bash +set -e + +case "$1" in + rest) + rest_arg="--rest" + server_app_address="http://localhost:9091" + server_address="http://localhost:9093" + db_arg="--database :flwr-in-memory-state:" + ;; + sqlite) + rest_arg="" + server_address="127.0.0.1:9092" + server_app_address="127.0.0.1:9091" + db_arg="--database $(date +%s).db" + ;; + *) + rest_arg="" + server_address="127.0.0.1:9092" + server_app_address="127.0.0.1:9091" + db_arg="--database :flwr-in-memory-state:" + ;; +esac + +dir_arg="--dir ./.." + +timeout 2m flower-superlink --insecure $db_arg $rest_arg & +sl_pid=$! +echo "Starting SuperLink" +sleep 3 + +timeout 2m flower-client-app client:app --insecure $rest_arg --server $server_address & +cl1_pid=$! +echo "Starting first client" +sleep 3 + +timeout 2m flower-client-app client:app --insecure $rest_arg --server $server_address & +cl2_pid=$! +echo "Starting second client" +sleep 3 + +# Kill superlink, this should send the clients into their retry loops +kill $sl_pid +echo "Killing Superlink" +sleep 3 + +# Restart superlink, the clients should now be able to reconnect to it +timeout 2m flower-superlink --insecure $db_arg $rest_arg & +sl_pid=$! +echo "Restarting Superlink" +sleep 20 + +# Kill first client, this should send a DeleteNode message to the Superlink +kill $cl1_pid +echo "Killing first client" +sleep 3 + +# Starting new client, this is so we have enough clients to start the server-app +timeout 2m flower-client-app client:app --insecure $rest_arg --server $server_address & +cl1_pid=$! +echo "Starting new client" +sleep 5 + +# We start the server-app to begining the training +timeout 2m flower-server-app server:app --insecure $dir_arg $rest_arg --server $server_app_address & +pid=$! +echo "Starting server-app to start training" + +# Kill first client as soon as the training starts, +# the server-app should just receive a failure in this case and continue the rounds +# when enough clients are connected +kill $cl1_pid +echo "Killing first client" +sleep 1 + +# Restart first client so enough clients are connected to continue the FL rounds +timeout 2m flower-client-app client:app --insecure $rest_arg --server $server_address & +cl1_pid=$! +echo "Starting new client" + +wait $pid +res=$? + +if [[ "$res" = "0" ]]; + then echo "Training worked correctly"; kill $cl1_pid; kill $cl2_pid; kill $sl_pid; + else echo "Training had an issue" && exit 1; +fi + From 0f64311c0f2c1e150906aa5f3b41be5dcacd2245 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sat, 29 Jun 2024 13:24:09 +0200 Subject: [PATCH 092/595] ci(*:skip) Update twine (#3691) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index dbab703c7671..5daf007471aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -122,7 +122,7 @@ mdformat-gfm = "==0.3.5" mdformat-frontmatter = "==2.0.1" mdformat-beautysh = "==0.1.1" mdformat-myst = "==0.1.5" -twine = "==4.0.2" +twine = "==5.1.1" pyroma = "==4.2" check-wheel-contents = "==0.4.0" GitPython = "==3.1.32" From bb8e41340b4c5b48cf6f607e5512345ab94bd06a Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sat, 29 Jun 2024 13:29:55 +0200 Subject: [PATCH 093/595] feat(framework) Add deployment engine executor (#3629) Co-authored-by: Daniel J. Beutel --- src/py/flwr/cli/build.py | 4 +- src/py/flwr/cli/run/run.py | 17 ++++- src/py/flwr/superexec/deployment.py | 109 ++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 src/py/flwr/superexec/deployment.py diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index 4a9b54f9223f..f63d0acd5d73 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -31,7 +31,7 @@ def build( directory: Annotated[ Optional[Path], - typer.Option(help="The Flower project directory to bundle into a FAB"), + typer.Option(help="Path of the Flower project to bundle into a FAB"), ] = None, ) -> str: """Build a Flower project into a Flower App Bundle (FAB). @@ -118,7 +118,7 @@ def build( fab_file.writestr(".info/CONTENT", list_file_content) typer.secho( - f"🎊 Successfully built {fab_filename}.", fg=typer.colors.GREEN, bold=True + f"🎊 Successfully built {fab_filename}", fg=typer.colors.GREEN, bold=True ) return fab_filename diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 4c95a4041c05..f5882bd14ab8 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -17,12 +17,14 @@ import sys from enum import Enum from logging import DEBUG +from pathlib import Path from typing import Optional import typer from typing_extensions import Annotated from flwr.cli import config_utils +from flwr.cli.build import build from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel from flwr.common.logger import log @@ -52,10 +54,14 @@ def run( case_sensitive=False, help="Use this flag to use the new SuperExec API" ), ] = False, + directory: Annotated[ + Optional[Path], + typer.Option(help="Path of the Flower project to run"), + ] = None, ) -> None: """Run Flower project.""" if use_superexec: - _start_superexec_run() + _start_superexec_run(directory) return typer.secho("Loading project configuration... ", fg=typer.colors.BLUE) @@ -109,7 +115,7 @@ def run( ) -def _start_superexec_run() -> None: +def _start_superexec_run(directory: Optional[Path]) -> None: def on_channel_state_change(channel_connectivity: str) -> None: """Log channel connectivity.""" log(DEBUG, channel_connectivity) @@ -124,5 +130,8 @@ def on_channel_state_change(channel_connectivity: str) -> None: channel.subscribe(on_channel_state_change) stub = ExecStub(channel) - req = StartRunRequest() - stub.StartRun(req) + fab_path = build(directory) + + req = StartRunRequest(fab_file=Path(fab_path).read_bytes()) + res = stub.StartRun(req) + typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN) diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py new file mode 100644 index 000000000000..6f931e81eefa --- /dev/null +++ b/src/py/flwr/superexec/deployment.py @@ -0,0 +1,109 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Deployment engine executor.""" + +import subprocess +import sys +from logging import ERROR, INFO +from typing import Optional + +from typing_extensions import override + +from flwr.cli.config_utils import get_fab_metadata +from flwr.cli.install import install_from_fab +from flwr.common.grpc import create_channel +from flwr.common.logger import log +from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611 +from flwr.proto.driver_pb2_grpc import DriverStub +from flwr.server.driver.grpc_driver import DEFAULT_SERVER_ADDRESS_DRIVER + +from .executor import Executor, RunTracker + + +class DeploymentEngine(Executor): + """Deployment engine executor.""" + + def __init__( + self, + address: str = DEFAULT_SERVER_ADDRESS_DRIVER, + root_certificates: Optional[bytes] = None, + ) -> None: + self.address = address + self.root_certificates = root_certificates + self.stub: Optional[DriverStub] = None + + def _connect(self) -> None: + if self.stub is None: + channel = create_channel( + server_address=self.address, + insecure=(self.root_certificates is None), + root_certificates=self.root_certificates, + ) + self.stub = DriverStub(channel) + + def _create_run(self, fab_id: str, fab_version: str) -> int: + if self.stub is None: + self._connect() + + assert self.stub is not None + + req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version) + res = self.stub.CreateRun(request=req) + return int(res.run_id) + + @override + def start_run(self, fab_file: bytes) -> Optional[RunTracker]: + """Start run using the Flower Deployment Engine.""" + try: + # Install FAB to flwr dir + fab_version, fab_id = get_fab_metadata(fab_file) + fab_path = install_from_fab(fab_file, None, True) + + # Install FAB Python package + subprocess.check_call( + [sys.executable, "-m", "pip", "install", str(fab_path)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + # Call SuperLink to create run + run_id: int = self._create_run(fab_id, fab_version) + log(INFO, "Created run %s", str(run_id)) + + # Start ServerApp + proc = subprocess.Popen( # pylint: disable=consider-using-with + [ + "flower-server-app", + "--run-id", + str(run_id), + "--insecure", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + log(INFO, "Started run %s", str(run_id)) + + return RunTracker( + run_id=run_id, + proc=proc, + ) + # pylint: disable-next=broad-except + except Exception as e: + log(ERROR, "Could not start run: %s", str(e)) + return None + + +executor = DeploymentEngine() From 14913bf6115b70010cf3ff28bbe5639ed9224113 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Jun 2024 15:45:03 +0200 Subject: [PATCH 094/595] feat(framework) Capture `partition_id` in `Context` (#3694) --- src/py/flwr/common/context.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index b6349307d150..8fe0f1781817 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -16,13 +16,14 @@ from dataclasses import dataclass +from typing import Optional from .record import RecordSet @dataclass class Context: - """State of your run. + """Context of your run. Parameters ---------- @@ -33,6 +34,15 @@ class Context: executing mods. It can also be used as a memory to access at different points during the lifecycle of this entity (e.g. across multiple rounds) + partition_id : Optional[int] (default: None) + An index that specifies the data partition that the ClientApp using this Context + object should make use of. Setting this attribute is better suited for + simulation or proto typing setups. """ state: RecordSet + partition_id: Optional[int] + + def __init__(self, state: RecordSet, partition_id: Optional[int] = None) -> None: + self.state = state + self.partition_id = partition_id From 059c9eb20ecaa732aaf473f438db83568cee06d6 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Jun 2024 19:25:01 +0200 Subject: [PATCH 095/595] feat(framework) Make `NodeState` capture `partition-id` (#3695) --- src/py/flwr/client/app.py | 6 +++++- src/py/flwr/client/node_state.py | 9 ++++++--- src/py/flwr/client/node_state_tests.py | 2 +- src/py/flwr/client/supernode/app.py | 8 ++++++++ src/py/flwr/server/superlink/fleet/vce/vce_api.py | 4 ++-- src/py/flwr/simulation/ray_transport/ray_client_proxy.py | 2 +- 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 1226a0d7bc21..23e5a4cd38c9 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -191,6 +191,7 @@ def _start_client_internal( ] = None, max_retries: Optional[int] = None, max_wait_time: Optional[float] = None, + partition_id: Optional[int] = None, ) -> None: """Start a Flower client node which connects to a Flower server. @@ -234,6 +235,9 @@ class `flwr.client.Client` (default: None) The maximum duration before the client stops trying to connect to the server in case of connection error. If set to None, there is no limit to the total time. + partitioni_id: Optional[int] (default: None) + The data partition index associated with this node. Better suited for + prototyping purposes. """ if insecure is None: insecure = root_certificates is None @@ -309,7 +313,7 @@ def _on_backoff(retry_state: RetryState) -> None: on_backoff=_on_backoff, ) - node_state = NodeState() + node_state = NodeState(partition_id=partition_id) # run_id -> (fab_id, fab_version) run_info: Dict[int, Tuple[str, str]] = {} diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 71681b783419..cda00d25b62c 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -15,7 +15,7 @@ """Node state.""" -from typing import Any, Dict +from typing import Any, Dict, Optional from flwr.common import Context, RecordSet @@ -23,14 +23,17 @@ class NodeState: """State of a node where client nodes execute runs.""" - def __init__(self) -> None: + def __init__(self, partition_id: Optional[int]) -> None: self._meta: Dict[str, Any] = {} # holds metadata about the node self.run_contexts: Dict[int, Context] = {} + self._partition_id = partition_id def register_context(self, run_id: int) -> None: """Register new run context for this node.""" if run_id not in self.run_contexts: - self.run_contexts[run_id] = Context(state=RecordSet()) + self.run_contexts[run_id] = Context( + state=RecordSet(), partition_id=self._partition_id + ) def retrieve_context(self, run_id: int) -> Context: """Get run context given a run_id.""" diff --git a/src/py/flwr/client/node_state_tests.py b/src/py/flwr/client/node_state_tests.py index 193f52661579..311dbd41d742 100644 --- a/src/py/flwr/client/node_state_tests.py +++ b/src/py/flwr/client/node_state_tests.py @@ -41,7 +41,7 @@ def test_multirun_in_node_state() -> None: expected_values = {0: "1", 1: "1" * 3, 2: "1" * 2, 3: "1", 5: "1"} # NodeState - node_state = NodeState() + node_state = NodeState(partition_id=None) for task in tasks: run_id = task.run_id diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index c9a16edeaf15..20e0c44eab14 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -67,6 +67,7 @@ def run_supernode() -> None: authentication_keys=authentication_keys, max_retries=args.max_retries, max_wait_time=args.max_wait_time, + partition_id=args.partition_id, ) # Graceful shutdown @@ -373,6 +374,13 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: type=str, help="The SuperNode's public key (as a path str) to enable authentication.", ) + parser.add_argument( + "--partition-id", + type=int, + help="The data partition index associated with this SuperNode. Better suited " + "for prototyping purposes where a SuperNode might only load a fraction of an " + "artificially partitioned dataset (e.g. using `flwr-datasets`)", + ) def _try_setup_client_authentication( diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 3c9628a6d2a3..ea4bf100cf80 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -291,8 +291,8 @@ def start_vce( # Construct mapping of NodeStates node_states: Dict[int, NodeState] = {} - for node_id in nodes_mapping: - node_states[node_id] = NodeState() + for node_id, partition_id in nodes_mapping.items(): + node_states[node_id] = NodeState(partition_id=partition_id) # Load backend config log(DEBUG, "Supported backends: %s", list(supported_backends.keys())) diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index d3d103bb377a..17edb8e576a7 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -53,7 +53,7 @@ def _load_app() -> ClientApp: self.app_fn = _load_app self.actor_pool = actor_pool - self.proxy_state = NodeState() + self.proxy_state = NodeState(partition_id=int(self.cid)) def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: """Sumbit a message to the ActorPool.""" From 1137e5901e9bbb2237bd47153cec00c2452ad9b9 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Jun 2024 20:01:56 +0200 Subject: [PATCH 096/595] refactor(framework) Consolidate node/run ID generation (#3569) --- src/py/flwr/common/constant.py | 3 +++ src/py/flwr/server/driver/inmemory_driver_test.py | 6 +++--- src/py/flwr/server/superlink/state/in_memory_state.py | 8 ++++---- src/py/flwr/server/superlink/state/sqlite_state.py | 8 ++++---- src/py/flwr/server/superlink/state/utils.py | 6 ++++++ 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index ce29b3edb30e..f14959589458 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -46,6 +46,9 @@ PING_RANDOM_RANGE = (-0.1, 0.1) PING_MAX_INTERVAL = 1e300 +# IDs +RUN_ID_NUM_BYTES = 8 +NODE_ID_NUM_BYTES = 8 GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version" GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit" diff --git a/src/py/flwr/server/driver/inmemory_driver_test.py b/src/py/flwr/server/driver/inmemory_driver_test.py index eff38f548826..0cc1c5a53e13 100644 --- a/src/py/flwr/server/driver/inmemory_driver_test.py +++ b/src/py/flwr/server/driver/inmemory_driver_test.py @@ -15,7 +15,6 @@ """Tests for in-memory driver.""" -import os import time import unittest from typing import Iterable, List, Tuple @@ -23,7 +22,7 @@ from uuid import uuid4 from flwr.common import RecordSet -from flwr.common.constant import PING_MAX_INTERVAL +from flwr.common.constant import NODE_ID_NUM_BYTES, PING_MAX_INTERVAL from flwr.common.message import Error from flwr.common.serde import ( error_to_proto, @@ -34,6 +33,7 @@ from flwr.common.typing import Run from flwr.proto.task_pb2 import Task, TaskRes # pylint: disable=E0611 from flwr.server.superlink.state import InMemoryState, SqliteState, StateFactory +from flwr.server.superlink.state.utils import generate_rand_int_from_bytes from .inmemory_driver import InMemoryDriver @@ -82,7 +82,7 @@ def setUp(self) -> None: self.num_nodes = 42 self.state = MagicMock() self.state.get_nodes.return_value = [ - int.from_bytes(os.urandom(8), "little", signed=True) + generate_rand_int_from_bytes(NODE_ID_NUM_BYTES) for _ in range(self.num_nodes) ] self.state.get_run.return_value = Run( diff --git a/src/py/flwr/server/superlink/state/in_memory_state.py b/src/py/flwr/server/superlink/state/in_memory_state.py index da9c754c3115..5a4e4eb0fd9a 100644 --- a/src/py/flwr/server/superlink/state/in_memory_state.py +++ b/src/py/flwr/server/superlink/state/in_memory_state.py @@ -15,7 +15,6 @@ """In-memory State implementation.""" -import os import threading import time from logging import ERROR @@ -23,12 +22,13 @@ from uuid import UUID, uuid4 from flwr.common import log, now +from flwr.common.constant import NODE_ID_NUM_BYTES, RUN_ID_NUM_BYTES from flwr.common.typing import Run from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 from flwr.server.superlink.state.state import State from flwr.server.utils import validate_task_ins_or_res -from .utils import make_node_unavailable_taskres +from .utils import generate_rand_int_from_bytes, make_node_unavailable_taskres class InMemoryState(State): # pylint: disable=R0902,R0904 @@ -216,7 +216,7 @@ def create_node( ) -> int: """Create, store in state, and return `node_id`.""" # Sample a random int64 as node_id - node_id: int = int.from_bytes(os.urandom(8), "little", signed=True) + node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES) with self.lock: if node_id in self.node_ids: @@ -279,7 +279,7 @@ def create_run(self, fab_id: str, fab_version: str) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" # Sample a random int64 as run_id with self.lock: - run_id: int = int.from_bytes(os.urandom(8), "little", signed=True) + run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) if run_id not in self.run_ids: self.run_ids[run_id] = Run( diff --git a/src/py/flwr/server/superlink/state/sqlite_state.py b/src/py/flwr/server/superlink/state/sqlite_state.py index 4df9470ded62..725f7c2dff4b 100644 --- a/src/py/flwr/server/superlink/state/sqlite_state.py +++ b/src/py/flwr/server/superlink/state/sqlite_state.py @@ -15,7 +15,6 @@ """SQLite based implemenation of server state.""" -import os import re import sqlite3 import time @@ -24,6 +23,7 @@ from uuid import UUID, uuid4 from flwr.common import log, now +from flwr.common.constant import NODE_ID_NUM_BYTES, RUN_ID_NUM_BYTES from flwr.common.typing import Run from flwr.proto.node_pb2 import Node # pylint: disable=E0611 from flwr.proto.recordset_pb2 import RecordSet # pylint: disable=E0611 @@ -31,7 +31,7 @@ from flwr.server.utils.validator import validate_task_ins_or_res from .state import State -from .utils import make_node_unavailable_taskres +from .utils import generate_rand_int_from_bytes, make_node_unavailable_taskres SQL_CREATE_TABLE_NODE = """ CREATE TABLE IF NOT EXISTS node( @@ -541,7 +541,7 @@ def create_node( ) -> int: """Create, store in state, and return `node_id`.""" # Sample a random int64 as node_id - node_id: int = int.from_bytes(os.urandom(8), "little", signed=True) + node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES) query = "SELECT node_id FROM node WHERE public_key = :public_key;" row = self.query(query, {"public_key": public_key}) @@ -616,7 +616,7 @@ def get_node_id(self, client_public_key: bytes) -> Optional[int]: def create_run(self, fab_id: str, fab_version: str) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" # Sample a random int64 as run_id - run_id: int = int.from_bytes(os.urandom(8), "little", signed=True) + run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) # Check conflicts query = "SELECT COUNT(*) FROM run WHERE run_id = ?;" diff --git a/src/py/flwr/server/superlink/state/utils.py b/src/py/flwr/server/superlink/state/utils.py index 233a90946cc7..b12a87ac998d 100644 --- a/src/py/flwr/server/superlink/state/utils.py +++ b/src/py/flwr/server/superlink/state/utils.py @@ -17,6 +17,7 @@ import time from logging import ERROR +from os import urandom from uuid import uuid4 from flwr.common import log @@ -31,6 +32,11 @@ ) +def generate_rand_int_from_bytes(num_bytes: int) -> int: + """Generate a random `num_bytes` integer.""" + return int.from_bytes(urandom(num_bytes), "little", signed=True) + + def make_node_unavailable_taskres(ref_taskins: TaskIns) -> TaskRes: """Generate a TaskRes with a node unavailable error from a TaskIns.""" current_time = time.time() From 5fe288aec10386a9f479750d5f968f47236dece6 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 29 Jun 2024 20:46:53 +0200 Subject: [PATCH 097/595] feat(framework) Pass `partition_id` from `Context` into `client_fn` (#3696) --- .../client/message_handler/message_handler.py | 2 +- src/py/flwr/common/message.py | 17 ----------------- src/py/flwr/common/message_test.py | 1 - .../superlink/fleet/vce/backend/raybackend.py | 2 +- .../flwr/server/superlink/fleet/vce/vce_api.py | 7 +------ .../ray_transport/ray_client_proxy.py | 1 - .../ray_transport/ray_client_proxy_test.py | 8 ++++++-- 7 files changed, 9 insertions(+), 29 deletions(-) diff --git a/src/py/flwr/client/message_handler/message_handler.py b/src/py/flwr/client/message_handler/message_handler.py index 68326852970f..96ff96fa2291 100644 --- a/src/py/flwr/client/message_handler/message_handler.py +++ b/src/py/flwr/client/message_handler/message_handler.py @@ -93,7 +93,7 @@ def handle_legacy_message_from_msgtype( client_fn: ClientFn, message: Message, context: Context ) -> Message: """Handle legacy message in the inner most mod.""" - client = client_fn(str(message.metadata.partition_id)) + client = client_fn(str(context.partition_id)) # Check if NumPyClient is returend if isinstance(client, NumPyClient): diff --git a/src/py/flwr/common/message.py b/src/py/flwr/common/message.py index 7f7a0e4dd995..4138fc95a591 100644 --- a/src/py/flwr/common/message.py +++ b/src/py/flwr/common/message.py @@ -48,10 +48,6 @@ class Metadata: # pylint: disable=too-many-instance-attributes message_type : str A string that encodes the action to be executed on the receiving end. - partition_id : Optional[int] - An identifier that can be used when loading a particular - data partition for a ClientApp. Making use of this identifier - is more relevant when conducting simulations. """ def __init__( # pylint: disable=too-many-arguments @@ -64,7 +60,6 @@ def __init__( # pylint: disable=too-many-arguments group_id: str, ttl: float, message_type: str, - partition_id: int | None = None, ) -> None: var_dict = { "_run_id": run_id, @@ -75,7 +70,6 @@ def __init__( # pylint: disable=too-many-arguments "_group_id": group_id, "_ttl": ttl, "_message_type": message_type, - "_partition_id": partition_id, } self.__dict__.update(var_dict) @@ -149,16 +143,6 @@ def message_type(self, value: str) -> None: """Set message_type.""" self.__dict__["_message_type"] = value - @property - def partition_id(self) -> int | None: - """An identifier telling which data partition a ClientApp should use.""" - return cast(int, self.__dict__["_partition_id"]) - - @partition_id.setter - def partition_id(self, value: int) -> None: - """Set partition_id.""" - self.__dict__["_partition_id"] = value - def __repr__(self) -> str: """Return a string representation of this instance.""" view = ", ".join([f"{k.lstrip('_')}={v!r}" for k, v in self.__dict__.items()]) @@ -398,5 +382,4 @@ def _create_reply_metadata(msg: Message, ttl: float) -> Metadata: group_id=msg.metadata.group_id, ttl=ttl, message_type=msg.metadata.message_type, - partition_id=msg.metadata.partition_id, ) diff --git a/src/py/flwr/common/message_test.py b/src/py/flwr/common/message_test.py index daee57896903..c6142cb18256 100644 --- a/src/py/flwr/common/message_test.py +++ b/src/py/flwr/common/message_test.py @@ -174,7 +174,6 @@ def test_create_reply( "group_id": "group_xyz", "ttl": 10.0, "message_type": "request", - "partition_id": None, }, ), (Error, {"code": 1, "reason": "reason_098"}), diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py index 8a21393db590..b6b9e248a656 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py @@ -168,7 +168,7 @@ async def process_message( Return output message and updated context. """ - partition_id = message.metadata.partition_id + partition_id = context.partition_id try: # Submit a task to the pool diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index ea4bf100cf80..78dc6a900aae 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -57,7 +57,6 @@ async def worker( queue: "asyncio.Queue[TaskIns]", node_states: Dict[int, NodeState], state_factory: StateFactory, - nodes_mapping: NodeToPartitionMapping, backend: Backend, ) -> None: """Get TaskIns from queue and pass it to an actor in the pool to execute it.""" @@ -74,8 +73,6 @@ async def worker( # Convert TaskIns to Message message = message_from_taskins(task_ins) - # Set partition_id - message.metadata.partition_id = nodes_mapping[node_id] # Let backend process message out_mssg, updated_context = await backend.process_message( @@ -187,9 +184,7 @@ async def run( # Add workers (they submit Messages to Backend) worker_tasks = [ asyncio.create_task( - worker( - app_fn, queue, node_states, state_factory, nodes_mapping, backend - ) + worker(app_fn, queue, node_states, state_factory, backend) ) for _ in range(backend.num_workers) ] diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index 17edb8e576a7..f8e3a00cdb0e 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -107,7 +107,6 @@ def _wrap_recordset_in_message( reply_to_message="", ttl=timeout if timeout else DEFAULT_TTL, message_type=message_type, - partition_id=int(self.cid), ), ) diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 9680b3846f1d..27fd6ee74089 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -205,12 +205,16 @@ def _load_app() -> ClientApp: reply_to_message="", ttl=DEFAULT_TTL, message_type=MessageTypeLegacy.GET_PROPERTIES, - partition_id=int(cid), ), ) pool.submit_client_job( lambda a, c_fn, j_fn, cid_, state: a.run.remote(c_fn, j_fn, cid_, state), - (_load_app, message, cid, Context(state=RecordSet())), + ( + _load_app, + message, + cid, + Context(state=RecordSet(), partition_id=int(cid)), + ), ) # fetch results one at a time From 04fb0d792fa2d4e18d9abe89377ee293d11023fa Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 30 Jun 2024 18:40:46 +0200 Subject: [PATCH 098/595] feat(framework) Introduce new `client_fn` signature (#3697) Co-authored-by: Daniel J. Beutel --- src/py/flwr/client/__init__.py | 2 ++ src/py/flwr/client/app.py | 17 +++++----- src/py/flwr/client/client_app.py | 33 ++++++++++++++++--- .../client/message_handler/message_handler.py | 7 ++-- .../message_handler/message_handler_test.py | 10 +++--- src/py/flwr/client/typing.py | 3 +- .../fleet/vce/backend/raybackend_test.py | 4 ++- src/py/flwr/simulation/app.py | 24 +++++++------- .../ray_transport/ray_client_proxy.py | 4 +-- .../ray_transport/ray_client_proxy_test.py | 16 +++++---- 10 files changed, 78 insertions(+), 42 deletions(-) diff --git a/src/py/flwr/client/__init__.py b/src/py/flwr/client/__init__.py index 58fd94448586..218f2fe20d62 100644 --- a/src/py/flwr/client/__init__.py +++ b/src/py/flwr/client/__init__.py @@ -23,11 +23,13 @@ from .supernode import run_client_app as run_client_app from .supernode import run_supernode as run_supernode from .typing import ClientFn as ClientFn +from .typing import ClientFnExt as ClientFnExt __all__ = [ "Client", "ClientApp", "ClientFn", + "ClientFnExt", "NumPyClient", "mod", "run_client_app", diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 23e5a4cd38c9..fa446afcc1fb 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -26,7 +26,7 @@ from flwr.client.client import Client from flwr.client.client_app import ClientApp, LoadClientAppError -from flwr.client.typing import ClientFn +from flwr.client.typing import ClientFnExt from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, Message, event from flwr.common.address import parse_address from flwr.common.constant import ( @@ -51,7 +51,7 @@ def _check_actionable_client( - client: Optional[Client], client_fn: Optional[ClientFn] + client: Optional[Client], client_fn: Optional[ClientFnExt] ) -> None: if client_fn is None and client is None: raise ValueError( @@ -72,7 +72,7 @@ def _check_actionable_client( def start_client( *, server_address: str, - client_fn: Optional[ClientFn] = None, + client_fn: Optional[ClientFnExt] = None, client: Optional[Client] = None, grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, root_certificates: Optional[Union[bytes, str]] = None, @@ -92,7 +92,7 @@ def start_client( The IPv4 or IPv6 address of the server. If the Flower server runs on the same machine on port 8080, then `server_address` would be `"[::]:8080"`. - client_fn : Optional[ClientFn] + client_fn : Optional[ClientFnExt] A callable that instantiates a Client. (default: None) client : Optional[flwr.client.Client] An implementation of the abstract base @@ -136,7 +136,7 @@ class `flwr.client.Client` (default: None) Starting an SSL-enabled gRPC client using system certificates: - >>> def client_fn(cid: str): + >>> def client_fn(node_id: int, partition_id: Optional[int]): >>> return FlowerClient() >>> >>> start_client( @@ -180,7 +180,7 @@ def _start_client_internal( *, server_address: str, load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None, - client_fn: Optional[ClientFn] = None, + client_fn: Optional[ClientFnExt] = None, client: Optional[Client] = None, grpc_max_message_length: int = GRPC_MAX_MESSAGE_LENGTH, root_certificates: Optional[Union[bytes, str]] = None, @@ -203,7 +203,7 @@ def _start_client_internal( would be `"[::]:8080"`. load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None) A function that can be used to load a `ClientApp` instance. - client_fn : Optional[ClientFn] + client_fn : Optional[ClientFnExt] A callable that instantiates a Client. (default: None) client : Optional[flwr.client.Client] An implementation of the abstract base @@ -248,7 +248,8 @@ class `flwr.client.Client` (default: None) if client_fn is None: # Wrap `Client` instance in `client_fn` def single_client_factory( - cid: str, # pylint: disable=unused-argument + node_id: int, # pylint: disable=unused-argument + partition_id: Optional[int], # pylint: disable=unused-argument ) -> Client: if client is None: # Added this to keep mypy happy raise ValueError( diff --git a/src/py/flwr/client/client_app.py b/src/py/flwr/client/client_app.py index 2e810f6560f2..663d83a8b19e 100644 --- a/src/py/flwr/client/client_app.py +++ b/src/py/flwr/client/client_app.py @@ -15,19 +15,42 @@ """Flower ClientApp.""" +import inspect from typing import Callable, List, Optional +from flwr.client.client import Client from flwr.client.message_handler.message_handler import ( handle_legacy_message_from_msgtype, ) from flwr.client.mod.utils import make_ffn -from flwr.client.typing import ClientFn, Mod +from flwr.client.typing import ClientFnExt, Mod from flwr.common import Context, Message, MessageType -from flwr.common.logger import warn_preview_feature +from flwr.common.logger import warn_deprecated_feature, warn_preview_feature from .typing import ClientAppCallable +def _inspect_maybe_adapt_client_fn_signature(client_fn: ClientFnExt) -> ClientFnExt: + client_fn_args = inspect.signature(client_fn).parameters + + if not all(key in client_fn_args for key in ["node_id", "partition_id"]): + warn_deprecated_feature( + "`client_fn` now expects a signature `def client_fn(node_id: int, " + "partition_id: Optional[int])`.\nYou provided `client_fn` with signature: " + f"{dict(client_fn_args.items())}" + ) + + # Wrap depcreated client_fn inside a function with the expected signature + def adaptor_fn( + node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument + ) -> Client: + return client_fn(str(partition_id)) # type: ignore + + return adaptor_fn + + return client_fn + + class ClientAppException(Exception): """Exception raised when an exception is raised while executing a ClientApp.""" @@ -48,7 +71,7 @@ class ClientApp: >>> class FlowerClient(NumPyClient): >>> # ... >>> - >>> def client_fn(cid): + >>> def client_fn(node_id: int, partition_id: Optional[int]): >>> return FlowerClient().to_client() >>> >>> app = ClientApp(client_fn) @@ -65,7 +88,7 @@ class ClientApp: def __init__( self, - client_fn: Optional[ClientFn] = None, # Only for backward compatibility + client_fn: Optional[ClientFnExt] = None, # Only for backward compatibility mods: Optional[List[Mod]] = None, ) -> None: self._mods: List[Mod] = mods if mods is not None else [] @@ -74,6 +97,8 @@ def __init__( self._call: Optional[ClientAppCallable] = None if client_fn is not None: + client_fn = _inspect_maybe_adapt_client_fn_signature(client_fn) + def ffn( message: Message, context: Context, diff --git a/src/py/flwr/client/message_handler/message_handler.py b/src/py/flwr/client/message_handler/message_handler.py index 96ff96fa2291..e9a853a92101 100644 --- a/src/py/flwr/client/message_handler/message_handler.py +++ b/src/py/flwr/client/message_handler/message_handler.py @@ -14,7 +14,6 @@ # ============================================================================== """Client-side message handler.""" - from logging import WARN from typing import Optional, Tuple, cast @@ -25,7 +24,7 @@ maybe_call_get_properties, ) from flwr.client.numpy_client import NumPyClient -from flwr.client.typing import ClientFn +from flwr.client.typing import ClientFnExt from flwr.common import ConfigsRecord, Context, Message, Metadata, RecordSet, log from flwr.common.constant import MessageType, MessageTypeLegacy from flwr.common.recordset_compat import ( @@ -90,10 +89,10 @@ def handle_control_message(message: Message) -> Tuple[Optional[Message], int]: def handle_legacy_message_from_msgtype( - client_fn: ClientFn, message: Message, context: Context + client_fn: ClientFnExt, message: Message, context: Context ) -> Message: """Handle legacy message in the inner most mod.""" - client = client_fn(str(context.partition_id)) + client = client_fn(message.metadata.dst_node_id, context.partition_id) # Check if NumPyClient is returend if isinstance(client, NumPyClient): diff --git a/src/py/flwr/client/message_handler/message_handler_test.py b/src/py/flwr/client/message_handler/message_handler_test.py index 40907942513d..cafcbbde3984 100644 --- a/src/py/flwr/client/message_handler/message_handler_test.py +++ b/src/py/flwr/client/message_handler/message_handler_test.py @@ -19,10 +19,10 @@ import unittest import uuid from copy import copy -from typing import List +from typing import List, Optional from flwr.client import Client -from flwr.client.typing import ClientFn +from flwr.client.typing import ClientFnExt from flwr.common import ( DEFAULT_TTL, Code, @@ -113,8 +113,10 @@ def evaluate(self, ins: EvaluateIns) -> EvaluateRes: ) -def _get_client_fn(client: Client) -> ClientFn: - def client_fn(cid: str) -> Client: # pylint: disable=unused-argument +def _get_client_fn(client: Client) -> ClientFnExt: + def client_fn( + node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument + ) -> Client: return client return client_fn diff --git a/src/py/flwr/client/typing.py b/src/py/flwr/client/typing.py index 956ac7a15c05..bf66a9082c77 100644 --- a/src/py/flwr/client/typing.py +++ b/src/py/flwr/client/typing.py @@ -15,7 +15,7 @@ """Custom types for Flower clients.""" -from typing import Callable +from typing import Callable, Optional from flwr.common import Context, Message @@ -23,6 +23,7 @@ # Compatibility ClientFn = Callable[[str], Client] +ClientFnExt = Callable[[int, Optional[int]], Client] ClientAppCallable = Callable[[Message, Context], Message] Mod = Callable[[Message, Context, ClientAppCallable], Message] diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index 57c952cc9310..fa7374f08853 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -54,7 +54,9 @@ def get_properties(self, config: Config) -> Dict[str, Scalar]: return {"result": result} -def get_dummy_client(cid: str) -> Client: # pylint: disable=unused-argument +def get_dummy_client( + node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument +) -> Client: """Return a DummyClient converted to Client type.""" return DummyClient().to_client() diff --git a/src/py/flwr/simulation/app.py b/src/py/flwr/simulation/app.py index 856d6fc45e22..50a7dcad70a9 100644 --- a/src/py/flwr/simulation/app.py +++ b/src/py/flwr/simulation/app.py @@ -27,7 +27,7 @@ import ray from ray.util.scheduling_strategies import NodeAffinitySchedulingStrategy -from flwr.client import ClientFn +from flwr.client import ClientFnExt from flwr.common import EventType, event from flwr.common.logger import log, set_logger_propagation from flwr.server.client_manager import ClientManager @@ -74,7 +74,7 @@ # pylint: disable=too-many-arguments,too-many-statements,too-many-branches def start_simulation( *, - client_fn: ClientFn, + client_fn: ClientFnExt, num_clients: Optional[int] = None, clients_ids: Optional[List[str]] = None, client_resources: Optional[Dict[str, float]] = None, @@ -92,16 +92,16 @@ def start_simulation( Parameters ---------- - client_fn : ClientFn - A function creating client instances. The function must take a single - `str` argument called `cid`. It should return a single client instance - of type Client. Note that the created client instances are ephemeral - and will often be destroyed after a single method invocation. Since client - instances are not long-lived, they should not attempt to carry state over - method invocations. Any state required by the instance (model, dataset, - hyperparameters, ...) should be (re-)created in either the call to `client_fn` - or the call to any of the client methods (e.g., load evaluation data in the - `evaluate` method itself). + client_fn : ClientFnExt + A function creating Client instances. The function must have the signature + `client_fn(node_id: int, partition_id: Optional[int]). It should return + a single client instance of type Client. Note that the created client + instances are ephemeral and will often be destroyed after a single method + invocation. Since client instances are not long-lived, they should not attempt + to carry state over method invocations. Any state required by the instance + (model, dataset, hyperparameters, ...) should be (re-)created in either the + call to `client_fn` or the call to any of the client methods (e.g., load + evaluation data in the `evaluate` method itself). num_clients : Optional[int] The total number of clients in this simulation. This must be set if `clients_ids` is not set and vice-versa. diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index f8e3a00cdb0e..3a9712df978e 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -20,7 +20,7 @@ from typing import Optional from flwr import common -from flwr.client import ClientFn +from flwr.client import ClientFnExt from flwr.client.client_app import ClientApp from flwr.client.node_state import NodeState from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet @@ -44,7 +44,7 @@ class RayActorClientProxy(ClientProxy): """Flower client proxy which delegates work using Ray.""" def __init__( - self, client_fn: ClientFn, cid: str, actor_pool: VirtualClientEngineActorPool + self, client_fn: ClientFnExt, cid: str, actor_pool: VirtualClientEngineActorPool ): super().__init__(cid) diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 27fd6ee74089..df059135b4e0 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -17,7 +17,7 @@ from math import pi from random import shuffle -from typing import Dict, List, Tuple, Type +from typing import Dict, List, Optional, Tuple, Type import ray @@ -50,12 +50,12 @@ class DummyClient(NumPyClient): """A dummy NumPyClient for tests.""" - def __init__(self, cid: str) -> None: - self.cid = int(cid) + def __init__(self, node_id: int) -> None: + self.node_id = node_id def get_properties(self, config: Config) -> Dict[str, Scalar]: """Return properties by doing a simple calculation.""" - result = int(self.cid) * pi + result = self.node_id * pi # store something in context self.context.state.configs_records["result"] = ConfigsRecord( @@ -64,9 +64,13 @@ def get_properties(self, config: Config) -> Dict[str, Scalar]: return {"result": result} -def get_dummy_client(cid: str) -> Client: +def get_dummy_client( + node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument +) -> Client: """Return a DummyClient converted to Client type.""" - return DummyClient(cid).to_client() + if partition_id is None: + raise ValueError("`partition_id` is not set.") + return DummyClient(partition_id).to_client() def prep( From 4418171c32a40f54fcefe342cc7ae5cbaf3afe6a Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Mon, 1 Jul 2024 19:06:44 +0200 Subject: [PATCH 099/595] docs(examples) Update XGBoost tutorial (#3634) --- doc/source/tutorial-quickstart-xgboost.rst | 212 +++++++++++++-------- 1 file changed, 134 insertions(+), 78 deletions(-) diff --git a/doc/source/tutorial-quickstart-xgboost.rst b/doc/source/tutorial-quickstart-xgboost.rst index 7ac055138814..34ad5f6e99c0 100644 --- a/doc/source/tutorial-quickstart-xgboost.rst +++ b/doc/source/tutorial-quickstart-xgboost.rst @@ -96,26 +96,26 @@ Prior to local training, we require loading the HIGGS dataset from Flower Datase fds = FederatedDataset(dataset="jxie/higgs", partitioners={"train": partitioner}) # Load the partition for this `node_id` - partition = fds.load_partition(node_id=args.node_id, split="train") + partition = fds.load_partition(partition_id=args.partition_id, split="train") partition.set_format("numpy") -In this example, we split the dataset into two partitions with uniform distribution (:code:`IidPartitioner(num_partitions=2)`). -Then, we load the partition for the given client based on :code:`node_id`: +In this example, we split the dataset into 30 partitions with uniform distribution (:code:`IidPartitioner(num_partitions=30)`). +Then, we load the partition for the given client based on :code:`partition_id`: .. code-block:: python - # We first define arguments parser for user to specify the client/node ID. + # We first define arguments parser for user to specify the client/partition ID. parser = argparse.ArgumentParser() parser.add_argument( - "--node-id", + "--partition-id", default=0, type=int, - help="Node ID used for the current client.", + help="Partition ID used for the current client.", ) args = parser.parse_args() - # Load the partition for this `node_id`. - partition = fds.load_partition(idx=args.node_id, split="train") + # Load the partition for this `partition_id`. + partition = fds.load_partition(idx=args.partition_id, split="train") partition.set_format("numpy") After that, we do train/test splitting on the given partition (client's local data), and transform data format for :code:`xgboost` package. @@ -186,12 +186,23 @@ We follow the general rule to define :code:`XgbClient` class inherited from :cod .. code-block:: python class XgbClient(fl.client.Client): - def __init__(self): - self.bst = None - self.config = None + def __init__( + self, + train_dmatrix, + valid_dmatrix, + num_train, + num_val, + num_local_round, + params, + ): + self.train_dmatrix = train_dmatrix + self.valid_dmatrix = valid_dmatrix + self.num_train = num_train + self.num_val = num_val + self.num_local_round = num_local_round + self.params = params -The :code:`self.bst` is used to keep the Booster objects that remain consistent across rounds, -allowing them to store predictions from trees integrated in earlier rounds and maintain other essential data structures for training. +All required parameters defined above are passed to :code:`XgbClient`'s constructor. Then, we override :code:`get_parameters`, :code:`fit` and :code:`evaluate` methods insides :code:`XgbClient` class as follows. @@ -214,27 +225,27 @@ As a result, let's return an empty tensor in :code:`get_parameters` when it is c .. code-block:: python def fit(self, ins: FitIns) -> FitRes: - if not self.bst: + global_round = int(ins.config["global_round"]) + if global_round == 1: # First round local training - log(INFO, "Start training at round 1") bst = xgb.train( - params, - train_dmatrix, - num_boost_round=num_local_round, - evals=[(valid_dmatrix, "validate"), (train_dmatrix, "train")], + self.params, + self.train_dmatrix, + num_boost_round=self.num_local_round, + evals=[(self.valid_dmatrix, "validate"), (self.train_dmatrix, "train")], ) - self.config = bst.save_config() - self.bst = bst else: + bst = xgb.Booster(params=self.params) for item in ins.parameters.tensors: global_model = bytearray(item) # Load global model into booster - self.bst.load_model(global_model) - self.bst.load_config(self.config) + bst.load_model(global_model) - bst = self._local_boost() + # Local training + bst = self._local_boost(bst) + # Save model local_model = bst.save_raw("json") local_model_bytes = bytes(local_model) @@ -244,60 +255,81 @@ As a result, let's return an empty tensor in :code:`get_parameters` when it is c message="OK", ), parameters=Parameters(tensor_type="", tensors=[local_model_bytes]), - num_examples=num_train, + num_examples=self.num_train, metrics={}, ) In :code:`fit`, at the first round, we call :code:`xgb.train()` to build up the first set of trees. -the returned Booster object and config are stored in :code:`self.bst` and :code:`self.config`, respectively. -From the second round, we load the global model sent from server to :code:`self.bst`, +From the second round, we load the global model sent from server to new build Booster object, and then update model weights on local training data with function :code:`local_boost` as follows: .. code-block:: python - def _local_boost(self): + def _local_boost(self, bst_input): # Update trees based on local training data. - for i in range(num_local_round): - self.bst.update(train_dmatrix, self.bst.num_boosted_rounds()) + for i in range(self.num_local_round): + bst_input.update(self.train_dmatrix, bst_input.num_boosted_rounds()) - # Extract the last N=num_local_round trees for sever aggregation - bst = self.bst[ - self.bst.num_boosted_rounds() - - num_local_round : self.bst.num_boosted_rounds() + # Bagging: extract the last N=num_local_round trees for sever aggregation + bst = bst_input[ + bst_input.num_boosted_rounds() + - self.num_local_round : bst_input.num_boosted_rounds() ] -Given :code:`num_local_round`, we update trees by calling :code:`self.bst.update` method. + return bst + +Given :code:`num_local_round`, we update trees by calling :code:`bst_input.update` method. After training, the last :code:`N=num_local_round` trees will be extracted to send to the server. .. code-block:: python def evaluate(self, ins: EvaluateIns) -> EvaluateRes: - eval_results = self.bst.eval_set( - evals=[(valid_dmatrix, "valid")], - iteration=self.bst.num_boosted_rounds() - 1, + # Load global model + bst = xgb.Booster(params=self.params) + for para in ins.parameters.tensors: + para_b = bytearray(para) + bst.load_model(para_b) + + # Run evaluation + eval_results = bst.eval_set( + evals=[(self.valid_dmatrix, "valid")], + iteration=bst.num_boosted_rounds() - 1, ) auc = round(float(eval_results.split("\t")[1].split(":")[1]), 4) + global_round = ins.config["global_round"] + log(INFO, f"AUC = {auc} at round {global_round}") + return EvaluateRes( status=Status( code=Code.OK, message="OK", ), loss=0.0, - num_examples=num_val, + num_examples=self.num_val, metrics={"AUC": auc}, ) -In :code:`evaluate`, we call :code:`self.bst.eval_set` function to conduct evaluation on valid set. +In :code:`evaluate`, after loading the global model, we call :code:`bst.eval_set` function to conduct evaluation on valid set. The AUC value will be returned. Now, we can create an instance of our class :code:`XgbClient` and add one line to actually run this client: .. code-block:: python - fl.client.start_client(server_address="127.0.0.1:8080", client=XgbClient()) + fl.client.start_client( + server_address="127.0.0.1:8080", + client=XgbClient( + train_dmatrix, + valid_dmatrix, + num_train, + num_val, + num_local_round, + params, + ).to_client(), + ) -That's it for the client. We only have to implement :code:`Client`and call :code:`fl.client.start_client()`. +That's it for the client. We only have to implement :code:`Client` and call :code:`fl.client.start_client()`. The string :code:`"[::]:8080"` tells the client which server to connect to. In our case we can run the server and the client on the same machine, therefore we use :code:`"[::]:8080"`. If we run a truly federated workload with the server and @@ -325,6 +357,8 @@ We first define a strategy for XGBoost bagging aggregation. min_evaluate_clients=2, fraction_evaluate=1.0, evaluate_metrics_aggregation_fn=evaluate_metrics_aggregation, + on_evaluate_config_fn=config_func, + on_fit_config_fn=config_func, ) def evaluate_metrics_aggregation(eval_metrics): @@ -336,8 +370,16 @@ We first define a strategy for XGBoost bagging aggregation. metrics_aggregated = {"AUC": auc_aggregated} return metrics_aggregated + def config_func(rnd: int) -> Dict[str, str]: + """Return a configuration with global epochs.""" + config = { + "global_round": str(rnd), + } + return config + We use two clients for this example. An :code:`evaluate_metrics_aggregation` function is defined to collect and wighted average the AUC values from clients. +The :code:`config_func` function is to return the current FL round number to client's :code:`fit()` and :code:`evaluate()` methods. Then, we start the server: @@ -346,7 +388,7 @@ Then, we start the server: # Start Flower server fl.server.start_server( server_address="0.0.0.0:8080", - config=fl.server.ServerConfig(num_rounds=num_rounds), + config=fl.server.ServerConfig(num_rounds=5), strategy=strategy, ) @@ -535,52 +577,66 @@ Open a new terminal and start the first client: .. code-block:: shell - $ python3 client.py --node-id=0 + $ python3 client.py --partition-id=0 Open another terminal and start the second client: .. code-block:: shell - $ python3 client.py --node-id=1 + $ python3 client.py --partition-id=1 Each client will have its own dataset. You should now see how the training does in the very first terminal (the one that started the server): .. code-block:: shell - INFO flwr 2023-11-20 11:21:56,454 | app.py:163 | Starting Flower server, config: ServerConfig(num_rounds=5, round_timeout=None) - INFO flwr 2023-11-20 11:21:56,473 | app.py:176 | Flower ECE: gRPC server running (5 rounds), SSL is disabled - INFO flwr 2023-11-20 11:21:56,473 | server.py:89 | Initializing global parameters - INFO flwr 2023-11-20 11:21:56,473 | server.py:276 | Requesting initial parameters from one random client - INFO flwr 2023-11-20 11:22:38,302 | server.py:280 | Received initial parameters from one random client - INFO flwr 2023-11-20 11:22:38,302 | server.py:91 | Evaluating initial parameters - INFO flwr 2023-11-20 11:22:38,302 | server.py:104 | FL starting - DEBUG flwr 2023-11-20 11:22:38,302 | server.py:222 | fit_round 1: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:38,636 | server.py:236 | fit_round 1 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:38,643 | server.py:173 | evaluate_round 1: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:38,653 | server.py:187 | evaluate_round 1 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:38,653 | server.py:222 | fit_round 2: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:38,721 | server.py:236 | fit_round 2 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:38,745 | server.py:173 | evaluate_round 2: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:38,756 | server.py:187 | evaluate_round 2 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:38,756 | server.py:222 | fit_round 3: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:38,831 | server.py:236 | fit_round 3 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:38,868 | server.py:173 | evaluate_round 3: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:38,881 | server.py:187 | evaluate_round 3 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:38,881 | server.py:222 | fit_round 4: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:38,960 | server.py:236 | fit_round 4 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:39,012 | server.py:173 | evaluate_round 4: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:39,026 | server.py:187 | evaluate_round 4 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:39,026 | server.py:222 | fit_round 5: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:39,111 | server.py:236 | fit_round 5 received 2 results and 0 failures - DEBUG flwr 2023-11-20 11:22:39,177 | server.py:173 | evaluate_round 5: strategy sampled 2 clients (out of 2) - DEBUG flwr 2023-11-20 11:22:39,193 | server.py:187 | evaluate_round 5 received 2 results and 0 failures - INFO flwr 2023-11-20 11:22:39,193 | server.py:153 | FL finished in 0.8905023969999988 - INFO flwr 2023-11-20 11:22:39,193 | app.py:226 | app_fit: losses_distributed [(1, 0), (2, 0), (3, 0), (4, 0), (5, 0)] - INFO flwr 2023-11-20 11:22:39,193 | app.py:227 | app_fit: metrics_distributed_fit {} - INFO flwr 2023-11-20 11:22:39,193 | app.py:228 | app_fit: metrics_distributed {'AUC': [(1, 0.7572), (2, 0.7705), (3, 0.77595), (4, 0.78), (5, 0.78385)]} - INFO flwr 2023-11-20 11:22:39,193 | app.py:229 | app_fit: losses_centralized [] - INFO flwr 2023-11-20 11:22:39,193 | app.py:230 | app_fit: metrics_centralized {} + INFO : Starting Flower server, config: num_rounds=5, no round_timeout + INFO : Flower ECE: gRPC server running (5 rounds), SSL is disabled + INFO : [INIT] + INFO : Requesting initial parameters from one random client + INFO : Received initial parameters from one random client + INFO : Evaluating initial global parameters + INFO : + INFO : [ROUND 1] + INFO : configure_fit: strategy sampled 2 clients (out of 2) + INFO : aggregate_fit: received 2 results and 0 failures + INFO : configure_evaluate: strategy sampled 2 clients (out of 2) + INFO : aggregate_evaluate: received 2 results and 0 failures + INFO : + INFO : [ROUND 2] + INFO : configure_fit: strategy sampled 2 clients (out of 2) + INFO : aggregate_fit: received 2 results and 0 failures + INFO : configure_evaluate: strategy sampled 2 clients (out of 2) + INFO : aggregate_evaluate: received 2 results and 0 failures + INFO : + INFO : [ROUND 3] + INFO : configure_fit: strategy sampled 2 clients (out of 2) + INFO : aggregate_fit: received 2 results and 0 failures + INFO : configure_evaluate: strategy sampled 2 clients (out of 2) + INFO : aggregate_evaluate: received 2 results and 0 failures + INFO : + INFO : [ROUND 4] + INFO : configure_fit: strategy sampled 2 clients (out of 2) + INFO : aggregate_fit: received 2 results and 0 failures + INFO : configure_evaluate: strategy sampled 2 clients (out of 2) + INFO : aggregate_evaluate: received 2 results and 0 failures + INFO : + INFO : [ROUND 5] + INFO : configure_fit: strategy sampled 2 clients (out of 2) + INFO : aggregate_fit: received 2 results and 0 failures + INFO : configure_evaluate: strategy sampled 2 clients (out of 2) + INFO : aggregate_evaluate: received 2 results and 0 failures + INFO : + INFO : [SUMMARY] + INFO : Run finished 5 round(s) in 1.67s + INFO : History (loss, distributed): + INFO : round 1: 0 + INFO : round 2: 0 + INFO : round 3: 0 + INFO : round 4: 0 + INFO : round 5: 0 + INFO : History (metrics, distributed, evaluate): + INFO : {'AUC': [(1, 0.76755), (2, 0.775), (3, 0.77935), (4, 0.7836), (5, 0.7872)]} Congratulations! You've successfully built and run your first federated XGBoost system. From d87b737f894946837fe7549cdbe7526b9fb364c7 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 2 Jul 2024 01:16:07 +0200 Subject: [PATCH 100/595] docs(examples) Add table and improve design (#3688) --- dev/build-docs.sh | 4 +- dev/build-example-docs.py | 236 ++++++++++++++++++ dev/update-examples.sh | 91 ------- examples/advanced-pytorch/README.md | 7 + examples/advanced-tensorflow/README.md | 7 + examples/android-kotlin/README.md | 8 + examples/android/README.md | 8 + examples/app-pytorch/README.md | 7 + examples/app-secure-aggregation/README.md | 7 + examples/custom-metrics/README.md | 7 + examples/custom-mods/README.md | 7 + examples/doc/source/.gitignore | 1 + examples/embedded-devices/README.md | 11 +- .../federated-kaplan-meier-fitter/README.md | 8 + examples/fl-dp-sa/README.md | 9 +- examples/fl-tabular/README.md | 7 + examples/flower-authentication/README.md | 7 + examples/flower-in-30-minutes/README.md | 7 + .../README.md | 7 + examples/flower-via-docker-compose/README.md | 7 + examples/ios/README.md | 7 + examples/llm-flowertune/README.md | 7 + examples/opacus/README.md | 7 + .../README.md | 7 + .../README.md | 7 + examples/quickstart-cpp/README.md | 7 + examples/quickstart-fastai/README.md | 7 + examples/quickstart-huggingface/README.md | 7 + examples/quickstart-jax/README.md | 7 + examples/quickstart-mlcube/README.md | 7 + examples/quickstart-mlx/README.md | 7 + examples/quickstart-monai/README.md | 7 + examples/quickstart-pandas/README.md | 7 + .../quickstart-pytorch-lightning/README.md | 7 + examples/quickstart-pytorch/README.md | 7 + examples/quickstart-sklearn-tabular/README.md | 7 + examples/quickstart-tabnet/README.md | 7 + examples/quickstart-tensorflow/README.md | 7 + examples/simulation-pytorch/README.md | 7 + examples/simulation-tensorflow/README.md | 7 + examples/sklearn-logreg-mnist/README.md | 7 + examples/tensorflow-privacy/README.md | 7 + examples/vertical-fl/README.md | 8 + examples/vit-finetune/README.md | 7 + .../whisper-federated-finetuning/README.md | 8 + examples/xgboost-comprehensive/README.md | 7 + examples/xgboost-quickstart/README.md | 7 + 47 files changed, 547 insertions(+), 97 deletions(-) create mode 100644 dev/build-example-docs.py delete mode 100755 dev/update-examples.sh diff --git a/dev/build-docs.sh b/dev/build-docs.sh index f8d4f91508de..f4bf958b0ebf 100755 --- a/dev/build-docs.sh +++ b/dev/build-docs.sh @@ -8,9 +8,7 @@ cd $ROOT ./dev/build-baseline-docs.sh cd $ROOT -./dev/update-examples.sh -cd examples/doc -make docs +python dev/build-example-docs.py cd $ROOT ./datasets/dev/build-flwr-datasets-docs.sh diff --git a/dev/build-example-docs.py b/dev/build-example-docs.py new file mode 100644 index 000000000000..204380f312ac --- /dev/null +++ b/dev/build-example-docs.py @@ -0,0 +1,236 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Build the Flower Example docs.""" + +import os +import shutil +import re +import subprocess +from pathlib import Path + +ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +INDEX = os.path.join(ROOT, "examples", "doc", "source", "index.rst") + +initial_text = """ +Flower Examples Documentation +----------------------------- + +Welcome to Flower Examples' documentation. `Flower `_ is +a friendly federated learning framework. + +Join the Flower Community +------------------------- + +The Flower Community is growing quickly - we're a friendly group of researchers, +engineers, students, professionals, academics, and other enthusiasts. + +.. button-link:: https://flower.ai/join-slack + :color: primary + :shadow: + + Join us on Slack + +Quickstart Examples +------------------- + +Flower Quickstart Examples are a collection of demo projects that show how you +can use Flower in combination with other existing frameworks or technologies. + +""" + +table_headers = ( + "\n.. list-table::\n :widths: 50 15 15 15\n " + ":header-rows: 1\n\n * - Title\n - Framework\n - Dataset\n - Tags\n\n" +) + +categories = { + "quickstart": {"table": table_headers, "list": ""}, + "advanced": {"table": table_headers, "list": ""}, + "other": {"table": table_headers, "list": ""}, +} + + +def _convert_to_link(search_result): + if "|" in search_result: + if "," in search_result: + result = "" + for part in search_result.split(","): + result += f"{_convert_to_link(part)}, " + return result[:-2] + + name, url = search_result.replace('"', "").split("|") + return f"`{name.strip()} <{url.strip()}>`_" + + return search_result + + +def _read_metadata(example): + with open(os.path.join(example, "README.md")) as f: + content = f.read() + + metadata_match = re.search(r"^---(.*?)^---", content, re.DOTALL | re.MULTILINE) + if not metadata_match: + raise ValueError("Metadata block not found") + metadata = metadata_match.group(1) + + title_match = re.search(r"^title:\s*(.+)$", metadata, re.MULTILINE) + if not title_match: + raise ValueError("Title not found in metadata") + title = title_match.group(1).strip() + + labels_match = re.search(r"^labels:\s*\[(.+?)\]$", metadata, re.MULTILINE) + if not labels_match: + raise ValueError("Labels not found in metadata") + labels = labels_match.group(1).strip() + + dataset_match = re.search( + r"^dataset:\s*\[(.*?)\]$", metadata, re.DOTALL | re.MULTILINE + ) + if not dataset_match: + raise ValueError("Dataset not found in metadata") + dataset = dataset_match.group(1).strip() + + framework_match = re.search( + r"^framework:\s*\[(.*?|)\]$", metadata, re.DOTALL | re.MULTILINE + ) + if not framework_match: + raise ValueError("Framework not found in metadata") + framework = framework_match.group(1).strip() + + dataset = _convert_to_link(re.sub(r"\s+", " ", dataset).strip()) + framework = _convert_to_link(re.sub(r"\s+", " ", framework).strip()) + return title, labels, dataset, framework + + +def _add_table_entry(example, label, table_var): + title, labels, dataset, framework = _read_metadata(example) + example_name = Path(example).stem + table_entry = ( + f" * - `{title} <{example_name}.html>`_ \n " + f"- {framework} \n - {dataset} \n - {labels}\n\n" + ) + if label in labels: + categories[table_var]["table"] += table_entry + categories[table_var]["list"] += f" {example_name}\n" + return True + return False + + +def _copy_markdown_files(example): + for file in os.listdir(example): + if file.endswith(".md"): + src = os.path.join(example, file) + dest = os.path.join( + ROOT, "examples", "doc", "source", os.path.basename(example) + ".md" + ) + shutil.copyfile(src, dest) + + +def _add_gh_button(example): + gh_text = f'[View on GitHub](https://github.com/adap/flower/blob/main/examples/{example})' + readme_file = os.path.join(ROOT, "examples", "doc", "source", example + ".md") + with open(readme_file, "r+") as f: + content = f.read() + if gh_text not in content: + content = re.sub( + r"(^# .+$)", rf"\1\n\n{gh_text}", content, count=1, flags=re.MULTILINE + ) + f.seek(0) + f.write(content) + f.truncate() + + +def _copy_images(example): + static_dir = os.path.join(example, "_static") + dest_dir = os.path.join(ROOT, "examples", "doc", "source", "_static") + if os.path.isdir(static_dir): + for file in os.listdir(static_dir): + if file.endswith((".jpg", ".png", ".jpeg")): + shutil.copyfile( + os.path.join(static_dir, file), os.path.join(dest_dir, file) + ) + + +def _add_all_entries(index_file): + examples_dir = os.path.join(ROOT, "examples") + for example in sorted(os.listdir(examples_dir)): + example_path = os.path.join(examples_dir, example) + if os.path.isdir(example_path) and example != "doc": + _copy_markdown_files(example_path) + _add_gh_button(example) + _copy_images(example) + + +def _main(): + if os.path.exists(INDEX): + os.remove(INDEX) + + with open(INDEX, "w") as index_file: + index_file.write(initial_text) + + examples_dir = os.path.join(ROOT, "examples") + for example in sorted(os.listdir(examples_dir)): + example_path = os.path.join(examples_dir, example) + if os.path.isdir(example_path) and example != "doc": + _copy_markdown_files(example_path) + _add_gh_button(example) + _copy_images(example_path) + if not _add_table_entry(example_path, "quickstart", "quickstart"): + if not _add_table_entry(example_path, "comprehensive", "comprehensive"): + if not _add_table_entry(example_path, "advanced", "advanced"): + _add_table_entry(example_path, "", "other") + + with open(INDEX, "a") as index_file: + index_file.write(categories["quickstart"]["table"]) + + index_file.write("\nAdvanced Examples\n-----------------\n") + index_file.write( + "Advanced Examples are mostly for users that are both familiar with " + "Federated Learning but also somewhat familiar with Flower's main " + "features.\n" + ) + index_file.write(categories["advanced"]["table"]) + + index_file.write("\nOther Examples\n--------------\n") + index_file.write( + "Flower Examples are a collection of example projects written with " + "Flower that explore different domains and features. You can check " + "which examples already exist and/or contribute your own example.\n" + ) + index_file.write(categories["other"]["table"]) + + _add_all_entries(index_file) + + index_file.write( + "\n.. toctree::\n :maxdepth: 1\n :caption: Quickstart\n :hidden:\n\n" + ) + index_file.write(categories["quickstart"]["list"]) + + index_file.write( + "\n.. toctree::\n :maxdepth: 1\n :caption: Advanced\n :hidden:\n\n" + ) + index_file.write(categories["advanced"]["list"]) + + index_file.write( + "\n.. toctree::\n :maxdepth: 1\n :caption: Others\n :hidden:\n\n" + ) + index_file.write(categories["other"]["list"]) + + index_file.write("\n") + + +if __name__ == "__main__": + _main() + subprocess.call(f"cd {ROOT}/examples/doc && make html", shell=True) diff --git a/dev/update-examples.sh b/dev/update-examples.sh deleted file mode 100755 index 1076b4621984..000000000000 --- a/dev/update-examples.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/bin/bash -set -e -cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"/../ - -ROOT=`pwd` -INDEX=$ROOT/examples/doc/source/index.md -INSERT_LINE=6 - -copy_markdown_files () { - for file in $1/*.md; do - # Copy the README into the source of the Example docs as the name of the example - if [[ $(basename "$file") = "README.md" ]]; then - cp $file $ROOT/examples/doc/source/$1.md 2>&1 >/dev/null - else - # If the example contains other markdown files, copy them to the source of the Example docs - cp $file $ROOT/examples/doc/source/$(basename "$file") 2>&1 >/dev/null - fi - done -} - -add_gh_button () { - gh_text="[\"View](https://github.com/adap/flower/blob/main/examples/$1)" - readme_file="$ROOT/examples/doc/source/$1.md" - - if ! grep -Fq "$gh_text" "$readme_file"; then - awk -v text="$gh_text" ' - /^# / && !found { - print $0 "\n" text; - found=1; - next; - } - { print } - ' "$readme_file" > tmpfile && mv tmpfile "$readme_file" - fi -} - -copy_images () { - if [ -d "$1/_static" ]; then - cp $1/_static/**.{jpg,png,jpeg} $ROOT/examples/doc/source/_static/ 2>/dev/null || true - fi -} - -add_to_index () { - (echo $INSERT_LINE; echo a; echo $1; echo .; echo wq) | ed $INDEX 2>&1 >/dev/null -} - -add_single_entry () { - # Copy markdown files to correct folder - copy_markdown_files $1 - - # Add button linked to GitHub - add_gh_button $1 - - # Copy all images of the _static folder into the examples - # docs static folder - copy_images $1 - - # Insert the name of the example into the index file - add_to_index $1 -} - -add_all_entries () { - cd $ROOT/examples - # Iterate through each folder in examples/ - for d in $(printf '%s\n' */ | sort -V); do - # Add entry based on the name of the folder - example=${d%/} - - if [[ $example != doc ]]; then - add_single_entry $example - fi - done -} - -# Clean up before starting -rm -f $ROOT/examples/doc/source/*.md -rm -f $INDEX - -# Create empty index file -touch $INDEX - -echo "# Flower Examples Documentation" >> $INDEX -echo "" >> $INDEX -echo "\`\`\`{toctree}" >> $INDEX -echo "---" >> $INDEX -echo "maxdepth: 1" >> $INDEX -echo "---" >> $INDEX - -add_all_entries - -echo "\`\`\`" >> $INDEX diff --git a/examples/advanced-pytorch/README.md b/examples/advanced-pytorch/README.md index c1ba85b95879..bce80ab2f53d 100644 --- a/examples/advanced-pytorch/README.md +++ b/examples/advanced-pytorch/README.md @@ -1,3 +1,10 @@ +--- +title: Advanced Flower Example using PyTorch +labels: [advanced, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + # Advanced Flower Example (PyTorch) This example demonstrates an advanced federated learning setup using Flower with PyTorch. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) and it differs from the quickstart example in the following ways: diff --git a/examples/advanced-tensorflow/README.md b/examples/advanced-tensorflow/README.md index 94707b5cbc98..2b44baded43e 100644 --- a/examples/advanced-tensorflow/README.md +++ b/examples/advanced-tensorflow/README.md @@ -1,3 +1,10 @@ +--- +title: Advanced Flower Example using TensorFlow/Keras +labels: [advanced, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] +--- + # Advanced Flower Example (TensorFlow/Keras) This example demonstrates an advanced federated learning setup using Flower with TensorFlow/Keras. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) and it differs from the quickstart example in the following ways: diff --git a/examples/android-kotlin/README.md b/examples/android-kotlin/README.md index 2d0f704fdc0e..28ca5565b64c 100644 --- a/examples/android-kotlin/README.md +++ b/examples/android-kotlin/README.md @@ -1,3 +1,11 @@ +--- +title: Flower Android Example using Kotlin and TF Lite +labels: [basic, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [Android | https://www.android.com/, Kotlin | https://kotlinlang.org/, + TensorFlowLite | https://www.tensorflow.org/lite] +--- + # Flower Android Client Example with Kotlin and TensorFlow Lite 2022 This example is similar to the Flower Android Example in Java: diff --git a/examples/android/README.md b/examples/android/README.md index f9f2bb93b8dc..78c3d3a2c243 100644 --- a/examples/android/README.md +++ b/examples/android/README.md @@ -1,3 +1,11 @@ +--- +title: Flower Android Example using Java and TF Lite +labels: [basic, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [Android | https://www.android.com/, Java | https://www.java.com/, TensorFlowLite + | https://www.tensorflow.org/lite] +--- + # Flower Android Example (TensorFlowLite) This example demonstrates a federated learning setup with Android clients in a background thread. The training on Android is done on a CIFAR10 dataset using TensorFlow Lite. The setup is as follows: diff --git a/examples/app-pytorch/README.md b/examples/app-pytorch/README.md index 14de3c7d632e..cb41b371ed22 100644 --- a/examples/app-pytorch/README.md +++ b/examples/app-pytorch/README.md @@ -1,3 +1,10 @@ +--- +title: Example Flower App using PyTorch +labels: [basic, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + # Flower App (PyTorch) 🧪 > 🧪 = This example covers experimental features that might change in future versions of Flower diff --git a/examples/app-secure-aggregation/README.md b/examples/app-secure-aggregation/README.md index d1ea7bdc893f..b21674b052eb 100644 --- a/examples/app-secure-aggregation/README.md +++ b/examples/app-secure-aggregation/README.md @@ -1,3 +1,10 @@ +--- +title: Example Flower App with Secure Aggregation +labels: [basic, vision, fds] +dataset: [] +framework: [numpy | https://numpy.org/] +--- + # Secure aggregation with Flower (the SecAgg+ protocol) 🧪 > 🧪 = This example covers experimental features that might change in future versions of Flower diff --git a/examples/custom-metrics/README.md b/examples/custom-metrics/README.md index 317fb6336106..e173d7d32703 100644 --- a/examples/custom-metrics/README.md +++ b/examples/custom-metrics/README.md @@ -1,3 +1,10 @@ +--- +title: Example Flower App with Custom Metrics +labels: [basic, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [tensorflow | https://www.tensorflow.org/] +--- + # Flower Example using Custom Metrics This simple example demonstrates how to calculate custom metrics over multiple clients beyond the traditional ones available in the ML frameworks. In this case, it demonstrates the use of ready-available `scikit-learn` metrics: accuracy, recall, precision, and f1-score. diff --git a/examples/custom-mods/README.md b/examples/custom-mods/README.md index 6b03abcfbfe0..7127a27975c5 100644 --- a/examples/custom-mods/README.md +++ b/examples/custom-mods/README.md @@ -1,3 +1,10 @@ +--- +title: Example Flower App with Custom Mods +labels: [mods, monitoring, app] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [wandb | https://wandb.ai/home, tensorboard | https://www.tensorflow.org/tensorboard] +--- + # Using custom mods 🧪 > 🧪 = This example covers experimental features that might change in future versions of Flower diff --git a/examples/doc/source/.gitignore b/examples/doc/source/.gitignore index dd449725e188..73ee14e96f68 100644 --- a/examples/doc/source/.gitignore +++ b/examples/doc/source/.gitignore @@ -1 +1,2 @@ *.md +index.rst diff --git a/examples/embedded-devices/README.md b/examples/embedded-devices/README.md index f1c5931b823a..e98570e95c69 100644 --- a/examples/embedded-devices/README.md +++ b/examples/embedded-devices/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Embedded Devices Example +labels: [basic, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10, MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [torch | https://pytorch.org/, tensorflow | https://www.tensorflow.org/] +--- + # Federated Learning on Embedded Devices with Flower This example will show you how Flower makes it very easy to run Federated Learning workloads on edge devices. Here we'll be showing how to use NVIDIA Jetson devices and Raspberry Pi as Flower clients. You can run this example using either PyTorch or Tensorflow. The FL workload (i.e. model, dataset and training loop) is mostly borrowed from the [quickstart-pytorch](https://github.com/adap/flower/tree/main/examples/simulation-pytorch) and [quickstart-tensorflow](https://github.com/adap/flower/tree/main/examples/quickstart-tensorflow) examples. @@ -65,7 +72,7 @@ If you are working on this tutorial on your laptop or desktop, it can host the F - Install `pip`. In the terminal type: `sudo apt install python3-pip -y` - Now clone this directory. You just need to execute the `git clone` command shown at the top of this README.md on your device. - - Install Flower and your ML framework: We have prepared some convenient installation scripts that will install everything you need. You are free to install other versions of these ML frameworks to suit your needs. + - Install Flower and your ML framework of choice: We have prepared some convenient installation scripts that will install everything you need. You are free to install other versions of these ML frameworks to suit your needs. - If you want your clients to use PyTorch: `pip3 install -r requirements_pytorch.txt` - If you want your clients to use TensorFlow: `pip3 install -r requirements_tf.txt` @@ -180,7 +187,7 @@ If you are working on this tutorial on your laptop or desktop, it can host the F ## Running Embedded FL with Flower -For this demo, we'll be using [CIFAR-10](https://www.cs.toronto.edu/~kriz/cifar.html), a popular dataset for image classification comprised of 10 classes (e.g. car, bird, airplane) and a total of 60K `32x32` RGB images. The training set contains 50K images. The server will automatically download the dataset should it not be found in `./data`. The clients do the same. The dataset is by default split into 50 partitions (each to be assigned to a different client). This can be controlled with the `NUM_CLIENTS` global variable in the client scripts. In this example, each device will play the role of a specific user (specified via `--cid` -- we'll show this later) and therefore only do local training with that portion of the data. For CIFAR-10, clients will be training a MobileNet-v2/3 model. +For this demo, we'll be using [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10](https://www.cs.toronto.edu/~kriz/cifar.html), a popular dataset for image classification comprised of 10 classes (e.g. car, bird, airplane) and a total of 60K `32x32` RGB images. The training set contains 50K images. The server will automatically download the dataset should it not be found in `./data`. The clients do the same. The dataset is by default split into 50 partitions (each to be assigned to a different client). This can be controlled with the `NUM_CLIENTS` global variable in the client scripts. In this example, each device will play the role of a specific user (specified via `--cid` -- we'll show this later) and therefore only do local training with that portion of the data. For CIFAR-10, clients will be training a MobileNet-v2/3 model. You can run this example using MNIST and a smaller CNN model by passing flag `--mnist`. This is useful if you are using devices with a very limited amount of memory (e.g. RaspberryPi Zero) or if you want the training taking place on the embedded devices to be much faster (specially if these are CPU-only). The partitioning of the dataset is done in the same way. diff --git a/examples/federated-kaplan-meier-fitter/README.md b/examples/federated-kaplan-meier-fitter/README.md index 1569467d6f82..5d0768d42ea6 100644 --- a/examples/federated-kaplan-meier-fitter/README.md +++ b/examples/federated-kaplan-meier-fitter/README.md @@ -1,3 +1,11 @@ +--- +title: Flower Example using KaplanMeierFitter +labels: [estimator, medical] +dataset: [Waltons | + https://lifelines.readthedocs.io/en/latest/lifelines.datasets.html#lifelines.datasets.load_waltons] +framework: [lifelines | https://lifelines.readthedocs.io/en/latest/index.html] +--- + # Flower Example using KaplanMeierFitter This is an introductory example on **federated survival analysis** using [Flower](https://flower.ai/) diff --git a/examples/fl-dp-sa/README.md b/examples/fl-dp-sa/README.md index 47eedb70a2b8..7269503af57f 100644 --- a/examples/fl-dp-sa/README.md +++ b/examples/fl-dp-sa/README.md @@ -1,4 +1,11 @@ -# fl_dp_sa +--- +title: Example of Flower App with DP and SA +labels: [basic, vision, fds] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + +# Example of Flower App with DP and SA This is a simple example that utilizes central differential privacy with client-side fixed clipping and secure aggregation. Note: This example is designed for a small number of rounds and is intended for demonstration purposes. diff --git a/examples/fl-tabular/README.md b/examples/fl-tabular/README.md index 58afd1080b70..2cc2a1481d60 100644 --- a/examples/fl-tabular/README.md +++ b/examples/fl-tabular/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Example on Adult Census Income Tabular Dataset +labels: [basic, tabular, fds] +dataset: [Adult Census Income | https://www.kaggle.com/datasets/uciml/adult-census-income/data] +framework: [scikit-learn | https://scikit-learn.org/, torch | https://pytorch.org/] +--- + # Flower Example on Adult Census Income Tabular Dataset This code exemplifies a federated learning setup using the Flower framework on the ["Adult Census Income"](https://huggingface.co/datasets/scikit-learn/adult-census-income) tabular dataset. The "Adult Census Income" dataset contains demographic information such as age, education, occupation, etc., with the target attribute being income level (\<=50K or >50K). The dataset is partitioned into subsets, simulating a federated environment with 5 clients, each holding a distinct portion of the data. Categorical variables are one-hot encoded, and the data is split into training and testing sets. Federated learning is conducted using the FedAvg strategy for 5 rounds. diff --git a/examples/flower-authentication/README.md b/examples/flower-authentication/README.md index 589270e621c9..3709ab4139c1 100644 --- a/examples/flower-authentication/README.md +++ b/examples/flower-authentication/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Example with Authentication +labels: [advanced, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + # Flower Authentication with PyTorch 🧪 > 🧪 = This example covers experimental features that might change in future versions of Flower diff --git a/examples/flower-in-30-minutes/README.md b/examples/flower-in-30-minutes/README.md index 5fd9b882413b..d598694c7b56 100644 --- a/examples/flower-in-30-minutes/README.md +++ b/examples/flower-in-30-minutes/README.md @@ -1,3 +1,10 @@ +--- +title: 30-minute tutorial running Flower simulation with PyTorch +labels: [colab, vision, simulation] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [torch | https://pytorch.org/] +--- + # 30-minute tutorial running Flower simulation with PyTorch This README links to a Jupyter notebook that you can either download and run locally or [![open it in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/adap/flower/blob/main/examples/flower-in-30-minutes/tutorial.ipynb). This is a short 30-minute (or less!) tutorial showcasing the basics of Flower federated learning simulations using PyTorch. diff --git a/examples/flower-simulation-step-by-step-pytorch/README.md b/examples/flower-simulation-step-by-step-pytorch/README.md index beb8dd7f6f95..28f99ab5fa6e 100644 --- a/examples/flower-simulation-step-by-step-pytorch/README.md +++ b/examples/flower-simulation-step-by-step-pytorch/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Simulation Step-by-Step +labels: [basic, vision, simulation] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [torch | https://pytorch.org/] +--- + # Flower Simulation Step-by-Step > Since this tutorial (and its video series) was put together, Flower has been updated a few times. As a result, some of the steps to construct the environment (see below) have been updated. Some parts of the code have also been updated. Overall, the content of this tutorial and how things work remains the same as in the video tutorials. diff --git a/examples/flower-via-docker-compose/README.md b/examples/flower-via-docker-compose/README.md index 3ef1ac37bcda..07bb8a2c00a3 100644 --- a/examples/flower-via-docker-compose/README.md +++ b/examples/flower-via-docker-compose/README.md @@ -1,3 +1,10 @@ +--- +title: Leveraging Flower and Docker for Device Heterogeneity Management in FL +labels: [deployment, vision, tutorial] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [Docker | https://www.docker.com/, tensorflow | https://www.tensorflow.org/] +--- + # Leveraging Flower and Docker for Device Heterogeneity Management in Federated Learning

    diff --git a/examples/ios/README.md b/examples/ios/README.md index 4e17e7a674f3..bb36a3f1729f 100644 --- a/examples/ios/README.md +++ b/examples/ios/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example on iOS +labels: [mobile, vision, fds] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [Swift | https://www.swift.org/] +--- + # FLiOS - A Flower SDK for iOS Devices with Example FLiOS is a sample application for testing and benchmarking the Swift implementation of Flower. The default scenario uses the MNIST dataset and the associated digit recognition model. The app includes the Swift package in `./src/swift` and allows extension for other benchmarking scenarios. The app guides the user through the steps of the machine learning process that would be executed in a normal production environment as a background task of the application. The app is therefore aimed at researchers and research institutions to test their hypotheses and perform performance analyses. diff --git a/examples/llm-flowertune/README.md b/examples/llm-flowertune/README.md index 4f98072f8c7f..2507dd1bb546 100644 --- a/examples/llm-flowertune/README.md +++ b/examples/llm-flowertune/README.md @@ -1,3 +1,10 @@ +--- +title: Federated LLM Fine-tuning with Flower +labels: [llm, nlp, LLama2] +dataset: [Alpaca-GPT4 | https://huggingface.co/datasets/vicgalle/alpaca-gpt4] +framework: [PEFT | https://huggingface.co/docs/peft/index, torch | https://pytorch.org/] +--- + # LLM FlowerTune: Federated LLM Fine-tuning with Flower Large language models (LLMs), which have been trained on vast amounts of publicly accessible data, have shown remarkable effectiveness in a wide range of areas. diff --git a/examples/opacus/README.md b/examples/opacus/README.md index 6fc0d2ff49a0..2c586ccabaff 100644 --- a/examples/opacus/README.md +++ b/examples/opacus/README.md @@ -1,3 +1,10 @@ +--- +title: Sample-Level Differential Privacy using Opacus +labels: [dp, security, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [opacus | https://opacus.ai/, torch | https://pytorch.org/] +--- + # Training with Sample-Level Differential Privacy using Opacus Privacy Engine In this example, we demonstrate how to train a model with differential privacy (DP) using Flower. We employ PyTorch and integrate the Opacus Privacy Engine to achieve sample-level differential privacy. This setup ensures robust privacy guarantees during the client training phase. The code is adapted from the [PyTorch Quickstart example](https://github.com/adap/flower/tree/main/examples/quickstart-pytorch). diff --git a/examples/pytorch-federated-variational-autoencoder/README.md b/examples/pytorch-federated-variational-autoencoder/README.md index 00af7a6328b2..539ab6904a1c 100644 --- a/examples/pytorch-federated-variational-autoencoder/README.md +++ b/examples/pytorch-federated-variational-autoencoder/README.md @@ -1,3 +1,10 @@ +--- +title: Federated Variational Autoencoder using Pytorch +labels: [basic, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + # Flower Example for Federated Variational Autoencoder using Pytorch This example demonstrates how a variational autoencoder (VAE) can be trained in a federated way using the Flower framework. diff --git a/examples/pytorch-from-centralized-to-federated/README.md b/examples/pytorch-from-centralized-to-federated/README.md index 06ee89dddcac..683604ec4eb9 100644 --- a/examples/pytorch-from-centralized-to-federated/README.md +++ b/examples/pytorch-from-centralized-to-federated/README.md @@ -1,3 +1,10 @@ +--- +title: PyTorch, From Centralized To Federated +labels: [basic, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [torch | https://pytorch.org/] +--- + # PyTorch: From Centralized To Federated This example demonstrates how an already existing centralized PyTorch-based machine learning project can be federated with Flower. diff --git a/examples/quickstart-cpp/README.md b/examples/quickstart-cpp/README.md index d6cbeebe1bc6..7e79aa63b851 100644 --- a/examples/quickstart-cpp/README.md +++ b/examples/quickstart-cpp/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using C++ +labels: [quickstart, linear regression, tabular] +dataset: [Synthetic] +framework: [C++ | https://isocpp.org/] +--- + # Flower Clients in C++ (under development) In this example you will train a linear model on synthetic data using C++ clients. diff --git a/examples/quickstart-fastai/README.md b/examples/quickstart-fastai/README.md index 38ef23c95a1e..8a8c41a49c22 100644 --- a/examples/quickstart-fastai/README.md +++ b/examples/quickstart-fastai/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using fastai +labels: [quickstart, vision] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [fastai | https://fast.ai] +--- + # Flower Example using fastai This introductory example to Flower uses [fastai](https://www.fast.ai/), but deep knowledge of fastai is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use case. diff --git a/examples/quickstart-huggingface/README.md b/examples/quickstart-huggingface/README.md index ce7790cd4af5..7235b12d7c8e 100644 --- a/examples/quickstart-huggingface/README.md +++ b/examples/quickstart-huggingface/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Transformers Example using HuggingFace +labels: [quickstart, llm, nlp, sentiment] +dataset: [IMDB | https://huggingface.co/datasets/stanfordnlp/imdb] +framework: [transformers | https://huggingface.co/docs/transformers/index] +--- + # Federated HuggingFace Transformers using Flower and PyTorch This introductory example to using [HuggingFace](https://huggingface.co) Transformers with Flower with PyTorch. This example has been extended from the [quickstart-pytorch](https://flower.ai/docs/examples/quickstart-pytorch.html) example. The training script closely follows the [HuggingFace course](https://huggingface.co/course/chapter3?fw=pt), so you are encouraged to check that out for a detailed explanation of the transformer pipeline. diff --git a/examples/quickstart-jax/README.md b/examples/quickstart-jax/README.md index 836adf558d88..5d9939a2b4fb 100644 --- a/examples/quickstart-jax/README.md +++ b/examples/quickstart-jax/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using Jax +labels: [quickstart, linear regression] +dataset: [Synthetic] +framework: [JAX | https://jax.readthedocs.io/en/latest/] +--- + # JAX: From Centralized To Federated This example demonstrates how an already existing centralized JAX-based machine learning project can be federated with Flower. diff --git a/examples/quickstart-mlcube/README.md b/examples/quickstart-mlcube/README.md index 8e6fc29b3ad8..a2e989e6804b 100644 --- a/examples/quickstart-mlcube/README.md +++ b/examples/quickstart-mlcube/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Example using TensorFlow/Keras + MLCube +labels: [quickstart, vision, deployment] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] +--- + # Flower Example using TensorFlow/Keras + MLCube This introductory example to Flower uses MLCube together with Keras, but deep knowledge of Keras is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use-cases with MLCube. Running this example in itself is quite easy. diff --git a/examples/quickstart-mlx/README.md b/examples/quickstart-mlx/README.md index cca55bcb946a..633b32f7bde7 100644 --- a/examples/quickstart-mlx/README.md +++ b/examples/quickstart-mlx/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using MLX +labels: [quickstart, vision] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [MLX | https://ml-explore.github.io/mlx/build/html/index.html] +--- + # Flower Example using MLX This introductory example to Flower uses [MLX](https://ml-explore.github.io/mlx/build/html/index.html), but deep knowledge of MLX is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use case. Running this example in itself is quite easy. diff --git a/examples/quickstart-monai/README.md b/examples/quickstart-monai/README.md index 4a9afef4f86a..43f8c1cacbc7 100644 --- a/examples/quickstart-monai/README.md +++ b/examples/quickstart-monai/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Example using MONAI +labels: [quickstart, medical, vision] +dataset: [MedNIST | https://medmnist.com/] +framework: [MONAI | https://monai.io/] +--- + # Flower Example using MONAI This introductory example to Flower uses MONAI, but deep knowledge of MONAI is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use case. diff --git a/examples/quickstart-pandas/README.md b/examples/quickstart-pandas/README.md index dd69f3ead3cb..d2cc7abbacd1 100644 --- a/examples/quickstart-pandas/README.md +++ b/examples/quickstart-pandas/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using Pandas +labels: [quickstart, tabular, federated analytics] +dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] +framework: [pandas | https://pandas.pydata.org/] +--- + # Flower Example using Pandas This introductory example to Flower uses Pandas, but deep knowledge of Pandas is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use case. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) to diff --git a/examples/quickstart-pytorch-lightning/README.md b/examples/quickstart-pytorch-lightning/README.md index fb29c7e9e9ea..ec968a1d8d0a 100644 --- a/examples/quickstart-pytorch-lightning/README.md +++ b/examples/quickstart-pytorch-lightning/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using PyTorch-Lightning +labels: [quickstart, vision, fds] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [lightning | https://lightning.ai/docs/pytorch/stable/] +--- + # Flower Example using PyTorch Lightning This introductory example to Flower uses PyTorch, but deep knowledge of PyTorch Lightning is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use case. Running this example in itself is quite easy. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) to download, partition and preprocess the MNIST dataset. diff --git a/examples/quickstart-pytorch/README.md b/examples/quickstart-pytorch/README.md index 93d6a593f362..63a357b37e58 100644 --- a/examples/quickstart-pytorch/README.md +++ b/examples/quickstart-pytorch/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using PyTorch +labels: [quickstart, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + # Flower Example using PyTorch This introductory example to Flower uses PyTorch, but deep knowledge of PyTorch is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use case. Running this example in itself is quite easy. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) to download, partition and preprocess the CIFAR-10 dataset. diff --git a/examples/quickstart-sklearn-tabular/README.md b/examples/quickstart-sklearn-tabular/README.md index a975a9392800..bb9aac58a0ed 100644 --- a/examples/quickstart-sklearn-tabular/README.md +++ b/examples/quickstart-sklearn-tabular/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Example using Scikit-Learn +labels: [quickstart, tabular, fds] +dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] +framework: [scikit-learn | https://scikit-learn.org/] +--- + # Flower Example using scikit-learn This example of Flower uses `scikit-learn`'s `LogisticRegression` model to train a federated learning system on diff --git a/examples/quickstart-tabnet/README.md b/examples/quickstart-tabnet/README.md index 19a139f83064..13cddc2bc737 100644 --- a/examples/quickstart-tabnet/README.md +++ b/examples/quickstart-tabnet/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using Tabnet +labels: [quickstart, tabular] +dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] +framework: [tabnet | https://github.com/titu1994/tf-TabNet] +--- + # Flower TabNet Example using TensorFlow This introductory example to Flower uses Keras but deep knowledge of Keras is not necessarily required to run the example. However, it will help you understanding how to adapt Flower to your use-cases. You can learn more about TabNet from [paper](https://arxiv.org/abs/1908.07442) and its implementation using TensorFlow at [this repository](https://github.com/titu1994/tf-TabNet). Note also that the basis of this example using federated learning is the example from the repository above. diff --git a/examples/quickstart-tensorflow/README.md b/examples/quickstart-tensorflow/README.md index ae1fe19834a3..ce7190b35ef9 100644 --- a/examples/quickstart-tensorflow/README.md +++ b/examples/quickstart-tensorflow/README.md @@ -1,3 +1,10 @@ +--- +title: Simple Flower Example using TensorFlow +labels: [quickstart, vision, fds] +dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] +framework: [tensorflow | https://www.tensorflow.org/] +--- + # Flower Example using TensorFlow/Keras This introductory example to Flower uses Keras but deep knowledge of Keras is not necessarily required to run the example. However, it will help you understand how to adapt Flower to your use case. diff --git a/examples/simulation-pytorch/README.md b/examples/simulation-pytorch/README.md index 93f9e1acbac7..85b9e136e6dc 100644 --- a/examples/simulation-pytorch/README.md +++ b/examples/simulation-pytorch/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Simulation Example using PyTorch +labels: [basic, vision, fds, simulation] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + # Flower Simulation example using PyTorch This introductory example uses the simulation capabilities of Flower to simulate a large number of clients on a single machine. Take a look at the [Documentation](https://flower.ai/docs/framework/how-to-run-simulations.html) for a deep dive into how Flower simulation works. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) to download, partition and preprocess the MNIST dataset. This examples uses 100 clients by default. diff --git a/examples/simulation-tensorflow/README.md b/examples/simulation-tensorflow/README.md index 917d7b34c7af..2dc9a41cb959 100644 --- a/examples/simulation-tensorflow/README.md +++ b/examples/simulation-tensorflow/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Simulation Example using TensorFlow/Keras +labels: [basic, vision, fds, simulation] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] +--- + # Flower Simulation example using TensorFlow/Keras This introductory example uses the simulation capabilities of Flower to simulate a large number of clients on a single machine. Take a look at the [Documentation](https://flower.ai/docs/framework/how-to-run-simulations.html) for a deep dive into how Flower simulation works. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) to download, partition and preprocess the MNIST dataset. This examples uses 100 clients by default. diff --git a/examples/sklearn-logreg-mnist/README.md b/examples/sklearn-logreg-mnist/README.md index 12b1a5e3bc1a..edea3d7b28e8 100644 --- a/examples/sklearn-logreg-mnist/README.md +++ b/examples/sklearn-logreg-mnist/README.md @@ -1,3 +1,10 @@ +--- +title: Flower LogReg Example using Scikit-Learn +labels: [basic, vision, logistic regression, fds] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [scikit-learn | https://scikit-learn.org/] +--- + # Flower Example using scikit-learn This example of Flower uses `scikit-learn`'s `LogisticRegression` model to train a federated learning system. It will help you understand how to adapt Flower for use with `scikit-learn`. diff --git a/examples/tensorflow-privacy/README.md b/examples/tensorflow-privacy/README.md index a1f1be00f6b0..48ed4594edac 100644 --- a/examples/tensorflow-privacy/README.md +++ b/examples/tensorflow-privacy/README.md @@ -1,3 +1,10 @@ +--- +title: Sample-Level DP using TensorFlow-Privacy Engine +labels: [basic, vision, fds, privacy, dp] +dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] +framework: [tensorflow | https://www.tensorflow.org/] +--- + # Training with Sample-Level Differential Privacy using TensorFlow-Privacy Engine In this example, we demonstrate how to train a model with sample-level differential privacy (DP) using Flower. We employ TensorFlow and integrate the tensorflow-privacy Engine to achieve sample-level differential privacy. This setup ensures robust privacy guarantees during the client training phase. diff --git a/examples/vertical-fl/README.md b/examples/vertical-fl/README.md index ba8228a059f9..30b38e59c619 100644 --- a/examples/vertical-fl/README.md +++ b/examples/vertical-fl/README.md @@ -1,3 +1,11 @@ +--- +title: Vertical FL Flower Example +labels: [vertical, tabular, advanced] +dataset: [Titanic | https://www.kaggle.com/competitions/titanic] +framework: [torch | https://pytorch.org/, pandas | https://pandas.pydata.org/, scikit-learn + | https://scikit-learn.org/] +--- + # Vertical Federated Learning example This example will showcase how you can perform Vertical Federated Learning using diff --git a/examples/vit-finetune/README.md b/examples/vit-finetune/README.md index ac1652acf02d..3ef38aca5773 100644 --- a/examples/vit-finetune/README.md +++ b/examples/vit-finetune/README.md @@ -1,3 +1,10 @@ +--- +title: Federated finetuning of a ViT +labels: [finetuneing, vision, fds] +dataset: [Oxford Flower-102 | https://www.robots.ox.ac.uk/~vgg/data/flowers/102/] +framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +--- + # Federated finetuning of a ViT This example shows how to use Flower's Simulation Engine to federate the finetuning of a Vision Transformer ([ViT-Base-16](https://pytorch.org/vision/main/models/generated/torchvision.models.vit_b_16.html#torchvision.models.vit_b_16)) that has been pretrained on ImageNet. To keep things simple we'll be finetuning it to [Oxford Flower-102](https://www.robots.ox.ac.uk/~vgg/data/flowers/102/index.html) datasset, creating 20 partitions using [Flower Datasets](https://flower.ai/docs/datasets/). We'll be finetuning just the exit `head` of the ViT, this means that the training is not that costly and each client requires just ~1GB of VRAM (for a batch size of 32 images). diff --git a/examples/whisper-federated-finetuning/README.md b/examples/whisper-federated-finetuning/README.md index ddebe51247b2..1a79e581016f 100644 --- a/examples/whisper-federated-finetuning/README.md +++ b/examples/whisper-federated-finetuning/README.md @@ -1,3 +1,11 @@ +--- +title: On-device Federated Finetuning for Speech Classification +labels: [finetuning, speech, transformers] +dataset: [SpeechCommands | https://huggingface.co/datasets/google/speech_commands] +framework: [transformers | https://huggingface.co/docs/transformers/index, whisper + | https://huggingface.co/openai/whisper-tiny] +--- + # On-device Federated Finetuning for Speech Classification This example demonstrates how to, from a pre-trained [Whisper](https://openai.com/research/whisper) model, finetune it for the downstream task of keyword spotting. We'll be implementing a federated downstream finetuning pipeline using Flower involving a total of 100 clients. As for the downstream dataset, we'll be using the [Google Speech Commands](https://huggingface.co/datasets/speech_commands) dataset for keyword spotting. We'll take the encoder part of the [Whisper-tiny](https://huggingface.co/openai/whisper-tiny) model, freeze its parameters, and learn a lightweight classification (\<800K parameters !!) head to correctly classify a spoken word. diff --git a/examples/xgboost-comprehensive/README.md b/examples/xgboost-comprehensive/README.md index dc6d7e3872d6..ea06febe43a7 100644 --- a/examples/xgboost-comprehensive/README.md +++ b/examples/xgboost-comprehensive/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Example using XGBoost +labels: [advanced, classification, tabular] +dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] +framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] +--- + # Flower Example using XGBoost (Comprehensive) This example demonstrates a comprehensive federated learning setup using Flower with XGBoost. diff --git a/examples/xgboost-quickstart/README.md b/examples/xgboost-quickstart/README.md index 713b6eab8bac..40edfd0c1870 100644 --- a/examples/xgboost-quickstart/README.md +++ b/examples/xgboost-quickstart/README.md @@ -1,3 +1,10 @@ +--- +title: Flower Example using PyTorch +labels: [quickstart, classification, tabular] +dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] +framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] +--- + # Flower Example using XGBoost This example demonstrates how to perform EXtreme Gradient Boosting (XGBoost) within Flower using `xgboost` package. From 999ea8374b117af46f1462f882eb8bc25e87ed17 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 2 Jul 2024 13:10:57 +0200 Subject: [PATCH 101/595] docs(examples:skip) Use more consistent naming for tags (#3706) --- dev/build-example-docs.py | 18 +++++++++--------- examples/advanced-pytorch/README.md | 2 +- examples/advanced-tensorflow/README.md | 2 +- examples/android-kotlin/README.md | 2 +- examples/android/README.md | 2 +- examples/app-pytorch/README.md | 2 +- examples/app-secure-aggregation/README.md | 2 +- examples/custom-metrics/README.md | 2 +- examples/custom-mods/README.md | 2 +- examples/embedded-devices/README.md | 2 +- .../federated-kaplan-meier-fitter/README.md | 2 +- examples/fl-dp-sa/README.md | 2 +- examples/fl-tabular/README.md | 2 +- examples/flower-authentication/README.md | 2 +- examples/flower-in-30-minutes/README.md | 2 +- .../README.md | 2 +- examples/flower-via-docker-compose/README.md | 2 +- examples/ios/README.md | 2 +- examples/llm-flowertune/README.md | 2 +- examples/opacus/README.md | 2 +- .../README.md | 2 +- .../README.md | 2 +- examples/quickstart-cpp/README.md | 2 +- examples/quickstart-fastai/README.md | 2 +- examples/quickstart-huggingface/README.md | 2 +- examples/quickstart-jax/README.md | 2 +- examples/quickstart-mlcube/README.md | 2 +- examples/quickstart-mlx/README.md | 2 +- examples/quickstart-monai/README.md | 2 +- examples/quickstart-pandas/README.md | 2 +- .../quickstart-pytorch-lightning/README.md | 2 +- examples/quickstart-pytorch/README.md | 2 +- examples/quickstart-sklearn-tabular/README.md | 2 +- examples/quickstart-tabnet/README.md | 2 +- examples/quickstart-tensorflow/README.md | 2 +- examples/simulation-pytorch/README.md | 2 +- examples/simulation-tensorflow/README.md | 2 +- examples/sklearn-logreg-mnist/README.md | 2 +- examples/tensorflow-privacy/README.md | 2 +- examples/vertical-fl/README.md | 2 +- examples/vit-finetune/README.md | 2 +- .../whisper-federated-finetuning/README.md | 2 +- examples/xgboost-comprehensive/README.md | 2 +- examples/xgboost-quickstart/README.md | 2 +- 44 files changed, 52 insertions(+), 52 deletions(-) diff --git a/dev/build-example-docs.py b/dev/build-example-docs.py index 204380f312ac..44d09383e7aa 100644 --- a/dev/build-example-docs.py +++ b/dev/build-example-docs.py @@ -90,10 +90,10 @@ def _read_metadata(example): raise ValueError("Title not found in metadata") title = title_match.group(1).strip() - labels_match = re.search(r"^labels:\s*\[(.+?)\]$", metadata, re.MULTILINE) - if not labels_match: - raise ValueError("Labels not found in metadata") - labels = labels_match.group(1).strip() + tags_match = re.search(r"^tags:\s*\[(.+?)\]$", metadata, re.MULTILINE) + if not tags_match: + raise ValueError("Tags not found in metadata") + tags = tags_match.group(1).strip() dataset_match = re.search( r"^dataset:\s*\[(.*?)\]$", metadata, re.DOTALL | re.MULTILINE @@ -111,17 +111,17 @@ def _read_metadata(example): dataset = _convert_to_link(re.sub(r"\s+", " ", dataset).strip()) framework = _convert_to_link(re.sub(r"\s+", " ", framework).strip()) - return title, labels, dataset, framework + return title, tags, dataset, framework -def _add_table_entry(example, label, table_var): - title, labels, dataset, framework = _read_metadata(example) +def _add_table_entry(example, tag, table_var): + title, tags, dataset, framework = _read_metadata(example) example_name = Path(example).stem table_entry = ( f" * - `{title} <{example_name}.html>`_ \n " - f"- {framework} \n - {dataset} \n - {labels}\n\n" + f"- {framework} \n - {dataset} \n - {tags}\n\n" ) - if label in labels: + if tag in tags: categories[table_var]["table"] += table_entry categories[table_var]["list"] += f" {example_name}\n" return True diff --git a/examples/advanced-pytorch/README.md b/examples/advanced-pytorch/README.md index bce80ab2f53d..98bb1713e09c 100644 --- a/examples/advanced-pytorch/README.md +++ b/examples/advanced-pytorch/README.md @@ -1,6 +1,6 @@ --- title: Advanced Flower Example using PyTorch -labels: [advanced, vision, fds] +tags: [advanced, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/advanced-tensorflow/README.md b/examples/advanced-tensorflow/README.md index 2b44baded43e..f5b93bca2800 100644 --- a/examples/advanced-tensorflow/README.md +++ b/examples/advanced-tensorflow/README.md @@ -1,6 +1,6 @@ --- title: Advanced Flower Example using TensorFlow/Keras -labels: [advanced, vision, fds] +tags: [advanced, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] --- diff --git a/examples/android-kotlin/README.md b/examples/android-kotlin/README.md index 28ca5565b64c..e51f008f9790 100644 --- a/examples/android-kotlin/README.md +++ b/examples/android-kotlin/README.md @@ -1,6 +1,6 @@ --- title: Flower Android Example using Kotlin and TF Lite -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [Android | https://www.android.com/, Kotlin | https://kotlinlang.org/, TensorFlowLite | https://www.tensorflow.org/lite] diff --git a/examples/android/README.md b/examples/android/README.md index 78c3d3a2c243..8b7cbaab51d0 100644 --- a/examples/android/README.md +++ b/examples/android/README.md @@ -1,6 +1,6 @@ --- title: Flower Android Example using Java and TF Lite -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [Android | https://www.android.com/, Java | https://www.java.com/, TensorFlowLite | https://www.tensorflow.org/lite] diff --git a/examples/app-pytorch/README.md b/examples/app-pytorch/README.md index cb41b371ed22..6ff674086358 100644 --- a/examples/app-pytorch/README.md +++ b/examples/app-pytorch/README.md @@ -1,6 +1,6 @@ --- title: Example Flower App using PyTorch -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/app-secure-aggregation/README.md b/examples/app-secure-aggregation/README.md index b21674b052eb..779501e5e7e1 100644 --- a/examples/app-secure-aggregation/README.md +++ b/examples/app-secure-aggregation/README.md @@ -1,6 +1,6 @@ --- title: Example Flower App with Secure Aggregation -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [] framework: [numpy | https://numpy.org/] --- diff --git a/examples/custom-metrics/README.md b/examples/custom-metrics/README.md index e173d7d32703..9f850df6b830 100644 --- a/examples/custom-metrics/README.md +++ b/examples/custom-metrics/README.md @@ -1,6 +1,6 @@ --- title: Example Flower App with Custom Metrics -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [tensorflow | https://www.tensorflow.org/] --- diff --git a/examples/custom-mods/README.md b/examples/custom-mods/README.md index 7127a27975c5..f275864c185c 100644 --- a/examples/custom-mods/README.md +++ b/examples/custom-mods/README.md @@ -1,6 +1,6 @@ --- title: Example Flower App with Custom Mods -labels: [mods, monitoring, app] +tags: [mods, monitoring, app] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [wandb | https://wandb.ai/home, tensorboard | https://www.tensorflow.org/tensorboard] --- diff --git a/examples/embedded-devices/README.md b/examples/embedded-devices/README.md index e98570e95c69..1cdf6c4efefb 100644 --- a/examples/embedded-devices/README.md +++ b/examples/embedded-devices/README.md @@ -1,6 +1,6 @@ --- title: Flower Embedded Devices Example -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10, MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [torch | https://pytorch.org/, tensorflow | https://www.tensorflow.org/] --- diff --git a/examples/federated-kaplan-meier-fitter/README.md b/examples/federated-kaplan-meier-fitter/README.md index 5d0768d42ea6..0bec9b78e9c8 100644 --- a/examples/federated-kaplan-meier-fitter/README.md +++ b/examples/federated-kaplan-meier-fitter/README.md @@ -1,6 +1,6 @@ --- title: Flower Example using KaplanMeierFitter -labels: [estimator, medical] +tags: [estimator, medical] dataset: [Waltons | https://lifelines.readthedocs.io/en/latest/lifelines.datasets.html#lifelines.datasets.load_waltons] framework: [lifelines | https://lifelines.readthedocs.io/en/latest/index.html] diff --git a/examples/fl-dp-sa/README.md b/examples/fl-dp-sa/README.md index 7269503af57f..a764499d22a5 100644 --- a/examples/fl-dp-sa/README.md +++ b/examples/fl-dp-sa/README.md @@ -1,6 +1,6 @@ --- title: Example of Flower App with DP and SA -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/fl-tabular/README.md b/examples/fl-tabular/README.md index 2cc2a1481d60..4586251dbd98 100644 --- a/examples/fl-tabular/README.md +++ b/examples/fl-tabular/README.md @@ -1,6 +1,6 @@ --- title: Flower Example on Adult Census Income Tabular Dataset -labels: [basic, tabular, fds] +tags: [basic, tabular, fds] dataset: [Adult Census Income | https://www.kaggle.com/datasets/uciml/adult-census-income/data] framework: [scikit-learn | https://scikit-learn.org/, torch | https://pytorch.org/] --- diff --git a/examples/flower-authentication/README.md b/examples/flower-authentication/README.md index 3709ab4139c1..e77cbdfe94b6 100644 --- a/examples/flower-authentication/README.md +++ b/examples/flower-authentication/README.md @@ -1,6 +1,6 @@ --- title: Flower Example with Authentication -labels: [advanced, vision, fds] +tags: [advanced, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/flower-in-30-minutes/README.md b/examples/flower-in-30-minutes/README.md index d598694c7b56..0d0dafb70ee3 100644 --- a/examples/flower-in-30-minutes/README.md +++ b/examples/flower-in-30-minutes/README.md @@ -1,6 +1,6 @@ --- title: 30-minute tutorial running Flower simulation with PyTorch -labels: [colab, vision, simulation] +tags: [colab, vision, simulation] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [torch | https://pytorch.org/] --- diff --git a/examples/flower-simulation-step-by-step-pytorch/README.md b/examples/flower-simulation-step-by-step-pytorch/README.md index 28f99ab5fa6e..66cc632bc6ce 100644 --- a/examples/flower-simulation-step-by-step-pytorch/README.md +++ b/examples/flower-simulation-step-by-step-pytorch/README.md @@ -1,6 +1,6 @@ --- title: Flower Simulation Step-by-Step -labels: [basic, vision, simulation] +tags: [basic, vision, simulation] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [torch | https://pytorch.org/] --- diff --git a/examples/flower-via-docker-compose/README.md b/examples/flower-via-docker-compose/README.md index 07bb8a2c00a3..b77c55dae03c 100644 --- a/examples/flower-via-docker-compose/README.md +++ b/examples/flower-via-docker-compose/README.md @@ -1,6 +1,6 @@ --- title: Leveraging Flower and Docker for Device Heterogeneity Management in FL -labels: [deployment, vision, tutorial] +tags: [deployment, vision, tutorial] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [Docker | https://www.docker.com/, tensorflow | https://www.tensorflow.org/] --- diff --git a/examples/ios/README.md b/examples/ios/README.md index bb36a3f1729f..3fc2908c6781 100644 --- a/examples/ios/README.md +++ b/examples/ios/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example on iOS -labels: [mobile, vision, fds] +tags: [mobile, vision, fds] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [Swift | https://www.swift.org/] --- diff --git a/examples/llm-flowertune/README.md b/examples/llm-flowertune/README.md index 2507dd1bb546..503306be052f 100644 --- a/examples/llm-flowertune/README.md +++ b/examples/llm-flowertune/README.md @@ -1,6 +1,6 @@ --- title: Federated LLM Fine-tuning with Flower -labels: [llm, nlp, LLama2] +tags: [llm, nlp, LLama2] dataset: [Alpaca-GPT4 | https://huggingface.co/datasets/vicgalle/alpaca-gpt4] framework: [PEFT | https://huggingface.co/docs/peft/index, torch | https://pytorch.org/] --- diff --git a/examples/opacus/README.md b/examples/opacus/README.md index 2c586ccabaff..c3b310b07418 100644 --- a/examples/opacus/README.md +++ b/examples/opacus/README.md @@ -1,6 +1,6 @@ --- title: Sample-Level Differential Privacy using Opacus -labels: [dp, security, fds] +tags: [dp, security, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [opacus | https://opacus.ai/, torch | https://pytorch.org/] --- diff --git a/examples/pytorch-federated-variational-autoencoder/README.md b/examples/pytorch-federated-variational-autoencoder/README.md index 539ab6904a1c..4cc5521adff4 100644 --- a/examples/pytorch-federated-variational-autoencoder/README.md +++ b/examples/pytorch-federated-variational-autoencoder/README.md @@ -1,6 +1,6 @@ --- title: Federated Variational Autoencoder using Pytorch -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/pytorch-from-centralized-to-federated/README.md b/examples/pytorch-from-centralized-to-federated/README.md index 683604ec4eb9..1a5ef610a25c 100644 --- a/examples/pytorch-from-centralized-to-federated/README.md +++ b/examples/pytorch-from-centralized-to-federated/README.md @@ -1,6 +1,6 @@ --- title: PyTorch, From Centralized To Federated -labels: [basic, vision, fds] +tags: [basic, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [torch | https://pytorch.org/] --- diff --git a/examples/quickstart-cpp/README.md b/examples/quickstart-cpp/README.md index 7e79aa63b851..1c766ea7474c 100644 --- a/examples/quickstart-cpp/README.md +++ b/examples/quickstart-cpp/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using C++ -labels: [quickstart, linear regression, tabular] +tags: [quickstart, linear regression, tabular] dataset: [Synthetic] framework: [C++ | https://isocpp.org/] --- diff --git a/examples/quickstart-fastai/README.md b/examples/quickstart-fastai/README.md index 8a8c41a49c22..aa4346754877 100644 --- a/examples/quickstart-fastai/README.md +++ b/examples/quickstart-fastai/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using fastai -labels: [quickstart, vision] +tags: [quickstart, vision] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [fastai | https://fast.ai] --- diff --git a/examples/quickstart-huggingface/README.md b/examples/quickstart-huggingface/README.md index 7235b12d7c8e..006c8c524816 100644 --- a/examples/quickstart-huggingface/README.md +++ b/examples/quickstart-huggingface/README.md @@ -1,6 +1,6 @@ --- title: Flower Transformers Example using HuggingFace -labels: [quickstart, llm, nlp, sentiment] +tags: [quickstart, llm, nlp, sentiment] dataset: [IMDB | https://huggingface.co/datasets/stanfordnlp/imdb] framework: [transformers | https://huggingface.co/docs/transformers/index] --- diff --git a/examples/quickstart-jax/README.md b/examples/quickstart-jax/README.md index 5d9939a2b4fb..581e97020955 100644 --- a/examples/quickstart-jax/README.md +++ b/examples/quickstart-jax/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using Jax -labels: [quickstart, linear regression] +tags: [quickstart, linear regression] dataset: [Synthetic] framework: [JAX | https://jax.readthedocs.io/en/latest/] --- diff --git a/examples/quickstart-mlcube/README.md b/examples/quickstart-mlcube/README.md index a2e989e6804b..92c22e00856f 100644 --- a/examples/quickstart-mlcube/README.md +++ b/examples/quickstart-mlcube/README.md @@ -1,6 +1,6 @@ --- title: Flower Example using TensorFlow/Keras + MLCube -labels: [quickstart, vision, deployment] +tags: [quickstart, vision, deployment] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] --- diff --git a/examples/quickstart-mlx/README.md b/examples/quickstart-mlx/README.md index 633b32f7bde7..8693c5bb4b5c 100644 --- a/examples/quickstart-mlx/README.md +++ b/examples/quickstart-mlx/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using MLX -labels: [quickstart, vision] +tags: [quickstart, vision] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [MLX | https://ml-explore.github.io/mlx/build/html/index.html] --- diff --git a/examples/quickstart-monai/README.md b/examples/quickstart-monai/README.md index 43f8c1cacbc7..8f4026acbd82 100644 --- a/examples/quickstart-monai/README.md +++ b/examples/quickstart-monai/README.md @@ -1,6 +1,6 @@ --- title: Flower Example using MONAI -labels: [quickstart, medical, vision] +tags: [quickstart, medical, vision] dataset: [MedNIST | https://medmnist.com/] framework: [MONAI | https://monai.io/] --- diff --git a/examples/quickstart-pandas/README.md b/examples/quickstart-pandas/README.md index d2cc7abbacd1..55a70322ab7f 100644 --- a/examples/quickstart-pandas/README.md +++ b/examples/quickstart-pandas/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using Pandas -labels: [quickstart, tabular, federated analytics] +tags: [quickstart, tabular, federated analytics] dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] framework: [pandas | https://pandas.pydata.org/] --- diff --git a/examples/quickstart-pytorch-lightning/README.md b/examples/quickstart-pytorch-lightning/README.md index ec968a1d8d0a..7013eae383d1 100644 --- a/examples/quickstart-pytorch-lightning/README.md +++ b/examples/quickstart-pytorch-lightning/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using PyTorch-Lightning -labels: [quickstart, vision, fds] +tags: [quickstart, vision, fds] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [lightning | https://lightning.ai/docs/pytorch/stable/] --- diff --git a/examples/quickstart-pytorch/README.md b/examples/quickstart-pytorch/README.md index 63a357b37e58..f0769646bf33 100644 --- a/examples/quickstart-pytorch/README.md +++ b/examples/quickstart-pytorch/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using PyTorch -labels: [quickstart, vision, fds] +tags: [quickstart, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/quickstart-sklearn-tabular/README.md b/examples/quickstart-sklearn-tabular/README.md index bb9aac58a0ed..ba4cea1d12d6 100644 --- a/examples/quickstart-sklearn-tabular/README.md +++ b/examples/quickstart-sklearn-tabular/README.md @@ -1,6 +1,6 @@ --- title: Flower Example using Scikit-Learn -labels: [quickstart, tabular, fds] +tags: [quickstart, tabular, fds] dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] framework: [scikit-learn | https://scikit-learn.org/] --- diff --git a/examples/quickstart-tabnet/README.md b/examples/quickstart-tabnet/README.md index 13cddc2bc737..41dfa8c5cc1d 100644 --- a/examples/quickstart-tabnet/README.md +++ b/examples/quickstart-tabnet/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using Tabnet -labels: [quickstart, tabular] +tags: [quickstart, tabular] dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] framework: [tabnet | https://github.com/titu1994/tf-TabNet] --- diff --git a/examples/quickstart-tensorflow/README.md b/examples/quickstart-tensorflow/README.md index ce7190b35ef9..cb1438a2f864 100644 --- a/examples/quickstart-tensorflow/README.md +++ b/examples/quickstart-tensorflow/README.md @@ -1,6 +1,6 @@ --- title: Simple Flower Example using TensorFlow -labels: [quickstart, vision, fds] +tags: [quickstart, vision, fds] dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] framework: [tensorflow | https://www.tensorflow.org/] --- diff --git a/examples/simulation-pytorch/README.md b/examples/simulation-pytorch/README.md index 85b9e136e6dc..787746909d06 100644 --- a/examples/simulation-pytorch/README.md +++ b/examples/simulation-pytorch/README.md @@ -1,6 +1,6 @@ --- title: Flower Simulation Example using PyTorch -labels: [basic, vision, fds, simulation] +tags: [basic, vision, fds, simulation] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/simulation-tensorflow/README.md b/examples/simulation-tensorflow/README.md index 2dc9a41cb959..b3262fcd684b 100644 --- a/examples/simulation-tensorflow/README.md +++ b/examples/simulation-tensorflow/README.md @@ -1,6 +1,6 @@ --- title: Flower Simulation Example using TensorFlow/Keras -labels: [basic, vision, fds, simulation] +tags: [basic, vision, fds, simulation] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] --- diff --git a/examples/sklearn-logreg-mnist/README.md b/examples/sklearn-logreg-mnist/README.md index edea3d7b28e8..6a6ed5651b5c 100644 --- a/examples/sklearn-logreg-mnist/README.md +++ b/examples/sklearn-logreg-mnist/README.md @@ -1,6 +1,6 @@ --- title: Flower LogReg Example using Scikit-Learn -labels: [basic, vision, logistic regression, fds] +tags: [basic, vision, logistic regression, fds] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [scikit-learn | https://scikit-learn.org/] --- diff --git a/examples/tensorflow-privacy/README.md b/examples/tensorflow-privacy/README.md index 48ed4594edac..9b4052c26d40 100644 --- a/examples/tensorflow-privacy/README.md +++ b/examples/tensorflow-privacy/README.md @@ -1,6 +1,6 @@ --- title: Sample-Level DP using TensorFlow-Privacy Engine -labels: [basic, vision, fds, privacy, dp] +tags: [basic, vision, fds, privacy, dp] dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] framework: [tensorflow | https://www.tensorflow.org/] --- diff --git a/examples/vertical-fl/README.md b/examples/vertical-fl/README.md index 30b38e59c619..45f6b19352a6 100644 --- a/examples/vertical-fl/README.md +++ b/examples/vertical-fl/README.md @@ -1,6 +1,6 @@ --- title: Vertical FL Flower Example -labels: [vertical, tabular, advanced] +tags: [vertical, tabular, advanced] dataset: [Titanic | https://www.kaggle.com/competitions/titanic] framework: [torch | https://pytorch.org/, pandas | https://pandas.pydata.org/, scikit-learn | https://scikit-learn.org/] diff --git a/examples/vit-finetune/README.md b/examples/vit-finetune/README.md index 3ef38aca5773..53dade19c67b 100644 --- a/examples/vit-finetune/README.md +++ b/examples/vit-finetune/README.md @@ -1,6 +1,6 @@ --- title: Federated finetuning of a ViT -labels: [finetuneing, vision, fds] +tags: [finetuneing, vision, fds] dataset: [Oxford Flower-102 | https://www.robots.ox.ac.uk/~vgg/data/flowers/102/] framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] --- diff --git a/examples/whisper-federated-finetuning/README.md b/examples/whisper-federated-finetuning/README.md index 1a79e581016f..2b8468df8ac2 100644 --- a/examples/whisper-federated-finetuning/README.md +++ b/examples/whisper-federated-finetuning/README.md @@ -1,6 +1,6 @@ --- title: On-device Federated Finetuning for Speech Classification -labels: [finetuning, speech, transformers] +tags: [finetuning, speech, transformers] dataset: [SpeechCommands | https://huggingface.co/datasets/google/speech_commands] framework: [transformers | https://huggingface.co/docs/transformers/index, whisper | https://huggingface.co/openai/whisper-tiny] diff --git a/examples/xgboost-comprehensive/README.md b/examples/xgboost-comprehensive/README.md index ea06febe43a7..8ebd5bbdf8d0 100644 --- a/examples/xgboost-comprehensive/README.md +++ b/examples/xgboost-comprehensive/README.md @@ -1,6 +1,6 @@ --- title: Flower Example using XGBoost -labels: [advanced, classification, tabular] +tags: [advanced, classification, tabular] dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] --- diff --git a/examples/xgboost-quickstart/README.md b/examples/xgboost-quickstart/README.md index 40edfd0c1870..476c889b7a89 100644 --- a/examples/xgboost-quickstart/README.md +++ b/examples/xgboost-quickstart/README.md @@ -1,6 +1,6 @@ --- title: Flower Example using PyTorch -labels: [quickstart, classification, tabular] +tags: [quickstart, classification, tabular] dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] --- From 0ef978cdd1c171913d1f63ca60a15961602ba394 Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Tue, 2 Jul 2024 15:09:26 +0200 Subject: [PATCH 102/595] fix(examples) Fix XGBoost examples' titles (#3707) --- examples/xgboost-comprehensive/README.md | 2 +- examples/xgboost-quickstart/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/xgboost-comprehensive/README.md b/examples/xgboost-comprehensive/README.md index 8ebd5bbdf8d0..579d5f22cc63 100644 --- a/examples/xgboost-comprehensive/README.md +++ b/examples/xgboost-comprehensive/README.md @@ -1,5 +1,5 @@ --- -title: Flower Example using XGBoost +title: Comprehensive Flower Example using XGBoost tags: [advanced, classification, tabular] dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] diff --git a/examples/xgboost-quickstart/README.md b/examples/xgboost-quickstart/README.md index 476c889b7a89..3455943914d1 100644 --- a/examples/xgboost-quickstart/README.md +++ b/examples/xgboost-quickstart/README.md @@ -1,5 +1,5 @@ --- -title: Flower Example using PyTorch +title: Simple Flower Example using XGBoost tags: [quickstart, classification, tabular] dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] From 9182a0bd9e7446562c71dbeb8de3424a08e92c36 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Tue, 2 Jul 2024 19:25:36 +0200 Subject: [PATCH 103/595] docs(framework:skip) Correct DP formula (#3708) --- doc/source/explanation-differential-privacy.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/explanation-differential-privacy.rst b/doc/source/explanation-differential-privacy.rst index 69fd333f9b13..e488f5ccbd57 100644 --- a/doc/source/explanation-differential-privacy.rst +++ b/doc/source/explanation-differential-privacy.rst @@ -32,7 +32,7 @@ and for all possible outputs S ⊆ Range(A): .. math:: \small - P[M(D_{1} \in A)] \leq e^{\delta} P[M(D_{2} \in A)] + \delta + P[M(D_{1} \in A)] \leq e^{\epsilon} P[M(D_{2} \in A)] + \delta The :math:`\epsilon` parameter, also known as the privacy budget, is a metric of privacy loss. From 69dd9913e3b62f661f6017e9ff90307222c50f87 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Wed, 3 Jul 2024 10:38:51 +0200 Subject: [PATCH 104/595] feat(framework:docker) add ca-certificates package (#3591) Signed-off-by: Robert Steiner --- src/docker/base/alpine/Dockerfile | 1 + src/docker/base/ubuntu/Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/src/docker/base/alpine/Dockerfile b/src/docker/base/alpine/Dockerfile index 04864b525e2e..54ff99097f0b 100644 --- a/src/docker/base/alpine/Dockerfile +++ b/src/docker/base/alpine/Dockerfile @@ -54,6 +54,7 @@ FROM python:${PYTHON_VERSION}-${DISTRO}${DISTRO_VERSION} as base # required by the grpc package RUN apk add --no-cache \ libstdc++ \ + ca-certificates \ # add non-root user && adduser \ --no-create-home \ diff --git a/src/docker/base/ubuntu/Dockerfile b/src/docker/base/ubuntu/Dockerfile index 4aeddc3f8d8d..64407555ef6e 100644 --- a/src/docker/base/ubuntu/Dockerfile +++ b/src/docker/base/ubuntu/Dockerfile @@ -55,6 +55,7 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get -y --no-install-recommends install \ libsqlite3-0 \ + ca-certificates \ && rm -rf /var/lib/apt/lists/* COPY --from=python /usr/local/bin/python /usr/local/bin/python From 552f4c5942fccec2d12eaba212b1c65186554db4 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Wed, 3 Jul 2024 10:45:35 +0200 Subject: [PATCH 105/595] ci(*:skip) Automate release of Docker images (#3643) Signed-off-by: Robert Steiner --- .github/workflows/docker-images.yml | 75 ------------------ .github/workflows/framework-release.yml | 67 ++++++++++++++++ doc/source/_static/docker-ci-release.png | Bin 119422 -> 0 bytes .../contributor-how-to-release-flower.rst | 18 ----- 4 files changed, 67 insertions(+), 93 deletions(-) delete mode 100644 .github/workflows/docker-images.yml delete mode 100644 doc/source/_static/docker-ci-release.png diff --git a/.github/workflows/docker-images.yml b/.github/workflows/docker-images.yml deleted file mode 100644 index e341ae62e3f7..000000000000 --- a/.github/workflows/docker-images.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: Build docker images - -on: - workflow_dispatch: - inputs: - flwr-version: - description: "Version of Flower." - required: true - type: string - -permissions: - contents: read - -jobs: - parameters: - name: Collect build parameters - runs-on: ubuntu-22.04 - timeout-minutes: 10 - outputs: - pip-version: ${{ steps.versions.outputs.pip-version }} - setuptools-version: ${{ steps.versions.outputs.setuptools-version }} - matrix: ${{ steps.matrix.outputs.matrix }} - steps: - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - uses: ./.github/actions/bootstrap - id: bootstrap - - - id: versions - run: | - echo "pip-version=${{ steps.bootstrap.outputs.pip-version }}" >> "$GITHUB_OUTPUT" - echo "setuptools-version=${{ steps.bootstrap.outputs.setuptools-version }}" >> "$GITHUB_OUTPUT" - - - id: matrix - run: | - python dev/build-docker-image-matrix.py --flwr-version ${{ github.event.inputs.flwr-version }} > matrix.json - echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT - - build-base-images: - name: Build base images - uses: ./.github/workflows/_docker-build.yml - needs: parameters - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.parameters.outputs.matrix).base }} - with: - namespace-repository: ${{ matrix.images.namespace_repository }} - file-dir: ${{ matrix.images.file_dir }} - build-args: | - PYTHON_VERSION=${{ matrix.images.python_version }} - PIP_VERSION=${{ needs.parameters.outputs.pip-version }} - SETUPTOOLS_VERSION=${{ needs.parameters.outputs.setuptools-version }} - DISTRO=${{ matrix.images.distro.name }} - DISTRO_VERSION=${{ matrix.images.distro.version }} - FLWR_VERSION=${{ matrix.images.flwr_version }} - tags: ${{ matrix.images.tag }} - secrets: - dockerhub-user: ${{ secrets.DOCKERHUB_USERNAME }} - dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - - build-binary-images: - name: Build binary images - uses: ./.github/workflows/_docker-build.yml - needs: [parameters, build-base-images] - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.parameters.outputs.matrix).binary }} - with: - namespace-repository: ${{ matrix.images.namespace_repository }} - file-dir: ${{ matrix.images.file_dir }} - build-args: BASE_IMAGE=${{ matrix.images.base_image }} - tags: ${{ matrix.images.tags }} - secrets: - dockerhub-user: ${{ secrets.DOCKERHUB_USERNAME }} - dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/framework-release.yml b/.github/workflows/framework-release.yml index a941b47d58fc..812d5b1e398e 100644 --- a/.github/workflows/framework-release.yml +++ b/.github/workflows/framework-release.yml @@ -43,3 +43,70 @@ jobs: curl $tar_url --output dist/$tar_name python -m poetry publish -u __token__ -p ${{ secrets.PYPI_TOKEN_RELEASE_FLWR }} + + parameters: + if: ${{ github.repository == 'adap/flower' }} + name: Collect docker build parameters + runs-on: ubuntu-22.04 + timeout-minutes: 10 + needs: publish + outputs: + pip-version: ${{ steps.versions.outputs.pip-version }} + setuptools-version: ${{ steps.versions.outputs.setuptools-version }} + matrix: ${{ steps.matrix.outputs.matrix }} + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - uses: ./.github/actions/bootstrap + id: bootstrap + + - id: versions + run: | + echo "pip-version=${{ steps.bootstrap.outputs.pip-version }}" >> "$GITHUB_OUTPUT" + echo "setuptools-version=${{ steps.bootstrap.outputs.setuptools-version }}" >> "$GITHUB_OUTPUT" + + - id: matrix + run: | + FLWR_VERSION=$(poetry version -s) + python dev/build-docker-image-matrix.py --flwr-version "${FLWR_VERSION}" > matrix.json + echo "matrix=$(cat matrix.json)" >> $GITHUB_OUTPUT + + build-base-images: + if: ${{ github.repository == 'adap/flower' }} + name: Build base images + uses: ./.github/workflows/_docker-build.yml + needs: parameters + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.parameters.outputs.matrix).base }} + with: + namespace-repository: ${{ matrix.images.namespace_repository }} + file-dir: ${{ matrix.images.file_dir }} + build-args: | + PYTHON_VERSION=${{ matrix.images.python_version }} + PIP_VERSION=${{ needs.parameters.outputs.pip-version }} + SETUPTOOLS_VERSION=${{ needs.parameters.outputs.setuptools-version }} + DISTRO=${{ matrix.images.distro.name }} + DISTRO_VERSION=${{ matrix.images.distro.version }} + FLWR_VERSION=${{ matrix.images.flwr_version }} + tags: ${{ matrix.images.tag }} + secrets: + dockerhub-user: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} + + build-binary-images: + if: ${{ github.repository == 'adap/flower' }} + name: Build binary images + uses: ./.github/workflows/_docker-build.yml + needs: [parameters, build-base-images] + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.parameters.outputs.matrix).binary }} + with: + namespace-repository: ${{ matrix.images.namespace_repository }} + file-dir: ${{ matrix.images.file_dir }} + build-args: BASE_IMAGE=${{ matrix.images.base_image }} + tags: ${{ matrix.images.tags }} + secrets: + dockerhub-user: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/doc/source/_static/docker-ci-release.png b/doc/source/_static/docker-ci-release.png deleted file mode 100644 index 6ec97ce9fb06405eb39bffb43c51c383140edcb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 119422 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

    J{3tgpyKvyFgj;d+ks8sx!Cd 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 a`36QpBBpX07uWf~58{-yY5B>EasLEu_-V`l diff --git a/doc/source/contributor-how-to-release-flower.rst b/doc/source/contributor-how-to-release-flower.rst index fc4c2d436b05..4853d87bc4c1 100644 --- a/doc/source/contributor-how-to-release-flower.rst +++ b/doc/source/contributor-how-to-release-flower.rst @@ -12,24 +12,6 @@ The version number of a release is stated in ``pyproject.toml``. To release a ne 2. Once the changelog has been updated with all the changes, run ``./dev/prepare-release-changelog.sh v``, where ```` is the version stated in ``pyproject.toml`` (notice the ``v`` added before it). This will replace the ``Unreleased`` header of the changelog by the version and current date, and it will add a thanking message for the contributors. Open a pull request with those changes. 3. Once the pull request is merged, tag the release commit with the version number as soon as the PR is merged: ``git tag v`` (notice the ``v`` added before the version number), then ``git push --tags``. This will create a draft release on GitHub containing the correct artifacts and the relevant part of the changelog. 4. Check the draft release on GitHub, and if everything is good, publish it. -5. Trigger the CI for building the Docker images. - -To trigger the workflow, a collaborator must create a ``workflow_dispatch`` event in the -GitHub CI. This can be done either through the UI or via the GitHub CLI. The event requires only one -input, the Flower version, to be released. - -**Via the UI** - -1. Go to the ``Build docker images`` workflow `page `_. -2. Click on the ``Run workflow`` button and type the new version of Flower in the ``Version of Flower`` input field. -3. Click on the **green** ``Run workflow`` button. - -.. image:: _static/docker-ci-release.png - -**Via the GitHub CI** - -1. Make sure you are logged in via ``gh auth login`` and that the current working directory is the root of the Flower repository. -2. Trigger the workflow via ``gh workflow run docker-images.yml -f flwr-version=``. After the release ----------------- From 498a560d47d848011029d29b12a4d639b56fbdca Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Wed, 3 Jul 2024 11:19:31 +0200 Subject: [PATCH 106/595] fix(*:skip) Set home directory of app user (#3652) Signed-off-by: Robert Steiner --- src/docker/base/alpine/Dockerfile | 1 + src/docker/base/ubuntu/Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/src/docker/base/alpine/Dockerfile b/src/docker/base/alpine/Dockerfile index 54ff99097f0b..9e58d82e3bda 100644 --- a/src/docker/base/alpine/Dockerfile +++ b/src/docker/base/alpine/Dockerfile @@ -58,6 +58,7 @@ RUN apk add --no-cache \ # add non-root user && adduser \ --no-create-home \ + --home /app \ --disabled-password \ --gecos "" \ --uid 49999 app \ diff --git a/src/docker/base/ubuntu/Dockerfile b/src/docker/base/ubuntu/Dockerfile index 64407555ef6e..c3d64b39cbb8 100644 --- a/src/docker/base/ubuntu/Dockerfile +++ b/src/docker/base/ubuntu/Dockerfile @@ -90,6 +90,7 @@ RUN pip install -U --no-cache-dir \ # add non-root user RUN adduser \ --no-create-home \ + --home /app \ --disabled-password \ --gecos "" \ --uid 49999 app \ From 049d929e5410f505ef84d5655efbc7ac8fd7b98d Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 4 Jul 2024 12:07:06 +0200 Subject: [PATCH 107/595] docs(examples:skip) Use dict for framework and datasets links (#3709) --- dev/build-example-docs.py | 74 +++++++++++++++---- examples/advanced-pytorch/README.md | 5 +- examples/advanced-tensorflow/README.md | 5 +- examples/android-kotlin/README.md | 8 +- examples/android/README.md | 8 +- examples/app-pytorch/README.md | 5 +- examples/app-secure-aggregation/README.md | 3 +- examples/custom-metrics/README.md | 4 +- examples/custom-mods/README.md | 7 +- examples/embedded-devices/README.md | 7 +- .../federated-kaplan-meier-fitter/README.md | 6 +- examples/fl-dp-sa/README.md | 5 +- examples/fl-tabular/README.md | 5 +- examples/flower-authentication/README.md | 5 +- examples/flower-in-30-minutes/README.md | 5 +- .../README.md | 5 +- examples/flower-via-docker-compose/README.md | 4 +- examples/ios/README.md | 7 +- examples/llm-flowertune/README.md | 4 +- examples/opacus/README.md | 5 +- .../README.md | 5 +- .../README.md | 5 +- examples/quickstart-cpp/README.md | 3 +- examples/quickstart-fastai/README.md | 5 +- examples/quickstart-huggingface/README.md | 5 +- examples/quickstart-jax/README.md | 3 +- examples/quickstart-mlcube/README.md | 5 +- examples/quickstart-mlx/README.md | 4 +- examples/quickstart-monai/README.md | 5 +- examples/quickstart-pandas/README.md | 5 +- .../quickstart-pytorch-lightning/README.md | 5 +- examples/quickstart-pytorch/README.md | 5 +- examples/quickstart-sklearn-tabular/README.md | 5 +- examples/quickstart-tabnet/README.md | 5 +- examples/quickstart-tensorflow/README.md | 5 +- examples/simulation-pytorch/README.md | 5 +- examples/simulation-tensorflow/README.md | 5 +- examples/sklearn-logreg-mnist/README.md | 7 +- examples/tensorflow-privacy/README.md | 5 +- examples/vertical-fl/README.md | 3 +- examples/vit-finetune/README.md | 4 +- .../whisper-federated-finetuning/README.md | 6 +- examples/xgboost-comprehensive/README.md | 5 +- examples/xgboost-quickstart/README.md | 5 +- 44 files changed, 148 insertions(+), 144 deletions(-) diff --git a/dev/build-example-docs.py b/dev/build-example-docs.py index 44d09383e7aa..f5d422db5903 100644 --- a/dev/build-example-docs.py +++ b/dev/build-example-docs.py @@ -61,19 +61,65 @@ "other": {"table": table_headers, "list": ""}, } +urls = { + # Frameworks + "Android": "https://www.android.com/", + "C++": "https://isocpp.org/", + "Docker": "https://www.docker.com/", + "JAX": "https://jax.readthedocs.io/en/latest/", + "Java": "https://www.java.com/", + "Keras": "https://keras.io/", + "Kotlin": "https://kotlinlang.org/", + "mlcube": "https://docs.mlcommons.org/mlcube/", + "MLX": "https://ml-explore.github.io/mlx/build/html/index.html", + "MONAI": "https://monai.io/", + "PEFT": "https://huggingface.co/docs/peft/index", + "Swift": "https://www.swift.org/", + "TensorFlowLite": "https://www.tensorflow.org/lite", + "fastai": "https://fast.ai/", + "lifelines": "https://lifelines.readthedocs.io/en/latest/index.html", + "lightning": "https://lightning.ai/docs/pytorch/stable/", + "numpy": "https://numpy.org/", + "opacus": "https://opacus.ai/", + "pandas": "https://pandas.pydata.org/", + "scikit-learn": "https://scikit-learn.org/", + "tabnet": "https://github.com/titu1994/tf-TabNet", + "tensorboard": "https://www.tensorflow.org/tensorboard", + "tensorflow": "https://www.tensorflow.org/", + "torch": "https://pytorch.org/", + "torchvision": "https://pytorch.org/vision/stable/index.html", + "transformers": "https://huggingface.co/docs/transformers/index", + "wandb": "https://wandb.ai/home", + "whisper": "https://huggingface.co/openai/whisper-tiny", + "xgboost": "https://xgboost.readthedocs.io/en/stable/", + # Datasets + "Adult Census Income": "https://www.kaggle.com/datasets/uciml/adult-census-income/data", + "Alpaca-GPT4": "https://huggingface.co/datasets/vicgalle/alpaca-gpt4", + "CIFAR-10": "https://huggingface.co/datasets/uoft-cs/cifar10", + "HIGGS": "https://archive.ics.uci.edu/dataset/280/higgs", + "IMDB": "https://huggingface.co/datasets/stanfordnlp/imdb", + "Iris": "https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html", + "MNIST": "https://huggingface.co/datasets/ylecun/mnist", + "MedNIST": "https://medmnist.com/", + "Oxford Flower-102": "https://www.robots.ox.ac.uk/~vgg/data/flowers/102/", + "SpeechCommands": "https://huggingface.co/datasets/google/speech_commands", + "Titanic": "https://www.kaggle.com/competitions/titanic", +} -def _convert_to_link(search_result): - if "|" in search_result: - if "," in search_result: - result = "" - for part in search_result.split(","): - result += f"{_convert_to_link(part)}, " - return result[:-2] - - name, url = search_result.replace('"', "").split("|") - return f"`{name.strip()} <{url.strip()}>`_" - return search_result +def _convert_to_link(search_result): + if "," in search_result: + result = "" + for part in search_result.split(","): + result += f"{_convert_to_link(part)}, " + return result[:-2] + else: + search_result = search_result.strip() + name, url = search_result, urls.get(search_result, None) + if url: + return f"`{name.strip()} <{url.strip()}>`_" + else: + return search_result def _read_metadata(example): @@ -85,7 +131,7 @@ def _read_metadata(example): raise ValueError("Metadata block not found") metadata = metadata_match.group(1) - title_match = re.search(r"^title:\s*(.+)$", metadata, re.MULTILINE) + title_match = re.search(r"^# (.+)$", content, re.MULTILINE) if not title_match: raise ValueError("Title not found in metadata") title = title_match.group(1).strip() @@ -163,7 +209,7 @@ def _copy_images(example): ) -def _add_all_entries(index_file): +def _add_all_entries(): examples_dir = os.path.join(ROOT, "examples") for example in sorted(os.listdir(examples_dir)): example_path = os.path.join(examples_dir, example) @@ -211,7 +257,7 @@ def _main(): ) index_file.write(categories["other"]["table"]) - _add_all_entries(index_file) + _add_all_entries() index_file.write( "\n.. toctree::\n :maxdepth: 1\n :caption: Quickstart\n :hidden:\n\n" diff --git a/examples/advanced-pytorch/README.md b/examples/advanced-pytorch/README.md index 98bb1713e09c..ac0737673407 100644 --- a/examples/advanced-pytorch/README.md +++ b/examples/advanced-pytorch/README.md @@ -1,8 +1,7 @@ --- -title: Advanced Flower Example using PyTorch tags: [advanced, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [CIFAR-10] +framework: [torch, torchvision] --- # Advanced Flower Example (PyTorch) diff --git a/examples/advanced-tensorflow/README.md b/examples/advanced-tensorflow/README.md index f5b93bca2800..375c539d13dd 100644 --- a/examples/advanced-tensorflow/README.md +++ b/examples/advanced-tensorflow/README.md @@ -1,8 +1,7 @@ --- -title: Advanced Flower Example using TensorFlow/Keras tags: [advanced, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] +dataset: [CIFAR-10] +framework: [tensorflow, Keras] --- # Advanced Flower Example (TensorFlow/Keras) diff --git a/examples/android-kotlin/README.md b/examples/android-kotlin/README.md index e51f008f9790..6cadb8e436fe 100644 --- a/examples/android-kotlin/README.md +++ b/examples/android-kotlin/README.md @@ -1,9 +1,7 @@ --- -title: Flower Android Example using Kotlin and TF Lite -tags: [basic, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [Android | https://www.android.com/, Kotlin | https://kotlinlang.org/, - TensorFlowLite | https://www.tensorflow.org/lite] +tags: [mobile, vision, sdk] +dataset: [CIFAR-10] +framework: [Android, Kotlin, TensorFlowLite] --- # Flower Android Client Example with Kotlin and TensorFlow Lite 2022 diff --git a/examples/android/README.md b/examples/android/README.md index 8b7cbaab51d0..83519f15d04d 100644 --- a/examples/android/README.md +++ b/examples/android/README.md @@ -1,9 +1,7 @@ --- -title: Flower Android Example using Java and TF Lite -tags: [basic, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [Android | https://www.android.com/, Java | https://www.java.com/, TensorFlowLite - | https://www.tensorflow.org/lite] +tags: [mobile, vision, sdk] +dataset: [CIFAR-10] +framework: [Android, Java, TensorFlowLite] --- # Flower Android Example (TensorFlowLite) diff --git a/examples/app-pytorch/README.md b/examples/app-pytorch/README.md index 6ff674086358..5cfae8440ed2 100644 --- a/examples/app-pytorch/README.md +++ b/examples/app-pytorch/README.md @@ -1,8 +1,7 @@ --- -title: Example Flower App using PyTorch tags: [basic, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [CIFAR-10] +framework: [torch, torchvision] --- # Flower App (PyTorch) 🧪 diff --git a/examples/app-secure-aggregation/README.md b/examples/app-secure-aggregation/README.md index 779501e5e7e1..8e483fb2f6bd 100644 --- a/examples/app-secure-aggregation/README.md +++ b/examples/app-secure-aggregation/README.md @@ -1,8 +1,7 @@ --- -title: Example Flower App with Secure Aggregation tags: [basic, vision, fds] dataset: [] -framework: [numpy | https://numpy.org/] +framework: [numpy] --- # Secure aggregation with Flower (the SecAgg+ protocol) 🧪 diff --git a/examples/custom-metrics/README.md b/examples/custom-metrics/README.md index 9f850df6b830..dd6985070cef 100644 --- a/examples/custom-metrics/README.md +++ b/examples/custom-metrics/README.md @@ -1,8 +1,8 @@ --- title: Example Flower App with Custom Metrics tags: [basic, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [tensorflow | https://www.tensorflow.org/] +dataset: [CIFAR-10] +framework: [tensorflow] --- # Flower Example using Custom Metrics diff --git a/examples/custom-mods/README.md b/examples/custom-mods/README.md index f275864c185c..c2007eb323ae 100644 --- a/examples/custom-mods/README.md +++ b/examples/custom-mods/README.md @@ -1,8 +1,7 @@ --- -title: Example Flower App with Custom Mods tags: [mods, monitoring, app] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [wandb | https://wandb.ai/home, tensorboard | https://www.tensorflow.org/tensorboard] +dataset: [CIFAR-10] +framework: [wandb, tensorboard] --- # Using custom mods 🧪 @@ -214,7 +213,7 @@ app = fl.client.ClientApp( client_fn=client_fn, mods=[ get_wandb_mod("Custom mods example"), - ], + ], ) ``` diff --git a/examples/embedded-devices/README.md b/examples/embedded-devices/README.md index 1cdf6c4efefb..86f19399932d 100644 --- a/examples/embedded-devices/README.md +++ b/examples/embedded-devices/README.md @@ -1,8 +1,7 @@ --- -title: Flower Embedded Devices Example tags: [basic, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10, MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [torch | https://pytorch.org/, tensorflow | https://www.tensorflow.org/] +dataset: [CIFAR-10, MNIST] +framework: [torch, tensorflow] --- # Federated Learning on Embedded Devices with Flower @@ -187,7 +186,7 @@ If you are working on this tutorial on your laptop or desktop, it can host the F ## Running Embedded FL with Flower -For this demo, we'll be using [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10](https://www.cs.toronto.edu/~kriz/cifar.html), a popular dataset for image classification comprised of 10 classes (e.g. car, bird, airplane) and a total of 60K `32x32` RGB images. The training set contains 50K images. The server will automatically download the dataset should it not be found in `./data`. The clients do the same. The dataset is by default split into 50 partitions (each to be assigned to a different client). This can be controlled with the `NUM_CLIENTS` global variable in the client scripts. In this example, each device will play the role of a specific user (specified via `--cid` -- we'll show this later) and therefore only do local training with that portion of the data. For CIFAR-10, clients will be training a MobileNet-v2/3 model. +For this demo, we'll be using [CIFAR-10](https://www.cs.toronto.edu/~kriz/cifar.html), a popular dataset for image classification comprised of 10 classes (e.g. car, bird, airplane) and a total of 60K `32x32` RGB images. The training set contains 50K images. The server will automatically download the dataset should it not be found in `./data`. The clients do the same. The dataset is by default split into 50 partitions (each to be assigned to a different client). This can be controlled with the `NUM_CLIENTS` global variable in the client scripts. In this example, each device will play the role of a specific user (specified via `--cid` -- we'll show this later) and therefore only do local training with that portion of the data. For CIFAR-10, clients will be training a MobileNet-v2/3 model. You can run this example using MNIST and a smaller CNN model by passing flag `--mnist`. This is useful if you are using devices with a very limited amount of memory (e.g. RaspberryPi Zero) or if you want the training taking place on the embedded devices to be much faster (specially if these are CPU-only). The partitioning of the dataset is done in the same way. diff --git a/examples/federated-kaplan-meier-fitter/README.md b/examples/federated-kaplan-meier-fitter/README.md index 0bec9b78e9c8..20d4ca4c47af 100644 --- a/examples/federated-kaplan-meier-fitter/README.md +++ b/examples/federated-kaplan-meier-fitter/README.md @@ -1,9 +1,7 @@ --- -title: Flower Example using KaplanMeierFitter tags: [estimator, medical] -dataset: [Waltons | - https://lifelines.readthedocs.io/en/latest/lifelines.datasets.html#lifelines.datasets.load_waltons] -framework: [lifelines | https://lifelines.readthedocs.io/en/latest/index.html] +dataset: [Waltons] +framework: [lifelines] --- # Flower Example using KaplanMeierFitter diff --git a/examples/fl-dp-sa/README.md b/examples/fl-dp-sa/README.md index a764499d22a5..65c8a5b18fa8 100644 --- a/examples/fl-dp-sa/README.md +++ b/examples/fl-dp-sa/README.md @@ -1,8 +1,7 @@ --- -title: Example of Flower App with DP and SA tags: [basic, vision, fds] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [MNIST] +framework: [torch, torchvision] --- # Example of Flower App with DP and SA diff --git a/examples/fl-tabular/README.md b/examples/fl-tabular/README.md index 4586251dbd98..ee6dd7d00ef0 100644 --- a/examples/fl-tabular/README.md +++ b/examples/fl-tabular/README.md @@ -1,8 +1,7 @@ --- -title: Flower Example on Adult Census Income Tabular Dataset tags: [basic, tabular, fds] -dataset: [Adult Census Income | https://www.kaggle.com/datasets/uciml/adult-census-income/data] -framework: [scikit-learn | https://scikit-learn.org/, torch | https://pytorch.org/] +dataset: [Adult Census Income] +framework: [scikit-learn, torch] --- # Flower Example on Adult Census Income Tabular Dataset diff --git a/examples/flower-authentication/README.md b/examples/flower-authentication/README.md index e77cbdfe94b6..d10780eeae5d 100644 --- a/examples/flower-authentication/README.md +++ b/examples/flower-authentication/README.md @@ -1,8 +1,7 @@ --- -title: Flower Example with Authentication tags: [advanced, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [CIFAR-10] +framework: [torch, torchvision] --- # Flower Authentication with PyTorch 🧪 diff --git a/examples/flower-in-30-minutes/README.md b/examples/flower-in-30-minutes/README.md index 0d0dafb70ee3..faec3d72dae2 100644 --- a/examples/flower-in-30-minutes/README.md +++ b/examples/flower-in-30-minutes/README.md @@ -1,8 +1,7 @@ --- -title: 30-minute tutorial running Flower simulation with PyTorch tags: [colab, vision, simulation] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [torch | https://pytorch.org/] +dataset: [CIFAR-10] +framework: [torch] --- # 30-minute tutorial running Flower simulation with PyTorch diff --git a/examples/flower-simulation-step-by-step-pytorch/README.md b/examples/flower-simulation-step-by-step-pytorch/README.md index 66cc632bc6ce..b00afedbe80b 100644 --- a/examples/flower-simulation-step-by-step-pytorch/README.md +++ b/examples/flower-simulation-step-by-step-pytorch/README.md @@ -1,8 +1,7 @@ --- -title: Flower Simulation Step-by-Step tags: [basic, vision, simulation] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [torch | https://pytorch.org/] +dataset: [MNIST] +framework: [torch] --- # Flower Simulation Step-by-Step diff --git a/examples/flower-via-docker-compose/README.md b/examples/flower-via-docker-compose/README.md index b77c55dae03c..3325a731fecf 100644 --- a/examples/flower-via-docker-compose/README.md +++ b/examples/flower-via-docker-compose/README.md @@ -1,8 +1,8 @@ --- title: Leveraging Flower and Docker for Device Heterogeneity Management in FL tags: [deployment, vision, tutorial] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [Docker | https://www.docker.com/, tensorflow | https://www.tensorflow.org/] +dataset: [CIFAR-10] +framework: [Docker, tensorflow] --- # Leveraging Flower and Docker for Device Heterogeneity Management in Federated Learning diff --git a/examples/ios/README.md b/examples/ios/README.md index 3fc2908c6781..aef4177dddf7 100644 --- a/examples/ios/README.md +++ b/examples/ios/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example on iOS -tags: [mobile, vision, fds] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [Swift | https://www.swift.org/] +tags: [mobile, vision, sdk] +dataset: [MNIST] +framework: [Swift] --- # FLiOS - A Flower SDK for iOS Devices with Example diff --git a/examples/llm-flowertune/README.md b/examples/llm-flowertune/README.md index 503306be052f..46076e0b2078 100644 --- a/examples/llm-flowertune/README.md +++ b/examples/llm-flowertune/README.md @@ -1,8 +1,8 @@ --- title: Federated LLM Fine-tuning with Flower tags: [llm, nlp, LLama2] -dataset: [Alpaca-GPT4 | https://huggingface.co/datasets/vicgalle/alpaca-gpt4] -framework: [PEFT | https://huggingface.co/docs/peft/index, torch | https://pytorch.org/] +dataset: [Alpaca-GPT4] +framework: [PEFT, torch] --- # LLM FlowerTune: Federated LLM Fine-tuning with Flower diff --git a/examples/opacus/README.md b/examples/opacus/README.md index c3b310b07418..aea5d0f689fe 100644 --- a/examples/opacus/README.md +++ b/examples/opacus/README.md @@ -1,8 +1,7 @@ --- -title: Sample-Level Differential Privacy using Opacus tags: [dp, security, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [opacus | https://opacus.ai/, torch | https://pytorch.org/] +dataset: [CIFAR-10] +framework: [opacus, torch] --- # Training with Sample-Level Differential Privacy using Opacus Privacy Engine diff --git a/examples/pytorch-federated-variational-autoencoder/README.md b/examples/pytorch-federated-variational-autoencoder/README.md index 4cc5521adff4..52f94a16307c 100644 --- a/examples/pytorch-federated-variational-autoencoder/README.md +++ b/examples/pytorch-federated-variational-autoencoder/README.md @@ -1,8 +1,7 @@ --- -title: Federated Variational Autoencoder using Pytorch tags: [basic, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [CIFAR-10] +framework: [torch, torchvision] --- # Flower Example for Federated Variational Autoencoder using Pytorch diff --git a/examples/pytorch-from-centralized-to-federated/README.md b/examples/pytorch-from-centralized-to-federated/README.md index 1a5ef610a25c..1bff7d02f52c 100644 --- a/examples/pytorch-from-centralized-to-federated/README.md +++ b/examples/pytorch-from-centralized-to-federated/README.md @@ -1,8 +1,7 @@ --- -title: PyTorch, From Centralized To Federated tags: [basic, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [torch | https://pytorch.org/] +dataset: [CIFAR-10] +framework: [torch] --- # PyTorch: From Centralized To Federated diff --git a/examples/quickstart-cpp/README.md b/examples/quickstart-cpp/README.md index 1c766ea7474c..61b76ece52b0 100644 --- a/examples/quickstart-cpp/README.md +++ b/examples/quickstart-cpp/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using C++ tags: [quickstart, linear regression, tabular] dataset: [Synthetic] -framework: [C++ | https://isocpp.org/] +framework: [C++] --- # Flower Clients in C++ (under development) diff --git a/examples/quickstart-fastai/README.md b/examples/quickstart-fastai/README.md index aa4346754877..d1bf97cd4203 100644 --- a/examples/quickstart-fastai/README.md +++ b/examples/quickstart-fastai/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using fastai tags: [quickstart, vision] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [fastai | https://fast.ai] +dataset: [MNIST] +framework: [fastai] --- # Flower Example using fastai diff --git a/examples/quickstart-huggingface/README.md b/examples/quickstart-huggingface/README.md index 006c8c524816..fa4330040ea7 100644 --- a/examples/quickstart-huggingface/README.md +++ b/examples/quickstart-huggingface/README.md @@ -1,8 +1,7 @@ --- -title: Flower Transformers Example using HuggingFace tags: [quickstart, llm, nlp, sentiment] -dataset: [IMDB | https://huggingface.co/datasets/stanfordnlp/imdb] -framework: [transformers | https://huggingface.co/docs/transformers/index] +dataset: [IMDB] +framework: [transformers] --- # Federated HuggingFace Transformers using Flower and PyTorch diff --git a/examples/quickstart-jax/README.md b/examples/quickstart-jax/README.md index 581e97020955..b47f3a82e13b 100644 --- a/examples/quickstart-jax/README.md +++ b/examples/quickstart-jax/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using Jax tags: [quickstart, linear regression] dataset: [Synthetic] -framework: [JAX | https://jax.readthedocs.io/en/latest/] +framework: [JAX] --- # JAX: From Centralized To Federated diff --git a/examples/quickstart-mlcube/README.md b/examples/quickstart-mlcube/README.md index 92c22e00856f..f0c6c5664a82 100644 --- a/examples/quickstart-mlcube/README.md +++ b/examples/quickstart-mlcube/README.md @@ -1,8 +1,7 @@ --- -title: Flower Example using TensorFlow/Keras + MLCube tags: [quickstart, vision, deployment] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] +dataset: [MNIST] +framework: [mlcube, tensorflow, Keras] --- # Flower Example using TensorFlow/Keras + MLCube diff --git a/examples/quickstart-mlx/README.md b/examples/quickstart-mlx/README.md index 8693c5bb4b5c..a4ac44bf8460 100644 --- a/examples/quickstart-mlx/README.md +++ b/examples/quickstart-mlx/README.md @@ -1,8 +1,8 @@ --- title: Simple Flower Example using MLX tags: [quickstart, vision] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [MLX | https://ml-explore.github.io/mlx/build/html/index.html] +dataset: [MNIST] +framework: [MLX] --- # Flower Example using MLX diff --git a/examples/quickstart-monai/README.md b/examples/quickstart-monai/README.md index 8f4026acbd82..dc31f03e4b1b 100644 --- a/examples/quickstart-monai/README.md +++ b/examples/quickstart-monai/README.md @@ -1,8 +1,7 @@ --- -title: Flower Example using MONAI tags: [quickstart, medical, vision] -dataset: [MedNIST | https://medmnist.com/] -framework: [MONAI | https://monai.io/] +dataset: [MedNIST] +framework: [MONAI] --- # Flower Example using MONAI diff --git a/examples/quickstart-pandas/README.md b/examples/quickstart-pandas/README.md index 55a70322ab7f..0b4b3a6ac78a 100644 --- a/examples/quickstart-pandas/README.md +++ b/examples/quickstart-pandas/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using Pandas tags: [quickstart, tabular, federated analytics] -dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] -framework: [pandas | https://pandas.pydata.org/] +dataset: [Iris] +framework: [pandas] --- # Flower Example using Pandas diff --git a/examples/quickstart-pytorch-lightning/README.md b/examples/quickstart-pytorch-lightning/README.md index 7013eae383d1..04eb911818fc 100644 --- a/examples/quickstart-pytorch-lightning/README.md +++ b/examples/quickstart-pytorch-lightning/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using PyTorch-Lightning tags: [quickstart, vision, fds] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [lightning | https://lightning.ai/docs/pytorch/stable/] +dataset: [MNIST] +framework: [lightning] --- # Flower Example using PyTorch Lightning diff --git a/examples/quickstart-pytorch/README.md b/examples/quickstart-pytorch/README.md index f0769646bf33..8eace1ea6845 100644 --- a/examples/quickstart-pytorch/README.md +++ b/examples/quickstart-pytorch/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using PyTorch tags: [quickstart, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [CIFAR-10] +framework: [torch, torchvision] --- # Flower Example using PyTorch diff --git a/examples/quickstart-sklearn-tabular/README.md b/examples/quickstart-sklearn-tabular/README.md index ba4cea1d12d6..b0b4cd1b84c0 100644 --- a/examples/quickstart-sklearn-tabular/README.md +++ b/examples/quickstart-sklearn-tabular/README.md @@ -1,8 +1,7 @@ --- -title: Flower Example using Scikit-Learn tags: [quickstart, tabular, fds] -dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] -framework: [scikit-learn | https://scikit-learn.org/] +dataset: [Iris] +framework: [scikit-learn] --- # Flower Example using scikit-learn diff --git a/examples/quickstart-tabnet/README.md b/examples/quickstart-tabnet/README.md index 41dfa8c5cc1d..e8be55eaacef 100644 --- a/examples/quickstart-tabnet/README.md +++ b/examples/quickstart-tabnet/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using Tabnet tags: [quickstart, tabular] -dataset: [Iris | https://scikit-learn.org/stable/auto_examples/datasets/plot_iris_dataset.html] -framework: [tabnet | https://github.com/titu1994/tf-TabNet] +dataset: [Iris] +framework: [tabnet] --- # Flower TabNet Example using TensorFlow diff --git a/examples/quickstart-tensorflow/README.md b/examples/quickstart-tensorflow/README.md index cb1438a2f864..386f8bbd96f0 100644 --- a/examples/quickstart-tensorflow/README.md +++ b/examples/quickstart-tensorflow/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using TensorFlow tags: [quickstart, vision, fds] -dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10] -framework: [tensorflow | https://www.tensorflow.org/] +dataset: [CIFAR-10] +framework: [tensorflow] --- # Flower Example using TensorFlow/Keras diff --git a/examples/simulation-pytorch/README.md b/examples/simulation-pytorch/README.md index 787746909d06..2dbfbc849ab7 100644 --- a/examples/simulation-pytorch/README.md +++ b/examples/simulation-pytorch/README.md @@ -1,8 +1,7 @@ --- -title: Flower Simulation Example using PyTorch tags: [basic, vision, fds, simulation] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [MNIST] +framework: [torch, torchvision] --- # Flower Simulation example using PyTorch diff --git a/examples/simulation-tensorflow/README.md b/examples/simulation-tensorflow/README.md index b3262fcd684b..047cb4379659 100644 --- a/examples/simulation-tensorflow/README.md +++ b/examples/simulation-tensorflow/README.md @@ -1,8 +1,7 @@ --- -title: Flower Simulation Example using TensorFlow/Keras tags: [basic, vision, fds, simulation] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/] +dataset: [MNIST] +framework: [tensorflow, Keras] --- # Flower Simulation example using TensorFlow/Keras diff --git a/examples/sklearn-logreg-mnist/README.md b/examples/sklearn-logreg-mnist/README.md index 6a6ed5651b5c..b117c5452086 100644 --- a/examples/sklearn-logreg-mnist/README.md +++ b/examples/sklearn-logreg-mnist/README.md @@ -1,11 +1,10 @@ --- -title: Flower LogReg Example using Scikit-Learn tags: [basic, vision, logistic regression, fds] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [scikit-learn | https://scikit-learn.org/] +dataset: [MNIST] +framework: [scikit-learn] --- -# Flower Example using scikit-learn +# Flower Logistic Regression Example using scikit-learn This example of Flower uses `scikit-learn`'s `LogisticRegression` model to train a federated learning system. It will help you understand how to adapt Flower for use with `scikit-learn`. Running this example in itself is quite easy. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) to download, partition and preprocess the MNIST dataset. diff --git a/examples/tensorflow-privacy/README.md b/examples/tensorflow-privacy/README.md index 9b4052c26d40..8156f92f60c9 100644 --- a/examples/tensorflow-privacy/README.md +++ b/examples/tensorflow-privacy/README.md @@ -1,8 +1,7 @@ --- -title: Sample-Level DP using TensorFlow-Privacy Engine tags: [basic, vision, fds, privacy, dp] -dataset: [MNIST | https://huggingface.co/datasets/ylecun/mnist] -framework: [tensorflow | https://www.tensorflow.org/] +dataset: [MNIST] +framework: [tensorflow] --- # Training with Sample-Level Differential Privacy using TensorFlow-Privacy Engine diff --git a/examples/vertical-fl/README.md b/examples/vertical-fl/README.md index 45f6b19352a6..e106a24e8654 100644 --- a/examples/vertical-fl/README.md +++ b/examples/vertical-fl/README.md @@ -2,8 +2,7 @@ title: Vertical FL Flower Example tags: [vertical, tabular, advanced] dataset: [Titanic | https://www.kaggle.com/competitions/titanic] -framework: [torch | https://pytorch.org/, pandas | https://pandas.pydata.org/, scikit-learn - | https://scikit-learn.org/] +framework: [torch, pandas, scikit-learn] --- # Vertical Federated Learning example diff --git a/examples/vit-finetune/README.md b/examples/vit-finetune/README.md index 53dade19c67b..957c0eda0b68 100644 --- a/examples/vit-finetune/README.md +++ b/examples/vit-finetune/README.md @@ -1,8 +1,8 @@ --- title: Federated finetuning of a ViT tags: [finetuneing, vision, fds] -dataset: [Oxford Flower-102 | https://www.robots.ox.ac.uk/~vgg/data/flowers/102/] -framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html] +dataset: [Oxford Flower-102] +framework: [torch, torchvision] --- # Federated finetuning of a ViT diff --git a/examples/whisper-federated-finetuning/README.md b/examples/whisper-federated-finetuning/README.md index 2b8468df8ac2..cfd0db842bae 100644 --- a/examples/whisper-federated-finetuning/README.md +++ b/examples/whisper-federated-finetuning/README.md @@ -1,9 +1,7 @@ --- -title: On-device Federated Finetuning for Speech Classification tags: [finetuning, speech, transformers] -dataset: [SpeechCommands | https://huggingface.co/datasets/google/speech_commands] -framework: [transformers | https://huggingface.co/docs/transformers/index, whisper - | https://huggingface.co/openai/whisper-tiny] +dataset: [SpeechCommands] +framework: [transformers, whisper] --- # On-device Federated Finetuning for Speech Classification diff --git a/examples/xgboost-comprehensive/README.md b/examples/xgboost-comprehensive/README.md index 579d5f22cc63..62fcba2bb06d 100644 --- a/examples/xgboost-comprehensive/README.md +++ b/examples/xgboost-comprehensive/README.md @@ -1,8 +1,7 @@ --- -title: Comprehensive Flower Example using XGBoost tags: [advanced, classification, tabular] -dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] -framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] +dataset: [HIGGS] +framework: [xgboost] --- # Flower Example using XGBoost (Comprehensive) diff --git a/examples/xgboost-quickstart/README.md b/examples/xgboost-quickstart/README.md index 3455943914d1..fa3e9d0dc6fb 100644 --- a/examples/xgboost-quickstart/README.md +++ b/examples/xgboost-quickstart/README.md @@ -1,8 +1,7 @@ --- -title: Simple Flower Example using XGBoost tags: [quickstart, classification, tabular] -dataset: [HIGGS | https://archive.ics.uci.edu/dataset/280/higgs] -framework: [xgboost | https://xgboost.readthedocs.io/en/stable/] +dataset: [HIGGS] +framework: [xgboost] --- # Flower Example using XGBoost From ac1297d946b9b0c878eec145be9e89436e4f1e78 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 4 Jul 2024 16:04:35 +0200 Subject: [PATCH 108/595] docs(examples:skip) Fix README metadata (#3711) --- dev/build-example-docs.py | 1 + examples/vertical-fl/README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/build-example-docs.py b/dev/build-example-docs.py index f5d422db5903..367994708bf9 100644 --- a/dev/build-example-docs.py +++ b/dev/build-example-docs.py @@ -104,6 +104,7 @@ "Oxford Flower-102": "https://www.robots.ox.ac.uk/~vgg/data/flowers/102/", "SpeechCommands": "https://huggingface.co/datasets/google/speech_commands", "Titanic": "https://www.kaggle.com/competitions/titanic", + "Waltons": "https://lifelines.readthedocs.io/en/latest/lifelines.datasets.html#lifelines.datasets.load_waltons", } diff --git a/examples/vertical-fl/README.md b/examples/vertical-fl/README.md index e106a24e8654..ab5d2210d8d5 100644 --- a/examples/vertical-fl/README.md +++ b/examples/vertical-fl/README.md @@ -1,7 +1,7 @@ --- title: Vertical FL Flower Example tags: [vertical, tabular, advanced] -dataset: [Titanic | https://www.kaggle.com/competitions/titanic] +dataset: [Titanic] framework: [torch, pandas, scikit-learn] --- From 131edbbb64ff400bad5283188276e2f5b7d102a1 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Fri, 5 Jul 2024 15:31:03 +0100 Subject: [PATCH 109/595] fix(framework:skip) Update the description of args for `flower-supernode` and `flower-client-app` and fix the usage of `args.max_retries` (#3710) --- src/py/flwr/client/app.py | 2 +- src/py/flwr/client/supernode/app.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index fa446afcc1fb..d2d5a79f32f3 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -298,7 +298,7 @@ def _on_backoff(retry_state: RetryState) -> None: retry_invoker = RetryInvoker( wait_gen_factory=exponential, recoverable_exceptions=connection_error_type, - max_tries=max_retries, + max_tries=max_retries + 1 if max_retries is not None else None, max_time=max_wait_time, on_giveup=lambda retry_state: ( log( diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 20e0c44eab14..4ee3544417aa 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -345,8 +345,8 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: "--max-retries", type=int, default=None, - help="The maximum number of times the client will try to connect to the" - "server before giving up in case of a connection error. By default," + help="The maximum number of times the client will try to reconnect to the" + "SuperLink before giving up in case of a connection error. By default," "it is set to None, meaning there is no limit to the number of tries.", ) parser.add_argument( @@ -354,7 +354,7 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: type=float, default=None, help="The maximum duration before the client stops trying to" - "connect to the server in case of connection error. By default, it" + "connect to the SuperLink in case of connection error. By default, it" "is set to None, meaning there is no limit to the total time.", ) parser.add_argument( From d3aec9254879c1cf640c1990514ef34191c64491 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 7 Jul 2024 13:55:08 +0200 Subject: [PATCH 110/595] break(framework) Remove support for `client_ids` in `start_simulation` (#3699) Co-authored-by: Daniel J. Beutel --- src/py/flwr/common/logger.py | 13 ++++ src/py/flwr/simulation/app.py | 63 ++++++++++++------- .../ray_transport/ray_client_proxy.py | 22 ++++--- .../ray_transport/ray_client_proxy_test.py | 43 ++++++------- 4 files changed, 89 insertions(+), 52 deletions(-) diff --git a/src/py/flwr/common/logger.py b/src/py/flwr/common/logger.py index 7225b0663ae7..f36ea2c1ce27 100644 --- a/src/py/flwr/common/logger.py +++ b/src/py/flwr/common/logger.py @@ -197,6 +197,19 @@ def warn_deprecated_feature(name: str) -> None: ) +def warn_unsupported_feature(name: str) -> None: + """Warn the user when they use an unsupported feature.""" + log( + WARN, + """UNSUPPORTED FEATURE: %s + + This is an unsupported feature. It will be removed + entirely in future versions of Flower. + """, + name, + ) + + def set_logger_propagation( child_logger: logging.Logger, value: bool = True ) -> logging.Logger: diff --git a/src/py/flwr/simulation/app.py b/src/py/flwr/simulation/app.py index 50a7dcad70a9..446b0bdeba38 100644 --- a/src/py/flwr/simulation/app.py +++ b/src/py/flwr/simulation/app.py @@ -29,12 +29,14 @@ from flwr.client import ClientFnExt from flwr.common import EventType, event -from flwr.common.logger import log, set_logger_propagation +from flwr.common.constant import NODE_ID_NUM_BYTES +from flwr.common.logger import log, set_logger_propagation, warn_unsupported_feature from flwr.server.client_manager import ClientManager from flwr.server.history import History from flwr.server.server import Server, init_defaults, run_fl from flwr.server.server_config import ServerConfig from flwr.server.strategy import Strategy +from flwr.server.superlink.state.utils import generate_rand_int_from_bytes from flwr.simulation.ray_transport.ray_actor import ( ClientAppActor, VirtualClientEngineActor, @@ -51,7 +53,7 @@ `start_simulation( *, client_fn: ClientFn, - num_clients: Optional[int] = None, + num_clients: int, clients_ids: Optional[List[str]] = None, client_resources: Optional[Dict[str, float]] = None, server: Optional[Server] = None, @@ -70,13 +72,29 @@ """ +NodeToPartitionMapping = Dict[int, int] + + +def _create_node_id_to_partition_mapping( + num_clients: int, +) -> NodeToPartitionMapping: + """Generate a node_id:partition_id mapping.""" + nodes_mapping: NodeToPartitionMapping = {} # {node-id; partition-id} + for i in range(num_clients): + while True: + node_id = generate_rand_int_from_bytes(NODE_ID_NUM_BYTES) + if node_id not in nodes_mapping: + break + nodes_mapping[node_id] = i + return nodes_mapping + # pylint: disable=too-many-arguments,too-many-statements,too-many-branches def start_simulation( *, client_fn: ClientFnExt, - num_clients: Optional[int] = None, - clients_ids: Optional[List[str]] = None, + num_clients: int, + clients_ids: Optional[List[str]] = None, # UNSUPPORTED, WILL BE REMOVED client_resources: Optional[Dict[str, float]] = None, server: Optional[Server] = None, config: Optional[ServerConfig] = None, @@ -102,13 +120,14 @@ def start_simulation( (model, dataset, hyperparameters, ...) should be (re-)created in either the call to `client_fn` or the call to any of the client methods (e.g., load evaluation data in the `evaluate` method itself). - num_clients : Optional[int] - The total number of clients in this simulation. This must be set if - `clients_ids` is not set and vice-versa. + num_clients : int + The total number of clients in this simulation. clients_ids : Optional[List[str]] + UNSUPPORTED, WILL BE REMOVED. USE `num_clients` INSTEAD. List `client_id`s for each client. This is only required if `num_clients` is not set. Setting both `num_clients` and `clients_ids` with `len(clients_ids)` not equal to `num_clients` generates an error. + Using this argument will raise an error. client_resources : Optional[Dict[str, float]] (default: `{"num_cpus": 1, "num_gpus": 0.0}`) CPU and GPU resources for a single client. Supported keys are `num_cpus` and `num_gpus`. To understand the GPU utilization caused by @@ -158,7 +177,6 @@ def start_simulation( is an advanced feature. For all details, please refer to the Ray documentation: https://docs.ray.io/en/latest/ray-core/scheduling/index.html - Returns ------- hist : flwr.server.history.History @@ -170,6 +188,14 @@ def start_simulation( {"num_clients": len(clients_ids) if clients_ids is not None else num_clients}, ) + if clients_ids is not None: + warn_unsupported_feature( + "Passing `clients_ids` to `start_simulation` is deprecated and not longer " + "used by `start_simulation`. Use `num_clients` exclusively instead." + ) + log(ERROR, "`clients_ids` argument used.") + sys.exit() + # Set logger propagation loop: Optional[asyncio.AbstractEventLoop] = None try: @@ -196,20 +222,8 @@ def start_simulation( initialized_config, ) - # clients_ids takes precedence - cids: List[str] - if clients_ids is not None: - if (num_clients is not None) and (len(clients_ids) != num_clients): - log(ERROR, INVALID_ARGUMENTS_START_SIMULATION) - sys.exit() - else: - cids = clients_ids - else: - if num_clients is None: - log(ERROR, INVALID_ARGUMENTS_START_SIMULATION) - sys.exit() - else: - cids = [str(x) for x in range(num_clients)] + # Create node-id to partition-id mapping + nodes_mapping = _create_node_id_to_partition_mapping(num_clients) # Default arguments for Ray initialization if not ray_init_args: @@ -308,10 +322,11 @@ def update_resources(f_stop: threading.Event) -> None: ) # Register one RayClientProxy object for each client with the ClientManager - for cid in cids: + for node_id, partition_id in nodes_mapping.items(): client_proxy = RayActorClientProxy( client_fn=client_fn, - cid=cid, + node_id=node_id, + partition_id=partition_id, actor_pool=pool, ) initialized_server.client_manager().register(client=client_proxy) diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index 3a9712df978e..31bc22c84bd5 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -44,16 +44,22 @@ class RayActorClientProxy(ClientProxy): """Flower client proxy which delegates work using Ray.""" def __init__( - self, client_fn: ClientFnExt, cid: str, actor_pool: VirtualClientEngineActorPool + self, + client_fn: ClientFnExt, + node_id: int, + partition_id: int, + actor_pool: VirtualClientEngineActorPool, ): - super().__init__(cid) + super().__init__(cid=str(node_id)) + self.node_id = node_id + self.partition_id = partition_id def _load_app() -> ClientApp: return ClientApp(client_fn=client_fn) self.app_fn = _load_app self.actor_pool = actor_pool - self.proxy_state = NodeState(partition_id=int(self.cid)) + self.proxy_state = NodeState(partition_id=self.partition_id) def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: """Sumbit a message to the ActorPool.""" @@ -67,11 +73,13 @@ def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: try: self.actor_pool.submit_client_job( - lambda a, a_fn, mssg, cid, state: a.run.remote(a_fn, mssg, cid, state), - (self.app_fn, message, self.cid, state), + lambda a, a_fn, mssg, partition_id, state: a.run.remote( + a_fn, mssg, partition_id, state + ), + (self.app_fn, message, str(self.partition_id), state), ) out_mssg, updated_context = self.actor_pool.get_client_result( - self.cid, timeout + str(self.partition_id), timeout ) # Update state @@ -103,7 +111,7 @@ def _wrap_recordset_in_message( message_id="", group_id=str(group_id) if group_id is not None else "", src_node_id=0, - dst_node_id=int(self.cid), + dst_node_id=self.node_id, reply_to_message="", ttl=timeout if timeout else DEFAULT_TTL, message_type=message_type, diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index df059135b4e0..8da579dc650e 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -39,6 +39,7 @@ recordset_to_getpropertiesres, ) from flwr.common.recordset_compat_test import _get_valid_getpropertiesins +from flwr.simulation.app import _create_node_id_to_partition_mapping from flwr.simulation.ray_transport.ray_actor import ( ClientAppActor, VirtualClientEngineActor, @@ -68,9 +69,7 @@ def get_dummy_client( node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument ) -> Client: """Return a DummyClient converted to Client type.""" - if partition_id is None: - raise ValueError("`partition_id` is not set.") - return DummyClient(partition_id).to_client() + return DummyClient(node_id).to_client() def prep( @@ -91,13 +90,15 @@ def create_actor_fn() -> Type[VirtualClientEngineActor]: # Create 373 client proxies num_proxies = 373 # a prime number + mapping = _create_node_id_to_partition_mapping(num_proxies) proxies = [ RayActorClientProxy( client_fn=get_dummy_client, - cid=str(cid), + node_id=node_id, + partition_id=partition_id, actor_pool=pool, ) - for cid in range(num_proxies) + for node_id, partition_id in mapping.items() ] return proxies, pool @@ -127,7 +128,7 @@ def test_cid_consistency_one_at_a_time() -> None: res = recordset_to_getpropertiesres(message_out.content) - assert int(prox.cid) * pi == res.properties["result"] + assert int(prox.node_id) * pi == res.properties["result"] ray.shutdown() @@ -160,21 +161,21 @@ def test_cid_consistency_all_submit_first_run_consistency() -> None: ) prox.actor_pool.submit_client_job( lambda a, a_fn, mssg, cid, state: a.run.remote(a_fn, mssg, cid, state), - (prox.app_fn, message, prox.cid, state), + (prox.app_fn, message, str(prox.node_id), state), ) # fetch results one at a time shuffle(proxies) for prox in proxies: message_out, updated_context = prox.actor_pool.get_client_result( - prox.cid, timeout=None + str(prox.node_id), timeout=None ) prox.proxy_state.update_context(run_id, context=updated_context) res = recordset_to_getpropertiesres(message_out.content) - assert int(prox.cid) * pi == res.properties["result"] + assert prox.node_id * pi == res.properties["result"] assert ( - str(int(prox.cid) * pi) + str(prox.node_id * pi) == prox.proxy_state.retrieve_context(run_id).state.configs_records[ "result" ]["result"] @@ -187,7 +188,7 @@ def test_cid_consistency_without_proxies() -> None: """Test cid consistency of jobs submitted/retrieved to/from pool w/o ClientProxy.""" proxies, pool = prep() num_clients = len(proxies) - cids = [str(cid) for cid in range(num_clients)] + node_ids = list(range(num_clients)) getproperties_ins = _get_valid_getpropertiesins() recordset = getpropertiesins_to_recordset(getproperties_ins) @@ -196,8 +197,8 @@ def _load_app() -> ClientApp: return ClientApp(client_fn=get_dummy_client) # submit all jobs (collect later) - shuffle(cids) - for cid in cids: + shuffle(node_ids) + for node_id in node_ids: message = Message( content=recordset, metadata=Metadata( @@ -205,27 +206,27 @@ def _load_app() -> ClientApp: message_id="", group_id=str(0), src_node_id=0, - dst_node_id=12345, + dst_node_id=node_id, reply_to_message="", ttl=DEFAULT_TTL, message_type=MessageTypeLegacy.GET_PROPERTIES, ), ) pool.submit_client_job( - lambda a, c_fn, j_fn, cid_, state: a.run.remote(c_fn, j_fn, cid_, state), + lambda a, c_fn, j_fn, nid_, state: a.run.remote(c_fn, j_fn, nid_, state), ( _load_app, message, - cid, - Context(state=RecordSet(), partition_id=int(cid)), + str(node_id), + Context(state=RecordSet(), partition_id=node_id), ), ) # fetch results one at a time - shuffle(cids) - for cid in cids: - message_out, _ = pool.get_client_result(cid, timeout=None) + shuffle(node_ids) + for node_id in node_ids: + message_out, _ = pool.get_client_result(str(node_id), timeout=None) res = recordset_to_getpropertiesres(message_out.content) - assert int(cid) * pi == res.properties["result"] + assert node_id * pi == res.properties["result"] ray.shutdown() From 384dd25f5cb808e772c52bf295fe621a6a192b83 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 11:15:14 +0200 Subject: [PATCH 111/595] feat(framework:skip) Add default executor for SuperExec (#3719) --- src/py/flwr/superexec/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py index fa89e83b5e75..b4d4b462bbcc 100644 --- a/src/py/flwr/superexec/app.py +++ b/src/py/flwr/superexec/app.py @@ -77,6 +77,7 @@ def _parse_args_run_superexec() -> argparse.ArgumentParser: parser.add_argument( "executor", help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.", + default="flwr.superexec.deployment:executor", ) parser.add_argument( "--address", From f7432a5b49d81ec690d075d63243c3df97617b77 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 12:59:34 +0200 Subject: [PATCH 112/595] feat(framework) Add proto changes for config overrides (#3728) --- src/proto/flwr/proto/driver.proto | 1 + src/proto/flwr/proto/exec.proto | 5 +++- src/proto/flwr/proto/run.proto | 1 + src/py/flwr/proto/common_pb2.py | 24 +++++++++++++++ src/py/flwr/proto/common_pb2.pyi | 7 +++++ src/py/flwr/proto/common_pb2_grpc.py | 4 +++ src/py/flwr/proto/common_pb2_grpc.pyi | 4 +++ src/py/flwr/proto/driver_pb2.py | 42 +++++++++++++++------------ src/py/flwr/proto/driver_pb2.pyi | 19 +++++++++++- src/py/flwr/proto/exec_pb2.py | 26 ++++++++++------- src/py/flwr/proto/exec_pb2.pyi | 20 ++++++++++++- src/py/flwr/proto/run_pb2.py | 18 +++++++----- src/py/flwr/proto/run_pb2.pyi | 20 ++++++++++++- 13 files changed, 150 insertions(+), 41 deletions(-) create mode 100644 src/py/flwr/proto/common_pb2.py create mode 100644 src/py/flwr/proto/common_pb2.pyi create mode 100644 src/py/flwr/proto/common_pb2_grpc.py create mode 100644 src/py/flwr/proto/common_pb2_grpc.pyi diff --git a/src/proto/flwr/proto/driver.proto b/src/proto/flwr/proto/driver.proto index edbd5d91bb5b..77dc52b3258b 100644 --- a/src/proto/flwr/proto/driver.proto +++ b/src/proto/flwr/proto/driver.proto @@ -42,6 +42,7 @@ service Driver { message CreateRunRequest { string fab_id = 1; string fab_version = 2; + map override_config = 3; } message CreateRunResponse { sint64 run_id = 1; } diff --git a/src/proto/flwr/proto/exec.proto b/src/proto/flwr/proto/exec.proto index 8e5f53b02ca8..d0d8dfcbb273 100644 --- a/src/proto/flwr/proto/exec.proto +++ b/src/proto/flwr/proto/exec.proto @@ -25,7 +25,10 @@ service Exec { rpc StreamLogs(StreamLogsRequest) returns (stream StreamLogsResponse) {} } -message StartRunRequest { bytes fab_file = 1; } +message StartRunRequest { + bytes fab_file = 1; + map override_config = 2; +} message StartRunResponse { sint64 run_id = 1; } message StreamLogsRequest { sint64 run_id = 1; } message StreamLogsResponse { string log_output = 1; } diff --git a/src/proto/flwr/proto/run.proto b/src/proto/flwr/proto/run.proto index 76a7fd91532f..e41748381cab 100644 --- a/src/proto/flwr/proto/run.proto +++ b/src/proto/flwr/proto/run.proto @@ -21,6 +21,7 @@ message Run { sint64 run_id = 1; string fab_id = 2; string fab_version = 3; + map override_config = 4; } message GetRunRequest { sint64 run_id = 1; } message GetRunResponse { Run run = 1; } diff --git a/src/py/flwr/proto/common_pb2.py b/src/py/flwr/proto/common_pb2.py new file mode 100644 index 000000000000..8a6430137f05 --- /dev/null +++ b/src/py/flwr/proto/common_pb2.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: flwr/proto/common.proto +# Protobuf Python Version: 4.25.0 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/common.proto\x12\nflwr.protob\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.common_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None +# @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/common_pb2.pyi b/src/py/flwr/proto/common_pb2.pyi new file mode 100644 index 000000000000..e08fa11c2caa --- /dev/null +++ b/src/py/flwr/proto/common_pb2.pyi @@ -0,0 +1,7 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import google.protobuf.descriptor + +DESCRIPTOR: google.protobuf.descriptor.FileDescriptor diff --git a/src/py/flwr/proto/common_pb2_grpc.py b/src/py/flwr/proto/common_pb2_grpc.py new file mode 100644 index 000000000000..2daafffebfc8 --- /dev/null +++ b/src/py/flwr/proto/common_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/src/py/flwr/proto/common_pb2_grpc.pyi b/src/py/flwr/proto/common_pb2_grpc.pyi new file mode 100644 index 000000000000..f3a5a087ef5d --- /dev/null +++ b/src/py/flwr/proto/common_pb2_grpc.pyi @@ -0,0 +1,4 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" diff --git a/src/py/flwr/proto/driver_pb2.py b/src/py/flwr/proto/driver_pb2.py index a2458b445563..07975937328d 100644 --- a/src/py/flwr/proto/driver_pb2.py +++ b/src/py/flwr/proto/driver_pb2.py @@ -17,29 +17,33 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\"7\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\x84\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\"\xb9\x01\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\x12I\n\x0foverride_config\x18\x03 \x03(\x0b\x32\x30.flwr.proto.CreateRunRequest.OverrideConfigEntry\x1a\x35\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\x84\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.driver_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_CREATERUNREQUEST']._serialized_start=107 - _globals['_CREATERUNREQUEST']._serialized_end=162 - _globals['_CREATERUNRESPONSE']._serialized_start=164 - _globals['_CREATERUNRESPONSE']._serialized_end=199 - _globals['_GETNODESREQUEST']._serialized_start=201 - _globals['_GETNODESREQUEST']._serialized_end=234 - _globals['_GETNODESRESPONSE']._serialized_start=236 - _globals['_GETNODESRESPONSE']._serialized_end=287 - _globals['_PUSHTASKINSREQUEST']._serialized_start=289 - _globals['_PUSHTASKINSREQUEST']._serialized_end=353 - _globals['_PUSHTASKINSRESPONSE']._serialized_start=355 - _globals['_PUSHTASKINSRESPONSE']._serialized_end=394 - _globals['_PULLTASKRESREQUEST']._serialized_start=396 - _globals['_PULLTASKRESREQUEST']._serialized_end=466 - _globals['_PULLTASKRESRESPONSE']._serialized_start=468 - _globals['_PULLTASKRESRESPONSE']._serialized_end=533 - _globals['_DRIVER']._serialized_start=536 - _globals['_DRIVER']._serialized_end=924 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._options = None + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' + _globals['_CREATERUNREQUEST']._serialized_start=108 + _globals['_CREATERUNREQUEST']._serialized_end=293 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=240 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=293 + _globals['_CREATERUNRESPONSE']._serialized_start=295 + _globals['_CREATERUNRESPONSE']._serialized_end=330 + _globals['_GETNODESREQUEST']._serialized_start=332 + _globals['_GETNODESREQUEST']._serialized_end=365 + _globals['_GETNODESRESPONSE']._serialized_start=367 + _globals['_GETNODESRESPONSE']._serialized_end=418 + _globals['_PUSHTASKINSREQUEST']._serialized_start=420 + _globals['_PUSHTASKINSREQUEST']._serialized_end=484 + _globals['_PUSHTASKINSRESPONSE']._serialized_start=486 + _globals['_PUSHTASKINSRESPONSE']._serialized_end=525 + _globals['_PULLTASKRESREQUEST']._serialized_start=527 + _globals['_PULLTASKRESREQUEST']._serialized_end=597 + _globals['_PULLTASKRESRESPONSE']._serialized_start=599 + _globals['_PULLTASKRESRESPONSE']._serialized_end=664 + _globals['_DRIVER']._serialized_start=667 + _globals['_DRIVER']._serialized_end=1055 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/driver_pb2.pyi b/src/py/flwr/proto/driver_pb2.pyi index 2d8d11fb59a3..95d4c9785ff1 100644 --- a/src/py/flwr/proto/driver_pb2.pyi +++ b/src/py/flwr/proto/driver_pb2.pyi @@ -16,16 +16,33 @@ DESCRIPTOR: google.protobuf.descriptor.FileDescriptor class CreateRunRequest(google.protobuf.message.Message): """CreateRun""" DESCRIPTOR: google.protobuf.descriptor.Descriptor + class OverrideConfigEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: typing.Text + value: typing.Text + def __init__(self, + *, + key: typing.Text = ..., + value: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... + FAB_ID_FIELD_NUMBER: builtins.int FAB_VERSION_FIELD_NUMBER: builtins.int + OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int fab_id: typing.Text fab_version: typing.Text + @property + def override_config(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ... def __init__(self, *, fab_id: typing.Text = ..., fab_version: typing.Text = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config"]) -> None: ... global___CreateRunRequest = CreateRunRequest class CreateRunResponse(google.protobuf.message.Message): diff --git a/src/py/flwr/proto/exec_pb2.py b/src/py/flwr/proto/exec_pb2.py index 7b037a9454c0..4aee0f4a882f 100644 --- a/src/py/flwr/proto/exec_pb2.py +++ b/src/py/flwr/proto/exec_pb2.py @@ -14,21 +14,25 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"#\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"\xa4\x01\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x1a\x35\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.exec_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_STARTRUNREQUEST']._serialized_start=37 - _globals['_STARTRUNREQUEST']._serialized_end=72 - _globals['_STARTRUNRESPONSE']._serialized_start=74 - _globals['_STARTRUNRESPONSE']._serialized_end=108 - _globals['_STREAMLOGSREQUEST']._serialized_start=110 - _globals['_STREAMLOGSREQUEST']._serialized_end=145 - _globals['_STREAMLOGSRESPONSE']._serialized_start=147 - _globals['_STREAMLOGSRESPONSE']._serialized_end=187 - _globals['_EXEC']._serialized_start=190 - _globals['_EXEC']._serialized_end=350 + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._options = None + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' + _globals['_STARTRUNREQUEST']._serialized_start=38 + _globals['_STARTRUNREQUEST']._serialized_end=202 + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=149 + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=202 + _globals['_STARTRUNRESPONSE']._serialized_start=204 + _globals['_STARTRUNRESPONSE']._serialized_end=238 + _globals['_STREAMLOGSREQUEST']._serialized_start=240 + _globals['_STREAMLOGSREQUEST']._serialized_end=275 + _globals['_STREAMLOGSRESPONSE']._serialized_start=277 + _globals['_STREAMLOGSRESPONSE']._serialized_end=317 + _globals['_EXEC']._serialized_start=320 + _globals['_EXEC']._serialized_end=480 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/exec_pb2.pyi b/src/py/flwr/proto/exec_pb2.pyi index 466812808da8..8065fc1de1b4 100644 --- a/src/py/flwr/proto/exec_pb2.pyi +++ b/src/py/flwr/proto/exec_pb2.pyi @@ -4,6 +4,7 @@ isort:skip_file """ import builtins import google.protobuf.descriptor +import google.protobuf.internal.containers import google.protobuf.message import typing import typing_extensions @@ -12,13 +13,30 @@ DESCRIPTOR: google.protobuf.descriptor.FileDescriptor class StartRunRequest(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor + class OverrideConfigEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: typing.Text + value: typing.Text + def __init__(self, + *, + key: typing.Text = ..., + value: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... + FAB_FILE_FIELD_NUMBER: builtins.int + OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int fab_file: builtins.bytes + @property + def override_config(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ... def __init__(self, *, fab_file: builtins.bytes = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["fab_file",b"fab_file"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["fab_file",b"fab_file","override_config",b"override_config"]) -> None: ... global___StartRunRequest = StartRunRequest class StartRunResponse(google.protobuf.message.Message): diff --git a/src/py/flwr/proto/run_pb2.py b/src/py/flwr/proto/run_pb2.py index 13f06e7169aa..d6531201f647 100644 --- a/src/py/flwr/proto/run_pb2.py +++ b/src/py/flwr/proto/run_pb2.py @@ -14,17 +14,21 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\":\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\"\xaf\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x1a\x35\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.run_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_RUN']._serialized_start=36 - _globals['_RUN']._serialized_end=94 - _globals['_GETRUNREQUEST']._serialized_start=96 - _globals['_GETRUNREQUEST']._serialized_end=127 - _globals['_GETRUNRESPONSE']._serialized_start=129 - _globals['_GETRUNRESPONSE']._serialized_end=175 + _globals['_RUN_OVERRIDECONFIGENTRY']._options = None + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' + _globals['_RUN']._serialized_start=37 + _globals['_RUN']._serialized_end=212 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=159 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=212 + _globals['_GETRUNREQUEST']._serialized_start=214 + _globals['_GETRUNREQUEST']._serialized_end=245 + _globals['_GETRUNRESPONSE']._serialized_start=247 + _globals['_GETRUNRESPONSE']._serialized_end=293 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/run_pb2.pyi b/src/py/flwr/proto/run_pb2.pyi index 401d27855a41..3c58c04c1734 100644 --- a/src/py/flwr/proto/run_pb2.pyi +++ b/src/py/flwr/proto/run_pb2.pyi @@ -4,6 +4,7 @@ isort:skip_file """ import builtins import google.protobuf.descriptor +import google.protobuf.internal.containers import google.protobuf.message import typing import typing_extensions @@ -12,19 +13,36 @@ DESCRIPTOR: google.protobuf.descriptor.FileDescriptor class Run(google.protobuf.message.Message): DESCRIPTOR: google.protobuf.descriptor.Descriptor + class OverrideConfigEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: typing.Text + value: typing.Text + def __init__(self, + *, + key: typing.Text = ..., + value: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... + RUN_ID_FIELD_NUMBER: builtins.int FAB_ID_FIELD_NUMBER: builtins.int FAB_VERSION_FIELD_NUMBER: builtins.int + OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int run_id: builtins.int fab_id: typing.Text fab_version: typing.Text + @property + def override_config(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ... def __init__(self, *, run_id: builtins.int = ..., fab_id: typing.Text = ..., fab_version: typing.Text = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","run_id",b"run_id"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config","run_id",b"run_id"]) -> None: ... global___Run = Run class GetRunRequest(google.protobuf.message.Message): From 7bbbdd188445793e2879ebc1f2c8acad4150cd47 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 13:18:05 +0200 Subject: [PATCH 113/595] feat(framework) Use `Run` as `get_run` return type (#3729) Co-authored-by: Daniel J. Beutel --- src/py/flwr/client/app.py | 15 +++++++++------ .../flwr/client/grpc_adapter_client/connection.py | 3 ++- src/py/flwr/client/grpc_client/connection.py | 3 ++- src/py/flwr/client/grpc_rere_client/connection.py | 11 ++++++++--- src/py/flwr/client/rest_client/connection.py | 13 +++++++++---- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index d2d5a79f32f3..3396b5c580cb 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -41,6 +41,7 @@ from flwr.common.logger import log, warn_deprecated_feature from flwr.common.message import Error from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential +from flwr.common.typing import Run from .grpc_adapter_client.connection import grpc_adapter from .grpc_client.connection import grpc_connection @@ -235,7 +236,7 @@ class `flwr.client.Client` (default: None) The maximum duration before the client stops trying to connect to the server in case of connection error. If set to None, there is no limit to the total time. - partitioni_id: Optional[int] (default: None) + partition_id: Optional[int] (default: None) The data partition index associated with this node. Better suited for prototyping purposes. """ @@ -315,8 +316,7 @@ def _on_backoff(retry_state: RetryState) -> None: ) node_state = NodeState(partition_id=partition_id) - # run_id -> (fab_id, fab_version) - run_info: Dict[int, Tuple[str, str]] = {} + run_info: Dict[int, Run] = {} while not app_state_tracker.interrupt: sleep_duration: int = 0 @@ -371,7 +371,7 @@ def _on_backoff(retry_state: RetryState) -> None: run_info[run_id] = get_run(run_id) # If get_run is None, i.e., in grpc-bidi mode else: - run_info[run_id] = ("", "") + run_info[run_id] = Run(run_id, "", "") # Register context for this run node_state.register_context(run_id=run_id) @@ -388,7 +388,10 @@ def _on_backoff(retry_state: RetryState) -> None: # Handle app loading and task message try: # Load ClientApp instance - client_app: ClientApp = load_client_app_fn(*run_info[run_id]) + run: Run = run_info[run_id] + client_app: ClientApp = load_client_app_fn( + run.fab_id, run.fab_version + ) # Execute ClientApp reply_message = client_app(message=message, context=context) @@ -573,7 +576,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[ Callable[[Message], None], Optional[Callable[[], None]], Optional[Callable[[], None]], - Optional[Callable[[int], Tuple[str, str]]], + Optional[Callable[[int], Run]], ] ], ], diff --git a/src/py/flwr/client/grpc_adapter_client/connection.py b/src/py/flwr/client/grpc_adapter_client/connection.py index e4e32b3accd0..971b630e470b 100644 --- a/src/py/flwr/client/grpc_adapter_client/connection.py +++ b/src/py/flwr/client/grpc_adapter_client/connection.py @@ -27,6 +27,7 @@ from flwr.common.logger import log from flwr.common.message import Message from flwr.common.retry_invoker import RetryInvoker +from flwr.common.typing import Run @contextmanager @@ -45,7 +46,7 @@ def grpc_adapter( # pylint: disable=R0913 Callable[[Message], None], Optional[Callable[[], None]], Optional[Callable[[], None]], - Optional[Callable[[int], Tuple[str, str]]], + Optional[Callable[[int], Run]], ] ]: """Primitives for request/response-based interaction with a server via GrpcAdapter. diff --git a/src/py/flwr/client/grpc_client/connection.py b/src/py/flwr/client/grpc_client/connection.py index 8c049861c672..3e9f261c1ecf 100644 --- a/src/py/flwr/client/grpc_client/connection.py +++ b/src/py/flwr/client/grpc_client/connection.py @@ -38,6 +38,7 @@ from flwr.common.grpc import create_channel from flwr.common.logger import log from flwr.common.retry_invoker import RetryInvoker +from flwr.common.typing import Run from flwr.proto.transport_pb2 import ( # pylint: disable=E0611 ClientMessage, Reason, @@ -73,7 +74,7 @@ def grpc_connection( # pylint: disable=R0913, R0915 Callable[[Message], None], Optional[Callable[[], None]], Optional[Callable[[], None]], - Optional[Callable[[int], Tuple[str, str]]], + Optional[Callable[[int], Run]], ] ]: """Establish a gRPC connection to a gRPC server. diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index 34dc0e417383..630d3090360d 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -41,6 +41,7 @@ from flwr.common.message import Message, Metadata from flwr.common.retry_invoker import RetryInvoker from flwr.common.serde import message_from_taskins, message_to_taskres +from flwr.common.typing import Run from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, DeleteNodeRequest, @@ -80,7 +81,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915 Callable[[Message], None], Optional[Callable[[], None]], Optional[Callable[[], None]], - Optional[Callable[[int], Tuple[str, str]]], + Optional[Callable[[int], Run]], ] ]: """Primitives for request/response-based interaction with a server. @@ -266,7 +267,7 @@ def send(message: Message) -> None: # Cleanup metadata = None - def get_run(run_id: int) -> Tuple[str, str]: + def get_run(run_id: int) -> Run: # Call FleetAPI get_run_request = GetRunRequest(run_id=run_id) get_run_response: GetRunResponse = retry_invoker.invoke( @@ -275,7 +276,11 @@ def get_run(run_id: int) -> Tuple[str, str]: ) # Return fab_id and fab_version - return get_run_response.run.fab_id, get_run_response.run.fab_version + return Run( + run_id, + get_run_response.run.fab_id, + get_run_response.run.fab_version, + ) try: # Yield methods diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index db5bd7eb6770..4b45d7fc24a5 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -41,6 +41,7 @@ from flwr.common.message import Message, Metadata from flwr.common.retry_invoker import RetryInvoker from flwr.common.serde import message_from_taskins, message_to_taskres +from flwr.common.typing import Run from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, CreateNodeResponse, @@ -91,7 +92,7 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915 Callable[[Message], None], Optional[Callable[[], None]], Optional[Callable[[], None]], - Optional[Callable[[int], Tuple[str, str]]], + Optional[Callable[[int], Run]], ] ]: """Primitives for request/response-based interaction with a server. @@ -344,16 +345,20 @@ def send(message: Message) -> None: res.results, # pylint: disable=no-member ) - def get_run(run_id: int) -> Tuple[str, str]: + def get_run(run_id: int) -> Run: # Construct the request req = GetRunRequest(run_id=run_id) # Send the request res = _request(req, GetRunResponse, PATH_GET_RUN) if res is None: - return "", "" + return Run(run_id, "", "") - return res.run.fab_id, res.run.fab_version + return Run( + run_id, + res.run.fab_id, + res.run.fab_version, + ) try: # Yield methods From 50cc246adf7404ef36d67c6d4bafefd10208f433 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Mon, 8 Jul 2024 12:26:11 +0100 Subject: [PATCH 114/595] refactor(framework:skip) Merge `GrpcDriver` and `GrpcDriverStub` (#3726) --- src/py/flwr/server/driver/grpc_driver.py | 215 +++++++----------- src/py/flwr/server/driver/grpc_driver_test.py | 51 +++-- src/py/flwr/server/run_serverapp.py | 32 ++- 3 files changed, 122 insertions(+), 176 deletions(-) diff --git a/src/py/flwr/server/driver/grpc_driver.py b/src/py/flwr/server/driver/grpc_driver.py index e614df659e3f..cdb9dd1ee87d 100644 --- a/src/py/flwr/server/driver/grpc_driver.py +++ b/src/py/flwr/server/driver/grpc_driver.py @@ -16,8 +16,8 @@ import time import warnings -from logging import DEBUG, ERROR, WARNING -from typing import Iterable, List, Optional, Tuple, cast +from logging import DEBUG, WARNING +from typing import Iterable, List, Optional, cast import grpc @@ -27,8 +27,6 @@ from flwr.common.serde import message_from_taskres, message_to_taskins from flwr.common.typing import Run from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 - CreateRunRequest, - CreateRunResponse, GetNodesRequest, GetNodesResponse, PullTaskResRequest, @@ -53,167 +51,102 @@ """ -class GrpcDriverStub: - """`GrpcDriverStub` provides access to the gRPC Driver API/service. +class GrpcDriver(Driver): + """`GrpcDriver` provides an interface to the Driver API. Parameters ---------- - driver_service_address : Optional[str] - The IPv4 or IPv6 address of the Driver API server. - Defaults to `"[::]:9091"`. + run_id : int + The identifier of the run. + driver_service_address : str (default: "[::]:9091") + The address (URL, IPv6, IPv4) of the SuperLink Driver API service. root_certificates : Optional[bytes] (default: None) The PEM-encoded root certificates as a byte string. If provided, a secure connection using the certificates will be established to an SSL-enabled Flower server. """ - def __init__( + def __init__( # pylint: disable=too-many-arguments self, + run_id: int, driver_service_address: str = DEFAULT_SERVER_ADDRESS_DRIVER, root_certificates: Optional[bytes] = None, ) -> None: - self.driver_service_address = driver_service_address - self.root_certificates = root_certificates - self.channel: Optional[grpc.Channel] = None - self.stub: Optional[DriverStub] = None + self._run_id = run_id + self._addr = driver_service_address + self._cert = root_certificates + self._run: Optional[Run] = None + self._grpc_stub: Optional[DriverStub] = None + self._channel: Optional[grpc.Channel] = None + self.node = Node(node_id=0, anonymous=True) + + @property + def _is_connected(self) -> bool: + """Check if connected to the Driver API server.""" + return self._channel is not None - def is_connected(self) -> bool: - """Return True if connected to the Driver API server, otherwise False.""" - return self.channel is not None + def _connect(self) -> None: + """Connect to the Driver API. - def connect(self) -> None: - """Connect to the Driver API.""" + This will not call GetRun. + """ event(EventType.DRIVER_CONNECT) - if self.channel is not None or self.stub is not None: + if self._is_connected: log(WARNING, "Already connected") return - self.channel = create_channel( - server_address=self.driver_service_address, - insecure=(self.root_certificates is None), - root_certificates=self.root_certificates, + self._channel = create_channel( + server_address=self._addr, + insecure=(self._cert is None), + root_certificates=self._cert, ) - self.stub = DriverStub(self.channel) - log(DEBUG, "[Driver] Connected to %s", self.driver_service_address) + self._grpc_stub = DriverStub(self._channel) + log(DEBUG, "[Driver] Connected to %s", self._addr) - def disconnect(self) -> None: + def _disconnect(self) -> None: """Disconnect from the Driver API.""" event(EventType.DRIVER_DISCONNECT) - if self.channel is None or self.stub is None: + if not self._is_connected: log(DEBUG, "Already disconnected") return - channel = self.channel - self.channel = None - self.stub = None + channel: grpc.Channel = self._channel + self._channel = None + self._grpc_stub = None channel.close() log(DEBUG, "[Driver] Disconnected") - def create_run(self, req: CreateRunRequest) -> CreateRunResponse: - """Request for run ID.""" - # Check if channel is open - if self.stub is None: - log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverStub` instance not connected") - - # Call Driver API - res: CreateRunResponse = self.stub.CreateRun(request=req) - return res - - def get_run(self, req: GetRunRequest) -> GetRunResponse: - """Get run information.""" - # Check if channel is open - if self.stub is None: - log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverStub` instance not connected") - - # Call gRPC Driver API - res: GetRunResponse = self.stub.GetRun(request=req) - return res - - def get_nodes(self, req: GetNodesRequest) -> GetNodesResponse: - """Get client IDs.""" - # Check if channel is open - if self.stub is None: - log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverStub` instance not connected") - - # Call gRPC Driver API - res: GetNodesResponse = self.stub.GetNodes(request=req) - return res - - def push_task_ins(self, req: PushTaskInsRequest) -> PushTaskInsResponse: - """Schedule tasks.""" - # Check if channel is open - if self.stub is None: - log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverStub` instance not connected") - - # Call gRPC Driver API - res: PushTaskInsResponse = self.stub.PushTaskIns(request=req) - return res - - def pull_task_res(self, req: PullTaskResRequest) -> PullTaskResResponse: - """Get task results.""" - # Check if channel is open - if self.stub is None: - log(ERROR, ERROR_MESSAGE_DRIVER_NOT_CONNECTED) - raise ConnectionError("`GrpcDriverStub` instance not connected") - - # Call Driver API - res: PullTaskResResponse = self.stub.PullTaskRes(request=req) - return res - - -class GrpcDriver(Driver): - """`Driver` class provides an interface to the Driver API. - - Parameters - ---------- - run_id : int - The identifier of the run. - stub : Optional[GrpcDriverStub] (default: None) - The ``GrpcDriverStub`` instance used to communicate with the SuperLink. - If None, an instance connected to "[::]:9091" will be created. - """ - - def __init__( # pylint: disable=too-many-arguments - self, - run_id: int, - stub: Optional[GrpcDriverStub] = None, - ) -> None: - self._run_id = run_id - self._run: Optional[Run] = None - self.stub = stub if stub is not None else GrpcDriverStub() - self.node = Node(node_id=0, anonymous=True) + def _init_run(self) -> None: + # Check if is initialized + if self._run is not None: + return + # Get the run info + req = GetRunRequest(run_id=self._run_id) + res: GetRunResponse = self._stub.GetRun(req) + if not res.HasField("run"): + raise RuntimeError(f"Cannot find the run with ID: {self._run_id}") + self._run = Run( + run_id=res.run.run_id, + fab_id=res.run.fab_id, + fab_version=res.run.fab_version, + ) @property def run(self) -> Run: """Run information.""" - self._get_stub_and_run_id() - return Run(**vars(cast(Run, self._run))) + self._init_run() + return Run(**vars(self._run)) - def _get_stub_and_run_id(self) -> Tuple[GrpcDriverStub, int]: - # Check if is initialized - if self._run is None: - # Connect - if not self.stub.is_connected(): - self.stub.connect() - # Get the run info - req = GetRunRequest(run_id=self._run_id) - res = self.stub.get_run(req) - if not res.HasField("run"): - raise RuntimeError(f"Cannot find the run with ID: {self._run_id}") - self._run = Run( - run_id=res.run.run_id, - fab_id=res.run.fab_id, - fab_version=res.run.fab_version, - ) - - return self.stub, self._run.run_id + @property + def _stub(self) -> DriverStub: + """Driver stub.""" + if not self._is_connected: + self._connect() + return cast(DriverStub, self._grpc_stub) def _check_message(self, message: Message) -> None: # Check if the message is valid if not ( - message.metadata.run_id == cast(Run, self._run).run_id + # Assume self._run being initialized + message.metadata.run_id == self._run_id and message.metadata.src_node_id == self.node.node_id and message.metadata.message_id == "" and message.metadata.reply_to_message == "" @@ -234,7 +167,7 @@ def create_message( # pylint: disable=too-many-arguments This method constructs a new `Message` with given content and metadata. The `run_id` and `src_node_id` will be set automatically. """ - _, run_id = self._get_stub_and_run_id() + self._init_run() if ttl: warnings.warn( "A custom TTL was set, but note that the SuperLink does not enforce " @@ -245,7 +178,7 @@ def create_message( # pylint: disable=too-many-arguments ttl_ = DEFAULT_TTL if ttl is None else ttl metadata = Metadata( - run_id=run_id, + run_id=self._run_id, message_id="", # Will be set by the server src_node_id=self.node.node_id, dst_node_id=dst_node_id, @@ -258,9 +191,11 @@ def create_message( # pylint: disable=too-many-arguments def get_node_ids(self) -> List[int]: """Get node IDs.""" - stub, run_id = self._get_stub_and_run_id() + self._init_run() # Call GrpcDriverStub method - res = stub.get_nodes(GetNodesRequest(run_id=run_id)) + res: GetNodesResponse = self._stub.GetNodes( + GetNodesRequest(run_id=self._run_id) + ) return [node.node_id for node in res.nodes] def push_messages(self, messages: Iterable[Message]) -> Iterable[str]: @@ -269,7 +204,7 @@ def push_messages(self, messages: Iterable[Message]) -> Iterable[str]: This method takes an iterable of messages and sends each message to the node specified in `dst_node_id`. """ - stub, _ = self._get_stub_and_run_id() + self._init_run() # Construct TaskIns task_ins_list: List[TaskIns] = [] for msg in messages: @@ -280,7 +215,9 @@ def push_messages(self, messages: Iterable[Message]) -> Iterable[str]: # Add to list task_ins_list.append(taskins) # Call GrpcDriverStub method - res = stub.push_task_ins(PushTaskInsRequest(task_ins_list=task_ins_list)) + res: PushTaskInsResponse = self._stub.PushTaskIns( + PushTaskInsRequest(task_ins_list=task_ins_list) + ) return list(res.task_ids) def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]: @@ -289,9 +226,9 @@ def pull_messages(self, message_ids: Iterable[str]) -> Iterable[Message]: This method is used to collect messages from the SuperLink that correspond to a set of given message IDs. """ - stub, _ = self._get_stub_and_run_id() + self._init_run() # Pull TaskRes - res = stub.pull_task_res( + res: PullTaskResResponse = self._stub.PullTaskRes( PullTaskResRequest(node=self.node, task_ids=message_ids) ) # Convert TaskRes to Message @@ -331,7 +268,7 @@ def send_and_receive( def close(self) -> None: """Disconnect from the SuperLink if connected.""" # Check if `connect` was called before - if not self.stub.is_connected(): + if not self._is_connected: return # Disconnect - self.stub.disconnect() + self._disconnect() diff --git a/src/py/flwr/server/driver/grpc_driver_test.py b/src/py/flwr/server/driver/grpc_driver_test.py index 72efc5f8b2c6..fdf3c676190d 100644 --- a/src/py/flwr/server/driver/grpc_driver_test.py +++ b/src/py/flwr/server/driver/grpc_driver_test.py @@ -41,10 +41,13 @@ def setUp(self) -> None: mock_response = Mock( run=Run(run_id=61016, fab_id="mock/mock", fab_version="v1.0.0") ) - self.mock_grpc_driver_stub = Mock() - self.mock_grpc_driver_stub.get_run.return_value = mock_response - self.mock_grpc_driver_stub.HasField.return_value = True - self.driver = GrpcDriver(run_id=61016, stub=self.mock_grpc_driver_stub) + self.mock_stub = Mock() + self.mock_channel = Mock() + self.mock_stub.GetRun.return_value = mock_response + mock_response.HasField.return_value = True + self.driver = GrpcDriver(run_id=61016) + self.driver._grpc_stub = self.mock_stub # pylint: disable=protected-access + self.driver._channel = self.mock_channel # pylint: disable=protected-access def test_init_grpc_driver(self) -> None: """Test GrpcDriverStub initialization.""" @@ -52,21 +55,21 @@ def test_init_grpc_driver(self) -> None: self.assertEqual(self.driver.run.run_id, 61016) self.assertEqual(self.driver.run.fab_id, "mock/mock") self.assertEqual(self.driver.run.fab_version, "v1.0.0") - self.mock_grpc_driver_stub.get_run.assert_called_once() + self.mock_stub.GetRun.assert_called_once() def test_get_nodes(self) -> None: """Test retrieval of nodes.""" # Prepare mock_response = Mock() mock_response.nodes = [Mock(node_id=404), Mock(node_id=200)] - self.mock_grpc_driver_stub.get_nodes.return_value = mock_response + self.mock_stub.GetNodes.return_value = mock_response # Execute node_ids = self.driver.get_node_ids() - args, kwargs = self.mock_grpc_driver_stub.get_nodes.call_args + args, kwargs = self.mock_stub.GetNodes.call_args # Assert - self.mock_grpc_driver_stub.get_run.assert_called_once() + self.mock_stub.GetRun.assert_called_once() self.assertEqual(len(args), 1) self.assertEqual(len(kwargs), 0) self.assertIsInstance(args[0], GetNodesRequest) @@ -77,7 +80,7 @@ def test_push_messages_valid(self) -> None: """Test pushing valid messages.""" # Prepare mock_response = Mock(task_ids=["id1", "id2"]) - self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response + self.mock_stub.PushTaskIns.return_value = mock_response msgs = [ self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL) for _ in range(2) @@ -85,10 +88,10 @@ def test_push_messages_valid(self) -> None: # Execute msg_ids = self.driver.push_messages(msgs) - args, kwargs = self.mock_grpc_driver_stub.push_task_ins.call_args + args, kwargs = self.mock_stub.PushTaskIns.call_args # Assert - self.mock_grpc_driver_stub.get_run.assert_called_once() + self.mock_stub.GetRun.assert_called_once() self.assertEqual(len(args), 1) self.assertEqual(len(kwargs), 0) self.assertIsInstance(args[0], PushTaskInsRequest) @@ -100,7 +103,7 @@ def test_push_messages_invalid(self) -> None: """Test pushing invalid messages.""" # Prepare mock_response = Mock(task_ids=["id1", "id2"]) - self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response + self.mock_stub.PushTaskIns.return_value = mock_response msgs = [ self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL) for _ in range(2) @@ -124,16 +127,16 @@ def test_pull_messages_with_given_message_ids(self) -> None: ), TaskRes(task=Task(ancestry=["id3"], error=error_to_proto(Error(code=0)))), ] - self.mock_grpc_driver_stub.pull_task_res.return_value = mock_response + self.mock_stub.PullTaskRes.return_value = mock_response msg_ids = ["id1", "id2", "id3"] # Execute msgs = self.driver.pull_messages(msg_ids) reply_tos = {msg.metadata.reply_to_message for msg in msgs} - args, kwargs = self.mock_grpc_driver_stub.pull_task_res.call_args + args, kwargs = self.mock_stub.PullTaskRes.call_args # Assert - self.mock_grpc_driver_stub.get_run.assert_called_once() + self.mock_stub.GetRun.assert_called_once() self.assertEqual(len(args), 1) self.assertEqual(len(kwargs), 0) self.assertIsInstance(args[0], PullTaskResRequest) @@ -144,14 +147,14 @@ def test_send_and_receive_messages_complete(self) -> None: """Test send and receive all messages successfully.""" # Prepare mock_response = Mock(task_ids=["id1"]) - self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response + self.mock_stub.PushTaskIns.return_value = mock_response # The response message must include either `content` (i.e. a recordset) or # an `Error`. We choose the latter in this case error_proto = error_to_proto(Error(code=0)) mock_response = Mock( task_res_list=[TaskRes(task=Task(ancestry=["id1"], error=error_proto))] ) - self.mock_grpc_driver_stub.pull_task_res.return_value = mock_response + self.mock_stub.PullTaskRes.return_value = mock_response msgs = [self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL)] # Execute @@ -166,9 +169,9 @@ def test_send_and_receive_messages_timeout(self) -> None: # Prepare sleep_fn = time.sleep mock_response = Mock(task_ids=["id1"]) - self.mock_grpc_driver_stub.push_task_ins.return_value = mock_response + self.mock_stub.PushTaskIns.return_value = mock_response mock_response = Mock(task_res_list=[]) - self.mock_grpc_driver_stub.pull_task_res.return_value = mock_response + self.mock_stub.PullTaskRes.return_value = mock_response msgs = [self.driver.create_message(RecordSet(), "", 0, "", DEFAULT_TTL)] # Execute @@ -182,22 +185,20 @@ def test_send_and_receive_messages_timeout(self) -> None: def test_del_with_initialized_driver(self) -> None: """Test cleanup behavior when Driver is initialized.""" - # Prepare - self.mock_grpc_driver_stub.is_connected.return_value = True - # Execute self.driver.close() # Assert - self.mock_grpc_driver_stub.disconnect.assert_called_once() + self.mock_channel.close.assert_called_once() def test_del_with_uninitialized_driver(self) -> None: """Test cleanup behavior when Driver is not initialized.""" # Prepare - self.mock_grpc_driver_stub.is_connected.return_value = False + self.driver._grpc_stub = None # pylint: disable=protected-access + self.driver._channel = None # pylint: disable=protected-access # Execute self.driver.close() # Assert - self.mock_grpc_driver_stub.disconnect.assert_not_called() + self.mock_channel.close.assert_not_called() diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 3505ebfdb0a9..70ce2da347f0 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -25,10 +25,13 @@ from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir from flwr.common.logger import log, update_console_handler, warn_deprecated_feature from flwr.common.object_ref import load_app -from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611 +from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 + CreateRunRequest, + CreateRunResponse, +) from .driver import Driver -from .driver.grpc_driver import GrpcDriver, GrpcDriverStub +from .driver.grpc_driver import GrpcDriver from .server_app import LoadServerAppError, ServerApp ADDRESS_DRIVER_API = "0.0.0.0:9091" @@ -144,22 +147,27 @@ def run_server_app() -> None: # pylint: disable=too-many-branches "For more details, use: ``flower-server-app -h``" ) - stub = GrpcDriverStub( - driver_service_address=args.superlink, root_certificates=root_certificates - ) + # Initialize GrpcDriver if args.run_id is not None: # User provided `--run-id`, but not `server-app` - run_id = args.run_id + driver = GrpcDriver( + run_id=args.run_id, + driver_service_address=args.superlink, + root_certificates=root_certificates, + ) else: # User provided `server-app`, but not `--run-id` # Create run if run_id is not provided - stub.connect() + driver = GrpcDriver( + run_id=0, # Will be overwritten + driver_service_address=args.superlink, + root_certificates=root_certificates, + ) + # Create run req = CreateRunRequest(fab_id=args.fab_id, fab_version=args.fab_version) - res = stub.create_run(req) - run_id = res.run_id - - # Initialize GrpcDriver - driver = GrpcDriver(run_id=run_id, stub=stub) + res: CreateRunResponse = driver._stub.CreateRun(req) # pylint: disable=W0212 + # Overwrite driver._run_id + driver._run_id = res.run_id # pylint: disable=W0212 # Dynamically obtain ServerApp path based on run_id if args.run_id is not None: From f0a0db78dc51e57f7227d2db293c568ce7a2417a Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 8 Jul 2024 13:34:04 +0200 Subject: [PATCH 115/595] fix(datasets) Fix GitHub code reference in Google Colab button for FDS (#3740) --- datasets/doc/source/conf.py | 2 +- datasets/flwr_datasets/mock_utils_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datasets/doc/source/conf.py b/datasets/doc/source/conf.py index 11285c375f96..c287ec318b5d 100644 --- a/datasets/doc/source/conf.py +++ b/datasets/doc/source/conf.py @@ -162,7 +162,7 @@ def find_test_modules(package_path): .. raw:: html
    - + Open in Colab """ diff --git a/datasets/flwr_datasets/mock_utils_test.py b/datasets/flwr_datasets/mock_utils_test.py index 78aff1f1cdd7..bd49de8033de 100644 --- a/datasets/flwr_datasets/mock_utils_test.py +++ b/datasets/flwr_datasets/mock_utils_test.py @@ -190,7 +190,7 @@ def _generate_random_image_column( pil_imgs = [] for np_image in np_images: # Convert the NumPy array to a PIL image - pil_img_beg = Image.fromarray(np_image) # type: ignore + pil_img_beg = Image.fromarray(np_image) # Save the image to an in-memory bytes buffer in_memory_file = io.BytesIO() From 7a5ef857da838a86703d842362249b00664a258b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:38:07 +0200 Subject: [PATCH 116/595] chore(deps): bump actions/download-artifact from 4.1.7 to 4.1.8 (#3733) Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.7 to 4.1.8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/65a9edc5881444af0b9093a5e628f2fe47ea3b2e...fa0a91b85d4f404e444e00e005971372dc801d16) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_docker-build.yml b/.github/workflows/_docker-build.yml index b8ddd355eb8e..4aa101890797 100644 --- a/.github/workflows/_docker-build.yml +++ b/.github/workflows/_docker-build.yml @@ -138,7 +138,7 @@ jobs: metadata: ${{ steps.meta.outputs.json }} steps: - name: Download digests - uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8 with: pattern: digests-${{ needs.build.outputs.build-id }}-* path: /tmp/digests From cface27baf2ca650e5222cf85189b1a308678e1f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:44:47 +0000 Subject: [PATCH 117/595] chore(deps): bump actions/upload-artifact from 4.3.3 to 4.3.4 (#3736) Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.3 to 4.3.4. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/65462800fd760344b1a7b4382951275a0abb4808...0b2256b8c012f0828dc542b3febcab082c67f72b) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_docker-build.yml b/.github/workflows/_docker-build.yml index 4aa101890797..4e87f1ca5a74 100644 --- a/.github/workflows/_docker-build.yml +++ b/.github/workflows/_docker-build.yml @@ -122,7 +122,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4 with: name: digests-${{ steps.build-id.outputs.id }}-${{ matrix.platform.name }} path: /tmp/digests/* From 2888cd49659d67f95b45d87da9f1da3e056e8701 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:51:11 +0000 Subject: [PATCH 118/595] chore(deps): bump docker/setup-qemu-action from 3.0.0 to 3.1.0 (#3734) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 3.0.0 to 3.1.0. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/68827325e0b33c7199eb31dd4e31fbe9023e06e3...5927c834f5b4fdf503fca6f4c7eccda82949e1ee) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_docker-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_docker-build.yml b/.github/workflows/_docker-build.yml index 4e87f1ca5a74..972c5230b902 100644 --- a/.github/workflows/_docker-build.yml +++ b/.github/workflows/_docker-build.yml @@ -81,7 +81,7 @@ jobs: - name: Set up QEMU if: matrix.platform.qemu != '' - uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 + uses: docker/setup-qemu-action@5927c834f5b4fdf503fca6f4c7eccda82949e1ee # v3.1.0 with: platforms: ${{ matrix.platform.qemu }} From 2f46b81ada4ab584f254541d96f3e1d59dcec096 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 12:57:45 +0000 Subject: [PATCH 119/595] chore(deps): bump docker/setup-buildx-action from 3.3.0 to 3.4.0 (#3735) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.3.0 to 3.4.0. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/d70bba72b1f3fd22344832f00baa16ece964efeb...4fd812986e6c8c2a69e18311145f9371337f27d4) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_docker-build.yml b/.github/workflows/_docker-build.yml index 972c5230b902..7d78ea881034 100644 --- a/.github/workflows/_docker-build.yml +++ b/.github/workflows/_docker-build.yml @@ -92,7 +92,7 @@ jobs: images: ${{ inputs.namespace-repository }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 - name: Login to Docker Hub uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 @@ -152,7 +152,7 @@ jobs: tags: ${{ inputs.tags }} - name: Set up Docker Buildx - uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + uses: docker/setup-buildx-action@4fd812986e6c8c2a69e18311145f9371337f27d4 # v3.4.0 - name: Login to Docker Hub uses: docker/login-action@0d4c9c5ea7693da7b068278f7b52bda2a190a446 # v3.2.0 From 4ce439ec182ad9424f5186351e31d1bd3476b853 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 15:37:29 +0200 Subject: [PATCH 120/595] ci(framework:skip) Make isort and black compatible (#3741) --- pyproject.toml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5daf007471aa..e8bb780351bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,12 +131,7 @@ licensecheck = "==2024" pre-commit = "==3.5.0" [tool.isort] -line_length = 88 -indent = " " -multi_line_output = 3 -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true +profile = "black" known_first_party = ["flwr", "flwr_tool"] [tool.black] From b735710cccbe300805bf567a1ed87772171f81b3 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 16:02:55 +0200 Subject: [PATCH 121/595] feat(framework) Add override config to SuperExec (#3731) --- src/py/flwr/superexec/deployment.py | 21 ++++++++++++++++----- src/py/flwr/superexec/exec_servicer.py | 5 ++++- src/py/flwr/superexec/exec_servicer_test.py | 2 +- src/py/flwr/superexec/executor.py | 5 ++--- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index 6f931e81eefa..d117f280b38d 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -17,7 +17,7 @@ import subprocess import sys from logging import ERROR, INFO -from typing import Optional +from typing import Dict, Optional from typing_extensions import override @@ -53,18 +53,29 @@ def _connect(self) -> None: ) self.stub = DriverStub(channel) - def _create_run(self, fab_id: str, fab_version: str) -> int: + def _create_run( + self, + fab_id: str, + fab_version: str, + override_config: Dict[str, str], + ) -> int: if self.stub is None: self._connect() assert self.stub is not None - req = CreateRunRequest(fab_id=fab_id, fab_version=fab_version) + req = CreateRunRequest( + fab_id=fab_id, + fab_version=fab_version, + override_config=override_config, + ) res = self.stub.CreateRun(request=req) return int(res.run_id) @override - def start_run(self, fab_file: bytes) -> Optional[RunTracker]: + def start_run( + self, fab_file: bytes, override_config: Dict[str, str] + ) -> Optional[RunTracker]: """Start run using the Flower Deployment Engine.""" try: # Install FAB to flwr dir @@ -79,7 +90,7 @@ def start_run(self, fab_file: bytes) -> Optional[RunTracker]: ) # Call SuperLink to create run - run_id: int = self._create_run(fab_id, fab_version) + run_id: int = self._create_run(fab_id, fab_version, override_config) log(INFO, "Created run %s", str(run_id)) # Start ServerApp diff --git a/src/py/flwr/superexec/exec_servicer.py b/src/py/flwr/superexec/exec_servicer.py index e5ef2bd59a79..61a7bc289af3 100644 --- a/src/py/flwr/superexec/exec_servicer.py +++ b/src/py/flwr/superexec/exec_servicer.py @@ -45,7 +45,10 @@ def StartRun( """Create run ID.""" log(INFO, "ExecServicer.StartRun") - run = self.executor.start_run(request.fab_file) + run = self.executor.start_run( + request.fab_file, + dict(request.override_config.items()), + ) if run is None: log(ERROR, "Executor failed to start run") diff --git a/src/py/flwr/superexec/exec_servicer_test.py b/src/py/flwr/superexec/exec_servicer_test.py index 41f67b74c48b..edc91df4530e 100644 --- a/src/py/flwr/superexec/exec_servicer_test.py +++ b/src/py/flwr/superexec/exec_servicer_test.py @@ -36,7 +36,7 @@ def test_start_run() -> None: run_res.proc = proc executor = MagicMock() - executor.start_run = lambda _: run_res + executor.start_run = lambda _, __: run_res context_mock = MagicMock() diff --git a/src/py/flwr/superexec/executor.py b/src/py/flwr/superexec/executor.py index f85ac4c157fc..85b6e5c3e095 100644 --- a/src/py/flwr/superexec/executor.py +++ b/src/py/flwr/superexec/executor.py @@ -17,7 +17,7 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from subprocess import Popen -from typing import Optional +from typing import Dict, Optional @dataclass @@ -33,8 +33,7 @@ class Executor(ABC): @abstractmethod def start_run( - self, - fab_file: bytes, + self, fab_file: bytes, override_config: Dict[str, str] ) -> Optional[RunTracker]: """Start a run using the given Flower FAB ID and version. From 7c2c6c3493907e518bb684ef8726715952045e8a Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 16:21:52 +0200 Subject: [PATCH 122/595] feat(framework) Add override config to `Run` (#3730) --- src/py/flwr/client/app.py | 2 +- .../client/grpc_rere_client/connection.py | 1 + src/py/flwr/client/rest_client/connection.py | 3 +- src/py/flwr/common/typing.py | 1 + src/py/flwr/server/driver/grpc_driver.py | 1 + .../server/driver/inmemory_driver_test.py | 10 ++-- .../superlink/driver/driver_servicer.py | 6 ++- .../grpc_rere/server_interceptor_test.py | 4 +- .../superlink/fleet/vce/vce_api_test.py | 4 +- .../server/superlink/state/in_memory_state.py | 12 ++++- .../server/superlink/state/sqlite_state.py | 29 +++++++++--- src/py/flwr/server/superlink/state/state.py | 9 +++- .../flwr/server/superlink/state/state_test.py | 47 ++++++++++--------- src/py/flwr/simulation/run_simulation.py | 2 +- 14 files changed, 87 insertions(+), 44 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 3396b5c580cb..15d384cb74a2 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -371,7 +371,7 @@ def _on_backoff(retry_state: RetryState) -> None: run_info[run_id] = get_run(run_id) # If get_run is None, i.e., in grpc-bidi mode else: - run_info[run_id] = Run(run_id, "", "") + run_info[run_id] = Run(run_id, "", "", {}) # Register context for this run node_state.register_context(run_id=run_id) diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index 630d3090360d..8062ce28fcc7 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -280,6 +280,7 @@ def get_run(run_id: int) -> Run: run_id, get_run_response.run.fab_id, get_run_response.run.fab_version, + dict(get_run_response.run.override_config.items()), ) try: diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index 4b45d7fc24a5..0efa5731ae51 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -352,12 +352,13 @@ def get_run(run_id: int) -> Run: # Send the request res = _request(req, GetRunResponse, PATH_GET_RUN) if res is None: - return Run(run_id, "", "") + return Run(run_id, "", "", {}) return Run( run_id, res.run.fab_id, res.run.fab_version, + dict(res.run.override_config.items()), ) try: diff --git a/src/py/flwr/common/typing.py b/src/py/flwr/common/typing.py index f51830955679..04d2cf5bbf7f 100644 --- a/src/py/flwr/common/typing.py +++ b/src/py/flwr/common/typing.py @@ -194,3 +194,4 @@ class Run: run_id: int fab_id: str fab_version: str + override_config: Dict[str, str] diff --git a/src/py/flwr/server/driver/grpc_driver.py b/src/py/flwr/server/driver/grpc_driver.py index cdb9dd1ee87d..84da5882eb73 100644 --- a/src/py/flwr/server/driver/grpc_driver.py +++ b/src/py/flwr/server/driver/grpc_driver.py @@ -127,6 +127,7 @@ def _init_run(self) -> None: run_id=res.run.run_id, fab_id=res.run.fab_id, fab_version=res.run.fab_version, + override_config=dict(res.run.override_config.items()), ) @property diff --git a/src/py/flwr/server/driver/inmemory_driver_test.py b/src/py/flwr/server/driver/inmemory_driver_test.py index 0cc1c5a53e13..d0f32e830f7d 100644 --- a/src/py/flwr/server/driver/inmemory_driver_test.py +++ b/src/py/flwr/server/driver/inmemory_driver_test.py @@ -86,7 +86,10 @@ def setUp(self) -> None: for _ in range(self.num_nodes) ] self.state.get_run.return_value = Run( - run_id=61016, fab_id="mock/mock", fab_version="v1.0.0" + run_id=61016, + fab_id="mock/mock", + fab_version="v1.0.0", + override_config={"test_key": "test_value"}, ) state_factory = MagicMock(state=lambda: self.state) self.driver = InMemoryDriver(run_id=61016, state_factory=state_factory) @@ -98,6 +101,7 @@ def test_get_run(self) -> None: self.assertEqual(self.driver.run.run_id, 61016) self.assertEqual(self.driver.run.fab_id, "mock/mock") self.assertEqual(self.driver.run.fab_version, "v1.0.0") + self.assertEqual(self.driver.run.override_config["test_key"], "test_value") def test_get_nodes(self) -> None: """Test retrieval of nodes.""" @@ -223,7 +227,7 @@ def test_task_store_consistency_after_push_pull_sqlitestate(self) -> None: # Prepare state = StateFactory("").state() self.driver = InMemoryDriver( - state.create_run("", ""), MagicMock(state=lambda: state) + state.create_run("", "", {}), MagicMock(state=lambda: state) ) msg_ids, node_id = push_messages(self.driver, self.num_nodes) assert isinstance(state, SqliteState) @@ -249,7 +253,7 @@ def test_task_store_consistency_after_push_pull_inmemory_state(self) -> None: # Prepare state_factory = StateFactory(":flwr-in-memory-state:") state = state_factory.state() - self.driver = InMemoryDriver(state.create_run("", ""), state_factory) + self.driver = InMemoryDriver(state.create_run("", "", {}), state_factory) msg_ids, node_id = push_messages(self.driver, self.num_nodes) assert isinstance(state, InMemoryState) diff --git a/src/py/flwr/server/superlink/driver/driver_servicer.py b/src/py/flwr/server/superlink/driver/driver_servicer.py index 03128f02158e..7f8ded3bdb85 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer.py @@ -69,7 +69,11 @@ def CreateRun( """Create run ID.""" log(DEBUG, "DriverServicer.CreateRun") state: State = self.state_factory.state() - run_id = state.create_run(request.fab_id, request.fab_version) + run_id = state.create_run( + request.fab_id, + request.fab_version, + dict(request.override_config.items()), + ) return CreateRunResponse(run_id=run_id) def PushTaskIns( diff --git a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py index 01499102b7d8..798e71435585 100644 --- a/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py +++ b/src/py/flwr/server/superlink/fleet/grpc_rere/server_interceptor_test.py @@ -328,7 +328,7 @@ def test_successful_get_run_with_metadata(self) -> None: self.state.create_node( ping_interval=30, public_key=public_key_to_bytes(self._client_public_key) ) - run_id = self.state.create_run("", "") + run_id = self.state.create_run("", "", {}) request = GetRunRequest(run_id=run_id) shared_secret = generate_shared_key( self._client_private_key, self._server_public_key @@ -359,7 +359,7 @@ def test_unsuccessful_get_run_with_metadata(self) -> None: self.state.create_node( ping_interval=30, public_key=public_key_to_bytes(self._client_public_key) ) - run_id = self.state.create_run("", "") + run_id = self.state.create_run("", "", {}) request = GetRunRequest(run_id=run_id) client_private_key, _ = generate_key_pairs() shared_secret = generate_shared_key(client_private_key, self._server_public_key) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py index df9f2cc96f95..c0bf506fd2b6 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py @@ -82,7 +82,9 @@ def register_messages_into_state( ) -> Dict[UUID, float]: """Register `num_messages` into the state factory.""" state: InMemoryState = state_factory.state() # type: ignore - state.run_ids[run_id] = Run(run_id=run_id, fab_id="Mock/mock", fab_version="v1.0.0") + state.run_ids[run_id] = Run( + run_id=run_id, fab_id="Mock/mock", fab_version="v1.0.0", override_config={} + ) # Artificially add TaskIns to state so they can be processed # by the Simulation Engine logic nodes_cycle = cycle(nodes_mapping.keys()) # we have more messages than supernodes diff --git a/src/py/flwr/server/superlink/state/in_memory_state.py b/src/py/flwr/server/superlink/state/in_memory_state.py index 5a4e4eb0fd9a..bc4bd4478a23 100644 --- a/src/py/flwr/server/superlink/state/in_memory_state.py +++ b/src/py/flwr/server/superlink/state/in_memory_state.py @@ -275,7 +275,12 @@ def get_node_id(self, client_public_key: bytes) -> Optional[int]: """Retrieve stored `node_id` filtered by `client_public_keys`.""" return self.public_key_to_node_id.get(client_public_key) - def create_run(self, fab_id: str, fab_version: str) -> int: + def create_run( + self, + fab_id: str, + fab_version: str, + override_config: Dict[str, str], + ) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" # Sample a random int64 as run_id with self.lock: @@ -283,7 +288,10 @@ def create_run(self, fab_id: str, fab_version: str) -> int: if run_id not in self.run_ids: self.run_ids[run_id] = Run( - run_id=run_id, fab_id=fab_id, fab_version=fab_version + run_id=run_id, + fab_id=fab_id, + fab_version=fab_version, + override_config=override_config, ) return run_id log(ERROR, "Unexpected run creation failure.") diff --git a/src/py/flwr/server/superlink/state/sqlite_state.py b/src/py/flwr/server/superlink/state/sqlite_state.py index 725f7c2dff4b..ea6f349b9f9a 100644 --- a/src/py/flwr/server/superlink/state/sqlite_state.py +++ b/src/py/flwr/server/superlink/state/sqlite_state.py @@ -15,6 +15,7 @@ """SQLite based implemenation of server state.""" +import json import re import sqlite3 import time @@ -61,9 +62,10 @@ SQL_CREATE_TABLE_RUN = """ CREATE TABLE IF NOT EXISTS run( - run_id INTEGER UNIQUE, - fab_id TEXT, - fab_version TEXT + run_id INTEGER UNIQUE, + fab_id TEXT, + fab_version TEXT, + override_config TEXT ); """ @@ -613,7 +615,12 @@ def get_node_id(self, client_public_key: bytes) -> Optional[int]: return node_id return None - def create_run(self, fab_id: str, fab_version: str) -> int: + def create_run( + self, + fab_id: str, + fab_version: str, + override_config: Dict[str, str], + ) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" # Sample a random int64 as run_id run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) @@ -622,8 +629,13 @@ def create_run(self, fab_id: str, fab_version: str) -> int: query = "SELECT COUNT(*) FROM run WHERE run_id = ?;" # If run_id does not exist if self.query(query, (run_id,))[0]["COUNT(*)"] == 0: - query = "INSERT INTO run (run_id, fab_id, fab_version) VALUES (?, ?, ?);" - self.query(query, (run_id, fab_id, fab_version)) + query = ( + "INSERT INTO run (run_id, fab_id, fab_version, override_config)" + "VALUES (?, ?, ?, ?);" + ) + self.query( + query, (run_id, fab_id, fab_version, json.dumps(override_config)) + ) return run_id log(ERROR, "Unexpected run creation failure.") return 0 @@ -687,7 +699,10 @@ def get_run(self, run_id: int) -> Optional[Run]: try: row = self.query(query, (run_id,))[0] return Run( - run_id=run_id, fab_id=row["fab_id"], fab_version=row["fab_version"] + run_id=run_id, + fab_id=row["fab_id"], + fab_version=row["fab_version"], + override_config=json.loads(row["override_config"]), ) except sqlite3.IntegrityError: log(ERROR, "`run_id` does not exist.") diff --git a/src/py/flwr/server/superlink/state/state.py b/src/py/flwr/server/superlink/state/state.py index 65e2c63cab69..c93f6ba756b8 100644 --- a/src/py/flwr/server/superlink/state/state.py +++ b/src/py/flwr/server/superlink/state/state.py @@ -16,7 +16,7 @@ import abc -from typing import List, Optional, Set +from typing import Dict, List, Optional, Set from uuid import UUID from flwr.common.typing import Run @@ -157,7 +157,12 @@ def get_node_id(self, client_public_key: bytes) -> Optional[int]: """Retrieve stored `node_id` filtered by `client_public_keys`.""" @abc.abstractmethod - def create_run(self, fab_id: str, fab_version: str) -> int: + def create_run( + self, + fab_id: str, + fab_version: str, + override_config: Dict[str, str], + ) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" @abc.abstractmethod diff --git a/src/py/flwr/server/superlink/state/state_test.py b/src/py/flwr/server/superlink/state/state_test.py index 373202d5cde6..5f0d23ffc4d8 100644 --- a/src/py/flwr/server/superlink/state/state_test.py +++ b/src/py/flwr/server/superlink/state/state_test.py @@ -52,7 +52,7 @@ def test_create_and_get_run(self) -> None: """Test if create_run and get_run work correctly.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("Mock/mock", "v1.0.0") + run_id = state.create_run("Mock/mock", "v1.0.0", {"test_key": "test_value"}) # Execute run = state.get_run(run_id) @@ -62,6 +62,7 @@ def test_create_and_get_run(self) -> None: assert run.run_id == run_id assert run.fab_id == "Mock/mock" assert run.fab_version == "v1.0.0" + assert run.override_config["test_key"] == "test_value" def test_get_task_ins_empty(self) -> None: """Validate that a new state has no TaskIns.""" @@ -90,7 +91,7 @@ def test_store_task_ins_one(self) -> None: # Prepare consumer_node_id = 1 state = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins = create_task_ins( consumer_node_id=consumer_node_id, anonymous=False, run_id=run_id ) @@ -125,7 +126,7 @@ def test_store_and_delete_tasks(self) -> None: # Prepare consumer_node_id = 1 state = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins_0 = create_task_ins( consumer_node_id=consumer_node_id, anonymous=False, run_id=run_id ) @@ -199,7 +200,7 @@ def test_task_ins_store_anonymous_and_retrieve_anonymous(self) -> None: """ # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins = create_task_ins(consumer_node_id=0, anonymous=True, run_id=run_id) # Execute @@ -214,7 +215,7 @@ def test_task_ins_store_anonymous_and_fail_retrieving_identitiy(self) -> None: """Store anonymous TaskIns and fail to retrieve it.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins = create_task_ins(consumer_node_id=0, anonymous=True, run_id=run_id) # Execute @@ -228,7 +229,7 @@ def test_task_ins_store_identity_and_fail_retrieving_anonymous(self) -> None: """Store identity TaskIns and fail retrieving it as anonymous.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins = create_task_ins(consumer_node_id=1, anonymous=False, run_id=run_id) # Execute @@ -242,7 +243,7 @@ def test_task_ins_store_identity_and_retrieve_identity(self) -> None: """Store identity TaskIns and retrieve it.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins = create_task_ins(consumer_node_id=1, anonymous=False, run_id=run_id) # Execute @@ -259,7 +260,7 @@ def test_task_ins_store_delivered_and_fail_retrieving(self) -> None: """Fail retrieving delivered task.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins = create_task_ins(consumer_node_id=1, anonymous=False, run_id=run_id) # Execute @@ -302,7 +303,7 @@ def test_task_res_store_and_retrieve_by_task_ins_id(self) -> None: """Store TaskRes retrieve it by task_ins_id.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_ins_id = uuid4() task_res = create_task_res( producer_node_id=0, @@ -323,7 +324,7 @@ def test_node_ids_initial_state(self) -> None: """Test retrieving all node_ids and empty initial state.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) # Execute retrieved_node_ids = state.get_nodes(run_id) @@ -335,7 +336,7 @@ def test_create_node_and_get_nodes(self) -> None: """Test creating a client node.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_ids = [] # Execute @@ -352,7 +353,7 @@ def test_create_node_public_key(self) -> None: # Prepare state: State = self.state_factory() public_key = b"mock" - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) # Execute node_id = state.create_node(ping_interval=10, public_key=public_key) @@ -368,7 +369,7 @@ def test_create_node_public_key_twice(self) -> None: # Prepare state: State = self.state_factory() public_key = b"mock" - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_id = state.create_node(ping_interval=10, public_key=public_key) # Execute @@ -390,7 +391,7 @@ def test_delete_node(self) -> None: """Test deleting a client node.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_id = state.create_node(ping_interval=10) # Execute @@ -405,7 +406,7 @@ def test_delete_node_public_key(self) -> None: # Prepare state: State = self.state_factory() public_key = b"mock" - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_id = state.create_node(ping_interval=10, public_key=public_key) # Execute @@ -422,7 +423,7 @@ def test_delete_node_public_key_none(self) -> None: # Prepare state: State = self.state_factory() public_key = b"mock" - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_id = 0 # Execute & Assert @@ -441,7 +442,7 @@ def test_delete_node_wrong_public_key(self) -> None: state: State = self.state_factory() public_key = b"mock" wrong_public_key = b"mock_mock" - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_id = state.create_node(ping_interval=10, public_key=public_key) # Execute & Assert @@ -460,7 +461,7 @@ def test_get_node_id_wrong_public_key(self) -> None: state: State = self.state_factory() public_key = b"mock" wrong_public_key = b"mock_mock" - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) # Execute state.create_node(ping_interval=10, public_key=public_key) @@ -475,7 +476,7 @@ def test_get_nodes_invalid_run_id(self) -> None: """Test retrieving all node_ids with invalid run_id.""" # Prepare state: State = self.state_factory() - state.create_run("mock/mock", "v1.0.0") + state.create_run("mock/mock", "v1.0.0", {}) invalid_run_id = 61016 state.create_node(ping_interval=10) @@ -489,7 +490,7 @@ def test_num_task_ins(self) -> None: """Test if num_tasks returns correct number of not delivered task_ins.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_0 = create_task_ins(consumer_node_id=0, anonymous=True, run_id=run_id) task_1 = create_task_ins(consumer_node_id=0, anonymous=True, run_id=run_id) @@ -507,7 +508,7 @@ def test_num_task_res(self) -> None: """Test if num_tasks returns correct number of not delivered task_res.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) task_0 = create_task_res( producer_node_id=0, anonymous=True, ancestry=["1"], run_id=run_id ) @@ -608,7 +609,7 @@ def test_acknowledge_ping(self) -> None: """Test if acknowledge_ping works and if get_nodes return online nodes.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_ids = [state.create_node(ping_interval=10) for _ in range(100)] for node_id in node_ids[:70]: state.acknowledge_ping(node_id, ping_interval=30) @@ -627,7 +628,7 @@ def test_node_unavailable_error(self) -> None: """Test if get_task_res return TaskRes containing node unavailable error.""" # Prepare state: State = self.state_factory() - run_id = state.create_run("mock/mock", "v1.0.0") + run_id = state.create_run("mock/mock", "v1.0.0", {}) node_id_0 = state.create_node(ping_interval=90) node_id_1 = state.create_node(ping_interval=30) # Create and store TaskIns diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 7c7a412a245b..91805dc5ed7b 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -209,7 +209,7 @@ def _main_loop( serverapp_th = None try: # Create run (with empty fab_id and fab_version) - run_id_ = state_factory.state().create_run("", "") + run_id_ = state_factory.state().create_run("", "", {}) if run_id: _override_run_id(state_factory, run_id_to_replace=run_id_, run_id=run_id) From 26d2dd9d603943db1ef037c9916aae02a5dd61c8 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 16:52:11 +0200 Subject: [PATCH 123/595] feat(framework) Add utility functions for config parsing and handling (#3732) --- src/py/flwr/common/config.py | 47 ++++++++- src/py/flwr/common/config_test.py | 164 ++++++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 src/py/flwr/common/config_test.py diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 20de00a6fba9..e615e497b808 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -16,7 +16,7 @@ import os from pathlib import Path -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, List, Optional, Tuple, Union import tomli @@ -30,7 +30,7 @@ def get_flwr_dir(provided_path: Optional[str] = None) -> Path: return Path( os.getenv( FLWR_HOME, - f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}/.flwr", + Path(f"{os.getenv('XDG_DATA_HOME', os.getenv('HOME'))}") / ".flwr", ) ) return Path(provided_path).absolute() @@ -71,3 +71,46 @@ def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]: ) return config + + +def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, str]: + """Flatten dict by joining nested keys with a given separator.""" + items: List[Tuple[str, str]] = [] + separator: str = "." + for k, v in raw_dict.items(): + new_key = f"{parent_key}{separator}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(flatten_dict(v, parent_key=new_key).items()) + elif isinstance(v, str): + items.append((new_key, v)) + else: + raise ValueError( + f"The value for key {k} needs to be a `str` or a `dict`.", + ) + return dict(items) + + +def parse_config_args( + config_overrides: Optional[str], + separator: str = ",", +) -> Dict[str, str]: + """Parse separator separated list of key-value pairs separated by '='.""" + overrides: Dict[str, str] = {} + + if config_overrides is None: + return overrides + + overrides_list = config_overrides.split(separator) + if ( + len(overrides_list) == 1 + and "=" not in overrides_list + and overrides_list[0].endswith(".toml") + ): + with Path(overrides_list[0]).open("rb") as config_file: + overrides = flatten_dict(tomli.load(config_file)) + else: + for kv_pair in overrides_list: + key, value = kv_pair.split("=") + overrides[key] = value + + return overrides diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py new file mode 100644 index 000000000000..d30f0d5cc755 --- /dev/null +++ b/src/py/flwr/common/config_test.py @@ -0,0 +1,164 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Test util functions handling Flower config.""" + +import os +import textwrap +from pathlib import Path +from unittest.mock import patch + +import pytest + +from .config import ( + flatten_dict, + get_flwr_dir, + get_project_config, + get_project_dir, + parse_config_args, +) + +# Mock constants +FAB_CONFIG_FILE = "pyproject.toml" + + +def test_get_flwr_dir_with_provided_path() -> None: + """Test get_flwr_dir with a provided valid path.""" + provided_path = "." + assert get_flwr_dir(provided_path) == Path(provided_path).absolute() + + +def test_get_flwr_dir_without_provided_path() -> None: + """Test get_flwr_dir without a provided path, using default home directory.""" + with patch.dict(os.environ, {"HOME": "/home/user"}): + assert get_flwr_dir() == Path("/home/user/.flwr") + + +def test_get_flwr_dir_with_flwr_home() -> None: + """Test get_flwr_dir with FLWR_HOME environment variable set.""" + with patch.dict(os.environ, {"FLWR_HOME": "/custom/flwr/home"}): + assert get_flwr_dir() == Path("/custom/flwr/home") + + +def test_get_flwr_dir_with_xdg_data_home() -> None: + """Test get_flwr_dir with FLWR_HOME environment variable set.""" + with patch.dict(os.environ, {"XDG_DATA_HOME": "/custom/data/home"}): + assert get_flwr_dir() == Path("/custom/data/home/.flwr") + + +def test_get_project_dir_invalid_fab_id() -> None: + """Test get_project_dir with an invalid fab_id.""" + with pytest.raises(ValueError): + get_project_dir("invalid_fab_id", "1.0.0") + + +def test_get_project_dir_valid() -> None: + """Test get_project_dir with an valid fab_id and version.""" + app_path = get_project_dir("app_name/user", "1.0.0", flwr_dir=".") + assert app_path == Path("apps") / "app_name" / "user" / "1.0.0" + + +def test_get_project_config_file_not_found() -> None: + """Test get_project_config when the configuration file is not found.""" + with pytest.raises(FileNotFoundError): + get_project_config("/invalid/dir") + + +def test_get_project_config_file_valid(tmp_path: Path) -> None: + """Test get_project_config when the configuration file is not found.""" + pyproject_toml_content = """ + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [project] + name = "fedgpt" + version = "1.0.0" + description = "" + license = {text = "Apache License (2.0)"} + dependencies = [ + "flwr[simulation]>=1.9.0,<2.0", + "numpy>=1.21.0", + ] + + [flower] + publisher = "flwrlabs" + + [flower.components] + serverapp = "fedgpt.server:app" + clientapp = "fedgpt.client:app" + + [flower.config] + num_server_rounds = "10" + momentum = "0.1" + lr = "0.01" + """ + expected_config = { + "build-system": {"build-backend": "hatchling.build", "requires": ["hatchling"]}, + "project": { + "name": "fedgpt", + "version": "1.0.0", + "description": "", + "license": {"text": "Apache License (2.0)"}, + "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], + }, + "flower": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, + "config": { + "num_server_rounds": "10", + "momentum": "0.1", + "lr": "0.01", + }, + }, + } + # Current directory + origin = Path.cwd() + + try: + # Change into the temporary directory + os.chdir(tmp_path) + with open(FAB_CONFIG_FILE, "w", encoding="utf-8") as f: + f.write(textwrap.dedent(pyproject_toml_content)) + + # Execute + config = get_project_config(tmp_path) + + # Assert + assert config == expected_config + finally: + os.chdir(origin) + + +def test_flatten_dict() -> None: + """Test flatten_dict with a nested dictionary.""" + raw_dict = {"a": {"b": {"c": "d"}}, "e": "f"} + expected = {"a.b.c": "d", "e": "f"} + assert flatten_dict(raw_dict) == expected + + +def test_parse_config_args_none() -> None: + """Test parse_config_args with None as input.""" + assert not parse_config_args(None) + + +def test_parse_config_args_overrides() -> None: + """Test parse_config_args with key-value pairs.""" + assert parse_config_args("key1=value1,key2=value2") == { + "key1": "value1", + "key2": "value2", + } From cbc199ee28e1f074b05ab41c9c656a8040127e25 Mon Sep 17 00:00:00 2001 From: Daniel Nata Nugraha Date: Mon, 8 Jul 2024 18:10:23 +0200 Subject: [PATCH 124/595] ci(*:skip) Use `flower-supernode` in E2E tests (#3744) --- .github/workflows/e2e.yml | 10 +++++----- e2e/{test.sh => test_legacy.sh} | 0 e2e/{test_driver.sh => test_superlink.sh} | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename e2e/{test.sh => test_legacy.sh} (100%) rename e2e/{test_driver.sh => test_superlink.sh} (91%) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4cbee6f770d6..782b0605a096 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -145,7 +145,7 @@ jobs: run: python -c "${{ matrix.dataset }}" - name: Run edge client test if: ${{ matrix.directory != 'bare-client-auth' }} - run: ./../test.sh "${{ matrix.directory }}" + run: ./../test_legacy.sh "${{ matrix.directory }}" - name: Run virtual client test if: ${{ matrix.directory != 'bare-client-auth' }} run: python simulation.py @@ -154,16 +154,16 @@ jobs: run: python simulation_next.py - name: Run driver test if: ${{ matrix.directory != 'bare-client-auth' }} - run: ./../test_driver.sh "${{ matrix.directory }}" + run: ./../test_superlink.sh "${{ matrix.directory }}" - name: Run driver test with REST if: ${{ matrix.directory == 'bare' }} - run: ./../test_driver.sh bare rest + run: ./../test_superlink.sh bare rest - name: Run driver test with SQLite database if: ${{ matrix.directory == 'bare' }} - run: ./../test_driver.sh bare sqlite + run: ./../test_superlink.sh bare sqlite - name: Run driver test with client authentication if: ${{ matrix.directory == 'bare-client-auth' }} - run: ./../test_driver.sh bare client-auth + run: ./../test_superlink.sh bare client-auth - name: Run reconnection test with SQLite database if: ${{ matrix.directory == 'bare' }} run: ./../test_reconnection.sh sqlite diff --git a/e2e/test.sh b/e2e/test_legacy.sh similarity index 100% rename from e2e/test.sh rename to e2e/test_legacy.sh diff --git a/e2e/test_driver.sh b/e2e/test_superlink.sh similarity index 91% rename from e2e/test_driver.sh rename to e2e/test_superlink.sh index e177863bab78..3df33eb5c444 100755 --- a/e2e/test_driver.sh +++ b/e2e/test_superlink.sh @@ -70,11 +70,11 @@ timeout 2m flower-superlink $server_arg $db_arg $rest_arg_superlink $server_auth sl_pid=$! sleep 3 -timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --superlink $server_address $client_auth_1 & +timeout 2m flower-supernode client:app $client_arg $rest_arg_supernode --superlink $server_address $client_auth_1 & cl1_pid=$! sleep 3 -timeout 2m flower-client-app client:app $client_arg $rest_arg_supernode --superlink $server_address $client_auth_2 & +timeout 2m flower-supernode client:app $client_arg $rest_arg_supernode --superlink $server_address $client_auth_2 & cl2_pid=$! sleep 3 From d1848e7bd948275b65845ec2859815ecc55ee27d Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 8 Jul 2024 18:19:57 +0200 Subject: [PATCH 125/595] feat(framework:skip) Add function to fuse config overrides (#3745) --- src/py/flwr/common/config.py | 30 ++++++++++++++ src/py/flwr/common/config_test.py | 66 +++++++++++++++++++++++++++++++ src/py/flwr/common/context.py | 10 ++++- 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index e615e497b808..9770bdb4af2b 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -22,6 +22,7 @@ from flwr.cli.config_utils import validate_fields from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME +from flwr.common.typing import Run def get_flwr_dir(provided_path: Optional[str] = None) -> Path: @@ -73,6 +74,35 @@ def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]: return config +def _fuse_dicts( + main_dict: Dict[str, str], override_dict: Dict[str, str] +) -> Dict[str, str]: + fused_dict = main_dict.copy() + + for key, value in override_dict.items(): + if key in main_dict: + fused_dict[key] = value + + return fused_dict + + +def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: + """Merge the overrides from a `Run` with the config from a FAB. + + Get the config using the fab_id and the fab_version, remove the nesting by adding + the nested keys as prefixes separated by dots, and fuse it with the override dict. + """ + if not run.fab_id or not run.fab_version: + return {} + + project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir) + + default_config = get_project_config(project_dir)["flower"].get("config", {}) + flat_default_config = flatten_dict(default_config) + + return _fuse_dicts(flat_default_config, run.override_config) + + def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, str]: """Flatten dict by joining nested keys with a given separator.""" items: List[Tuple[str, str]] = [] diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py index d30f0d5cc755..fe429bab9cb5 100644 --- a/src/py/flwr/common/config_test.py +++ b/src/py/flwr/common/config_test.py @@ -22,6 +22,7 @@ import pytest from .config import ( + _fuse_dicts, flatten_dict, get_flwr_dir, get_project_config, @@ -75,6 +76,71 @@ def test_get_project_config_file_not_found() -> None: get_project_config("/invalid/dir") +def test_get_fused_config_valid(tmp_path: Path) -> None: + """Test get_project_config when the configuration file is not found.""" + pyproject_toml_content = """ + [build-system] + requires = ["hatchling"] + build-backend = "hatchling.build" + + [project] + name = "fedgpt" + version = "1.0.0" + description = "" + license = {text = "Apache License (2.0)"} + dependencies = [ + "flwr[simulation]>=1.9.0,<2.0", + "numpy>=1.21.0", + ] + + [flower] + publisher = "flwrlabs" + + [flower.components] + serverapp = "fedgpt.server:app" + clientapp = "fedgpt.client:app" + + [flower.config] + num_server_rounds = "10" + momentum = "0.1" + lr = "0.01" + serverapp.test = "key" + + [flower.config.clientapp] + test = "key" + """ + overrides = { + "num_server_rounds": "5", + "lr": "0.2", + "serverapp.test": "overriden", + } + expected_config = { + "num_server_rounds": "5", + "momentum": "0.1", + "lr": "0.2", + "serverapp.test": "overriden", + "clientapp.test": "key", + } + # Current directory + origin = Path.cwd() + + try: + # Change into the temporary directory + os.chdir(tmp_path) + with open(FAB_CONFIG_FILE, "w", encoding="utf-8") as f: + f.write(textwrap.dedent(pyproject_toml_content)) + + # Execute + default_config = get_project_config(tmp_path)["flower"].get("config", {}) + + config = _fuse_dicts(flatten_dict(default_config), overrides) + + # Assert + assert config == expected_config + finally: + os.chdir(origin) + + def test_get_project_config_file_valid(tmp_path: Path) -> None: """Test get_project_config when the configuration file is not found.""" pyproject_toml_content = """ diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index 8fe0f1781817..5a5cfea40e4b 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -16,7 +16,7 @@ from dataclasses import dataclass -from typing import Optional +from typing import Dict, Optional from .record import RecordSet @@ -42,7 +42,13 @@ class Context: state: RecordSet partition_id: Optional[int] + run_config: Dict[str, str] - def __init__(self, state: RecordSet, partition_id: Optional[int] = None) -> None: + def __init__( + self, + state: RecordSet, + partition_id: Optional[int] = None, + ) -> None: self.state = state self.partition_id = partition_id + self.run_config = {} From 864571f43fa7e0792125de688abb5f5cd05c68ca Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Mon, 8 Jul 2024 21:53:25 +0200 Subject: [PATCH 126/595] ci(*:skip) Add `benchmarks` tag for PR title definition (#3752) --- dev/changelog_config.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/changelog_config.toml b/dev/changelog_config.toml index c5ff1bcdd1c1..637ea9b4b2c6 100644 --- a/dev/changelog_config.toml +++ b/dev/changelog_config.toml @@ -3,7 +3,7 @@ type = ["ci", "docs", "feat", "fix", "refactor", "break"] -project = ["framework", "baselines", "datasets", "examples"] +project = ["framework", "baselines", "datasets", "examples", "benchmarks"] scope = "skip" From 1e209cfff954e42361258939f39336c9a6cf728e Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Mon, 8 Jul 2024 21:59:06 +0200 Subject: [PATCH 127/595] ci(*:skip) Update `format.sh`/`test.sh` to include `benchmarks` (#3749) --- dev/format.sh | 4 ++++ dev/test.sh | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/dev/format.sh b/dev/format.sh index 71edf9c6065a..6f9e7931879b 100755 --- a/dev/format.sh +++ b/dev/format.sh @@ -18,6 +18,10 @@ find src/proto/flwr/proto -name *.proto | grep "\.proto" | xargs clang-format -i python -m black -q examples python -m docformatter -i -r examples +# Benchmarks +python -m black -q benchmarks +python -m docformatter -i -r benchmarks + # E2E python -m isort e2e python -m black -q e2e diff --git a/dev/test.sh b/dev/test.sh index 8cbe88c9298b..c9da77ccb82c 100755 --- a/dev/test.sh +++ b/dev/test.sh @@ -15,7 +15,7 @@ python -m isort --check-only --skip src/py/flwr/proto src/py/flwr e2e echo "- isort: done" echo "- black: start" -python -m black --exclude "src\/py\/flwr\/proto" --check src/py/flwr examples e2e +python -m black --exclude "src\/py\/flwr\/proto" --check src/py/flwr benchmarks examples e2e echo "- black: done" echo "- init_py_check: start" From cc7d94de00e25c218024bffd859efc7c67ab4b95 Mon Sep 17 00:00:00 2001 From: Daniel Nata Nugraha Date: Tue, 9 Jul 2024 09:12:56 +0200 Subject: [PATCH 128/595] ci(*:skip) Delete tabnet e2e tests (#3753) --- .github/workflows/e2e.yml | 5 --- e2e/tabnet/README.md | 5 --- e2e/tabnet/client.py | 95 --------------------------------------- e2e/tabnet/pyproject.toml | 25 ----------- e2e/tabnet/simulation.py | 14 ------ 5 files changed, 144 deletions(-) delete mode 100644 e2e/tabnet/README.md delete mode 100644 e2e/tabnet/client.py delete mode 100644 e2e/tabnet/pyproject.toml delete mode 100644 e2e/tabnet/simulation.py diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 782b0605a096..d015f1387b17 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -78,11 +78,6 @@ jobs: import tensorflow as tf tf.keras.datasets.cifar10.load_data() - - directory: tabnet - dataset: | - import tensorflow_datasets as tfds - tfds.load(name='iris', split=tfds.Split.TRAIN) - - directory: opacus dataset: | from torchvision.datasets import CIFAR10 diff --git a/e2e/tabnet/README.md b/e2e/tabnet/README.md deleted file mode 100644 index 258043c3ffa8..000000000000 --- a/e2e/tabnet/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Flower with Tabnet testing - -This directory is used for testing Flower with Tabnet. - -It uses the `FedAvg` strategy. diff --git a/e2e/tabnet/client.py b/e2e/tabnet/client.py deleted file mode 100644 index 1a7ecfd68f73..000000000000 --- a/e2e/tabnet/client.py +++ /dev/null @@ -1,95 +0,0 @@ -import os - -import tabnet -import tensorflow as tf -import tensorflow_datasets as tfds - -import flwr as fl - -train_size = 125 -BATCH_SIZE = 50 -col_names = ["sepal_length", "sepal_width", "petal_length", "petal_width"] - - -def transform(ds): - features = tf.unstack(ds["features"]) - labels = ds["label"] - - x = dict(zip(col_names, features)) - y = tf.one_hot(labels, 3) - return x, y - - -def prepare_iris_dataset(): - ds_full = tfds.load(name="iris", split=tfds.Split.TRAIN) - ds_full = ds_full.shuffle(150, seed=0) - - ds_train = ds_full.take(train_size) - ds_train = ds_train.map(transform) - ds_train = ds_train.batch(BATCH_SIZE) - - ds_test = ds_full.skip(train_size) - ds_test = ds_test.map(transform) - ds_test = ds_test.batch(BATCH_SIZE) - - feature_columns = [] - for col_name in col_names: - feature_columns.append(tf.feature_column.numeric_column(col_name)) - - return ds_train, ds_test, feature_columns - - -ds_train, ds_test, feature_columns = prepare_iris_dataset() -# Make TensorFlow log less verbose -os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" - -# Load TabNet model -model = tabnet.TabNetClassifier( - feature_columns, - num_classes=3, - feature_dim=8, - output_dim=4, - num_decision_steps=4, - relaxation_factor=1.0, - sparsity_coefficient=1e-5, - batch_momentum=0.98, - virtual_batch_size=None, - norm_type="group", - num_groups=1, -) -lr = tf.keras.optimizers.schedules.ExponentialDecay( - 0.01, decay_steps=100, decay_rate=0.9, staircase=False -) -optimizer = tf.keras.optimizers.Adam(lr) -model.compile(optimizer, loss="categorical_crossentropy", metrics=["accuracy"]) - - -# Define Flower client -class FlowerClient(fl.client.NumPyClient): - def get_parameters(self, config): - return model.get_weights() - - def fit(self, parameters, config): - model.set_weights(parameters) - model.fit(ds_train, epochs=25) - return model.get_weights(), len(ds_train), {} - - def evaluate(self, parameters, config): - model.set_weights(parameters) - loss, accuracy = model.evaluate(ds_test) - return loss, len(ds_train), {"accuracy": accuracy} - - -def client_fn(cid): - return FlowerClient().to_client() - - -app = fl.client.ClientApp( - client_fn=client_fn, -) - -if __name__ == "__main__": - # Start Flower client - fl.client.start_client( - server_address="127.0.0.1:8080", client=FlowerClient().to_client() - ) diff --git a/e2e/tabnet/pyproject.toml b/e2e/tabnet/pyproject.toml deleted file mode 100644 index 99379ddb607e..000000000000 --- a/e2e/tabnet/pyproject.toml +++ /dev/null @@ -1,25 +0,0 @@ -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[project] -name = "quickstart-tabnet-test" -version = "0.1.0" -description = "Tabnet Federated Learning E2E test with Flower" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] -dependencies = [ - "flwr[simulation] @ {root:parent:parent:uri}", - "tensorflow-cpu>=2.9.1,!=2.11.1; platform_machine == \"x86_64\"", - "tensorflow-macos>=2.9.1,!=2.11.1; sys_platform == \"darwin\" and platform_machine == \"arm64\"", - "tensorflow_datasets==4.9.2", - "tensorflow-io-gcs-filesystem<0.35.0", - "tabnet==0.1.6", -] - -[tool.hatch.build.targets.wheel] -packages = ["."] - -[tool.hatch.metadata] -allow-direct-references = true diff --git a/e2e/tabnet/simulation.py b/e2e/tabnet/simulation.py deleted file mode 100644 index bf05a77cf32a..000000000000 --- a/e2e/tabnet/simulation.py +++ /dev/null @@ -1,14 +0,0 @@ -from client import client_fn - -import flwr as fl - -hist = fl.simulation.start_simulation( - client_fn=client_fn, - num_clients=2, - config=fl.server.ServerConfig(num_rounds=3), -) - -assert ( - hist.losses_distributed[-1][1] == 0 - or (hist.losses_distributed[0][1] / hist.losses_distributed[-1][1]) >= 0.98 -) From 6918a562dfe8179179da8658bfac9c9f86d04d1f Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 9 Jul 2024 09:29:24 +0200 Subject: [PATCH 129/595] feat(framework) Add SuperExec Dockerfile (#3723) Signed-off-by: Robert Steiner Co-authored-by: Taner Topal --- src/docker/superexec/Dockerfile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 src/docker/superexec/Dockerfile diff --git a/src/docker/superexec/Dockerfile b/src/docker/superexec/Dockerfile new file mode 100644 index 000000000000..9e4cc722921e --- /dev/null +++ b/src/docker/superexec/Dockerfile @@ -0,0 +1,20 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== + +ARG BASE_REPOSITORY=flwr/base +ARG BASE_IMAGE +FROM $BASE_REPOSITORY:$BASE_IMAGE + +ENTRYPOINT ["flower-superexec"] From 20a8cbdec1260c5137f85fdb4682267545ad67ee Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 9 Jul 2024 10:39:20 +0200 Subject: [PATCH 130/595] feat(*:skip) Use supernode instead of client-app (#3721) Signed-off-by: Robert Steiner Co-authored-by: Taner Topal --- src/docker/supernode/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/docker/supernode/Dockerfile b/src/docker/supernode/Dockerfile index 8dce1c389a5b..8b78577b1201 100644 --- a/src/docker/supernode/Dockerfile +++ b/src/docker/supernode/Dockerfile @@ -17,4 +17,4 @@ ARG BASE_REPOSITORY=flwr/base ARG BASE_IMAGE FROM $BASE_REPOSITORY:$BASE_IMAGE -ENTRYPOINT ["flower-client-app"] +ENTRYPOINT ["flower-supernode"] From f743b36769a2987e5ee395bb4c8f91dd6fc40b0c Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 9 Jul 2024 15:55:23 +0200 Subject: [PATCH 131/595] refactor(framework:skip) Add `run_config` as required `Context` parameter (#3755) --- src/py/flwr/client/message_handler/message_handler_test.py | 4 ++-- .../client/mod/secure_aggregation/secaggplus_mod_test.py | 2 +- src/py/flwr/client/mod/utils_test.py | 4 ++-- src/py/flwr/client/node_state.py | 2 +- src/py/flwr/common/context.py | 7 ++++++- src/py/flwr/server/compat/legacy_context.py | 2 +- src/py/flwr/server/run_serverapp.py | 2 +- src/py/flwr/server/server_app.py | 2 +- src/py/flwr/server/server_app_test.py | 2 +- .../server/superlink/fleet/vce/backend/raybackend_test.py | 2 +- .../flwr/simulation/ray_transport/ray_client_proxy_test.py | 2 +- 11 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/py/flwr/client/message_handler/message_handler_test.py b/src/py/flwr/client/message_handler/message_handler_test.py index cafcbbde3984..9ce4c9620c43 100644 --- a/src/py/flwr/client/message_handler/message_handler_test.py +++ b/src/py/flwr/client/message_handler/message_handler_test.py @@ -145,7 +145,7 @@ def test_client_without_get_properties() -> None: actual_msg = handle_legacy_message_from_msgtype( client_fn=_get_client_fn(client), message=message, - context=Context(state=RecordSet()), + context=Context(state=RecordSet(), run_config={}), ) # Assert @@ -209,7 +209,7 @@ def test_client_with_get_properties() -> None: actual_msg = handle_legacy_message_from_msgtype( client_fn=_get_client_fn(client), message=message, - context=Context(state=RecordSet()), + context=Context(state=RecordSet(), run_config={}), ) # Assert diff --git a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py index 36844a2983a1..5e4c4411e1f7 100644 --- a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py +++ b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py @@ -73,7 +73,7 @@ def func(configs: Dict[str, ConfigsRecordValues]) -> ConfigsRecord: def _make_ctxt() -> Context: cfg = ConfigsRecord(SecAggPlusState().to_dict()) - return Context(RecordSet(configs_records={RECORD_KEY_STATE: cfg})) + return Context(RecordSet(configs_records={RECORD_KEY_STATE: cfg}), run_config={}) def _make_set_state_fn( diff --git a/src/py/flwr/client/mod/utils_test.py b/src/py/flwr/client/mod/utils_test.py index 035e41639b10..7a1dd8988399 100644 --- a/src/py/flwr/client/mod/utils_test.py +++ b/src/py/flwr/client/mod/utils_test.py @@ -104,7 +104,7 @@ def test_multiple_mods(self) -> None: state = RecordSet() state.metrics_records[METRIC] = MetricsRecord({COUNTER: 0.0}) - context = Context(state=state) + context = Context(state=state, run_config={}) message = _get_dummy_flower_message() # Execute @@ -129,7 +129,7 @@ def test_filter(self) -> None: # Prepare footprint: List[str] = [] mock_app = make_mock_app("app", footprint) - context = Context(state=RecordSet()) + context = Context(state=RecordSet(), run_config={}) message = _get_dummy_flower_message() def filter_mod( diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index cda00d25b62c..64a0d348b23e 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -32,7 +32,7 @@ def register_context(self, run_id: int) -> None: """Register new run context for this node.""" if run_id not in self.run_contexts: self.run_contexts[run_id] = Context( - state=RecordSet(), partition_id=self._partition_id + state=RecordSet(), run_config={}, partition_id=self._partition_id ) def retrieve_context(self, run_id: int) -> Context: diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index 5a5cfea40e4b..8120723ce9e9 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -34,6 +34,10 @@ class Context: executing mods. It can also be used as a memory to access at different points during the lifecycle of this entity (e.g. across multiple rounds) + run_config : Dict[str, str] + A config (key/value mapping) held by the entity in a given run and that will + stay local. It can be used at any point during the lifecycle of this entity + (e.g. across multiple rounds) partition_id : Optional[int] (default: None) An index that specifies the data partition that the ClientApp using this Context object should make use of. Setting this attribute is better suited for @@ -47,8 +51,9 @@ class Context: def __init__( self, state: RecordSet, + run_config: Dict[str, str], partition_id: Optional[int] = None, ) -> None: self.state = state + self.run_config = run_config self.partition_id = partition_id - self.run_config = {} diff --git a/src/py/flwr/server/compat/legacy_context.py b/src/py/flwr/server/compat/legacy_context.py index 0b00c98bb16d..9e120c824103 100644 --- a/src/py/flwr/server/compat/legacy_context.py +++ b/src/py/flwr/server/compat/legacy_context.py @@ -52,4 +52,4 @@ def __init__( self.strategy = strategy self.client_manager = client_manager self.history = History() - super().__init__(state) + super().__init__(state, run_config={}) diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 70ce2da347f0..934f204ec47d 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -72,7 +72,7 @@ def _load() -> ServerApp: server_app = _load() # Initialize Context - context = Context(state=RecordSet()) + context = Context(state=RecordSet(), run_config={}) # Call ServerApp server_app(driver=driver, context=context) diff --git a/src/py/flwr/server/server_app.py b/src/py/flwr/server/server_app.py index 43b3bcce3f36..f19a9d91986b 100644 --- a/src/py/flwr/server/server_app.py +++ b/src/py/flwr/server/server_app.py @@ -80,7 +80,7 @@ def __call__(self, driver: Driver, context: Context) -> None: return # New execution mode - context = Context(state=RecordSet()) + context = Context(state=RecordSet(), run_config={}) self._main(driver, context) def main(self) -> Callable[[ServerAppCallable], ServerAppCallable]: diff --git a/src/py/flwr/server/server_app_test.py b/src/py/flwr/server/server_app_test.py index 0751a0cb2bc5..7de8774d4c81 100644 --- a/src/py/flwr/server/server_app_test.py +++ b/src/py/flwr/server/server_app_test.py @@ -29,7 +29,7 @@ def test_server_app_custom_mode() -> None: # Prepare app = ServerApp() driver = MagicMock() - context = Context(state=RecordSet()) + context = Context(state=RecordSet(), run_config={}) called = {"called": False} diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index fa7374f08853..b822db87673f 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -121,7 +121,7 @@ def _create_message_and_context() -> Tuple[Message, Context, float]: ) # Construct emtpy Context - context = Context(state=RecordSet()) + context = Context(state=RecordSet(), run_config={}) # Expected output expected_output = pi * mult_factor diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 8da579dc650e..83f6cfe05313 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -218,7 +218,7 @@ def _load_app() -> ClientApp: _load_app, message, str(node_id), - Context(state=RecordSet(), partition_id=node_id), + Context(state=RecordSet(), run_config={}, partition_id=node_id), ), ) From 3e414c5b1da81903d4278a9389fc236d6066a8dd Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 9 Jul 2024 17:57:06 +0200 Subject: [PATCH 132/595] ci(framework) Build SuperExec nightly Docker image (#3724) Signed-off-by: Robert Steiner --- .github/workflows/release-nightly.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index 2b72190bede5..97751aafc031 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -69,7 +69,8 @@ jobs: images: [ { repository: "flwr/superlink", file_dir: "src/docker/superlink" }, { repository: "flwr/supernode", file_dir: "src/docker/supernode" }, - { repository: "flwr/serverapp", file_dir: "src/docker/serverapp" } + { repository: "flwr/serverapp", file_dir: "src/docker/serverapp" }, + { repository: "flwr/superexec", file_dir: "src/docker/superexec" } ] with: namespace-repository: ${{ matrix.images.repository }} From 937c26ac0e2c2ff1ef6b205cde7fc83611c7c01e Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 9 Jul 2024 18:02:41 +0200 Subject: [PATCH 133/595] feat(*:skip) Build SuperExec Docker Image in CI (#3748) Signed-off-by: Robert Steiner Co-authored-by: Taner Topal --- dev/build-docker-image-matrix.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dev/build-docker-image-matrix.py b/dev/build-docker-image-matrix.py index 51d7fd0083d1..b7c4d2daaefd 100644 --- a/dev/build-docker-image-matrix.py +++ b/dev/build-docker-image-matrix.py @@ -168,6 +168,13 @@ def tag_latest_ubuntu_with_flwr_version(image: BaseImage) -> List[str]: tag_latest_ubuntu_with_flwr_version, lambda image: image.distro.name == DistroName.UBUNTU, ) + # ubuntu images for each supported python version + + generate_binary_images( + "superexec", + base_images, + tag_latest_ubuntu_with_flwr_version, + lambda image: image.distro.name == DistroName.UBUNTU, + ) ) print( From b1529248b868eef4edd487e95e797e944d964856 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 9 Jul 2024 19:55:48 +0200 Subject: [PATCH 134/595] feat(framework) Add `run_config` to `ServerApp` `Context` (#3750) Co-authored-by: Daniel J. Beutel --- src/py/flwr/server/run_serverapp.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 934f204ec47d..b4697e99913f 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -19,10 +19,15 @@ import sys from logging import DEBUG, INFO, WARN from pathlib import Path -from typing import Optional +from typing import Dict, Optional from flwr.common import Context, EventType, RecordSet, event -from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir +from flwr.common.config import ( + get_flwr_dir, + get_fused_config, + get_project_config, + get_project_dir, +) from flwr.common.logger import log, update_console_handler, warn_deprecated_feature from flwr.common.object_ref import load_app from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 @@ -40,6 +45,7 @@ def run( driver: Driver, server_app_dir: str, + server_app_run_config: Dict[str, str], server_app_attr: Optional[str] = None, loaded_server_app: Optional[ServerApp] = None, ) -> None: @@ -72,7 +78,7 @@ def _load() -> ServerApp: server_app = _load() # Initialize Context - context = Context(state=RecordSet(), run_config={}) + context = Context(state=RecordSet(), run_config=server_app_run_config) # Call ServerApp server_app(driver=driver, context=context) @@ -169,6 +175,8 @@ def run_server_app() -> None: # pylint: disable=too-many-branches # Overwrite driver._run_id driver._run_id = res.run_id # pylint: disable=W0212 + server_app_run_config = {} + # Dynamically obtain ServerApp path based on run_id if args.run_id is not None: # User provided `--run-id`, but not `server-app` @@ -177,6 +185,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir)) config = get_project_config(server_app_dir) server_app_attr = config["flower"]["components"]["serverapp"] + server_app_run_config = get_fused_config(run_, flwr_dir) else: # User provided `server-app`, but not `--run-id` server_app_dir = str(Path(args.dir).absolute()) @@ -190,7 +199,12 @@ def run_server_app() -> None: # pylint: disable=too-many-branches ) # Run the ServerApp with the Driver - run(driver=driver, server_app_dir=server_app_dir, server_app_attr=server_app_attr) + run( + driver=driver, + server_app_dir=server_app_dir, + server_app_run_config=server_app_run_config, + server_app_attr=server_app_attr, + ) # Clean up driver.close() From 11a8f86e0417514e6dbb21b77d00e828676a0cd9 Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Tue, 9 Jul 2024 20:33:20 +0200 Subject: [PATCH 135/595] ci(*:skip) Use isort for `benchmarks` (#3759) --- dev/format.sh | 1 + dev/test.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/format.sh b/dev/format.sh index 6f9e7931879b..e1e2abc307f1 100755 --- a/dev/format.sh +++ b/dev/format.sh @@ -19,6 +19,7 @@ python -m black -q examples python -m docformatter -i -r examples # Benchmarks +python -m isort benchmarks python -m black -q benchmarks python -m docformatter -i -r benchmarks diff --git a/dev/test.sh b/dev/test.sh index c9da77ccb82c..58ac0b3d24cd 100755 --- a/dev/test.sh +++ b/dev/test.sh @@ -11,7 +11,7 @@ clang-format --Werror --dry-run src/proto/flwr/proto/* echo "- clang-format: done" echo "- isort: start" -python -m isort --check-only --skip src/py/flwr/proto src/py/flwr e2e +python -m isort --check-only --skip src/py/flwr/proto src/py/flwr benchmarks e2e echo "- isort: done" echo "- black: start" From 8e8fb225c047cfaf1cdf5f78b01d4c4d9dd59cb6 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Wed, 10 Jul 2024 10:29:40 +0200 Subject: [PATCH 136/595] refactor(*:skip) align ubuntu image with apline image (#3713) Signed-off-by: Robert Steiner --- src/docker/base/ubuntu/Dockerfile | 57 ++++++++++++++++--------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/src/docker/base/ubuntu/Dockerfile b/src/docker/base/ubuntu/Dockerfile index c3d64b39cbb8..960ed07edf96 100644 --- a/src/docker/base/ubuntu/Dockerfile +++ b/src/docker/base/ubuntu/Dockerfile @@ -48,30 +48,7 @@ RUN git clone https://github.com/pyenv/pyenv.git \ RUN LATEST=$(pyenv latest -k ${PYTHON_VERSION}) \ && python-build "${LATEST}" /usr/local/bin/python -FROM $DISTRO:$DISTRO_VERSION as base - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update \ - && apt-get -y --no-install-recommends install \ - libsqlite3-0 \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=python /usr/local/bin/python /usr/local/bin/python - -ENV PATH=/usr/local/bin/python/bin:$PATH \ - # Send stdout and stderr stream directly to the terminal. Ensures that no - # output is retained in a buffer if the application crashes. - PYTHONUNBUFFERED=1 \ - # Typically, bytecode is created on the first invocation to speed up following invocation. - # However, in Docker we only make a single invocation (when we start the container). - # Therefore, we can disable bytecode writing. - PYTHONDONTWRITEBYTECODE=1 \ - # Ensure that python encoding is always UTF-8. - PYTHONIOENCODING=UTF-8 \ - LANG=C.UTF-8 \ - LC_ALL=C.UTF-8 +ENV PATH=/usr/local/bin/python/bin:$PATH # Use a virtual environment to ensure that Python packages are installed in the same location # regardless of whether the subsequent image build is run with the app or the root user @@ -87,17 +64,43 @@ RUN pip install -U --no-cache-dir \ setuptools==${SETUPTOOLS_VERSION} \ ${FLWR_PACKAGE}==${FLWR_VERSION} -# add non-root user -RUN adduser \ +FROM $DISTRO:$DISTRO_VERSION as base + +COPY --from=python /usr/local/bin/python /usr/local/bin/python + +ENV DEBIAN_FRONTEND=noninteractive \ + PATH=/usr/local/bin/python/bin:$PATH + +RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + libsqlite3-0 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* \ + # add non-root user + && adduser \ --no-create-home \ --home /app \ --disabled-password \ --gecos "" \ --uid 49999 app \ && mkdir -p /app \ - && chown -R app:app /python \ && chown -R app:app /app +COPY --from=python --chown=app:app /python/venv /python/venv + +ENV PATH=/python/venv/bin:$PATH \ + # Send stdout and stderr stream directly to the terminal. Ensures that no + # output is retained in a buffer if the application crashes. + PYTHONUNBUFFERED=1 \ + # Typically, bytecode is created on the first invocation to speed up following invocation. + # However, in Docker we only make a single invocation (when we start the container). + # Therefore, we can disable bytecode writing. + PYTHONDONTWRITEBYTECODE=1 \ + # Ensure that python encoding is always UTF-8. + PYTHONIOENCODING=UTF-8 \ + LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 + WORKDIR /app USER app ENV HOME=/app From a9795a4b7b287b1db2152802c4891ff4401e4fc9 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 10 Jul 2024 14:02:45 +0200 Subject: [PATCH 137/595] refactor(framework) Introduce double queue mechanism for Simulation Engine (#3468) Co-authored-by: Heng Pan --- .../server/superlink/fleet/vce/vce_api.py | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 78dc6a900aae..0e8171485a59 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -30,7 +30,7 @@ from flwr.common.message import Error from flwr.common.object_ref import load_app from flwr.common.serde import message_from_taskins, message_to_taskres -from flwr.proto.task_pb2 import TaskIns # pylint: disable=E0611 +from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 from flwr.server.superlink.state import StateFactory from .backend import Backend, error_messages_backends, supported_backends @@ -54,17 +54,16 @@ def _register_nodes( # pylint: disable=too-many-arguments,too-many-locals async def worker( app_fn: Callable[[], ClientApp], - queue: "asyncio.Queue[TaskIns]", + taskins_queue: "asyncio.Queue[TaskIns]", + taskres_queue: "asyncio.Queue[TaskRes]", node_states: Dict[int, NodeState], - state_factory: StateFactory, backend: Backend, ) -> None: """Get TaskIns from queue and pass it to an actor in the pool to execute it.""" - state = state_factory.state() while True: out_mssg = None try: - task_ins: TaskIns = await queue.get() + task_ins: TaskIns = await taskins_queue.get() node_id = task_ins.task.consumer.node_id # Register and retrieve runstate @@ -111,7 +110,7 @@ async def worker( task_res = message_to_taskres(out_mssg) # Store TaskRes in state task_res.task.pushed_at = time.time() - state.store_task_res(task_res) + await taskres_queue.put(task_res) async def add_taskins_to_queue( @@ -162,6 +161,21 @@ async def add_taskins_to_queue( log(DEBUG, "Async producer: Stopped pulling from StateFactory.") +async def put_taskres_into_state( + queue: "asyncio.Queue[TaskRes]", + state_factory: StateFactory, + f_stop: asyncio.Event, +) -> None: + """Remove TaskRes from queue and add into State.""" + state = state_factory.state() + while not f_stop.is_set(): + if queue.qsize(): + task_res = await queue.get() + state.store_task_res(task_res) + else: + await asyncio.sleep(0.1) + + async def run( app_fn: Callable[[], ClientApp], backend_fn: Callable[[], Backend], @@ -171,7 +185,8 @@ async def run( f_stop: asyncio.Event, ) -> None: """Run the VCE async.""" - queue: "asyncio.Queue[TaskIns]" = asyncio.Queue(128) + taskins_queue: "asyncio.Queue[TaskIns]" = asyncio.Queue(128) + taskres_queue: "asyncio.Queue[TaskRes]" = asyncio.Queue(128) try: @@ -184,22 +199,37 @@ async def run( # Add workers (they submit Messages to Backend) worker_tasks = [ asyncio.create_task( - worker(app_fn, queue, node_states, state_factory, backend) + worker( + app_fn, + taskins_queue, + taskres_queue, + node_states, + backend, + ) ) for _ in range(backend.num_workers) ] # Create producer (adds TaskIns into Queue) - producer = asyncio.create_task( + taskins_producer = asyncio.create_task( add_taskins_to_queue( - queue, state_factory, nodes_mapping, backend, worker_tasks, f_stop + taskins_queue, + state_factory, + nodes_mapping, + backend, + worker_tasks, + f_stop, ) ) - # Wait for producer to finish - # The producer runs forever until f_stop is set or until + taskres_consumer = asyncio.create_task( + put_taskres_into_state(taskres_queue, state_factory, f_stop) + ) + + # Wait for asyncio taks pulling/pushing TaskIns/TaskRes. + # These run forever until f_stop is set or until # all worker (consumer) coroutines are completed. Workers # also run forever and only end if an exception is raised. - await asyncio.gather(producer) + await asyncio.gather(*(taskins_producer, taskres_consumer)) except Exception as ex: From cf75d4111b5690487ca7338d3c64b92318ddec23 Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Wed, 10 Jul 2024 14:30:34 +0200 Subject: [PATCH 138/595] feat(framework) Add additional user prompt for `flowertune` template in `flwr new` (#3760) --- src/py/flwr/cli/new/new.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/cli/new/new.py b/src/py/flwr/cli/new/new.py index 9367cf6c9ffb..a0a2dc98556d 100644 --- a/src/py/flwr/cli/new/new.py +++ b/src/py/flwr/cli/new/new.py @@ -264,9 +264,11 @@ def new( bold=True, ) ) + + _add = " huggingface-cli login\n" if framework_str == "flowertune" else "" print( typer.style( - f" cd {package_name}\n" + " pip install -e .\n flwr run\n", + f" cd {package_name}\n" + " pip install -e .\n" + _add + " flwr run\n", fg=typer.colors.BRIGHT_CYAN, bold=True, ) From d1ec08b59a2a4139108801a696bbda41848a72a4 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Wed, 10 Jul 2024 15:30:21 +0100 Subject: [PATCH 139/595] fix(framework:skip) Enable dynamic `sys.path` update when loading apps (#3762) --- src/py/flwr/client/supernode/app.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 4ee3544417aa..4115c57d4738 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -178,7 +178,7 @@ def _get_load_client_app_fn( else: flwr_dir = Path(args.flwr_dir).absolute() - sys.path.insert(0, str(flwr_dir.absolute())) + inserted_path = None default_app_ref: str = getattr(args, "client-app") @@ -188,6 +188,11 @@ def _get_load_client_app_fn( "Flower SuperNode will load and validate ClientApp `%s`", getattr(args, "client-app"), ) + # Insert sys.path + dir_path = Path(args.dir).absolute() + sys.path.insert(0, str(dir_path)) + inserted_path = str(dir_path) + valid, error_msg = validate(default_app_ref) if not valid and error_msg: raise LoadClientAppError(error_msg) from None @@ -196,7 +201,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: # If multi-app feature is disabled if not multi_app: # Get sys path to be inserted - sys_path = Path(args.dir).absolute() + dir_path = Path(args.dir).absolute() # Set app reference client_app_ref = default_app_ref @@ -209,7 +214,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.") # Get sys path to be inserted - sys_path = Path(args.dir).absolute() + dir_path = Path(args.dir).absolute() # Set app reference client_app_ref = default_app_ref @@ -222,13 +227,21 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: raise LoadClientAppError("Failed to load ClientApp") from e # Get sys path to be inserted - sys_path = Path(project_dir).absolute() + dir_path = Path(project_dir).absolute() # Set app reference client_app_ref = config["flower"]["components"]["clientapp"] # Set sys.path - sys.path.insert(0, str(sys_path)) + nonlocal inserted_path + if inserted_path != str(dir_path): + # Remove the previously inserted path + if inserted_path is not None: + sys.path.remove(inserted_path) + # Insert the new path + sys.path.insert(0, str(dir_path)) + + inserted_path = str(dir_path) # Load ClientApp log( @@ -236,7 +249,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: "Loading ClientApp `%s`", client_app_ref, ) - client_app = load_app(client_app_ref, LoadClientAppError, sys_path) + client_app = load_app(client_app_ref, LoadClientAppError, dir_path) if not isinstance(client_app, ClientApp): raise LoadClientAppError( From 915ebd8e414074f070b3ed238f023f9a0fee8ea2 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Jul 2024 12:05:56 +0200 Subject: [PATCH 140/595] fix(framework:skip) Pass arguments to run the `ServerApp` explicitly (#3764) --- src/py/flwr/simulation/run_simulation.py | 49 ++++++++++++++++++------ 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 91805dc5ed7b..0ea917109363 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -22,7 +22,7 @@ import traceback from logging import DEBUG, ERROR, INFO, WARNING from time import sleep -from typing import Optional +from typing import Dict, Optional from flwr.client import ClientApp from flwr.common import EventType, event, log @@ -126,16 +126,25 @@ def run_simulation( def run_serverapp_th( server_app_attr: Optional[str], server_app: Optional[ServerApp], + server_app_run_config: Dict[str, str], driver: Driver, app_dir: str, f_stop: asyncio.Event, + has_exception: threading.Event, enable_tf_gpu_growth: bool, delay_launch: int = 3, ) -> threading.Thread: """Run SeverApp in a thread.""" - def server_th_with_start_checks( # type: ignore - tf_gpu_growth: bool, stop_event: asyncio.Event, **kwargs + def server_th_with_start_checks( + tf_gpu_growth: bool, + stop_event: asyncio.Event, + exception_event: threading.Event, + _driver: Driver, + _server_app_dir: str, + _server_app_run_config: Dict[str, str], + _server_app_attr: Optional[str], + _server_app: Optional[ServerApp], ) -> None: """Run SeverApp, after check if GPU memory growth has to be set. @@ -147,10 +156,18 @@ def server_th_with_start_checks( # type: ignore enable_gpu_growth() # Run ServerApp - run(**kwargs) + run( + driver=_driver, + server_app_dir=_server_app_dir, + server_app_run_config=_server_app_run_config, + server_app_attr=_server_app_attr, + loaded_server_app=_server_app, + ) except Exception as ex: # pylint: disable=broad-exception-caught log(ERROR, "ServerApp thread raised an exception: %s", ex) log(ERROR, traceback.format_exc()) + exception_event.set() + raise finally: log(DEBUG, "ServerApp finished running.") # Upon completion, trigger stop event if one was passed @@ -160,13 +177,16 @@ def server_th_with_start_checks( # type: ignore serverapp_th = threading.Thread( target=server_th_with_start_checks, - args=(enable_tf_gpu_growth, f_stop), - kwargs={ - "server_app_attr": server_app_attr, - "loaded_server_app": server_app, - "driver": driver, - "server_app_dir": app_dir, - }, + args=( + enable_tf_gpu_growth, + f_stop, + has_exception, + driver, + app_dir, + server_app_run_config, + server_app_attr, + server_app, + ), ) sleep(delay_launch) serverapp_th.start() @@ -206,10 +226,13 @@ def _main_loop( state_factory = StateFactory(":flwr-in-memory-state:") f_stop = asyncio.Event() + # A Threading event to indicate if an exception was raised in the ServerApp thread + server_app_thread_has_exception = threading.Event() serverapp_th = None try: # Create run (with empty fab_id and fab_version) run_id_ = state_factory.state().create_run("", "", {}) + server_app_run_config: Dict[str, str] = {} if run_id: _override_run_id(state_factory, run_id_to_replace=run_id_, run_id=run_id) @@ -222,9 +245,11 @@ def _main_loop( serverapp_th = run_serverapp_th( server_app_attr=server_app_attr, server_app=server_app, + server_app_run_config=server_app_run_config, driver=driver, app_dir=app_dir, f_stop=f_stop, + has_exception=server_app_thread_has_exception, enable_tf_gpu_growth=enable_tf_gpu_growth, ) @@ -253,6 +278,8 @@ def _main_loop( event(EventType.RUN_SUPERLINK_LEAVE) if serverapp_th: serverapp_th.join() + if server_app_thread_has_exception.is_set(): + raise RuntimeError("Exception in ServerApp thread") log(DEBUG, "Stopping Simulation Engine now.") From 3089520977145e00d8eb92c8043e96a396bdcb75 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Jul 2024 12:15:20 +0200 Subject: [PATCH 141/595] refactor(framework) Remove `asyncio` from `Backend` definitions (#3469) Co-authored-by: Heng Pan --- .../superlink/fleet/vce/backend/backend.py | 8 +- .../superlink/fleet/vce/backend/raybackend.py | 17 +- .../fleet/vce/backend/raybackend_test.py | 31 +-- .../server/superlink/fleet/vce/vce_api.py | 198 ++++++++---------- .../superlink/fleet/vce/vce_api_test.py | 8 +- .../simulation/ray_transport/ray_actor.py | 34 ++- 6 files changed, 124 insertions(+), 172 deletions(-) diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/backend.py b/src/py/flwr/server/superlink/fleet/vce/backend/backend.py index 1d5e3a6a51ad..31c64bd3b233 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/backend.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/backend.py @@ -33,8 +33,8 @@ def __init__(self, backend_config: BackendConfig, work_dir: str) -> None: """Construct a backend.""" @abstractmethod - async def build(self) -> None: - """Build backend asynchronously. + def build(self) -> None: + """Build backend. Different components need to be in place before workers in a backend are ready to accept jobs. When this method finishes executing, the backend should be fully @@ -54,11 +54,11 @@ def is_worker_idle(self) -> bool: """Report whether a backend worker is idle and can therefore run a ClientApp.""" @abstractmethod - async def terminate(self) -> None: + def terminate(self) -> None: """Terminate backend.""" @abstractmethod - async def process_message( + def process_message( self, app: Callable[[], ClientApp], message: Message, diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py index b6b9e248a656..0d2f4d193f0b 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py @@ -153,12 +153,12 @@ def is_worker_idle(self) -> bool: """Report whether the pool has idle actors.""" return self.pool.is_actor_available() - async def build(self) -> None: + def build(self) -> None: """Build pool of Ray actors that this backend will submit jobs to.""" - await self.pool.add_actors_to_pool(self.pool.actors_capacity) + self.pool.add_actors_to_pool(self.pool.actors_capacity) log(DEBUG, "Constructed ActorPool with: %i actors", self.pool.num_actors) - async def process_message( + def process_message( self, app: Callable[[], ClientApp], message: Message, @@ -172,17 +172,16 @@ async def process_message( try: # Submit a task to the pool - future = await self.pool.submit( + future = self.pool.submit( lambda a, a_fn, mssg, cid, state: a.run.remote(a_fn, mssg, cid, state), (app, message, str(partition_id), context), ) - await future # Fetch result ( out_mssg, updated_context, - ) = await self.pool.fetch_result_and_return_actor_to_pool(future) + ) = self.pool.fetch_result_and_return_actor_to_pool(future) return out_mssg, updated_context @@ -193,11 +192,11 @@ async def process_message( self.__class__.__name__, ) # add actor back into pool - await self.pool.add_actor_back_to_pool(future) + self.pool.add_actor_back_to_pool(future) raise ex - async def terminate(self) -> None: + def terminate(self) -> None: """Terminate all actors in actor pool.""" - await self.pool.terminate_all_actors() + self.pool.terminate_all_actors() ray.shutdown() log(DEBUG, "Terminated %s", self.__class__.__name__) diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index b822db87673f..287983003f8c 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -14,11 +14,10 @@ # ============================================================================== """Test for Ray backend for the Fleet API using the Simulation Engine.""" -import asyncio from math import pi from pathlib import Path from typing import Callable, Dict, Optional, Tuple, Union -from unittest import IsolatedAsyncioTestCase +from unittest import TestCase import ray @@ -84,18 +83,18 @@ def _load_app() -> ClientApp: return _load_app -async def backend_build_process_and_termination( +def backend_build_process_and_termination( backend: RayBackend, process_args: Optional[Tuple[Callable[[], ClientApp], Message, Context]] = None, ) -> Union[Tuple[Message, Context], None]: """Build, process job and terminate RayBackend.""" - await backend.build() + backend.build() to_return = None if process_args: - to_return = await backend.process_message(*process_args) + to_return = backend.process_message(*process_args) - await backend.terminate() + backend.terminate() return to_return @@ -129,10 +128,10 @@ def _create_message_and_context() -> Tuple[Message, Context, float]: return message, context, expected_output -class AsyncTestRayBackend(IsolatedAsyncioTestCase): - """A basic class that allows runnig multliple asyncio tests.""" +class TestRayBackend(TestCase): + """A basic class that allows runnig multliple tests.""" - async def on_cleanup(self) -> None: + def doCleanups(self) -> None: """Ensure Ray has shutdown.""" if ray.is_initialized(): ray.shutdown() @@ -140,9 +139,7 @@ async def on_cleanup(self) -> None: def test_backend_creation_and_termination(self) -> None: """Test creation of RayBackend and its termination.""" backend = RayBackend(backend_config={}, work_dir="") - asyncio.run( - backend_build_process_and_termination(backend=backend, process_args=None) - ) + backend_build_process_and_termination(backend=backend, process_args=None) def test_backend_creation_submit_and_termination( self, @@ -157,10 +154,8 @@ def test_backend_creation_submit_and_termination( message, context, expected_output = _create_message_and_context() - res = asyncio.run( - backend_build_process_and_termination( - backend=backend, process_args=(client_app_callable, message, context) - ) + res = backend_build_process_and_termination( + backend=backend, process_args=(client_app_callable, message, context) ) if res is None: @@ -189,7 +184,6 @@ def test_backend_creation_submit_and_termination_non_existing_client_app( self.test_backend_creation_submit_and_termination( client_app_loader=_load_from_module("a_non_existing_module:app") ) - self.addAsyncCleanup(self.on_cleanup) def test_backend_creation_submit_and_termination_existing_client_app( self, @@ -217,7 +211,6 @@ def test_backend_creation_submit_and_termination_existing_client_app_unsetworkdi client_app_loader=_load_from_module("raybackend_test:client_app"), workdir="/?&%$^#%@$!", ) - self.addAsyncCleanup(self.on_cleanup) def test_backend_creation_with_init_arguments(self) -> None: """Testing whether init args are properly parsed to Ray.""" @@ -248,5 +241,3 @@ def test_backend_creation_with_init_arguments(self) -> None: nodes = ray.nodes() assert nodes[0]["Resources"]["CPU"] == backend_config_2["init_args"]["num_cpus"] - - self.addAsyncCleanup(self.on_cleanup) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 0e8171485a59..4eeaa5197006 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -14,14 +14,19 @@ # ============================================================================== """Fleet Simulation Engine API.""" + import asyncio import json import sys +import threading import time import traceback +from concurrent.futures import ThreadPoolExecutor from logging import DEBUG, ERROR, INFO, WARN from pathlib import Path -from typing import Callable, Dict, List, Optional +from queue import Empty, Queue +from time import sleep +from typing import Callable, Dict, Optional from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError from flwr.client.node_state import NodeState @@ -31,7 +36,7 @@ from flwr.common.object_ref import load_app from flwr.common.serde import message_from_taskins, message_to_taskres from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 -from flwr.server.superlink.state import StateFactory +from flwr.server.superlink.state import State, StateFactory from .backend import Backend, error_messages_backends, supported_backends @@ -52,18 +57,21 @@ def _register_nodes( # pylint: disable=too-many-arguments,too-many-locals -async def worker( +def worker( app_fn: Callable[[], ClientApp], - taskins_queue: "asyncio.Queue[TaskIns]", - taskres_queue: "asyncio.Queue[TaskRes]", + taskins_queue: "Queue[TaskIns]", + taskres_queue: "Queue[TaskRes]", node_states: Dict[int, NodeState], backend: Backend, + f_stop: asyncio.Event, ) -> None: """Get TaskIns from queue and pass it to an actor in the pool to execute it.""" - while True: + while not f_stop.is_set(): out_mssg = None try: - task_ins: TaskIns = await taskins_queue.get() + # Fetch from queue with timeout. We use a timeout so + # the stopping event can be evaluated even when the queue is empty. + task_ins: TaskIns = taskins_queue.get(timeout=1.0) node_id = task_ins.task.consumer.node_id # Register and retrieve runstate @@ -74,7 +82,7 @@ async def worker( message = message_from_taskins(task_ins) # Let backend process message - out_mssg, updated_context = await backend.process_message( + out_mssg, updated_context = backend.process_message( app_fn, message, context ) @@ -82,11 +90,9 @@ async def worker( node_states[node_id].update_context( task_ins.run_id, context=updated_context ) - - except asyncio.CancelledError as e: - log(DEBUG, "Terminating async worker: %s", e) - break - + except Empty: + # An exception raised if queue.get times out + pass # Exceptions aren't raised but reported as an error message except Exception as ex: # pylint: disable=broad-exception-caught log(ERROR, ex) @@ -110,73 +116,38 @@ async def worker( task_res = message_to_taskres(out_mssg) # Store TaskRes in state task_res.task.pushed_at = time.time() - await taskres_queue.put(task_res) + taskres_queue.put(task_res) -async def add_taskins_to_queue( - queue: "asyncio.Queue[TaskIns]", - state_factory: StateFactory, +def add_taskins_to_queue( + state: State, + queue: "Queue[TaskIns]", nodes_mapping: NodeToPartitionMapping, - backend: Backend, - consumers: List["asyncio.Task[None]"], f_stop: asyncio.Event, ) -> None: - """Retrieve TaskIns and add it to the queue.""" - state = state_factory.state() - num_initial_consumers = len(consumers) + """Put TaskIns in a queue from State.""" while not f_stop.is_set(): for node_id in nodes_mapping.keys(): - task_ins = state.get_task_ins(node_id=node_id, limit=1) - if task_ins: - await queue.put(task_ins[0]) - - # Count consumers that are running - num_active = sum(not (cc.done()) for cc in consumers) - - # Alert if number of consumers decreased by half - if num_active < num_initial_consumers // 2: - log( - WARN, - "Number of active workers has more than halved: (%i/%i active)", - num_active, - num_initial_consumers, - ) - - # Break if consumers died - if num_active == 0: - raise RuntimeError("All workers have died. Ending Simulation.") - - # Log some stats - log( - DEBUG, - "Simulation Engine stats: " - "Active workers: (%i/%i) | %s (%i workers) | Tasks in queue: %i)", - num_active, - num_initial_consumers, - backend.__class__.__name__, - backend.num_workers, - queue.qsize(), - ) - await asyncio.sleep(1.0) - log(DEBUG, "Async producer: Stopped pulling from StateFactory.") + task_ins_list = state.get_task_ins(node_id=node_id, limit=1) + for task_ins in task_ins_list: + queue.put(task_ins) + sleep(0.1) -async def put_taskres_into_state( - queue: "asyncio.Queue[TaskRes]", - state_factory: StateFactory, - f_stop: asyncio.Event, +def put_taskres_into_state( + state: State, queue: "Queue[TaskRes]", f_stop: threading.Event ) -> None: - """Remove TaskRes from queue and add into State.""" - state = state_factory.state() + """Put TaskRes into State from a queue.""" while not f_stop.is_set(): - if queue.qsize(): - task_res = await queue.get() - state.store_task_res(task_res) - else: - await asyncio.sleep(0.1) + try: + taskres = queue.get(timeout=1.0) + state.store_task_res(taskres) + except Empty: + # queue is empty when timeout was triggered + pass -async def run( +def run( app_fn: Callable[[], ClientApp], backend_fn: Callable[[], Backend], nodes_mapping: NodeToPartitionMapping, @@ -184,9 +155,9 @@ async def run( node_states: Dict[int, NodeState], f_stop: asyncio.Event, ) -> None: - """Run the VCE async.""" - taskins_queue: "asyncio.Queue[TaskIns]" = asyncio.Queue(128) - taskres_queue: "asyncio.Queue[TaskRes]" = asyncio.Queue(128) + """Run the VCE.""" + taskins_queue: "Queue[TaskIns]" = Queue() + taskres_queue: "Queue[TaskRes]" = Queue() try: @@ -194,42 +165,48 @@ async def run( backend = backend_fn() # Build backend - await backend.build() + backend.build() # Add workers (they submit Messages to Backend) - worker_tasks = [ - asyncio.create_task( - worker( - app_fn, - taskins_queue, - taskres_queue, - node_states, - backend, - ) - ) - for _ in range(backend.num_workers) - ] - # Create producer (adds TaskIns into Queue) - taskins_producer = asyncio.create_task( - add_taskins_to_queue( + state = state_factory.state() + + extractor_th = threading.Thread( + target=add_taskins_to_queue, + args=( + state, taskins_queue, - state_factory, nodes_mapping, - backend, - worker_tasks, f_stop, - ) + ), ) + extractor_th.start() - taskres_consumer = asyncio.create_task( - put_taskres_into_state(taskres_queue, state_factory, f_stop) + injector_th = threading.Thread( + target=put_taskres_into_state, + args=( + state, + taskres_queue, + f_stop, + ), ) + injector_th.start() - # Wait for asyncio taks pulling/pushing TaskIns/TaskRes. - # These run forever until f_stop is set or until - # all worker (consumer) coroutines are completed. Workers - # also run forever and only end if an exception is raised. - await asyncio.gather(*(taskins_producer, taskres_consumer)) + with ThreadPoolExecutor() as executor: + _ = [ + executor.submit( + worker, + app_fn, + taskins_queue, + taskres_queue, + node_states, + backend, + f_stop, + ) + for _ in range(backend.num_workers) + ] + + extractor_th.join() + injector_th.join() except Exception as ex: @@ -244,18 +221,9 @@ async def run( raise RuntimeError("Simulation Engine crashed.") from ex finally: - # Produced task terminated, now cancel worker tasks - for w_t in worker_tasks: - _ = w_t.cancel() - - while not all(w_t.done() for w_t in worker_tasks): - log(DEBUG, "Terminating async workers...") - await asyncio.sleep(0.5) - - await asyncio.gather(*[w_t for w_t in worker_tasks if not w_t.done()]) # Terminate backend - await backend.terminate() + backend.terminate() # pylint: disable=too-many-arguments,unused-argument,too-many-locals,too-many-branches @@ -368,15 +336,13 @@ def _load() -> ClientApp: _ = app_fn() # Run main simulation loop - asyncio.run( - run( - app_fn, - backend_fn, - nodes_mapping, - state_factory, - node_states, - f_stop, - ) + run( + app_fn, + backend_fn, + nodes_mapping, + state_factory, + node_states, + f_stop, ) except LoadClientAppError as loadapp_ex: f_stop_delay = 10 diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py index c0bf506fd2b6..6c247b91a4ec 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py @@ -24,7 +24,7 @@ from pathlib import Path from time import sleep from typing import Dict, Optional, Set, Tuple -from unittest import IsolatedAsyncioTestCase +from unittest import TestCase from uuid import UUID from flwr.client.client_app import LoadClientAppError @@ -149,7 +149,7 @@ def start_and_shutdown( """Start Simulation Engine and terminate after specified number of seconds. Some tests need to be terminated by triggering externally an asyncio.Event. This - is enabled whtn passing `duration`>0. + is enabled when passing `duration`>0. """ f_stop = asyncio.Event() @@ -181,8 +181,8 @@ def start_and_shutdown( termination_th.join() -class AsyncTestFleetSimulationEngineRayBackend(IsolatedAsyncioTestCase): - """A basic class that enables testing asyncio functionalities.""" +class TestFleetSimulationEngineRayBackend(TestCase): + """A basic class that enables testing functionalities.""" def test_erroneous_no_supernodes_client_mapping(self) -> None: """Test with unset arguments.""" diff --git a/src/py/flwr/simulation/ray_transport/ray_actor.py b/src/py/flwr/simulation/ray_transport/ray_actor.py index 7afffb865334..b1c9d2b9c0c1 100644 --- a/src/py/flwr/simulation/ray_transport/ray_actor.py +++ b/src/py/flwr/simulation/ray_transport/ray_actor.py @@ -14,7 +14,6 @@ # ============================================================================== """Ray-based Flower Actor and ActorPool implementation.""" -import asyncio import threading from abc import ABC from logging import DEBUG, ERROR, WARNING @@ -411,9 +410,7 @@ def __init__( self.client_resources = client_resources # Queue of idle actors - self.pool: "asyncio.Queue[Type[VirtualClientEngineActor]]" = asyncio.Queue( - maxsize=1024 - ) + self.pool: List[VirtualClientEngineActor] = [] self.num_actors = 0 # Resolve arguments to pass during actor init @@ -427,38 +424,37 @@ def __init__( # Figure out how many actors can be created given the cluster resources # and the resources the user indicates each VirtualClient will need self.actors_capacity = pool_size_from_resources(client_resources) - self._future_to_actor: Dict[Any, Type[VirtualClientEngineActor]] = {} + self._future_to_actor: Dict[Any, VirtualClientEngineActor] = {} def is_actor_available(self) -> bool: """Return true if there is an idle actor.""" - return self.pool.qsize() > 0 + return len(self.pool) > 0 - async def add_actors_to_pool(self, num_actors: int) -> None: + def add_actors_to_pool(self, num_actors: int) -> None: """Add actors to the pool. This method may be executed also if new resources are added to your Ray cluster (e.g. you add a new node). """ for _ in range(num_actors): - await self.pool.put(self.create_actor_fn()) # type: ignore + self.pool.append(self.create_actor_fn()) # type: ignore self.num_actors += num_actors - async def terminate_all_actors(self) -> None: + def terminate_all_actors(self) -> None: """Terminate actors in pool.""" num_terminated = 0 - while self.pool.qsize(): - actor = await self.pool.get() + for actor in self.pool: actor.terminate.remote() # type: ignore num_terminated += 1 log(DEBUG, "Terminated %i actors", num_terminated) - async def submit( + def submit( self, actor_fn: Any, job: Tuple[ClientAppFn, Message, str, Context] ) -> Any: """On idle actor, submit job and return future.""" # Remove idle actor from pool - actor = await self.pool.get() + actor = self.pool.pop() # Submit job to actor app_fn, mssg, cid, context = job future = actor_fn(actor, app_fn, mssg, cid, context) @@ -467,18 +463,18 @@ async def submit( self._future_to_actor[future] = actor return future - async def add_actor_back_to_pool(self, future: Any) -> None: + def add_actor_back_to_pool(self, future: Any) -> None: """Ad actor assigned to run future back into the pool.""" actor = self._future_to_actor.pop(future) - await self.pool.put(actor) + self.pool.append(actor) - async def fetch_result_and_return_actor_to_pool( + def fetch_result_and_return_actor_to_pool( self, future: Any ) -> Tuple[Message, Context]: """Pull result given a future and add actor back to pool.""" - # Get actor that ran job - await self.add_actor_back_to_pool(future) # Retrieve result for object store # Instead of doing ray.get(future) we await it - _, out_mssg, updated_context = await future + _, out_mssg, updated_context = ray.get(future) + # Get actor that ran job + self.add_actor_back_to_pool(future) return out_mssg, updated_context From 78f137e66375f45b33e0b5d55acbe306052c649e Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Jul 2024 12:30:47 +0200 Subject: [PATCH 142/595] refactor(framework) Replace `asyncio.Event` with `threading.Event` (#3471) Co-authored-by: Heng Pan --- src/py/flwr/server/superlink/fleet/vce/vce_api.py | 9 ++++----- src/py/flwr/server/superlink/fleet/vce/vce_api_test.py | 9 ++++----- src/py/flwr/simulation/run_simulation.py | 6 +++--- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 4eeaa5197006..3c0b36e1ca3c 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -15,7 +15,6 @@ """Fleet Simulation Engine API.""" -import asyncio import json import sys import threading @@ -63,7 +62,7 @@ def worker( taskres_queue: "Queue[TaskRes]", node_states: Dict[int, NodeState], backend: Backend, - f_stop: asyncio.Event, + f_stop: threading.Event, ) -> None: """Get TaskIns from queue and pass it to an actor in the pool to execute it.""" while not f_stop.is_set(): @@ -123,7 +122,7 @@ def add_taskins_to_queue( state: State, queue: "Queue[TaskIns]", nodes_mapping: NodeToPartitionMapping, - f_stop: asyncio.Event, + f_stop: threading.Event, ) -> None: """Put TaskIns in a queue from State.""" while not f_stop.is_set(): @@ -153,7 +152,7 @@ def run( nodes_mapping: NodeToPartitionMapping, state_factory: StateFactory, node_states: Dict[int, NodeState], - f_stop: asyncio.Event, + f_stop: threading.Event, ) -> None: """Run the VCE.""" taskins_queue: "Queue[TaskIns]" = Queue() @@ -232,7 +231,7 @@ def start_vce( backend_name: str, backend_config_json_stream: str, app_dir: str, - f_stop: asyncio.Event, + f_stop: threading.Event, client_app: Optional[ClientApp] = None, client_app_attr: Optional[str] = None, num_supernodes: Optional[int] = None, diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py index 6c247b91a4ec..7d37f03f6ade 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py @@ -15,7 +15,6 @@ """Test Fleet Simulation Engine API.""" -import asyncio import threading import time from itertools import cycle @@ -46,7 +45,7 @@ from flwr.server.superlink.state import InMemoryState, StateFactory -def terminate_simulation(f_stop: asyncio.Event, sleep_duration: int) -> None: +def terminate_simulation(f_stop: threading.Event, sleep_duration: int) -> None: """Set event to terminate Simulation Engine after `sleep_duration` seconds.""" sleep(sleep_duration) f_stop.set() @@ -148,15 +147,15 @@ def start_and_shutdown( ) -> None: """Start Simulation Engine and terminate after specified number of seconds. - Some tests need to be terminated by triggering externally an asyncio.Event. This + Some tests need to be terminated by triggering externally an threading.Event. This is enabled when passing `duration`>0. """ - f_stop = asyncio.Event() + f_stop = threading.Event() if duration: # Setup thread that will set the f_stop event, triggering the termination of all - # asyncio logic in the Simulation Engine. It will also terminate the Backend. + # logic in the Simulation Engine. It will also terminate the Backend. termination_th = threading.Thread( target=terminate_simulation, args=(f_stop, duration) ) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 0ea917109363..1dd3806f850f 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -129,7 +129,7 @@ def run_serverapp_th( server_app_run_config: Dict[str, str], driver: Driver, app_dir: str, - f_stop: asyncio.Event, + f_stop: threading.Event, has_exception: threading.Event, enable_tf_gpu_growth: bool, delay_launch: int = 3, @@ -138,7 +138,7 @@ def run_serverapp_th( def server_th_with_start_checks( tf_gpu_growth: bool, - stop_event: asyncio.Event, + stop_event: threading.Event, exception_event: threading.Event, _driver: Driver, _server_app_dir: str, @@ -225,7 +225,7 @@ def _main_loop( # Initialize StateFactory state_factory = StateFactory(":flwr-in-memory-state:") - f_stop = asyncio.Event() + f_stop = threading.Event() # A Threading event to indicate if an exception was raised in the ServerApp thread server_app_thread_has_exception = threading.Event() serverapp_th = None From ed566a7b21707e07655ee3d1437fefd30ab98a09 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Jul 2024 12:52:29 +0200 Subject: [PATCH 143/595] refactor(framework) Stop launching simulation in thread if asyncio loop detected (#3472) Co-authored-by: Heng Pan --- src/py/flwr/simulation/run_simulation.py | 29 ++++++++---------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 1dd3806f850f..de101fe3e09f 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -216,12 +216,7 @@ def _main_loop( server_app: Optional[ServerApp] = None, server_app_attr: Optional[str] = None, ) -> None: - """Launch SuperLink with Simulation Engine, then ServerApp on a separate thread. - - Everything runs on the main thread or a separate one, depending on whether the main - thread already contains a running Asyncio event loop. This is the case if running - the Simulation Engine on a Jupyter/Colab notebook. - """ + """Launch SuperLink with Simulation Engine, then ServerApp on a separate thread.""" # Initialize StateFactory state_factory = StateFactory(":flwr-in-memory-state:") @@ -376,7 +371,6 @@ def _run_simulation( # Convert config to original JSON-stream format backend_config_stream = json.dumps(backend_config) - simulation_engine_th = None args = ( num_supernodes, backend_name, @@ -390,31 +384,26 @@ def _run_simulation( server_app_attr, ) # Detect if there is an Asyncio event loop already running. - # If yes, run everything on a separate thread. In environments - # like Jupyter/Colab notebooks, there is an event loop present. - run_in_thread = False + # If yes, disable logger propagation. In environmnets + # like Jupyter/Colab notebooks, it's often better to do this. + asyncio_loop_running = False try: _ = ( asyncio.get_running_loop() ) # Raises RuntimeError if no event loop is present log(DEBUG, "Asyncio event loop already running.") - run_in_thread = True + asyncio_loop_running = True except RuntimeError: - log(DEBUG, "No asyncio event loop running") + pass finally: - if run_in_thread: + if asyncio_loop_running: # Set logger propagation to False to prevent duplicated log output in Colab. logger = set_logger_propagation(logger, False) - log(DEBUG, "Starting Simulation Engine on a new thread.") - simulation_engine_th = threading.Thread(target=_main_loop, args=args) - simulation_engine_th.start() - simulation_engine_th.join() - else: - log(DEBUG, "Starting Simulation Engine on the main thread.") - _main_loop(*args) + + _main_loop(*args) def _parse_args_run_simulation() -> argparse.ArgumentParser: From bcb3740af9ce0092f844278177e7fa5c7fb0ebb0 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Thu, 11 Jul 2024 14:08:21 +0100 Subject: [PATCH 144/595] fix(framework) Exclude incompatible `grpcio` versions (#3772) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8bb780351bf..c5ab0e5edcee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ flower-simulation = "flwr.simulation.run_simulation:run_simulation_from_cli" python = "^3.8" # Mandatory dependencies numpy = "^1.21.0" -grpcio = "^1.60.0" +grpcio = "^1.60.0,!=1.64.2,!=1.65.0" protobuf = "^4.25.2" cryptography = "^42.0.4" pycryptodome = "^3.18.0" From a1f9fc2b8ee350bdd41a6a1a0b75bb42b90898de Mon Sep 17 00:00:00 2001 From: Daniel Nata Nugraha Date: Thu, 11 Jul 2024 15:17:19 +0200 Subject: [PATCH 145/595] ci(*:skip) Add framework- prefix to e2e directories (#3757) --- .github/workflows/e2e.yml | 16 ++++++++-------- e2e/{fastai => framework-fastai}/README.md | 0 e2e/{fastai => framework-fastai}/client.py | 0 e2e/{fastai => framework-fastai}/pyproject.toml | 0 e2e/{fastai => framework-fastai}/simulation.py | 0 e2e/{jax => framework-jax}/README.md | 0 e2e/{jax => framework-jax}/client.py | 0 e2e/{jax => framework-jax}/jax_training.py | 0 e2e/{jax => framework-jax}/pyproject.toml | 0 e2e/{jax => framework-jax}/simulation.py | 0 e2e/{opacus => framework-opacus}/.gitignore | 0 e2e/{opacus => framework-opacus}/README.md | 0 e2e/{opacus => framework-opacus}/client.py | 0 e2e/{opacus => framework-opacus}/pyproject.toml | 0 e2e/{opacus => framework-opacus}/simulation.py | 0 e2e/{pandas => framework-pandas}/README.md | 0 e2e/{pandas => framework-pandas}/client.py | 0 e2e/{pandas => framework-pandas}/pyproject.toml | 0 e2e/{pandas => framework-pandas}/server.py | 0 e2e/{pandas => framework-pandas}/simulation.py | 0 e2e/{pandas => framework-pandas}/strategy.py | 0 .../README.md | 0 .../client.py | 0 .../mnist.py | 0 .../pyproject.toml | 0 .../simulation.py | 0 e2e/{pytorch => framework-pytorch}/README.md | 0 e2e/{pytorch => framework-pytorch}/client.py | 0 .../pyproject.toml | 0 e2e/{pytorch => framework-pytorch}/simulation.py | 0 .../simulation_next.py | 0 .../README.md | 0 .../client.py | 0 .../pyproject.toml | 0 .../simulation.py | 0 .../utils.py | 0 .../README.md | 0 .../client.py | 0 .../pyproject.toml | 0 .../simulation.py | 0 .../simulation_next.py | 0 e2e/test_legacy.sh | 2 +- e2e/test_superlink.sh | 2 +- 43 files changed, 10 insertions(+), 10 deletions(-) rename e2e/{fastai => framework-fastai}/README.md (100%) rename e2e/{fastai => framework-fastai}/client.py (100%) rename e2e/{fastai => framework-fastai}/pyproject.toml (100%) rename e2e/{fastai => framework-fastai}/simulation.py (100%) rename e2e/{jax => framework-jax}/README.md (100%) rename e2e/{jax => framework-jax}/client.py (100%) rename e2e/{jax => framework-jax}/jax_training.py (100%) rename e2e/{jax => framework-jax}/pyproject.toml (100%) rename e2e/{jax => framework-jax}/simulation.py (100%) rename e2e/{opacus => framework-opacus}/.gitignore (100%) rename e2e/{opacus => framework-opacus}/README.md (100%) rename e2e/{opacus => framework-opacus}/client.py (100%) rename e2e/{opacus => framework-opacus}/pyproject.toml (100%) rename e2e/{opacus => framework-opacus}/simulation.py (100%) rename e2e/{pandas => framework-pandas}/README.md (100%) rename e2e/{pandas => framework-pandas}/client.py (100%) rename e2e/{pandas => framework-pandas}/pyproject.toml (100%) rename e2e/{pandas => framework-pandas}/server.py (100%) rename e2e/{pandas => framework-pandas}/simulation.py (100%) rename e2e/{pandas => framework-pandas}/strategy.py (100%) rename e2e/{pytorch-lightning => framework-pytorch-lightning}/README.md (100%) rename e2e/{pytorch-lightning => framework-pytorch-lightning}/client.py (100%) rename e2e/{pytorch-lightning => framework-pytorch-lightning}/mnist.py (100%) rename e2e/{pytorch-lightning => framework-pytorch-lightning}/pyproject.toml (100%) rename e2e/{pytorch-lightning => framework-pytorch-lightning}/simulation.py (100%) rename e2e/{pytorch => framework-pytorch}/README.md (100%) rename e2e/{pytorch => framework-pytorch}/client.py (100%) rename e2e/{pytorch => framework-pytorch}/pyproject.toml (100%) rename e2e/{pytorch => framework-pytorch}/simulation.py (100%) rename e2e/{pytorch => framework-pytorch}/simulation_next.py (100%) rename e2e/{scikit-learn => framework-scikit-learn}/README.md (100%) rename e2e/{scikit-learn => framework-scikit-learn}/client.py (100%) rename e2e/{scikit-learn => framework-scikit-learn}/pyproject.toml (100%) rename e2e/{scikit-learn => framework-scikit-learn}/simulation.py (100%) rename e2e/{scikit-learn => framework-scikit-learn}/utils.py (100%) rename e2e/{tensorflow => framework-tensorflow}/README.md (100%) rename e2e/{tensorflow => framework-tensorflow}/client.py (100%) rename e2e/{tensorflow => framework-tensorflow}/pyproject.toml (100%) rename e2e/{tensorflow => framework-tensorflow}/simulation.py (100%) rename e2e/{tensorflow => framework-tensorflow}/simulation_next.py (100%) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d015f1387b17..11c0073fcf1f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -66,39 +66,39 @@ jobs: - directory: bare-client-auth - - directory: jax + - directory: framework-jax - - directory: pytorch + - directory: framework-pytorch dataset: | from torchvision.datasets import CIFAR10 CIFAR10('./data', download=True) - - directory: tensorflow + - directory: framework-tensorflow dataset: | import tensorflow as tf tf.keras.datasets.cifar10.load_data() - - directory: opacus + - directory: framework-opacus dataset: | from torchvision.datasets import CIFAR10 CIFAR10('./data', download=True) - - directory: pytorch-lightning + - directory: framework-pytorch-lightning dataset: | from torchvision.datasets import MNIST MNIST('./data', download=True) - - directory: scikit-learn + - directory: framework-scikit-learn dataset: | import openml openml.datasets.get_dataset(554) - - directory: fastai + - directory: framework-fastai dataset: | from fastai.vision.all import untar_data, URLs untar_data(URLs.MNIST) - - directory: pandas + - directory: framework-pandas dataset: | from pathlib import Path from sklearn.datasets import load_iris diff --git a/e2e/fastai/README.md b/e2e/framework-fastai/README.md similarity index 100% rename from e2e/fastai/README.md rename to e2e/framework-fastai/README.md diff --git a/e2e/fastai/client.py b/e2e/framework-fastai/client.py similarity index 100% rename from e2e/fastai/client.py rename to e2e/framework-fastai/client.py diff --git a/e2e/fastai/pyproject.toml b/e2e/framework-fastai/pyproject.toml similarity index 100% rename from e2e/fastai/pyproject.toml rename to e2e/framework-fastai/pyproject.toml diff --git a/e2e/fastai/simulation.py b/e2e/framework-fastai/simulation.py similarity index 100% rename from e2e/fastai/simulation.py rename to e2e/framework-fastai/simulation.py diff --git a/e2e/jax/README.md b/e2e/framework-jax/README.md similarity index 100% rename from e2e/jax/README.md rename to e2e/framework-jax/README.md diff --git a/e2e/jax/client.py b/e2e/framework-jax/client.py similarity index 100% rename from e2e/jax/client.py rename to e2e/framework-jax/client.py diff --git a/e2e/jax/jax_training.py b/e2e/framework-jax/jax_training.py similarity index 100% rename from e2e/jax/jax_training.py rename to e2e/framework-jax/jax_training.py diff --git a/e2e/jax/pyproject.toml b/e2e/framework-jax/pyproject.toml similarity index 100% rename from e2e/jax/pyproject.toml rename to e2e/framework-jax/pyproject.toml diff --git a/e2e/jax/simulation.py b/e2e/framework-jax/simulation.py similarity index 100% rename from e2e/jax/simulation.py rename to e2e/framework-jax/simulation.py diff --git a/e2e/opacus/.gitignore b/e2e/framework-opacus/.gitignore similarity index 100% rename from e2e/opacus/.gitignore rename to e2e/framework-opacus/.gitignore diff --git a/e2e/opacus/README.md b/e2e/framework-opacus/README.md similarity index 100% rename from e2e/opacus/README.md rename to e2e/framework-opacus/README.md diff --git a/e2e/opacus/client.py b/e2e/framework-opacus/client.py similarity index 100% rename from e2e/opacus/client.py rename to e2e/framework-opacus/client.py diff --git a/e2e/opacus/pyproject.toml b/e2e/framework-opacus/pyproject.toml similarity index 100% rename from e2e/opacus/pyproject.toml rename to e2e/framework-opacus/pyproject.toml diff --git a/e2e/opacus/simulation.py b/e2e/framework-opacus/simulation.py similarity index 100% rename from e2e/opacus/simulation.py rename to e2e/framework-opacus/simulation.py diff --git a/e2e/pandas/README.md b/e2e/framework-pandas/README.md similarity index 100% rename from e2e/pandas/README.md rename to e2e/framework-pandas/README.md diff --git a/e2e/pandas/client.py b/e2e/framework-pandas/client.py similarity index 100% rename from e2e/pandas/client.py rename to e2e/framework-pandas/client.py diff --git a/e2e/pandas/pyproject.toml b/e2e/framework-pandas/pyproject.toml similarity index 100% rename from e2e/pandas/pyproject.toml rename to e2e/framework-pandas/pyproject.toml diff --git a/e2e/pandas/server.py b/e2e/framework-pandas/server.py similarity index 100% rename from e2e/pandas/server.py rename to e2e/framework-pandas/server.py diff --git a/e2e/pandas/simulation.py b/e2e/framework-pandas/simulation.py similarity index 100% rename from e2e/pandas/simulation.py rename to e2e/framework-pandas/simulation.py diff --git a/e2e/pandas/strategy.py b/e2e/framework-pandas/strategy.py similarity index 100% rename from e2e/pandas/strategy.py rename to e2e/framework-pandas/strategy.py diff --git a/e2e/pytorch-lightning/README.md b/e2e/framework-pytorch-lightning/README.md similarity index 100% rename from e2e/pytorch-lightning/README.md rename to e2e/framework-pytorch-lightning/README.md diff --git a/e2e/pytorch-lightning/client.py b/e2e/framework-pytorch-lightning/client.py similarity index 100% rename from e2e/pytorch-lightning/client.py rename to e2e/framework-pytorch-lightning/client.py diff --git a/e2e/pytorch-lightning/mnist.py b/e2e/framework-pytorch-lightning/mnist.py similarity index 100% rename from e2e/pytorch-lightning/mnist.py rename to e2e/framework-pytorch-lightning/mnist.py diff --git a/e2e/pytorch-lightning/pyproject.toml b/e2e/framework-pytorch-lightning/pyproject.toml similarity index 100% rename from e2e/pytorch-lightning/pyproject.toml rename to e2e/framework-pytorch-lightning/pyproject.toml diff --git a/e2e/pytorch-lightning/simulation.py b/e2e/framework-pytorch-lightning/simulation.py similarity index 100% rename from e2e/pytorch-lightning/simulation.py rename to e2e/framework-pytorch-lightning/simulation.py diff --git a/e2e/pytorch/README.md b/e2e/framework-pytorch/README.md similarity index 100% rename from e2e/pytorch/README.md rename to e2e/framework-pytorch/README.md diff --git a/e2e/pytorch/client.py b/e2e/framework-pytorch/client.py similarity index 100% rename from e2e/pytorch/client.py rename to e2e/framework-pytorch/client.py diff --git a/e2e/pytorch/pyproject.toml b/e2e/framework-pytorch/pyproject.toml similarity index 100% rename from e2e/pytorch/pyproject.toml rename to e2e/framework-pytorch/pyproject.toml diff --git a/e2e/pytorch/simulation.py b/e2e/framework-pytorch/simulation.py similarity index 100% rename from e2e/pytorch/simulation.py rename to e2e/framework-pytorch/simulation.py diff --git a/e2e/pytorch/simulation_next.py b/e2e/framework-pytorch/simulation_next.py similarity index 100% rename from e2e/pytorch/simulation_next.py rename to e2e/framework-pytorch/simulation_next.py diff --git a/e2e/scikit-learn/README.md b/e2e/framework-scikit-learn/README.md similarity index 100% rename from e2e/scikit-learn/README.md rename to e2e/framework-scikit-learn/README.md diff --git a/e2e/scikit-learn/client.py b/e2e/framework-scikit-learn/client.py similarity index 100% rename from e2e/scikit-learn/client.py rename to e2e/framework-scikit-learn/client.py diff --git a/e2e/scikit-learn/pyproject.toml b/e2e/framework-scikit-learn/pyproject.toml similarity index 100% rename from e2e/scikit-learn/pyproject.toml rename to e2e/framework-scikit-learn/pyproject.toml diff --git a/e2e/scikit-learn/simulation.py b/e2e/framework-scikit-learn/simulation.py similarity index 100% rename from e2e/scikit-learn/simulation.py rename to e2e/framework-scikit-learn/simulation.py diff --git a/e2e/scikit-learn/utils.py b/e2e/framework-scikit-learn/utils.py similarity index 100% rename from e2e/scikit-learn/utils.py rename to e2e/framework-scikit-learn/utils.py diff --git a/e2e/tensorflow/README.md b/e2e/framework-tensorflow/README.md similarity index 100% rename from e2e/tensorflow/README.md rename to e2e/framework-tensorflow/README.md diff --git a/e2e/tensorflow/client.py b/e2e/framework-tensorflow/client.py similarity index 100% rename from e2e/tensorflow/client.py rename to e2e/framework-tensorflow/client.py diff --git a/e2e/tensorflow/pyproject.toml b/e2e/framework-tensorflow/pyproject.toml similarity index 100% rename from e2e/tensorflow/pyproject.toml rename to e2e/framework-tensorflow/pyproject.toml diff --git a/e2e/tensorflow/simulation.py b/e2e/framework-tensorflow/simulation.py similarity index 100% rename from e2e/tensorflow/simulation.py rename to e2e/framework-tensorflow/simulation.py diff --git a/e2e/tensorflow/simulation_next.py b/e2e/framework-tensorflow/simulation_next.py similarity index 100% rename from e2e/tensorflow/simulation_next.py rename to e2e/framework-tensorflow/simulation_next.py diff --git a/e2e/test_legacy.sh b/e2e/test_legacy.sh index 4ea17a4f994b..dc17ca8c6378 100755 --- a/e2e/test_legacy.sh +++ b/e2e/test_legacy.sh @@ -2,7 +2,7 @@ set -e case "$1" in - pandas) + framework-pandas) server_file="server.py" ;; bare-https) diff --git a/e2e/test_superlink.sh b/e2e/test_superlink.sh index 3df33eb5c444..1bb81cc47ea1 100755 --- a/e2e/test_superlink.sh +++ b/e2e/test_superlink.sh @@ -2,7 +2,7 @@ set -e case "$1" in - pandas) + framework-pandas) server_arg="--insecure" client_arg="--insecure" server_dir="./" From 2570af5f6bd2645e32e9ad29f6827c9b551e6578 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Jul 2024 15:43:32 +0200 Subject: [PATCH 146/595] feat(framework) Introduce `ServerAppComponents` dataclass (#3771) --- src/py/flwr/server/__init__.py | 2 + src/py/flwr/server/serverapp_components.py | 52 ++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/py/flwr/server/serverapp_components.py diff --git a/src/py/flwr/server/__init__.py b/src/py/flwr/server/__init__.py index 546ce263e2d5..896b46298327 100644 --- a/src/py/flwr/server/__init__.py +++ b/src/py/flwr/server/__init__.py @@ -28,6 +28,7 @@ from .server import Server as Server from .server_app import ServerApp as ServerApp from .server_config import ServerConfig as ServerConfig +from .serverapp_components import ServerAppComponents as ServerAppComponents __all__ = [ "ClientManager", @@ -36,6 +37,7 @@ "LegacyContext", "Server", "ServerApp", + "ServerAppComponents", "ServerConfig", "SimpleClientManager", "run_server_app", diff --git a/src/py/flwr/server/serverapp_components.py b/src/py/flwr/server/serverapp_components.py new file mode 100644 index 000000000000..315f0a889a61 --- /dev/null +++ b/src/py/flwr/server/serverapp_components.py @@ -0,0 +1,52 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""ServerAppComponents for the ServerApp.""" + + +from dataclasses import dataclass +from typing import Optional + +from .client_manager import ClientManager +from .server import Server +from .server_config import ServerConfig +from .strategy import Strategy + + +@dataclass +class ServerAppComponents: # pylint: disable=too-many-instance-attributes + """Components to construct a ServerApp. + + Parameters + ---------- + server : Optional[Server] (default: None) + A server implementation, either `flwr.server.Server` or a subclass + thereof. If no instance is provided, one will be created internally. + config : Optional[ServerConfig] (default: None) + Currently supported values are `num_rounds` (int, default: 1) and + `round_timeout` in seconds (float, default: None). + strategy : Optional[Strategy] (default: None) + An implementation of the abstract base class + `flwr.server.strategy.Strategy`. If no strategy is provided, then + `flwr.server.strategy.FedAvg` will be used. + client_manager : Optional[ClientManager] (default: None) + An implementation of the class `flwr.server.ClientManager`. If no + implementation is provided, then `flwr.server.SimpleClientManager` + will be used. + """ + + server: Optional[Server] = None + config: Optional[ServerConfig] = None + strategy: Optional[Strategy] = None + client_manager: Optional[ClientManager] = None From 2f2e346aa9e91576f65ecb23806e9a321bbf70a0 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 11 Jul 2024 16:48:11 +0200 Subject: [PATCH 147/595] feat(framework) Add `run_config` to `ClientApp` `Context` (#3751) Co-authored-by: Daniel J. Beutel --- src/py/flwr/client/app.py | 18 +++++++---- src/py/flwr/client/node_state.py | 44 +++++++++++++++++++++----- src/py/flwr/client/node_state_tests.py | 5 +-- src/py/flwr/client/supernode/app.py | 1 + 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 15d384cb74a2..851083d4abb7 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -19,6 +19,7 @@ import time from dataclasses import dataclass from logging import DEBUG, ERROR, INFO, WARN +from pathlib import Path from typing import Callable, ContextManager, Dict, Optional, Tuple, Type, Union from cryptography.hazmat.primitives.asymmetric import ec @@ -193,6 +194,7 @@ def _start_client_internal( max_retries: Optional[int] = None, max_wait_time: Optional[float] = None, partition_id: Optional[int] = None, + flwr_dir: Optional[Path] = None, ) -> None: """Start a Flower client node which connects to a Flower server. @@ -239,6 +241,8 @@ class `flwr.client.Client` (default: None) partition_id: Optional[int] (default: None) The data partition index associated with this node. Better suited for prototyping purposes. + flwr_dir: Optional[Path] (default: None) + The fully resolved path containing installed Flower Apps. """ if insecure is None: insecure = root_certificates is None @@ -316,7 +320,7 @@ def _on_backoff(retry_state: RetryState) -> None: ) node_state = NodeState(partition_id=partition_id) - run_info: Dict[int, Run] = {} + runs: Dict[int, Run] = {} while not app_state_tracker.interrupt: sleep_duration: int = 0 @@ -366,15 +370,17 @@ def _on_backoff(retry_state: RetryState) -> None: # Get run info run_id = message.metadata.run_id - if run_id not in run_info: + if run_id not in runs: if get_run is not None: - run_info[run_id] = get_run(run_id) + runs[run_id] = get_run(run_id) # If get_run is None, i.e., in grpc-bidi mode else: - run_info[run_id] = Run(run_id, "", "", {}) + runs[run_id] = Run(run_id, "", "", {}) # Register context for this run - node_state.register_context(run_id=run_id) + node_state.register_context( + run_id=run_id, run=runs[run_id], flwr_dir=flwr_dir + ) # Retrieve context for this run context = node_state.retrieve_context(run_id=run_id) @@ -388,7 +394,7 @@ def _on_backoff(retry_state: RetryState) -> None: # Handle app loading and task message try: # Load ClientApp instance - run: Run = run_info[run_id] + run: Run = runs[run_id] client_app: ClientApp = load_client_app_fn( run.fab_id, run.fab_version ) diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 64a0d348b23e..2b090eba9720 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -15,9 +15,21 @@ """Node state.""" +from dataclasses import dataclass +from pathlib import Path from typing import Any, Dict, Optional from flwr.common import Context, RecordSet +from flwr.common.config import get_fused_config +from flwr.common.typing import Run + + +@dataclass() +class RunInfo: + """Contains the Context and initial run_config of a Run.""" + + context: Context + initial_run_config: Dict[str, str] class NodeState: @@ -25,20 +37,31 @@ class NodeState: def __init__(self, partition_id: Optional[int]) -> None: self._meta: Dict[str, Any] = {} # holds metadata about the node - self.run_contexts: Dict[int, Context] = {} + self.run_infos: Dict[int, RunInfo] = {} self._partition_id = partition_id - def register_context(self, run_id: int) -> None: + def register_context( + self, + run_id: int, + run: Optional[Run] = None, + flwr_dir: Optional[Path] = None, + ) -> None: """Register new run context for this node.""" - if run_id not in self.run_contexts: - self.run_contexts[run_id] = Context( - state=RecordSet(), run_config={}, partition_id=self._partition_id + if run_id not in self.run_infos: + initial_run_config = get_fused_config(run, flwr_dir) if run else {} + self.run_infos[run_id] = RunInfo( + initial_run_config=initial_run_config, + context=Context( + state=RecordSet(), + run_config=initial_run_config.copy(), + partition_id=self._partition_id, + ), ) def retrieve_context(self, run_id: int) -> Context: """Get run context given a run_id.""" - if run_id in self.run_contexts: - return self.run_contexts[run_id] + if run_id in self.run_infos: + return self.run_infos[run_id].context raise RuntimeError( f"Context for run_id={run_id} doesn't exist." @@ -48,4 +71,9 @@ def retrieve_context(self, run_id: int) -> Context: def update_context(self, run_id: int, context: Context) -> None: """Update run context.""" - self.run_contexts[run_id] = context + if context.run_config != self.run_infos[run_id].initial_run_config: + raise ValueError( + "The `run_config` field of the `Context` object cannot be " + f"modified (run_id: {run_id})." + ) + self.run_infos[run_id].context = context diff --git a/src/py/flwr/client/node_state_tests.py b/src/py/flwr/client/node_state_tests.py index 311dbd41d742..effd64a3ae7a 100644 --- a/src/py/flwr/client/node_state_tests.py +++ b/src/py/flwr/client/node_state_tests.py @@ -59,7 +59,8 @@ def test_multirun_in_node_state() -> None: node_state.update_context(run_id=run_id, context=updated_state) # Verify values - for run_id, context in node_state.run_contexts.items(): + for run_id, run_info in node_state.run_infos.items(): assert ( - context.state.configs_records["counter"]["count"] == expected_values[run_id] + run_info.context.state.configs_records["counter"]["count"] + == expected_values[run_id] ) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 4115c57d4738..355a2a13a0e5 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -68,6 +68,7 @@ def run_supernode() -> None: max_retries=args.max_retries, max_wait_time=args.max_wait_time, partition_id=args.partition_id, + flwr_dir=get_flwr_dir(args.flwr_dir), ) # Graceful shutdown From ac98491e6d81ab9f79f205672490b17d4f7ef6a1 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Jul 2024 19:24:19 +0200 Subject: [PATCH 148/595] feat(framework) Introduce a new deprecation function that warns users and provides an code example (#3776) Co-authored-by: Daniel J. Beutel --- src/py/flwr/common/logger.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/py/flwr/common/logger.py b/src/py/flwr/common/logger.py index f36ea2c1ce27..2077f9beaca0 100644 --- a/src/py/flwr/common/logger.py +++ b/src/py/flwr/common/logger.py @@ -197,6 +197,31 @@ def warn_deprecated_feature(name: str) -> None: ) +def warn_deprecated_feature_with_example( + deprecation_message: str, example_message: str, code_example: str +) -> None: + """Warn if a feature is deprecated and show code example.""" + log( + WARN, + """DEPRECATED FEATURE: %s + + Check the following `FEATURE UPDATE` warning message for the preferred + new mechanism to use this feature in Flower. + """, + deprecation_message, + ) + log( + WARN, + """FEATURE UPDATE: %s + ------------------------------------------------------------ + %s + ------------------------------------------------------------ + """, + example_message, + code_example, + ) + + def warn_unsupported_feature(name: str) -> None: """Warn the user when they use an unsupported feature.""" log( From ea01fd1f4557c38119fbbe74de356f2f384eeb49 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 11 Jul 2024 21:09:46 +0200 Subject: [PATCH 149/595] feat(framework) Add run configs (#3725) --- src/py/flwr/cli/config_utils.py | 10 ++++++++++ src/py/flwr/cli/run/run.py | 33 +++++++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index d06a1d6dba96..33bf12e34b04 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -108,6 +108,14 @@ def load(path: Optional[Path] = None) -> Optional[Dict[str, Any]]: return load_from_string(toml_file.read()) +def _validate_run_config(config_dict: Dict[str, Any], errors: List[str]) -> None: + for key, value in config_dict.items(): + if isinstance(value, dict): + _validate_run_config(config_dict[key], errors) + elif not isinstance(value, str): + errors.append(f"Config value of key {key} is not of type `str`.") + + # pylint: disable=too-many-branches def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]]: """Validate pyproject.toml fields.""" @@ -133,6 +141,8 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]] else: if "publisher" not in config["flower"]: errors.append('Property "publisher" missing in [flower]') + if "config" in config["flower"]: + _validate_run_config(config["flower"]["config"], errors) if "components" not in config["flower"]: errors.append("Missing [flower.components] section") else: diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index f5882bd14ab8..4ee2368f5794 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -18,13 +18,14 @@ from enum import Enum from logging import DEBUG from pathlib import Path -from typing import Optional +from typing import Dict, Optional import typer from typing_extensions import Annotated from flwr.cli import config_utils from flwr.cli.build import build +from flwr.common.config import parse_config_args from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel from flwr.common.logger import log @@ -58,15 +59,20 @@ def run( Optional[Path], typer.Option(help="Path of the Flower project to run"), ] = None, + config_overrides: Annotated[ + Optional[str], + typer.Option( + "--config", + "-c", + help="Override configuration key-value pairs", + ), + ] = None, ) -> None: """Run Flower project.""" - if use_superexec: - _start_superexec_run(directory) - return - typer.secho("Loading project configuration... ", fg=typer.colors.BLUE) - config, errors, warnings = config_utils.load_and_validate() + pyproject_path = directory / "pyproject.toml" if directory else None + config, errors, warnings = config_utils.load_and_validate(path=pyproject_path) if config is None: typer.secho( @@ -88,6 +94,12 @@ def run( typer.secho("Success", fg=typer.colors.GREEN) + if use_superexec: + _start_superexec_run( + parse_config_args(config_overrides, separator=","), directory + ) + return + server_app_ref = config["flower"]["components"]["serverapp"] client_app_ref = config["flower"]["components"]["clientapp"] @@ -115,7 +127,9 @@ def run( ) -def _start_superexec_run(directory: Optional[Path]) -> None: +def _start_superexec_run( + override_config: Dict[str, str], directory: Optional[Path] +) -> None: def on_channel_state_change(channel_connectivity: str) -> None: """Log channel connectivity.""" log(DEBUG, channel_connectivity) @@ -132,6 +146,9 @@ def on_channel_state_change(channel_connectivity: str) -> None: fab_path = build(directory) - req = StartRunRequest(fab_file=Path(fab_path).read_bytes()) + req = StartRunRequest( + fab_file=Path(fab_path).read_bytes(), + override_config=override_config, + ) res = stub.StartRun(req) typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN) From 67bbd4d32a1fbce7d610c7e056eab20ac3bf319e Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 11 Jul 2024 21:25:10 +0200 Subject: [PATCH 150/595] feat(framework) Introduce `server_fn` to setup `ServerApp` (#3773) Co-authored-by: Daniel J. Beutel --- src/py/flwr/server/server_app.py | 66 +++++++++++++++++++++++++++----- src/py/flwr/server/typing.py | 2 + 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/py/flwr/server/server_app.py b/src/py/flwr/server/server_app.py index f19a9d91986b..e9cb4ddcaf0d 100644 --- a/src/py/flwr/server/server_app.py +++ b/src/py/flwr/server/server_app.py @@ -17,8 +17,11 @@ from typing import Callable, Optional -from flwr.common import Context, RecordSet -from flwr.common.logger import warn_preview_feature +from flwr.common import Context +from flwr.common.logger import ( + warn_deprecated_feature_with_example, + warn_preview_feature, +) from flwr.server.strategy import Strategy from .client_manager import ClientManager @@ -26,7 +29,20 @@ from .driver import Driver from .server import Server from .server_config import ServerConfig -from .typing import ServerAppCallable +from .typing import ServerAppCallable, ServerFn + +SERVER_FN_USAGE_EXAMPLE = """ + + def server_fn(context: Context): + server_config = ServerConfig(num_rounds=3) + strategy = FedAvg() + return ServerAppComponents( + strategy=strategy, + server_config=server_config, + ) + + app = ServerApp(server_fn=server_fn) +""" class ServerApp: @@ -36,13 +52,15 @@ class ServerApp: -------- Use the `ServerApp` with an existing `Strategy`: - >>> server_config = ServerConfig(num_rounds=3) - >>> strategy = FedAvg() + >>> def server_fn(context: Context): + >>> server_config = ServerConfig(num_rounds=3) + >>> strategy = FedAvg() + >>> return ServerAppComponents( + >>> strategy=strategy, + >>> server_config=server_config, + >>> ) >>> - >>> app = ServerApp( - >>> server_config=server_config, - >>> strategy=strategy, - >>> ) + >>> app = ServerApp(server_fn=server_fn) Use the `ServerApp` with a custom main function: @@ -53,23 +71,52 @@ class ServerApp: >>> print("ServerApp running") """ + # pylint: disable=too-many-arguments def __init__( self, server: Optional[Server] = None, config: Optional[ServerConfig] = None, strategy: Optional[Strategy] = None, client_manager: Optional[ClientManager] = None, + server_fn: Optional[ServerFn] = None, ) -> None: + if any([server, config, strategy, client_manager]): + warn_deprecated_feature_with_example( + deprecation_message="Passing either `server`, `config`, `strategy` or " + "`client_manager` directly to the ServerApp " + "constructor is deprecated.", + example_message="Pass `ServerApp` arguments wrapped " + "in a `flwr.server.ServerAppComponents` object that gets " + "returned by a function passed as the `server_fn` argument " + "to the `ServerApp` constructor. For example: ", + code_example=SERVER_FN_USAGE_EXAMPLE, + ) + + if server_fn: + raise ValueError( + "Passing `server_fn` is incompatible with passing the " + "other arguments (now deprecated) to ServerApp. " + "Use `server_fn` exclusively." + ) + self._server = server self._config = config self._strategy = strategy self._client_manager = client_manager + self._server_fn = server_fn self._main: Optional[ServerAppCallable] = None def __call__(self, driver: Driver, context: Context) -> None: """Execute `ServerApp`.""" # Compatibility mode if not self._main: + if self._server_fn: + # Execute server_fn() + components = self._server_fn(context) + self._server = components.server + self._config = components.config + self._strategy = components.strategy + self._client_manager = components.client_manager start_driver( server=self._server, config=self._config, @@ -80,7 +127,6 @@ def __call__(self, driver: Driver, context: Context) -> None: return # New execution mode - context = Context(state=RecordSet(), run_config={}) self._main(driver, context) def main(self) -> Callable[[ServerAppCallable], ServerAppCallable]: diff --git a/src/py/flwr/server/typing.py b/src/py/flwr/server/typing.py index 01143af74392..cdb1c0db4fe7 100644 --- a/src/py/flwr/server/typing.py +++ b/src/py/flwr/server/typing.py @@ -20,6 +20,8 @@ from flwr.common import Context from .driver import Driver +from .serverapp_components import ServerAppComponents ServerAppCallable = Callable[[Driver, Context], None] Workflow = Callable[[Driver, Context], None] +ServerFn = Callable[[Context], ServerAppComponents] From 356e3f4b65d8d6a47816c1efafdb48622109af86 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:55:16 +0200 Subject: [PATCH 151/595] feat(datasets) Add pathological partitioner (#3623) Co-authored-by: jafermarq --- .../flwr_datasets/partitioner/__init__.py | 2 + .../partitioner/pathological_partitioner.py | 305 ++++++++++++++++++ .../pathological_partitioner_test.py | 262 +++++++++++++++ 3 files changed, 569 insertions(+) create mode 100644 datasets/flwr_datasets/partitioner/pathological_partitioner.py create mode 100644 datasets/flwr_datasets/partitioner/pathological_partitioner_test.py diff --git a/datasets/flwr_datasets/partitioner/__init__.py b/datasets/flwr_datasets/partitioner/__init__.py index 1fc00ed90323..0c75dbce387a 100644 --- a/datasets/flwr_datasets/partitioner/__init__.py +++ b/datasets/flwr_datasets/partitioner/__init__.py @@ -22,6 +22,7 @@ from .linear_partitioner import LinearPartitioner from .natural_id_partitioner import NaturalIdPartitioner from .partitioner import Partitioner +from .pathological_partitioner import PathologicalPartitioner from .shard_partitioner import ShardPartitioner from .size_partitioner import SizePartitioner from .square_partitioner import SquarePartitioner @@ -34,6 +35,7 @@ "LinearPartitioner", "NaturalIdPartitioner", "Partitioner", + "PathologicalPartitioner", "ShardPartitioner", "SizePartitioner", "SquarePartitioner", diff --git a/datasets/flwr_datasets/partitioner/pathological_partitioner.py b/datasets/flwr_datasets/partitioner/pathological_partitioner.py new file mode 100644 index 000000000000..1ee60d283044 --- /dev/null +++ b/datasets/flwr_datasets/partitioner/pathological_partitioner.py @@ -0,0 +1,305 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Pathological partitioner class that works with Hugging Face Datasets.""" + + +import warnings +from typing import Any, Dict, List, Literal, Optional + +import numpy as np + +import datasets +from flwr_datasets.common.typing import NDArray +from flwr_datasets.partitioner.partitioner import Partitioner + + +# pylint: disable=too-many-arguments, too-many-instance-attributes +class PathologicalPartitioner(Partitioner): + """Partition dataset such that each partition has a chosen number of classes. + + Implementation based on Federated Learning on Non-IID Data Silos: An Experimental + Study https://arxiv.org/pdf/2102.02079. + + The algorithm firstly determines which classe will be assigned to which partitions. + For each partition `num_classes_per_partition` are sampled in a way chosen in + `class_assignment_mode`. Given the information about the required classes for each + partition, it is determined into how many parts the samples corresponding to this + label should be divided. Such division is performed for each class. + + Parameters + ---------- + num_partitions : int + The total number of partitions that the data will be divided into. + partition_by : str + Column name of the labels (targets) based on which partitioning works. + num_classes_per_partition: int + The (exact) number of unique classes that each partition will have. + class_assignment_mode: Literal["random", "deterministic", "first-deterministic"] + The way how the classes are assigned to the partitions. The default is "random". + The possible values are: + + - "random": Randomly assign classes to the partitions. For each partition choose + the `num_classes_per_partition` classes without replacement. + - "first-deterministic": Assign the first class for each partition in a + deterministic way (class id is the partition_id % num_unique_classes). + The rest of the classes are assigned randomly. In case the number of + partitions is smaller than the number of unique classes, not all classes will + be used in the first iteration, otherwise all the classes will be used (such + it will be present in at least one partition). + - "deterministic": Assign all the classes to the partitions in a deterministic + way. Classes are assigned based on the formula: partion_id has classes + identified by the index: (partition_id + i) % num_unique_classes + where i in {0, ..., num_classes_per_partition}. So, partition 0 will have + classes 0, 1, 2, ..., `num_classes_per_partition`-1, partition 1 will have + classes 1, 2, 3, ...,`num_classes_per_partition`, .... + + The list representing the unique lables is sorted in ascending order. In case + of numbers starting from zero the class id corresponds to the number itself. + `class_assignment_mode="first-deterministic"` was used in the orginal paper, + here we provide the option to use the other modes as well. + shuffle: bool + Whether to randomize the order of samples. Shuffling applied after the + samples assignment to partitions. + seed: int + Seed used for dataset shuffling. It has no effect if `shuffle` is False. + + Examples + -------- + In order to mimic the original behavior of the paper follow the setup below + (the `class_assignment_mode="first-deterministic"`): + + >>> from flwr_datasets.partitioner import PathologicalPartitioner + >>> from flwr_datasets import FederatedDataset + >>> + >>> partitioner = PathologicalPartitioner( + >>> num_partitions=10, + >>> partition_by="label", + >>> num_classes_per_partition=2, + >>> class_assignment_mode="first-deterministic" + >>> ) + >>> fds = FederatedDataset(dataset="mnist", partitioners={"train": partitioner}) + >>> partition = fds.load_partition(0) + """ + + def __init__( + self, + num_partitions: int, + partition_by: str, + num_classes_per_partition: int, + class_assignment_mode: Literal[ + "random", "deterministic", "first-deterministic" + ] = "random", + shuffle: bool = True, + seed: Optional[int] = 42, + ) -> None: + super().__init__() + self._num_partitions = num_partitions + self._partition_by = partition_by + self._num_classes_per_partition = num_classes_per_partition + self._class_assignment_mode = class_assignment_mode + self._shuffle = shuffle + self._seed = seed + self._rng = np.random.default_rng(seed=self._seed) + + # Utility attributes + self._partition_id_to_indices: Dict[int, List[int]] = {} + self._partition_id_to_unique_labels: Dict[int, List[Any]] = { + pid: [] for pid in range(self._num_partitions) + } + self._unique_labels: List[Any] = [] + # Count in how many partitions the label is used + self._unique_label_to_times_used_counter: Dict[Any, int] = {} + self._partition_id_to_indices_determined = False + + def load_partition(self, partition_id: int) -> datasets.Dataset: + """Load a partition based on the partition index. + + Parameters + ---------- + partition_id : int + The index that corresponds to the requested partition. + + Returns + ------- + dataset_partition : Dataset + Single partition of a dataset. + """ + # The partitioning is done lazily - only when the first partition is + # requested. Only the first call creates the indices assignments for all the + # partition indices. + self._check_num_partitions_correctness_if_needed() + self._determine_partition_id_to_indices_if_needed() + return self.dataset.select(self._partition_id_to_indices[partition_id]) + + @property + def num_partitions(self) -> int: + """Total number of partitions.""" + self._check_num_partitions_correctness_if_needed() + self._determine_partition_id_to_indices_if_needed() + return self._num_partitions + + def _determine_partition_id_to_indices_if_needed(self) -> None: + """Create an assignment of indices to the partition indices.""" + if self._partition_id_to_indices_determined: + return + self._determine_partition_id_to_unique_labels() + assert self._unique_labels is not None + self._count_partitions_having_each_unique_label() + + labels = np.asarray(self.dataset[self._partition_by]) + self._check_correctness_of_unique_label_to_times_used_counter(labels) + for partition_id in range(self._num_partitions): + self._partition_id_to_indices[partition_id] = [] + + unused_labels = [] + for unique_label in self._unique_labels: + if self._unique_label_to_times_used_counter[unique_label] == 0: + unused_labels.append(unique_label) + continue + # Get the indices in the original dataset where the y == unique_label + unique_label_to_indices = np.where(labels == unique_label)[0] + + split_unique_labels_to_indices = np.array_split( + unique_label_to_indices, + self._unique_label_to_times_used_counter[unique_label], + ) + + split_index = 0 + for partition_id in range(self._num_partitions): + if unique_label in self._partition_id_to_unique_labels[partition_id]: + self._partition_id_to_indices[partition_id].extend( + split_unique_labels_to_indices[split_index] + ) + split_index += 1 + + if len(unused_labels) >= 1: + warnings.warn( + f"Classes: {unused_labels} will NOT be used due to the chosen " + f"configuration. If it is undesired behavior consider setting" + f" 'first_class_deterministic_assignment=True' which in case when" + f" the number of classes is smaller than the number of partitions will " + f"utilize all the classes for the created partitions.", + stacklevel=1, + ) + if self._shuffle: + for indices in self._partition_id_to_indices.values(): + # In place shuffling + self._rng.shuffle(indices) + + self._partition_id_to_indices_determined = True + + def _check_num_partitions_correctness_if_needed(self) -> None: + """Test num_partitions when the dataset is given (in load_partition).""" + if not self._partition_id_to_indices_determined: + if self._num_partitions > self.dataset.num_rows: + raise ValueError( + "The number of partitions needs to be smaller than the number of " + "samples in the dataset." + ) + + def _determine_partition_id_to_unique_labels(self) -> None: + """Determine the assignment of unique labels to the partitions.""" + self._unique_labels = sorted(self.dataset.unique(self._partition_by)) + num_unique_classes = len(self._unique_labels) + + if self._num_classes_per_partition > num_unique_classes: + raise ValueError( + f"The specified `num_classes_per_partition`" + f"={self._num_classes_per_partition} is greater than the number " + f"of unique classes in the given dataset={num_unique_classes}. " + f"Reduce the `num_classes_per_partition` or make use different dataset " + f"to apply this partitioning." + ) + if self._class_assignment_mode == "first-deterministic": + # if self._first_class_deterministic_assignment: + for partition_id in range(self._num_partitions): + label = partition_id % num_unique_classes + self._partition_id_to_unique_labels[partition_id].append(label) + + while ( + len(self._partition_id_to_unique_labels[partition_id]) + < self._num_classes_per_partition + ): + label = self._rng.choice(self._unique_labels, size=1)[0] + if label not in self._partition_id_to_unique_labels[partition_id]: + self._partition_id_to_unique_labels[partition_id].append(label) + elif self._class_assignment_mode == "deterministic": + for partition_id in range(self._num_partitions): + labels = [] + for i in range(self._num_classes_per_partition): + label = self._unique_labels[ + (partition_id + i) % len(self._unique_labels) + ] + labels.append(label) + self._partition_id_to_unique_labels[partition_id] = labels + elif self._class_assignment_mode == "random": + for partition_id in range(self._num_partitions): + labels = self._rng.choice( + self._unique_labels, + size=self._num_classes_per_partition, + replace=False, + ).tolist() + self._partition_id_to_unique_labels[partition_id] = labels + else: + raise ValueError( + f"The supported class_assignment_mode are: 'random', 'deterministic', " + f"'first-deterministic'. You provided: {self._class_assignment_mode}." + ) + + def _count_partitions_having_each_unique_label(self) -> None: + """Count the number of partitions that have each unique label. + + This computation is based on the assigment of the label to the partition_id in + the `_determine_partition_id_to_unique_labels` method. + Given: + * partition 0 has only labels: 0,1 (not necessarily just two samples it can have + many samples but either from 0 or 1) + * partition 1 has only labels: 1, 2 (same count note as above) + * and there are only two partitions then the following will be computed: + { + 0: 1, + 1: 2, + 2: 1 + } + """ + for unique_label in self._unique_labels: + self._unique_label_to_times_used_counter[unique_label] = 0 + for unique_labels in self._partition_id_to_unique_labels.values(): + for unique_label in unique_labels: + self._unique_label_to_times_used_counter[unique_label] += 1 + + def _check_correctness_of_unique_label_to_times_used_counter( + self, labels: NDArray + ) -> None: + """Check if partitioning is possible given the presence requirements. + + The number of times the label can be used must be smaller or equal to the number + of times that the label is present in the dataset. + """ + for unique_label in self._unique_labels: + num_unique = np.sum(labels == unique_label) + if self._unique_label_to_times_used_counter[unique_label] > num_unique: + raise ValueError( + f"Label: {unique_label} is needed to be assigned to more " + f"partitions " + f"({self._unique_label_to_times_used_counter[unique_label]})" + f" than there are samples (corresponding to this label) in the " + f"dataset ({num_unique}). Please decrease the `num_partitions`, " + f"`num_classes_per_partition` to avoid this situation, " + f"or try `class_assigment_mode='deterministic'` to create a more " + f"even distribution of classes along the partitions. " + f"Alternatively use a different dataset if you can not adjust" + f" the any of these parameters." + ) diff --git a/datasets/flwr_datasets/partitioner/pathological_partitioner_test.py b/datasets/flwr_datasets/partitioner/pathological_partitioner_test.py new file mode 100644 index 000000000000..151b7e14659c --- /dev/null +++ b/datasets/flwr_datasets/partitioner/pathological_partitioner_test.py @@ -0,0 +1,262 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Test cases for PathologicalPartitioner.""" + + +import unittest +from typing import Dict + +import numpy as np +from parameterized import parameterized + +import datasets +from datasets import Dataset +from flwr_datasets.partitioner.pathological_partitioner import PathologicalPartitioner + + +def _dummy_dataset_setup( + num_samples: int, partition_by: str, num_unique_classes: int +) -> Dataset: + """Create a dummy dataset for testing.""" + data = { + partition_by: np.tile( + np.arange(num_unique_classes), num_samples // num_unique_classes + 1 + )[:num_samples], + "features": np.random.randn(num_samples), + } + return Dataset.from_dict(data) + + +def _dummy_heterogeneous_dataset_setup( + num_samples: int, partition_by: str, num_unique_classes: int +) -> Dataset: + """Create a dummy dataset for testing.""" + data = { + partition_by: np.tile( + np.arange(num_unique_classes), num_samples // num_unique_classes + 1 + )[:num_samples], + "features": np.random.randn(num_samples), + } + return Dataset.from_dict(data) + + +class TestClassConstrainedPartitioner(unittest.TestCase): + """Unit tests for PathologicalPartitioner.""" + + @parameterized.expand( # type: ignore + [ + # num_partition, num_classes_per_partition, num_samples, total_classes + (3, 1, 60, 3), # Single class per partition scenario + (5, 2, 100, 5), + (5, 2, 100, 10), + (4, 3, 120, 6), + ] + ) + def test_correct_num_classes_when_partitioned( + self, + num_partitions: int, + num_classes_per_partition: int, + num_samples: int, + num_unique_classes: int, + ) -> None: + """Test correct number of unique classes.""" + dataset = _dummy_dataset_setup(num_samples, "labels", num_unique_classes) + partitioner = PathologicalPartitioner( + num_partitions=num_partitions, + partition_by="labels", + num_classes_per_partition=num_classes_per_partition, + ) + partitioner.dataset = dataset + partitions: Dict[int, Dataset] = { + pid: partitioner.load_partition(pid) for pid in range(num_partitions) + } + unique_classes_per_partition = { + pid: np.unique(partition["labels"]) for pid, partition in partitions.items() + } + + for unique_classes in unique_classes_per_partition.values(): + self.assertEqual(num_classes_per_partition, len(unique_classes)) + + def test_first_class_deterministic_assignment(self) -> None: + """Test deterministic assignment of first classes to partitions. + + Test if all the classes are used (which has to be the case, given num_partitions + >= than the number of unique classes). + """ + dataset = _dummy_dataset_setup(100, "labels", 10) + partitioner = PathologicalPartitioner( + num_partitions=10, + partition_by="labels", + num_classes_per_partition=2, + class_assignment_mode="first-deterministic", + ) + partitioner.dataset = dataset + partitioner.load_partition(0) + expected_classes = set(range(10)) + actual_classes = set() + for pid in range(10): + partition = partitioner.load_partition(pid) + actual_classes.update(np.unique(partition["labels"])) + self.assertEqual(expected_classes, actual_classes) + + @parameterized.expand( + [ # type: ignore + # num_partitions, num_classes_per_partition, num_samples, num_unique_classes + (4, 2, 80, 8), + (10, 2, 100, 10), + ] + ) + def test_deterministic_class_assignment( + self, num_partitions, num_classes_per_partition, num_samples, num_unique_classes + ): + """Test deterministic assignment of classes to partitions.""" + dataset = _dummy_dataset_setup(num_samples, "labels", num_unique_classes) + partitioner = PathologicalPartitioner( + num_partitions=num_partitions, + partition_by="labels", + num_classes_per_partition=num_classes_per_partition, + class_assignment_mode="deterministic", + ) + partitioner.dataset = dataset + partitions = { + pid: partitioner.load_partition(pid) for pid in range(num_partitions) + } + + # Verify each partition has the expected classes, order does not matter + for pid, partition in partitions.items(): + expected_labels = sorted( + [ + (pid + i) % num_unique_classes + for i in range(num_classes_per_partition) + ] + ) + actual_labels = sorted(np.unique(partition["labels"])) + self.assertTrue( + np.array_equal(expected_labels, actual_labels), + f"Partition {pid} does not have the expected labels: " + f"{expected_labels} but instead {actual_labels}.", + ) + + @parameterized.expand( + [ # type: ignore + # num_partitions, num_classes_per_partition, num_samples, num_unique_classes + (10, 3, 20, 3), + ] + ) + def test_too_many_partitions_for_a_class( + self, num_partitions, num_classes_per_partition, num_samples, num_unique_classes + ) -> None: + """Test too many partitions for the number of samples in a class.""" + dataset_1 = _dummy_dataset_setup( + num_samples // 2, "labels", num_unique_classes - 1 + ) + # Create a skewed part of the dataset for the last label + data = { + "labels": np.array([num_unique_classes - 1] * (num_samples // 2)), + "features": np.random.randn(num_samples // 2), + } + dataset_2 = Dataset.from_dict(data) + dataset = datasets.concatenate_datasets([dataset_1, dataset_2]) + + partitioner = PathologicalPartitioner( + num_partitions=num_partitions, + partition_by="labels", + num_classes_per_partition=num_classes_per_partition, + class_assignment_mode="random", + ) + partitioner.dataset = dataset + + with self.assertRaises(ValueError) as context: + _ = partitioner.load_partition(0) + self.assertEqual( + str(context.exception), + "Label: 0 is needed to be assigned to more partitions (10) than there are " + "samples (corresponding to this label) in the dataset (5). " + "Please decrease the `num_partitions`, `num_classes_per_partition` to " + "avoid this situation, or try `class_assigment_mode='deterministic'` to " + "create a more even distribution of classes along the partitions. " + "Alternatively use a different dataset if you can not adjust the any of " + "these parameters.", + ) + + @parameterized.expand( # type: ignore + [ + # num_partitions, num_classes_per_partition, num_samples, num_unique_classes + (10, 11, 100, 10), # 11 > 10 + (5, 11, 100, 10), # 11 > 10 + (10, 20, 100, 5), # 20 > 5 + ] + ) + def test_more_classes_per_partition_than_num_unique_classes_in_dataset_raises( + self, + num_partitions: int, + num_classes_per_partition: int, + num_samples: int, + num_unique_classes: int, + ) -> None: + """Test more num_classes_per_partition > num_unique_classes in the dataset.""" + dataset = _dummy_dataset_setup(num_samples, "labels", num_unique_classes) + with self.assertRaises(ValueError) as context: + partitioner = PathologicalPartitioner( + num_partitions=num_partitions, + partition_by="labels", + num_classes_per_partition=num_classes_per_partition, + ) + partitioner.dataset = dataset + partitioner.load_partition(0) + self.assertEqual( + str(context.exception), + "The specified " + f"`num_classes_per_partition`={num_classes_per_partition} is " + f"greater than the number of unique classes in the given " + f"dataset={len(dataset.unique('labels'))}. Reduce the " + f"`num_classes_per_partition` or make use different dataset " + f"to apply this partitioning.", + ) + + @parameterized.expand( # type: ignore + [ + # num_classes_per_partition should be irrelevant since the exception should + # be raised at the very beginning + # num_partitions, num_classes_per_partition, num_samples + (10, 2, 5), + (10, 10, 5), + (100, 10, 99), + ] + ) + def test_more_partitions_than_samples_raises( + self, num_partitions: int, num_classes_per_partition: int, num_samples: int + ) -> None: + """Test if generation of more partitions that there are samples raises.""" + # The number of unique classes in the dataset should be irrelevant since the + # exception should be raised at the very beginning + dataset = _dummy_dataset_setup(num_samples, "labels", num_unique_classes=5) + with self.assertRaises(ValueError) as context: + partitioner = PathologicalPartitioner( + num_partitions=num_partitions, + partition_by="labels", + num_classes_per_partition=num_classes_per_partition, + ) + partitioner.dataset = dataset + partitioner.load_partition(0) + self.assertEqual( + str(context.exception), + "The number of partitions needs to be smaller than the number of " + "samples in the dataset.", + ) + + +if __name__ == "__main__": + unittest.main() From 237988943ab8f0e80fbf467a665f48d4dc92d45e Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Fri, 12 Jul 2024 13:09:59 +0200 Subject: [PATCH 152/595] docs(datasets:skip) Update the partitioner list in README and index (#3785) --- datasets/README.md | 11 ++++++----- datasets/doc/source/index.rst | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/datasets/README.md b/datasets/README.md index 1d8014d57ea3..50fc67376ae4 100644 --- a/datasets/README.md +++ b/datasets/README.md @@ -42,11 +42,12 @@ Create **custom partitioning schemes** or choose from the **implemented [partiti * IID partitioning `IidPartitioner(num_partitions)` * Dirichlet partitioning `DirichletPartitioner(num_partitions, partition_by, alpha)` * InnerDirichlet partitioning `InnerDirichletPartitioner(partition_sizes, partition_by, alpha)` -* Natural ID partitioner `NaturalIdPartitioner(partition_by)` -* Size partitioner (the abstract base class for the partitioners dictating the division based the number of samples) `SizePartitioner` -* Linear partitioner `LinearPartitioner(num_partitions)` -* Square partitioner `SquarePartitioner(num_partitions)` -* Exponential partitioner `ExponentialPartitioner(num_partitions)` +* Pathological partitioning `PathologicalPartitioner(num_partitions, partition_by, num_classes_per_partition, class_assignment_mode)` +* Natural ID partitioning `NaturalIdPartitioner(partition_by)` +* Size based partitioning (the abstract base class for the partitioners dictating the division based the number of samples) `SizePartitioner` +* Linear partitioning `LinearPartitioner(num_partitions)` +* Square partitioning `SquarePartitioner(num_partitions)` +* Exponential partitioning `ExponentialPartitioner(num_partitions)` * more to come in the future releases (contributions are welcome).

    Comparison of partitioning schemes. diff --git a/datasets/doc/source/index.rst b/datasets/doc/source/index.rst index bdcea7650bbc..fcc7920711bf 100644 --- a/datasets/doc/source/index.rst +++ b/datasets/doc/source/index.rst @@ -94,6 +94,7 @@ Here are a few of the ``Partitioner`` s that are available: (for a full list see * IID partitioning ``IidPartitioner(num_partitions)`` * Dirichlet partitioning ``DirichletPartitioner(num_partitions, partition_by, alpha)`` * InnerDirichlet partitioning ``InnerDirichletPartitioner(partition_sizes, partition_by, alpha)`` +* PathologicalPartitioner ``PathologicalPartitioner(num_partitions, partition_by, num_classes_per_partition, class_assignment_mode)`` * Natural ID partitioner ``NaturalIdPartitioner(partition_by)`` * Size partitioner (the abstract base class for the partitioners dictating the division based the number of samples) ``SizePartitioner`` * Linear partitioner ``LinearPartitioner(num_partitions)`` From 2464534578a982d44e631281672ede9f9b57e567 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 12 Jul 2024 15:06:02 +0200 Subject: [PATCH 153/595] refactor(framework) Log warnings on SuperNode retry attempts (#3789) --- src/py/flwr/client/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 851083d4abb7..bfe5147f78e1 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -18,7 +18,7 @@ import sys import time from dataclasses import dataclass -from logging import DEBUG, ERROR, INFO, WARN +from logging import ERROR, INFO, WARN from pathlib import Path from typing import Callable, ContextManager, Dict, Optional, Tuple, Type, Union @@ -295,7 +295,7 @@ def _on_backoff(retry_state: RetryState) -> None: log(WARN, "Connection attempt failed, retrying...") else: log( - DEBUG, + WARN, "Connection attempt failed, retrying in %.2f seconds", retry_state.actual_wait, ) From 19fad01444d09ccd701e29ab6654fe8611889074 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 12 Jul 2024 15:52:03 +0200 Subject: [PATCH 154/595] fix(framework:skip) Update certificate check (#3786) Signed-off-by: Danny Heinrich Co-authored-by: Charles Beauville --- src/py/flwr/superexec/app.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py index b4d4b462bbcc..372ccb443a76 100644 --- a/src/py/flwr/superexec/app.py +++ b/src/py/flwr/superexec/app.py @@ -127,11 +127,11 @@ def _try_obtain_certificates( return None # Check if certificates are provided if args.ssl_certfile and args.ssl_keyfile and args.ssl_ca_certfile: - if not Path.is_file(args.ssl_ca_certfile): + if not Path(args.ssl_ca_certfile).is_file(): sys.exit("Path argument `--ssl-ca-certfile` does not point to a file.") - if not Path.is_file(args.ssl_certfile): + if not Path(args.ssl_certfile).is_file(): sys.exit("Path argument `--ssl-certfile` does not point to a file.") - if not Path.is_file(args.ssl_keyfile): + if not Path(args.ssl_keyfile).is_file(): sys.exit("Path argument `--ssl-keyfile` does not point to a file.") certificates = ( Path(args.ssl_ca_certfile).read_bytes(), # CA certificate From 01ca846aeae8e298a6b663861c9f1b5155b1c14c Mon Sep 17 00:00:00 2001 From: Javier Date: Fri, 12 Jul 2024 17:29:38 +0200 Subject: [PATCH 155/595] feat(framework) Capture `node_id`/`node_config` in `Context` via `NodeState` (#3780) Co-authored-by: Daniel J. Beutel --- src/py/flwr/client/app.py | 40 ++++++++++++++++--- .../client/grpc_adapter_client/connection.py | 2 +- src/py/flwr/client/grpc_client/connection.py | 2 +- .../client/grpc_rere_client/connection.py | 5 ++- .../message_handler/message_handler_test.py | 4 +- .../secure_aggregation/secaggplus_mod_test.py | 7 +++- src/py/flwr/client/mod/utils_test.py | 4 +- src/py/flwr/client/node_state.py | 11 +++-- src/py/flwr/client/node_state_tests.py | 2 +- src/py/flwr/client/rest_client/connection.py | 7 ++-- src/py/flwr/common/context.py | 15 ++++++- src/py/flwr/server/compat/legacy_context.py | 2 +- src/py/flwr/server/run_serverapp.py | 4 +- src/py/flwr/server/server_app_test.py | 2 +- .../fleet/vce/backend/raybackend_test.py | 2 +- .../server/superlink/fleet/vce/vce_api.py | 4 +- .../ray_transport/ray_client_proxy.py | 4 +- .../ray_transport/ray_client_proxy_test.py | 8 +++- 18 files changed, 95 insertions(+), 30 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index bfe5147f78e1..fa17ba9a8481 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -319,7 +319,13 @@ def _on_backoff(retry_state: RetryState) -> None: on_backoff=_on_backoff, ) - node_state = NodeState(partition_id=partition_id) + # Empty dict (for now) + # This will be removed once users can pass node_config via flower-supernode + node_config: Dict[str, str] = {} + + # NodeState gets initialized when the first connection is established + node_state: Optional[NodeState] = None + runs: Dict[int, Run] = {} while not app_state_tracker.interrupt: @@ -334,9 +340,33 @@ def _on_backoff(retry_state: RetryState) -> None: ) as conn: receive, send, create_node, delete_node, get_run = conn - # Register node - if create_node is not None: - create_node() # pylint: disable=not-callable + # Register node when connecting the first time + if node_state is None: + if create_node is None: + if transport not in ["grpc-bidi", None]: + raise NotImplementedError( + "All transports except `grpc-bidi` require " + "an implementation for `create_node()`.'" + ) + # gRPC-bidi doesn't have the concept of node_id, + # so we set it to -1 + node_state = NodeState( + node_id=-1, + node_config={}, + partition_id=partition_id, + ) + else: + # Call create_node fn to register node + node_id: Optional[int] = ( # pylint: disable=assignment-from-none + create_node() + ) # pylint: disable=not-callable + if node_id is None: + raise ValueError("Node registration failed") + node_state = NodeState( + node_id=node_id, + node_config=node_config, + partition_id=partition_id, + ) app_state_tracker.register_signal_handler() while not app_state_tracker.interrupt: @@ -580,7 +610,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> Tuple[ Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] diff --git a/src/py/flwr/client/grpc_adapter_client/connection.py b/src/py/flwr/client/grpc_adapter_client/connection.py index 971b630e470b..80a5cf0b4656 100644 --- a/src/py/flwr/client/grpc_adapter_client/connection.py +++ b/src/py/flwr/client/grpc_adapter_client/connection.py @@ -44,7 +44,7 @@ def grpc_adapter( # pylint: disable=R0913 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] diff --git a/src/py/flwr/client/grpc_client/connection.py b/src/py/flwr/client/grpc_client/connection.py index 3e9f261c1ecf..a6417106d51b 100644 --- a/src/py/flwr/client/grpc_client/connection.py +++ b/src/py/flwr/client/grpc_client/connection.py @@ -72,7 +72,7 @@ def grpc_connection( # pylint: disable=R0913, R0915 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index 8062ce28fcc7..e573df6854bc 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -79,7 +79,7 @@ def grpc_request_response( # pylint: disable=R0913, R0914, R0915 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] @@ -176,7 +176,7 @@ def ping() -> None: if not ping_stop_event.is_set(): ping_stop_event.wait(next_interval) - def create_node() -> None: + def create_node() -> Optional[int]: """Set create_node.""" # Call FleetAPI create_node_request = CreateNodeRequest(ping_interval=PING_DEFAULT_INTERVAL) @@ -189,6 +189,7 @@ def create_node() -> None: nonlocal node, ping_thread node = cast(Node, create_node_response.node) ping_thread = start_ping_loop(ping, ping_stop_event) + return node.node_id def delete_node() -> None: """Set delete_node.""" diff --git a/src/py/flwr/client/message_handler/message_handler_test.py b/src/py/flwr/client/message_handler/message_handler_test.py index 9ce4c9620c43..96de7ce0c2cb 100644 --- a/src/py/flwr/client/message_handler/message_handler_test.py +++ b/src/py/flwr/client/message_handler/message_handler_test.py @@ -145,7 +145,7 @@ def test_client_without_get_properties() -> None: actual_msg = handle_legacy_message_from_msgtype( client_fn=_get_client_fn(client), message=message, - context=Context(state=RecordSet(), run_config={}), + context=Context(node_id=1123, node_config={}, state=RecordSet(), run_config={}), ) # Assert @@ -209,7 +209,7 @@ def test_client_with_get_properties() -> None: actual_msg = handle_legacy_message_from_msgtype( client_fn=_get_client_fn(client), message=message, - context=Context(state=RecordSet(), run_config={}), + context=Context(node_id=1123, node_config={}, state=RecordSet(), run_config={}), ) # Assert diff --git a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py index 5e4c4411e1f7..2832576fb4fc 100644 --- a/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py +++ b/src/py/flwr/client/mod/secure_aggregation/secaggplus_mod_test.py @@ -73,7 +73,12 @@ def func(configs: Dict[str, ConfigsRecordValues]) -> ConfigsRecord: def _make_ctxt() -> Context: cfg = ConfigsRecord(SecAggPlusState().to_dict()) - return Context(RecordSet(configs_records={RECORD_KEY_STATE: cfg}), run_config={}) + return Context( + node_id=123, + node_config={}, + state=RecordSet(configs_records={RECORD_KEY_STATE: cfg}), + run_config={}, + ) def _make_set_state_fn( diff --git a/src/py/flwr/client/mod/utils_test.py b/src/py/flwr/client/mod/utils_test.py index 7a1dd8988399..a5bbd0a0bb4d 100644 --- a/src/py/flwr/client/mod/utils_test.py +++ b/src/py/flwr/client/mod/utils_test.py @@ -104,7 +104,7 @@ def test_multiple_mods(self) -> None: state = RecordSet() state.metrics_records[METRIC] = MetricsRecord({COUNTER: 0.0}) - context = Context(state=state, run_config={}) + context = Context(node_id=0, node_config={}, state=state, run_config={}) message = _get_dummy_flower_message() # Execute @@ -129,7 +129,7 @@ def test_filter(self) -> None: # Prepare footprint: List[str] = [] mock_app = make_mock_app("app", footprint) - context = Context(state=RecordSet(), run_config={}) + context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) message = _get_dummy_flower_message() def filter_mod( diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 2b090eba9720..d0a349b0cae0 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -17,7 +17,7 @@ from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, Optional +from typing import Dict, Optional from flwr.common import Context, RecordSet from flwr.common.config import get_fused_config @@ -35,8 +35,11 @@ class RunInfo: class NodeState: """State of a node where client nodes execute runs.""" - def __init__(self, partition_id: Optional[int]) -> None: - self._meta: Dict[str, Any] = {} # holds metadata about the node + def __init__( + self, node_id: int, node_config: Dict[str, str], partition_id: Optional[int] + ) -> None: + self.node_id = node_id + self.node_config = node_config self.run_infos: Dict[int, RunInfo] = {} self._partition_id = partition_id @@ -52,6 +55,8 @@ def register_context( self.run_infos[run_id] = RunInfo( initial_run_config=initial_run_config, context=Context( + node_id=self.node_id, + node_config=self.node_config, state=RecordSet(), run_config=initial_run_config.copy(), partition_id=self._partition_id, diff --git a/src/py/flwr/client/node_state_tests.py b/src/py/flwr/client/node_state_tests.py index effd64a3ae7a..8d7971fa5280 100644 --- a/src/py/flwr/client/node_state_tests.py +++ b/src/py/flwr/client/node_state_tests.py @@ -41,7 +41,7 @@ def test_multirun_in_node_state() -> None: expected_values = {0: "1", 1: "1" * 3, 2: "1" * 2, 3: "1", 5: "1"} # NodeState - node_state = NodeState(partition_id=None) + node_state = NodeState(node_id=0, node_config={}, partition_id=None) for task in tasks: run_id = task.run_id diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index 0efa5731ae51..3e81969d898c 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -90,7 +90,7 @@ def http_request_response( # pylint: disable=,R0913, R0914, R0915 Tuple[ Callable[[], Optional[Message]], Callable[[Message], None], - Optional[Callable[[], None]], + Optional[Callable[[], Optional[int]]], Optional[Callable[[], None]], Optional[Callable[[int], Run]], ] @@ -237,19 +237,20 @@ def ping() -> None: if not ping_stop_event.is_set(): ping_stop_event.wait(next_interval) - def create_node() -> None: + def create_node() -> Optional[int]: """Set create_node.""" req = CreateNodeRequest(ping_interval=PING_DEFAULT_INTERVAL) # Send the request res = _request(req, CreateNodeResponse, PATH_CREATE_NODE) if res is None: - return + return None # Remember the node and the ping-loop thread nonlocal node, ping_thread node = res.node ping_thread = start_ping_loop(ping, ping_stop_event) + return node.node_id def delete_node() -> None: """Set delete_node.""" diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index 8120723ce9e9..e65300278c84 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -27,6 +27,11 @@ class Context: Parameters ---------- + node_id : int + The ID that identifies the node. + node_config : Dict[str, str] + A config (key/value mapping) unique to the node and independent of the + `run_config`. This config persists across all runs this node participates in. state : RecordSet Holds records added by the entity in a given run and that will stay local. This means that the data it holds will never leave the system it's running from. @@ -44,16 +49,22 @@ class Context: simulation or proto typing setups. """ + node_id: int + node_config: Dict[str, str] state: RecordSet - partition_id: Optional[int] run_config: Dict[str, str] + partition_id: Optional[int] - def __init__( + def __init__( # pylint: disable=too-many-arguments self, + node_id: int, + node_config: Dict[str, str], state: RecordSet, run_config: Dict[str, str], partition_id: Optional[int] = None, ) -> None: + self.node_id = node_id + self.node_config = node_config self.state = state self.run_config = run_config self.partition_id = partition_id diff --git a/src/py/flwr/server/compat/legacy_context.py b/src/py/flwr/server/compat/legacy_context.py index 9e120c824103..ee09d79012dc 100644 --- a/src/py/flwr/server/compat/legacy_context.py +++ b/src/py/flwr/server/compat/legacy_context.py @@ -52,4 +52,4 @@ def __init__( self.strategy = strategy self.client_manager = client_manager self.history = History() - super().__init__(state, run_config={}) + super().__init__(node_id=0, node_config={}, state=state, run_config={}) diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index b4697e99913f..4cc25feb7e0e 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -78,7 +78,9 @@ def _load() -> ServerApp: server_app = _load() # Initialize Context - context = Context(state=RecordSet(), run_config=server_app_run_config) + context = Context( + node_id=0, node_config={}, state=RecordSet(), run_config=server_app_run_config + ) # Call ServerApp server_app(driver=driver, context=context) diff --git a/src/py/flwr/server/server_app_test.py b/src/py/flwr/server/server_app_test.py index 7de8774d4c81..b0672b3202ed 100644 --- a/src/py/flwr/server/server_app_test.py +++ b/src/py/flwr/server/server_app_test.py @@ -29,7 +29,7 @@ def test_server_app_custom_mode() -> None: # Prepare app = ServerApp() driver = MagicMock() - context = Context(state=RecordSet(), run_config={}) + context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) called = {"called": False} diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index 287983003f8c..da4390194d05 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -120,7 +120,7 @@ def _create_message_and_context() -> Tuple[Message, Context, float]: ) # Construct emtpy Context - context = Context(state=RecordSet(), run_config={}) + context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) # Expected output expected_output = pi * mult_factor diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 3c0b36e1ca3c..134fd34ed8f0 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -284,7 +284,9 @@ def start_vce( # Construct mapping of NodeStates node_states: Dict[int, NodeState] = {} for node_id, partition_id in nodes_mapping.items(): - node_states[node_id] = NodeState(partition_id=partition_id) + node_states[node_id] = NodeState( + node_id=node_id, node_config={}, partition_id=partition_id + ) # Load backend config log(DEBUG, "Supported backends: %s", list(supported_backends.keys())) diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index 31bc22c84bd5..f2684016048e 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -59,7 +59,9 @@ def _load_app() -> ClientApp: self.app_fn = _load_app self.actor_pool = actor_pool - self.proxy_state = NodeState(partition_id=self.partition_id) + self.proxy_state = NodeState( + node_id=node_id, node_config={}, partition_id=self.partition_id + ) def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: """Sumbit a message to the ActorPool.""" diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 83f6cfe05313..8831e5f475ea 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -218,7 +218,13 @@ def _load_app() -> ClientApp: _load_app, message, str(node_id), - Context(state=RecordSet(), run_config={}, partition_id=node_id), + Context( + node_id=0, + node_config={}, + state=RecordSet(), + run_config={}, + partition_id=node_id, + ), ), ) From ea8f940a465cd43de5293ffc8b719431f46679ae Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Fri, 12 Jul 2024 18:26:06 +0200 Subject: [PATCH 156/595] feat(framework) Add `node-config` arg to SuperNode (#3782) Co-authored-by: jafermarq Co-authored-by: Daniel J. Beutel --- src/py/flwr/client/app.py | 16 ++++++---------- src/py/flwr/client/supernode/app.py | 20 +++++++++++++------- src/py/flwr/common/config.py | 6 +++--- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index fa17ba9a8481..ffcc95489d62 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -160,6 +160,7 @@ class `flwr.client.Client` (default: None) event(EventType.START_CLIENT_ENTER) _start_client_internal( server_address=server_address, + node_config={}, load_client_app_fn=None, client_fn=client_fn, client=client, @@ -181,6 +182,7 @@ class `flwr.client.Client` (default: None) def _start_client_internal( *, server_address: str, + node_config: Dict[str, str], load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None, client_fn: Optional[ClientFnExt] = None, client: Optional[Client] = None, @@ -193,7 +195,6 @@ def _start_client_internal( ] = None, max_retries: Optional[int] = None, max_wait_time: Optional[float] = None, - partition_id: Optional[int] = None, flwr_dir: Optional[Path] = None, ) -> None: """Start a Flower client node which connects to a Flower server. @@ -204,6 +205,8 @@ def _start_client_internal( The IPv4 or IPv6 address of the server. If the Flower server runs on the same machine on port 8080, then `server_address` would be `"[::]:8080"`. + node_config: Dict[str, str] + The configuration of the node. load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None) A function that can be used to load a `ClientApp` instance. client_fn : Optional[ClientFnExt] @@ -238,9 +241,6 @@ class `flwr.client.Client` (default: None) The maximum duration before the client stops trying to connect to the server in case of connection error. If set to None, there is no limit to the total time. - partition_id: Optional[int] (default: None) - The data partition index associated with this node. Better suited for - prototyping purposes. flwr_dir: Optional[Path] (default: None) The fully resolved path containing installed Flower Apps. """ @@ -319,10 +319,6 @@ def _on_backoff(retry_state: RetryState) -> None: on_backoff=_on_backoff, ) - # Empty dict (for now) - # This will be removed once users can pass node_config via flower-supernode - node_config: Dict[str, str] = {} - # NodeState gets initialized when the first connection is established node_state: Optional[NodeState] = None @@ -353,7 +349,7 @@ def _on_backoff(retry_state: RetryState) -> None: node_state = NodeState( node_id=-1, node_config={}, - partition_id=partition_id, + partition_id=None, ) else: # Call create_node fn to register node @@ -365,7 +361,7 @@ def _on_backoff(retry_state: RetryState) -> None: node_state = NodeState( node_id=node_id, node_config=node_config, - partition_id=partition_id, + partition_id=None, ) app_state_tracker.register_signal_handler() diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 355a2a13a0e5..d61b986bc7af 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -29,7 +29,12 @@ from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.common import EventType, event -from flwr.common.config import get_flwr_dir, get_project_config, get_project_dir +from flwr.common.config import ( + get_flwr_dir, + get_project_config, + get_project_dir, + parse_config_args, +) from flwr.common.constant import ( TRANSPORT_TYPE_GRPC_ADAPTER, TRANSPORT_TYPE_GRPC_RERE, @@ -67,7 +72,7 @@ def run_supernode() -> None: authentication_keys=authentication_keys, max_retries=args.max_retries, max_wait_time=args.max_wait_time, - partition_id=args.partition_id, + node_config=parse_config_args(args.node_config), flwr_dir=get_flwr_dir(args.flwr_dir), ) @@ -93,6 +98,7 @@ def run_client_app() -> None: _start_client_internal( server_address=args.superlink, + node_config=parse_config_args(args.node_config), load_client_app_fn=load_fn, transport=args.transport, root_certificates=root_certificates, @@ -389,11 +395,11 @@ def _parse_args_common(parser: argparse.ArgumentParser) -> None: help="The SuperNode's public key (as a path str) to enable authentication.", ) parser.add_argument( - "--partition-id", - type=int, - help="The data partition index associated with this SuperNode. Better suited " - "for prototyping purposes where a SuperNode might only load a fraction of an " - "artificially partitioned dataset (e.g. using `flwr-datasets`)", + "--node-config", + type=str, + help="A comma separated list of key/value pairs (separated by `=`) to " + "configure the SuperNode. " + "E.g. --node-config 'key1=\"value1\",partition-id=0,num-partitions=100'", ) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 9770bdb4af2b..54d74353e4ed 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -121,16 +121,16 @@ def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, st def parse_config_args( - config_overrides: Optional[str], + config: Optional[str], separator: str = ",", ) -> Dict[str, str]: """Parse separator separated list of key-value pairs separated by '='.""" overrides: Dict[str, str] = {} - if config_overrides is None: + if config is None: return overrides - overrides_list = config_overrides.split(separator) + overrides_list = config.split(separator) if ( len(overrides_list) == 1 and "=" not in overrides_list From 64dd32591da10251958ae5f0b3a0fb7466c56f52 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Jul 2024 08:39:50 +0200 Subject: [PATCH 157/595] feat(framework) Introduce new `client_fn` signature passing the `Context` (#3779) Co-authored-by: Daniel J. Beutel --- src/py/flwr/client/app.py | 7 ++-- src/py/flwr/client/client_app.py | 34 +++++++++++++++---- .../client/message_handler/message_handler.py | 2 +- .../message_handler/message_handler_test.py | 6 ++-- src/py/flwr/client/typing.py | 4 +-- .../fleet/vce/backend/raybackend_test.py | 4 +-- .../server/superlink/fleet/vce/vce_api.py | 4 ++- .../ray_transport/ray_client_proxy.py | 17 ++++++---- .../ray_transport/ray_client_proxy_test.py | 32 +++++++++-------- 9 files changed, 66 insertions(+), 44 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index ffcc95489d62..380185ed26e7 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -28,7 +28,7 @@ from flwr.client.client import Client from flwr.client.client_app import ClientApp, LoadClientAppError from flwr.client.typing import ClientFnExt -from flwr.common import GRPC_MAX_MESSAGE_LENGTH, EventType, Message, event +from flwr.common import GRPC_MAX_MESSAGE_LENGTH, Context, EventType, Message, event from flwr.common.address import parse_address from flwr.common.constant import ( MISSING_EXTRA_REST, @@ -138,7 +138,7 @@ class `flwr.client.Client` (default: None) Starting an SSL-enabled gRPC client using system certificates: - >>> def client_fn(node_id: int, partition_id: Optional[int]): + >>> def client_fn(context: Context): >>> return FlowerClient() >>> >>> start_client( @@ -253,8 +253,7 @@ class `flwr.client.Client` (default: None) if client_fn is None: # Wrap `Client` instance in `client_fn` def single_client_factory( - node_id: int, # pylint: disable=unused-argument - partition_id: Optional[int], # pylint: disable=unused-argument + context: Context, # pylint: disable=unused-argument ) -> Client: if client is None: # Added this to keep mypy happy raise ValueError( diff --git a/src/py/flwr/client/client_app.py b/src/py/flwr/client/client_app.py index 663d83a8b19e..9566302d0721 100644 --- a/src/py/flwr/client/client_app.py +++ b/src/py/flwr/client/client_app.py @@ -30,21 +30,41 @@ from .typing import ClientAppCallable +def _alert_erroneous_client_fn() -> None: + raise ValueError( + "A `ClientApp` cannot make use of a `client_fn` that does " + "not have a signature in the form: `def client_fn(context: " + "Context)`. You can import the `Context` like this: " + "`from flwr.common import Context`" + ) + + def _inspect_maybe_adapt_client_fn_signature(client_fn: ClientFnExt) -> ClientFnExt: client_fn_args = inspect.signature(client_fn).parameters + first_arg = list(client_fn_args.keys())[0] + + if len(client_fn_args) != 1: + _alert_erroneous_client_fn() + + first_arg_type = client_fn_args[first_arg].annotation - if not all(key in client_fn_args for key in ["node_id", "partition_id"]): + if first_arg_type is str or first_arg == "cid": + # Warn previous signature for `client_fn` seems to be used warn_deprecated_feature( - "`client_fn` now expects a signature `def client_fn(node_id: int, " - "partition_id: Optional[int])`.\nYou provided `client_fn` with signature: " - f"{dict(client_fn_args.items())}" + "`client_fn` now expects a signature `def client_fn(context: Context)`." + "The provided `client_fn` has signature: " + f"{dict(client_fn_args.items())}. You can import the `Context` like this:" + " `from flwr.common import Context`" ) # Wrap depcreated client_fn inside a function with the expected signature def adaptor_fn( - node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument - ) -> Client: - return client_fn(str(partition_id)) # type: ignore + context: Context, + ) -> Client: # pylint: disable=unused-argument + # if patition-id is defined, pass it. Else pass node_id that should + # always be defined during Context init. + cid = context.node_config.get("partition-id", context.node_id) + return client_fn(str(cid)) # type: ignore return adaptor_fn diff --git a/src/py/flwr/client/message_handler/message_handler.py b/src/py/flwr/client/message_handler/message_handler.py index e9a853a92101..1ab84eb01468 100644 --- a/src/py/flwr/client/message_handler/message_handler.py +++ b/src/py/flwr/client/message_handler/message_handler.py @@ -92,7 +92,7 @@ def handle_legacy_message_from_msgtype( client_fn: ClientFnExt, message: Message, context: Context ) -> Message: """Handle legacy message in the inner most mod.""" - client = client_fn(message.metadata.dst_node_id, context.partition_id) + client = client_fn(context) # Check if NumPyClient is returend if isinstance(client, NumPyClient): diff --git a/src/py/flwr/client/message_handler/message_handler_test.py b/src/py/flwr/client/message_handler/message_handler_test.py index 96de7ce0c2cb..557d61ffb32a 100644 --- a/src/py/flwr/client/message_handler/message_handler_test.py +++ b/src/py/flwr/client/message_handler/message_handler_test.py @@ -19,7 +19,7 @@ import unittest import uuid from copy import copy -from typing import List, Optional +from typing import List from flwr.client import Client from flwr.client.typing import ClientFnExt @@ -114,9 +114,7 @@ def evaluate(self, ins: EvaluateIns) -> EvaluateRes: def _get_client_fn(client: Client) -> ClientFnExt: - def client_fn( - node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument - ) -> Client: + def client_fn(contex: Context) -> Client: # pylint: disable=unused-argument return client return client_fn diff --git a/src/py/flwr/client/typing.py b/src/py/flwr/client/typing.py index bf66a9082c77..9faed4bc7283 100644 --- a/src/py/flwr/client/typing.py +++ b/src/py/flwr/client/typing.py @@ -15,7 +15,7 @@ """Custom types for Flower clients.""" -from typing import Callable, Optional +from typing import Callable from flwr.common import Context, Message @@ -23,7 +23,7 @@ # Compatibility ClientFn = Callable[[str], Client] -ClientFnExt = Callable[[int, Optional[int]], Client] +ClientFnExt = Callable[[Context], Client] ClientAppCallable = Callable[[Message, Context], Message] Mod = Callable[[Message, Context, ClientAppCallable], Message] diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index da4390194d05..3abdac7a232b 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -53,9 +53,7 @@ def get_properties(self, config: Config) -> Dict[str, Scalar]: return {"result": result} -def get_dummy_client( - node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument -) -> Client: +def get_dummy_client(context: Context) -> Client: # pylint: disable=unused-argument """Return a DummyClient converted to Client type.""" return DummyClient().to_client() diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 134fd34ed8f0..422148489b4a 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -285,7 +285,9 @@ def start_vce( node_states: Dict[int, NodeState] = {} for node_id, partition_id in nodes_mapping.items(): node_states[node_id] = NodeState( - node_id=node_id, node_config={}, partition_id=partition_id + node_id=node_id, + node_config={"partition-id": str(partition_id)}, + partition_id=None, ) # Load backend config diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index f2684016048e..b62e04aeed79 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -60,7 +60,9 @@ def _load_app() -> ClientApp: self.app_fn = _load_app self.actor_pool = actor_pool self.proxy_state = NodeState( - node_id=node_id, node_config={}, partition_id=self.partition_id + node_id=node_id, + node_config={"partition-id": str(partition_id)}, + partition_id=None, ) def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: @@ -70,18 +72,19 @@ def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: # Register state self.proxy_state.register_context(run_id=run_id) - # Retrieve state - state = self.proxy_state.retrieve_context(run_id=run_id) + # Retrieve context + context = self.proxy_state.retrieve_context(run_id=run_id) + partition_id_str = context.node_config["partition-id"] try: self.actor_pool.submit_client_job( - lambda a, a_fn, mssg, partition_id, state: a.run.remote( - a_fn, mssg, partition_id, state + lambda a, a_fn, mssg, partition_id, context: a.run.remote( + a_fn, mssg, partition_id, context ), - (self.app_fn, message, str(self.partition_id), state), + (self.app_fn, message, partition_id_str, context), ) out_mssg, updated_context = self.actor_pool.get_client_result( - str(self.partition_id), timeout + partition_id_str, timeout ) # Update state diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 8831e5f475ea..1d44ea1d8d2b 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -17,7 +17,7 @@ from math import pi from random import shuffle -from typing import Dict, List, Optional, Tuple, Type +from typing import Dict, List, Tuple, Type import ray @@ -39,7 +39,10 @@ recordset_to_getpropertiesres, ) from flwr.common.recordset_compat_test import _get_valid_getpropertiesins -from flwr.simulation.app import _create_node_id_to_partition_mapping +from flwr.simulation.app import ( + NodeToPartitionMapping, + _create_node_id_to_partition_mapping, +) from flwr.simulation.ray_transport.ray_actor import ( ClientAppActor, VirtualClientEngineActor, @@ -65,16 +68,16 @@ def get_properties(self, config: Config) -> Dict[str, Scalar]: return {"result": result} -def get_dummy_client( - node_id: int, partition_id: Optional[int] # pylint: disable=unused-argument -) -> Client: +def get_dummy_client(context: Context) -> Client: """Return a DummyClient converted to Client type.""" - return DummyClient(node_id).to_client() + return DummyClient(context.node_id).to_client() def prep( actor_type: Type[VirtualClientEngineActor] = ClientAppActor, -) -> Tuple[List[RayActorClientProxy], VirtualClientEngineActorPool]: # pragma: no cover +) -> Tuple[ + List[RayActorClientProxy], VirtualClientEngineActorPool, NodeToPartitionMapping +]: # pragma: no cover """Prepare ClientProxies and pool for tests.""" client_resources = {"num_cpus": 1, "num_gpus": 0.0} @@ -101,7 +104,7 @@ def create_actor_fn() -> Type[VirtualClientEngineActor]: for node_id, partition_id in mapping.items() ] - return proxies, pool + return proxies, pool, mapping def test_cid_consistency_one_at_a_time() -> None: @@ -109,7 +112,7 @@ def test_cid_consistency_one_at_a_time() -> None: Submit one job and waits for completion. Then submits the next and so on """ - proxies, _ = prep() + proxies, _, _ = prep() getproperties_ins = _get_valid_getpropertiesins() recordset = getpropertiesins_to_recordset(getproperties_ins) @@ -139,7 +142,7 @@ def test_cid_consistency_all_submit_first_run_consistency() -> None: All jobs are submitted at the same time. Then fetched one at a time. This also tests NodeState (at each Proxy) and RunState basic functionality. """ - proxies, _ = prep() + proxies, _, _ = prep() run_id = 0 getproperties_ins = _get_valid_getpropertiesins() @@ -186,9 +189,8 @@ def test_cid_consistency_all_submit_first_run_consistency() -> None: def test_cid_consistency_without_proxies() -> None: """Test cid consistency of jobs submitted/retrieved to/from pool w/o ClientProxy.""" - proxies, pool = prep() - num_clients = len(proxies) - node_ids = list(range(num_clients)) + _, pool, mapping = prep() + node_ids = list(mapping.keys()) getproperties_ins = _get_valid_getpropertiesins() recordset = getpropertiesins_to_recordset(getproperties_ins) @@ -219,11 +221,11 @@ def _load_app() -> ClientApp: message, str(node_id), Context( - node_id=0, + node_id=node_id, node_config={}, state=RecordSet(), run_config={}, - partition_id=node_id, + partition_id=mapping[node_id], ), ), ) From 48f223fb82d4f7f6df5b02c73eed7aca522b5129 Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Jul 2024 10:31:07 +0200 Subject: [PATCH 158/595] refactor(framework) Remove `partition_id` from `Context` (#3792) --- src/py/flwr/client/app.py | 2 - src/py/flwr/client/node_state.py | 6 +-- src/py/flwr/client/node_state_tests.py | 2 +- src/py/flwr/common/constant.py | 3 ++ src/py/flwr/common/context.py | 9 +---- .../superlink/fleet/vce/backend/raybackend.py | 3 +- .../fleet/vce/backend/raybackend_test.py | 12 ++++-- .../server/superlink/fleet/vce/vce_api.py | 17 +++++++-- src/py/flwr/simulation/app.py | 1 + .../ray_transport/ray_client_proxy.py | 18 ++++++--- .../ray_transport/ray_client_proxy_test.py | 38 +++++++++++-------- 11 files changed, 68 insertions(+), 43 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 380185ed26e7..700ac85f341f 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -348,7 +348,6 @@ def _on_backoff(retry_state: RetryState) -> None: node_state = NodeState( node_id=-1, node_config={}, - partition_id=None, ) else: # Call create_node fn to register node @@ -360,7 +359,6 @@ def _on_backoff(retry_state: RetryState) -> None: node_state = NodeState( node_id=node_id, node_config=node_config, - partition_id=None, ) app_state_tracker.register_signal_handler() diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index d0a349b0cae0..393ca4564a35 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -36,12 +36,13 @@ class NodeState: """State of a node where client nodes execute runs.""" def __init__( - self, node_id: int, node_config: Dict[str, str], partition_id: Optional[int] + self, + node_id: int, + node_config: Dict[str, str], ) -> None: self.node_id = node_id self.node_config = node_config self.run_infos: Dict[int, RunInfo] = {} - self._partition_id = partition_id def register_context( self, @@ -59,7 +60,6 @@ def register_context( node_config=self.node_config, state=RecordSet(), run_config=initial_run_config.copy(), - partition_id=self._partition_id, ), ) diff --git a/src/py/flwr/client/node_state_tests.py b/src/py/flwr/client/node_state_tests.py index 8d7971fa5280..26ac4fea6855 100644 --- a/src/py/flwr/client/node_state_tests.py +++ b/src/py/flwr/client/node_state_tests.py @@ -41,7 +41,7 @@ def test_multirun_in_node_state() -> None: expected_values = {0: "1", 1: "1" * 3, 2: "1" * 2, 3: "1", 5: "1"} # NodeState - node_state = NodeState(node_id=0, node_config={}, partition_id=None) + node_state = NodeState(node_id=0, node_config={}) for task in tasks: run_id = task.run_id diff --git a/src/py/flwr/common/constant.py b/src/py/flwr/common/constant.py index f14959589458..72256a62add7 100644 --- a/src/py/flwr/common/constant.py +++ b/src/py/flwr/common/constant.py @@ -57,6 +57,9 @@ FAB_CONFIG_FILE = "pyproject.toml" FLWR_HOME = "FLWR_HOME" +# Constants entries in Node config for Simulation +PARTITION_ID_KEY = "partition-id" +NUM_PARTITIONS_KEY = "num-partitions" GRPC_ADAPTER_METADATA_FLOWER_VERSION_KEY = "flower-version" GRPC_ADAPTER_METADATA_SHOULD_EXIT_KEY = "should-exit" diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index e65300278c84..4da52ba44481 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -16,7 +16,7 @@ from dataclasses import dataclass -from typing import Dict, Optional +from typing import Dict from .record import RecordSet @@ -43,17 +43,12 @@ class Context: A config (key/value mapping) held by the entity in a given run and that will stay local. It can be used at any point during the lifecycle of this entity (e.g. across multiple rounds) - partition_id : Optional[int] (default: None) - An index that specifies the data partition that the ClientApp using this Context - object should make use of. Setting this attribute is better suited for - simulation or proto typing setups. """ node_id: int node_config: Dict[str, str] state: RecordSet run_config: Dict[str, str] - partition_id: Optional[int] def __init__( # pylint: disable=too-many-arguments self, @@ -61,10 +56,8 @@ def __init__( # pylint: disable=too-many-arguments node_config: Dict[str, str], state: RecordSet, run_config: Dict[str, str], - partition_id: Optional[int] = None, ) -> None: self.node_id = node_id self.node_config = node_config self.state = state self.run_config = run_config - self.partition_id = partition_id diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py index 0d2f4d193f0b..0ab29a234f88 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend.py @@ -21,6 +21,7 @@ import ray from flwr.client.client_app import ClientApp +from flwr.common.constant import PARTITION_ID_KEY from flwr.common.context import Context from flwr.common.logger import log from flwr.common.message import Message @@ -168,7 +169,7 @@ def process_message( Return output message and updated context. """ - partition_id = context.partition_id + partition_id = context.node_config[PARTITION_ID_KEY] try: # Submit a task to the pool diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py index 3abdac7a232b..a38cff96ceef 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/raybackend_test.py @@ -23,6 +23,7 @@ from flwr.client import Client, NumPyClient from flwr.client.client_app import ClientApp, LoadClientAppError +from flwr.client.node_state import NodeState from flwr.common import ( DEFAULT_TTL, Config, @@ -32,9 +33,9 @@ Message, MessageTypeLegacy, Metadata, - RecordSet, Scalar, ) +from flwr.common.constant import PARTITION_ID_KEY from flwr.common.object_ref import load_app from flwr.common.recordset_compat import getpropertiesins_to_recordset from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig @@ -101,12 +102,13 @@ def _create_message_and_context() -> Tuple[Message, Context, float]: # Construct a Message mult_factor = 2024 + run_id = 0 getproperties_ins = GetPropertiesIns(config={"factor": mult_factor}) recordset = getpropertiesins_to_recordset(getproperties_ins) message = Message( content=recordset, metadata=Metadata( - run_id=0, + run_id=run_id, message_id="", group_id="", src_node_id=0, @@ -117,8 +119,10 @@ def _create_message_and_context() -> Tuple[Message, Context, float]: ), ) - # Construct emtpy Context - context = Context(node_id=0, node_config={}, state=RecordSet(), run_config={}) + # Construct NodeState and retrieve context + node_state = NodeState(node_id=run_id, node_config={PARTITION_ID_KEY: str(0)}) + node_state.register_context(run_id=run_id) + context = node_state.retrieve_context(run_id=run_id) # Expected output expected_output = pi * mult_factor diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 422148489b4a..cd30c40167c5 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -29,7 +29,12 @@ from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError from flwr.client.node_state import NodeState -from flwr.common.constant import PING_MAX_INTERVAL, ErrorCode +from flwr.common.constant import ( + NUM_PARTITIONS_KEY, + PARTITION_ID_KEY, + PING_MAX_INTERVAL, + ErrorCode, +) from flwr.common.logger import log from flwr.common.message import Error from flwr.common.object_ref import load_app @@ -73,7 +78,7 @@ def worker( task_ins: TaskIns = taskins_queue.get(timeout=1.0) node_id = task_ins.task.consumer.node_id - # Register and retrieve runstate + # Register and retrieve context node_states[node_id].register_context(run_id=task_ins.run_id) context = node_states[node_id].retrieve_context(run_id=task_ins.run_id) @@ -283,11 +288,15 @@ def start_vce( # Construct mapping of NodeStates node_states: Dict[int, NodeState] = {} + # Number of unique partitions + num_partitions = len(set(nodes_mapping.values())) for node_id, partition_id in nodes_mapping.items(): node_states[node_id] = NodeState( node_id=node_id, - node_config={"partition-id": str(partition_id)}, - partition_id=None, + node_config={ + PARTITION_ID_KEY: str(partition_id), + NUM_PARTITIONS_KEY: str(num_partitions), + }, ) # Load backend config diff --git a/src/py/flwr/simulation/app.py b/src/py/flwr/simulation/app.py index 446b0bdeba38..fc52267f9a04 100644 --- a/src/py/flwr/simulation/app.py +++ b/src/py/flwr/simulation/app.py @@ -327,6 +327,7 @@ def update_resources(f_stop: threading.Event) -> None: client_fn=client_fn, node_id=node_id, partition_id=partition_id, + num_partitions=num_clients, actor_pool=pool, ) initialized_server.client_manager().register(client=client_proxy) diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index b62e04aeed79..895272c2fd79 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -24,7 +24,12 @@ from flwr.client.client_app import ClientApp from flwr.client.node_state import NodeState from flwr.common import DEFAULT_TTL, Message, Metadata, RecordSet -from flwr.common.constant import MessageType, MessageTypeLegacy +from flwr.common.constant import ( + NUM_PARTITIONS_KEY, + PARTITION_ID_KEY, + MessageType, + MessageTypeLegacy, +) from flwr.common.logger import log from flwr.common.recordset_compat import ( evaluateins_to_recordset, @@ -43,11 +48,12 @@ class RayActorClientProxy(ClientProxy): """Flower client proxy which delegates work using Ray.""" - def __init__( + def __init__( # pylint: disable=too-many-arguments self, client_fn: ClientFnExt, node_id: int, partition_id: int, + num_partitions: int, actor_pool: VirtualClientEngineActorPool, ): super().__init__(cid=str(node_id)) @@ -61,8 +67,10 @@ def _load_app() -> ClientApp: self.actor_pool = actor_pool self.proxy_state = NodeState( node_id=node_id, - node_config={"partition-id": str(partition_id)}, - partition_id=None, + node_config={ + PARTITION_ID_KEY: str(partition_id), + NUM_PARTITIONS_KEY: str(num_partitions), + }, ) def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: @@ -74,7 +82,7 @@ def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: # Retrieve context context = self.proxy_state.retrieve_context(run_id=run_id) - partition_id_str = context.node_config["partition-id"] + partition_id_str = context.node_config[PARTITION_ID_KEY] try: self.actor_pool.submit_client_job( diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 1d44ea1d8d2b..62e0cfd61c99 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -23,6 +23,7 @@ from flwr.client import Client, NumPyClient from flwr.client.client_app import ClientApp +from flwr.client.node_state import NodeState from flwr.common import ( DEFAULT_TTL, Config, @@ -31,9 +32,9 @@ Message, MessageTypeLegacy, Metadata, - RecordSet, Scalar, ) +from flwr.common.constant import NUM_PARTITIONS_KEY, PARTITION_ID_KEY from flwr.common.recordset_compat import ( getpropertiesins_to_recordset, recordset_to_getpropertiesres, @@ -99,6 +100,7 @@ def create_actor_fn() -> Type[VirtualClientEngineActor]: client_fn=get_dummy_client, node_id=node_id, partition_id=partition_id, + num_partitions=num_proxies, actor_pool=pool, ) for node_id, partition_id in mapping.items() @@ -192,6 +194,17 @@ def test_cid_consistency_without_proxies() -> None: _, pool, mapping = prep() node_ids = list(mapping.keys()) + # register node states + node_states: Dict[int, NodeState] = {} + for node_id, partition_id in mapping.items(): + node_states[node_id] = NodeState( + node_id=node_id, + node_config={ + PARTITION_ID_KEY: str(partition_id), + NUM_PARTITIONS_KEY: str(len(node_ids)), + }, + ) + getproperties_ins = _get_valid_getpropertiesins() recordset = getpropertiesins_to_recordset(getproperties_ins) @@ -200,11 +213,12 @@ def _load_app() -> ClientApp: # submit all jobs (collect later) shuffle(node_ids) + run_id = 0 for node_id in node_ids: message = Message( content=recordset, metadata=Metadata( - run_id=0, + run_id=run_id, message_id="", group_id=str(0), src_node_id=0, @@ -214,26 +228,20 @@ def _load_app() -> ClientApp: message_type=MessageTypeLegacy.GET_PROPERTIES, ), ) + # register and retrieve context + node_states[node_id].register_context(run_id=run_id) + context = node_states[node_id].retrieve_context(run_id=run_id) + partition_id_str = context.node_config[PARTITION_ID_KEY] pool.submit_client_job( lambda a, c_fn, j_fn, nid_, state: a.run.remote(c_fn, j_fn, nid_, state), - ( - _load_app, - message, - str(node_id), - Context( - node_id=node_id, - node_config={}, - state=RecordSet(), - run_config={}, - partition_id=mapping[node_id], - ), - ), + (_load_app, message, partition_id_str, context), ) # fetch results one at a time shuffle(node_ids) for node_id in node_ids: - message_out, _ = pool.get_client_result(str(node_id), timeout=None) + partition_id_str = str(mapping[node_id]) + message_out, _ = pool.get_client_result(partition_id_str, timeout=None) res = recordset_to_getpropertiesres(message_out.content) assert node_id * pi == res.properties["result"] From 5505e0a31d91290cd3c8a88fb958b1c87a52733f Mon Sep 17 00:00:00 2001 From: Daniel Nata Nugraha Date: Sat, 13 Jul 2024 16:29:36 +0200 Subject: [PATCH 159/595] ci(*:skip) Update client_fn args in e2e tests (#3775) Co-authored-by: Daniel J. Beutel --- e2e/bare-client-auth/client.py | 9 +++++---- e2e/bare-https/client.py | 11 ++++++----- e2e/bare/client.py | 14 ++++++-------- e2e/docker/client.py | 3 ++- e2e/framework-fastai/client.py | 11 ++++++----- e2e/framework-jax/client.py | 13 ++++++------- e2e/framework-opacus/client.py | 11 ++++++----- e2e/framework-pandas/client.py | 11 ++++++----- e2e/framework-pytorch-lightning/client.py | 11 ++++++----- e2e/framework-pytorch/client.py | 12 ++++++------ e2e/framework-scikit-learn/client.py | 13 ++++++------- e2e/framework-tensorflow/client.py | 13 ++++++------- e2e/strategies/client.py | 13 ++++++------- e2e/strategies/test.py | 12 ++++++------ 14 files changed, 79 insertions(+), 78 deletions(-) diff --git a/e2e/bare-client-auth/client.py b/e2e/bare-client-auth/client.py index e82f17088bd9..c7b0d59b8ea5 100644 --- a/e2e/bare-client-auth/client.py +++ b/e2e/bare-client-auth/client.py @@ -1,13 +1,14 @@ import numpy as np -import flwr as fl +from flwr.client import ClientApp, NumPyClient +from flwr.common import Context model_params = np.array([1]) objective = 5 # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): return model_params @@ -23,10 +24,10 @@ def evaluate(self, parameters, config): return loss, 1, {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) diff --git a/e2e/bare-https/client.py b/e2e/bare-https/client.py index 8f5c1412fd01..4a682af3aec3 100644 --- a/e2e/bare-https/client.py +++ b/e2e/bare-https/client.py @@ -2,14 +2,15 @@ import numpy as np -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context model_params = np.array([1]) objective = 5 # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): return model_params @@ -25,17 +26,17 @@ def evaluate(self, parameters, config): return loss, 1, {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( + start_client( server_address="127.0.0.1:8080", client=FlowerClient().to_client(), root_certificates=Path("certificates/ca.crt").read_bytes(), diff --git a/e2e/bare/client.py b/e2e/bare/client.py index 402d775ac3a9..943e60d5db9f 100644 --- a/e2e/bare/client.py +++ b/e2e/bare/client.py @@ -2,8 +2,8 @@ import numpy as np -import flwr as fl -from flwr.common import ConfigsRecord +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import ConfigsRecord, Context SUBSET_SIZE = 1000 STATE_VAR = "timestamp" @@ -14,7 +14,7 @@ # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): return model_params @@ -51,16 +51,14 @@ def evaluate(self, parameters, config): ) -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( - server_address="127.0.0.1:8080", client=FlowerClient().to_client() - ) + start_client(server_address="127.0.0.1:8080", client=FlowerClient().to_client()) diff --git a/e2e/docker/client.py b/e2e/docker/client.py index 8451b810416b..44313c7c3af6 100644 --- a/e2e/docker/client.py +++ b/e2e/docker/client.py @@ -9,6 +9,7 @@ from torchvision.transforms import Compose, Normalize, ToTensor from flwr.client import ClientApp, NumPyClient +from flwr.common import Context # ############################################################################# # 1. Regular PyTorch pipeline: nn.Module, train, test, and DataLoader @@ -122,7 +123,7 @@ def evaluate(self, parameters, config): return loss, len(testloader.dataset), {"accuracy": accuracy} -def client_fn(cid: str): +def client_fn(context: Context): """Create and return an instance of Flower `Client`.""" return FlowerClient().to_client() diff --git a/e2e/framework-fastai/client.py b/e2e/framework-fastai/client.py index 1d98a1134941..161b27b5a548 100644 --- a/e2e/framework-fastai/client.py +++ b/e2e/framework-fastai/client.py @@ -5,7 +5,8 @@ import torch from fastai.vision.all import * -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context warnings.filterwarnings("ignore", category=UserWarning) @@ -29,7 +30,7 @@ # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): return [val.cpu().numpy() for _, val in learn.model.state_dict().items()] @@ -49,18 +50,18 @@ def evaluate(self, parameters, config): return loss, len(dls.valid), {"accuracy": 1 - error_rate} -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( + start_client( server_address="127.0.0.1:8080", client=FlowerClient().to_client(), ) diff --git a/e2e/framework-jax/client.py b/e2e/framework-jax/client.py index 347a005d923a..c9ff67b3e38e 100644 --- a/e2e/framework-jax/client.py +++ b/e2e/framework-jax/client.py @@ -6,7 +6,8 @@ import jax_training import numpy as np -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context # Load data and determine model shape train_x, train_y, test_x, test_y = jax_training.load_data() @@ -14,7 +15,7 @@ model_shape = train_x.shape[1:] -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def __init__(self): self.params = jax_training.load_model(model_shape) @@ -48,16 +49,14 @@ def evaluate( return float(loss), num_examples, {"loss": float(loss)} -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( - server_address="127.0.0.1:8080", client=FlowerClient().to_client() - ) + start_client(server_address="127.0.0.1:8080", client=FlowerClient().to_client()) diff --git a/e2e/framework-opacus/client.py b/e2e/framework-opacus/client.py index c9ebe319063a..167fa4584e37 100644 --- a/e2e/framework-opacus/client.py +++ b/e2e/framework-opacus/client.py @@ -9,7 +9,8 @@ from torch.utils.data import DataLoader from torchvision.datasets import CIFAR10 -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context # Define parameters. PARAMS = { @@ -95,7 +96,7 @@ def load_data(): # Define Flower client. -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def __init__(self, model) -> None: super().__init__() # Create a privacy engine which will add DP and keep track of the privacy budget. @@ -139,16 +140,16 @@ def evaluate(self, parameters, config): return float(loss), len(testloader), {"accuracy": float(accuracy)} -def client_fn(cid): +def client_fn(context: Context): model = Net() return FlowerClient(model).to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": - fl.client.start_client( + start_client( server_address="127.0.0.1:8080", client=FlowerClient(model).to_client() ) diff --git a/e2e/framework-pandas/client.py b/e2e/framework-pandas/client.py index 19e15f5a3b11..0c3300e1dd3f 100644 --- a/e2e/framework-pandas/client.py +++ b/e2e/framework-pandas/client.py @@ -3,7 +3,8 @@ import numpy as np import pandas as pd -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context df = pd.read_csv("./data/client.csv") @@ -16,7 +17,7 @@ def compute_hist(df: pd.DataFrame, col_name: str) -> np.ndarray: # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def fit( self, parameters: List[np.ndarray], config: Dict[str, str] ) -> Tuple[List[np.ndarray], int, Dict]: @@ -32,17 +33,17 @@ def fit( ) -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( + start_client( server_address="127.0.0.1:8080", client=FlowerClient().to_client(), ) diff --git a/e2e/framework-pytorch-lightning/client.py b/e2e/framework-pytorch-lightning/client.py index fdd55b3dc344..bf291a1ca2c5 100644 --- a/e2e/framework-pytorch-lightning/client.py +++ b/e2e/framework-pytorch-lightning/client.py @@ -4,10 +4,11 @@ import pytorch_lightning as pl import torch -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def __init__(self, model, train_loader, val_loader, test_loader): self.model = model self.train_loader = train_loader @@ -51,7 +52,7 @@ def _set_parameters(model, parameters): model.load_state_dict(state_dict, strict=True) -def client_fn(cid): +def client_fn(context: Context): model = mnist.LitAutoEncoder() train_loader, val_loader, test_loader = mnist.load_data() @@ -59,7 +60,7 @@ def client_fn(cid): return FlowerClient(model, train_loader, val_loader, test_loader).to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) @@ -71,7 +72,7 @@ def main() -> None: # Flower client client = FlowerClient(model, train_loader, val_loader, test_loader).to_client() - fl.client.start_client(server_address="127.0.0.1:8080", client=client) + start_client(server_address="127.0.0.1:8080", client=client) if __name__ == "__main__": diff --git a/e2e/framework-pytorch/client.py b/e2e/framework-pytorch/client.py index dbfbfed1ffa7..ab4bc7b5c5b9 100644 --- a/e2e/framework-pytorch/client.py +++ b/e2e/framework-pytorch/client.py @@ -10,8 +10,8 @@ from torchvision.transforms import Compose, Normalize, ToTensor from tqdm import tqdm -import flwr as fl -from flwr.common import ConfigsRecord +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import ConfigsRecord, Context # ############################################################################# # 1. Regular PyTorch pipeline: nn.Module, train, test, and DataLoader @@ -89,7 +89,7 @@ def load_data(): # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): return [val.cpu().numpy() for _, val in net.state_dict().items()] @@ -136,18 +136,18 @@ def set_parameters(model, parameters): return -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( + start_client( server_address="127.0.0.1:8080", client=FlowerClient().to_client(), ) diff --git a/e2e/framework-scikit-learn/client.py b/e2e/framework-scikit-learn/client.py index b0691e75a79d..24c6617c1289 100644 --- a/e2e/framework-scikit-learn/client.py +++ b/e2e/framework-scikit-learn/client.py @@ -5,7 +5,8 @@ from sklearn.linear_model import LogisticRegression from sklearn.metrics import log_loss -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context # Load MNIST dataset from https://www.openml.org/d/554 (X_train, y_train), (X_test, y_test) = utils.load_mnist() @@ -26,7 +27,7 @@ # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): # type: ignore return utils.get_model_parameters(model) @@ -45,16 +46,14 @@ def evaluate(self, parameters, config): # type: ignore return loss, len(X_test), {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( - server_address="0.0.0.0:8080", client=FlowerClient().to_client() - ) + start_client(server_address="0.0.0.0:8080", client=FlowerClient().to_client()) diff --git a/e2e/framework-tensorflow/client.py b/e2e/framework-tensorflow/client.py index 779be0c3746d..351f495a3acb 100644 --- a/e2e/framework-tensorflow/client.py +++ b/e2e/framework-tensorflow/client.py @@ -2,7 +2,8 @@ import tensorflow as tf -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context SUBSET_SIZE = 1000 @@ -18,7 +19,7 @@ # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): return model.get_weights() @@ -33,16 +34,14 @@ def evaluate(self, parameters, config): return loss, len(x_test), {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( - server_address="127.0.0.1:8080", client=FlowerClient().to_client() - ) + start_client(server_address="127.0.0.1:8080", client=FlowerClient().to_client()) diff --git a/e2e/strategies/client.py b/e2e/strategies/client.py index 505340e013a5..0403416cc3b7 100644 --- a/e2e/strategies/client.py +++ b/e2e/strategies/client.py @@ -2,7 +2,8 @@ import tensorflow as tf -import flwr as fl +from flwr.client import ClientApp, NumPyClient, start_client +from flwr.common import Context SUBSET_SIZE = 1000 @@ -33,7 +34,7 @@ def get_model(): # Define Flower client -class FlowerClient(fl.client.NumPyClient): +class FlowerClient(NumPyClient): def get_parameters(self, config): return model.get_weights() @@ -48,17 +49,15 @@ def evaluate(self, parameters, config): return loss, len(x_test), {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): return FlowerClient().to_client() -app = fl.client.ClientApp( +app = ClientApp( client_fn=client_fn, ) if __name__ == "__main__": # Start Flower client - fl.client.start_client( - server_address="127.0.0.1:8080", client=FlowerClient().to_client() - ) + start_client(server_address="127.0.0.1:8080", client=FlowerClient().to_client()) diff --git a/e2e/strategies/test.py b/e2e/strategies/test.py index abf9cdb5a5c7..c567f33b236b 100644 --- a/e2e/strategies/test.py +++ b/e2e/strategies/test.py @@ -3,8 +3,8 @@ import tensorflow as tf from client import SUBSET_SIZE, FlowerClient, get_model -import flwr as fl -from flwr.common import ndarrays_to_parameters +from flwr.common import Context, ndarrays_to_parameters +from flwr.server import ServerConfig from flwr.server.strategy import ( FaultTolerantFedAvg, FedAdagrad, @@ -15,6 +15,7 @@ FedYogi, QFedAvg, ) +from flwr.simulation import start_simulation STRATEGY_LIST = [ FedMedian, @@ -42,8 +43,7 @@ def get_strat(name): init_model = get_model() -def client_fn(cid): - _ = cid +def client_fn(context: Context): return FlowerClient() @@ -71,10 +71,10 @@ def evaluate(server_round, parameters, config): if start_idx >= OPT_IDX: strat_args["tau"] = 0.01 -hist = fl.simulation.start_simulation( +hist = start_simulation( client_fn=client_fn, num_clients=2, - config=fl.server.ServerConfig(num_rounds=3), + config=ServerConfig(num_rounds=3), strategy=strategy(**strat_args), ) From bdc76028391630ab154234a41f39e22298760ac1 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sat, 13 Jul 2024 17:23:36 +0200 Subject: [PATCH 160/595] docs(framework) Update client_fn docstrings to new signature (#3793) --- src/py/flwr/client/app.py | 2 +- src/py/flwr/client/client_app.py | 2 +- src/py/flwr/simulation/app.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 700ac85f341f..348ef8910dd3 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -139,7 +139,7 @@ class `flwr.client.Client` (default: None) Starting an SSL-enabled gRPC client using system certificates: >>> def client_fn(context: Context): - >>> return FlowerClient() + >>> return FlowerClient().to_client() >>> >>> start_client( >>> server_address=localhost:8080, diff --git a/src/py/flwr/client/client_app.py b/src/py/flwr/client/client_app.py index 9566302d0721..2a913b3a248d 100644 --- a/src/py/flwr/client/client_app.py +++ b/src/py/flwr/client/client_app.py @@ -91,7 +91,7 @@ class ClientApp: >>> class FlowerClient(NumPyClient): >>> # ... >>> - >>> def client_fn(node_id: int, partition_id: Optional[int]): + >>> def client_fn(context: Context): >>> return FlowerClient().to_client() >>> >>> app = ClientApp(client_fn) diff --git a/src/py/flwr/simulation/app.py b/src/py/flwr/simulation/app.py index fc52267f9a04..973a9a89e652 100644 --- a/src/py/flwr/simulation/app.py +++ b/src/py/flwr/simulation/app.py @@ -111,9 +111,9 @@ def start_simulation( Parameters ---------- client_fn : ClientFnExt - A function creating Client instances. The function must have the signature - `client_fn(node_id: int, partition_id: Optional[int]). It should return - a single client instance of type Client. Note that the created client + A function creating `Client` instances. The function must have the signature + `client_fn(context: Context). It should return + a single client instance of type `Client`. Note that the created client instances are ephemeral and will often be destroyed after a single method invocation. Since client instances are not long-lived, they should not attempt to carry state over method invocations. Any state required by the instance From 7d5d6f38f87e5cb321a24f30745c17fe5d40310e Mon Sep 17 00:00:00 2001 From: Javier Date: Sat, 13 Jul 2024 22:31:25 +0200 Subject: [PATCH 161/595] refactor(framework) Update `flwr new` templates with new `client_fn` signature (#3795) Co-authored-by: Daniel J. Beutel --- src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl | 8 ++++++-- src/py/flwr/cli/new/templates/app/code/client.jax.py.tpl | 3 ++- src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl | 7 +++++-- .../flwr/cli/new/templates/app/code/client.numpy.py.tpl | 3 ++- .../flwr/cli/new/templates/app/code/client.pytorch.py.tpl | 7 +++++-- .../flwr/cli/new/templates/app/code/client.sklearn.py.tpl | 6 ++++-- .../cli/new/templates/app/code/client.tensorflow.py.tpl | 7 +++++-- src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl | 4 ++-- src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl | 4 ++-- .../flwr/cli/new/templates/app/code/task.pytorch.py.tpl | 2 +- 10 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl index 314da2120c53..56bac8543c50 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl @@ -1,6 +1,7 @@ """$project_name: A Flower / HuggingFace Transformers app.""" from flwr.client import ClientApp, NumPyClient +from flwr.common import Context from transformers import AutoModelForSequenceClassification from $import_name.task import ( @@ -38,12 +39,15 @@ class FlowerClient(NumPyClient): return float(loss), len(self.testloader), {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): # Load model and data net = AutoModelForSequenceClassification.from_pretrained( CHECKPOINT, num_labels=2 ).to(DEVICE) - trainloader, valloader = load_data(int(cid), 2) + + partition_id = int(context.node_config['partition-id']) + num_partitions = int(context.node_config['num-partitions]) + trainloader, valloader = load_data(partition_id, num_partitions) # Return Client instance return FlowerClient(net, trainloader, valloader).to_client() diff --git a/src/py/flwr/cli/new/templates/app/code/client.jax.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.jax.py.tpl index 3c6d2f03637a..48b667665f3f 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.jax.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.jax.py.tpl @@ -2,6 +2,7 @@ import jax from flwr.client import NumPyClient, ClientApp +from flwr.common import Context from $import_name.task import ( evaluation, @@ -44,7 +45,7 @@ class FlowerClient(NumPyClient): ) return float(loss), num_examples, {"loss": float(loss)} -def client_fn(cid): +def client_fn(context: Context): # Return Client instance return FlowerClient().to_client() diff --git a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl index 1722561370a8..37207c940d83 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl @@ -4,6 +4,7 @@ import mlx.core as mx import mlx.nn as nn import mlx.optimizers as optim from flwr.client import NumPyClient, ClientApp +from flwr.common import Context from $import_name.task import ( batch_iterate, @@ -57,8 +58,10 @@ class FlowerClient(NumPyClient): return loss.item(), len(self.test_images), {"accuracy": accuracy.item()} -def client_fn(cid): - data = load_data(int(cid), 2) +def client_fn(context: Context): + partition_id = int(context.node_config["partition-id"]) + num_partitions = int(context.node_config["num-partitions"]) + data = load_data(partition_id, num_partitions) # Return Client instance return FlowerClient(data).to_client() diff --git a/src/py/flwr/cli/new/templates/app/code/client.numpy.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.numpy.py.tpl index 232c305fc2a9..1dd83e108bb5 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.numpy.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.numpy.py.tpl @@ -1,6 +1,7 @@ """$project_name: A Flower / NumPy app.""" from flwr.client import NumPyClient, ClientApp +from flwr.common import Context import numpy as np @@ -15,7 +16,7 @@ class FlowerClient(NumPyClient): return float(0.0), 1, {"accuracy": float(1.0)} -def client_fn(cid: str): +def client_fn(context: Context): return FlowerClient().to_client() diff --git a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl index c68974efaadf..addc71023a09 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl @@ -1,6 +1,7 @@ """$project_name: A Flower / PyTorch app.""" from flwr.client import NumPyClient, ClientApp +from flwr.common import Context from $import_name.task import ( Net, @@ -31,10 +32,12 @@ class FlowerClient(NumPyClient): return loss, len(self.valloader.dataset), {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): # Load model and data net = Net().to(DEVICE) - trainloader, valloader = load_data(int(cid), 2) + partition_id = int(context.node_config["partition-id"]) + num_partitions = int(context.node_config["num-partitions"]) + trainloader, valloader = load_data(partition_id, num_partitions) # Return Client instance return FlowerClient(net, trainloader, valloader).to_client() diff --git a/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl index 9181389cad1c..a1eefa034e7b 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl @@ -4,6 +4,7 @@ import warnings import numpy as np from flwr.client import NumPyClient, ClientApp +from flwr.common import Context from flwr_datasets import FederatedDataset from sklearn.linear_model import LogisticRegression from sklearn.metrics import log_loss @@ -68,8 +69,9 @@ class FlowerClient(NumPyClient): fds = FederatedDataset(dataset="mnist", partitioners={"train": 2}) -def client_fn(cid: str): - dataset = fds.load_partition(int(cid), "train").with_format("numpy") +def client_fn(context: Context): + partition_id = int(context.node_config["partition-id"]) + dataset = fds.load_partition(partition_id, "train").with_format("numpy") X, y = dataset["image"].reshape((len(dataset), -1)), dataset["label"] diff --git a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl index dc55d4ca6569..0fe1c405a110 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl @@ -1,6 +1,7 @@ """$project_name: A Flower / TensorFlow app.""" from flwr.client import NumPyClient, ClientApp +from flwr.common import Context from $import_name.task import load_data, load_model @@ -28,10 +29,12 @@ class FlowerClient(NumPyClient): return loss, len(self.x_test), {"accuracy": accuracy} -def client_fn(cid): +def client_fn(context: Context): # Load model and data net = load_model() - x_train, y_train, x_test, y_test = load_data(int(cid), 2) + + partition_id = int(context.node_config["partition-id"]) + x_train, y_train, x_test, y_test = load_data(partition_id, 2) # Return Client instance return FlowerClient(net, x_train, y_train, x_test, y_test).to_client() diff --git a/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl index 8e89add66835..eb43acfce976 100644 --- a/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl @@ -16,9 +16,9 @@ DEVICE = torch.device("cpu") CHECKPOINT = "distilbert-base-uncased" # transformer model checkpoint -def load_data(partition_id, num_clients): +def load_data(partition_id: int, num_partitions: int): """Load IMDB data (training and eval)""" - fds = FederatedDataset(dataset="imdb", partitioners={"train": num_clients}) + fds = FederatedDataset(dataset="imdb", partitioners={"train": num_partitions}) partition = fds.load_partition(partition_id) # Divide data: 80% train, 20% test partition_train_test = partition.train_test_split(test_size=0.2, seed=42) diff --git a/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl index bcd4dde93310..88053b0cd590 100644 --- a/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl @@ -43,8 +43,8 @@ def batch_iterate(batch_size, X, y): yield X[ids], y[ids] -def load_data(partition_id, num_clients): - fds = FederatedDataset(dataset="mnist", partitioners={"train": num_clients}) +def load_data(partition_id: int, num_partitions: int): + fds = FederatedDataset(dataset="mnist", partitioners={"train": num_partitions}) partition = fds.load_partition(partition_id) partition_splits = partition.train_test_split(test_size=0.2, seed=42) diff --git a/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl index b30c65a285b5..d5971ffb6ce5 100644 --- a/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl @@ -34,7 +34,7 @@ class Net(nn.Module): return self.fc3(x) -def load_data(partition_id, num_partitions): +def load_data(partition_id: int, num_partitions: int): """Load partition CIFAR10 data.""" fds = FederatedDataset(dataset="cifar10", partitioners={"train": num_partitions}) partition = fds.load_partition(partition_id) From aee2137bde30d829eb6593cbcf879d8f1471c080 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 14 Jul 2024 13:49:35 +0200 Subject: [PATCH 162/595] feat(framework) Add SuperExec `--executor-config` (#3720) Co-authored-by: Daniel J. Beutel --- src/py/flwr/superexec/app.py | 16 ++++-- src/py/flwr/superexec/deployment.py | 86 ++++++++++++++++++++++++----- src/py/flwr/superexec/exec_grpc.py | 7 ++- src/py/flwr/superexec/executor.py | 19 ++++++- 4 files changed, 106 insertions(+), 22 deletions(-) diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py index 372ccb443a76..b51c3e6821dc 100644 --- a/src/py/flwr/superexec/app.py +++ b/src/py/flwr/superexec/app.py @@ -24,6 +24,7 @@ from flwr.common import EventType, event, log from flwr.common.address import parse_address +from flwr.common.config import parse_config_args from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS from flwr.common.exit_handlers import register_exit_handlers from flwr.common.object_ref import load_app, validate @@ -55,6 +56,7 @@ def run_superexec() -> None: address=address, executor=_load_executor(args), certificates=certificates, + config=parse_config_args(args.executor_config), ) grpc_servers = [superexec_server] @@ -74,21 +76,25 @@ def _parse_args_run_superexec() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( description="Start a Flower SuperExec", ) - parser.add_argument( - "executor", - help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.", - default="flwr.superexec.deployment:executor", - ) parser.add_argument( "--address", help="SuperExec (gRPC) server address (IPv4, IPv6, or a domain name)", default=SUPEREXEC_DEFAULT_ADDRESS, ) + parser.add_argument( + "--executor", + help="For example: `deployment:exec` or `project.package.module:wrapper.exec`.", + default="flwr.superexec.deployment:executor", + ) parser.add_argument( "--executor-dir", help="The directory for the executor.", default=".", ) + parser.add_argument( + "--executor-config", + help="Key-value pairs for the executor config, separated by commas.", + ) parser.add_argument( "--insecure", action="store_true", diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index d117f280b38d..f9a272e6b0bf 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -17,6 +17,7 @@ import subprocess import sys from logging import ERROR, INFO +from pathlib import Path from typing import Dict, Optional from typing_extensions import override @@ -33,25 +34,73 @@ class DeploymentEngine(Executor): - """Deployment engine executor.""" + """Deployment engine executor. + + Parameters + ---------- + superlink: str (default: "0.0.0.0:9091") + Address of the SuperLink to connect to. + root_certificates: Optional[str] (default: None) + Specifies the path to the PEM-encoded root certificate file for + establishing secure HTTPS connections. + flwr_dir: Optional[str] (default: None) + The path containing installed Flower Apps. + """ def __init__( self, - address: str = DEFAULT_SERVER_ADDRESS_DRIVER, - root_certificates: Optional[bytes] = None, + superlink: str = DEFAULT_SERVER_ADDRESS_DRIVER, + root_certificates: Optional[str] = None, + flwr_dir: Optional[str] = None, ) -> None: - self.address = address - self.root_certificates = root_certificates + self.superlink = superlink + if root_certificates is None: + self.root_certificates = None + self.root_certificates_bytes = None + else: + self.root_certificates = root_certificates + self.root_certificates_bytes = Path(root_certificates).read_bytes() + self.flwr_dir = flwr_dir self.stub: Optional[DriverStub] = None + @override + def set_config( + self, + config: Dict[str, str], + ) -> None: + """Set executor config arguments. + + Parameters + ---------- + config : Dict[str, str] + A dictionary for configuration values. + Supported configuration key/value pairs: + - "superlink": str + The address of the SuperLink Driver API. + - "root-certificates": str + The path to the root certificates. + - "flwr-dir": str + The path to the Flower directory. + """ + if not config: + return + if superlink_address := config.get("superlink"): + self.superlink = superlink_address + if root_certificates := config.get("root-certificates"): + self.root_certificates = root_certificates + self.root_certificates_bytes = Path(root_certificates).read_bytes() + if flwr_dir := config.get("flwr-dir"): + self.flwr_dir = flwr_dir + def _connect(self) -> None: - if self.stub is None: - channel = create_channel( - server_address=self.address, - insecure=(self.root_certificates is None), - root_certificates=self.root_certificates, - ) - self.stub = DriverStub(channel) + if self.stub is not None: + return + channel = create_channel( + server_address=self.superlink, + insecure=(self.root_certificates_bytes is None), + root_certificates=self.root_certificates_bytes, + ) + self.stub = DriverStub(channel) def _create_run( self, @@ -74,7 +123,9 @@ def _create_run( @override def start_run( - self, fab_file: bytes, override_config: Dict[str, str] + self, + fab_file: bytes, + override_config: Dict[str, str], ) -> Optional[RunTracker]: """Start run using the Flower Deployment Engine.""" try: @@ -99,7 +150,14 @@ def start_run( "flower-server-app", "--run-id", str(run_id), - "--insecure", + f"--flwr-dir {self.flwr_dir}" if self.flwr_dir else "", + "--superlink", + self.superlink, + ( + "--insecure" + if self.root_certificates is None + else f"--root-certificates {self.root_certificates}" + ), ], stdout=subprocess.PIPE, stderr=subprocess.PIPE, diff --git a/src/py/flwr/superexec/exec_grpc.py b/src/py/flwr/superexec/exec_grpc.py index 127d5615dd84..d90cec3e47cd 100644 --- a/src/py/flwr/superexec/exec_grpc.py +++ b/src/py/flwr/superexec/exec_grpc.py @@ -15,7 +15,7 @@ """SuperExec gRPC API.""" from logging import INFO -from typing import Optional, Tuple +from typing import Dict, Optional, Tuple import grpc @@ -32,8 +32,11 @@ def run_superexec_api_grpc( address: str, executor: Executor, certificates: Optional[Tuple[bytes, bytes, bytes]], + config: Dict[str, str], ) -> grpc.Server: """Run SuperExec API (gRPC, request-response).""" + executor.set_config(config) + exec_servicer: grpc.Server = ExecServicer( executor=executor, ) @@ -45,7 +48,7 @@ def run_superexec_api_grpc( certificates=certificates, ) - log(INFO, "Flower ECE: Starting SuperExec API (gRPC-rere) on %s", address) + log(INFO, "Starting Flower SuperExec gRPC server on %s", address) superexec_grpc_server.start() return superexec_grpc_server diff --git a/src/py/flwr/superexec/executor.py b/src/py/flwr/superexec/executor.py index 85b6e5c3e095..62d64f366cec 100644 --- a/src/py/flwr/superexec/executor.py +++ b/src/py/flwr/superexec/executor.py @@ -31,9 +31,24 @@ class RunTracker: class Executor(ABC): """Execute and monitor a Flower run.""" + @abstractmethod + def set_config( + self, + config: Dict[str, str], + ) -> None: + """Register provided config as class attributes. + + Parameters + ---------- + config : Optional[Dict[str, str]] + A dictionary for configuration values. + """ + @abstractmethod def start_run( - self, fab_file: bytes, override_config: Dict[str, str] + self, + fab_file: bytes, + override_config: Dict[str, str], ) -> Optional[RunTracker]: """Start a run using the given Flower FAB ID and version. @@ -44,6 +59,8 @@ def start_run( ---------- fab_file : bytes The Flower App Bundle file bytes. + override_config: Dict[str, str] + The config overrides dict sent by the user (using `flwr run`). Returns ------- From 79eb86c490f9da90b9d50cee2885cbebf959a154 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 14 Jul 2024 14:18:56 +0200 Subject: [PATCH 163/595] refactor(framework) Update `flwr new` templates with new `server_fn` signature (#3796) Co-authored-by: Daniel J. Beutel --- .../new/templates/app/code/server.hf.py.tpl | 24 +++++++++-------- .../new/templates/app/code/server.jax.py.tpl | 20 ++++++++------ .../new/templates/app/code/server.mlx.py.tpl | 15 ++++++----- .../templates/app/code/server.numpy.py.tpl | 20 ++++++++------ .../templates/app/code/server.pytorch.py.tpl | 27 +++++++++---------- .../templates/app/code/server.sklearn.py.tpl | 23 +++++++++------- .../app/code/server.tensorflow.py.tpl | 26 +++++++++--------- 7 files changed, 84 insertions(+), 71 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl index d7d86931335b..039ea8619532 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl @@ -1,17 +1,19 @@ """$project_name: A Flower / HuggingFace Transformers app.""" +from flwr.common import Context from flwr.server.strategy import FedAvg -from flwr.server import ServerApp, ServerConfig +from flwr.server import ServerApp, ServerAppComponents, ServerConfig -# Define strategy -strategy = FedAvg( - fraction_fit=1.0, - fraction_evaluate=1.0, -) +def server_fn(context: Context): + # Define strategy + strategy = FedAvg( + fraction_fit=1.0, + fraction_evaluate=1.0, + ) + config = ServerConfig(num_rounds=3) -# Start server -app = ServerApp( - config=ServerConfig(num_rounds=3), - strategy=strategy, -) + return ServerAppComponents(strategy=strategy, config=config) + +# Create ServerApp +app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl index 53cff7b905f4..122b884ab8bb 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl @@ -1,12 +1,16 @@ """$project_name: A Flower / JAX app.""" -import flwr as fl +from flwr.common import Context +from flwr.server.strategy import FedAvg +from flwr.server import ServerApp, ServerAppComponents, ServerConfig -# Configure the strategy -strategy = fl.server.strategy.FedAvg() -# Flower ServerApp -app = fl.server.ServerApp( - config=fl.server.ServerConfig(num_rounds=3), - strategy=strategy, -) +def server_fn(context: Context): + # Define strategy + strategy = FedAvg() + config = ServerConfig(num_rounds=3) + + return ServerAppComponents(strategy=strategy, config=config) + +# Create ServerApp +app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl index b475e0e7dc36..403c68ac3405 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl @@ -1,15 +1,16 @@ """$project_name: A Flower / MLX app.""" -from flwr.server import ServerApp, ServerConfig +from flwr.common import Context +from flwr.server import ServerApp, ServerAppComponents, ServerConfig from flwr.server.strategy import FedAvg -# Define strategy -strategy = FedAvg() +def server_fn(context: Context): + # Define strategy + strategy = FedAvg() + config = ServerConfig(num_rounds=3) + return ServerAppComponents(strategy=strategy, config=config) # Create ServerApp -app = ServerApp( - config=ServerConfig(num_rounds=3), - strategy=strategy, -) +app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl index 03f95ae35cfd..1ed2d36339db 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl @@ -1,12 +1,16 @@ """$project_name: A Flower / NumPy app.""" -import flwr as fl +from flwr.common import Context +from flwr.server import ServerApp, ServerAppComponents, ServerConfig +from flwr.server.strategy import FedAvg -# Configure the strategy -strategy = fl.server.strategy.FedAvg() -# Flower ServerApp -app = fl.server.ServerApp( - config=fl.server.ServerConfig(num_rounds=1), - strategy=strategy, -) +def server_fn(context: Context): + # Define strategy + strategy = FedAvg() + config = ServerConfig(num_rounds=3) + + return ServerAppComponents(strategy=strategy, config=config) + +# Create ServerApp +app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl index dc635f79a664..3638b9eba7b0 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl @@ -1,7 +1,7 @@ """$project_name: A Flower / PyTorch app.""" -from flwr.common import ndarrays_to_parameters -from flwr.server import ServerApp, ServerConfig +from flwr.common import Context, ndarrays_to_parameters +from flwr.server import ServerApp, ServerAppComponents, ServerConfig from flwr.server.strategy import FedAvg from $import_name.task import Net, get_weights @@ -11,18 +11,17 @@ from $import_name.task import Net, get_weights ndarrays = get_weights(Net()) parameters = ndarrays_to_parameters(ndarrays) +def server_fn(context: Context): + # Define strategy + strategy = FedAvg( + fraction_fit=1.0, + fraction_evaluate=1.0, + min_available_clients=2, + initial_parameters=parameters, + ) + config = ServerConfig(num_rounds=3) -# Define strategy -strategy = FedAvg( - fraction_fit=1.0, - fraction_evaluate=1.0, - min_available_clients=2, - initial_parameters=parameters, -) - + return ServerAppComponents(strategy=strategy, config=config) # Create ServerApp -app = ServerApp( - config=ServerConfig(num_rounds=3), - strategy=strategy, -) +app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl index 266a53ac5794..2e463e8da09e 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl @@ -1,17 +1,20 @@ """$project_name: A Flower / Scikit-Learn app.""" -from flwr.server import ServerApp, ServerConfig +from flwr.common import Context +from flwr.server import ServerApp, ServerAppComponents, ServerConfig from flwr.server.strategy import FedAvg -strategy = FedAvg( - fraction_fit=1.0, - fraction_evaluate=1.0, - min_available_clients=2, -) +def server_fn(context: Context): + # Define strategy + strategy = FedAvg( + fraction_fit=1.0, + fraction_evaluate=1.0, + min_available_clients=2, + ) + config = ServerConfig(num_rounds=3) + + return ServerAppComponents(strategy=strategy, config=config) # Create ServerApp -app = ServerApp( - config=ServerConfig(num_rounds=3), - strategy=strategy, -) +app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl index 8d092164a468..eee727ba9025 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl @@ -1,7 +1,7 @@ """$project_name: A Flower / TensorFlow app.""" -from flwr.common import ndarrays_to_parameters -from flwr.server import ServerApp, ServerConfig +from flwr.common import Context, ndarrays_to_parameters +from flwr.server import ServerApp, ServerAppComponents, ServerConfig from flwr.server.strategy import FedAvg from $import_name.task import load_model @@ -11,17 +11,17 @@ config = ServerConfig(num_rounds=3) parameters = ndarrays_to_parameters(load_model().get_weights()) -# Define strategy -strategy = FedAvg( - fraction_fit=1.0, - fraction_evaluate=1.0, - min_available_clients=2, - initial_parameters=parameters, -) +def server_fn(context: Context): + # Define strategy + strategy = strategy = FedAvg( + fraction_fit=1.0, + fraction_evaluate=1.0, + min_available_clients=2, + initial_parameters=parameters, + ) + config = ServerConfig(num_rounds=3) + return ServerAppComponents(strategy=strategy, config=config) # Create ServerApp -app = ServerApp( - config=config, - strategy=strategy, -) +app = ServerApp(server_fn=server_fn) From 52597bd59a816eb939e877b454043d185d78e4c6 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 14 Jul 2024 14:49:30 +0200 Subject: [PATCH 164/595] fix(framework:skip) Remove dependency installation from SuperExec (#3781) Co-authored-by: Daniel J. Beutel --- src/py/flwr/superexec/deployment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index f9a272e6b0bf..3a3bc3bf2b1e 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -135,7 +135,7 @@ def start_run( # Install FAB Python package subprocess.check_call( - [sys.executable, "-m", "pip", "install", str(fab_path)], + [sys.executable, "-m", "pip", "install", "--no-deps", str(fab_path)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) From 0531668cbd3a6f1a1f17d6d6e00209e69ba29e5c Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 14 Jul 2024 15:46:41 +0200 Subject: [PATCH 165/595] refactor(framework) Rename `--config` to `--run-config` (#3798) --- src/py/flwr/cli/run/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 4ee2368f5794..b23ba3f7d0cf 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -62,7 +62,7 @@ def run( config_overrides: Annotated[ Optional[str], typer.Option( - "--config", + "--run-config", "-c", help="Override configuration key-value pairs", ), From 9831e8a68abfbd6024d9d34076299cc2e17c0ed0 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Sun, 14 Jul 2024 22:27:28 +0200 Subject: [PATCH 166/595] docs(framework) Document public/private API approach (#3562) --- ...or-explanation-public-and-private-apis.rst | 118 ++++++++++++++++++ doc/source/index.rst | 1 + 2 files changed, 119 insertions(+) create mode 100644 doc/source/contributor-explanation-public-and-private-apis.rst diff --git a/doc/source/contributor-explanation-public-and-private-apis.rst b/doc/source/contributor-explanation-public-and-private-apis.rst new file mode 100644 index 000000000000..1dfdf88f97d3 --- /dev/null +++ b/doc/source/contributor-explanation-public-and-private-apis.rst @@ -0,0 +1,118 @@ +Public and private APIs +======================= + +In Python, everything is public. +To enable developers to understand which components can be relied upon, Flower declares a public API. +Components that are part of the public API can be relied upon. +Changes to the public API are announced in the release notes and are subject to deprecation policies. + +Everything that is not part of the public API is part of the private API. +Even though Python allows accessing them, user code should never use those components. +Private APIs can change at any time, even in patch releases. + +How can you determine whether a component is part of the public API or not? Easy: + +- `Use the Flower API reference documentation `_ +- `Use the Flower CLI reference documentation `_ + +Everything listed in the reference documentation is part of the public API. +This document explains how Flower maintainers define the public API and how you can determine whether a component is part of the public API or not by reading the Flower source code. + +Flower public API +----------------- + +Flower has a well-defined public API. Let's look at this in more detail. + +.. important:: + + Every component that is reachable by recursively following ``__init__.__all__`` starting from the root package (``flwr``) is part of the public API. + +If you want to determine whether a component (class/function/generator/...) is part of the public API or not, you need to start at the root of the ``flwr`` package. +Let's use ``tree -L 1 -d src/py/flwr`` to look at the Python sub-packages contained ``flwr``: + +.. code-block:: bash + + flwr + ├── cli + ├── client + ├── common + ├── proto + ├── server + └── simulation + +Contrast this with the definition of ``__all__`` in the root ``src/py/flwr/__init__.py``: + +.. code-block:: python + + # From `flwr/__init__.py` + __all__ = [ + "client", + "common", + "server", + "simulation", + ] + +You can see that ``flwr`` has six subpackages (``cli``, ``client``, ``common``, ``proto``, ``server``, ``simulation``), but only four of them are "exported" via ``__all__`` (``client``, ``common``, ``server``, ``simulation``). + +What does this mean? It means that ``client``, ``common``, ``server`` and ``simulation`` are part of the public API, but ``cli`` and ``proto`` are not. +The ``flwr`` subpackages ``cli`` and ``proto`` are private APIs. +A private API can change completely from one release to the next (even in patch releases). +It can change in a breaking way, it can be renamed (for example, ``flwr.cli`` could be renamed to ``flwr.command``) and it can even be removed completely. + +Therefore, as a Flower user: + +- ``from flwr import client`` ✅ Ok, you're importing a public API. +- ``from flwr import proto`` ❌ Not recommended, you're importing a private API. + +What about components that are nested deeper in the hierarchy? Let's look at Flower strategies to see another typical pattern. +Flower strategies like ``FedAvg`` are often imported using ``from flwr.server.strategy import FedAvg``. +Let's look at ``src/py/flwr/server/strategy/__init__.py``: + +.. code-block:: python + + from .fedavg import FedAvg as FedAvg + # ... more imports + + __all__ = [ + "FedAvg", + # ... more exports + ] + +What's notable here is that all strategies are implemented in dedicated modules (e.g., ``fedavg.py``). +In ``__init__.py``, we *import* the components we want to make part of the public API and then *export* them via ``__all__``. +Note that we export the component itself (for example, the ``FedAvg`` class), but not the module it is defined in (for example, ``fedavg.py``). +This allows us to move the definition of ``FedAvg`` into a different module (or even a module in a subpackage) without breaking the public API (as long as we update the import path in ``__init__.py``). + +Therefore: + +- ``from flwr.server.strategy import FedAvg`` ✅ Ok, you're importing a class that is part of the public API. +- ``from flwr.server.strategy import fedavg`` ❌ Not recommended, you're importing a private module. + +This approach is also implemented in the tooling that automatically builds API reference docs. + +Flower public API of private packages +------------------------------------- + +We also use this to define the public API of private subpackages. +Public, in this context, means the API that other ``flwr`` subpackages should use. +For example, ``flwr.server.driver`` is a private subpackage (it's not exported via ``src/py/flwr/server/__init__.py``'s ``__all__``). + +Still, the private sub-package ``flwr.server.driver`` defines a "public" API using ``__all__`` in ``src/py/flwr/server/driver/__init__.py``: + +.. code-block:: python + + from .driver import Driver + from .grpc_driver import GrpcDriver + from .inmemory_driver import InMemoryDriver + + __all__ = [ + "Driver", + "GrpcDriver", + "InMemoryDriver", + ] + +The interesting part is that both ``GrpcDriver`` and ``InMemoryDriver`` are never used by Flower framework users, only by other parts of the Flower framework codebase. +Those other parts of the codebase import, for example, ``InMemoryDriver`` using ``from flwr.server.driver import InMemoryDriver`` (i.e., the ``InMemoryDriver`` exported via ``__all__``), not ``from flwr.server.driver.in_memory_driver import InMemoryDriver`` (``in_memory_driver.py`` is the module containing the actual ``InMemoryDriver`` class definition). + +This is because ``flwr.server.driver`` defines a public interface for other ``flwr`` subpackages. +This allows codeowners of ``flwr.server.driver`` to refactor the package without breaking other ``flwr``-internal users. diff --git a/doc/source/index.rst b/doc/source/index.rst index f62c5ebf4786..a0115620fce9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -174,6 +174,7 @@ The Flower community welcomes contributions. The following docs are intended to :caption: Contributor explanations contributor-explanation-architecture + contributor-explanation-public-and-private-apis .. toctree:: :maxdepth: 1 From db9759733e49aed6df6eb971840087974c63ac2e Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 14 Jul 2024 22:43:38 +0200 Subject: [PATCH 167/595] fix(framework:skip) Use correct arguments (#3799) --- src/py/flwr/superexec/deployment.py | 34 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index 3a3bc3bf2b1e..bbe7882692f0 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -144,21 +144,27 @@ def start_run( run_id: int = self._create_run(fab_id, fab_version, override_config) log(INFO, "Created run %s", str(run_id)) - # Start ServerApp + command = [ + "flower-server-app", + "--run-id", + str(run_id), + "--superlink", + str(self.superlink), + ] + + if self.flwr_dir: + command.append("--flwr-dir") + command.append(self.flwr_dir) + + if self.root_certificates is None: + command.append("--insecure") + else: + command.append("--root-certificates") + command.append(self.root_certificates) + + # Execute the command proc = subprocess.Popen( # pylint: disable=consider-using-with - [ - "flower-server-app", - "--run-id", - str(run_id), - f"--flwr-dir {self.flwr_dir}" if self.flwr_dir else "", - "--superlink", - self.superlink, - ( - "--insecure" - if self.root_certificates is None - else f"--root-certificates {self.root_certificates}" - ), - ], + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, From 5155a62de2721fd58fd85cecfcc51c4302b940fb Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 15 Jul 2024 10:12:54 +0200 Subject: [PATCH 168/595] feat(framework) Add simulation engine `SuperExec` plugin (#3589) Co-authored-by: Charles Beauville Co-authored-by: Daniel J. Beutel --- src/py/flwr/superexec/simulation.py | 157 ++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/py/flwr/superexec/simulation.py diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py new file mode 100644 index 000000000000..9a8e19365ab9 --- /dev/null +++ b/src/py/flwr/superexec/simulation.py @@ -0,0 +1,157 @@ +# Copyright 2024 Flower Labs GmbH. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ============================================================================== +"""Simulation engine executor.""" + + +import subprocess +import sys +from logging import ERROR, INFO, WARN +from typing import Dict, Optional + +from typing_extensions import override + +from flwr.cli.config_utils import load_and_validate +from flwr.cli.install import install_from_fab +from flwr.common.constant import RUN_ID_NUM_BYTES +from flwr.common.logger import log +from flwr.server.superlink.state.utils import generate_rand_int_from_bytes + +from .executor import Executor, RunTracker + + +class SimulationEngine(Executor): + """Simulation engine executor. + + Parameters + ---------- + num_supernodes: Opitonal[str] (default: None) + Total number of nodes to involve in the simulation. + """ + + def __init__( + self, + num_supernodes: Optional[str] = None, + ) -> None: + self.num_supernodes = num_supernodes + + @override + def set_config( + self, + config: Dict[str, str], + ) -> None: + """Set executor config arguments. + + Parameters + ---------- + config : Dict[str, str] + A dictionary for configuration values. + Supported configuration key/value pairs: + - "num-supernodes": str + Number of nodes to register for the simulation. + """ + if not config: + return + if num_supernodes := config.get("num-supernodes"): + self.num_supernodes = num_supernodes + + # Validate config + if self.num_supernodes is None: + log( + ERROR, + "To start a run with the simulation plugin, please specify " + "the number of SuperNodes. This can be done by using the " + "`--executor-config` argument when launching the SuperExec.", + ) + raise ValueError("`num-supernodes` must not be `None`") + + @override + def start_run( + self, fab_file: bytes, override_config: Dict[str, str] + ) -> Optional[RunTracker]: + """Start run using the Flower Simulation Engine.""" + try: + if override_config: + raise ValueError( + "Overriding the run config is not yet supported with the " + "simulation executor.", + ) + + # Install FAB to flwr dir + fab_path = install_from_fab(fab_file, None, True) + + # Install FAB Python package + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "--no-deps", str(fab_path)], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + # Load and validate config + config, errors, warnings = load_and_validate(fab_path / "pyproject.toml") + if errors: + raise ValueError(errors) + + if warnings: + log(WARN, warnings) + + if config is None: + raise ValueError( + "Config extracted from FAB's pyproject.toml is not valid" + ) + + # Get ClientApp and SeverApp components + flower_components = config["flower"]["components"] + clientapp = flower_components["clientapp"] + serverapp = flower_components["serverapp"] + + # In Simulation there is no SuperLink, still we create a run_id + run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) + log(INFO, "Created run %s", str(run_id)) + + # Prepare commnand + command = [ + "flower-simulation", + "--client-app", + f"{clientapp}", + "--server-app", + f"{serverapp}", + "--num-supernodes", + f"{self.num_supernodes}", + "--run-id", + str(run_id), + ] + + # Start Simulation + proc = subprocess.Popen( # pylint: disable=consider-using-with + command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + log(INFO, "Started run %s", str(run_id)) + + return RunTracker( + run_id=run_id, + proc=proc, + ) + + # pylint: disable-next=broad-except + except Exception as e: + log(ERROR, "Could not start run: %s", str(e)) + return None + + +executor = SimulationEngine() From 31b86b0acdbefa87ce288ada9f3190035d42e2ea Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 15 Jul 2024 16:06:16 +0200 Subject: [PATCH 169/595] refactor(framework) Replace `run_id` with `Run` in simulation (#3802) --- src/py/flwr/simulation/run_simulation.py | 48 ++++++++++++------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index de101fe3e09f..7060a972dd9a 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -26,14 +26,16 @@ from flwr.client import ClientApp from flwr.common import EventType, event, log +from flwr.common.constant import RUN_ID_NUM_BYTES from flwr.common.logger import set_logger_propagation, update_console_handler from flwr.common.typing import Run from flwr.server.driver import Driver, InMemoryDriver -from flwr.server.run_serverapp import run +from flwr.server.run_serverapp import run as run_server_app from flwr.server.server_app import ServerApp from flwr.server.superlink.fleet import vce from flwr.server.superlink.fleet.vce.backend.backend import BackendConfig from flwr.server.superlink.state import StateFactory +from flwr.server.superlink.state.utils import generate_rand_int_from_bytes from flwr.simulation.ray_transport.utils import ( enable_tf_gpu_growth as enable_gpu_growth, ) @@ -54,7 +56,11 @@ def run_simulation_from_cli() -> None: backend_name=args.backend, backend_config=backend_config_dict, app_dir=args.app_dir, - run_id=args.run_id, + run=( + Run(run_id=args.run_id, fab_id="", fab_version="", override_config={}) + if args.run_id + else None + ), enable_tf_gpu_growth=args.enable_tf_gpu_growth, verbose_logging=args.verbose, ) @@ -156,7 +162,7 @@ def server_th_with_start_checks( enable_gpu_growth() # Run ServerApp - run( + run_server_app( driver=_driver, server_app_dir=_server_app_dir, server_app_run_config=_server_app_run_config, @@ -193,16 +199,6 @@ def server_th_with_start_checks( return serverapp_th -def _override_run_id(state: StateFactory, run_id_to_replace: int, run_id: int) -> None: - """Override the run_id of an existing Run.""" - log(DEBUG, "Pre-registering run with id %s", run_id) - # Remove run - run_info: Run = state.state().run_ids.pop(run_id_to_replace) # type: ignore - # Update with new run_id and insert back in state - run_info.run_id = run_id - state.state().run_ids[run_id] = run_info # type: ignore - - # pylint: disable=too-many-locals def _main_loop( num_supernodes: int, @@ -210,7 +206,7 @@ def _main_loop( backend_config_stream: str, app_dir: str, enable_tf_gpu_growth: bool, - run_id: Optional[int] = None, + run: Run, client_app: Optional[ClientApp] = None, client_app_attr: Optional[str] = None, server_app: Optional[ServerApp] = None, @@ -225,16 +221,13 @@ def _main_loop( server_app_thread_has_exception = threading.Event() serverapp_th = None try: - # Create run (with empty fab_id and fab_version) - run_id_ = state_factory.state().create_run("", "", {}) + # Register run + log(DEBUG, "Pre-registering run with id %s", run.run_id) + state_factory.state().run_ids[run.run_id] = run # type: ignore server_app_run_config: Dict[str, str] = {} - if run_id: - _override_run_id(state_factory, run_id_to_replace=run_id_, run_id=run_id) - run_id_ = run_id - # Initialize Driver - driver = InMemoryDriver(run_id=run_id_, state_factory=state_factory) + driver = InMemoryDriver(run_id=run.run_id, state_factory=state_factory) # Get and run ServerApp thread serverapp_th = run_serverapp_th( @@ -289,7 +282,7 @@ def _run_simulation( client_app_attr: Optional[str] = None, server_app_attr: Optional[str] = None, app_dir: str = "", - run_id: Optional[int] = None, + run: Optional[Run] = None, enable_tf_gpu_growth: bool = False, verbose_logging: bool = False, ) -> None: @@ -332,8 +325,8 @@ def _run_simulation( Add specified directory to the PYTHONPATH and load `ClientApp` from there. (Default: current working directory.) - run_id : Optional[int] - An integer specifying the ID of the run started when running this function. + run : Optional[Run] + An object carrying details about the run. enable_tf_gpu_growth : bool (default: False) A boolean to indicate whether to enable GPU growth on the main thread. This is @@ -371,13 +364,18 @@ def _run_simulation( # Convert config to original JSON-stream format backend_config_stream = json.dumps(backend_config) + # If no `Run` object is set, create one + if run is None: + run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) + run = Run(run_id=run_id, fab_id="", fab_version="", override_config={}) + args = ( num_supernodes, backend_name, backend_config_stream, app_dir, enable_tf_gpu_growth, - run_id, + run, client_app, client_app_attr, server_app, From ee5b2878bd6efe35a649f044a16f9e5e6f95d4a2 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 15 Jul 2024 16:29:08 +0200 Subject: [PATCH 170/595] refactor(framework) Register `Context` early in Simulation Engine (#3804) --- .../server/superlink/fleet/vce/vce_api.py | 42 ++++++++++++------- .../superlink/fleet/vce/vce_api_test.py | 3 ++ src/py/flwr/simulation/run_simulation.py | 1 + 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index cd30c40167c5..66bca5a391c7 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -39,6 +39,7 @@ from flwr.common.message import Error from flwr.common.object_ref import load_app from flwr.common.serde import message_from_taskins, message_to_taskres +from flwr.common.typing import Run from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 from flwr.server.superlink.state import State, StateFactory @@ -60,6 +61,27 @@ def _register_nodes( return nodes_mapping +def _register_node_states( + nodes_mapping: NodeToPartitionMapping, run: Run +) -> Dict[int, NodeState]: + """Create NodeState objects and pre-register the context for the run.""" + node_states: Dict[int, NodeState] = {} + num_partitions = len(set(nodes_mapping.values())) + for node_id, partition_id in nodes_mapping.items(): + node_states[node_id] = NodeState( + node_id=node_id, + node_config={ + PARTITION_ID_KEY: str(partition_id), + NUM_PARTITIONS_KEY: str(num_partitions), + }, + ) + + # Pre-register Context objects + node_states[node_id].register_context(run_id=run.run_id, run=run) + + return node_states + + # pylint: disable=too-many-arguments,too-many-locals def worker( app_fn: Callable[[], ClientApp], @@ -78,8 +100,7 @@ def worker( task_ins: TaskIns = taskins_queue.get(timeout=1.0) node_id = task_ins.task.consumer.node_id - # Register and retrieve context - node_states[node_id].register_context(run_id=task_ins.run_id) + # Retrieve context context = node_states[node_id].retrieve_context(run_id=task_ins.run_id) # Convert TaskIns to Message @@ -151,7 +172,7 @@ def put_taskres_into_state( pass -def run( +def run_api( app_fn: Callable[[], ClientApp], backend_fn: Callable[[], Backend], nodes_mapping: NodeToPartitionMapping, @@ -237,6 +258,7 @@ def start_vce( backend_config_json_stream: str, app_dir: str, f_stop: threading.Event, + run: Run, client_app: Optional[ClientApp] = None, client_app_attr: Optional[str] = None, num_supernodes: Optional[int] = None, @@ -287,17 +309,7 @@ def start_vce( ) # Construct mapping of NodeStates - node_states: Dict[int, NodeState] = {} - # Number of unique partitions - num_partitions = len(set(nodes_mapping.values())) - for node_id, partition_id in nodes_mapping.items(): - node_states[node_id] = NodeState( - node_id=node_id, - node_config={ - PARTITION_ID_KEY: str(partition_id), - NUM_PARTITIONS_KEY: str(num_partitions), - }, - ) + node_states = _register_node_states(nodes_mapping=nodes_mapping, run=run) # Load backend config log(DEBUG, "Supported backends: %s", list(supported_backends.keys())) @@ -348,7 +360,7 @@ def _load() -> ClientApp: _ = app_fn() # Run main simulation loop - run( + run_api( app_fn, backend_fn, nodes_mapping, diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py index 7d37f03f6ade..4dfc08560523 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py @@ -165,6 +165,8 @@ def start_and_shutdown( if not app_dir: app_dir = _autoresolve_app_dir() + run = Run(run_id=1234, fab_id="", fab_version="", override_config={}) + start_vce( num_supernodes=num_supernodes, client_app_attr=client_app_attr, @@ -173,6 +175,7 @@ def start_and_shutdown( state_factory=state_factory, app_dir=app_dir, f_stop=f_stop, + run=run, existing_nodes_mapping=nodes_mapping, ) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 7060a972dd9a..60e6e16eed27 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -252,6 +252,7 @@ def _main_loop( app_dir=app_dir, state_factory=state_factory, f_stop=f_stop, + run=run, ) except Exception as ex: From 2f6cec2dcc51303ae44f7e65ae36cea26b84edad Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 15 Jul 2024 18:39:36 +0200 Subject: [PATCH 171/595] feat(framework) Use federations config in `flwr run` (#3800) Co-authored-by: Daniel J. Beutel --- .../app/pyproject.flowertune.toml.tpl | 14 +-- .../new/templates/app/pyproject.hf.toml.tpl | 11 +- .../new/templates/app/pyproject.jax.toml.tpl | 9 +- .../new/templates/app/pyproject.mlx.toml.tpl | 11 +- .../templates/app/pyproject.numpy.toml.tpl | 11 +- .../templates/app/pyproject.pytorch.toml.tpl | 11 +- .../templates/app/pyproject.sklearn.toml.tpl | 11 +- .../app/pyproject.tensorflow.toml.tpl | 11 +- src/py/flwr/cli/run/run.py | 111 +++++++++--------- 9 files changed, 87 insertions(+), 113 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl index 2ed6bd36fd89..109cbf66a35b 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = { text = "Apache License (2.0)" } dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -32,11 +29,8 @@ publisher = "$username" serverapp = "$import_name.app:server" clientapp = "$import_name.app:client" -[flower.engine] -name = "simulation" - -[flower.engine.simulation.supernode] -num = $num_clients +[flower.federations] +default = "localhost" -[flower.engine.simulation] -backend_config = { client_resources = { num_cpus = 8, num_gpus = 1.0 } } +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 71004f3421cd..6c7e50393098 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = { text = "Apache License (2.0)" } dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -30,8 +27,8 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.engine] -name = "simulation" +[flower.federations] +default = "localhost" -[flower.engine.simulation.supernode] -num = 2 +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index c5463e08b92c..f5c66cc729b8 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = {text = "Apache License (2.0)"} dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -26,3 +23,9 @@ publisher = "$username" [flower.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" + +[flower.federations] +default = "localhost" + +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index a850135a1fc5..eaeec144adb2 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = { text = "Apache License (2.0)" } dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -27,8 +24,8 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.engine] -name = "simulation" +[flower.federations] +default = "localhost" -[flower.engine.simulation.supernode] -num = 2 +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index d49015eb567f..6f386990ba6e 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = { text = "Apache License (2.0)" } dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -25,8 +22,8 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.engine] -name = "simulation" +[flower.federations] +default = "localhost" -[flower.engine.simulation.supernode] -num = 2 +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index b56c0041b96c..4313079fa74a 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = { text = "Apache License (2.0)" } dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -27,8 +24,8 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.engine] -name = "simulation" +[flower.federations] +default = "localhost" -[flower.engine.simulation.supernode] -num = 2 +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 6f914ae659b1..8ab7c10d0107 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = { text = "Apache License (2.0)" } dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -26,8 +23,8 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.engine] -name = "simulation" +[flower.federations] +default = "localhost" -[flower.engine.simulation.supernode] -num = 2 +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index 4ecd16143dcc..a64dfbe6bf77 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -6,9 +6,6 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, -] license = { text = "Apache License (2.0)" } dependencies = [ "flwr[simulation]>=1.9.0,<2.0", @@ -26,8 +23,8 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.engine] -name = "simulation" +[flower.federations] +default = "localhost" -[flower.engine.simulation.supernode] -num = 2 +[flower.federations.localhost] +options.num-supernodes = 10 diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index b23ba3f7d0cf..76d1f47e4fa9 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -15,18 +15,16 @@ """Flower command line interface `run` command.""" import sys -from enum import Enum from logging import DEBUG from pathlib import Path -from typing import Dict, Optional +from typing import Any, Dict, Optional import typer from typing_extensions import Annotated -from flwr.cli import config_utils from flwr.cli.build import build +from flwr.cli.config_utils import load_and_validate from flwr.common.config import parse_config_args -from flwr.common.constant import SUPEREXEC_DEFAULT_ADDRESS from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel from flwr.common.logger import log from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611 @@ -34,31 +32,12 @@ from flwr.simulation.run_simulation import _run_simulation -class Engine(str, Enum): - """Enum defining the engine to run on.""" - - SIMULATION = "simulation" - - # pylint: disable-next=too-many-locals def run( - engine: Annotated[ - Optional[Engine], - typer.Option( - case_sensitive=False, - help="The engine to run FL with (currently only simulation is supported).", - ), - ] = None, - use_superexec: Annotated[ - bool, - typer.Option( - case_sensitive=False, help="Use this flag to use the new SuperExec API" - ), - ] = False, directory: Annotated[ - Optional[Path], - typer.Option(help="Path of the Flower project to run"), - ] = None, + Path, + typer.Argument(help="Path of the Flower project to run"), + ] = Path("."), config_overrides: Annotated[ Optional[str], typer.Option( @@ -72,7 +51,7 @@ def run( typer.secho("Loading project configuration... ", fg=typer.colors.BLUE) pyproject_path = directory / "pyproject.toml" if directory else None - config, errors, warnings = config_utils.load_and_validate(path=pyproject_path) + config, errors, warnings = load_and_validate(path=pyproject_path) if config is None: typer.secho( @@ -94,48 +73,37 @@ def run( typer.secho("Success", fg=typer.colors.GREEN) - if use_superexec: - _start_superexec_run( - parse_config_args(config_overrides, separator=","), directory - ) - return - - server_app_ref = config["flower"]["components"]["serverapp"] - client_app_ref = config["flower"]["components"]["clientapp"] - - if engine is None: - engine = config["flower"]["engine"]["name"] - - if engine == Engine.SIMULATION: - num_supernodes = config["flower"]["engine"]["simulation"]["supernode"]["num"] - backend_config = config["flower"]["engine"]["simulation"].get( - "backend_config", None - ) - - typer.secho("Starting run... ", fg=typer.colors.BLUE) - _run_simulation( - server_app_attr=server_app_ref, - client_app_attr=client_app_ref, - num_supernodes=num_supernodes, - backend_config=backend_config, - ) - else: + try: + federation_name = config["flower"]["federations"]["default"] + federation = config["flower"]["federations"][federation_name] + except KeyError as err: typer.secho( - f"Engine '{engine}' is not yet supported in `flwr run`", + "❌ The project's `pyproject.toml` needs to declare " + "a default federation (with a SuperExec address or an " + "`options.num-supernodes` value).", fg=typer.colors.RED, bold=True, ) + raise typer.Exit(code=1) from err + if "address" in federation: + _run_with_superexec(federation, directory, config_overrides) + else: + _run_without_superexec(config, federation, federation_name) -def _start_superexec_run( - override_config: Dict[str, str], directory: Optional[Path] + +def _run_with_superexec( + federation: Dict[str, str], + directory: Optional[Path], + config_overrides: Optional[str], ) -> None: + def on_channel_state_change(channel_connectivity: str) -> None: """Log channel connectivity.""" log(DEBUG, channel_connectivity) channel = create_channel( - server_address=SUPEREXEC_DEFAULT_ADDRESS, + server_address=federation["address"], insecure=True, root_certificates=None, max_message_length=GRPC_MAX_MESSAGE_LENGTH, @@ -148,7 +116,34 @@ def on_channel_state_change(channel_connectivity: str) -> None: req = StartRunRequest( fab_file=Path(fab_path).read_bytes(), - override_config=override_config, + override_config=parse_config_args(config_overrides, separator=","), ) res = stub.StartRun(req) typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN) + + +def _run_without_superexec( + config: Dict[str, Any], federation: Dict[str, Any], federation_name: str +) -> None: + server_app_ref = config["flower"]["components"]["serverapp"] + client_app_ref = config["flower"]["components"]["clientapp"] + + try: + num_supernodes = federation["options"]["num-supernodes"] + except KeyError as err: + typer.secho( + "❌ The project's `pyproject.toml` needs to declare the number of" + " SuperNodes in the simulation. To simulate 10 SuperNodes," + " use the following notation:\n\n" + f"[flower.federations.{federation_name}]\n" + "options.num-supernodes = 10\n", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) from err + + _run_simulation( + server_app_attr=server_app_ref, + client_app_attr=client_app_ref, + num_supernodes=num_supernodes, + ) From 125a0c7617e8081193b40584ad094e7ec43ccf2d Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 15 Jul 2024 20:19:16 +0200 Subject: [PATCH 172/595] refactor(framework) Refactor `ClientApp` loading to use explicit arguments (#3805) --- src/py/flwr/client/supernode/app.py | 37 +++++++++++++++++++---------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index d61b986bc7af..2f2fa58b428c 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -60,7 +60,12 @@ def run_supernode() -> None: _warn_deprecated_server_arg(args) root_certificates = _get_certificates(args) - load_fn = _get_load_client_app_fn(args, multi_app=True) + load_fn = _get_load_client_app_fn( + default_app_ref=getattr(args, "client-app"), + dir_arg=args.dir, + flwr_dir_arg=args.flwr_dir, + multi_app=True, + ) authentication_keys = _try_setup_client_authentication(args) _start_client_internal( @@ -93,7 +98,11 @@ def run_client_app() -> None: _warn_deprecated_server_arg(args) root_certificates = _get_certificates(args) - load_fn = _get_load_client_app_fn(args, multi_app=False) + load_fn = _get_load_client_app_fn( + default_app_ref=getattr(args, "client-app"), + dir_arg=args.dir, + multi_app=False, + ) authentication_keys = _try_setup_client_authentication(args) _start_client_internal( @@ -166,7 +175,10 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]: def _get_load_client_app_fn( - args: argparse.Namespace, multi_app: bool + default_app_ref: str, + dir_arg: str, + multi_app: bool, + flwr_dir_arg: Optional[str] = None, ) -> Callable[[str, str], ClientApp]: """Get the load_client_app_fn function. @@ -178,25 +190,24 @@ def _get_load_client_app_fn( loads a default ClientApp. """ # Find the Flower directory containing Flower Apps (only for multi-app) - flwr_dir = Path("") - if "flwr_dir" in args: - if args.flwr_dir is None: + if not multi_app: + flwr_dir = Path("") + else: + if flwr_dir_arg is None: flwr_dir = get_flwr_dir() else: - flwr_dir = Path(args.flwr_dir).absolute() + flwr_dir = Path(flwr_dir_arg).absolute() inserted_path = None - default_app_ref: str = getattr(args, "client-app") - if not multi_app: log( DEBUG, "Flower SuperNode will load and validate ClientApp `%s`", - getattr(args, "client-app"), + default_app_ref, ) # Insert sys.path - dir_path = Path(args.dir).absolute() + dir_path = Path(dir_arg).absolute() sys.path.insert(0, str(dir_path)) inserted_path = str(dir_path) @@ -208,7 +219,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: # If multi-app feature is disabled if not multi_app: # Get sys path to be inserted - dir_path = Path(args.dir).absolute() + dir_path = Path(dir_arg).absolute() # Set app reference client_app_ref = default_app_ref @@ -221,7 +232,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.") # Get sys path to be inserted - dir_path = Path(args.dir).absolute() + dir_path = Path(dir_arg).absolute() # Set app reference client_app_ref = default_app_ref From 285acfa210eec1532465a64e84f170209e2991ae Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 15 Jul 2024 21:22:17 +0200 Subject: [PATCH 173/595] feat(framework) Add federation argument to `flwr run` (#3807) --- src/py/flwr/cli/run/run.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 76d1f47e4fa9..1ae4017492b0 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -38,6 +38,10 @@ def run( Path, typer.Argument(help="Path of the Flower project to run"), ] = Path("."), + federation_name: Annotated[ + Optional[str], + typer.Argument(help="Name of the federation to run the app on"), + ] = None, config_overrides: Annotated[ Optional[str], typer.Option( @@ -73,18 +77,30 @@ def run( typer.secho("Success", fg=typer.colors.GREEN) - try: - federation_name = config["flower"]["federations"]["default"] - federation = config["flower"]["federations"][federation_name] - except KeyError as err: + federation_name = federation_name or config["flower"]["federations"].get("default") + + if federation_name is None: typer.secho( - "❌ The project's `pyproject.toml` needs to declare " - "a default federation (with a SuperExec address or an " + "❌ No federation name was provided and the project's `pyproject.toml` " + "doesn't declare a default federation (with a SuperExec address or an " "`options.num-supernodes` value).", fg=typer.colors.RED, bold=True, ) - raise typer.Exit(code=1) from err + raise typer.Exit(code=1) + + # Validate the federation exists in the configuration + federation = config["flower"]["federations"].get(federation_name) + if federation is None: + available_feds = list(config["flower"]["federations"]) + typer.secho( + f"❌ There is no `{federation_name}` federation declared in the " + "`pyproject.toml`.\n The following federations were found:\n\n" + "\n".join(available_feds) + "\n\n", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) if "address" in federation: _run_with_superexec(federation, directory, config_overrides) From 22bbc006cff57dc0963ad79b4d694aabe3b86e69 Mon Sep 17 00:00:00 2001 From: Javier Date: Mon, 15 Jul 2024 21:36:18 +0200 Subject: [PATCH 174/595] refactor(framework) Improve app loading in simulation engine (#3806) --- examples/simulation-pytorch/sim.py | 37 +++++++++++-------- .../server/superlink/fleet/vce/vce_api.py | 31 +++++++++------- src/py/flwr/simulation/run_simulation.py | 18 +++++++++ 3 files changed, 58 insertions(+), 28 deletions(-) diff --git a/examples/simulation-pytorch/sim.py b/examples/simulation-pytorch/sim.py index db68e75653fc..dcc0f39a79ef 100644 --- a/examples/simulation-pytorch/sim.py +++ b/examples/simulation-pytorch/sim.py @@ -87,11 +87,13 @@ def get_client_fn(dataset: FederatedDataset): the strategy to participate. """ - def client_fn(cid: str) -> fl.client.Client: + def client_fn(context) -> fl.client.Client: """Construct a FlowerClient with its own dataset partition.""" # Let's get the partition corresponding to the i-th client - client_dataset = dataset.load_partition(int(cid), "train") + client_dataset = dataset.load_partition( + int(context.node_config["partition-id"]), "train" + ) # Now let's split it into train (90%) and validation (10%) client_dataset_splits = client_dataset.train_test_split(test_size=0.1, seed=42) @@ -171,15 +173,23 @@ def evaluate( mnist_fds = FederatedDataset(dataset="mnist", partitioners={"train": NUM_CLIENTS}) centralized_testset = mnist_fds.load_split("test") -# Configure the strategy -strategy = fl.server.strategy.FedAvg( - fraction_fit=0.1, # Sample 10% of available clients for training - fraction_evaluate=0.05, # Sample 5% of available clients for evaluation - min_available_clients=10, - on_fit_config_fn=fit_config, - evaluate_metrics_aggregation_fn=weighted_average, # Aggregate federated metrics - evaluate_fn=get_evaluate_fn(centralized_testset), # Global evaluation function -) +from flwr.server import ServerAppComponents + + +def server_fn(context): + # Configure the strategy + strategy = fl.server.strategy.FedAvg( + fraction_fit=0.1, # Sample 10% of available clients for training + fraction_evaluate=0.05, # Sample 5% of available clients for evaluation + min_available_clients=10, + on_fit_config_fn=fit_config, + evaluate_metrics_aggregation_fn=weighted_average, # Aggregate federated metrics + evaluate_fn=get_evaluate_fn(centralized_testset), # Global evaluation function + ) + return ServerAppComponents( + strategy=strategy, config=fl.server.ServerConfig(num_rounds=NUM_ROUNDS) + ) + # ClientApp for Flower-Next client = fl.client.ClientApp( @@ -187,10 +197,7 @@ def evaluate( ) # ServerApp for Flower-Next -server = fl.server.ServerApp( - config=fl.server.ServerConfig(num_rounds=NUM_ROUNDS), - strategy=strategy, -) +server = fl.server.ServerApp(server_fn=server_fn) def main(): diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 66bca5a391c7..b652207961a1 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -16,7 +16,6 @@ import json -import sys import threading import time import traceback @@ -29,6 +28,7 @@ from flwr.client.client_app import ClientApp, ClientAppException, LoadClientAppError from flwr.client.node_state import NodeState +from flwr.client.supernode.app import _get_load_client_app_fn from flwr.common.constant import ( NUM_PARTITIONS_KEY, PARTITION_ID_KEY, @@ -37,7 +37,6 @@ ) from flwr.common.logger import log from flwr.common.message import Error -from flwr.common.object_ref import load_app from flwr.common.serde import message_from_taskins, message_to_taskres from flwr.common.typing import Run from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 @@ -259,6 +258,7 @@ def start_vce( app_dir: str, f_stop: threading.Event, run: Run, + flwr_dir: Optional[str] = None, client_app: Optional[ClientApp] = None, client_app_attr: Optional[str] = None, num_supernodes: Optional[int] = None, @@ -338,16 +338,12 @@ def backend_fn() -> Backend: def _load() -> ClientApp: if client_app_attr: - - if app_dir is not None: - sys.path.insert(0, app_dir) - - app: ClientApp = load_app(client_app_attr, LoadClientAppError, app_dir) - - if not isinstance(app, ClientApp): - raise LoadClientAppError( - f"Attribute {client_app_attr} is not of type {ClientApp}", - ) from None + app = _get_load_client_app_fn( + default_app_ref=client_app_attr, + dir_arg=app_dir, + flwr_dir_arg=flwr_dir, + multi_app=True, + )(run.fab_id, run.fab_version) if client_app: app = client_app @@ -357,7 +353,16 @@ def _load() -> ClientApp: try: # Test if ClientApp can be loaded - _ = app_fn() + client_app = app_fn() + + # Cache `ClientApp` + if client_app_attr: + # Now wrap the loaded ClientApp in a dummy function + # this prevent unnecesary low-level loading of ClientApp + def _load_client_app() -> ClientApp: + return client_app + + app_fn = _load_client_app # Run main simulation loop run_api( diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 60e6e16eed27..8c70bf8374d0 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -207,6 +207,7 @@ def _main_loop( app_dir: str, enable_tf_gpu_growth: bool, run: Run, + flwr_dir: Optional[str] = None, client_app: Optional[ClientApp] = None, client_app_attr: Optional[str] = None, server_app: Optional[ServerApp] = None, @@ -253,6 +254,7 @@ def _main_loop( state_factory=state_factory, f_stop=f_stop, run=run, + flwr_dir=flwr_dir, ) except Exception as ex: @@ -283,6 +285,7 @@ def _run_simulation( client_app_attr: Optional[str] = None, server_app_attr: Optional[str] = None, app_dir: str = "", + flwr_dir: Optional[str] = None, run: Optional[Run] = None, enable_tf_gpu_growth: bool = False, verbose_logging: bool = False, @@ -326,6 +329,9 @@ def _run_simulation( Add specified directory to the PYTHONPATH and load `ClientApp` from there. (Default: current working directory.) + flwr_dir : Optional[str] + The path containing installed Flower Apps. + run : Optional[Run] An object carrying details about the run. @@ -377,6 +383,7 @@ def _run_simulation( app_dir, enable_tf_gpu_growth, run, + flwr_dir, client_app, client_app_attr, server_app, @@ -464,6 +471,17 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: "ClientApp and ServerApp from there." " Default: current working directory.", ) + parser.add_argument( + "--flwr-dir", + default=None, + help="""The path containing installed Flower Apps. + By default, this value is equal to: + + - `$FLWR_HOME/` if `$FLWR_HOME` is defined + - `$XDG_DATA_HOME/.flwr/` if `$XDG_DATA_HOME` is defined + - `$HOME/.flwr/` in all other cases + """, + ) parser.add_argument( "--run-id", type=int, From 5ec1697659a9f6396e40d1fcdb9017b55a259609 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 15 Jul 2024 22:26:37 +0200 Subject: [PATCH 175/595] refactor(framework) Switch to `tool.flwr` instead of `flower` in `pyproject.toml` (#3809) --- src/py/flwr/cli/build.py | 2 +- src/py/flwr/cli/config_utils.py | 30 +++--- src/py/flwr/cli/config_utils_test.py | 96 ++++++++----------- src/py/flwr/cli/install.py | 2 +- .../app/pyproject.flowertune.toml.tpl | 8 +- .../new/templates/app/pyproject.hf.toml.tpl | 8 +- .../new/templates/app/pyproject.jax.toml.tpl | 8 +- .../new/templates/app/pyproject.mlx.toml.tpl | 8 +- .../templates/app/pyproject.numpy.toml.tpl | 8 +- .../templates/app/pyproject.pytorch.toml.tpl | 8 +- .../templates/app/pyproject.sklearn.toml.tpl | 8 +- .../app/pyproject.tensorflow.toml.tpl | 8 +- src/py/flwr/cli/run/run.py | 14 +-- src/py/flwr/client/supernode/app.py | 2 +- src/py/flwr/common/config.py | 2 +- src/py/flwr/common/config_test.py | 38 ++++---- src/py/flwr/server/run_serverapp.py | 2 +- src/py/flwr/superexec/simulation.py | 2 +- 18 files changed, 120 insertions(+), 134 deletions(-) diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index f63d0acd5d73..599ce613698c 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -85,7 +85,7 @@ def build( # Set the name of the zip file fab_filename = ( - f"{conf['flower']['publisher']}" + f"{conf['tool']['flwr']['publisher']}" f".{directory.name}" f".{conf['project']['version'].replace('.', '-')}.fab" ) diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index 33bf12e34b04..9147ebba4995 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -60,7 +60,7 @@ def get_fab_metadata(fab_file: Union[Path, bytes]) -> Tuple[str, str]: return ( conf["project"]["version"], - f"{conf['flower']['publisher']}/{conf['project']['name']}", + f"{conf['tool']['flwr']['publisher']}/{conf['project']['name']}", ) @@ -136,20 +136,20 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]] if "authors" not in config["project"]: warnings.append('Recommended property "authors" missing in [project]') - if "flower" not in config: - errors.append("Missing [flower] section") + if "tool" not in config or "flwr" not in config["tool"]: + errors.append("Missing [tool.flwr] section") else: - if "publisher" not in config["flower"]: - errors.append('Property "publisher" missing in [flower]') - if "config" in config["flower"]: - _validate_run_config(config["flower"]["config"], errors) - if "components" not in config["flower"]: - errors.append("Missing [flower.components] section") + if "publisher" not in config["tool"]["flwr"]: + errors.append('Property "publisher" missing in [tool.flwr]') + if "config" in config["tool"]["flwr"]: + _validate_run_config(config["tool"]["flwr"]["config"], errors) + if "components" not in config["tool"]["flwr"]: + errors.append("Missing [tool.flwr.components] section") else: - if "serverapp" not in config["flower"]["components"]: - errors.append('Property "serverapp" missing in [flower.components]') - if "clientapp" not in config["flower"]["components"]: - errors.append('Property "clientapp" missing in [flower.components]') + if "serverapp" not in config["tool"]["flwr"]["components"]: + errors.append('Property "serverapp" missing in [tool.flwr.components]') + if "clientapp" not in config["tool"]["flwr"]["components"]: + errors.append('Property "clientapp" missing in [tool.flwr.components]') return len(errors) == 0, errors, warnings @@ -165,14 +165,14 @@ def validate( # Validate serverapp is_valid, reason = object_ref.validate( - config["flower"]["components"]["serverapp"], check_module + config["tool"]["flwr"]["components"]["serverapp"], check_module ) if not is_valid and isinstance(reason, str): return False, [reason], [] # Validate clientapp is_valid, reason = object_ref.validate( - config["flower"]["components"]["clientapp"], check_module + config["tool"]["flwr"]["components"]["clientapp"], check_module ) if not is_valid and isinstance(reason, str): diff --git a/src/py/flwr/cli/config_utils_test.py b/src/py/flwr/cli/config_utils_test.py index b24425cd08f4..35d9900703b6 100644 --- a/src/py/flwr/cli/config_utils_test.py +++ b/src/py/flwr/cli/config_utils_test.py @@ -34,27 +34,18 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: name = "fedgpt" version = "1.0.0" description = "" - authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, - ] license = {text = "Apache License (2.0)"} dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - - [flower.engine] - name = "simulation" # optional - - [flower.engine.simulation.supernode] - count = 10 # optional """ expected_config = { "build-system": {"build-backend": "hatchling.build", "requires": ["hatchling"]}, @@ -62,19 +53,16 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: "name": "fedgpt", "version": "1.0.0", "description": "", - "authors": [{"email": "hello@flower.ai", "name": "The Flower Authors"}], "license": {"text": "Apache License (2.0)"}, "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", - }, - "engine": { - "name": "simulation", - "simulation": {"supernode": {"count": 10}}, + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, }, }, } @@ -109,27 +97,18 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: name = "fedgpt" version = "1.0.0" description = "" - authors = [ - { name = "The Flower Authors", email = "hello@flower.ai" }, - ] license = {text = "Apache License (2.0)"} dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - - [flower.engine] - name = "simulation" # optional - - [flower.engine.simulation.supernode] - count = 10 # optional """ expected_config = { "build-system": {"build-backend": "hatchling.build", "requires": ["hatchling"]}, @@ -137,19 +116,16 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: "name": "fedgpt", "version": "1.0.0", "description": "", - "authors": [{"email": "hello@flower.ai", "name": "The Flower Authors"}], "license": {"text": "Apache License (2.0)"}, "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", - }, - "engine": { - "name": "simulation", - "simulation": {"supernode": {"count": 10}}, + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, }, }, } @@ -219,7 +195,7 @@ def test_validate_pyproject_toml_fields_no_flower_components() -> None: "license": "", "authors": [], }, - "flower": {}, + "tool": {"flwr": {}}, } # Execute @@ -242,7 +218,7 @@ def test_validate_pyproject_toml_fields_no_server_and_client_app() -> None: "license": "", "authors": [], }, - "flower": {"components": {}}, + "tool": {"flwr": {"components": {}}}, } # Execute @@ -265,9 +241,11 @@ def test_validate_pyproject_toml_fields() -> None: "license": "", "authors": [], }, - "flower": { - "publisher": "flwrlabs", - "components": {"serverapp": "", "clientapp": ""}, + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": {"serverapp": "", "clientapp": ""}, + }, }, } @@ -291,11 +269,13 @@ def test_validate_pyproject_toml() -> None: "license": "", "authors": [], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "flwr.cli.run:run", - "clientapp": "flwr.cli.run:run", + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "flwr.cli.run:run", + "clientapp": "flwr.cli.run:run", + }, }, }, } @@ -320,11 +300,13 @@ def test_validate_pyproject_toml_fail() -> None: "license": "", "authors": [], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "flwr.cli.run:run", - "clientapp": "flwr.cli.run:runa", + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "flwr.cli.run:run", + "clientapp": "flwr.cli.run:runa", + }, }, }, } diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index de9227bee450..7444f10c1eb7 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -149,7 +149,7 @@ def validate_and_install( ) raise typer.Exit(code=1) - publisher = config["flower"]["publisher"] + publisher = config["tool"]["flwr"]["publisher"] project_name = config["project"]["name"] version = config["project"]["version"] diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl index 109cbf66a35b..17630dd9d0dc 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -22,15 +22,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.app:server" clientapp = "$import_name.app:client" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 6c7e50393098..6f46d6de5bf5 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -20,15 +20,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index f5c66cc729b8..045a1f4e57eb 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -17,15 +17,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index eaeec144adb2..5ea2c420d6f8 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -17,15 +17,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index 6f386990ba6e..d166616bb616 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -15,15 +15,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index 4313079fa74a..c0323126516d 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -17,15 +17,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 8ab7c10d0107..0e63375aab00 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -16,15 +16,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index a64dfbe6bf77..aeca4a17805f 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -16,15 +16,15 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[flower] +[tool.flwr] publisher = "$username" -[flower.components] +[tool.flwr.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" -[flower.federations] +[tool.flwr.federations] default = "localhost" -[flower.federations.localhost] +[tool.flwr.federations.localhost] options.num-supernodes = 10 diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 1ae4017492b0..c39ae0decd4b 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -77,7 +77,9 @@ def run( typer.secho("Success", fg=typer.colors.GREEN) - federation_name = federation_name or config["flower"]["federations"].get("default") + federation_name = federation_name or config["tool"]["flwr"]["federations"].get( + "default" + ) if federation_name is None: typer.secho( @@ -90,9 +92,9 @@ def run( raise typer.Exit(code=1) # Validate the federation exists in the configuration - federation = config["flower"]["federations"].get(federation_name) + federation = config["tool"]["flwr"]["federations"].get(federation_name) if federation is None: - available_feds = list(config["flower"]["federations"]) + available_feds = list(config["tool"]["flwr"]["federations"]) typer.secho( f"❌ There is no `{federation_name}` federation declared in the " "`pyproject.toml`.\n The following federations were found:\n\n" @@ -141,8 +143,8 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( config: Dict[str, Any], federation: Dict[str, Any], federation_name: str ) -> None: - server_app_ref = config["flower"]["components"]["serverapp"] - client_app_ref = config["flower"]["components"]["clientapp"] + server_app_ref = config["tool"]["flwr"]["components"]["serverapp"] + client_app_ref = config["tool"]["flwr"]["components"]["clientapp"] try: num_supernodes = federation["options"]["num-supernodes"] @@ -151,7 +153,7 @@ def _run_without_superexec( "❌ The project's `pyproject.toml` needs to declare the number of" " SuperNodes in the simulation. To simulate 10 SuperNodes," " use the following notation:\n\n" - f"[flower.federations.{federation_name}]\n" + f"[tool.flwr.federations.{federation_name}]\n" "options.num-supernodes = 10\n", fg=typer.colors.RED, bold=True, diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 2f2fa58b428c..027c3376b7f3 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -248,7 +248,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: dir_path = Path(project_dir).absolute() # Set app reference - client_app_ref = config["flower"]["components"]["clientapp"] + client_app_ref = config["tool"]["flwr"]["components"]["clientapp"] # Set sys.path nonlocal inserted_path diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 54d74353e4ed..e2b06ff86110 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -97,7 +97,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir) - default_config = get_project_config(project_dir)["flower"].get("config", {}) + default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {}) flat_default_config = flatten_dict(default_config) return _fuse_dicts(flat_default_config, run.override_config) diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py index fe429bab9cb5..899240c1e76a 100644 --- a/src/py/flwr/common/config_test.py +++ b/src/py/flwr/common/config_test.py @@ -93,20 +93,20 @@ def test_get_fused_config_valid(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - [flower.config] + [tool.flwr.config] num_server_rounds = "10" momentum = "0.1" lr = "0.01" serverapp.test = "key" - [flower.config.clientapp] + [tool.flwr.config.clientapp] test = "key" """ overrides = { @@ -131,7 +131,7 @@ def test_get_fused_config_valid(tmp_path: Path) -> None: f.write(textwrap.dedent(pyproject_toml_content)) # Execute - default_config = get_project_config(tmp_path)["flower"].get("config", {}) + default_config = get_project_config(tmp_path)["tool"]["flwr"].get("config", {}) config = _fuse_dicts(flatten_dict(default_config), overrides) @@ -158,14 +158,14 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [flower] + [tool.flwr] publisher = "flwrlabs" - [flower.components] + [tool.flwr.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - [flower.config] + [tool.flwr.config] num_server_rounds = "10" momentum = "0.1" lr = "0.01" @@ -179,16 +179,18 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: "license": {"text": "Apache License (2.0)"}, "dependencies": ["flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0"], }, - "flower": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", - }, - "config": { - "num_server_rounds": "10", - "momentum": "0.1", - "lr": "0.01", + "tool": { + "flwr": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, + "config": { + "num_server_rounds": "10", + "momentum": "0.1", + "lr": "0.01", + }, }, }, } diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 4cc25feb7e0e..efaba24f05f9 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -186,7 +186,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches run_ = driver.run server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir)) config = get_project_config(server_app_dir) - server_app_attr = config["flower"]["components"]["serverapp"] + server_app_attr = config["tool"]["flwr"]["components"]["serverapp"] server_app_run_config = get_fused_config(run_, flwr_dir) else: # User provided `server-app`, but not `--run-id` diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index 9a8e19365ab9..fa7a8ad9b0d3 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -112,7 +112,7 @@ def start_run( ) # Get ClientApp and SeverApp components - flower_components = config["flower"]["components"] + flower_components = config["tool"]["flwr"]["components"] clientapp = flower_components["clientapp"] serverapp = flower_components["serverapp"] From 690e6acd84e42609e3be10a60b06444b252f8f74 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 15 Jul 2024 22:39:08 +0200 Subject: [PATCH 176/595] feat(framework) Add secure channel support for SuperExec (#3808) Co-authored-by: Daniel J. Beutel --- src/py/flwr/cli/run/run.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index c39ae0decd4b..512da83d13fe 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -120,10 +120,38 @@ def on_channel_state_change(channel_connectivity: str) -> None: """Log channel connectivity.""" log(DEBUG, channel_connectivity) + insecure_str = federation.get("insecure") + if root_certificates := federation.get("root-certificates"): + root_certificates_bytes = Path(root_certificates).read_bytes() + if insecure := bool(insecure_str): + typer.secho( + "❌ `root_certificates` were provided but the `insecure` parameter" + "is set to `True`.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + else: + root_certificates_bytes = None + if insecure_str is None: + typer.secho( + "❌ To disable TLS, set `insecure = true` in `pyproject.toml`.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + if not (insecure := bool(insecure_str)): + typer.secho( + "❌ No certificate were given yet `insecure` is set to `False`.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + channel = create_channel( server_address=federation["address"], - insecure=True, - root_certificates=None, + insecure=insecure, + root_certificates=root_certificates_bytes, max_message_length=GRPC_MAX_MESSAGE_LENGTH, interceptors=None, ) From b9c3a363bf2d801677f663909b326b723d275124 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 16 Jul 2024 10:57:48 +0200 Subject: [PATCH 177/595] refactor(framework) Move `tool.flwr` to `tool.flwr.app` (#3811) --- src/py/flwr/cli/build.py | 2 +- src/py/flwr/cli/config_utils.py | 38 +++++++----- src/py/flwr/cli/config_utils_test.py | 58 +++++++++++-------- src/py/flwr/cli/install.py | 2 +- .../app/pyproject.flowertune.toml.tpl | 4 +- .../new/templates/app/pyproject.hf.toml.tpl | 4 +- .../new/templates/app/pyproject.jax.toml.tpl | 4 +- .../new/templates/app/pyproject.mlx.toml.tpl | 4 +- .../templates/app/pyproject.numpy.toml.tpl | 4 +- .../templates/app/pyproject.pytorch.toml.tpl | 4 +- .../templates/app/pyproject.sklearn.toml.tpl | 4 +- .../app/pyproject.tensorflow.toml.tpl | 4 +- src/py/flwr/common/config.py | 4 +- src/py/flwr/common/config_test.py | 38 ++++++------ 14 files changed, 99 insertions(+), 75 deletions(-) diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index 599ce613698c..670b8bd64908 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -85,7 +85,7 @@ def build( # Set the name of the zip file fab_filename = ( - f"{conf['tool']['flwr']['publisher']}" + f"{conf['tool']['flwr']['app']['publisher']}" f".{directory.name}" f".{conf['project']['version'].replace('.', '-')}.fab" ) diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index 9147ebba4995..f46a53857dfc 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -60,7 +60,7 @@ def get_fab_metadata(fab_file: Union[Path, bytes]) -> Tuple[str, str]: return ( conf["project"]["version"], - f"{conf['tool']['flwr']['publisher']}/{conf['project']['name']}", + f"{conf['tool']['flwr']['app']['publisher']}/{conf['project']['name']}", ) @@ -136,20 +136,28 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]] if "authors" not in config["project"]: warnings.append('Recommended property "authors" missing in [project]') - if "tool" not in config or "flwr" not in config["tool"]: - errors.append("Missing [tool.flwr] section") + if ( + "tool" not in config + or "flwr" not in config["tool"] + or "app" not in config["tool"]["flwr"] + ): + errors.append("Missing [tool.flwr.app] section") else: - if "publisher" not in config["tool"]["flwr"]: - errors.append('Property "publisher" missing in [tool.flwr]') - if "config" in config["tool"]["flwr"]: - _validate_run_config(config["tool"]["flwr"]["config"], errors) - if "components" not in config["tool"]["flwr"]: - errors.append("Missing [tool.flwr.components] section") + if "publisher" not in config["tool"]["flwr"]["app"]: + errors.append('Property "publisher" missing in [tool.flwr.app]') + if "config" in config["tool"]["flwr"]["app"]: + _validate_run_config(config["tool"]["flwr"]["app"]["config"], errors) + if "components" not in config["tool"]["flwr"]["app"]: + errors.append("Missing [tool.flwr.app.components] section") else: - if "serverapp" not in config["tool"]["flwr"]["components"]: - errors.append('Property "serverapp" missing in [tool.flwr.components]') - if "clientapp" not in config["tool"]["flwr"]["components"]: - errors.append('Property "clientapp" missing in [tool.flwr.components]') + if "serverapp" not in config["tool"]["flwr"]["app"]["components"]: + errors.append( + 'Property "serverapp" missing in [tool.flwr.app.components]' + ) + if "clientapp" not in config["tool"]["flwr"]["app"]["components"]: + errors.append( + 'Property "clientapp" missing in [tool.flwr.app.components]' + ) return len(errors) == 0, errors, warnings @@ -165,14 +173,14 @@ def validate( # Validate serverapp is_valid, reason = object_ref.validate( - config["tool"]["flwr"]["components"]["serverapp"], check_module + config["tool"]["flwr"]["app"]["components"]["serverapp"], check_module ) if not is_valid and isinstance(reason, str): return False, [reason], [] # Validate clientapp is_valid, reason = object_ref.validate( - config["tool"]["flwr"]["components"]["clientapp"], check_module + config["tool"]["flwr"]["app"]["components"]["clientapp"], check_module ) if not is_valid and isinstance(reason, str): diff --git a/src/py/flwr/cli/config_utils_test.py b/src/py/flwr/cli/config_utils_test.py index 35d9900703b6..077f254fb914 100644 --- a/src/py/flwr/cli/config_utils_test.py +++ b/src/py/flwr/cli/config_utils_test.py @@ -40,10 +40,10 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [tool.flwr] + [tool.flwr.app] publisher = "flwrlabs" - [tool.flwr.components] + [tool.flwr.app.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" """ @@ -58,10 +58,12 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: }, "tool": { "flwr": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", + "app": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, }, }, }, @@ -103,10 +105,10 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [tool.flwr] + [tool.flwr.app] publisher = "flwrlabs" - [tool.flwr.components] + [tool.flwr.app.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" """ @@ -121,10 +123,12 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: }, "tool": { "flwr": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", + "app": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, }, }, }, @@ -195,7 +199,7 @@ def test_validate_pyproject_toml_fields_no_flower_components() -> None: "license": "", "authors": [], }, - "tool": {"flwr": {}}, + "tool": {"flwr": {"app": {}}}, } # Execute @@ -218,7 +222,7 @@ def test_validate_pyproject_toml_fields_no_server_and_client_app() -> None: "license": "", "authors": [], }, - "tool": {"flwr": {"components": {}}}, + "tool": {"flwr": {"app": {"components": {}}}}, } # Execute @@ -243,8 +247,10 @@ def test_validate_pyproject_toml_fields() -> None: }, "tool": { "flwr": { - "publisher": "flwrlabs", - "components": {"serverapp": "", "clientapp": ""}, + "app": { + "publisher": "flwrlabs", + "components": {"serverapp": "", "clientapp": ""}, + }, }, }, } @@ -271,10 +277,12 @@ def test_validate_pyproject_toml() -> None: }, "tool": { "flwr": { - "publisher": "flwrlabs", - "components": { - "serverapp": "flwr.cli.run:run", - "clientapp": "flwr.cli.run:run", + "app": { + "publisher": "flwrlabs", + "components": { + "serverapp": "flwr.cli.run:run", + "clientapp": "flwr.cli.run:run", + }, }, }, }, @@ -302,10 +310,12 @@ def test_validate_pyproject_toml_fail() -> None: }, "tool": { "flwr": { - "publisher": "flwrlabs", - "components": { - "serverapp": "flwr.cli.run:run", - "clientapp": "flwr.cli.run:runa", + "app": { + "publisher": "flwrlabs", + "components": { + "serverapp": "flwr.cli.run:run", + "clientapp": "flwr.cli.run:runa", + }, }, }, }, diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index 7444f10c1eb7..a1a66e42fd65 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -149,7 +149,7 @@ def validate_and_install( ) raise typer.Exit(code=1) - publisher = config["tool"]["flwr"]["publisher"] + publisher = config["tool"]["flwr"]["app"]["publisher"] project_name = config["project"]["name"] version = config["project"]["version"] diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl index 17630dd9d0dc..5934a258564b 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -22,10 +22,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.app:server" clientapp = "$import_name.app:client" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 6f46d6de5bf5..59f41f062af4 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -20,10 +20,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index 045a1f4e57eb..27f4b30ec3b8 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -17,10 +17,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index 5ea2c420d6f8..9c8905a0e8e5 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -17,10 +17,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index d166616bb616..38bfa1888c4c 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -15,10 +15,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index c0323126516d..2fd366d8e350 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -17,10 +17,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 0e63375aab00..143c3756858c 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -16,10 +16,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index aeca4a17805f..964bef58c498 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -16,10 +16,10 @@ dependencies = [ [tool.hatch.build.targets.wheel] packages = ["."] -[tool.flwr] +[tool.flwr.app] publisher = "$username" -[tool.flwr.components] +[tool.flwr.app.components] serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index e2b06ff86110..247c4ef775a7 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -97,7 +97,9 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir) - default_config = get_project_config(project_dir)["tool"]["flwr"].get("config", {}) + default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get( + "config", {} + ) flat_default_config = flatten_dict(default_config) return _fuse_dicts(flat_default_config, run.override_config) diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py index 899240c1e76a..feef89e7d5cb 100644 --- a/src/py/flwr/common/config_test.py +++ b/src/py/flwr/common/config_test.py @@ -93,20 +93,20 @@ def test_get_fused_config_valid(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [tool.flwr] + [tool.flwr.app] publisher = "flwrlabs" - [tool.flwr.components] + [tool.flwr.app.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - [tool.flwr.config] + [tool.flwr.app.config] num_server_rounds = "10" momentum = "0.1" lr = "0.01" serverapp.test = "key" - [tool.flwr.config.clientapp] + [tool.flwr.app.config.clientapp] test = "key" """ overrides = { @@ -131,7 +131,9 @@ def test_get_fused_config_valid(tmp_path: Path) -> None: f.write(textwrap.dedent(pyproject_toml_content)) # Execute - default_config = get_project_config(tmp_path)["tool"]["flwr"].get("config", {}) + default_config = get_project_config(tmp_path)["tool"]["flwr"]["app"].get( + "config", {} + ) config = _fuse_dicts(flatten_dict(default_config), overrides) @@ -158,14 +160,14 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: "numpy>=1.21.0", ] - [tool.flwr] + [tool.flwr.app] publisher = "flwrlabs" - [tool.flwr.components] + [tool.flwr.app.components] serverapp = "fedgpt.server:app" clientapp = "fedgpt.client:app" - [tool.flwr.config] + [tool.flwr.app.config] num_server_rounds = "10" momentum = "0.1" lr = "0.01" @@ -181,15 +183,17 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: }, "tool": { "flwr": { - "publisher": "flwrlabs", - "components": { - "serverapp": "fedgpt.server:app", - "clientapp": "fedgpt.client:app", - }, - "config": { - "num_server_rounds": "10", - "momentum": "0.1", - "lr": "0.01", + "app": { + "publisher": "flwrlabs", + "components": { + "serverapp": "fedgpt.server:app", + "clientapp": "fedgpt.client:app", + }, + "config": { + "num_server_rounds": "10", + "momentum": "0.1", + "lr": "0.01", + }, }, }, }, From 028f619d44cb4280bc13fd1d45ab2eddbafaa281 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 16 Jul 2024 11:13:59 +0200 Subject: [PATCH 178/595] feat(framework:skip) Add config function for fusing dicts (#3813) Co-authored-by: Daniel J. Beutel --- src/py/flwr/common/config.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 247c4ef775a7..6049fcbcceed 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -86,6 +86,18 @@ def _fuse_dicts( return fused_dict +def get_fused_config_from_dir( + project_dir: Path, override_config: Dict[str, str] +) -> Dict[str, str]: + """Merge the overrides from a given dict with the config from a Flower App.""" + default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get( + "config", {} + ) + flat_default_config = flatten_dict(default_config) + + return _fuse_dicts(flat_default_config, override_config) + + def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: """Merge the overrides from a `Run` with the config from a FAB. @@ -97,12 +109,7 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: project_dir = get_project_dir(run.fab_id, run.fab_version, flwr_dir) - default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get( - "config", {} - ) - flat_default_config = flatten_dict(default_config) - - return _fuse_dicts(flat_default_config, run.override_config) + return get_fused_config_from_dir(project_dir, run.override_config) def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, str]: From 517016c79c6c83154480ed4c6952e3513210e7fb Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 16 Jul 2024 13:10:37 +0200 Subject: [PATCH 179/595] feat(framework) Remove federations field from FAB (#3814) --- pyproject.toml | 1 + src/py/flwr/cli/build.py | 16 +++++++++++++++- .../templates/app/pyproject.flowertune.toml.tpl | 2 +- .../cli/new/templates/app/pyproject.hf.toml.tpl | 2 +- .../cli/new/templates/app/pyproject.jax.toml.tpl | 2 +- .../cli/new/templates/app/pyproject.mlx.toml.tpl | 2 +- .../new/templates/app/pyproject.numpy.toml.tpl | 2 +- .../new/templates/app/pyproject.pytorch.toml.tpl | 2 +- .../new/templates/app/pyproject.sklearn.toml.tpl | 2 +- .../templates/app/pyproject.tensorflow.toml.tpl | 2 +- 10 files changed, 24 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c5ab0e5edcee..7fe1ef7843d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,7 @@ pycryptodome = "^3.18.0" iterators = "^0.0.2" typer = { version = "^0.9.0", extras=["all"] } tomli = "^2.0.1" +tomli-w = "^1.0.0" pathspec = "^0.12.1" # Optional dependencies (Simulation Engine) ray = { version = "==2.10.0", optional = true, python = ">=3.8,<3.12" } diff --git a/src/py/flwr/cli/build.py b/src/py/flwr/cli/build.py index 670b8bd64908..1f7f75d36184 100644 --- a/src/py/flwr/cli/build.py +++ b/src/py/flwr/cli/build.py @@ -20,6 +20,7 @@ from typing import Optional import pathspec +import tomli_w import typer from typing_extensions import Annotated @@ -93,15 +94,28 @@ def build( allowed_extensions = {".py", ".toml", ".md"} + # Remove the 'federations' field from 'tool.flwr' if it exists + if ( + "tool" in conf + and "flwr" in conf["tool"] + and "federations" in conf["tool"]["flwr"] + ): + del conf["tool"]["flwr"]["federations"] + + toml_contents = tomli_w.dumps(conf) + with zipfile.ZipFile(fab_filename, "w", zipfile.ZIP_DEFLATED) as fab_file: + fab_file.writestr("pyproject.toml", toml_contents) + + # Continue with adding other files for root, _, files in os.walk(directory, topdown=True): - # Filter directories and files based on .gitignore files = [ f for f in files if not ignore_spec.match_file(Path(root) / f) and f != fab_filename and Path(f).suffix in allowed_extensions + and f != "pyproject.toml" # Exclude the original pyproject.toml ] for file in files: diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl index 5934a258564b..507b5d50b843 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = { text = "Apache License (2.0)" } +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets>=0.1.0,<1.0.0", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 59f41f062af4..7a63e1ab5368 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = { text = "Apache License (2.0)" } +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets>=0.0.2,<1.0.0", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index 27f4b30ec3b8..297784a4d2d8 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = {text = "Apache License (2.0)"} +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "jax==0.4.26", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index 9c8905a0e8e5..fb55f6628cea 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = { text = "Apache License (2.0)" } +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index 38bfa1888c4c..ae88472647dc 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = { text = "Apache License (2.0)" } +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "numpy>=1.21.0", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index 2fd366d8e350..2dd49a25fd90 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = { text = "Apache License (2.0)" } +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 143c3756858c..8458fa64ea2d 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = { text = "Apache License (2.0)" } +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index 964bef58c498..2bf0e7d5642c 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "$package_name" version = "1.0.0" description = "" -license = { text = "Apache License (2.0)" } +license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets[vision]>=0.0.2,<1.0.0", From 284921db5dc5442ed5e156c80101be98f51e8ad4 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 16 Jul 2024 15:43:03 +0200 Subject: [PATCH 180/595] ci(datasets:skip) Fix pyarrow version (#3817) --- datasets/pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/datasets/pyproject.toml b/datasets/pyproject.toml index 017374181f59..e3afd8b87075 100644 --- a/datasets/pyproject.toml +++ b/datasets/pyproject.toml @@ -59,6 +59,7 @@ pillow = { version = ">=6.2.1", optional = true } soundfile = { version = ">=0.12.1", optional = true } librosa = { version = ">=0.10.0.post2", optional = true } tqdm ="^4.66.1" +pyarrow = "==16.1.0" matplotlib = "^3.7.5" seaborn = "^0.13.0" From 1f3fe0f7fe6d4191d24084fdaa74eb528d57eec3 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 16 Jul 2024 15:49:05 +0200 Subject: [PATCH 181/595] feat(framework) Update context registration when running an app directory (#3815) --- src/py/flwr/client/app.py | 6 +++--- src/py/flwr/client/node_state.py | 20 +++++++++++++++++--- src/py/flwr/client/supernode/app.py | 2 +- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 348ef8910dd3..127bb423851f 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -195,7 +195,7 @@ def _start_client_internal( ] = None, max_retries: Optional[int] = None, max_wait_time: Optional[float] = None, - flwr_dir: Optional[Path] = None, + flwr_path: Optional[Path] = None, ) -> None: """Start a Flower client node which connects to a Flower server. @@ -241,7 +241,7 @@ class `flwr.client.Client` (default: None) The maximum duration before the client stops trying to connect to the server in case of connection error. If set to None, there is no limit to the total time. - flwr_dir: Optional[Path] (default: None) + flwr_path: Optional[Path] (default: None) The fully resolved path containing installed Flower Apps. """ if insecure is None: @@ -402,7 +402,7 @@ def _on_backoff(retry_state: RetryState) -> None: # Register context for this run node_state.register_context( - run_id=run_id, run=runs[run_id], flwr_dir=flwr_dir + run_id=run_id, run=runs[run_id], flwr_path=flwr_path ) # Retrieve context for this run diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 393ca4564a35..08c19967ea3d 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -20,7 +20,7 @@ from typing import Dict, Optional from flwr.common import Context, RecordSet -from flwr.common.config import get_fused_config +from flwr.common.config import get_fused_config, get_fused_config_from_dir from flwr.common.typing import Run @@ -48,11 +48,25 @@ def register_context( self, run_id: int, run: Optional[Run] = None, - flwr_dir: Optional[Path] = None, + flwr_path: Optional[Path] = None, + app_dir: Optional[str] = None, ) -> None: """Register new run context for this node.""" if run_id not in self.run_infos: - initial_run_config = get_fused_config(run, flwr_dir) if run else {} + initial_run_config = {} + if app_dir: + # Load from app directory + app_path = Path(app_dir) + if app_path.is_dir(): + override_config = run.override_config if run else {} + initial_run_config = get_fused_config_from_dir( + app_path, override_config + ) + else: + raise ValueError("The specified `app_dir` must be a directory.") + else: + # Load from .fab + initial_run_config = get_fused_config(run, flwr_path) if run else {} self.run_infos[run_id] = RunInfo( initial_run_config=initial_run_config, context=Context( diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 027c3376b7f3..a364318c766c 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -78,7 +78,7 @@ def run_supernode() -> None: max_retries=args.max_retries, max_wait_time=args.max_wait_time, node_config=parse_config_args(args.node_config), - flwr_dir=get_flwr_dir(args.flwr_dir), + flwr_path=get_flwr_dir(args.flwr_dir), ) # Graceful shutdown From 87de8aee2e02721f79f142da1076d2445eca438c Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 16 Jul 2024 19:27:50 +0200 Subject: [PATCH 182/595] feat(framework) Support running app directories directly via `flower-simulation` (#3810) Co-authored-by: Charles Beauville Co-authored-by: Daniel J. Beutel --- .../server/superlink/fleet/vce/vce_api.py | 13 +- .../superlink/fleet/vce/vce_api_test.py | 1 + src/py/flwr/simulation/run_simulation.py | 162 ++++++++++++++++-- 3 files changed, 159 insertions(+), 17 deletions(-) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index b652207961a1..320f839e9e01 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -61,7 +61,9 @@ def _register_nodes( def _register_node_states( - nodes_mapping: NodeToPartitionMapping, run: Run + nodes_mapping: NodeToPartitionMapping, + run: Run, + app_dir: Optional[str] = None, ) -> Dict[int, NodeState]: """Create NodeState objects and pre-register the context for the run.""" node_states: Dict[int, NodeState] = {} @@ -76,7 +78,9 @@ def _register_node_states( ) # Pre-register Context objects - node_states[node_id].register_context(run_id=run.run_id, run=run) + node_states[node_id].register_context( + run_id=run.run_id, run=run, app_dir=app_dir + ) return node_states @@ -256,6 +260,7 @@ def start_vce( backend_name: str, backend_config_json_stream: str, app_dir: str, + is_app: bool, f_stop: threading.Event, run: Run, flwr_dir: Optional[str] = None, @@ -309,7 +314,9 @@ def start_vce( ) # Construct mapping of NodeStates - node_states = _register_node_states(nodes_mapping=nodes_mapping, run=run) + node_states = _register_node_states( + nodes_mapping=nodes_mapping, run=run, app_dir=app_dir if is_app else None + ) # Load backend config log(DEBUG, "Supported backends: %s", list(supported_backends.keys())) diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py index 4dfc08560523..33c359af5cc8 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api_test.py @@ -174,6 +174,7 @@ def start_and_shutdown( backend_config_json_stream=backend_config, state_factory=state_factory, app_dir=app_dir, + is_app=False, f_stop=f_stop, run=run, existing_nodes_mapping=nodes_mapping, diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 8c70bf8374d0..8a3c7739e595 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -18,14 +18,19 @@ import asyncio import json import logging +import sys import threading import traceback +from argparse import Namespace from logging import DEBUG, ERROR, INFO, WARNING +from pathlib import Path from time import sleep -from typing import Dict, Optional +from typing import Dict, List, Optional +from flwr.cli.config_utils import load_and_validate from flwr.client import ClientApp from flwr.common import EventType, event, log +from flwr.common.config import get_fused_config_from_dir, parse_config_args from flwr.common.constant import RUN_ID_NUM_BYTES from flwr.common.logger import set_logger_propagation, update_console_handler from flwr.common.typing import Run @@ -41,28 +46,129 @@ ) +def _check_args_do_not_interfere(args: Namespace) -> bool: + """Ensure decoupling of flags for different ways to start the simulation.""" + mode_one_args = ["app", "run_config"] + mode_two_args = ["client_app", "server_app"] + + def _resolve_message(conflict_keys: List[str]) -> str: + return ",".join([f"`--{key}`".replace("_", "-") for key in conflict_keys]) + + # When passing `--app`, `--app-dir` is ignored + if args.app and args.app_dir: + log(ERROR, "Either `--app` or `--app-dir` can be set, but not both.") + return False + + if any(getattr(args, key) for key in mode_one_args): + if any(getattr(args, key) for key in mode_two_args): + log( + ERROR, + "Passing any of {%s} alongside with any of {%s}", + _resolve_message(mode_one_args), + _resolve_message(mode_two_args), + ) + return False + + if not args.app: + log(ERROR, "You need to pass --app") + return False + + return True + + # Ensure all args are set (required for the non-FAB mode of execution) + if not all(getattr(args, key) for key in mode_two_args): + log( + ERROR, + "Passing all of %s keys are required.", + _resolve_message(mode_two_args), + ) + return False + + return True + + # Entry point from CLI +# pylint: disable=too-many-locals def run_simulation_from_cli() -> None: """Run Simulation Engine from the CLI.""" args = _parse_args_run_simulation().parse_args() + # We are supporting two modes for the CLI entrypoint: + # 1) Running an app dir containing a `pyproject.toml` + # 2) Running any ClientApp and SeverApp w/o pyproject.toml being present + # For 2), some CLI args are compulsory, but they are not required for 1) + # We first do these checks + args_check_pass = _check_args_do_not_interfere(args) + if not args_check_pass: + sys.exit("Simulation Engine cannot start.") + + run_id = ( + generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) + if args.run_id is None + else args.run_id + ) + if args.app: + # Mode 1 + app_path = Path(args.app) + if not app_path.is_dir(): + log(ERROR, "--app is not a directory") + sys.exit("Simulation Engine cannot start.") + + # Load pyproject.toml + config, errors, warnings = load_and_validate( + app_path / "pyproject.toml", check_module=False + ) + if errors: + raise ValueError(errors) + + if warnings: + log(WARNING, warnings) + + if config is None: + raise ValueError("Config extracted from FAB's pyproject.toml is not valid") + + # Get ClientApp and SeverApp components + app_components = config["tool"]["flwr"]["app"]["components"] + client_app_attr = app_components["clientapp"] + server_app_attr = app_components["serverapp"] + + override_config = parse_config_args(args.run_config) + fused_config = get_fused_config_from_dir(app_path, override_config) + app_dir = args.app + is_app = True + + else: + # Mode 2 + client_app_attr = args.client_app + server_app_attr = args.server_app + override_config = {} + fused_config = None + app_dir = args.app_dir + is_app = False + + # Create run + run = Run( + run_id=run_id, + fab_id="", + fab_version="", + override_config=override_config, + ) + # Load JSON config backend_config_dict = json.loads(args.backend_config) _run_simulation( - server_app_attr=args.server_app, - client_app_attr=args.client_app, + server_app_attr=server_app_attr, + client_app_attr=client_app_attr, num_supernodes=args.num_supernodes, backend_name=args.backend, backend_config=backend_config_dict, - app_dir=args.app_dir, - run=( - Run(run_id=args.run_id, fab_id="", fab_version="", override_config={}) - if args.run_id - else None - ), + app_dir=app_dir, + run=run, enable_tf_gpu_growth=args.enable_tf_gpu_growth, verbose_logging=args.verbose, + server_app_run_config=fused_config, + is_app=is_app, ) @@ -205,6 +311,7 @@ def _main_loop( backend_name: str, backend_config_stream: str, app_dir: str, + is_app: bool, enable_tf_gpu_growth: bool, run: Run, flwr_dir: Optional[str] = None, @@ -212,6 +319,7 @@ def _main_loop( client_app_attr: Optional[str] = None, server_app: Optional[ServerApp] = None, server_app_attr: Optional[str] = None, + server_app_run_config: Optional[Dict[str, str]] = None, ) -> None: """Launch SuperLink with Simulation Engine, then ServerApp on a separate thread.""" # Initialize StateFactory @@ -225,7 +333,9 @@ def _main_loop( # Register run log(DEBUG, "Pre-registering run with id %s", run.run_id) state_factory.state().run_ids[run.run_id] = run # type: ignore - server_app_run_config: Dict[str, str] = {} + + if server_app_run_config is None: + server_app_run_config = {} # Initialize Driver driver = InMemoryDriver(run_id=run.run_id, state_factory=state_factory) @@ -251,6 +361,7 @@ def _main_loop( backend_name=backend_name, backend_config_json_stream=backend_config_stream, app_dir=app_dir, + is_app=is_app, state_factory=state_factory, f_stop=f_stop, run=run, @@ -284,11 +395,13 @@ def _run_simulation( backend_config: Optional[BackendConfig] = None, client_app_attr: Optional[str] = None, server_app_attr: Optional[str] = None, + server_app_run_config: Optional[Dict[str, str]] = None, app_dir: str = "", flwr_dir: Optional[str] = None, run: Optional[Run] = None, enable_tf_gpu_growth: bool = False, verbose_logging: bool = False, + is_app: bool = False, ) -> None: r"""Launch the Simulation Engine. @@ -317,14 +430,18 @@ def _run_simulation( parameters. Values supported in are those included by `flwr.common.typing.ConfigsRecordValues`. - client_app_attr : str + client_app_attr : Optional[str] A path to a `ClientApp` module to be loaded: For example: `client:app` or `project.package.module:wrapper.app`." - server_app_attr : str + server_app_attr : Optional[str] A path to a `ServerApp` module to be loaded: For example: `server:app` or `project.package.module:wrapper.app`." + server_app_run_config : Optional[Dict[str, str]] + Config dictionary that parameterizes the run config. It will be made accesible + to the ServerApp. + app_dir : str Add specified directory to the PYTHONPATH and load `ClientApp` from there. (Default: current working directory.) @@ -346,6 +463,11 @@ def _run_simulation( verbose_logging : bool (default: False) When disabled, only INFO, WARNING and ERROR log messages will be shown. If enabled, DEBUG-level logs will be displayed. + + is_app : bool (default: False) + A flag that indicates whether the simulation is running an app or not. This is + needed in order to attempt loading an app's pyproject.toml when nodes register + a context object. """ if backend_config is None: backend_config = {} @@ -381,6 +503,7 @@ def _run_simulation( backend_name, backend_config_stream, app_dir, + is_app, enable_tf_gpu_growth, run, flwr_dir, @@ -388,6 +511,7 @@ def _run_simulation( client_app_attr, server_app, server_app_attr, + server_app_run_config, ) # Detect if there is an Asyncio event loop already running. # If yes, disable logger propagation. In environmnets @@ -419,12 +543,10 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: ) parser.add_argument( "--server-app", - required=True, help="For example: `server:app` or `project.package.module:wrapper.app`", ) parser.add_argument( "--client-app", - required=True, help="For example: `client:app` or `project.package.module:wrapper.app`", ) parser.add_argument( @@ -433,6 +555,18 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: required=True, help="Number of simulated SuperNodes.", ) + parser.add_argument( + "--app", + type=str, + default=None, + help="Path to a directory containing a FAB-like structure with a " + "pyproject.toml.", + ) + parser.add_argument( + "--run-config", + default=None, + help="Override configuration key-value pairs.", + ) parser.add_argument( "--backend", default="ray", From 749d4e55d1e70ce22119e7db3915d13aab1869a0 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 16 Jul 2024 21:16:37 +0200 Subject: [PATCH 183/595] fix(framework:skip) Fix TOML fields renaming (#3818) --- src/py/flwr/cli/run/run.py | 4 ++-- src/py/flwr/client/supernode/app.py | 2 +- src/py/flwr/server/run_serverapp.py | 2 +- src/py/flwr/superexec/simulation.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 512da83d13fe..288cf0838a52 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -171,8 +171,8 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( config: Dict[str, Any], federation: Dict[str, Any], federation_name: str ) -> None: - server_app_ref = config["tool"]["flwr"]["components"]["serverapp"] - client_app_ref = config["tool"]["flwr"]["components"]["clientapp"] + server_app_ref = config["tool"]["flwr"]["app"]["components"]["serverapp"] + client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"] try: num_supernodes = federation["options"]["num-supernodes"] diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index a364318c766c..0ef0a145b1b6 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -248,7 +248,7 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: dir_path = Path(project_dir).absolute() # Set app reference - client_app_ref = config["tool"]["flwr"]["components"]["clientapp"] + client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"] # Set sys.path nonlocal inserted_path diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index efaba24f05f9..0169946e237d 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -186,7 +186,7 @@ def run_server_app() -> None: # pylint: disable=too-many-branches run_ = driver.run server_app_dir = str(get_project_dir(run_.fab_id, run_.fab_version, flwr_dir)) config = get_project_config(server_app_dir) - server_app_attr = config["tool"]["flwr"]["components"]["serverapp"] + server_app_attr = config["tool"]["flwr"]["app"]["components"]["serverapp"] server_app_run_config = get_fused_config(run_, flwr_dir) else: # User provided `server-app`, but not `--run-id` diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index fa7a8ad9b0d3..e71e39e38e36 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -112,7 +112,7 @@ def start_run( ) # Get ClientApp and SeverApp components - flower_components = config["tool"]["flwr"]["components"] + flower_components = config["tool"]["flwr"]["app"]["components"] clientapp = flower_components["clientapp"] serverapp = flower_components["serverapp"] From 744e0e93ee58b0846be21cf7e610dda9dddfe672 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 16 Jul 2024 22:36:00 +0200 Subject: [PATCH 184/595] refactor(framework) Run app with `flwr run` calling `flower-simulation` (#3819) Co-authored-by: Daniel J. Beutel Co-authored-by: Charles Beauville --- src/py/flwr/cli/run/run.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 288cf0838a52..5db575abcb87 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -14,6 +14,7 @@ # ============================================================================== """Flower command line interface `run` command.""" +import subprocess import sys from logging import DEBUG from pathlib import Path @@ -29,7 +30,6 @@ from flwr.common.logger import log from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611 from flwr.proto.exec_pb2_grpc import ExecStub -from flwr.simulation.run_simulation import _run_simulation # pylint: disable-next=too-many-locals @@ -107,7 +107,7 @@ def run( if "address" in federation: _run_with_superexec(federation, directory, config_overrides) else: - _run_without_superexec(config, federation, federation_name) + _run_without_superexec(directory, federation, federation_name, config_overrides) def _run_with_superexec( @@ -169,11 +169,11 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( - config: Dict[str, Any], federation: Dict[str, Any], federation_name: str + app_path: Optional[Path], + federation: Dict[str, Any], + federation_name: str, + config_overrides: Optional[str], ) -> None: - server_app_ref = config["tool"]["flwr"]["app"]["components"]["serverapp"] - client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"] - try: num_supernodes = federation["options"]["num-supernodes"] except KeyError as err: @@ -188,8 +188,20 @@ def _run_without_superexec( ) raise typer.Exit(code=1) from err - _run_simulation( - server_app_attr=server_app_ref, - client_app_attr=client_app_ref, - num_supernodes=num_supernodes, + command = [ + "flower-simulation", + "--app", + f"{app_path}", + "--num-supernodes", + f"{num_supernodes}", + ] + + if config_overrides: + command.extend(["--run-config", f"{config_overrides}"]) + + # Run the simulation + subprocess.run( + command, + check=True, + text=True, ) From 9f1603e20af63d9e5e2185825115266e7df0a231 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 16 Jul 2024 23:28:32 +0200 Subject: [PATCH 185/595] feat(framework) Include default number of server rounds in templates (#3821) --- src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl | 5 ++++- src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl | 5 ++++- src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl | 5 ++++- src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl | 5 ++++- src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl | 5 ++++- src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl | 5 ++++- .../flwr/cli/new/templates/app/code/server.tensorflow.py.tpl | 5 ++++- .../flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl | 3 +++ src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl | 3 +++ src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl | 3 +++ src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl | 3 +++ src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl | 3 +++ src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl | 3 +++ src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl | 3 +++ .../flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl | 3 +++ 15 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl index 039ea8619532..43fce9e481c6 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl @@ -6,12 +6,15 @@ from flwr.server import ServerApp, ServerAppComponents, ServerConfig def server_fn(context: Context): + # Read from config + num_rounds = int(context.run_config["num-server-rounds"]) + # Define strategy strategy = FedAvg( fraction_fit=1.0, fraction_evaluate=1.0, ) - config = ServerConfig(num_rounds=3) + config = ServerConfig(num_rounds=num_rounds) return ServerAppComponents(strategy=strategy, config=config) diff --git a/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl index 122b884ab8bb..4eb7149de999 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl @@ -6,9 +6,12 @@ from flwr.server import ServerApp, ServerAppComponents, ServerConfig def server_fn(context: Context): + # Read from config + num_rounds = int(context.run_config["num-server-rounds"]) + # Define strategy strategy = FedAvg() - config = ServerConfig(num_rounds=3) + config = ServerConfig(num_rounds=num_rounds) return ServerAppComponents(strategy=strategy, config=config) diff --git a/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl index 403c68ac3405..72aed878553d 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl @@ -6,9 +6,12 @@ from flwr.server.strategy import FedAvg def server_fn(context: Context): + # Read from config + num_rounds = int(context.run_config["num-server-rounds"]) + # Define strategy strategy = FedAvg() - config = ServerConfig(num_rounds=3) + config = ServerConfig(num_rounds=num_rounds) return ServerAppComponents(strategy=strategy, config=config) diff --git a/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl index 1ed2d36339db..d324b4f24fed 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl @@ -6,9 +6,12 @@ from flwr.server.strategy import FedAvg def server_fn(context: Context): + # Read from config + num_rounds = int(context.run_config["num-server-rounds"]) + # Define strategy strategy = FedAvg() - config = ServerConfig(num_rounds=3) + config = ServerConfig(num_rounds=num_rounds) return ServerAppComponents(strategy=strategy, config=config) diff --git a/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl index 3638b9eba7b0..7ac9508f8a25 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl @@ -12,6 +12,9 @@ ndarrays = get_weights(Net()) parameters = ndarrays_to_parameters(ndarrays) def server_fn(context: Context): + # Read from config + num_rounds = int(context.run_config["num-server-rounds"]) + # Define strategy strategy = FedAvg( fraction_fit=1.0, @@ -19,7 +22,7 @@ def server_fn(context: Context): min_available_clients=2, initial_parameters=parameters, ) - config = ServerConfig(num_rounds=3) + config = ServerConfig(num_rounds=num_rounds) return ServerAppComponents(strategy=strategy, config=config) diff --git a/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl index 2e463e8da09e..d8837798d5a6 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl @@ -6,13 +6,16 @@ from flwr.server.strategy import FedAvg def server_fn(context: Context): + # Read from config + num_rounds = int(context.run_config["num-server-rounds"]) + # Define strategy strategy = FedAvg( fraction_fit=1.0, fraction_evaluate=1.0, min_available_clients=2, ) - config = ServerConfig(num_rounds=3) + config = ServerConfig(num_rounds=num_rounds) return ServerAppComponents(strategy=strategy, config=config) diff --git a/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl index eee727ba9025..abd2a977b503 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl @@ -12,6 +12,9 @@ config = ServerConfig(num_rounds=3) parameters = ndarrays_to_parameters(load_model().get_weights()) def server_fn(context: Context): + # Read from config + num_rounds = int(context.run_config["num-server-rounds"]) + # Define strategy strategy = strategy = FedAvg( fraction_fit=1.0, @@ -19,7 +22,7 @@ def server_fn(context: Context): min_available_clients=2, initial_parameters=parameters, ) - config = ServerConfig(num_rounds=3) + config = ServerConfig(num_rounds=num_rounds) return ServerAppComponents(strategy=strategy, config=config) diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl index 507b5d50b843..ca0b25f172fb 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -29,6 +29,9 @@ publisher = "$username" serverapp = "$import_name.app:server" clientapp = "$import_name.app:client" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 7a63e1ab5368..b39facbec5a0 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -27,6 +27,9 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index 297784a4d2d8..405decf38f16 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -24,6 +24,9 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index fb55f6628cea..a2b743800595 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -24,6 +24,9 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index ae88472647dc..ad074b90d24a 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -22,6 +22,9 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index 2dd49a25fd90..ecd1497500ab 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -24,6 +24,9 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 8458fa64ea2d..4bc407c34262 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -23,6 +23,9 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index 2bf0e7d5642c..9dab874e50ff 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -23,6 +23,9 @@ publisher = "$username" serverapp = "$import_name.server:app" clientapp = "$import_name.client:app" +[tool.flwr.app.config] +num-server-rounds = "3" + [tool.flwr.federations] default = "localhost" From bb71bbc4d1396c3d90ee9e46fb7e8107516b459e Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 17 Jul 2024 11:07:46 +0200 Subject: [PATCH 186/595] feat(framework) Allow multiple separated run-config arguments (#3824) --- src/py/flwr/cli/run/run.py | 8 ++++---- src/py/flwr/common/config.py | 27 ++++++++++++++------------- src/py/flwr/common/config_test.py | 7 ++++++- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 5db575abcb87..f627a917eb53 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -18,7 +18,7 @@ import sys from logging import DEBUG from pathlib import Path -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional import typer from typing_extensions import Annotated @@ -43,7 +43,7 @@ def run( typer.Argument(help="Name of the federation to run the app on"), ] = None, config_overrides: Annotated[ - Optional[str], + Optional[List[str]], typer.Option( "--run-config", "-c", @@ -113,7 +113,7 @@ def run( def _run_with_superexec( federation: Dict[str, str], directory: Optional[Path], - config_overrides: Optional[str], + config_overrides: Optional[List[str]], ) -> None: def on_channel_state_change(channel_connectivity: str) -> None: @@ -172,7 +172,7 @@ def _run_without_superexec( app_path: Optional[Path], federation: Dict[str, Any], federation_name: str, - config_overrides: Optional[str], + config_overrides: Optional[List[str]], ) -> None: try: num_supernodes = federation["options"]["num-supernodes"] diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 6049fcbcceed..3bd7a103f4c2 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -130,7 +130,7 @@ def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, st def parse_config_args( - config: Optional[str], + config: Optional[List[str]], separator: str = ",", ) -> Dict[str, str]: """Parse separator separated list of key-value pairs separated by '='.""" @@ -139,17 +139,18 @@ def parse_config_args( if config is None: return overrides - overrides_list = config.split(separator) - if ( - len(overrides_list) == 1 - and "=" not in overrides_list - and overrides_list[0].endswith(".toml") - ): - with Path(overrides_list[0]).open("rb") as config_file: - overrides = flatten_dict(tomli.load(config_file)) - else: - for kv_pair in overrides_list: - key, value = kv_pair.split("=") - overrides[key] = value + for config_line in config: + overrides_list = config_line.split(separator) + if ( + len(overrides_list) == 1 + and "=" not in overrides_list + and overrides_list[0].endswith(".toml") + ): + with Path(overrides_list[0]).open("rb") as config_file: + overrides = flatten_dict(tomli.load(config_file)) + else: + for kv_pair in overrides_list: + key, value = kv_pair.split("=") + overrides[key] = value return overrides diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py index feef89e7d5cb..e1597aa5a2ec 100644 --- a/src/py/flwr/common/config_test.py +++ b/src/py/flwr/common/config_test.py @@ -230,7 +230,12 @@ def test_parse_config_args_none() -> None: def test_parse_config_args_overrides() -> None: """Test parse_config_args with key-value pairs.""" - assert parse_config_args("key1=value1,key2=value2") == { + assert parse_config_args( + ["key1=value1,key2=value2", "key3=value3", "key4=value4,key5=value5"] + ) == { "key1": "value1", "key2": "value2", + "key3": "value3", + "key4": "value4", + "key5": "value5", } From 0201b462a3158c7fa1b3e108e6f5f7b11205fc30 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 17 Jul 2024 11:13:28 +0200 Subject: [PATCH 187/595] fix(framework) Display correct federation error message (#3825) --- src/py/flwr/cli/run/run.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index f627a917eb53..5dedb701fea9 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -94,11 +94,13 @@ def run( # Validate the federation exists in the configuration federation = config["tool"]["flwr"]["federations"].get(federation_name) if federation is None: - available_feds = list(config["tool"]["flwr"]["federations"]) + available_feds = { + fed for fed in config["tool"]["flwr"]["federations"] if fed != "default" + } typer.secho( f"❌ There is no `{federation_name}` federation declared in the " "`pyproject.toml`.\n The following federations were found:\n\n" - "\n".join(available_feds) + "\n\n", + + "\n".join(available_feds), fg=typer.colors.RED, bold=True, ) From a453505e1fd4e943d627d3d08f3bdca0e8454f7f Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 17 Jul 2024 11:39:00 +0200 Subject: [PATCH 188/595] feat(framework) Install Python package(s) on `flwr install` (#3816) Co-authored-by: Daniel J. Beutel --- src/py/flwr/cli/install.py | 16 ++++++++++++++++ src/py/flwr/superexec/deployment.py | 10 +--------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/py/flwr/cli/install.py b/src/py/flwr/cli/install.py index a1a66e42fd65..749a4516f65c 100644 --- a/src/py/flwr/cli/install.py +++ b/src/py/flwr/cli/install.py @@ -16,6 +16,7 @@ import shutil +import subprocess import tempfile import zipfile from io import BytesIO @@ -192,6 +193,21 @@ def validate_and_install( else: shutil.copy2(item, install_dir / item.name) + try: + subprocess.run( + ["pip", "install", "-e", install_dir, "--no-deps"], + capture_output=True, + text=True, + check=True, + ) + except subprocess.CalledProcessError as e: + typer.secho( + f"❌ Failed to `pip install` package(s) from {install_dir}:\n{e.stderr}", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) from e + typer.secho( f"🎊 Successfully installed {project_name} to {install_dir}.", fg=typer.colors.GREEN, diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index bbe7882692f0..d012d408a9ff 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -15,7 +15,6 @@ """Deployment engine executor.""" import subprocess -import sys from logging import ERROR, INFO from pathlib import Path from typing import Dict, Optional @@ -131,14 +130,7 @@ def start_run( try: # Install FAB to flwr dir fab_version, fab_id = get_fab_metadata(fab_file) - fab_path = install_from_fab(fab_file, None, True) - - # Install FAB Python package - subprocess.check_call( - [sys.executable, "-m", "pip", "install", "--no-deps", str(fab_path)], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) + install_from_fab(fab_file, None, True) # Call SuperLink to create run run_id: int = self._create_run(fab_id, fab_version, override_config) From 99f11c24e34cf5906ead4095a9ecef893329176e Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Wed, 17 Jul 2024 18:45:11 +0900 Subject: [PATCH 189/595] docs(examples) Fix typo in iOS example notebook (#3823) --- examples/ios/scenarios.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ios/scenarios.ipynb b/examples/ios/scenarios.ipynb index de3e0e0c8c49..01da347cfee6 100644 --- a/examples/ios/scenarios.ipynb +++ b/examples/ios/scenarios.ipynb @@ -7,7 +7,7 @@ "source": [ "# Extending FLiOS Scenarios\n", "\n", - "This notebook demonstrates how to download and preprocess further benchmarking datasets and its associated machine learning models for the extenstion of the FLiOS application." + "This notebook demonstrates how to download and preprocess further benchmarking datasets and its associated machine learning models for the extension of the FLiOS application." ] }, { From da49c357b54fba9d0f288c6a9777c4867c063129 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 17 Jul 2024 15:46:19 +0200 Subject: [PATCH 190/595] refactor(framework) Update launch of simulation from executor plugin (#3829) --- src/py/flwr/superexec/simulation.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index e71e39e38e36..63c6b3270917 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -111,11 +111,6 @@ def start_run( "Config extracted from FAB's pyproject.toml is not valid" ) - # Get ClientApp and SeverApp components - flower_components = config["tool"]["flwr"]["app"]["components"] - clientapp = flower_components["clientapp"] - serverapp = flower_components["serverapp"] - # In Simulation there is no SuperLink, still we create a run_id run_id = generate_rand_int_from_bytes(RUN_ID_NUM_BYTES) log(INFO, "Created run %s", str(run_id)) @@ -123,16 +118,17 @@ def start_run( # Prepare commnand command = [ "flower-simulation", - "--client-app", - f"{clientapp}", - "--server-app", - f"{serverapp}", + "--app", + f"{str(fab_path)}", "--num-supernodes", f"{self.num_supernodes}", "--run-id", str(run_id), ] + if override_config: + command.extend(["--run-config", f"{override_config}"]) + # Start Simulation proc = subprocess.Popen( # pylint: disable=consider-using-with command, From e737515c051092c445ace07735307c9c46c8fffd Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 17 Jul 2024 16:48:59 +0200 Subject: [PATCH 191/595] fix(framework:skip) Pass list of strings to parsing func (#3836) --- src/py/flwr/client/supernode/app.py | 4 ++-- src/py/flwr/common/config.py | 25 ++++++++++++------------ src/py/flwr/simulation/run_simulation.py | 2 +- src/py/flwr/superexec/app.py | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index 0ef0a145b1b6..f3fb0e97805a 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -77,7 +77,7 @@ def run_supernode() -> None: authentication_keys=authentication_keys, max_retries=args.max_retries, max_wait_time=args.max_wait_time, - node_config=parse_config_args(args.node_config), + node_config=parse_config_args([args.node_config]), flwr_path=get_flwr_dir(args.flwr_dir), ) @@ -107,7 +107,7 @@ def run_client_app() -> None: _start_client_internal( server_address=args.superlink, - node_config=parse_config_args(args.node_config), + node_config=parse_config_args([args.node_config]), load_client_app_fn=load_fn, transport=args.transport, root_certificates=root_certificates, diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 3bd7a103f4c2..789433a287e7 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -140,17 +140,18 @@ def parse_config_args( return overrides for config_line in config: - overrides_list = config_line.split(separator) - if ( - len(overrides_list) == 1 - and "=" not in overrides_list - and overrides_list[0].endswith(".toml") - ): - with Path(overrides_list[0]).open("rb") as config_file: - overrides = flatten_dict(tomli.load(config_file)) - else: - for kv_pair in overrides_list: - key, value = kv_pair.split("=") - overrides[key] = value + if config_line: + overrides_list = config_line.split(separator) + if ( + len(overrides_list) == 1 + and "=" not in overrides_list + and overrides_list[0].endswith(".toml") + ): + with Path(overrides_list[0]).open("rb") as config_file: + overrides = flatten_dict(tomli.load(config_file)) + else: + for kv_pair in overrides_list: + key, value = kv_pair.split("=") + overrides[key] = value return overrides diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 8a3c7739e595..e5d82207e08b 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -132,7 +132,7 @@ def run_simulation_from_cli() -> None: client_app_attr = app_components["clientapp"] server_app_attr = app_components["serverapp"] - override_config = parse_config_args(args.run_config) + override_config = parse_config_args([args.run_config]) fused_config = get_fused_config_from_dir(app_path, override_config) app_dir = args.app is_app = True diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py index b51c3e6821dc..9f1753ce041b 100644 --- a/src/py/flwr/superexec/app.py +++ b/src/py/flwr/superexec/app.py @@ -56,7 +56,7 @@ def run_superexec() -> None: address=address, executor=_load_executor(args), certificates=certificates, - config=parse_config_args(args.executor_config), + config=parse_config_args([args.executor_config]), ) grpc_servers = [superexec_server] From d10d6b8cbff4d496eaa6707b1a9cc60ad310525c Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Wed, 17 Jul 2024 16:57:49 +0200 Subject: [PATCH 192/595] ci(*:skip) Upgrade pip and setuptools (#3835) Signed-off-by: Robert Steiner --- .devcontainer/Dockerfile | 4 ++-- .github/actions/bootstrap/action.yml | 4 ++-- dev/bootstrap.sh | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ce4f8a1a5b8d..7ab0812e7cff 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -29,8 +29,8 @@ RUN apt-get install -y curl wget gnupg python3 python-is-python3 python3-pip git build-essential tmux vim RUN python -m pip install \ - pip==24.0.0 \ - setuptools==69.5.1 \ + pip==24.1.2 \ + setuptools==70.3.0 \ poetry==1.7.1 USER $USERNAME diff --git a/.github/actions/bootstrap/action.yml b/.github/actions/bootstrap/action.yml index bee90beffa7d..4cde8dddfa3f 100644 --- a/.github/actions/bootstrap/action.yml +++ b/.github/actions/bootstrap/action.yml @@ -6,10 +6,10 @@ inputs: default: 3.8 pip-version: description: "Version of pip to be installed using pip" - default: 24.0.0 + default: 24.1.2 setuptools-version: description: "Version of setuptools to be installed using pip" - default: 69.5.1 + default: 70.3.0 poetry-version: description: "Version of poetry to be installed using pip" default: 1.7.1 diff --git a/dev/bootstrap.sh b/dev/bootstrap.sh index 154fe0f1cbaf..bfcdc8a4369e 100755 --- a/dev/bootstrap.sh +++ b/dev/bootstrap.sh @@ -9,8 +9,8 @@ cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/../ ./dev/rm-caches.sh # Upgrade/install spcific versions of `pip`, `setuptools`, and `poetry` -python -m pip install -U pip==24.0.0 -python -m pip install -U setuptools==69.5.1 +python -m pip install -U pip==24.1.2 +python -m pip install -U setuptools==70.3.0 python -m pip install -U poetry==1.7.1 # Use `poetry` to install project dependencies From e0cb149e9a3b0997d093be5bb656ac6e1ea26260 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Wed, 17 Jul 2024 17:30:56 +0200 Subject: [PATCH 193/595] feat(framework:skip) Add `app` suffix to `client` and `server` (#3828) --- src/py/flwr/cli/new/new.py | 7 ++++--- src/py/flwr/cli/new/new_test.py | 4 ++-- .../flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl | 4 ++-- .../cli/new/templates/app/code/flwr_tune/server.py.tpl | 2 +- src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl | 4 ++-- src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl | 4 ++-- src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl | 4 ++-- src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl | 4 ++-- .../flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl | 4 ++-- .../flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl | 4 ++-- .../cli/new/templates/app/pyproject.tensorflow.toml.tpl | 4 ++-- 11 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/py/flwr/cli/new/new.py b/src/py/flwr/cli/new/new.py index a0a2dc98556d..4bde009742f8 100644 --- a/src/py/flwr/cli/new/new.py +++ b/src/py/flwr/cli/new/new.py @@ -136,6 +136,7 @@ def new( framework_str = framework_str.lower() + llm_challenge_str = None if framework_str == "flowertune": llm_challenge_value = prompt_options( "Please select LLM challenge by typing in the number", @@ -171,7 +172,7 @@ def new( } # List of files to render - if framework_str == "flowertune": + if llm_challenge_str: files = { ".gitignore": {"template": "app/.gitignore.tpl"}, "pyproject.toml": {"template": f"app/pyproject.{framework_str}.toml.tpl"}, @@ -228,10 +229,10 @@ def new( "README.md": {"template": "app/README.md.tpl"}, "pyproject.toml": {"template": f"app/pyproject.{framework_str}.toml.tpl"}, f"{import_name}/__init__.py": {"template": "app/code/__init__.py.tpl"}, - f"{import_name}/server.py": { + f"{import_name}/server_app.py": { "template": f"app/code/server.{framework_str}.py.tpl" }, - f"{import_name}/client.py": { + f"{import_name}/client_app.py": { "template": f"app/code/client.{framework_str}.py.tpl" }, } diff --git a/src/py/flwr/cli/new/new_test.py b/src/py/flwr/cli/new/new_test.py index 33ad745efa93..7f22bd5f9825 100644 --- a/src/py/flwr/cli/new/new_test.py +++ b/src/py/flwr/cli/new/new_test.py @@ -86,8 +86,8 @@ def test_new_correct_name(tmp_path: str) -> None: } expected_files_module = { "__init__.py", - "server.py", - "client.py", + "server_app.py", + "client_app.py", "task.py", } diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl index ecb87bd71e3f..a0f781df04a1 100644 --- a/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl @@ -12,10 +12,10 @@ from flwr.client import ClientApp from flwr.common import ndarrays_to_parameters from flwr.server import ServerApp, ServerConfig -from $import_name.client import gen_client_fn, get_parameters +from $import_name.client_app import gen_client_fn, get_parameters from $import_name.dataset import get_tokenizer_and_data_collator_and_propt_formatting from $import_name.models import get_model -from $import_name.server import fit_weighted_average, get_evaluate_fn, get_on_fit_config +from $import_name.server_app import fit_weighted_average, get_evaluate_fn, get_on_fit_config # Avoid warnings warnings.filterwarnings("ignore", category=UserWarning) diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl index 19223148bca5..5dd4d881f2f1 100644 --- a/src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/server.py.tpl @@ -1,6 +1,6 @@ """$project_name: A Flower / FlowerTune app.""" -from $import_name.client import set_parameters +from $import_name.client_app import set_parameters from $import_name.models import get_model diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index b39facbec5a0..92c954e754cf 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -24,8 +24,8 @@ packages = ["."] publisher = "$username" [tool.flwr.app.components] -serverapp = "$import_name.server:app" -clientapp = "$import_name.client:app" +serverapp = "$import_name.server_app:app" +clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index 405decf38f16..e899f48f4c5c 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -21,8 +21,8 @@ packages = ["."] publisher = "$username" [tool.flwr.app.components] -serverapp = "$import_name.server:app" -clientapp = "$import_name.client:app" +serverapp = "$import_name.server_app:app" +clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index a2b743800595..6004c076cf87 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -21,8 +21,8 @@ packages = ["."] publisher = "$username" [tool.flwr.app.components] -serverapp = "$import_name.server:app" -clientapp = "$import_name.client:app" +serverapp = "$import_name.server_app:app" +clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index ad074b90d24a..543936ed4a89 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -19,8 +19,8 @@ packages = ["."] publisher = "$username" [tool.flwr.app.components] -serverapp = "$import_name.server:app" -clientapp = "$import_name.client:app" +serverapp = "$import_name.server_app:app" +clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index ecd1497500ab..8a92cf0eca9a 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -21,8 +21,8 @@ packages = ["."] publisher = "$username" [tool.flwr.app.components] -serverapp = "$import_name.server:app" -clientapp = "$import_name.client:app" +serverapp = "$import_name.server_app:app" +clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 4bc407c34262..5c1ffa09aed2 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -20,8 +20,8 @@ packages = ["."] publisher = "$username" [tool.flwr.app.components] -serverapp = "$import_name.server:app" -clientapp = "$import_name.client:app" +serverapp = "$import_name.server_app:app" +clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index 9dab874e50ff..de1a445e33f9 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -20,8 +20,8 @@ packages = ["."] publisher = "$username" [tool.flwr.app.components] -serverapp = "$import_name.server:app" -clientapp = "$import_name.client:app" +serverapp = "$import_name.server_app:app" +clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" From c6547efdf71ee2ef258552923b267fc8e61f253b Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 17 Jul 2024 18:35:46 +0200 Subject: [PATCH 194/595] fix(framework) Enable overriding of run configs with simulation plugin (#3839) --- src/py/flwr/superexec/simulation.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index 63c6b3270917..732d749c5c53 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -82,11 +82,6 @@ def start_run( ) -> Optional[RunTracker]: """Start run using the Flower Simulation Engine.""" try: - if override_config: - raise ValueError( - "Overriding the run config is not yet supported with the " - "simulation executor.", - ) # Install FAB to flwr dir fab_path = install_from_fab(fab_file, None, True) From a20e4c9a13681587aba2760bfdad23e5343af1f7 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 17 Jul 2024 19:06:39 +0200 Subject: [PATCH 195/595] feat(framework) Update subprocess launch mechanism for simulation plugin (#3826) Signed-off-by: Danny Heinrich Co-authored-by: Danny Heinrich --- src/py/flwr/superexec/simulation.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index 732d749c5c53..58cc194a16d4 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -125,10 +125,9 @@ def start_run( command.extend(["--run-config", f"{override_config}"]) # Start Simulation - proc = subprocess.Popen( # pylint: disable=consider-using-with + proc = subprocess.run( # pylint: disable=consider-using-with command, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + check=True, text=True, ) @@ -136,7 +135,7 @@ def start_run( return RunTracker( run_id=run_id, - proc=proc, + proc=proc, # type:ignore ) # pylint: disable-next=broad-except From 89436dfe74bb6cfd437f730be0f5462bf63a9022 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 18 Jul 2024 12:55:09 +0200 Subject: [PATCH 196/595] feat(framework) Add run_config to templates (#3845) --- .../new/templates/app/code/client.hf.py.tpl | 10 +++++++--- .../new/templates/app/code/client.mlx.py.tpl | 18 ++++++++++-------- .../templates/app/code/client.pytorch.py.tpl | 8 +++++++- .../templates/app/code/client.sklearn.py.tpl | 3 ++- .../app/code/client.tensorflow.py.tpl | 11 +++++++++-- .../new/templates/app/pyproject.hf.toml.tpl | 1 + .../new/templates/app/pyproject.mlx.toml.tpl | 5 +++++ .../templates/app/pyproject.pytorch.toml.tpl | 1 + .../app/pyproject.tensorflow.toml.tpl | 3 +++ 9 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl index 56bac8543c50..13b071013076 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl @@ -30,7 +30,11 @@ class FlowerClient(NumPyClient): def fit(self, parameters, config): self.set_parameters(parameters) - train(self.net, self.trainloader, epochs=1) + train( + self.net, + self.trainloader, + epochs=int(self.context.run_config["local-epochs"]), + ) return self.get_parameters(config={}), len(self.trainloader), {} def evaluate(self, parameters, config): @@ -45,8 +49,8 @@ def client_fn(context: Context): CHECKPOINT, num_labels=2 ).to(DEVICE) - partition_id = int(context.node_config['partition-id']) - num_partitions = int(context.node_config['num-partitions]) + partition_id = int(context.node_config["partition-id"]) + num_partitions = int(context.node_config["num-partitions"]) trainloader, valloader = load_data(partition_id, num_partitions) # Return Client instance diff --git a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl index 37207c940d83..fe1f4041a076 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl @@ -20,17 +20,19 @@ from $import_name.task import ( # Define Flower Client and client_fn class FlowerClient(NumPyClient): def __init__(self, data): - num_layers = 2 - hidden_dim = 32 + num_layers = int(self.context.run_config["num-layers"]) + hidden_dim = int(self.context.run_config["hidden-dim"]) num_classes = 10 - batch_size = 256 - num_epochs = 1 - learning_rate = 1e-1 + batch_size = int(self.context.run_config["batch-size"]) + learning_rate = float(self.context.run_config["lr"]) + num_epochs = int(self.context.run_config["local-epochs"]) self.train_images, self.train_labels, self.test_images, self.test_labels = data - self.model = MLP(num_layers, self.train_images.shape[-1], hidden_dim, num_classes) - self.optimizer = optim.SGD(learning_rate=learning_rate) - self.loss_and_grad_fn = nn.value_and_grad(self.model, loss_fn) + self.model = MLP( + num_layers, self.train_images.shape[-1], hidden_dim, num_classes + ) + self.optimizer = optim.SGD(learning_rate=learning_rate) + self.loss_and_grad_fn = nn.value_and_grad(self.model, loss_fn) self.num_epochs = num_epochs self.batch_size = batch_size diff --git a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl index addc71023a09..3635843ba0be 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl @@ -23,7 +23,13 @@ class FlowerClient(NumPyClient): def fit(self, parameters, config): set_weights(self.net, parameters) - results = train(self.net, self.trainloader, self.valloader, 1, DEVICE) + results = train( + self.net, + self.trainloader, + self.valloader, + int(self.context.run_config["local-epochs"]), + DEVICE, + ) return get_weights(self.net), len(self.trainloader.dataset), results def evaluate(self, parameters, config): diff --git a/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl index a1eefa034e7b..9642ae490155 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl @@ -67,10 +67,11 @@ class FlowerClient(NumPyClient): return loss, len(self.X_test), {"accuracy": accuracy} -fds = FederatedDataset(dataset="mnist", partitioners={"train": 2}) def client_fn(context: Context): partition_id = int(context.node_config["partition-id"]) + num_partitions = int(context.node_config["num-partitions"]) + fds = FederatedDataset(dataset="mnist", partitioners={"train": num_partitions}) dataset = fds.load_partition(partition_id, "train").with_format("numpy") X, y = dataset["image"].reshape((len(dataset), -1)), dataset["label"] diff --git a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl index 0fe1c405a110..5702f5b9c0d0 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl @@ -20,7 +20,13 @@ class FlowerClient(NumPyClient): def fit(self, parameters, config): self.model.set_weights(parameters) - self.model.fit(self.x_train, self.y_train, epochs=1, batch_size=32, verbose=0) + self.model.fit( + self.x_train, + self.y_train, + epochs=int(self.context.run_config["local-epochs"]), + batch_size=int(self.context.run_config["batch-size"]), + verbose=bool(self.context.run_config.get("verbose")), + ) return self.model.get_weights(), len(self.x_train), {} def evaluate(self, parameters, config): @@ -34,7 +40,8 @@ def client_fn(context: Context): net = load_model() partition_id = int(context.node_config["partition-id"]) - x_train, y_train, x_test, y_test = load_data(partition_id, 2) + num_partitions = int(context.node_config["num-partitions"]) + x_train, y_train, x_test, y_test = load_data(partition_id, num_partitions) # Return Client instance return FlowerClient(net, x_train, y_train, x_test, y_test).to_client() diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 92c954e754cf..7b7ea9d01200 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -29,6 +29,7 @@ clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" +local-epochs = "1" [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index 6004c076cf87..fde693e6c3de 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -26,6 +26,11 @@ clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" +local-epochs = "1" +num-layers = "2" +hidden-dim = "32" +batch-size = "256" +lr = "0.1" [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index 8a92cf0eca9a..d7991c05daf7 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -26,6 +26,7 @@ clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" +local-epochs = "1" [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index de1a445e33f9..400689cc541a 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -25,6 +25,9 @@ clientapp = "$import_name.client_app:app" [tool.flwr.app.config] num-server-rounds = "3" +local-epochs = "1" +batch-size = "32" +verbose = "" # Empty string means False [tool.flwr.federations] default = "localhost" From 445a18e14ac2a4dcb3e544f66b2b72d0f09a699a Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Thu, 18 Jul 2024 14:35:05 +0200 Subject: [PATCH 197/595] fix(framework:skip) Send correct override_dict to simulation (#3849) --- src/py/flwr/cli/run/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 5dedb701fea9..a8dd5a59a627 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -199,7 +199,7 @@ def _run_without_superexec( ] if config_overrides: - command.extend(["--run-config", f"{config_overrides}"]) + command.extend(["--run-config", f"{','.join(config_overrides)}"]) # Run the simulation subprocess.run( From 247012ed77c08071035e65dad3b394e5b6904a4e Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:39:18 +0200 Subject: [PATCH 198/595] feat(datasets) Enable passing kwargs to load_dataset in FederatedDataset (#3827) --- datasets/flwr_datasets/federated_dataset.py | 20 +++++++++-- .../flwr_datasets/federated_dataset_test.py | 34 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/datasets/flwr_datasets/federated_dataset.py b/datasets/flwr_datasets/federated_dataset.py index accfb783f368..23d98e9c7bb0 100644 --- a/datasets/flwr_datasets/federated_dataset.py +++ b/datasets/flwr_datasets/federated_dataset.py @@ -15,7 +15,7 @@ """FederatedDataset.""" -from typing import Dict, Optional, Tuple, Union +from typing import Any, Dict, Optional, Tuple, Union import datasets from datasets import Dataset, DatasetDict @@ -65,6 +65,12 @@ class FederatedDataset: Seed used for dataset shuffling. It has no effect if `shuffle` is False. The seed cannot be set in the later stages. If `None`, then fresh, unpredictable entropy will be pulled from the OS. Defaults to 42. + load_dataset_kwargs : Any + Additional keyword arguments passed to `datasets.load_dataset` function. + Currently used paramters used are dataset => path (in load_dataset), + subset => name (in load_dataset). You can pass e.g., `num_proc=4`, + `trust_remote_code=True`. Do not pass any parameters that modify the + return type such as another type than DatasetDict is returned. Examples -------- @@ -73,7 +79,7 @@ class FederatedDataset: >>> from flwr_datasets import FederatedDataset >>> >>> fds = FederatedDataset(dataset="mnist", partitioners={"train": 100}) - >>> # Load partition for client with ID 10. + >>> # Load partition for a client with ID 10. >>> partition = fds.load_partition(10) >>> # Use test split for centralized evaluation. >>> centralized = fds.load_split("test") @@ -107,6 +113,7 @@ def __init__( partitioners: Dict[str, Union[Partitioner, int]], shuffle: bool = True, seed: Optional[int] = 42, + **load_dataset_kwargs: Any, ) -> None: _check_if_dataset_tested(dataset) self._dataset_name: str = dataset @@ -127,6 +134,7 @@ def __init__( self._event = { "load_partition": {split: False for split in self._partitioners}, } + self._load_dataset_kwargs = load_dataset_kwargs def load_partition( self, @@ -289,8 +297,14 @@ def _prepare_dataset(self) -> None: happen before the resplitting. """ self._dataset = datasets.load_dataset( - path=self._dataset_name, name=self._subset + path=self._dataset_name, name=self._subset, **self._load_dataset_kwargs ) + if not isinstance(self._dataset, datasets.DatasetDict): + raise ValueError( + "Probably one of the specified parameter in `load_dataset_kwargs` " + "change the return type of the datasets.load_dataset function. " + "Make sure to use parameter such that the return type is DatasetDict." + ) if self._shuffle: # Note it shuffles all the splits. The self._dataset is DatasetDict # so e.g. {"train": train_data, "test": test_data}. All splits get shuffled. diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index f65aa6346f3a..100e9943c530 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -216,6 +216,23 @@ def resplit(dataset: DatasetDict) -> DatasetDict: dataset_length = sum([len(ds) for ds in dataset.values()]) self.assertEqual(len(full), dataset_length) + def test_use_load_dataset_kwargs(self) -> None: + """Test if the FederatedDataset works correctly with load_dataset_kwargs.""" + try: + fds = FederatedDataset( + dataset=self.dataset_name, + shuffle=False, + partitioners={"train": 10}, + num_proc=2, + ) + _ = fds.load_partition(0) + # Try to catch as broad as possible + except Exception as e: # pylint: disable=broad-except + self.fail( + f"Error when using load_dataset_kwargs: {e}. " + f"This code should not raise any exceptions." + ) + class ShufflingResplittingOnArtificialDatasetTest(unittest.TestCase): """Test shuffling and resplitting using small artificial dataset. @@ -416,6 +433,23 @@ def test_cannot_use_the_old_split_names(self) -> None: with self.assertRaises(ValueError): fds.load_partition(0, "train") + def test_use_load_dataset_kwargs(self) -> None: + """Test if the FederatedDataset raises with incorrect load_dataset_kwargs. + + The FederatedDataset should throw an error when the load_dataset_kwargs make the + return type different from a DatasetDict. + + Use split which makes the load_dataset return a Dataset. + """ + fds = FederatedDataset( + dataset="mnist", + shuffle=False, + partitioners={"train": 10}, + split="train", + ) + with self.assertRaises(ValueError): + _ = fds.load_partition(0) + def datasets_are_equal(ds1: Dataset, ds2: Dataset) -> bool: """Check if two Datasets have the same values.""" From 02b1959aa094e742fc75c282ea45053047861fba Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:50:31 +0200 Subject: [PATCH 199/595] fix(datasets:skip) Update tests for multiple partitioners (#3830) --- datasets/flwr_datasets/federated_dataset_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index 100e9943c530..bb6d46f266e9 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -144,10 +144,10 @@ def test_multiple_partitioners(self) -> None: dataset_test_partition0 = dataset_fds.load_partition(0, self.test_split) dataset = datasets.load_dataset(self.dataset_name) - self.assertEqual( - len(dataset_test_partition0), - len(dataset[self.test_split]) // num_test_partitions, - ) + expected_len = len(dataset[self.test_split]) // num_test_partitions + mod = len(dataset[self.test_split]) % num_test_partitions + expected_len += 1 if 0 < mod else 0 + self.assertEqual(len(dataset_test_partition0), expected_len) def test_no_need_for_split_keyword_if_one_partitioner(self) -> None: """Test if partitions got with and without split args are the same.""" From 4b94caae038e888e8e34c780b71ecaf256bbd21f Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:57:47 +0200 Subject: [PATCH 200/595] feat(datasets) Add tests for usps dataset (#3832) --- datasets/flwr_datasets/federated_dataset_test.py | 1 + datasets/flwr_datasets/utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index bb6d46f266e9..9faf5d93afc9 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -43,6 +43,7 @@ ("fashion_mnist", "test", ""), ("sasha/dog-food", "test", ""), ("zh-plus/tiny-imagenet", "valid", ""), + ("flwrlabs/usps", "test", ""), # Text ("scikit-learn/adult-census-income", None, ""), # Mocked diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index 0ecb96ac9456..2c58340233d9 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -34,6 +34,7 @@ "svhn", "sentiment140", "speech_commands", + "flwrlabs/usps", ] From f91583541808aec206d2f6d7ffbccf749d6b2245 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:10:22 +0200 Subject: [PATCH 201/595] feat(datasets) Add tests for `mnist-m` dataset (#3834) --- datasets/flwr_datasets/federated_dataset_test.py | 1 + datasets/flwr_datasets/utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index 9faf5d93afc9..2f6bdbd6376d 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -43,6 +43,7 @@ ("fashion_mnist", "test", ""), ("sasha/dog-food", "test", ""), ("zh-plus/tiny-imagenet", "valid", ""), + ("Mike0307/MNIST-M", "test", ""), ("flwrlabs/usps", "test", ""), # Text ("scikit-learn/adult-census-income", None, ""), diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index 2c58340233d9..00d1f10756ae 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -34,6 +34,7 @@ "svhn", "sentiment140", "speech_commands", + "Mike0307/MNIST-M", "flwrlabs/usps", ] From ddb1eec04bc3e0345a88f4799718feea96f01574 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:17:25 +0200 Subject: [PATCH 202/595] feat(datasets) Add tests for uci-mushrooms dataset (#3841) --- datasets/flwr_datasets/federated_dataset_test.py | 1 + datasets/flwr_datasets/utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index 2f6bdbd6376d..cc3fc6708c36 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -47,6 +47,7 @@ ("flwrlabs/usps", "test", ""), # Text ("scikit-learn/adult-census-income", None, ""), + ("jlh/uci-mushrooms", None, ""), # Mocked # #Image ("cifar100", "test", ""), diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index 00d1f10756ae..b49475baf4e0 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -34,6 +34,7 @@ "svhn", "sentiment140", "speech_commands", + "jlh/uci-mushrooms", "Mike0307/MNIST-M", "flwrlabs/usps", ] From f6c659ef55cdbc37def85daddff597d98ad7b1e1 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:28:15 +0200 Subject: [PATCH 203/595] feat(datasets) Add tests for femnist dataset (#3840) --- .../flwr_datasets/federated_dataset_test.py | 35 ++++++++++++++++++- datasets/flwr_datasets/utils.py | 1 + 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index cc3fc6708c36..fe1ee5f1f14e 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -28,7 +28,7 @@ from datasets import Dataset, DatasetDict, concatenate_datasets from flwr_datasets.federated_dataset import FederatedDataset from flwr_datasets.mock_utils_test import _load_mocked_dataset -from flwr_datasets.partitioner import IidPartitioner, Partitioner +from flwr_datasets.partitioner import IidPartitioner, NaturalIdPartitioner, Partitioner mocked_datasets = ["cifar100", "svhn", "sentiment140", "speech_commands"] @@ -400,6 +400,39 @@ def test_mixed_type_partitioners_creates_from_int(self) -> None: ) +natural_id_datasets = [ + "flwrlabs/femnist", +] + + +@parameterized_class( + ("dataset_name", "test_split", "subset", "partition_by"), + [ + ("flwrlabs/femnist", "", "", "writer_id"), + ], +) +class NaturalIdPartitionerIntegrationTest(unittest.TestCase): + """General FederatedDataset tests with NaturalIdPartitioner.""" + + dataset_name = "" + test_split = "" + subset = "" + partition_by = "" + + def test_if_the_partitions_have_unique_values(self) -> None: + """Test if each partition has a single unique id value.""" + fds = FederatedDataset( + dataset=self.dataset_name, + partitioners={ + "train": NaturalIdPartitioner(partition_by=self.partition_by) + }, + ) + for partition_id in range(fds.partitioners["train"].num_partitions): + partition = fds.load_partition(partition_id) + unique_ids_in_partition = list(set(partition[self.partition_by])) + self.assertEqual(len(unique_ids_in_partition), 1) + + class IncorrectUsageFederatedDatasets(unittest.TestCase): """Test incorrect usages in FederatedDatasets.""" diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index b49475baf4e0..f4305f5dba34 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -34,6 +34,7 @@ "svhn", "sentiment140", "speech_commands", + "flwrlabs/femnist", "jlh/uci-mushrooms", "Mike0307/MNIST-M", "flwrlabs/usps", From 3ead85046b5d9672f4722c91246c56b5d8a70a5f Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:41:21 +0200 Subject: [PATCH 204/595] feat(datasets) Add ted-lium dataset to the tested set (#3844) --- datasets/flwr_datasets/utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index f4305f5dba34..fa3f7a54f198 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -34,6 +34,7 @@ "svhn", "sentiment140", "speech_commands", + "LIUM/tedlium", # Feature wise it's just like speech_commands "flwrlabs/femnist", "jlh/uci-mushrooms", "Mike0307/MNIST-M", From 49aa57086ca032d9545314624918c653ff096635 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 21 Jul 2024 18:40:34 +0200 Subject: [PATCH 205/595] fix(framework) Keep `BackendConfig` passed to `run_simulation` (#3861) --- src/py/flwr/simulation/run_simulation.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index e5d82207e08b..1b7c6f87591e 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -480,8 +480,12 @@ def _run_simulation( if verbose_logging: update_console_handler(level=DEBUG, timestamps=True, colored=True) else: - backend_config["init_args"]["logging_level"] = WARNING - backend_config["init_args"]["log_to_driver"] = True + backend_config["init_args"]["logging_level"] = backend_config["init_args"].get( + "logging_level", WARNING + ) + backend_config["init_args"]["log_to_driver"] = backend_config["init_args"].get( + "log_to_driver", True + ) if enable_tf_gpu_growth: # Check that Backend config has also enabled using GPU growth From 3f3d81dd0f7c75b4245c7ffc37569da5ac344440 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 21 Jul 2024 19:14:36 +0200 Subject: [PATCH 206/595] feat(framework) Prompt user for confirmation before flwr new override (#3859) --- dev/changelog_config.toml | 1 + src/py/flwr/cli/new/new.py | 40 ++++++++++++++++++++------------- src/py/flwr/cli/new/new_test.py | 23 ++++++++++--------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/dev/changelog_config.toml b/dev/changelog_config.toml index 637ea9b4b2c6..05527e2b2cb3 100644 --- a/dev/changelog_config.toml +++ b/dev/changelog_config.toml @@ -624,6 +624,7 @@ allowed_verbs=[ "Prolong", "Promise", "Promote", + "Prompt", "Propagate", "Propose", "Prosecute", diff --git a/src/py/flwr/cli/new/new.py b/src/py/flwr/cli/new/new.py index 4bde009742f8..306f20efccfa 100644 --- a/src/py/flwr/cli/new/new.py +++ b/src/py/flwr/cli/new/new.py @@ -14,9 +14,9 @@ # ============================================================================== """Flower command line interface `new` command.""" -import os import re from enum import Enum +from pathlib import Path from string import Template from typing import Dict, Optional @@ -59,10 +59,10 @@ class TemplateNotFound(Exception): def load_template(name: str) -> str: """Load template from template directory and return as text.""" - tpl_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "templates")) - tpl_file_path = os.path.join(tpl_dir, name) + tpl_dir = (Path(__file__).parent / "templates").absolute() + tpl_file_path = tpl_dir / name - if not os.path.isfile(tpl_file_path): + if not tpl_file_path.is_file(): raise TemplateNotFound(f"Template '{name}' not found") with open(tpl_file_path, encoding="utf-8") as tpl_file: @@ -78,14 +78,13 @@ def render_template(template: str, data: Dict[str, str]) -> str: return tpl.template -def create_file(file_path: str, content: str) -> None: +def create_file(file_path: Path, content: str) -> None: """Create file including all nessecary directories and write content into file.""" - os.makedirs(os.path.dirname(file_path), exist_ok=True) - with open(file_path, "w", encoding="utf-8") as f: - f.write(content) + file_path.parent.mkdir(exist_ok=True) + file_path.write_text(content) -def render_and_create(file_path: str, template: str, context: Dict[str, str]) -> None: +def render_and_create(file_path: Path, template: str, context: Dict[str, str]) -> None: """Render template and write to file.""" content = render_template(template, context) create_file(file_path, content) @@ -117,6 +116,21 @@ def new( default=sanitize_project_name(project_name), ) + # Set project directory path + package_name = re.sub(r"[-_.]+", "-", project_name).lower() + import_name = package_name.replace("-", "_") + project_dir = Path.cwd() / package_name + + if project_dir.exists(): + if not typer.confirm( + typer.style( + f"\n💬 {project_name} already exists, do you want to override it?", + fg=typer.colors.MAGENTA, + bold=True, + ) + ): + return + if username is None: username = prompt_text("Please provide your Flower username") @@ -158,12 +172,6 @@ def new( ) ) - # Set project directory path - cwd = os.getcwd() - package_name = re.sub(r"[-_.]+", "-", project_name).lower() - import_name = package_name.replace("-", "_") - project_dir = os.path.join(cwd, package_name) - context = { "project_name": project_name, "package_name": package_name, @@ -252,7 +260,7 @@ def new( for file_path, value in files.items(): render_and_create( - file_path=os.path.join(project_dir, file_path), + file_path=project_dir / file_path, template=value["template"], context=context, ) diff --git a/src/py/flwr/cli/new/new_test.py b/src/py/flwr/cli/new/new_test.py index 7f22bd5f9825..dea10ab35013 100644 --- a/src/py/flwr/cli/new/new_test.py +++ b/src/py/flwr/cli/new/new_test.py @@ -15,6 +15,7 @@ """Test for Flower command line interface `new` command.""" import os +from pathlib import Path import pytest @@ -54,7 +55,7 @@ def test_render_template() -> None: def test_create_file(tmp_path: str) -> None: """Test if file with content is created.""" # Prepare - file_path = os.path.join(tmp_path, "test.txt") + file_path = Path(tmp_path) / "test.txt" content = "Foobar" # Execute @@ -92,7 +93,7 @@ def test_new_correct_name(tmp_path: str) -> None: } # Current directory - origin = os.getcwd() + origin = Path.cwd() try: # Change into the temprorary directory @@ -101,13 +102,15 @@ def test_new_correct_name(tmp_path: str) -> None: new(project_name=project_name, framework=framework, username=username) # Assert - file_list = os.listdir(os.path.join(tmp_path, expected_top_level_dir)) - assert set(file_list) == expected_files_top_level - - file_list = os.listdir( - os.path.join(tmp_path, expected_top_level_dir, expected_module_dir) - ) - assert set(file_list) == expected_files_module + file_list = (Path(tmp_path) / expected_top_level_dir).iterdir() + assert { + file_path.name for file_path in file_list + } == expected_files_top_level + + file_list = ( + Path(tmp_path) / expected_top_level_dir / expected_module_dir + ).iterdir() + assert {file_path.name for file_path in file_list} == expected_files_module finally: os.chdir(origin) @@ -119,7 +122,7 @@ def test_new_incorrect_name(tmp_path: str) -> None: for project_name in ["My_Flower_App", "My.Flower App"]: # Current directory - origin = os.getcwd() + origin = Path.cwd() try: # Change into the temprorary directory From 249bf41c7a547e164e86176e9955fc23aafdde66 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Sun, 21 Jul 2024 18:44:27 +0100 Subject: [PATCH 207/595] fix(framework:skip) Exclude incompatible `grpcio` version (#3853) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7fe1ef7843d9..854b88847197 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,7 +64,7 @@ flower-simulation = "flwr.simulation.run_simulation:run_simulation_from_cli" python = "^3.8" # Mandatory dependencies numpy = "^1.21.0" -grpcio = "^1.60.0,!=1.64.2,!=1.65.0" +grpcio = "^1.60.0,!=1.64.2,!=1.65.1" protobuf = "^4.25.2" cryptography = "^42.0.4" pycryptodome = "^3.18.0" From da766cf3adc77be479651d56b5b0fa6121620950 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Sun, 21 Jul 2024 22:12:04 +0200 Subject: [PATCH 208/595] fix(framework:skip) Fix flwr new templates config (#3863) --- src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl | 8 ++++---- src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index 7b7ea9d01200..a15455a3b225 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -11,10 +11,10 @@ dependencies = [ "flwr[simulation]>=1.9.0,<2.0", "flwr-datasets>=0.0.2,<1.0.0", "torch==2.2.1", - "transformers>=4.30.0,<5.0" - "evaluate>=0.4.0,<1.0" - "datasets>=2.0.0, <3.0" - "scikit-learn>=1.3.1, <2.0" + "transformers>=4.30.0,<5.0", + "evaluate>=0.4.0,<1.0", + "datasets>=2.0.0, <3.0", + "scikit-learn>=1.3.1, <2.0", ] [tool.hatch.build.targets.wheel] diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index e899f48f4c5c..c0cce3e19d06 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -9,9 +9,9 @@ description = "" license = "Apache-2.0" dependencies = [ "flwr[simulation]>=1.9.0,<2.0", - "jax==0.4.26", - "jaxlib==0.4.26", - "scikit-learn==1.4.2", + "jax==0.4.13", + "jaxlib==0.4.13", + "scikit-learn==1.3.2", ] [tool.hatch.build.targets.wheel] From 9eafbddd0042937c214750161b8f8cb050a90ded Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 22 Jul 2024 10:30:24 +0200 Subject: [PATCH 209/595] ci(datasets:skip) Extend datasets GitHub workflow to trigger test if it changes (#3866) --- .github/workflows/datasets.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/datasets.yml b/.github/workflows/datasets.yml index 80cece262754..0a12059dd0ce 100644 --- a/.github/workflows/datasets.yml +++ b/.github/workflows/datasets.yml @@ -6,11 +6,15 @@ on: - main paths: - "datasets/**" + - ".github/workflows/datasets.yml" + - ".github/workflows/datasets-e2e.yml" pull_request: branches: - main paths: - "datasets/**" + - ".github/workflows/datasets.yml" + - ".github/workflows/datasets-e2e.yml" concurrency: group: ${{ github.workflow }}-${{ github.ref == 'refs/heads/main' && github.run_id || github.event.pull_request.number || github.ref }} From 7d7b6981a62ddce21dfba3808deaee28b2da3287 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 22 Jul 2024 11:52:28 +0200 Subject: [PATCH 210/595] ci(framework:skip) Add E2E tests for `flwr new` (#3848) --- .github/workflows/e2e.yml | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 11c0073fcf1f..241ec8057c7c 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -105,7 +105,7 @@ jobs: Path('data').mkdir(exist_ok=True) load_iris(as_frame=True)['data'].to_csv('./data/client.csv') - name: Framework / ${{matrix.directory}} + name: Framework / ${{ matrix.directory }} defaults: run: @@ -206,3 +206,40 @@ jobs: - name: Test strategies run: | python test.py "${{ matrix.strat }}" + + templates: + runs-on: ubuntu-22.04 + timeout-minutes: 10 + needs: wheel + strategy: + matrix: + framework: ["numpy", "pytorch", "tensorflow", "hf", "jax", "sklearn"] + + name: Template / ${{ matrix.framework }} + + steps: + - uses: actions/checkout@v4 + - name: Bootstrap + uses: ./.github/actions/bootstrap + - name: Install Flower from repo + if: ${{ github.repository != 'adap/flower' || github.event.pull_request.head.repo.fork || github.actor == 'dependabot[bot]' }} + run: | + python -m pip install . + - name: Install Flower wheel from artifact store + if: ${{ github.repository == 'adap/flower' && !github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]' }} + run: | + python -m pip install https://${{ env.ARTIFACT_BUCKET }}/py/${{ needs.wheel.outputs.dir }}/${{ needs.wheel.outputs.short_sha }}/${{ needs.wheel.outputs.whl_path }} + - name: Create project and install it + run: | + flwr new tmp-${{ matrix.framework }} --framework ${{ matrix.framework }} --username gh_ci + cd tmp-${{ matrix.framework }} + pip install . + - name: Cache Datasets + uses: actions/cache@v4 + with: + path: "~/.cache/huggingface/datasets" + key: ${{ matrix.framework }}-template-datasets + - name: Run project + run: | + cd tmp-${{ matrix.framework }} + flwr run --run-config num-server-rounds=1 From de33f9bf3d553c9d29753e716f415116dc6b350d Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 22 Jul 2024 12:07:02 +0200 Subject: [PATCH 211/595] ci(datasets:skip) Update pylint version (#3867) --- datasets/flwr_datasets/federated_dataset.py | 2 +- datasets/flwr_datasets/federated_dataset_test.py | 14 ++++++-------- datasets/flwr_datasets/metrics/utils_test.py | 1 - .../partitioner/natural_id_partitioner.py | 1 - .../partitioner/natural_id_partitioner_test.py | 2 +- datasets/flwr_datasets/partitioner/partitioner.py | 2 +- datasets/flwr_datasets/preprocessor/merger_test.py | 2 +- datasets/flwr_datasets/utils.py | 2 +- datasets/flwr_datasets/visualization/bar_plot.py | 2 +- datasets/pyproject.toml | 4 ++-- 10 files changed, 14 insertions(+), 18 deletions(-) diff --git a/datasets/flwr_datasets/federated_dataset.py b/datasets/flwr_datasets/federated_dataset.py index 23d98e9c7bb0..7514516e09e7 100644 --- a/datasets/flwr_datasets/federated_dataset.py +++ b/datasets/flwr_datasets/federated_dataset.py @@ -103,7 +103,7 @@ class FederatedDataset: >>> ) """ - # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-instance-attributes, too-many-arguments def __init__( self, *, diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index fe1ee5f1f14e..38f032a4ccfe 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -169,7 +169,7 @@ def test_resplit_dataset_into_one(self) -> None: if self.test_split is None: return dataset = datasets.load_dataset(self.dataset_name) - dataset_length = sum([len(ds) for ds in dataset.values()]) + dataset_length = sum(len(ds) for ds in dataset.values()) fds = FederatedDataset( dataset=self.dataset_name, partitioners={"train": 100}, @@ -216,7 +216,7 @@ def resplit(dataset: DatasetDict) -> DatasetDict: ) full = fds.load_split("full") dataset = datasets.load_dataset(self.dataset_name) - dataset_length = sum([len(ds) for ds in dataset.values()]) + dataset_length = sum(len(ds) for ds in dataset.values()) self.assertEqual(len(full), dataset_length) def test_use_load_dataset_kwargs(self) -> None: @@ -245,7 +245,6 @@ class ShufflingResplittingOnArtificialDatasetTest(unittest.TestCase): The load_dataset method is mocked and the artificial dataset is returned. """ - # pylint: disable=no-self-use def _dummy_setup(self, train_rows: int = 10, test_rows: int = 5) -> DatasetDict: """Create a dummy DatasetDict with train, test splits.""" data_train = { @@ -436,14 +435,14 @@ def test_if_the_partitions_have_unique_values(self) -> None: class IncorrectUsageFederatedDatasets(unittest.TestCase): """Test incorrect usages in FederatedDatasets.""" - def test_no_partitioner_for_split(self) -> None: # pylint: disable=R0201 + def test_no_partitioner_for_split(self) -> None: """Test using load_partition with missing partitioner.""" dataset_fds = FederatedDataset(dataset="mnist", partitioners={"train": 100}) with pytest.raises(ValueError): dataset_fds.load_partition(0, "test") - def test_no_split_in_the_dataset(self) -> None: # pylint: disable=R0201 + def test_no_split_in_the_dataset(self) -> None: """Test using load_partition with non-existent split name.""" dataset_fds = FederatedDataset( dataset="mnist", partitioners={"non-existent-split": 100} @@ -452,15 +451,14 @@ def test_no_split_in_the_dataset(self) -> None: # pylint: disable=R0201 with pytest.raises(ValueError): dataset_fds.load_partition(0, "non-existent-split") - def test_unsupported_dataset(self) -> None: # pylint: disable=R0201 + def test_unsupported_dataset(self) -> None: """Test creating FederatedDataset for unsupported dataset.""" with pytest.warns(UserWarning): FederatedDataset(dataset="food101", partitioners={"train": 100}) def test_cannot_use_the_old_split_names(self) -> None: """Test if the initial split names can not be used.""" - dataset = datasets.load_dataset("mnist") - sum([len(ds) for ds in dataset.values()]) + datasets.load_dataset("mnist") fds = FederatedDataset( dataset="mnist", partitioners={"train": 100}, diff --git a/datasets/flwr_datasets/metrics/utils_test.py b/datasets/flwr_datasets/metrics/utils_test.py index 9e0f3acdf805..9fab9994c430 100644 --- a/datasets/flwr_datasets/metrics/utils_test.py +++ b/datasets/flwr_datasets/metrics/utils_test.py @@ -13,7 +13,6 @@ # limitations under the License. # ============================================================================== """Tests for metrics utils.""" -# pylint: disable=no-self-use import unittest diff --git a/datasets/flwr_datasets/partitioner/natural_id_partitioner.py b/datasets/flwr_datasets/partitioner/natural_id_partitioner.py index 85f1b3af43c2..f71d7004b42b 100644 --- a/datasets/flwr_datasets/partitioner/natural_id_partitioner.py +++ b/datasets/flwr_datasets/partitioner/natural_id_partitioner.py @@ -143,7 +143,6 @@ def partition_id_to_natural_id(self) -> Dict[int, str]: """ return self._partition_id_to_natural_id - # pylint: disable=R0201 @partition_id_to_natural_id.setter def partition_id_to_natural_id(self, value: Dict[int, str]) -> None: raise AttributeError( diff --git a/datasets/flwr_datasets/partitioner/natural_id_partitioner_test.py b/datasets/flwr_datasets/partitioner/natural_id_partitioner_test.py index f447634ad9ed..b74a044967ef 100644 --- a/datasets/flwr_datasets/partitioner/natural_id_partitioner_test.py +++ b/datasets/flwr_datasets/partitioner/natural_id_partitioner_test.py @@ -86,7 +86,7 @@ def test_load_partition_max_partition_size( print(num_unique_natural_ids) _, partitioner = _dummy_setup(num_rows, num_unique_natural_ids) max_size = max( - [len(partitioner.load_partition(i)) for i in range(num_unique_natural_ids)] + len(partitioner.load_partition(i)) for i in range(num_unique_natural_ids) ) self.assertEqual(max_size, math.ceil(num_rows / num_unique_natural_ids)) diff --git a/datasets/flwr_datasets/partitioner/partitioner.py b/datasets/flwr_datasets/partitioner/partitioner.py index 10ade52640e8..24ca22bbebcb 100644 --- a/datasets/flwr_datasets/partitioner/partitioner.py +++ b/datasets/flwr_datasets/partitioner/partitioner.py @@ -44,7 +44,7 @@ def dataset(self) -> Dataset: @dataset.setter def dataset(self, value: Dataset) -> None: if self._dataset is not None: - raise Exception( + raise ValueError( "The dataset should be assigned only once to the partitioner." "This operation might also wipe out the saved references to the " "created partitions (in case the partitioning scheme needs to create " diff --git a/datasets/flwr_datasets/preprocessor/merger_test.py b/datasets/flwr_datasets/preprocessor/merger_test.py index d5c69387e53d..137b0dd1a660 100644 --- a/datasets/flwr_datasets/preprocessor/merger_test.py +++ b/datasets/flwr_datasets/preprocessor/merger_test.py @@ -107,7 +107,7 @@ def test_nonexistent_split_in_strategy(self) -> None: ): merger(self.dataset_dict) - def test_duplicate_merge_split_name(self) -> None: # pylint: disable=R0201 + def test_duplicate_merge_split_name(self) -> None: """Test that the new split names are not the same.""" strategy: Dict[str, Tuple[str, ...]] = { "new_train": ("train", "valid"), diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index fa3f7a54f198..578270c3735a 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -183,7 +183,7 @@ def _create_division_indices_ranges( ranges.append(range(start_idx, end_idx)) start_idx = end_idx else: - TypeError( + raise TypeError( f"The type of the `division` should be dict, " f"tuple or list but is {type(division)} instead. " ) diff --git a/datasets/flwr_datasets/visualization/bar_plot.py b/datasets/flwr_datasets/visualization/bar_plot.py index 339ff0967906..352c99a572f5 100644 --- a/datasets/flwr_datasets/visualization/bar_plot.py +++ b/datasets/flwr_datasets/visualization/bar_plot.py @@ -100,7 +100,7 @@ def _plot_bar( legend_kwargs["loc"] = "outside center right" if "bbox_to_anchor" not in legend_kwargs: - max_len_label_str = max([len(str(column)) for column in dataframe.columns]) + max_len_label_str = max(len(str(column)) for column in dataframe.columns) shift = min(0.05 + max_len_label_str / 100, 0.15) legend_kwargs["bbox_to_anchor"] = (1.0 + shift, 0.5) diff --git a/datasets/pyproject.toml b/datasets/pyproject.toml index e3afd8b87075..0a3b14990699 100644 --- a/datasets/pyproject.toml +++ b/datasets/pyproject.toml @@ -68,7 +68,7 @@ isort = "==5.13.2" black = { version = "==24.2.0", extras = ["jupyter"] } docformatter = "==1.7.5" mypy = "==1.4.0" -pylint = "==2.13.9" +pylint = "==3.0.3" flake8 = "==3.9.2" parameterized = "==0.9.0" pytest = "==7.1.2" @@ -95,7 +95,7 @@ line-length = 88 target-version = ["py38", "py39", "py310", "py311"] [tool.pylint."MESSAGES CONTROL"] -disable = "bad-continuation,duplicate-code,too-few-public-methods,useless-import-alias" +disable = "duplicate-code,too-few-public-methods,useless-import-alias" [tool.pytest.ini_options] minversion = "6.2" From dce187739ea0d8cceabdf9f4e596000ff40d6900 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Mon, 22 Jul 2024 12:12:51 +0200 Subject: [PATCH 212/595] docs(*:skip) Add sphinx substitution extension (#3856) Signed-off-by: Robert Steiner --- doc/source/conf.py | 1 + pyproject.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/source/conf.py b/doc/source/conf.py index 1452f9c9a7b0..d6c082ec110b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -109,6 +109,7 @@ "sphinx_reredirects", "nbsphinx", "sphinx_click", + "sphinx_substitution_extensions", ] # Generate .rst files diff --git a/pyproject.toml b/pyproject.toml index 854b88847197..68d493e39e66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -130,6 +130,7 @@ GitPython = "==3.1.32" PyGithub = "==2.1.1" licensecheck = "==2024" pre-commit = "==3.5.0" +sphinx-substitution-extensions = "2022.02.16" [tool.isort] profile = "black" From fbb70c7b98bfa7058d2b61909426a8e3249be74e Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Mon, 22 Jul 2024 12:23:29 +0200 Subject: [PATCH 213/595] ci(datasets) Add Python 3.11 to Flower Datasets CI (#3865) --- .github/workflows/datasets.yml | 6 +++--- .github/workflows/framework.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/datasets.yml b/.github/workflows/datasets.yml index 0a12059dd0ce..ca5aa29248cf 100644 --- a/.github/workflows/datasets.yml +++ b/.github/workflows/datasets.yml @@ -35,9 +35,9 @@ jobs: # Latest version which comes cached in the host image can be found here: # https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md#python # In case of a mismatch, the job has to download Python to install it. - # Note: Due to a bug in actions/setup-python we have to put 3.10 in - # qoutes as it will otherwise will assume 3.1 - python: [3.8, 3.9, '3.10'] + # Note: Due to a bug in actions/setup-python, we have to put "3.10" in + # quotes as it will otherwise assume "3.1" + python: [3.8, 3.9, '3.10', '3.11'] name: Python ${{ matrix.python }} diff --git a/.github/workflows/framework.yml b/.github/workflows/framework.yml index feb08229be06..a5d2b71f7beb 100644 --- a/.github/workflows/framework.yml +++ b/.github/workflows/framework.yml @@ -23,8 +23,8 @@ jobs: # Latest version which comes cached in the host image can be found here: # https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2204-Readme.md#python # In case of a mismatch, the job has to download Python to install it. - # Note: Due to a bug in actions/setup-python we have to put 3.10 in - # qoutes as it will otherwise will assume 3.1 + # Note: Due to a bug in actions/setup-python, we have to put "3.10" in + # quotes as it will otherwise assume "3.1" python: [3.8, 3.9, '3.10', '3.11'] name: Python ${{ matrix.python }} From df1084fe9de33872c64feeec9da493491eb93d69 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Mon, 22 Jul 2024 14:13:07 +0100 Subject: [PATCH 214/595] feat(framework:skip) Update the `LegacyContext` to align with `Context` (#3858) Co-authored-by: Daniel J. Beutel --- e2e/bare-client-auth/server.py | 2 +- e2e/bare-https/server.py | 2 +- e2e/framework-pandas/server.py | 2 +- e2e/server.py | 2 +- examples/app-pytorch/server_workflow.py | 2 +- examples/app-secure-aggregation/server.py | 2 +- examples/fl-dp-sa/fl_dp_sa/server.py | 2 +- src/py/flwr/server/compat/legacy_context.py | 9 +++++---- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/e2e/bare-client-auth/server.py b/e2e/bare-client-auth/server.py index e10d5ebc5760..035f6e3ecab2 100644 --- a/e2e/bare-client-auth/server.py +++ b/e2e/bare-client-auth/server.py @@ -9,7 +9,7 @@ def main(driver, context): # Construct the LegacyContext context = fl.server.LegacyContext( - state=context.state, + context=context, config=fl.server.ServerConfig(num_rounds=3), ) diff --git a/e2e/bare-https/server.py b/e2e/bare-https/server.py index e10d5ebc5760..035f6e3ecab2 100644 --- a/e2e/bare-https/server.py +++ b/e2e/bare-https/server.py @@ -9,7 +9,7 @@ def main(driver, context): # Construct the LegacyContext context = fl.server.LegacyContext( - state=context.state, + context=context, config=fl.server.ServerConfig(num_rounds=3), ) diff --git a/e2e/framework-pandas/server.py b/e2e/framework-pandas/server.py index ef0e92a11ea2..815cc9cee352 100644 --- a/e2e/framework-pandas/server.py +++ b/e2e/framework-pandas/server.py @@ -9,7 +9,7 @@ def main(driver, context): # Construct the LegacyContext context = fl.server.LegacyContext( - state=context.state, + context=context, config=fl.server.ServerConfig(num_rounds=3), strategy=FedAnalytics(), ) diff --git a/e2e/server.py b/e2e/server.py index c678cd0a2446..cb4f65eed0da 100644 --- a/e2e/server.py +++ b/e2e/server.py @@ -38,7 +38,7 @@ def record_state_metrics(metrics): def main(driver, context): # Construct the LegacyContext context = fl.server.LegacyContext( - state=context.state, + context=context, config=fl.server.ServerConfig(num_rounds=3), ) diff --git a/examples/app-pytorch/server_workflow.py b/examples/app-pytorch/server_workflow.py index 6923010ecf7b..270e1ef2c7cd 100644 --- a/examples/app-pytorch/server_workflow.py +++ b/examples/app-pytorch/server_workflow.py @@ -51,7 +51,7 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: def main(driver: Driver, context: Context) -> None: # Construct the LegacyContext context = LegacyContext( - state=context.state, + context=context, config=fl.server.ServerConfig(num_rounds=3), strategy=strategy, ) diff --git a/examples/app-secure-aggregation/server.py b/examples/app-secure-aggregation/server.py index e9737a5a3c7f..ebd70045fdc5 100644 --- a/examples/app-secure-aggregation/server.py +++ b/examples/app-secure-aggregation/server.py @@ -22,7 +22,7 @@ def main(driver: Driver, context: Context) -> None: # Construct the LegacyContext context = LegacyContext( - state=context.state, + context=context, config=ServerConfig(num_rounds=3), strategy=strategy, ) diff --git a/examples/fl-dp-sa/fl_dp_sa/server.py b/examples/fl-dp-sa/fl_dp_sa/server.py index f7da75997e98..76ff0a9491ff 100644 --- a/examples/fl-dp-sa/fl_dp_sa/server.py +++ b/examples/fl-dp-sa/fl_dp_sa/server.py @@ -60,7 +60,7 @@ def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: def main(driver: Driver, context: Context) -> None: # Construct the LegacyContext context = LegacyContext( - state=context.state, + context=context, config=ServerConfig(num_rounds=3), strategy=strategy, ) diff --git a/src/py/flwr/server/compat/legacy_context.py b/src/py/flwr/server/compat/legacy_context.py index ee09d79012dc..589342556b17 100644 --- a/src/py/flwr/server/compat/legacy_context.py +++ b/src/py/flwr/server/compat/legacy_context.py @@ -18,7 +18,7 @@ from dataclasses import dataclass from typing import Optional -from flwr.common import Context, RecordSet +from flwr.common import Context from ..client_manager import ClientManager, SimpleClientManager from ..history import History @@ -35,9 +35,9 @@ class LegacyContext(Context): client_manager: ClientManager history: History - def __init__( + def __init__( # pylint: disable=too-many-arguments self, - state: RecordSet, + context: Context, config: Optional[ServerConfig] = None, strategy: Optional[Strategy] = None, client_manager: Optional[ClientManager] = None, @@ -52,4 +52,5 @@ def __init__( self.strategy = strategy self.client_manager = client_manager self.history = History() - super().__init__(node_id=0, node_config={}, state=state, run_config={}) + + super().__init__(**vars(context)) From b7ae03dcb1bbfc8eed5bd29d98bc546608e418cd Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 22 Jul 2024 15:44:15 +0200 Subject: [PATCH 215/595] feat(datasets) Add function to perform partial download of dataset for tests (#3860) --- datasets/flwr_datasets/mock_utils_test.py | 61 ++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/datasets/flwr_datasets/mock_utils_test.py b/datasets/flwr_datasets/mock_utils_test.py index bd49de8033de..7ee3bae890ff 100644 --- a/datasets/flwr_datasets/mock_utils_test.py +++ b/datasets/flwr_datasets/mock_utils_test.py @@ -19,7 +19,7 @@ import random import string from datetime import datetime, timedelta -from typing import Any, Dict, List, Set, Tuple, Union +from typing import Any, Dict, List, Optional, Set, Tuple, Union import numpy as np from PIL import Image @@ -375,3 +375,62 @@ def _load_mocked_dataset( for params in zip(num_rows, split_names): dataset_dict[params[1]] = dataset_creation_fnc(params[0]) return datasets.DatasetDict(dataset_dict) + + +def _load_mocked_dataset_by_partial_download( + dataset_name: str, + split_name: str, + skip_take_list: List[Tuple[int, int]], + subset_name: Optional[str] = None, +) -> Dataset: + """Download a partial dataset. + + This functionality is not supported in the datasets library. This is an informal + way of achieving partial dataset download by using the `streaming=True` and creating + a dataset.Dataset from in-memory objects. + + Parameters + ---------- + dataset_name: str + Name of the dataset (passed to load_dataset). + split_name: str + Name of the split (passed to load_dataset) e.g. "train". + skip_take_list: List[Tuple[int, int]] + The streaming mode has a specific type of accessing the data, the first tuple + value is how many samples to skip, the second is how many samples to take. Due + to this mechanism, diverse samples can be taken (especially if the dataset is + sorted by the natual_id for NaturalIdPartitioner). + subset_name: Optional[str] + Name of the subset (passed to load_dataset) e.g. "v0.01" for speech_commands. + + Returns + ------- + dataset: Dataset + The dataset with the requested samples. + """ + dataset = datasets.load_dataset( + dataset_name, name=subset_name, split=split_name, streaming=True + ) + dataset_list = [] + # It's a list of dict such that each dict represent a single sample of the dataset + # The sample is exactly the same as if the full dataset was downloaded and indexed + for skip, take in skip_take_list: + # dataset.skip(n).take(m) in streaming mode is equivalent (in terms of return) + # to the fully downloaded dataset index: dataset[n+1: (n+1 + m)] + dataset_list.extend(list(dataset.skip(skip).take(take))) + return Dataset.from_list(dataset_list) + + +def _load_mocked_dataset_dict_by_partial_download( + dataset_name: str, + split_names: List[str], + skip_take_lists: List[List[Tuple[int, int]]], + subset_name: Optional[str] = None, +) -> DatasetDict: + """Like _load_mocked_dataset_by_partial_download but for many splits.""" + dataset_dict = {} + for split_name, skip_take_list in zip(split_names, skip_take_lists): + dataset_dict[split_name] = _load_mocked_dataset_by_partial_download( + dataset_name, split_name, skip_take_list, subset_name + ) + return DatasetDict(dataset_dict) From 9f685be1c231852723dbbea2bf2a2966ff87a878 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:29:51 +0200 Subject: [PATCH 216/595] feat(datasets) Add tests for ucf101 dataset (#3842) Co-authored-by: jafermarq --- .../flwr_datasets/federated_dataset_test.py | 46 ++++++++++++++++++- datasets/flwr_datasets/utils.py | 1 + 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index 38f032a4ccfe..278cf4c9f5ad 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -27,7 +27,10 @@ import datasets from datasets import Dataset, DatasetDict, concatenate_datasets from flwr_datasets.federated_dataset import FederatedDataset -from flwr_datasets.mock_utils_test import _load_mocked_dataset +from flwr_datasets.mock_utils_test import ( + _load_mocked_dataset, + _load_mocked_dataset_dict_by_partial_download, +) from flwr_datasets.partitioner import IidPartitioner, NaturalIdPartitioner, Partitioner mocked_datasets = ["cifar100", "svhn", "sentiment140", "speech_commands"] @@ -403,11 +406,14 @@ def test_mixed_type_partitioners_creates_from_int(self) -> None: "flwrlabs/femnist", ] +mocked_natural_id_datasets = ["flwrlabs/ucf101"] + @parameterized_class( ("dataset_name", "test_split", "subset", "partition_by"), [ ("flwrlabs/femnist", "", "", "writer_id"), + ("flwrlabs/ucf101", "test", None, "video_id"), ], ) class NaturalIdPartitionerIntegrationTest(unittest.TestCase): @@ -418,6 +424,28 @@ class NaturalIdPartitionerIntegrationTest(unittest.TestCase): subset = "" partition_by = "" + def setUp(self) -> None: + """Mock the dataset download prior to each method if needed. + + If the `dataset_name` is in the `mocked_datasets` list, then the dataset + download is mocked. + """ + if self.dataset_name in mocked_natural_id_datasets: + mock_return_value = _load_mocked_dataset_dict_by_partial_download( + dataset_name=self.dataset_name, + split_names=["train"], + skip_take_lists=[[(0, 30), (1000, 30), (2000, 40)]], + subset_name=self.subset, + ) + self.patcher = patch("datasets.load_dataset") + self.mock_load_dataset = self.patcher.start() + self.mock_load_dataset.return_value = mock_return_value + + def tearDown(self) -> None: + """Clean up after the dataset mocking.""" + if self.dataset_name in mocked_natural_id_datasets: + patch.stopall() + def test_if_the_partitions_have_unique_values(self) -> None: """Test if each partition has a single unique id value.""" fds = FederatedDataset( @@ -431,6 +459,22 @@ def test_if_the_partitions_have_unique_values(self) -> None: unique_ids_in_partition = list(set(partition[self.partition_by])) self.assertEqual(len(unique_ids_in_partition), 1) + def tests_if_the_columns_are_unchanged(self) -> None: + """Test if the columns are unchanged after partitioning.""" + fds = FederatedDataset( + dataset=self.dataset_name, + partitioners={ + "train": NaturalIdPartitioner(partition_by=self.partition_by) + }, + ) + dataset = fds.load_split("train") + columns_in_dataset = set(dataset.column_names) + + for partition_id in range(fds.partitioners["train"].num_partitions): + partition = fds.load_partition(partition_id) + columns_in_partition = set(partition.column_names) + self.assertEqual(columns_in_partition, columns_in_dataset) + class IncorrectUsageFederatedDatasets(unittest.TestCase): """Test incorrect usages in FederatedDatasets.""" diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index 578270c3735a..955ea041c2a6 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -36,6 +36,7 @@ "speech_commands", "LIUM/tedlium", # Feature wise it's just like speech_commands "flwrlabs/femnist", + "flwrlabs/ucf101", "jlh/uci-mushrooms", "Mike0307/MNIST-M", "flwrlabs/usps", From df3f8762a65c6728c086fc7bcf5f3dc3a038a6f2 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Mon, 22 Jul 2024 16:22:28 +0100 Subject: [PATCH 217/595] fix(framework:skip) Check if the message has content when iterating over all messages in `SecAggPlus` Workflow (#3870) --- .../secure_aggregation/secaggplus_workflow.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/server/workflow/secure_aggregation/secaggplus_workflow.py b/src/py/flwr/server/workflow/secure_aggregation/secaggplus_workflow.py index d6d97c28f313..58f26c85d7eb 100644 --- a/src/py/flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +++ b/src/py/flwr/server/workflow/secure_aggregation/secaggplus_workflow.py @@ -81,6 +81,7 @@ class WorkflowState: # pylint: disable=R0902 forward_ciphertexts: Dict[int, List[bytes]] = field(default_factory=dict) aggregate_ndarrays: NDArrays = field(default_factory=list) legacy_results: List[Tuple[ClientProxy, FitRes]] = field(default_factory=list) + failures: List[Exception] = field(default_factory=list) class SecAggPlusWorkflow: @@ -394,6 +395,7 @@ def make(nid: int) -> Message: for msg in msgs: if msg.has_error(): + state.failures.append(Exception(msg.error)) continue key_dict = msg.content.configs_records[RECORD_KEY_CONFIGS] node_id = msg.metadata.src_node_id @@ -451,6 +453,9 @@ def make(nid: int) -> Message: nid: [] for nid in state.active_node_ids } # dest node ID -> list of src node IDs for msg in msgs: + if msg.has_error(): + state.failures.append(Exception(msg.error)) + continue node_id = msg.metadata.src_node_id res_dict = msg.content.configs_records[RECORD_KEY_CONFIGS] dst_lst = cast(List[int], res_dict[Key.DESTINATION_LIST]) @@ -515,6 +520,9 @@ def make(nid: int) -> Message: # Sum collected masked vectors and compute active/dead node IDs masked_vector = None for msg in msgs: + if msg.has_error(): + state.failures.append(Exception(msg.error)) + continue res_dict = msg.content.configs_records[RECORD_KEY_CONFIGS] bytes_list = cast(List[bytes], res_dict[Key.MASKED_PARAMETERS]) client_masked_vec = [bytes_to_ndarray(b) for b in bytes_list] @@ -528,6 +536,9 @@ def make(nid: int) -> Message: # Backward compatibility with Strategy for msg in msgs: + if msg.has_error(): + state.failures.append(Exception(msg.error)) + continue fitres = compat.recordset_to_fitres(msg.content, True) proxy = state.nid_to_proxies[msg.metadata.src_node_id] state.legacy_results.append((proxy, fitres)) @@ -584,6 +595,9 @@ def make(nid: int) -> Message: for nid in state.sampled_node_ids: collected_shares_dict[nid] = [] for msg in msgs: + if msg.has_error(): + state.failures.append(Exception(msg.error)) + continue res_dict = msg.content.configs_records[RECORD_KEY_CONFIGS] nids = cast(List[int], res_dict[Key.NODE_ID_LIST]) shares = cast(List[bytes], res_dict[Key.SHARE_LIST]) @@ -652,9 +666,11 @@ def make(nid: int) -> Message: INFO, "aggregate_fit: received %s results and %s failures", len(results), - 0, + len(state.failures), + ) + aggregated_result = context.strategy.aggregate_fit( + current_round, results, state.failures # type: ignore ) - aggregated_result = context.strategy.aggregate_fit(current_round, results, []) parameters_aggregated, metrics_aggregated = aggregated_result # Update the parameters and write history From 399787de4ca1cdcc84453042471eab5e64f26c17 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 22 Jul 2024 17:40:14 +0200 Subject: [PATCH 218/595] feat(datasets) Add tests for ambient acoustic context (#3843) --- datasets/flwr_datasets/federated_dataset_test.py | 3 ++- datasets/flwr_datasets/utils.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index 278cf4c9f5ad..4737562e8814 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -406,7 +406,7 @@ def test_mixed_type_partitioners_creates_from_int(self) -> None: "flwrlabs/femnist", ] -mocked_natural_id_datasets = ["flwrlabs/ucf101"] +mocked_natural_id_datasets = ["flwrlabs/ucf101", "flwrlabs/ambient-acoustic-context"] @parameterized_class( @@ -414,6 +414,7 @@ def test_mixed_type_partitioners_creates_from_int(self) -> None: [ ("flwrlabs/femnist", "", "", "writer_id"), ("flwrlabs/ucf101", "test", None, "video_id"), + ("flwrlabs/ambient-acoustic-context", "", None, "speaker_id"), ], ) class NaturalIdPartitionerIntegrationTest(unittest.TestCase): diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index 955ea041c2a6..b611fd8cc300 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -37,6 +37,7 @@ "LIUM/tedlium", # Feature wise it's just like speech_commands "flwrlabs/femnist", "flwrlabs/ucf101", + "flwrlabs/ambient-acoustic-context", "jlh/uci-mushrooms", "Mike0307/MNIST-M", "flwrlabs/usps", From 5e3fc023b8ad455b291c7dcb36e6fd5c16318fc4 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 22 Jul 2024 17:52:05 +0200 Subject: [PATCH 219/595] feat(datasets) Update tests for ted lium dataset (#3868) --- datasets/flwr_datasets/federated_dataset_test.py | 7 ++++++- datasets/flwr_datasets/utils.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/datasets/flwr_datasets/federated_dataset_test.py b/datasets/flwr_datasets/federated_dataset_test.py index 4737562e8814..24c59ce029f0 100644 --- a/datasets/flwr_datasets/federated_dataset_test.py +++ b/datasets/flwr_datasets/federated_dataset_test.py @@ -406,7 +406,11 @@ def test_mixed_type_partitioners_creates_from_int(self) -> None: "flwrlabs/femnist", ] -mocked_natural_id_datasets = ["flwrlabs/ucf101", "flwrlabs/ambient-acoustic-context"] +mocked_natural_id_datasets = [ + "flwrlabs/ucf101", + "flwrlabs/ambient-acoustic-context", + "LIUM/tedlium", +] @parameterized_class( @@ -415,6 +419,7 @@ def test_mixed_type_partitioners_creates_from_int(self) -> None: ("flwrlabs/femnist", "", "", "writer_id"), ("flwrlabs/ucf101", "test", None, "video_id"), ("flwrlabs/ambient-acoustic-context", "", None, "speaker_id"), + ("LIUM/tedlium", "test", "release3", "speaker_id"), ], ) class NaturalIdPartitionerIntegrationTest(unittest.TestCase): diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index b611fd8cc300..8dccac180e55 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -34,7 +34,7 @@ "svhn", "sentiment140", "speech_commands", - "LIUM/tedlium", # Feature wise it's just like speech_commands + "LIUM/tedlium", "flwrlabs/femnist", "flwrlabs/ucf101", "flwrlabs/ambient-acoustic-context", From c4bb563c6fed67aabe2abbe7c04499a07a0abcbf Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Mon, 22 Jul 2024 18:05:44 +0200 Subject: [PATCH 220/595] docs(datasets) Fix docs examples formatting (#3702) --- datasets/flwr_datasets/federated_dataset.py | 4 +++- .../flwr_datasets/partitioner/dirichlet_partitioner.py | 2 +- .../flwr_datasets/partitioner/exponential_partitioner.py | 9 +++++++++ datasets/flwr_datasets/partitioner/iid_partitioner.py | 9 +++++++++ datasets/flwr_datasets/partitioner/linear_partitioner.py | 9 +++++++++ .../flwr_datasets/partitioner/natural_id_partitioner.py | 2 ++ datasets/flwr_datasets/partitioner/square_partitioner.py | 9 +++++++++ 7 files changed, 42 insertions(+), 2 deletions(-) diff --git a/datasets/flwr_datasets/federated_dataset.py b/datasets/flwr_datasets/federated_dataset.py index 7514516e09e7..ecdaa1653e12 100644 --- a/datasets/flwr_datasets/federated_dataset.py +++ b/datasets/flwr_datasets/federated_dataset.py @@ -85,6 +85,7 @@ class FederatedDataset: >>> centralized = fds.load_split("test") Use CIFAR10 dataset for Federated Laerning with 100 clients: + >>> from flwr_datasets import FederatedDataset >>> from flwr_datasets.partitioner import DirichletPartitioner >>> @@ -93,7 +94,8 @@ class FederatedDataset: >>> fds = FederatedDataset(dataset="cifar10", partitioners={"train": partitioner}) >>> partition = fds.load_partition(partition_id=0) - Visualize the partitioned datasets + Visualize the partitioned datasets: + >>> from flwr_datasets.visualization import plot_label_distributions >>> >>> _ = plot_label_distributions( diff --git a/datasets/flwr_datasets/partitioner/dirichlet_partitioner.py b/datasets/flwr_datasets/partitioner/dirichlet_partitioner.py index f3feb2174bde..dce208419181 100644 --- a/datasets/flwr_datasets/partitioner/dirichlet_partitioner.py +++ b/datasets/flwr_datasets/partitioner/dirichlet_partitioner.py @@ -78,7 +78,7 @@ class DirichletPartitioner(Partitioner): >>> print(partition[0]) # Print the first example {'image': , 'label': 4} - >>> partition_sizes = partition_sizes = [ + >>> partition_sizes = [ >>> len(fds.load_partition(partition_id)) for partition_id in range(10) >>> ] >>> print(sorted(partition_sizes)) diff --git a/datasets/flwr_datasets/partitioner/exponential_partitioner.py b/datasets/flwr_datasets/partitioner/exponential_partitioner.py index d35944f29f6f..5d9f34352af1 100644 --- a/datasets/flwr_datasets/partitioner/exponential_partitioner.py +++ b/datasets/flwr_datasets/partitioner/exponential_partitioner.py @@ -35,6 +35,15 @@ class ExponentialPartitioner(SizePartitioner): ---------- num_partitions : int The total number of partitions that the data will be divided into. + + Examples + -------- + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import ExponentialPartitioner + >>> + >>> partitioner = ExponentialPartitioner(num_partitions=10) + >>> fds = FederatedDataset(dataset="mnist", partitioners={"train": partitioner}) + >>> partition = fds.load_partition(0) """ def __init__(self, num_partitions: int) -> None: diff --git a/datasets/flwr_datasets/partitioner/iid_partitioner.py b/datasets/flwr_datasets/partitioner/iid_partitioner.py index ceddd386c7d3..f0f470072eb5 100644 --- a/datasets/flwr_datasets/partitioner/iid_partitioner.py +++ b/datasets/flwr_datasets/partitioner/iid_partitioner.py @@ -26,6 +26,15 @@ class IidPartitioner(Partitioner): ---------- num_partitions : int The total number of partitions that the data will be divided into. + + Examples + -------- + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import IidPartitioner + >>> + >>> partitioner = IidPartitioner(num_partitions=10) + >>> fds = FederatedDataset(dataset="mnist", partitioners={"train": partitioner}) + >>> partition = fds.load_partition(0) """ def __init__(self, num_partitions: int) -> None: diff --git a/datasets/flwr_datasets/partitioner/linear_partitioner.py b/datasets/flwr_datasets/partitioner/linear_partitioner.py index 84d419ab5592..840307edcac6 100644 --- a/datasets/flwr_datasets/partitioner/linear_partitioner.py +++ b/datasets/flwr_datasets/partitioner/linear_partitioner.py @@ -29,6 +29,15 @@ class LinearPartitioner(SizePartitioner): ---------- num_partitions : int The total number of partitions that the data will be divided into. + + Examples + -------- + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import LinearPartitioner + >>> + >>> partitioner = LinearPartitioner(num_partitions=10) + >>> fds = FederatedDataset(dataset="mnist", partitioners={"train": partitioner}) + >>> partition = fds.load_partition(0) """ def __init__(self, num_partitions: int) -> None: diff --git a/datasets/flwr_datasets/partitioner/natural_id_partitioner.py b/datasets/flwr_datasets/partitioner/natural_id_partitioner.py index f71d7004b42b..5a9af3271cb4 100644 --- a/datasets/flwr_datasets/partitioner/natural_id_partitioner.py +++ b/datasets/flwr_datasets/partitioner/natural_id_partitioner.py @@ -37,6 +37,7 @@ class NaturalIdPartitioner(Partitioner): Examples -------- "flwrlabs/shakespeare" dataset + >>> from flwr_datasets import FederatedDataset >>> from flwr_datasets.partitioner import NaturalIdPartitioner >>> @@ -46,6 +47,7 @@ class NaturalIdPartitioner(Partitioner): >>> partition = fds.load_partition(0) "sentiment140" (aka Twitter) dataset + >>> from flwr_datasets import FederatedDataset >>> from flwr_datasets.partitioner import NaturalIdPartitioner >>> diff --git a/datasets/flwr_datasets/partitioner/square_partitioner.py b/datasets/flwr_datasets/partitioner/square_partitioner.py index 4c894e47eedf..0fa0a0803a0e 100644 --- a/datasets/flwr_datasets/partitioner/square_partitioner.py +++ b/datasets/flwr_datasets/partitioner/square_partitioner.py @@ -31,6 +31,15 @@ class SquarePartitioner(SizePartitioner): ---------- num_partitions : int The total number of partitions that the data will be divided into. + + Examples + -------- + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import SquarePartitioner + >>> + >>> partitioner = SquarePartitioner(num_partitions=10) + >>> fds = FederatedDataset(dataset="mnist", partitioners={"train": partitioner}) + >>> partition = fds.load_partition(0) """ def __init__(self, num_partitions: int) -> None: From be83ae86ec3916d966e45759ddac24ed4b461070 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 22 Jul 2024 20:16:17 +0200 Subject: [PATCH 221/595] refactor(framework:skip) Support `partner_id` (#3869) Co-authored-by: Daniel J. Beutel --- src/py/flwr/common/telemetry.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/py/flwr/common/telemetry.py b/src/py/flwr/common/telemetry.py index eeb255e8d6eb..399f400b7edc 100644 --- a/src/py/flwr/common/telemetry.py +++ b/src/py/flwr/common/telemetry.py @@ -64,6 +64,18 @@ def _get_home() -> Path: return Path().home() +def _get_partner_id() -> str: + """Get partner ID.""" + partner_id = os.getenv("FLWR_TELEMETRY_PARTNER_ID") + if not partner_id: + return "unavailable" + try: + uuid.UUID(partner_id) + except ValueError: + partner_id = "invalid" + return partner_id + + def _get_source_id() -> str: """Get existing or new source ID.""" source_id = "unavailable" @@ -177,6 +189,7 @@ def _generate_next_value_(name: str, start: int, count: int, last_values: List[A "executor": None, "source": None, "cluster": None, + "partner": None, } @@ -202,11 +215,15 @@ def create_event(event_type: EventType, event_details: Optional[Dict[str, Any]]) if state["cluster"] is None: state["cluster"] = str(uuid.uuid4()) + if state["partner"] is None: + state["partner"] = _get_partner_id() + if event_details is None: event_details = {} date = datetime.datetime.now(tz=datetime.timezone.utc).isoformat() context = { + "partner": state["partner"], "source": state["source"], "cluster": state["cluster"], "date": date, From d24ebe5c55c97e5c71e2d5fc1e4d8ed5d917b029 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 22 Jul 2024 20:32:33 +0200 Subject: [PATCH 222/595] feat(framework) Support non-`str` config value types (#3746) --- src/proto/flwr/proto/driver.proto | 3 +- src/proto/flwr/proto/exec.proto | 4 +- src/proto/flwr/proto/run.proto | 4 +- src/proto/flwr/proto/task.proto | 1 - src/py/flwr/cli/config_utils.py | 10 +- src/py/flwr/cli/run/run.py | 5 +- src/py/flwr/client/app.py | 6 +- .../client/grpc_rere_client/connection.py | 8 +- src/py/flwr/client/node_state.py | 6 +- src/py/flwr/client/rest_client/connection.py | 8 +- src/py/flwr/common/config.py | 35 +++--- src/py/flwr/common/config_test.py | 41 ++++--- src/py/flwr/common/context.py | 14 +-- src/py/flwr/common/serde.py | 45 +++++++ src/py/flwr/common/typing.py | 6 +- src/py/flwr/proto/common_pb2.py | 14 ++- src/py/flwr/proto/common_pb2.pyi | 114 ++++++++++++++++++ src/py/flwr/proto/driver_pb2.py | 43 +++---- src/py/flwr/proto/driver_pb2.pyi | 11 +- src/py/flwr/proto/exec_pb2.py | 27 +++-- src/py/flwr/proto/exec_pb2.pyi | 11 +- src/py/flwr/proto/run_pb2.py | 19 +-- src/py/flwr/proto/run_pb2.pyi | 11 +- src/py/flwr/proto/task_pb2.py | 15 ++- src/py/flwr/server/driver/grpc_driver.py | 8 +- src/py/flwr/server/run_serverapp.py | 5 +- .../superlink/driver/driver_servicer.py | 17 ++- .../fleet/message_handler/message_handler.py | 15 ++- .../server/superlink/state/in_memory_state.py | 4 +- .../server/superlink/state/sqlite_state.py | 4 +- src/py/flwr/server/superlink/state/state.py | 6 +- .../ray_transport/ray_client_proxy.py | 2 +- .../ray_transport/ray_client_proxy_test.py | 2 +- src/py/flwr/simulation/run_simulation.py | 14 +-- src/py/flwr/superexec/deployment.py | 26 ++-- src/py/flwr/superexec/exec_grpc.py | 5 +- src/py/flwr/superexec/exec_servicer.py | 4 +- src/py/flwr/superexec/executor.py | 12 +- src/py/flwr/superexec/simulation.py | 24 ++-- 39 files changed, 433 insertions(+), 176 deletions(-) diff --git a/src/proto/flwr/proto/driver.proto b/src/proto/flwr/proto/driver.proto index 77dc52b3258b..531d18b4f3ad 100644 --- a/src/proto/flwr/proto/driver.proto +++ b/src/proto/flwr/proto/driver.proto @@ -20,6 +20,7 @@ package flwr.proto; import "flwr/proto/node.proto"; import "flwr/proto/task.proto"; import "flwr/proto/run.proto"; +import "flwr/proto/transport.proto"; service Driver { // Request run_id @@ -42,7 +43,7 @@ service Driver { message CreateRunRequest { string fab_id = 1; string fab_version = 2; - map override_config = 3; + map override_config = 3; } message CreateRunResponse { sint64 run_id = 1; } diff --git a/src/proto/flwr/proto/exec.proto b/src/proto/flwr/proto/exec.proto index d0d8dfcbb273..0968857bdd71 100644 --- a/src/proto/flwr/proto/exec.proto +++ b/src/proto/flwr/proto/exec.proto @@ -17,6 +17,8 @@ syntax = "proto3"; package flwr.proto; +import "flwr/proto/transport.proto"; + service Exec { // Start run upon request rpc StartRun(StartRunRequest) returns (StartRunResponse) {} @@ -27,7 +29,7 @@ service Exec { message StartRunRequest { bytes fab_file = 1; - map override_config = 2; + map override_config = 2; } message StartRunResponse { sint64 run_id = 1; } message StreamLogsRequest { sint64 run_id = 1; } diff --git a/src/proto/flwr/proto/run.proto b/src/proto/flwr/proto/run.proto index e41748381cab..f46f7146c846 100644 --- a/src/proto/flwr/proto/run.proto +++ b/src/proto/flwr/proto/run.proto @@ -17,11 +17,13 @@ syntax = "proto3"; package flwr.proto; +import "flwr/proto/transport.proto"; + message Run { sint64 run_id = 1; string fab_id = 2; string fab_version = 3; - map override_config = 4; + map override_config = 4; } message GetRunRequest { sint64 run_id = 1; } message GetRunResponse { Run run = 1; } diff --git a/src/proto/flwr/proto/task.proto b/src/proto/flwr/proto/task.proto index cf77d110acab..936b8120e495 100644 --- a/src/proto/flwr/proto/task.proto +++ b/src/proto/flwr/proto/task.proto @@ -19,7 +19,6 @@ package flwr.proto; import "flwr/proto/node.proto"; import "flwr/proto/recordset.proto"; -import "flwr/proto/transport.proto"; import "flwr/proto/error.proto"; message Task { diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index f46a53857dfc..d150e1b5f53d 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -17,11 +17,12 @@ import zipfile from io import BytesIO from pathlib import Path -from typing import IO, Any, Dict, List, Optional, Tuple, Union +from typing import IO, Any, Dict, List, Optional, Tuple, Union, get_args import tomli from flwr.common import object_ref +from flwr.common.typing import UserConfigValue def get_fab_metadata(fab_file: Union[Path, bytes]) -> Tuple[str, str]: @@ -112,8 +113,11 @@ def _validate_run_config(config_dict: Dict[str, Any], errors: List[str]) -> None for key, value in config_dict.items(): if isinstance(value, dict): _validate_run_config(config_dict[key], errors) - elif not isinstance(value, str): - errors.append(f"Config value of key {key} is not of type `str`.") + elif not isinstance(value, get_args(UserConfigValue)): + raise ValueError( + f"The value for key {key} needs to be of type `int`, `float`, " + "`bool, `str`, or a `dict` of those.", + ) # pylint: disable=too-many-branches diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index a8dd5a59a627..00588fec4224 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -28,6 +28,7 @@ from flwr.common.config import parse_config_args from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel from flwr.common.logger import log +from flwr.common.serde import user_config_to_proto from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611 from flwr.proto.exec_pb2_grpc import ExecStub @@ -164,7 +165,9 @@ def on_channel_state_change(channel_connectivity: str) -> None: req = StartRunRequest( fab_file=Path(fab_path).read_bytes(), - override_config=parse_config_args(config_overrides, separator=","), + override_config=user_config_to_proto( + parse_config_args(config_overrides, separator=",") + ), ) res = stub.StartRun(req) typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN) diff --git a/src/py/flwr/client/app.py b/src/py/flwr/client/app.py index 127bb423851f..526f26cb8cc3 100644 --- a/src/py/flwr/client/app.py +++ b/src/py/flwr/client/app.py @@ -42,7 +42,7 @@ from flwr.common.logger import log, warn_deprecated_feature from flwr.common.message import Error from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential -from flwr.common.typing import Run +from flwr.common.typing import Run, UserConfig from .grpc_adapter_client.connection import grpc_adapter from .grpc_client.connection import grpc_connection @@ -182,7 +182,7 @@ class `flwr.client.Client` (default: None) def _start_client_internal( *, server_address: str, - node_config: Dict[str, str], + node_config: UserConfig, load_client_app_fn: Optional[Callable[[str, str], ClientApp]] = None, client_fn: Optional[ClientFnExt] = None, client: Optional[Client] = None, @@ -205,7 +205,7 @@ def _start_client_internal( The IPv4 or IPv6 address of the server. If the Flower server runs on the same machine on port 8080, then `server_address` would be `"[::]:8080"`. - node_config: Dict[str, str] + node_config: UserConfig The configuration of the node. load_client_app_fn : Optional[Callable[[], ClientApp]] (default: None) A function that can be used to load a `ClientApp` instance. diff --git a/src/py/flwr/client/grpc_rere_client/connection.py b/src/py/flwr/client/grpc_rere_client/connection.py index e573df6854bc..64543626e695 100644 --- a/src/py/flwr/client/grpc_rere_client/connection.py +++ b/src/py/flwr/client/grpc_rere_client/connection.py @@ -40,7 +40,11 @@ from flwr.common.logger import log from flwr.common.message import Message, Metadata from flwr.common.retry_invoker import RetryInvoker -from flwr.common.serde import message_from_taskins, message_to_taskres +from flwr.common.serde import ( + message_from_taskins, + message_to_taskres, + user_config_from_proto, +) from flwr.common.typing import Run from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, @@ -281,7 +285,7 @@ def get_run(run_id: int) -> Run: run_id, get_run_response.run.fab_id, get_run_response.run.fab_version, - dict(get_run_response.run.override_config.items()), + user_config_from_proto(get_run_response.run.override_config), ) try: diff --git a/src/py/flwr/client/node_state.py b/src/py/flwr/client/node_state.py index 08c19967ea3d..3320c90cb8cc 100644 --- a/src/py/flwr/client/node_state.py +++ b/src/py/flwr/client/node_state.py @@ -21,7 +21,7 @@ from flwr.common import Context, RecordSet from flwr.common.config import get_fused_config, get_fused_config_from_dir -from flwr.common.typing import Run +from flwr.common.typing import Run, UserConfig @dataclass() @@ -29,7 +29,7 @@ class RunInfo: """Contains the Context and initial run_config of a Run.""" context: Context - initial_run_config: Dict[str, str] + initial_run_config: UserConfig class NodeState: @@ -38,7 +38,7 @@ class NodeState: def __init__( self, node_id: int, - node_config: Dict[str, str], + node_config: UserConfig, ) -> None: self.node_id = node_id self.node_config = node_config diff --git a/src/py/flwr/client/rest_client/connection.py b/src/py/flwr/client/rest_client/connection.py index 3e81969d898c..e2bb1f62bc43 100644 --- a/src/py/flwr/client/rest_client/connection.py +++ b/src/py/flwr/client/rest_client/connection.py @@ -40,7 +40,11 @@ from flwr.common.logger import log from flwr.common.message import Message, Metadata from flwr.common.retry_invoker import RetryInvoker -from flwr.common.serde import message_from_taskins, message_to_taskres +from flwr.common.serde import ( + message_from_taskins, + message_to_taskres, + user_config_from_proto, +) from flwr.common.typing import Run from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, @@ -359,7 +363,7 @@ def get_run(run_id: int) -> Run: run_id, res.run.fab_id, res.run.fab_version, - dict(res.run.override_config.items()), + user_config_from_proto(res.run.override_config), ) try: diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index 789433a287e7..c915a3ef1621 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -16,13 +16,13 @@ import os from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Tuple, Union, cast, get_args import tomli from flwr.cli.config_utils import validate_fields from flwr.common.constant import APP_DIR, FAB_CONFIG_FILE, FLWR_HOME -from flwr.common.typing import Run +from flwr.common.typing import Run, UserConfig, UserConfigValue def get_flwr_dir(provided_path: Optional[str] = None) -> Path: @@ -75,8 +75,9 @@ def get_project_config(project_dir: Union[str, Path]) -> Dict[str, Any]: def _fuse_dicts( - main_dict: Dict[str, str], override_dict: Dict[str, str] -) -> Dict[str, str]: + main_dict: UserConfig, + override_dict: UserConfig, +) -> UserConfig: fused_dict = main_dict.copy() for key, value in override_dict.items(): @@ -87,8 +88,8 @@ def _fuse_dicts( def get_fused_config_from_dir( - project_dir: Path, override_config: Dict[str, str] -) -> Dict[str, str]: + project_dir: Path, override_config: UserConfig +) -> UserConfig: """Merge the overrides from a given dict with the config from a Flower App.""" default_config = get_project_config(project_dir)["tool"]["flwr"]["app"].get( "config", {} @@ -98,7 +99,7 @@ def get_fused_config_from_dir( return _fuse_dicts(flat_default_config, override_config) -def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: +def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig: """Merge the overrides from a `Run` with the config from a FAB. Get the config using the fab_id and the fab_version, remove the nesting by adding @@ -112,19 +113,20 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> Dict[str, str]: return get_fused_config_from_dir(project_dir, run.override_config) -def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, str]: +def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> UserConfig: """Flatten dict by joining nested keys with a given separator.""" - items: List[Tuple[str, str]] = [] + items: List[Tuple[str, UserConfigValue]] = [] separator: str = "." for k, v in raw_dict.items(): new_key = f"{parent_key}{separator}{k}" if parent_key else k if isinstance(v, dict): items.extend(flatten_dict(v, parent_key=new_key).items()) - elif isinstance(v, str): - items.append((new_key, v)) + elif isinstance(v, get_args(UserConfigValue)): + items.append((new_key, cast(UserConfigValue, v))) else: raise ValueError( - f"The value for key {k} needs to be a `str` or a `dict`.", + f"The value for key {k} needs to be of type `int`, `float`, " + "`bool, `str`, or a `dict` of those.", ) return dict(items) @@ -132,9 +134,9 @@ def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> Dict[str, st def parse_config_args( config: Optional[List[str]], separator: str = ",", -) -> Dict[str, str]: +) -> UserConfig: """Parse separator separated list of key-value pairs separated by '='.""" - overrides: Dict[str, str] = {} + overrides: UserConfig = {} if config is None: return overrides @@ -150,8 +152,7 @@ def parse_config_args( with Path(overrides_list[0]).open("rb") as config_file: overrides = flatten_dict(tomli.load(config_file)) else: - for kv_pair in overrides_list: - key, value = kv_pair.split("=") - overrides[key] = value + toml_str = "\n".join(overrides_list) + overrides.update(tomli.loads(toml_str)) return overrides diff --git a/src/py/flwr/common/config_test.py b/src/py/flwr/common/config_test.py index e1597aa5a2ec..52dcc0f9121e 100644 --- a/src/py/flwr/common/config_test.py +++ b/src/py/flwr/common/config_test.py @@ -21,6 +21,8 @@ import pytest +from flwr.common.typing import UserConfig + from .config import ( _fuse_dicts, flatten_dict, @@ -101,23 +103,25 @@ def test_get_fused_config_valid(tmp_path: Path) -> None: clientapp = "fedgpt.client:app" [tool.flwr.app.config] - num_server_rounds = "10" - momentum = "0.1" - lr = "0.01" + num_server_rounds = 10 + momentum = 0.1 + lr = 0.01 + progress_bar = true serverapp.test = "key" [tool.flwr.app.config.clientapp] test = "key" """ - overrides = { - "num_server_rounds": "5", - "lr": "0.2", + overrides: UserConfig = { + "num_server_rounds": 5, + "lr": 0.2, "serverapp.test": "overriden", } expected_config = { - "num_server_rounds": "5", - "momentum": "0.1", - "lr": "0.2", + "num_server_rounds": 5, + "momentum": 0.1, + "lr": 0.2, + "progress_bar": True, "serverapp.test": "overriden", "clientapp.test": "key", } @@ -168,8 +172,9 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: clientapp = "fedgpt.client:app" [tool.flwr.app.config] - num_server_rounds = "10" - momentum = "0.1" + num_server_rounds = 10 + momentum = 0.1 + progress_bar = true lr = "0.01" """ expected_config = { @@ -190,8 +195,9 @@ def test_get_project_config_file_valid(tmp_path: Path) -> None: "clientapp": "fedgpt.client:app", }, "config": { - "num_server_rounds": "10", - "momentum": "0.1", + "num_server_rounds": 10, + "momentum": 0.1, + "progress_bar": True, "lr": "0.01", }, }, @@ -231,11 +237,12 @@ def test_parse_config_args_none() -> None: def test_parse_config_args_overrides() -> None: """Test parse_config_args with key-value pairs.""" assert parse_config_args( - ["key1=value1,key2=value2", "key3=value3", "key4=value4,key5=value5"] + ["key1='value1',key2='value2'", "key3=1", "key4=2.0,key5=true,key6='value6'"] ) == { "key1": "value1", "key2": "value2", - "key3": "value3", - "key4": "value4", - "key5": "value5", + "key3": 1, + "key4": 2.0, + "key5": True, + "key6": "value6", } diff --git a/src/py/flwr/common/context.py b/src/py/flwr/common/context.py index 4da52ba44481..1544b96d3fa3 100644 --- a/src/py/flwr/common/context.py +++ b/src/py/flwr/common/context.py @@ -16,9 +16,9 @@ from dataclasses import dataclass -from typing import Dict from .record import RecordSet +from .typing import UserConfig @dataclass @@ -29,7 +29,7 @@ class Context: ---------- node_id : int The ID that identifies the node. - node_config : Dict[str, str] + node_config : UserConfig A config (key/value mapping) unique to the node and independent of the `run_config`. This config persists across all runs this node participates in. state : RecordSet @@ -39,23 +39,23 @@ class Context: executing mods. It can also be used as a memory to access at different points during the lifecycle of this entity (e.g. across multiple rounds) - run_config : Dict[str, str] + run_config : UserConfig A config (key/value mapping) held by the entity in a given run and that will stay local. It can be used at any point during the lifecycle of this entity (e.g. across multiple rounds) """ node_id: int - node_config: Dict[str, str] + node_config: UserConfig state: RecordSet - run_config: Dict[str, str] + run_config: UserConfig def __init__( # pylint: disable=too-many-arguments self, node_id: int, - node_config: Dict[str, str], + node_config: UserConfig, state: RecordSet, - run_config: Dict[str, str], + run_config: UserConfig, ) -> None: self.node_id = node_id self.node_config = node_config diff --git a/src/py/flwr/common/serde.py b/src/py/flwr/common/serde.py index 84932b806aff..5e34b2b4b5f8 100644 --- a/src/py/flwr/common/serde.py +++ b/src/py/flwr/common/serde.py @@ -671,3 +671,48 @@ def message_from_taskres(taskres: TaskRes) -> Message: ) message.metadata.created_at = taskres.task.created_at return message + + +# === User configs === + + +def user_config_to_proto(user_config: typing.UserConfig) -> Any: + """Serialize `UserConfig` to ProtoBuf.""" + proto = {} + for key, value in user_config.items(): + proto[key] = user_config_value_to_proto(value) + return proto + + +def user_config_from_proto(proto: Any) -> typing.UserConfig: + """Deserialize `UserConfig` from ProtoBuf.""" + metrics = {} + for key, value in proto.items(): + metrics[key] = user_config_value_from_proto(value) + return metrics + + +def user_config_value_to_proto(user_config_value: typing.UserConfigValue) -> Scalar: + """Serialize `UserConfigValue` to ProtoBuf.""" + if isinstance(user_config_value, bool): + return Scalar(bool=user_config_value) + + if isinstance(user_config_value, float): + return Scalar(double=user_config_value) + + if isinstance(user_config_value, int): + return Scalar(sint64=user_config_value) + + if isinstance(user_config_value, str): + return Scalar(string=user_config_value) + + raise ValueError( + f"Accepted types: {bool, float, int, str} (but not {type(user_config_value)})" + ) + + +def user_config_value_from_proto(scalar_msg: Scalar) -> typing.UserConfigValue: + """Deserialize `UserConfigValue` from ProtoBuf.""" + scalar_field = scalar_msg.WhichOneof("scalar") + scalar = getattr(scalar_msg, cast(str, scalar_field)) + return cast(typing.UserConfigValue, scalar) diff --git a/src/py/flwr/common/typing.py b/src/py/flwr/common/typing.py index 04d2cf5bbf7f..c050fe6d4a13 100644 --- a/src/py/flwr/common/typing.py +++ b/src/py/flwr/common/typing.py @@ -60,6 +60,10 @@ Config = Dict[str, Scalar] Properties = Dict[str, Scalar] +# Value type for user configs +UserConfigValue = Union[bool, float, int, str] +UserConfig = Dict[str, UserConfigValue] + class Code(Enum): """Client status codes.""" @@ -194,4 +198,4 @@ class Run: run_id: int fab_id: str fab_version: str - override_config: Dict[str, str] + override_config: UserConfig diff --git a/src/py/flwr/proto/common_pb2.py b/src/py/flwr/proto/common_pb2.py index 8a6430137f05..1025aa862933 100644 --- a/src/py/flwr/proto/common_pb2.py +++ b/src/py/flwr/proto/common_pb2.py @@ -14,11 +14,23 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/common.proto\x12\nflwr.protob\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/common.proto\x12\nflwr.proto\"\x1a\n\nDoubleList\x12\x0c\n\x04vals\x18\x01 \x03(\x01\"\x1a\n\nSint64List\x12\x0c\n\x04vals\x18\x01 \x03(\x12\"\x18\n\x08\x42oolList\x12\x0c\n\x04vals\x18\x01 \x03(\x08\"\x1a\n\nStringList\x12\x0c\n\x04vals\x18\x01 \x03(\t\"\x19\n\tBytesList\x12\x0c\n\x04vals\x18\x01 \x03(\x0c\"\xd9\x02\n\x12\x43onfigsRecordValue\x12\x10\n\x06\x64ouble\x18\x01 \x01(\x01H\x00\x12\x10\n\x06sint64\x18\x02 \x01(\x12H\x00\x12\x0e\n\x04\x62ool\x18\x03 \x01(\x08H\x00\x12\x10\n\x06string\x18\x04 \x01(\tH\x00\x12\x0f\n\x05\x62ytes\x18\x05 \x01(\x0cH\x00\x12-\n\x0b\x64ouble_list\x18\x15 \x01(\x0b\x32\x16.flwr.proto.DoubleListH\x00\x12-\n\x0bsint64_list\x18\x16 \x01(\x0b\x32\x16.flwr.proto.Sint64ListH\x00\x12)\n\tbool_list\x18\x17 \x01(\x0b\x32\x14.flwr.proto.BoolListH\x00\x12-\n\x0bstring_list\x18\x18 \x01(\x0b\x32\x16.flwr.proto.StringListH\x00\x12+\n\nbytes_list\x18\x19 \x01(\x0b\x32\x15.flwr.proto.BytesListH\x00\x42\x07\n\x05valueb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.common_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None + _globals['_DOUBLELIST']._serialized_start=39 + _globals['_DOUBLELIST']._serialized_end=65 + _globals['_SINT64LIST']._serialized_start=67 + _globals['_SINT64LIST']._serialized_end=93 + _globals['_BOOLLIST']._serialized_start=95 + _globals['_BOOLLIST']._serialized_end=119 + _globals['_STRINGLIST']._serialized_start=121 + _globals['_STRINGLIST']._serialized_end=147 + _globals['_BYTESLIST']._serialized_start=149 + _globals['_BYTESLIST']._serialized_end=174 + _globals['_CONFIGSRECORDVALUE']._serialized_start=177 + _globals['_CONFIGSRECORDVALUE']._serialized_end=522 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/common_pb2.pyi b/src/py/flwr/proto/common_pb2.pyi index e08fa11c2caa..e2539a7300a9 100644 --- a/src/py/flwr/proto/common_pb2.pyi +++ b/src/py/flwr/proto/common_pb2.pyi @@ -2,6 +2,120 @@ @generated by mypy-protobuf. Do not edit manually! isort:skip_file """ +import builtins import google.protobuf.descriptor +import google.protobuf.internal.containers +import google.protobuf.message +import typing +import typing_extensions DESCRIPTOR: google.protobuf.descriptor.FileDescriptor + +class DoubleList(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + VALS_FIELD_NUMBER: builtins.int + @property + def vals(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.float]: ... + def __init__(self, + *, + vals: typing.Optional[typing.Iterable[builtins.float]] = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["vals",b"vals"]) -> None: ... +global___DoubleList = DoubleList + +class Sint64List(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + VALS_FIELD_NUMBER: builtins.int + @property + def vals(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: ... + def __init__(self, + *, + vals: typing.Optional[typing.Iterable[builtins.int]] = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["vals",b"vals"]) -> None: ... +global___Sint64List = Sint64List + +class BoolList(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + VALS_FIELD_NUMBER: builtins.int + @property + def vals(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bool]: ... + def __init__(self, + *, + vals: typing.Optional[typing.Iterable[builtins.bool]] = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["vals",b"vals"]) -> None: ... +global___BoolList = BoolList + +class StringList(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + VALS_FIELD_NUMBER: builtins.int + @property + def vals(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[typing.Text]: ... + def __init__(self, + *, + vals: typing.Optional[typing.Iterable[typing.Text]] = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["vals",b"vals"]) -> None: ... +global___StringList = StringList + +class BytesList(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + VALS_FIELD_NUMBER: builtins.int + @property + def vals(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.bytes]: ... + def __init__(self, + *, + vals: typing.Optional[typing.Iterable[builtins.bytes]] = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["vals",b"vals"]) -> None: ... +global___BytesList = BytesList + +class ConfigsRecordValue(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + DOUBLE_FIELD_NUMBER: builtins.int + SINT64_FIELD_NUMBER: builtins.int + BOOL_FIELD_NUMBER: builtins.int + STRING_FIELD_NUMBER: builtins.int + BYTES_FIELD_NUMBER: builtins.int + DOUBLE_LIST_FIELD_NUMBER: builtins.int + SINT64_LIST_FIELD_NUMBER: builtins.int + BOOL_LIST_FIELD_NUMBER: builtins.int + STRING_LIST_FIELD_NUMBER: builtins.int + BYTES_LIST_FIELD_NUMBER: builtins.int + double: builtins.float + """Single element""" + + sint64: builtins.int + bool: builtins.bool + string: typing.Text + bytes: builtins.bytes + @property + def double_list(self) -> global___DoubleList: + """List types""" + pass + @property + def sint64_list(self) -> global___Sint64List: ... + @property + def bool_list(self) -> global___BoolList: ... + @property + def string_list(self) -> global___StringList: ... + @property + def bytes_list(self) -> global___BytesList: ... + def __init__(self, + *, + double: builtins.float = ..., + sint64: builtins.int = ..., + bool: builtins.bool = ..., + string: typing.Text = ..., + bytes: builtins.bytes = ..., + double_list: typing.Optional[global___DoubleList] = ..., + sint64_list: typing.Optional[global___Sint64List] = ..., + bool_list: typing.Optional[global___BoolList] = ..., + string_list: typing.Optional[global___StringList] = ..., + bytes_list: typing.Optional[global___BytesList] = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["bool",b"bool","bool_list",b"bool_list","bytes",b"bytes","bytes_list",b"bytes_list","double",b"double","double_list",b"double_list","sint64",b"sint64","sint64_list",b"sint64_list","string",b"string","string_list",b"string_list","value",b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["bool",b"bool","bool_list",b"bool_list","bytes",b"bytes","bytes_list",b"bytes_list","double",b"double","double_list",b"double_list","sint64",b"sint64","sint64_list",b"sint64_list","string",b"string","string_list",b"string_list","value",b"value"]) -> None: ... + def WhichOneof(self, oneof_group: typing_extensions.Literal["value",b"value"]) -> typing.Optional[typing_extensions.Literal["double","sint64","bool","string","bytes","double_list","sint64_list","bool_list","string_list","bytes_list"]]: ... +global___ConfigsRecordValue = ConfigsRecordValue diff --git a/src/py/flwr/proto/driver_pb2.py b/src/py/flwr/proto/driver_pb2.py index 07975937328d..6359e2f7d5fa 100644 --- a/src/py/flwr/proto/driver_pb2.py +++ b/src/py/flwr/proto/driver_pb2.py @@ -15,9 +15,10 @@ from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2 from flwr.proto import task_pb2 as flwr_dot_proto_dot_task__pb2 from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 +from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\"\xb9\x01\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\x12I\n\x0foverride_config\x18\x03 \x03(\x0b\x32\x30.flwr.proto.CreateRunRequest.OverrideConfigEntry\x1a\x35\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\x84\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x17\x66lwr/proto/driver.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x15\x66lwr/proto/task.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xcd\x01\n\x10\x43reateRunRequest\x12\x0e\n\x06\x66\x61\x62_id\x18\x01 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x02 \x01(\t\x12I\n\x0foverride_config\x18\x03 \x03(\x0b\x32\x30.flwr.proto.CreateRunRequest.OverrideConfigEntry\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"#\n\x11\x43reateRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"@\n\x12PushTaskInsRequest\x12*\n\rtask_ins_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskIns\"\'\n\x13PushTaskInsResponse\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"F\n\x12PullTaskResRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x10\n\x08task_ids\x18\x02 \x03(\t\"A\n\x13PullTaskResResponse\x12*\n\rtask_res_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.TaskRes2\x84\x03\n\x06\x44river\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12P\n\x0bPushTaskIns\x12\x1e.flwr.proto.PushTaskInsRequest\x1a\x1f.flwr.proto.PushTaskInsResponse\"\x00\x12P\n\x0bPullTaskRes\x12\x1e.flwr.proto.PullTaskResRequest\x1a\x1f.flwr.proto.PullTaskResResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -26,24 +27,24 @@ DESCRIPTOR._options = None _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._options = None _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' - _globals['_CREATERUNREQUEST']._serialized_start=108 - _globals['_CREATERUNREQUEST']._serialized_end=293 - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=240 - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=293 - _globals['_CREATERUNRESPONSE']._serialized_start=295 - _globals['_CREATERUNRESPONSE']._serialized_end=330 - _globals['_GETNODESREQUEST']._serialized_start=332 - _globals['_GETNODESREQUEST']._serialized_end=365 - _globals['_GETNODESRESPONSE']._serialized_start=367 - _globals['_GETNODESRESPONSE']._serialized_end=418 - _globals['_PUSHTASKINSREQUEST']._serialized_start=420 - _globals['_PUSHTASKINSREQUEST']._serialized_end=484 - _globals['_PUSHTASKINSRESPONSE']._serialized_start=486 - _globals['_PUSHTASKINSRESPONSE']._serialized_end=525 - _globals['_PULLTASKRESREQUEST']._serialized_start=527 - _globals['_PULLTASKRESREQUEST']._serialized_end=597 - _globals['_PULLTASKRESRESPONSE']._serialized_start=599 - _globals['_PULLTASKRESRESPONSE']._serialized_end=664 - _globals['_DRIVER']._serialized_start=667 - _globals['_DRIVER']._serialized_end=1055 + _globals['_CREATERUNREQUEST']._serialized_start=136 + _globals['_CREATERUNREQUEST']._serialized_end=341 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=268 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=341 + _globals['_CREATERUNRESPONSE']._serialized_start=343 + _globals['_CREATERUNRESPONSE']._serialized_end=378 + _globals['_GETNODESREQUEST']._serialized_start=380 + _globals['_GETNODESREQUEST']._serialized_end=413 + _globals['_GETNODESRESPONSE']._serialized_start=415 + _globals['_GETNODESRESPONSE']._serialized_end=466 + _globals['_PUSHTASKINSREQUEST']._serialized_start=468 + _globals['_PUSHTASKINSREQUEST']._serialized_end=532 + _globals['_PUSHTASKINSRESPONSE']._serialized_start=534 + _globals['_PUSHTASKINSRESPONSE']._serialized_end=573 + _globals['_PULLTASKRESREQUEST']._serialized_start=575 + _globals['_PULLTASKRESREQUEST']._serialized_end=645 + _globals['_PULLTASKRESRESPONSE']._serialized_start=647 + _globals['_PULLTASKRESRESPONSE']._serialized_end=712 + _globals['_DRIVER']._serialized_start=715 + _globals['_DRIVER']._serialized_end=1103 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/driver_pb2.pyi b/src/py/flwr/proto/driver_pb2.pyi index 95d4c9785ff1..748399be4e6b 100644 --- a/src/py/flwr/proto/driver_pb2.pyi +++ b/src/py/flwr/proto/driver_pb2.pyi @@ -5,6 +5,7 @@ isort:skip_file import builtins import flwr.proto.node_pb2 import flwr.proto.task_pb2 +import flwr.proto.transport_pb2 import google.protobuf.descriptor import google.protobuf.internal.containers import google.protobuf.message @@ -21,12 +22,14 @@ class CreateRunRequest(google.protobuf.message.Message): KEY_FIELD_NUMBER: builtins.int VALUE_FIELD_NUMBER: builtins.int key: typing.Text - value: typing.Text + @property + def value(self) -> flwr.proto.transport_pb2.Scalar: ... def __init__(self, *, key: typing.Text = ..., - value: typing.Text = ..., + value: typing.Optional[flwr.proto.transport_pb2.Scalar] = ..., ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ... def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... FAB_ID_FIELD_NUMBER: builtins.int @@ -35,12 +38,12 @@ class CreateRunRequest(google.protobuf.message.Message): fab_id: typing.Text fab_version: typing.Text @property - def override_config(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ... + def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... def __init__(self, *, fab_id: typing.Text = ..., fab_version: typing.Text = ..., - override_config: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config"]) -> None: ... global___CreateRunRequest = CreateRunRequest diff --git a/src/py/flwr/proto/exec_pb2.py b/src/py/flwr/proto/exec_pb2.py index 4aee0f4a882f..5f3a9f1e9f7d 100644 --- a/src/py/flwr/proto/exec_pb2.py +++ b/src/py/flwr/proto/exec_pb2.py @@ -12,9 +12,10 @@ _sym_db = _symbol_database.Default() +from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\"\xa4\x01\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x1a\x35\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xb8\x01\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -23,16 +24,16 @@ DESCRIPTOR._options = None _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._options = None _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' - _globals['_STARTRUNREQUEST']._serialized_start=38 - _globals['_STARTRUNREQUEST']._serialized_end=202 - _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=149 - _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=202 - _globals['_STARTRUNRESPONSE']._serialized_start=204 - _globals['_STARTRUNRESPONSE']._serialized_end=238 - _globals['_STREAMLOGSREQUEST']._serialized_start=240 - _globals['_STREAMLOGSREQUEST']._serialized_end=275 - _globals['_STREAMLOGSRESPONSE']._serialized_start=277 - _globals['_STREAMLOGSRESPONSE']._serialized_end=317 - _globals['_EXEC']._serialized_start=320 - _globals['_EXEC']._serialized_end=480 + _globals['_STARTRUNREQUEST']._serialized_start=66 + _globals['_STARTRUNREQUEST']._serialized_end=250 + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=177 + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=250 + _globals['_STARTRUNRESPONSE']._serialized_start=252 + _globals['_STARTRUNRESPONSE']._serialized_end=286 + _globals['_STREAMLOGSREQUEST']._serialized_start=288 + _globals['_STREAMLOGSREQUEST']._serialized_end=323 + _globals['_STREAMLOGSRESPONSE']._serialized_start=325 + _globals['_STREAMLOGSRESPONSE']._serialized_end=365 + _globals['_EXEC']._serialized_start=368 + _globals['_EXEC']._serialized_end=528 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/exec_pb2.pyi b/src/py/flwr/proto/exec_pb2.pyi index 8065fc1de1b4..fc8a615a6b65 100644 --- a/src/py/flwr/proto/exec_pb2.pyi +++ b/src/py/flwr/proto/exec_pb2.pyi @@ -3,6 +3,7 @@ isort:skip_file """ import builtins +import flwr.proto.transport_pb2 import google.protobuf.descriptor import google.protobuf.internal.containers import google.protobuf.message @@ -18,23 +19,25 @@ class StartRunRequest(google.protobuf.message.Message): KEY_FIELD_NUMBER: builtins.int VALUE_FIELD_NUMBER: builtins.int key: typing.Text - value: typing.Text + @property + def value(self) -> flwr.proto.transport_pb2.Scalar: ... def __init__(self, *, key: typing.Text = ..., - value: typing.Text = ..., + value: typing.Optional[flwr.proto.transport_pb2.Scalar] = ..., ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ... def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... FAB_FILE_FIELD_NUMBER: builtins.int OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int fab_file: builtins.bytes @property - def override_config(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ... + def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... def __init__(self, *, fab_file: builtins.bytes = ..., - override_config: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["fab_file",b"fab_file","override_config",b"override_config"]) -> None: ... global___StartRunRequest = StartRunRequest diff --git a/src/py/flwr/proto/run_pb2.py b/src/py/flwr/proto/run_pb2.py index d6531201f647..c4bf382f1cf9 100644 --- a/src/py/flwr/proto/run_pb2.py +++ b/src/py/flwr/proto/run_pb2.py @@ -12,9 +12,10 @@ _sym_db = _symbol_database.Default() +from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\"\xaf\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x1a\x35\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xc3\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -23,12 +24,12 @@ DESCRIPTOR._options = None _globals['_RUN_OVERRIDECONFIGENTRY']._options = None _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' - _globals['_RUN']._serialized_start=37 - _globals['_RUN']._serialized_end=212 - _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=159 - _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=212 - _globals['_GETRUNREQUEST']._serialized_start=214 - _globals['_GETRUNREQUEST']._serialized_end=245 - _globals['_GETRUNRESPONSE']._serialized_start=247 - _globals['_GETRUNRESPONSE']._serialized_end=293 + _globals['_RUN']._serialized_start=65 + _globals['_RUN']._serialized_end=260 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=187 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=260 + _globals['_GETRUNREQUEST']._serialized_start=262 + _globals['_GETRUNREQUEST']._serialized_end=293 + _globals['_GETRUNRESPONSE']._serialized_start=295 + _globals['_GETRUNRESPONSE']._serialized_end=341 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/run_pb2.pyi b/src/py/flwr/proto/run_pb2.pyi index 3c58c04c1734..4db1645da5e2 100644 --- a/src/py/flwr/proto/run_pb2.pyi +++ b/src/py/flwr/proto/run_pb2.pyi @@ -3,6 +3,7 @@ isort:skip_file """ import builtins +import flwr.proto.transport_pb2 import google.protobuf.descriptor import google.protobuf.internal.containers import google.protobuf.message @@ -18,12 +19,14 @@ class Run(google.protobuf.message.Message): KEY_FIELD_NUMBER: builtins.int VALUE_FIELD_NUMBER: builtins.int key: typing.Text - value: typing.Text + @property + def value(self) -> flwr.proto.transport_pb2.Scalar: ... def __init__(self, *, key: typing.Text = ..., - value: typing.Text = ..., + value: typing.Optional[flwr.proto.transport_pb2.Scalar] = ..., ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ... def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... RUN_ID_FIELD_NUMBER: builtins.int @@ -34,13 +37,13 @@ class Run(google.protobuf.message.Message): fab_id: typing.Text fab_version: typing.Text @property - def override_config(self) -> google.protobuf.internal.containers.ScalarMap[typing.Text, typing.Text]: ... + def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... def __init__(self, *, run_id: builtins.int = ..., fab_id: typing.Text = ..., fab_version: typing.Text = ..., - override_config: typing.Optional[typing.Mapping[typing.Text, typing.Text]] = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., ) -> None: ... def ClearField(self, field_name: typing_extensions.Literal["fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config","run_id",b"run_id"]) -> None: ... global___Run = Run diff --git a/src/py/flwr/proto/task_pb2.py b/src/py/flwr/proto/task_pb2.py index 5f6e9e7be583..3e044f9ec846 100644 --- a/src/py/flwr/proto/task_pb2.py +++ b/src/py/flwr/proto/task_pb2.py @@ -14,21 +14,20 @@ from flwr.proto import node_pb2 as flwr_dot_proto_dot_node__pb2 from flwr.proto import recordset_pb2 as flwr_dot_proto_dot_recordset__pb2 -from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2 from flwr.proto import error_pb2 as flwr_dot_proto_dot_error__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/task.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x1a\x66lwr/proto/recordset.proto\x1a\x1a\x66lwr/proto/transport.proto\x1a\x16\x66lwr/proto/error.proto\"\x89\x02\n\x04Task\x12\"\n\x08producer\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\"\n\x08\x63onsumer\x18\x02 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x12\n\ncreated_at\x18\x03 \x01(\x01\x12\x14\n\x0c\x64\x65livered_at\x18\x04 \x01(\t\x12\x11\n\tpushed_at\x18\x05 \x01(\x01\x12\x0b\n\x03ttl\x18\x06 \x01(\x01\x12\x10\n\x08\x61ncestry\x18\x07 \x03(\t\x12\x11\n\ttask_type\x18\x08 \x01(\t\x12(\n\trecordset\x18\t \x01(\x0b\x32\x15.flwr.proto.RecordSet\x12 \n\x05\x65rror\x18\n \x01(\x0b\x32\x11.flwr.proto.Error\"\\\n\x07TaskIns\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x10\n\x08group_id\x18\x02 \x01(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\x12\x12\x1e\n\x04task\x18\x04 \x01(\x0b\x32\x10.flwr.proto.Task\"\\\n\x07TaskRes\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x10\n\x08group_id\x18\x02 \x01(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\x12\x12\x1e\n\x04task\x18\x04 \x01(\x0b\x32\x10.flwr.proto.Taskb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/task.proto\x12\nflwr.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x1a\x66lwr/proto/recordset.proto\x1a\x16\x66lwr/proto/error.proto\"\x89\x02\n\x04Task\x12\"\n\x08producer\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\"\n\x08\x63onsumer\x18\x02 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x12\n\ncreated_at\x18\x03 \x01(\x01\x12\x14\n\x0c\x64\x65livered_at\x18\x04 \x01(\t\x12\x11\n\tpushed_at\x18\x05 \x01(\x01\x12\x0b\n\x03ttl\x18\x06 \x01(\x01\x12\x10\n\x08\x61ncestry\x18\x07 \x03(\t\x12\x11\n\ttask_type\x18\x08 \x01(\t\x12(\n\trecordset\x18\t \x01(\x0b\x32\x15.flwr.proto.RecordSet\x12 \n\x05\x65rror\x18\n \x01(\x0b\x32\x11.flwr.proto.Error\"\\\n\x07TaskIns\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x10\n\x08group_id\x18\x02 \x01(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\x12\x12\x1e\n\x04task\x18\x04 \x01(\x0b\x32\x10.flwr.proto.Task\"\\\n\x07TaskRes\x12\x0f\n\x07task_id\x18\x01 \x01(\t\x12\x10\n\x08group_id\x18\x02 \x01(\t\x12\x0e\n\x06run_id\x18\x03 \x01(\x12\x12\x1e\n\x04task\x18\x04 \x01(\x0b\x32\x10.flwr.proto.Taskb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.task_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_TASK']._serialized_start=141 - _globals['_TASK']._serialized_end=406 - _globals['_TASKINS']._serialized_start=408 - _globals['_TASKINS']._serialized_end=500 - _globals['_TASKRES']._serialized_start=502 - _globals['_TASKRES']._serialized_end=594 + _globals['_TASK']._serialized_start=113 + _globals['_TASK']._serialized_end=378 + _globals['_TASKINS']._serialized_start=380 + _globals['_TASKINS']._serialized_end=472 + _globals['_TASKRES']._serialized_start=474 + _globals['_TASKRES']._serialized_end=566 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/server/driver/grpc_driver.py b/src/py/flwr/server/driver/grpc_driver.py index 84da5882eb73..60439892d946 100644 --- a/src/py/flwr/server/driver/grpc_driver.py +++ b/src/py/flwr/server/driver/grpc_driver.py @@ -24,7 +24,11 @@ from flwr.common import DEFAULT_TTL, EventType, Message, Metadata, RecordSet, event from flwr.common.grpc import create_channel from flwr.common.logger import log -from flwr.common.serde import message_from_taskres, message_to_taskins +from flwr.common.serde import ( + message_from_taskres, + message_to_taskins, + user_config_from_proto, +) from flwr.common.typing import Run from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 GetNodesRequest, @@ -127,7 +131,7 @@ def _init_run(self) -> None: run_id=res.run.run_id, fab_id=res.run.fab_id, fab_version=res.run.fab_version, - override_config=dict(res.run.override_config.items()), + override_config=user_config_from_proto(res.run.override_config), ) @property diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index 0169946e237d..b6baca0dff54 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -19,7 +19,7 @@ import sys from logging import DEBUG, INFO, WARN from pathlib import Path -from typing import Dict, Optional +from typing import Optional from flwr.common import Context, EventType, RecordSet, event from flwr.common.config import ( @@ -30,6 +30,7 @@ ) from flwr.common.logger import log, update_console_handler, warn_deprecated_feature from flwr.common.object_ref import load_app +from flwr.common.typing import UserConfig from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 CreateRunRequest, CreateRunResponse, @@ -45,7 +46,7 @@ def run( driver: Driver, server_app_dir: str, - server_app_run_config: Dict[str, str], + server_app_run_config: UserConfig, server_app_attr: Optional[str] = None, loaded_server_app: Optional[ServerApp] = None, ) -> None: diff --git a/src/py/flwr/server/superlink/driver/driver_servicer.py b/src/py/flwr/server/superlink/driver/driver_servicer.py index 7f8ded3bdb85..0741138d2dd1 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer.py @@ -23,6 +23,7 @@ import grpc from flwr.common.logger import log +from flwr.common.serde import user_config_from_proto, user_config_to_proto from flwr.proto import driver_pb2_grpc # pylint: disable=E0611 from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 CreateRunRequest, @@ -72,7 +73,7 @@ def CreateRun( run_id = state.create_run( request.fab_id, request.fab_version, - dict(request.override_config.items()), + user_config_from_proto(request.override_config), ) return CreateRunResponse(run_id=run_id) @@ -149,8 +150,18 @@ def GetRun( # Retrieve run information run = state.get_run(request.run_id) - run_proto = None if run is None else Run(**vars(run)) - return GetRunResponse(run=run_proto) + + if run is None: + return GetRunResponse() + + return GetRunResponse( + run=Run( + run_id=run.run_id, + fab_id=run.fab_id, + fab_version=run.fab_version, + override_config=user_config_to_proto(run.override_config), + ) + ) def _raise_if(validation_error: bool, detail: str) -> None: diff --git a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py index b70cd54035fe..30865f04d373 100644 --- a/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py +++ b/src/py/flwr/server/superlink/fleet/message_handler/message_handler.py @@ -19,6 +19,7 @@ from typing import List, Optional from uuid import UUID +from flwr.common.serde import user_config_to_proto from flwr.proto.fleet_pb2 import ( # pylint: disable=E0611 CreateNodeRequest, CreateNodeResponse, @@ -113,5 +114,15 @@ def get_run( ) -> GetRunResponse: """Get run information.""" run = state.get_run(request.run_id) - run_proto = None if run is None else Run(**vars(run)) - return GetRunResponse(run=run_proto) + + if run is None: + return GetRunResponse() + + return GetRunResponse( + run=Run( + run_id=run.run_id, + fab_id=run.fab_id, + fab_version=run.fab_version, + override_config=user_config_to_proto(run.override_config), + ) + ) diff --git a/src/py/flwr/server/superlink/state/in_memory_state.py b/src/py/flwr/server/superlink/state/in_memory_state.py index bc4bd4478a23..beb25ba4e84f 100644 --- a/src/py/flwr/server/superlink/state/in_memory_state.py +++ b/src/py/flwr/server/superlink/state/in_memory_state.py @@ -23,7 +23,7 @@ from flwr.common import log, now from flwr.common.constant import NODE_ID_NUM_BYTES, RUN_ID_NUM_BYTES -from flwr.common.typing import Run +from flwr.common.typing import Run, UserConfig from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 from flwr.server.superlink.state.state import State from flwr.server.utils import validate_task_ins_or_res @@ -279,7 +279,7 @@ def create_run( self, fab_id: str, fab_version: str, - override_config: Dict[str, str], + override_config: UserConfig, ) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" # Sample a random int64 as run_id diff --git a/src/py/flwr/server/superlink/state/sqlite_state.py b/src/py/flwr/server/superlink/state/sqlite_state.py index ea6f349b9f9a..bd3b6ebabd83 100644 --- a/src/py/flwr/server/superlink/state/sqlite_state.py +++ b/src/py/flwr/server/superlink/state/sqlite_state.py @@ -25,7 +25,7 @@ from flwr.common import log, now from flwr.common.constant import NODE_ID_NUM_BYTES, RUN_ID_NUM_BYTES -from flwr.common.typing import Run +from flwr.common.typing import Run, UserConfig from flwr.proto.node_pb2 import Node # pylint: disable=E0611 from flwr.proto.recordset_pb2 import RecordSet # pylint: disable=E0611 from flwr.proto.task_pb2 import Task, TaskIns, TaskRes # pylint: disable=E0611 @@ -619,7 +619,7 @@ def create_run( self, fab_id: str, fab_version: str, - override_config: Dict[str, str], + override_config: UserConfig, ) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" # Sample a random int64 as run_id diff --git a/src/py/flwr/server/superlink/state/state.py b/src/py/flwr/server/superlink/state/state.py index c93f6ba756b8..23c95805948e 100644 --- a/src/py/flwr/server/superlink/state/state.py +++ b/src/py/flwr/server/superlink/state/state.py @@ -16,10 +16,10 @@ import abc -from typing import Dict, List, Optional, Set +from typing import List, Optional, Set from uuid import UUID -from flwr.common.typing import Run +from flwr.common.typing import Run, UserConfig from flwr.proto.task_pb2 import TaskIns, TaskRes # pylint: disable=E0611 @@ -161,7 +161,7 @@ def create_run( self, fab_id: str, fab_version: str, - override_config: Dict[str, str], + override_config: UserConfig, ) -> int: """Create a new run for the specified `fab_id` and `fab_version`.""" diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py index 895272c2fd79..90e932aa8015 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy.py @@ -82,7 +82,7 @@ def _submit_job(self, message: Message, timeout: Optional[float]) -> Message: # Retrieve context context = self.proxy_state.retrieve_context(run_id=run_id) - partition_id_str = context.node_config[PARTITION_ID_KEY] + partition_id_str = str(context.node_config[PARTITION_ID_KEY]) try: self.actor_pool.submit_client_job( diff --git a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py index 62e0cfd61c99..a0df3fc1eb8e 100644 --- a/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py +++ b/src/py/flwr/simulation/ray_transport/ray_client_proxy_test.py @@ -231,7 +231,7 @@ def _load_app() -> ClientApp: # register and retrieve context node_states[node_id].register_context(run_id=run_id) context = node_states[node_id].retrieve_context(run_id=run_id) - partition_id_str = context.node_config[PARTITION_ID_KEY] + partition_id_str = str(context.node_config[PARTITION_ID_KEY]) pool.submit_client_job( lambda a, c_fn, j_fn, nid_, state: a.run.remote(c_fn, j_fn, nid_, state), (_load_app, message, partition_id_str, context), diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 1b7c6f87591e..7cebb90451d6 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -25,7 +25,7 @@ from logging import DEBUG, ERROR, INFO, WARNING from pathlib import Path from time import sleep -from typing import Dict, List, Optional +from typing import List, Optional from flwr.cli.config_utils import load_and_validate from flwr.client import ClientApp @@ -33,7 +33,7 @@ from flwr.common.config import get_fused_config_from_dir, parse_config_args from flwr.common.constant import RUN_ID_NUM_BYTES from flwr.common.logger import set_logger_propagation, update_console_handler -from flwr.common.typing import Run +from flwr.common.typing import Run, UserConfig from flwr.server.driver import Driver, InMemoryDriver from flwr.server.run_serverapp import run as run_server_app from flwr.server.server_app import ServerApp @@ -238,7 +238,7 @@ def run_simulation( def run_serverapp_th( server_app_attr: Optional[str], server_app: Optional[ServerApp], - server_app_run_config: Dict[str, str], + server_app_run_config: UserConfig, driver: Driver, app_dir: str, f_stop: threading.Event, @@ -254,7 +254,7 @@ def server_th_with_start_checks( exception_event: threading.Event, _driver: Driver, _server_app_dir: str, - _server_app_run_config: Dict[str, str], + _server_app_run_config: UserConfig, _server_app_attr: Optional[str], _server_app: Optional[ServerApp], ) -> None: @@ -319,7 +319,7 @@ def _main_loop( client_app_attr: Optional[str] = None, server_app: Optional[ServerApp] = None, server_app_attr: Optional[str] = None, - server_app_run_config: Optional[Dict[str, str]] = None, + server_app_run_config: Optional[UserConfig] = None, ) -> None: """Launch SuperLink with Simulation Engine, then ServerApp on a separate thread.""" # Initialize StateFactory @@ -395,7 +395,7 @@ def _run_simulation( backend_config: Optional[BackendConfig] = None, client_app_attr: Optional[str] = None, server_app_attr: Optional[str] = None, - server_app_run_config: Optional[Dict[str, str]] = None, + server_app_run_config: Optional[UserConfig] = None, app_dir: str = "", flwr_dir: Optional[str] = None, run: Optional[Run] = None, @@ -438,7 +438,7 @@ def _run_simulation( A path to a `ServerApp` module to be loaded: For example: `server:app` or `project.package.module:wrapper.app`." - server_app_run_config : Optional[Dict[str, str]] + server_app_run_config : Optional[UserConfig] Config dictionary that parameterizes the run config. It will be made accesible to the ServerApp. diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index d012d408a9ff..2eb40a7464c9 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -17,7 +17,7 @@ import subprocess from logging import ERROR, INFO from pathlib import Path -from typing import Dict, Optional +from typing import Optional from typing_extensions import override @@ -25,6 +25,8 @@ from flwr.cli.install import install_from_fab from flwr.common.grpc import create_channel from flwr.common.logger import log +from flwr.common.serde import user_config_to_proto +from flwr.common.typing import UserConfig from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611 from flwr.proto.driver_pb2_grpc import DriverStub from flwr.server.driver.grpc_driver import DEFAULT_SERVER_ADDRESS_DRIVER @@ -65,13 +67,13 @@ def __init__( @override def set_config( self, - config: Dict[str, str], + config: UserConfig, ) -> None: """Set executor config arguments. Parameters ---------- - config : Dict[str, str] + config : UserConfig A dictionary for configuration values. Supported configuration key/value pairs: - "superlink": str @@ -84,12 +86,20 @@ def set_config( if not config: return if superlink_address := config.get("superlink"): + if not isinstance(superlink_address, str): + raise ValueError("The `superlink` value should be of type `str`.") self.superlink = superlink_address if root_certificates := config.get("root-certificates"): + if not isinstance(root_certificates, str): + raise ValueError( + "The `root-certificates` value should be of type `str`." + ) self.root_certificates = root_certificates - self.root_certificates_bytes = Path(root_certificates).read_bytes() + self.root_certificates_bytes = Path(str(root_certificates)).read_bytes() if flwr_dir := config.get("flwr-dir"): - self.flwr_dir = flwr_dir + if not isinstance(flwr_dir, str): + raise ValueError("The `flwr-dir` value should be of type `str`.") + self.flwr_dir = str(flwr_dir) def _connect(self) -> None: if self.stub is not None: @@ -105,7 +115,7 @@ def _create_run( self, fab_id: str, fab_version: str, - override_config: Dict[str, str], + override_config: UserConfig, ) -> int: if self.stub is None: self._connect() @@ -115,7 +125,7 @@ def _create_run( req = CreateRunRequest( fab_id=fab_id, fab_version=fab_version, - override_config=override_config, + override_config=user_config_to_proto(override_config), ) res = self.stub.CreateRun(request=req) return int(res.run_id) @@ -124,7 +134,7 @@ def _create_run( def start_run( self, fab_file: bytes, - override_config: Dict[str, str], + override_config: UserConfig, ) -> Optional[RunTracker]: """Start run using the Flower Deployment Engine.""" try: diff --git a/src/py/flwr/superexec/exec_grpc.py b/src/py/flwr/superexec/exec_grpc.py index d90cec3e47cd..a32ebc1b3e35 100644 --- a/src/py/flwr/superexec/exec_grpc.py +++ b/src/py/flwr/superexec/exec_grpc.py @@ -15,12 +15,13 @@ """SuperExec gRPC API.""" from logging import INFO -from typing import Dict, Optional, Tuple +from typing import Optional, Tuple import grpc from flwr.common import GRPC_MAX_MESSAGE_LENGTH from flwr.common.logger import log +from flwr.common.typing import UserConfig from flwr.proto.exec_pb2_grpc import add_ExecServicer_to_server from flwr.server.superlink.fleet.grpc_bidi.grpc_server import generic_create_grpc_server @@ -32,7 +33,7 @@ def run_superexec_api_grpc( address: str, executor: Executor, certificates: Optional[Tuple[bytes, bytes, bytes]], - config: Dict[str, str], + config: UserConfig, ) -> grpc.Server: """Run SuperExec API (gRPC, request-response).""" executor.set_config(config) diff --git a/src/py/flwr/superexec/exec_servicer.py b/src/py/flwr/superexec/exec_servicer.py index 61a7bc289af3..fa54590d3b7b 100644 --- a/src/py/flwr/superexec/exec_servicer.py +++ b/src/py/flwr/superexec/exec_servicer.py @@ -21,6 +21,7 @@ import grpc from flwr.common.logger import log +from flwr.common.serde import user_config_from_proto from flwr.proto import exec_pb2_grpc # pylint: disable=E0611 from flwr.proto.exec_pb2 import ( # pylint: disable=E0611 StartRunRequest, @@ -46,8 +47,7 @@ def StartRun( log(INFO, "ExecServicer.StartRun") run = self.executor.start_run( - request.fab_file, - dict(request.override_config.items()), + request.fab_file, user_config_from_proto(request.override_config) ) if run is None: diff --git a/src/py/flwr/superexec/executor.py b/src/py/flwr/superexec/executor.py index 62d64f366cec..ed941d47e764 100644 --- a/src/py/flwr/superexec/executor.py +++ b/src/py/flwr/superexec/executor.py @@ -17,7 +17,9 @@ from abc import ABC, abstractmethod from dataclasses import dataclass from subprocess import Popen -from typing import Dict, Optional +from typing import Optional + +from flwr.common.typing import UserConfig @dataclass @@ -34,13 +36,13 @@ class Executor(ABC): @abstractmethod def set_config( self, - config: Dict[str, str], + config: UserConfig, ) -> None: """Register provided config as class attributes. Parameters ---------- - config : Optional[Dict[str, str]] + config : UserConfig A dictionary for configuration values. """ @@ -48,7 +50,7 @@ def set_config( def start_run( self, fab_file: bytes, - override_config: Dict[str, str], + override_config: UserConfig, ) -> Optional[RunTracker]: """Start a run using the given Flower FAB ID and version. @@ -59,7 +61,7 @@ def start_run( ---------- fab_file : bytes The Flower App Bundle file bytes. - override_config: Dict[str, str] + override_config: UserConfig The config overrides dict sent by the user (using `flwr run`). Returns diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index 58cc194a16d4..737c037375d7 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -18,7 +18,7 @@ import subprocess import sys from logging import ERROR, INFO, WARN -from typing import Dict, Optional +from typing import Optional from typing_extensions import override @@ -26,6 +26,7 @@ from flwr.cli.install import install_from_fab from flwr.common.constant import RUN_ID_NUM_BYTES from flwr.common.logger import log +from flwr.common.typing import UserConfig from flwr.server.superlink.state.utils import generate_rand_int_from_bytes from .executor import Executor, RunTracker @@ -42,43 +43,46 @@ class SimulationEngine(Executor): def __init__( self, - num_supernodes: Optional[str] = None, + num_supernodes: Optional[int] = None, ) -> None: self.num_supernodes = num_supernodes @override def set_config( self, - config: Dict[str, str], + config: UserConfig, ) -> None: """Set executor config arguments. Parameters ---------- - config : Dict[str, str] + config : UserConfig A dictionary for configuration values. Supported configuration key/value pairs: - - "num-supernodes": str + - "num-supernodes": int Number of nodes to register for the simulation. """ if not config: return if num_supernodes := config.get("num-supernodes"): + if not isinstance(num_supernodes, int): + raise ValueError("The `num-supernodes` value should be of type `int`.") self.num_supernodes = num_supernodes - - # Validate config - if self.num_supernodes is None: + else: log( ERROR, "To start a run with the simulation plugin, please specify " "the number of SuperNodes. This can be done by using the " "`--executor-config` argument when launching the SuperExec.", ) - raise ValueError("`num-supernodes` must not be `None`") + raise ValueError( + "`num-supernodes` must not be `None`, it must be a valid " + "positive integer." + ) @override def start_run( - self, fab_file: bytes, override_config: Dict[str, str] + self, fab_file: bytes, override_config: UserConfig ) -> Optional[RunTracker]: """Start run using the Flower Simulation Engine.""" try: From 488bc9163da0dec5e0379d90f03a5a1bee48481f Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 10:06:24 +0200 Subject: [PATCH 223/595] feat(framework) Send federation config to SuperExec (#3838) Co-authored-by: Daniel J. Beutel --- src/proto/flwr/proto/exec.proto | 1 + src/py/flwr/cli/run/run.py | 5 ++-- src/py/flwr/common/config.py | 7 +++++- src/py/flwr/proto/exec_pb2.py | 28 ++++++++++++--------- src/py/flwr/proto/exec_pb2.pyi | 21 +++++++++++++++- src/py/flwr/superexec/deployment.py | 1 + src/py/flwr/superexec/exec_servicer.py | 4 ++- src/py/flwr/superexec/exec_servicer_test.py | 2 +- src/py/flwr/superexec/executor.py | 3 +++ src/py/flwr/superexec/simulation.py | 7 ++++-- 10 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/proto/flwr/proto/exec.proto b/src/proto/flwr/proto/exec.proto index 0968857bdd71..047b0d0910ff 100644 --- a/src/proto/flwr/proto/exec.proto +++ b/src/proto/flwr/proto/exec.proto @@ -30,6 +30,7 @@ service Exec { message StartRunRequest { bytes fab_file = 1; map override_config = 2; + map federation_config = 3; } message StartRunResponse { sint64 run_id = 1; } message StreamLogsRequest { sint64 run_id = 1; } diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 00588fec4224..1c57b20e3026 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -25,7 +25,7 @@ from flwr.cli.build import build from flwr.cli.config_utils import load_and_validate -from flwr.common.config import parse_config_args +from flwr.common.config import flatten_dict, parse_config_args from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel from flwr.common.logger import log from flwr.common.serde import user_config_to_proto @@ -114,7 +114,7 @@ def run( def _run_with_superexec( - federation: Dict[str, str], + federation: Dict[str, Any], directory: Optional[Path], config_overrides: Optional[List[str]], ) -> None: @@ -168,6 +168,7 @@ def on_channel_state_change(channel_connectivity: str) -> None: override_config=user_config_to_proto( parse_config_args(config_overrides, separator=",") ), + federation_config=user_config_to_proto(flatten_dict(federation.get("options"))), ) res = stub.StartRun(req) typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN) diff --git a/src/py/flwr/common/config.py b/src/py/flwr/common/config.py index c915a3ef1621..c83fd59df184 100644 --- a/src/py/flwr/common/config.py +++ b/src/py/flwr/common/config.py @@ -113,8 +113,13 @@ def get_fused_config(run: Run, flwr_dir: Optional[Path]) -> UserConfig: return get_fused_config_from_dir(project_dir, run.override_config) -def flatten_dict(raw_dict: Dict[str, Any], parent_key: str = "") -> UserConfig: +def flatten_dict( + raw_dict: Optional[Dict[str, Any]], parent_key: str = "" +) -> UserConfig: """Flatten dict by joining nested keys with a given separator.""" + if raw_dict is None: + return {} + items: List[Tuple[str, UserConfigValue]] = [] separator: str = "." for k, v in raw_dict.items(): diff --git a/src/py/flwr/proto/exec_pb2.py b/src/py/flwr/proto/exec_pb2.py index 5f3a9f1e9f7d..6dfb061aff90 100644 --- a/src/py/flwr/proto/exec_pb2.py +++ b/src/py/flwr/proto/exec_pb2.py @@ -15,7 +15,7 @@ from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xb8\x01\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x15\x66lwr/proto/exec.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd3\x02\n\x0fStartRunRequest\x12\x10\n\x08\x66\x61\x62_file\x18\x01 \x01(\x0c\x12H\n\x0foverride_config\x18\x02 \x03(\x0b\x32/.flwr.proto.StartRunRequest.OverrideConfigEntry\x12L\n\x11\x66\x65\x64\x65ration_config\x18\x03 \x03(\x0b\x32\x31.flwr.proto.StartRunRequest.FederationConfigEntry\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\x1aK\n\x15\x46\x65\x64\x65rationConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"\"\n\x10StartRunResponse\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"#\n\x11StreamLogsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x12\"(\n\x12StreamLogsResponse\x12\x12\n\nlog_output\x18\x01 \x01(\t2\xa0\x01\n\x04\x45xec\x12G\n\x08StartRun\x12\x1b.flwr.proto.StartRunRequest\x1a\x1c.flwr.proto.StartRunResponse\"\x00\x12O\n\nStreamLogs\x12\x1d.flwr.proto.StreamLogsRequest\x1a\x1e.flwr.proto.StreamLogsResponse\"\x00\x30\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -24,16 +24,20 @@ DESCRIPTOR._options = None _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._options = None _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' + _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._options = None + _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_options = b'8\001' _globals['_STARTRUNREQUEST']._serialized_start=66 - _globals['_STARTRUNREQUEST']._serialized_end=250 - _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=177 - _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=250 - _globals['_STARTRUNRESPONSE']._serialized_start=252 - _globals['_STARTRUNRESPONSE']._serialized_end=286 - _globals['_STREAMLOGSREQUEST']._serialized_start=288 - _globals['_STREAMLOGSREQUEST']._serialized_end=323 - _globals['_STREAMLOGSRESPONSE']._serialized_start=325 - _globals['_STREAMLOGSRESPONSE']._serialized_end=365 - _globals['_EXEC']._serialized_start=368 - _globals['_EXEC']._serialized_end=528 + _globals['_STARTRUNREQUEST']._serialized_end=405 + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=255 + _globals['_STARTRUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=328 + _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_start=330 + _globals['_STARTRUNREQUEST_FEDERATIONCONFIGENTRY']._serialized_end=405 + _globals['_STARTRUNRESPONSE']._serialized_start=407 + _globals['_STARTRUNRESPONSE']._serialized_end=441 + _globals['_STREAMLOGSREQUEST']._serialized_start=443 + _globals['_STREAMLOGSREQUEST']._serialized_end=478 + _globals['_STREAMLOGSRESPONSE']._serialized_start=480 + _globals['_STREAMLOGSRESPONSE']._serialized_end=520 + _globals['_EXEC']._serialized_start=523 + _globals['_EXEC']._serialized_end=683 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/exec_pb2.pyi b/src/py/flwr/proto/exec_pb2.pyi index fc8a615a6b65..79d54a90856b 100644 --- a/src/py/flwr/proto/exec_pb2.pyi +++ b/src/py/flwr/proto/exec_pb2.pyi @@ -29,17 +29,36 @@ class StartRunRequest(google.protobuf.message.Message): def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ... def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... + class FederationConfigEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: typing.Text + @property + def value(self) -> flwr.proto.transport_pb2.Scalar: ... + def __init__(self, + *, + key: typing.Text = ..., + value: typing.Optional[flwr.proto.transport_pb2.Scalar] = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ... + FAB_FILE_FIELD_NUMBER: builtins.int OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int + FEDERATION_CONFIG_FIELD_NUMBER: builtins.int fab_file: builtins.bytes @property def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... + @property + def federation_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... def __init__(self, *, fab_file: builtins.bytes = ..., override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., + federation_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., ) -> None: ... - def ClearField(self, field_name: typing_extensions.Literal["fab_file",b"fab_file","override_config",b"override_config"]) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["fab_file",b"fab_file","federation_config",b"federation_config","override_config",b"override_config"]) -> None: ... global___StartRunRequest = StartRunRequest class StartRunResponse(google.protobuf.message.Message): diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index 2eb40a7464c9..bd27d6b21017 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -135,6 +135,7 @@ def start_run( self, fab_file: bytes, override_config: UserConfig, + federation_config: UserConfig, ) -> Optional[RunTracker]: """Start run using the Flower Deployment Engine.""" try: diff --git a/src/py/flwr/superexec/exec_servicer.py b/src/py/flwr/superexec/exec_servicer.py index fa54590d3b7b..83aac7bd5fd6 100644 --- a/src/py/flwr/superexec/exec_servicer.py +++ b/src/py/flwr/superexec/exec_servicer.py @@ -47,7 +47,9 @@ def StartRun( log(INFO, "ExecServicer.StartRun") run = self.executor.start_run( - request.fab_file, user_config_from_proto(request.override_config) + request.fab_file, + user_config_from_proto(request.override_config), + user_config_from_proto(request.federation_config), ) if run is None: diff --git a/src/py/flwr/superexec/exec_servicer_test.py b/src/py/flwr/superexec/exec_servicer_test.py index edc91df4530e..e55427572fd9 100644 --- a/src/py/flwr/superexec/exec_servicer_test.py +++ b/src/py/flwr/superexec/exec_servicer_test.py @@ -36,7 +36,7 @@ def test_start_run() -> None: run_res.proc = proc executor = MagicMock() - executor.start_run = lambda _, __: run_res + executor.start_run = lambda _, __, ___: run_res context_mock = MagicMock() diff --git a/src/py/flwr/superexec/executor.py b/src/py/flwr/superexec/executor.py index ed941d47e764..8d630d108b66 100644 --- a/src/py/flwr/superexec/executor.py +++ b/src/py/flwr/superexec/executor.py @@ -51,6 +51,7 @@ def start_run( self, fab_file: bytes, override_config: UserConfig, + federation_config: UserConfig, ) -> Optional[RunTracker]: """Start a run using the given Flower FAB ID and version. @@ -63,6 +64,8 @@ def start_run( The Flower App Bundle file bytes. override_config: UserConfig The config overrides dict sent by the user (using `flwr run`). + federation_config: UserConfig + The federation options dict sent by the user (using `flwr run`). Returns ------- diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index 737c037375d7..be49c83be716 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -82,7 +82,10 @@ def set_config( @override def start_run( - self, fab_file: bytes, override_config: UserConfig + self, + fab_file: bytes, + override_config: UserConfig, + federation_config: UserConfig, ) -> Optional[RunTracker]: """Start run using the Flower Simulation Engine.""" try: @@ -120,7 +123,7 @@ def start_run( "--app", f"{str(fab_path)}", "--num-supernodes", - f"{self.num_supernodes}", + f"{federation_config.get('num-supernodes', self.num_supernodes)}", "--run-id", str(run_id), ] From 7ad7bd4f51fcbd1f9e5289883407d5ff8ad7da23 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:41:27 +0200 Subject: [PATCH 224/595] Update FDS list of supported datasets (#3857) --- datasets/flwr_datasets/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/datasets/flwr_datasets/utils.py b/datasets/flwr_datasets/utils.py index 8dccac180e55..6cea5bd14137 100644 --- a/datasets/flwr_datasets/utils.py +++ b/datasets/flwr_datasets/utils.py @@ -25,14 +25,20 @@ tested_datasets = [ "mnist", + "ylecun/mnist", "cifar10", + "uoft-cs/cifar10", "fashion_mnist", + "zalando-datasets/fashion_mnist", "sasha/dog-food", "zh-plus/tiny-imagenet", "scikit-learn/adult-census-income", "cifar100", + "uoft-cs/cifar100", "svhn", + "ufldl-stanford/svhn", "sentiment140", + "stanfordnlp/sentiment140", "speech_commands", "LIUM/tedlium", "flwrlabs/femnist", From f3602b6ca569fc274486d58752940e667ec45750 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 11:54:43 +0200 Subject: [PATCH 225/595] feat(framework) Use types in template configs (#3875) --- .../new/templates/app/code/client.hf.py.tpl | 8 +++-- .../new/templates/app/code/client.mlx.py.tpl | 34 ++++++++++++++----- .../templates/app/code/client.pytorch.py.tpl | 8 +++-- .../app/code/client.tensorflow.py.tpl | 20 ++++++++--- .../new/templates/app/code/server.hf.py.tpl | 3 +- .../new/templates/app/code/server.jax.py.tpl | 3 +- .../new/templates/app/code/server.mlx.py.tpl | 3 +- .../templates/app/code/server.numpy.py.tpl | 3 +- .../templates/app/code/server.pytorch.py.tpl | 2 +- .../templates/app/code/server.sklearn.py.tpl | 3 +- .../app/code/server.tensorflow.py.tpl | 2 +- .../app/pyproject.flowertune.toml.tpl | 2 +- .../new/templates/app/pyproject.hf.toml.tpl | 4 +-- .../new/templates/app/pyproject.jax.toml.tpl | 2 +- .../new/templates/app/pyproject.mlx.toml.tpl | 12 +++---- .../templates/app/pyproject.numpy.toml.tpl | 2 +- .../templates/app/pyproject.pytorch.toml.tpl | 4 +-- .../templates/app/pyproject.sklearn.toml.tpl | 2 +- .../app/pyproject.tensorflow.toml.tpl | 8 ++--- 19 files changed, 81 insertions(+), 44 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl index 13b071013076..e79952ea09ae 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl @@ -17,10 +17,11 @@ from $import_name.task import ( # Flower client class FlowerClient(NumPyClient): - def __init__(self, net, trainloader, testloader): + def __init__(self, net, trainloader, testloader, local_epochs): self.net = net self.trainloader = trainloader self.testloader = testloader + self.local_epochs = local_epochs def get_parameters(self, config): return get_weights(self.net) @@ -33,7 +34,7 @@ class FlowerClient(NumPyClient): train( self.net, self.trainloader, - epochs=int(self.context.run_config["local-epochs"]), + epochs=self.local_epochs, ) return self.get_parameters(config={}), len(self.trainloader), {} @@ -52,9 +53,10 @@ def client_fn(context: Context): partition_id = int(context.node_config["partition-id"]) num_partitions = int(context.node_config["num-partitions"]) trainloader, valloader = load_data(partition_id, num_partitions) + local_epochs = context.run_config["local-epochs"] # Return Client instance - return FlowerClient(net, trainloader, valloader).to_client() + return FlowerClient(net, trainloader, valloader, local_epochs).to_client() # Flower ClientApp diff --git a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl index fe1f4041a076..3817b325f5e3 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl @@ -19,13 +19,22 @@ from $import_name.task import ( # Define Flower Client and client_fn class FlowerClient(NumPyClient): - def __init__(self, data): - num_layers = int(self.context.run_config["num-layers"]) - hidden_dim = int(self.context.run_config["hidden-dim"]) - num_classes = 10 - batch_size = int(self.context.run_config["batch-size"]) - learning_rate = float(self.context.run_config["lr"]) - num_epochs = int(self.context.run_config["local-epochs"]) + def __init__( + self, + data, + num_layers, + hidden_dim, + num_classes, + batch_size, + learning_rate, + num_epochs, + ): + self.num_layers = num_layers + self.hidden_dim = hidden_dim + self.num_classes = num_classes + self.batch_size = batch_size + self.learning_rate = learning_rate + self.num_epochs = num_epochs self.train_images, self.train_labels, self.test_images, self.test_labels = data self.model = MLP( @@ -65,8 +74,17 @@ def client_fn(context: Context): num_partitions = int(context.node_config["num-partitions"]) data = load_data(partition_id, num_partitions) + num_layers = context.run_config["num-layers"] + hidden_dim = context.run_config["hidden-dim"] + num_classes = 10 + batch_size = context.run_config["batch-size"] + learning_rate = context.run_config["lr"] + num_epochs = context.run_config["local-epochs"] + # Return Client instance - return FlowerClient(data).to_client() + return FlowerClient( + data, num_layers, hidden_dim, num_classes, batch_size, learning_rate, num_epochs + ).to_client() # Flower ClientApp diff --git a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl index 3635843ba0be..6fb201e20b28 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl @@ -16,10 +16,11 @@ from $import_name.task import ( # Define Flower Client and client_fn class FlowerClient(NumPyClient): - def __init__(self, net, trainloader, valloader): + def __init__(self, net, trainloader, valloader, local_epochs): self.net = net self.trainloader = trainloader self.valloader = valloader + self.local_epochs = local_epochs def fit(self, parameters, config): set_weights(self.net, parameters) @@ -27,7 +28,7 @@ class FlowerClient(NumPyClient): self.net, self.trainloader, self.valloader, - int(self.context.run_config["local-epochs"]), + self.local_epochs, DEVICE, ) return get_weights(self.net), len(self.trainloader.dataset), results @@ -44,9 +45,10 @@ def client_fn(context: Context): partition_id = int(context.node_config["partition-id"]) num_partitions = int(context.node_config["num-partitions"]) trainloader, valloader = load_data(partition_id, num_partitions) + local_epochs = context.run_config["local-epochs"] # Return Client instance - return FlowerClient(net, trainloader, valloader).to_client() + return FlowerClient(net, trainloader, valloader, local_epochs).to_client() # Flower ClientApp diff --git a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl index 5702f5b9c0d0..54a7c28dedf9 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl @@ -8,12 +8,17 @@ from $import_name.task import load_data, load_model # Define Flower Client and client_fn class FlowerClient(NumPyClient): - def __init__(self, model, x_train, y_train, x_test, y_test): + def __init__( + self, model, x_train, y_train, x_test, y_test, epochs, batch_size, verbose + ): self.model = model self.x_train = x_train self.y_train = y_train self.x_test = x_test self.y_test = y_test + self.epochs = epochs + self.batch_size = batch_size + self.verbose = verbose def get_parameters(self, config): return self.model.get_weights() @@ -23,9 +28,9 @@ class FlowerClient(NumPyClient): self.model.fit( self.x_train, self.y_train, - epochs=int(self.context.run_config["local-epochs"]), - batch_size=int(self.context.run_config["batch-size"]), - verbose=bool(self.context.run_config.get("verbose")), + epochs=self.epochs, + batch_size=self.batch_size, + verbose=self.verbose, ) return self.model.get_weights(), len(self.x_train), {} @@ -42,9 +47,14 @@ def client_fn(context: Context): partition_id = int(context.node_config["partition-id"]) num_partitions = int(context.node_config["num-partitions"]) x_train, y_train, x_test, y_test = load_data(partition_id, num_partitions) + epochs = context.run_config["local-epochs"] + batch_size = context.run_config["batch-size"] + verbose = context.run_config.get("verbose") # Return Client instance - return FlowerClient(net, x_train, y_train, x_test, y_test).to_client() + return FlowerClient( + net, x_train, y_train, x_test, y_test, epochs, batch_size, verbose + ).to_client() # Flower ClientApp diff --git a/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl index 43fce9e481c6..529c2e66ba77 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl @@ -7,7 +7,7 @@ from flwr.server import ServerApp, ServerAppComponents, ServerConfig def server_fn(context: Context): # Read from config - num_rounds = int(context.run_config["num-server-rounds"]) + num_rounds = context.run_config["num-server-rounds"] # Define strategy strategy = FedAvg( @@ -18,5 +18,6 @@ def server_fn(context: Context): return ServerAppComponents(strategy=strategy, config=config) + # Create ServerApp app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl index 4eb7149de999..c1f53e91eadd 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.jax.py.tpl @@ -7,7 +7,7 @@ from flwr.server import ServerApp, ServerAppComponents, ServerConfig def server_fn(context: Context): # Read from config - num_rounds = int(context.run_config["num-server-rounds"]) + num_rounds = context.run_config["num-server-rounds"] # Define strategy strategy = FedAvg() @@ -15,5 +15,6 @@ def server_fn(context: Context): return ServerAppComponents(strategy=strategy, config=config) + # Create ServerApp app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl index 72aed878553d..05bb7b6203b3 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.mlx.py.tpl @@ -7,7 +7,7 @@ from flwr.server.strategy import FedAvg def server_fn(context: Context): # Read from config - num_rounds = int(context.run_config["num-server-rounds"]) + num_rounds = context.run_config["num-server-rounds"] # Define strategy strategy = FedAvg() @@ -15,5 +15,6 @@ def server_fn(context: Context): return ServerAppComponents(strategy=strategy, config=config) + # Create ServerApp app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl index d324b4f24fed..1313712189b3 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.numpy.py.tpl @@ -7,7 +7,7 @@ from flwr.server.strategy import FedAvg def server_fn(context: Context): # Read from config - num_rounds = int(context.run_config["num-server-rounds"]) + num_rounds = context.run_config["num-server-rounds"] # Define strategy strategy = FedAvg() @@ -15,5 +15,6 @@ def server_fn(context: Context): return ServerAppComponents(strategy=strategy, config=config) + # Create ServerApp app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl index 7ac9508f8a25..d5f1d9332758 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.pytorch.py.tpl @@ -13,7 +13,7 @@ parameters = ndarrays_to_parameters(ndarrays) def server_fn(context: Context): # Read from config - num_rounds = int(context.run_config["num-server-rounds"]) + num_rounds = context.run_config["num-server-rounds"] # Define strategy strategy = FedAvg( diff --git a/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl index d8837798d5a6..d1c8e1606f8f 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.sklearn.py.tpl @@ -7,7 +7,7 @@ from flwr.server.strategy import FedAvg def server_fn(context: Context): # Read from config - num_rounds = int(context.run_config["num-server-rounds"]) + num_rounds = context.run_config["num-server-rounds"] # Define strategy strategy = FedAvg( @@ -19,5 +19,6 @@ def server_fn(context: Context): return ServerAppComponents(strategy=strategy, config=config) + # Create ServerApp app = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl index abd2a977b503..023f0b66c055 100644 --- a/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/server.tensorflow.py.tpl @@ -13,7 +13,7 @@ parameters = ndarrays_to_parameters(load_model().get_weights()) def server_fn(context: Context): # Read from config - num_rounds = int(context.run_config["num-server-rounds"]) + num_rounds = context.run_config["num-server-rounds"] # Define strategy strategy = strategy = FedAvg( diff --git a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl index ca0b25f172fb..191959d0eb59 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl @@ -30,7 +30,7 @@ serverapp = "$import_name.app:server" clientapp = "$import_name.app:client" [tool.flwr.app.config] -num-server-rounds = "3" +num-server-rounds = 3 [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl index a15455a3b225..cd70862ee9bf 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl @@ -28,8 +28,8 @@ serverapp = "$import_name.server_app:app" clientapp = "$import_name.client_app:app" [tool.flwr.app.config] -num-server-rounds = "3" -local-epochs = "1" +num-server-rounds = 3 +local-epochs = 1 [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl index c0cce3e19d06..03dea493c4ea 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.jax.toml.tpl @@ -25,7 +25,7 @@ serverapp = "$import_name.server_app:app" clientapp = "$import_name.client_app:app" [tool.flwr.app.config] -num-server-rounds = "3" +num-server-rounds = 3 [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl index fde693e6c3de..6439faf9ebaa 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.mlx.toml.tpl @@ -25,12 +25,12 @@ serverapp = "$import_name.server_app:app" clientapp = "$import_name.client_app:app" [tool.flwr.app.config] -num-server-rounds = "3" -local-epochs = "1" -num-layers = "2" -hidden-dim = "32" -batch-size = "256" -lr = "0.1" +num-server-rounds = 3 +local-epochs = 1 +num-layers = 2 +hidden-dim = 32 +batch-size = 256 +lr = 0.1 [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl index 543936ed4a89..048c0736765c 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.numpy.toml.tpl @@ -23,7 +23,7 @@ serverapp = "$import_name.server_app:app" clientapp = "$import_name.client_app:app" [tool.flwr.app.config] -num-server-rounds = "3" +num-server-rounds = 3 [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl index d7991c05daf7..b2968b8179ea 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl @@ -25,8 +25,8 @@ serverapp = "$import_name.server_app:app" clientapp = "$import_name.client_app:app" [tool.flwr.app.config] -num-server-rounds = "3" -local-epochs = "1" +num-server-rounds = 3 +local-epochs = 1 [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl index 5c1ffa09aed2..87613a37f2cb 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl @@ -24,7 +24,7 @@ serverapp = "$import_name.server_app:app" clientapp = "$import_name.client_app:app" [tool.flwr.app.config] -num-server-rounds = "3" +num-server-rounds = 3 [tool.flwr.federations] default = "localhost" diff --git a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl index 400689cc541a..4d0df4398fcf 100644 --- a/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +++ b/src/py/flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl @@ -24,10 +24,10 @@ serverapp = "$import_name.server_app:app" clientapp = "$import_name.client_app:app" [tool.flwr.app.config] -num-server-rounds = "3" -local-epochs = "1" -batch-size = "32" -verbose = "" # Empty string means False +num-server-rounds = 3 +local-epochs = 1 +batch-size = 32 +verbose = false [tool.flwr.federations] default = "localhost" From 3c1a154c880e8bb2a0c1ccfdebeed24d5990c377 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 12:30:52 +0200 Subject: [PATCH 226/595] refactor(framework:skip) Add `partner_id` tests (#3872) --- src/py/flwr/common/telemetry_test.py | 40 +++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/common/telemetry_test.py b/src/py/flwr/common/telemetry_test.py index a5eea48443b5..f1ad635773a7 100644 --- a/src/py/flwr/common/telemetry_test.py +++ b/src/py/flwr/common/telemetry_test.py @@ -15,12 +15,14 @@ """Telemetry tests.""" +import os import time import unittest from typing import Callable from unittest import mock +from uuid import uuid4 -from flwr.common.telemetry import EventType, _get_source_id, event +from flwr.common.telemetry import EventType, _get_partner_id, _get_source_id, event class TelemetryTest(unittest.TestCase): @@ -109,3 +111,39 @@ def _new_failing_get_home() -> None: # Assert self.assertEqual(source_id, except_value) + + def test_get_partner_id(self) -> None: + """Test if _get_partner_id returns an ID successfully.""" + # Prepare + generated_id = str(uuid4()) + os.environ["FLWR_TELEMETRY_PARTNER_ID"] = generated_id + + # Execute + partner_id = _get_partner_id() + + # Assert + self.assertEqual(partner_id, generated_id) + + def test_get_partner_id_no_env(self) -> None: + """Test if _get_partner_id returns unavailable without an env variable.""" + # Prepare + os.environ["FLWR_TELEMETRY_PARTNER_ID"] = "" + expected_value = "unavailable" + + # Execute + partner_id = _get_partner_id() + + # Assert + self.assertEqual(partner_id, expected_value) + + def test_get_partner_id_invalid(self) -> None: + """Test if _get_partner_id returns invalid with an incorrect env variable.""" + # Prepare + os.environ["FLWR_TELEMETRY_PARTNER_ID"] = "not a valid ID" + expected_value = "invalid" + + # Execute + partner_id = _get_partner_id() + + # Assert + self.assertEqual(partner_id, expected_value) From 081da74235f2b79bdce71b3eec0dcdcfc2cfe1c1 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 12:53:09 +0200 Subject: [PATCH 227/595] refactor(framework:skip) Rename `directory` variable in `flwr run` (#3876) --- src/py/flwr/cli/run/run.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 1c57b20e3026..04949d255647 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -35,7 +35,7 @@ # pylint: disable-next=too-many-locals def run( - directory: Annotated[ + app_dir: Annotated[ Path, typer.Argument(help="Path of the Flower project to run"), ] = Path("."), @@ -55,7 +55,7 @@ def run( """Run Flower project.""" typer.secho("Loading project configuration... ", fg=typer.colors.BLUE) - pyproject_path = directory / "pyproject.toml" if directory else None + pyproject_path = app_dir / "pyproject.toml" if app_dir else None config, errors, warnings = load_and_validate(path=pyproject_path) if config is None: @@ -108,14 +108,14 @@ def run( raise typer.Exit(code=1) if "address" in federation: - _run_with_superexec(federation, directory, config_overrides) + _run_with_superexec(federation, app_dir, config_overrides) else: - _run_without_superexec(directory, federation, federation_name, config_overrides) + _run_without_superexec(app_dir, federation, federation_name, config_overrides) def _run_with_superexec( federation: Dict[str, Any], - directory: Optional[Path], + app_dir: Optional[Path], config_overrides: Optional[List[str]], ) -> None: @@ -161,7 +161,7 @@ def on_channel_state_change(channel_connectivity: str) -> None: channel.subscribe(on_channel_state_change) stub = ExecStub(channel) - fab_path = build(directory) + fab_path = build(app_dir) req = StartRunRequest( fab_file=Path(fab_path).read_bytes(), From fc7825666235b447acd065c4f61b25e137a89166 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 13:50:52 +0200 Subject: [PATCH 228/595] docs(framework) Fix install instructions for all shells (#3864) Co-authored-by: KarhouTam --- doc/locales/fr/LC_MESSAGES/framework-docs.po | 24 +++++++-------- doc/locales/ko/LC_MESSAGES/framework-docs.po | 22 +++++++------- .../pt_BR/LC_MESSAGES/framework-docs.po | 12 ++++---- .../zh_Hans/LC_MESSAGES/framework-docs.po | 30 +++++++++---------- ...or-how-to-install-development-versions.rst | 6 ++-- doc/source/how-to-install-flower.rst | 2 +- doc/source/how-to-upgrade-to-flower-1.0.rst | 2 +- doc/source/how-to-upgrade-to-flower-next.rst | 2 +- doc/source/ref-changelog.md | 2 +- .../superlink/fleet/vce/backend/__init__.py | 2 +- src/py/flwr/simulation/__init__.py | 2 +- 11 files changed, 53 insertions(+), 53 deletions(-) diff --git a/doc/locales/fr/LC_MESSAGES/framework-docs.po b/doc/locales/fr/LC_MESSAGES/framework-docs.po index 6624d91f9e64..efa10a69531c 100644 --- a/doc/locales/fr/LC_MESSAGES/framework-docs.po +++ b/doc/locales/fr/LC_MESSAGES/framework-docs.po @@ -650,8 +650,8 @@ msgid "``pip install -U --pre flwr`` (without extras)" msgstr "``pip install -U --pre flwr`` (sans les extras)" #: ../../source/contributor-how-to-install-development-versions.rst:33 -msgid "``pip install -U --pre flwr[simulation]`` (with extras)" -msgstr "``pip install -U --pre flwr[simulation]`` (avec les extras)" +msgid "``pip install -U --pre 'flwr[simulation]'`` (with extras)" +msgstr "``pip install -U --pre 'flwr[simulation]'`` (avec les extras)" #: ../../source/contributor-how-to-install-development-versions.rst:35 msgid "" @@ -676,10 +676,10 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:40 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git'`` " "(with extras)" msgstr "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git'`` " "(avec les extras)" #: ../../source/contributor-how-to-install-development-versions.rst:42 @@ -698,11 +698,11 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" -"@branch-name`` (with extras)" +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git``" +"@branch-name'`` (with extras)" msgstr "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" -"@nom-de-branche`` (avec des extras)" +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git``" +"@nom-de-la-branche'`` (avec des extras)" #: ../../source/contributor-how-to-install-development-versions.rst:49 msgid "Open Jupyter Notebooks on Google Colab" @@ -6963,10 +6963,10 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:15 msgid "" -"``python -m pip install -U flwr[simulation]`` (when using " +"``python -m pip install -U 'flwr[simulation]'`` (when using " "``start_simulation``)" msgstr "" -"``python -m pip install -U flwr[simulation]`` (lors de l'utilisation de " +"``python -m pip install -U 'flwr[simulation]'`` (lors de l'utilisation de " "``start_simulation``)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:17 @@ -20007,12 +20007,12 @@ msgid "" "Simulations (using the Virtual Client Engine through `start_simulation`) " "now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " "installing Flower with the `simulation` extra (`pip install " -"flwr[simulation]`)." +"'flwr[simulation]'`)." msgstr "" "Les simulations (utilisant le moteur de client virtuel via " "`start_simulation`) fonctionnent maintenant plus facilement sur les " "Notebooks Jupyter (y compris Google Colab) après avoir installé Flower " -"avec l'option `simulation` (`pip install flwr[simulation]`)." +"avec l'option `simulation` (`pip install 'flwr[simulation]'`)." #: ../../source/ref-changelog.md:887 msgid "" diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index f01f9eaf7bd9..d0ba4f6ed5a1 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/LC_MESSAGES/framework-docs.po @@ -666,8 +666,8 @@ msgid "``pip install -U --pre flwr`` (without extras)" msgstr "``pip install -U --pre flwr`` (extras 제외)" #: ../../source/contributor-how-to-install-development-versions.rst:33 -msgid "``pip install -U --pre flwr[simulation]`` (with extras)" -msgstr "``pip install -U --pre flwr[simulation]`` (extras 포함)" +msgid "``pip install -U --pre 'flwr[simulation]'`` (with extras)" +msgstr "``pip install -U --pre 'flwr[simulation]'`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:35 msgid "" @@ -689,10 +689,10 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:40 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git'`` " "(with extras)" msgstr "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git'`` " "(extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:42 @@ -709,11 +709,11 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" -"name`` (with extras)" +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git@branch-" +"name'`` (with extras)" msgstr "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-" -"name`` (extras 포함)" +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git@branch-" +"name'`` (extras 포함)" #: ../../source/contributor-how-to-install-development-versions.rst:49 msgid "Open Jupyter Notebooks on Google Colab" @@ -7021,10 +7021,10 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:15 msgid "" -"``python -m pip install -U flwr[simulation]`` (when using " +"``python -m pip install -U 'flwr[simulation]'`` (when using " "``start_simulation``)" msgstr "" -"``python -m pip install -U flwr[simulation]``(``start_simulation`` 사용 시)" +"``python -m pip install -U 'flwr[simulation]'``(``start_simulation`` 사용 시)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:17 msgid "" @@ -18594,7 +18594,7 @@ msgid "" "Simulations (using the Virtual Client Engine through `start_simulation`) now " "work more smoothly on Jupyter Notebooks (incl. Google Colab) after " "installing Flower with the `simulation` extra (`pip install " -"flwr[simulation]`)." +"'flwr[simulation]'`)." msgstr "" #: ../../source/ref-changelog.md:887 diff --git a/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po b/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po index 49bb01908421..e50c290432cc 100644 --- a/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po +++ b/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po @@ -674,7 +674,7 @@ msgid "``pip install -U --pre flwr`` (without extras)" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:33 -msgid "``pip install -U --pre flwr[simulation]`` (with extras)" +msgid "``pip install -U --pre 'flwr[simulation]'`` (with extras)" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:35 @@ -695,7 +695,7 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:40 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git'`` " "(with extras)" msgstr "" @@ -711,8 +711,8 @@ msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" -"@branch-name`` (with extras)" +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git" +"@branch-name'`` (with extras)" msgstr "" #: ../../source/contributor-how-to-install-development-versions.rst:49 @@ -5758,7 +5758,7 @@ msgstr "" #: ../../source/how-to-upgrade-to-flower-1.0.rst:15 msgid "" -"``python -m pip install -U flwr[simulation]`` (when using " +"``python -m pip install -U 'flwr[simulation]'`` (when using " "``start_simulation``)" msgstr "" @@ -17221,7 +17221,7 @@ msgid "" "Simulations (using the Virtual Client Engine through `start_simulation`) " "now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " "installing Flower with the `simulation` extra (`pip install " -"flwr[simulation]`)." +"'flwr[simulation]'`)." msgstr "" #: ../../source/ref-changelog.md:887 diff --git a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po b/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po index d07217ea35f7..e9279db19043 100644 --- a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po +++ b/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po @@ -664,11 +664,11 @@ msgstr "从 PyPI 安装 ``flwr`` 预发行版:" #: ../../source/contributor-how-to-install-development-versions.rst:32 msgid "``pip install -U --pre flwr`` (without extras)" -msgstr "`pip install -U -pre flwr``(不含额外功能)" +msgstr "``pip install -U -pre flwr``(不含额外功能)" #: ../../source/contributor-how-to-install-development-versions.rst:33 -msgid "``pip install -U --pre flwr[simulation]`` (with extras)" -msgstr "`pip install -U -pre flwr[simulation]``(包含额外功能)" +msgid "``pip install -U --pre 'flwr[simulation]'`` (with extras)" +msgstr "``pip install -U -pre 'flwr[simulation]'``(包含额外功能)" #: ../../source/contributor-how-to-install-development-versions.rst:35 msgid "" @@ -684,15 +684,15 @@ msgstr "从 GitHub 的默认分支 (``main`) 安装 ``flwr``:" msgid "" "``pip install flwr@git+https://github.com/adap/flower.git`` (without " "extras)" -msgstr "`pip install flwr@git+https://github.com/adap/flower.git`` (不含额外功能)" +msgstr "``pip install flwr@git+https://github.com/adap/flower.git`` (不含额外功能)" #: ../../source/contributor-how-to-install-development-versions.rst:40 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` " +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git'`` " "(with extras)" msgstr "" -"`pip install " -"flwr[simulation]@git+https://github.com/adap/flower.git``(带附加功能)" +"``pip install " +"'flwr[simulation]@git+https://github.com/adap/flower.git'``(带附加功能)" #: ../../source/contributor-how-to-install-development-versions.rst:42 msgid "Install ``flwr`` from a specific GitHub branch (``branch-name``):" @@ -703,14 +703,14 @@ msgid "" "``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` " "(without extras)" msgstr "" -"`pip install flwr@git+https://github.com/adap/flower.git@branch-name`` " +"``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` " "(不含附加功能)" #: ../../source/contributor-how-to-install-development-versions.rst:45 msgid "" -"``pip install flwr[simulation]@git+https://github.com/adap/flower.git" -"@branch-name`` (with extras)" -msgstr "`pip安装flwr[模拟]@git+https://github.com/adap/flower.git@分支名``(带附加功能)" +"``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git" +"@branch-name'`` (with extras)" +msgstr "``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git@分支名'``(带附加功能)" #: ../../source/contributor-how-to-install-development-versions.rst:49 msgid "Open Jupyter Notebooks on Google Colab" @@ -6561,9 +6561,9 @@ msgstr "`python -m pip install -U flwr``(当使用`start_server`和`start_clie #: ../../source/how-to-upgrade-to-flower-1.0.rst:15 msgid "" -"``python -m pip install -U flwr[simulation]`` (when using " +"``python -m pip install -U 'flwr[simulation]'`` (when using " "``start_simulation``)" -msgstr "`python -m pip install -U flwr[simulation]``(当使用`start_simulation``时)" +msgstr "``python -m pip install -U 'flwr[simulation]'``(当使用`start_simulation``时)" #: ../../source/how-to-upgrade-to-flower-1.0.rst:17 msgid "" @@ -21363,10 +21363,10 @@ msgid "" "Simulations (using the Virtual Client Engine through `start_simulation`) " "now work more smoothly on Jupyter Notebooks (incl. Google Colab) after " "installing Flower with the `simulation` extra (`pip install " -"flwr[simulation]`)." +"'flwr[simulation]'`)." msgstr "" "通过 `start_simulation` 在 Jupyter 笔记本(包括 Google Colab)上安装 Flower 并附加 " -"`simulation` (`pip install flwr[simulation]`)后,模拟(通过 `start_simulation` " +"`simulation` (`pip install 'flwr[simulation]'`)后,模拟(通过 `start_simulation` " "使用虚拟客户端引擎)现在可以更流畅地运行。" #: ../../source/ref-changelog.md:887 diff --git a/doc/source/contributor-how-to-install-development-versions.rst b/doc/source/contributor-how-to-install-development-versions.rst index 15e2939ef138..0f0773c85e73 100644 --- a/doc/source/contributor-how-to-install-development-versions.rst +++ b/doc/source/contributor-how-to-install-development-versions.rst @@ -30,19 +30,19 @@ Using pip (recommended on Colab) Install a ``flwr`` pre-release from PyPI: - ``pip install -U --pre flwr`` (without extras) -- ``pip install -U --pre flwr[simulation]`` (with extras) +- ``pip install -U --pre 'flwr[simulation]'`` (with extras) Python packages can be installed from git repositories. Use one of the following commands to install the Flower directly from GitHub. Install ``flwr`` from the default GitHub branch (``main``): - ``pip install flwr@git+https://github.com/adap/flower.git`` (without extras) -- ``pip install flwr[simulation]@git+https://github.com/adap/flower.git`` (with extras) +- ``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git'`` (with extras) Install ``flwr`` from a specific GitHub branch (``branch-name``): - ``pip install flwr@git+https://github.com/adap/flower.git@branch-name`` (without extras) -- ``pip install flwr[simulation]@git+https://github.com/adap/flower.git@branch-name`` (with extras) +- ``pip install 'flwr[simulation]@git+https://github.com/adap/flower.git@branch-name'`` (with extras) Open Jupyter Notebooks on Google Colab diff --git a/doc/source/how-to-install-flower.rst b/doc/source/how-to-install-flower.rst index b00e2ae803ab..b9107995c226 100644 --- a/doc/source/how-to-install-flower.rst +++ b/doc/source/how-to-install-flower.rst @@ -68,7 +68,7 @@ New (possibly unstable) versions of Flower are sometimes available as pre-releas For simulations that use the Virtual Client Engine, ``flwr`` pre-releases should be installed with the ``simulation`` extra:: - python -m pip install -U --pre flwr[simulation] + python -m pip install -U --pre 'flwr[simulation]' Install nightly release ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/how-to-upgrade-to-flower-1.0.rst b/doc/source/how-to-upgrade-to-flower-1.0.rst index 3a55a1a953f5..c0721b0f3736 100644 --- a/doc/source/how-to-upgrade-to-flower-1.0.rst +++ b/doc/source/how-to-upgrade-to-flower-1.0.rst @@ -12,7 +12,7 @@ Here's how to update an existing installation to Flower 1.0 using either pip or - pip: add ``-U`` when installing. - ``python -m pip install -U flwr`` (when using ``start_server`` and ``start_client``) - - ``python -m pip install -U flwr[simulation]`` (when using ``start_simulation``) + - ``python -m pip install -U 'flwr[simulation]'`` (when using ``start_simulation``) - Poetry: update the ``flwr`` dependency in ``pyproject.toml`` and then reinstall (don't forget to delete ``poetry.lock`` via ``rm poetry.lock`` before running ``poetry install``). diff --git a/doc/source/how-to-upgrade-to-flower-next.rst b/doc/source/how-to-upgrade-to-flower-next.rst index a17756247566..e1e94f095b60 100644 --- a/doc/source/how-to-upgrade-to-flower-next.rst +++ b/doc/source/how-to-upgrade-to-flower-next.rst @@ -55,7 +55,7 @@ or if you need Flower Next with simulation: .. code-block:: bash - $ python -m pip install -U flwr[simulation] + $ python -m pip install -U "flwr[simulation]" Ensure you set the following version constraint in your ``requirements.txt`` diff --git a/doc/source/ref-changelog.md b/doc/source/ref-changelog.md index 58fc8b4f69b1..d28446c4dd06 100644 --- a/doc/source/ref-changelog.md +++ b/doc/source/ref-changelog.md @@ -882,7 +882,7 @@ We would like to give our **special thanks** to all the contributors who made Fl - **Improved Virtual Client Engine compatibility with Jupyter Notebook / Google Colab** ([#866](https://github.com/adap/flower/pull/866), [#872](https://github.com/adap/flower/pull/872), [#833](https://github.com/adap/flower/pull/833), [#1036](https://github.com/adap/flower/pull/1036)) - Simulations (using the Virtual Client Engine through `start_simulation`) now work more smoothly on Jupyter Notebooks (incl. Google Colab) after installing Flower with the `simulation` extra (`pip install flwr[simulation]`). + Simulations (using the Virtual Client Engine through `start_simulation`) now work more smoothly on Jupyter Notebooks (incl. Google Colab) after installing Flower with the `simulation` extra (`pip install 'flwr[simulation]'`). - **New Jupyter Notebook code example** ([#833](https://github.com/adap/flower/pull/833)) diff --git a/src/py/flwr/server/superlink/fleet/vce/backend/__init__.py b/src/py/flwr/server/superlink/fleet/vce/backend/__init__.py index d751cf4bcae1..a8c671810a51 100644 --- a/src/py/flwr/server/superlink/fleet/vce/backend/__init__.py +++ b/src/py/flwr/server/superlink/fleet/vce/backend/__init__.py @@ -38,7 +38,7 @@ To install the necessary dependencies, install `flwr` with the `simulation` extra: - pip install -U flwr["simulation"] + pip install -U "flwr[simulation]" """ diff --git a/src/py/flwr/simulation/__init__.py b/src/py/flwr/simulation/__init__.py index 5db90a352e3f..a171347b1507 100644 --- a/src/py/flwr/simulation/__init__.py +++ b/src/py/flwr/simulation/__init__.py @@ -28,7 +28,7 @@ To install the necessary dependencies, install `flwr` with the `simulation` extra: - pip install -U flwr["simulation"] + pip install -U "flwr[simulation]" """ def start_simulation(*args, **kwargs): # type: ignore From d66214b562c589e8e31d47d8f56a0843d327c120 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 14:19:01 +0200 Subject: [PATCH 229/595] refactor(framework) Rename `flwr run` CLI argument (#3880) --- src/py/flwr/cli/run/run.py | 49 ++++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/py/flwr/cli/run/run.py b/src/py/flwr/cli/run/run.py index 04949d255647..ae49981d765e 100644 --- a/src/py/flwr/cli/run/run.py +++ b/src/py/flwr/cli/run/run.py @@ -37,18 +37,21 @@ def run( app_dir: Annotated[ Path, - typer.Argument(help="Path of the Flower project to run"), + typer.Argument(help="Path of the Flower project to run."), ] = Path("."), - federation_name: Annotated[ + federation: Annotated[ Optional[str], - typer.Argument(help="Name of the federation to run the app on"), + typer.Argument(help="Name of the federation to run the app on."), ] = None, config_overrides: Annotated[ Optional[List[str]], typer.Option( "--run-config", "-c", - help="Override configuration key-value pairs", + help="Override configuration key-value pairs, should be of the format:\n\n" + "`--run-config key1=value1,key2=value2 --run-config key3=value3`\n\n" + "Note that `key1`, `key2`, and `key3` in this example need to exist " + "inside the `pyproject.toml` in order to be properly overriden.", ), ] = None, ) -> None: @@ -78,11 +81,9 @@ def run( typer.secho("Success", fg=typer.colors.GREEN) - federation_name = federation_name or config["tool"]["flwr"]["federations"].get( - "default" - ) + federation = federation or config["tool"]["flwr"]["federations"].get("default") - if federation_name is None: + if federation is None: typer.secho( "❌ No federation name was provided and the project's `pyproject.toml` " "doesn't declare a default federation (with a SuperExec address or an " @@ -93,13 +94,13 @@ def run( raise typer.Exit(code=1) # Validate the federation exists in the configuration - federation = config["tool"]["flwr"]["federations"].get(federation_name) - if federation is None: + federation_config = config["tool"]["flwr"]["federations"].get(federation) + if federation_config is None: available_feds = { fed for fed in config["tool"]["flwr"]["federations"] if fed != "default" } typer.secho( - f"❌ There is no `{federation_name}` federation declared in the " + f"❌ There is no `{federation}` federation declared in " "`pyproject.toml`.\n The following federations were found:\n\n" + "\n".join(available_feds), fg=typer.colors.RED, @@ -107,14 +108,14 @@ def run( ) raise typer.Exit(code=1) - if "address" in federation: - _run_with_superexec(federation, app_dir, config_overrides) + if "address" in federation_config: + _run_with_superexec(federation_config, app_dir, config_overrides) else: - _run_without_superexec(app_dir, federation, federation_name, config_overrides) + _run_without_superexec(app_dir, federation_config, federation, config_overrides) def _run_with_superexec( - federation: Dict[str, Any], + federation_config: Dict[str, Any], app_dir: Optional[Path], config_overrides: Optional[List[str]], ) -> None: @@ -123,8 +124,8 @@ def on_channel_state_change(channel_connectivity: str) -> None: """Log channel connectivity.""" log(DEBUG, channel_connectivity) - insecure_str = federation.get("insecure") - if root_certificates := federation.get("root-certificates"): + insecure_str = federation_config.get("insecure") + if root_certificates := federation_config.get("root-certificates"): root_certificates_bytes = Path(root_certificates).read_bytes() if insecure := bool(insecure_str): typer.secho( @@ -152,7 +153,7 @@ def on_channel_state_change(channel_connectivity: str) -> None: raise typer.Exit(code=1) channel = create_channel( - server_address=federation["address"], + server_address=federation_config["address"], insecure=insecure, root_certificates=root_certificates_bytes, max_message_length=GRPC_MAX_MESSAGE_LENGTH, @@ -168,7 +169,9 @@ def on_channel_state_change(channel_connectivity: str) -> None: override_config=user_config_to_proto( parse_config_args(config_overrides, separator=",") ), - federation_config=user_config_to_proto(flatten_dict(federation.get("options"))), + federation_config=user_config_to_proto( + flatten_dict(federation_config.get("options")) + ), ) res = stub.StartRun(req) typer.secho(f"🎊 Successfully started run {res.run_id}", fg=typer.colors.GREEN) @@ -176,18 +179,18 @@ def on_channel_state_change(channel_connectivity: str) -> None: def _run_without_superexec( app_path: Optional[Path], - federation: Dict[str, Any], - federation_name: str, + federation_config: Dict[str, Any], + federation: str, config_overrides: Optional[List[str]], ) -> None: try: - num_supernodes = federation["options"]["num-supernodes"] + num_supernodes = federation_config["options"]["num-supernodes"] except KeyError as err: typer.secho( "❌ The project's `pyproject.toml` needs to declare the number of" " SuperNodes in the simulation. To simulate 10 SuperNodes," " use the following notation:\n\n" - f"[tool.flwr.federations.{federation_name}]\n" + f"[tool.flwr.federations.{federation}]\n" "options.num-supernodes = 10\n", fg=typer.colors.RED, bold=True, From 7ce98551c29fabb59b9afab9b7c20a2e7c148bcc Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Tue, 23 Jul 2024 13:27:43 +0100 Subject: [PATCH 230/595] refactor(framework:skip) Manage `sys.path` in `object_ref` module (#3847) --- src/py/flwr/cli/config_utils.py | 28 +++-- src/py/flwr/cli/config_utils_test.py | 6 +- src/py/flwr/client/supernode/app.py | 55 ++------- src/py/flwr/common/object_ref.py | 105 ++++++++++++++---- src/py/flwr/server/run_serverapp.py | 3 - .../server/superlink/fleet/vce/vce_api.py | 4 +- src/py/flwr/superexec/app.py | 5 +- 7 files changed, 115 insertions(+), 91 deletions(-) diff --git a/src/py/flwr/cli/config_utils.py b/src/py/flwr/cli/config_utils.py index d150e1b5f53d..74eda81f5c16 100644 --- a/src/py/flwr/cli/config_utils.py +++ b/src/py/flwr/cli/config_utils.py @@ -77,6 +77,9 @@ def load_and_validate( A tuple with the optional config in case it exists and is valid and associated errors and warnings. """ + if path is None: + path = Path.cwd() / "pyproject.toml" + config = load(path) if config is None: @@ -86,7 +89,7 @@ def load_and_validate( ] return (None, errors, []) - is_valid, errors, warnings = validate(config, check_module) + is_valid, errors, warnings = validate(config, check_module, path.parent) if not is_valid: return (None, errors, warnings) @@ -94,14 +97,8 @@ def load_and_validate( return (config, errors, warnings) -def load(path: Optional[Path] = None) -> Optional[Dict[str, Any]]: +def load(toml_path: Path) -> Optional[Dict[str, Any]]: """Load pyproject.toml and return as dict.""" - if path is None: - cur_dir = Path.cwd() - toml_path = cur_dir / "pyproject.toml" - else: - toml_path = path - if not toml_path.is_file(): return None @@ -167,7 +164,9 @@ def validate_fields(config: Dict[str, Any]) -> Tuple[bool, List[str], List[str]] def validate( - config: Dict[str, Any], check_module: bool = True + config: Dict[str, Any], + check_module: bool = True, + project_dir: Optional[Union[str, Path]] = None, ) -> Tuple[bool, List[str], List[str]]: """Validate pyproject.toml.""" is_valid, errors, warnings = validate_fields(config) @@ -176,16 +175,15 @@ def validate( return False, errors, warnings # Validate serverapp - is_valid, reason = object_ref.validate( - config["tool"]["flwr"]["app"]["components"]["serverapp"], check_module - ) + serverapp_ref = config["tool"]["flwr"]["app"]["components"]["serverapp"] + is_valid, reason = object_ref.validate(serverapp_ref, check_module, project_dir) + if not is_valid and isinstance(reason, str): return False, [reason], [] # Validate clientapp - is_valid, reason = object_ref.validate( - config["tool"]["flwr"]["app"]["components"]["clientapp"], check_module - ) + clientapp_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"] + is_valid, reason = object_ref.validate(clientapp_ref, check_module, project_dir) if not is_valid and isinstance(reason, str): return False, [reason], [] diff --git a/src/py/flwr/cli/config_utils_test.py b/src/py/flwr/cli/config_utils_test.py index 077f254fb914..cad6714521e3 100644 --- a/src/py/flwr/cli/config_utils_test.py +++ b/src/py/flwr/cli/config_utils_test.py @@ -79,7 +79,7 @@ def test_load_pyproject_toml_load_from_cwd(tmp_path: Path) -> None: f.write(textwrap.dedent(pyproject_toml_content)) # Execute - config = load() + config = load(toml_path=Path.cwd() / "pyproject.toml") # Assert assert config == expected_config @@ -135,7 +135,7 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: } # Current directory - origin = os.getcwd() + origin = Path.cwd() try: # Change into the temporary directory @@ -144,7 +144,7 @@ def test_load_pyproject_toml_from_path(tmp_path: Path) -> None: f.write(textwrap.dedent(pyproject_toml_content)) # Execute - config = load(path=tmp_path / "pyproject.toml") + config = load(toml_path=tmp_path / "pyproject.toml") # Assert assert config == expected_config diff --git a/src/py/flwr/client/supernode/app.py b/src/py/flwr/client/supernode/app.py index f3fb0e97805a..2c60f803f960 100644 --- a/src/py/flwr/client/supernode/app.py +++ b/src/py/flwr/client/supernode/app.py @@ -62,8 +62,8 @@ def run_supernode() -> None: root_certificates = _get_certificates(args) load_fn = _get_load_client_app_fn( default_app_ref=getattr(args, "client-app"), - dir_arg=args.dir, - flwr_dir_arg=args.flwr_dir, + project_dir=args.dir, + flwr_dir=args.flwr_dir, multi_app=True, ) authentication_keys = _try_setup_client_authentication(args) @@ -100,7 +100,7 @@ def run_client_app() -> None: root_certificates = _get_certificates(args) load_fn = _get_load_client_app_fn( default_app_ref=getattr(args, "client-app"), - dir_arg=args.dir, + project_dir=args.dir, multi_app=False, ) authentication_keys = _try_setup_client_authentication(args) @@ -176,9 +176,9 @@ def _get_certificates(args: argparse.Namespace) -> Optional[bytes]: def _get_load_client_app_fn( default_app_ref: str, - dir_arg: str, + project_dir: str, multi_app: bool, - flwr_dir_arg: Optional[str] = None, + flwr_dir: Optional[str] = None, ) -> Callable[[str, str], ClientApp]: """Get the load_client_app_fn function. @@ -189,38 +189,21 @@ def _get_load_client_app_fn( If `multi_app` is False, it ignores `fab_id` and `fab_version` and loads a default ClientApp. """ - # Find the Flower directory containing Flower Apps (only for multi-app) - if not multi_app: - flwr_dir = Path("") - else: - if flwr_dir_arg is None: - flwr_dir = get_flwr_dir() - else: - flwr_dir = Path(flwr_dir_arg).absolute() - - inserted_path = None - if not multi_app: log( DEBUG, "Flower SuperNode will load and validate ClientApp `%s`", default_app_ref, ) - # Insert sys.path - dir_path = Path(dir_arg).absolute() - sys.path.insert(0, str(dir_path)) - inserted_path = str(dir_path) - valid, error_msg = validate(default_app_ref) + valid, error_msg = validate(default_app_ref, project_dir=project_dir) if not valid and error_msg: raise LoadClientAppError(error_msg) from None def _load(fab_id: str, fab_version: str) -> ClientApp: + runtime_project_dir = Path(project_dir).absolute() # If multi-app feature is disabled if not multi_app: - # Get sys path to be inserted - dir_path = Path(dir_arg).absolute() - # Set app reference client_app_ref = default_app_ref # If multi-app feature is enabled but the fab id is not specified @@ -231,43 +214,29 @@ def _load(fab_id: str, fab_version: str) -> ClientApp: ) from None log(WARN, "FAB ID is not provided; the default ClientApp will be loaded.") - # Get sys path to be inserted - dir_path = Path(dir_arg).absolute() # Set app reference client_app_ref = default_app_ref # If multi-app feature is enabled else: try: - project_dir = get_project_dir(fab_id, fab_version, flwr_dir) - config = get_project_config(project_dir) + runtime_project_dir = get_project_dir( + fab_id, fab_version, get_flwr_dir(flwr_dir) + ) + config = get_project_config(runtime_project_dir) except Exception as e: raise LoadClientAppError("Failed to load ClientApp") from e - # Get sys path to be inserted - dir_path = Path(project_dir).absolute() - # Set app reference client_app_ref = config["tool"]["flwr"]["app"]["components"]["clientapp"] - # Set sys.path - nonlocal inserted_path - if inserted_path != str(dir_path): - # Remove the previously inserted path - if inserted_path is not None: - sys.path.remove(inserted_path) - # Insert the new path - sys.path.insert(0, str(dir_path)) - - inserted_path = str(dir_path) - # Load ClientApp log( DEBUG, "Loading ClientApp `%s`", client_app_ref, ) - client_app = load_app(client_app_ref, LoadClientAppError, dir_path) + client_app = load_app(client_app_ref, LoadClientAppError, runtime_project_dir) if not isinstance(client_app, ClientApp): raise LoadClientAppError( diff --git a/src/py/flwr/common/object_ref.py b/src/py/flwr/common/object_ref.py index ac52be160c2e..9723c14037a0 100644 --- a/src/py/flwr/common/object_ref.py +++ b/src/py/flwr/common/object_ref.py @@ -33,22 +33,41 @@ """ +_current_sys_path: Optional[str] = None + + def validate( module_attribute_str: str, check_module: bool = True, + project_dir: Optional[Union[str, Path]] = None, ) -> Tuple[bool, Optional[str]]: """Validate object reference. - The object reference string should have the form :. Valid - examples include `client:app` and `project.package.module:wrapper.app`. It must - refer to a module on the PYTHONPATH and the module needs to have the specified - attribute. + Parameters + ---------- + module_attribute_str : str + The reference to the object. It should have the form `:`. + Valid examples include `client:app` and `project.package.module:wrapper.app`. + It must refer to a module on the PYTHONPATH or in the provided `project_dir` + and the module needs to have the specified attribute. + check_module : bool (default: True) + Flag indicating whether to verify the existence of the module and the + specified attribute within it. + project_dir : Optional[Union[str, Path]] (default: None) + The directory containing the module. If None, the current working directory + is used. If `check_module` is True, the `project_dir` will be inserted into + the system path, and the previously inserted `project_dir` will be removed. Returns ------- Tuple[bool, Optional[str]] A boolean indicating whether an object reference is valid and the reason why it might not be. + + Note + ---- + This function will modify `sys.path` by inserting the provided `project_dir` + and removing the previously inserted `project_dir`. """ module_str, _, attributes_str = module_attribute_str.partition(":") if not module_str: @@ -63,6 +82,9 @@ def validate( ) if check_module: + # Set the system path + _set_sys_path(project_dir) + # Load module module = find_spec(module_str) if module and module.origin: @@ -89,18 +111,40 @@ def load_app( # pylint: disable= too-many-branches ) -> Any: """Return the object specified in a module attribute string. - The module/attribute string should have the form :. Valid - examples include `client:app` and `project.package.module:wrapper.app`. It must - refer to a module on the PYTHONPATH, the module needs to have the specified - attribute. + Parameters + ---------- + module_attribute_str : str + The reference to the object. It should have the form `:`. + Valid examples include `client:app` and `project.package.module:wrapper.app`. + It must refer to a module on the PYTHONPATH or in the provided `project_dir` + and the module needs to have the specified attribute. + error_type : Type[Exception] + The type of exception to be raised if the provided `module_attribute_str` is + in an invalid format. + project_dir : Optional[Union[str, Path]], optional (default=None) + The directory containing the module. If None, the current working directory + is used. The `project_dir` will be inserted into the system path, and the + previously inserted `project_dir` will be removed. + + Returns + ------- + Any + The object specified by the module attribute string. + + Note + ---- + This function will modify `sys.path` by inserting the provided `project_dir` + and removing the previously inserted `project_dir`. """ - valid, error_msg = validate(module_attribute_str) + valid, error_msg = validate(module_attribute_str, check_module=False) if not valid and error_msg: raise error_type(error_msg) from None module_str, _, attributes_str = module_attribute_str.partition(":") try: + _set_sys_path(project_dir) + if module_str not in sys.modules: module = importlib.import_module(module_str) # Hack: `tabnet` does not work with `importlib.reload` @@ -116,19 +160,15 @@ def load_app( # pylint: disable= too-many-branches module = sys.modules[module_str] else: module = sys.modules[module_str] + if project_dir is None: - path: Optional[str] = getattr(module, "__file__", None) - if path is not None: - project_dir = str(Path(path).parent) - else: - project_dir = str(Path(project_dir).absolute()) + project_dir = Path.cwd() # Reload cached modules in the project directory - if project_dir is not None: - for m in list(sys.modules.values()): - path = getattr(m, "__file__", None) - if path is not None and path.startswith(project_dir): - importlib.reload(m) + for m in list(sys.modules.values()): + path: Optional[str] = getattr(m, "__file__", None) + if path is not None and path.startswith(str(project_dir)): + importlib.reload(m) except ModuleNotFoundError as err: raise error_type( @@ -140,15 +180,38 @@ def load_app( # pylint: disable= too-many-branches try: for attribute_str in attributes_str.split("."): attribute = getattr(attribute, attribute_str) - except AttributeError: + except AttributeError as err: raise error_type( f"Unable to load attribute {attributes_str} from module {module_str}" f"{OBJECT_REF_HELP_STR}", - ) from None + ) from err return attribute +def _set_sys_path(directory: Optional[Union[str, Path]]) -> None: + """Set the system path.""" + if directory is None: + directory = Path.cwd() + else: + directory = Path(directory).absolute() + + # If the directory has already been added to `sys.path`, return + if str(directory) in sys.path: + return + + # Remove the old path if it exists and is not `""`. + global _current_sys_path # pylint: disable=global-statement + if _current_sys_path is not None: + sys.path.remove(_current_sys_path) + + # Add the new path to sys.path + sys.path.insert(0, str(directory)) + + # Update the current_sys_path + _current_sys_path = str(directory) + + def _find_attribute_in_module(file_path: str, attribute_name: str) -> bool: """Check if attribute_name exists in module's abstract symbolic tree.""" with open(file_path, encoding="utf-8") as file: diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index b6baca0dff54..3f062351e48d 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -57,9 +57,6 @@ def run( "but not both." ) - if server_app_dir is not None: - sys.path.insert(0, str(Path(server_app_dir).absolute())) - # Load ServerApp if needed def _load() -> ServerApp: if server_app_attr: diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 320f839e9e01..4327783045ad 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -347,8 +347,8 @@ def _load() -> ClientApp: if client_app_attr: app = _get_load_client_app_fn( default_app_ref=client_app_attr, - dir_arg=app_dir, - flwr_dir_arg=flwr_dir, + project_dir=app_dir, + flwr_dir=flwr_dir, multi_app=True, )(run.fab_id, run.fab_version) diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py index 9f1753ce041b..b852577d943f 100644 --- a/src/py/flwr/superexec/app.py +++ b/src/py/flwr/superexec/app.py @@ -163,11 +163,8 @@ def _load_executor( args: argparse.Namespace, ) -> Executor: """Get the executor plugin.""" - if args.executor_dir is not None: - sys.path.insert(0, args.executor_dir) - executor_ref: str = args.executor - valid, error_msg = validate(executor_ref) + valid, error_msg = validate(executor_ref, project_dir=args.executor_dir) if not valid and error_msg: raise LoadExecutorError(error_msg) from None From 54b26828e6f632cea69fc871fa025cfb57b8c005 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 23 Jul 2024 13:41:43 +0100 Subject: [PATCH 231/595] feat(framework) Use dataset caching in `flwr new` templates (#3877) --- .../cli/new/templates/app/code/task.hf.py.tpl | 14 +++++++++++++- .../cli/new/templates/app/code/task.mlx.py.tpl | 15 ++++++++++++++- .../new/templates/app/code/task.pytorch.py.tpl | 15 +++++++++++++-- .../new/templates/app/code/task.tensorflow.py.tpl | 14 +++++++++++++- 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl index eb43acfce976..51a21dd17418 100644 --- a/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl @@ -10,15 +10,27 @@ from torch.utils.data import DataLoader from transformers import AutoTokenizer, DataCollatorWithPadding from flwr_datasets import FederatedDataset +from flwr_datasets.partitioner import IidPartitioner + warnings.filterwarnings("ignore", category=UserWarning) DEVICE = torch.device("cpu") CHECKPOINT = "distilbert-base-uncased" # transformer model checkpoint +fds = None # Cache FederatedDataset + + def load_data(partition_id: int, num_partitions: int): """Load IMDB data (training and eval)""" - fds = FederatedDataset(dataset="imdb", partitioners={"train": num_partitions}) + # Only initialize `FederatedDataset` once + global fds + if fds is None: + partitioner = IidPartitioner(num_partitions=num_partitions) + fds = FederatedDataset( + dataset="stanfordnlp/imdb", + partitioners={"train": partitioner}, + ) partition = fds.load_partition(partition_id) # Divide data: 80% train, 20% test partition_train_test = partition.train_test_split(test_size=0.2, seed=42) diff --git a/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl index 88053b0cd590..1759fe8c0b42 100644 --- a/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/task.mlx.py.tpl @@ -5,10 +5,12 @@ import mlx.nn as nn import numpy as np from datasets.utils.logging import disable_progress_bar from flwr_datasets import FederatedDataset +from flwr_datasets.partitioner import IidPartitioner disable_progress_bar() + class MLP(nn.Module): """A simple MLP.""" @@ -43,8 +45,19 @@ def batch_iterate(batch_size, X, y): yield X[ids], y[ids] +fds = None # Cache FederatedDataset + + def load_data(partition_id: int, num_partitions: int): - fds = FederatedDataset(dataset="mnist", partitioners={"train": num_partitions}) + # Only initialize `FederatedDataset` once + global fds + if fds is None: + partitioner = IidPartitioner(num_partitions=num_partitions) + fds = FederatedDataset( + dataset="ylecun/mnist", + partitioners={"train": partitioner}, + trust_remote_code=True, + ) partition = fds.load_partition(partition_id) partition_splits = partition.train_test_split(test_size=0.2, seed=42) diff --git a/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl index d5971ffb6ce5..bd2fad5be589 100644 --- a/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/task.pytorch.py.tpl @@ -6,9 +6,10 @@ import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader -from torchvision.datasets import CIFAR10 from torchvision.transforms import Compose, Normalize, ToTensor from flwr_datasets import FederatedDataset +from flwr_datasets.partitioner import IidPartitioner + DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") @@ -34,9 +35,19 @@ class Net(nn.Module): return self.fc3(x) +fds = None # Cache FederatedDataset + + def load_data(partition_id: int, num_partitions: int): """Load partition CIFAR10 data.""" - fds = FederatedDataset(dataset="cifar10", partitioners={"train": num_partitions}) + # Only initialize `FederatedDataset` once + global fds + if fds is None: + partitioner = IidPartitioner(num_partitions=num_partitions) + fds = FederatedDataset( + dataset="uoft-cs/cifar10", + partitioners={"train": partitioner}, + ) partition = fds.load_partition(partition_id) # Divide data on each node: 80% train, 20% test partition_train_test = partition.train_test_split(test_size=0.2, seed=42) diff --git a/src/py/flwr/cli/new/templates/app/code/task.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.tensorflow.py.tpl index fa07f93713ed..c495774ffeb3 100644 --- a/src/py/flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/task.tensorflow.py.tpl @@ -4,11 +4,13 @@ import os import tensorflow as tf from flwr_datasets import FederatedDataset +from flwr_datasets.partitioner import IidPartitioner # Make TensorFlow log less verbose os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3" + def load_model(): # Load model and data (MobileNetV2, CIFAR-10) model = tf.keras.applications.MobileNetV2((32, 32, 3), classes=10, weights=None) @@ -16,9 +18,19 @@ def load_model(): return model +fds = None # Cache FederatedDataset + + def load_data(partition_id, num_partitions): # Download and partition dataset - fds = FederatedDataset(dataset="cifar10", partitioners={"train": num_partitions}) + # Only initialize `FederatedDataset` once + global fds + if fds is None: + partitioner = IidPartitioner(num_partitions=num_partitions) + fds = FederatedDataset( + dataset="uoft-cs/cifar10", + partitioners={"train": partitioner}, + ) partition = fds.load_partition(partition_id, "train") partition.set_format("numpy") From 4636b12f1c63c8bd68579ec425421774ab183cb2 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 23 Jul 2024 14:55:10 +0200 Subject: [PATCH 232/595] feat(framework:skip) Upgrade system Python pip and setuptools (#3820) Signed-off-by: Robert Steiner --- src/docker/base/alpine/Dockerfile | 8 ++++++-- src/docker/base/ubuntu/Dockerfile | 13 ++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/docker/base/alpine/Dockerfile b/src/docker/base/alpine/Dockerfile index 9e58d82e3bda..441e0fdd9b85 100644 --- a/src/docker/base/alpine/Dockerfile +++ b/src/docker/base/alpine/Dockerfile @@ -26,7 +26,7 @@ ARG PYTHON_VERSION=3.11 ARG DISTRO=alpine ARG DISTRO_VERSION=3.19 -FROM python:${PYTHON_VERSION}-${DISTRO}${DISTRO_VERSION} as compile +FROM python:${PYTHON_VERSION}-${DISTRO}${DISTRO_VERSION} AS compile # Install system dependencies RUN apk add --no-cache \ @@ -49,7 +49,11 @@ RUN pip install -U --no-cache-dir \ setuptools==${SETUPTOOLS_VERSION} \ ${FLWR_PACKAGE}==${FLWR_VERSION} -FROM python:${PYTHON_VERSION}-${DISTRO}${DISTRO_VERSION} as base +FROM python:${PYTHON_VERSION}-${DISTRO}${DISTRO_VERSION} AS base + +# Upgrade system Python pip and setuptools +# hadolint ignore=DL3013 +RUN pip install -U --no-cache-dir pip setuptools # required by the grpc package RUN apk add --no-cache \ diff --git a/src/docker/base/ubuntu/Dockerfile b/src/docker/base/ubuntu/Dockerfile index 960ed07edf96..31cc8381b7c5 100644 --- a/src/docker/base/ubuntu/Dockerfile +++ b/src/docker/base/ubuntu/Dockerfile @@ -16,7 +16,7 @@ # hadolint global ignore=DL3008 ARG DISTRO=ubuntu ARG DISTRO_VERSION=22.04 -FROM $DISTRO:$DISTRO_VERSION as python +FROM $DISTRO:$DISTRO_VERSION AS python ENV DEBIAN_FRONTEND=noninteractive @@ -50,9 +50,12 @@ RUN LATEST=$(pyenv latest -k ${PYTHON_VERSION}) \ ENV PATH=/usr/local/bin/python/bin:$PATH -# Use a virtual environment to ensure that Python packages are installed in the same location -# regardless of whether the subsequent image build is run with the app or the root user -RUN python -m venv /python/venv +# Upgrade system Python pip and setuptools +# hadolint ignore=DL3013 +RUN pip install -U --no-cache-dir pip setuptools \ + # Use a virtual environment to ensure that Python packages are installed in the same location + # regardless of whether the subsequent image build is run with the app or the root user + && python -m venv /python/venv ENV PATH=/python/venv/bin:$PATH ARG PIP_VERSION @@ -64,7 +67,7 @@ RUN pip install -U --no-cache-dir \ setuptools==${SETUPTOOLS_VERSION} \ ${FLWR_PACKAGE}==${FLWR_VERSION} -FROM $DISTRO:$DISTRO_VERSION as base +FROM $DISTRO:$DISTRO_VERSION AS base COPY --from=python /usr/local/bin/python /usr/local/bin/python From d32d3123cb990494f42077a837957e0a3e5a25e0 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 23 Jul 2024 14:03:29 +0100 Subject: [PATCH 233/595] fix(framework:skip) Convert `UserConfig` to `str` for simulation executor (#3878) --- src/py/flwr/superexec/simulation.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index be49c83be716..5f0b5bf814fa 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -32,6 +32,25 @@ from .executor import Executor, RunTracker +def _user_config_to_str(user_config: UserConfig) -> str: + """Convert override user config to string.""" + user_config_list_str = [] + for key, value in user_config.items(): + if isinstance(value, bool): + user_config_list_str.append(f"{key}={str(value).lower()}") + elif isinstance(value, (int, float)): + user_config_list_str.append(f"{key}={value}") + elif isinstance(value, str): + user_config_list_str.append(f'{key}="{value}"') + else: + raise ValueError( + "Only types `bool`, `float`, `int` and `str` are supported" + ) + + user_config_str = ",".join(user_config_list_str) + return user_config_str + + class SimulationEngine(Executor): """Simulation engine executor. @@ -129,7 +148,8 @@ def start_run( ] if override_config: - command.extend(["--run-config", f"{override_config}"]) + override_config_str = _user_config_to_str(override_config) + command.extend(["--run-config", f"{override_config_str}"]) # Start Simulation proc = subprocess.run( # pylint: disable=consider-using-with From 3f79f140b7a23432ab88a7dc9e29a9d6fd2d2a94 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 23 Jul 2024 14:57:41 +0100 Subject: [PATCH 234/595] fix(framework) Remove `str` casting for node config (#3886) --- src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl | 4 ++-- src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl | 4 ++-- src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl | 4 ++-- src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl | 4 ++-- .../flwr/cli/new/templates/app/code/client.tensorflow.py.tpl | 4 ++-- src/py/flwr/server/superlink/fleet/vce/vce_api.py | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl index e79952ea09ae..5a2037897d4d 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl @@ -50,8 +50,8 @@ def client_fn(context: Context): CHECKPOINT, num_labels=2 ).to(DEVICE) - partition_id = int(context.node_config["partition-id"]) - num_partitions = int(context.node_config["num-partitions"]) + partition_id = context.node_config["partition-id"] + num_partitions = context.node_config["num-partitions"] trainloader, valloader = load_data(partition_id, num_partitions) local_epochs = context.run_config["local-epochs"] diff --git a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl index 3817b325f5e3..a28c59eda232 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.mlx.py.tpl @@ -70,8 +70,8 @@ class FlowerClient(NumPyClient): def client_fn(context: Context): - partition_id = int(context.node_config["partition-id"]) - num_partitions = int(context.node_config["num-partitions"]) + partition_id = context.node_config["partition-id"] + num_partitions = context.node_config["num-partitions"] data = load_data(partition_id, num_partitions) num_layers = context.run_config["num-layers"] diff --git a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl index 6fb201e20b28..ec7f5ffd9b00 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.pytorch.py.tpl @@ -42,8 +42,8 @@ class FlowerClient(NumPyClient): def client_fn(context: Context): # Load model and data net = Net().to(DEVICE) - partition_id = int(context.node_config["partition-id"]) - num_partitions = int(context.node_config["num-partitions"]) + partition_id = context.node_config["partition-id"] + num_partitions = context.node_config["num-partitions"] trainloader, valloader = load_data(partition_id, num_partitions) local_epochs = context.run_config["local-epochs"] diff --git a/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl index 9642ae490155..af1cdb512bee 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.sklearn.py.tpl @@ -69,8 +69,8 @@ class FlowerClient(NumPyClient): def client_fn(context: Context): - partition_id = int(context.node_config["partition-id"]) - num_partitions = int(context.node_config["num-partitions"]) + partition_id = context.node_config["partition-id"] + num_partitions = context.node_config["num-partitions"] fds = FederatedDataset(dataset="mnist", partitioners={"train": num_partitions}) dataset = fds.load_partition(partition_id, "train").with_format("numpy") diff --git a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl index 54a7c28dedf9..2e1d55d82aa0 100644 --- a/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/client.tensorflow.py.tpl @@ -44,8 +44,8 @@ def client_fn(context: Context): # Load model and data net = load_model() - partition_id = int(context.node_config["partition-id"]) - num_partitions = int(context.node_config["num-partitions"]) + partition_id = context.node_config["partition-id"] + num_partitions = context.node_config["num-partitions"] x_train, y_train, x_test, y_test = load_data(partition_id, num_partitions) epochs = context.run_config["local-epochs"] batch_size = context.run_config["batch-size"] diff --git a/src/py/flwr/server/superlink/fleet/vce/vce_api.py b/src/py/flwr/server/superlink/fleet/vce/vce_api.py index 4327783045ad..f11576d63396 100644 --- a/src/py/flwr/server/superlink/fleet/vce/vce_api.py +++ b/src/py/flwr/server/superlink/fleet/vce/vce_api.py @@ -72,8 +72,8 @@ def _register_node_states( node_states[node_id] = NodeState( node_id=node_id, node_config={ - PARTITION_ID_KEY: str(partition_id), - NUM_PARTITIONS_KEY: str(num_partitions), + PARTITION_ID_KEY: partition_id, + NUM_PARTITIONS_KEY: num_partitions, }, ) From ec088a8cedac216deb7cc262cf9416d3bdaa408e Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 23 Jul 2024 15:19:19 +0100 Subject: [PATCH 235/595] fix(framework:skip) Update `flwrtune` templates to use new `client_fn` and `server_fn` (#3885) --- .../templates/app/code/flwr_tune/app.py.tpl | 37 ++++++++++--------- .../app/code/flwr_tune/client.py.tpl | 8 ++-- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl index a0f781df04a1..637658c5b23c 100644 --- a/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/app.py.tpl @@ -9,8 +9,8 @@ from hydra import compose, initialize from hydra.utils import instantiate from flwr.client import ClientApp -from flwr.common import ndarrays_to_parameters -from flwr.server import ServerApp, ServerConfig +from flwr.common import Context, ndarrays_to_parameters +from flwr.server import ServerApp, ServerAppComponents, ServerConfig from $import_name.client_app import gen_client_fn, get_parameters from $import_name.dataset import get_tokenizer_and_data_collator_and_propt_formatting @@ -67,20 +67,23 @@ init_model = get_model(cfg.model) init_model_parameters = get_parameters(init_model) init_model_parameters = ndarrays_to_parameters(init_model_parameters) -# Instantiate strategy according to config. Here we pass other arguments -# that are only defined at runtime. -strategy = instantiate( - cfg.strategy, - on_fit_config_fn=get_on_fit_config(), - fit_metrics_aggregation_fn=fit_weighted_average, - initial_parameters=init_model_parameters, - evaluate_fn=get_evaluate_fn( - cfg.model, cfg.train.save_every_round, cfg_static.num_rounds, save_path - ), -) +def server_fn(context: Context): + # Instantiate strategy according to config. Here we pass other arguments + # that are only defined at runtime. + strategy = instantiate( + cfg.strategy, + on_fit_config_fn=get_on_fit_config(), + fit_metrics_aggregation_fn=fit_weighted_average, + initial_parameters=init_model_parameters, + evaluate_fn=get_evaluate_fn( + cfg.model, cfg.train.save_every_round, cfg_static.num_rounds, save_path + ), + ) + + config = ServerConfig(num_rounds=cfg_static.num_rounds) + + return ServerAppComponents(strategy=strategy, config=config) + # ServerApp for Flower Next -server = ServerApp( - config=ServerConfig(num_rounds=cfg_static.num_rounds), - strategy=strategy, -) +server = ServerApp(server_fn=server_fn) diff --git a/src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl b/src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl index c0d5842964fd..2472e23ece44 100644 --- a/src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl +++ b/src/py/flwr/cli/new/templates/app/code/flwr_tune/client.py.tpl @@ -10,6 +10,7 @@ from transformers import TrainingArguments from trl import SFTTrainer from flwr.client import NumPyClient +from flwr.common import Context from flwr.common.typing import NDArrays, Scalar from $import_name.dataset import reformat from $import_name.models import cosine_annealing, get_model @@ -102,13 +103,14 @@ def gen_client_fn( model_cfg: DictConfig, train_cfg: DictConfig, save_path: str, -) -> Callable[[str], FlowerClient]: # pylint: disable=too-many-arguments +) -> Callable[[Context], FlowerClient]: # pylint: disable=too-many-arguments """Generate the client function that creates the Flower Clients.""" - def client_fn(cid: str) -> FlowerClient: + def client_fn(context: Context) -> FlowerClient: """Create a Flower client representing a single organization.""" # Let's get the partition corresponding to the i-th client - client_trainset = fds.load_partition(int(cid), "train") + partition_id = context.node_config["partition-id"] + client_trainset = fds.load_partition(partition_id, "train") client_trainset = reformat(client_trainset, llm_task="$llm_challenge_str") return FlowerClient( From 4a9286cc4dc4d2ad7f4b42191d60b8fe7363da9d Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 17:39:01 +0200 Subject: [PATCH 236/595] fix(framework:skip) Use full name for HuggingFace template (#3883) --- .github/workflows/e2e.yml | 2 +- src/py/flwr/cli/new/new.py | 2 +- .../app/code/{client.hf.py.tpl => client.huggingface.py.tpl} | 0 .../app/code/{server.hf.py.tpl => server.huggingface.py.tpl} | 0 .../app/code/{task.hf.py.tpl => task.huggingface.py.tpl} | 0 .../{pyproject.hf.toml.tpl => pyproject.huggingface.toml.tpl} | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename src/py/flwr/cli/new/templates/app/code/{client.hf.py.tpl => client.huggingface.py.tpl} (100%) rename src/py/flwr/cli/new/templates/app/code/{server.hf.py.tpl => server.huggingface.py.tpl} (100%) rename src/py/flwr/cli/new/templates/app/code/{task.hf.py.tpl => task.huggingface.py.tpl} (100%) rename src/py/flwr/cli/new/templates/app/{pyproject.hf.toml.tpl => pyproject.huggingface.toml.tpl} (100%) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 241ec8057c7c..f5ed1d99012a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -213,7 +213,7 @@ jobs: needs: wheel strategy: matrix: - framework: ["numpy", "pytorch", "tensorflow", "hf", "jax", "sklearn"] + framework: ["numpy", "pytorch", "tensorflow", "huggingface", "jax", "sklearn"] name: Template / ${{ matrix.framework }} diff --git a/src/py/flwr/cli/new/new.py b/src/py/flwr/cli/new/new.py index 306f20efccfa..237b8847e193 100644 --- a/src/py/flwr/cli/new/new.py +++ b/src/py/flwr/cli/new/new.py @@ -38,7 +38,7 @@ class MlFramework(str, Enum): PYTORCH = "PyTorch" TENSORFLOW = "TensorFlow" JAX = "JAX" - HUGGINGFACE = "HF" + HUGGINGFACE = "HuggingFace" MLX = "MLX" SKLEARN = "sklearn" FLOWERTUNE = "FlowerTune" diff --git a/src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/client.huggingface.py.tpl similarity index 100% rename from src/py/flwr/cli/new/templates/app/code/client.hf.py.tpl rename to src/py/flwr/cli/new/templates/app/code/client.huggingface.py.tpl diff --git a/src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/server.huggingface.py.tpl similarity index 100% rename from src/py/flwr/cli/new/templates/app/code/server.hf.py.tpl rename to src/py/flwr/cli/new/templates/app/code/server.huggingface.py.tpl diff --git a/src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl b/src/py/flwr/cli/new/templates/app/code/task.huggingface.py.tpl similarity index 100% rename from src/py/flwr/cli/new/templates/app/code/task.hf.py.tpl rename to src/py/flwr/cli/new/templates/app/code/task.huggingface.py.tpl diff --git a/src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl b/src/py/flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl similarity index 100% rename from src/py/flwr/cli/new/templates/app/pyproject.hf.toml.tpl rename to src/py/flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl From edee810e1f4acc9662e2da2d0c834c9064f9d4e4 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Tue, 23 Jul 2024 17:29:40 +0100 Subject: [PATCH 237/595] fix(framework:skip) Fix `SimulationEngine` log (#3888) --- src/py/flwr/superexec/simulation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/py/flwr/superexec/simulation.py b/src/py/flwr/superexec/simulation.py index 5f0b5bf814fa..3c182fd8b002 100644 --- a/src/py/flwr/superexec/simulation.py +++ b/src/py/flwr/superexec/simulation.py @@ -152,9 +152,8 @@ def start_run( command.extend(["--run-config", f"{override_config_str}"]) # Start Simulation - proc = subprocess.run( # pylint: disable=consider-using-with + proc = subprocess.Popen( # pylint: disable=consider-using-with command, - check=True, text=True, ) @@ -162,7 +161,7 @@ def start_run( return RunTracker( run_id=run_id, - proc=proc, # type:ignore + proc=proc, ) # pylint: disable-next=broad-except From 723c56f272bfa1d54d07b0c5a8605c1bcf11b2be Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 23 Jul 2024 18:37:17 +0200 Subject: [PATCH 238/595] refactor(framework:skip) Improve SuperExec docs (#3889) --- src/py/flwr/superexec/app.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/superexec/app.py b/src/py/flwr/superexec/app.py index b852577d943f..2ad5f12d227f 100644 --- a/src/py/flwr/superexec/app.py +++ b/src/py/flwr/superexec/app.py @@ -93,7 +93,9 @@ def _parse_args_run_superexec() -> argparse.ArgumentParser: ) parser.add_argument( "--executor-config", - help="Key-value pairs for the executor config, separated by commas.", + help="Key-value pairs for the executor config, separated by commas. " + 'For example:\n\n`--executor-config superlink="superlink:9091",' + 'root-certificates="certificates/superlink-ca.crt"`', ) parser.add_argument( "--insecure", From 8cd9ab1d80c291f59e108d3c2f38da4806b9d388 Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 23 Jul 2024 18:07:17 +0100 Subject: [PATCH 239/595] fix(framework:skip) Unify default `client_resources` and deprecate setting GPU growth as input argument (#3890) --- src/py/flwr/simulation/run_simulation.py | 36 +++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/py/flwr/simulation/run_simulation.py b/src/py/flwr/simulation/run_simulation.py index 7cebb90451d6..51799074ef6f 100644 --- a/src/py/flwr/simulation/run_simulation.py +++ b/src/py/flwr/simulation/run_simulation.py @@ -32,7 +32,11 @@ from flwr.common import EventType, event, log from flwr.common.config import get_fused_config_from_dir, parse_config_args from flwr.common.constant import RUN_ID_NUM_BYTES -from flwr.common.logger import set_logger_propagation, update_console_handler +from flwr.common.logger import ( + set_logger_propagation, + update_console_handler, + warn_deprecated_feature_with_example, +) from flwr.common.typing import Run, UserConfig from flwr.server.driver import Driver, InMemoryDriver from flwr.server.run_serverapp import run as run_server_app @@ -93,6 +97,14 @@ def run_simulation_from_cli() -> None: """Run Simulation Engine from the CLI.""" args = _parse_args_run_simulation().parse_args() + if args.enable_tf_gpu_growth: + warn_deprecated_feature_with_example( + "Passing `--enable-tf-gpu-growth` is deprecated.", + example_message="Instead, set the `TF_FORCE_GPU_ALLOW_GROWTH` environmnet " + "variable to true.", + code_example='TF_FORCE_GPU_ALLOW_GROWTH="true" flower-simulation <...>', + ) + # We are supporting two modes for the CLI entrypoint: # 1) Running an app dir containing a `pyproject.toml` # 2) Running any ClientApp and SeverApp w/o pyproject.toml being present @@ -223,6 +235,15 @@ def run_simulation( When disabled, only INFO, WARNING and ERROR log messages will be shown. If enabled, DEBUG-level logs will be displayed. """ + if enable_tf_gpu_growth: + warn_deprecated_feature_with_example( + "Passing `enable_tf_gpu_growth=True` is deprecated.", + example_message="Instead, set the `TF_FORCE_GPU_ALLOW_GROWTH` environmnet " + "variable to true.", + code_example='import os;os.environ["TF_FORCE_GPU_ALLOW_GROWTH"]="true"' + "\n\tflwr.simulation.run_simulationt(...)", + ) + _run_simulation( num_supernodes=num_supernodes, client_app=client_app, @@ -264,7 +285,7 @@ def server_th_with_start_checks( """ try: if tf_gpu_growth: - log(INFO, "Enabling GPU growth for Tensorflow on the main thread.") + log(INFO, "Enabling GPU growth for Tensorflow on the server thread.") enable_gpu_growth() # Run ServerApp @@ -475,6 +496,14 @@ def _run_simulation( if "init_args" not in backend_config: backend_config["init_args"] = {} + # Set default client_resources if not passed + if "client_resources" not in backend_config: + backend_config["client_resources"] = {"num_cpus": 2, "num_gpus": 0} + + # Initialization of backend config to enable GPU growth globally when set + if "actor" not in backend_config: + backend_config["actor"] = {"tensorflow": 0} + # Set logging level logger = logging.getLogger("flwr") if verbose_logging: @@ -580,8 +609,7 @@ def _parse_args_run_simulation() -> argparse.ArgumentParser: parser.add_argument( "--backend-config", type=str, - default='{"client_resources": {"num_cpus":2, "num_gpus":0.0},' - '"actor": {"tensorflow": 0}}', + default="{}", help='A JSON formatted stream, e.g \'{"":, "":}\' to ' "configure a backend. Values supported in are those included by " "`flwr.common.typing.ConfigsRecordValues`. ", From 26411b04ed06b8483d637189f27f75b63c5fe934 Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Wed, 24 Jul 2024 00:18:53 +0200 Subject: [PATCH 240/595] docs(datasets) Rewrite Flower Datasets quickstart tutorial as a notebook (#3854) Co-authored-by: jafermarq --- .../tutorial-quickstart/choose-hf-dataset.png | Bin 0 -> 808670 bytes .../tutorial-quickstart/copy-dataset-name.png | Bin 0 -> 392600 bytes datasets/doc/source/tutorial-quickstart.ipynb | 550 ++++++++++++++++++ datasets/doc/source/tutorial-quickstart.rst | 99 ---- 4 files changed, 550 insertions(+), 99 deletions(-) create mode 100644 datasets/doc/source/_static/tutorial-quickstart/choose-hf-dataset.png create mode 100644 datasets/doc/source/_static/tutorial-quickstart/copy-dataset-name.png create mode 100644 datasets/doc/source/tutorial-quickstart.ipynb delete mode 100644 datasets/doc/source/tutorial-quickstart.rst diff --git a/datasets/doc/source/_static/tutorial-quickstart/choose-hf-dataset.png b/datasets/doc/source/_static/tutorial-quickstart/choose-hf-dataset.png new file mode 100644 index 0000000000000000000000000000000000000000..ffce2008e178883307b951efe0f77b3ba2730b6c GIT binary patch literal 808670 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

    krJ^quga%Cq!fB)2|Ltj^v$tBotB!) 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