Skip to content

DiLoCo: min_workers pauses (quorum gate), not aborts - #237

Merged
jdinalt merged 2 commits into
devfrom
fix/diloco-min-workers-quorum-gate
Jun 15, 2026
Merged

jdinalt merged 2 commits into
devfrom
fix/diloco-min-workers-quorum-gate

Conversation

@jdinalt

@jdinalt jdinalt commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Summary

Corrects the min_workers below-quorum behavior. PR #233 made a drop abort the run (relay save_and_stop); the intended behavior is a recoverable pause (block), never a forced stop. This reworks it into a quorum gate.

Semantics (confirmed with the operator)

The group never takes an outer step with fewer than min_workers contributors — at startup (no one syncs until everyone has joined) or after a drop. Below quorum the run pauses (workers idle, GPU parked, no garbage gradients) and is recoverable (a replacement worker, or lowering min_workers). Async permits one extra step to slip through as a worker drops; sync does not. Critically, a paused worker is never hard-blocked — it polls its stop flag, so diloco shutdown is always clean.

Server (server.py)

  • _handle_worker_death no longer aborts below min_workers (removed _min_workers_aborted + the save_and_stop relay).
  • Heartbeat response carries below_min_workers.
  • Sync and fragment-sync barriers apply a round only when contributors >= min_workers.
  • Barrier waits break on _shutting_down; graceful_shutdown wakes barrier-parked workers.
  • _below_min_workers() helper.

Worker (worker.py)

  • Reads below_min_workers off the heartbeat (_quorum_blocked) and idles at its sync point while blocked, polling _pending_stop() so a stop command breaks the pause immediately. _quorum_blocked starts False (optimistic) — the server barrier gate is the authoritative no-partial-step enforcement, so a worker driving _sync with no heartbeat loop (unit tests) never idles forever. (That optimistic default is also the fix for a hang the pessimistic init caused in the test suite.)

Limitation

Assumes single rank per worker (the in-scope case). DDP-per-worker would need the pause broadcast to followers — a follow-up (noted in code/docs).

Tests / docs

Replaced the obsolete abort tests with quorum-gate tests (drop ⇒ no abort, _below_min_workers, worker _pending_stop/_quorum_blocked). Full tests/unit/ml/diloco/ green (594 passed). black/isort clean. Rewrote the architecture-doc min_workers section.

🤖 Generated with Claude Code

jdinalt and others added 2 commits June 15, 2026 00:53
…ate)

Replaces the #233 behavior (relay save_and_stop = abort) with a recoverable
quorum gate, per the intended semantics: the group never takes an outer step
with fewer than min_workers contributors — at startup before everyone has
joined, OR after a drop — and pausing is recoverable (a replacement worker, or
lowering min_workers), never a forced stop.

Server:
- _handle_worker_death no longer aborts below min_workers; it just logs. Removed
  _min_workers_aborted and the save_and_stop relay.
- Heartbeat response carries below_min_workers (num_registered < min_workers).
- Sync and fragment-sync barriers only apply a round when contributors >=
  min_workers (no partial outer step; async permits one extra to slip through).
- Barrier waits break on _shutting_down, and graceful_shutdown wakes barrier-
  parked workers — a paused worker is always cleanly stoppable, never a hard
  unkillable block.
- _below_min_workers() helper.

Worker:
- Reads below_min_workers off the heartbeat (_quorum_blocked) and, at its sync
  point, idles instead of syncing/training ahead while blocked — GPU parked, no
  garbage gradients. The idle polls _pending_stop() so save_and_stop/abort
  breaks it immediately (clean shutdown). _quorum_blocked starts False
  (optimistic): the server barrier gate is the authoritative no-partial-step
  enforcement, so a worker with no heartbeat loop (e.g. unit tests driving _sync
  directly) never idles forever.

Limitation (noted in code/docs): assumes single rank per worker (the in-scope
case); DDP-per-worker would need the pause broadcast to followers — a follow-up.

Tests: replaced the abort tests with quorum-gate tests (drop => no abort,
_below_min_workers, worker _pending_stop/_quorum_blocked). Full diloco suite
green (594).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Critical (caught in review): the >= min_workers gate was on the two submit
handlers but NOT on the _handle_worker_death barrier-release sites. A death that
shrinks _round_expected_workers can flip _round_complete() True for the
survivors, which then drove a partial outer step below quorum — the exact bug
this PR exists to prevent. Gate both death-path releases (full-model + fragment)
on contributors >= min_workers; below quorum, leave the survivors parked (the
pause), released by a later submit once quorum returns or by _shutting_down.
(+ regression test: min_workers=2, A submits, B dies -> no partial step, A stays
parked, _sync_round unchanged.)

Also: fixed the misleading _quorum_blocked test (it set the value it asserted and
documented the opposite of the real optimistic default) — replaced with a
heartbeat-flag-update test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jdinalt
jdinalt merged commit 70b5d41 into dev Jun 15, 2026
1 check passed
@jdinalt
jdinalt deleted the fix/diloco-min-workers-quorum-gate branch June 15, 2026 01:08
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