Describe the bug
aggregate() in framework/py/flwr/server/strategy/aggregate.py divides by the total number of examples without checking for zero. If every result in a round reports num_examples=0, the division produces NaN for every parameter and returns it as the aggregated model. There is no exception, only a numpy RuntimeWarning, so the NaN model is redistributed to clients and the run continues.
The if not results guards upstream do not catch this: the results list is non empty, the counts inside it are zero.
Steps/Code to Reproduce
import numpy as np
from flwr.server.strategy.aggregate import aggregate, weighted_loss_avg
results = [([np.array([1.0, 2.0])], 0), ([np.array([3.0, 4.0])], 0)]
print(aggregate(results)) # -> [array([nan, nan])], no exception
weighted_loss_avg([(0, 0.5), (0, 0.7)]) # -> raises ZeroDivisionError
Output:
aggregate() RETURNED (no exception): [array([nan, nan])]
contains NaN: True
warning: RuntimeWarning
weighted_loss_avg RAISED: ZeroDivisionError - float division by zero
Expected Results
aggregate() should fail loudly, like the other aggregation paths already do. weighted_loss_avg and aggregate_inplace both raise ZeroDivisionError on the same all zero input, so today the three entry points disagree and only aggregate() corrupts silently.
Actual Results
A NaN model is returned and becomes the new global model. Training can continue for many rounds before anyone notices.
Additional context
Happy to send a PR, i have a fix and a regression test ready.
Describe the bug
aggregate()inframework/py/flwr/server/strategy/aggregate.pydivides by the total number of examples without checking for zero. If every result in a round reportsnum_examples=0, the division producesNaNfor every parameter and returns it as the aggregated model. There is no exception, only a numpyRuntimeWarning, so the NaN model is redistributed to clients and the run continues.The
if not resultsguards upstream do not catch this: the results list is non empty, the counts inside it are zero.Steps/Code to Reproduce
Output:
Expected Results
aggregate()should fail loudly, like the other aggregation paths already do.weighted_loss_avgandaggregate_inplaceboth raiseZeroDivisionErroron the same all zero input, so today the three entry points disagree and onlyaggregate()corrupts silently.Actual Results
A
NaNmodel is returned and becomes the new global model. Training can continue for many rounds before anyone notices.Additional context
Happy to send a PR, i have a fix and a regression test ready.