def compute_outcome_advantage(
token_level_rewards: torch.Tensor, response_mask: torch.Tensor,
index: np.ndarray, epsilon: float = 1e-6,
):
"""Maps the N rewards of each prompt group to N advantages."""
scores = token_level_rewards.sum(dim=-1)
id2score = group_by_prompt(scores, index) # unchanged verl grouping: {prompt: (N,)}
id2adv = {}
with torch.no_grad():
for idx, r in id2score.items():
N = r.numel()
# GRPO: standardize within the group
id2adv[idx] = (r - r.mean()) / (r.std() + epsilon)
# TailRL: w_(i) = sum_{k<=i} (r_(k) - r_(k-1)) / (N - k + 1)
rs, perm = torch.sort(r) # ascending rewards
w = (rs.diff(prepend=r.new_zeros(1)) / torch.arange(N, 0, -1).to(r)).cumsum(0)
id2adv[idx] = (w - w.mean())[perm.argsort()] # mean-center, unsort
adv = scatter_to_batch(id2adv, index) * response_mask # unchanged verl scatter
return adv, adv