Configure Clients
=================

Flower provides the ability to send configuration values to clients, allowing
server-side control over client behavior. This feature enables flexible and dynamic
adjustment of client-side hyperparameters, improving collaboration and experimentation.

Configuration values
--------------------

``FitConfig`` and ``EvaluateConfig`` are dictionaries containing configuration values
that the server sends to clients during federated learning rounds. These values must be
of type ``Scalar``, which includes ``bool``, ``bytes``, ``float``, ``int``, or ``str``
(or equivalent types in different languages). Scalar is the value type directly
supported by Flower for these configurations.

For example, a ``FitConfig`` dictionary might look like this:

.. code-block:: python

    config = {
        "batch_size": 32,  # int value
        "learning_rate": 0.01,  # float value
        "optimizer": "sgd",  # str value
        "dropout": True,  # bool value
    }

Flower serializes these configuration dictionaries (or *config dicts* for short) to
their ProtoBuf representation, transports them to the client using gRPC, and then
deserializes them back to Python dictionaries.

.. note::

    Currently, there is no support for directly sending collection types (e.g., ``Set``,
    ``List``, ``Map``) as values in configuration dictionaries. To send collections,
    convert them to a supported type (e.g., JSON string) and decode on the client side.

    Example:

    .. code-block:: python

        import json

        # On the server
        config_dict = {"data_splits": json.dumps([0.8, 0.1, 0.1])}

        # On the client
        data_splits = json.loads(config["data_splits"])

Configuration through Built-in Strategies
-----------------------------------------

Flower provides configuration options to control client behavior dynamically through
``FitConfig`` and ``EvaluateConfig``. These configurations allow server-side control
over client-side parameters such as batch size, number of local epochs, learning rate,
and evaluation settings, improving collaboration and experimentation.

``FitConfig`` and ``EvaluateConfig``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``FitConfig`` and ``EvaluateConfig`` are dictionaries containing configuration values
that the server sends to clients during federated learning rounds. These dictionaries
enable the server to adjust client-side hyperparameters and monitor progress
effectively.

``FitConfig``
+++++++++++++

``FitConfig`` specifies the hyperparameters for training rounds, such as the batch size,
number of local epochs, and other parameters that influence training.

For example, a ``fit_config`` callback might look like this:

.. code-block:: python

    import json


    def fit_config(server_round: int):
        """Generate training configuration for each round."""
        # Create the configuration dictionary
        config = {
            "batch_size": 32,
            "current_round": server_round,
            "local_epochs": 2,
            "data_splits": json.dumps([0.8, 0.1, 0.1]),  # Example of serialized list
        }
        return config

You can then pass this ``fit_config`` callback to a built-in strategy such as
``FedAvg``:

.. code-block:: python

    from flwr.server.strategy import FedAvg

    strategy = FedAvg(
        on_fit_config_fn=fit_config,  # Pass the `fit_config` function
    )

On the client side, the configuration is received in the ``fit`` method, where it can be
read and used:

.. code-block:: python

    import json

    from flwr.client import NumPyClient


    class FlowerClient(NumPyClient):
        def fit(self, parameters, config):
            # Read configuration values
            batch_size = config["batch_size"]
            local_epochs = config["local_epochs"]
            data_splits = json.loads(config["data_splits"])  # Deserialize JSON

            # Use configuration values
            print(f"Training with batch size {batch_size}, epochs {local_epochs}")
            print(f"Data splits: {data_splits}")
            # Training logic here

``EvaluateConfig``
++++++++++++++++++

``EvaluateConfig`` specifies hyperparameters for the evaluation process, such as the
batch size, evaluation frequency, or metrics to compute during evaluation.

For example, an ``evaluate_config`` callback might look like this:

.. code-block:: python

    def evaluate_config(server_round: int):
        """Generate evaluation configuration for each round."""
        # Create the configuration dictionary
        config = {
            "batch_size": 64,
            "current_round": server_round,
            "metrics": ["accuracy"],  # Example metrics to compute
        }
        return config

You can pass this ``evaluate_config`` callback to a built-in strategy like ``FedAvg``:

.. code-block:: python

    strategy = FedAvg(
        on_evaluate_config_fn=evaluate_config  # Assign the evaluate_config function
    )

On the client side, the configuration is received in the ``evaluate`` method, where it
can be used during the evaluation process:

.. code-block:: python

    from flwr.client import NumPyClient


    class FlowerClient(NumPyClient):
        def evaluate(self, parameters, config):
            # Read configuration values
            batch_size = config["batch_size"]
            current_round = config["current_round"]
            metrics = config["metrics"]

            # Use configuration values
            print(f"Evaluating with batch size {batch_size}")
            print(f"Metrics to compute: {metrics}")

            # Evaluation logic here

            return 0.5, {"accuracy": 0.85}  # Example return values

Example: Sending Training Configurations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Imagine we want to send (a) the batch size, (b) the current global round, and (c) the
number of local epochs. Our configuration function could look like this:

.. code-block:: python

    def fit_config(server_round: int):
        """Generate training configuration for each round."""
        return {
            "batch_size": 32,
            "current_round": server_round,
            "local_epochs": 2,
        }

To use this function with a built-in strategy like ``FedAvg``, pass it to the ``FedAvg``
constructor (typically in your ``server_fn``):

.. code-block:: python

    from flwr.server import ServerApp, ServerAppComponents
    from flwr.server.strategy import FedAvg


    def server_fn(context):
        """Define server behavior."""
        strategy = FedAvg(
            on_fit_config_fn=fit_config,
            # Other arguments...
        )
        return ServerAppComponents(strategy=strategy, ...)


    app = ServerApp(server_fn=server_fn)

Client-Side Configuration
+++++++++++++++++++++++++

On the client side, configurations are received as input to the ``fit`` and ``evaluate``
methods. For example:

.. code-block:: python

    class FlowerClient(flwr.client.NumPyClient):
        def fit(self, parameters, config):
            print(config["batch_size"])  # Output: 32
            print(config["current_round"])  # Output: current round number
            print(config["local_epochs"])  # Output: 2
            # Training logic here

        def evaluate(self, parameters, config):
            # Handle evaluation configurations if needed
            pass

Dynamic Configurations per Round
++++++++++++++++++++++++++++++++

Configuration functions are called at the beginning of every round. This allows for
dynamic adjustments based on progress. For example, you can increase the number of local
epochs in later rounds:

.. code-block:: python

    def fit_config(server_round: int):
        """Dynamic configuration for training."""
        return {
            "batch_size": 32,
            "current_round": server_round,
            "local_epochs": 1 if server_round < 3 else 2,
        }

Customizing Client Configurations
---------------------------------

In some cases, it may be necessary to send different configurations to individual
clients. To achieve this, you can create a custom strategy by extending a built-in one,
such as ``FedAvg``:

Example: Client-Specific Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    from flwr.server.strategy import FedAvg


    class CustomClientConfigStrategy(FedAvg):
        def configure_fit(self, server_round, parameters, client_manager):
            client_instructions = super().configure_fit(
                server_round, parameters, client_manager
            )

            # Modify configuration for a specific client
            client_proxy, fit_ins = client_instructions[0]
            fit_ins.config["special_key"] = "special_value"

            return client_instructions

Next, use this custom strategy as usual:

.. code-block:: python

    def server_fn(context):
        strategy = CustomClientConfigStrategy(
            # Other FedAvg parameters
        )
        return ServerAppComponents(strategy=strategy, ...)


    app = ServerApp(server_fn=server_fn)

Summary of Enhancements
-----------------------

- **Dynamic Configurations**: Enables per-round adjustments via functions.
- **Advanced Customization**: Supports client-specific strategies.
- **Client-Side Integration**: Configurations accessible in ``fit`` and ``evaluate``.
