The Average Client Is A Fiction Everyone Agreed To
FedAvg is easy to explain because every client politely disappears into one neat average. Real clients do not disappear. They carry different feature shifts, different label balances, and different, creative ways to punish a global model that was never built for them specifically.
fedcal builds that mess directly into the benchmark. Six clients get genuinely different feature shifts and class skews, then the repo compares plain FedAvg, FedProx, and a conservative local calibration step against each other on the client nobody wants to be.
Personalization With A Seatbelt On
The local calibration step only moves a client's bias if validation accuracy actually improves. That matters because personalization overfits fast the moment the local slice of data gets small, which is most of the time in the real world.
Client updates are clipped, the logistic model is readable end to end, and the whole benchmark stays dependency-free. This is not a full federated platform pretending to be production infrastructure. It is the failure mode, isolated and put under a microscope.
The Number
FedAvg gets 0.829 macro accuracy while stranding its worst client at 0.667, which is exactly how a model quietly fails one real user while the dashboard reads green. FedProx plus conservative calibration trades 1.4 points of macro accuracy for a 2.7 point gain on that worst client, landing at 0.694, on purpose.
4 tests pass locally and on GitHub Actions across Python 3.9, 3.11, and 3.13.