Repository navigation
[reward] fix: add optional per-sample compute_score timeout to NaiveRewardManager - #6673
Conversation
โฆewardManager NaiveRewardManager scores samples serially in the driver process, so a single compute_score call that hangs on a degenerate/long RL rollout (e.g. catastrophic regex backtracking or slow symbolic comparison in math scorers) blocks the whole training loop with no recovery. Add an opt-in compute_score_timeout (default None = unchanged behavior). When set, each call is time-bounded via SIGALRM; on timeout the sample is assigned reward 0.0 and training continues. Mirrors the timeout PrimeRewardManager already has. Includes CPU unit tests. Signed-off-by: qinglin <zengqlin3@mail2.sysu.edu.cn>
There was a problem hiding this comment.
Code Review
This pull request introduces a per-sample timeout mechanism for the compute_score function in NaiveRewardManager to prevent slow or hanging computations (such as catastrophic regex backtracking or slow symbolic evaluations) from blocking the main training loop. It implements a _score_timeout context manager using SIGALRM and updates NaiveRewardManager to accept a compute_score_timeout parameter, falling back to a reward of 0.0 if a timeout occurs. Comprehensive unit tests are also added to verify this functionality. There are no review comments to address, so no additional feedback is provided.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
|
Friendly ping on this one. The three failing checks (npu_unit_tests, the Ascend E2E job, and e2e_fully_async_policy_fsdp2) are unrelated to this change โ they also fail on main and on other recently merged PRs, and they exercise the NPU/Ascend and fully-async-policy paths, none of which this PR touches. The reward-manager unit tests and the rest of CI are green. The change is opt-in: compute_score_timeout defaults to None, so existing behavior is unchanged. Happy to switch the SIGALRM approach to a process pool like PrimeRewardManager uses if maintainers prefer that. Would appreciate a review when someone has a moment. Thanks! |
What does this PR do?
While running a multi-teacher on-policy distillation job (reverse-KL distillation of a 4B student), training reliably froze after ~5-8 steps: all GPUs dropped to 0% util and it looked like an sglang/NCCL deadlock.
sglang/NCCL turned out to be a red herring โ the NCCL watchdog never fired, and the sglang scheduler was just idle-spinning in its
recv_requests()loop waiting for the next request. ASIGABRT+faulthandlerdump showed the driver was actually stuck in reward scoring:The student had drifted into generating long, degenerate responses, and one such sample made a single
compute_scorecall extremely slow insidenormalize_final_answer. BecauseNaiveRewardManagerscores samples serially in the driver process with no timeout, that one stuck call blocks the entire training loop with no recovery. Swappingcompute_scorefor a no-op made the deterministic hang disappear, confirming reward computation was the blocker.PrimeRewardManageralready guards against this with a per-task timeout;NaiveRewardManagerdoes not. This PR adds an opt-in per-sample timeout toNaiveRewardManager. Default isNone(no timeout), so existing behavior is unchanged.Related: #2236, #4318
Checklist Before Starting
[reward] fix: ...Test
Added CPU unit tests in
tests/workers/reward_manager/test_naive_on_cpu.py:_score_timeoutraisesTimeoutErroron a slow block and is a no-op when disabled (None/0).NaiveRewardManagerwith a 10s mockcompute_scoreandcompute_score_timeout=1.0returns in ~1s with reward0.0instead of blocking.Also verified in verl's actual execution context: running the patched
NaiveRewardManagerinside a Ray actor with a 30s mockcompute_scoreandcompute_score_timeout=2.0returns in ~2.0s with reward0.0. This confirms theSIGALRM-based timeout fires in the Ray actor's main thread, which is where the reward manager runs in the training loop.API and Usage Example
New optional kwarg
compute_score_timeout(seconds), settable viareward_model.reward_kwargs:When a sample exceeds the timeout it is assigned reward
0.0(with a warning) and training continues.Design & Code Changes
_score_timeout(seconds): a context manager built onSIGALRM. It degrades to a no-op off the main thread (whereSIGALRMis unavailable), so it never crashes.NaiveRewardManager.__init__gainscompute_score_timeout=None.compute_scorecall is wrapped; onTimeoutErrorthe sample gets reward0.0+ a warning, and the loop continues.Note:
SIGALRMonly fires on the main thread, which is whereNaiveRewardManagerruns today. Happy to switch to a process-pool approach (likePrimeRewardManager) if maintainers prefer a thread-agnostic mechanism.Checklist Before Submitting
ruffclean).tests/workers/reward_manager/test_naive_on_cpu.py).