Companion illustration for
NVIDIA-NeMo/RL #3590
(part 2/4 of the SingleController resiliency stack).
Two snapshots: the first map and its links pin the review-time head cff673e;
the second map shows the PR after the author's review fixes and pins the current head 5c9b813.
Six pages, one question each โ this one: #3590 ยท the ledger.
The confusing part of this PR is that it looks like three watchers doing the same job. They are not.
The GenerationFleetMonitor
is a ledger โ a plain-Python table of per-shard health that watches nothing by itself.
Two things write facts into it: a probe loop that asks every serving vLLM shard "are you alive?" every 5 seconds
(single_controller.py:718),
and the generate path, which reports failures it hits on real requests
(vllm_generation.py:910).
Three things read it: the shard picker for the next request, the membership push to the NeMo-Gym router, and the watchdog.
The watchdog is a different animal โ it does not care about shards; it checks one number every 30 seconds
(how long since any rollout finished?) and kills the run if the answer is "too long"
(:715).
On the NeMo-Gym path there is one more piece: a router actor that all Gym requests flow through.
The ledger's serving set is pushed into it โ at review time the router asked nothing and reported
nothing back. The map below is the review-time picture, kept as-is; the names in it are the old ones,
and the second map further down shows what the author changed.
The same map after the review fixes
Same layout, current head 5c9b813. Green marks what changed.
Everything got renamed for what it watches (GenerationFleetHealth, gen fleet probe
loop, stall watchdog, controller liveness watch, GenerationRouter),
and one arrow is new: the router now counts the failures it sees on real traffic and the probe tick
pulls those counts into the ledger โ the router is no longer silent, and a wedged engine that
passes every probe is condemned anyway.
One dead engine, step by step
Two shards, default settings: probe every 5 s, 3 failures to condemn, floor of 1 serving shard.
Kill shard 1's engine at t=0.
t
what happens
shard 1 state
who acted
5 s
probe fails (1 of 3)
SUSPECT โ still takes traffic
probe loop
~6 s
a real request hits shard 1, fails, is retried on shard 0
SUSPECT (2 of 3)
generate path
10 s
probe fails (3 of 3) โ quarantined, epoch 0โ1
DEAD
ledger rule
10 s+
picker skips shard 1; next 5 s tick pushes [shard0] to the Gym router
DEAD
readers
30 s tick
watchdog publishes fleet/shards/dead=1; 1 serving โฅ floor โ run continues
DEAD
watchdog
If shard 0 also died, the watchdog's floor check would end the run with
GenerationFleetExhausted โ the failure names the cause. Without this PR, the only
backstop is the stall watchdog noticing silence minutes later and saying RolloutStall โ
true, but it names the symptom. A restarted engine does not come back on its own:
answering probes again proves nothing about its weights, so re-entry requires a completed weight sync
(that machinery lands in parts 3โ4 of the stack).
Does it work?
claim
evidence
faster, better-named failure
same kill-one-shard test on a 2-GPU box: part 1 dies in 222 s with RolloutStall; this PR dies in 62 s with GenerationFleetExhausted (from the PR description)
the router does not corrupt payloads
end-to-end Gym run through the router: gen_kl_error (vLLM logprobs vs trainer recompute) unchanged
ledger + router behavior
unit tests run against real HTTP servers, now including the wedge cases (500-never-504, reflex drop, drained counts, probe-proof failure streak). The review's caveat is fixed: both files are collected by the L0 vLLM unit lanes. PR CI at 5c9b813: 91 checks pass, 0 fail
failover end to end
grpo_sc_gym_router_failover.sh: kill one of two shards mid-run, training finishes through the router (needs โฅ3 GPUs, so it self-skips on 2-GPU CI machines)
What to remember. The ledger never acts on its own โ observers write it, readers
consult it, and it is now named for that (GenerationFleetHealth, not "monitor"). The
watchdog stays even with fleet health on, because it answers a question no per-shard probe can: is the
run as a whole still making progress? The router is no longer a silent reader of pushes: it counts the
failures only real traffic can reveal, and the probe tick pulls them into the ledger โ see the
wedge-quarantine page for that path in detail. And enabling
the router without generation_fleet_health.enabled=true now warns at setup and disarms the
router's reflex drop, instead of silently staying health-blind.