Back to Articles

Flower: Building Federated Learning Systems Without Reinventing Privacy Infrastructure

[ View on GitHub ]

Flower: Building Federated Learning Systems Without Reinventing Privacy Infrastructure

Hook

Google trained Gboard's next-word prediction on millions of phones without ever seeing your texts. The open-source framework making this possible for any team isn't what you'd expect—it's framework-agnostic, runs on Raspberry Pis, and started in an Oxford research lab.

Context

Traditional machine learning hits a wall when data can't be centralized. Healthcare records can't leave hospital servers. Mobile keyboard data is too sensitive to upload. IoT sensors generate terabytes that cost too much to transmit. The standard solution—ship all data to a central server, train a model, deploy back to edge devices—breaks under regulatory constraints (GDPR, HIPAA), user privacy expectations, and bandwidth economics.

Federated learning flips this paradigm: the model travels to the data, not vice versa. Each client trains locally on private data, sends only model updates to a central server, which aggregates them into a global model. Google pioneered this for Gboard in 2017, but their TensorFlow Federated framework locks you into TensorFlow. Academic implementations of federated learning papers rarely make it beyond simulation scripts. Flower emerged from Oxford University research to solve this gap—a production-ready federated learning framework that works with PyTorch, TensorFlow, JAX, scikit-learn, or even raw NumPy, with 6,800+ GitHub stars validating its approach.

Technical Insight

Flower's architecture centers on a gRPC-based client-server model with pluggable strategies. The server doesn't touch your raw data—it orchestrates federated rounds, selecting clients, aggregating their updates, and distributing the improved global model. This abstraction works because Flower treats ML frameworks as implementation details, not architectural constraints.

Here's a minimal PyTorch client that participates in federated training:

import flwr as fl
import torch
import torch.nn as nn
from torch.utils.data import DataLoader

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 10)
    
    def forward(self, x):
        x = torch.relu(self.fc1(x))
        return self.fc2(x)

class FlowerClient(fl.client.NumPyClient):
    def __init__(self, model, trainloader, valloader):
        self.model = model
        self.trainloader = trainloader
        self.valloader = valloader

    def get_parameters(self, config):
        return [val.cpu().numpy() for _, val in self.model.state_dict().items()]

    def set_parameters(self, parameters):
        params_dict = zip(self.model.state_dict().keys(), parameters)
        state_dict = {k: torch.tensor(v) for k, v in params_dict}
        self.model.load_state_dict(state_dict, strict=True)

    def fit(self, parameters, config):
        self.set_parameters(parameters)
        optimizer = torch.optim.SGD(self.model.parameters(), lr=0.01)
        criterion = nn.CrossEntropyLoss()
        
        self.model.train()
        for epoch in range(1):
            for batch in self.trainloader:
                optimizer.zero_grad()
                outputs = self.model(batch['data'])
                loss = criterion(outputs, batch['labels'])
                loss.backward()
                optimizer.step()
        
        return self.get_parameters(config={}), len(self.trainloader.dataset), {}

    def evaluate(self, parameters, config):
        self.set_parameters(parameters)
        self.model.eval()
        loss, accuracy = 0.0, 0.0
        # Evaluation logic here
        return float(loss), len(self.valloader.dataset), {"accuracy": float(accuracy)}

# Start client
model = Net()
fl.client.start_numpy_client(
    server_address="localhost:8080",
    client=FlowerClient(model, train_loader, val_loader)
)

The NumPyClient interface is Flower's secret weapon for framework agnosticism. Your client converts model parameters to NumPy arrays for transmission, then reconstructs them in your framework of choice. The server never knows whether you're using PyTorch, TensorFlow, or a custom C++ model—it only sees numerical arrays to aggregate.

Server-side aggregation strategies determine how client updates merge. Flower ships with FedAvg (weighted averaging), FedProx (handles heterogeneous clients), and FedYogi (adaptive optimization). But the real power is custom strategies:

from flwr.server.strategy import Strategy
from flwr.common import Parameters, FitRes, Scalar
from typing import List, Tuple, Optional, Dict
import numpy as np

class SecureAggregation(Strategy):
    def __init__(self, min_available_clients: int = 2):
        super().__init__()
        self.min_available_clients = min_available_clients

    def aggregate_fit(
        self,
        server_round: int,
        results: List[Tuple[ClientProxy, FitRes]],
        failures: List[BaseException],
    ) -> Tuple[Optional[Parameters], Dict[str, Scalar]]:
        if len(results) < self.min_available_clients:
            return None, {}
        
        # Custom aggregation: median instead of mean for robustness
        weights_results = [
            (parameters_to_ndarrays(fit_res.parameters), fit_res.num_examples)
            for _, fit_res in results
        ]
        
        # Stack all client parameters
        all_layers = []
        for layer_idx in range(len(weights_results[0][0])):
            layer_updates = np.stack([weights[layer_idx] for weights, _ in weights_results])
            # Median aggregation is more robust to poisoning attacks
            aggregated_layer = np.median(layer_updates, axis=0)
            all_layers.append(aggregated_layer)
        
        return ndarrays_to_parameters(all_layers), {"aggregation_method": "median"}

This custom strategy uses median aggregation instead of mean, making the system more robust against poisoned client updates—a critical security consideration in federated learning where you can't verify client data integrity.

Flower's simulation mode is invaluable for research. You can simulate 100 clients on a single machine with different data distributions:

import flwr as fl
from flwr.server.strategy import FedAvg

def client_fn(cid: str) -> fl.client.Client:
    # Each client gets different data partition
    partition_id = int(cid)
    trainloader = load_partition(partition_id)
    return FlowerClient(Net(), trainloader, valloader).to_client()

fl.simulation.start_simulation(
    client_fn=client_fn,
    num_clients=100,
    config=fl.server.ServerConfig(num_rounds=10),
    strategy=FedAvg(
        fraction_fit=0.1,  # Sample 10% of clients per round
        min_fit_clients=10,
        min_available_clients=100,
    ),
)

This simulates realistic federated scenarios—testing how your algorithm handles client dropout, data heterogeneity, and partial participation—without deploying to actual edge devices. The same code scales from simulation to production by swapping start_simulation for start_server and deploying real clients.

For production edge deployment, Flower supports Android (via TensorFlow Lite), iOS (via Core ML), and embedded devices. The framework handles connection management, retry logic, and graceful degradation when clients disconnect mid-training. This production readiness separates Flower from academic prototypes that assume stable networks and reliable clients.

Gotcha

Flower provides the plumbing for federated learning, not the privacy guarantees. Data stays on client devices, which prevents raw data exposure, but model updates can still leak information through gradient attacks or membership inference. If you need differential privacy, you'll implement clipping and noise addition yourself or integrate libraries like Opacus (PyTorch) or TensorFlow Privacy. Flower doesn't include secure aggregation protocols that prevent the server from seeing individual client updates—only their aggregate. For high-stakes applications like medical data, you'll need to layer additional privacy-preserving techniques on top.

The learning curve is steeper than traditional ML frameworks. You're not just training a model—you're designing a distributed system. Questions multiply: How do you handle clients with vastly different data distributions (non-IID data)? What happens when 30% of clients drop during a round? How do you prevent malicious clients from poisoning the global model? Flower gives you tools to address these issues, but it doesn't make the fundamental challenges disappear. Expect to spend time tuning client sampling strategies, aggregation parameters, and fault tolerance thresholds. Production deployments require infrastructure for client authentication, encrypted communication (Flower uses gRPC, which you should wrap in TLS), and monitoring federation health across potentially thousands of edge devices.

Verdict

Use if: You're building ML systems where data must stay distributed—mobile apps that can't upload user data, multi-institution research collaborations bound by data sharing agreements, IoT deployments where bandwidth costs prohibit centralized training, or regulated industries (healthcare, finance) where compliance requires local data storage. Also use Flower if you're researching federated learning algorithms and need reproducible baselines (their implementation of FedProx, MOON, and other papers saves weeks), or if you need framework flexibility because your clients run heterogeneous models (PyTorch on phones, TensorFlow on servers, scikit-learn on sensors). Skip if: Your data can be centralized without regulatory or privacy concerns—standard distributed training frameworks (Horovod, PyTorch DDP) will be simpler and faster. Skip if you need turnkey differential privacy or secure multi-party computation without custom implementation. Skip if you're building real-time inference systems rather than training pipelines. And skip if you're locked into a cloud provider's ecosystem—AWS, Google Cloud, and Azure have proprietary federated learning services with tighter platform integration, though less flexibility.

// ADD TO YOUR README
[![Featured on Starlog](https://starlog.is/api/badge/data-knowledge/flwrlabs-flower.svg)](https://starlog.is/api/badge-click/data-knowledge/flwrlabs-flower)