130 lines
5.0 KiB
Python
130 lines
5.0 KiB
Python
import argparse
|
|
import random
|
|
|
|
import torch
|
|
|
|
|
|
def masked_sequence_mean(per_token_loss: torch.Tensor, mask: torch.Tensor) -> torch.Tensor:
|
|
return (per_token_loss * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
|
|
|
|
|
|
def group_relative_advantages(rewards: torch.Tensor, num_generations: int, eps: float) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
grouped_rewards = rewards.view(-1, num_generations)
|
|
mean_r = grouped_rewards.mean(dim=1).repeat_interleave(num_generations)
|
|
std_r = grouped_rewards.std(dim=1, unbiased=False).repeat_interleave(num_generations)
|
|
advantages = (rewards - mean_r) / (std_r + eps)
|
|
return advantages, mean_r, std_r
|
|
|
|
|
|
def token_kl(policy_logps: torch.Tensor, ref_logps: torch.Tensor) -> torch.Tensor:
|
|
kl_div = ref_logps - policy_logps
|
|
return torch.exp(kl_div) - kl_div - 1.0
|
|
|
|
|
|
def grpo_loss(
|
|
policy_logps: torch.Tensor,
|
|
old_logps: torch.Tensor,
|
|
ref_logps: torch.Tensor,
|
|
advantages: torch.Tensor,
|
|
mask: torch.Tensor,
|
|
epsilon: float,
|
|
beta: float,
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
ratio = torch.exp(policy_logps - old_logps)
|
|
clipped_ratio = torch.clamp(ratio, 1.0 - epsilon, 1.0 + epsilon)
|
|
per_token_kl = token_kl(policy_logps, ref_logps)
|
|
loss1 = ratio * advantages.unsqueeze(1)
|
|
loss2 = clipped_ratio * advantages.unsqueeze(1)
|
|
per_token_loss = -(torch.min(loss1, loss2) - beta * per_token_kl)
|
|
return masked_sequence_mean(per_token_loss, mask).mean(), ratio, per_token_kl
|
|
|
|
|
|
def cispo_loss(
|
|
policy_logps: torch.Tensor,
|
|
old_logps: torch.Tensor,
|
|
ref_logps: torch.Tensor,
|
|
advantages: torch.Tensor,
|
|
mask: torch.Tensor,
|
|
epsilon_high: float,
|
|
beta: float,
|
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
ratio = torch.exp(policy_logps - old_logps)
|
|
clamped_ratio = torch.clamp(ratio, max=epsilon_high).detach()
|
|
per_token_kl = token_kl(policy_logps, ref_logps)
|
|
per_token_loss = -(clamped_ratio * advantages.unsqueeze(1) * policy_logps - beta * per_token_kl)
|
|
return masked_sequence_mean(per_token_loss, mask).mean(), ratio, per_token_kl
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Trace MiniMind GRPO/CISPO group advantages and losses.")
|
|
parser.add_argument("--batch_size", type=int, default=2)
|
|
parser.add_argument("--num_generations", type=int, default=3)
|
|
parser.add_argument("--response_len", type=int, default=4)
|
|
parser.add_argument("--epsilon", type=float, default=0.2)
|
|
parser.add_argument("--epsilon_high", type=float, default=5.0)
|
|
parser.add_argument("--beta", type=float, default=0.1)
|
|
args = parser.parse_args()
|
|
|
|
random.seed(42)
|
|
torch.manual_seed(42)
|
|
|
|
total = args.batch_size * args.num_generations
|
|
rewards = torch.tensor([2.0, 1.0, -1.0, 0.5, 0.5, 0.5], dtype=torch.float32)[:total]
|
|
if rewards.numel() < total:
|
|
rewards = torch.linspace(-1.0, 1.0, total)
|
|
|
|
old_logps = torch.randn(total, args.response_len) * 0.2 - 1.5
|
|
policy_logps = old_logps + torch.randn(total, args.response_len) * 0.15
|
|
ref_logps = old_logps + torch.randn(total, args.response_len) * 0.10
|
|
completion_mask = torch.ones(total, args.response_len)
|
|
completion_mask[1, -1] = 0
|
|
completion_mask[-1, -2:] = 0
|
|
|
|
advantages, mean_r, std_r = group_relative_advantages(rewards, args.num_generations, eps=1e-4)
|
|
grpo, grpo_ratio, grpo_kl = grpo_loss(
|
|
policy_logps,
|
|
old_logps,
|
|
ref_logps,
|
|
advantages,
|
|
completion_mask,
|
|
args.epsilon,
|
|
args.beta,
|
|
)
|
|
cispo, cispo_ratio, cispo_kl = cispo_loss(
|
|
policy_logps,
|
|
old_logps,
|
|
ref_logps,
|
|
advantages,
|
|
completion_mask,
|
|
args.epsilon_high,
|
|
args.beta,
|
|
)
|
|
|
|
grouped_adv = advantages.view(args.batch_size, args.num_generations)
|
|
print("[Group rewards]")
|
|
print(f"rewards.shape={tuple(rewards.shape)}")
|
|
print(f"grouped_rewards={rewards.view(args.batch_size, args.num_generations).tolist()}")
|
|
print(f"group_mean={mean_r.view(args.batch_size, args.num_generations)[:, 0].tolist()}")
|
|
print(f"group_std={std_r.view(args.batch_size, args.num_generations)[:, 0].tolist()}")
|
|
print(f"advantages={grouped_adv.tolist()}")
|
|
print(f"group_adv_mean={grouped_adv.mean(dim=1).tolist()}")
|
|
print()
|
|
print("[Token tensors]")
|
|
print(f"policy_logps.shape={tuple(policy_logps.shape)}")
|
|
print(f"old_logps.shape={tuple(old_logps.shape)}")
|
|
print(f"ref_logps.shape={tuple(ref_logps.shape)}")
|
|
print(f"completion_mask={completion_mask.tolist()}")
|
|
print()
|
|
print("[Losses]")
|
|
print(f"ratio_min={grpo_ratio[completion_mask.bool()].min().item():.8f}")
|
|
print(f"ratio_max={grpo_ratio[completion_mask.bool()].max().item():.8f}")
|
|
print(f"kl_mean={(grpo_kl * completion_mask).sum().item() / completion_mask.sum().item():.8f}")
|
|
print(f"grpo_loss={grpo.item():.8f}")
|
|
print(f"cispo_loss={cispo.item():.8f}")
|
|
print(f"ratio_tensors_match={torch.allclose(grpo_ratio, cispo_ratio)}")
|
|
print(f"kl_tensors_match={torch.allclose(grpo_kl, cispo_kl)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|