Explainer for lauradang/RL #2,
a PR into NVIDIA-NeMo/RL #4129.
All permalinks pin the PR head b311ce9 and Megatron-LM bde6af2.
tq_token_sink.py:694.Two pages on this PR, one question each â this one: the colocated bug.
Megatron keeps one list of routers per process. Every MoE router with replay turned on adds
itself to RouterReplay.global_router_replay_instances when it is built
(router_replay.py:112).
Megatron Inference (MInf) finds routers only through this list: it tells each one to record
(router_replay.py:61-64)
and reads the recorded routes back from it
(routing_metadata.py:75).
When the KL penalty is above 0, the policy worker also builds a reference model, and that setup ends by
emptying the whole list, the policy's routers included
(setup.py:2666-2667).
When generation shares the training GPUs (colocated) and has no separate inference model, MInf serves the
training model itself (_gen_model),
whose routers are no longer in the list.
The strict check in step 5 is correct â the PR does not want rollouts with missing routes. It is simply what turns the empty list into a 100% failure. Each failed call is marked failed (fail_call), the reassembler drops the group because no rollout has routes yet, and the SingleController never commits a dropped group (single_controller.py:1640-1655). The GPUs keep generating; the first training step never runs.
Which runs hit it â colocated only (a separate generation worker builds just one model):
| MInf serves | KL penalty | list when MInf runs | result |
|---|---|---|---|
| the training model | > 0 | [ ] | every call fails â this page |
| the training model | 0 | [P1, P2, P3] | works |
| a separate inference model (reshard) | > 0 | [I1, I2, I3] | works, by luck: it is built after the clear (:823) |
| a separate inference model (reshard) | 0 | [P1, P2, P3, I1, I2, I3] | likely fails: 6 layers of routes where 3 are expected (route_assembly.py:168); not run on a GPU |
Nothing below is implemented. Rebuild the list from the model MInf actually serves, at the top of
_initialize_inference_engine,
before DynamicInferenceEngine(...) is built
(megatron_worker.py:627).
Same example: 3 MoE layers, colocated, KL 0.01.
The order matters: the engine constructor captures CUDA graphs
(dynamic_engine.py:481) and sizes its route buffer
from the list (routing_metadata.py:44), so fixing the
list later is too late. _router_replay_instances_for_model already exists
(router_replay.py:159) and skips the extra multi-token-prediction (MTP) routers by default, which
matches the trainer's layer count.
Two fixes that look right but are not: deleting the clear leaves R1âŚR3 in the list (length 6, not 3); saving and restoring the list around the reference build fixes the main case but not reshard with KL 0.
grpo_math_1B.yaml
(colocated on, KL 0.01) plus the MInf config the PR adds to docs/guides/router-replay.md hits it.
No shipped recipe turns on router replay with MInf generation, so no recipe run shows it. Nothing here was run on
a GPU; it follows from the code above.