Skip to content

Commit 2fe8a47

Browse files
macandro96root
authored andcommitted
fix(sc): address reward penalty review feedback
Signed-off-by: Anish Mahishi <amahishi@nvidia.com>
1 parent b684b46 commit 2fe8a47

6 files changed

Lines changed: 88 additions & 25 deletions

File tree

examples/configs/grpo_math_1B_megatron_single_controller.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ grpo:
88
async_grpo: null
99
val_period: 0
1010

11+
# Reward-zeroing penalties applied to NeMo-Gym rollout results.
12+
reward_penalties:
13+
penalize_duplicated_reasoning: false
14+
penalize_empty_final_answer: false
15+
penalize_unwanted_tokens: false
16+
penalize_malformed_think_tag: false
17+
# Optional model/tokenizer-specific IDs. Example:
18+
# token_ids: {unwanted: [2], think_open: 12, think_close: 13}
19+
1120
async_rl:
1221
sampler:
1322
name: in_order

examples/configs/ppo_math_1B_megatron_single_controller.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ ppo:
1616
reward_scaling:
1717
enabled: false
1818

19+
# Reward-zeroing penalties applied to NeMo-Gym rollout results.
20+
reward_penalties:
21+
penalize_duplicated_reasoning: false
22+
penalize_empty_final_answer: false
23+
penalize_unwanted_tokens: false
24+
penalize_malformed_think_tag: false
25+
# Optional model/tokenizer-specific IDs. Example:
26+
# token_ids: {unwanted: [2], think_open: 12, think_close: 13}
27+
1928
async_rl:
2029
sampler:
2130
name: in_order

nemo_rl/experience/rollout_manager.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@
5656
_effort_shaping_metrics,
5757
_find_routed_experts_template,
5858
_tensorize_by_key,
59-
attach_static_multimodal_payload,
6059
apply_reward_penalties,
60+
attach_static_multimodal_payload,
6161
calculate_rewards,
6262
compute_reward_penalty_metrics,
6363
)
@@ -1085,7 +1085,9 @@ def _compute_reward_penalty_metrics(
10851085
) -> dict[str, float]:
10861086
"""Return enabled penalty rates using the legacy Gym metric names."""
10871087
return compute_reward_penalty_metrics(
1088-
penalty_counts, num_results, self._reward_penalty_config
1088+
penalty_counts,
1089+
num_results,
1090+
self._reward_penalty_config,
10891091
)
10901092

10911093
def _compute_rollout_metrics(

nemo_rl/experience/rollouts.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,22 @@
7676

7777
TokenizerType = PreTrainedTokenizerBase
7878

79+
_REWARD_PENALTY_METRICS = {
80+
"duplicated_reasoning": (
81+
"penalize_duplicated_reasoning",
82+
"reasoning_equal_to_final_answer_rate",
83+
),
84+
"empty_final_answer": (
85+
"penalize_empty_final_answer",
86+
"empty_final_answer_rate",
87+
),
88+
"unwanted_token": ("penalize_unwanted_tokens", "unwanted_token_rate"),
89+
"malformed_think_tag": (
90+
"penalize_malformed_think_tag",
91+
"malformed_think_tag_rate",
92+
),
93+
}
94+
7995

8096
def attach_initial_nemo_gym_image_payloads(
8197
batch: BatchedDataDict[DatumSpec],
@@ -1875,6 +1891,22 @@ def _get_reward_penalty_config_value(
18751891
return getattr(reward_penalty_config, key, None)
18761892

18771893

1894+
def compute_reward_penalty_metrics(
1895+
penalty_counts: dict[str, int],
1896+
num_results: int,
1897+
reward_penalty_config: dict[str, Any] | BaseModel | None,
1898+
) -> dict[str, float]:
1899+
"""Return enabled penalty rates using the legacy NeMo-Gym metric names."""
1900+
if reward_penalty_config is None or not num_results:
1901+
return {}
1902+
1903+
return {
1904+
metric_name: penalty_counts[count_key] / num_results
1905+
for count_key, (flag, metric_name) in _REWARD_PENALTY_METRICS.items()
1906+
if _get_reward_penalty_config_value(reward_penalty_config, flag)
1907+
}
1908+
1909+
18781910
def _get_reward_penalty_token_id(
18791911
reward_penalty_config: dict[str, Any] | BaseModel,
18801912
key: str,
@@ -2823,26 +2855,13 @@ def _postprocess_single_nemo_gym_group(
28232855

28242856
rollout_metrics.update(_effort_shaping_metrics(shaping))
28252857

2826-
# Penalty metrics — map count keys to (config flag, metric name)
2827-
_PENALTY_METRICS = {
2828-
"duplicated_reasoning": (
2829-
"penalize_duplicated_reasoning",
2830-
"reasoning_equal_to_final_answer_rate",
2831-
),
2832-
"empty_final_answer": (
2833-
"penalize_empty_final_answer",
2834-
"empty_final_answer_rate",
2835-
),
2836-
"unwanted_token": ("penalize_unwanted_tokens", "unwanted_token_rate"),
2837-
"malformed_think_tag": (
2838-
"penalize_malformed_think_tag",
2839-
"malformed_think_tag_rate",
2840-
),
2841-
}
2842-
if resolved_reward_penalty_config and results:
2843-
for key, (flag, metric_name) in _PENALTY_METRICS.items():
2844-
if _get_reward_penalty_config_value(resolved_reward_penalty_config, flag):
2845-
rollout_metrics[metric_name] = penalty_counts[key] / len(results)
2858+
rollout_metrics.update(
2859+
compute_reward_penalty_metrics(
2860+
penalty_counts,
2861+
len(results),
2862+
resolved_reward_penalty_config,
2863+
)
2864+
)
28462865

28472866
# Expose per-component rewards as `reward/<name>` batch keys for multi-reward NeMo
28482867
# Gym environments so GDPO can compute per-component advantages; single-reward envs

tests/unit/experience/test_rollout_manager.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,12 +612,16 @@ def _mask_gate_result():
612612

613613

614614
def test_result_to_completion_keeps_mask_flag_when_gate_on():
615-
completion = _nemo_gym_impl(True)._results_to_completions([_mask_gate_result()])[0][0]
615+
completion = _nemo_gym_impl(True)._results_to_completions([_mask_gate_result()])[0][
616+
0
617+
]
616618
assert completion.env_extras["instance_config"]["mask_sample"] is True
617619

618620

619621
def test_result_to_completion_drops_mask_flag_when_gate_off():
620-
completion = _nemo_gym_impl(False)._results_to_completions([_mask_gate_result()])[0][0]
622+
completion = _nemo_gym_impl(False)._results_to_completions([_mask_gate_result()])[
623+
0
624+
][0]
621625
assert "mask_sample" not in completion.env_extras["instance_config"]
622626
assert completion.env_extras["instance_config"]["other_key"] == "kept"
623627

@@ -714,6 +718,22 @@ def test_nemo_gym_reward_penalties_match_legacy_rewards_counts_and_metrics(
714718
assert impl._compute_reward_penalty_metrics(penalty_counts, 1) == {metric_name: 1.0}
715719

716720

721+
def test_nemo_gym_reward_penalty_metrics_compute_fractional_rate():
722+
impl = _nemo_gym_impl(True, {"penalize_empty_final_answer": True})
723+
724+
metrics = impl._compute_reward_penalty_metrics(
725+
{
726+
"duplicated_reasoning": 0,
727+
"empty_final_answer": 1,
728+
"unwanted_token": 0,
729+
"malformed_think_tag": 0,
730+
},
731+
3,
732+
)
733+
734+
assert metrics == {"empty_final_answer_rate": 1 / 3}
735+
736+
717737
# ---------------------------------------------------------------------------
718738
# Tests for AsyncRolloutManager (native async path)
719739
# ---------------------------------------------------------------------------

tests/unit/experience/test_rollouts.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2210,7 +2210,11 @@ def remote(self, inputs, timer_prefix):
22102210
}
22112211
manager._tokenizer = None
22122212
manager._effort_config = None
2213-
manager._result_to_completion = lambda result: result["value"]
2213+
manager._results_to_completions = lambda results: (
2214+
[result["value"] for result in results],
2215+
{},
2216+
)
2217+
manager._compute_reward_penalty_metrics = lambda counts, num_results: {}
22142218
manager._compute_rollout_metrics = lambda completions, agent: {
22152219
"completion_count": len(completions),
22162220
"agent": agent,

0 commit comments

Comments
 (0)