[trainer] feat: early stopping via early_stop_patience config - #7069
[trainer] feat: early stopping via early_stop_patience config#7069Secbone wants to merge 4 commits into
Conversation
Add optional early_stop_patience config field for RayPPOTrainer. When set > 0 and critic/rewards/mean does not improve for that many steps, the training loop terminates early. Co-authored-by: Claude Signed-off-by: Secbone <secbone@gmail.com>
There was a problem hiding this comment.
Code Review
This pull request introduces early stopping logic to the PPO Ray trainer based on the mean critic rewards and a configured patience threshold. However, there is a logic bug in the initialization of the best reward tracking: using sequential if statements instead of an elif causes the patience counter to prematurely increment on the very first step, which can trigger early stopping immediately if patience is set to 1. Using an elif statement resolves this issue.
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.
| if not hasattr(self, "_best_reward"): | ||
| self._best_reward = reward | ||
| self._patience_counter = 0 | ||
| if reward > self._best_reward: | ||
| self._best_reward = reward | ||
| self._patience_counter = 0 | ||
| else: | ||
| self._patience_counter += 1 |
There was a problem hiding this comment.
There is a logic bug in the initialization of _best_reward. When _best_reward is not yet set, the first if block initializes it and sets _patience_counter = 0. However, because the subsequent check is a separate if statement rather than an elif, it immediately compares reward > self._best_reward (which is False since they are equal) and executes the else block, incrementing _patience_counter to 1 on the very first step. If early_stop_patience is set to 1, this will trigger early stopping immediately on the first step. Changing the second if to elif resolves this issue.
| if not hasattr(self, "_best_reward"): | |
| self._best_reward = reward | |
| self._patience_counter = 0 | |
| if reward > self._best_reward: | |
| self._best_reward = reward | |
| self._patience_counter = 0 | |
| else: | |
| self._patience_counter += 1 | |
| if not hasattr(self, "_best_reward"): | |
| self._best_reward = reward | |
| self._patience_counter = 0 | |
| elif reward > self._best_reward: | |
| self._best_reward = reward | |
| self._patience_counter = 0 | |
| else: | |
| self._patience_counter += 1 |
Co-authored-by: Claude Signed-off-by: Secbone <secbone@gmail.com>
| # TODO: make a canonical logger that supports various backend | ||
| logger.log(data=metrics, step=self.global_steps) | ||
|
|
||
| # early stopping |
There was a problem hiding this comment.
Legacy RayPPOTrainer is deprecated, please use V1 trainer instead.
What does this PR do?
Add optional
early_stop_patienceconfig field forRayPPOTrainer. When set > 0 andcritic/rewards/meandoes not improve for that many consecutive steps, the training loop terminates early. This helps avoid wasting compute when the model has converged.Checklist Before Starting
gh pr list --search "early stop early_stop"— no duplicates found.[{modules}] {type}: {description}(This will be checked by the CI){modules}includefsdp,megatron,veomni,sglang,vllm,rollout,trainer,ci,training_utils,recipe,hardware,deployment,ray,worker,single_controller,misc,perf,model,algo,env,tool,ckpt,doc,data,cfg,reward,fully_async,one_step_off,like[megatron, fsdp, doc]{type}is infeat,fix,refactor,chore,test[BREAKING]to the beginning of the title.[BREAKING][fsdp, megatron] feat: dynamic batchingTest
Set
early_stop_patience=3in the trainer config and verify that training stops after 3 steps without reward improvement. The feature is disabled by default (early_stop_patience=0) and has no side effects when not configured.API and Usage Example
Design & Code Changes
verl/trainer/ppo/ray_trainer.py: Insert ~20 lines of early stopping logic beforeprogress_bar.update(1)in the training loop.self.config.trainer.get("early_stop_patience", 0)— no config schema change needed.self._best_rewardand a patience counter.is_last_step = Trueandself.total_training_steps = self.global_stepsto trigger normal loop termination, checkpoint saving, and cleanup.Checklist Before Submitting
Important
Please check all the following items before requesting a review, otherwise the reviewer might deprioritize this PR for review.
pre-commit install && pre-commit run --all-files --show-diff-on-failure --color=alwaysci-requestchannel in theverlSlack workspace. (If not accessible, please try the Feishu group (飞书群).)recipesubmodule, please also update the reference to the submodule commit viagit submodule update --remoteorcd recipe && git pull origin main.