Source code for trinity.algorithm.advantage_fn.opmd_advantage
"""OPMD advantage computation"""
from collections import defaultdict
from typing import TYPE_CHECKING, Dict, List, Tuple
import torch
if TYPE_CHECKING:
from verl import DataProto
from trinity.algorithm.advantage_fn.advantage_fn import AdvantageFn, GroupAdvantage
from trinity.common.experience import Experience, group_by
from trinity.utils.annotations import Deprecated
[docs]
@Deprecated
class OPMDAdvantageFn(AdvantageFn):
"""OPMD advantage computation"""
[docs]
def __init__(
self,
opmd_baseline: str = "mean",
tau: float = 1.0,
) -> None:
self.opmd_baseline = opmd_baseline
self.tau = tau
def __call__(
self,
exps: "DataProto",
**kwargs,
) -> Tuple["DataProto", Dict]:
"""Modified from compute_grpo_outcome_advantage
Compute advantage for OPMD, operating only on Outcome reward
(with only one scalar reward for each response).
token_level_rewards: `(torch.Tensor)`
shape: (bs, response_length)
eos_mask: `(torch.Tensor)`
shape: (bs, response_length)
scores: `(torch.Tensor)`
shape: (bs, response_length)
"""
token_level_rewards = exps.batch["token_level_rewards"]
eos_mask = exps.batch["response_mask"]
# TODO (yanxi): confirm consistency with exps.batch["attention_mask"][:, -response_length:] in original implementation
index = exps.non_tensor_batch["uid"]
opmd_baseline = self.opmd_baseline
tau = self.tau
response_length = token_level_rewards.shape[-1]
scores = token_level_rewards.sum(dim=-1)
id2score = defaultdict(list)
id2baseline = {}
with torch.no_grad():
bsz = scores.shape[0]
for i in range(bsz):
id2score[index[i]].append(scores[i])
for idx in id2score:
if len(id2score[idx]) == 1:
id2baseline[idx] = torch.tensor(0.0)
# TODO: consider id2baseline[idx] = id2score[idx] (so that this sample won't take effect?)
elif len(id2score[idx]) > 1:
if opmd_baseline == "mean":
id2baseline[idx] = torch.mean(
torch.tensor(id2score[idx], dtype=torch.float32)
)
elif opmd_baseline == "logavgexp":
rewards_tensor = torch.tensor(id2score[idx], dtype=torch.float32)
# here we use the fact that logavgexp(x) = logsumexp(x) - log(len(x))
id2baseline[idx] = tau * (
torch.logsumexp(rewards_tensor / tau, dim=-1)
- torch.log(torch.tensor(len(id2score[idx])))
)
else:
raise NotImplementedError
else:
raise ValueError(f"no score in prompt index: {idx}")
for i in range(bsz):
scores[i] = scores[i] - id2baseline[index[i]]
scores = scores.unsqueeze(-1).tile([1, response_length]) * eos_mask
exps.batch["advantages"] = scores
exps.batch["returns"] = scores
metrics = {
# TODO: add meaningful metrics
}
return exps, metrics
[docs]
@classmethod
def default_args(cls) -> Dict:
return {
"opmd_baseline": "mean",
"tau": 1.0,
}
[docs]
class OPMDGroupAdvantage(GroupAdvantage):
"""OPMD Group Advantage computation"""
[docs]
def __init__(self, opmd_baseline: str = "mean", tau: float = 1.0, **kwargs) -> None:
super().__init__(**kwargs)
self.opmd_baseline = opmd_baseline
self.tau = tau
[docs]
def group_experiences(self, exps):
return group_by(exps, id_type="task")
[docs]
def calculate_group_advantage(
self, group_id: str, exps: List[Experience]
) -> Tuple[List[Experience], Dict]:
with torch.no_grad():
group_rewards = torch.tensor([exp.reward for exp in exps], dtype=torch.float32)
reward_mean = torch.mean(group_rewards)
if len(exps) == 1:
group_baseline = torch.tensor(0.0)
else:
if self.opmd_baseline == "mean":
group_baseline = reward_mean
else:
group_baseline = self.tau * (
torch.logsumexp(group_rewards / self.tau, dim=-1)
- torch.log(torch.tensor(len(exps)))
)
for exp in exps:
score = exp.reward - group_baseline
exp.advantages = score * exp.action_mask
exp.returns = exp.advantages.clone()
metrics = {
"group_baseline": group_baseline.item(),
"reward_mean": reward_mean.item(),
}
return exps, metrics
[docs]
@classmethod
def default_args(cls) -> dict:
return {"opmd_baseline": "mean", "tau": 1.0}