feat(reward): add configurable reward clipping and batch standardization for GSPO/GRPO - #423
Open
Beisheng114 wants to merge 2 commits into
Open
feat(reward): add configurable reward clipping and batch standardization for GSPO/GRPO#423Beisheng114 wants to merge 2 commits into
Beisheng114 wants to merge 2 commits into
Conversation
added 2 commits
July 30, 2026 18:05
Add an optional reward transform layer between raw reward scoring and advantage computation for GSPO/GRPO training. Three modes (default disabled = numeric identity): - clip: clamp rewards to [clip_min, clip_max] - standardize: per-batch z-score (r - mean) / (std + eps) Key design: - RewardTransformConfig validates at construction; CLI preflight runs before model/worker init - seq.reward stays raw; seq.transformed_reward set only when enabled - Separate rollout/transformed_reward_* TensorBoard scalars - PPO/SFT/DPO rejected at validation (not silently ignored) - Empty/NaN/extreme/constant inputs handled with finite output or clear error Files: - areno/api/reward_transform.py (new): RewardTransformConfig + transform_rewards - areno/api/trainer_config.py: 4 new fields + reward_transform_config() factory - areno/api/models.py: TrainSequence.transformed_reward field - areno/api/metrics.py: collect + record transformed_reward distribution - areno/api/trainers/policy_only.py: apply transform before advantage computation - areno/cli/train.py: CLI options, preflight validation, summary, dashboard JSON - docs/cli/training.rst + observability.rst: option docs, examples, metric tags - tests/: 106 CPU tests (unit + integration + CLI + metrics) - CODE_REVIEW_REWARD_TRANSFORM.md + PR_REWARD_TRANSFORM.md: review docs Verified on Python 3.11.15: 106 focused tests passed, 379 broader CPU suite passed (1 unrelated failure from missing openai package).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds configurable reward clipping and per-batch standardization to AReno's GSPO/GRPO training pipeline, enabling operators to shape the reward distribution after scoring and before advantage computation. Three modes are supported —
disabled(default, numeric identity),clip(fixed-range clamping), andstandardize(per-batch z-score) — with raw and transformed reward distributions reported separately through logs, TensorBoard scalars, and CLI output.Related Issue
#208
Quick Start
What Changed
New Files
areno/api/reward_transform.pyRewardTransformConfig+transform_rewardstests/test_reward_transform_cpu.pyModified Files
areno/api/trainer_config.pyreward_transform_mode,reward_clip_min,reward_clip_max,reward_transform_epsfields +reward_transform_config()factory onPolicyTrainerConfigareno/api/models.pyTrainSequence.transformed_reward: float | None = Noneareno/api/metrics.pycollect_train_batch_statscollectstransformed_rewardsonly when set;record_training_statsemitsrollout/transformed_reward_*scalarsareno/api/trainers/policy_only.py_transform_batch_rewardshelper; both materialize methods apply transform beforecompute_group_advantagesareno/cli/train.py--reward-transform-mode/min/max/eps),_validate_reward_transformpreflight, summary row, dashboard JSON fieldsdocs/cli/training.rstdocs/cli/observability.rstrollout/transformed_reward_*TensorBoard tags +stage=reward_transformlog linetests/test_metrics_cpu.pytests/test_train_cli_config_cpu.py_optionshelper extendedKey Features
disabled(default, numeric identity),clip(clamp to[min, max]),standardize(per-batch z-score(r - mean) / (std + eps))disabledreturnslist(raw)exactly; no new TensorBoard scalars, log lines, or model fields appearValueErrorwith stage name + index (no sample payload exposed)seq.rewardstays raw;seq.transformed_rewardset only when enabled;rollout/rewards_*vsrollout/transformed_reward_*kept distinct end-to-endstandardizewith std=0 uses unit scale so output stays exactly zero and finiteRuntimeWarning)Reward Transform Flow
seq.rewardseq.transformed_rewardNoneotherwise)rewards_all(return)rollout/rewards_*rollout/transformed_reward_*stage=reward_transformraw[count mean std min max]transformed[...]Test Coverage
106 tests across 3 files, all CPU-only.
PYTHONPATH=. python -m pytest \ tests/test_reward_transform_cpu.py \ tests/test_train_cli_config_cpu.py \ tests/test_metrics_cpu.py -v # 106 passed, 1 warning in 1.86sRewardTransformUnitTestRewardTransformConfigTestenabledpropertyPolicyOnlyMaterializeIntegrationTesttransformed_reward, disabled emits no transform logMetricsUtilityTestReview Fixes
During two review passes, 7 issues were found and fixed:
transform_rewards([], standardize)emittedMean of empty sliceRuntimeWarnings. Fixed: early-return guard before mode dispatch. Addedtest_empty_input_emits_no_numpy_warnings._validate_reward_transformchecked eps but no CLI test exercised it. Addedtest_train_config_reward_transform_rejects_non_positive_eps+_rejects_nan_eps.test_train_config_reward_transform_clip_rejects_non_finite_bounds.test_training_config_summary_shows_reward_transform_standardize.observability.rstmissingtransformed_reward_*tags andstage=reward_transformlog line. Updated.raw[andtransformed[blocks in log. Added assertions.Breaking Changes
None. Default
reward_transform_mode="disabled"is a numeric identity.transformed_rewarddefaults toNone, so no new TensorBoard scalars or log lines appear.seq.rewardandrewards_allstay raw. All new config fields have safe defaults. Full CPU suite: 379 passed (1 unrelated failure from missingopenaipackage).Files Modified
Hardware Limitations
object.__new__(PolicyOnlyTrainer)withSimpleNamespaceconfig