Skip to content

Score Krum-family rules on the paper neighborhoods - #65

Open
ArthurDanjou wants to merge 13 commits into
mainfrom
fix-multikrum-n-f-2-scoring
Open

ArthurDanjou wants to merge 13 commits into
mainfrom
fix-multikrum-n-f-2-scoring

Conversation

@ArthurDanjou

@ArthurDanjou ArthurDanjou commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

Two related fidelity gaps against the papers, plus the attack-side calibration that depends on them, all conflating the scoring neighborhood with the number of averaged gradients:

  • MultiKrum (Blanchard et al., NIPS 2017): aggregate called score(..., num_peers=m). The paper always scores on the n-f-2 nearest neighbors and averages the top-m. For Krum (m=1) the code scored each gradient on its single nearest neighbor. The num_peers=n-f default was additionally mislabeled as the paper score.
  • Bulyan (El Mhamdi et al., ICML 2018, Algorithm 1): scores were computed once with num_peers=m, then frozen while removed gradients were masked with inf. Removed gradients therefore stayed in the distance matrix and scores were never updated between iterations.
  • SmallPerturbation: the Krum-family membership test scored candidates on num_peers=m instead of the n-f-2 neighborhood, so the boundary search was miscalibrated whenever the target's m differed from that neighborhood.

Fix

Scoring

  • MultiKrum.aggregate scores on n-f-2 peers, top-m selection unchanged; score() default is now n-f-2; docstring corrected.
  • Bulyan recomputes Krum scores every iteration over the remaining candidates (masked-out gradients leave the matrix) on their n-i-f-2 closest peers.
  • Ties: top-m selection uses a stable argsort, so score ties resolve to the smallest indices, as the paper definitions state.

SmallPerturbation selection test

  • _is_selected always scores on the n-f-2 neighborhood and mirrors the target's selection size: the explicit m from aggregator_kwargs, 1 for Krum, and MultiKrum's n-2f-3 default; an explicit m=None falls back to the class default.

Audit leftovers

  • Medoid returns a clone instead of a view into the input list; Aksel builds its median pivot with quantile(0.5) (consistent with Median on even worker counts); Brute and Bulyan accept f = 0, which the theory allows.
  • MultiKrum enforces the honest-majority premise n >= 2f + 3 for every m (previously only for m = 1; larger m could reach f = n, where n - f - 2 <= 0 silently produced all-zero scores or a wrong distance slice).
  • Documented f bounds now match the code: MultiKrum and Bulyan accept f = 0, and Brute documents its n >= 2f + 1 condition.

References

  • sign_flip.py now cites Xie et al., UAI 2020, PMLR 115:261-270 (the previous entry had the wrong title and venue).
  • alie.py now lists the published authors: Gilad Baruch, Moran Baruch, Yoav Goldberg.

Tests

  • Paper-scoring values for Krum/MultiKrum/Bulyan (honest selections, Byzantine excluded in both fixtures); endpoint tests (m=1 matches Krum, m=n matches Average); stable tie-breaking on tied scores.
  • SmallPerturbation: the score neighborhood argument is pinned, the per-class m defaults (Krum = 1, MultiKrum = n-2f-3) are asserted, and the explicit m=None fallback is covered.
  • MultiKrum: the honest-majority check is asserted for m > 1, which previously bypassed it.

Deliberately unchanged

  • theta = n-2f-2 and the Bulyan m default (n-f), kept by team decision and documented.

Notes (by design)

  • MultiKrum's m is intentionally free in [1, n]: it is a trade-off knob (larger m averages more gradients, m = n recovers Average); values above n-2f-3 leave the Multi-Krum resilience bound and are documented as such. The bound is an assumption of the theorem, not an input constraint, so only the honest-majority premise n >= 2f + 3 is enforced.

Verification

  • Full suite: 447 tests green; ruff format --check, ruff check and ty check clean (also as pre-commit hooks).
  • Sanity: Bulyan on 9 honest + 2 far outliers returns a vector close to the honest mean.

Follow-ups (out of scope)

  • Reproduction curves using Krum/MultiKrum/Bulyan must be regenerated after merge.

aggregate() passed num_peers=m to score(), conflating the scoring
neighborhood with the number of averaged gradients. The paper always
scores on n-f-2 peers and averages the top-m. Fix the call site and
the score() default, correct the docstring, update tests.
Selection now follows Algorithm 1: every iteration scores the
remaining candidates (removed gradients leave the distance matrix)
on their n-i-f-2 closest peers instead of reusing frozen scores
computed once with num_peers=m. Theta and the m default are
unchanged.
@ArthurDanjou ArthurDanjou changed the title Score MultiKrum on n-f-2 peers per Blanchard et al. Score Krum-family rules on the paper neighborhoods Sep 14, 2026
ArthurDanjou and others added 3 commits September 14, 2026 13:17
… and Bulyan

Medoid returned a view into the inputs; return a copy like the other
selection rules. Aksel pivot uses quantile(0.5) for consistency with
Median on even worker counts. Brute and Bulyan accept f=0, which the
theory allows.
Removed comments explaining quantile calculation for median.
Top-m selection uses stable argsort so score ties resolve to the
smallest indices, as the paper definitions state. The MultiKrum m
docstring now separates the mechanical bound (m <= n) from the
resilience bound (m <= n-2f-3); the Bulyan m-default docstring now
matches the code (n-f).
@ArthurDanjou

Copy link
Copy Markdown
Collaborator Author

torch.topk does not specify the order of tied elements: with equal scores, the returned indices are implementation-dependent and can vary across runs and devices. The paper definitions break score ties by smallest worker identifier, so selection must be deterministic there. argsort(scores, stable=True)[:m] guarantees exactly that: equal scores come out in ascending index order. On distinct scores both forms return the same top-$\nselected$. The extra cost (full sort instead of partial selection) is negligible at aggregation scale (n vectors, not d dimensions), and determinism is load-bearing here since simulations promise bit-identical reruns at fixed seed.

@ArthurDanjou

Copy link
Copy Markdown
Collaborator Author

Good point, and that is exactly what this PR does. There are two distinct parameters here.

  • The scoring neighborhood num_peers is fixed to n-f-2, following Blanchard et al. (NIPS 2017), Section 4, definition of s(i). See multikrum.py:88 and the score() default at multikrum.py:132.
  • The number of averaged vectors m stays adjustable. The default is n-2f-3 (multikrum.py:73), selection is [:m] (multikrum.py:90), and validation allows 1 <= m <= n with a documented warning beyond the Section 6 resilience bound n-m > 2f+2.

So n-f-2 does not cap m. The old code conflated the two with score(..., num_peers=m), and this PR separates them.

ArthurDanjou and others added 6 commits September 14, 2026 16:09
Updated reference for the sign-flip gradient attack.
Krum keeps exactly one vector and MultiKrum defaults to n - 2f - 3, so neither matched the previous n - f - 2 fallback. The selection test now mirrors the target class default, accepts an explicit m=None, and always scores on the n - f - 2 neighborhood.
Assert the score neighborhood argument, the per-class m defaults (1 for Krum, n - 2f - 3 for MultiKrum) and the explicit m=None fallback.
The n >= 2f + 3 check only ran for m = 1; larger m accepted f up to n, where n - f - 2 <= 0 silently produced all-zero scores or a wrong distance slice. The check is hoisted for every m. m itself stays free in [1, n]: values above n - 2f - 3 leave the resilience bound by design (larger m averages more gradients, m = n recovers Average), and the docstring now says so.

Also align the documented f bounds with the code (Bulyan and MultiKrum accept f = 0, Brute documents its n >= 2f + 1 condition).
@ArthurDanjou

Copy link
Copy Markdown
Collaborator Author

Design note: why m is free in [1, n]

The m <= n-2f-3 bound is deliberately not enforced. It is a sufficient condition for the Multi-Krum resilience proof (Blanchard et al., Proposition 3), not a validity constraint on the aggregation rule:

  • m is a trade-off knob: larger m averages more gradients and relaxes the distance-based selection, and m = n recovers Average, which is a useful comparison point (pinned by test_aggregate_m_equals_n_matches_average).
  • Sweeps need to explore this range: the reproduction experiment runs m = 12 with n = 20, f = 6, above the resilience bound, to isolate the effect of a strict selection (documented in the report).
  • What is enforced is the honest-majority premise n >= 2f + 3, for every m. Before commit b389634 the check only ran for m = 1, and larger m could reach f = n, where n - f - 2 <= 0 silently produced all-zero scores or a wrong distance slice.

The docstrings state the bound explicitly, so callers can tell when they leave the guaranteed regime.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant