Skip to content

Commit 441f5a3

Browse files
committed
[algo] fix: remove dead code in GPG advantage estimator
`compute_gpg_outcome_advantage` builds an `id2std` dict (and the per-group std/constant assignments that populate it) that is never read: the normalization on the final line divides by `f_norm`, not by the group std. It also accepts an `alpha` parameter that is unconditionally overwritten by `alpha = bsz / count_nonzero(scores)` before its first use, so the caller-supplied value can never take effect. No caller passes `alpha` (the advantage dispatch in ray_trainer.py builds kwargs without it). Remove the unused `id2std` dict and the no-op `alpha` parameter. Behavior is unchanged; the function still applies the documented N/N_nonzero scaling to group-centered scores. Add a CPU unit test covering the scaling and the singleton-group case. Addresses issue #6478 (Item B). Item A (clamp) is handled separately in #6538.
1 parent 8a69493 commit 441f5a3

2 files changed

Lines changed: 61 additions & 5 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Copyright 2025 Bytedance Ltd. and/or its affiliates
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import numpy as np
16+
import torch
17+
18+
from verl.trainer.ppo.core_algos import compute_gpg_outcome_advantage
19+
20+
21+
def test_gpg_singleton_group_returns_raw_score():
22+
"""A single response whose score is non-zero gets advantage == raw score.
23+
24+
For a singleton group the group mean is 0, and with every score non-zero
25+
the GPG scale ``alpha = bsz / count_nonzero(scores)`` is 1, so the
26+
advantage reduces to the raw (masked) reward.
27+
"""
28+
token_level_rewards = torch.tensor([[0.0, 3.0, 0.0]], dtype=torch.float32)
29+
response_mask = torch.tensor([[1.0, 1.0, 1.0]], dtype=torch.float32)
30+
index = np.array(["prompt-a"], dtype=object)
31+
32+
advantages, returns = compute_gpg_outcome_advantage(
33+
token_level_rewards=token_level_rewards,
34+
response_mask=response_mask,
35+
index=index,
36+
)
37+
38+
raw_score = token_level_rewards.sum(dim=-1, keepdim=True) * response_mask
39+
torch.testing.assert_close(advantages, raw_score)
40+
torch.testing.assert_close(returns, advantages)
41+
42+
43+
def test_gpg_applies_n_over_nonzero_scaling():
44+
"""GPG scales centered scores by ``N / N_nonzero``.
45+
46+
With two responses in one group (scores 4 and 0) the group mean is 2.
47+
``alpha = bsz / count_nonzero = 2 / 1 = 2``, so each advantage is
48+
``2 * (score - 2)``.
49+
"""
50+
token_level_rewards = torch.tensor([[4.0], [0.0]], dtype=torch.float32)
51+
response_mask = torch.ones_like(token_level_rewards)
52+
index = np.array(["prompt-a", "prompt-a"], dtype=object)
53+
54+
advantages, _ = compute_gpg_outcome_advantage(
55+
token_level_rewards=token_level_rewards,
56+
response_mask=response_mask,
57+
index=index,
58+
)
59+
60+
expected = torch.tensor([[4.0], [-4.0]], dtype=torch.float32)
61+
torch.testing.assert_close(advantages, expected)

verl/trainer/ppo/core_algos.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -772,7 +772,6 @@ def compute_gpg_outcome_advantage(
772772
index: np.ndarray,
773773
epsilon: float = 1e-6,
774774
f_norm: float = 1.0,
775-
alpha: float = 1.0,
776775
config=None,
777776
**kwargs,
778777
):
@@ -788,7 +787,6 @@ def compute_gpg_outcome_advantage(
788787
shape: (bs,)
789788
epsilon: (float)
790789
f_norm: (float)
791-
alpha: (float)
792790
config: (dict) algorithm config
793791
794792
Returns:
@@ -801,7 +799,6 @@ def compute_gpg_outcome_advantage(
801799

802800
id2score = defaultdict(list)
803801
id2mean = {}
804-
id2std = {}
805802

806803
with torch.no_grad():
807804
bsz = scores.shape[0]
@@ -814,11 +811,9 @@ def compute_gpg_outcome_advantage(
814811
for idx in id2score:
815812
if len(id2score[idx]) == 1:
816813
id2mean[idx] = torch.tensor(0.0)
817-
id2std[idx] = torch.tensor(1.0)
818814
elif len(id2score[idx]) > 1:
819815
scores_tensor = torch.stack(id2score[idx])
820816
id2mean[idx] = torch.mean(scores_tensor)
821-
id2std[idx] = torch.std(scores_tensor)
822817
else:
823818
raise ValueError(f"no score in prompt index: {idx}")
824819
for i in range(bsz):

0 commit comments

Comments
 (0)