Skip to content

aggregate() returns NaN weights instead of raising when all results report num_examples=0 #7645

Description

@Atishyy27

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions