From 89618b04ca6ac84303099014f8ee67697bdb9544 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 18:25:20 +0100 Subject: [PATCH 01/28] docs(framework) Update Quickstart Tutorial documentation for TensorFlow with `flwr run` (#3338) Co-authored-by: Javier Co-authored-by: Charles Beauville --- doc/source/tutorial-quickstart-tensorflow.rst | 452 ++++++++++++------ 1 file changed, 294 insertions(+), 158 deletions(-) diff --git a/doc/source/tutorial-quickstart-tensorflow.rst b/doc/source/tutorial-quickstart-tensorflow.rst index bd63eb461d21..ffcd9efeb9bc 100644 --- a/doc/source/tutorial-quickstart-tensorflow.rst +++ b/doc/source/tutorial-quickstart-tensorflow.rst @@ -1,171 +1,307 @@ .. _quickstart-tensorflow: +####################### + Quickstart TensorFlow +####################### + +In this tutorial we will learn how to train a Convolutional Neural +Network on CIFAR-10 using the Flower framework and TensorFlow. First of +all, it is recommended to create a virtual environment and run +everything within a :doc:`virtualenv +`. + +Let's use `flwr new` to create a complete Flower+TensorFlow project. It +will generate all the files needed to run, by default with the Flower +Simulation Engine, a federation of 10 nodes using `FedAvg +`_. +The dataset will be partitioned using Flower Dataset's `IidPartitioner +`_. + +Now that we have a rough idea of what this example is about, let's get +started. First, install Flower in your new environment: + +.. code:: shell + + # In a new Python environment + $ pip install flwr + +Then, run the command below. You will be prompted to select one of the +available templates (choose ``TensorFlow``), give a name to your +project, and type in your developer name: + +.. code:: shell + + $ flwr new + +After running it you'll notice a new directory with your project name +has been created. It should have the following structure: + +.. code:: shell + + + ├── + │ ├── __init__.py + │ ├── client_app.py # Defines your ClientApp + │ ├── server_app.py # Defines your ServerApp + │ └── task.py # Defines your model, training and data loading + ├── pyproject.toml # Project metadata like dependencies and configs + └── README.md + +If you haven't yet installed the project and its dependencies, you can +do so by: + +.. code:: shell + + # From the directory where your pyproject.toml is + $ pip install -e . + +To run the project, do: + +.. code:: shell + + # Run with default arguments + $ flwr run . + +With default arguments you will see an output like this one: + +.. code:: shell + + Loading project configuration... + Success + INFO : Starting Flower ServerApp, config: num_rounds=3, no round_timeout + INFO : + INFO : [INIT] + INFO : Using initial global parameters provided by strategy + INFO : Starting evaluation of initial global parameters + INFO : Evaluation returned no results (`None`) + INFO : + INFO : [ROUND 1] + INFO : configure_fit: strategy sampled 10 clients (out of 10) + INFO : aggregate_fit: received 10 results and 0 failures + WARNING : No fit_metrics_aggregation_fn provided + INFO : configure_evaluate: strategy sampled 10 clients (out of 10) + INFO : aggregate_evaluate: received 10 results and 0 failures + WARNING : No evaluate_metrics_aggregation_fn provided + INFO : + INFO : [ROUND 2] + INFO : configure_fit: strategy sampled 10 clients (out of 10) + INFO : aggregate_fit: received 10 results and 0 failures + INFO : configure_evaluate: strategy sampled 10 clients (out of 10) + INFO : aggregate_evaluate: received 10 results and 0 failures + INFO : + INFO : [ROUND 3] + INFO : configure_fit: strategy sampled 10 clients (out of 10) + INFO : aggregate_fit: received 10 results and 0 failures + INFO : configure_evaluate: strategy sampled 10 clients (out of 10) + INFO : aggregate_evaluate: received 10 results and 0 failures + INFO : + INFO : [SUMMARY] + INFO : Run finished 3 round(s) in 31.31s + INFO : History (loss, distributed): + INFO : round 1: 1.9066195368766785 + INFO : round 2: 1.657227087020874 + INFO : round 3: 1.559039831161499 + INFO : + +You can also override the parameters defined in the +``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this: + +.. code:: shell + + # Override some arguments + $ flwr run . --run-config "num-server-rounds=5 batch-size=16" + +********** + The Data +********** + +This tutorial uses `Flower Datasets `_ +to easily download and partition the `CIFAR-10` dataset. In this example +you'll make use of the `IidPartitioner +`_ +to generate `num_partitions` partitions. You can choose `other +partitioners +`_ +available in Flower Datasets. Each ``ClientApp`` will call this function +to create the ``NumPy`` arrays that correspond to their data partition. + +.. code:: python + + 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") + + # Divide data on each node: 80% train, 20% test + partition = partition.train_test_split(test_size=0.2) + x_train, y_train = partition["train"]["img"] / 255.0, partition["train"]["label"] + x_test, y_test = partition["test"]["img"] / 255.0, partition["test"]["label"] + +*********** + The Model +*********** + +Next, we need a model. We defined a simple Convolutional Neural Network +(CNN), but feel free to replace it with a more sophisticated model if +you'd like: + +.. code:: python + + def load_model(learning_rate: float = 0.001): + # Define a simple CNN for CIFAR-10 and set Adam optimizer + model = keras.Sequential( + [ + keras.Input(shape=(32, 32, 3)), + layers.Conv2D(32, kernel_size=(3, 3), activation="relu"), + layers.MaxPooling2D(pool_size=(2, 2)), + layers.Conv2D(64, kernel_size=(3, 3), activation="relu"), + layers.MaxPooling2D(pool_size=(2, 2)), + layers.Flatten(), + layers.Dropout(0.5), + layers.Dense(10, activation="softmax"), + ] + ) + model.compile( + "adam", + loss="sparse_categorical_crossentropy", + metrics=["accuracy"], + ) + return model + +*************** + The ClientApp +*************** + +With `TensorFlow`, we can use the built-in ``get_weights()`` and +``set_weights()`` functions, which simplifies the implementation with +`Flower`. The rest of the functionality in the ClientApp is directly +inspired by the centralized case. The ``fit()`` method in the client +trains the model using the local dataset. Similarly, the ``evaluate()`` +method is used to evaluate the model received on a held-out validation +set that the client might have: + +.. code:: python + + class FlowerClient(NumPyClient): + def __init__(self, model, data, epochs, batch_size, verbose): + self.model = model + self.x_train, self.y_train, self.x_test, self.y_test = data + self.epochs = epochs + self.batch_size = batch_size + self.verbose = verbose + + def fit(self, parameters, config): + self.model.set_weights(parameters) + self.model.fit( + self.x_train, + self.y_train, + epochs=self.epochs, + batch_size=self.batch_size, + verbose=self.verbose, + ) + return self.model.get_weights(), len(self.x_train), {} + + def evaluate(self, parameters, config): + self.model.set_weights(parameters) + loss, accuracy = self.model.evaluate(self.x_test, self.y_test, verbose=0) + return loss, len(self.x_test), {"accuracy": accuracy} + +Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` +defined above by means of a ``client_fn()`` callback. Note that the +`context` enables you to get access to hyperparameters defined in your +``pyproject.toml`` to configure the run. For example, in this tutorial +we access the `local-epochs` setting to control the number of epochs a +``ClientApp`` will perform when running the ``fit()`` method, in +addition to `batch-size`. You could define additional hyperparameters in +``pyproject.toml`` and access them here. + +.. code:: python + + def client_fn(context: Context): + # Load model and data + net = load_model() + + partition_id = context.node_config["partition-id"] + num_partitions = context.node_config["num-partitions"] + data = 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, data, epochs, batch_size, verbose + ).to_client() + + + # Flower ClientApp + app = ClientApp(client_fn=client_fn) + +*************** + The ServerApp +*************** + +To construct a ``ServerApp`` we define a ``server_fn()`` callback with +an identical signature to that of ``client_fn()`` but the return type is +`ServerAppComponents +`_ +as opposed to a `Client +`_. +In this example we use the `FedAvg`. To it we pass a randomly +initialized model that will serve as the global model to federate. + +.. code:: python + + def server_fn(context: Context): + # Read from config + num_rounds = context.run_config["num-server-rounds"] + + # Get parameters to initialize global model + parameters = ndarrays_to_parameters(load_model().get_weights()) + + # Define strategy + strategy = strategy = FedAvg( + fraction_fit=1.0, + fraction_evaluate=1.0, + min_available_clients=2, + initial_parameters=parameters, + ) + config = ServerConfig(num_rounds=num_rounds) + + return ServerAppComponents(strategy=strategy, config=config) + + # Create ServerApp + app = ServerApp(server_fn=server_fn) -Quickstart TensorFlow -===================== - -.. meta:: - :description: Check out this Federated Learning quickstart tutorial for using Flower with TensorFlow to train a MobilNetV2 model on CIFAR-10. - -.. youtube:: FGTc2TQq7VM - :width: 100% - -Let's build a federated learning system in less than 20 lines of code! - -Before Flower can be imported we have to install it: - -.. code-block:: shell - - $ pip install flwr - -Since we want to use the Keras API of TensorFlow (TF), we have to install TF as well: - -.. code-block:: shell - - $ pip install tensorflow - - -Flower Client -------------- - -Next, in a file called :code:`client.py`, import Flower and TensorFlow: - -.. code-block:: python - - import flwr as fl - import tensorflow as tf - -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. - -.. code-block:: python - - (x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data() - -Next, we need a model. For the purpose of this tutorial, we use MobilNetV2 with 10 output classes: - -.. code-block:: python - - model = tf.keras.applications.MobileNetV2((32, 32, 3), classes=10, weights=None) - model.compile("adam", "sparse_categorical_crossentropy", metrics=["accuracy"]) - -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). - -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: - -.. code-block:: python - - class CifarClient(fl.client.NumPyClient): - def get_parameters(self, config): - return model.get_weights() - - def fit(self, parameters, config): - model.set_weights(parameters) - model.fit(x_train, y_train, epochs=1, batch_size=32, steps_per_epoch=3) - return model.get_weights(), len(x_train), {} - - def evaluate(self, parameters, config): - model.set_weights(parameters) - loss, accuracy = model.evaluate(x_test, y_test) - return loss, len(x_test), {"accuracy": float(accuracy)} - - -We can now create an instance of our class :code:`CifarClient` and add one line -to actually run this client: - -.. code-block:: python - - fl.client.start_client(server_address="[::]:8080", client=CifarClient().to_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:`"[::]: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. - - -Flower Server -------------- - -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: - -.. code-block:: python - - import flwr as fl - - fl.server.start_server(config=fl.server.ServerConfig(num_rounds=3)) - - -Train the model, federated! ---------------------------- - -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: - -.. code-block:: shell - - $ python server.py - -Once the server is running we can start the clients in different terminals. -Open a new terminal and start the first client: +Congratulations! You've successfully built and run your first federated +learning system. -.. code-block:: shell +.. note:: - $ python client.py + Check the source code of the extended version of this tutorial in + |quickstart_tf_link|_ in the Flower GitHub repository. -Open another terminal and start the second client: +.. |quickstart_tf_link| replace:: -.. code-block:: shell + :code:`examples/quickstart-tensorflow` - $ python client.py +.. _quickstart_tf_link: https://github.com/adap/flower/blob/main/examples/quickstart-tensorflow -Each client will have its own dataset. +**************** + Video tutorial +**************** -You should now see how the training does in the very first terminal (the one -that started the server): +.. note:: -.. code-block:: shell + The video shown below shows how to setup a TensorFlow + Flower + project using our previously recommended APIs. A new video tutorial + will be released that shows the new APIs (as the content above does) - INFO flower 2021-02-25 14:15:46,741 | app.py:76 | Flower server running (insecure, 3 rounds) - INFO flower 2021-02-25 14:15:46,742 | server.py:72 | Getting initial parameters - INFO flower 2021-02-25 14:16:01,770 | server.py:74 | Evaluating initial parameters - INFO flower 2021-02-25 14:16:01,770 | server.py:87 | [TIME] FL starting - DEBUG flower 2021-02-25 14:16:12,341 | server.py:165 | fit_round: strategy sampled 2 clients (out of 2) - DEBUG flower 2021-02-25 14:21:17,235 | server.py:177 | fit_round received 2 results and 0 failures - DEBUG flower 2021-02-25 14:21:17,512 | server.py:139 | evaluate: strategy sampled 2 clients - DEBUG flower 2021-02-25 14:21:29,628 | server.py:149 | evaluate received 2 results and 0 failures - DEBUG flower 2021-02-25 14:21:29,696 | server.py:165 | fit_round: strategy sampled 2 clients (out of 2) - DEBUG flower 2021-02-25 14:25:59,917 | server.py:177 | fit_round received 2 results and 0 failures - DEBUG flower 2021-02-25 14:26:00,227 | server.py:139 | evaluate: strategy sampled 2 clients - DEBUG flower 2021-02-25 14:26:11,457 | server.py:149 | evaluate received 2 results and 0 failures - DEBUG flower 2021-02-25 14:26:11,530 | server.py:165 | fit_round: strategy sampled 2 clients (out of 2) - DEBUG flower 2021-02-25 14:30:43,389 | server.py:177 | fit_round received 2 results and 0 failures - DEBUG flower 2021-02-25 14:30:43,630 | server.py:139 | evaluate: strategy sampled 2 clients - DEBUG flower 2021-02-25 14:30:53,384 | server.py:149 | evaluate received 2 results and 0 failures - INFO flower 2021-02-25 14:30:53,384 | server.py:122 | [TIME] FL finished in 891.6143046000007 - INFO flower 2021-02-25 14:30:53,385 | app.py:109 | app_fit: losses_distributed [(1, 2.3196680545806885), (2, 2.3202896118164062), (3, 2.1818180084228516)] - INFO flower 2021-02-25 14:30:53,385 | app.py:110 | app_fit: accuracies_distributed [] - INFO flower 2021-02-25 14:30:53,385 | app.py:111 | app_fit: losses_centralized [] - INFO flower 2021-02-25 14:30:53,385 | app.py:112 | app_fit: accuracies_centralized [] - DEBUG flower 2021-02-25 14:30:53,442 | server.py:139 | evaluate: strategy sampled 2 clients - DEBUG flower 2021-02-25 14:31:02,848 | server.py:149 | evaluate received 2 results and 0 failures - INFO flower 2021-02-25 14:31:02,848 | app.py:121 | app_evaluate: federated loss: 2.1818180084228516 - INFO flower 2021-02-25 14:31:02,848 | app.py:125 | app_evaluate: results [('ipv4:127.0.0.1:57158', EvaluateRes(loss=2.1818180084228516, num_examples=10000, accuracy=0.0, metrics={'accuracy': 0.21610000729560852})), ('ipv4:127.0.0.1:57160', EvaluateRes(loss=2.1818180084228516, num_examples=10000, accuracy=0.0, metrics={'accuracy': 0.21610000729560852}))] - INFO flower 2021-02-25 14:31:02,848 | app.py:127 | app_evaluate: failures [] flower 2020-07-15 10:07:56,396 | app.py:77 | app_evaluate: failures [] +.. meta:: + :description: Check out this Federated Learning quickstart tutorial for using Flower with TensorFlow to train a CNN model on CIFAR-10. -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`. +.. youtube:: FGTc2TQq7VM + :width: 100% From 87712d46ad6cd4256571b83803d12721d56580fb Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Wed, 18 Sep 2024 18:32:42 +0100 Subject: [PATCH 02/28] docs(framework) Update quickstart tutorial documentation for Hugging Face with `flwr run` (#3341) Co-authored-by: Javier --- .../tutorial-quickstart-huggingface.rst | 634 ++++++++++++------ 1 file changed, 421 insertions(+), 213 deletions(-) diff --git a/doc/source/tutorial-quickstart-huggingface.rst b/doc/source/tutorial-quickstart-huggingface.rst index 7d8128230901..e5caa3b19dd6 100644 --- a/doc/source/tutorial-quickstart-huggingface.rst +++ b/doc/source/tutorial-quickstart-huggingface.rst @@ -1,229 +1,437 @@ .. _quickstart-huggingface: +########################### + Quickstart 🤗 Transformers +########################### + +In this federated learning tutorial we will learn how to train a large +language model (LLM) on the `IMDB +`_ dataset using +Flower and the 🤗 Hugging Face Transformers library. It is recommended to +create a virtual environment and run everything within a +:doc:`virtualenv `. + +Let's use ``flwr new`` to create a complete Flower+🤗 Hugging Face +project. It will generate all the files needed to run, by default with +the Flower Simulation Engine, a federation of 10 nodes using |fedavg|_ +The dataset will be partitioned using |flowerdatasets|_'s +|iidpartitioner|_. + +Now that we have a rough idea of what this example is about, let's get +started. First, install Flower in your new environment: + +.. code:: shell + + # In a new Python environment + $ pip install flwr + +Then, run the command below. You will be prompted to select one of the +available templates (choose ``HuggingFace``), give a name to your +project, and type in your developer name: + +.. code:: shell + + $ flwr new + +After running it you'll notice a new directory with your project name +has been created. It should have the following structure: + +.. code:: shell + + + ├── + │ ├── __init__.py + │ ├── client_app.py # Defines your ClientApp + │ ├── server_app.py # Defines your ServerApp + │ └── task.py # Defines your model, training and data loading + ├── pyproject.toml # Project metadata like dependencies and configs + └── README.md + +If you haven't yet installed the project and its dependencies, you can +do so by: + +.. code:: shell + + # From the directory where your pyproject.toml is + $ pip install -e . + +To run the project, do: + +.. code:: shell + + # Run with default arguments + $ flwr run . + +With default arguments you will see an output like this one: + +.. code:: shell + + Loading project configuration... + Success + INFO : Starting Flower ServerApp, config: num_rounds=3, no round_timeout + INFO : + INFO : [INIT] + INFO : Using initial global parameters provided by strategy + INFO : Starting evaluation of initial global parameters + INFO : Evaluation returned no results (`None`) + INFO : + INFO : [ROUND 1] + INFO : configure_fit: strategy sampled 2 clients (out of 10) + INFO : aggregate_fit: received 2 results and 0 failures + WARNING : No fit_metrics_aggregation_fn provided + INFO : configure_evaluate: strategy sampled 10 clients (out of 10) + INFO : aggregate_evaluate: received 10 results and 0 failures + WARNING : No evaluate_metrics_aggregation_fn provided + INFO : + INFO : [ROUND 2] + INFO : configure_fit: strategy sampled 5 clients (out of 10) + INFO : aggregate_fit: received 5 results and 0 failures + INFO : configure_evaluate: strategy sampled 10 clients (out of 10) + INFO : aggregate_evaluate: received 10 results and 0 failures + INFO : + INFO : [ROUND 3] + INFO : configure_fit: strategy sampled 5 clients (out of 10) + INFO : aggregate_fit: received 5 results and 0 failures + INFO : configure_evaluate: strategy sampled 10 clients (out of 10) + INFO : aggregate_evaluate: received 10 results and 0 failures + INFO : + INFO : [SUMMARY] + INFO : Run finished 3 round(s) in 249.11s + INFO : History (loss, distributed): + INFO : round 1: 0.02111011856794357 + INFO : round 2: 0.019722302150726317 + INFO : round 3: 0.018227258533239362 + INFO : + +You can also run the project with GPU as follows: + +.. code:: shell + + # Run with default arguments + $ flwr run . localhost-gpu + +This will use the default arguments where each ``ClientApp`` will use 2 +CPUs and at most 4 ``ClientApp``\s will run in a given GPU. + +You can also override the parameters defined in the +``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this: + +.. code:: shell + + # Override some arguments + $ flwr run . --run-config "num-server-rounds=5 fraction-fit=0.2" + +What follows is an explanation of each component in the project you just +created: dataset partition, the model, defining the ``ClientApp`` and +defining the ``ServerApp``. + +********** + The Data +********** + +This tutorial uses |flowerdatasets|_ to easily download and partition +the `IMDB `_ dataset. +In this example you'll make use of the |iidpartitioner|_ to generate +``num_partitions`` partitions. You can choose |otherpartitioners|_ +available in Flower Datasets. To tokenize the text, we will also load +the tokenizer from the pre-trained Transformer model that we'll use +during training - more on that in the next section. Each ``ClientApp`` +will call this function to create dataloaders with the data that +correspond to their data partition. + +.. code:: python + + 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) + + tokenizer = AutoTokenizer.from_pretrained(model_name) + + def tokenize_function(examples): + return tokenizer( + examples["text"], truncation=True, add_special_tokens=True, max_length=512 + ) + + partition_train_test = partition_train_test.map(tokenize_function, batched=True) + partition_train_test = partition_train_test.remove_columns("text") + partition_train_test = partition_train_test.rename_column("label", "labels") + + data_collator = DataCollatorWithPadding(tokenizer=tokenizer) + trainloader = DataLoader( + partition_train_test["train"], + shuffle=True, + batch_size=32, + collate_fn=data_collator, + ) + + testloader = DataLoader( + partition_train_test["test"], batch_size=32, collate_fn=data_collator + ) + +*********** + The Model +*********** + +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 (|berttiny|_) for sequence +classification over the dataset of IMDB ratings. The end goal is to +detect if a movie rating is positive or negative. If you have access to +larger GPUs, feel free to use larger models! + +.. code:: python + + net = AutoModelForSequenceClassification.from_pretrained( + model_name, num_labels=num_labels + ) + +Note that here, ``model_name`` is a string that will be loaded from the +``Context`` in the ClientApp and ServerApp. + +In addition to loading the pretrained model weights and architecture, we +also include two utility functions to perform both training (i.e. +``train()``) and evaluation (i.e. ``test()``) using the above model. +These functions should look fairly familiar if you have some prior +experience with PyTorch. Note these functions do not have anything +specific to Flower. That being said, the training function will normally +be called, as we'll see later, from a Flower client passing its own +data. In summary, your clients can use standard training/testing +functions to perform local training or evaluation: + +.. code:: python + + def train(net, trainloader, epochs, device): + optimizer = AdamW(net.parameters(), lr=5e-5) + net.train() + for _ in range(epochs): + for batch in trainloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = net(**batch) + loss = outputs.loss + loss.backward() + optimizer.step() + optimizer.zero_grad() + + + def test(net, testloader, device): + metric = load_metric("accuracy") + loss = 0 + net.eval() + for batch in testloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = net(**batch) + logits = outputs.logits + loss += outputs.loss.item() + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + loss /= len(testloader.dataset) + accuracy = metric.compute()["accuracy"] + return loss, accuracy + +*************** + The ClientApp +*************** + +The main changes we have to make to use 🤗 Hugging Face with Flower will +be found in the ``get_weights()`` and ``set_weights()`` functions. Under +the hood, the ``transformers`` library uses PyTorch, which means we can +reuse the ``get_weights()`` and ``set_weights()`` code that we defined +in the :doc:`Quickstart PyTorch ` tutorial. +As a reminder, in ``get_weights()``, PyTorch model parameters are +extracted and represented as a list of NumPy arrays. The +``set_weights()`` function that's the opposite: given a list of NumPy +arrays it applies them to an existing PyTorch model. Doing this in +fairly easy in PyTorch. + +.. note:: + + The specific implementation of ``get_weights()`` and + ``set_weights()`` depends on the type of models you use. The ones + shown below work for a wide range of PyTorch models but you might + need to adjust them if you have more exotic model architectures. + +.. code:: python + + def get_weights(net): + return [val.cpu().numpy() for _, val in net.state_dict().items()] + + 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) + +The rest of the functionality is directly inspired by the centralized +case. The ``fit()`` method in the client trains the model using the +local dataset. Similarly, the ``evaluate()`` method is used to evaluate +the model received on a held-out validation set that the client might +have: + +.. code:: python + + class FlowerClient(NumPyClient): + def __init__(self, net, trainloader, testloader, local_epochs): + self.net = net + self.trainloader = trainloader + self.testloader = testloader + self.local_epochs = local_epochs + self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + self.net.to(self.device) + + def fit(self, parameters, config): + set_weights(self.net, parameters) + train(self.net, self.trainloader, epochs=self.local_epochs, device=self.device) + return get_weights(self.net), len(self.trainloader), {} + + def evaluate(self, parameters, config): + set_weights(self.net, parameters) + loss, accuracy = test(self.net, self.testloader, self.device) + return float(loss), len(self.testloader), {"accuracy": accuracy} + +Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` +defined above by means of a ``client_fn()`` callback. Note that the +`context` enables you to get access to hyperparemeters defined in your +``pyproject.toml`` to configure the run. In this tutorial we access the +``local-epochs`` setting to control the number of epochs a ``ClientApp`` +will perform when running the ``fit()`` method. You could define +additional hyperparameters in ``pyproject.toml`` and access them here. + +.. code:: python -Quickstart 🤗 Transformers -========================== + def client_fn(context: Context): -.. meta:: - :description: Check out this Federating Learning quickstart tutorial for using Flower with HuggingFace Transformers in order to fine-tune an LLM. - -Let's build a federated learning system using Hugging Face Transformers and Flower! - -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. - -Dependencies ------------- - -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`: - -.. code-block:: shell - - $ pip install datasets evaluate flwr torch transformers - - -Standard Hugging Face workflow ------------------------------- - -Handling the data -^^^^^^^^^^^^^^^^^ - -To fetch the IMDB dataset, we will use Hugging Face's :code:`datasets` library. -We then need to tokenize the data and create :code:`PyTorch` dataloaders, -this is all done in the :code:`load_data` function: - -.. code-block:: python - - import random - import torch - from datasets import load_dataset - from torch.utils.data import DataLoader - from transformers import AutoTokenizer, DataCollatorWithPadding - - DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") - CHECKPOINT = "distilbert-base-uncased" - - def load_data(): - """Load IMDB data (training and eval)""" - raw_datasets = load_dataset("imdb") - raw_datasets = raw_datasets.shuffle(seed=42) - # remove unnecessary data split - del raw_datasets["unsupervised"] - tokenizer = AutoTokenizer.from_pretrained(CHECKPOINT) - def tokenize_function(examples): - return tokenizer(examples["text"], truncation=True) - # We will take a small sample in order to reduce the compute time, this is optional - train_population = random.sample(range(len(raw_datasets["train"])), 100) - test_population = random.sample(range(len(raw_datasets["test"])), 100) - tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) - tokenized_datasets["train"] = tokenized_datasets["train"].select(train_population) - tokenized_datasets["test"] = tokenized_datasets["test"].select(test_population) - tokenized_datasets = tokenized_datasets.remove_columns("text") - tokenized_datasets = tokenized_datasets.rename_column("label", "labels") - data_collator = DataCollatorWithPadding(tokenizer=tokenizer) - trainloader = DataLoader( - tokenized_datasets["train"], - shuffle=True, - batch_size=32, - collate_fn=data_collator, - ) - testloader = DataLoader( - tokenized_datasets["test"], batch_size=32, collate_fn=data_collator - ) - return trainloader, testloader - - -Training and testing the model -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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: - -.. code-block:: python - - from evaluate import load as load_metric - from transformers import AdamW - - def train(net, trainloader, epochs): - optimizer = AdamW(net.parameters(), lr=5e-5) - net.train() - for _ in range(epochs): - for batch in trainloader: - batch = {k: v.to(DEVICE) for k, v in batch.items()} - outputs = net(**batch) - loss = outputs.loss - loss.backward() - optimizer.step() - optimizer.zero_grad() - def test(net, testloader): - metric = load_metric("accuracy") - loss = 0 - net.eval() - for batch in testloader: - batch = {k: v.to(DEVICE) for k, v in batch.items()} - with torch.no_grad(): - outputs = net(**batch) - logits = outputs.logits - loss += outputs.loss.item() - predictions = torch.argmax(logits, dim=-1) - metric.add_batch(predictions=predictions, references=batch["labels"]) - loss /= len(testloader.dataset) - accuracy = metric.compute()["accuracy"] - return loss, accuracy - - -Creating the model itself -^^^^^^^^^^^^^^^^^^^^^^^^^ - -To create the model itself, -we will just load the pre-trained distillBERT model using Hugging Face’s :code:`AutoModelForSequenceClassification` : - -.. code-block:: python - - from transformers import AutoModelForSequenceClassification - - net = AutoModelForSequenceClassification.from_pretrained( - CHECKPOINT, num_labels=2 - ).to(DEVICE) - - -Federating the example ----------------------- - -Creating the IMDBClient -^^^^^^^^^^^^^^^^^^^^^^^ - -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: - -.. code-block:: python - - from collections import OrderedDict - import flwr as fl - - class IMDBClient(fl.client.NumPyClient): - def get_parameters(self, config): - return [val.cpu().numpy() for _, val in net.state_dict().items()] - def set_parameters(self, 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 fit(self, parameters, config): - self.set_parameters(parameters) - print("Training Started...") - train(net, trainloader, epochs=1) - print("Training Finished.") - return self.get_parameters(config={}), len(trainloader), {} - def evaluate(self, parameters, config): - self.set_parameters(parameters) - loss, accuracy = test(net, testloader) - return float(loss), len(testloader), {"accuracy": float(accuracy)} - - -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. - -Starting the server -^^^^^^^^^^^^^^^^^^^ + # Get this client's dataset partition + partition_id = context.node_config["partition-id"] + num_partitions = context.node_config["num-partitions"] + model_name = context.run_config["model-name"] + trainloader, valloader = load_data(partition_id, num_partitions, model_name) + + # Load model + num_labels = context.run_config["num-labels"] + net = AutoModelForSequenceClassification.from_pretrained( + model_name, num_labels=num_labels + ) + + local_epochs = context.run_config["local-epochs"] + + # Return Client instance + return FlowerClient(net, trainloader, valloader, local_epochs).to_client() + + # Flower ClientApp + app = ClientApp(client_fn) + +*************** + The ServerApp +*************** + +To construct a ``ServerApp`` we define a ``server_fn()`` callback with +an identical signature to that of ``client_fn()`` but the return type is +|serverappcomponents|_ as opposed to a |client|_ In this example we use +the `FedAvg` strategy. To it we pass a randomly initialized model that +will server as the global model to federated. Note that the value of +``fraction_fit`` is read from the run config. You can find the default +value defined in the ``pyproject.toml``. + +.. code:: python + + def server_fn(context: Context): + # Read from config + num_rounds = context.run_config["num-server-rounds"] + fraction_fit = context.run_config["fraction-fit"] + + # Initialize global model + model_name = context.run_config["model-name"] + num_labels = context.run_config["num-labels"] + net = AutoModelForSequenceClassification.from_pretrained( + model_name, num_labels=num_labels + ) + + weights = get_weights(net) + initial_parameters = ndarrays_to_parameters(weights) + + # Define strategy + strategy = FedAvg( + fraction_fit=fraction_fit, + fraction_evaluate=1.0, + initial_parameters=initial_parameters, + ) + config = ServerConfig(num_rounds=num_rounds) + + return ServerAppComponents(strategy=strategy, config=config) + + + # Create ServerApp + app = ServerApp(server_fn=server_fn) + +Congratulations! You've successfully built and run your first federated +learning system for an LLM. + +.. note:: + + Check the source code of the extended version of this tutorial in + |quickstart_hf_link|_ in the Flower GitHub repository. For a + comprehensive example of a federated fine-tuning of an LLM with + Flower, refer to the |flowertune|_ example in the Flower GitHub + repository. -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: - -.. code-block:: python - - def weighted_average(metrics): - accuracies = [num_examples * m["accuracy"] for num_examples, m in metrics] - losses = [num_examples * m["loss"] for num_examples, m in metrics] - examples = [num_examples for num_examples, _ in metrics] - return {"accuracy": sum(accuracies) / sum(examples), "loss": sum(losses) / sum(examples)} - - # Define strategy - strategy = fl.server.strategy.FedAvg( - fraction_fit=1.0, - fraction_evaluate=1.0, - evaluate_metrics_aggregation_fn=weighted_average, - ) - - # Start server - fl.server.start_server( - server_address="0.0.0.0:8080", - config=fl.server.ServerConfig(num_rounds=3), - strategy=strategy, - ) +.. |quickstart_hf_link| replace:: + ``examples/quickstart-huggingface`` -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). +.. |fedavg| replace:: -Putting everything together ---------------------------- + ``FedAvg`` -We can now start client instances using: +.. |iidpartitioner| replace:: -.. code-block:: python + ``IidPartitioner`` - fl.client.start_client( - server_address="127.0.0.1:8080", - client=IMDBClient().to_client() - ) +.. |otherpartitioners| replace:: + other partitioners -And they will be able to connect to the server and start the federated training. +.. |berttiny| replace:: -If you want to check out everything put together, -you should check out the `full code example `_ . + ``bert-tiny`` -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. +.. |serverappcomponents| replace:: -Note that in this example we used :code:`PyTorch`, but we could have very well used :code:`TensorFlow`. + ``ServerAppComponents`` + +.. |client| replace:: + + ``Client`` + +.. |flowerdatasets| replace:: + + Flower Datasets + +.. |flowertune| replace:: + + FlowerTune LLM + +.. _berttiny: https://huggingface.co/prajjwal1/bert-tiny + +.. _client: ref-api/flwr.client.Client.html#client + +.. _fedavg: ref-api/flwr.server.strategy.FedAvg.html#flwr.server.strategy.FedAvg + +.. _flowerdatasets: https://flower.ai/docs/datasets/ + +.. _flowertune: https://github.com/adap/flower/tree/main/examples/flowertune-llm + +.. _iidpartitioner: https://flower.ai/docs/datasets/ref-api/flwr_datasets.partitioner.IidPartitioner.html#flwr_datasets.partitioner.IidPartitioner + +.. _otherpartitioners: https://flower.ai/docs/datasets/ref-api/flwr_datasets.partitioner.html + +.. _quickstart_hf_link: https://github.com/adap/flower/tree/main/examples/quickstart-huggingface + +.. _serverappcomponents: ref-api/flwr.server.ServerAppComponents.html#serverappcomponents + +.. meta:: + :description: Check out this Federating Learning quickstart tutorial for using Flower with 🤗 HuggingFace Transformers in order to fine-tune an LLM. From 13d5f4068d9ac2832bcbec875122ba2daceb1d48 Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 18 Sep 2024 20:12:05 +0200 Subject: [PATCH 03/28] docs(framework) Update fastai quickstart tutorial (#4151) --- doc/source/tutorial-quickstart-fastai.rst | 113 ++++++++++++++++++++-- 1 file changed, 107 insertions(+), 6 deletions(-) diff --git a/doc/source/tutorial-quickstart-fastai.rst b/doc/source/tutorial-quickstart-fastai.rst index 63f5ac176082..e42328e6f712 100644 --- a/doc/source/tutorial-quickstart-fastai.rst +++ b/doc/source/tutorial-quickstart-fastai.rst @@ -1,12 +1,113 @@ .. _quickstart-fastai: +################### + Quickstart fastai +################### -Quickstart fastai -================= +In this federated learning tutorial we will learn how to train a +SqueezeNet model on MNIST using Flower and fastai. It is recommended to +create a virtual environment and run everything within a +:doc:`virtualenv `. -.. meta:: - :description: Check out this Federated Learning quickstart tutorial for using Flower with FastAI to train a vision model on CIFAR-10. +Then, clone the code example directly from GitHub: -Let's build a federated learning system using fastai and Flower! +.. code:: shell -Please refer to the `full code example `_ to learn more. + git clone --depth=1 https://github.com/adap/flower.git _tmp \ + && mv _tmp/examples/quickstart-fastai . \ + && rm -rf _tmp && cd quickstart-fastai + +This will create a new directory called `quickstart-fastai` containing +the following files: + +.. code:: shell + + quickstart-fastai + ├── fastai_example + │ ├── client_app.py # Defines your ClientApp + │ ├── server_app.py # Defines your ServerApp + │ └── task.py # Defines your model, training and data loading + ├── pyproject.toml # Project metadata like dependencies and configs + └── README.md + +Next, activate your environment, then run: + +.. code:: shell + + # Navigate to the example directory + $ cd path/to/quickstart-fastai + + # Install project and dependencies + $ pip install -e . + +This example by default runs the Flower Simulation Engine, creating a +federation of 10 nodes using `FedAvg +`_ +as the aggregation strategy. The dataset will be partitioned using +Flower Dataset's `IidPartitioner +`_. +Let's run the project: + +.. code:: shell + + # Run with default arguments + $ flwr run . + +With default arguments you will see an output like this one: + +.. code:: shell + + Loading project configuration... + Success + INFO : Starting Flower ServerApp, config: num_rounds=3, no round_timeout + INFO : + INFO : [INIT] + INFO : Using initial global parameters provided by strategy + INFO : Starting evaluation of initial global parameters + INFO : Evaluation returned no results (`None`) + INFO : + INFO : [ROUND 1] + INFO : configure_fit: strategy sampled 5 clients (out of 10) + INFO : aggregate_fit: received 5 results and 0 failures + WARNING : No fit_metrics_aggregation_fn provided + INFO : configure_evaluate: strategy sampled 5 clients (out of 10) + INFO : aggregate_evaluate: received 5 results and 0 failures + INFO : + INFO : [ROUND 2] + INFO : configure_fit: strategy sampled 5 clients (out of 10) + INFO : aggregate_fit: received 5 results and 0 failures + INFO : configure_evaluate: strategy sampled 5 clients (out of 10) + INFO : aggregate_evaluate: received 5 results and 0 failures + INFO : + INFO : [ROUND 3] + INFO : configure_fit: strategy sampled 5 clients (out of 10) + INFO : aggregate_fit: received 5 results and 0 failures + INFO : configure_evaluate: strategy sampled 5 clients (out of 10) + INFO : aggregate_evaluate: received 5 results and 0 failures + INFO : + INFO : [SUMMARY] + INFO : Run finished 3 round(s) in 143.02s + INFO : History (loss, distributed): + INFO : round 1: 2.699497365951538 + INFO : round 2: 0.9549586296081543 + INFO : round 3: 0.6627192616462707 + INFO : History (metrics, distributed, evaluate): + INFO : {'accuracy': [(1, 0.09766666889190674), + INFO : (2, 0.6948333323001862), + INFO : (3, 0.7721666693687439)]} + INFO : + +You can also override the parameters defined in the +``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this: + +.. code:: shell + + # Override some arguments + $ flwr run . --run-config num-server-rounds=5 + +.. note:: + + Check the `source code + `_ + of this tutorial in ``examples/quickstart-fasai`` in the Flower + GitHub repository. From c9d2ff683415d059d2dafd8020c024d3c195e82c Mon Sep 17 00:00:00 2001 From: Javier Date: Wed, 18 Sep 2024 20:24:45 +0200 Subject: [PATCH 04/28] docs(framework) Update pytorch-lightning quickstart tutorial (#4152) --- .../tutorial-quickstart-pytorch-lightning.rst | 119 +++++++++++++++++- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/doc/source/tutorial-quickstart-pytorch-lightning.rst b/doc/source/tutorial-quickstart-pytorch-lightning.rst index acfbecf41260..7c74c9a1682f 100644 --- a/doc/source/tutorial-quickstart-pytorch-lightning.rst +++ b/doc/source/tutorial-quickstart-pytorch-lightning.rst @@ -1,12 +1,119 @@ .. _quickstart-pytorch-lightning: +############################## + Quickstart PyTorch Lightning +############################## -Quickstart PyTorch Lightning -============================ +In this federated learning tutorial we will learn how to train an +AutoEncoder model on MNIST using Flower and PyTorch Lightning. It is +recommended to create a virtual environment and run everything within a +:doc:`virtualenv `. -.. meta:: - :description: Check out this Federated Learning quickstart tutorial for using Flower with PyTorch Lightning to train an Auto Encoder model on MNIST. +Then, clone the code example directly from GitHub: -Let's build a horizontal federated learning system using PyTorch Lightning and Flower! +.. code:: shell -Please refer to the `full code example `_ to learn more. + git clone --depth=1 https://github.com/adap/flower.git _tmp \ + && mv _tmp/examples/quickstart-pytorch-lightning . \ + && rm -rf _tmp && cd quickstart-pytorch-lightning + +This will create a new directory called `quickstart-pytorch-lightning` +containing the following files: + +.. code:: shell + + quickstart-pytorch-lightning + ├── pytorchlightning_example + │ ├── client_app.py # Defines your ClientApp + │ ├── server_app.py # Defines your ServerApp + │ └── task.py # Defines your model, training and data loading + ├── pyproject.toml # Project metadata like dependencies and configs + └── README.md + +Next, activate your environment, then run: + +.. code:: shell + + # Navigate to the example directory + $ cd path/to/quickstart-pytorch-lightning + + # Install project and dependencies + $ pip install -e . + +By default, Flower Simulation Engine will be started and it will create +a federation of 4 nodes using `FedAvg +`_ +as the aggregation strategy. The dataset will be partitioned using +Flower Dataset's `IidPartitioner +`_. +To run the project, do: + +.. code:: shell + + # Run with default arguments + $ flwr run . + +With default arguments you will see an output like this one: + +.. code:: shell + + Loading project configuration... + Success + INFO : Starting Flower ServerApp, config: num_rounds=3, no round_timeout + INFO : + INFO : [INIT] + INFO : Using initial global parameters provided by strategy + INFO : Starting evaluation of initial global parameters + INFO : Evaluation returned no results (`None`) + INFO : + INFO : [ROUND 1] + INFO : configure_fit: strategy sampled 2 clients (out of 4) + INFO : aggregate_evaluate: received 2 results and 0 failures + WARNING : No evaluate_metrics_aggregation_fn provided + INFO : + INFO : [ROUND 2] + INFO : configure_fit: strategy sampled 2 clients (out of 4) + INFO : aggregate_fit: received 2 results and 0 failures + INFO : configure_evaluate: strategy sampled 2 clients (out of 4) + INFO : aggregate_evaluate: received 2 results and 0 failures + INFO : + INFO : [ROUND 3] + INFO : configure_fit: strategy sampled 2 clients (out of 4) + INFO : aggregate_fit: received 2 results and 0 failures + INFO : configure_evaluate: strategy sampled 2 clients (out of 4) + INFO : aggregate_evaluate: received 2 results and 0 failures + INFO : + INFO : [SUMMARY] + INFO : Run finished 3 round(s) in 136.92s + INFO : History (loss, distributed): + INFO : round 1: 0.04982871934771538 + INFO : round 2: 0.046457378193736076 + INFO : round 3: 0.04506748169660568 + INFO : + +Each simulated `ClientApp` (two per round) will also log a summary of +their local training process. Expect this output to be similar to: + +.. code:: shell + + # The left part indicates the process ID running the `ClientApp` + (ClientAppActor pid=38155) ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + (ClientAppActor pid=38155) ┃ Test metric ┃ DataLoader 0 ┃ + (ClientAppActor pid=38155) ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ + (ClientAppActor pid=38155) │ test_loss │ 0.045175597071647644 │ + (ClientAppActor pid=38155) └───────────────────────────┴───────────────────────────┘ + +You can also override the parameters defined in the +``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this: + +.. code:: shell + + # Override some arguments + $ flwr run . --run-config num-server-rounds=5 + +.. note:: + + Check the `source code + `_ + of this tutorial in ``examples/quickstart-pytorch-lightning`` in the + Flower GitHub repository. From f60658a6b3157926965f1e8d65bad096e20ea8ec Mon Sep 17 00:00:00 2001 From: Adam Narozniak <51029327+adam-narozniak@users.noreply.github.com> Date: Thu, 19 Sep 2024 09:54:24 +0200 Subject: [PATCH 05/28] feat(datasets) Add SizePartitioner (#4111) Co-authored-by: jafermarq --- .../flwr_datasets/partitioner/__init__.py | 2 + .../partitioner/size_partitioner.py | 128 ++++++ .../partitioner/size_partitioner_test.py | 392 ++++++++++++++++++ 3 files changed, 522 insertions(+) create mode 100644 datasets/flwr_datasets/partitioner/size_partitioner.py create mode 100644 datasets/flwr_datasets/partitioner/size_partitioner_test.py diff --git a/datasets/flwr_datasets/partitioner/__init__.py b/datasets/flwr_datasets/partitioner/__init__.py index 3fed4446db42..a14efa1cc905 100644 --- a/datasets/flwr_datasets/partitioner/__init__.py +++ b/datasets/flwr_datasets/partitioner/__init__.py @@ -27,6 +27,7 @@ from .partitioner import Partitioner from .pathological_partitioner import PathologicalPartitioner from .shard_partitioner import ShardPartitioner +from .size_partitioner import SizePartitioner from .square_partitioner import SquarePartitioner __all__ = [ @@ -42,5 +43,6 @@ "Partitioner", "PathologicalPartitioner", "ShardPartitioner", + "SizePartitioner", "SquarePartitioner", ] diff --git a/datasets/flwr_datasets/partitioner/size_partitioner.py b/datasets/flwr_datasets/partitioner/size_partitioner.py new file mode 100644 index 000000000000..a79b6b7249f2 --- /dev/null +++ b/datasets/flwr_datasets/partitioner/size_partitioner.py @@ -0,0 +1,128 @@ +# 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. +# ============================================================================== +"""SizePartitioner class.""" + + +import warnings +from collections.abc import Sequence + +import datasets +from flwr_datasets.partitioner.partitioner import Partitioner + + +class SizePartitioner(Partitioner): + """Partitioner that creates each partition with the size specified by a user. + + Parameters + ---------- + partition_sizes : Sequence[int] + The size of each partition. partition_id 0 will have partition_sizes[0] + samples, partition_id 1 will have partition_sizes[1] samples, etc. + + Examples + -------- + >>> from flwr_datasets import FederatedDataset + >>> from flwr_datasets.partitioner import SizePartitioner + >>> + >>> partition_sizes = [15_000, 5_000, 30_000] + >>> partitioner = SizePartitioner(partition_sizes) + >>> fds = FederatedDataset(dataset="cifar10", partitioners={"train": partitioner}) + """ + + def __init__(self, partition_sizes: Sequence[int]) -> None: + super().__init__() + self._pre_ds_validate_partition_sizes(partition_sizes) + self._partition_sizes = partition_sizes + self._partition_id_to_indices: dict[int, list[int]] = {} + self._partition_id_to_indices_determined = False + + def load_partition(self, partition_id: int) -> datasets.Dataset: + """Load a single partition of the size of partition_sizes[partition_id]. + + For example if given partition_sizes=[20_000, 10_000, 30_000], + then partition_id=0 will return a partition of size 20_000, + partition_id=1 will return a partition of size 10_000, etc. + + Parameters + ---------- + partition_id : int + The index that corresponds to the requested partition. + + Returns + ------- + dataset_partition : Dataset + Single dataset partition. + """ + 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._determine_partition_id_to_indices_if_needed() + return len(self._partition_sizes) + + @property + def partition_id_to_indices(self) -> dict[int, list[int]]: + """Partition id to indices (the result of partitioning).""" + self._determine_partition_id_to_indices_if_needed() + return self._partition_id_to_indices + + 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._post_ds_validate_partition_sizes() + start = 0 + end = 0 + for partition_id, partition_size in enumerate(self._partition_sizes): + end += partition_size + indices = list(range(start, end)) + self._partition_id_to_indices[partition_id] = indices + start = end + self._partition_id_to_indices_determined = True + + def _pre_ds_validate_partition_sizes(self, partition_sizes: Sequence[int]) -> None: + """Check if the partition sizes are valid (no information about the dataset).""" + if not isinstance(partition_sizes, Sequence): + raise ValueError("Partition sizes must be a sequence.") + if len(partition_sizes) == 0: + raise ValueError("Partition sizes must not be empty.") + if not all( + isinstance(partition_size, int) for partition_size in partition_sizes + ): + raise ValueError("All partition sizes must be integers.") + if not all(partition_size > 0 for partition_size in partition_sizes): + raise ValueError("All partition sizes must be greater than zero.") + + def _post_ds_validate_partition_sizes(self) -> None: + """Validate the partition sizes against the dataset size.""" + desired_partition_sizes = sum(self._partition_sizes) + dataset_size = len(self.dataset) + if desired_partition_sizes > dataset_size: + raise ValueError( + f"The sum of partition sizes sum({self._partition_sizes})" + f"= {desired_partition_sizes} is greater than the size of" + f" the dataset {dataset_size}." + ) + if desired_partition_sizes < dataset_size: + warnings.warn( + f"The sum of partition sizes is {desired_partition_sizes}, which is" + f"smaller than the size of the dataset: {dataset_size}. " + f"Ignore this warning if it is the desired behavior.", + stacklevel=1, + ) diff --git a/datasets/flwr_datasets/partitioner/size_partitioner_test.py b/datasets/flwr_datasets/partitioner/size_partitioner_test.py new file mode 100644 index 000000000000..be8edf9d2764 --- /dev/null +++ b/datasets/flwr_datasets/partitioner/size_partitioner_test.py @@ -0,0 +1,392 @@ +# 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. +# ============================================================================== +"""Test ShardPartitioner.""" + + +# pylint: disable=W0212, R0913 +import unittest +from typing import Optional + +from datasets import Dataset +from flwr_datasets.partitioner.shard_partitioner import ShardPartitioner + + +def _dummy_setup( + num_rows: int, + partition_by: str, + num_partitions: int, + num_shards_per_partition: Optional[int], + shard_size: Optional[int], + keep_incomplete_shard: bool = False, +) -> tuple[Dataset, ShardPartitioner]: + """Create a dummy dataset for testing.""" + data = { + partition_by: [i % 3 for i in range(num_rows)], + "features": list(range(num_rows)), + } + dataset = Dataset.from_dict(data) + partitioner = ShardPartitioner( + num_partitions=num_partitions, + num_shards_per_partition=num_shards_per_partition, + partition_by=partition_by, + shard_size=shard_size, + keep_incomplete_shard=keep_incomplete_shard, + ) + partitioner.dataset = dataset + return dataset, partitioner + + +class TestShardPartitionerSpec1(unittest.TestCase): + """Test first possible initialization of ShardPartitioner. + + Specify num_shards_per_partition and shard_size arguments. + """ + + def test_correct_num_partitions(self) -> None: + """Test the correct number of partitions is created.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = 3 + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + _ = partitioner.load_partition(0) + num_partitions_created = len(partitioner._partition_id_to_indices.keys()) + self.assertEqual(num_partitions_created, num_partitions) + + def test_correct_partition_sizes(self) -> None: + """Test if the partitions sizes are as theoretically calculated.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = 3 + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + sizes = [len(partitioner.load_partition(i)) for i in range(num_partitions)] + sizes = sorted(sizes) + self.assertEqual(sizes, [30, 30, 30]) + + def test_unique_samples(self) -> None: + """Test if each partition has unique samples. + + (No duplicates along partitions). + """ + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = 3 + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + partitions = [ + partitioner.load_partition(i)["features"] for i in range(num_partitions) + ] + combined_list = [item for sublist in partitions for item in sublist] + combined_set = set(combined_list) + self.assertEqual(len(combined_list), len(combined_set)) + + +class TestShardPartitionerSpec2(unittest.TestCase): + """Test second possible initialization of ShardPartitioner. + + Specify shard_size and keep_incomplete_shard=False. This setting creates partitions + that might have various sizes (each shard is same size). + """ + + def test_correct_num_partitions(self) -> None: + """Test the correct number of partitions is created.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = None + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + _ = partitioner.load_partition(0) + num_partitions_created = len(partitioner._partition_id_to_indices.keys()) + self.assertEqual(num_partitions_created, num_partitions) + + def test_correct_partition_sizes(self) -> None: + """Test if the partitions sizes are as theoretically calculated.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = None + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + sizes = [len(partitioner.load_partition(i)) for i in range(num_partitions)] + sizes = sorted(sizes) + self.assertEqual(sizes, [30, 40, 40]) + + def test_unique_samples(self) -> None: + """Test if each partition has unique samples. + + (No duplicates along partitions). + """ + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = None + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + partitions = [ + partitioner.load_partition(i)["features"] for i in range(num_partitions) + ] + combined_list = [item for sublist in partitions for item in sublist] + combined_set = set(combined_list) + self.assertEqual(len(combined_list), len(combined_set)) + + +class TestShardPartitionerSpec3(unittest.TestCase): + """Test third possible initialization of ShardPartitioner. + + Specify shard_size and keep_incomplete_shard=True. This setting creates partitions + that might have various sizes (each shard is same size). + """ + + def test_correct_num_partitions(self) -> None: + """Test the correct number of partitions is created.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = None + shard_size = 10 + keep_incomplete_shard = True + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + _ = partitioner.load_partition(0) + num_partitions_created = len(partitioner._partition_id_to_indices.keys()) + self.assertEqual(num_partitions_created, num_partitions) + + def test_correct_partition_sizes(self) -> None: + """Test if the partitions sizes are as theoretically calculated.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = None + shard_size = 10 + keep_incomplete_shard = True + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + sizes = [len(partitioner.load_partition(i)) for i in range(num_partitions)] + sizes = sorted(sizes) + self.assertEqual(sizes, [33, 40, 40]) + + def test_unique_samples(self) -> None: + """Test if each partition has unique samples. + + (No duplicates along partitions). + """ + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = None + shard_size = 10 + keep_incomplete_shard = True + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + partitions = [ + partitioner.load_partition(i)["features"] for i in range(num_partitions) + ] + combined_list = [item for sublist in partitions for item in sublist] + combined_set = set(combined_list) + self.assertEqual(len(combined_list), len(combined_set)) + + +class TestShardPartitionerSpec4(unittest.TestCase): + """Test fourth possible initialization of ShardPartitioner. + + Specify num_shards_per_partition but not shard_size arguments. + """ + + def test_correct_num_partitions(self) -> None: + """Test the correct number of partitions is created.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = 3 + shard_size = None + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + _ = partitioner.load_partition(0) + num_partitions_created = len(partitioner._partition_id_to_indices.keys()) + self.assertEqual(num_partitions_created, num_partitions) + + def test_correct_partition_sizes(self) -> None: + """Test if the partitions sizes are as theoretically calculated.""" + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = 3 + shard_size = None + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + sizes = [len(partitioner.load_partition(i)) for i in range(num_partitions)] + sizes = sorted(sizes) + self.assertEqual(sizes, [36, 36, 36]) + + def test_unique_samples(self) -> None: + """Test if each partition has unique samples. + + (No duplicates along partitions). + """ + partition_by = "label" + num_rows = 113 + num_partitions = 3 + num_shards_per_partition = 3 + shard_size = None + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + partitions = [ + partitioner.load_partition(i)["features"] for i in range(num_partitions) + ] + combined_list = [item for sublist in partitions for item in sublist] + combined_set = set(combined_list) + self.assertEqual(len(combined_list), len(combined_set)) + + +class TestShardPartitionerIncorrectSpec(unittest.TestCase): + """Test the incorrect specification cases. + + The lack of correctness can be caused by the num_partitions, shard_size and + num_shards_per_partition can create. + """ + + def test_incorrect_specification(self) -> None: + """Test if the given specification makes the partitioning possible.""" + partition_by = "label" + num_rows = 10 + num_partitions = 3 + num_shards_per_partition = 2 + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + with self.assertRaises(ValueError): + _ = partitioner.load_partition(0) + + def test_too_big_shard_size(self) -> None: + """Test if it is impossible to create an empty partition.""" + partition_by = "label" + num_rows = 20 + num_partitions = 3 + num_shards_per_partition = None + shard_size = 10 + keep_incomplete_shard = False + _, partitioner = _dummy_setup( + num_rows, + partition_by, + num_partitions, + num_shards_per_partition, + shard_size, + keep_incomplete_shard, + ) + with self.assertRaises(ValueError): + _ = partitioner.load_partition(2).num_rows + + +if __name__ == "__main__": + unittest.main() From 22875a74ac0747333f91d97ba16ba0d35d1b0397 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 19 Sep 2024 10:25:58 +0200 Subject: [PATCH 06/28] refactor(examples) Update `advanced-pytorch` example (#4130) Co-authored-by: Chong Shen Ng --- examples/advanced-pytorch/.gitignore | 3 + examples/advanced-pytorch/README.md | 105 +++++++----- .../_static/fmnist_50_lda.png | Bin 0 -> 33801 bytes .../advanced-pytorch/_static/wandb_plots.png | Bin 0 -> 251711 bytes examples/advanced-pytorch/client.py | 160 ------------------ examples/advanced-pytorch/pyproject.toml | 58 +++++-- .../pytorch_example/__init__.py | 1 + .../pytorch_example/client_app.py | 122 +++++++++++++ .../pytorch_example/server_app.py | 96 +++++++++++ .../pytorch_example/strategy.py | 116 +++++++++++++ .../advanced-pytorch/pytorch_example/task.py | 159 +++++++++++++++++ examples/advanced-pytorch/requirements.txt | 5 - examples/advanced-pytorch/run.sh | 16 -- examples/advanced-pytorch/server.py | 121 ------------- examples/advanced-pytorch/utils.py | 117 ------------- 15 files changed, 598 insertions(+), 481 deletions(-) create mode 100644 examples/advanced-pytorch/.gitignore create mode 100644 examples/advanced-pytorch/_static/fmnist_50_lda.png create mode 100644 examples/advanced-pytorch/_static/wandb_plots.png delete mode 100644 examples/advanced-pytorch/client.py create mode 100644 examples/advanced-pytorch/pytorch_example/__init__.py create mode 100644 examples/advanced-pytorch/pytorch_example/client_app.py create mode 100644 examples/advanced-pytorch/pytorch_example/server_app.py create mode 100644 examples/advanced-pytorch/pytorch_example/strategy.py create mode 100644 examples/advanced-pytorch/pytorch_example/task.py delete mode 100644 examples/advanced-pytorch/requirements.txt delete mode 100755 examples/advanced-pytorch/run.sh delete mode 100644 examples/advanced-pytorch/server.py delete mode 100644 examples/advanced-pytorch/utils.py diff --git a/examples/advanced-pytorch/.gitignore b/examples/advanced-pytorch/.gitignore new file mode 100644 index 000000000000..014ee796bf45 --- /dev/null +++ b/examples/advanced-pytorch/.gitignore @@ -0,0 +1,3 @@ +__pycache__/ +outputs/ +wandb/ diff --git a/examples/advanced-pytorch/README.md b/examples/advanced-pytorch/README.md index ac0737673407..1771173c3925 100644 --- a/examples/advanced-pytorch/README.md +++ b/examples/advanced-pytorch/README.md @@ -1,77 +1,90 @@ --- -tags: [advanced, vision, fds] -dataset: [CIFAR-10] +tags: [advanced, vision, fds, wandb] +dataset: [Fashion-MNIST] framework: [torch, torchvision] --- -# Advanced Flower Example (PyTorch) +# Federated Learning with PyTorch and Flower (Advanced Example) -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: +> \[!TIP\] +> This example shows intermediate and advanced functionality of Flower. It you are new to Flower, it is recommended to start from the [quickstart-pytorch](https://github.com/adap/flower/tree/main/examples/quickstart-pytorch) example or the [quickstart PyTorch tutorial](https://flower.ai/docs/framework/tutorial-quickstart-pytorch.html). -- 10 clients (instead of just 2) -- Each client holds a local dataset of 5000 training examples and 1000 test examples (note that using the `run.sh` script will only select 10 data samples by default, as the `--toy` argument is set). -- Server-side model evaluation after parameter aggregation -- Hyperparameter schedule using config functions -- Custom return values -- Server-side parameter initialization +This example shows how to extend your `ClientApp` and `ServerApp` capabilities compared to what's shown in the [`quickstart-pytorch`](https://github.com/adap/flower/tree/main/examples/quickstart-pytorch) example. In particular, it will show how the `ClientApp`'s state (and object of type [RecordSet](https://flower.ai/docs/framework/ref-api/flwr.common.RecordSet.html)) can be used to enable stateful clients, facilitating the design of personalized federated learning strategies, among others. The `ServerApp` in this example makes use of a custom strategy derived from the built-in [FedAvg](https://flower.ai/docs/framework/ref-api/flwr.server.strategy.FedAvg.html). In addition, it will also showcase how to: -## Project Setup +1. Save model checkpoints +2. Save the metrics available at the strategy (e.g. accuracies, losses) +3. Log training artefacts to [Weights & Biases](https://wandb.ai/site) +4. Implement a simple decaying learning rate schedule across rounds -Start by cloning the example project. We prepared a single-line command that you can copy into your shell which will checkout the example for you: +The structure of this directory is as follows: ```shell -git clone --depth=1 https://github.com/adap/flower.git && mv flower/examples/advanced-pytorch . && rm -rf flower && cd advanced-pytorch +advanced-pytorch +├── pytorch_example +│ ├── __init__.py +│ ├── client_app.py # Defines your ClientApp +│ ├── server_app.py # Defines your ServerApp +│ ├── strategy.py # Defines a custom strategy +│ └── task.py # Defines your model, training and data loading +├── pyproject.toml # Project metadata like dependencies and configs +└── README.md ``` -This will create a new directory called `advanced-pytorch` containing the following files: +> \[!NOTE\] +> By default this example will log metrics to Weights & Biases. For this, you need to ensure that your system has logged in. Often it's as simple as executing `wandb login` on the terminal after installing `wandb`. Please, refer to this [quickstart guide](https://docs.wandb.ai/quickstart#2-log-in-to-wb) for more information. -```shell --- pyproject.toml --- requirements.txt --- client.py --- server.py --- README.md --- run.sh -``` +This examples uses [Flower Datasets](https://flower.ai/docs/datasets/) with the [Dirichlet Partitioner](https://flower.ai/docs/datasets/ref-api/flwr_datasets.partitioner.DirichletPartitioner.html#flwr_datasets.partitioner.DirichletPartitioner) to partition the [Fashion-MNIST](https://huggingface.co/datasets/zalando-datasets/fashion_mnist) dataset in a non-IID fashion into 50 partitions. -### Installing Dependencies +![](_static/fmnist_50_lda.png) -Project dependencies (such as `torch` and `flwr`) are defined in `pyproject.toml` and `requirements.txt`. We recommend [Poetry](https://python-poetry.org/docs/) to install those dependencies and manage your virtual environment ([Poetry installation](https://python-poetry.org/docs/#installation)) or [pip](https://pip.pypa.io/en/latest/development/), but feel free to use a different way of installing dependencies and managing virtual environments if you have other preferences. +> \[!TIP\] +> You can use Flower Datasets [built-in visualization tools](https://flower.ai/docs/datasets/tutorial-visualize-label-distribution.html) to easily generate plots like the one above. -#### Poetry +### Install dependencies and project -```shell -poetry install -poetry shell +Install the dependencies defined in `pyproject.toml` as well as the `pytorch_example` package. + +```bash +pip install -e . ``` -Poetry will install all your dependencies in a newly created virtual environment. To verify that everything works correctly you can run the following command: +## Run the project -```shell -poetry run python3 -c "import flwr" -``` +You can run your Flower project in both _simulation_ and _deployment_ mode without making changes to the code. If you are starting with Flower, we recommend you using the _simulation_ mode as it requires fewer components to be launched manually. By default, `flwr run` will make use of the Simulation Engine. -If you don't see any errors you're good to go! +When you run the project, the strategy will create a directory structure in the form of `outputs/date/time` and store two `JSON` files: `config.json` containing the `run-config` that the `ServerApp` receives; and `results.json` containing the results (accuracies, losses) that are generated at the strategy. -#### pip +By default, the metrics: {`centralized_accuracy`, `centralized_loss`, `federated_evaluate_accuracy`, `federated_evaluate_loss`} will be logged to Weights & Biases (they are also stored to the `results.json` previously mentioned). Upon executing `flwr run` you'll see a URL linking to your Weight&Biases dashboard wher you can see the metrics. -Write the command below in your terminal to install the dependencies according to the configuration file requirements.txt. +![](_static/wandb_plots.png) -```shell -pip install -r requirements.txt +### Run with the Simulation Engine + +With default parameters, 25% of the total 50 nodes (see `num-supernodes` in `pyproject.toml`) will be sampled for `fit` and 50% for an `evaluate` round. By default `ClientApp` objects will run on CPU. + +> \[!TIP\] +> To run your `ClientApps` on GPU or to adjust the degree or parallelism of your simulation, edit the `[tool.flwr.federations.local-simulation]` section in the `pyproject.tom`. + +```bash +flwr run . + +# To disable W&B +flwr run . --run-config use-wandb=false ``` -## Run Federated Learning with PyTorch and Flower +You can run the app using another federation (see `pyproject.toml`). For example, if you have a GPU available, select the `local-sim-gpu` federation: -The included `run.sh` will start the Flower server (using `server.py`), -sleep for 2 seconds to ensure that the server is up, and then start 10 Flower clients (using `client.py`) with only a small subset of the data (in order to run on any machine), -but this can be changed by removing the `--toy` argument in the script. You can simply start everything in a terminal as follows: +```bash +flwr run . local-sim-gpu +``` -```shell -# After activating your environment -./run.sh +You can also override some of the settings for your `ClientApp` and `ServerApp` defined in `pyproject.toml`. For example: + +```bash +flwr run . --run-config "num-server-rounds=5 fraction-fit=0.5" ``` -The `run.sh` script starts processes in the background so that you don't have to open eleven terminal windows. If you experiment with the code example and something goes wrong, simply using `CTRL + C` on Linux (or `CMD + C` on macOS) wouldn't normally kill all these processes, which is why the script ends with `trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT` and `wait`. This simply allows you to stop the experiment using `CTRL + C` (or `CMD + C`). If you change the script and anything goes wrong you can still use `killall python` (or `killall python3`) to kill all background processes (or a more specific command if you have other Python processes running that you don't want to kill). +### Run with the Deployment Engine -You can also manually run `python3 server.py` and `python3 client.py --client-id ` for as many clients as you want but you have to make sure that each command is run in a different terminal window (or a different computer on the network). In addition, you can make your clients use either `EfficienNet` (default) or `AlexNet` (but all clients in the experiment should use the same). Switch between models using the `--model` flag when launching `client.py` and `server.py`. +> \[!NOTE\] +> An update to this example will show how to run this Flower application with the Deployment Engine and TLS certificates, or with Docker. diff --git a/examples/advanced-pytorch/_static/fmnist_50_lda.png b/examples/advanced-pytorch/_static/fmnist_50_lda.png new file mode 100644 index 0000000000000000000000000000000000000000..9dfedc59a3de86c3670f0a6ab1175dcef15fd04c GIT binary patch literal 33801 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

    Hvrfe&ZRNcM-`kdDgQHy=`}kuB7&qqr z#GqHVy5FuKt#D*-aN}z}Lso5gekS1(Tw)K;hXWKnpg2^9bp@wG_0NN6?|!+O6vV(M*4w zRoX6dlPWC1Fvz|Sx#F(PAPc)QKOkIcSX?pRTc^k8+nEFZy3t|0YbIP-n|&O!vSM^a z6jC2{;}-CN`{1;HspT~WN?`{1h;Um#?aa-YUyNB4E{;^rbx&+7OFs@d8y9_Vu4r}$ z6L;?}4RN&%(umhs>F`W}^uMA`ljtsK(77bZncwsBJc_p7vutIQsa+xN*!5a7?nH&g z86O(Er*Bt4c0v9~*S*aJ<)(b_;vOt$#`pS$+OKIP)r1~g<+XAV(M4z+$4#{O6k-SQ z{(a?OIJPz~oD$UHB=p|mXaZf>qcydox zH^)-+wpD)|FKC9W4y{OPp3is8TzDYfiNbJeS3Q++g5 z?Fl*jxV}?}=I3uQeF~$S`V`beCn_LBur_V)tD0R2XxYN)`|Oc7=3R^HM)h~YRkvP= zMk91Y3eJk*v58Tg2CZ2n1AWNssZp3(zRRIz?kONv%^oJU*i@TeEl_*;DRe`i`+Kh? z!X#y^58}(P?u>uO$Nb9>wk7YtM^&+7EI(Xwx1cV=!;M-vz^z3H*i=%OgnI!FvFO{2G#Ukbm z9oÐ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%0Dh6ux@PHcubBR9XEZ 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

    =) z5Wgb{4|^eRa1mpJ4!5q88S&M@ELQRoiVONTOg>1sxdUXb&gX?go|JppX4IXB|PO8fRGsyw_q&g>$LM~f?rYJI>;aJ z-GR~nwe<0p2PvF7)Mx%&JRXe9@_4Fz5Ge0^<^$y3#ILQ*l53<@yh;H;Gb38kAi|2itES`gRQ^GS)zt%BStVWR}Xy$ni_Z zWI+QOyIiBMf`;lGsG}at0Etgk&r({L>^cShJ6JD(C7h~CE(kPHkBMy@R`~h`HC?K( z1-w)Ow&x83vkp2gG(QzLHL`~obAF{X>bL)N24TY-gUnS}4XvE*x;OpFX`LPyBTVQ| z!EdUHO+U-B?z^q$;`mv2r(lth|JX{C_9Kj2`8?p#rdj}Y%toE~F;yGH^l^fFLA9nQ z36!;RiU_c8XO`$K*@= z$o28s!%BivCJ*lIV@lZ+C?P8S)XlZC})6c_-2AmdUUjk=L#p&=X;U?PnaHetGBuETT9wCjFUas zzws-D{DZh_ZvBGQf%Y!fM&p8z{FU$tL%0AW5XoW^xjA!Yi^t;R{UXK14kzpV8acb= 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 N^sxSK*n{V9{x3QlxlRB8 literal 0 HcmV?d00001 diff --git a/examples/advanced-pytorch/client.py b/examples/advanced-pytorch/client.py deleted file mode 100644 index 1b93d45d950e..000000000000 --- a/examples/advanced-pytorch/client.py +++ /dev/null @@ -1,160 +0,0 @@ -import argparse -import warnings -from collections import OrderedDict - -import datasets -import flwr as fl -import torch -from torch.utils.data import DataLoader - -import utils - -warnings.filterwarnings("ignore") - - -class CifarClient(fl.client.NumPyClient): - def __init__( - self, - trainset: datasets.Dataset, - testset: datasets.Dataset, - device: torch.device, - model_str: str, - validation_split: int = 0.1, - ): - self.device = device - self.trainset = trainset - self.testset = testset - self.validation_split = validation_split - if model_str == "alexnet": - self.model = utils.load_alexnet(classes=10) - else: - self.model = utils.load_efficientnet(classes=10) - - def set_parameters(self, parameters): - """Loads a alexnet or efficientnet model and replaces it parameters with the - ones given.""" - - params_dict = zip(self.model.state_dict().keys(), parameters) - state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict}) - self.model.load_state_dict(state_dict, strict=True) - - def fit(self, parameters, config): - """Train parameters on the locally held training set.""" - - # Update local model parameters - self.set_parameters(parameters) - - # Get hyperparameters for this round - batch_size: int = config["batch_size"] - epochs: int = config["local_epochs"] - - train_valid = self.trainset.train_test_split(self.validation_split, seed=42) - trainset = train_valid["train"] - valset = train_valid["test"] - - train_loader = DataLoader(trainset, batch_size=batch_size, shuffle=True) - val_loader = DataLoader(valset, batch_size=batch_size) - - results = utils.train(self.model, train_loader, val_loader, epochs, self.device) - - parameters_prime = utils.get_model_params(self.model) - num_examples_train = len(trainset) - - return parameters_prime, num_examples_train, results - - def evaluate(self, parameters, config): - """Evaluate parameters on the locally held test set.""" - # Update local model parameters - self.set_parameters(parameters) - - # Get config values - steps: int = config["val_steps"] - - # Evaluate global model parameters on the local test data and return results - testloader = DataLoader(self.testset, batch_size=16) - - loss, accuracy = utils.test(self.model, testloader, steps, self.device) - return float(loss), len(self.testset), {"accuracy": float(accuracy)} - - -def client_dry_run(device: torch.device = "cpu"): - """Weak tests to check whether all client methods are working as expected.""" - - model = utils.load_efficientnet(classes=10) - trainset, testset = utils.load_partition(0) - trainset = trainset.select(range(10)) - testset = testset.select(range(10)) - client = CifarClient(trainset, testset, device) - client.fit( - utils.get_model_params(model), - {"batch_size": 16, "local_epochs": 1}, - ) - - client.evaluate(utils.get_model_params(model), {"val_steps": 32}) - - print("Dry Run Successful") - - -def main() -> None: - # Parse command line argument `partition` - parser = argparse.ArgumentParser(description="Flower") - parser.add_argument( - "--dry", - type=bool, - default=False, - required=False, - help="Do a dry-run to check the client", - ) - parser.add_argument( - "--client-id", - type=int, - default=0, - choices=range(0, 10), - required=False, - help="Specifies the artificial data partition of CIFAR10 to be used. \ - Picks partition 0 by default", - ) - parser.add_argument( - "--toy", - action="store_true", - help="Set to true to quicky run the client using only 10 datasamples. \ - Useful for testing purposes. Default: False", - ) - parser.add_argument( - "--use_cuda", - type=bool, - default=False, - required=False, - help="Set to true to use GPU. Default: False", - ) - parser.add_argument( - "--model", - type=str, - default="efficientnet", - choices=["efficientnet", "alexnet"], - help="Use either Efficientnet or Alexnet models. \ - If you want to achieve differential privacy, please use the Alexnet model", - ) - - args = parser.parse_args() - - device = torch.device( - "cuda:0" if torch.cuda.is_available() and args.use_cuda else "cpu" - ) - - if args.dry: - client_dry_run(device) - else: - # Load a subset of CIFAR-10 to simulate the local data partition - trainset, testset = utils.load_partition(args.client_id) - - if args.toy: - trainset = trainset.select(range(10)) - testset = testset.select(range(10)) - # Start Flower client - client = CifarClient(trainset, testset, device, args.model).to_client() - fl.client.start_client(server_address="127.0.0.1:8080", client=client) - - -if __name__ == "__main__": - main() diff --git a/examples/advanced-pytorch/pyproject.toml b/examples/advanced-pytorch/pyproject.toml index f2c9ad731196..553abeecb6ad 100644 --- a/examples/advanced-pytorch/pyproject.toml +++ b/examples/advanced-pytorch/pyproject.toml @@ -1,20 +1,46 @@ [build-system] -requires = ["poetry-core>=1.4.0"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" -[tool.poetry] -name = "advanced-pytorch" -version = "0.1.0" -description = "Advanced Flower/PyTorch Example" -authors = [ - "The Flower Authors ", - "Kaushik Amar Das ", +[project] +name = "pytorch-example" +version = "1.0.0" +description = "Federated Learning with PyTorch and Flower (Advanced Example)" +license = "Apache-2.0" +dependencies = [ + "flwr[simulation]>=1.11.0", + "flwr-datasets[vision]>=0.3.0", + "torch==2.2.1", + "torchvision==0.17.1", + "wandb==0.17.8", ] -[tool.poetry.dependencies] -python = ">=3.9,<3.11" -flwr = ">=1.0,<2.0" -flwr-datasets = { extras = ["vision"], version = ">=0.0.2,<1.0.0" } -torch = "1.13.1" -torchvision = "0.14.1" -validators = "0.18.2" +[tool.hatch.build.targets.wheel] +packages = ["."] + +[tool.flwr.app] +publisher = "flwrlabs" + +[tool.flwr.app.components] +serverapp = "pytorch_example.server_app:app" +clientapp = "pytorch_example.client_app:app" + +[tool.flwr.app.config] +num-server-rounds = 10 +fraction-fit = 0.25 +fraction-evaluate = 0.5 +local-epochs = 1 +server-device = "cpu" +use-wandb = true + +[tool.flwr.federations] +default = "local-sim" + +[tool.flwr.federations.local-sim] +options.num-supernodes = 50 +options.backend.client-resources.num-cpus = 2 # each ClientApp assumes to use 2CPUs +options.backend.client-resources.num-gpus = 0.0 # ratio of VRAM a ClientApp has access to +[tool.flwr.federations.local-sim-gpu] +options.num-supernodes = 50 +options.backend.client-resources.num-cpus = 2 +options.backend.client-resources.num-gpus = 0.25 diff --git a/examples/advanced-pytorch/pytorch_example/__init__.py b/examples/advanced-pytorch/pytorch_example/__init__.py new file mode 100644 index 000000000000..d93e8cdb922d --- /dev/null +++ b/examples/advanced-pytorch/pytorch_example/__init__.py @@ -0,0 +1 @@ +"""pytorch-example: A Flower / PyTorch app.""" diff --git a/examples/advanced-pytorch/pytorch_example/client_app.py b/examples/advanced-pytorch/pytorch_example/client_app.py new file mode 100644 index 000000000000..72a9c8323686 --- /dev/null +++ b/examples/advanced-pytorch/pytorch_example/client_app.py @@ -0,0 +1,122 @@ +"""pytorch-example: A Flower / PyTorch app.""" + +import torch +from pytorch_example.task import Net, get_weights, load_data, set_weights, test, train + +from flwr.client import ClientApp, NumPyClient +from flwr.common import Context, ParametersRecord, RecordSet, array_from_numpy + + +# Define Flower Client and client_fn +class FlowerClient(NumPyClient): + """A simple client that showcases how to use the state. + + It implements a basic version of `personalization` by which + the classification layer of the CNN is stored locally and used + and updated during `fit()` and used during `evaluate()`. + """ + + def __init__( + self, net, client_state: RecordSet, trainloader, valloader, local_epochs + ): + self.net: Net = net + self.client_state = client_state + self.trainloader = trainloader + self.valloader = valloader + self.local_epochs = local_epochs + self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + self.net.to(self.device) + self.local_layer_name = "classification-head" + + def fit(self, parameters, config): + """Train model locally. + + The client stores in its context the parameters of the last layer in the model + (i.e. the classification head). The classifier is saved at the end of the + training and used the next time this client participates. + """ + + # Apply weights from global models (the whole model is replaced) + set_weights(self.net, parameters) + + # Override weights in classification layer with those this client + # had at the end of the last fit() round it participated in + self._load_layer_weights_from_state() + + train_loss = train( + self.net, + self.trainloader, + self.local_epochs, + lr=float(config["lr"]), + device=self.device, + ) + # Save classification head to context's state to use in a future fit() call + self._save_layer_weights_to_state() + + # Return locally-trained model and metrics + return ( + get_weights(self.net), + len(self.trainloader.dataset), + {"train_loss": train_loss}, + ) + + def _save_layer_weights_to_state(self): + """Save last layer weights to state.""" + state_dict_arrays = {} + for k, v in self.net.fc2.state_dict().items(): + state_dict_arrays[k] = array_from_numpy(v.cpu().numpy()) + + # Add to recordset (replace if already exists) + self.client_state.parameters_records[self.local_layer_name] = ParametersRecord( + state_dict_arrays + ) + + def _load_layer_weights_from_state(self): + """Load last layer weights to state.""" + if self.local_layer_name not in self.client_state.parameters_records: + return + + state_dict = {} + param_records = self.client_state.parameters_records + for k, v in param_records[self.local_layer_name].items(): + state_dict[k] = torch.from_numpy(v.numpy()) + + # apply previously saved classification head by this client + self.net.fc2.load_state_dict(state_dict, strict=True) + + def evaluate(self, parameters, config): + """Evaluate the global model on the local validation set. + + Note the classification head is replaced with the weights this client had the + last time it trained the model. + """ + set_weights(self.net, parameters) + # Override weights in classification layer with those this client + # had at the end of the last fit() round it participated in + self._load_layer_weights_from_state() + loss, accuracy = test(self.net, self.valloader, self.device) + return loss, len(self.valloader.dataset), {"accuracy": accuracy} + + +def client_fn(context: Context): + # Load model and data + net = Net() + 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"] + + # Return Client instance + # We pass the state to persist information across + # participation rounds. Note that each client always + # receives the same Context instance (it's a 1:1 mapping) + client_state = context.state + return FlowerClient( + net, client_state, trainloader, valloader, local_epochs + ).to_client() + + +# Flower ClientApp +app = ClientApp( + client_fn, +) diff --git a/examples/advanced-pytorch/pytorch_example/server_app.py b/examples/advanced-pytorch/pytorch_example/server_app.py new file mode 100644 index 000000000000..3fa2ae26dc7f --- /dev/null +++ b/examples/advanced-pytorch/pytorch_example/server_app.py @@ -0,0 +1,96 @@ +"""pytorch-example: A Flower / PyTorch app.""" + +import torch +from pytorch_example.strategy import CustomFedAvg +from pytorch_example.task import ( + Net, + apply_eval_transforms, + get_weights, + set_weights, + test, +) +from torch.utils.data import DataLoader + +from datasets import load_dataset +from flwr.common import Context, ndarrays_to_parameters +from flwr.server import ServerApp, ServerAppComponents, ServerConfig + + +def gen_evaluate_fn( + testloader: DataLoader, + device: torch.device, +): + """Generate the function for centralized evaluation.""" + + def evaluate(server_round, parameters_ndarrays, config): + """Evaluate global model on centralized test set.""" + net = Net() + set_weights(net, parameters_ndarrays) + net.to(device) + loss, accuracy = test(net, testloader, device=device) + return loss, {"centralized_accuracy": accuracy} + + return evaluate + + +def on_fit_config(server_round: int): + """Construct `config` that clients receive when running `fit()`""" + lr = 0.1 + # Enable a simple form of learning rate decay + if server_round > 10: + lr /= 2 + return {"lr": lr} + + +# Define metric aggregation function +def weighted_average(metrics): + # Multiply accuracy of each client by number of examples used + accuracies = [num_examples * m["accuracy"] for num_examples, m in metrics] + examples = [num_examples for num_examples, _ in metrics] + + # Aggregate and return custom metric (weighted average) + return {"federated_evaluate_accuracy": sum(accuracies) / sum(examples)} + + +def server_fn(context: Context): + # Read from config + num_rounds = context.run_config["num-server-rounds"] + fraction_fit = context.run_config["fraction-fit"] + fraction_eval = context.run_config["fraction-evaluate"] + server_device = context.run_config["server-device"] + + # Initialize model parameters + ndarrays = get_weights(Net()) + parameters = ndarrays_to_parameters(ndarrays) + + # Prepare dataset for central evaluation + + # This is the exact same dataset as the one donwloaded by the clients via + # FlowerDatasets. However, we don't use FlowerDatasets for the server since + # partitioning is not needed. + # We make use of the "test" split only + global_test_set = load_dataset("zalando-datasets/fashion_mnist")["test"] + + testloader = DataLoader( + global_test_set.with_transform(apply_eval_transforms), + batch_size=32, + ) + + # Define strategy + strategy = CustomFedAvg( + run_config=context.run_config, + use_wandb=context.run_config["use-wandb"], + fraction_fit=fraction_fit, + fraction_evaluate=fraction_eval, + initial_parameters=parameters, + on_fit_config_fn=on_fit_config, + evaluate_fn=gen_evaluate_fn(testloader, device=server_device), + evaluate_metrics_aggregation_fn=weighted_average, + ) + config = ServerConfig(num_rounds=num_rounds) + + return ServerAppComponents(strategy=strategy, config=config) + + +# Create ServerApp +app = ServerApp(server_fn=server_fn) diff --git a/examples/advanced-pytorch/pytorch_example/strategy.py b/examples/advanced-pytorch/pytorch_example/strategy.py new file mode 100644 index 000000000000..97fc0010f143 --- /dev/null +++ b/examples/advanced-pytorch/pytorch_example/strategy.py @@ -0,0 +1,116 @@ +"""pytorch-example: A Flower / PyTorch app.""" + +import json +from logging import INFO + +import torch +import wandb +from pytorch_example.task import Net, create_run_dir, set_weights + +from flwr.common import logger, parameters_to_ndarrays +from flwr.common.typing import UserConfig +from flwr.server.strategy import FedAvg + +PROJECT_NAME = "FLOWER-advanced-pytorch" + + +class CustomFedAvg(FedAvg): + """A class that behaves like FedAvg but has extra functionality. + + This strategy: (1) saves results to the filesystem, (2) saves a + checkpoint of the global model when a new best is found, (3) logs + results to W&B if enabled. + """ + + def __init__(self, run_config: UserConfig, use_wandb: bool, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Create a directory where to save results from this run + self.save_path, self.run_dir = create_run_dir(run_config) + self.use_wandb = use_wandb + # Initialise W&B if set + if use_wandb: + self._init_wandb_project() + + # Keep track of best acc + self.best_acc_so_far = 0.0 + + # A dictionary to store results as they come + self.results = {} + + def _init_wandb_project(self): + # init W&B + wandb.init(project=PROJECT_NAME, name=f"{str(self.run_dir)}-ServerApp") + + def _store_results(self, tag: str, results_dict): + """Store results in dictionary, then save as JSON.""" + # Update results dict + if tag in self.results: + self.results[tag].append(results_dict) + else: + self.results[tag] = [results_dict] + + # Save results to disk. + # Note we overwrite the same file with each call to this function. + # While this works, a more sophisticated approach is preferred + # in situations where the contents to be saved are larger. + with open(f"{self.save_path}/results.json", "w", encoding="utf-8") as fp: + json.dump(self.results, fp) + + def _update_best_acc(self, round, accuracy, parameters): + """Determines if a new best global model has been found. + + If so, the model checkpoint is saved to disk. + """ + if accuracy > self.best_acc_so_far: + self.best_acc_so_far = accuracy + logger.log(INFO, "💡 New best global model found: %f", accuracy) + # You could save the parameters object directly. + # Instead we are going to apply them to a PyTorch + # model and save the state dict. + # Converts flwr.common.Parameters to ndarrays + ndarrays = parameters_to_ndarrays(parameters) + model = Net() + set_weights(model, ndarrays) + # Save the PyTorch model + file_name = f"model_state_acc_{accuracy}_round_{round}.pth" + torch.save(model.state_dict(), self.save_path / file_name) + + def store_results_and_log(self, server_round: int, tag: str, results_dict): + """A helper method that stores results and logs them to W&B if enabled.""" + # Store results + self._store_results( + tag=tag, + results_dict={"round": server_round, **results_dict}, + ) + + if self.use_wandb: + # Log centralized loss and metrics to W&B + wandb.log(results_dict, step=server_round) + + def evaluate(self, server_round, parameters): + """Run centralized evaluation if callback was passed to strategy init.""" + loss, metrics = super().evaluate(server_round, parameters) + + # Save model if new best central accuracy is found + self._update_best_acc(server_round, metrics["centralized_accuracy"], parameters) + + # Store and log + self.store_results_and_log( + server_round=server_round, + tag="centralized_evaluate", + results_dict={"centralized_loss": loss, **metrics}, + ) + return loss, metrics + + def aggregate_evaluate(self, server_round, results, failures): + """Aggregate results from federated evaluation.""" + loss, metrics = super().aggregate_evaluate(server_round, results, failures) + + # Store and log + self.store_results_and_log( + server_round=server_round, + tag="federated_evaluate", + results_dict={"federated_evaluate_loss": loss, **metrics}, + ) + return loss, metrics diff --git a/examples/advanced-pytorch/pytorch_example/task.py b/examples/advanced-pytorch/pytorch_example/task.py new file mode 100644 index 000000000000..0224e8236408 --- /dev/null +++ b/examples/advanced-pytorch/pytorch_example/task.py @@ -0,0 +1,159 @@ +"""pytorch-example: A Flower / PyTorch app.""" + +import json +from collections import OrderedDict +from datetime import datetime +from pathlib import Path + +import torch +import torch.nn as nn +import torch.nn.functional as F +from flwr_datasets import FederatedDataset +from flwr_datasets.partitioner import DirichletPartitioner +from torch.utils.data import DataLoader +from torchvision.transforms import ( + Compose, + Normalize, + RandomCrop, + RandomHorizontalFlip, + ToTensor, +) + +from flwr.common.typing import UserConfig + +FM_NORMALIZATION = ((0.1307,), (0.3081,)) +EVAL_TRANSFORMS = Compose([ToTensor(), Normalize(*FM_NORMALIZATION)]) +TRAIN_TRANSFORMS = Compose( + [ + RandomCrop(28, padding=4), + RandomHorizontalFlip(), + ToTensor(), + Normalize(*FM_NORMALIZATION), + ] +) + + +class Net(nn.Module): + """Model (simple CNN adapted for Fashion-MNIST)""" + + def __init__(self): + super().__init__() + self.conv1 = nn.Conv2d(1, 16, 5) + self.pool = nn.MaxPool2d(2, 2) + self.conv2 = nn.Conv2d(16, 32, 5) + self.fc1 = nn.Linear(32 * 4 * 4, 128) + self.fc2 = nn.Linear(128, 10) + + def forward(self, x): + x = self.pool(F.relu(self.conv1(x))) + x = self.pool(F.relu(self.conv2(x))) + x = x.view(-1, 32 * 4 * 4) + x = F.relu(self.fc1(x)) + return self.fc2(x) + + +def train(net, trainloader, epochs, lr, device): + """Train the model on the training set.""" + net.to(device) # move model to GPU if available + criterion = torch.nn.CrossEntropyLoss().to(device) + optimizer = torch.optim.SGD(net.parameters(), lr=lr, momentum=0.9) + net.train() + running_loss = 0.0 + for _ in range(epochs): + for batch in trainloader: + images = batch["image"] + labels = batch["label"] + optimizer.zero_grad() + loss = criterion(net(images.to(device)), labels.to(device)) + loss.backward() + optimizer.step() + running_loss += loss.item() + + avg_trainloss = running_loss / len(trainloader) + return avg_trainloss + + +def test(net, testloader, device): + """Validate the model on the test set.""" + net.to(device) + criterion = torch.nn.CrossEntropyLoss() + correct, loss = 0, 0.0 + with torch.no_grad(): + for batch in testloader: + images = batch["image"].to(device) + labels = batch["label"].to(device) + outputs = net(images) + loss += criterion(outputs, labels).item() + correct += (torch.max(outputs.data, 1)[1] == labels).sum().item() + accuracy = correct / len(testloader.dataset) + loss = loss / len(testloader) + return loss, accuracy + + +def get_weights(net): + return [val.cpu().numpy() for _, val in net.state_dict().items()] + + +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 apply_train_transforms(batch): + """Apply transforms to the partition from FederatedDataset.""" + batch["image"] = [TRAIN_TRANSFORMS(img) for img in batch["image"]] + return batch + + +def apply_eval_transforms(batch): + """Apply transforms to the partition from FederatedDataset.""" + batch["image"] = [EVAL_TRANSFORMS(img) for img in batch["image"]] + return batch + + +fds = None # Cache FederatedDataset + + +def load_data(partition_id: int, num_partitions: int): + """Load partition FashionMNIST data.""" + # Only initialize `FederatedDataset` once + global fds + if fds is None: + partitioner = DirichletPartitioner( + num_partitions=num_partitions, + partition_by="label", + alpha=1.0, + seed=42, + ) + fds = FederatedDataset( + dataset="zalando-datasets/fashion_mnist", + 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) + + train_partition = partition_train_test["train"].with_transform( + apply_train_transforms + ) + test_partition = partition_train_test["test"].with_transform(apply_eval_transforms) + trainloader = DataLoader(train_partition, batch_size=32, shuffle=True) + testloader = DataLoader(test_partition, batch_size=32) + return trainloader, testloader + + +def create_run_dir(config: UserConfig) -> Path: + """Create a directory where to save results from this run.""" + # Create output directory given current timestamp + current_time = datetime.now() + run_dir = current_time.strftime("%Y-%m-%d/%H-%M-%S") + # Save path is based on the current directory + save_path = Path.cwd() / f"outputs/{run_dir}" + save_path.mkdir(parents=True, exist_ok=False) + + # Save run config as json + with open(f"{save_path}/run_config.json", "w", encoding="utf-8") as fp: + json.dump(config, fp) + + return save_path, run_dir diff --git a/examples/advanced-pytorch/requirements.txt b/examples/advanced-pytorch/requirements.txt deleted file mode 100644 index f4d6a0774162..000000000000 --- a/examples/advanced-pytorch/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -flwr>=1.0, <2.0 -flwr-datasets[vision]>=0.0.2, <1.0.0 -torch==1.13.1 -torchvision==0.14.1 -validators==0.18.2 diff --git a/examples/advanced-pytorch/run.sh b/examples/advanced-pytorch/run.sh deleted file mode 100755 index c3d52491b987..000000000000 --- a/examples/advanced-pytorch/run.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -set -e -cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"/ - -python server.py --toy & -sleep 10 # Sleep for 10s to give the server enough time to start and dowload the dataset - -for i in `seq 0 9`; do - echo "Starting client $i" - python client.py --client-id=${i} --toy & -done - -# Enable CTRL+C to stop all background processes -trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM -# Wait for all background processes to complete -wait diff --git a/examples/advanced-pytorch/server.py b/examples/advanced-pytorch/server.py deleted file mode 100644 index 6b69512fb3b7..000000000000 --- a/examples/advanced-pytorch/server.py +++ /dev/null @@ -1,121 +0,0 @@ -import argparse -import warnings -from collections import OrderedDict -from typing import Dict, Optional, Tuple - -import flwr as fl -import torch -from flwr_datasets import FederatedDataset -from torch.utils.data import DataLoader - -import utils - -warnings.filterwarnings("ignore") - - -def fit_config(server_round: int): - """Return training configuration dict for each round. - - Keep batch size fixed at 32, perform two rounds of training with one local epoch, - increase to two local epochs afterwards. - """ - config = { - "batch_size": 16, - "local_epochs": 1 if server_round < 2 else 2, - } - return config - - -def evaluate_config(server_round: int): - """Return evaluation configuration dict for each round. - - Perform five local evaluation steps on each client (i.e., use five batches) during - rounds one to three, then increase to ten local evaluation steps. - """ - val_steps = 5 if server_round < 4 else 10 - return {"val_steps": val_steps} - - -def get_evaluate_fn(model: torch.nn.Module, toy: bool): - """Return an evaluation function for server-side evaluation.""" - - # Load data here to avoid the overhead of doing it in `evaluate` itself - centralized_data = utils.load_centralized_data() - if toy: - # use only 10 samples as validation set - centralized_data = centralized_data.select(range(10)) - - val_loader = DataLoader(centralized_data, batch_size=16) - - # The `evaluate` function will be called after every round - def evaluate( - server_round: int, - parameters: fl.common.NDArrays, - config: Dict[str, fl.common.Scalar], - ) -> Optional[Tuple[float, Dict[str, fl.common.Scalar]]]: - # Update model with the latest parameters - params_dict = zip(model.state_dict().keys(), parameters) - state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict}) - model.load_state_dict(state_dict, strict=True) - - loss, accuracy = utils.test(model, val_loader) - return loss, {"accuracy": accuracy} - - return evaluate - - -def main(): - """Load model for - 1. server-side parameter initialization - 2. server-side parameter evaluation - """ - - # Parse command line argument `partition` - parser = argparse.ArgumentParser(description="Flower") - parser.add_argument( - "--toy", - action="store_true", - help="Set to true to use only 10 datasamples for validation. \ - Useful for testing purposes. Default: False", - ) - parser.add_argument( - "--model", - type=str, - default="efficientnet", - choices=["efficientnet", "alexnet"], - help="Use either Efficientnet or Alexnet models. \ - If you want to achieve differential privacy, please use the Alexnet model", - ) - - args = parser.parse_args() - - if args.model == "alexnet": - model = utils.load_alexnet(classes=10) - else: - model = utils.load_efficientnet(classes=10) - - model_parameters = [val.cpu().numpy() for _, val in model.state_dict().items()] - - # Create strategy - strategy = fl.server.strategy.FedAvg( - fraction_fit=1.0, - fraction_evaluate=1.0, - min_fit_clients=2, - min_evaluate_clients=2, - min_available_clients=10, - evaluate_fn=get_evaluate_fn(model, args.toy), - on_fit_config_fn=fit_config, - on_evaluate_config_fn=evaluate_config, - initial_parameters=fl.common.ndarrays_to_parameters(model_parameters), - ) - - # Start Flower server for four rounds of federated learning - fl.server.start_server( - server_address="0.0.0.0:8080", - config=fl.server.ServerConfig(num_rounds=4), - strategy=strategy, - ) - - -if __name__ == "__main__": - main() diff --git a/examples/advanced-pytorch/utils.py b/examples/advanced-pytorch/utils.py deleted file mode 100644 index d2b3955c9fde..000000000000 --- a/examples/advanced-pytorch/utils.py +++ /dev/null @@ -1,117 +0,0 @@ -import warnings - -import torch -from flwr_datasets import FederatedDataset -from torchvision.models import AlexNet, efficientnet_b0 -from torchvision.transforms import CenterCrop, Compose, Normalize, Resize, ToTensor - -warnings.filterwarnings("ignore") - - -def load_partition(partition_id, toy: bool = False): - """Load partition CIFAR10 data.""" - fds = FederatedDataset(dataset="cifar10", partitioners={"train": 10}) - 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) - partition_train_test = partition_train_test.with_transform(apply_transforms) - return partition_train_test["train"], partition_train_test["test"] - - -def load_centralized_data(): - fds = FederatedDataset(dataset="cifar10", partitioners={"train": 10}) - centralized_data = fds.load_split("test") - centralized_data = centralized_data.with_transform(apply_transforms) - return centralized_data - - -def apply_transforms(batch): - """Apply transforms to the partition from FederatedDataset.""" - pytorch_transforms = Compose( - [ - Resize(256), - CenterCrop(224), - ToTensor(), - Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), - ] - ) - batch["img"] = [pytorch_transforms(img) for img in batch["img"]] - return batch - - -def train( - net, trainloader, valloader, epochs, device: torch.device = torch.device("cpu") -): - """Train the network on the training set.""" - print("Starting training...") - net.to(device) # move model to GPU if available - criterion = torch.nn.CrossEntropyLoss().to(device) - optimizer = torch.optim.SGD( - net.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4 - ) - net.train() - for _ in range(epochs): - for batch in trainloader: - images, labels = batch["img"], batch["label"] - images, labels = images.to(device), labels.to(device) - optimizer.zero_grad() - loss = criterion(net(images), labels) - loss.backward() - optimizer.step() - - net.to("cpu") # move model back to CPU - - train_loss, train_acc = test(net, trainloader) - val_loss, val_acc = test(net, valloader) - - results = { - "train_loss": train_loss, - "train_accuracy": train_acc, - "val_loss": val_loss, - "val_accuracy": val_acc, - } - return results - - -def test( - net, testloader, steps: int = None, device: torch.device = torch.device("cpu") -): - """Validate the network on the entire test set.""" - print("Starting evalutation...") - net.to(device) # move model to GPU if available - criterion = torch.nn.CrossEntropyLoss() - correct, loss = 0, 0.0 - net.eval() - with torch.no_grad(): - for batch_idx, batch in enumerate(testloader): - images, labels = batch["img"], batch["label"] - images, labels = images.to(device), labels.to(device) - outputs = net(images) - loss += criterion(outputs, labels).item() - _, predicted = torch.max(outputs.data, 1) - correct += (predicted == labels).sum().item() - if steps is not None and batch_idx == steps: - break - accuracy = correct / len(testloader.dataset) - net.to("cpu") # move model back to CPU - return loss, accuracy - - -def load_efficientnet(classes: int = 10): - """Loads EfficienNetB0 from TorchVision.""" - efficientnet = efficientnet_b0(pretrained=True) - # Re-init output linear layer with the right number of classes - model_classes = efficientnet.classifier[1].in_features - if classes != model_classes: - efficientnet.classifier[1] = torch.nn.Linear(model_classes, classes) - return efficientnet - - -def get_model_params(model): - """Returns a model's parameters.""" - return [val.cpu().numpy() for _, val in model.state_dict().items()] - - -def load_alexnet(classes): - """Load AlexNet model from TorchVision.""" - return AlexNet(num_classes=classes) From b5dc9965ee8a577356caa3cc08b6f89302ad4638 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 19 Sep 2024 10:33:18 +0200 Subject: [PATCH 07/28] refactor(examples) Make `quickstart-huggingface`use a smaller LM (#4206) --- examples/quickstart-huggingface/README.md | 6 +++--- examples/quickstart-huggingface/huggingface_example/task.py | 2 +- examples/quickstart-huggingface/pyproject.toml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/quickstart-huggingface/README.md b/examples/quickstart-huggingface/README.md index ac0acebb9b99..124689441656 100644 --- a/examples/quickstart-huggingface/README.md +++ b/examples/quickstart-huggingface/README.md @@ -8,7 +8,7 @@ framework: [transformers] This introductory example to using [🤗Transformers](https://huggingface.co/docs/transformers/en/index) with Flower. 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. -In this example, we will federated the training of a [DistilBERT](https://huggingface.co/distilbert/distilbert-base-uncased) modle on the [IMDB](https://huggingface.co/datasets/stanfordnlp/imdb) dataset. The data will be downloaded and partitioned using [Flower Datasets](https://flower.ai/docs/datasets/). This example runs best when a GPU is available. +In this example, we will federated the training of a [BERT-tiny](https://huggingface.co/prajjwal1/bert-tiny) modle on the [IMDB](https://huggingface.co/datasets/stanfordnlp/imdb) dataset. The data will be downloaded and partitioned using [Flower Datasets](https://flower.ai/docs/datasets/). This example runs best when a GPU is available. ## Set up the project @@ -57,7 +57,7 @@ You can run your Flower project in both _simulation_ and _deployment_ mode witho flwr run . ``` -Run the project in the `local-simulation-gpu` federation that gives CPU and GPU resources to each `ClientApp`. By default, at most 1x`ClientApp` (using ~12 GB of VRAM) will run in parallel in each available GPU. Note you can adjust the degree of paralellism but modifying the `client-resources` specification. +Run the project in the `local-simulation-gpu` federation that gives CPU and GPU resources to each `ClientApp`. By default, at most 4x`ClientApp` (using ~1 GB of VRAM each) will run in parallel in each available GPU. Note you can adjust the degree of paralellism but modifying the `client-resources` specification. ```bash # Run with the `local-simulation-gpu` federation @@ -67,7 +67,7 @@ flwr run . local-simulation-gpu You can also override some of the settings for your `ClientApp` and `ServerApp` defined in `pyproject.toml`. For example ```bash -flwr run --run-config num-server-rounds=5 +flwr run --run-config "num-server-rounds=5 fraction-fit=0.1" ``` > \[!TIP\] diff --git a/examples/quickstart-huggingface/huggingface_example/task.py b/examples/quickstart-huggingface/huggingface_example/task.py index 25304d134a67..1c5b8d087dca 100644 --- a/examples/quickstart-huggingface/huggingface_example/task.py +++ b/examples/quickstart-huggingface/huggingface_example/task.py @@ -40,7 +40,7 @@ def load_data( tokenizer = AutoTokenizer.from_pretrained(model_name, model_max_length=512) def tokenize_function(examples): - return tokenizer(examples["text"], truncation=True) + return tokenizer(examples["text"], truncation=True, add_special_tokens=True) partition_train_test = partition_train_test.map(tokenize_function, batched=True) partition_train_test = partition_train_test.remove_columns("text") diff --git a/examples/quickstart-huggingface/pyproject.toml b/examples/quickstart-huggingface/pyproject.toml index 696f05b33ebf..f479acfa0918 100644 --- a/examples/quickstart-huggingface/pyproject.toml +++ b/examples/quickstart-huggingface/pyproject.toml @@ -33,7 +33,7 @@ clientapp = "huggingface_example.client_app:app" [tool.flwr.app.config] num-server-rounds = 3 -model-name = "distilbert-base-uncased" +model-name = "prajjwal1/bert-tiny" fraction-fit = 0.05 fraction-evaluate = 0.1 @@ -46,4 +46,4 @@ options.num-supernodes = 100 [tool.flwr.federations.local-simulation-gpu] options.num-supernodes = 100 options.backend.client-resources.num-cpus = 4 # each ClientApp assumes to use 4CPUs -options.backend.client-resources.num-gpus = 1.0 # at most 1 ClientApp will run in a given GPU (lower it to increase parallelism) \ No newline at end of file +options.backend.client-resources.num-gpus = 0.25 # at most 4 ClientApp will run in a given GPU (lower it to increase parallelism) From 09c96c196c4bb04f695c4325c6d702cccb992b50 Mon Sep 17 00:00:00 2001 From: "Daniel J. Beutel" Date: Thu, 19 Sep 2024 10:40:58 +0200 Subject: [PATCH 08/28] fix(*:skip) Update .editorconfig (#4238) --- .editorconfig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.editorconfig b/.editorconfig index 321808ebaecf..103fe51237c8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -16,6 +16,14 @@ profile = black indent_style = space indent_size = 2 +[*.md] +indent_style = space +indent_size = 2 + [*.yml] indent_style = space indent_size = 2 + +[*.toml] +indent_style = space +indent_size = 4 From a8ec4fa53aadc430c26f2b15f592f0cd970d6163 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Thu, 19 Sep 2024 10:54:36 +0200 Subject: [PATCH 09/28] feat(framework:skip) Add gcc in Docker compose setup (#4187) Signed-off-by: Robert Steiner Co-authored-by: Flower <148336023+flwrmachine@users.noreply.github.com> --- .../tutorial-quickstart-docker-compose.rst | 7 ++++ src/docker/complete/compose.yml | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/doc/source/docker/tutorial-quickstart-docker-compose.rst b/doc/source/docker/tutorial-quickstart-docker-compose.rst index 49cef55ec5a2..36d9bd58e29e 100644 --- a/doc/source/docker/tutorial-quickstart-docker-compose.rst +++ b/doc/source/docker/tutorial-quickstart-docker-compose.rst @@ -283,6 +283,13 @@ In ``compose.yml``, add the following: dockerfile_inline: | FROM flwr/clientapp:${FLWR_VERSION:-|stable_flwr_version|} + USER root + RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + USER app + WORKDIR /app COPY --chown=app:app pyproject.toml . RUN sed -i 's/.*flwr\[simulation\].*//' pyproject.toml \ diff --git a/src/docker/complete/compose.yml b/src/docker/complete/compose.yml index 8874dc7f4c53..e1dc2f5ffc56 100644 --- a/src/docker/complete/compose.yml +++ b/src/docker/complete/compose.yml @@ -12,6 +12,14 @@ services: dockerfile_inline: | FROM flwr/superexec:${FLWR_VERSION:-1.11.1} + # gcc is required for the fastai quickstart example + USER root + RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + USER app + WORKDIR /app COPY --chown=app:app pyproject.toml . RUN sed -i 's/.*flwr\[simulation\].*//' pyproject.toml \ @@ -83,6 +91,14 @@ services: dockerfile_inline: | FROM flwr/clientapp:${FLWR_VERSION:-1.11.1} + # gcc is required for the fastai quickstart example + USER root + RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + USER app + WORKDIR /app COPY --chown=app:app pyproject.toml . RUN sed -i 's/.*flwr\[simulation\].*//' pyproject.toml \ @@ -106,6 +122,14 @@ services: dockerfile_inline: | FROM flwr/clientapp:${FLWR_VERSION:-1.11.1} + # gcc is required for the fastai quickstart example + USER root + RUN apt-get update \ + && apt-get -y --no-install-recommends install \ + build-essential \ + && rm -rf /var/lib/apt/lists/* + USER app + WORKDIR /app COPY --chown=app:app pyproject.toml . RUN sed -i 's/.*flwr\[simulation\].*//' pyproject.toml \ @@ -131,6 +155,14 @@ services: # dockerfile_inline: | # FROM flwr/clientapp:${FLWR_VERSION:-1.11.1} + # # gcc is required for the fastai quickstart example + # USER root + # RUN apt-get update \ + # && apt-get -y --no-install-recommends install \ + # build-essential \ + # && rm -rf /var/lib/apt/lists/* + # USER app + # WORKDIR /app # COPY --chown=app:app pyproject.toml . # RUN sed -i 's/.*flwr\[simulation\].*//' pyproject.toml \ From e49e837ebaceb546dbc2c84b27a543c647ea2155 Mon Sep 17 00:00:00 2001 From: Mohammad Naseri Date: Thu, 19 Sep 2024 10:02:03 +0100 Subject: [PATCH 10/28] refactor(framework) Add further unit tests to sint64 to uint64 conversion utils (#4237) --- .../flwr/server/superlink/state/utils_test.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/py/flwr/server/superlink/state/utils_test.py b/src/py/flwr/server/superlink/state/utils_test.py index 93e678ddd38a..d55e2ffd9aa3 100644 --- a/src/py/flwr/server/superlink/state/utils_test.py +++ b/src/py/flwr/server/superlink/state/utils_test.py @@ -20,7 +20,9 @@ from .utils import ( convert_sint64_to_uint64, + convert_sint64_values_in_dict_to_uint64, convert_uint64_to_sint64, + convert_uint64_values_in_dict_to_sint64, generate_rand_int_from_bytes, ) @@ -84,6 +86,62 @@ def test_uint64_to_sint64_to_uint64(self, expected: int) -> None: actual = convert_sint64_to_uint64(convert_uint64_to_sint64(expected)) self.assertEqual(expected, actual) + @parameterized.expand( # type: ignore + [ + # Test cases with uint64 values + ( + {"a": 0, "b": 2**63 - 1, "c": 2**63, "d": 2**64 - 1}, + ["a", "b", "c", "d"], + {"a": 0, "b": 2**63 - 1, "c": -(2**63), "d": -1}, + ), + ( + {"a": 1, "b": 2**62, "c": 2**63 + 1}, + ["a", "b", "c"], + {"a": 1, "b": 2**62, "c": -(2**63) + 1}, + ), + # Edge cases with mixed uint64 values and keys + ( + {"a": 2**64 - 1, "b": 12345, "c": 0}, + ["a", "b"], + {"a": -1, "b": 12345, "c": 0}, + ), + ] + ) + def test_convert_uint64_values_in_dict_to_sint64( + self, input_dict: dict[str, int], keys: list[str], expected_dict: dict[str, int] + ) -> None: + """Test uint64 to sint64 conversion in a dictionary.""" + convert_uint64_values_in_dict_to_sint64(input_dict, keys) + self.assertEqual(input_dict, expected_dict) + + @parameterized.expand( # type: ignore + [ + # Test cases with sint64 values + ( + {"a": 0, "b": 2**63 - 1, "c": -(2**63), "d": -1}, + ["a", "b", "c", "d"], + {"a": 0, "b": 2**63 - 1, "c": 2**63, "d": 2**64 - 1}, + ), + ( + {"a": -1, "b": -(2**63) + 1, "c": 12345}, + ["a", "b", "c"], + {"a": 2**64 - 1, "b": 2**63 + 1, "c": 12345}, + ), + # Edge cases with mixed sint64 values and keys + ( + {"a": -1, "b": 12345, "c": 0}, + ["a", "b"], + {"a": 2**64 - 1, "b": 12345, "c": 0}, + ), + ] + ) + def test_convert_sint64_values_in_dict_to_uint64( + self, input_dict: dict[str, int], keys: list[str], expected_dict: dict[str, int] + ) -> None: + """Test sint64 to uint64 conversion in a dictionary.""" + convert_sint64_values_in_dict_to_uint64(input_dict, keys) + self.assertEqual(input_dict, expected_dict) + def test_generate_rand_int_from_bytes_unsigned_int(self) -> None: """Test that the generated integer is unsigned (non-negative).""" for num_bytes in range(1, 9): From efdb900671d84cc189af489bea3ac04006fa15d7 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Thu, 19 Sep 2024 11:09:38 +0200 Subject: [PATCH 11/28] docs(framework) Add quickstart examples Docker compose guide (#4189) Signed-off-by: Robert Steiner Co-authored-by: Javier --- doc/source/docker/index.rst | 1 + ...run-quickstart-examples-docker-compose.rst | 122 ++++++++++++++++++ .../tutorial-quickstart-docker-compose.rst | 5 + 3 files changed, 128 insertions(+) create mode 100644 doc/source/docker/run-quickstart-examples-docker-compose.rst diff --git a/doc/source/docker/index.rst b/doc/source/docker/index.rst index ac6124b4c138..968f01581b34 100644 --- a/doc/source/docker/index.rst +++ b/doc/source/docker/index.rst @@ -44,3 +44,4 @@ Run Flower using Docker Compose :maxdepth: 1 tutorial-quickstart-docker-compose + run-quickstart-examples-docker-compose diff --git a/doc/source/docker/run-quickstart-examples-docker-compose.rst b/doc/source/docker/run-quickstart-examples-docker-compose.rst new file mode 100644 index 000000000000..b279fb66c45b --- /dev/null +++ b/doc/source/docker/run-quickstart-examples-docker-compose.rst @@ -0,0 +1,122 @@ +Run Flower Quickstart Examples with Docker Compose +================================================== + +Flower provides a set of `quickstart examples `_ +to help you get started with the framework. These examples are designed to demonstrate the +capabilities of Flower and by default run using the Simulation Engine. This guide demonstrates +how to run them using Flower's Deployment Engine via Docker Compose. + +.. important:: + + Some quickstart examples may have limitations or requirements that prevent them from running + on every environment. For more information, please see `Limitations`_. + +Prerequisites +------------- + +Before you start, make sure that: + +- The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally. +- The Docker daemon is running. +- Docker Compose is `installed `_. + +Run the Quickstart Example +-------------------------- + +#. Clone the quickstart example you like to run. For example, ``quickstart-pytorch``: + + .. code-block:: bash + + $ git clone --depth=1 https://github.com/adap/flower.git \ + && mv flower/examples/quickstart-pytorch . \ + && rm -rf flower && cd quickstart-pytorch + +#. Download the `compose.yml `_ file into the example directory: + + .. code-block:: bash + + $ curl https://raw.githubusercontent.com/adap/flower/refs/heads/main/src/docker/complete/compose.yml \ + -o compose.yml + +#. Build and start the services using the following command: + + .. code-block:: bash + + $ docker compose up --build -d + +#. Append the following lines to the end of the ``pyproject.toml`` file and save it: + + .. code-block:: toml + :caption: pyproject.toml + + [tool.flwr.federations.local-deployment] + address = "127.0.0.1:9093" + insecure = true + + .. note:: + + You can customize the string that follows ``tool.flwr.federations.`` to fit your needs. + However, please note that the string cannot contain a dot (``.``). + + In this example, ``local-deployment`` has been used. Just remember to replace + ``local-deployment`` with your chosen name in both the ``tool.flwr.federations.`` string + and the corresponding ``flwr run .`` command. + +#. Run the example: + + .. code-block:: bash + + $ flwr run . local-deployment + +#. Follow the logs of the SuperExec service: + + .. code-block:: bash + + $ docker compose logs superexec -f + +That is all it takes! You can monitor the progress of the run through the logs of the SuperExec. + +Run a Different Quickstart Example +---------------------------------- + +To run a different quickstart example, such as ``quickstart-tensorflow``, first, shut down the Docker +Compose services of the current example: + +.. code-block:: bash + + $ docker compose down + +After that, you can repeat the steps above. + +Limitations +----------- + +.. list-table:: + :header-rows: 1 + + * - Quickstart Example + - Limitations + * - quickstart-fastai + - None + * - examples/quickstart-huggingface + - For CPU-only environments, it requires at least 32GB of memory. + * - quickstart-jax + - The example has not yet been updated to work with the latest ``flwr`` version. + * - quickstart-mlcube + - The example has not yet been updated to work with the latest ``flwr`` version. + * - quickstart-mlx + - `Requires to run on macOS with Apple Silicon `_. + * - quickstart-monai + - None + * - quickstart-pandas + - The example has not yet been updated to work with the latest ``flwr`` version. + * - quickstart-pytorch-lightning + - Requires an older pip version that is not supported by the Flower Docker images. + * - quickstart-pytorch + - None + * - quickstart-sklearn-tabular + - None + * - quickstart-tabnet + - The example has not yet been updated to work with the latest ``flwr`` version. + * - quickstart-tensorflow + - Only runs on AMD64. diff --git a/doc/source/docker/tutorial-quickstart-docker-compose.rst b/doc/source/docker/tutorial-quickstart-docker-compose.rst index 36d9bd58e29e..7aeae1e2fb6b 100644 --- a/doc/source/docker/tutorial-quickstart-docker-compose.rst +++ b/doc/source/docker/tutorial-quickstart-docker-compose.rst @@ -396,3 +396,8 @@ Remove all services and volumes: $ docker compose down -v $ docker compose -f certs.yml down -v + +Where to Go Next +---------------- + +* :doc:`run-quickstart-examples-docker-compose` From a70449d431ddda26a7461bb66300f84a4b7cbc2a Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 19 Sep 2024 10:33:41 +0100 Subject: [PATCH 12/28] fix(framework:skip) Fix the `SqliteState.get_run` method (#4222) --- src/py/flwr/server/superlink/state/sqlite_state.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/py/flwr/server/superlink/state/sqlite_state.py b/src/py/flwr/server/superlink/state/sqlite_state.py index 286ab881f891..28d957a90bd3 100644 --- a/src/py/flwr/server/superlink/state/sqlite_state.py +++ b/src/py/flwr/server/superlink/state/sqlite_state.py @@ -782,8 +782,9 @@ def get_run(self, run_id: int) -> Optional[Run]: # Convert the uint64 value to sint64 for SQLite sint64_run_id = convert_uint64_to_sint64(run_id) query = "SELECT * FROM run WHERE run_id = ?;" - try: - row = self.query(query, (sint64_run_id,))[0] + rows = self.query(query, (sint64_run_id,)) + if rows: + row = rows[0] return Run( run_id=convert_sint64_to_uint64(row["run_id"]), fab_id=row["fab_id"], @@ -791,9 +792,8 @@ def get_run(self, run_id: int) -> Optional[Run]: fab_hash=row["fab_hash"], override_config=json.loads(row["override_config"]), ) - except sqlite3.IntegrityError: - log(ERROR, "`run_id` does not exist.") - return None + log(ERROR, "`run_id` does not exist.") + return None def acknowledge_ping(self, node_id: int, ping_interval: float) -> bool: """Acknowledge a ping received from a node, serving as a heartbeat.""" From ebb78ae705e413718231ffb873846405c88a733a Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Thu, 19 Sep 2024 12:19:12 +0100 Subject: [PATCH 13/28] feat(framework) Add SuperExec logcatcher (#3584) Signed-off-by: Robert Steiner Co-authored-by: Robert Steiner Co-authored-by: Chong Shen Ng Co-authored-by: Charles Beauville Co-authored-by: jafermarq Co-authored-by: Taner Topal Co-authored-by: Daniel J. Beutel --- src/py/flwr/superexec/exec_servicer.py | 64 ++++++++++++++++++++- src/py/flwr/superexec/exec_servicer_test.py | 21 ++++++- src/py/flwr/superexec/executor.py | 3 +- 3 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/py/flwr/superexec/exec_servicer.py b/src/py/flwr/superexec/exec_servicer.py index 5b729dbc2b8e..8bf384312fca 100644 --- a/src/py/flwr/superexec/exec_servicer.py +++ b/src/py/flwr/superexec/exec_servicer.py @@ -15,6 +15,9 @@ """SuperExec API servicer.""" +import select +import threading +import time from collections.abc import Generator from logging import ERROR, INFO from typing import Any @@ -33,6 +36,8 @@ from .executor import Executor, RunTracker +SELECT_TIMEOUT = 1 # Timeout for selecting ready-to-read file descriptors (in seconds) + class ExecServicer(exec_pb2_grpc.ExecServicer): """SuperExec API servicer.""" @@ -59,13 +64,66 @@ def StartRun( self.runs[run.run_id] = run + # Start a background thread to capture the log output + capture_thread = threading.Thread( + target=_capture_logs, args=(run,), daemon=True + ) + capture_thread.start() + return StartRunResponse(run_id=run.run_id) - def StreamLogs( + def StreamLogs( # pylint: disable=C0103 self, request: StreamLogsRequest, context: grpc.ServicerContext ) -> Generator[StreamLogsResponse, Any, None]: """Get logs.""" - logs = ["a", "b", "c"] + log(INFO, "ExecServicer.StreamLogs") + + # Exit if `run_id` not found + if request.run_id not in self.runs: + context.abort(grpc.StatusCode.NOT_FOUND, "Run ID not found") + + last_sent_index = 0 while context.is_active(): - for i in range(len(logs)): # pylint: disable=C0200 + # Yield n'th row of logs, if n'th row < len(logs) + logs = self.runs[request.run_id].logs + for i in range(last_sent_index, len(logs)): yield StreamLogsResponse(log_output=logs[i]) + last_sent_index = len(logs) + + # Wait for and continue to yield more log responses only if the + # run isn't completed yet. If the run is finished, the entire log + # is returned at this point and the server ends the stream. + if self.runs[request.run_id].proc.poll() is not None: + log(INFO, "All logs for run ID `%s` returned", request.run_id) + return + + time.sleep(1.0) # Sleep briefly to avoid busy waiting + + +def _capture_logs( + run: RunTracker, +) -> None: + while True: + # Explicitly check if Popen.poll() is None. Required for `pytest`. + if run.proc.poll() is None: + # Select streams only when ready to read + ready_to_read, _, _ = select.select( + [run.proc.stdout, run.proc.stderr], + [], + [], + SELECT_TIMEOUT, + ) + # Read from std* and append to RunTracker.logs + for stream in ready_to_read: + line = stream.readline().rstrip() + if line: + run.logs.append(f"{line}") + + # Close std* to prevent blocking + elif run.proc.poll() is not None: + log(INFO, "Subprocess finished, exiting log capture") + if run.proc.stdout: + run.proc.stdout.close() + if run.proc.stderr: + run.proc.stderr.close() + break diff --git a/src/py/flwr/superexec/exec_servicer_test.py b/src/py/flwr/superexec/exec_servicer_test.py index 83717d63a36e..b777bc806fe5 100644 --- a/src/py/flwr/superexec/exec_servicer_test.py +++ b/src/py/flwr/superexec/exec_servicer_test.py @@ -16,11 +16,11 @@ import subprocess -from unittest.mock import MagicMock +from unittest.mock import MagicMock, Mock from flwr.proto.exec_pb2 import StartRunRequest # pylint: disable=E0611 -from .exec_servicer import ExecServicer +from .exec_servicer import ExecServicer, _capture_logs def test_start_run() -> None: @@ -50,3 +50,20 @@ def test_start_run() -> None: response = servicer.StartRun(request, context_mock) assert response.run_id == 10 + + +def test_capture_logs() -> None: + """Test capture_logs function.""" + run_res = Mock() + run_res.logs = [] + with subprocess.Popen( + ["echo", "success"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) as proc: + run_res.proc = proc + _capture_logs(run_res) + + assert len(run_res.logs) == 1 + assert run_res.logs[0] == "success" diff --git a/src/py/flwr/superexec/executor.py b/src/py/flwr/superexec/executor.py index 8d630d108b66..08b66a438e4d 100644 --- a/src/py/flwr/superexec/executor.py +++ b/src/py/flwr/superexec/executor.py @@ -15,7 +15,7 @@ """Execute and monitor a Flower run.""" from abc import ABC, abstractmethod -from dataclasses import dataclass +from dataclasses import dataclass, field from subprocess import Popen from typing import Optional @@ -28,6 +28,7 @@ class RunTracker: run_id: int proc: Popen # type: ignore + logs: list[str] = field(default_factory=list) class Executor(ABC): From 839e5d9e75e7c5b31c44d355a741022c28086214 Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Thu, 19 Sep 2024 12:34:34 +0100 Subject: [PATCH 14/28] refactor(framework) Update dependency and fix a print issue for FlowerTune template (#4220) --- src/py/flwr/cli/new/new.py | 2 +- src/py/flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/py/flwr/cli/new/new.py b/src/py/flwr/cli/new/new.py index d2f7179b45b4..e7dbee894314 100644 --- a/src/py/flwr/cli/new/new.py +++ b/src/py/flwr/cli/new/new.py @@ -275,7 +275,7 @@ def new( ) ) - _add = " huggingface-cli login\n" if framework_str == "flowertune" else "" + _add = " huggingface-cli login\n" if llm_challenge_str else "" print( typer.style( f" cd {package_name}\n" + " pip install -e .\n" + _add + " flwr run\n", 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 8c15739e92f6..8a4d49e7fd84 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 @@ -17,6 +17,7 @@ dependencies = [ "transformers==4.39.3", "sentencepiece==0.2.0", "omegaconf==2.3.0", + "hf_transfer==0.1.8", ] [tool.hatch.build.targets.wheel] From 94ba2375bb5d8bff94410bbbb1828263fbce18d2 Mon Sep 17 00:00:00 2001 From: Javier Date: Thu, 19 Sep 2024 16:13:36 +0200 Subject: [PATCH 15/28] refactor(examples) Remove mention of Docker in `quickstart-mlx` example (#4202) --- examples/quickstart-mlx/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/quickstart-mlx/README.md b/examples/quickstart-mlx/README.md index ef28c3728279..5914ce5f31dd 100644 --- a/examples/quickstart-mlx/README.md +++ b/examples/quickstart-mlx/README.md @@ -67,4 +67,4 @@ flwr run . --run-config "num-server-rounds=5 learning-rate=0.05" ### Run with the Deployment Engine > \[!NOTE\] -> An update to this example will show how to run this Flower project with the Deployment Engine and TLS certificates, or with Docker. +> An update to this example will show how to run this Flower project with the Deployment Engine and TLS certificates. From 1073b56bdb6725e2a4339ae0756ad910b6d508e5 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 19 Sep 2024 16:12:59 +0100 Subject: [PATCH 16/28] feat(framework) Add minimal `Control` service (#4239) --- src/proto/flwr/proto/control.proto | 25 ++++++++++ src/py/flwr/proto/control_pb2.py | 27 +++++++++++ src/py/flwr/proto/control_pb2.pyi | 7 +++ src/py/flwr/proto/control_pb2_grpc.py | 67 ++++++++++++++++++++++++++ src/py/flwr/proto/control_pb2_grpc.pyi | 27 +++++++++++ src/py/flwr_tool/protoc_test.py | 2 +- 6 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 src/proto/flwr/proto/control.proto create mode 100644 src/py/flwr/proto/control_pb2.py create mode 100644 src/py/flwr/proto/control_pb2.pyi create mode 100644 src/py/flwr/proto/control_pb2_grpc.py create mode 100644 src/py/flwr/proto/control_pb2_grpc.pyi diff --git a/src/proto/flwr/proto/control.proto b/src/proto/flwr/proto/control.proto new file mode 100644 index 000000000000..4e0867bbf9e4 --- /dev/null +++ b/src/proto/flwr/proto/control.proto @@ -0,0 +1,25 @@ +// 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; + +import "flwr/proto/driver.proto"; + +service Control { + // Request to create a new run + rpc CreateRun(CreateRunRequest) returns (CreateRunResponse) {} +} diff --git a/src/py/flwr/proto/control_pb2.py b/src/py/flwr/proto/control_pb2.py new file mode 100644 index 000000000000..d206e75cb388 --- /dev/null +++ b/src/py/flwr/proto/control_pb2.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: flwr/proto/control.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() + + +from flwr.proto import driver_pb2 as flwr_dot_proto_dot_driver__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x17\x66lwr/proto/driver.proto2U\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x62\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.control_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: + DESCRIPTOR._options = None + _globals['_CONTROL']._serialized_start=65 + _globals['_CONTROL']._serialized_end=150 +# @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/control_pb2.pyi b/src/py/flwr/proto/control_pb2.pyi new file mode 100644 index 000000000000..e08fa11c2caa --- /dev/null +++ b/src/py/flwr/proto/control_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/control_pb2_grpc.py b/src/py/flwr/proto/control_pb2_grpc.py new file mode 100644 index 000000000000..9c671be88a47 --- /dev/null +++ b/src/py/flwr/proto/control_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 driver_pb2 as flwr_dot_proto_dot_driver__pb2 + + +class ControlStub(object): + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.CreateRun = channel.unary_unary( + '/flwr.proto.Control/CreateRun', + request_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + ) + + +class ControlServicer(object): + """Missing associated documentation comment in .proto file.""" + + def CreateRun(self, request, context): + """Request to create a new run + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ControlServicer_to_server(servicer, server): + rpc_method_handlers = { + 'CreateRun': grpc.unary_unary_rpc_method_handler( + servicer.CreateRun, + request_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'flwr.proto.Control', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + + + # This class is part of an EXPERIMENTAL API. +class Control(object): + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def CreateRun(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.Control/CreateRun', + flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, + flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/control_pb2_grpc.pyi b/src/py/flwr/proto/control_pb2_grpc.pyi new file mode 100644 index 000000000000..f4613fa0e2f3 --- /dev/null +++ b/src/py/flwr/proto/control_pb2_grpc.pyi @@ -0,0 +1,27 @@ +""" +@generated by mypy-protobuf. Do not edit manually! +isort:skip_file +""" +import abc +import flwr.proto.driver_pb2 +import grpc + +class ControlStub: + def __init__(self, channel: grpc.Channel) -> None: ... + CreateRun: grpc.UnaryUnaryMultiCallable[ + flwr.proto.driver_pb2.CreateRunRequest, + flwr.proto.driver_pb2.CreateRunResponse] + """Request to create a new run""" + + +class ControlServicer(metaclass=abc.ABCMeta): + @abc.abstractmethod + def CreateRun(self, + request: flwr.proto.driver_pb2.CreateRunRequest, + context: grpc.ServicerContext, + ) -> flwr.proto.driver_pb2.CreateRunResponse: + """Request to create a new run""" + pass + + +def add_ControlServicer_to_server(servicer: ControlServicer, server: grpc.Server) -> None: ... diff --git a/src/py/flwr_tool/protoc_test.py b/src/py/flwr_tool/protoc_test.py index 6f9127304f25..f0784a4498d2 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) == 13 + assert len(PROTO_FILES) == 14 From fe16ff9c2c4d859d050f7c8a8ed3e7384801a756 Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Thu, 19 Sep 2024 16:50:56 +0100 Subject: [PATCH 17/28] feat(framework) Move run-related request/response to `run.proto` (#4240) --- src/proto/flwr/proto/control.proto | 2 +- src/proto/flwr/proto/driver.proto | 10 ---- src/proto/flwr/proto/run.proto | 12 +++++ src/py/flwr/proto/control_pb2.py | 8 +-- src/py/flwr/proto/control_pb2_grpc.py | 14 ++--- src/py/flwr/proto/control_pb2_grpc.pyi | 10 ++-- src/py/flwr/proto/driver_pb2.py | 39 ++++++-------- src/py/flwr/proto/driver_pb2.pyi | 52 ------------------- src/py/flwr/proto/driver_pb2_grpc.py | 12 ++--- src/py/flwr/proto/driver_pb2_grpc.pyi | 8 +-- src/py/flwr/proto/run_pb2.py | 27 ++++++---- src/py/flwr/proto/run_pb2.pyi | 52 +++++++++++++++++++ src/py/flwr/server/run_serverapp.py | 4 +- .../superlink/driver/driver_servicer.py | 4 +- src/py/flwr/superexec/deployment.py | 2 +- 15 files changed, 129 insertions(+), 127 deletions(-) diff --git a/src/proto/flwr/proto/control.proto b/src/proto/flwr/proto/control.proto index 4e0867bbf9e4..9747c2e3ea11 100644 --- a/src/proto/flwr/proto/control.proto +++ b/src/proto/flwr/proto/control.proto @@ -17,7 +17,7 @@ syntax = "proto3"; package flwr.proto; -import "flwr/proto/driver.proto"; +import "flwr/proto/run.proto"; service Control { // Request to create a new run diff --git a/src/proto/flwr/proto/driver.proto b/src/proto/flwr/proto/driver.proto index c7ae7dcf30f0..e26003862a76 100644 --- a/src/proto/flwr/proto/driver.proto +++ b/src/proto/flwr/proto/driver.proto @@ -21,7 +21,6 @@ import "flwr/proto/node.proto"; import "flwr/proto/task.proto"; import "flwr/proto/run.proto"; import "flwr/proto/fab.proto"; -import "flwr/proto/transport.proto"; service Driver { // Request run_id @@ -43,15 +42,6 @@ service Driver { rpc GetFab(GetFabRequest) returns (GetFabResponse) {} } -// CreateRun -message CreateRunRequest { - string fab_id = 1; - string fab_version = 2; - map override_config = 3; - Fab fab = 4; -} -message CreateRunResponse { uint64 run_id = 1; } - // GetNodes messages message GetNodesRequest { uint64 run_id = 1; } message GetNodesResponse { repeated Node nodes = 1; } diff --git a/src/proto/flwr/proto/run.proto b/src/proto/flwr/proto/run.proto index fc3294f7a583..ada72610e182 100644 --- a/src/proto/flwr/proto/run.proto +++ b/src/proto/flwr/proto/run.proto @@ -17,6 +17,7 @@ syntax = "proto3"; package flwr.proto; +import "flwr/proto/fab.proto"; import "flwr/proto/transport.proto"; message Run { @@ -26,5 +27,16 @@ message Run { map override_config = 4; string fab_hash = 5; } + +// CreateRun +message CreateRunRequest { + string fab_id = 1; + string fab_version = 2; + map override_config = 3; + Fab fab = 4; +} +message CreateRunResponse { uint64 run_id = 1; } + +// GetRun message GetRunRequest { uint64 run_id = 1; } message GetRunResponse { Run run = 1; } diff --git a/src/py/flwr/proto/control_pb2.py b/src/py/flwr/proto/control_pb2.py index d206e75cb388..2b8776509d32 100644 --- a/src/py/flwr/proto/control_pb2.py +++ b/src/py/flwr/proto/control_pb2.py @@ -12,16 +12,16 @@ _sym_db = _symbol_database.Default() -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 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x17\x66lwr/proto/driver.proto2U\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/run.proto2U\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.control_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_CONTROL']._serialized_start=65 - _globals['_CONTROL']._serialized_end=150 + _globals['_CONTROL']._serialized_start=62 + _globals['_CONTROL']._serialized_end=147 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/control_pb2_grpc.py b/src/py/flwr/proto/control_pb2_grpc.py index 9c671be88a47..987b9d8d7433 100644 --- a/src/py/flwr/proto/control_pb2_grpc.py +++ b/src/py/flwr/proto/control_pb2_grpc.py @@ -2,7 +2,7 @@ """Client and server classes corresponding to protobuf-defined services.""" 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 ControlStub(object): @@ -16,8 +16,8 @@ def __init__(self, channel): """ self.CreateRun = channel.unary_unary( '/flwr.proto.Control/CreateRun', - request_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - response_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + request_serializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, ) @@ -36,8 +36,8 @@ def add_ControlServicer_to_server(servicer, server): rpc_method_handlers = { 'CreateRun': grpc.unary_unary_rpc_method_handler( servicer.CreateRun, - request_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.FromString, - response_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.SerializeToString, + request_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( @@ -61,7 +61,7 @@ def CreateRun(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/flwr.proto.Control/CreateRun', - flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/control_pb2_grpc.pyi b/src/py/flwr/proto/control_pb2_grpc.pyi index f4613fa0e2f3..c38b7f15d125 100644 --- a/src/py/flwr/proto/control_pb2_grpc.pyi +++ b/src/py/flwr/proto/control_pb2_grpc.pyi @@ -3,23 +3,23 @@ isort:skip_file """ import abc -import flwr.proto.driver_pb2 +import flwr.proto.run_pb2 import grpc class ControlStub: def __init__(self, channel: grpc.Channel) -> None: ... CreateRun: grpc.UnaryUnaryMultiCallable[ - flwr.proto.driver_pb2.CreateRunRequest, - flwr.proto.driver_pb2.CreateRunResponse] + flwr.proto.run_pb2.CreateRunRequest, + flwr.proto.run_pb2.CreateRunResponse] """Request to create a new run""" class ControlServicer(metaclass=abc.ABCMeta): @abc.abstractmethod def CreateRun(self, - request: flwr.proto.driver_pb2.CreateRunRequest, + request: flwr.proto.run_pb2.CreateRunRequest, context: grpc.ServicerContext, - ) -> flwr.proto.driver_pb2.CreateRunResponse: + ) -> flwr.proto.run_pb2.CreateRunResponse: """Request to create a new run""" pass diff --git a/src/py/flwr/proto/driver_pb2.py b/src/py/flwr/proto/driver_pb2.py index 1322660cfac1..d294b03be5af 100644 --- a/src/py/flwr/proto/driver_pb2.py +++ b/src/py/flwr/proto/driver_pb2.py @@ -16,36 +16,27 @@ 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 fab_pb2 as flwr_dot_proto_dot_fab__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\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xeb\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\x12\x1c\n\x03\x66\x61\x62\x18\x04 \x01(\x0b\x32\x0f.flwr.proto.Fab\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(\x04\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"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\xc7\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\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\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\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"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\xc7\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\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\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_OVERRIDECONFIGENTRY']._options = None - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' - _globals['_CREATERUNREQUEST']._serialized_start=158 - _globals['_CREATERUNREQUEST']._serialized_end=393 - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=320 - _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=393 - _globals['_CREATERUNRESPONSE']._serialized_start=395 - _globals['_CREATERUNRESPONSE']._serialized_end=430 - _globals['_GETNODESREQUEST']._serialized_start=432 - _globals['_GETNODESREQUEST']._serialized_end=465 - _globals['_GETNODESRESPONSE']._serialized_start=467 - _globals['_GETNODESRESPONSE']._serialized_end=518 - _globals['_PUSHTASKINSREQUEST']._serialized_start=520 - _globals['_PUSHTASKINSREQUEST']._serialized_end=584 - _globals['_PUSHTASKINSRESPONSE']._serialized_start=586 - _globals['_PUSHTASKINSRESPONSE']._serialized_end=625 - _globals['_PULLTASKRESREQUEST']._serialized_start=627 - _globals['_PULLTASKRESREQUEST']._serialized_end=697 - _globals['_PULLTASKRESRESPONSE']._serialized_start=699 - _globals['_PULLTASKRESRESPONSE']._serialized_end=764 - _globals['_DRIVER']._serialized_start=767 - _globals['_DRIVER']._serialized_end=1222 + _globals['_GETNODESREQUEST']._serialized_start=129 + _globals['_GETNODESREQUEST']._serialized_end=162 + _globals['_GETNODESRESPONSE']._serialized_start=164 + _globals['_GETNODESRESPONSE']._serialized_end=215 + _globals['_PUSHTASKINSREQUEST']._serialized_start=217 + _globals['_PUSHTASKINSREQUEST']._serialized_end=281 + _globals['_PUSHTASKINSRESPONSE']._serialized_start=283 + _globals['_PUSHTASKINSRESPONSE']._serialized_end=322 + _globals['_PULLTASKRESREQUEST']._serialized_start=324 + _globals['_PULLTASKRESREQUEST']._serialized_end=394 + _globals['_PULLTASKRESRESPONSE']._serialized_start=396 + _globals['_PULLTASKRESRESPONSE']._serialized_end=461 + _globals['_DRIVER']._serialized_start=464 + _globals['_DRIVER']._serialized_end=919 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/driver_pb2.pyi b/src/py/flwr/proto/driver_pb2.pyi index d025e00474eb..77ceb496d70c 100644 --- a/src/py/flwr/proto/driver_pb2.pyi +++ b/src/py/flwr/proto/driver_pb2.pyi @@ -3,10 +3,8 @@ isort:skip_file """ import builtins -import flwr.proto.fab_pb2 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 @@ -15,56 +13,6 @@ import typing_extensions 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 - @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_ID_FIELD_NUMBER: builtins.int - FAB_VERSION_FIELD_NUMBER: builtins.int - OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int - FAB_FIELD_NUMBER: builtins.int - fab_id: typing.Text - fab_version: typing.Text - @property - def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... - @property - def fab(self) -> flwr.proto.fab_pb2.Fab: ... - def __init__(self, - *, - fab_id: typing.Text = ..., - fab_version: typing.Text = ..., - override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., - fab: typing.Optional[flwr.proto.fab_pb2.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","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): - 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___CreateRunResponse = CreateRunResponse - class GetNodesRequest(google.protobuf.message.Message): """GetNodes messages""" DESCRIPTOR: google.protobuf.descriptor.Descriptor diff --git a/src/py/flwr/proto/driver_pb2_grpc.py b/src/py/flwr/proto/driver_pb2_grpc.py index 6745bc7af62a..91e9fd8b9bdd 100644 --- a/src/py/flwr/proto/driver_pb2_grpc.py +++ b/src/py/flwr/proto/driver_pb2_grpc.py @@ -18,8 +18,8 @@ def __init__(self, channel): """ self.CreateRun = channel.unary_unary( '/flwr.proto.Driver/CreateRun', - request_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - response_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + request_serializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, ) self.GetNodes = channel.unary_unary( '/flwr.proto.Driver/GetNodes', @@ -98,8 +98,8 @@ def add_DriverServicer_to_server(servicer, server): rpc_method_handlers = { 'CreateRun': grpc.unary_unary_rpc_method_handler( servicer.CreateRun, - request_deserializer=flwr_dot_proto_dot_driver__pb2.CreateRunRequest.FromString, - response_serializer=flwr_dot_proto_dot_driver__pb2.CreateRunResponse.SerializeToString, + request_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.SerializeToString, ), 'GetNodes': grpc.unary_unary_rpc_method_handler( servicer.GetNodes, @@ -148,8 +148,8 @@ def CreateRun(request, timeout=None, metadata=None): return grpc.experimental.unary_unary(request, target, '/flwr.proto.Driver/CreateRun', - flwr_dot_proto_dot_driver__pb2.CreateRunRequest.SerializeToString, - flwr_dot_proto_dot_driver__pb2.CreateRunResponse.FromString, + flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.CreateRunResponse.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 7f9fd0acbd82..8f665301073d 100644 --- a/src/py/flwr/proto/driver_pb2_grpc.pyi +++ b/src/py/flwr/proto/driver_pb2_grpc.pyi @@ -11,8 +11,8 @@ import grpc class DriverStub: def __init__(self, channel: grpc.Channel) -> None: ... CreateRun: grpc.UnaryUnaryMultiCallable[ - flwr.proto.driver_pb2.CreateRunRequest, - flwr.proto.driver_pb2.CreateRunResponse] + flwr.proto.run_pb2.CreateRunRequest, + flwr.proto.run_pb2.CreateRunResponse] """Request run_id""" GetNodes: grpc.UnaryUnaryMultiCallable[ @@ -44,9 +44,9 @@ class DriverStub: class DriverServicer(metaclass=abc.ABCMeta): @abc.abstractmethod def CreateRun(self, - request: flwr.proto.driver_pb2.CreateRunRequest, + request: flwr.proto.run_pb2.CreateRunRequest, context: grpc.ServicerContext, - ) -> flwr.proto.driver_pb2.CreateRunResponse: + ) -> flwr.proto.run_pb2.CreateRunResponse: """Request run_id""" pass diff --git a/src/py/flwr/proto/run_pb2.py b/src/py/flwr/proto/run_pb2.py index 13fc43f90f8c..99ca4df5c44c 100644 --- a/src/py/flwr/proto/run_pb2.py +++ b/src/py/flwr/proto/run_pb2.py @@ -12,10 +12,11 @@ _sym_db = _symbol_database.Default() +from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2 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\x1a\x1a\x66lwr/proto/transport.proto\"\xd5\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\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\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\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(\x04\".\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\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd5\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\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\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\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\"\xeb\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\x12\x1c\n\x03\x66\x61\x62\x18\x04 \x01(\x0b\x32\x0f.flwr.proto.Fab\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(\x04\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Runb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -24,12 +25,20 @@ DESCRIPTOR._options = None _globals['_RUN_OVERRIDECONFIGENTRY']._options = None _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' - _globals['_RUN']._serialized_start=65 - _globals['_RUN']._serialized_end=278 - _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=205 - _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=278 - _globals['_GETRUNREQUEST']._serialized_start=280 - _globals['_GETRUNREQUEST']._serialized_end=311 - _globals['_GETRUNRESPONSE']._serialized_start=313 - _globals['_GETRUNRESPONSE']._serialized_end=359 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._options = None + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' + _globals['_RUN']._serialized_start=87 + _globals['_RUN']._serialized_end=300 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=227 + _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=300 + _globals['_CREATERUNREQUEST']._serialized_start=303 + _globals['_CREATERUNREQUEST']._serialized_end=538 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=227 + _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=300 + _globals['_CREATERUNRESPONSE']._serialized_start=540 + _globals['_CREATERUNRESPONSE']._serialized_end=575 + _globals['_GETRUNREQUEST']._serialized_start=577 + _globals['_GETRUNREQUEST']._serialized_end=608 + _globals['_GETRUNRESPONSE']._serialized_start=610 + _globals['_GETRUNRESPONSE']._serialized_end=656 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/run_pb2.pyi b/src/py/flwr/proto/run_pb2.pyi index e65feee9c518..26b69e7eed27 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.fab_pb2 import flwr.proto.transport_pb2 import google.protobuf.descriptor import google.protobuf.internal.containers @@ -51,7 +52,58 @@ class Run(google.protobuf.message.Message): def ClearField(self, field_name: typing_extensions.Literal["fab_hash",b"fab_hash","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 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 + @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_ID_FIELD_NUMBER: builtins.int + FAB_VERSION_FIELD_NUMBER: builtins.int + OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int + FAB_FIELD_NUMBER: builtins.int + fab_id: typing.Text + fab_version: typing.Text + @property + def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ... + @property + def fab(self) -> flwr.proto.fab_pb2.Fab: ... + def __init__(self, + *, + fab_id: typing.Text = ..., + fab_version: typing.Text = ..., + override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ..., + fab: typing.Optional[flwr.proto.fab_pb2.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","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): + 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___CreateRunResponse = CreateRunResponse + class GetRunRequest(google.protobuf.message.Message): + """GetRun""" DESCRIPTOR: google.protobuf.descriptor.Descriptor RUN_ID_FIELD_NUMBER: builtins.int run_id: builtins.int diff --git a/src/py/flwr/server/run_serverapp.py b/src/py/flwr/server/run_serverapp.py index c0628db04360..a9ec05fe90e0 100644 --- a/src/py/flwr/server/run_serverapp.py +++ b/src/py/flwr/server/run_serverapp.py @@ -35,11 +35,11 @@ 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 +from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611 +from flwr.proto.run_pb2 import ( # pylint: disable=E0611 CreateRunRequest, CreateRunResponse, ) -from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611 from .driver import Driver from .driver.grpc_driver import GrpcDriver diff --git a/src/py/flwr/server/superlink/driver/driver_servicer.py b/src/py/flwr/server/superlink/driver/driver_servicer.py index 4d7d6cb6ce89..3cafb3e71f7c 100644 --- a/src/py/flwr/server/superlink/driver/driver_servicer.py +++ b/src/py/flwr/server/superlink/driver/driver_servicer.py @@ -32,8 +32,6 @@ from flwr.common.typing import Fab from flwr.proto import driver_pb2_grpc # pylint: disable=E0611 from flwr.proto.driver_pb2 import ( # pylint: disable=E0611 - CreateRunRequest, - CreateRunResponse, GetNodesRequest, GetNodesResponse, PullTaskResRequest, @@ -44,6 +42,8 @@ from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611 from flwr.proto.node_pb2 import Node # pylint: disable=E0611 from flwr.proto.run_pb2 import ( # pylint: disable=E0611 + CreateRunRequest, + CreateRunResponse, GetRunRequest, GetRunResponse, Run, diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index 55e519cf5f27..d60870995d39 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -28,8 +28,8 @@ from flwr.common.logger import log from flwr.common.serde import fab_to_proto, user_config_to_proto from flwr.common.typing import Fab, UserConfig -from flwr.proto.driver_pb2 import CreateRunRequest # pylint: disable=E0611 from flwr.proto.driver_pb2_grpc import DriverStub +from flwr.proto.run_pb2 import CreateRunRequest # pylint: disable=E0611 from .executor import Executor, RunTracker From 02e181382df4b8f880bf92f92aafb357de50f161 Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Thu, 19 Sep 2024 18:17:12 +0100 Subject: [PATCH 18/28] fix(framework:skip) Fix SuperExec log capturing (#4242) --- src/py/flwr/superexec/deployment.py | 2 ++ src/py/flwr/superexec/exec_servicer.py | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/py/flwr/superexec/deployment.py b/src/py/flwr/superexec/deployment.py index d60870995d39..331fd817228e 100644 --- a/src/py/flwr/superexec/deployment.py +++ b/src/py/flwr/superexec/deployment.py @@ -167,6 +167,8 @@ def start_run( # Execute the command 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)) diff --git a/src/py/flwr/superexec/exec_servicer.py b/src/py/flwr/superexec/exec_servicer.py index 8bf384312fca..ebb12b5ddbd2 100644 --- a/src/py/flwr/superexec/exec_servicer.py +++ b/src/py/flwr/superexec/exec_servicer.py @@ -16,6 +16,7 @@ import select +import sys import threading import time from collections.abc import Generator @@ -95,7 +96,8 @@ def StreamLogs( # pylint: disable=C0103 # is returned at this point and the server ends the stream. if self.runs[request.run_id].proc.poll() is not None: log(INFO, "All logs for run ID `%s` returned", request.run_id) - return + context.set_code(grpc.StatusCode.OK) + context.cancel() time.sleep(1.0) # Sleep briefly to avoid busy waiting @@ -115,7 +117,12 @@ def _capture_logs( ) # Read from std* and append to RunTracker.logs for stream in ready_to_read: - line = stream.readline().rstrip() + # Flush stdout to view output in real time + readline = stream.readline() + sys.stdout.write(readline) + sys.stdout.flush() + # Append to logs + line = readline.rstrip() if line: run.logs.append(f"{line}") From 247cada644c059df4575063d2ed3010a0c6a753b Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Fri, 20 Sep 2024 16:32:04 +0100 Subject: [PATCH 19/28] fix(examples:skip) Specify `min_fit_clients` in `FedAvg` in the Secure Aggregation example (#4247) --- examples/flower-secure-aggregation/secaggexample/server_app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/flower-secure-aggregation/secaggexample/server_app.py b/examples/flower-secure-aggregation/secaggexample/server_app.py index 0f1b594317fa..0b95d68e4183 100644 --- a/examples/flower-secure-aggregation/secaggexample/server_app.py +++ b/examples/flower-secure-aggregation/secaggexample/server_app.py @@ -40,6 +40,7 @@ def main(driver: Driver, context: Context) -> None: strategy = FedAvg( # Select all available clients fraction_fit=1.0, + min_fit_clients=5, # Disable evaluation in demo fraction_evaluate=(0.0 if is_demo else context.run_config["fraction-evaluate"]), min_available_clients=5, From b6babe95addcbd1380f3894cc3ea71772119b72c Mon Sep 17 00:00:00 2001 From: Heng Pan Date: Mon, 23 Sep 2024 11:37:28 +0100 Subject: [PATCH 20/28] feat(framework) Add new RPCs to `Control` service (#4241) --- src/proto/flwr/proto/control.proto | 7 +++ src/proto/flwr/proto/run.proto | 20 ++++++ src/py/flwr/proto/control_pb2.py | 6 +- src/py/flwr/proto/control_pb2_grpc.py | 68 ++++++++++++++++++++ src/py/flwr/proto/control_pb2_grpc.pyi | 26 ++++++++ src/py/flwr/proto/run_pb2.py | 32 +++++++--- src/py/flwr/proto/run_pb2.pyi | 86 ++++++++++++++++++++++++++ 7 files changed, 233 insertions(+), 12 deletions(-) diff --git a/src/proto/flwr/proto/control.proto b/src/proto/flwr/proto/control.proto index 9747c2e3ea11..8b75c66fccaa 100644 --- a/src/proto/flwr/proto/control.proto +++ b/src/proto/flwr/proto/control.proto @@ -22,4 +22,11 @@ import "flwr/proto/run.proto"; service Control { // Request to create a new run rpc CreateRun(CreateRunRequest) returns (CreateRunResponse) {} + + // Get the status of a given run + rpc GetRunStatus(GetRunStatusRequest) returns (GetRunStatusResponse) {} + + // Update the status of a given run + rpc UpdateRunStatus(UpdateRunStatusRequest) + returns (UpdateRunStatusResponse) {} } diff --git a/src/proto/flwr/proto/run.proto b/src/proto/flwr/proto/run.proto index ada72610e182..2c9bd877f66c 100644 --- a/src/proto/flwr/proto/run.proto +++ b/src/proto/flwr/proto/run.proto @@ -28,6 +28,15 @@ message Run { string fab_hash = 5; } +message RunStatus { + // "starting", "running", "finished" + string status = 1; + // "completed", "failed", "stopped" or "" (non-finished) + string sub_status = 2; + // failure details + string details = 3; +} + // CreateRun message CreateRunRequest { string fab_id = 1; @@ -40,3 +49,14 @@ message CreateRunResponse { uint64 run_id = 1; } // GetRun message GetRunRequest { uint64 run_id = 1; } message GetRunResponse { Run run = 1; } + +// UpdateRunStatus +message UpdateRunStatusRequest { + uint64 run_id = 1; + RunStatus run_status = 2; +} +message UpdateRunStatusResponse {} + +// GetRunStatus +message GetRunStatusRequest { repeated uint64 run_ids = 1; } +message GetRunStatusResponse { map run_status_dict = 1; } diff --git a/src/py/flwr/proto/control_pb2.py b/src/py/flwr/proto/control_pb2.py index 2b8776509d32..eb1c18d8dcff 100644 --- a/src/py/flwr/proto/control_pb2.py +++ b/src/py/flwr/proto/control_pb2.py @@ -15,13 +15,13 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/run.proto2U\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x18\x66lwr/proto/control.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/run.proto2\x88\x02\n\x07\x43ontrol\x12J\n\tCreateRun\x12\x1c.flwr.proto.CreateRunRequest\x1a\x1d.flwr.proto.CreateRunResponse\"\x00\x12S\n\x0cGetRunStatus\x12\x1f.flwr.proto.GetRunStatusRequest\x1a .flwr.proto.GetRunStatusResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flwr.proto.control_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: DESCRIPTOR._options = None - _globals['_CONTROL']._serialized_start=62 - _globals['_CONTROL']._serialized_end=147 + _globals['_CONTROL']._serialized_start=63 + _globals['_CONTROL']._serialized_end=327 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/control_pb2_grpc.py b/src/py/flwr/proto/control_pb2_grpc.py index 987b9d8d7433..a59f90f15935 100644 --- a/src/py/flwr/proto/control_pb2_grpc.py +++ b/src/py/flwr/proto/control_pb2_grpc.py @@ -19,6 +19,16 @@ def __init__(self, channel): request_serializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString, response_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, ) + self.GetRunStatus = channel.unary_unary( + '/flwr.proto.Control/GetRunStatus', + request_serializer=flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.FromString, + ) + self.UpdateRunStatus = channel.unary_unary( + '/flwr.proto.Control/UpdateRunStatus', + request_serializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusRequest.SerializeToString, + response_deserializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusResponse.FromString, + ) class ControlServicer(object): @@ -31,6 +41,20 @@ def CreateRun(self, request, context): context.set_details('Method not implemented!') raise NotImplementedError('Method not implemented!') + def GetRunStatus(self, request, context): + """Get the status of a given run + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateRunStatus(self, request, context): + """Update the status of a given run + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + def add_ControlServicer_to_server(servicer, server): rpc_method_handlers = { @@ -39,6 +63,16 @@ def add_ControlServicer_to_server(servicer, server): request_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.FromString, response_serializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.SerializeToString, ), + 'GetRunStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetRunStatus, + request_deserializer=flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.SerializeToString, + ), + 'UpdateRunStatus': grpc.unary_unary_rpc_method_handler( + servicer.UpdateRunStatus, + request_deserializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusRequest.FromString, + response_serializer=flwr_dot_proto_dot_run__pb2.UpdateRunStatusResponse.SerializeToString, + ), } generic_handler = grpc.method_handlers_generic_handler( 'flwr.proto.Control', rpc_method_handlers) @@ -65,3 +99,37 @@ def CreateRun(request, flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString, options, channel_credentials, insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def GetRunStatus(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.Control/GetRunStatus', + flwr_dot_proto_dot_run__pb2.GetRunStatusRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.GetRunStatusResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) + + @staticmethod + def UpdateRunStatus(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.Control/UpdateRunStatus', + flwr_dot_proto_dot_run__pb2.UpdateRunStatusRequest.SerializeToString, + flwr_dot_proto_dot_run__pb2.UpdateRunStatusResponse.FromString, + options, channel_credentials, + insecure, call_credentials, compression, wait_for_ready, timeout, metadata) diff --git a/src/py/flwr/proto/control_pb2_grpc.pyi b/src/py/flwr/proto/control_pb2_grpc.pyi index c38b7f15d125..7817e2b12e31 100644 --- a/src/py/flwr/proto/control_pb2_grpc.pyi +++ b/src/py/flwr/proto/control_pb2_grpc.pyi @@ -13,6 +13,16 @@ class ControlStub: flwr.proto.run_pb2.CreateRunResponse] """Request to create a new run""" + GetRunStatus: grpc.UnaryUnaryMultiCallable[ + flwr.proto.run_pb2.GetRunStatusRequest, + flwr.proto.run_pb2.GetRunStatusResponse] + """Get the status of a given run""" + + UpdateRunStatus: grpc.UnaryUnaryMultiCallable[ + flwr.proto.run_pb2.UpdateRunStatusRequest, + flwr.proto.run_pb2.UpdateRunStatusResponse] + """Update the status of a given run""" + class ControlServicer(metaclass=abc.ABCMeta): @abc.abstractmethod @@ -23,5 +33,21 @@ class ControlServicer(metaclass=abc.ABCMeta): """Request to create a new run""" pass + @abc.abstractmethod + def GetRunStatus(self, + request: flwr.proto.run_pb2.GetRunStatusRequest, + context: grpc.ServicerContext, + ) -> flwr.proto.run_pb2.GetRunStatusResponse: + """Get the status of a given run""" + pass + + @abc.abstractmethod + def UpdateRunStatus(self, + request: flwr.proto.run_pb2.UpdateRunStatusRequest, + context: grpc.ServicerContext, + ) -> flwr.proto.run_pb2.UpdateRunStatusResponse: + """Update the status of a given run""" + pass + def add_ControlServicer_to_server(servicer: ControlServicer, server: grpc.Server) -> None: ... diff --git a/src/py/flwr/proto/run_pb2.py b/src/py/flwr/proto/run_pb2.py index 99ca4df5c44c..d59cc26fbb48 100644 --- a/src/py/flwr/proto/run_pb2.py +++ b/src/py/flwr/proto/run_pb2.py @@ -16,7 +16,7 @@ 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\x1a\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd5\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\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\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\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\"\xeb\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\x12\x1c\n\x03\x66\x61\x62\x18\x04 \x01(\x0b\x32\x0f.flwr.proto.Fab\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(\x04\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\".\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\x14\x66lwr/proto/fab.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xd5\x01\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\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\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\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\tRunStatus\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x12\n\nsub_status\x18\x02 \x01(\t\x12\x0f\n\x07\x64\x65tails\x18\x03 \x01(\t\"\xeb\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\x12\x1c\n\x03\x66\x61\x62\x18\x04 \x01(\x0b\x32\x0f.flwr.proto.Fab\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(\x04\"\x1f\n\rGetRunRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Run\"S\n\x16UpdateRunStatusRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12)\n\nrun_status\x18\x02 \x01(\x0b\x32\x15.flwr.proto.RunStatus\"\x19\n\x17UpdateRunStatusResponse\"&\n\x13GetRunStatusRequest\x12\x0f\n\x07run_ids\x18\x01 \x03(\x04\"\xb1\x01\n\x14GetRunStatusResponse\x12L\n\x0frun_status_dict\x18\x01 \x03(\x0b\x32\x33.flwr.proto.GetRunStatusResponse.RunStatusDictEntry\x1aK\n\x12RunStatusDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12$\n\x05value\x18\x02 \x01(\x0b\x32\x15.flwr.proto.RunStatus:\x02\x38\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -27,18 +27,32 @@ _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._options = None _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001' + _globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._options = None + _globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_options = b'8\001' _globals['_RUN']._serialized_start=87 _globals['_RUN']._serialized_end=300 _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_start=227 _globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=300 - _globals['_CREATERUNREQUEST']._serialized_start=303 - _globals['_CREATERUNREQUEST']._serialized_end=538 + _globals['_RUNSTATUS']._serialized_start=302 + _globals['_RUNSTATUS']._serialized_end=366 + _globals['_CREATERUNREQUEST']._serialized_start=369 + _globals['_CREATERUNREQUEST']._serialized_end=604 _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_start=227 _globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_end=300 - _globals['_CREATERUNRESPONSE']._serialized_start=540 - _globals['_CREATERUNRESPONSE']._serialized_end=575 - _globals['_GETRUNREQUEST']._serialized_start=577 - _globals['_GETRUNREQUEST']._serialized_end=608 - _globals['_GETRUNRESPONSE']._serialized_start=610 - _globals['_GETRUNRESPONSE']._serialized_end=656 + _globals['_CREATERUNRESPONSE']._serialized_start=606 + _globals['_CREATERUNRESPONSE']._serialized_end=641 + _globals['_GETRUNREQUEST']._serialized_start=643 + _globals['_GETRUNREQUEST']._serialized_end=674 + _globals['_GETRUNRESPONSE']._serialized_start=676 + _globals['_GETRUNRESPONSE']._serialized_end=722 + _globals['_UPDATERUNSTATUSREQUEST']._serialized_start=724 + _globals['_UPDATERUNSTATUSREQUEST']._serialized_end=807 + _globals['_UPDATERUNSTATUSRESPONSE']._serialized_start=809 + _globals['_UPDATERUNSTATUSRESPONSE']._serialized_end=834 + _globals['_GETRUNSTATUSREQUEST']._serialized_start=836 + _globals['_GETRUNSTATUSREQUEST']._serialized_end=874 + _globals['_GETRUNSTATUSRESPONSE']._serialized_start=877 + _globals['_GETRUNSTATUSRESPONSE']._serialized_end=1054 + _globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_start=979 + _globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_end=1054 # @@protoc_insertion_point(module_scope) diff --git a/src/py/flwr/proto/run_pb2.pyi b/src/py/flwr/proto/run_pb2.pyi index 26b69e7eed27..cec90c4d2d4c 100644 --- a/src/py/flwr/proto/run_pb2.pyi +++ b/src/py/flwr/proto/run_pb2.pyi @@ -52,6 +52,29 @@ class Run(google.protobuf.message.Message): def ClearField(self, field_name: typing_extensions.Literal["fab_hash",b"fab_hash","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 RunStatus(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + STATUS_FIELD_NUMBER: builtins.int + SUB_STATUS_FIELD_NUMBER: builtins.int + DETAILS_FIELD_NUMBER: builtins.int + status: typing.Text + """"starting", "running", "finished" """ + + sub_status: typing.Text + """"completed", "failed", "stopped" or "" (non-finished)""" + + details: typing.Text + """failure details""" + + def __init__(self, + *, + status: typing.Text = ..., + sub_status: typing.Text = ..., + details: typing.Text = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["details",b"details","status",b"status","sub_status",b"sub_status"]) -> None: ... +global___RunStatus = RunStatus + class CreateRunRequest(google.protobuf.message.Message): """CreateRun""" DESCRIPTOR: google.protobuf.descriptor.Descriptor @@ -126,3 +149,66 @@ class GetRunResponse(google.protobuf.message.Message): 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 UpdateRunStatusRequest(google.protobuf.message.Message): + """UpdateRunStatus""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_ID_FIELD_NUMBER: builtins.int + RUN_STATUS_FIELD_NUMBER: builtins.int + run_id: builtins.int + @property + def run_status(self) -> global___RunStatus: ... + def __init__(self, + *, + run_id: builtins.int = ..., + run_status: typing.Optional[global___RunStatus] = ..., + ) -> None: ... + def HasField(self, field_name: typing_extensions.Literal["run_status",b"run_status"]) -> builtins.bool: ... + def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id","run_status",b"run_status"]) -> None: ... +global___UpdateRunStatusRequest = UpdateRunStatusRequest + +class UpdateRunStatusResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + def __init__(self, + ) -> None: ... +global___UpdateRunStatusResponse = UpdateRunStatusResponse + +class GetRunStatusRequest(google.protobuf.message.Message): + """GetRunStatus""" + DESCRIPTOR: google.protobuf.descriptor.Descriptor + RUN_IDS_FIELD_NUMBER: builtins.int + @property + def run_ids(self) -> google.protobuf.internal.containers.RepeatedScalarFieldContainer[builtins.int]: ... + def __init__(self, + *, + run_ids: typing.Optional[typing.Iterable[builtins.int]] = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["run_ids",b"run_ids"]) -> None: ... +global___GetRunStatusRequest = GetRunStatusRequest + +class GetRunStatusResponse(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + class RunStatusDictEntry(google.protobuf.message.Message): + DESCRIPTOR: google.protobuf.descriptor.Descriptor + KEY_FIELD_NUMBER: builtins.int + VALUE_FIELD_NUMBER: builtins.int + key: builtins.int + @property + def value(self) -> global___RunStatus: ... + def __init__(self, + *, + key: builtins.int = ..., + value: typing.Optional[global___RunStatus] = ..., + ) -> 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_STATUS_DICT_FIELD_NUMBER: builtins.int + @property + def run_status_dict(self) -> google.protobuf.internal.containers.MessageMap[builtins.int, global___RunStatus]: ... + def __init__(self, + *, + run_status_dict: typing.Optional[typing.Mapping[builtins.int, global___RunStatus]] = ..., + ) -> None: ... + def ClearField(self, field_name: typing_extensions.Literal["run_status_dict",b"run_status_dict"]) -> None: ... +global___GetRunStatusResponse = GetRunStatusResponse From b370b0618fe53d6f01e5486c3269ee09dc0dcc6e Mon Sep 17 00:00:00 2001 From: Chong Shen Ng Date: Mon, 23 Sep 2024 13:54:22 +0100 Subject: [PATCH 21/28] feat(framework) Add `flwr log` (#3577) Co-authored-by: Charles Beauville Co-authored-by: jafermarq Co-authored-by: Taner Topal --- src/py/flwr/cli/app.py | 2 + src/py/flwr/cli/log.py | 196 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 src/py/flwr/cli/log.py diff --git a/src/py/flwr/cli/app.py b/src/py/flwr/cli/app.py index 93effea6df98..8baccb4638fc 100644 --- a/src/py/flwr/cli/app.py +++ b/src/py/flwr/cli/app.py @@ -19,6 +19,7 @@ from .build import build from .install import install +from .log import log from .new import new from .run import run @@ -35,6 +36,7 @@ app.command()(run) app.command()(build) app.command()(install) +app.command()(log) typer_click_object = get_command(app) diff --git a/src/py/flwr/cli/log.py b/src/py/flwr/cli/log.py new file mode 100644 index 000000000000..6915de1e00c5 --- /dev/null +++ b/src/py/flwr/cli/log.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 `log` command.""" + +import sys +import time +from logging import DEBUG, ERROR, INFO +from pathlib import Path +from typing import Annotated, Optional + +import grpc +import typer + +from flwr.cli.config_utils import load_and_validate +from flwr.common.grpc import GRPC_MAX_MESSAGE_LENGTH, create_channel +from flwr.common.logger import log as logger + +CONN_REFRESH_PERIOD = 60 # Connection refresh period for log streaming (seconds) + + +# pylint: disable=unused-argument +def stream_logs(run_id: int, channel: grpc.Channel, period: int) -> None: + """Stream logs from the beginning of a run with connection refresh.""" + + +# pylint: disable=unused-argument +def print_logs(run_id: int, channel: grpc.Channel, timeout: int) -> None: + """Print logs from the beginning of a run.""" + + +def on_channel_state_change(channel_connectivity: str) -> None: + """Log channel connectivity.""" + logger(DEBUG, channel_connectivity) + + +def log( + run_id: Annotated[ + int, + typer.Argument(help="The Flower run ID to query"), + ], + app: Annotated[ + Path, + typer.Argument(help="Path of the Flower project to run"), + ] = Path("."), + federation: Annotated[ + Optional[str], + typer.Argument(help="Name of the federation to run the app on"), + ] = None, + stream: Annotated[ + bool, + typer.Option( + "--stream/--show", + help="Flag to stream or print logs from the Flower run", + ), + ] = True, +) -> None: + """Get logs from a Flower project run.""" + typer.secho("Loading project configuration... ", fg=typer.colors.BLUE) + + pyproject_path = app / "pyproject.toml" if app else None + config, errors, warnings = load_and_validate(path=pyproject_path) + + if config is None: + typer.secho( + "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, + ) + sys.exit() + + if warnings: + typer.secho( + "Project configuration is missing the following " + "recommended properties:\n" + "\n".join([f"- {line}" for line in warnings]), + fg=typer.colors.RED, + bold=True, + ) + + typer.secho("Success", fg=typer.colors.GREEN) + + federation = federation or config["tool"]["flwr"]["federations"].get("default") + + 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 " + "`options.num-supernodes` value).", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + # Validate the federation exists in the configuration + 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}` federation declared in the " + "`pyproject.toml`.\n The following federations were found:\n\n" + + "\n".join(available_feds), + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + if "address" not in federation_config: + typer.secho( + "❌ `flwr log` currently works with `SuperExec`. Ensure that the correct" + "`SuperExec` address is provided in the `pyproject.toml`.", + fg=typer.colors.RED, + bold=True, + ) + raise typer.Exit(code=1) + + _log_with_superexec(federation_config, run_id, stream) + + +# pylint: disable-next=too-many-branches +def _log_with_superexec( + federation_config: dict[str, str], + run_id: int, + stream: bool, +) -> None: + 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( + "❌ `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_config["address"], + insecure=insecure, + root_certificates=root_certificates_bytes, + max_message_length=GRPC_MAX_MESSAGE_LENGTH, + interceptors=None, + ) + channel.subscribe(on_channel_state_change) + + if stream: + try: + while True: + logger(INFO, "Starting logstream for run_id `%s`", run_id) + stream_logs(run_id, channel, CONN_REFRESH_PERIOD) + time.sleep(2) + logger(DEBUG, "Reconnecting to logstream") + except KeyboardInterrupt: + logger(INFO, "Exiting logstream") + except grpc.RpcError as e: + # pylint: disable=E1101 + if e.code() == grpc.StatusCode.NOT_FOUND: + logger(ERROR, "Invalid run_id `%s`, exiting", run_id) + if e.code() == grpc.StatusCode.CANCELLED: + pass + finally: + channel.close() + else: + logger(INFO, "Printing logstream for run_id `%s`", run_id) + print_logs(run_id, channel, timeout=5) From fc6962d4983edc8172d8d8a38a2918a3632ab857 Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Mon, 23 Sep 2024 16:39:19 +0200 Subject: [PATCH 22/28] ci(framework:skip) Add job to update translation text (#3154) --- .github/workflows/update_translations.yml | 79 +++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 .github/workflows/update_translations.yml diff --git a/.github/workflows/update_translations.yml b/.github/workflows/update_translations.yml new file mode 100644 index 000000000000..9419f4aaef25 --- /dev/null +++ b/.github/workflows/update_translations.yml @@ -0,0 +1,79 @@ +name: Translations + +on: + schedule: + - cron: '0 0 * * *' # Runs every day at midnight + workflow_dispatch: # Allows to manually trigger the workflow + +jobs: + update-and-pr: + runs-on: ubuntu-22.04 + permissions: + contents: write + pull-requests: write + env: + branch-name: auto-update-trans-text + name: Update text + steps: + - uses: actions/checkout@v4 + + - name: Bootstrap + uses: ./.github/actions/bootstrap + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m poetry install + pip install sphinx==7.3.7 + + - name: Install pandoc + uses: nikeee/setup-pandoc@v1 + + - name: Update text and translations for all locales + run: | + cd doc + make update-text + for langDir in locales/*; do + if [ -d "$langDir" ]; then + lang=$(basename $langDir) + echo "Updating language $lang" + make update-lang lang=$lang + fi + done + + - name: Commit changes + run: | + git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add doc/locales + git commit -m "Update text and language files" + continue-on-error: true + + - name: Calculate diff # Even without doc changes the update-lang command will generate 228 additions and 60 deletions, so we only want to open a PR when there is more + id: calculate_diff + run: | + additions=$(git diff --numstat HEAD^1 | awk '{s+=$1} END {print s}') + deletions=$(git diff --numstat HEAD^1 | awk '{s+=$2} END {print s}') + echo "Additions: $additions" + echo "Deletions: $deletions" + echo "additions=$additions" >> $GITHUB_OUTPUT + echo "deletions=$deletions" >> $GITHUB_OUTPUT + + - name: Push changes + if: steps.calculate_diff.outputs.additions > 228 && steps.calculate_diff.outputs.deletions > 60 + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: '${{ env.branch-name }}' + + - name: Create Pull Request + if: steps.calculate_diff.outputs.additions > 228 && steps.calculate_diff.outputs.deletions > 60 + uses: peter-evans/create-pull-request@v6 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: '${{ env.branch-name }}' + delete-branch: true + title: 'docs(framework:skip) Update source texts for translations (automated)' + body: 'This PR is auto-generated to update text and language files.' + draft: false From 5e87ce88a411898f94b0b108fc8080d79e87918e Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Mon, 23 Sep 2024 23:29:59 +0200 Subject: [PATCH 23/28] docs(framework:skip) Update docker quickstart table (#4251) Signed-off-by: Robert Steiner --- .../docker/run-quickstart-examples-docker-compose.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/source/docker/run-quickstart-examples-docker-compose.rst b/doc/source/docker/run-quickstart-examples-docker-compose.rst index b279fb66c45b..5bdb33e991dd 100644 --- a/doc/source/docker/run-quickstart-examples-docker-compose.rst +++ b/doc/source/docker/run-quickstart-examples-docker-compose.rst @@ -98,8 +98,8 @@ Limitations - Limitations * - quickstart-fastai - None - * - examples/quickstart-huggingface - - For CPU-only environments, it requires at least 32GB of memory. + * - quickstart-huggingface + - None * - quickstart-jax - The example has not yet been updated to work with the latest ``flwr`` version. * - quickstart-mlcube @@ -109,7 +109,7 @@ Limitations * - quickstart-monai - None * - quickstart-pandas - - The example has not yet been updated to work with the latest ``flwr`` version. + - None * - quickstart-pytorch-lightning - Requires an older pip version that is not supported by the Flower Docker images. * - quickstart-pytorch From 2339b5180336c8496e361c37b59080cd3d584c0d Mon Sep 17 00:00:00 2001 From: Yan Gao Date: Tue, 24 Sep 2024 07:34:28 +0100 Subject: [PATCH 24/28] refactor(benchmarks) Update llm leaderboard readmes (#4249) --- benchmarks/flowertune-llm/README.md | 4 ++-- benchmarks/flowertune-llm/evaluation/README.md | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/benchmarks/flowertune-llm/README.md b/benchmarks/flowertune-llm/README.md index f45b7a6198b7..45cd8a828a89 100644 --- a/benchmarks/flowertune-llm/README.md +++ b/benchmarks/flowertune-llm/README.md @@ -13,13 +13,13 @@ As the first step, please register for a Flower account on [flower.ai/login](htt Then, create a new Python environment and install Flower. > [!TIP] -> We recommend using `pyenv` and the `virtualenv` plugin to create your environment. Other manager such as Conda would likely work too. Check the [documentation](https://flower.ai/docs/framework/how-to-install-flower.html) for alternative ways of installing Flower. +> We recommend using `pyenv` with the `virtualenv` plugin to create your environment. Other managers, such as Conda, will likely work as well. Check the [documentation](https://flower.ai/docs/framework/how-to-install-flower.html) for alternative ways to install Flower. ```shell pip install flwr ``` -On the new environment, create a new Flower project using the `FlowerTune` template. You will be prompted for a name to give to your project, your username, and for your choice of LLM challenge: +In the new environment, create a new Flower project using the `FlowerTune` template. You will be prompted for a name to give to your project, your username, and for your choice of LLM challenge: ```shell flwr new --framework=FlowerTune ``` diff --git a/benchmarks/flowertune-llm/evaluation/README.md b/benchmarks/flowertune-llm/evaluation/README.md index e2a7477fca76..c99ad640203a 100644 --- a/benchmarks/flowertune-llm/evaluation/README.md +++ b/benchmarks/flowertune-llm/evaluation/README.md @@ -39,6 +39,9 @@ The default template generated by `flwr new` (see the [Project Creation Instruct |:----------:|:-----:|:---------:|:--------------:|:---------------:|:-----:| | Pass@1 (%) | 31.60 | 23.78 | 28.57 | 25.47 | 27.36 | +> [!NOTE] +> In the LLM Leaderboard, we rank the submissions based on the **average** value derived from different evaluation datasets for each challenge. + ## Make submission on FlowerTune LLM Leaderboard From 983217f69b021b6804def1bbe3a0833caa419880 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 06:41:09 +0000 Subject: [PATCH 25/28] Update text and language files (#4252) --- doc/locales/fr/LC_MESSAGES/framework-docs.po | 2194 +++++++++++------ doc/locales/ko/LC_MESSAGES/framework-docs.po | 1989 ++++++++++----- .../pt_BR/LC_MESSAGES/framework-docs.po | 1939 ++++++++++----- .../zh_Hans/LC_MESSAGES/framework-docs.po | 2179 ++++++++++------ 4 files changed, 5574 insertions(+), 2727 deletions(-) diff --git a/doc/locales/fr/LC_MESSAGES/framework-docs.po b/doc/locales/fr/LC_MESSAGES/framework-docs.po index bee09019489f..681916e78ed5 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-09-15 09:09+0200\n" +"POT-Creation-Date: 2024-09-24 00:29+0000\n" "PO-Revision-Date: 2023-09-05 17:54+0000\n" "Last-Translator: Charles Beauville \n" "Language: fr\n" @@ -13,7 +13,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.15.0\n" +"Generated-By: Babel 2.16.0\n" #: ../../source/contributor-explanation-public-and-private-apis.rst:2 msgid "Public and private APIs" @@ -1522,7 +1522,7 @@ msgstr "" msgid "Setting up the repository" msgstr "Mise en place du référentiel" -#: ../../source/contributor-tutorial-contribute-on-github.rst:12 +#: ../../source/contributor-tutorial-contribute-on-github.rst:21 msgid "**Create a GitHub account and setup Git**" msgstr "**Créer un compte GitHub et configurer Git**" @@ -1571,7 +1571,7 @@ msgstr "" " des modifications localement et tu en gardes une trace à l'aide de Git, " "puis tu télécharges ton nouvel historique à nouveau sur GitHub." -#: ../../source/contributor-tutorial-contribute-on-github.rst:23 +#: ../../source/contributor-tutorial-contribute-on-github.rst:32 msgid "**Forking the Flower repository**" msgstr "**Fourche le dépôt de Flower**" @@ -1601,7 +1601,7 @@ msgstr "" " devrais voir dans le coin supérieur gauche que tu es en train de " "regarder ta propre version de Flower." -#: ../../source/contributor-tutorial-contribute-on-github.rst:34 +#: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "**Cloning your forked repository**" msgstr "**Clonage de ton dépôt forké**" @@ -1635,7 +1635,7 @@ msgstr "" "Cela créera un dossier `flower/` (ou le nom de ta fourche si tu l'as " "renommée) dans le répertoire de travail actuel." -#: ../../source/contributor-tutorial-contribute-on-github.rst:49 +#: ../../source/contributor-tutorial-contribute-on-github.rst:66 msgid "**Add origin**" msgstr "**Ajouter l'origine**" @@ -1663,7 +1663,7 @@ msgstr "" "Une fois que le \\ est copié, nous pouvons taper la commande " "suivante dans notre terminal :" -#: ../../source/contributor-tutorial-contribute-on-github.rst:68 +#: ../../source/contributor-tutorial-contribute-on-github.rst:90 msgid "**Add upstream**" msgstr "**Ajouter en amont**" @@ -1739,7 +1739,7 @@ msgstr "" msgid "And with Flower's repository:" msgstr "Et avec le référentiel de Flower :" -#: ../../source/contributor-tutorial-contribute-on-github.rst:114 +#: ../../source/contributor-tutorial-contribute-on-github.rst:122 msgid "**Create a new branch**" msgstr "**Créer une nouvelle branche**" @@ -1761,7 +1761,7 @@ msgstr "" "Pour ce faire, il suffit d'exécuter la commande suivante dans le " "répertoire du référentiel :" -#: ../../source/contributor-tutorial-contribute-on-github.rst:124 +#: ../../source/contributor-tutorial-contribute-on-github.rst:125 msgid "**Make changes**" msgstr "**Apporter des modifications**" @@ -1771,7 +1771,7 @@ msgstr "" "Écris du bon code et crée de merveilleuses modifications à l'aide de ton " "éditeur préféré !" -#: ../../source/contributor-tutorial-contribute-on-github.rst:127 +#: ../../source/contributor-tutorial-contribute-on-github.rst:138 msgid "**Test and format your code**" msgstr "**Teste et mets en forme ton code**" @@ -1789,7 +1789,7 @@ msgstr "" msgid "To do so, we have written a few scripts that you can execute:" msgstr "Pour ce faire, nous avons écrit quelques scripts que tu peux exécuter :" -#: ../../source/contributor-tutorial-contribute-on-github.rst:140 +#: ../../source/contributor-tutorial-contribute-on-github.rst:150 msgid "**Stage changes**" msgstr "**Changements de scène**" @@ -1815,7 +1815,7 @@ msgstr "" "version (last commit) et pour voir quels fichiers sont mis à disposition " "pour le commit, tu peux utiliser la commande :code:`git status`." -#: ../../source/contributor-tutorial-contribute-on-github.rst:152 +#: ../../source/contributor-tutorial-contribute-on-github.rst:160 msgid "**Commit changes**" msgstr "**Commit changes**" @@ -1838,7 +1838,7 @@ msgstr "" "commit. Il doit être écrit dans un style impératif et être concis. Un " "exemple serait :code:`git commit -m \"Ajouter des images au README\"`." -#: ../../source/contributor-tutorial-contribute-on-github.rst:162 +#: ../../source/contributor-tutorial-contribute-on-github.rst:171 msgid "**Push the changes to the fork**" msgstr "**Pousser les changements vers la fourche**" @@ -1865,7 +1865,7 @@ msgstr "" msgid "Creating and merging a pull request (PR)" msgstr "Créer et fusionner une pull request (PR)" -#: ../../source/contributor-tutorial-contribute-on-github.rst:176 +#: ../../source/contributor-tutorial-contribute-on-github.rst:206 msgid "**Create the PR**" msgstr "**Créer le PR**" @@ -1949,7 +1949,7 @@ msgstr "" " personne, tu as la possibilité de créer un brouillon de demande de " "traction :" -#: ../../source/contributor-tutorial-contribute-on-github.rst:208 +#: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "**Making new changes**" msgstr "**Faire de nouveaux changements**" @@ -1963,7 +1963,7 @@ msgstr "" "toujours y pousser de nouveaux commits de la même manière qu'auparavant, " "en apportant des modifications à la branche associée au PR." -#: ../../source/contributor-tutorial-contribute-on-github.rst:211 +#: ../../source/contributor-tutorial-contribute-on-github.rst:231 msgid "**Review the PR**" msgstr "**Review the PR**" @@ -2008,7 +2008,7 @@ msgstr "" "Une fois que toutes les conversations ont été résolues, tu peux " "redemander un examen." -#: ../../source/contributor-tutorial-contribute-on-github.rst:233 +#: ../../source/contributor-tutorial-contribute-on-github.rst:251 msgid "**Once the PR is merged**" msgstr "**Une fois que le PR est fusionné**" @@ -2340,6 +2340,7 @@ msgstr "Devenez un·e contributeur·ice" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 #: ../../source/docker/run-as-subprocess.rst:11 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:15 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:12 #: ../../source/docker/tutorial-quickstart-docker.rst:11 msgid "Prerequisites" @@ -3098,6 +3099,241 @@ msgid "" " the SuperNode to execute the ClientApp as a subprocess:" msgstr "" +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:2 +#, fuzzy +msgid "Run Flower Quickstart Examples with Docker Compose" +msgstr "Démarrage rapide XGBoost" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:4 +msgid "" +"Flower provides a set of `quickstart examples " +"`_ to help you get " +"started with the framework. These examples are designed to demonstrate " +"the capabilities of Flower and by default run using the Simulation " +"Engine. This guide demonstrates how to run them using Flower's Deployment" +" Engine via Docker Compose." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:11 +msgid "" +"Some quickstart examples may have limitations or requirements that " +"prevent them from running on every environment. For more information, " +"please see `Limitations`_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 +#: ../../source/docker/tutorial-quickstart-docker.rst:13 +msgid "Before you start, make sure that:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:19 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 +#: ../../source/docker/tutorial-quickstart-docker.rst:15 +msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:20 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker.rst:16 +msgid "The Docker daemon is running." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:21 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 +msgid "Docker Compose is `installed `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:24 +#, fuzzy +msgid "Run the Quickstart Example" +msgstr "Demande pour un nouveau Flower Example" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:26 +msgid "" +"Clone the quickstart example you like to run. For example, ``quickstart-" +"pytorch``:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:34 +msgid "" +"Download the `compose.yml " +"`_" +" file into the example directory:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:41 +#, fuzzy +msgid "Build and start the services using the following command:" +msgstr "Active la virtualenv en exécutant la commande suivante :" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:47 +#, fuzzy +msgid "" +"Append the following lines to the end of the ``pyproject.toml`` file and " +"save it:" +msgstr "Augmente la version mineure de ``pyproject.toml`` d'une unité." + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:49 +#: ../../source/docker/tutorial-quickstart-docker.rst:319 +msgid "pyproject.toml" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:58 +msgid "" +"You can customize the string that follows ``tool.flwr.federations.`` to " +"fit your needs. However, please note that the string cannot contain a dot" +" (``.``)." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:61 +msgid "" +"In this example, ``local-deployment`` has been used. Just remember to " +"replace ``local-deployment`` with your chosen name in both the " +"``tool.flwr.federations.`` string and the corresponding ``flwr run .`` " +"command." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:65 +#, fuzzy +msgid "Run the example:" +msgstr "Fédérer l'exemple" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:71 +msgid "Follow the logs of the SuperExec service:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:77 +msgid "" +"That is all it takes! You can monitor the progress of the run through the" +" logs of the SuperExec." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:80 +msgid "Run a Different Quickstart Example" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:82 +msgid "" +"To run a different quickstart example, such as ``quickstart-tensorflow``," +" first, shut down the Docker Compose services of the current example:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:89 +msgid "After that, you can repeat the steps above." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:92 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:98 +#, fuzzy +msgid "Limitations" +msgstr "Simulation de moniteur" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:97 +#, fuzzy +msgid "Quickstart Example" +msgstr "Démarrage rapide de JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:99 +#, fuzzy +msgid "quickstart-fastai" +msgstr "Démarrage rapide fastai" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:100 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:102 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:110 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:112 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:116 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:118 +#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 +#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 +#: ../../source/ref-changelog.md:929 +msgid "None" +msgstr "Aucun" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:101 +#, fuzzy +msgid "quickstart-huggingface" +msgstr "Quickstart tutorials" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:103 +#, fuzzy +msgid "quickstart-jax" +msgstr "Démarrage rapide de JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:104 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:106 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:120 +#, fuzzy +msgid "" +"The example has not yet been updated to work with the latest ``flwr`` " +"version." +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/docker/run-quickstart-examples-docker-compose.rst:105 +#, fuzzy +msgid "quickstart-mlcube" +msgstr "Démarrage rapide de JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:107 +#, fuzzy +msgid "quickstart-mlx" +msgstr "Démarrage rapide de JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:108 +msgid "" +"`Requires to run on macOS with Apple Silicon `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:109 +#, fuzzy +msgid "quickstart-monai" +msgstr "Démarrage rapide de JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:111 +#, fuzzy +msgid "quickstart-pandas" +msgstr "Démarrage rapide des Pandas" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:113 +#, fuzzy +msgid "quickstart-pytorch-lightning" +msgstr "Démarrage rapide de PyTorch Lightning" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:114 +msgid "" +"Requires an older pip version that is not supported by the Flower Docker " +"images." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:115 +#, fuzzy +msgid "quickstart-pytorch" +msgstr "Démarrage rapide de PyTorch" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:117 +#, fuzzy +msgid "quickstart-sklearn-tabular" +msgstr "Démarrage rapide de scikit-learn" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:119 +#, fuzzy +msgid "quickstart-tabnet" +msgstr "Démarrage rapide de JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:121 +#, fuzzy +msgid "quickstart-tensorflow" +msgstr "Démarrage rapide de TensorFlow" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:122 +msgid "Only runs on AMD64." +msgstr "" + #: ../../source/docker/set-environment-variables.rst:2 #, fuzzy msgid "Set Environment Variables" @@ -3128,21 +3364,6 @@ msgid "" " understanding the basic workflow that uses the minimum configurations." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 -#: ../../source/docker/tutorial-quickstart-docker.rst:13 -msgid "Before you start, make sure that:" -msgstr "" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 -#: ../../source/docker/tutorial-quickstart-docker.rst:15 -msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." -msgstr "" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 -#: ../../source/docker/tutorial-quickstart-docker.rst:16 -msgid "The Docker daemon is running." -msgstr "" - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:21 #: ../../source/docker/tutorial-quickstart-docker.rst:19 msgid "Step 1: Set Up" @@ -3568,10 +3789,6 @@ msgstr "" msgid "Add the following lines to the ``pyproject.toml``:" msgstr "Augmente la version mineure de ``pyproject.toml`` d'une unité." -#: ../../source/docker/tutorial-quickstart-docker.rst:319 -msgid "pyproject.toml" -msgstr "" - #: ../../source/docker/tutorial-quickstart-docker.rst:326 msgid "Run the ``quickstart-docker`` project by executing the command:" msgstr "" @@ -3621,6 +3838,7 @@ msgstr "" msgid "Remove the containers and the bridge network:" msgstr "" +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:401 #: ../../source/docker/tutorial-quickstart-docker.rst:399 #, fuzzy msgid "Where to Go Next" @@ -3657,10 +3875,6 @@ msgid "" "configuration that best suits your project's needs." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 -msgid "Docker Compose is `installed `_." -msgstr "" - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:23 msgid "Clone the Docker Compose ``complete`` directory:" msgstr "" @@ -3856,7 +4070,7 @@ msgstr "" #: ../../source/docker/tutorial-quickstart-docker-compose.rst:188 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:241 -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:362 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:369 msgid "Rerun the ``quickstart-compose`` project:" msgstr "" @@ -3920,75 +4134,80 @@ msgstr "" msgid "compose.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:303 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 msgid "" "If you also want to enable TLS for the new SuperNodes, duplicate the " "SuperNode definition for each new SuperNode service in the ``with-" "tls.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:306 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:313 msgid "" "Make sure that the names of the services match with the one in the " "``compose.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:308 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:315 msgid "In ``with-tls.yml``, add the following:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:317 msgid "with-tls.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:332 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 msgid "Step 8: Persisting the SuperLink State and Enabling TLS" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:334 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:341 msgid "" "To run Flower with persisted SuperLink state and enabled TLS, a slight " "change in the ``with-state.yml`` file is required:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:337 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:344 msgid "Comment out the lines 2-4 and uncomment the lines 5-9:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:346 msgid "with-state.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:356 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:363 #, fuzzy msgid "Restart the services:" msgstr "Démarrer le serveur" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:370 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:377 msgid "Step 9: Merge Multiple Compose Files" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:372 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:379 msgid "" "You can merge multiple Compose files into a single file. For instance, if" " you wish to combine the basic configuration with the TLS configuration, " "execute the following command:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:380 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:387 msgid "" "This will merge the contents of ``compose.yml`` and ``with-tls.yml`` into" " a new file called ``my_compose.yml``." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:384 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:391 msgid "Step 10: Clean Up" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:386 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:393 msgid "Remove all services and volumes:" msgstr "" +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:403 +#, fuzzy +msgid ":doc:`run-quickstart-examples-docker-compose`" +msgstr "Démarrage rapide XGBoost" + #: ../../source/docker/use-a-different-version.rst:2 msgid "Use a Different Flower Version" msgstr "" @@ -4389,7 +4608,7 @@ msgstr "" "getting/setting model parameters, one method for training the model, and " "one method for testing the model:" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:218 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:219 msgid ":code:`set_parameters`" msgstr ":code:`set_parameters`" @@ -4425,9 +4644,9 @@ msgstr "" ":code:`ndarray` NumPy (ce qui correspond à ce que " ":code:`flwr.client.NumPyClient` attend)" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:223 -#: ../../source/tutorial-quickstart-jax.rst:171 -#: ../../source/tutorial-quickstart-scikitlearn.rst:123 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:225 +#: ../../source/tutorial-quickstart-jax.rst:173 +#: ../../source/tutorial-quickstart-scikitlearn.rst:125 msgid ":code:`fit`" msgstr ":code:`fit`" @@ -4451,9 +4670,9 @@ msgstr "entraîne le modèle sur l'ensemble d'apprentissage local" msgid "get the updated local model weights and return them to the server" msgstr "récupère les poids du modèle local mis à jour et les renvoie au serveur" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:227 -#: ../../source/tutorial-quickstart-jax.rst:175 -#: ../../source/tutorial-quickstart-scikitlearn.rst:127 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:230 +#: ../../source/tutorial-quickstart-jax.rst:178 +#: ../../source/tutorial-quickstart-scikitlearn.rst:128 msgid ":code:`evaluate`" msgstr ":code:`évaluer`" @@ -4572,7 +4791,7 @@ msgid "" " individual's information remains hidden in the crowd." msgstr "" -#: ../../source/explanation-differential-privacy.rst:16 +#: ../../source/explanation-differential-privacy.rst:-1 msgid "DP Intro" msgstr "" @@ -4685,8 +4904,8 @@ msgid "" "the client's data." msgstr "" +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:68 -#: ../../source/explanation-differential-privacy.rst:71 #: ../../source/how-to-use-differential-privacy.rst:11 #, fuzzy msgid "Central Differential Privacy" @@ -4714,7 +4933,7 @@ msgid "" "that larger updates are scaled down to fit within the norm `S`." msgstr "" -#: ../../source/explanation-differential-privacy.rst:84 +#: ../../source/explanation-differential-privacy.rst:-1 msgid "clipping" msgstr "" @@ -4759,8 +4978,8 @@ msgid "" "others." msgstr "" +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:105 -#: ../../source/explanation-differential-privacy.rst:110 #: ../../source/how-to-use-differential-privacy.rst:96 #, fuzzy msgid "Local Differential Privacy" @@ -5047,7 +5266,7 @@ msgstr "" msgid "This is sometimes called a hub-and-spoke topology:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:18 +#: ../../source/explanation-flower-architecture.rst:24 #, fuzzy msgid "Hub-and-spoke topology in federated learning" msgstr "Qu'est-ce que l'apprentissage fédéré ?" @@ -5120,7 +5339,7 @@ msgid "" "`missing link` between all those SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:65 +#: ../../source/explanation-flower-architecture.rst:71 #, fuzzy msgid "Basic Flower architecture" msgstr "Architecture florale" @@ -5158,7 +5377,7 @@ msgid "" "SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:91 +#: ../../source/explanation-flower-architecture.rst:97 #, fuzzy msgid "Multi-tenancy federated learning architecture" msgstr "Stratégie de moyenne fédérée." @@ -5182,7 +5401,7 @@ msgid "" "their corresponding ``ClientApp``\\s:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:107 +#: ../../source/explanation-flower-architecture.rst:113 #, fuzzy msgid "Multi-tenancy federated learning architecture - Run 1" msgstr "Stratégie de moyenne fédérée." @@ -5199,7 +5418,7 @@ msgid "" " to participate in the training:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:119 +#: ../../source/explanation-flower-architecture.rst:125 #, fuzzy msgid "Multi-tenancy federated learning architecture - Run 2" msgstr "Stratégie de moyenne fédérée." @@ -5236,7 +5455,7 @@ msgid "" "developer machine." msgstr "" -#: ../../source/explanation-flower-architecture.rst:145 +#: ../../source/explanation-flower-architecture.rst:151 msgid "Flower Deployment Engine with SuperExec" msgstr "" @@ -8526,7 +8745,7 @@ msgid "" "adaptive clipping." msgstr "" -#: ../../source/how-to-use-differential-privacy.rst:25 +#: ../../source/how-to-use-differential-privacy.rst:-1 #, fuzzy msgid "server side clipping" msgstr "Logique côté serveur" @@ -8557,7 +8776,7 @@ msgid "" ":code:`DifferentialPrivacyClientSideAdaptiveClipping`." msgstr "" -#: ../../source/how-to-use-differential-privacy.rst:57 +#: ../../source/how-to-use-differential-privacy.rst:-1 #, fuzzy msgid "client side clipping" msgstr "Logique côté client" @@ -8585,7 +8804,7 @@ msgid "" "clipping norm value, sensitivity, epsilon, and delta." msgstr "" -#: ../../source/how-to-use-differential-privacy.rst:99 +#: ../../source/how-to-use-differential-privacy.rst:-1 msgid "local DP mod" msgstr "" @@ -9047,11 +9266,33 @@ msgstr "" msgid "Arguments" msgstr "Amélioration de la documentation" -#: ../../flwr install:1 new:1 run:1 +#: ../../flwr install:1 log:1 new:1 run:1 #, fuzzy msgid "Optional argument" msgstr "Améliorations facultatives" +#: ../../flwr log:1 +msgid "Get logs from a Flower project run." +msgstr "" + +#: ../../flwr log:1 +msgid "Flag to stream or print logs from the Flower run" +msgstr "" + +#: ../../flwr log +#, fuzzy +msgid "default" +msgstr "Flux de travail" + +#: ../../flwr log:1 +msgid "``True``" +msgstr "" + +#: ../../flwr log:1 +#, fuzzy +msgid "Required argument" +msgstr "Amélioration de la documentation" + #: ../../flwr new:1 #, fuzzy msgid "Create new Flower App." @@ -9143,7 +9384,7 @@ msgstr "" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`client `\\" +msgid ":py:obj:`flwr.client `\\" msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.rst:35::1 flwr.client:1 of @@ -9153,7 +9394,7 @@ msgstr "Client de Flower" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`common `\\" +msgid ":py:obj:`flwr.common `\\" msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.rst:35::1 flwr.common:1 of @@ -9162,7 +9403,7 @@ msgstr "Composants communs partagés entre le serveur et le client." #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`server `\\" +msgid ":py:obj:`flwr.server `\\" msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.rst:35::1 @@ -9174,7 +9415,7 @@ msgstr "Serveur de Flower" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`simulation `\\" +msgid ":py:obj:`flwr.simulation `\\" msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.rst:35::1 flwr.simulation:1 of @@ -9258,7 +9499,7 @@ msgstr "" #: ../../source/ref-api/flwr.client.rst:50::1 #, fuzzy -msgid ":py:obj:`mod `\\" +msgid ":py:obj:`flwr.client.mod `\\" msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.client.rst:50::1 flwr.client.mod:1 of @@ -9459,48 +9700,57 @@ msgstr "" msgid "Getter for `Context` client attribute." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst -#: ../../source/ref-api/flwr.common.Array.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Context.rst -#: ../../source/ref-api/flwr.common.Error.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.Metadata.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst #: ../../source/ref-api/flwr.common.Parameters.rst:2 -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.common.RecordSet.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.ServerAppComponents.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.Bulyan.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdam.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgAndroid.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgM.rst -#: ../../source/ref-api/flwr.server.strategy.FedOpt.rst -#: ../../source/ref-api/flwr.server.strategy.FedProx.rst -#: ../../source/ref-api/flwr.server.strategy.FedTrimmedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedYogi.rst -#: ../../source/ref-api/flwr.server.strategy.Krum.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggPlusWorkflow.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggWorkflow.rst -#: ../../source/ref-api/flwr.simulation.run_simulation.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst #: flwr.client.app.start_client flwr.client.app.start_numpy_client -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: 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 +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.context.Context flwr.common.message.Error +#: flwr.common.message.Message flwr.common.message.Message.create_error_reply +#: flwr.common.message.Message.create_reply flwr.common.message.Metadata +#: flwr.common.record.configsrecord.ConfigsRecord +#: flwr.common.record.metricsrecord.MetricsRecord +#: flwr.common.record.parametersrecord.Array +#: flwr.common.record.parametersrecord.ParametersRecord +#: flwr.common.record.recordset.RecordSet flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.ClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.serverapp_components.ServerAppComponents +#: flwr.server.strategy.bulyan.Bulyan +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.fedadagrad.FedAdagrad +#: flwr.server.strategy.fedadam.FedAdam flwr.server.strategy.fedavg.FedAvg +#: flwr.server.strategy.fedavg_android.FedAvgAndroid +#: flwr.server.strategy.fedavgm.FedAvgM flwr.server.strategy.fedopt.FedOpt +#: flwr.server.strategy.fedprox.FedProx +#: flwr.server.strategy.fedtrimmedavg.FedTrimmedAvg +#: flwr.server.strategy.fedyogi.FedYogi flwr.server.strategy.krum.Krum +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow +#: flwr.simulation.run_simulation.run_simulation of #, fuzzy msgid "Parameters" msgstr "Paramètres du modèle." @@ -9512,21 +9762,31 @@ msgid "" "customize the local evaluation process." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.evaluate +#: flwr.client.numpy_client.NumPyClient.fit +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of #, fuzzy msgid "Returns" msgstr "Ressources" @@ -9537,18 +9797,29 @@ msgid "" "details such as the number of local data examples used for evaluation." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of msgid "Return type" msgstr "" @@ -9879,6 +10150,11 @@ msgstr "Logique côté client" 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:`message_size_mod `\\ \\(msg\\," @@ -10046,10 +10322,6 @@ msgstr "" msgid "make\\_ffn" msgstr "" -#: flwr.client.mod.utils.make_ffn:1 of -msgid "." -msgstr "" - #: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 msgid "message\\_size\\_mod" msgstr "" @@ -10078,15 +10350,6 @@ msgstr "" msgid "secaggplus\\_mod" msgstr "Flux de travail" -#: ../../source/ref-api/flwr.client.run_client_app.rst:2 -msgid "run\\_client\\_app" -msgstr "" - -#: ../../source/ref-api/flwr.client.run_supernode.rst:2 -#, fuzzy -msgid "run\\_supernode" -msgstr "flower-superlink" - #: ../../source/ref-api/flwr.client.start_client.rst:2 #, fuzzy msgid "start\\_client" @@ -10822,17 +11085,12 @@ msgstr "" #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr "" "Flower 1.0 : ``start_server(..., " "config=flwr.server.ServerConfig(num_rounds=3, round_timeout=600.0), " "...)``" -#: collections.abc.Mapping.get:1 -#: collections.abc.MutableMapping.clear:1::1 of -msgid "Retrieve the corresponding layout by the string key." -msgstr "" - #: collections.abc.MutableMapping.clear:1::1 of msgid ":py:obj:`items `\\ \\(\\)" msgstr "" @@ -10889,22 +11147,6 @@ msgstr "" msgid "This function counts booleans as occupying 1 Byte." msgstr "" -#: collections.abc.Mapping.get:3 of -msgid "" -"When there isn't an exact match, all the existing keys in the layout map " -"will be treated as a regex and map against the input key again. The first" -" match will be returned, based on the key insertion order. Return None if" -" there isn't any match found." -msgstr "" - -#: collections.abc.Mapping.get:8 of -msgid "the string key as the query for the layout." -msgstr "" - -#: collections.abc.Mapping.get:10 of -msgid "Corresponding layout based on the query." -msgstr "" - #: ../../source/ref-api/flwr.common.Context.rst:2 msgid "Context" msgstr "" @@ -11663,7 +11905,7 @@ msgstr "" msgid "The encoding in which to encode the string." msgstr "" -#: flwr.common.EventType.encode:5 of +#: flwr.common.EventType.encode:9 of msgid "errors" msgstr "" @@ -11839,7 +12081,7 @@ msgid "" "string." msgstr "" -#: flwr.common.EventType.replace:3 of +#: flwr.common.EventType.replace:5 of msgid "count" msgstr "" @@ -11875,7 +12117,7 @@ msgid "" "strings and the original string." msgstr "" -#: flwr.common.EventType.rsplit:3 flwr.common.EventType.split:3 of +#: flwr.common.EventType.rsplit:7 flwr.common.EventType.split:7 of msgid "sep" msgstr "" @@ -11890,7 +12132,7 @@ msgid "" " empty strings from the result." msgstr "" -#: flwr.common.EventType.rsplit:9 flwr.common.EventType.split:9 of +#: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of msgid "maxsplit" msgstr "" @@ -11931,7 +12173,7 @@ msgid "" "remaining cased characters have lower case." msgstr "" -#: flwr.common.EventType.translate:3 of +#: flwr.common.EventType.translate:5 of #, fuzzy msgid "table" msgstr "Database" @@ -12354,7 +12596,7 @@ msgstr "" #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr "serveur.stratégie.Stratégie" #: collections.abc.MutableMapping.clear:1::1 of @@ -12490,9 +12732,7 @@ msgstr "" #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid "" -":py:obj:`get `\\ \\(key\\[\\, " -"default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr "serveur.stratégie.Stratégie" #: collections.abc.MutableMapping.clear:1::1 of @@ -12839,7 +13079,7 @@ msgstr "" #: ../../source/ref-api/flwr.server.rst:56::1 #, fuzzy -msgid ":py:obj:`strategy `\\" +msgid ":py:obj:`flwr.server.strategy `\\" msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -12849,7 +13089,7 @@ msgstr "" #: ../../source/ref-api/flwr.server.rst:56::1 #, fuzzy -msgid ":py:obj:`workflow `\\" +msgid ":py:obj:`flwr.server.workflow `\\" msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -13353,8 +13593,7 @@ msgid "" msgstr "" #: flwr.server.app.start_server:9 -#: flwr.server.serverapp_components.ServerAppComponents:6 -#: flwr.simulation.app.start_simulation:29 of +#: flwr.server.serverapp_components.ServerAppComponents:6 of msgid "" "Currently supported values are `num_rounds` (int, default: 1) and " "`round_timeout` in seconds (float, default: None)." @@ -13478,15 +13717,6 @@ msgstr "" msgid "**success**" msgstr "" -#: ../../source/ref-api/flwr.server.run_server_app.rst:2 -msgid "run\\_server\\_app" -msgstr "" - -#: ../../source/ref-api/flwr.server.run_superlink.rst:2 -#, fuzzy -msgid "run\\_superlink" -msgstr "flower-superlink" - #: ../../source/ref-api/flwr.server.start_server.rst:2 #, fuzzy msgid "start\\_server" @@ -16567,16 +16797,16 @@ msgid "Run a Flower App using the Simulation Engine." msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 +#, fuzzy msgid "" -":py:obj:`start_simulation `\\ \\(\\*\\," -" client\\_fn\\, num\\_clients\\)" -msgstr "" +":py:obj:`start_simulation `\\ " +"\\(\\*args\\, \\*\\*kwargs\\)" +msgstr "serveur.stratégie.Stratégie" #: ../../source/ref-api/flwr.simulation.rst:18::1 -#: flwr.simulation.app.start_simulation:1 of -#, fuzzy -msgid "Start a Ray-based Flower simulation server." -msgstr "Simulation de moniteur" +#: flwr.simulation.start_simulation:1 of +msgid "Log error stating that module `ray` could not be imported." +msgstr "" #: ../../source/ref-api/flwr.simulation.run_simulation.rst:2 #, fuzzy @@ -16638,120 +16868,6 @@ msgstr "" msgid "start\\_simulation" msgstr "démarrer_simulation" -#: flwr.simulation.app.start_simulation:3 of -msgid "" -"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 " -"(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)." -msgstr "" - -#: flwr.simulation.app.start_simulation:13 of -msgid "The total number of clients in this simulation." -msgstr "" - -#: flwr.simulation.app.start_simulation:15 of -msgid "" -"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." -msgstr "" - -#: flwr.simulation.app.start_simulation:21 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." -msgstr "" - -#: flwr.simulation.app.start_simulation:26 of -msgid "" -"An implementation of the abstract base class `flwr.server.Server`. If no " -"instance is provided, then `start_server` will create one." -msgstr "" - -#: flwr.simulation.app.start_simulation:32 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`." -msgstr "" - -#: flwr.simulation.app.start_simulation:36 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`." -msgstr "" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"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." -msgstr "" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"ray_init_args is None (the default), Ray will be initialized with the " -"following default args:" -msgstr "" - -#: flwr.simulation.app.start_simulation:44 of -msgid "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" -msgstr "" - -#: flwr.simulation.app.start_simulation:46 of -msgid "" -"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:49 of -msgid "" -"Set to True to prevent `ray.shutdown()` in case " -"`ray.is_initialized()=True`." -msgstr "" - -#: flwr.simulation.app.start_simulation:51 of -msgid "" -"Optionally specify the type of actor to use. The actor object, which " -"persists throughout the simulation, will be the process in charge of " -"executing a ClientApp wrapping input argument `client_fn`." -msgstr "" - -#: flwr.simulation.app.start_simulation:55 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." -msgstr "" - -#: flwr.simulation.app.start_simulation:58 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" -msgstr "" - -#: flwr.simulation.app.start_simulation:67 of -msgid "**hist** -- Object containing metrics from training." -msgstr "" - #: ../../source/ref-changelog.md:1 msgid "Changelog" msgstr "Changelog" @@ -16889,13 +17005,6 @@ msgstr "" msgid "Incompatible changes" msgstr "Changements incompatibles" -#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 -#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 -#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 -#: ../../source/ref-changelog.md:929 -msgid "None" -msgstr "Aucun" - #: ../../source/ref-changelog.md:35 #, fuzzy msgid "v1.11.0 (2024-08-30)" @@ -23786,7 +23895,20 @@ msgstr "" "Oui, bien sûr, une liste d'exemples disponibles utilisant Flower dans un " "environnement blockchain est disponible ici :" -#: ../../source/ref-faq.rst:28 +#: ../../source/ref-faq.rst:29 +msgid "`FLock: A Decentralised AI Training Platform `_." +msgstr "" + +#: ../../source/ref-faq.rst:29 +msgid "Contribute to on-chain training the model and earn rewards." +msgstr "" + +#: ../../source/ref-faq.rst:30 +#, fuzzy +msgid "Local blockchain with federated learning simulation." +msgstr "Mise à l'échelle de l'apprentissage fédéré" + +#: ../../source/ref-faq.rst:31 msgid "" "`Flower meets Nevermined GitHub Repository `_." @@ -23794,7 +23916,7 @@ msgstr "" "`Flower meets Nevermined GitHub Repository `_." -#: ../../source/ref-faq.rst:29 +#: ../../source/ref-faq.rst:32 msgid "" "`Flower meets Nevermined YouTube video " "`_." @@ -23802,7 +23924,7 @@ msgstr "" "`Flower rencontre Nevermined vidéo YouTube " "`_." -#: ../../source/ref-faq.rst:30 +#: ../../source/ref-faq.rst:33 #, fuzzy msgid "" "`Flower meets KOSMoS `_." -#: ../../source/ref-faq.rst:31 +#: ../../source/ref-faq.rst:34 msgid "" "`Flower meets Talan blog post `_ ." -#: ../../source/ref-faq.rst:32 +#: ../../source/ref-faq.rst:35 msgid "" "`Flower meets Talan GitHub Repository " "`_ ." @@ -24147,239 +24269,325 @@ msgstr "" "`_ " "pour en savoir plus." -#: ../../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." -msgstr "" - #: ../../source/tutorial-quickstart-fastai.rst:5 msgid "Quickstart fastai" msgstr "Démarrage rapide fastai" -#: ../../source/tutorial-quickstart-fastai.rst:10 -msgid "Let's build a federated learning system using fastai and Flower!" +#: ../../source/tutorial-quickstart-fastai.rst:7 +#, fuzzy +msgid "" +"In this federated learning tutorial we will learn how to train a " +"SqueezeNet model on MNIST using Flower and fastai. It is recommended to " +"create a virtual environment and run everything within a :doc:`virtualenv" +" `." msgstr "" -"Construisons un système d'apprentissage fédéré en utilisant fastai et " -"Flower !" +"Tout d'abord, il est recommandé de créer un environnement virtuel et de " +"tout exécuter au sein d'un `virtualenv `_." #: ../../source/tutorial-quickstart-fastai.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +msgid "Then, clone the code example directly from GitHub:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:20 +msgid "" +"This will create a new directory called `quickstart-fastai` containing " +"the following files:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:33 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:33 #, fuzzy +msgid "Next, activate your environment, then run:" +msgstr "et active l'environnement virtuel avec :" + +#: ../../source/tutorial-quickstart-fastai.rst:43 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"This example by default runs the Flower Simulation Engine, creating a " +"federation of 10 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" Let's run the project:" msgstr "" -"Réfère-toi à l'exemple de code complet " -"`_ " -"pour en savoir plus." + +#: ../../source/tutorial-quickstart-fastai.rst:56 +#: ../../source/tutorial-quickstart-huggingface.rst:65 +#: ../../source/tutorial-quickstart-mlx.rst:64 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:56 +#: ../../source/tutorial-quickstart-pytorch.rst:64 +#: ../../source/tutorial-quickstart-tensorflow.rst:65 +msgid "With default arguments you will see an output like this one:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:100 +#: ../../source/tutorial-quickstart-huggingface.rst:116 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:106 +#: ../../source/tutorial-quickstart-pytorch.rst:105 +#: ../../source/tutorial-quickstart-tensorflow.rst:106 +msgid "" +"You can also override the parameters defined in the " +"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:110 +#, fuzzy +msgid "" +"Check the `source code `_ of this tutorial in ``examples/quickstart-fasai`` " +"in the Flower GitHub repository." +msgstr "" +"Félicitations ! Tu as réussi à construire et à faire fonctionner ton " +"premier système d'apprentissage fédéré. Le code source complet " +"`_ de cet exemple se trouve dans :code:`examples" +"/quickstart-mxnet`." #: ../../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." +"with 🤗 HuggingFace Transformers in order to fine-tune an LLM." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:5 msgid "Quickstart 🤗 Transformers" msgstr "Démarrage rapide 🤗 Transformateurs" -#: ../../source/tutorial-quickstart-huggingface.rst:10 +#: ../../source/tutorial-quickstart-huggingface.rst:7 +#, fuzzy msgid "" -"Let's build a federated learning system using Hugging Face Transformers " -"and Flower!" +"In this federated learning tutorial we will learn how to train a large " +"language model (LLM) on the `IMDB " +"`_ dataset using Flower" +" and the 🤗 Hugging Face Transformers library. It is recommended to create" +" a virtual environment and run everything within a :doc:`virtualenv " +"`." msgstr "" -"Construisons un système d'apprentissage fédéré à l'aide des " -"transformateurs Hugging Face et de Flower !" +"Tout d'abord, il est recommandé de créer un environnement virtuel et de " +"tout exécuter au sein d'un `virtualenv `_." -#: ../../source/tutorial-quickstart-huggingface.rst:12 +#: ../../source/tutorial-quickstart-huggingface.rst:14 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." +"Let's use ``flwr new`` to create a complete Flower+🤗 Hugging Face " +"project. It will generate all the files needed to run, by default with " +"the Flower Simulation Engine, a federation of 10 nodes using |fedavg|_ " +"The dataset will be partitioned using |flowerdatasets|_'s " +"|iidpartitioner|_." msgstr "" -"Nous nous appuierons sur Hugging Face pour fédérer l'entraînement de " -"modèles de langage sur plusieurs clients à l'aide de Flower. Plus " -"précisément, nous mettrons au point un modèle Transformer pré-entraîné " -"(distilBERT) pour la classification de séquences sur un ensemble de " -"données d'évaluations IMDB. L'objectif final est de détecter si " -"l'évaluation d'un film est positive ou négative." - -#: ../../source/tutorial-quickstart-huggingface.rst:18 -msgid "Dependencies" -msgstr "Dépendances" #: ../../source/tutorial-quickstart-huggingface.rst:20 +#: ../../source/tutorial-quickstart-mlx.rst:19 +#: ../../source/tutorial-quickstart-pytorch.rst:19 +#: ../../source/tutorial-quickstart-tensorflow.rst:20 +#, fuzzy 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`:" +"Now that we have a rough idea of what this example is about, let's get " +"started. First, install Flower in your new environment:" msgstr "" -"Pour suivre ce tutoriel, tu devras installer les paquets suivants : " -":code:`datasets`, :code:`evaluate`, :code:`flwr`, :code:`torch`, et " -":code:`transformers`. Cela peut être fait en utilisant :code:`pip` :" +"Maintenant que nous avons une idée approximative de ce qui se passe, " +"commençons. Nous devons d'abord installer Flower. Tu peux le faire en " +"lançant :" -#: ../../source/tutorial-quickstart-huggingface.rst:30 -msgid "Standard Hugging Face workflow" -msgstr "Flux de travail standard pour le visage" +#: ../../source/tutorial-quickstart-huggingface.rst:28 +msgid "" +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``HuggingFace``), give a name to your " +"project, and type in your developer name:" +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:33 -msgid "Handling the data" -msgstr "Traitement des données" +#: ../../source/tutorial-quickstart-huggingface.rst:36 +#: ../../source/tutorial-quickstart-mlx.rst:35 +#: ../../source/tutorial-quickstart-pytorch.rst:35 +#: ../../source/tutorial-quickstart-tensorflow.rst:36 +msgid "" +"After running it you'll notice a new directory with your project name has" +" been created. It should have the following structure:" +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:35 +#: ../../source/tutorial-quickstart-huggingface.rst:50 +#: ../../source/tutorial-quickstart-mlx.rst:49 +#: ../../source/tutorial-quickstart-pytorch.rst:49 +#: ../../source/tutorial-quickstart-tensorflow.rst:50 msgid "" -"To fetch the IMDB dataset, we will use Hugging Face's :code:`datasets` " -"library. We then need to tokenize the data and create :code:`PyTorch` " -"dataloaders, this is all done in the :code:`load_data` function:" +"If you haven't yet installed the project and its dependencies, you can do" +" so by:" +msgstr "" + +#: ../../source/tutorial-quickstart-huggingface.rst:58 +#: ../../source/tutorial-quickstart-pytorch.rst:57 +#: ../../source/tutorial-quickstart-tensorflow.rst:58 +msgid "To run the project, do:" +msgstr "" + +#: ../../source/tutorial-quickstart-huggingface.rst:106 +msgid "You can also run the project with GPU as follows:" msgstr "" -"Pour récupérer le jeu de données IMDB, nous utiliserons la bibliothèque " -":code:`datasets` de Hugging Face. Nous devons ensuite tokeniser les " -"données et créer des :code:`PyTorch` dataloaders, ce qui est fait dans la" -" fonction :code:`load_data` :" -#: ../../source/tutorial-quickstart-huggingface.rst:81 -msgid "Training and testing the model" -msgstr "Former et tester le modèle" +#: ../../source/tutorial-quickstart-huggingface.rst:113 +msgid "" +"This will use the default arguments where each ``ClientApp`` will use 2 " +"CPUs and at most 4 ``ClientApp``\\s will run in a given GPU." +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:83 +#: ../../source/tutorial-quickstart-huggingface.rst:124 +#: ../../source/tutorial-quickstart-mlx.rst:114 +#: ../../source/tutorial-quickstart-pytorch.rst:113 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:" +"What follows is an explanation of each component in the project you just " +"created: dataset partition, the model, defining the ``ClientApp`` and " +"defining the ``ServerApp``." msgstr "" -"Une fois que nous avons trouvé un moyen de créer notre trainloader et " -"notre testloader, nous pouvons nous occuper de l'entraînement et du test." -" C'est très similaire à n'importe quelle boucle d'entraînement ou de test" -" :code:`PyTorch` :" -#: ../../source/tutorial-quickstart-huggingface.rst:121 -msgid "Creating the model itself" -msgstr "Créer le modèle lui-même" +#: ../../source/tutorial-quickstart-huggingface.rst:130 +#: ../../source/tutorial-quickstart-mlx.rst:120 +#: ../../source/tutorial-quickstart-pytorch.rst:119 +#: ../../source/tutorial-quickstart-tensorflow.rst:116 +#, fuzzy +msgid "The Data" +msgstr "Chargement des données" -#: ../../source/tutorial-quickstart-huggingface.rst:123 +#: ../../source/tutorial-quickstart-huggingface.rst:132 msgid "" -"To create the model itself, we will just load the pre-trained distillBERT" -" model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" +"This tutorial uses |flowerdatasets|_ to easily download and partition the" +" `IMDB `_ dataset. In " +"this example you'll make use of the |iidpartitioner|_ to generate " +"``num_partitions`` partitions. You can choose |otherpartitioners|_ " +"available in Flower Datasets. To tokenize the text, we will also load the" +" tokenizer from the pre-trained Transformer model that we'll use during " +"training - more on that in the next section. Each ``ClientApp`` will call" +" this function to create dataloaders with the data that correspond to " +"their data partition." msgstr "" -"Pour créer le modèle lui-même, nous allons simplement charger le modèle " -"distillBERT pré-entraîné en utilisant le " -":code:`AutoModelForSequenceClassification` de Hugging Face :" -#: ../../source/tutorial-quickstart-huggingface.rst:136 -msgid "Federating the example" -msgstr "Fédérer l'exemple" +#: ../../source/tutorial-quickstart-huggingface.rst:178 +#: ../../source/tutorial-quickstart-mlx.rst:164 +#: ../../source/tutorial-quickstart-pytorch.rst:157 +#: ../../source/tutorial-quickstart-tensorflow.rst:145 +#, fuzzy +msgid "The Model" +msgstr "Entraîne le modèle" -#: ../../source/tutorial-quickstart-huggingface.rst:139 -msgid "Creating the IMDBClient" -msgstr "Création du client IMDBC" +#: ../../source/tutorial-quickstart-huggingface.rst:180 +#, fuzzy +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 (|berttiny|_) for sequence " +"classification over the dataset of IMDB ratings. The end goal is to " +"detect if a movie rating is positive or negative. If you have access to " +"larger GPUs, feel free to use larger models!" +msgstr "" +"Nous nous appuierons sur Hugging Face pour fédérer l'entraînement de " +"modèles de langage sur plusieurs clients à l'aide de Flower. Plus " +"précisément, nous mettrons au point un modèle Transformer pré-entraîné " +"(distilBERT) pour la classification de séquences sur un ensemble de " +"données d'évaluations IMDB. L'objectif final est de détecter si " +"l'évaluation d'un film est positive ou négative." -#: ../../source/tutorial-quickstart-huggingface.rst:141 +#: ../../source/tutorial-quickstart-huggingface.rst:193 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:" +"Note that here, ``model_name`` is a string that will be loaded from the " +"``Context`` in the ClientApp and ServerApp." msgstr "" -"Pour fédérer notre exemple à plusieurs clients, nous devons d'abord " -"écrire notre classe de client Flower (héritant de " -":code:`flwr.client.NumPyClient`). C'est très facile, car notre modèle est" -" un modèle :code:`PyTorch` standard :" -#: ../../source/tutorial-quickstart-huggingface.rst:169 +#: ../../source/tutorial-quickstart-huggingface.rst:196 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." +"In addition to loading the pretrained model weights and architecture, we " +"also include two utility functions to perform both training (i.e. " +"``train()``) and evaluation (i.e. ``test()``) using the above model. " +"These functions should look fairly familiar if you have some prior " +"experience with PyTorch. Note these functions do not have anything " +"specific to Flower. That being said, the training function will normally " +"be called, as we'll see later, from a Flower client passing its own data." +" In summary, your clients can use standard training/testing functions to " +"perform local training or evaluation:" msgstr "" -"La fonction :code:`get_parameters` permet au serveur d'obtenir les " -"paramètres du client. Inversement, la fonction :code:`set_parameters` " -"permet au serveur d'envoyer ses paramètres au client. Enfin, la fonction " -":code:`fit` forme le modèle localement pour le client, et la fonction " -":code:`evaluate` teste le modèle localement et renvoie les mesures " -"correspondantes." -#: ../../source/tutorial-quickstart-huggingface.rst:175 -msgid "Starting the server" -msgstr "Démarrer le serveur" +#: ../../source/tutorial-quickstart-huggingface.rst:239 +#: ../../source/tutorial-quickstart-mlx.rst:210 +#: ../../source/tutorial-quickstart-pytorch.rst:234 +#: ../../source/tutorial-quickstart-tensorflow.rst:176 +#, fuzzy +msgid "The ClientApp" +msgstr "client" -#: ../../source/tutorial-quickstart-huggingface.rst:177 +#: ../../source/tutorial-quickstart-huggingface.rst:241 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:" +"The main changes we have to make to use 🤗 Hugging Face with Flower will " +"be found in the ``get_weights()`` and ``set_weights()`` functions. Under " +"the hood, the ``transformers`` library uses PyTorch, which means we can " +"reuse the ``get_weights()`` and ``set_weights()`` code that we defined in" +" the :doc:`Quickstart PyTorch ` tutorial. As" +" a reminder, in ``get_weights()``, PyTorch model parameters are extracted" +" and represented as a list of NumPy arrays. The ``set_weights()`` " +"function that's the opposite: given a list of NumPy arrays it applies " +"them to an existing PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -"Maintenant que nous avons un moyen d'instancier les clients, nous devons " -"créer notre serveur afin d'agréger les résultats. Avec Flower, cela peut " -"être fait très facilement en choisissant d'abord une stratégie (ici, nous" -" utilisons :code:`FedAvg`, qui définira les poids globaux comme la " -"moyenne des poids de tous les clients à chaque tour) et en utilisant " -"ensuite la fonction :code:`flwr.server.start_server` :" -#: ../../source/tutorial-quickstart-huggingface.rst:205 +#: ../../source/tutorial-quickstart-huggingface.rst:254 +#: ../../source/tutorial-quickstart-pytorch.rst:245 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 specific implementation of ``get_weights()`` and ``set_weights()`` " +"depends on the type of models you use. The ones shown below work for a " +"wide range of PyTorch models but you might need to adjust them if you " +"have more exotic model architectures." msgstr "" -"La fonction :code:`weighted_average` est là pour fournir un moyen " -"d'agréger les mesures réparties entre les clients (en gros, cela nous " -"permet d'afficher une belle moyenne de précision et de perte pour chaque " -"tour)." -#: ../../source/tutorial-quickstart-huggingface.rst:209 -msgid "Putting everything together" -msgstr "Tout assembler" - -#: ../../source/tutorial-quickstart-huggingface.rst:211 -msgid "We can now start client instances using:" -msgstr "Nous pouvons maintenant démarrer des instances de clients en utilisant :" +#: ../../source/tutorial-quickstart-huggingface.rst:269 +#: ../../source/tutorial-quickstart-pytorch.rst:261 +msgid "" +"The rest of the functionality is directly inspired by the centralized " +"case. The ``fit()`` method in the client trains the model using the local" +" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " +"model received on a held-out validation set that the client might have:" +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:221 +#: ../../source/tutorial-quickstart-huggingface.rst:296 msgid "" -"And they will be able to connect to the server and start the federated " -"training." -msgstr "Et ils pourront se connecter au serveur et démarrer la formation fédérée." +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparemeters defined in your " +"``pyproject.toml`` to configure the run. In this tutorial we access the " +"``local-epochs`` setting to control the number of epochs a ``ClientApp`` " +"will perform when running the ``fit()`` method. You could define " +"additional hyperparameters in ``pyproject.toml`` and access them here." +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:223 +#: ../../source/tutorial-quickstart-huggingface.rst:330 +#: ../../source/tutorial-quickstart-mlx.rst:376 +#: ../../source/tutorial-quickstart-pytorch.rst:321 +#: ../../source/tutorial-quickstart-tensorflow.rst:245 #, fuzzy +msgid "The ServerApp" +msgstr "serveur" + +#: ../../source/tutorial-quickstart-huggingface.rst:332 msgid "" -"If you want to check out everything put together, you should check out " -"the `full code example `_ ." +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"|serverappcomponents|_ as opposed to a |client|_ In this example we use " +"the `FedAvg` strategy. To it we pass a randomly initialized model that " +"will server as the global model to federated. Note that the value of " +"``fraction_fit`` is read from the run config. You can find the default " +"value defined in the ``pyproject.toml``." msgstr "" -"Si tu veux voir tout ce qui est mis ensemble, tu devrais consulter " -"l'exemple de code complet : " -"[https://github.com/adap/flower/tree/main/examples/quickstart-" -"huggingface](https://github.com/adap/flower/tree/main/examples" -"/quickstart-huggingface)." -#: ../../source/tutorial-quickstart-huggingface.rst:226 +#: ../../source/tutorial-quickstart-huggingface.rst:371 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." +"Congratulations! You've successfully built and run your first federated " +"learning system for an LLM." msgstr "" -"Bien sûr, c'est un exemple très basique, et beaucoup de choses peuvent " -"être ajoutées ou modifiées, il s'agissait juste de montrer avec quelle " -"simplicité on pouvait fédérer un flux de travail Hugging Face à l'aide de" -" Flower." -#: ../../source/tutorial-quickstart-huggingface.rst:229 +#: ../../source/tutorial-quickstart-huggingface.rst:376 msgid "" -"Note that in this example we used :code:`PyTorch`, but we could have very" -" well used :code:`TensorFlow`." +"Check the source code of the extended version of this tutorial in " +"|quickstart_hf_link|_ in the Flower GitHub repository. For a " +"comprehensive example of a federated fine-tuning of an LLM with Flower, " +"refer to the |flowertune|_ example in the Flower GitHub repository." msgstr "" -"Notez que dans cet exemple, nous avons utilisé :code:`PyTorch`, mais nous" -" aurions très bien pu utiliser :code:`TensorFlow`." #: ../../source/tutorial-quickstart-ios.rst:-1 msgid "" @@ -24455,7 +24663,6 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:34 #: ../../source/tutorial-quickstart-scikitlearn.rst:40 -#: ../../source/tutorial-quickstart-tensorflow.rst:29 #: ../../source/tutorial-quickstart-xgboost.rst:55 msgid "Flower Client" msgstr "Client de la fleur" @@ -24529,13 +24736,11 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:129 #: ../../source/tutorial-quickstart-scikitlearn.rst:167 -#: ../../source/tutorial-quickstart-tensorflow.rst:98 #: ../../source/tutorial-quickstart-xgboost.rst:341 msgid "Flower Server" msgstr "Serveur de Flower" #: ../../source/tutorial-quickstart-ios.rst:131 -#: ../../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 " @@ -24548,12 +24753,10 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:142 #: ../../source/tutorial-quickstart-scikitlearn.rst:230 -#: ../../source/tutorial-quickstart-tensorflow.rst:112 msgid "Train the model, federated!" msgstr "Entraîne le modèle, fédéré !" #: ../../source/tutorial-quickstart-ios.rst:144 -#: ../../source/tutorial-quickstart-tensorflow.rst:114 #: ../../source/tutorial-quickstart-xgboost.rst:567 msgid "" "With both client and server ready, we can now run everything and see " @@ -24806,7 +25009,7 @@ msgstr "" "paramètres du modèle, une méthode pour former le modèle, et une méthode " "pour tester le modèle :" -#: ../../source/tutorial-quickstart-jax.rst:165 +#: ../../source/tutorial-quickstart-jax.rst:167 msgid ":code:`set_parameters (optional)`" msgstr ":code:`set_parameters (optional)`" @@ -24920,17 +25123,6 @@ msgid "" "api/flwr_datasets.partitioner.IidPartitioner.html#flwr_datasets.partitioner.IidPartitioner>`_." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:19 -#: ../../source/tutorial-quickstart-pytorch.rst:19 -#, fuzzy -msgid "" -"Now that we have a rough idea of what this example is about, let's get " -"started. First, install Flower in your new environment:" -msgstr "" -"Maintenant que nous avons une idée approximative de ce qui se passe, " -"commençons. Nous devons d'abord installer Flower. Tu peux le faire en " -"lançant :" - #: ../../source/tutorial-quickstart-mlx.rst:27 msgid "" "Then, run the command below. You will be prompted to select of the " @@ -24938,49 +25130,16 @@ msgid "" "type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:35 -#: ../../source/tutorial-quickstart-pytorch.rst:35 -msgid "" -"After running it you'll notice a new directory with your project name has" -" been created. It should have the following structure:" -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:49 -#: ../../source/tutorial-quickstart-pytorch.rst:49 -msgid "" -"If you haven't yet installed the project and its dependencies, you can do" -" so by:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:57 msgid "To run the project do:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:64 -#: ../../source/tutorial-quickstart-pytorch.rst:64 -msgid "With default arguments you will see an output like this one:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:106 msgid "" "You can also override the parameters defined in " "``[tool.flwr.app.config]`` section in the ``pyproject.toml`` like this:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:114 -#: ../../source/tutorial-quickstart-pytorch.rst:113 -msgid "" -"What follows is an explanation of each component in the project you just " -"created: dataset partition, the model, defining the ``ClientApp`` and " -"defining the ``ServerApp``." -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:120 -#: ../../source/tutorial-quickstart-pytorch.rst:119 -#, fuzzy -msgid "The Data" -msgstr "Chargement des données" - #: ../../source/tutorial-quickstart-mlx.rst:122 msgid "" "We will use `Flower Datasets `_ to " @@ -24992,12 +25151,6 @@ msgid "" "api/flwr_datasets.partitioner.html>`_ available in Flower Datasets:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:164 -#: ../../source/tutorial-quickstart-pytorch.rst:157 -#, fuzzy -msgid "The Model" -msgstr "Entraîne le modèle" - #: ../../source/tutorial-quickstart-mlx.rst:166 msgid "" "We define the model as in the `centralized MLX example " @@ -25011,12 +25164,6 @@ msgid "" "over batches." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:210 -#: ../../source/tutorial-quickstart-pytorch.rst:234 -#, fuzzy -msgid "The ClientApp" -msgstr "client" - #: ../../source/tutorial-quickstart-mlx.rst:212 msgid "" "The main changes we have to make to use `MLX` with `Flower` will be found" @@ -25085,12 +25232,6 @@ msgid "" "method." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:376 -#: ../../source/tutorial-quickstart-pytorch.rst:321 -#, fuzzy -msgid "The ServerApp" -msgstr "serveur" - #: ../../source/tutorial-quickstart-mlx.rst:378 msgid "" "To construct a ``ServerApp``, we define a ``server_fn()`` callback with " @@ -25104,6 +25245,7 @@ msgstr "" #: ../../source/tutorial-quickstart-mlx.rst:402 #: ../../source/tutorial-quickstart-pytorch.rst:360 +#: ../../source/tutorial-quickstart-tensorflow.rst:279 msgid "" "Congratulations! You've successfully built and run your first federated " "learning system." @@ -25184,16 +25326,6 @@ msgid "" "and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:57 -msgid "To run the project, do:" -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:105 -msgid "" -"You can also override the parameters defined in the " -"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:121 msgid "" "This tutorial uses `Flower Datasets `_ " @@ -25237,22 +25369,6 @@ msgid "" "PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:245 -msgid "" -"The specific implementation of ``get_weights()`` and ``set_weights()`` " -"depends on the type of models you use. The ones shown below work for a " -"wide range of PyTorch models but you might need to adjust them if you " -"have more exotic model architectures." -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:261 -msgid "" -"The rest of the functionality is directly inspired by the centralized " -"case. The ``fit()`` method in the client trains the model using the local" -" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " -"model received on a held-out validation set that the client might have:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:294 msgid "" "Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " @@ -25292,6 +25408,7 @@ msgstr "" "/quickstart-mxnet`." #: ../../source/tutorial-quickstart-pytorch.rst:372 +#: ../../source/tutorial-quickstart-tensorflow.rst:295 #, fuzzy msgid "Video tutorial" msgstr "Tutoriel" @@ -25303,35 +25420,57 @@ msgid "" "that shows the new APIs (as the content above does)" 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." -msgstr "" - #: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 msgid "Quickstart PyTorch Lightning" msgstr "Démarrage rapide de PyTorch Lightning" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:10 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:7 #, fuzzy msgid "" -"Let's build a horizontal federated learning system using PyTorch " -"Lightning and Flower!" +"In this federated learning tutorial we will learn how to train an " +"AutoEncoder model on MNIST using Flower and PyTorch Lightning. It is " +"recommended to create a virtual environment and run everything within a " +":doc:`virtualenv `." msgstr "" -"Construisons un système d'apprentissage fédéré en utilisant PyTorch " -"Lightning et Flower !" +"Tout d'abord, il est recommandé de créer un environnement virtuel et de " +"tout exécuter au sein d'un `virtualenv `_." -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:20 +msgid "" +"This will create a new directory called `quickstart-pytorch-lightning` " +"containing the following files:" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:43 +msgid "" +"By default, Flower Simulation Engine will be started and it will create a" +" federation of 4 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" To run the project, do:" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:94 +msgid "" +"Each simulated `ClientApp` (two per round) will also log a summary of " +"their local training process. Expect this output to be similar to:" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:116 #, fuzzy msgid "" -"Please refer to the `full code example " -"`_ to learn more." +"Check the `source code `_ of this tutorial in ``examples" +"/quickstart-pytorch-lightning`` in the Flower GitHub repository." msgstr "" -"Réfère-toi à l'exemple de code complet " -"`_ pour en savoir plus." +"Félicitations ! Tu as réussi à construire et à faire fonctionner ton " +"premier système d'apprentissage fédéré. Le code source complet " +"`_ de cet exemple se trouve dans :code:`examples" +"/quickstart-mxnet`." #: ../../source/tutorial-quickstart-scikitlearn.rst:-1 msgid "" @@ -25439,7 +25578,7 @@ msgstr ":code:`set_model_params()`" msgid "Sets the parameters of a :code:`sklearn` LogisticRegression model" msgstr "Définit les paramètres d'un modèle de régression logistique :code:`sklean`" -#: ../../source/tutorial-quickstart-scikitlearn.rst:49 +#: ../../source/tutorial-quickstart-scikitlearn.rst:50 msgid ":code:`set_initial_params()`" msgstr ":code:`set_initial_params()`" @@ -25514,7 +25653,7 @@ msgstr "" msgid "return the model weight as a list of NumPy ndarrays" msgstr "renvoie le poids du modèle sous la forme d'une liste de ndarrays NumPy" -#: ../../source/tutorial-quickstart-scikitlearn.rst:120 +#: ../../source/tutorial-quickstart-scikitlearn.rst:121 msgid ":code:`set_parameters` (optional)" msgstr ":code:`set_parameters` (optionnel)" @@ -25644,7 +25783,6 @@ msgstr "" " commencer par lancer le serveur :" #: ../../source/tutorial-quickstart-scikitlearn.rst:239 -#: ../../source/tutorial-quickstart-tensorflow.rst:122 #: ../../source/tutorial-quickstart-xgboost.rst:575 msgid "" "Once the server is running we can start the clients in different " @@ -25655,7 +25793,6 @@ msgstr "" "premier client :" #: ../../source/tutorial-quickstart-scikitlearn.rst:246 -#: ../../source/tutorial-quickstart-tensorflow.rst:129 #: ../../source/tutorial-quickstart-xgboost.rst:582 msgid "Open another terminal and start the second client:" msgstr "Ouvre un autre terminal et démarre le deuxième client :" @@ -25688,144 +25825,118 @@ 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." +"with TensorFlow to train a CNN model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:5 msgid "Quickstart TensorFlow" msgstr "Démarrage rapide de TensorFlow" -#: ../../source/tutorial-quickstart-tensorflow.rst:13 -msgid "Let's build a federated learning system in less than 20 lines of code!" -msgstr "" -"Construisons un système d'apprentissage fédéré en moins de 20 lignes de " -"code !" - -#: ../../source/tutorial-quickstart-tensorflow.rst:15 -msgid "Before Flower can be imported we have to install it:" -msgstr "Avant de pouvoir importer une fleur, nous devons l'installer :" - -#: ../../source/tutorial-quickstart-tensorflow.rst:21 +#: ../../source/tutorial-quickstart-tensorflow.rst:7 +#, fuzzy msgid "" -"Since we want to use the Keras API of TensorFlow (TF), we have to install" -" TF as well:" -msgstr "" -"Comme nous voulons utiliser l'API Keras de TensorFlow (TF), nous devons " -"également installer TF :" - -#: ../../source/tutorial-quickstart-tensorflow.rst:31 -msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" +"In this tutorial we will learn how to train a Convolutional Neural " +"Network on CIFAR-10 using the Flower framework and TensorFlow. First of " +"all, it is recommended to create a virtual environment and run everything" +" within a :doc:`virtualenv `." msgstr "" -"Ensuite, dans un fichier appelé :code:`client.py`, importe Flower et " -"TensorFlow :" +"Tout d'abord, il est recommandé de créer un environnement virtuel et de " +"tout exécuter au sein d'un `virtualenv `_." -#: ../../source/tutorial-quickstart-tensorflow.rst:38 +#: ../../source/tutorial-quickstart-tensorflow.rst:13 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." +"Let's use `flwr new` to create a complete Flower+TensorFlow project. It " +"will generate all the files needed to run, by default with the Flower " +"Simulation Engine, a federation of 10 nodes using `FedAvg " +"`_. The " +"dataset will be partitioned using Flower Dataset's `IidPartitioner " +"`_." msgstr "" -"Nous utilisons les utilitaires Keras de TF pour charger CIFAR10, un " -"ensemble de données de classification d'images colorées populaire pour " -"l'apprentissage automatique. L'appel à " -":code:`tf.keras.datasets.cifar10.load_data()` télécharge CIFAR10, le met " -"en cache localement, puis renvoie l'ensemble d'entraînement et de test " -"sous forme de NumPy ndarrays." -#: ../../source/tutorial-quickstart-tensorflow.rst:47 +#: ../../source/tutorial-quickstart-tensorflow.rst:28 msgid "" -"Next, we need a model. For the purpose of this tutorial, we use " -"MobilNetV2 with 10 output classes:" +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``TensorFlow``), give a name to your project," +" and type in your developer name:" msgstr "" -"Ensuite, nous avons besoin d'un modèle. Pour les besoins de ce tutoriel, " -"nous utilisons MobilNetV2 avec 10 classes de sortie :" -#: ../../source/tutorial-quickstart-tensorflow.rst:54 +#: ../../source/tutorial-quickstart-tensorflow.rst:118 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)." +"This tutorial uses `Flower Datasets `_ " +"to easily download and partition the `CIFAR-10` dataset. In this example " +"you'll make use of the `IidPartitioner `_" +" to generate `num_partitions` partitions. You can choose `other " +"partitioners `_ available in Flower Datasets. Each " +"``ClientApp`` will call this function to create the ``NumPy`` arrays that" +" correspond to their data partition." msgstr "" -"Le serveur Flower interagit avec les clients par le biais d'une interface" -" appelée :code:`Client`. Lorsque le serveur sélectionne un client " -"particulier pour la formation, il envoie des instructions de formation " -"sur le réseau. Le client reçoit ces instructions et appelle l'une des " -"méthodes :code:`Client` pour exécuter ton code (c'est-à-dire pour former " -"le réseau neuronal que nous avons défini plus tôt)." -#: ../../source/tutorial-quickstart-tensorflow.rst:60 +#: ../../source/tutorial-quickstart-tensorflow.rst:147 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:" +"Next, we need a model. We defined a simple Convolutional Neural Network " +"(CNN), but feel free to replace it with a more sophisticated model if " +"you'd like:" msgstr "" -"Flower fournit une classe de commodité appelée :code:`NumPyClient` qui " -"facilite la mise en œuvre de l'interface :code:`Client` lorsque ta charge" -" de travail utilise Keras. L'interface :code:`NumPyClient` définit trois " -"méthodes qui peuvent être mises en œuvre de la manière suivante :" -#: ../../source/tutorial-quickstart-tensorflow.rst:82 +#: ../../source/tutorial-quickstart-tensorflow.rst:178 msgid "" -"We can now create an instance of our class :code:`CifarClient` and add " -"one line to actually run this client:" +"With `TensorFlow`, we can use the built-in ``get_weights()`` and " +"``set_weights()`` functions, which simplifies the implementation with " +"`Flower`. The rest of the functionality in the ClientApp is directly " +"inspired by the centralized case. The ``fit()`` method in the client " +"trains the model using the local dataset. Similarly, the ``evaluate()`` " +"method is used to evaluate the model received on a held-out validation " +"set that the client might have:" msgstr "" -"Nous pouvons maintenant créer une instance de notre classe " -":code:`CifarClient` et ajouter une ligne pour exécuter ce client :" -#: ../../source/tutorial-quickstart-tensorflow.rst:90 -#, fuzzy +#: ../../source/tutorial-quickstart-tensorflow.rst:212 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." +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparameters defined in your " +"``pyproject.toml`` to configure the run. For example, in this tutorial we" +" access the `local-epochs` setting to control the number of epochs a " +"``ClientApp`` will perform when running the ``fit()`` method, in addition" +" to `batch-size`. You could define additional hyperparameters in " +"``pyproject.toml`` and access them here." msgstr "" -"C'est tout pour le client. Il nous suffit d'implémenter :code:`Client` ou" -" :code:`NumPyClient` et d'appeler :code:`fl.client.start_client()`. La " -"chaîne :code:`\"[: :]:8080\"` indique au client à quel serveur se " -"connecter. Dans notre cas, nous pouvons exécuter le serveur et le client " -"sur la même machine, c'est pourquoi nous utilisons :code:`\"[: " -":]:8080\"`. Si nous exécutons une charge de travail véritablement fédérée" -" avec le serveur et les clients fonctionnant sur des machines " -"différentes, tout ce qui doit changer est l'adresse " -":code:`server_address` vers laquelle nous dirigeons le client." - -#: ../../source/tutorial-quickstart-tensorflow.rst:135 -msgid "Each client will have its own dataset." -msgstr "Chaque client aura son propre ensemble de données." -#: ../../source/tutorial-quickstart-tensorflow.rst:137 +#: ../../source/tutorial-quickstart-tensorflow.rst:247 msgid "" -"You should now see how the training does in the very first terminal (the " -"one that started the server):" +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"`ServerAppComponents `_ as " +"opposed to a `Client `_. In this example we use the " +"`FedAvg`. To it we pass a randomly initialized model that will serve as " +"the global model to federate." msgstr "" -"Tu devrais maintenant voir comment la formation se déroule dans le tout " -"premier terminal (celui qui a démarré le serveur) :" -#: ../../source/tutorial-quickstart-tensorflow.rst:169 +#: ../../source/tutorial-quickstart-tensorflow.rst:284 #, fuzzy 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`." +"Check the source code of the extended version of this tutorial in " +"|quickstart_tf_link|_ in the Flower GitHub repository." msgstr "" "Félicitations ! Tu as réussi à construire et à faire fonctionner ton " -"premier système d'apprentissage fédéré. Le `code source complet " +"premier système d'apprentissage fédéré. Le code source complet " "`_ pour cela se trouve dans :code:`examples" -"/quickstart-tensorflow/client.py`." +"mxnet/client.py>`_ de cet exemple se trouve dans :code:`examples" +"/quickstart-mxnet`." + +#: ../../source/tutorial-quickstart-tensorflow.rst:299 +msgid "" +"The video shown below shows how to setup a TensorFlow + Flower project " +"using our previously recommended APIs. A new video tutorial will be " +"released that shows the new APIs (as the content above does)" +msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:-1 msgid "" @@ -28377,7 +28488,7 @@ msgstr "" "chose d'autre, comme la régression linéaire classique." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -msgid "|e5918c1c06a4434bbe4bf49235e40059|" +msgid "|e87b69b2ada74ea49412df16f4a0b9cc|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 @@ -28396,7 +28507,7 @@ msgstr "" " Go." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -msgid "|c0165741bd1944f09ec55ce49032377d|" +msgid "|33cacb7d985c4906b348515c1a5cd993|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 @@ -28427,7 +28538,7 @@ msgstr "" "chanson." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +msgid "|cc080a555947492fa66131dc3a967603|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 @@ -28448,7 +28559,7 @@ msgstr "" " données pour la même tâche." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +msgid "|085c3e0fb8664c6aa06246636524b20b|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 @@ -28469,7 +28580,7 @@ msgstr "" "cloud." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -msgid "|74f26ca701254d3db57d7899bd91eb55|" +msgid "|bfe69c74e48c45d49b50251c38c2a019|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 @@ -28490,7 +28601,7 @@ msgstr "" "appuyés." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -msgid "|bda79f21f8154258a40e5766b2634ad7|" +msgid "|ebbecd651f0348d99c6511ea859bf4ca|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 @@ -28515,7 +28626,7 @@ msgstr "" " sur un serveur centralisé." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -msgid "|89d30862e62e4f9989e193483a08680a|" +msgid "|163117eb654a4273babba413cf8065f5|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 @@ -28534,7 +28645,7 @@ msgstr "" "suffisantes pour former un bon modèle." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -msgid "|77e9918671c54b4f86e01369c0785ce8|" +msgid "|452ac3ba453b4cd1be27be1ba7560d64|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 @@ -28756,7 +28867,7 @@ msgstr "" "partir d'un point de contrôle précédemment sauvegardé." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -msgid "|7e4ccef37cc94148a067107b34eb7447|" +msgid "|f403fcd69e4e44409627e748b404c086|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 @@ -28791,7 +28902,7 @@ msgstr "" "rendements décroissants." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -msgid "|28e47e4cded14479a0846c8e5f22c872|" +msgid "|4b00fe63870145968f8443619a792a42|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 @@ -28824,7 +28935,7 @@ msgstr "" "données locales, ou même de quelques étapes (mini-batchs)." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +msgid "|368378731066486fa4397e89bc6b870c|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 @@ -28855,7 +28966,7 @@ msgstr "" " l'entraînement local." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +msgid "|a66aa83d85bf4ffba7ed660b718066da|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 @@ -28914,7 +29025,7 @@ msgstr "" "times as much as each of the 100 examples." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -msgid "|81749d0ac0834c36a83bd38f433fea31|" +msgid "|82324b9af72a4582a81839d55caab767|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 @@ -29057,7 +29168,7 @@ msgstr "" "quel cadre de ML et n'importe quel langage de programmation." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -msgid "|ed9aae51da70428eab7eef32f21e819e|" +msgid "|fbf2da0da3cc4f8ab3b3eff852d80c41|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 @@ -36805,3 +36916,618 @@ msgstr "" #~ msgid "|c00bf2750bc24d229737a0fe1395f0fc|" #~ msgstr "" +#~ msgid "run\\_client\\_app" +#~ msgstr "" + +#~ msgid "run\\_supernode" +#~ msgstr "flower-superlink" + +#~ msgid "Retrieve the corresponding layout by the string key." +#~ msgstr "" + +#~ msgid "" +#~ "When there isn't an exact match, " +#~ "all the existing keys in the " +#~ "layout map will be treated as a" +#~ " regex and map against the input " +#~ "key again. The first match will be" +#~ " returned, based on the key insertion" +#~ " order. Return None if there isn't" +#~ " any match found." +#~ msgstr "" + +#~ msgid "the string key as the query for the layout." +#~ msgstr "" + +#~ msgid "Corresponding layout based on the query." +#~ msgstr "" + +#~ msgid "run\\_server\\_app" +#~ msgstr "" + +#~ msgid "run\\_superlink" +#~ msgstr "flower-superlink" + +#~ msgid "" +#~ ":py:obj:`start_simulation `\\" +#~ " \\(\\*\\, client\\_fn\\, num\\_clients\\)" +#~ msgstr "" + +#~ msgid "" +#~ "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 (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)." +#~ msgstr "" + +#~ msgid "The total number of clients in this simulation." +#~ msgstr "" + +#~ msgid "" +#~ "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." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "" +#~ "Optionally specify the type of actor " +#~ "to use. The actor object, which " +#~ "persists throughout the simulation, will " +#~ "be the process in charge of " +#~ "executing a ClientApp wrapping input " +#~ "argument `client_fn`." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ 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" +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with FastAI to train a vision " +#~ "model on CIFAR-10." +#~ msgstr "" + +#~ msgid "Let's build a federated learning system using fastai and Flower!" +#~ msgstr "" +#~ "Construisons un système d'apprentissage fédéré" +#~ " en utilisant fastai et Flower !" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn more." +#~ msgstr "" +#~ "Réfère-toi à l'exemple de code " +#~ "complet `_ pour en savoir plus." + +#~ msgid "" +#~ "Check out this Federating Learning " +#~ "quickstart tutorial for using Flower " +#~ "with HuggingFace Transformers in order " +#~ "to fine-tune an LLM." +#~ msgstr "" + +#~ msgid "" +#~ "Let's build a federated learning system" +#~ " using Hugging Face Transformers and " +#~ "Flower!" +#~ msgstr "" +#~ "Construisons un système d'apprentissage fédéré" +#~ " à l'aide des transformateurs Hugging " +#~ "Face et de Flower !" + +#~ msgid "Dependencies" +#~ msgstr "Dépendances" + +#~ 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`:" +#~ msgstr "" +#~ "Pour suivre ce tutoriel, tu devras " +#~ "installer les paquets suivants : " +#~ ":code:`datasets`, :code:`evaluate`, :code:`flwr`, " +#~ ":code:`torch`, et :code:`transformers`. Cela " +#~ "peut être fait en utilisant :code:`pip`" +#~ " :" + +#~ msgid "Standard Hugging Face workflow" +#~ msgstr "Flux de travail standard pour le visage" + +#~ msgid "Handling the data" +#~ msgstr "Traitement des données" + +#~ msgid "" +#~ "To fetch the IMDB dataset, we will" +#~ " use Hugging Face's :code:`datasets` " +#~ "library. We then need to tokenize " +#~ "the data and create :code:`PyTorch` " +#~ "dataloaders, this is all done in " +#~ "the :code:`load_data` function:" +#~ msgstr "" +#~ "Pour récupérer le jeu de données " +#~ "IMDB, nous utiliserons la bibliothèque " +#~ ":code:`datasets` de Hugging Face. Nous " +#~ "devons ensuite tokeniser les données et" +#~ " créer des :code:`PyTorch` dataloaders, ce" +#~ " qui est fait dans la fonction " +#~ ":code:`load_data` :" + +#~ msgid "Training and testing the model" +#~ msgstr "Former et tester le modèle" + +#~ 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:" +#~ msgstr "" +#~ "Une fois que nous avons trouvé un" +#~ " moyen de créer notre trainloader et" +#~ " notre testloader, nous pouvons nous " +#~ "occuper de l'entraînement et du test." +#~ " C'est très similaire à n'importe " +#~ "quelle boucle d'entraînement ou de test" +#~ " :code:`PyTorch` :" + +#~ msgid "Creating the model itself" +#~ msgstr "Créer le modèle lui-même" + +#~ msgid "" +#~ "To create the model itself, we " +#~ "will just load the pre-trained " +#~ "distillBERT model using Hugging Face’s " +#~ ":code:`AutoModelForSequenceClassification` :" +#~ msgstr "" +#~ "Pour créer le modèle lui-même, " +#~ "nous allons simplement charger le modèle" +#~ " distillBERT pré-entraîné en utilisant le" +#~ " :code:`AutoModelForSequenceClassification` de Hugging" +#~ " Face :" + +#~ msgid "Creating the IMDBClient" +#~ msgstr "Création du client IMDBC" + +#~ 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:" +#~ msgstr "" +#~ "Pour fédérer notre exemple à plusieurs" +#~ " clients, nous devons d'abord écrire " +#~ "notre classe de client Flower (héritant" +#~ " de :code:`flwr.client.NumPyClient`). C'est très" +#~ " facile, car notre modèle est un " +#~ "modèle :code:`PyTorch` standard :" + +#~ 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." +#~ msgstr "" +#~ "La fonction :code:`get_parameters` permet au" +#~ " serveur d'obtenir les paramètres du " +#~ "client. Inversement, la fonction " +#~ ":code:`set_parameters` permet au serveur " +#~ "d'envoyer ses paramètres au client. " +#~ "Enfin, la fonction :code:`fit` forme le" +#~ " modèle localement pour le client, et" +#~ " la fonction :code:`evaluate` teste le " +#~ "modèle localement et renvoie les mesures" +#~ " correspondantes." + +#~ msgid "Starting the server" +#~ msgstr "Démarrer le serveur" + +#~ 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:" +#~ msgstr "" +#~ "Maintenant que nous avons un moyen " +#~ "d'instancier les clients, nous devons " +#~ "créer notre serveur afin d'agréger les" +#~ " résultats. Avec Flower, cela peut " +#~ "être fait très facilement en choisissant" +#~ " d'abord une stratégie (ici, nous " +#~ "utilisons :code:`FedAvg`, qui définira les " +#~ "poids globaux comme la moyenne des " +#~ "poids de tous les clients à chaque" +#~ " tour) et en utilisant ensuite la " +#~ "fonction :code:`flwr.server.start_server` :" + +#~ 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)." +#~ msgstr "" +#~ "La fonction :code:`weighted_average` est là" +#~ " pour fournir un moyen d'agréger les" +#~ " mesures réparties entre les clients " +#~ "(en gros, cela nous permet d'afficher" +#~ " une belle moyenne de précision et" +#~ " de perte pour chaque tour)." + +#~ msgid "Putting everything together" +#~ msgstr "Tout assembler" + +#~ msgid "We can now start client instances using:" +#~ msgstr "" +#~ "Nous pouvons maintenant démarrer des " +#~ "instances de clients en utilisant :" + +#~ msgid "" +#~ "And they will be able to connect" +#~ " to the server and start the " +#~ "federated training." +#~ msgstr "" +#~ "Et ils pourront se connecter au " +#~ "serveur et démarrer la formation " +#~ "fédérée." + +#~ msgid "" +#~ "If you want to check out " +#~ "everything put together, you should " +#~ "check out the `full code example " +#~ "`_ ." +#~ msgstr "" +#~ "Si tu veux voir tout ce qui " +#~ "est mis ensemble, tu devrais consulter" +#~ " l'exemple de code complet : " +#~ "[https://github.com/adap/flower/tree/main/examples/quickstart-" +#~ "huggingface](https://github.com/adap/flower/tree/main/examples" +#~ "/quickstart-huggingface)." + +#~ 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." +#~ msgstr "" +#~ "Bien sûr, c'est un exemple très " +#~ "basique, et beaucoup de choses peuvent" +#~ " être ajoutées ou modifiées, il " +#~ "s'agissait juste de montrer avec quelle" +#~ " simplicité on pouvait fédérer un " +#~ "flux de travail Hugging Face à " +#~ "l'aide de Flower." + +#~ msgid "" +#~ "Note that in this example we used" +#~ " :code:`PyTorch`, but we could have " +#~ "very well used :code:`TensorFlow`." +#~ msgstr "" +#~ "Notez que dans cet exemple, nous " +#~ "avons utilisé :code:`PyTorch`, mais nous " +#~ "aurions très bien pu utiliser " +#~ ":code:`TensorFlow`." + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with PyTorch Lightning to train an " +#~ "Auto Encoder model on MNIST." +#~ msgstr "" + +#~ msgid "" +#~ "Let's build a horizontal federated " +#~ "learning system using PyTorch Lightning " +#~ "and Flower!" +#~ msgstr "" +#~ "Construisons un système d'apprentissage fédéré" +#~ " en utilisant PyTorch Lightning et " +#~ "Flower !" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn " +#~ "more." +#~ msgstr "" +#~ "Réfère-toi à l'exemple de code " +#~ "complet `_ pour en " +#~ "savoir plus." + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with TensorFlow to train a MobilNetV2" +#~ " model on CIFAR-10." +#~ msgstr "" + +#~ msgid "Let's build a federated learning system in less than 20 lines of code!" +#~ msgstr "" +#~ "Construisons un système d'apprentissage fédéré" +#~ " en moins de 20 lignes de code" +#~ " !" + +#~ msgid "Before Flower can be imported we have to install it:" +#~ msgstr "Avant de pouvoir importer une fleur, nous devons l'installer :" + +#~ msgid "" +#~ "Since we want to use the Keras " +#~ "API of TensorFlow (TF), we have to" +#~ " install TF as well:" +#~ msgstr "" +#~ "Comme nous voulons utiliser l'API Keras" +#~ " de TensorFlow (TF), nous devons " +#~ "également installer TF :" + +#~ msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" +#~ msgstr "" +#~ "Ensuite, dans un fichier appelé " +#~ ":code:`client.py`, importe Flower et " +#~ "TensorFlow :" + +#~ 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." +#~ msgstr "" +#~ "Nous utilisons les utilitaires Keras de" +#~ " TF pour charger CIFAR10, un ensemble" +#~ " de données de classification d'images " +#~ "colorées populaire pour l'apprentissage " +#~ "automatique. L'appel à " +#~ ":code:`tf.keras.datasets.cifar10.load_data()` télécharge " +#~ "CIFAR10, le met en cache localement, " +#~ "puis renvoie l'ensemble d'entraînement et " +#~ "de test sous forme de NumPy " +#~ "ndarrays." + +#~ msgid "" +#~ "Next, we need a model. For the " +#~ "purpose of this tutorial, we use " +#~ "MobilNetV2 with 10 output classes:" +#~ msgstr "" +#~ "Ensuite, nous avons besoin d'un modèle." +#~ " Pour les besoins de ce tutoriel, " +#~ "nous utilisons MobilNetV2 avec 10 " +#~ "classes de sortie :" + +#~ 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)." +#~ msgstr "" +#~ "Le serveur Flower interagit avec les " +#~ "clients par le biais d'une interface " +#~ "appelée :code:`Client`. Lorsque le serveur " +#~ "sélectionne un client particulier pour " +#~ "la formation, il envoie des instructions" +#~ " de formation sur le réseau. Le " +#~ "client reçoit ces instructions et " +#~ "appelle l'une des méthodes :code:`Client` " +#~ "pour exécuter ton code (c'est-à-dire " +#~ "pour former le réseau neuronal que " +#~ "nous avons défini plus tôt)." + +#~ 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:" +#~ msgstr "" +#~ "Flower fournit une classe de commodité" +#~ " appelée :code:`NumPyClient` qui facilite " +#~ "la mise en œuvre de l'interface " +#~ ":code:`Client` lorsque ta charge de " +#~ "travail utilise Keras. L'interface " +#~ ":code:`NumPyClient` définit trois méthodes qui" +#~ " peuvent être mises en œuvre de " +#~ "la manière suivante :" + +#~ msgid "" +#~ "We can now create an instance of" +#~ " our class :code:`CifarClient` and add " +#~ "one line to actually run this " +#~ "client:" +#~ msgstr "" +#~ "Nous pouvons maintenant créer une " +#~ "instance de notre classe :code:`CifarClient`" +#~ " et ajouter une ligne pour exécuter" +#~ " ce client :" + +#~ 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." +#~ msgstr "" +#~ "C'est tout pour le client. Il nous" +#~ " suffit d'implémenter :code:`Client` ou " +#~ ":code:`NumPyClient` et d'appeler " +#~ ":code:`fl.client.start_client()`. La chaîne " +#~ ":code:`\"[: :]:8080\"` indique au client " +#~ "à quel serveur se connecter. Dans " +#~ "notre cas, nous pouvons exécuter le " +#~ "serveur et le client sur la même" +#~ " machine, c'est pourquoi nous utilisons " +#~ ":code:`\"[: :]:8080\"`. Si nous exécutons " +#~ "une charge de travail véritablement " +#~ "fédérée avec le serveur et les " +#~ "clients fonctionnant sur des machines " +#~ "différentes, tout ce qui doit changer" +#~ " est l'adresse :code:`server_address` vers " +#~ "laquelle nous dirigeons le client." + +#~ msgid "Each client will have its own dataset." +#~ msgstr "Chaque client aura son propre ensemble de données." + +#~ msgid "" +#~ "You should now see how the " +#~ "training does in the very first " +#~ "terminal (the one that started the " +#~ "server):" +#~ msgstr "" +#~ "Tu devrais maintenant voir comment la" +#~ " formation se déroule dans le tout" +#~ " premier terminal (celui qui a " +#~ "démarré le serveur) :" + +#~ 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`." +#~ msgstr "" +#~ "Félicitations ! Tu as réussi à " +#~ "construire et à faire fonctionner ton" +#~ " premier système d'apprentissage fédéré. Le" +#~ " `code source complet " +#~ "`_ pour cela se trouve" +#~ " dans :code:`examples/quickstart-tensorflow/client.py`." + +#~ msgid "|e5918c1c06a4434bbe4bf49235e40059|" +#~ msgstr "" + +#~ msgid "|c0165741bd1944f09ec55ce49032377d|" +#~ msgstr "" + +#~ msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +#~ msgstr "" + +#~ msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +#~ msgstr "" + +#~ msgid "|74f26ca701254d3db57d7899bd91eb55|" +#~ msgstr "" + +#~ msgid "|bda79f21f8154258a40e5766b2634ad7|" +#~ msgstr "" + +#~ msgid "|89d30862e62e4f9989e193483a08680a|" +#~ msgstr "" + +#~ msgid "|77e9918671c54b4f86e01369c0785ce8|" +#~ msgstr "" + +#~ msgid "|7e4ccef37cc94148a067107b34eb7447|" +#~ msgstr "" + +#~ msgid "|28e47e4cded14479a0846c8e5f22c872|" +#~ msgstr "" + +#~ msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +#~ msgstr "" + +#~ msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +#~ msgstr "" + +#~ msgid "|81749d0ac0834c36a83bd38f433fea31|" +#~ msgstr "" + +#~ msgid "|ed9aae51da70428eab7eef32f21e819e|" +#~ msgstr "" + diff --git a/doc/locales/ko/LC_MESSAGES/framework-docs.po b/doc/locales/ko/LC_MESSAGES/framework-docs.po index db201f613126..4c738e16b434 100644 --- a/doc/locales/ko/LC_MESSAGES/framework-docs.po +++ b/doc/locales/ko/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-09-15 09:09+0200\n" +"POT-Creation-Date: 2024-09-24 00:29+0000\n" "PO-Revision-Date: 2024-08-23 13:09+0000\n" "Last-Translator: Seulki Yun \n" "Language: ko\n" @@ -17,7 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.15.0\n" +"Generated-By: Babel 2.16.0\n" #: ../../source/contributor-explanation-public-and-private-apis.rst:2 msgid "Public and private APIs" @@ -1468,7 +1468,7 @@ msgstr "" msgid "Setting up the repository" msgstr "레포지토리 설정하기" -#: ../../source/contributor-tutorial-contribute-on-github.rst:12 +#: ../../source/contributor-tutorial-contribute-on-github.rst:21 msgid "**Create a GitHub account and setup Git**" msgstr "**GitHub 계정을 만들고 Git을 설정합니다**" @@ -1509,7 +1509,7 @@ msgstr "" "일반적인 Git 및 GitHub 워크플로우의 기본 개념은 다음과 같이 요약됩니다. GitHub의 원격 레포지토리에서 코드를 " "다운로드하고 로컬에서 변경한 후 Git을 사용하여 추적한 다음 새 기록을 다시 GitHub에 업로드하는 것입니다." -#: ../../source/contributor-tutorial-contribute-on-github.rst:23 +#: ../../source/contributor-tutorial-contribute-on-github.rst:32 msgid "**Forking the Flower repository**" msgstr "**Flower 레포지토리 포크하기**" @@ -1534,7 +1534,7 @@ msgstr "" "원하는 경우 이름을 변경할 수 있지만, 이 버전의 Flower는 자신의 계정(즉, 자신의 리포지토리 목록)에 위치하게 되므로 변경할" " 필요는 없습니다. 만들기가 완료되면 왼쪽 상단에Flower 버전이 표시되는 것을 볼 수 있습니다." -#: ../../source/contributor-tutorial-contribute-on-github.rst:34 +#: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "**Cloning your forked repository**" msgstr "**포크된 레포지토리 클론하기**" @@ -1560,7 +1560,7 @@ msgid "" "it) folder in the current working directory." msgstr "현재 작업 디렉터리에``flower/``(또는 포크 이름을 변경한 경우 포크 이름) 폴더가 생성됩니다." -#: ../../source/contributor-tutorial-contribute-on-github.rst:49 +#: ../../source/contributor-tutorial-contribute-on-github.rst:66 msgid "**Add origin**" msgstr "**origin 추가**" @@ -1584,7 +1584,7 @@ msgid "" "terminal:" msgstr "\\ 이 복사되면 터미널에 다음 명령을 입력하면 됩니다:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:68 +#: ../../source/contributor-tutorial-contribute-on-github.rst:90 msgid "**Add upstream**" msgstr "**Upstream 추가하기**" @@ -1645,7 +1645,7 @@ msgstr "변경하기 전에 레포지토리를 최신 상태로 유지하세요: msgid "And with Flower's repository:" msgstr "Flower의 레포지토리도 있습니다:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:114 +#: ../../source/contributor-tutorial-contribute-on-github.rst:122 msgid "**Create a new branch**" msgstr "**새 브랜치 만들기**" @@ -1662,7 +1662,7 @@ msgid "" "directory:" msgstr "이렇게 하려면 레포지토리 디렉토리에서 다음 명령을 실행하면 됩니다:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:124 +#: ../../source/contributor-tutorial-contribute-on-github.rst:125 msgid "**Make changes**" msgstr "**변경하기**" @@ -1670,7 +1670,7 @@ msgstr "**변경하기**" msgid "Write great code and create wonderful changes using your favorite editor!" msgstr "선호하는 편집기를 사용하여 멋진 코드를 작성하고 훌륭한 변화를 만들어 보세요!" -#: ../../source/contributor-tutorial-contribute-on-github.rst:127 +#: ../../source/contributor-tutorial-contribute-on-github.rst:138 msgid "**Test and format your code**" msgstr "**코드 테스트 및 서식 지정**" @@ -1687,7 +1687,7 @@ msgstr "" msgid "To do so, we have written a few scripts that you can execute:" msgstr "이를 위해 실행할 수 있는 몇 가지 스크립트를 작성했습니다:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:140 +#: ../../source/contributor-tutorial-contribute-on-github.rst:150 msgid "**Stage changes**" msgstr "**변경사항 스테이징**" @@ -1710,7 +1710,7 @@ msgstr "" "마지막 버전(마지막 커밋)과 비교하여 수정된 파일을 확인하고 커밋을 위해 스테이징된 파일을 확인하려면 :code:`git " "status` 명령을 사용하면 됩니다." -#: ../../source/contributor-tutorial-contribute-on-github.rst:152 +#: ../../source/contributor-tutorial-contribute-on-github.rst:160 msgid "**Commit changes**" msgstr "**변경사항 커밋**" @@ -1729,7 +1729,7 @@ msgstr "" "커밋의 내용을 다른 사람에게 설명하기 위해 \\가 있습니다. 명령형 스타일로 작성해야 하며 간결해야" " 합니다. 예를 들면 :code:`git commit -m \"Add images to README\"`." -#: ../../source/contributor-tutorial-contribute-on-github.rst:162 +#: ../../source/contributor-tutorial-contribute-on-github.rst:171 msgid "**Push the changes to the fork**" msgstr "**변경 사항을 포크에 푸시**" @@ -1752,7 +1752,7 @@ msgstr "이 작업이 완료되면 변경한 내용으로 포크된 레포지토 msgid "Creating and merging a pull request (PR)" msgstr "pull request(PR) 만들기 및 병합하기" -#: ../../source/contributor-tutorial-contribute-on-github.rst:176 +#: ../../source/contributor-tutorial-contribute-on-github.rst:206 msgid "**Create the PR**" msgstr "**PR 만들기**" @@ -1823,7 +1823,7 @@ msgid "" "anyone, you have the option to create a draft pull request:" msgstr "PR이 아직 검토할 준비가 되지 않았고 다른 사람에게 알리고 싶지 않은 경우 pull request 초안을 만드는 옵션이 있습니다:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:208 +#: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "**Making new changes**" msgstr "**new changes 만들기**" @@ -1834,7 +1834,7 @@ msgid "" " associated with the PR." msgstr "PR이 초안으로 열렸든 아니든, PR과 연결된 브랜치를 변경하여 이전과 같은 방식으로 새 커밋을 푸시할 수 있습니다." -#: ../../source/contributor-tutorial-contribute-on-github.rst:211 +#: ../../source/contributor-tutorial-contribute-on-github.rst:231 msgid "**Review the PR**" msgstr "**PR 검토하기**" @@ -1870,7 +1870,7 @@ msgid "" "review." msgstr "모든 대화가 해결되면 검토를 다시 요청할 수 있습니다." -#: ../../source/contributor-tutorial-contribute-on-github.rst:233 +#: ../../source/contributor-tutorial-contribute-on-github.rst:251 msgid "**Once the PR is merged**" msgstr "**PR이 병합되면**" @@ -2157,6 +2157,7 @@ msgstr "기여자로 시작하기" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 #: ../../source/docker/run-as-subprocess.rst:11 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:15 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:12 #: ../../source/docker/tutorial-quickstart-docker.rst:11 msgid "Prerequisites" @@ -2941,6 +2942,239 @@ msgid "" " the SuperNode to execute the ClientApp as a subprocess:" msgstr "" +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:2 +#, fuzzy +msgid "Run Flower Quickstart Examples with Docker Compose" +msgstr "빠른 시작 튜토리얼" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:4 +msgid "" +"Flower provides a set of `quickstart examples " +"`_ to help you get " +"started with the framework. These examples are designed to demonstrate " +"the capabilities of Flower and by default run using the Simulation " +"Engine. This guide demonstrates how to run them using Flower's Deployment" +" Engine via Docker Compose." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:11 +msgid "" +"Some quickstart examples may have limitations or requirements that " +"prevent them from running on every environment. For more information, " +"please see `Limitations`_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 +#: ../../source/docker/tutorial-quickstart-docker.rst:13 +#, fuzzy +msgid "Before you start, make sure that:" +msgstr "시작하기 전에 Docker daemon이 실행 중인지 확인하세요:" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:19 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 +#: ../../source/docker/tutorial-quickstart-docker.rst:15 +msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:20 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker.rst:16 +#, fuzzy +msgid "The Docker daemon is running." +msgstr "Docker 데몬이 실행 중인지 확인하십시오." + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:21 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 +msgid "Docker Compose is `installed `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:24 +#, fuzzy +msgid "Run the Quickstart Example" +msgstr "예시 요청" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:26 +msgid "" +"Clone the quickstart example you like to run. For example, ``quickstart-" +"pytorch``:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:34 +msgid "" +"Download the `compose.yml " +"`_" +" file into the example directory:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:41 +#, fuzzy +msgid "Build and start the services using the following command:" +msgstr "다음 명령을 실행하여 가상 환경을 활성화합니다:" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:47 +#, fuzzy +msgid "" +"Append the following lines to the end of the ``pyproject.toml`` file and " +"save it:" +msgstr "``pyproject.toml``에 다음 버전 제약 조건을 설정했는지 확인하세요:" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:49 +#: ../../source/docker/tutorial-quickstart-docker.rst:319 +#, fuzzy +msgid "pyproject.toml" +msgstr "또는 ``pyproject.toml``:" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:58 +msgid "" +"You can customize the string that follows ``tool.flwr.federations.`` to " +"fit your needs. However, please note that the string cannot contain a dot" +" (``.``)." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:61 +msgid "" +"In this example, ``local-deployment`` has been used. Just remember to " +"replace ``local-deployment`` with your chosen name in both the " +"``tool.flwr.federations.`` string and the corresponding ``flwr run .`` " +"command." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:65 +#, fuzzy +msgid "Run the example:" +msgstr "전체 코드 예제" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:71 +msgid "Follow the logs of the SuperExec service:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:77 +msgid "" +"That is all it takes! You can monitor the progress of the run through the" +" logs of the SuperExec." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:80 +msgid "Run a Different Quickstart Example" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:82 +msgid "" +"To run a different quickstart example, such as ``quickstart-tensorflow``," +" first, shut down the Docker Compose services of the current example:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:89 +msgid "After that, you can repeat the steps above." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:92 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:98 +#, fuzzy +msgid "Limitations" +msgstr "동기" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:97 +#, fuzzy +msgid "Quickstart Example" +msgstr "빠른 시작" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:99 +#, fuzzy +msgid "quickstart-fastai" +msgstr "빠른 시작 튜토리얼" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:100 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:102 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:110 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:112 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:116 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:118 +#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 +#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 +#: ../../source/ref-changelog.md:929 +msgid "None" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:101 +#, fuzzy +msgid "quickstart-huggingface" +msgstr "빠른 시작 튜토리얼" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:103 +#, fuzzy +msgid "quickstart-jax" +msgstr "빠른 시작" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:104 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:106 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:120 +msgid "" +"The example has not yet been updated to work with the latest ``flwr`` " +"version." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:105 +#, fuzzy +msgid "quickstart-mlcube" +msgstr "빠른 시작" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:107 +#, fuzzy +msgid "quickstart-mlx" +msgstr "빠른 시작" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:108 +msgid "" +"`Requires to run on macOS with Apple Silicon `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:109 +#, fuzzy +msgid "quickstart-monai" +msgstr "빠른 시작" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:111 +#, fuzzy +msgid "quickstart-pandas" +msgstr "빠른 시작 튜토리얼" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:113 +msgid "quickstart-pytorch-lightning" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:114 +msgid "" +"Requires an older pip version that is not supported by the Flower Docker " +"images." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:115 +#, fuzzy +msgid "quickstart-pytorch" +msgstr "빠른 시작 튜토리얼" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:117 +msgid "quickstart-sklearn-tabular" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:119 +#, fuzzy +msgid "quickstart-tabnet" +msgstr "빠른 시작 튜토리얼" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:121 +#, fuzzy +msgid "quickstart-tensorflow" +msgstr "빠른 시작 튜토리얼" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:122 +msgid "Only runs on AMD64." +msgstr "" + #: ../../source/docker/set-environment-variables.rst:2 #, fuzzy msgid "Set Environment Variables" @@ -2972,23 +3206,6 @@ msgid "" " understanding the basic workflow that uses the minimum configurations." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 -#: ../../source/docker/tutorial-quickstart-docker.rst:13 -#, fuzzy -msgid "Before you start, make sure that:" -msgstr "시작하기 전에 Docker daemon이 실행 중인지 확인하세요:" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 -#: ../../source/docker/tutorial-quickstart-docker.rst:15 -msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." -msgstr "" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 -#: ../../source/docker/tutorial-quickstart-docker.rst:16 -#, fuzzy -msgid "The Docker daemon is running." -msgstr "Docker 데몬이 실행 중인지 확인하십시오." - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:21 #: ../../source/docker/tutorial-quickstart-docker.rst:19 msgid "Step 1: Set Up" @@ -3419,11 +3636,6 @@ msgstr "" msgid "Add the following lines to the ``pyproject.toml``:" msgstr "``pyproject.toml``에 다음 버전 제약 조건을 설정했는지 확인하세요:" -#: ../../source/docker/tutorial-quickstart-docker.rst:319 -#, fuzzy -msgid "pyproject.toml" -msgstr "또는 ``pyproject.toml``:" - #: ../../source/docker/tutorial-quickstart-docker.rst:326 msgid "Run the ``quickstart-docker`` project by executing the command:" msgstr "" @@ -3472,6 +3684,7 @@ msgstr "" msgid "Remove the containers and the bridge network:" msgstr "" +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:401 #: ../../source/docker/tutorial-quickstart-docker.rst:399 #, fuzzy msgid "Where to Go Next" @@ -3508,10 +3721,6 @@ msgid "" "configuration that best suits your project's needs." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 -msgid "Docker Compose is `installed `_." -msgstr "" - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:23 msgid "Clone the Docker Compose ``complete`` directory:" msgstr "" @@ -3707,7 +3916,7 @@ msgstr "" #: ../../source/docker/tutorial-quickstart-docker-compose.rst:188 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:241 -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:362 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:369 msgid "Rerun the ``quickstart-compose`` project:" msgstr "" @@ -3771,76 +3980,81 @@ msgstr "" msgid "compose.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:303 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 msgid "" "If you also want to enable TLS for the new SuperNodes, duplicate the " "SuperNode definition for each new SuperNode service in the ``with-" "tls.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:306 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:313 msgid "" "Make sure that the names of the services match with the one in the " "``compose.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:308 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:315 msgid "In ``with-tls.yml``, add the following:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:317 msgid "with-tls.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:332 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 msgid "Step 8: Persisting the SuperLink State and Enabling TLS" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:334 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:341 msgid "" "To run Flower with persisted SuperLink state and enabled TLS, a slight " "change in the ``with-state.yml`` file is required:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:337 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:344 msgid "Comment out the lines 2-4 and uncomment the lines 5-9:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:346 msgid "with-state.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:356 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:363 #, fuzzy msgid "Restart the services:" msgstr "이미 *서버*를 시작할 수 있습니다:" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:370 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:377 msgid "Step 9: Merge Multiple Compose Files" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:372 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:379 msgid "" "You can merge multiple Compose files into a single file. For instance, if" " you wish to combine the basic configuration with the TLS configuration, " "execute the following command:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:380 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:387 msgid "" "This will merge the contents of ``compose.yml`` and ``with-tls.yml`` into" " a new file called ``my_compose.yml``." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:384 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:391 msgid "Step 10: Clean Up" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:386 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:393 #, fuzzy msgid "Remove all services and volumes:" msgstr "R에서 모든 항목을 제거합니다." +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:403 +#, fuzzy +msgid ":doc:`run-quickstart-examples-docker-compose`" +msgstr "빠른 시작 튜토리얼" + #: ../../source/docker/use-a-different-version.rst:2 #, fuzzy msgid "Use a Different Flower Version" @@ -4184,7 +4398,7 @@ msgstr "" "code:`CifarClient`는 모델 파라미터를 가져오거나 설정하는 메서드 2개, 모델 학습을 위한 메서드 1개, 모델 테스트를" " 위한 메서드 1개 등 네 가지 메서드를 구현해야 합니다:" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:218 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:219 msgid ":code:`set_parameters`" msgstr ":code:`set_parameters`" @@ -4217,9 +4431,9 @@ msgstr "" "모델 매개변수를 가져와서 NumPy :code:`ndarray`의 목록으로 반환합니다(이는 " ":code:`flwr.client.NumPyClient`가 기대하는 바와 같습니다)" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:223 -#: ../../source/tutorial-quickstart-jax.rst:171 -#: ../../source/tutorial-quickstart-scikitlearn.rst:123 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:225 +#: ../../source/tutorial-quickstart-jax.rst:173 +#: ../../source/tutorial-quickstart-scikitlearn.rst:125 msgid ":code:`fit`" msgstr ":code:`fit`" @@ -4241,9 +4455,9 @@ msgstr "로컬 훈련 세트에서 모델을 훈련합니다" msgid "get the updated local model weights and return them to the server" msgstr "업데이트된 로컬 모델 가중치를 가져와 서버로 반환합니다" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:227 -#: ../../source/tutorial-quickstart-jax.rst:175 -#: ../../source/tutorial-quickstart-scikitlearn.rst:127 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:230 +#: ../../source/tutorial-quickstart-jax.rst:178 +#: ../../source/tutorial-quickstart-scikitlearn.rst:128 msgid ":code:`evaluate`" msgstr ":code:`evaluate`" @@ -4354,7 +4568,7 @@ msgstr "" "평균 소득 계산과 같은 모든 분석(M)이 두 데이터 세트에 대해 거의 동일한 결과를 산출하도록 보장합니다(O와 O' 는 비슷할 " "것입니다). 이렇게 하면 그룹 패턴은 보존하면서 개별 세부 정보는 가려져 개인의 정보가 군중 속에 숨겨집니다." -#: ../../source/explanation-differential-privacy.rst:16 +#: ../../source/explanation-differential-privacy.rst:-1 msgid "DP Intro" msgstr "DP 소개" @@ -4493,8 +4707,8 @@ msgstr "" "**로컬 차등 프라이버시**: DP는 정보를 서버로 보내기 전에 클라이언트 측에서 적용되며, 서버로 전송되는 업데이트가 클라이언트 " "데이터에 대한 정보를 유출하는 것을 방지하는 것이 목표입니다." +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:68 -#: ../../source/explanation-differential-privacy.rst:71 #: ../../source/how-to-use-differential-privacy.rst:11 msgid "Central Differential Privacy" msgstr "중앙 차등 프라이버시" @@ -4529,7 +4743,7 @@ msgstr "" "개인정보 보호에 중요하고 견고성에 도움이 되는 경우가 많습니다. 이를 달성하기 위한 일반적인 접근 방식은 클라이언트 모델 업데이트의" " `L2` 규범을 제한하여 더 큰 업데이트가 규범 `S`에 맞도록 축소되도록 하는 것입니다." -#: ../../source/explanation-differential-privacy.rst:84 +#: ../../source/explanation-differential-privacy.rst:-1 msgid "clipping" msgstr "클리핑" @@ -4581,8 +4795,8 @@ msgid "" "others." msgstr "고정 클리핑과 조정 클리핑 중 선택은 개인정보 보호 요구 사항, 데이터 배포, 모델 복잡성 등 다양한 요인에 따라 달라집니다." +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:105 -#: ../../source/explanation-differential-privacy.rst:110 #: ../../source/how-to-use-differential-privacy.rst:96 msgid "Local Differential Privacy" msgstr "로컬 차등 프라이버시" @@ -4850,7 +5064,7 @@ msgstr "" msgid "This is sometimes called a hub-and-spoke topology:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:18 +#: ../../source/explanation-flower-architecture.rst:24 #, fuzzy msgid "Hub-and-spoke topology in federated learning" msgstr "연합 학습이란 무엇입니까?" @@ -4923,7 +5137,7 @@ msgid "" "`missing link` between all those SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:65 +#: ../../source/explanation-flower-architecture.rst:71 #, fuzzy msgid "Basic Flower architecture" msgstr "Flower 아키텍처" @@ -4960,7 +5174,7 @@ msgid "" "SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:91 +#: ../../source/explanation-flower-architecture.rst:97 msgid "Multi-tenancy federated learning architecture" msgstr "" @@ -4982,7 +5196,7 @@ msgid "" "their corresponding ``ClientApp``\\s:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:107 +#: ../../source/explanation-flower-architecture.rst:113 msgid "Multi-tenancy federated learning architecture - Run 1" msgstr "" @@ -4998,7 +5212,7 @@ msgid "" " to participate in the training:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:119 +#: ../../source/explanation-flower-architecture.rst:125 msgid "Multi-tenancy federated learning architecture - Run 2" msgstr "" @@ -5034,7 +5248,7 @@ msgid "" "developer machine." msgstr "" -#: ../../source/explanation-flower-architecture.rst:145 +#: ../../source/explanation-flower-architecture.rst:151 msgid "Flower Deployment Engine with SuperExec" msgstr "" @@ -8205,7 +8419,7 @@ msgstr "" " 위한 :code:`DifferentialPrivacyServerSideFixedClipping`과 " ":code:`DifferentialPrivacyServerSideAdaptiveClipping`입니다." -#: ../../source/how-to-use-differential-privacy.rst:25 +#: ../../source/how-to-use-differential-privacy.rst:-1 msgid "server side clipping" msgstr "서버 측 클리핑" @@ -8244,7 +8458,7 @@ msgstr "" " 해당 서버 측 래퍼 :code:`DifferentialPrivacyClientSideFixedClipping` 및 " ":code:`DifferentialPrivacyClientSideAdaptiveClipping`이 있습니다." -#: ../../source/how-to-use-differential-privacy.rst:57 +#: ../../source/how-to-use-differential-privacy.rst:-1 msgid "client side clipping" msgstr "클라이언트 측 클리핑" @@ -8278,7 +8492,7 @@ msgstr "" "로컬 차분 프라이버시(DP)를 활용하고 클라이언트 모델 파라미터를 서버로 전송하기 전에 노이즈를 추가하려면 `LocalDpMod`를" " 사용하면 됩니다. 클리핑 노멀 값, 감도, 엡실론, 델타 등의 하이퍼파라미터를 설정해야 합니다." -#: ../../source/how-to-use-differential-privacy.rst:99 +#: ../../source/how-to-use-differential-privacy.rst:-1 msgid "local DP mod" msgstr "로컬 DP mod" @@ -8704,11 +8918,33 @@ msgstr "" msgid "Arguments" msgstr "빌드 전달인자" -#: ../../flwr install:1 new:1 run:1 +#: ../../flwr install:1 log:1 new:1 run:1 #, fuzzy msgid "Optional argument" msgstr "선택적 개선 사항" +#: ../../flwr log:1 +msgid "Get logs from a Flower project run." +msgstr "" + +#: ../../flwr log:1 +msgid "Flag to stream or print logs from the Flower run" +msgstr "" + +#: ../../flwr log +msgid "default" +msgstr "" + +#: ../../flwr log:1 +#, fuzzy +msgid "``True``" +msgstr "``DISTRO``" + +#: ../../flwr log:1 +#, fuzzy +msgid "Required argument" +msgstr "빌드 전달인자" + #: ../../flwr new:1 #, fuzzy msgid "Create new Flower App." @@ -8797,7 +9033,7 @@ msgstr "Modules" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`client `\\" +msgid ":py:obj:`flwr.client `\\" msgstr ":py:obj:`flwr.client `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.client:1 of @@ -8806,7 +9042,7 @@ msgstr "Flower 클라이언트." #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`common `\\" +msgid ":py:obj:`flwr.common `\\" msgstr ":py:obj:`flwr.common `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.common:1 of @@ -8815,7 +9051,7 @@ msgstr "서버와 클라이언트 간에 공유되는 공통 구성 요소입니 #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`server `\\" +msgid ":py:obj:`flwr.server `\\" msgstr ":py:obj:`flwr.server `\\" #: ../../source/ref-api/flwr.rst:35::1 @@ -8826,7 +9062,7 @@ msgstr "Flower 서버." #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`simulation `\\" +msgid ":py:obj:`flwr.simulation `\\" msgstr ":py:obj:`flwr.simulation `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.simulation:1 of @@ -8913,7 +9149,7 @@ msgstr "NumPy를 사용하는 Flower 클라이언트를 위한 추상 베이스 #: ../../source/ref-api/flwr.client.rst:50::1 #, fuzzy -msgid ":py:obj:`mod `\\" +msgid ":py:obj:`flwr.client.mod `\\" msgstr ":py:obj:`flwr.client.mod `\\" #: ../../source/ref-api/flwr.client.rst:50::1 flwr.client.mod:1 of @@ -9110,48 +9346,57 @@ msgstr ":py:obj:`context `\\" msgid "Getter for `Context` client attribute." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst -#: ../../source/ref-api/flwr.common.Array.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Context.rst -#: ../../source/ref-api/flwr.common.Error.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.Metadata.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst #: ../../source/ref-api/flwr.common.Parameters.rst:2 -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.common.RecordSet.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.ServerAppComponents.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.Bulyan.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdam.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgAndroid.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgM.rst -#: ../../source/ref-api/flwr.server.strategy.FedOpt.rst -#: ../../source/ref-api/flwr.server.strategy.FedProx.rst -#: ../../source/ref-api/flwr.server.strategy.FedTrimmedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedYogi.rst -#: ../../source/ref-api/flwr.server.strategy.Krum.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggPlusWorkflow.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggWorkflow.rst -#: ../../source/ref-api/flwr.simulation.run_simulation.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst #: flwr.client.app.start_client flwr.client.app.start_numpy_client -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: 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 +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.context.Context flwr.common.message.Error +#: flwr.common.message.Message flwr.common.message.Message.create_error_reply +#: flwr.common.message.Message.create_reply flwr.common.message.Metadata +#: flwr.common.record.configsrecord.ConfigsRecord +#: flwr.common.record.metricsrecord.MetricsRecord +#: flwr.common.record.parametersrecord.Array +#: flwr.common.record.parametersrecord.ParametersRecord +#: flwr.common.record.recordset.RecordSet flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.ClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.serverapp_components.ServerAppComponents +#: flwr.server.strategy.bulyan.Bulyan +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.fedadagrad.FedAdagrad +#: flwr.server.strategy.fedadam.FedAdam flwr.server.strategy.fedavg.FedAvg +#: flwr.server.strategy.fedavg_android.FedAvgAndroid +#: flwr.server.strategy.fedavgm.FedAvgM flwr.server.strategy.fedopt.FedOpt +#: flwr.server.strategy.fedprox.FedProx +#: flwr.server.strategy.fedtrimmedavg.FedTrimmedAvg +#: flwr.server.strategy.fedyogi.FedYogi flwr.server.strategy.krum.Krum +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow +#: flwr.simulation.run_simulation.run_simulation of msgid "Parameters" msgstr "파라미터" @@ -9162,21 +9407,31 @@ msgid "" "customize the local evaluation process." msgstr "서버에서 받은 (전역) 모델 파라미터와 로컬 평가 프로세스를 사용자 지정하는 데 사용되는 구성 값 사전이 포함된 평가 지침입니다." -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.evaluate +#: flwr.client.numpy_client.NumPyClient.fit +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of msgid "Returns" msgstr "반환" @@ -9186,18 +9441,29 @@ msgid "" "details such as the number of local data examples used for evaluation." msgstr "로컬 데이터 세트의 손실 및 평가에 사용된 로컬 데이터 예제 수와 같은 기타 세부 정보가 포함된 평가 결과입니다." -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of msgid "Return type" msgstr "반환 타입" @@ -9554,6 +9820,11 @@ msgstr "클라이언트 측 고정 클리핑 수정자." msgid ":py:obj:`make_ffn `\\ \\(ffn\\, mods\\)" 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 "." + #: ../../source/ref-api/flwr.client.mod.rst:28::1 msgid "" ":py:obj:`message_size_mod `\\ \\(msg\\," @@ -9730,10 +10001,6 @@ msgstr "일반적으로 fixedclipping_mod는 매개변수에서 가장 마지막 msgid "make\\_ffn" msgstr "make\\_ffn" -#: flwr.client.mod.utils.make_ffn:1 of -msgid "." -msgstr "." - #: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 msgid "message\\_size\\_mod" msgstr "message\\_size\\_mod" @@ -9760,14 +10027,6 @@ msgstr "secagg\\_mod" msgid "secaggplus\\_mod" msgstr "secaggplus\\_mod" -#: ../../source/ref-api/flwr.client.run_client_app.rst:2 -msgid "run\\_client\\_app" -msgstr "run\\_client\\_app" - -#: ../../source/ref-api/flwr.client.run_supernode.rst:2 -msgid "run\\_supernode" -msgstr "run\\_supernode" - #: ../../source/ref-api/flwr.client.start_client.rst:2 msgid "start\\_client" msgstr "start\\_client" @@ -10569,14 +10828,9 @@ msgstr "이 객체에 저장된 바이트 수를 반환합니다." #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" -#: collections.abc.Mapping.get:1 -#: collections.abc.MutableMapping.clear:1::1 of -msgid "Retrieve the corresponding layout by the string key." -msgstr "" - #: collections.abc.MutableMapping.clear:1::1 of msgid ":py:obj:`items `\\ \\(\\)" msgstr ":py:obj:`items `\\ \\(\\)" @@ -10635,22 +10889,6 @@ msgstr ":py:obj:`values `\\ \\(\\)" msgid "This function counts booleans as occupying 1 Byte." msgstr "이 함수는 booleans을 1바이트를 차지하는 것으로 계산합니다." -#: collections.abc.Mapping.get:3 of -msgid "" -"When there isn't an exact match, all the existing keys in the layout map " -"will be treated as a regex and map against the input key again. The first" -" match will be returned, based on the key insertion order. Return None if" -" there isn't any match found." -msgstr "" - -#: collections.abc.Mapping.get:8 of -msgid "the string key as the query for the layout." -msgstr "" - -#: collections.abc.Mapping.get:10 of -msgid "Corresponding layout based on the query." -msgstr "" - #: ../../source/ref-api/flwr.common.Context.rst:2 msgid "Context" msgstr "컨텍스트" @@ -11446,7 +11684,7 @@ msgstr "인코딩" msgid "The encoding in which to encode the string." msgstr "문자열을 인코딩합니다." -#: flwr.common.EventType.encode:5 of +#: flwr.common.EventType.encode:9 of msgid "errors" msgstr "오류" @@ -11640,7 +11878,7 @@ msgstr "" "문자열이 접미사 문자열로 끝나고 해당 접미사가 비어 있지 않으면 문자열[:-len(suffix)]을 반환합니다. 그렇지 않으면 원본" " 문자열의 복사본을 반환합니다." -#: flwr.common.EventType.replace:3 of +#: flwr.common.EventType.replace:5 of msgid "count" msgstr "카운트" @@ -11680,7 +11918,7 @@ msgid "" "strings and the original string." msgstr "구분 기호를 찾을 수 없는 경우 빈 문자열 2개와 원래 문자열을 포함하는 3-tuple을 반환합니다." -#: flwr.common.EventType.rsplit:3 flwr.common.EventType.split:3 of +#: flwr.common.EventType.rsplit:7 flwr.common.EventType.split:7 of msgid "sep" msgstr "sep" @@ -11697,7 +11935,7 @@ msgstr "" "None(기본값)으로 설정하면 모든 공백 문자(\\\\n \\\\r \\\\t \\\\f 및 공백 포함)를 분할하고 결과에서 빈 " "문자열을 삭제합니다." -#: flwr.common.EventType.rsplit:9 flwr.common.EventType.split:9 of +#: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of msgid "maxsplit" msgstr "maxsplit" @@ -11743,7 +11981,7 @@ msgid "" "remaining cased characters have lower case." msgstr "보다 구체적으로, 단어는 대문자로 시작하고 나머지 모든 대소문자는 소문자로 표기합니다." -#: flwr.common.EventType.translate:3 of +#: flwr.common.EventType.translate:5 of msgid "table" msgstr "table" @@ -12178,7 +12416,7 @@ msgstr ":py:obj:`count_bytes `\\ \\(\\)" #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" #: collections.abc.MutableMapping.clear:1::1 of @@ -12325,9 +12563,7 @@ msgstr ":py:obj:`count_bytes `\\ \\(\\ #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid "" -":py:obj:`get `\\ \\(key\\[\\, " -"default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" #: collections.abc.MutableMapping.clear:1::1 of @@ -12669,7 +12905,7 @@ msgstr "사용 가능한 클라이언트 그룹 제공." #: ../../source/ref-api/flwr.server.rst:56::1 #, fuzzy -msgid ":py:obj:`strategy `\\" +msgid ":py:obj:`flwr.server.strategy `\\" msgstr ":py:obj:`state `\\" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -12679,7 +12915,7 @@ msgstr "" #: ../../source/ref-api/flwr.server.rst:56::1 #, fuzzy -msgid ":py:obj:`workflow `\\" +msgid ":py:obj:`flwr.server.workflow `\\" msgstr ":py:obj:`flwr.server `\\" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -13161,8 +13397,7 @@ msgid "" msgstr "" #: flwr.server.app.start_server:9 -#: flwr.server.serverapp_components.ServerAppComponents:6 -#: flwr.simulation.app.start_simulation:29 of +#: flwr.server.serverapp_components.ServerAppComponents:6 of msgid "" "Currently supported values are `num_rounds` (int, default: 1) and " "`round_timeout` in seconds (float, default: None)." @@ -13284,14 +13519,6 @@ msgstr "" msgid "**success**" msgstr "" -#: ../../source/ref-api/flwr.server.run_server_app.rst:2 -msgid "run\\_server\\_app" -msgstr "" - -#: ../../source/ref-api/flwr.server.run_superlink.rst:2 -msgid "run\\_superlink" -msgstr "" - #: ../../source/ref-api/flwr.server.start_server.rst:2 msgid "start\\_server" msgstr "" @@ -16319,15 +16546,15 @@ msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 #, fuzzy msgid "" -":py:obj:`start_simulation `\\ \\(\\*\\," -" client\\_fn\\, num\\_clients\\)" +":py:obj:`start_simulation `\\ " +"\\(\\*args\\, \\*\\*kwargs\\)" msgstr "" ":py:obj:`start_client `\\ \\(\\*\\, " "server\\_address\\[\\, client\\_fn\\, ...\\]\\)" #: ../../source/ref-api/flwr.simulation.rst:18::1 -#: flwr.simulation.app.start_simulation:1 of -msgid "Start a Ray-based Flower simulation server." +#: flwr.simulation.start_simulation:1 of +msgid "Log error stating that module `ray` could not be imported." msgstr "" #: ../../source/ref-api/flwr.simulation.run_simulation.rst:2 @@ -16388,170 +16615,56 @@ msgstr "" msgid "start\\_simulation" msgstr "" -#: flwr.simulation.app.start_simulation:3 of -msgid "" -"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 " -"(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)." +#: ../../source/ref-changelog.md:1 +msgid "Changelog" msgstr "" -#: flwr.simulation.app.start_simulation:13 of -msgid "The total number of clients in this simulation." +#: ../../source/ref-changelog.md:3 +msgid "v1.11.1 (2024-09-11)" msgstr "" -#: flwr.simulation.app.start_simulation:15 of -msgid "" -"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." +#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:37 +#: ../../source/ref-changelog.md:141 ../../source/ref-changelog.md:239 +#: ../../source/ref-changelog.md:339 ../../source/ref-changelog.md:403 +#: ../../source/ref-changelog.md:496 ../../source/ref-changelog.md:596 +#: ../../source/ref-changelog.md:680 ../../source/ref-changelog.md:744 +#: ../../source/ref-changelog.md:802 ../../source/ref-changelog.md:871 +#: ../../source/ref-changelog.md:940 +msgid "Thanks to our contributors" msgstr "" -#: flwr.simulation.app.start_simulation:21 of +#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:39 +#: ../../source/ref-changelog.md:143 ../../source/ref-changelog.md:241 +#: ../../source/ref-changelog.md:341 ../../source/ref-changelog.md:405 +#: ../../source/ref-changelog.md:498 ../../source/ref-changelog.md:598 +#: ../../source/ref-changelog.md:682 ../../source/ref-changelog.md:746 +#: ../../source/ref-changelog.md:804 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." +"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 "" -#: flwr.simulation.app.start_simulation:26 of +#: ../../source/ref-changelog.md:9 msgid "" -"An implementation of the abstract base class `flwr.server.Server`. If no " -"instance is provided, then `start_server` will create one." +"`Charles Beauville`, `Chong Shen Ng`, `Daniel J. Beutel`, `Heng Pan`, " +"`Javier`, `Robert Steiner`, `Yan Gao` " msgstr "" -#: flwr.simulation.app.start_simulation:32 of +#: ../../source/ref-changelog.md:11 +#, fuzzy +msgid "Improvements" +msgstr "선택적 개선 사항" + +#: ../../source/ref-changelog.md:13 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`." +"**Implement** `keys/values/items` **methods for** `TypedDict` " +"([#4146](https://github.com/adap/flower/pull/4146))" msgstr "" -#: flwr.simulation.app.start_simulation:36 of +#: ../../source/ref-changelog.md:15 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`." -msgstr "" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"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." -msgstr "" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"ray_init_args is None (the default), Ray will be initialized with the " -"following default args:" -msgstr "" - -#: flwr.simulation.app.start_simulation:44 of -msgid "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" -msgstr "" - -#: flwr.simulation.app.start_simulation:46 of -msgid "" -"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:49 of -msgid "" -"Set to True to prevent `ray.shutdown()` in case " -"`ray.is_initialized()=True`." -msgstr "" - -#: flwr.simulation.app.start_simulation:51 of -msgid "" -"Optionally specify the type of actor to use. The actor object, which " -"persists throughout the simulation, will be the process in charge of " -"executing a ClientApp wrapping input argument `client_fn`." -msgstr "" - -#: flwr.simulation.app.start_simulation:55 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." -msgstr "" - -#: flwr.simulation.app.start_simulation:58 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" -msgstr "" - -#: flwr.simulation.app.start_simulation:67 of -msgid "**hist** -- Object containing metrics from training." -msgstr "" - -#: ../../source/ref-changelog.md:1 -msgid "Changelog" -msgstr "" - -#: ../../source/ref-changelog.md:3 -msgid "v1.11.1 (2024-09-11)" -msgstr "" - -#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:37 -#: ../../source/ref-changelog.md:141 ../../source/ref-changelog.md:239 -#: ../../source/ref-changelog.md:339 ../../source/ref-changelog.md:403 -#: ../../source/ref-changelog.md:496 ../../source/ref-changelog.md:596 -#: ../../source/ref-changelog.md:680 ../../source/ref-changelog.md:744 -#: ../../source/ref-changelog.md:802 ../../source/ref-changelog.md:871 -#: ../../source/ref-changelog.md:940 -msgid "Thanks to our contributors" -msgstr "" - -#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:39 -#: ../../source/ref-changelog.md:143 ../../source/ref-changelog.md:241 -#: ../../source/ref-changelog.md:341 ../../source/ref-changelog.md:405 -#: ../../source/ref-changelog.md:498 ../../source/ref-changelog.md:598 -#: ../../source/ref-changelog.md:682 ../../source/ref-changelog.md:746 -#: ../../source/ref-changelog.md:804 -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:9 -msgid "" -"`Charles Beauville`, `Chong Shen Ng`, `Daniel J. Beutel`, `Heng Pan`, " -"`Javier`, `Robert Steiner`, `Yan Gao` " -msgstr "" - -#: ../../source/ref-changelog.md:11 -#, fuzzy -msgid "Improvements" -msgstr "선택적 개선 사항" - -#: ../../source/ref-changelog.md:13 -msgid "" -"**Implement** `keys/values/items` **methods for** `TypedDict` " -"([#4146](https://github.com/adap/flower/pull/4146))" -msgstr "" - -#: ../../source/ref-changelog.md:15 -msgid "" -"**Fix parsing of** `--executor-config` **if present** " -"([#4125](https://github.com/adap/flower/pull/4125))" +"**Fix parsing of** `--executor-config` **if present** " +"([#4125](https://github.com/adap/flower/pull/4125))" msgstr "" #: ../../source/ref-changelog.md:17 @@ -16608,13 +16721,6 @@ msgstr "" msgid "Incompatible changes" msgstr "" -#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 -#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 -#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 -#: ../../source/ref-changelog.md:929 -msgid "None" -msgstr "" - #: ../../source/ref-changelog.md:35 msgid "v1.11.0 (2024-08-30)" msgstr "" @@ -21742,32 +21848,44 @@ msgid "" "blockchain environment is available here:" msgstr "" -#: ../../source/ref-faq.rst:28 +#: ../../source/ref-faq.rst:29 +msgid "`FLock: A Decentralised AI Training Platform `_." +msgstr "" + +#: ../../source/ref-faq.rst:29 +msgid "Contribute to on-chain training the model and earn rewards." +msgstr "" + +#: ../../source/ref-faq.rst:30 +msgid "Local blockchain with federated learning simulation." +msgstr "" + +#: ../../source/ref-faq.rst:31 msgid "" "`Flower meets Nevermined GitHub Repository `_." msgstr "" -#: ../../source/ref-faq.rst:29 +#: ../../source/ref-faq.rst:32 msgid "" "`Flower meets Nevermined YouTube video " "`_." msgstr "" -#: ../../source/ref-faq.rst:30 +#: ../../source/ref-faq.rst:33 msgid "" "`Flower meets KOSMoS `_." msgstr "" -#: ../../source/ref-faq.rst:31 +#: ../../source/ref-faq.rst:34 msgid "" "`Flower meets Talan blog post `_ ." msgstr "" -#: ../../source/ref-faq.rst:32 +#: ../../source/ref-faq.rst:35 msgid "" "`Flower meets Talan GitHub Repository " "`_ ." @@ -21990,178 +22108,298 @@ msgid "" "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." -msgstr "" - #: ../../source/tutorial-quickstart-fastai.rst:5 msgid "Quickstart fastai" msgstr "" -#: ../../source/tutorial-quickstart-fastai.rst:10 -msgid "Let's build a federated learning system using fastai and Flower!" +#: ../../source/tutorial-quickstart-fastai.rst:7 +msgid "" +"In this federated learning tutorial we will learn how to train a " +"SqueezeNet model on MNIST using Flower and fastai. It is recommended to " +"create a virtual environment and run everything within a :doc:`virtualenv" +" `." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +msgid "Then, clone the code example directly from GitHub:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:20 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"This will create a new directory called `quickstart-fastai` containing " +"the following files:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:33 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:33 +#, fuzzy +msgid "Next, activate your environment, then run:" +msgstr "그 후 가상 환경을 활성화합니다:" + +#: ../../source/tutorial-quickstart-fastai.rst:43 +msgid "" +"This example by default runs the Flower Simulation Engine, creating a " +"federation of 10 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" Let's run the project:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:56 +#: ../../source/tutorial-quickstart-huggingface.rst:65 +#: ../../source/tutorial-quickstart-mlx.rst:64 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:56 +#: ../../source/tutorial-quickstart-pytorch.rst:64 +#: ../../source/tutorial-quickstart-tensorflow.rst:65 +msgid "With default arguments you will see an output like this one:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:100 +#: ../../source/tutorial-quickstart-huggingface.rst:116 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:106 +#: ../../source/tutorial-quickstart-pytorch.rst:105 +#: ../../source/tutorial-quickstart-tensorflow.rst:106 +msgid "" +"You can also override the parameters defined in the " +"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:110 +msgid "" +"Check the `source code `_ of this tutorial in ``examples/quickstart-fasai`` " +"in the Flower GitHub repository." 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." +"with 🤗 HuggingFace Transformers in order to fine-tune an LLM." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:5 msgid "Quickstart 🤗 Transformers" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:10 +#: ../../source/tutorial-quickstart-huggingface.rst:7 msgid "" -"Let's build a federated learning system using Hugging Face Transformers " -"and Flower!" +"In this federated learning tutorial we will learn how to train a large " +"language model (LLM) on the `IMDB " +"`_ dataset using Flower" +" and the 🤗 Hugging Face Transformers library. It is recommended to create" +" a virtual environment and run everything within a :doc:`virtualenv " +"`." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:12 +#: ../../source/tutorial-quickstart-huggingface.rst:14 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." -msgstr "" - -#: ../../source/tutorial-quickstart-huggingface.rst:18 -msgid "Dependencies" +"Let's use ``flwr new`` to create a complete Flower+🤗 Hugging Face " +"project. It will generate all the files needed to run, by default with " +"the Flower Simulation Engine, a federation of 10 nodes using |fedavg|_ " +"The dataset will be partitioned using |flowerdatasets|_'s " +"|iidpartitioner|_." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:20 +#: ../../source/tutorial-quickstart-mlx.rst:19 +#: ../../source/tutorial-quickstart-pytorch.rst:19 +#: ../../source/tutorial-quickstart-tensorflow.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`:" +"Now that we have a rough idea of what this example is about, let's get " +"started. First, install Flower in your new environment:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:30 -msgid "Standard Hugging Face workflow" +#: ../../source/tutorial-quickstart-huggingface.rst:28 +msgid "" +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``HuggingFace``), give a name to your " +"project, and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:33 -msgid "Handling the data" +#: ../../source/tutorial-quickstart-huggingface.rst:36 +#: ../../source/tutorial-quickstart-mlx.rst:35 +#: ../../source/tutorial-quickstart-pytorch.rst:35 +#: ../../source/tutorial-quickstart-tensorflow.rst:36 +msgid "" +"After running it you'll notice a new directory with your project name has" +" been created. It should have the following structure:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:35 +#: ../../source/tutorial-quickstart-huggingface.rst:50 +#: ../../source/tutorial-quickstart-mlx.rst:49 +#: ../../source/tutorial-quickstart-pytorch.rst:49 +#: ../../source/tutorial-quickstart-tensorflow.rst:50 msgid "" -"To fetch the IMDB dataset, we will use Hugging Face's :code:`datasets` " -"library. We then need to tokenize the data and create :code:`PyTorch` " -"dataloaders, this is all done in the :code:`load_data` function:" +"If you haven't yet installed the project and its dependencies, you can do" +" so by:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:81 -msgid "Training and testing the model" +#: ../../source/tutorial-quickstart-huggingface.rst:58 +#: ../../source/tutorial-quickstart-pytorch.rst:57 +#: ../../source/tutorial-quickstart-tensorflow.rst:58 +msgid "To run the project, do:" 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:" +#: ../../source/tutorial-quickstart-huggingface.rst:106 +msgid "You can also run the project with GPU as follows:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:121 -msgid "Creating the model itself" +#: ../../source/tutorial-quickstart-huggingface.rst:113 +msgid "" +"This will use the default arguments where each ``ClientApp`` will use 2 " +"CPUs and at most 4 ``ClientApp``\\s will run in a given GPU." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:123 +#: ../../source/tutorial-quickstart-huggingface.rst:124 +#: ../../source/tutorial-quickstart-mlx.rst:114 +#: ../../source/tutorial-quickstart-pytorch.rst:113 msgid "" -"To create the model itself, we will just load the pre-trained distillBERT" -" model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" +"What follows is an explanation of each component in the project you just " +"created: dataset partition, the model, defining the ``ClientApp`` and " +"defining the ``ServerApp``." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:136 -msgid "Federating the example" -msgstr "" +#: ../../source/tutorial-quickstart-huggingface.rst:130 +#: ../../source/tutorial-quickstart-mlx.rst:120 +#: ../../source/tutorial-quickstart-pytorch.rst:119 +#: ../../source/tutorial-quickstart-tensorflow.rst:116 +#, fuzzy +msgid "The Data" +msgstr "Metadata" -#: ../../source/tutorial-quickstart-huggingface.rst:139 -msgid "Creating the IMDBClient" +#: ../../source/tutorial-quickstart-huggingface.rst:132 +msgid "" +"This tutorial uses |flowerdatasets|_ to easily download and partition the" +" `IMDB `_ dataset. In " +"this example you'll make use of the |iidpartitioner|_ to generate " +"``num_partitions`` partitions. You can choose |otherpartitioners|_ " +"available in Flower Datasets. To tokenize the text, we will also load the" +" tokenizer from the pre-trained Transformer model that we'll use during " +"training - more on that in the next section. Each ``ClientApp`` will call" +" this function to create dataloaders with the data that correspond to " +"their data partition." 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:" +#: ../../source/tutorial-quickstart-huggingface.rst:178 +#: ../../source/tutorial-quickstart-mlx.rst:164 +#: ../../source/tutorial-quickstart-pytorch.rst:157 +#: ../../source/tutorial-quickstart-tensorflow.rst:145 +msgid "The Model" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:169 +#: ../../source/tutorial-quickstart-huggingface.rst:180 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." +"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 (|berttiny|_) for sequence " +"classification over the dataset of IMDB ratings. The end goal is to " +"detect if a movie rating is positive or negative. If you have access to " +"larger GPUs, feel free to use larger models!" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:175 -msgid "Starting the server" +#: ../../source/tutorial-quickstart-huggingface.rst:193 +msgid "" +"Note that here, ``model_name`` is a string that will be loaded from the " +"``Context`` in the ClientApp and ServerApp." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:177 +#: ../../source/tutorial-quickstart-huggingface.rst:196 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:" +"In addition to loading the pretrained model weights and architecture, we " +"also include two utility functions to perform both training (i.e. " +"``train()``) and evaluation (i.e. ``test()``) using the above model. " +"These functions should look fairly familiar if you have some prior " +"experience with PyTorch. Note these functions do not have anything " +"specific to Flower. That being said, the training function will normally " +"be called, as we'll see later, from a Flower client passing its own data." +" In summary, your clients can use standard training/testing functions to " +"perform local training or evaluation:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:205 +#: ../../source/tutorial-quickstart-huggingface.rst:239 +#: ../../source/tutorial-quickstart-mlx.rst:210 +#: ../../source/tutorial-quickstart-pytorch.rst:234 +#: ../../source/tutorial-quickstart-tensorflow.rst:176 +#, fuzzy +msgid "The ClientApp" +msgstr "클라이언트앱" + +#: ../../source/tutorial-quickstart-huggingface.rst:241 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 main changes we have to make to use 🤗 Hugging Face with Flower will " +"be found in the ``get_weights()`` and ``set_weights()`` functions. Under " +"the hood, the ``transformers`` library uses PyTorch, which means we can " +"reuse the ``get_weights()`` and ``set_weights()`` code that we defined in" +" the :doc:`Quickstart PyTorch ` tutorial. As" +" a reminder, in ``get_weights()``, PyTorch model parameters are extracted" +" and represented as a list of NumPy arrays. The ``set_weights()`` " +"function that's the opposite: given a list of NumPy arrays it applies " +"them to an existing PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:209 -msgid "Putting everything together" +#: ../../source/tutorial-quickstart-huggingface.rst:254 +#: ../../source/tutorial-quickstart-pytorch.rst:245 +msgid "" +"The specific implementation of ``get_weights()`` and ``set_weights()`` " +"depends on the type of models you use. The ones shown below work for a " +"wide range of PyTorch models but you might need to adjust them if you " +"have more exotic model architectures." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:211 -msgid "We can now start client instances using:" +#: ../../source/tutorial-quickstart-huggingface.rst:269 +#: ../../source/tutorial-quickstart-pytorch.rst:261 +msgid "" +"The rest of the functionality is directly inspired by the centralized " +"case. The ``fit()`` method in the client trains the model using the local" +" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " +"model received on a held-out validation set that the client might have:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:221 +#: ../../source/tutorial-quickstart-huggingface.rst:296 msgid "" -"And they will be able to connect to the server and start the federated " -"training." +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparemeters defined in your " +"``pyproject.toml`` to configure the run. In this tutorial we access the " +"``local-epochs`` setting to control the number of epochs a ``ClientApp`` " +"will perform when running the ``fit()`` method. You could define " +"additional hyperparameters in ``pyproject.toml`` and access them here." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:223 +#: ../../source/tutorial-quickstart-huggingface.rst:330 +#: ../../source/tutorial-quickstart-mlx.rst:376 +#: ../../source/tutorial-quickstart-pytorch.rst:321 +#: ../../source/tutorial-quickstart-tensorflow.rst:245 +#, fuzzy +msgid "The ServerApp" +msgstr "Flower 서버앱" + +#: ../../source/tutorial-quickstart-huggingface.rst:332 msgid "" -"If you want to check out everything put together, you should check out " -"the `full code example `_ ." +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"|serverappcomponents|_ as opposed to a |client|_ In this example we use " +"the `FedAvg` strategy. To it we pass a randomly initialized model that " +"will server as the global model to federated. Note that the value of " +"``fraction_fit`` is read from the run config. You can find the default " +"value defined in the ``pyproject.toml``." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:226 +#: ../../source/tutorial-quickstart-huggingface.rst:371 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." +"Congratulations! You've successfully built and run your first federated " +"learning system for an LLM." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:229 +#: ../../source/tutorial-quickstart-huggingface.rst:376 msgid "" -"Note that in this example we used :code:`PyTorch`, but we could have very" -" well used :code:`TensorFlow`." +"Check the source code of the extended version of this tutorial in " +"|quickstart_hf_link|_ in the Flower GitHub repository. For a " +"comprehensive example of a federated fine-tuning of an LLM with Flower, " +"refer to the |flowertune|_ example in the Flower GitHub repository." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:-1 @@ -22216,7 +22454,6 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:34 #: ../../source/tutorial-quickstart-scikitlearn.rst:40 -#: ../../source/tutorial-quickstart-tensorflow.rst:29 #: ../../source/tutorial-quickstart-xgboost.rst:55 msgid "Flower Client" msgstr "" @@ -22290,13 +22527,11 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:129 #: ../../source/tutorial-quickstart-scikitlearn.rst:167 -#: ../../source/tutorial-quickstart-tensorflow.rst:98 #: ../../source/tutorial-quickstart-xgboost.rst:341 msgid "Flower Server" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:131 -#: ../../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 " @@ -22305,12 +22540,10 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:142 #: ../../source/tutorial-quickstart-scikitlearn.rst:230 -#: ../../source/tutorial-quickstart-tensorflow.rst:112 msgid "Train the model, federated!" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:144 -#: ../../source/tutorial-quickstart-tensorflow.rst:114 #: ../../source/tutorial-quickstart-xgboost.rst:567 msgid "" "With both client and server ready, we can now run everything and see " @@ -22515,7 +22748,7 @@ msgstr "" "code:`FlowerClient`는 모델 매개변수를 가져오거나 설정하는 메서드 2개, 모델 학습을 위한 메서드 1개, 모델 " "테스트를 위한 메서드 1개 등 총 4개의 메서드를 구현해야 합니다:" -#: ../../source/tutorial-quickstart-jax.rst:165 +#: ../../source/tutorial-quickstart-jax.rst:167 msgid ":code:`set_parameters (optional)`" msgstr ":code:`set_parameters (선택사항)`" @@ -22614,13 +22847,6 @@ msgid "" "api/flwr_datasets.partitioner.IidPartitioner.html#flwr_datasets.partitioner.IidPartitioner>`_." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:19 -#: ../../source/tutorial-quickstart-pytorch.rst:19 -msgid "" -"Now that we have a rough idea of what this example is about, let's get " -"started. First, install Flower in your new environment:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:27 msgid "" "Then, run the command below. You will be prompted to select of the " @@ -22628,49 +22854,16 @@ msgid "" "type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:35 -#: ../../source/tutorial-quickstart-pytorch.rst:35 -msgid "" -"After running it you'll notice a new directory with your project name has" -" been created. It should have the following structure:" -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:49 -#: ../../source/tutorial-quickstart-pytorch.rst:49 -msgid "" -"If you haven't yet installed the project and its dependencies, you can do" -" so by:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:57 msgid "To run the project do:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:64 -#: ../../source/tutorial-quickstart-pytorch.rst:64 -msgid "With default arguments you will see an output like this one:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:106 msgid "" "You can also override the parameters defined in " "``[tool.flwr.app.config]`` section in the ``pyproject.toml`` like this:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:114 -#: ../../source/tutorial-quickstart-pytorch.rst:113 -msgid "" -"What follows is an explanation of each component in the project you just " -"created: dataset partition, the model, defining the ``ClientApp`` and " -"defining the ``ServerApp``." -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:120 -#: ../../source/tutorial-quickstart-pytorch.rst:119 -#, fuzzy -msgid "The Data" -msgstr "Metadata" - #: ../../source/tutorial-quickstart-mlx.rst:122 msgid "" "We will use `Flower Datasets `_ to " @@ -22682,11 +22875,6 @@ msgid "" "api/flwr_datasets.partitioner.html>`_ available in Flower Datasets:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:164 -#: ../../source/tutorial-quickstart-pytorch.rst:157 -msgid "The Model" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:166 msgid "" "We define the model as in the `centralized MLX example " @@ -22700,12 +22888,6 @@ msgid "" "over batches." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:210 -#: ../../source/tutorial-quickstart-pytorch.rst:234 -#, fuzzy -msgid "The ClientApp" -msgstr "클라이언트앱" - #: ../../source/tutorial-quickstart-mlx.rst:212 msgid "" "The main changes we have to make to use `MLX` with `Flower` will be found" @@ -22773,12 +22955,6 @@ msgid "" "method." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:376 -#: ../../source/tutorial-quickstart-pytorch.rst:321 -#, fuzzy -msgid "The ServerApp" -msgstr "Flower 서버앱" - #: ../../source/tutorial-quickstart-mlx.rst:378 msgid "" "To construct a ``ServerApp``, we define a ``server_fn()`` callback with " @@ -22792,6 +22968,7 @@ msgstr "" #: ../../source/tutorial-quickstart-mlx.rst:402 #: ../../source/tutorial-quickstart-pytorch.rst:360 +#: ../../source/tutorial-quickstart-tensorflow.rst:279 msgid "" "Congratulations! You've successfully built and run your first federated " "learning system." @@ -22858,16 +23035,6 @@ msgid "" "and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:57 -msgid "To run the project, do:" -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:105 -msgid "" -"You can also override the parameters defined in the " -"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:121 msgid "" "This tutorial uses `Flower Datasets `_ " @@ -22911,22 +23078,6 @@ msgid "" "PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:245 -msgid "" -"The specific implementation of ``get_weights()`` and ``set_weights()`` " -"depends on the type of models you use. The ones shown below work for a " -"wide range of PyTorch models but you might need to adjust them if you " -"have more exotic model architectures." -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:261 -msgid "" -"The rest of the functionality is directly inspired by the centralized " -"case. The ``fit()`` method in the client trains the model using the local" -" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " -"model received on a held-out validation set that the client might have:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:294 msgid "" "Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " @@ -22960,6 +23111,7 @@ msgid "" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:372 +#: ../../source/tutorial-quickstart-tensorflow.rst:295 #, fuzzy msgid "Video tutorial" msgstr "튜토리얼" @@ -22971,27 +23123,46 @@ msgid "" "that shows the new APIs (as the content above does)" msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:-1 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 +msgid "Quickstart PyTorch Lightning" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:7 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with PyTorch Lightning to train an Auto Encoder model on MNIST." +"In this federated learning tutorial we will learn how to train an " +"AutoEncoder model on MNIST using Flower and PyTorch Lightning. It is " +"recommended to create a virtual environment and run everything within a " +":doc:`virtualenv `." msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 -msgid "Quickstart PyTorch Lightning" +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:20 +msgid "" +"This will create a new directory called `quickstart-pytorch-lightning` " +"containing the following files:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:10 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:43 msgid "" -"Let's build a horizontal federated learning system using PyTorch " -"Lightning and Flower!" +"By default, Flower Simulation Engine will be started and it will create a" +" federation of 4 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" To run the project, do:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:94 msgid "" -"Please refer to the `full code example " -"`_ to learn more." +"Each simulated `ClientApp` (two per round) will also log a summary of " +"their local training process. Expect this output to be similar to:" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:116 +msgid "" +"Check the `source code `_ of this tutorial in ``examples" +"/quickstart-pytorch-lightning`` in the Flower GitHub repository." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:-1 @@ -23072,7 +23243,7 @@ msgstr "" msgid "Sets the parameters of a :code:`sklearn` LogisticRegression model" msgstr "" -#: ../../source/tutorial-quickstart-scikitlearn.rst:49 +#: ../../source/tutorial-quickstart-scikitlearn.rst:50 msgid ":code:`set_initial_params()`" msgstr "" @@ -23128,7 +23299,7 @@ msgstr "" msgid "return the model weight as a list of NumPy ndarrays" msgstr "" -#: ../../source/tutorial-quickstart-scikitlearn.rst:120 +#: ../../source/tutorial-quickstart-scikitlearn.rst:121 msgid ":code:`set_parameters` (optional)" msgstr "" @@ -23223,7 +23394,6 @@ msgid "" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:239 -#: ../../source/tutorial-quickstart-tensorflow.rst:122 #: ../../source/tutorial-quickstart-xgboost.rst:575 msgid "" "Once the server is running we can start the clients in different " @@ -23231,7 +23401,6 @@ msgid "" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:246 -#: ../../source/tutorial-quickstart-tensorflow.rst:129 #: ../../source/tutorial-quickstart-xgboost.rst:582 msgid "Open another terminal and start the second client:" msgstr "" @@ -23256,99 +23425,107 @@ 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." +"with TensorFlow to train a CNN model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:5 msgid "Quickstart TensorFlow" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:13 -msgid "Let's build a federated learning system in less than 20 lines of code!" -msgstr "" - -#: ../../source/tutorial-quickstart-tensorflow.rst:15 -msgid "Before Flower can be imported we have to install it:" -msgstr "" - -#: ../../source/tutorial-quickstart-tensorflow.rst:21 +#: ../../source/tutorial-quickstart-tensorflow.rst:7 msgid "" -"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 -msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" +"In this tutorial we will learn how to train a Convolutional Neural " +"Network on CIFAR-10 using the Flower framework and TensorFlow. First of " +"all, it is recommended to create a virtual environment and run everything" +" within a :doc:`virtualenv `." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:38 +#: ../../source/tutorial-quickstart-tensorflow.rst:13 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." +"Let's use `flwr new` to create a complete Flower+TensorFlow project. It " +"will generate all the files needed to run, by default with the Flower " +"Simulation Engine, a federation of 10 nodes using `FedAvg " +"`_. The " +"dataset will be partitioned using Flower Dataset's `IidPartitioner " +"`_." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:47 +#: ../../source/tutorial-quickstart-tensorflow.rst:28 msgid "" -"Next, we need a model. For the purpose of this tutorial, we use " -"MobilNetV2 with 10 output classes:" +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``TensorFlow``), give a name to your project," +" and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:54 +#: ../../source/tutorial-quickstart-tensorflow.rst:118 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)." +"This tutorial uses `Flower Datasets `_ " +"to easily download and partition the `CIFAR-10` dataset. In this example " +"you'll make use of the `IidPartitioner `_" +" to generate `num_partitions` partitions. You can choose `other " +"partitioners `_ available in Flower Datasets. Each " +"``ClientApp`` will call this function to create the ``NumPy`` arrays that" +" correspond to their data partition." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:60 +#: ../../source/tutorial-quickstart-tensorflow.rst:147 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:" +"Next, we need a model. We defined a simple Convolutional Neural Network " +"(CNN), but feel free to replace it with a more sophisticated model if " +"you'd like:" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:82 +#: ../../source/tutorial-quickstart-tensorflow.rst:178 msgid "" -"We can now create an instance of our class :code:`CifarClient` and add " -"one line to actually run this client:" +"With `TensorFlow`, we can use the built-in ``get_weights()`` and " +"``set_weights()`` functions, which simplifies the implementation with " +"`Flower`. The rest of the functionality in the ClientApp is directly " +"inspired by the centralized case. The ``fit()`` method in the client " +"trains the model using the local dataset. Similarly, the ``evaluate()`` " +"method is used to evaluate the model received on a held-out validation " +"set that the client might have:" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:90 +#: ../../source/tutorial-quickstart-tensorflow.rst:212 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." +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparameters defined in your " +"``pyproject.toml`` to configure the run. For example, in this tutorial we" +" access the `local-epochs` setting to control the number of epochs a " +"``ClientApp`` will perform when running the ``fit()`` method, in addition" +" to `batch-size`. You could define additional hyperparameters in " +"``pyproject.toml`` and access them here." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:135 -msgid "Each client will have its own dataset." +#: ../../source/tutorial-quickstart-tensorflow.rst:247 +msgid "" +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"`ServerAppComponents `_ as " +"opposed to a `Client `_. In this example we use the " +"`FedAvg`. To it we pass a randomly initialized model that will serve as " +"the global model to federate." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:137 +#: ../../source/tutorial-quickstart-tensorflow.rst:284 msgid "" -"You should now see how the training does in the very first terminal (the " -"one that started the server):" +"Check the source code of the extended version of this tutorial in " +"|quickstart_tf_link|_ in the Flower GitHub repository." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:169 +#: ../../source/tutorial-quickstart-tensorflow.rst:299 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`." +"The video shown below shows how to setup a TensorFlow + Flower project " +"using our previously recommended APIs. A new video tutorial will be " +"released that shows the new APIs (as the content above does)" msgstr "" #: ../../source/tutorial-quickstart-xgboost.rst:-1 @@ -25217,7 +25394,7 @@ msgstr "" " 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -msgid "|e5918c1c06a4434bbe4bf49235e40059|" +msgid "|e87b69b2ada74ea49412df16f4a0b9cc|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 @@ -25234,7 +25411,7 @@ msgstr "" " 바둑과 같은 게임을 하는 것일 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -msgid "|c0165741bd1944f09ec55ce49032377d|" +msgid "|33cacb7d985c4906b348515c1a5cd993|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 @@ -25257,7 +25434,7 @@ msgstr "" "부르리는 것을 듣는 스마트 스피커에서 비롯됩니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +msgid "|cc080a555947492fa66131dc3a967603|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 @@ -25275,7 +25452,7 @@ msgstr "" "있습니다. 하지만 여러 조직이 모두 같은 작업을 위해 데이터를 생성하는 것일 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +msgid "|085c3e0fb8664c6aa06246636524b20b|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 @@ -25293,7 +25470,7 @@ msgstr "" "서버는 데이터 센터 어딘가에 있을 수도 있고 클라우드 어딘가에 있을 수도 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -msgid "|74f26ca701254d3db57d7899bd91eb55|" +msgid "|bfe69c74e48c45d49b50251c38c2a019|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 @@ -25310,7 +25487,7 @@ msgstr "" " 우리가 기본적으로 사용해 온 머신러닝 방법입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -msgid "|bda79f21f8154258a40e5766b2634ad7|" +msgid "|ebbecd651f0348d99c6511ea859bf4ca|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 @@ -25332,7 +25509,7 @@ msgstr "" "트래픽을 분석하는 것이 있습니다. 이러한 사례에서 모든 데이터는 자연스럽게 중앙 서버에 존재합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -msgid "|89d30862e62e4f9989e193483a08680a|" +msgid "|163117eb654a4273babba413cf8065f5|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 @@ -25349,7 +25526,7 @@ msgstr "" "좋은 모델을 훈련하기에 충분하지 않을 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -msgid "|77e9918671c54b4f86e01369c0785ce8|" +msgid "|452ac3ba453b4cd1be27be1ba7560d64|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 @@ -25516,7 +25693,7 @@ msgstr "" "체크포인트에서 모델 매개변수를 초기화합니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -msgid "|7e4ccef37cc94148a067107b34eb7447|" +msgid "|f403fcd69e4e44409627e748b404c086|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 @@ -25543,7 +25720,7 @@ msgstr "" "개의 연결 노드만 사용합니다. 그 이유는 점점 더 많은 클라이언트 노드를 선택하면 학습의 효율성이 감소하기 때문입니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -msgid "|28e47e4cded14479a0846c8e5f22c872|" +msgid "|4b00fe63870145968f8443619a792a42|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 @@ -25570,7 +25747,7 @@ msgstr "" "데이터에서 한 단계 정도로 짧거나 몇 단계(mini-batches)에 불과할 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +msgid "|368378731066486fa4397e89bc6b870c|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 @@ -25596,7 +25773,7 @@ msgstr "" "보냅니다. 보내는 모델 업데이트는 전체 모델 파라미터거나 로컬 교육 중에 누적된 그레디언트(gradient)일 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +msgid "|a66aa83d85bf4ffba7ed660b718066da|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 @@ -25645,7 +25822,7 @@ msgstr "" "많은 영향을 미칩니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -msgid "|81749d0ac0834c36a83bd38f433fea31|" +msgid "|82324b9af72a4582a81839d55caab767|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 @@ -25764,7 +25941,7 @@ msgstr "" "사용자는 모든 워크로드, 머신러닝 프레임워크 및 모든 프로그래밍 언어를 통합할 수 있습니다." #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -msgid "|ed9aae51da70428eab7eef32f21e819e|" +msgid "|fbf2da0da3cc4f8ab3b3eff852d80c41|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 @@ -27424,3 +27601,523 @@ msgstr "" #~ msgid "|c00bf2750bc24d229737a0fe1395f0fc|" #~ msgstr "|c00bf2750bc24d229737a0fe1395f0fc|" +#~ msgid "run\\_client\\_app" +#~ msgstr "run\\_client\\_app" + +#~ msgid "run\\_supernode" +#~ msgstr "run\\_supernode" + +#~ msgid "Retrieve the corresponding layout by the string key." +#~ msgstr "" + +#~ msgid "" +#~ "When there isn't an exact match, " +#~ "all the existing keys in the " +#~ "layout map will be treated as a" +#~ " regex and map against the input " +#~ "key again. The first match will be" +#~ " returned, based on the key insertion" +#~ " order. Return None if there isn't" +#~ " any match found." +#~ msgstr "" + +#~ msgid "the string key as the query for the layout." +#~ msgstr "" + +#~ msgid "Corresponding layout based on the query." +#~ msgstr "" + +#~ msgid "run\\_server\\_app" +#~ msgstr "" + +#~ msgid "run\\_superlink" +#~ msgstr "" + +#~ msgid "Start a Ray-based Flower simulation server." +#~ msgstr "" + +#~ msgid "" +#~ "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 (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)." +#~ msgstr "" + +#~ msgid "The total number of clients in this simulation." +#~ msgstr "" + +#~ msgid "" +#~ "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." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "" +#~ "An implementation of the abstract base" +#~ " class `flwr.server.Server`. If no instance" +#~ " is provided, then `start_server` will " +#~ "create one." +#~ msgstr "" + +#~ 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`." +#~ msgstr "" + +#~ 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`." +#~ msgstr "" + +#~ msgid "" +#~ "Optional dictionary containing arguments for" +#~ " the call to `ray.init`. If " +#~ "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." +#~ msgstr "" + +#~ msgid "" +#~ "Optional dictionary containing arguments for" +#~ " the call to `ray.init`. If " +#~ "ray_init_args is None (the default), Ray" +#~ " will be initialized with the " +#~ "following default args:" +#~ msgstr "" + +#~ msgid "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" +#~ msgstr "" + +#~ msgid "" +#~ "An empty dictionary can be used " +#~ "(ray_init_args={}) to prevent any arguments" +#~ " from being passed to ray.init." +#~ msgstr "" + +#~ msgid "" +#~ "Set to True to prevent `ray.shutdown()`" +#~ " in case `ray.is_initialized()=True`." +#~ msgstr "" + +#~ msgid "" +#~ "Optionally specify the type of actor " +#~ "to use. The actor object, which " +#~ "persists throughout the simulation, will " +#~ "be the process in charge of " +#~ "executing a ClientApp wrapping input " +#~ "argument `client_fn`." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ 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" +#~ msgstr "" + +#~ msgid "**hist** -- Object containing metrics from training." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with FastAI to train a vision " +#~ "model on CIFAR-10." +#~ msgstr "" + +#~ msgid "Let's build a federated learning system using fastai and Flower!" +#~ msgstr "" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn more." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federating Learning " +#~ "quickstart tutorial for using Flower " +#~ "with HuggingFace Transformers in order " +#~ "to fine-tune an LLM." +#~ msgstr "" + +#~ msgid "" +#~ "Let's build a federated learning system" +#~ " using Hugging Face Transformers and " +#~ "Flower!" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "Dependencies" +#~ msgstr "" + +#~ 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`:" +#~ msgstr "" + +#~ msgid "Standard Hugging Face workflow" +#~ msgstr "" + +#~ msgid "Handling the data" +#~ msgstr "" + +#~ msgid "" +#~ "To fetch the IMDB dataset, we will" +#~ " use Hugging Face's :code:`datasets` " +#~ "library. We then need to tokenize " +#~ "the data and create :code:`PyTorch` " +#~ "dataloaders, this is all done in " +#~ "the :code:`load_data` function:" +#~ msgstr "" + +#~ msgid "Training and testing the model" +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ msgid "Creating the model itself" +#~ msgstr "" + +#~ msgid "" +#~ "To create the model itself, we " +#~ "will just load the pre-trained " +#~ "distillBERT model using Hugging Face’s " +#~ ":code:`AutoModelForSequenceClassification` :" +#~ msgstr "" + +#~ msgid "Federating the example" +#~ msgstr "" + +#~ msgid "Creating the IMDBClient" +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "Starting the server" +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ 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)." +#~ msgstr "" + +#~ msgid "Putting everything together" +#~ msgstr "" + +#~ msgid "We can now start client instances using:" +#~ msgstr "" + +#~ msgid "" +#~ "And they will be able to connect" +#~ " to the server and start the " +#~ "federated training." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to check out " +#~ "everything put together, you should " +#~ "check out the `full code example " +#~ "`_ ." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "" +#~ "Note that in this example we used" +#~ " :code:`PyTorch`, but we could have " +#~ "very well used :code:`TensorFlow`." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with PyTorch Lightning to train an " +#~ "Auto Encoder model on MNIST." +#~ msgstr "" + +#~ msgid "" +#~ "Let's build a horizontal federated " +#~ "learning system using PyTorch Lightning " +#~ "and Flower!" +#~ msgstr "" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn " +#~ "more." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with TensorFlow to train a MobilNetV2" +#~ " model on CIFAR-10." +#~ msgstr "" + +#~ msgid "Let's build a federated learning system in less than 20 lines of code!" +#~ msgstr "" + +#~ msgid "Before Flower can be imported we have to install it:" +#~ msgstr "" + +#~ msgid "" +#~ "Since we want to use the Keras " +#~ "API of TensorFlow (TF), we have to" +#~ " install TF as well:" +#~ msgstr "" + +#~ msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "" +#~ "Next, we need a model. For the " +#~ "purpose of this tutorial, we use " +#~ "MobilNetV2 with 10 output classes:" +#~ msgstr "" + +#~ 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)." +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ msgid "" +#~ "We can now create an instance of" +#~ " our class :code:`CifarClient` and add " +#~ "one line to actually run this " +#~ "client:" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "Each client will have its own dataset." +#~ msgstr "" + +#~ msgid "" +#~ "You should now see how the " +#~ "training does in the very first " +#~ "terminal (the one that started the " +#~ "server):" +#~ msgstr "" + +#~ 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`." +#~ msgstr "" + +#~ msgid "|e5918c1c06a4434bbe4bf49235e40059|" +#~ msgstr "" + +#~ msgid "|c0165741bd1944f09ec55ce49032377d|" +#~ msgstr "" + +#~ msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +#~ msgstr "" + +#~ msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +#~ msgstr "" + +#~ msgid "|74f26ca701254d3db57d7899bd91eb55|" +#~ msgstr "" + +#~ msgid "|bda79f21f8154258a40e5766b2634ad7|" +#~ msgstr "" + +#~ msgid "|89d30862e62e4f9989e193483a08680a|" +#~ msgstr "" + +#~ msgid "|77e9918671c54b4f86e01369c0785ce8|" +#~ msgstr "" + +#~ msgid "|7e4ccef37cc94148a067107b34eb7447|" +#~ msgstr "" + +#~ msgid "|28e47e4cded14479a0846c8e5f22c872|" +#~ msgstr "" + +#~ msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +#~ msgstr "" + +#~ msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +#~ msgstr "" + +#~ msgid "|81749d0ac0834c36a83bd38f433fea31|" +#~ msgstr "" + +#~ msgid "|ed9aae51da70428eab7eef32f21e819e|" +#~ msgstr "" + diff --git a/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po b/doc/locales/pt_BR/LC_MESSAGES/framework-docs.po index d5f52b193e87..44223940cdce 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-09-15 09:09+0200\n" +"POT-Creation-Date: 2024-09-24 00:29+0000\n" "PO-Revision-Date: 2024-05-25 11:09+0000\n" "Last-Translator: Gustavo Bertoli \n" "Language: pt_BR\n" @@ -17,7 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.15.0\n" +"Generated-By: Babel 2.16.0\n" #: ../../source/contributor-explanation-public-and-private-apis.rst:2 msgid "Public and private APIs" @@ -1401,7 +1401,7 @@ msgstr "" msgid "Setting up the repository" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:12 +#: ../../source/contributor-tutorial-contribute-on-github.rst:21 msgid "**Create a GitHub account and setup Git**" msgstr "" @@ -1435,7 +1435,7 @@ msgid "" "history back to GitHub." msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:23 +#: ../../source/contributor-tutorial-contribute-on-github.rst:32 msgid "**Forking the Flower repository**" msgstr "" @@ -1455,7 +1455,7 @@ msgid "" " the top left corner that you are looking at your own version of Flower." msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:34 +#: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "**Cloning your forked repository**" msgstr "" @@ -1479,7 +1479,7 @@ msgid "" "it) folder in the current working directory." msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:49 +#: ../../source/contributor-tutorial-contribute-on-github.rst:66 msgid "**Add origin**" msgstr "" @@ -1501,7 +1501,7 @@ msgid "" "terminal:" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:68 +#: ../../source/contributor-tutorial-contribute-on-github.rst:90 msgid "**Add upstream**" msgstr "" @@ -1556,7 +1556,7 @@ msgstr "" msgid "And with Flower's repository:" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:114 +#: ../../source/contributor-tutorial-contribute-on-github.rst:122 msgid "**Create a new branch**" msgstr "" @@ -1573,7 +1573,7 @@ msgid "" "directory:" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:124 +#: ../../source/contributor-tutorial-contribute-on-github.rst:125 msgid "**Make changes**" msgstr "" @@ -1581,7 +1581,7 @@ msgstr "" msgid "Write great code and create wonderful changes using your favorite editor!" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:127 +#: ../../source/contributor-tutorial-contribute-on-github.rst:138 msgid "**Test and format your code**" msgstr "" @@ -1596,7 +1596,7 @@ msgstr "" msgid "To do so, we have written a few scripts that you can execute:" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:140 +#: ../../source/contributor-tutorial-contribute-on-github.rst:150 msgid "**Stage changes**" msgstr "" @@ -1617,7 +1617,7 @@ msgid "" "the :code:`git status` command." msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:152 +#: ../../source/contributor-tutorial-contribute-on-github.rst:160 msgid "**Commit changes**" msgstr "" @@ -1634,7 +1634,7 @@ msgid "" "example would be :code:`git commit -m \"Add images to README\"`." msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:162 +#: ../../source/contributor-tutorial-contribute-on-github.rst:171 msgid "**Push the changes to the fork**" msgstr "" @@ -1655,7 +1655,7 @@ msgstr "" msgid "Creating and merging a pull request (PR)" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:176 +#: ../../source/contributor-tutorial-contribute-on-github.rst:206 msgid "**Create the PR**" msgstr "" @@ -1718,7 +1718,7 @@ msgid "" "anyone, you have the option to create a draft pull request:" msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:208 +#: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "**Making new changes**" msgstr "" @@ -1729,7 +1729,7 @@ msgid "" " associated with the PR." msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:211 +#: ../../source/contributor-tutorial-contribute-on-github.rst:231 msgid "**Review the PR**" msgstr "" @@ -1765,7 +1765,7 @@ msgid "" "review." msgstr "" -#: ../../source/contributor-tutorial-contribute-on-github.rst:233 +#: ../../source/contributor-tutorial-contribute-on-github.rst:251 msgid "**Once the PR is merged**" msgstr "" @@ -2033,6 +2033,7 @@ msgstr "" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 #: ../../source/docker/run-as-subprocess.rst:11 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:15 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:12 #: ../../source/docker/tutorial-quickstart-docker.rst:11 msgid "Prerequisites" @@ -2737,6 +2738,221 @@ msgid "" " the SuperNode to execute the ClientApp as a subprocess:" msgstr "" +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:2 +msgid "Run Flower Quickstart Examples with Docker Compose" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:4 +msgid "" +"Flower provides a set of `quickstart examples " +"`_ to help you get " +"started with the framework. These examples are designed to demonstrate " +"the capabilities of Flower and by default run using the Simulation " +"Engine. This guide demonstrates how to run them using Flower's Deployment" +" Engine via Docker Compose." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:11 +msgid "" +"Some quickstart examples may have limitations or requirements that " +"prevent them from running on every environment. For more information, " +"please see `Limitations`_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 +#: ../../source/docker/tutorial-quickstart-docker.rst:13 +msgid "Before you start, make sure that:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:19 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 +#: ../../source/docker/tutorial-quickstart-docker.rst:15 +msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:20 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker.rst:16 +#, fuzzy +msgid "The Docker daemon is running." +msgstr "Verifique que o serviço Docker está rodando." + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:21 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 +msgid "Docker Compose is `installed `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:24 +msgid "Run the Quickstart Example" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:26 +msgid "" +"Clone the quickstart example you like to run. For example, ``quickstart-" +"pytorch``:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:34 +msgid "" +"Download the `compose.yml " +"`_" +" file into the example directory:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:41 +msgid "Build and start the services using the following command:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:47 +msgid "" +"Append the following lines to the end of the ``pyproject.toml`` file and " +"save it:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:49 +#: ../../source/docker/tutorial-quickstart-docker.rst:319 +msgid "pyproject.toml" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:58 +msgid "" +"You can customize the string that follows ``tool.flwr.federations.`` to " +"fit your needs. However, please note that the string cannot contain a dot" +" (``.``)." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:61 +msgid "" +"In this example, ``local-deployment`` has been used. Just remember to " +"replace ``local-deployment`` with your chosen name in both the " +"``tool.flwr.federations.`` string and the corresponding ``flwr run .`` " +"command." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:65 +#, fuzzy +msgid "Run the example:" +msgstr "Exemplo" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:71 +msgid "Follow the logs of the SuperExec service:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:77 +msgid "" +"That is all it takes! You can monitor the progress of the run through the" +" logs of the SuperExec." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:80 +msgid "Run a Different Quickstart Example" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:82 +msgid "" +"To run a different quickstart example, such as ``quickstart-tensorflow``," +" first, shut down the Docker Compose services of the current example:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:89 +msgid "After that, you can repeat the steps above." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:92 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:98 +msgid "Limitations" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:97 +msgid "Quickstart Example" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:99 +msgid "quickstart-fastai" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:100 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:102 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:110 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:112 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:116 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:118 +#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 +#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 +#: ../../source/ref-changelog.md:929 +msgid "None" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:101 +msgid "quickstart-huggingface" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:103 +msgid "quickstart-jax" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:104 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:106 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:120 +msgid "" +"The example has not yet been updated to work with the latest ``flwr`` " +"version." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:105 +msgid "quickstart-mlcube" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:107 +msgid "quickstart-mlx" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:108 +msgid "" +"`Requires to run on macOS with Apple Silicon `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:109 +msgid "quickstart-monai" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:111 +msgid "quickstart-pandas" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:113 +msgid "quickstart-pytorch-lightning" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:114 +msgid "" +"Requires an older pip version that is not supported by the Flower Docker " +"images." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:115 +msgid "quickstart-pytorch" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:117 +msgid "quickstart-sklearn-tabular" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:119 +msgid "quickstart-tabnet" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:121 +msgid "quickstart-tensorflow" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:122 +msgid "Only runs on AMD64." +msgstr "" + #: ../../source/docker/set-environment-variables.rst:2 msgid "Set Environment Variables" msgstr "" @@ -2765,22 +2981,6 @@ msgid "" " understanding the basic workflow that uses the minimum configurations." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 -#: ../../source/docker/tutorial-quickstart-docker.rst:13 -msgid "Before you start, make sure that:" -msgstr "" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 -#: ../../source/docker/tutorial-quickstart-docker.rst:15 -msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." -msgstr "" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 -#: ../../source/docker/tutorial-quickstart-docker.rst:16 -#, fuzzy -msgid "The Docker daemon is running." -msgstr "Verifique que o serviço Docker está rodando." - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:21 #: ../../source/docker/tutorial-quickstart-docker.rst:19 msgid "Step 1: Set Up" @@ -3197,10 +3397,6 @@ msgstr "" msgid "Add the following lines to the ``pyproject.toml``:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker.rst:319 -msgid "pyproject.toml" -msgstr "" - #: ../../source/docker/tutorial-quickstart-docker.rst:326 msgid "Run the ``quickstart-docker`` project by executing the command:" msgstr "" @@ -3248,6 +3444,7 @@ msgstr "" msgid "Remove the containers and the bridge network:" msgstr "" +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:401 #: ../../source/docker/tutorial-quickstart-docker.rst:399 msgid "Where to Go Next" msgstr "" @@ -3282,10 +3479,6 @@ msgid "" "configuration that best suits your project's needs." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 -msgid "Docker Compose is `installed `_." -msgstr "" - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:23 msgid "Clone the Docker Compose ``complete`` directory:" msgstr "" @@ -3478,7 +3671,7 @@ msgstr "" #: ../../source/docker/tutorial-quickstart-docker-compose.rst:188 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:241 -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:362 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:369 msgid "Rerun the ``quickstart-compose`` project:" msgstr "" @@ -3542,74 +3735,78 @@ msgstr "" msgid "compose.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:303 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 msgid "" "If you also want to enable TLS for the new SuperNodes, duplicate the " "SuperNode definition for each new SuperNode service in the ``with-" "tls.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:306 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:313 msgid "" "Make sure that the names of the services match with the one in the " "``compose.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:308 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:315 msgid "In ``with-tls.yml``, add the following:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:317 msgid "with-tls.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:332 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 msgid "Step 8: Persisting the SuperLink State and Enabling TLS" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:334 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:341 msgid "" "To run Flower with persisted SuperLink state and enabled TLS, a slight " "change in the ``with-state.yml`` file is required:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:337 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:344 msgid "Comment out the lines 2-4 and uncomment the lines 5-9:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:346 msgid "with-state.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:356 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:363 msgid "Restart the services:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:370 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:377 msgid "Step 9: Merge Multiple Compose Files" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:372 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:379 msgid "" "You can merge multiple Compose files into a single file. For instance, if" " you wish to combine the basic configuration with the TLS configuration, " "execute the following command:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:380 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:387 msgid "" "This will merge the contents of ``compose.yml`` and ``with-tls.yml`` into" " a new file called ``my_compose.yml``." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:384 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:391 msgid "Step 10: Clean Up" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:386 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:393 msgid "Remove all services and volumes:" msgstr "" +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:403 +msgid ":doc:`run-quickstart-examples-docker-compose`" +msgstr "" + #: ../../source/docker/use-a-different-version.rst:2 msgid "Use a Different Flower Version" msgstr "" @@ -3873,7 +4070,7 @@ msgid "" "one method for testing the model:" msgstr "" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:218 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:219 msgid ":code:`set_parameters`" msgstr "" @@ -3904,9 +4101,9 @@ msgid "" ":code:`ndarray`'s (which is what :code:`flwr.client.NumPyClient` expects)" msgstr "" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:223 -#: ../../source/tutorial-quickstart-jax.rst:171 -#: ../../source/tutorial-quickstart-scikitlearn.rst:123 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:225 +#: ../../source/tutorial-quickstart-jax.rst:173 +#: ../../source/tutorial-quickstart-scikitlearn.rst:125 msgid ":code:`fit`" msgstr "" @@ -3928,9 +4125,9 @@ msgstr "" msgid "get the updated local model weights and return them to the server" msgstr "" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:227 -#: ../../source/tutorial-quickstart-jax.rst:175 -#: ../../source/tutorial-quickstart-scikitlearn.rst:127 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:230 +#: ../../source/tutorial-quickstart-jax.rst:178 +#: ../../source/tutorial-quickstart-scikitlearn.rst:128 msgid ":code:`evaluate`" msgstr "" @@ -4019,7 +4216,7 @@ msgid "" " individual's information remains hidden in the crowd." msgstr "" -#: ../../source/explanation-differential-privacy.rst:16 +#: ../../source/explanation-differential-privacy.rst:-1 msgid "DP Intro" msgstr "" @@ -4129,8 +4326,8 @@ msgid "" "the client's data." msgstr "" +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:68 -#: ../../source/explanation-differential-privacy.rst:71 #: ../../source/how-to-use-differential-privacy.rst:11 msgid "Central Differential Privacy" msgstr "" @@ -4157,7 +4354,7 @@ msgid "" "that larger updates are scaled down to fit within the norm `S`." msgstr "" -#: ../../source/explanation-differential-privacy.rst:84 +#: ../../source/explanation-differential-privacy.rst:-1 msgid "clipping" msgstr "" @@ -4202,8 +4399,8 @@ msgid "" "others." msgstr "" +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:105 -#: ../../source/explanation-differential-privacy.rst:110 #: ../../source/how-to-use-differential-privacy.rst:96 msgid "Local Differential Privacy" msgstr "" @@ -4434,7 +4631,7 @@ msgstr "" msgid "This is sometimes called a hub-and-spoke topology:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:18 +#: ../../source/explanation-flower-architecture.rst:24 msgid "Hub-and-spoke topology in federated learning" msgstr "" @@ -4506,7 +4703,7 @@ msgid "" "`missing link` between all those SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:65 +#: ../../source/explanation-flower-architecture.rst:71 #, fuzzy msgid "Basic Flower architecture" msgstr "Arquitetura do Flower" @@ -4543,7 +4740,7 @@ msgid "" "SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:91 +#: ../../source/explanation-flower-architecture.rst:97 msgid "Multi-tenancy federated learning architecture" msgstr "" @@ -4565,7 +4762,7 @@ msgid "" "their corresponding ``ClientApp``\\s:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:107 +#: ../../source/explanation-flower-architecture.rst:113 msgid "Multi-tenancy federated learning architecture - Run 1" msgstr "" @@ -4581,7 +4778,7 @@ msgid "" " to participate in the training:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:119 +#: ../../source/explanation-flower-architecture.rst:125 msgid "Multi-tenancy federated learning architecture - Run 2" msgstr "" @@ -4617,7 +4814,7 @@ msgid "" "developer machine." msgstr "" -#: ../../source/explanation-flower-architecture.rst:145 +#: ../../source/explanation-flower-architecture.rst:151 msgid "Flower Deployment Engine with SuperExec" msgstr "" @@ -7286,7 +7483,7 @@ msgid "" "adaptive clipping." msgstr "" -#: ../../source/how-to-use-differential-privacy.rst:25 +#: ../../source/how-to-use-differential-privacy.rst:-1 msgid "server side clipping" msgstr "" @@ -7315,7 +7512,7 @@ msgid "" ":code:`DifferentialPrivacyClientSideAdaptiveClipping`." msgstr "" -#: ../../source/how-to-use-differential-privacy.rst:57 +#: ../../source/how-to-use-differential-privacy.rst:-1 msgid "client side clipping" msgstr "" @@ -7342,7 +7539,7 @@ msgid "" "clipping norm value, sensitivity, epsilon, and delta." msgstr "" -#: ../../source/how-to-use-differential-privacy.rst:99 +#: ../../source/how-to-use-differential-privacy.rst:-1 msgid "local DP mod" msgstr "" @@ -7725,11 +7922,32 @@ msgstr "" msgid "Arguments" msgstr "Argumento de compilação" -#: ../../flwr install:1 new:1 run:1 +#: ../../flwr install:1 log:1 new:1 run:1 #, fuzzy msgid "Optional argument" msgstr "Argumento de compilação" +#: ../../flwr log:1 +msgid "Get logs from a Flower project run." +msgstr "" + +#: ../../flwr log:1 +msgid "Flag to stream or print logs from the Flower run" +msgstr "" + +#: ../../flwr log +msgid "default" +msgstr "" + +#: ../../flwr log:1 +msgid "``True``" +msgstr "" + +#: ../../flwr log:1 +#, fuzzy +msgid "Required argument" +msgstr "Argumento de compilação" + #: ../../flwr new:1 msgid "Create new Flower App." msgstr "" @@ -7812,7 +8030,7 @@ msgid "Modules" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 -msgid ":py:obj:`client `\\" +msgid ":py:obj:`flwr.client `\\" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 flwr.client:1 of @@ -7820,7 +8038,7 @@ msgid "Flower client." msgstr "" #: ../../source/ref-api/flwr.rst:35::1 -msgid ":py:obj:`common `\\" +msgid ":py:obj:`flwr.common `\\" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 flwr.common:1 of @@ -7828,7 +8046,7 @@ msgid "Common components shared between server and client." msgstr "" #: ../../source/ref-api/flwr.rst:35::1 -msgid ":py:obj:`server `\\" +msgid ":py:obj:`flwr.server `\\" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 @@ -7838,7 +8056,7 @@ msgid "Flower server." msgstr "" #: ../../source/ref-api/flwr.rst:35::1 -msgid ":py:obj:`simulation `\\" +msgid ":py:obj:`flwr.simulation `\\" msgstr "" #: ../../source/ref-api/flwr.rst:35::1 flwr.simulation:1 of @@ -7918,7 +8136,7 @@ msgid "Abstract base class for Flower clients using NumPy." msgstr "" #: ../../source/ref-api/flwr.client.rst:50::1 -msgid ":py:obj:`mod `\\" +msgid ":py:obj:`flwr.client.mod `\\" msgstr "" #: ../../source/ref-api/flwr.client.rst:50::1 flwr.client.mod:1 of @@ -8115,48 +8333,57 @@ msgstr "" msgid "Getter for `Context` client attribute." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst -#: ../../source/ref-api/flwr.common.Array.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Context.rst -#: ../../source/ref-api/flwr.common.Error.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.Metadata.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst #: ../../source/ref-api/flwr.common.Parameters.rst:2 -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.common.RecordSet.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.ServerAppComponents.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.Bulyan.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdam.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgAndroid.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgM.rst -#: ../../source/ref-api/flwr.server.strategy.FedOpt.rst -#: ../../source/ref-api/flwr.server.strategy.FedProx.rst -#: ../../source/ref-api/flwr.server.strategy.FedTrimmedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedYogi.rst -#: ../../source/ref-api/flwr.server.strategy.Krum.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggPlusWorkflow.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggWorkflow.rst -#: ../../source/ref-api/flwr.simulation.run_simulation.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst #: flwr.client.app.start_client flwr.client.app.start_numpy_client -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: 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 +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.context.Context flwr.common.message.Error +#: flwr.common.message.Message flwr.common.message.Message.create_error_reply +#: flwr.common.message.Message.create_reply flwr.common.message.Metadata +#: flwr.common.record.configsrecord.ConfigsRecord +#: flwr.common.record.metricsrecord.MetricsRecord +#: flwr.common.record.parametersrecord.Array +#: flwr.common.record.parametersrecord.ParametersRecord +#: flwr.common.record.recordset.RecordSet flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.ClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.serverapp_components.ServerAppComponents +#: flwr.server.strategy.bulyan.Bulyan +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.fedadagrad.FedAdagrad +#: flwr.server.strategy.fedadam.FedAdam flwr.server.strategy.fedavg.FedAvg +#: flwr.server.strategy.fedavg_android.FedAvgAndroid +#: flwr.server.strategy.fedavgm.FedAvgM flwr.server.strategy.fedopt.FedOpt +#: flwr.server.strategy.fedprox.FedProx +#: flwr.server.strategy.fedtrimmedavg.FedTrimmedAvg +#: flwr.server.strategy.fedyogi.FedYogi flwr.server.strategy.krum.Krum +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow +#: flwr.simulation.run_simulation.run_simulation of msgid "Parameters" msgstr "" @@ -8167,21 +8394,31 @@ msgid "" "customize the local evaluation process." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.evaluate +#: flwr.client.numpy_client.NumPyClient.fit +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of msgid "Returns" msgstr "" @@ -8191,18 +8428,29 @@ msgid "" "details such as the number of local data examples used for evaluation." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of msgid "Return type" msgstr "" @@ -8521,6 +8769,11 @@ msgstr "" 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:`message_size_mod `\\ \\(msg\\," @@ -8685,10 +8938,6 @@ msgstr "" msgid "make\\_ffn" msgstr "" -#: flwr.client.mod.utils.make_ffn:1 of -msgid "." -msgstr "" - #: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 msgid "message\\_size\\_mod" msgstr "" @@ -8715,14 +8964,6 @@ msgstr "" msgid "secaggplus\\_mod" msgstr "" -#: ../../source/ref-api/flwr.client.run_client_app.rst:2 -msgid "run\\_client\\_app" -msgstr "" - -#: ../../source/ref-api/flwr.client.run_supernode.rst:2 -msgid "run\\_supernode" -msgstr "" - #: ../../source/ref-api/flwr.client.start_client.rst:2 msgid "start\\_client" msgstr "" @@ -9428,12 +9669,7 @@ msgid "Return number of Bytes stored in this object." msgstr "" #: collections.abc.MutableMapping.clear:1::1 of -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" -msgstr "" - -#: collections.abc.Mapping.get:1 -#: collections.abc.MutableMapping.clear:1::1 of -msgid "Retrieve the corresponding layout by the string key." +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr "" #: collections.abc.MutableMapping.clear:1::1 of @@ -9490,22 +9726,6 @@ msgstr "" msgid "This function counts booleans as occupying 1 Byte." msgstr "" -#: collections.abc.Mapping.get:3 of -msgid "" -"When there isn't an exact match, all the existing keys in the layout map " -"will be treated as a regex and map against the input key again. The first" -" match will be returned, based on the key insertion order. Return None if" -" there isn't any match found." -msgstr "" - -#: collections.abc.Mapping.get:8 of -msgid "the string key as the query for the layout." -msgstr "" - -#: collections.abc.Mapping.get:10 of -msgid "Corresponding layout based on the query." -msgstr "" - #: ../../source/ref-api/flwr.common.Context.rst:2 msgid "Context" msgstr "" @@ -10218,7 +10438,7 @@ msgstr "" msgid "The encoding in which to encode the string." msgstr "" -#: flwr.common.EventType.encode:5 of +#: flwr.common.EventType.encode:9 of msgid "errors" msgstr "" @@ -10394,7 +10614,7 @@ msgid "" "string." msgstr "" -#: flwr.common.EventType.replace:3 of +#: flwr.common.EventType.replace:5 of msgid "count" msgstr "" @@ -10430,7 +10650,7 @@ msgid "" "strings and the original string." msgstr "" -#: flwr.common.EventType.rsplit:3 flwr.common.EventType.split:3 of +#: flwr.common.EventType.rsplit:7 flwr.common.EventType.split:7 of msgid "sep" msgstr "" @@ -10445,7 +10665,7 @@ msgid "" " empty strings from the result." msgstr "" -#: flwr.common.EventType.rsplit:9 flwr.common.EventType.split:9 of +#: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of msgid "maxsplit" msgstr "" @@ -10486,7 +10706,7 @@ msgid "" "remaining cased characters have lower case." msgstr "" -#: flwr.common.EventType.translate:3 of +#: flwr.common.EventType.translate:5 of msgid "table" msgstr "" @@ -10901,7 +11121,7 @@ msgid ":py:obj:`count_bytes `\\ \\(\\)" msgstr "" #: collections.abc.MutableMapping.clear:1::1 of -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr "" #: collections.abc.MutableMapping.clear:1::1 of @@ -11033,9 +11253,7 @@ msgid ":py:obj:`count_bytes `\\ \\(\\) msgstr "" #: collections.abc.MutableMapping.clear:1::1 of -msgid "" -":py:obj:`get `\\ \\(key\\[\\, " -"default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr "" #: collections.abc.MutableMapping.clear:1::1 of @@ -11362,7 +11580,7 @@ msgid "Provides a pool of available clients." msgstr "" #: ../../source/ref-api/flwr.server.rst:56::1 -msgid ":py:obj:`strategy `\\" +msgid ":py:obj:`flwr.server.strategy `\\" msgstr "" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -11371,7 +11589,7 @@ msgid "Contains the strategy abstraction and different implementations." msgstr "" #: ../../source/ref-api/flwr.server.rst:56::1 -msgid ":py:obj:`workflow `\\" +msgid ":py:obj:`flwr.server.workflow `\\" msgstr "" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -11848,8 +12066,7 @@ msgid "" msgstr "" #: flwr.server.app.start_server:9 -#: flwr.server.serverapp_components.ServerAppComponents:6 -#: flwr.simulation.app.start_simulation:29 of +#: flwr.server.serverapp_components.ServerAppComponents:6 of msgid "" "Currently supported values are `num_rounds` (int, default: 1) and " "`round_timeout` in seconds (float, default: None)." @@ -11965,14 +12182,6 @@ msgstr "" msgid "**success**" msgstr "" -#: ../../source/ref-api/flwr.server.run_server_app.rst:2 -msgid "run\\_server\\_app" -msgstr "" - -#: ../../source/ref-api/flwr.server.run_superlink.rst:2 -msgid "run\\_superlink" -msgstr "" - #: ../../source/ref-api/flwr.server.start_server.rst:2 msgid "start\\_server" msgstr "" @@ -14999,13 +15208,13 @@ msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 msgid "" -":py:obj:`start_simulation `\\ \\(\\*\\," -" client\\_fn\\, num\\_clients\\)" +":py:obj:`start_simulation `\\ " +"\\(\\*args\\, \\*\\*kwargs\\)" msgstr "" #: ../../source/ref-api/flwr.simulation.rst:18::1 -#: flwr.simulation.app.start_simulation:1 of -msgid "Start a Ray-based Flower simulation server." +#: flwr.simulation.start_simulation:1 of +msgid "Log error stating that module `ray` could not be imported." msgstr "" #: ../../source/ref-api/flwr.simulation.run_simulation.rst:2 @@ -15066,120 +15275,6 @@ msgstr "" msgid "start\\_simulation" msgstr "" -#: flwr.simulation.app.start_simulation:3 of -msgid "" -"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 " -"(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)." -msgstr "" - -#: flwr.simulation.app.start_simulation:13 of -msgid "The total number of clients in this simulation." -msgstr "" - -#: flwr.simulation.app.start_simulation:15 of -msgid "" -"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." -msgstr "" - -#: flwr.simulation.app.start_simulation:21 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." -msgstr "" - -#: flwr.simulation.app.start_simulation:26 of -msgid "" -"An implementation of the abstract base class `flwr.server.Server`. If no " -"instance is provided, then `start_server` will create one." -msgstr "" - -#: flwr.simulation.app.start_simulation:32 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`." -msgstr "" - -#: flwr.simulation.app.start_simulation:36 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`." -msgstr "" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"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." -msgstr "" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"ray_init_args is None (the default), Ray will be initialized with the " -"following default args:" -msgstr "" - -#: flwr.simulation.app.start_simulation:44 of -msgid "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" -msgstr "" - -#: flwr.simulation.app.start_simulation:46 of -msgid "" -"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:49 of -msgid "" -"Set to True to prevent `ray.shutdown()` in case " -"`ray.is_initialized()=True`." -msgstr "" - -#: flwr.simulation.app.start_simulation:51 of -msgid "" -"Optionally specify the type of actor to use. The actor object, which " -"persists throughout the simulation, will be the process in charge of " -"executing a ClientApp wrapping input argument `client_fn`." -msgstr "" - -#: flwr.simulation.app.start_simulation:55 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." -msgstr "" - -#: flwr.simulation.app.start_simulation:58 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" -msgstr "" - -#: flwr.simulation.app.start_simulation:67 of -msgid "**hist** -- Object containing metrics from training." -msgstr "" - #: ../../source/ref-changelog.md:1 msgid "Changelog" msgstr "" @@ -15285,13 +15380,6 @@ msgstr "" msgid "Incompatible changes" msgstr "" -#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 -#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 -#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 -#: ../../source/ref-changelog.md:929 -msgid "None" -msgstr "" - #: ../../source/ref-changelog.md:35 msgid "v1.11.0 (2024-08-30)" msgstr "" @@ -20418,32 +20506,44 @@ msgid "" "blockchain environment is available here:" msgstr "" -#: ../../source/ref-faq.rst:28 -msgid "" -"`Flower meets Nevermined GitHub Repository `_." +#: ../../source/ref-faq.rst:29 +msgid "`FLock: A Decentralised AI Training Platform `_." msgstr "" #: ../../source/ref-faq.rst:29 -msgid "" +msgid "Contribute to on-chain training the model and earn rewards." +msgstr "" + +#: ../../source/ref-faq.rst:30 +msgid "Local blockchain with federated learning simulation." +msgstr "" + +#: ../../source/ref-faq.rst:31 +msgid "" +"`Flower meets Nevermined GitHub Repository `_." +msgstr "" + +#: ../../source/ref-faq.rst:32 +msgid "" "`Flower meets Nevermined YouTube video " "`_." msgstr "" -#: ../../source/ref-faq.rst:30 +#: ../../source/ref-faq.rst:33 msgid "" "`Flower meets KOSMoS `_." msgstr "" -#: ../../source/ref-faq.rst:31 +#: ../../source/ref-faq.rst:34 msgid "" "`Flower meets Talan blog post `_ ." msgstr "" -#: ../../source/ref-faq.rst:32 +#: ../../source/ref-faq.rst:35 msgid "" "`Flower meets Talan GitHub Repository " "`_ ." @@ -20666,178 +20766,294 @@ msgid "" "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." -msgstr "" - #: ../../source/tutorial-quickstart-fastai.rst:5 msgid "Quickstart fastai" msgstr "" -#: ../../source/tutorial-quickstart-fastai.rst:10 -msgid "Let's build a federated learning system using fastai and Flower!" +#: ../../source/tutorial-quickstart-fastai.rst:7 +msgid "" +"In this federated learning tutorial we will learn how to train a " +"SqueezeNet model on MNIST using Flower and fastai. It is recommended to " +"create a virtual environment and run everything within a :doc:`virtualenv" +" `." msgstr "" #: ../../source/tutorial-quickstart-fastai.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +msgid "Then, clone the code example directly from GitHub:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:20 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"This will create a new directory called `quickstart-fastai` containing " +"the following files:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:33 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:33 +msgid "Next, activate your environment, then run:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:43 +msgid "" +"This example by default runs the Flower Simulation Engine, creating a " +"federation of 10 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" Let's run the project:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:56 +#: ../../source/tutorial-quickstart-huggingface.rst:65 +#: ../../source/tutorial-quickstart-mlx.rst:64 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:56 +#: ../../source/tutorial-quickstart-pytorch.rst:64 +#: ../../source/tutorial-quickstart-tensorflow.rst:65 +msgid "With default arguments you will see an output like this one:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:100 +#: ../../source/tutorial-quickstart-huggingface.rst:116 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:106 +#: ../../source/tutorial-quickstart-pytorch.rst:105 +#: ../../source/tutorial-quickstart-tensorflow.rst:106 +msgid "" +"You can also override the parameters defined in the " +"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:110 +msgid "" +"Check the `source code `_ of this tutorial in ``examples/quickstart-fasai`` " +"in the Flower GitHub repository." 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." +"with 🤗 HuggingFace Transformers in order to fine-tune an LLM." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:5 msgid "Quickstart 🤗 Transformers" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:10 +#: ../../source/tutorial-quickstart-huggingface.rst:7 msgid "" -"Let's build a federated learning system using Hugging Face Transformers " -"and Flower!" +"In this federated learning tutorial we will learn how to train a large " +"language model (LLM) on the `IMDB " +"`_ dataset using Flower" +" and the 🤗 Hugging Face Transformers library. It is recommended to create" +" a virtual environment and run everything within a :doc:`virtualenv " +"`." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:12 +#: ../../source/tutorial-quickstart-huggingface.rst:14 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." -msgstr "" - -#: ../../source/tutorial-quickstart-huggingface.rst:18 -msgid "Dependencies" +"Let's use ``flwr new`` to create a complete Flower+🤗 Hugging Face " +"project. It will generate all the files needed to run, by default with " +"the Flower Simulation Engine, a federation of 10 nodes using |fedavg|_ " +"The dataset will be partitioned using |flowerdatasets|_'s " +"|iidpartitioner|_." msgstr "" #: ../../source/tutorial-quickstart-huggingface.rst:20 +#: ../../source/tutorial-quickstart-mlx.rst:19 +#: ../../source/tutorial-quickstart-pytorch.rst:19 +#: ../../source/tutorial-quickstart-tensorflow.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`:" +"Now that we have a rough idea of what this example is about, let's get " +"started. First, install Flower in your new environment:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:30 -msgid "Standard Hugging Face workflow" +#: ../../source/tutorial-quickstart-huggingface.rst:28 +msgid "" +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``HuggingFace``), give a name to your " +"project, and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:33 -msgid "Handling the data" +#: ../../source/tutorial-quickstart-huggingface.rst:36 +#: ../../source/tutorial-quickstart-mlx.rst:35 +#: ../../source/tutorial-quickstart-pytorch.rst:35 +#: ../../source/tutorial-quickstart-tensorflow.rst:36 +msgid "" +"After running it you'll notice a new directory with your project name has" +" been created. It should have the following structure:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:35 +#: ../../source/tutorial-quickstart-huggingface.rst:50 +#: ../../source/tutorial-quickstart-mlx.rst:49 +#: ../../source/tutorial-quickstart-pytorch.rst:49 +#: ../../source/tutorial-quickstart-tensorflow.rst:50 msgid "" -"To fetch the IMDB dataset, we will use Hugging Face's :code:`datasets` " -"library. We then need to tokenize the data and create :code:`PyTorch` " -"dataloaders, this is all done in the :code:`load_data` function:" +"If you haven't yet installed the project and its dependencies, you can do" +" so by:" +msgstr "" + +#: ../../source/tutorial-quickstart-huggingface.rst:58 +#: ../../source/tutorial-quickstart-pytorch.rst:57 +#: ../../source/tutorial-quickstart-tensorflow.rst:58 +msgid "To run the project, do:" +msgstr "" + +#: ../../source/tutorial-quickstart-huggingface.rst:106 +msgid "You can also run the project with GPU as follows:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:81 -msgid "Training and testing the model" +#: ../../source/tutorial-quickstart-huggingface.rst:113 +msgid "" +"This will use the default arguments where each ``ClientApp`` will use 2 " +"CPUs and at most 4 ``ClientApp``\\s will run in a given GPU." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:83 +#: ../../source/tutorial-quickstart-huggingface.rst:124 +#: ../../source/tutorial-quickstart-mlx.rst:114 +#: ../../source/tutorial-quickstart-pytorch.rst:113 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:" +"What follows is an explanation of each component in the project you just " +"created: dataset partition, the model, defining the ``ClientApp`` and " +"defining the ``ServerApp``." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:121 -msgid "Creating the model itself" +#: ../../source/tutorial-quickstart-huggingface.rst:130 +#: ../../source/tutorial-quickstart-mlx.rst:120 +#: ../../source/tutorial-quickstart-pytorch.rst:119 +#: ../../source/tutorial-quickstart-tensorflow.rst:116 +msgid "The Data" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:123 +#: ../../source/tutorial-quickstart-huggingface.rst:132 msgid "" -"To create the model itself, we will just load the pre-trained distillBERT" -" model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" +"This tutorial uses |flowerdatasets|_ to easily download and partition the" +" `IMDB `_ dataset. In " +"this example you'll make use of the |iidpartitioner|_ to generate " +"``num_partitions`` partitions. You can choose |otherpartitioners|_ " +"available in Flower Datasets. To tokenize the text, we will also load the" +" tokenizer from the pre-trained Transformer model that we'll use during " +"training - more on that in the next section. Each ``ClientApp`` will call" +" this function to create dataloaders with the data that correspond to " +"their data partition." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:136 -msgid "Federating the example" +#: ../../source/tutorial-quickstart-huggingface.rst:178 +#: ../../source/tutorial-quickstart-mlx.rst:164 +#: ../../source/tutorial-quickstart-pytorch.rst:157 +#: ../../source/tutorial-quickstart-tensorflow.rst:145 +msgid "The Model" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:139 -msgid "Creating the IMDBClient" +#: ../../source/tutorial-quickstart-huggingface.rst:180 +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 (|berttiny|_) for sequence " +"classification over the dataset of IMDB ratings. The end goal is to " +"detect if a movie rating is positive or negative. If you have access to " +"larger GPUs, feel free to use larger models!" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:141 +#: ../../source/tutorial-quickstart-huggingface.rst:193 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:" +"Note that here, ``model_name`` is a string that will be loaded from the " +"``Context`` in the ClientApp and ServerApp." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:169 +#: ../../source/tutorial-quickstart-huggingface.rst:196 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." +"In addition to loading the pretrained model weights and architecture, we " +"also include two utility functions to perform both training (i.e. " +"``train()``) and evaluation (i.e. ``test()``) using the above model. " +"These functions should look fairly familiar if you have some prior " +"experience with PyTorch. Note these functions do not have anything " +"specific to Flower. That being said, the training function will normally " +"be called, as we'll see later, from a Flower client passing its own data." +" In summary, your clients can use standard training/testing functions to " +"perform local training or evaluation:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:175 -msgid "Starting the server" +#: ../../source/tutorial-quickstart-huggingface.rst:239 +#: ../../source/tutorial-quickstart-mlx.rst:210 +#: ../../source/tutorial-quickstart-pytorch.rst:234 +#: ../../source/tutorial-quickstart-tensorflow.rst:176 +msgid "The ClientApp" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:177 +#: ../../source/tutorial-quickstart-huggingface.rst:241 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:" +"The main changes we have to make to use 🤗 Hugging Face with Flower will " +"be found in the ``get_weights()`` and ``set_weights()`` functions. Under " +"the hood, the ``transformers`` library uses PyTorch, which means we can " +"reuse the ``get_weights()`` and ``set_weights()`` code that we defined in" +" the :doc:`Quickstart PyTorch ` tutorial. As" +" a reminder, in ``get_weights()``, PyTorch model parameters are extracted" +" and represented as a list of NumPy arrays. The ``set_weights()`` " +"function that's the opposite: given a list of NumPy arrays it applies " +"them to an existing PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:205 +#: ../../source/tutorial-quickstart-huggingface.rst:254 +#: ../../source/tutorial-quickstart-pytorch.rst:245 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 specific implementation of ``get_weights()`` and ``set_weights()`` " +"depends on the type of models you use. The ones shown below work for a " +"wide range of PyTorch models but you might need to adjust them if you " +"have more exotic model architectures." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:209 -msgid "Putting everything together" +#: ../../source/tutorial-quickstart-huggingface.rst:269 +#: ../../source/tutorial-quickstart-pytorch.rst:261 +msgid "" +"The rest of the functionality is directly inspired by the centralized " +"case. The ``fit()`` method in the client trains the model using the local" +" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " +"model received on a held-out validation set that the client might have:" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:211 -msgid "We can now start client instances using:" +#: ../../source/tutorial-quickstart-huggingface.rst:296 +msgid "" +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparemeters defined in your " +"``pyproject.toml`` to configure the run. In this tutorial we access the " +"``local-epochs`` setting to control the number of epochs a ``ClientApp`` " +"will perform when running the ``fit()`` method. You could define " +"additional hyperparameters in ``pyproject.toml`` and access them here." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:221 -msgid "" -"And they will be able to connect to the server and start the federated " -"training." +#: ../../source/tutorial-quickstart-huggingface.rst:330 +#: ../../source/tutorial-quickstart-mlx.rst:376 +#: ../../source/tutorial-quickstart-pytorch.rst:321 +#: ../../source/tutorial-quickstart-tensorflow.rst:245 +msgid "The ServerApp" msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:223 +#: ../../source/tutorial-quickstart-huggingface.rst:332 msgid "" -"If you want to check out everything put together, you should check out " -"the `full code example `_ ." +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"|serverappcomponents|_ as opposed to a |client|_ In this example we use " +"the `FedAvg` strategy. To it we pass a randomly initialized model that " +"will server as the global model to federated. Note that the value of " +"``fraction_fit`` is read from the run config. You can find the default " +"value defined in the ``pyproject.toml``." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:226 +#: ../../source/tutorial-quickstart-huggingface.rst:371 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." +"Congratulations! You've successfully built and run your first federated " +"learning system for an LLM." msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:229 +#: ../../source/tutorial-quickstart-huggingface.rst:376 msgid "" -"Note that in this example we used :code:`PyTorch`, but we could have very" -" well used :code:`TensorFlow`." +"Check the source code of the extended version of this tutorial in " +"|quickstart_hf_link|_ in the Flower GitHub repository. For a " +"comprehensive example of a federated fine-tuning of an LLM with Flower, " +"refer to the |flowertune|_ example in the Flower GitHub repository." msgstr "" #: ../../source/tutorial-quickstart-ios.rst:-1 @@ -20892,7 +21108,6 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:34 #: ../../source/tutorial-quickstart-scikitlearn.rst:40 -#: ../../source/tutorial-quickstart-tensorflow.rst:29 #: ../../source/tutorial-quickstart-xgboost.rst:55 msgid "Flower Client" msgstr "" @@ -20966,13 +21181,11 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:129 #: ../../source/tutorial-quickstart-scikitlearn.rst:167 -#: ../../source/tutorial-quickstart-tensorflow.rst:98 #: ../../source/tutorial-quickstart-xgboost.rst:341 msgid "Flower Server" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:131 -#: ../../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 " @@ -20981,12 +21194,10 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:142 #: ../../source/tutorial-quickstart-scikitlearn.rst:230 -#: ../../source/tutorial-quickstart-tensorflow.rst:112 msgid "Train the model, federated!" msgstr "" #: ../../source/tutorial-quickstart-ios.rst:144 -#: ../../source/tutorial-quickstart-tensorflow.rst:114 #: ../../source/tutorial-quickstart-xgboost.rst:567 msgid "" "With both client and server ready, we can now run everything and see " @@ -21151,7 +21362,7 @@ msgid "" " the model:" msgstr "" -#: ../../source/tutorial-quickstart-jax.rst:165 +#: ../../source/tutorial-quickstart-jax.rst:167 msgid ":code:`set_parameters (optional)`" msgstr "" @@ -21235,13 +21446,6 @@ msgid "" "api/flwr_datasets.partitioner.IidPartitioner.html#flwr_datasets.partitioner.IidPartitioner>`_." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:19 -#: ../../source/tutorial-quickstart-pytorch.rst:19 -msgid "" -"Now that we have a rough idea of what this example is about, let's get " -"started. First, install Flower in your new environment:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:27 msgid "" "Then, run the command below. You will be prompted to select of the " @@ -21249,48 +21453,16 @@ msgid "" "type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:35 -#: ../../source/tutorial-quickstart-pytorch.rst:35 -msgid "" -"After running it you'll notice a new directory with your project name has" -" been created. It should have the following structure:" -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:49 -#: ../../source/tutorial-quickstart-pytorch.rst:49 -msgid "" -"If you haven't yet installed the project and its dependencies, you can do" -" so by:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:57 msgid "To run the project do:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:64 -#: ../../source/tutorial-quickstart-pytorch.rst:64 -msgid "With default arguments you will see an output like this one:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:106 msgid "" "You can also override the parameters defined in " "``[tool.flwr.app.config]`` section in the ``pyproject.toml`` like this:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:114 -#: ../../source/tutorial-quickstart-pytorch.rst:113 -msgid "" -"What follows is an explanation of each component in the project you just " -"created: dataset partition, the model, defining the ``ClientApp`` and " -"defining the ``ServerApp``." -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:120 -#: ../../source/tutorial-quickstart-pytorch.rst:119 -msgid "The Data" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:122 msgid "" "We will use `Flower Datasets `_ to " @@ -21302,11 +21474,6 @@ msgid "" "api/flwr_datasets.partitioner.html>`_ available in Flower Datasets:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:164 -#: ../../source/tutorial-quickstart-pytorch.rst:157 -msgid "The Model" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:166 msgid "" "We define the model as in the `centralized MLX example " @@ -21320,11 +21487,6 @@ msgid "" "over batches." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:210 -#: ../../source/tutorial-quickstart-pytorch.rst:234 -msgid "The ClientApp" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:212 msgid "" "The main changes we have to make to use `MLX` with `Flower` will be found" @@ -21392,11 +21554,6 @@ msgid "" "method." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:376 -#: ../../source/tutorial-quickstart-pytorch.rst:321 -msgid "The ServerApp" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:378 msgid "" "To construct a ``ServerApp``, we define a ``server_fn()`` callback with " @@ -21410,6 +21567,7 @@ msgstr "" #: ../../source/tutorial-quickstart-mlx.rst:402 #: ../../source/tutorial-quickstart-pytorch.rst:360 +#: ../../source/tutorial-quickstart-tensorflow.rst:279 msgid "" "Congratulations! You've successfully built and run your first federated " "learning system." @@ -21476,16 +21634,6 @@ msgid "" "and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:57 -msgid "To run the project, do:" -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:105 -msgid "" -"You can also override the parameters defined in the " -"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:121 msgid "" "This tutorial uses `Flower Datasets `_ " @@ -21529,22 +21677,6 @@ msgid "" "PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:245 -msgid "" -"The specific implementation of ``get_weights()`` and ``set_weights()`` " -"depends on the type of models you use. The ones shown below work for a " -"wide range of PyTorch models but you might need to adjust them if you " -"have more exotic model architectures." -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:261 -msgid "" -"The rest of the functionality is directly inspired by the centralized " -"case. The ``fit()`` method in the client trains the model using the local" -" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " -"model received on a held-out validation set that the client might have:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:294 msgid "" "Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " @@ -21578,6 +21710,7 @@ msgid "" msgstr "" #: ../../source/tutorial-quickstart-pytorch.rst:372 +#: ../../source/tutorial-quickstart-tensorflow.rst:295 msgid "Video tutorial" msgstr "" @@ -21588,27 +21721,46 @@ msgid "" "that shows the new APIs (as the content above does)" msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:-1 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 +msgid "Quickstart PyTorch Lightning" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:7 msgid "" -"Check out this Federated Learning quickstart tutorial for using Flower " -"with PyTorch Lightning to train an Auto Encoder model on MNIST." +"In this federated learning tutorial we will learn how to train an " +"AutoEncoder model on MNIST using Flower and PyTorch Lightning. It is " +"recommended to create a virtual environment and run everything within a " +":doc:`virtualenv `." msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 -msgid "Quickstart PyTorch Lightning" +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:20 +msgid "" +"This will create a new directory called `quickstart-pytorch-lightning` " +"containing the following files:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:10 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:43 msgid "" -"Let's build a horizontal federated learning system using PyTorch " -"Lightning and Flower!" +"By default, Flower Simulation Engine will be started and it will create a" +" federation of 4 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" To run the project, do:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:94 msgid "" -"Please refer to the `full code example " -"`_ to learn more." +"Each simulated `ClientApp` (two per round) will also log a summary of " +"their local training process. Expect this output to be similar to:" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:116 +msgid "" +"Check the `source code `_ of this tutorial in ``examples" +"/quickstart-pytorch-lightning`` in the Flower GitHub repository." msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:-1 @@ -21689,7 +21841,7 @@ msgstr "" msgid "Sets the parameters of a :code:`sklearn` LogisticRegression model" msgstr "" -#: ../../source/tutorial-quickstart-scikitlearn.rst:49 +#: ../../source/tutorial-quickstart-scikitlearn.rst:50 msgid ":code:`set_initial_params()`" msgstr "" @@ -21745,7 +21897,7 @@ msgstr "" msgid "return the model weight as a list of NumPy ndarrays" msgstr "" -#: ../../source/tutorial-quickstart-scikitlearn.rst:120 +#: ../../source/tutorial-quickstart-scikitlearn.rst:121 msgid ":code:`set_parameters` (optional)" msgstr "" @@ -21839,7 +21991,6 @@ msgid "" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:239 -#: ../../source/tutorial-quickstart-tensorflow.rst:122 #: ../../source/tutorial-quickstart-xgboost.rst:575 msgid "" "Once the server is running we can start the clients in different " @@ -21847,7 +21998,6 @@ msgid "" msgstr "" #: ../../source/tutorial-quickstart-scikitlearn.rst:246 -#: ../../source/tutorial-quickstart-tensorflow.rst:129 #: ../../source/tutorial-quickstart-xgboost.rst:582 msgid "Open another terminal and start the second client:" msgstr "" @@ -21872,102 +22022,110 @@ 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." +"with TensorFlow to train a CNN model on CIFAR-10." msgstr "" #: ../../source/tutorial-quickstart-tensorflow.rst:5 msgid "Quickstart TensorFlow" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:13 -msgid "Let's build a federated learning system in less than 20 lines of code!" -msgstr "" - -#: ../../source/tutorial-quickstart-tensorflow.rst:15 -msgid "Before Flower can be imported we have to install it:" -msgstr "" - -#: ../../source/tutorial-quickstart-tensorflow.rst:21 +#: ../../source/tutorial-quickstart-tensorflow.rst:7 msgid "" -"Since we want to use the Keras API of TensorFlow (TF), we have to install" -" TF as well:" +"In this tutorial we will learn how to train a Convolutional Neural " +"Network on CIFAR-10 using the Flower framework and TensorFlow. First of " +"all, it is recommended to create a virtual environment and run everything" +" within a :doc:`virtualenv `." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:31 -msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" +#: ../../source/tutorial-quickstart-tensorflow.rst:13 +msgid "" +"Let's use `flwr new` to create a complete Flower+TensorFlow project. It " +"will generate all the files needed to run, by default with the Flower " +"Simulation Engine, a federation of 10 nodes using `FedAvg " +"`_. The " +"dataset will be partitioned using Flower Dataset's `IidPartitioner " +"`_." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:38 +#: ../../source/tutorial-quickstart-tensorflow.rst:28 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." +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``TensorFlow``), give a name to your project," +" and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:47 +#: ../../source/tutorial-quickstart-tensorflow.rst:118 msgid "" -"Next, we need a model. For the purpose of this tutorial, we use " -"MobilNetV2 with 10 output classes:" +"This tutorial uses `Flower Datasets `_ " +"to easily download and partition the `CIFAR-10` dataset. In this example " +"you'll make use of the `IidPartitioner `_" +" to generate `num_partitions` partitions. You can choose `other " +"partitioners `_ available in Flower Datasets. Each " +"``ClientApp`` will call this function to create the ``NumPy`` arrays that" +" correspond to their data partition." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:54 +#: ../../source/tutorial-quickstart-tensorflow.rst:147 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)." +"Next, we need a model. We defined a simple Convolutional Neural Network " +"(CNN), but feel free to replace it with a more sophisticated model if " +"you'd like:" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:60 +#: ../../source/tutorial-quickstart-tensorflow.rst:178 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:" +"With `TensorFlow`, we can use the built-in ``get_weights()`` and " +"``set_weights()`` functions, which simplifies the implementation with " +"`Flower`. The rest of the functionality in the ClientApp is directly " +"inspired by the centralized case. The ``fit()`` method in the client " +"trains the model using the local dataset. Similarly, the ``evaluate()`` " +"method is used to evaluate the model received on a held-out validation " +"set that the client might have:" msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:82 +#: ../../source/tutorial-quickstart-tensorflow.rst:212 msgid "" -"We can now create an instance of our class :code:`CifarClient` and add " -"one line to actually run this client:" +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparameters defined in your " +"``pyproject.toml`` to configure the run. For example, in this tutorial we" +" access the `local-epochs` setting to control the number of epochs a " +"``ClientApp`` will perform when running the ``fit()`` method, in addition" +" to `batch-size`. You could define additional hyperparameters in " +"``pyproject.toml`` and access them here." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:90 +#: ../../source/tutorial-quickstart-tensorflow.rst:247 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." +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"`ServerAppComponents `_ as " +"opposed to a `Client `_. In this example we use the " +"`FedAvg`. To it we pass a randomly initialized model that will serve as " +"the global model to federate." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:135 -msgid "Each client will have its own dataset." +#: ../../source/tutorial-quickstart-tensorflow.rst:284 +msgid "" +"Check the source code of the extended version of this tutorial in " +"|quickstart_tf_link|_ in the Flower GitHub repository." msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:137 +#: ../../source/tutorial-quickstart-tensorflow.rst:299 msgid "" -"You should now see how the training does in the very first terminal (the " -"one that started the server):" +"The video shown below shows how to setup a TensorFlow + Flower project " +"using our previously recommended APIs. A new video tutorial will be " +"released that shows the new APIs (as the content above does)" 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`." -msgstr "" - -#: ../../source/tutorial-quickstart-xgboost.rst:-1 +#: ../../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." @@ -23811,7 +23969,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -msgid "|e5918c1c06a4434bbe4bf49235e40059|" +msgid "|e87b69b2ada74ea49412df16f4a0b9cc|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 @@ -23826,7 +23984,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -msgid "|c0165741bd1944f09ec55ce49032377d|" +msgid "|33cacb7d985c4906b348515c1a5cd993|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 @@ -23847,7 +24005,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +msgid "|cc080a555947492fa66131dc3a967603|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 @@ -23863,7 +24021,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +msgid "|085c3e0fb8664c6aa06246636524b20b|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 @@ -23879,7 +24037,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -msgid "|74f26ca701254d3db57d7899bd91eb55|" +msgid "|bfe69c74e48c45d49b50251c38c2a019|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 @@ -23894,7 +24052,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -msgid "|bda79f21f8154258a40e5766b2634ad7|" +msgid "|ebbecd651f0348d99c6511ea859bf4ca|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 @@ -23914,7 +24072,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -msgid "|89d30862e62e4f9989e193483a08680a|" +msgid "|163117eb654a4273babba413cf8065f5|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 @@ -23929,7 +24087,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -msgid "|77e9918671c54b4f86e01369c0785ce8|" +msgid "|452ac3ba453b4cd1be27be1ba7560d64|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 @@ -24069,7 +24227,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -msgid "|7e4ccef37cc94148a067107b34eb7447|" +msgid "|f403fcd69e4e44409627e748b404c086|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 @@ -24093,7 +24251,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -msgid "|28e47e4cded14479a0846c8e5f22c872|" +msgid "|4b00fe63870145968f8443619a792a42|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 @@ -24117,7 +24275,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +msgid "|368378731066486fa4397e89bc6b870c|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 @@ -24140,7 +24298,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +msgid "|a66aa83d85bf4ffba7ed660b718066da|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 @@ -24178,7 +24336,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -msgid "|81749d0ac0834c36a83bd38f433fea31|" +msgid "|82324b9af72a4582a81839d55caab767|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 @@ -24272,7 +24430,7 @@ msgid "" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -msgid "|ed9aae51da70428eab7eef32f21e819e|" +msgid "|fbf2da0da3cc4f8ab3b3eff852d80c41|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 @@ -29467,3 +29625,564 @@ msgstr "" #~ msgid "|c00bf2750bc24d229737a0fe1395f0fc|" #~ msgstr "" +#~ msgid ":py:obj:`client `\\" +#~ msgstr "" + +#~ msgid ":py:obj:`common `\\" +#~ msgstr "" + +#~ msgid ":py:obj:`server `\\" +#~ msgstr "" + +#~ msgid ":py:obj:`simulation `\\" +#~ msgstr "" + +#~ msgid ":py:obj:`mod `\\" +#~ msgstr "" + +#~ msgid "run\\_client\\_app" +#~ msgstr "" + +#~ msgid "run\\_supernode" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`get `\\ " +#~ "\\(key\\[\\, default\\]\\)" +#~ msgstr "" + +#~ msgid "Retrieve the corresponding layout by the string key." +#~ msgstr "" + +#~ msgid "" +#~ "When there isn't an exact match, " +#~ "all the existing keys in the " +#~ "layout map will be treated as a" +#~ " regex and map against the input " +#~ "key again. The first match will be" +#~ " returned, based on the key insertion" +#~ " order. Return None if there isn't" +#~ " any match found." +#~ msgstr "" + +#~ msgid "the string key as the query for the layout." +#~ msgstr "" + +#~ msgid "Corresponding layout based on the query." +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`get `\\ " +#~ "\\(key\\[\\, default\\]\\)" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`get `\\ " +#~ "\\(key\\[\\, default\\]\\)" +#~ msgstr "" + +#~ msgid ":py:obj:`strategy `\\" +#~ msgstr "" + +#~ msgid ":py:obj:`workflow `\\" +#~ msgstr "" + +#~ msgid "run\\_server\\_app" +#~ msgstr "" + +#~ msgid "run\\_superlink" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`start_simulation `\\" +#~ " \\(\\*\\, client\\_fn\\, num\\_clients\\)" +#~ msgstr "" + +#~ msgid "Start a Ray-based Flower simulation server." +#~ msgstr "" + +#~ msgid "" +#~ "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 (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)." +#~ msgstr "" + +#~ msgid "The total number of clients in this simulation." +#~ msgstr "" + +#~ msgid "" +#~ "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." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "" +#~ "An implementation of the abstract base" +#~ " class `flwr.server.Server`. If no instance" +#~ " is provided, then `start_server` will " +#~ "create one." +#~ msgstr "" + +#~ 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`." +#~ msgstr "" + +#~ 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`." +#~ msgstr "" + +#~ msgid "" +#~ "Optional dictionary containing arguments for" +#~ " the call to `ray.init`. If " +#~ "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." +#~ msgstr "" + +#~ msgid "" +#~ "Optional dictionary containing arguments for" +#~ " the call to `ray.init`. If " +#~ "ray_init_args is None (the default), Ray" +#~ " will be initialized with the " +#~ "following default args:" +#~ msgstr "" + +#~ msgid "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" +#~ msgstr "" + +#~ msgid "" +#~ "An empty dictionary can be used " +#~ "(ray_init_args={}) to prevent any arguments" +#~ " from being passed to ray.init." +#~ msgstr "" + +#~ msgid "" +#~ "Set to True to prevent `ray.shutdown()`" +#~ " in case `ray.is_initialized()=True`." +#~ msgstr "" + +#~ msgid "" +#~ "Optionally specify the type of actor " +#~ "to use. The actor object, which " +#~ "persists throughout the simulation, will " +#~ "be the process in charge of " +#~ "executing a ClientApp wrapping input " +#~ "argument `client_fn`." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ 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" +#~ msgstr "" + +#~ msgid "**hist** -- Object containing metrics from training." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with FastAI to train a vision " +#~ "model on CIFAR-10." +#~ msgstr "" + +#~ msgid "Let's build a federated learning system using fastai and Flower!" +#~ msgstr "" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn more." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federating Learning " +#~ "quickstart tutorial for using Flower " +#~ "with HuggingFace Transformers in order " +#~ "to fine-tune an LLM." +#~ msgstr "" + +#~ msgid "" +#~ "Let's build a federated learning system" +#~ " using Hugging Face Transformers and " +#~ "Flower!" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "Dependencies" +#~ msgstr "" + +#~ 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`:" +#~ msgstr "" + +#~ msgid "Standard Hugging Face workflow" +#~ msgstr "" + +#~ msgid "Handling the data" +#~ msgstr "" + +#~ msgid "" +#~ "To fetch the IMDB dataset, we will" +#~ " use Hugging Face's :code:`datasets` " +#~ "library. We then need to tokenize " +#~ "the data and create :code:`PyTorch` " +#~ "dataloaders, this is all done in " +#~ "the :code:`load_data` function:" +#~ msgstr "" + +#~ msgid "Training and testing the model" +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ msgid "Creating the model itself" +#~ msgstr "" + +#~ msgid "" +#~ "To create the model itself, we " +#~ "will just load the pre-trained " +#~ "distillBERT model using Hugging Face’s " +#~ ":code:`AutoModelForSequenceClassification` :" +#~ msgstr "" + +#~ msgid "Federating the example" +#~ msgstr "" + +#~ msgid "Creating the IMDBClient" +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "Starting the server" +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ 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)." +#~ msgstr "" + +#~ msgid "Putting everything together" +#~ msgstr "" + +#~ msgid "We can now start client instances using:" +#~ msgstr "" + +#~ msgid "" +#~ "And they will be able to connect" +#~ " to the server and start the " +#~ "federated training." +#~ msgstr "" + +#~ msgid "" +#~ "If you want to check out " +#~ "everything put together, you should " +#~ "check out the `full code example " +#~ "`_ ." +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "" +#~ "Note that in this example we used" +#~ " :code:`PyTorch`, but we could have " +#~ "very well used :code:`TensorFlow`." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with PyTorch Lightning to train an " +#~ "Auto Encoder model on MNIST." +#~ msgstr "" + +#~ msgid "" +#~ "Let's build a horizontal federated " +#~ "learning system using PyTorch Lightning " +#~ "and Flower!" +#~ msgstr "" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn " +#~ "more." +#~ msgstr "" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with TensorFlow to train a MobilNetV2" +#~ " model on CIFAR-10." +#~ msgstr "" + +#~ msgid "Let's build a federated learning system in less than 20 lines of code!" +#~ msgstr "" + +#~ msgid "Before Flower can be imported we have to install it:" +#~ msgstr "" + +#~ msgid "" +#~ "Since we want to use the Keras " +#~ "API of TensorFlow (TF), we have to" +#~ " install TF as well:" +#~ msgstr "" + +#~ msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "" +#~ "Next, we need a model. For the " +#~ "purpose of this tutorial, we use " +#~ "MobilNetV2 with 10 output classes:" +#~ msgstr "" + +#~ 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)." +#~ msgstr "" + +#~ 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:" +#~ msgstr "" + +#~ msgid "" +#~ "We can now create an instance of" +#~ " our class :code:`CifarClient` and add " +#~ "one line to actually run this " +#~ "client:" +#~ msgstr "" + +#~ 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." +#~ msgstr "" + +#~ msgid "Each client will have its own dataset." +#~ msgstr "" + +#~ msgid "" +#~ "You should now see how the " +#~ "training does in the very first " +#~ "terminal (the one that started the " +#~ "server):" +#~ msgstr "" + +#~ 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`." +#~ msgstr "" + +#~ msgid "|e5918c1c06a4434bbe4bf49235e40059|" +#~ msgstr "" + +#~ msgid "|c0165741bd1944f09ec55ce49032377d|" +#~ msgstr "" + +#~ msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +#~ msgstr "" + +#~ msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +#~ msgstr "" + +#~ msgid "|74f26ca701254d3db57d7899bd91eb55|" +#~ msgstr "" + +#~ msgid "|bda79f21f8154258a40e5766b2634ad7|" +#~ msgstr "" + +#~ msgid "|89d30862e62e4f9989e193483a08680a|" +#~ msgstr "" + +#~ msgid "|77e9918671c54b4f86e01369c0785ce8|" +#~ msgstr "" + +#~ msgid "|7e4ccef37cc94148a067107b34eb7447|" +#~ msgstr "" + +#~ msgid "|28e47e4cded14479a0846c8e5f22c872|" +#~ msgstr "" + +#~ msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +#~ msgstr "" + +#~ msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +#~ msgstr "" + +#~ msgid "|81749d0ac0834c36a83bd38f433fea31|" +#~ msgstr "" + +#~ msgid "|ed9aae51da70428eab7eef32f21e819e|" +#~ msgstr "" + diff --git a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po b/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po index 674417e69791..9af452fb0be2 100644 --- a/doc/locales/zh_Hans/LC_MESSAGES/framework-docs.po +++ b/doc/locales/zh_Hans/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-09-15 09:09+0200\n" +"POT-Creation-Date: 2024-09-24 00:29+0000\n" "PO-Revision-Date: 2024-06-12 10:09+0000\n" "Last-Translator: Yan Gao \n" "Language: zh_Hans\n" @@ -17,7 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.15.0\n" +"Generated-By: Babel 2.16.0\n" #: ../../source/contributor-explanation-public-and-private-apis.rst:2 msgid "Public and private APIs" @@ -1476,7 +1476,7 @@ msgstr "" msgid "Setting up the repository" msgstr "建立资源库" -#: ../../source/contributor-tutorial-contribute-on-github.rst:12 +#: ../../source/contributor-tutorial-contribute-on-github.rst:21 msgid "**Create a GitHub account and setup Git**" msgstr "**创建 GitHub 账户并设置 Git**" @@ -1515,7 +1515,7 @@ msgstr "" "通用的 Git 和 GitHub 工作流程背后的理念可以归结为:从 GitHub 上的远程仓库下载代码,在本地进行修改并使用 Git " "进行跟踪,然后将新的历史记录上传回 GitHub。" -#: ../../source/contributor-tutorial-contribute-on-github.rst:23 +#: ../../source/contributor-tutorial-contribute-on-github.rst:32 msgid "**Forking the Flower repository**" msgstr "**叉花仓库**" @@ -1540,7 +1540,7 @@ msgstr "" "您可以更改名称,但没有必要,因为这个版本的 Flower " "将是您自己的,并位于您自己的账户中(即,在您自己的版本库列表中)。创建完成后,您会在左上角看到自己的 Flower 版本。" -#: ../../source/contributor-tutorial-contribute-on-github.rst:34 +#: ../../source/contributor-tutorial-contribute-on-github.rst:47 msgid "**Cloning your forked repository**" msgstr "**克隆你的分叉仓库**" @@ -1567,7 +1567,7 @@ msgid "" "it) folder in the current working directory." msgstr "这将在当前工作目录下创建一个 `flower/`(如果重命名了,则使用 fork 的名称)文件夹。" -#: ../../source/contributor-tutorial-contribute-on-github.rst:49 +#: ../../source/contributor-tutorial-contribute-on-github.rst:66 msgid "**Add origin**" msgstr "**添加原产地**" @@ -1591,7 +1591,7 @@ msgid "" "terminal:" msgstr "一旦复制了 \\ ,我们就可以在终端中键入以下命令:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:68 +#: ../../source/contributor-tutorial-contribute-on-github.rst:90 msgid "**Add upstream**" msgstr "**增加上游**" @@ -1650,7 +1650,7 @@ msgstr "在进行任何更改之前,请确保您的版本库是最新的:" msgid "And with Flower's repository:" msgstr "还有Flower的存储库:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:114 +#: ../../source/contributor-tutorial-contribute-on-github.rst:122 msgid "**Create a new branch**" msgstr "**创建一个新分支**" @@ -1667,7 +1667,7 @@ msgid "" "directory:" msgstr "为此,只需在版本库目录下运行以下命令即可:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:124 +#: ../../source/contributor-tutorial-contribute-on-github.rst:125 msgid "**Make changes**" msgstr "**进行修改**" @@ -1675,7 +1675,7 @@ msgstr "**进行修改**" msgid "Write great code and create wonderful changes using your favorite editor!" msgstr "使用您最喜欢的编辑器编写优秀的代码并创建精彩的更改!" -#: ../../source/contributor-tutorial-contribute-on-github.rst:127 +#: ../../source/contributor-tutorial-contribute-on-github.rst:138 msgid "**Test and format your code**" msgstr "**测试并格式化您的代码**" @@ -1690,7 +1690,7 @@ msgstr "不要忘记测试和格式化您的代码!否则您的代码将无法 msgid "To do so, we have written a few scripts that you can execute:" msgstr "为此,我们编写了一些脚本供您执行:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:140 +#: ../../source/contributor-tutorial-contribute-on-github.rst:150 msgid "**Stage changes**" msgstr "**舞台变化**" @@ -1711,7 +1711,7 @@ msgid "" "the :code:`git status` command." msgstr "要查看与上一版本(上次提交)相比哪些文件已被修改,以及哪些文件处于提交阶段,可以使用 :code:`git status` 命令。" -#: ../../source/contributor-tutorial-contribute-on-github.rst:152 +#: ../../source/contributor-tutorial-contribute-on-github.rst:160 msgid "**Commit changes**" msgstr "**提交更改**" @@ -1730,7 +1730,7 @@ msgstr "" " 用于向他人解释提交的作用。它应该以命令式风格书写,并且简明扼要。例如 :code:`git commit " "-m \"Add images to README\"`。" -#: ../../source/contributor-tutorial-contribute-on-github.rst:162 +#: ../../source/contributor-tutorial-contribute-on-github.rst:171 msgid "**Push the changes to the fork**" msgstr "**将更改推送到分叉**" @@ -1751,7 +1751,7 @@ msgstr "完成此操作后,您将在 GitHub 上看到您的分叉仓库已根 msgid "Creating and merging a pull request (PR)" msgstr "创建和合并拉取请求 (PR)" -#: ../../source/contributor-tutorial-contribute-on-github.rst:176 +#: ../../source/contributor-tutorial-contribute-on-github.rst:206 msgid "**Create the PR**" msgstr "**创建 PR**" @@ -1820,7 +1820,7 @@ msgid "" "anyone, you have the option to create a draft pull request:" msgstr "如果您的 PR 尚未准备好接受审核,而且您不想通知任何人,您可以选择创建一个草案拉取请求:" -#: ../../source/contributor-tutorial-contribute-on-github.rst:208 +#: ../../source/contributor-tutorial-contribute-on-github.rst:209 msgid "**Making new changes**" msgstr "**作出新的改变**" @@ -1831,7 +1831,7 @@ msgid "" " associated with the PR." msgstr "一旦 PR 被打开(无论是否作为草案),你仍然可以像以前一样,通过修改与 PR 关联的分支来推送新的提交。" -#: ../../source/contributor-tutorial-contribute-on-github.rst:211 +#: ../../source/contributor-tutorial-contribute-on-github.rst:231 msgid "**Review the PR**" msgstr "**审查 PR**" @@ -1867,7 +1867,7 @@ msgid "" "review." msgstr "一旦所有对话都得到解决,您就可以重新申请审核。" -#: ../../source/contributor-tutorial-contribute-on-github.rst:233 +#: ../../source/contributor-tutorial-contribute-on-github.rst:251 msgid "**Once the PR is merged**" msgstr "**一旦 PR 被合并**" @@ -2179,6 +2179,7 @@ msgstr "成为贡献者" #: ../../source/contributor-tutorial-get-started-as-a-contributor.rst:5 #: ../../source/docker/run-as-subprocess.rst:11 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:15 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:12 #: ../../source/docker/tutorial-quickstart-docker.rst:11 msgid "Prerequisites" @@ -2960,6 +2961,242 @@ msgid "" " the SuperNode to execute the ClientApp as a subprocess:" msgstr "" +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:2 +#, fuzzy +msgid "Run Flower Quickstart Examples with Docker Compose" +msgstr "快速入门 iOS" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:4 +msgid "" +"Flower provides a set of `quickstart examples " +"`_ to help you get " +"started with the framework. These examples are designed to demonstrate " +"the capabilities of Flower and by default run using the Simulation " +"Engine. This guide demonstrates how to run them using Flower's Deployment" +" Engine via Docker Compose." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:11 +msgid "" +"Some quickstart examples may have limitations or requirements that " +"prevent them from running on every environment. For more information, " +"please see `Limitations`_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 +#: ../../source/docker/tutorial-quickstart-docker.rst:13 +#, fuzzy +msgid "Before you start, make sure that:" +msgstr "开始之前,请确保 Docker 守护进程正在运行:" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:19 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 +#: ../../source/docker/tutorial-quickstart-docker.rst:15 +msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:20 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 +#: ../../source/docker/tutorial-quickstart-docker.rst:16 +#, fuzzy +msgid "The Docker daemon is running." +msgstr "验证 Docker 守护进程是否正在运行。" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:21 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 +msgid "Docker Compose is `installed `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:24 +#, fuzzy +msgid "Run the Quickstart Example" +msgstr "示例请求" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:26 +msgid "" +"Clone the quickstart example you like to run. For example, ``quickstart-" +"pytorch``:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:34 +msgid "" +"Download the `compose.yml " +"`_" +" file into the example directory:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:41 +#, fuzzy +msgid "Build and start the services using the following command:" +msgstr "运行以下命令激活 virtualenv:" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:47 +#, fuzzy +msgid "" +"Append the following lines to the end of the ``pyproject.toml`` file and " +"save it:" +msgstr "将 ``pyproject.toml`` 中的次要版本增加一个。" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:49 +#: ../../source/docker/tutorial-quickstart-docker.rst:319 +#, fuzzy +msgid "pyproject.toml" +msgstr "或 ``pyproject.toml```:" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:58 +msgid "" +"You can customize the string that follows ``tool.flwr.federations.`` to " +"fit your needs. However, please note that the string cannot contain a dot" +" (``.``)." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:61 +msgid "" +"In this example, ``local-deployment`` has been used. Just remember to " +"replace ``local-deployment`` with your chosen name in both the " +"``tool.flwr.federations.`` string and the corresponding ``flwr run .`` " +"command." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:65 +#, fuzzy +msgid "Run the example:" +msgstr "将示例联邦化" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:71 +msgid "Follow the logs of the SuperExec service:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:77 +msgid "" +"That is all it takes! You can monitor the progress of the run through the" +" logs of the SuperExec." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:80 +msgid "Run a Different Quickstart Example" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:82 +msgid "" +"To run a different quickstart example, such as ``quickstart-tensorflow``," +" first, shut down the Docker Compose services of the current example:" +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:89 +msgid "After that, you can repeat the steps above." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:92 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:98 +#, fuzzy +msgid "Limitations" +msgstr "运行模拟" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:97 +#, fuzzy +msgid "Quickstart Example" +msgstr "快速入门 JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:99 +#, fuzzy +msgid "quickstart-fastai" +msgstr "快速入门 fastai" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:100 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:102 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:110 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:112 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:116 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:118 +#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 +#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 +#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 +#: ../../source/ref-changelog.md:929 +msgid "None" +msgstr "无" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:101 +#, fuzzy +msgid "quickstart-huggingface" +msgstr "快速入门教程" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:103 +#, fuzzy +msgid "quickstart-jax" +msgstr "快速入门 JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:104 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:106 +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:120 +#, fuzzy +msgid "" +"The example has not yet been updated to work with the latest ``flwr`` " +"version." +msgstr "涵盖 scikit-learn 和 PyTorch Lightning 的代码示例已更新,以便与最新版本的 Flower 配合使用。" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:105 +#, fuzzy +msgid "quickstart-mlcube" +msgstr "快速入门 JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:107 +#, fuzzy +msgid "quickstart-mlx" +msgstr "快速入门 JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:108 +msgid "" +"`Requires to run on macOS with Apple Silicon `_." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:109 +#, fuzzy +msgid "quickstart-monai" +msgstr "快速入门 JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:111 +#, fuzzy +msgid "quickstart-pandas" +msgstr "快速入门Pandas" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:113 +#, fuzzy +msgid "quickstart-pytorch-lightning" +msgstr "快速入门 PyTorch Lightning" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:114 +msgid "" +"Requires an older pip version that is not supported by the Flower Docker " +"images." +msgstr "" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:115 +#, fuzzy +msgid "quickstart-pytorch" +msgstr "PyTorch快速入门" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:117 +#, fuzzy +msgid "quickstart-sklearn-tabular" +msgstr "scikit-learn快速入门" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:119 +#, fuzzy +msgid "quickstart-tabnet" +msgstr "快速入门 JAX" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:121 +#, fuzzy +msgid "quickstart-tensorflow" +msgstr "快速入门 TensorFlow" + +#: ../../source/docker/run-quickstart-examples-docker-compose.rst:122 +msgid "Only runs on AMD64." +msgstr "" + #: ../../source/docker/set-environment-variables.rst:2 #, fuzzy msgid "Set Environment Variables" @@ -2991,23 +3228,6 @@ msgid "" " understanding the basic workflow that uses the minimum configurations." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:14 -#: ../../source/docker/tutorial-quickstart-docker.rst:13 -#, fuzzy -msgid "Before you start, make sure that:" -msgstr "开始之前,请确保 Docker 守护进程正在运行:" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:16 -#: ../../source/docker/tutorial-quickstart-docker.rst:15 -msgid "The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` locally." -msgstr "" - -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:17 -#: ../../source/docker/tutorial-quickstart-docker.rst:16 -#, fuzzy -msgid "The Docker daemon is running." -msgstr "验证 Docker 守护进程是否正在运行。" - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:21 #: ../../source/docker/tutorial-quickstart-docker.rst:19 msgid "Step 1: Set Up" @@ -3440,11 +3660,6 @@ msgstr "" msgid "Add the following lines to the ``pyproject.toml``:" msgstr "将 ``pyproject.toml`` 中的次要版本增加一个。" -#: ../../source/docker/tutorial-quickstart-docker.rst:319 -#, fuzzy -msgid "pyproject.toml" -msgstr "或 ``pyproject.toml```:" - #: ../../source/docker/tutorial-quickstart-docker.rst:326 msgid "Run the ``quickstart-docker`` project by executing the command:" msgstr "" @@ -3495,6 +3710,7 @@ msgstr "" msgid "Remove the containers and the bridge network:" msgstr "" +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:401 #: ../../source/docker/tutorial-quickstart-docker.rst:399 #, fuzzy msgid "Where to Go Next" @@ -3531,10 +3747,6 @@ msgid "" "configuration that best suits your project's needs." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:18 -msgid "Docker Compose is `installed `_." -msgstr "" - #: ../../source/docker/tutorial-quickstart-docker-compose.rst:23 msgid "Clone the Docker Compose ``complete`` directory:" msgstr "" @@ -3731,7 +3943,7 @@ msgstr "" #: ../../source/docker/tutorial-quickstart-docker-compose.rst:188 #: ../../source/docker/tutorial-quickstart-docker-compose.rst:241 -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:362 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:369 msgid "Rerun the ``quickstart-compose`` project:" msgstr "" @@ -3795,76 +4007,81 @@ msgstr "" msgid "compose.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:303 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 msgid "" "If you also want to enable TLS for the new SuperNodes, duplicate the " "SuperNode definition for each new SuperNode service in the ``with-" "tls.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:306 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:313 msgid "" "Make sure that the names of the services match with the one in the " "``compose.yml`` file." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:308 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:315 msgid "In ``with-tls.yml``, add the following:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:310 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:317 msgid "with-tls.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:332 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 msgid "Step 8: Persisting the SuperLink State and Enabling TLS" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:334 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:341 msgid "" "To run Flower with persisted SuperLink state and enabled TLS, a slight " "change in the ``with-state.yml`` file is required:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:337 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:344 msgid "Comment out the lines 2-4 and uncomment the lines 5-9:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:339 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:346 msgid "with-state.yml" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:356 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:363 #, fuzzy msgid "Restart the services:" msgstr "启动服务器" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:370 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:377 msgid "Step 9: Merge Multiple Compose Files" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:372 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:379 msgid "" "You can merge multiple Compose files into a single file. For instance, if" " you wish to combine the basic configuration with the TLS configuration, " "execute the following command:" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:380 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:387 msgid "" "This will merge the contents of ``compose.yml`` and ``with-tls.yml`` into" " a new file called ``my_compose.yml``." msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:384 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:391 msgid "Step 10: Clean Up" msgstr "" -#: ../../source/docker/tutorial-quickstart-docker-compose.rst:386 +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:393 #, fuzzy msgid "Remove all services and volumes:" msgstr "从 R 中删除所有项目。" +#: ../../source/docker/tutorial-quickstart-docker-compose.rst:403 +#, fuzzy +msgid ":doc:`run-quickstart-examples-docker-compose`" +msgstr "快速入门 iOS" + #: ../../source/docker/use-a-different-version.rst:2 #, fuzzy msgid "Use a Different Flower Version" @@ -4189,7 +4406,7 @@ msgstr "" ":code:`Client`略微容易一些,因为它避免了一些不必要的操作。:code:`CifarClient` " "需要实现四个方法,两个用于获取/设置模型参数,一个用于训练模型,一个用于测试模型:" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:218 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:219 msgid ":code:`set_parameters`" msgstr ":code:`set_parameters`" @@ -4222,9 +4439,9 @@ msgstr "" "获取模型参数,并以 NumPy :code:`ndarray`的列表形式返回(这正是 " ":code:`flwr.client.NumPyClient`所匹配的格式)" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:223 -#: ../../source/tutorial-quickstart-jax.rst:171 -#: ../../source/tutorial-quickstart-scikitlearn.rst:123 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:225 +#: ../../source/tutorial-quickstart-jax.rst:173 +#: ../../source/tutorial-quickstart-scikitlearn.rst:125 msgid ":code:`fit`" msgstr ":code:`fit`" @@ -4246,9 +4463,9 @@ msgstr "在本地训练集上训练模型" msgid "get the updated local model weights and return them to the server" msgstr "获取更新后的本地模型参数并发送回服务器" -#: ../../source/example-pytorch-from-centralized-to-federated.rst:227 -#: ../../source/tutorial-quickstart-jax.rst:175 -#: ../../source/tutorial-quickstart-scikitlearn.rst:127 +#: ../../source/example-pytorch-from-centralized-to-federated.rst:230 +#: ../../source/tutorial-quickstart-jax.rst:178 +#: ../../source/tutorial-quickstart-scikitlearn.rst:128 msgid ":code:`evaluate`" msgstr ":code:`evaluate`" @@ -4352,7 +4569,7 @@ msgstr "" "的数据)之外完全相同。差分隐私(DP)可以保证任何分析(M),比如计算平均收入,对两个数据集都会产生几乎相同的结果(O 和 O' " "将是相似的)。这既保留了群体模式,又掩盖了个人细节,确保个人的信息隐藏在人群中。" -#: ../../source/explanation-differential-privacy.rst:16 +#: ../../source/explanation-differential-privacy.rst:-1 #, fuzzy msgid "DP Intro" msgstr "DP 介绍" @@ -4490,8 +4707,8 @@ msgid "" "the client's data." msgstr "**本地差分隐私**: 在向服务器发送任何信息之前,在客户端应用 DP,目的是防止向服务器发送的更新泄露任何有关客户端数据的信息。" +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:68 -#: ../../source/explanation-differential-privacy.rst:71 #: ../../source/how-to-use-differential-privacy.rst:11 #, fuzzy msgid "Central Differential Privacy" @@ -4523,7 +4740,7 @@ msgstr "" "虽然在联合学习中实现中央数据处理的方法有很多种,但我们将重点放在[2]和[3]提出的算法上。总体方法是剪辑客户端发送的模型更新,并在聚合模型中添加一定量的噪声。在每次迭代中,以特定概率随机选择一组客户端进行训练。每个客户端对自己的数据进行局部训练。然后,每个客户端的更新会被某个值`S`(灵敏度`S`)剪切。这将限制任何单个客户端的影响,这对隐私至关重要,通常也有利于稳健性。实现这一点的常用方法是限制客户机模型更新的" " `L2` 准则,确保较大的更新被缩减以适应 `S` 准则。" -#: ../../source/explanation-differential-privacy.rst:84 +#: ../../source/explanation-differential-privacy.rst:-1 #, fuzzy msgid "clipping" msgstr "剪贴" @@ -4577,8 +4794,8 @@ msgid "" "others." msgstr "在固定剪切和自适应剪切之间做出选择取决于各种因素,如隐私要求、数据分布、模型复杂性等。" +#: ../../source/explanation-differential-privacy.rst:-1 #: ../../source/explanation-differential-privacy.rst:105 -#: ../../source/explanation-differential-privacy.rst:110 #: ../../source/how-to-use-differential-privacy.rst:96 #, fuzzy msgid "Local Differential Privacy" @@ -4849,7 +5066,7 @@ msgstr "" msgid "This is sometimes called a hub-and-spoke topology:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:18 +#: ../../source/explanation-flower-architecture.rst:24 #, fuzzy msgid "Hub-and-spoke topology in federated learning" msgstr "什么是联邦学习?" @@ -4922,7 +5139,7 @@ msgid "" "`missing link` between all those SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:65 +#: ../../source/explanation-flower-architecture.rst:71 #, fuzzy msgid "Basic Flower architecture" msgstr "Flower的架构" @@ -4960,7 +5177,7 @@ msgid "" "SuperNodes." msgstr "" -#: ../../source/explanation-flower-architecture.rst:91 +#: ../../source/explanation-flower-architecture.rst:97 #, fuzzy msgid "Multi-tenancy federated learning architecture" msgstr "使用联邦学习策略" @@ -4984,7 +5201,7 @@ msgid "" "their corresponding ``ClientApp``\\s:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:107 +#: ../../source/explanation-flower-architecture.rst:113 #, fuzzy msgid "Multi-tenancy federated learning architecture - Run 1" msgstr "使用联邦学习策略" @@ -5001,7 +5218,7 @@ msgid "" " to participate in the training:" msgstr "" -#: ../../source/explanation-flower-architecture.rst:119 +#: ../../source/explanation-flower-architecture.rst:125 #, fuzzy msgid "Multi-tenancy federated learning architecture - Run 2" msgstr "使用联邦学习策略" @@ -5038,7 +5255,7 @@ msgid "" "developer machine." msgstr "" -#: ../../source/explanation-flower-architecture.rst:145 +#: ../../source/explanation-flower-architecture.rst:151 msgid "Flower Deployment Engine with SuperExec" msgstr "" @@ -8153,7 +8370,7 @@ msgstr "" ":code:`DifferentialPrivacyServerSideFixedClipping` 和 " ":code:`DifferentialPrivacyServerSideAdaptiveClipping` ,用于固定剪辑和自适应剪辑。" -#: ../../source/how-to-use-differential-privacy.rst:25 +#: ../../source/how-to-use-differential-privacy.rst:-1 #, fuzzy msgid "server side clipping" msgstr "服务器端逻辑" @@ -8194,7 +8411,7 @@ msgstr "" ":code:`DifferentialPrivacyClientSideFixedClipping` 和 " ":code:`DifferentialPrivacyClientSideAdaptiveClipping`。" -#: ../../source/how-to-use-differential-privacy.rst:57 +#: ../../source/how-to-use-differential-privacy.rst:-1 #, fuzzy msgid "client side clipping" msgstr "客户端逻辑" @@ -8231,7 +8448,7 @@ msgstr "" "要利用本地差分隐私(DP)并在将客户端模型参数传输到 Flower 服务器之前为其添加噪声,可以使用 " "`LocalDpMod`。需要设置以下超参数:剪切规范值、灵敏度、ε 和 delta。" -#: ../../source/how-to-use-differential-privacy.rst:99 +#: ../../source/how-to-use-differential-privacy.rst:-1 #, fuzzy msgid "local DP mod" msgstr "本地 DP 模式" @@ -8653,11 +8870,33 @@ msgstr "" msgid "Arguments" msgstr "参数解析器" -#: ../../flwr install:1 new:1 run:1 +#: ../../flwr install:1 log:1 new:1 run:1 #, fuzzy msgid "Optional argument" msgstr "可选的改进措施" +#: ../../flwr log:1 +msgid "Get logs from a Flower project run." +msgstr "" + +#: ../../flwr log:1 +msgid "Flag to stream or print logs from the Flower run" +msgstr "" + +#: ../../flwr log +#, fuzzy +msgid "default" +msgstr "工作流程" + +#: ../../flwr log:1 +msgid "``True``" +msgstr "" + +#: ../../flwr log:1 +#, fuzzy +msgid "Required argument" +msgstr "构建文档" + #: ../../flwr new:1 #, fuzzy msgid "Create new Flower App." @@ -8750,7 +8989,7 @@ msgstr "模块" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`client `\\" +msgid ":py:obj:`flwr.client `\\" msgstr ":py:obj:`flwr.client `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.client:1 of @@ -8759,7 +8998,7 @@ msgstr "Flower 客户端。" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`common `\\" +msgid ":py:obj:`flwr.common `\\" msgstr ":py:obj:`flwr.common `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.common:1 of @@ -8768,7 +9007,7 @@ msgstr "服务器和客户端共享的通用组件。" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`server `\\" +msgid ":py:obj:`flwr.server `\\" msgstr ":py:obj:`flwr.server `\\" #: ../../source/ref-api/flwr.rst:35::1 @@ -8779,7 +9018,7 @@ msgstr "Flower 服务器。" #: ../../source/ref-api/flwr.rst:35::1 #, fuzzy -msgid ":py:obj:`simulation `\\" +msgid ":py:obj:`flwr.simulation `\\" msgstr ":py:obj:`flwr.simulation `\\" #: ../../source/ref-api/flwr.rst:35::1 flwr.simulation:1 of @@ -8875,7 +9114,7 @@ msgstr "使用 NumPy 的 Flower 客户端的抽象基类。" #: ../../source/ref-api/flwr.client.rst:50::1 #, fuzzy -msgid ":py:obj:`mod `\\" +msgid ":py:obj:`flwr.client.mod `\\" msgstr ":py:obj:`flwr.client `\\" #: ../../source/ref-api/flwr.client.rst:50::1 flwr.client.mod:1 of @@ -9086,48 +9325,57 @@ msgstr ":py:obj:`context `\\" msgid "Getter for `Context` client attribute." msgstr "" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.client.mod.LocalDpMod.rst -#: ../../source/ref-api/flwr.common.Array.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Context.rst -#: ../../source/ref-api/flwr.common.Error.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.Metadata.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst #: ../../source/ref-api/flwr.common.Parameters.rst:2 -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.common.RecordSet.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.ServerAppComponents.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.Bulyan.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyClientSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideAdaptiveClipping.rst -#: ../../source/ref-api/flwr.server.strategy.DifferentialPrivacyServerSideFixedClipping.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdagrad.rst -#: ../../source/ref-api/flwr.server.strategy.FedAdam.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgAndroid.rst -#: ../../source/ref-api/flwr.server.strategy.FedAvgM.rst -#: ../../source/ref-api/flwr.server.strategy.FedOpt.rst -#: ../../source/ref-api/flwr.server.strategy.FedProx.rst -#: ../../source/ref-api/flwr.server.strategy.FedTrimmedAvg.rst -#: ../../source/ref-api/flwr.server.strategy.FedYogi.rst -#: ../../source/ref-api/flwr.server.strategy.Krum.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggPlusWorkflow.rst -#: ../../source/ref-api/flwr.server.workflow.SecAggWorkflow.rst -#: ../../source/ref-api/flwr.simulation.run_simulation.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst #: flwr.client.app.start_client flwr.client.app.start_numpy_client -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: 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 +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.context.Context flwr.common.message.Error +#: flwr.common.message.Message flwr.common.message.Message.create_error_reply +#: flwr.common.message.Message.create_reply flwr.common.message.Metadata +#: flwr.common.record.configsrecord.ConfigsRecord +#: flwr.common.record.metricsrecord.MetricsRecord +#: flwr.common.record.parametersrecord.Array +#: flwr.common.record.parametersrecord.ParametersRecord +#: flwr.common.record.recordset.RecordSet flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.ClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.unregister +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.serverapp_components.ServerAppComponents +#: flwr.server.strategy.bulyan.Bulyan +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyClientSideAdaptiveClipping +#: flwr.server.strategy.dp_adaptive_clipping.DifferentialPrivacyServerSideAdaptiveClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyClientSideFixedClipping +#: flwr.server.strategy.dp_fixed_clipping.DifferentialPrivacyServerSideFixedClipping +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.fedadagrad.FedAdagrad +#: flwr.server.strategy.fedadam.FedAdam flwr.server.strategy.fedavg.FedAvg +#: flwr.server.strategy.fedavg_android.FedAvgAndroid +#: flwr.server.strategy.fedavgm.FedAvgM flwr.server.strategy.fedopt.FedOpt +#: flwr.server.strategy.fedprox.FedProx +#: flwr.server.strategy.fedtrimmedavg.FedTrimmedAvg +#: flwr.server.strategy.fedyogi.FedYogi flwr.server.strategy.krum.Krum +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters +#: flwr.server.workflow.secure_aggregation.secagg_workflow.SecAggWorkflow +#: flwr.server.workflow.secure_aggregation.secaggplus_workflow.SecAggPlusWorkflow +#: flwr.simulation.run_simulation.run_simulation of msgid "Parameters" msgstr "参数" @@ -9138,21 +9386,31 @@ msgid "" "customize the local evaluation process." msgstr "评估指令包含从服务器接收的(全局)模型参数,以及用于定制本地评估流程的配置值字典。" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.ConfigsRecord.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.common.MetricsRecord.rst -#: ../../source/ref-api/flwr.common.ParametersRecord.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.evaluate +#: flwr.client.numpy_client.NumPyClient.fit +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of msgid "Returns" msgstr "返回" @@ -9162,18 +9420,29 @@ msgid "" "details such as the number of local data examples used for evaluation." msgstr "评估结果包含本地数据集上的损失值和其他详细信息,如用于评估的本地数据的数量。" -#: ../../source/ref-api/flwr.client.Client.rst -#: ../../source/ref-api/flwr.client.NumPyClient.rst -#: ../../source/ref-api/flwr.common.Message.rst -#: ../../source/ref-api/flwr.server.ClientManager.rst -#: ../../source/ref-api/flwr.server.Driver.rst -#: ../../source/ref-api/flwr.server.SimpleClientManager.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgAdaptive.rst -#: ../../source/ref-api/flwr.server.strategy.DPFedAvgFixed.rst -#: ../../source/ref-api/flwr.server.strategy.Strategy.rst -#: ../../source/ref-api/flwr.simulation.start_simulation.rst -#: flwr.server.app.start_server -#: flwr.server.driver.driver.Driver.send_and_receive of +#: flwr.client.client.Client.evaluate flwr.client.client.Client.fit +#: flwr.client.client.Client.get_parameters +#: flwr.client.client.Client.get_properties +#: flwr.client.numpy_client.NumPyClient.get_parameters +#: flwr.client.numpy_client.NumPyClient.get_properties +#: flwr.common.message.Message.create_reply flwr.server.app.start_server +#: flwr.server.client_manager.ClientManager.num_available +#: flwr.server.client_manager.ClientManager.register +#: flwr.server.client_manager.SimpleClientManager.num_available +#: flwr.server.client_manager.SimpleClientManager.register +#: flwr.server.client_manager.SimpleClientManager.wait_for +#: flwr.server.driver.driver.Driver.create_message +#: flwr.server.driver.driver.Driver.pull_messages +#: flwr.server.driver.driver.Driver.push_messages +#: flwr.server.driver.driver.Driver.send_and_receive +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_evaluate +#: flwr.server.strategy.dpfedavg_fixed.DPFedAvgFixed.configure_fit +#: flwr.server.strategy.strategy.Strategy.aggregate_evaluate +#: flwr.server.strategy.strategy.Strategy.aggregate_fit +#: flwr.server.strategy.strategy.Strategy.configure_evaluate +#: flwr.server.strategy.strategy.Strategy.configure_fit +#: flwr.server.strategy.strategy.Strategy.evaluate +#: flwr.server.strategy.strategy.Strategy.initialize_parameters of msgid "Return type" msgstr "返回类型" @@ -9537,6 +9806,11 @@ msgstr "客户端逻辑" 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 "" @@ -9724,10 +9998,6 @@ msgstr "" msgid "make\\_ffn" msgstr "" -#: flwr.client.mod.utils.make_ffn:1 of -msgid "." -msgstr "" - #: ../../source/ref-api/flwr.client.mod.message_size_mod.rst:2 msgid "message\\_size\\_mod" msgstr "" @@ -9756,16 +10026,6 @@ msgstr "" msgid "secaggplus\\_mod" msgstr "工作流程" -#: ../../source/ref-api/flwr.client.run_client_app.rst:2 -#, fuzzy -msgid "run\\_client\\_app" -msgstr "run\\_client\\_app" - -#: ../../source/ref-api/flwr.client.run_supernode.rst:2 -#, fuzzy -msgid "run\\_supernode" -msgstr "flower-superlink" - #: ../../source/ref-api/flwr.client.start_client.rst:2 #, fuzzy msgid "start\\_client" @@ -10643,14 +10903,9 @@ msgstr "返回存储在此对象中的字节数。" #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" -#: collections.abc.Mapping.get:1 -#: collections.abc.MutableMapping.clear:1::1 of -msgid "Retrieve the corresponding layout by the string key." -msgstr "" - #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy msgid ":py:obj:`items `\\ \\(\\)" @@ -10716,22 +10971,6 @@ msgstr ":py:obj:`values `\\ \\(\\)" msgid "This function counts booleans as occupying 1 Byte." msgstr "该函数将布尔值计算为占用 1 个字节。" -#: collections.abc.Mapping.get:3 of -msgid "" -"When there isn't an exact match, all the existing keys in the layout map " -"will be treated as a regex and map against the input key again. The first" -" match will be returned, based on the key insertion order. Return None if" -" there isn't any match found." -msgstr "" - -#: collections.abc.Mapping.get:8 of -msgid "the string key as the query for the layout." -msgstr "" - -#: collections.abc.Mapping.get:10 of -msgid "Corresponding layout based on the query." -msgstr "" - #: ../../source/ref-api/flwr.common.Context.rst:2 #, fuzzy msgid "Context" @@ -11620,7 +11859,7 @@ msgstr "编码" msgid "The encoding in which to encode the string." msgstr "字符串的编码。" -#: flwr.common.EventType.encode:5 of +#: flwr.common.EventType.encode:9 of #, fuzzy msgid "errors" msgstr "错误" @@ -11832,7 +12071,7 @@ msgid "" "string." msgstr "如果字符串以后缀字符串结尾,且后缀不为空,则返回 string[:-len(suffix)]。否则,返回原始字符串的副本。" -#: flwr.common.EventType.replace:3 of +#: flwr.common.EventType.replace:5 of #, fuzzy msgid "count" msgstr "背景" @@ -11874,7 +12113,7 @@ msgid "" "strings and the original string." msgstr "如果找不到分隔符,则返回一个包含两个空字符串和原始字符串的 3 元组。" -#: flwr.common.EventType.rsplit:3 flwr.common.EventType.split:3 of +#: flwr.common.EventType.rsplit:7 flwr.common.EventType.split:7 of #, fuzzy msgid "sep" msgstr "sep" @@ -11892,7 +12131,7 @@ msgid "" " empty strings from the result." msgstr "当设置为 \"无\"(默认值)时,将对任何空白字符(包括 \\n \\r \\t \\f 和空格)进行分割,并从结果中剔除空字符串。" -#: flwr.common.EventType.rsplit:9 flwr.common.EventType.split:9 of +#: flwr.common.EventType.rsplit:11 flwr.common.EventType.split:11 of #, fuzzy msgid "maxsplit" msgstr "最大分割" @@ -11942,7 +12181,7 @@ msgid "" "remaining cased characters have lower case." msgstr "更具体地说,单词以大写字母开头,其余所有大小写字符均为小写。" -#: flwr.common.EventType.translate:3 of +#: flwr.common.EventType.translate:5 of #, fuzzy msgid "table" msgstr "数据库" @@ -12451,7 +12690,7 @@ msgstr ":py:obj:`count_bytes `\\ \\(\\)" #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid ":py:obj:`get `\\ \\(key\\[\\, default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" #: collections.abc.MutableMapping.clear:1::1 of @@ -12607,9 +12846,7 @@ msgstr ":py:obj:`count_bytes `\\ \\(\\ #: collections.abc.MutableMapping.clear:1::1 of #, fuzzy -msgid "" -":py:obj:`get `\\ \\(key\\[\\, " -"default\\]\\)" +msgid ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" msgstr ":py:obj:`get `\\ \\(k\\[\\,d\\]\\)" #: collections.abc.MutableMapping.clear:1::1 of @@ -13005,7 +13242,7 @@ msgstr "使用部分可用客户进行评估。" #: ../../source/ref-api/flwr.server.rst:56::1 #, fuzzy -msgid ":py:obj:`strategy `\\" +msgid ":py:obj:`flwr.server.strategy `\\" msgstr "server.strategy.Strategy" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -13015,7 +13252,7 @@ msgstr "包含策略抽象和不同的实现方法。" #: ../../source/ref-api/flwr.server.rst:56::1 #, fuzzy -msgid ":py:obj:`workflow `\\" +msgid ":py:obj:`flwr.server.workflow `\\" msgstr "server.strategy.Strategy" #: ../../source/ref-api/flwr.server.rst:56::1 @@ -13619,8 +13856,7 @@ msgid "" msgstr "服务器实现,可以是 `flwr.server.Server` 或其子类。如果没有提供实例,`start_server` 将创建一个。" #: flwr.server.app.start_server:9 -#: flwr.server.serverapp_components.ServerAppComponents:6 -#: flwr.simulation.app.start_simulation:29 of +#: flwr.server.serverapp_components.ServerAppComponents:6 of msgid "" "Currently supported values are `num_rounds` (int, default: 1) and " "`round_timeout` in seconds (float, default: None)." @@ -13772,16 +14008,6 @@ msgstr "以秒为单位的等待时间,默认为 86400(24 小时)。" msgid "**success**" msgstr "**success**" -#: ../../source/ref-api/flwr.server.run_server_app.rst:2 -#, fuzzy -msgid "run\\_server\\_app" -msgstr "run\\_server\\_app" - -#: ../../source/ref-api/flwr.server.run_superlink.rst:2 -#, fuzzy -msgid "run\\_superlink" -msgstr "flower-superlink" - #: ../../source/ref-api/flwr.server.start_server.rst:2 #, fuzzy msgid "start\\_server" @@ -17774,16 +18000,16 @@ msgstr "使用模拟引擎运行花朵应用程序。" #: ../../source/ref-api/flwr.simulation.rst:18::1 #, fuzzy msgid "" -":py:obj:`start_simulation `\\ \\(\\*\\," -" client\\_fn\\, num\\_clients\\)" +":py:obj:`start_simulation `\\ " +"\\(\\*args\\, \\*\\*kwargs\\)" msgstr "" ":py:obj:`start_simulation `\\ \\(\\*\\," " client\\_fn\\[\\, ...\\]\\)" #: ../../source/ref-api/flwr.simulation.rst:18::1 -#: flwr.simulation.app.start_simulation:1 of -msgid "Start a Ray-based Flower simulation server." -msgstr "启动基于 Ray 的Flower模拟服务器。" +#: flwr.simulation.start_simulation:1 of +msgid "Log error stating that module `ray` could not be imported." +msgstr "" #: ../../source/ref-api/flwr.simulation.run_simulation.rst:2 #, fuzzy @@ -17855,168 +18081,31 @@ msgstr "启用后,将只显示 INFO、WARNING 和 ERROR 日志信息。启用 msgid "start\\_simulation" msgstr "start_simulation" -#: flwr.simulation.app.start_simulation:3 of -#, fuzzy -msgid "" -"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 " -"(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)." -msgstr "" -"创建客户端实例的函数。该函数必须接受一个名为 `cid` 的 `str` 参数。它应返回一个 Client " -"类型的客户端实例。请注意,创建的客户端实例是短暂的,通常在调用一个方法后就会被销毁。由于客户机实例不是长期存在的,它们不应试图在方法调用时携带状态数据。实例所需的任何状态数据(模型、数据集、超参数......)都应在调用" -" `client_fn` 或任何客户端方法(例如,在 `evaluate` 方法中加载评估数据)时(重新)创建。" +#: ../../source/ref-changelog.md:1 +msgid "Changelog" +msgstr "更新日志" -#: flwr.simulation.app.start_simulation:13 of +#: ../../source/ref-changelog.md:3 #, fuzzy -msgid "The total number of clients in this simulation." -msgstr "需要等待的客户数量。" +msgid "v1.11.1 (2024-09-11)" +msgstr "v1.3.0 (2023-02-06)" -#: flwr.simulation.app.start_simulation:15 of -#, fuzzy -msgid "" -"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." -msgstr "" -"列出每个客户的 `client_id`。只有在未设置 `num_clients` " -"时才需要这样做。同时设置`num_clients`和`clients_ids`,且`len(clients_ids)`不等于`num_clients`,会产生错误。" +#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:37 +#: ../../source/ref-changelog.md:141 ../../source/ref-changelog.md:239 +#: ../../source/ref-changelog.md:339 ../../source/ref-changelog.md:403 +#: ../../source/ref-changelog.md:496 ../../source/ref-changelog.md:596 +#: ../../source/ref-changelog.md:680 ../../source/ref-changelog.md:744 +#: ../../source/ref-changelog.md:802 ../../source/ref-changelog.md:871 +#: ../../source/ref-changelog.md:940 +msgid "Thanks to our contributors" +msgstr "感谢我们的贡献者" -#: flwr.simulation.app.start_simulation:21 of -#, fuzzy -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." -msgstr "" -"\"num_gpus\": 0.0` 单个客户端的 CPU 和 GPU 资源。支持的键值为 `num_cpus` 和 `num_gpus`。要了解" -" `num_gpus` 所导致的 GPU 利用率,以及使用自定义资源的情况,请查阅 Ray 文档。" - -#: flwr.simulation.app.start_simulation:26 of -msgid "" -"An implementation of the abstract base class `flwr.server.Server`. If no " -"instance is provided, then `start_server` will create one." -msgstr "抽象基类 `flwr.server.Server`的实现。如果没有提供实例,`start_server` 将创建一个。" - -#: flwr.simulation.app.start_simulation:32 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`." -msgstr "" -"抽象基类 `flwr.server.strategy` 的实现。如果没有提供策略,`start_server` 将使用 " -"`flwr.server.strategy.FedAvg`。" - -#: flwr.simulation.app.start_simulation:36 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`." -msgstr "" -"抽象基类 `flwr.server.ClientManager` 的实现。如果没有提供实现,`start_simulation` 将使用 " -"`flwr.server.client_manager.SimpleClientManager`。" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"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." -msgstr "" -"可选字典,包含调用 `ray.init` 时的参数。如果 ray_init_args 为 None(默认值),则将使用以下默认参数初始化 Ray:" -" { \"ignore_reinit_error\": True, \"include_dashboard\": False } " -"可以使用空字典(ray_init_args={})来防止向 ray.init 传递任何参数。" - -#: flwr.simulation.app.start_simulation:40 of -msgid "" -"Optional dictionary containing arguments for the call to `ray.init`. If " -"ray_init_args is None (the default), Ray will be initialized with the " -"following default args:" -msgstr "可选字典,包含调用 `ray.init` 时的参数。如果 ray_init_args 为 None(默认值),则将使用以下默认参数初始化 Ray:" - -#: flwr.simulation.app.start_simulation:44 of -msgid "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" -msgstr "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" - -#: flwr.simulation.app.start_simulation:46 of -msgid "" -"An empty dictionary can be used (ray_init_args={}) to prevent any " -"arguments from being passed to ray.init." -msgstr "可以使用空字典 (ray_init_args={}) 来防止向 ray.init 传递任何参数。" - -#: flwr.simulation.app.start_simulation:49 of -msgid "" -"Set to True to prevent `ray.shutdown()` in case " -"`ray.is_initialized()=True`." -msgstr "设为 True 可在 `ray.is_initialized()=True` 情况下阻止 `ray.shutdown()` 。" - -#: flwr.simulation.app.start_simulation:51 of -#, fuzzy -msgid "" -"Optionally specify the type of actor to use. The actor object, which " -"persists throughout the simulation, will be the process in charge of " -"executing a ClientApp wrapping input argument `client_fn`." -msgstr "可选择指定要使用的actor类型。actor对象将在整个模拟过程中持续存在,它将是负责运行客户端作业(即其 `fit()`方法)的进程。" - -#: flwr.simulation.app.start_simulation:55 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." -msgstr "如果您想创建自己的 Actor 类,可能需要传递一些输入参数。为此,您可以使用本字典。" - -#: flwr.simulation.app.start_simulation:58 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" -msgstr "" -"(默认:\"DEFAULT\")可选字符串(\"DEFAULT \"或 \"SPREAD\"),供 VCE " -"选择将行为体放置在哪个节点上。如果你是需要更多控制权的高级用户,可以使用低级调度策略将actor固定到特定计算节点(例如,通过 " -"NodeAffinitySchedulingStrategy)。请注意,这是一项高级功能。有关详细信息,请参阅 Ray " -"文档:https://docs.ray.io/en/latest/ray-core/scheduling/index.html" - -#: flwr.simulation.app.start_simulation:67 of -msgid "**hist** -- Object containing metrics from training." -msgstr "**hist** -- 包含训练指标的对象。" - -#: ../../source/ref-changelog.md:1 -msgid "Changelog" -msgstr "更新日志" - -#: ../../source/ref-changelog.md:3 -#, fuzzy -msgid "v1.11.1 (2024-09-11)" -msgstr "v1.3.0 (2023-02-06)" - -#: ../../source/ref-changelog.md:5 ../../source/ref-changelog.md:37 -#: ../../source/ref-changelog.md:141 ../../source/ref-changelog.md:239 -#: ../../source/ref-changelog.md:339 ../../source/ref-changelog.md:403 -#: ../../source/ref-changelog.md:496 ../../source/ref-changelog.md:596 -#: ../../source/ref-changelog.md:680 ../../source/ref-changelog.md:744 -#: ../../source/ref-changelog.md:802 ../../source/ref-changelog.md:871 -#: ../../source/ref-changelog.md:940 -msgid "Thanks to our contributors" -msgstr "感谢我们的贡献者" - -#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:39 -#: ../../source/ref-changelog.md:143 ../../source/ref-changelog.md:241 -#: ../../source/ref-changelog.md:341 ../../source/ref-changelog.md:405 -#: ../../source/ref-changelog.md:498 ../../source/ref-changelog.md:598 -#: ../../source/ref-changelog.md:682 ../../source/ref-changelog.md:746 -#: ../../source/ref-changelog.md:804 +#: ../../source/ref-changelog.md:7 ../../source/ref-changelog.md:39 +#: ../../source/ref-changelog.md:143 ../../source/ref-changelog.md:241 +#: ../../source/ref-changelog.md:341 ../../source/ref-changelog.md:405 +#: ../../source/ref-changelog.md:498 ../../source/ref-changelog.md:598 +#: ../../source/ref-changelog.md:682 ../../source/ref-changelog.md:746 +#: ../../source/ref-changelog.md:804 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):" @@ -18122,13 +18211,6 @@ msgstr "** 添加一个新的 gRPC 选项**([#2197](https://github.com/adap/fl msgid "Incompatible changes" msgstr "不兼容的更改" -#: ../../source/ref-changelog.md:33 ../../source/ref-changelog.md:399 -#: ../../source/ref-changelog.md:676 ../../source/ref-changelog.md:740 -#: ../../source/ref-changelog.md:798 ../../source/ref-changelog.md:867 -#: ../../source/ref-changelog.md:929 -msgid "None" -msgstr "无" - #: ../../source/ref-changelog.md:35 #, fuzzy msgid "v1.11.0 (2024-08-30)" @@ -24877,7 +24959,20 @@ msgid "" "blockchain environment is available here:" msgstr "当然可以。有关在区块链环境中使用 Flower 的可用示例列表,请点击此处:" -#: ../../source/ref-faq.rst:28 +#: ../../source/ref-faq.rst:29 +msgid "`FLock: A Decentralised AI Training Platform `_." +msgstr "" + +#: ../../source/ref-faq.rst:29 +msgid "Contribute to on-chain training the model and earn rewards." +msgstr "" + +#: ../../source/ref-faq.rst:30 +#, fuzzy +msgid "Local blockchain with federated learning simulation." +msgstr "扩大联邦学习的规模" + +#: ../../source/ref-faq.rst:31 msgid "" "`Flower meets Nevermined GitHub Repository `_." @@ -24885,7 +24980,7 @@ msgstr "" "`Flower meets Nevermined GitHub Repository `_." -#: ../../source/ref-faq.rst:29 +#: ../../source/ref-faq.rst:32 msgid "" "`Flower meets Nevermined YouTube video " "`_." @@ -24893,7 +24988,7 @@ msgstr "" "`Flower meets Nevermined YouTube 视频 " "`_." -#: ../../source/ref-faq.rst:30 +#: ../../source/ref-faq.rst:33 #, fuzzy msgid "" "`Flower meets KOSMoS `_." -#: ../../source/ref-faq.rst:31 +#: ../../source/ref-faq.rst:34 msgid "" "`Flower meets Talan blog post `_ 。" -#: ../../source/ref-faq.rst:32 +#: ../../source/ref-faq.rst:35 msgid "" "`Flower meets Talan GitHub Repository " "`_ ." @@ -25163,205 +25258,314 @@ 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." -msgstr "查看此联邦学习快速入门教程,了解如何使用 Flower 和 FastAI 在 CIFAR-10 上训练视觉模型。" - #: ../../source/tutorial-quickstart-fastai.rst:5 msgid "Quickstart fastai" msgstr "快速入门 fastai" -#: ../../source/tutorial-quickstart-fastai.rst:10 -msgid "Let's build a federated learning system using fastai and Flower!" -msgstr "让我们用 fastai 和 Flower 建立一个联邦学习系统!" +#: ../../source/tutorial-quickstart-fastai.rst:7 +#, fuzzy +msgid "" +"In this federated learning tutorial we will learn how to train a " +"SqueezeNet model on MNIST using Flower and fastai. It is recommended to " +"create a virtual environment and run everything within a :doc:`virtualenv" +" `." +msgstr "" +"首先,建议创建一个虚拟环境,并在 `virtualenv `_ 中运行一切。" #: ../../source/tutorial-quickstart-fastai.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +msgid "Then, clone the code example directly from GitHub:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:20 msgid "" -"Please refer to the `full code example " -"`_ " -"to learn more." +"This will create a new directory called `quickstart-fastai` containing " +"the following files:" msgstr "" -"请参阅 `完整代码示例 `_了解更多信息。" + +#: ../../source/tutorial-quickstart-fastai.rst:33 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:33 +#, fuzzy +msgid "Next, activate your environment, then run:" +msgstr "并激活虚拟环境:" + +#: ../../source/tutorial-quickstart-fastai.rst:43 +msgid "" +"This example by default runs the Flower Simulation Engine, creating a " +"federation of 10 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" Let's run the project:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:56 +#: ../../source/tutorial-quickstart-huggingface.rst:65 +#: ../../source/tutorial-quickstart-mlx.rst:64 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:56 +#: ../../source/tutorial-quickstart-pytorch.rst:64 +#: ../../source/tutorial-quickstart-tensorflow.rst:65 +msgid "With default arguments you will see an output like this one:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:100 +#: ../../source/tutorial-quickstart-huggingface.rst:116 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:106 +#: ../../source/tutorial-quickstart-pytorch.rst:105 +#: ../../source/tutorial-quickstart-tensorflow.rst:106 +msgid "" +"You can also override the parameters defined in the " +"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" +msgstr "" + +#: ../../source/tutorial-quickstart-fastai.rst:110 +#, fuzzy +msgid "" +"Check the `source code `_ of this tutorial in ``examples/quickstart-fasai`` " +"in the Flower GitHub repository." +msgstr "" +"此示例的`完整源代码 `_ 可在 :code:`examples/xgboost-quickstart` 中找到。" #: ../../source/tutorial-quickstart-huggingface.rst:-1 +#, fuzzy msgid "" "Check out this Federating Learning quickstart tutorial for using Flower " -"with HuggingFace Transformers in order to fine-tune an LLM." +"with 🤗 HuggingFace Transformers in order to fine-tune an LLM." msgstr "查看此联邦学习 快速入门教程,了解如何使用 Flower 和 HuggingFace Transformers 来微调 LLM。" #: ../../source/tutorial-quickstart-huggingface.rst:5 msgid "Quickstart 🤗 Transformers" msgstr "🤗 Transformers快速入门" -#: ../../source/tutorial-quickstart-huggingface.rst:10 +#: ../../source/tutorial-quickstart-huggingface.rst:7 +#, fuzzy msgid "" -"Let's build a federated learning system using Hugging Face Transformers " -"and Flower!" -msgstr "让我们用Hugging Face Transformers和Flower来构建一个联邦学习系统!" +"In this federated learning tutorial we will learn how to train a large " +"language model (LLM) on the `IMDB " +"`_ dataset using Flower" +" and the 🤗 Hugging Face Transformers library. It is recommended to create" +" a virtual environment and run everything within a :doc:`virtualenv " +"`." +msgstr "" +"首先,建议创建一个虚拟环境,并在 `virtualenv `_ 中运行一切。" -#: ../../source/tutorial-quickstart-huggingface.rst:12 +#: ../../source/tutorial-quickstart-huggingface.rst:14 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." +"Let's use ``flwr new`` to create a complete Flower+🤗 Hugging Face " +"project. It will generate all the files needed to run, by default with " +"the Flower Simulation Engine, a federation of 10 nodes using |fedavg|_ " +"The dataset will be partitioned using |flowerdatasets|_'s " +"|iidpartitioner|_." msgstr "" -"我们将利用Hugging Face技术,使用 Flower 在多个客户端上联邦训练语言模型。更具体地说,我们将对预先训练好的 " -"Transformer 模型(distilBERT)进行微调,以便在 IMDB 评分数据集上进行序列分类。最终目标是检测电影评分是正面还是负面。" - -#: ../../source/tutorial-quickstart-huggingface.rst:18 -msgid "Dependencies" -msgstr "依赖关系" #: ../../source/tutorial-quickstart-huggingface.rst:20 +#: ../../source/tutorial-quickstart-mlx.rst:19 +#: ../../source/tutorial-quickstart-pytorch.rst:19 +#: ../../source/tutorial-quickstart-tensorflow.rst:20 +#, fuzzy 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`:" -msgstr "" -"要学习本教程,您需要安装以下软件包: :code:`datasets`、 :code:`evaluate`、 :code:`flwr`、 " -":code:`torch`和 :code:`transformers`。这可以通过 :code:`pip` 来完成:" +"Now that we have a rough idea of what this example is about, let's get " +"started. First, install Flower in your new environment:" +msgstr "现在,我们已经有了一个大致的概念,让我们开始吧。首先,我们需要安装 Flower。运行:" -#: ../../source/tutorial-quickstart-huggingface.rst:30 -msgid "Standard Hugging Face workflow" -msgstr "标准Hugging Face工作流程" +#: ../../source/tutorial-quickstart-huggingface.rst:28 +msgid "" +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``HuggingFace``), give a name to your " +"project, and type in your developer name:" +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:33 -msgid "Handling the data" -msgstr "处理数据" +#: ../../source/tutorial-quickstart-huggingface.rst:36 +#: ../../source/tutorial-quickstart-mlx.rst:35 +#: ../../source/tutorial-quickstart-pytorch.rst:35 +#: ../../source/tutorial-quickstart-tensorflow.rst:36 +msgid "" +"After running it you'll notice a new directory with your project name has" +" been created. It should have the following structure:" +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:35 +#: ../../source/tutorial-quickstart-huggingface.rst:50 +#: ../../source/tutorial-quickstart-mlx.rst:49 +#: ../../source/tutorial-quickstart-pytorch.rst:49 +#: ../../source/tutorial-quickstart-tensorflow.rst:50 msgid "" -"To fetch the IMDB dataset, we will use Hugging Face's :code:`datasets` " -"library. We then need to tokenize the data and create :code:`PyTorch` " -"dataloaders, this is all done in the :code:`load_data` function:" +"If you haven't yet installed the project and its dependencies, you can do" +" so by:" +msgstr "" + +#: ../../source/tutorial-quickstart-huggingface.rst:58 +#: ../../source/tutorial-quickstart-pytorch.rst:57 +#: ../../source/tutorial-quickstart-tensorflow.rst:58 +msgid "To run the project, do:" +msgstr "" + +#: ../../source/tutorial-quickstart-huggingface.rst:106 +msgid "You can also run the project with GPU as follows:" msgstr "" -"为了获取 IMDB 数据集,我们将使用 Hugging Face 的 :code:`datasets` 库。然后,我们需要对数据进行标记化,并创建" -" :code:`PyTorch` 数据加载器,这些都将在 :code:`load_data` 函数中完成:" -#: ../../source/tutorial-quickstart-huggingface.rst:81 -msgid "Training and testing the model" -msgstr "训练和测试模型" +#: ../../source/tutorial-quickstart-huggingface.rst:113 +msgid "" +"This will use the default arguments where each ``ClientApp`` will use 2 " +"CPUs and at most 4 ``ClientApp``\\s will run in a given GPU." +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:83 +#: ../../source/tutorial-quickstart-huggingface.rst:124 +#: ../../source/tutorial-quickstart-mlx.rst:114 +#: ../../source/tutorial-quickstart-pytorch.rst:113 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:" +"What follows is an explanation of each component in the project you just " +"created: dataset partition, the model, defining the ``ClientApp`` and " +"defining the ``ServerApp``." msgstr "" -"有了创建 trainloader 和 testloader 的方法后,我们就可以进行训练和测试了。这与任何 :code:`PyTorch` " -"训练或测试循环都非常相似:" -#: ../../source/tutorial-quickstart-huggingface.rst:121 -msgid "Creating the model itself" -msgstr "创建模型本身" +#: ../../source/tutorial-quickstart-huggingface.rst:130 +#: ../../source/tutorial-quickstart-mlx.rst:120 +#: ../../source/tutorial-quickstart-pytorch.rst:119 +#: ../../source/tutorial-quickstart-tensorflow.rst:116 +#, fuzzy +msgid "The Data" +msgstr "加载数据" -#: ../../source/tutorial-quickstart-huggingface.rst:123 +#: ../../source/tutorial-quickstart-huggingface.rst:132 msgid "" -"To create the model itself, we will just load the pre-trained distillBERT" -" model using Hugging Face’s :code:`AutoModelForSequenceClassification` :" +"This tutorial uses |flowerdatasets|_ to easily download and partition the" +" `IMDB `_ dataset. In " +"this example you'll make use of the |iidpartitioner|_ to generate " +"``num_partitions`` partitions. You can choose |otherpartitioners|_ " +"available in Flower Datasets. To tokenize the text, we will also load the" +" tokenizer from the pre-trained Transformer model that we'll use during " +"training - more on that in the next section. Each ``ClientApp`` will call" +" this function to create dataloaders with the data that correspond to " +"their data partition." msgstr "" -"要创建模型本身,我们只需使用 Hugging Face 的 :code:`AutoModelForSequenceClassification` " -"加载预训练的 distillBERT 模型:" -#: ../../source/tutorial-quickstart-huggingface.rst:136 -msgid "Federating the example" -msgstr "将示例联邦化" +#: ../../source/tutorial-quickstart-huggingface.rst:178 +#: ../../source/tutorial-quickstart-mlx.rst:164 +#: ../../source/tutorial-quickstart-pytorch.rst:157 +#: ../../source/tutorial-quickstart-tensorflow.rst:145 +#, fuzzy +msgid "The Model" +msgstr "训练模型" -#: ../../source/tutorial-quickstart-huggingface.rst:139 -msgid "Creating the IMDBClient" -msgstr "创建 IMDBClient" +#: ../../source/tutorial-quickstart-huggingface.rst:180 +#, fuzzy +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 (|berttiny|_) for sequence " +"classification over the dataset of IMDB ratings. The end goal is to " +"detect if a movie rating is positive or negative. If you have access to " +"larger GPUs, feel free to use larger models!" +msgstr "" +"我们将利用Hugging Face技术,使用 Flower 在多个客户端上联邦训练语言模型。更具体地说,我们将对预先训练好的 " +"Transformer 模型(distilBERT)进行微调,以便在 IMDB 评分数据集上进行序列分类。最终目标是检测电影评分是正面还是负面。" -#: ../../source/tutorial-quickstart-huggingface.rst:141 +#: ../../source/tutorial-quickstart-huggingface.rst:193 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:" +"Note that here, ``model_name`` is a string that will be loaded from the " +"``Context`` in the ClientApp and ServerApp." msgstr "" -"要将我们的示例联邦到多个客户端,我们首先需要编写 Flower 客户端类(继承自 " -":code:`flwr.client.NumPyClient`)。这很容易,因为我们的模型是一个标准的 :code:`PyTorch` 模型:" -#: ../../source/tutorial-quickstart-huggingface.rst:169 +#: ../../source/tutorial-quickstart-huggingface.rst:196 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." +"In addition to loading the pretrained model weights and architecture, we " +"also include two utility functions to perform both training (i.e. " +"``train()``) and evaluation (i.e. ``test()``) using the above model. " +"These functions should look fairly familiar if you have some prior " +"experience with PyTorch. Note these functions do not have anything " +"specific to Flower. That being said, the training function will normally " +"be called, as we'll see later, from a Flower client passing its own data." +" In summary, your clients can use standard training/testing functions to " +"perform local training or evaluation:" msgstr "" -":code:`get_parameters` " -"函数允许服务器获取客户端的参数。相反,:code:`set_parameters`函数允许服务器将其参数发送给客户端。最后,:code:`fit`函数在本地为客户端训练模型,:code:`evaluate`函数在本地测试模型并返回相关指标。" -#: ../../source/tutorial-quickstart-huggingface.rst:175 -msgid "Starting the server" -msgstr "启动服务器" +#: ../../source/tutorial-quickstart-huggingface.rst:239 +#: ../../source/tutorial-quickstart-mlx.rst:210 +#: ../../source/tutorial-quickstart-pytorch.rst:234 +#: ../../source/tutorial-quickstart-tensorflow.rst:176 +#, fuzzy +msgid "The ClientApp" +msgstr "客户端" -#: ../../source/tutorial-quickstart-huggingface.rst:177 +#: ../../source/tutorial-quickstart-huggingface.rst:241 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:" +"The main changes we have to make to use 🤗 Hugging Face with Flower will " +"be found in the ``get_weights()`` and ``set_weights()`` functions. Under " +"the hood, the ``transformers`` library uses PyTorch, which means we can " +"reuse the ``get_weights()`` and ``set_weights()`` code that we defined in" +" the :doc:`Quickstart PyTorch ` tutorial. As" +" a reminder, in ``get_weights()``, PyTorch model parameters are extracted" +" and represented as a list of NumPy arrays. The ``set_weights()`` " +"function that's the opposite: given a list of NumPy arrays it applies " +"them to an existing PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -"现在我们有了实例化客户端的方法,我们需要创建服务器,以便汇总结果。使用 Flower,首先选择一个策略(这里我们使用 " -":code:`FedAvg`,它将把全局模型参数定义为每轮所有客户端模型参数的平均值),然后使用 " -":code:`flwr.server.start_server`函数,就可以非常轻松地完成这项工作:" -#: ../../source/tutorial-quickstart-huggingface.rst:205 +#: ../../source/tutorial-quickstart-huggingface.rst:254 +#: ../../source/tutorial-quickstart-pytorch.rst:245 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 specific implementation of ``get_weights()`` and ``set_weights()`` " +"depends on the type of models you use. The ones shown below work for a " +"wide range of PyTorch models but you might need to adjust them if you " +"have more exotic model architectures." msgstr "" -"使用 :code:`weighted_average` " -"函数是为了提供一种方法来汇总分布在客户端的指标(基本上,这可以让我们显示每一轮的平均精度和损失值)。" - -#: ../../source/tutorial-quickstart-huggingface.rst:209 -msgid "Putting everything together" -msgstr "把所有东西放在一起" -#: ../../source/tutorial-quickstart-huggingface.rst:211 -msgid "We can now start client instances using:" -msgstr "现在我们可以使用:" +#: ../../source/tutorial-quickstart-huggingface.rst:269 +#: ../../source/tutorial-quickstart-pytorch.rst:261 +msgid "" +"The rest of the functionality is directly inspired by the centralized " +"case. The ``fit()`` method in the client trains the model using the local" +" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " +"model received on a held-out validation set that the client might have:" +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:221 +#: ../../source/tutorial-quickstart-huggingface.rst:296 msgid "" -"And they will be able to connect to the server and start the federated " -"training." -msgstr "他们就能连接到服务器,开始联邦训练。" +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparemeters defined in your " +"``pyproject.toml`` to configure the run. In this tutorial we access the " +"``local-epochs`` setting to control the number of epochs a ``ClientApp`` " +"will perform when running the ``fit()`` method. You could define " +"additional hyperparameters in ``pyproject.toml`` and access them here." +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:223 +#: ../../source/tutorial-quickstart-huggingface.rst:330 +#: ../../source/tutorial-quickstart-mlx.rst:376 +#: ../../source/tutorial-quickstart-pytorch.rst:321 +#: ../../source/tutorial-quickstart-tensorflow.rst:245 #, fuzzy +msgid "The ServerApp" +msgstr "服务器" + +#: ../../source/tutorial-quickstart-huggingface.rst:332 msgid "" -"If you want to check out everything put together, you should check out " -"the `full code example `_ ." +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"|serverappcomponents|_ as opposed to a |client|_ In this example we use " +"the `FedAvg` strategy. To it we pass a randomly initialized model that " +"will server as the global model to federated. Note that the value of " +"``fraction_fit`` is read from the run config. You can find the default " +"value defined in the ``pyproject.toml``." msgstr "" -"如果您想查看所有内容,请查看完整的代码示例: [https://github.com/adap/flower/tree/main/examples" -"/quickstart-" -"huggingface](https://github.com/adap/flower/tree/main/examples" -"/quickstart-huggingface)." -#: ../../source/tutorial-quickstart-huggingface.rst:226 +#: ../../source/tutorial-quickstart-huggingface.rst:371 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." -msgstr "当然,这只是一个非常基本的示例,还可以添加或修改很多内容,只是为了展示我们可以如何简单地使用 Flower 联合Hugging Face的工作流程。" +"Congratulations! You've successfully built and run your first federated " +"learning system for an LLM." +msgstr "" -#: ../../source/tutorial-quickstart-huggingface.rst:229 +#: ../../source/tutorial-quickstart-huggingface.rst:376 msgid "" -"Note that in this example we used :code:`PyTorch`, but we could have very" -" well used :code:`TensorFlow`." -msgstr "请注意,在本例中我们使用了 :code:`PyTorch`,但也完全可以使用 :code:`TensorFlow`。" +"Check the source code of the extended version of this tutorial in " +"|quickstart_hf_link|_ in the Flower GitHub repository. For a " +"comprehensive example of a federated fine-tuning of an LLM with Flower, " +"refer to the |flowertune|_ example in the Flower GitHub repository." +msgstr "" #: ../../source/tutorial-quickstart-ios.rst:-1 msgid "" @@ -25419,7 +25623,6 @@ msgstr "或者Poetry:" #: ../../source/tutorial-quickstart-ios.rst:34 #: ../../source/tutorial-quickstart-scikitlearn.rst:40 -#: ../../source/tutorial-quickstart-tensorflow.rst:29 #: ../../source/tutorial-quickstart-xgboost.rst:55 msgid "Flower Client" msgstr "Flower 客户端" @@ -25511,13 +25714,11 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:129 #: ../../source/tutorial-quickstart-scikitlearn.rst:167 -#: ../../source/tutorial-quickstart-tensorflow.rst:98 #: ../../source/tutorial-quickstart-xgboost.rst:341 msgid "Flower Server" msgstr "Flower 服务器" #: ../../source/tutorial-quickstart-ios.rst:131 -#: ../../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 " @@ -25528,12 +25729,10 @@ msgstr "" #: ../../source/tutorial-quickstart-ios.rst:142 #: ../../source/tutorial-quickstart-scikitlearn.rst:230 -#: ../../source/tutorial-quickstart-tensorflow.rst:112 msgid "Train the model, federated!" msgstr "联邦训练模型!" #: ../../source/tutorial-quickstart-ios.rst:144 -#: ../../source/tutorial-quickstart-tensorflow.rst:114 #: ../../source/tutorial-quickstart-xgboost.rst:567 msgid "" "With both client and server ready, we can now run everything and see " @@ -25736,7 +25935,7 @@ msgstr "" ":code:`Client`更容易实现,因为它避免了一些不必要的操作。:code:`FlowerClient` " "需要实现四个方法,两个用于获取/设置模型参数,一个用于训练模型,一个用于测试模型:" -#: ../../source/tutorial-quickstart-jax.rst:165 +#: ../../source/tutorial-quickstart-jax.rst:167 msgid ":code:`set_parameters (optional)`" msgstr ":code:`set_parameters (可选)`" @@ -25833,14 +26032,6 @@ msgid "" "api/flwr_datasets.partitioner.IidPartitioner.html#flwr_datasets.partitioner.IidPartitioner>`_." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:19 -#: ../../source/tutorial-quickstart-pytorch.rst:19 -#, fuzzy -msgid "" -"Now that we have a rough idea of what this example is about, let's get " -"started. First, install Flower in your new environment:" -msgstr "现在,我们已经有了一个大致的概念,让我们开始吧。首先,我们需要安装 Flower。运行:" - #: ../../source/tutorial-quickstart-mlx.rst:27 msgid "" "Then, run the command below. You will be prompted to select of the " @@ -25848,66 +26039,27 @@ msgid "" "type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:35 -#: ../../source/tutorial-quickstart-pytorch.rst:35 -msgid "" -"After running it you'll notice a new directory with your project name has" -" been created. It should have the following structure:" -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:49 -#: ../../source/tutorial-quickstart-pytorch.rst:49 -msgid "" -"If you haven't yet installed the project and its dependencies, you can do" -" so by:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:57 msgid "To run the project do:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:64 -#: ../../source/tutorial-quickstart-pytorch.rst:64 -msgid "With default arguments you will see an output like this one:" -msgstr "" - #: ../../source/tutorial-quickstart-mlx.rst:106 msgid "" "You can also override the parameters defined in " "``[tool.flwr.app.config]`` section in the ``pyproject.toml`` like this:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:114 -#: ../../source/tutorial-quickstart-pytorch.rst:113 +#: ../../source/tutorial-quickstart-mlx.rst:122 msgid "" -"What follows is an explanation of each component in the project you just " -"created: dataset partition, the model, defining the ``ClientApp`` and " -"defining the ``ServerApp``." +"We will use `Flower Datasets `_ to " +"easily download and partition the `MNIST` dataset. In this example you'll" +" make use of the `IidPartitioner `_" +" to generate `num_partitions` partitions. You can choose `other " +"partitioners `_ available in Flower Datasets:" msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:120 -#: ../../source/tutorial-quickstart-pytorch.rst:119 -#, fuzzy -msgid "The Data" -msgstr "加载数据" - -#: ../../source/tutorial-quickstart-mlx.rst:122 -msgid "" -"We will use `Flower Datasets `_ to " -"easily download and partition the `MNIST` dataset. In this example you'll" -" make use of the `IidPartitioner `_" -" to generate `num_partitions` partitions. You can choose `other " -"partitioners `_ available in Flower Datasets:" -msgstr "" - -#: ../../source/tutorial-quickstart-mlx.rst:164 -#: ../../source/tutorial-quickstart-pytorch.rst:157 -#, fuzzy -msgid "The Model" -msgstr "训练模型" - #: ../../source/tutorial-quickstart-mlx.rst:166 msgid "" "We define the model as in the `centralized MLX example " @@ -25921,12 +26073,6 @@ msgid "" "over batches." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:210 -#: ../../source/tutorial-quickstart-pytorch.rst:234 -#, fuzzy -msgid "The ClientApp" -msgstr "客户端" - #: ../../source/tutorial-quickstart-mlx.rst:212 msgid "" "The main changes we have to make to use `MLX` with `Flower` will be found" @@ -25995,12 +26141,6 @@ msgid "" "method." msgstr "" -#: ../../source/tutorial-quickstart-mlx.rst:376 -#: ../../source/tutorial-quickstart-pytorch.rst:321 -#, fuzzy -msgid "The ServerApp" -msgstr "服务器" - #: ../../source/tutorial-quickstart-mlx.rst:378 msgid "" "To construct a ``ServerApp``, we define a ``server_fn()`` callback with " @@ -26014,6 +26154,7 @@ msgstr "" #: ../../source/tutorial-quickstart-mlx.rst:402 #: ../../source/tutorial-quickstart-pytorch.rst:360 +#: ../../source/tutorial-quickstart-tensorflow.rst:279 msgid "" "Congratulations! You've successfully built and run your first federated " "learning system." @@ -26088,16 +26229,6 @@ msgid "" "and type in your developer name:" msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:57 -msgid "To run the project, do:" -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:105 -msgid "" -"You can also override the parameters defined in the " -"``[tool.flwr.app.config]`` section in ``pyproject.toml`` like this:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:121 msgid "" "This tutorial uses `Flower Datasets `_ " @@ -26141,22 +26272,6 @@ msgid "" "PyTorch model. Doing this in fairly easy in PyTorch." msgstr "" -#: ../../source/tutorial-quickstart-pytorch.rst:245 -msgid "" -"The specific implementation of ``get_weights()`` and ``set_weights()`` " -"depends on the type of models you use. The ones shown below work for a " -"wide range of PyTorch models but you might need to adjust them if you " -"have more exotic model architectures." -msgstr "" - -#: ../../source/tutorial-quickstart-pytorch.rst:261 -msgid "" -"The rest of the functionality is directly inspired by the centralized " -"case. The ``fit()`` method in the client trains the model using the local" -" dataset. Similarly, the ``evaluate()`` method is used to evaluate the " -"model received on a held-out validation set that the client might have:" -msgstr "" - #: ../../source/tutorial-quickstart-pytorch.rst:294 msgid "" "Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " @@ -26193,6 +26308,7 @@ msgstr "" "quickstart/>`_ 可在 :code:`examples/xgboost-quickstart` 中找到。" #: ../../source/tutorial-quickstart-pytorch.rst:372 +#: ../../source/tutorial-quickstart-tensorflow.rst:295 #, fuzzy msgid "Video tutorial" msgstr "教程" @@ -26204,30 +26320,53 @@ msgid "" "that shows the new APIs (as the content above does)" 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." -msgstr "查看此联邦学习快速入门教程,了解如何使用 Flower 和 PyTorch Lightning 在 MNIST 上训练自动编码器模型。" - #: ../../source/tutorial-quickstart-pytorch-lightning.rst:5 msgid "Quickstart PyTorch Lightning" msgstr "快速入门 PyTorch Lightning" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:10 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:7 +#, fuzzy +msgid "" +"In this federated learning tutorial we will learn how to train an " +"AutoEncoder model on MNIST using Flower and PyTorch Lightning. It is " +"recommended to create a virtual environment and run everything within a " +":doc:`virtualenv `." +msgstr "" +"首先,建议创建一个虚拟环境,并在 `virtualenv `_ 中运行一切。" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:20 msgid "" -"Let's build a horizontal federated learning system using PyTorch " -"Lightning and Flower!" -msgstr "让我们使用 PyTorch Lightning 和 Flower 构建一个水平联邦学习系统!" +"This will create a new directory called `quickstart-pytorch-lightning` " +"containing the following files:" +msgstr "" -#: ../../source/tutorial-quickstart-pytorch-lightning.rst:12 +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:43 msgid "" -"Please refer to the `full code example " -"`_ to learn more." +"By default, Flower Simulation Engine will be started and it will create a" +" federation of 4 nodes using `FedAvg `_ " +"as the aggregation strategy. The dataset will be partitioned using Flower" +" Dataset's `IidPartitioner `_." +" To run the project, do:" +msgstr "" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:94 +msgid "" +"Each simulated `ClientApp` (two per round) will also log a summary of " +"their local training process. Expect this output to be similar to:" msgstr "" -"请参阅 `完整代码示例 `_ 了解更多信息。" + +#: ../../source/tutorial-quickstart-pytorch-lightning.rst:116 +#, fuzzy +msgid "" +"Check the `source code `_ of this tutorial in ``examples" +"/quickstart-pytorch-lightning`` in the Flower GitHub repository." +msgstr "" +"此示例的`完整源代码 `_ 可在 :code:`examples/xgboost-quickstart` 中找到。" #: ../../source/tutorial-quickstart-scikitlearn.rst:-1 msgid "" @@ -26316,7 +26455,7 @@ msgstr ":code:`set_model_params()`" msgid "Sets the parameters of a :code:`sklearn` LogisticRegression model" msgstr "设置:code:`sklean`的LogisticRegression模型的参数" -#: ../../source/tutorial-quickstart-scikitlearn.rst:49 +#: ../../source/tutorial-quickstart-scikitlearn.rst:50 msgid ":code:`set_initial_params()`" msgstr ":code:`set_initial_params()`" @@ -26387,7 +26526,7 @@ msgstr "" msgid "return the model weight as a list of NumPy ndarrays" msgstr "以 NumPy ndarrays 列表形式返回模型参数" -#: ../../source/tutorial-quickstart-scikitlearn.rst:120 +#: ../../source/tutorial-quickstart-scikitlearn.rst:121 msgid ":code:`set_parameters` (optional)" msgstr ":code:`set_parameters` (可选)" @@ -26499,7 +26638,6 @@ msgid "" msgstr "客户端和服务器都准备就绪后,我们现在就可以运行一切,看看联邦学习的运行情况。联邦学习系统通常有一个服务器和多个客户端。因此,我们必须先启动服务器:" #: ../../source/tutorial-quickstart-scikitlearn.rst:239 -#: ../../source/tutorial-quickstart-tensorflow.rst:122 #: ../../source/tutorial-quickstart-xgboost.rst:575 msgid "" "Once the server is running we can start the clients in different " @@ -26507,7 +26645,6 @@ msgid "" msgstr "服务器运行后,我们就可以在不同终端启动客户端了。打开一个新终端,启动第一个客户端:" #: ../../source/tutorial-quickstart-scikitlearn.rst:246 -#: ../../source/tutorial-quickstart-tensorflow.rst:129 #: ../../source/tutorial-quickstart-xgboost.rst:582 msgid "Open another terminal and start the second client:" msgstr "打开另一台终端,启动第二个客户端:" @@ -26533,121 +26670,117 @@ msgstr "" "mnist>`_ 可以在 :code:`examples/sklearn-logreg-mnist` 中找到。" #: ../../source/tutorial-quickstart-tensorflow.rst:-1 +#, fuzzy msgid "" "Check out this Federated Learning quickstart tutorial for using Flower " -"with TensorFlow to train a MobilNetV2 model on CIFAR-10." +"with TensorFlow to train a CNN model on CIFAR-10." msgstr "查看此联邦学习快速入门教程,了解如何使用 Flower 和 TensorFlow 在 CIFAR-10 上训练 MobilNetV2 模型。" #: ../../source/tutorial-quickstart-tensorflow.rst:5 msgid "Quickstart TensorFlow" msgstr "快速入门 TensorFlow" -#: ../../source/tutorial-quickstart-tensorflow.rst:13 -msgid "Let's build a federated learning system in less than 20 lines of code!" -msgstr "让我们用不到 20 行代码构建一个联邦学习系统!" - -#: ../../source/tutorial-quickstart-tensorflow.rst:15 -msgid "Before Flower can be imported we have to install it:" -msgstr "在导入 Flower 之前,我们必须先安装它:" - -#: ../../source/tutorial-quickstart-tensorflow.rst:21 +#: ../../source/tutorial-quickstart-tensorflow.rst:7 +#, fuzzy msgid "" -"Since we want to use the Keras API of TensorFlow (TF), we have to install" -" TF as well:" -msgstr "由于我们要使用 TensorFlow (TF) 的 Keras API,因此还必须安装 TF:" - -#: ../../source/tutorial-quickstart-tensorflow.rst:31 -msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" -msgstr "接下来,在名为 :code:`client.py` 的文件中导入 Flower 和 TensorFlow:" +"In this tutorial we will learn how to train a Convolutional Neural " +"Network on CIFAR-10 using the Flower framework and TensorFlow. First of " +"all, it is recommended to create a virtual environment and run everything" +" within a :doc:`virtualenv `." +msgstr "" +"首先,建议创建一个虚拟环境,并在 `virtualenv `_ 中运行一切。" -#: ../../source/tutorial-quickstart-tensorflow.rst:38 +#: ../../source/tutorial-quickstart-tensorflow.rst:13 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." +"Let's use `flwr new` to create a complete Flower+TensorFlow project. It " +"will generate all the files needed to run, by default with the Flower " +"Simulation Engine, a federation of 10 nodes using `FedAvg " +"`_. The " +"dataset will be partitioned using Flower Dataset's `IidPartitioner " +"`_." msgstr "" -"我们使用 TF 的 Keras 实用程序加载 CIFAR10,这是一个用于机器学习的流行彩色图像分类数据集。调用 " -":code:`tf.keras.datasets.cifar10.load_data()` 会下载 CIFAR10,将其缓存到本地,然后以 " -"NumPy ndarrays 的形式返回整个训练集和测试集。" -#: ../../source/tutorial-quickstart-tensorflow.rst:47 +#: ../../source/tutorial-quickstart-tensorflow.rst:28 msgid "" -"Next, we need a model. For the purpose of this tutorial, we use " -"MobilNetV2 with 10 output classes:" -msgstr "接下来,我们需要一个模型。在本教程中,我们使用带有 10 个输出类的 MobilNetV2:" +"Then, run the command below. You will be prompted to select one of the " +"available templates (choose ``TensorFlow``), give a name to your project," +" and type in your developer name:" +msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:54 +#: ../../source/tutorial-quickstart-tensorflow.rst:118 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)." +"This tutorial uses `Flower Datasets `_ " +"to easily download and partition the `CIFAR-10` dataset. In this example " +"you'll make use of the `IidPartitioner `_" +" to generate `num_partitions` partitions. You can choose `other " +"partitioners `_ available in Flower Datasets. Each " +"``ClientApp`` will call this function to create the ``NumPy`` arrays that" +" correspond to their data partition." msgstr "" -"Flower 服务器通过一个名为 :code:`Client` " -"的接口与客户端交互。当服务器选择一个特定的客户端进行训练时,它会通过网络发送训练指令。客户端接收到这些指令后,会调用 :code:`Client`" -" 方法之一来运行您的代码(即训练我们之前定义的神经网络)。" -#: ../../source/tutorial-quickstart-tensorflow.rst:60 +#: ../../source/tutorial-quickstart-tensorflow.rst:147 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:" +"Next, we need a model. We defined a simple Convolutional Neural Network " +"(CNN), but feel free to replace it with a more sophisticated model if " +"you'd like:" msgstr "" -"Flower 提供了一个名为 :code:`NumPyClient` 的便捷类,当您的工作负载使用 Keras 时,该类可以更轻松地实现 " -":code:`Client` 接口。:code:`NumPyClient` 接口定义了三个方法,可以通过以下方式实现:" -#: ../../source/tutorial-quickstart-tensorflow.rst:82 +#: ../../source/tutorial-quickstart-tensorflow.rst:178 msgid "" -"We can now create an instance of our class :code:`CifarClient` and add " -"one line to actually run this client:" -msgstr "现在我们可以创建一个 :code:`CifarClient` 类的实例,并添加一行来实际运行该客户端:" +"With `TensorFlow`, we can use the built-in ``get_weights()`` and " +"``set_weights()`` functions, which simplifies the implementation with " +"`Flower`. The rest of the functionality in the ClientApp is directly " +"inspired by the centralized case. The ``fit()`` method in the client " +"trains the model using the local dataset. Similarly, the ``evaluate()`` " +"method is used to evaluate the model received on a held-out validation " +"set that the client might have:" +msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:90 -#, fuzzy +#: ../../source/tutorial-quickstart-tensorflow.rst:212 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." +"Finally, we can construct a ``ClientApp`` using the ``FlowerClient`` " +"defined above by means of a ``client_fn()`` callback. Note that the " +"`context` enables you to get access to hyperparameters defined in your " +"``pyproject.toml`` to configure the run. For example, in this tutorial we" +" access the `local-epochs` setting to control the number of epochs a " +"``ClientApp`` will perform when running the ``fit()`` method, in addition" +" to `batch-size`. You could define additional hyperparameters in " +"``pyproject.toml`` and access them here." msgstr "" -"这就是客户端。我们只需实现 :code:`Client` 或 :code:`NumPyClient` 并调用 " -":code:`fl.client.start_client()` 或 " -":code:`fl.client.start_numpy_client()`。字符串 " -":code:`\"[::]:8080\"`会告诉客户端要连接的服务器。在本例中,我们可以在同一台机器上运行服务器和客户端,因此使用 " -":code:`\"[::]:8080\"。如果我们运行的是真正的联邦工作负载,服务器和客户端运行在不同的机器上,那么需要改变的只是客户端指向的 " -":code:`server_address`。" -#: ../../source/tutorial-quickstart-tensorflow.rst:135 -msgid "Each client will have its own dataset." -msgstr "每个客户都有自己的数据集。" +#: ../../source/tutorial-quickstart-tensorflow.rst:247 +msgid "" +"To construct a ``ServerApp`` we define a ``server_fn()`` callback with an" +" identical signature to that of ``client_fn()`` but the return type is " +"`ServerAppComponents `_ as " +"opposed to a `Client `_. In this example we use the " +"`FedAvg`. To it we pass a randomly initialized model that will serve as " +"the global model to federate." +msgstr "" -#: ../../source/tutorial-quickstart-tensorflow.rst:137 +#: ../../source/tutorial-quickstart-tensorflow.rst:284 +#, fuzzy msgid "" -"You should now see how the training does in the very first terminal (the " -"one that started the server):" -msgstr "现在你应该能在第一个终端(启动服务器的终端)看到训练的效果了:" +"Check the source code of the extended version of this tutorial in " +"|quickstart_tf_link|_ in the Flower GitHub repository." +msgstr "" +"此示例的`完整源代码 `_ 可在 :code:`examples/xgboost-quickstart` 中找到。" -#: ../../source/tutorial-quickstart-tensorflow.rst:169 +#: ../../source/tutorial-quickstart-tensorflow.rst:299 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`." +"The video shown below shows how to setup a TensorFlow + Flower project " +"using our previously recommended APIs. A new video tutorial will be " +"released that shows the new APIs (as the content above does)" msgstr "" -"恭喜您!您已经成功构建并运行了第一个联邦学习系统。`完整的源代码 " -"`_ 可以在 :code:`examples/quickstart-" -"tensorflow/client.py` 中找到。" #: ../../source/tutorial-quickstart-xgboost.rst:-1 msgid "" @@ -28861,7 +28994,7 @@ msgid "" msgstr "在机器学习中,我们有一个模型和数据。模型可以是一个神经网络(如图所示),也可以是其他东西,比如经典的线性回归。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:41 -msgid "|e5918c1c06a4434bbe4bf49235e40059|" +msgid "|e87b69b2ada74ea49412df16f4a0b9cc|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:109 @@ -28876,7 +29009,7 @@ msgid "" msgstr "我们使用数据来训练模型,以完成一项有用的任务。任务可以是检测图像中的物体、转录音频或玩围棋等游戏。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:53 -msgid "|c0165741bd1944f09ec55ce49032377d|" +msgid "|33cacb7d985c4906b348515c1a5cd993|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:111 @@ -28897,7 +29030,7 @@ msgid "" msgstr "它源于智能手机上用户与应用程序的交互、汽车上传感器数据的收集、笔记本电脑上键盘输入的接收,或者智能扬声器上某人试着唱的歌。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:67 -msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +msgid "|cc080a555947492fa66131dc3a967603|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:113 @@ -28915,7 +29048,7 @@ msgstr "" "\"通常不只是一个地方,而是很多地方。它可能是多个运行同一应用程序的设备。但也可能是多个组织,都在为同一任务生成数据。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:79 -msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +msgid "|085c3e0fb8664c6aa06246636524b20b|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:115 @@ -28931,7 +29064,7 @@ msgid "" msgstr "因此,要使用机器学习或任何类型的数据分析,过去使用的方法是在中央服务器上收集所有数据。这个服务器可以在数据中心的某个地方,也可以在云端的某个地方。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:91 -msgid "|74f26ca701254d3db57d7899bd91eb55|" +msgid "|bfe69c74e48c45d49b50251c38c2a019|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:117 @@ -28946,7 +29079,7 @@ msgid "" msgstr "一旦所有数据都收集到一处,我们最终就可以使用机器学习算法在数据上训练我们的模型。这就是我们基本上一直依赖的机器学习方法。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:103 -msgid "|bda79f21f8154258a40e5766b2634ad7|" +msgid "|ebbecd651f0348d99c6511ea859bf4ca|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:119 @@ -28966,7 +29099,7 @@ msgid "" msgstr "我们刚刚看到的经典机器学习方法可以在某些情况下使用。很好的例子包括对假日照片进行分类或分析网络流量。在这些案例中,所有数据自然都可以在中央服务器上获得。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:138 -msgid "|89d30862e62e4f9989e193483a08680a|" +msgid "|163117eb654a4273babba413cf8065f5|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:173 @@ -28981,7 +29114,7 @@ msgid "" msgstr "但这种方法并不适用于许多其他情况。例如,集中服务器上没有数据,或者一台服务器上的数据不足以训练出一个好的模型。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:150 -msgid "|77e9918671c54b4f86e01369c0785ce8|" +msgid "|452ac3ba453b4cd1be27be1ba7560d64|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:175 @@ -29129,7 +29262,7 @@ msgid "" msgstr "我们首先在服务器上初始化模型。这与经典的集中式学习完全相同:我们随机或从先前保存的检查点初始化模型参数。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:210 -msgid "|7e4ccef37cc94148a067107b34eb7447|" +msgid "|f403fcd69e4e44409627e748b404c086|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:307 @@ -29153,7 +29286,7 @@ msgid "" msgstr "接下来,我们会将全局模型的参数发送到连接的客户端节点(如智能手机等边缘设备或企业的服务器)。这是为了确保每个参与节点都使用相同的模型参数开始本地训练。我们通常只使用几个连接节点,而不是所有节点。这样做的原因是,选择越来越多的客户端节点会导致收益递减。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:225 -msgid "|28e47e4cded14479a0846c8e5f22c872|" +msgid "|4b00fe63870145968f8443619a792a42|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:309 @@ -29179,7 +29312,7 @@ msgstr "" "(mini-batches)。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:240 -msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +msgid "|368378731066486fa4397e89bc6b870c|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:311 @@ -29202,7 +29335,7 @@ msgid "" msgstr "经过本地训练后,每个客户节点最初收到的模型参数都会略有不同。参数之所以不同,是因为每个客户端节点的本地数据集中都有不同的数据。然后,客户端节点将这些模型更新发回服务器。它们发送的模型更新既可以是完整的模型参数,也可以只是本地训练过程中积累的梯度。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:255 -msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +msgid "|a66aa83d85bf4ffba7ed660b718066da|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:313 @@ -29248,7 +29381,7 @@ msgstr "" " 100 个示例的 10 倍。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:273 -msgid "|81749d0ac0834c36a83bd38f433fea31|" +msgid "|82324b9af72a4582a81839d55caab767|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:315 @@ -29355,7 +29488,7 @@ msgstr "" "为联邦学习、分析和评估提供了一种统一的方法。它允许用户联邦化任何工作负载、任何 ML 框架和任何编程语言。" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:334 -msgid "|ed9aae51da70428eab7eef32f21e819e|" +msgid "|fbf2da0da3cc4f8ab3b3eff852d80c41|" msgstr "" #: ../../source/tutorial-series-what-is-federated-learning.ipynb:340 @@ -32665,3 +32798,575 @@ msgstr "" #~ msgid "|c00bf2750bc24d229737a0fe1395f0fc|" #~ msgstr "" +#~ msgid "run\\_client\\_app" +#~ msgstr "run\\_client\\_app" + +#~ msgid "run\\_supernode" +#~ msgstr "flower-superlink" + +#~ msgid "Retrieve the corresponding layout by the string key." +#~ msgstr "" + +#~ msgid "" +#~ "When there isn't an exact match, " +#~ "all the existing keys in the " +#~ "layout map will be treated as a" +#~ " regex and map against the input " +#~ "key again. The first match will be" +#~ " returned, based on the key insertion" +#~ " order. Return None if there isn't" +#~ " any match found." +#~ msgstr "" + +#~ msgid "the string key as the query for the layout." +#~ msgstr "" + +#~ msgid "Corresponding layout based on the query." +#~ msgstr "" + +#~ msgid "run\\_server\\_app" +#~ msgstr "run\\_server\\_app" + +#~ msgid "run\\_superlink" +#~ msgstr "flower-superlink" + +#~ msgid "Start a Ray-based Flower simulation server." +#~ msgstr "启动基于 Ray 的Flower模拟服务器。" + +#~ msgid "" +#~ "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 (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)." +#~ msgstr "" +#~ "创建客户端实例的函数。该函数必须接受一个名为 `cid` 的 `str` 参数。它应返回一个" +#~ " Client " +#~ "类型的客户端实例。请注意,创建的客户端实例是短暂的,通常在调用一个方法后就会被销毁。由于客户机实例不是长期存在的,它们不应试图在方法调用时携带状态数据。实例所需的任何状态数据(模型、数据集、超参数......)都应在调用" +#~ " `client_fn` 或任何客户端方法(例如,在 `evaluate` " +#~ "方法中加载评估数据)时(重新)创建。" + +#~ msgid "The total number of clients in this simulation." +#~ msgstr "需要等待的客户数量。" + +#~ msgid "" +#~ "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." +#~ msgstr "" +#~ "列出每个客户的 `client_id`。只有在未设置 `num_clients` " +#~ "时才需要这样做。同时设置`num_clients`和`clients_ids`,且`len(clients_ids)`不等于`num_clients`,会产生错误。" + +#~ 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." +#~ msgstr "" +#~ "\"num_gpus\": 0.0` 单个客户端的 CPU 和 GPU " +#~ "资源。支持的键值为 `num_cpus` 和 `num_gpus`。要了解 " +#~ "`num_gpus` 所导致的 GPU 利用率,以及使用自定义资源的情况,请查阅 Ray" +#~ " 文档。" + +#~ msgid "" +#~ "An implementation of the abstract base" +#~ " class `flwr.server.Server`. If no instance" +#~ " is provided, then `start_server` will " +#~ "create one." +#~ msgstr "抽象基类 `flwr.server.Server`的实现。如果没有提供实例,`start_server` 将创建一个。" + +#~ 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`." +#~ msgstr "" +#~ "抽象基类 `flwr.server.strategy` 的实现。如果没有提供策略,`start_server`" +#~ " 将使用 `flwr.server.strategy.FedAvg`。" + +#~ 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`." +#~ msgstr "" +#~ "抽象基类 `flwr.server.ClientManager` " +#~ "的实现。如果没有提供实现,`start_simulation` 将使用 " +#~ "`flwr.server.client_manager.SimpleClientManager`。" + +#~ msgid "" +#~ "Optional dictionary containing arguments for" +#~ " the call to `ray.init`. If " +#~ "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." +#~ msgstr "" +#~ "可选字典,包含调用 `ray.init` 时的参数。如果 ray_init_args 为" +#~ " None(默认值),则将使用以下默认参数初始化 Ray: { " +#~ "\"ignore_reinit_error\": True, \"include_dashboard\": " +#~ "False } 可以使用空字典(ray_init_args={})来防止向 ray.init " +#~ "传递任何参数。" + +#~ msgid "" +#~ "Optional dictionary containing arguments for" +#~ " the call to `ray.init`. If " +#~ "ray_init_args is None (the default), Ray" +#~ " will be initialized with the " +#~ "following default args:" +#~ msgstr "" +#~ "可选字典,包含调用 `ray.init` 时的参数。如果 ray_init_args 为" +#~ " None(默认值),则将使用以下默认参数初始化 Ray:" + +#~ msgid "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" +#~ msgstr "{ \"ignore_reinit_error\": True, \"include_dashboard\": False }" + +#~ msgid "" +#~ "An empty dictionary can be used " +#~ "(ray_init_args={}) to prevent any arguments" +#~ " from being passed to ray.init." +#~ msgstr "可以使用空字典 (ray_init_args={}) 来防止向 ray.init 传递任何参数。" + +#~ msgid "" +#~ "Set to True to prevent `ray.shutdown()`" +#~ " in case `ray.is_initialized()=True`." +#~ msgstr "设为 True 可在 `ray.is_initialized()=True` 情况下阻止 `ray.shutdown()` 。" + +#~ msgid "" +#~ "Optionally specify the type of actor " +#~ "to use. The actor object, which " +#~ "persists throughout the simulation, will " +#~ "be the process in charge of " +#~ "executing a ClientApp wrapping input " +#~ "argument `client_fn`." +#~ msgstr "可选择指定要使用的actor类型。actor对象将在整个模拟过程中持续存在,它将是负责运行客户端作业(即其 `fit()`方法)的进程。" + +#~ 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." +#~ msgstr "如果您想创建自己的 Actor 类,可能需要传递一些输入参数。为此,您可以使用本字典。" + +#~ 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" +#~ msgstr "" +#~ "(默认:\"DEFAULT\")可选字符串(\"DEFAULT \"或 \"SPREAD\"),供 " +#~ "VCE " +#~ "选择将行为体放置在哪个节点上。如果你是需要更多控制权的高级用户,可以使用低级调度策略将actor固定到特定计算节点(例如,通过 " +#~ "NodeAffinitySchedulingStrategy)。请注意,这是一项高级功能。有关详细信息,请参阅 Ray " +#~ "文档:https://docs.ray.io/en/latest/ray-core/scheduling/index.html" + +#~ msgid "**hist** -- Object containing metrics from training." +#~ msgstr "**hist** -- 包含训练指标的对象。" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with FastAI to train a vision " +#~ "model on CIFAR-10." +#~ msgstr "查看此联邦学习快速入门教程,了解如何使用 Flower 和 FastAI 在 CIFAR-10 上训练视觉模型。" + +#~ msgid "Let's build a federated learning system using fastai and Flower!" +#~ msgstr "让我们用 fastai 和 Flower 建立一个联邦学习系统!" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn more." +#~ msgstr "" +#~ "请参阅 `完整代码示例 " +#~ "`_了解更多信息。" + +#~ msgid "" +#~ "Let's build a federated learning system" +#~ " using Hugging Face Transformers and " +#~ "Flower!" +#~ msgstr "让我们用Hugging Face Transformers和Flower来构建一个联邦学习系统!" + +#~ msgid "Dependencies" +#~ msgstr "依赖关系" + +#~ 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`:" +#~ msgstr "" +#~ "要学习本教程,您需要安装以下软件包: :code:`datasets`、 :code:`evaluate`、 " +#~ ":code:`flwr`、 :code:`torch`和 :code:`transformers`。这可以通过" +#~ " :code:`pip` 来完成:" + +#~ msgid "Standard Hugging Face workflow" +#~ msgstr "标准Hugging Face工作流程" + +#~ msgid "Handling the data" +#~ msgstr "处理数据" + +#~ msgid "" +#~ "To fetch the IMDB dataset, we will" +#~ " use Hugging Face's :code:`datasets` " +#~ "library. We then need to tokenize " +#~ "the data and create :code:`PyTorch` " +#~ "dataloaders, this is all done in " +#~ "the :code:`load_data` function:" +#~ msgstr "" +#~ "为了获取 IMDB 数据集,我们将使用 Hugging Face 的 " +#~ ":code:`datasets` 库。然后,我们需要对数据进行标记化,并创建 :code:`PyTorch` " +#~ "数据加载器,这些都将在 :code:`load_data` 函数中完成:" + +#~ msgid "Training and testing the model" +#~ msgstr "训练和测试模型" + +#~ 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:" +#~ msgstr "" +#~ "有了创建 trainloader 和 testloader " +#~ "的方法后,我们就可以进行训练和测试了。这与任何 :code:`PyTorch` 训练或测试循环都非常相似:" + +#~ msgid "Creating the model itself" +#~ msgstr "创建模型本身" + +#~ msgid "" +#~ "To create the model itself, we " +#~ "will just load the pre-trained " +#~ "distillBERT model using Hugging Face’s " +#~ ":code:`AutoModelForSequenceClassification` :" +#~ msgstr "" +#~ "要创建模型本身,我们只需使用 Hugging Face 的 " +#~ ":code:`AutoModelForSequenceClassification` 加载预训练的 " +#~ "distillBERT 模型:" + +#~ msgid "Creating the IMDBClient" +#~ msgstr "创建 IMDBClient" + +#~ 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:" +#~ msgstr "" +#~ "要将我们的示例联邦到多个客户端,我们首先需要编写 Flower 客户端类(继承自 " +#~ ":code:`flwr.client.NumPyClient`)。这很容易,因为我们的模型是一个标准的 " +#~ ":code:`PyTorch` 模型:" + +#~ 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." +#~ msgstr "" +#~ ":code:`get_parameters` " +#~ "函数允许服务器获取客户端的参数。相反,:code:`set_parameters`函数允许服务器将其参数发送给客户端。最后,:code:`fit`函数在本地为客户端训练模型,:code:`evaluate`函数在本地测试模型并返回相关指标。" + +#~ msgid "Starting the server" +#~ msgstr "启动服务器" + +#~ 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:" +#~ msgstr "" +#~ "现在我们有了实例化客户端的方法,我们需要创建服务器,以便汇总结果。使用 Flower,首先选择一个策略(这里我们使用 " +#~ ":code:`FedAvg`,它将把全局模型参数定义为每轮所有客户端模型参数的平均值),然后使用 " +#~ ":code:`flwr.server.start_server`函数,就可以非常轻松地完成这项工作:" + +#~ 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)." +#~ msgstr "" +#~ "使用 :code:`weighted_average` " +#~ "函数是为了提供一种方法来汇总分布在客户端的指标(基本上,这可以让我们显示每一轮的平均精度和损失值)。" + +#~ msgid "Putting everything together" +#~ msgstr "把所有东西放在一起" + +#~ msgid "We can now start client instances using:" +#~ msgstr "现在我们可以使用:" + +#~ msgid "" +#~ "And they will be able to connect" +#~ " to the server and start the " +#~ "federated training." +#~ msgstr "他们就能连接到服务器,开始联邦训练。" + +#~ msgid "" +#~ "If you want to check out " +#~ "everything put together, you should " +#~ "check out the `full code example " +#~ "`_ ." +#~ msgstr "" +#~ "如果您想查看所有内容,请查看完整的代码示例: " +#~ "[https://github.com/adap/flower/tree/main/examples/quickstart-" +#~ "huggingface](https://github.com/adap/flower/tree/main/examples" +#~ "/quickstart-huggingface)." + +#~ 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." +#~ msgstr "" +#~ "当然,这只是一个非常基本的示例,还可以添加或修改很多内容,只是为了展示我们可以如何简单地使用 Flower " +#~ "联合Hugging Face的工作流程。" + +#~ msgid "" +#~ "Note that in this example we used" +#~ " :code:`PyTorch`, but we could have " +#~ "very well used :code:`TensorFlow`." +#~ msgstr "请注意,在本例中我们使用了 :code:`PyTorch`,但也完全可以使用 :code:`TensorFlow`。" + +#~ msgid "" +#~ "Check out this Federated Learning " +#~ "quickstart tutorial for using Flower " +#~ "with PyTorch Lightning to train an " +#~ "Auto Encoder model on MNIST." +#~ msgstr "查看此联邦学习快速入门教程,了解如何使用 Flower 和 PyTorch Lightning 在 MNIST 上训练自动编码器模型。" + +#~ msgid "" +#~ "Let's build a horizontal federated " +#~ "learning system using PyTorch Lightning " +#~ "and Flower!" +#~ msgstr "让我们使用 PyTorch Lightning 和 Flower 构建一个水平联邦学习系统!" + +#~ msgid "" +#~ "Please refer to the `full code " +#~ "example `_ to learn " +#~ "more." +#~ msgstr "" +#~ "请参阅 `完整代码示例 " +#~ "`_ 了解更多信息。" + +#~ msgid "Let's build a federated learning system in less than 20 lines of code!" +#~ msgstr "让我们用不到 20 行代码构建一个联邦学习系统!" + +#~ msgid "Before Flower can be imported we have to install it:" +#~ msgstr "在导入 Flower 之前,我们必须先安装它:" + +#~ msgid "" +#~ "Since we want to use the Keras " +#~ "API of TensorFlow (TF), we have to" +#~ " install TF as well:" +#~ msgstr "由于我们要使用 TensorFlow (TF) 的 Keras API,因此还必须安装 TF:" + +#~ msgid "Next, in a file called :code:`client.py`, import Flower and TensorFlow:" +#~ msgstr "接下来,在名为 :code:`client.py` 的文件中导入 Flower 和 TensorFlow:" + +#~ 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." +#~ msgstr "" +#~ "我们使用 TF 的 Keras 实用程序加载 " +#~ "CIFAR10,这是一个用于机器学习的流行彩色图像分类数据集。调用 " +#~ ":code:`tf.keras.datasets.cifar10.load_data()` 会下载 " +#~ "CIFAR10,将其缓存到本地,然后以 NumPy ndarrays 的形式返回整个训练集和测试集。" + +#~ msgid "" +#~ "Next, we need a model. For the " +#~ "purpose of this tutorial, we use " +#~ "MobilNetV2 with 10 output classes:" +#~ msgstr "接下来,我们需要一个模型。在本教程中,我们使用带有 10 个输出类的 MobilNetV2:" + +#~ 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)." +#~ msgstr "" +#~ "Flower 服务器通过一个名为 :code:`Client` " +#~ "的接口与客户端交互。当服务器选择一个特定的客户端进行训练时,它会通过网络发送训练指令。客户端接收到这些指令后,会调用 " +#~ ":code:`Client` 方法之一来运行您的代码(即训练我们之前定义的神经网络)。" + +#~ 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:" +#~ msgstr "" +#~ "Flower 提供了一个名为 :code:`NumPyClient` 的便捷类,当您的工作负载使用" +#~ " Keras 时,该类可以更轻松地实现 :code:`Client` " +#~ "接口。:code:`NumPyClient` 接口定义了三个方法,可以通过以下方式实现:" + +#~ msgid "" +#~ "We can now create an instance of" +#~ " our class :code:`CifarClient` and add " +#~ "one line to actually run this " +#~ "client:" +#~ msgstr "现在我们可以创建一个 :code:`CifarClient` 类的实例,并添加一行来实际运行该客户端:" + +#~ 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." +#~ msgstr "" +#~ "这就是客户端。我们只需实现 :code:`Client` 或 :code:`NumPyClient`" +#~ " 并调用 :code:`fl.client.start_client()` 或 " +#~ ":code:`fl.client.start_numpy_client()`。字符串 " +#~ ":code:`\"[::]:8080\"`会告诉客户端要连接的服务器。在本例中,我们可以在同一台机器上运行服务器和客户端,因此使用 " +#~ ":code:`\"[::]:8080\"。如果我们运行的是真正的联邦工作负载,服务器和客户端运行在不同的机器上,那么需要改变的只是客户端指向的" +#~ " :code:`server_address`。" + +#~ msgid "Each client will have its own dataset." +#~ msgstr "每个客户都有自己的数据集。" + +#~ msgid "" +#~ "You should now see how the " +#~ "training does in the very first " +#~ "terminal (the one that started the " +#~ "server):" +#~ msgstr "现在你应该能在第一个终端(启动服务器的终端)看到训练的效果了:" + +#~ 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`." +#~ msgstr "" +#~ "恭喜您!您已经成功构建并运行了第一个联邦学习系统。`完整的源代码 " +#~ "`_ 可以在 :code:`examples/quickstart-" +#~ "tensorflow/client.py` 中找到。" + +#~ msgid "|e5918c1c06a4434bbe4bf49235e40059|" +#~ msgstr "" + +#~ msgid "|c0165741bd1944f09ec55ce49032377d|" +#~ msgstr "" + +#~ msgid "|0a0ac9427ac7487b8e52d75ed514f04e|" +#~ msgstr "" + +#~ msgid "|5defee3ea4ca40d99fcd3e4ea045be25|" +#~ msgstr "" + +#~ msgid "|74f26ca701254d3db57d7899bd91eb55|" +#~ msgstr "" + +#~ msgid "|bda79f21f8154258a40e5766b2634ad7|" +#~ msgstr "" + +#~ msgid "|89d30862e62e4f9989e193483a08680a|" +#~ msgstr "" + +#~ msgid "|77e9918671c54b4f86e01369c0785ce8|" +#~ msgstr "" + +#~ msgid "|7e4ccef37cc94148a067107b34eb7447|" +#~ msgstr "" + +#~ msgid "|28e47e4cded14479a0846c8e5f22c872|" +#~ msgstr "" + +#~ msgid "|4b8c5d1afa144294b76ffc76e4658a38|" +#~ msgstr "" + +#~ msgid "|9dbdb3a0f6cb4a129fac863eaa414c17|" +#~ msgstr "" + +#~ msgid "|81749d0ac0834c36a83bd38f433fea31|" +#~ msgstr "" + +#~ msgid "|ed9aae51da70428eab7eef32f21e819e|" +#~ msgstr "" + From 55a2a97da7b44631bcbefba24860beeda6a1fdfb Mon Sep 17 00:00:00 2001 From: Charles Beauville Date: Tue, 24 Sep 2024 12:35:54 +0200 Subject: [PATCH 26/28] docs(framework:skip) Update Sphinx and related dependencies to latest (#2938) --- pyproject.toml | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 536d0ddd20c4..f4555a6f1762 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,25 +109,24 @@ mypy-protobuf = "==3.2.0" jupyterlab = "==4.0.12" rope = "==1.11.0" semver = "==3.0.2" -sphinx = "==6.2.1" +sphinx = "==7.4.7" sphinx-intl = "==2.2.0" -sphinx-click = "==5.1.0" -myst-parser = "==1.0.0" -sphinx-design = "==0.5.0" +sphinx-click = "==6.0.0" +myst-parser = "==3.0.1" +sphinx-design = "==0.6.1" sphinx-copybutton = "==0.5.2" sphinxcontrib-mermaid = "==0.9.2" sphinxcontrib-youtube = "==1.4.1" -furo = "==2023.9.10" -sphinx-reredirects = "==0.1.3" -nbsphinx = "==0.9.4" +furo = "==2024.8.6" +sphinx-reredirects = "==0.1.5" +nbsphinx = "==0.9.5" nbstripout = "==0.6.1" ruff = "==0.1.9" sphinx-argparse = "==0.4.0" pipreqs = "==0.4.13" -mdformat-gfm = "==0.3.5" +mdformat-gfm = "==0.3.6" mdformat-frontmatter = "==2.0.1" mdformat-beautysh = "==0.1.1" -mdformat-myst = "==0.1.5" twine = "==5.1.1" pyroma = "==4.2" check-wheel-contents = "==0.4.0" From b911d8da34478055242ee42c9957b046cdde4e96 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 24 Sep 2024 12:43:35 +0200 Subject: [PATCH 27/28] feat(framework) Upgrade Ubuntu base image to version 24.04 (#4226) Signed-off-by: Robert Steiner --- dev/build-docker-image-matrix.py | 2 +- doc/source/conf.py | 2 +- src/docker/base/ubuntu/Dockerfile | 9 ++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/dev/build-docker-image-matrix.py b/dev/build-docker-image-matrix.py index c19949e358b9..52c96e3cca7a 100644 --- a/dev/build-docker-image-matrix.py +++ b/dev/build-docker-image-matrix.py @@ -134,7 +134,7 @@ def tag_latest_ubuntu_with_flwr_version(image: BaseImage) -> List[str]: ubuntu_base_images = generate_base_images( flwr_version, SUPPORTED_PYTHON_VERSIONS, - [Distro(DistroName.UBUNTU, "22.04")], + [Distro(DistroName.UBUNTU, "24.04")], ) # alpine base images for the latest supported python version alpine_base_images = generate_base_images( diff --git a/doc/source/conf.py b/doc/source/conf.py index 033b345b60cc..d78aeda0d48e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -95,7 +95,7 @@ rst_prolog = """ .. |stable_flwr_version| replace:: 1.11.1 .. |stable_flwr_superlink_docker_digest| replace:: 4b317d5b6030710b476f4dbfab2c3a33021ad40a0fcfa54d7edd45e0c51d889c -.. |ubuntu_version| replace:: 22.04 +.. |ubuntu_version| replace:: 24.04 .. |setuptools_version| replace:: 70.3.0 .. |pip_version| replace:: 24.1.2 """ diff --git a/src/docker/base/ubuntu/Dockerfile b/src/docker/base/ubuntu/Dockerfile index 47655b1a52a1..b52599a80784 100644 --- a/src/docker/base/ubuntu/Dockerfile +++ b/src/docker/base/ubuntu/Dockerfile @@ -15,7 +15,7 @@ # hadolint global ignore=DL3008 ARG DISTRO=ubuntu -ARG DISTRO_VERSION=22.04 +ARG DISTRO_VERSION=24.04 FROM $DISTRO:$DISTRO_VERSION AS python ENV DEBIAN_FRONTEND=noninteractive @@ -87,11 +87,10 @@ RUN apt-get update \ ca-certificates \ && rm -rf /var/lib/apt/lists/* \ # add non-root user - && adduser \ + && useradd \ --no-create-home \ - --home /app \ - --disabled-password \ - --gecos "" \ + --home-dir /app \ + -c "" \ --uid 49999 app \ && mkdir -p /app \ && chown -R app:app /app From 42e1460739df6f20aa09478715fe1eb5e0842e28 Mon Sep 17 00:00:00 2001 From: Robert Steiner Date: Tue, 24 Sep 2024 13:18:20 +0200 Subject: [PATCH 28/28] docs(framework:skip) Add docs for distributed Docker Compose guide (#3928) Signed-off-by: Robert Steiner Co-authored-by: Chong Shen Ng --- doc/source/docker/index.rst | 1 + .../tutorial-deploy-on-multiple-machines.rst | 191 ++++++++++++++++++ 2 files changed, 192 insertions(+) create mode 100644 doc/source/docker/tutorial-deploy-on-multiple-machines.rst diff --git a/doc/source/docker/index.rst b/doc/source/docker/index.rst index 968f01581b34..2c972b805930 100644 --- a/doc/source/docker/index.rst +++ b/doc/source/docker/index.rst @@ -45,3 +45,4 @@ Run Flower using Docker Compose tutorial-quickstart-docker-compose run-quickstart-examples-docker-compose + tutorial-deploy-on-multiple-machines diff --git a/doc/source/docker/tutorial-deploy-on-multiple-machines.rst b/doc/source/docker/tutorial-deploy-on-multiple-machines.rst new file mode 100644 index 000000000000..7b6cec8292eb --- /dev/null +++ b/doc/source/docker/tutorial-deploy-on-multiple-machines.rst @@ -0,0 +1,191 @@ +######################################################## + Deploy Flower on Multiple Machines with Docker Compose +######################################################## + +This guide will help you set up a Flower project on multiple machines +using Docker Compose. + +You will learn how to run the Flower client and server components on two +separate machines, with Flower configured to use TLS encryption and +persist SuperLink state across restarts. A server consists of a +SuperLink and ``SuperExec``. For more details about the Flower +architecture, refer to the :doc:`../explanation-flower-architecture` +explainer page. + +This guide assumes you have completed the +:doc:`tutorial-quickstart-docker-compose` tutorial. It is highly +recommended that you follow and understand the contents of that tutorial +before proceeding with this guide. + +*************** + Prerequisites +*************** + +Before you begin, make sure you have the following prerequisites: + +- The ``flwr`` CLI is :doc:`installed <../how-to-install-flower>` + locally. +- The Docker daemon is running on your local machine and the remote + machine. +- Docker Compose V2 is installed on both your local machine and the + remote machine. +- You can connect to the remote machine from your local machine. +- Ports ``9091`` and ``9093`` are accessible on the remote machine. + +.. note:: + + The guide uses the |quickstart_sklearn_tabular|_ example as an + example project. + + If your project has a different name or location, please remember to + adjust the commands/paths accordingly. + +**************** + Step 1: Set Up +**************** + +#. Clone the Flower repository and change to the ``distributed`` + directory: + + .. code:: bash + + $ git clone --depth=1 https://github.com/adap/flower.git + $ cd flower/src/docker/distributed + +#. Get the IP address from the remote machine and save it for later. + +#. Use the ``certs.yml`` Compose file to generate your own self-signed + certificates. If you have certificates, you can continue with Step 2. + + .. important:: + + These certificates should be used only for development purposes. + + For production environments, you may have to use dedicated + services to obtain your certificates. + + First, set the environment variables ``SUPERLINK_IP`` and + ``SUPEREXEC_IP`` with the IP address from the remote machine. For + example, if the IP is ``192.168.2.33``, execute: + + .. code:: bash + + $ export SUPERLINK_IP=192.168.2.33 + $ export SUPEREXEC_IP=192.168.2.33 + + Next, generate the self-signed certificates: + + .. code:: bash + + $ docker compose -f certs.yml -f ../complete/certs.yml up --build + +*************************************** + Step 2: Copy the Server Compose Files +*************************************** + +Use the method that works best for you to copy the ``server`` directory, +the certificates, and your Flower project to the remote machine. + +For example, you can use ``scp`` to copy the directories: + +.. code:: bash + + $ scp -r ./server \ + ./superexec-certificates \ + ./superlink-certificates \ + ../../../examples/quickstart-sklearn-tabular remote:~/distributed + +******************************************** + Step 3: Start the Flower Server Components +******************************************** + +Log into the remote machine using ``ssh`` and run the following command +to start the SuperLink and SuperExec services: + +.. code:: bash + + $ ssh + # In your remote machine + $ cd + $ export PROJECT_DIR=../quickstart-sklearn-tabular + $ docker compose -f server/compose.yml up --build -d + +.. note:: + + The Path of the ``PROJECT_DIR`` should be relative to the location of + the ``server`` Docker Compose files. + +Go back to your terminal on your local machine. + +******************************************** + Step 4: Start the Flower Client Components +******************************************** + +On your local machine, run the following command to start the client +components: + +.. code:: bash + + # In the `docker/distributed` directory + $ export PROJECT_DIR=../../../../examples/quickstart-sklearn-tabular + $ docker compose -f client/compose.yml up --build -d + +.. note:: + + The Path of the ``PROJECT_DIR`` should be relative to the location of + the ``client`` Docker Compose files. + +********************************* + Step 5: Run Your Flower Project +********************************* + +Specify the remote SuperExec IP addresses and the path to the root +certificate in the ``[tool.flwr.federations.remote-superexec]`` table in +the ``pyproject.toml`` file. Here, we have named our remote federation +``remote-superexec``: + +.. code:: toml + :caption: examples/quickstart-sklearn-tabular/pyproject.toml + + [tool.flwr.federations.remote-superexec] + address = "192.168.2.33:9093" + root-certificates = "../../src/docker/distributed/superexec-certificates/ca.crt" + +.. note:: + + The Path of the ``root-certificates`` should be relative to the + location of the ``pyproject.toml`` file. + +To run the project, execute: + +.. code:: bash + + $ flwr run ../../../examples/quickstart-sklearn-tabular remote-superexec + +That's it! With these steps, you've set up Flower on two separate +machines and are ready to start using it. + +****************** + Step 6: Clean Up +****************** + +Shut down the Flower client components: + +.. code:: bash + + # In the `docker/distributed` directory + $ docker compose -f client/compose.yml down + +Shut down the Flower server components and delete the SuperLink state: + +.. code:: bash + + $ ssh + $ cd + $ docker compose -f server/compose.yml down -v + +.. |quickstart_sklearn_tabular| replace:: + + ``examples/quickstart-sklearn-tabular`` + +.. _quickstart_sklearn_tabular: https://github.com/adap/flower/tree/main/examples/quickstart-sklearn-tabular