Companion illustration for
NVIDIA-NeMo/RL #3592.
Permalinks pin the PR head a815a8cb.
Seven pages on this PR, one question each β this one: one bug, start to finish.
participants β the shards eligible for this refit β and promotes only
shards that are STALE and in that set.A generation engine that restarts comes back holding whatever weights it read off disk at
startup, so the ledger parks it in STALE: the process is alive and can join a weight
transfer, but it is not allowed to answer requests. The
module docstring
is firm that a completed weight transfer β a refit β is the only way out of
STALE. The code that does the letting-out,
_record_refit_landed,
used to ask each shard only "are you STALE?" and never "were you in the group I just
sent weights to?" Before this PR those two questions always had the same answer. This PR adds
a way for a shard to turn STALE in the middle of a refit, so it now asks both β and
the figure below is the case that still slips through.
Only W2 and W3 were ever wrong. Both were wrong before this PR; the PR fixes W3 and
leaves W2. "Promoted" means the ledger moves the shard from STALE to
HEALTHY β it starts answering rollout requests again.
| The reload finishes⦠| In the communicator? (membership read, L3443) | In participants?(second read, L4705) | Gets weights? | Before | After this PR | With the fix |
|---|---|---|---|---|---|---|
| W1 before the membership read | yes | yes | yes | correct | correct | correct |
| W2 during the rebuild | no | yes | no | promoted | promoted | skipped |
| W3 during the transfer | no | no | no | promoted | skipped | skipped |
| W4 after the promotion | no | no | no | correct | correct | correct |
W2 is the odd row, and the reason is the two reads. The communicator is built from
the membership read; participants comes from
a second read taken after that one has already awaited. A reload landing
between them makes the shard STALE β and STALE is not
absent β so it is in the set the promotion trusts while the communicator was built without it.
There is a metric that looks like it was built for exactly this. as_metrics
publishes a per-shard weight version with the comment
"Catches a STALE shard that somehow served: a serving shard whose weight version trails the
trainer is a correctness bug, not a capacity problem."
It cannot catch it, because the number is written by the promotion rather than by the transfer.
report_refit L476
is the only line in the repo that assigns it, and report_refit is only ever called
from the promotion, only for shards that are STALE. So a shard that has never been
restarted never goes through it and reads 0 forever, however many refits it has
received β and the one shard that got nothing reads the current version, because the promotion is
what put it there. The metric says the opposite of the truth in both directions.
The PR takes the right shape. It computes the eligible set once, before the transfer, and promotes against it:
The comment above it names the hazard exactly: "a restart takes minutes and nothing blocks it,
so mark_loaded can turn a shard STALE mid-transfer." Right idea. The gap is where
participants gets its answer.
So the two sets come from two different reads with an await between them. A reload
landing in that gap puts shard 2 in participants while the communicator was built
without it β and the promotion lets it out on the strength of a transfer it never received.
Nothing below is implemented. The right set is the one the reconcile already used. Have
_reconcile_refit_membership return the absent
set it reconciled against, and have
_refit_participants take that set instead of calling
absent_shards() itself. Then one read feeds both, and there
is no gap to land in.
Both call sites need it β the main one at
L4705 and the recovery re-snapshot at
L4739, where the rebuild happens inside the recovery and
participants is read after it returns. By the code's own note the recovery one is the
likelier of the two: the shard is restarting because that refit just failed.
What this costs. The shard serves training rollouts generated from an old checkpoint, and nothing reports it β no error, no crash, no metric. Rollouts from stale weights are still valid-looking text, so the run keeps going and the numbers quietly get worse. The module docstring names this as the exact outcome the design exists to prevent: "re-admitting it because it started answering probes again would feed training rollouts generated from a checkpoint hundreds of steps stale β invisible, and far worse than the outage that caused it." The rule is right; the promotion just does not enforce it.