MiniSEAL / src /alignment /losses.py
aaronr43210's picture
Upload src
27d37fd verified
Raw History Blame Contribute Delete
3.16 kB
"""
Loss functions for SFT and DPO.
SFT:
Standard causal LM loss on response tokens.
DPO:
Direct Preference Optimization:
-log sigmoid(beta * [(log pi chosen - log ref chosen)
- (log pi rejected - log ref rejected)])
"""
import torch
import torch.nn.functional as F
def causal_lm_loss(logits: torch.Tensor, labels: torch.Tensor) -> torch.Tensor:
"""
Calculates causal LM loss.
Input:
logits: [batch, seq, vocab]
labels: [batch, seq]
Labels use -100 for ignored positions.
Output:
scalar loss.
Important:
logits[:, t] predicts labels[:, t+1]
"""
shift_logits = logits[:, :-1, :].contiguous()
shift_labels = labels[:, 1:].contiguous()
loss = F.cross_entropy(
shift_logits.view(-1, shift_logits.size(-1)),
shift_labels.view(-1),
ignore_index=-100,
)
return loss
def sequence_logprobs(
model,
input_ids: torch.Tensor,
labels: torch.Tensor,
) -> torch.Tensor:
"""
Computes sequence log probability of only supervised answer tokens.
Input:
model: policy or reference model.
input_ids: [batch, seq]
labels: [batch, seq], -100 on ignored tokens.
Output:
log probability per sequence: [batch]
"""
logits, _ = model(input_ids, targets=None)
shift_logits = logits[:, :-1, :].contiguous()
shift_labels = labels[:, 1:].contiguous()
log_probs = F.log_softmax(shift_logits, dim=-1)
mask = shift_labels != -100
safe_labels = shift_labels.masked_fill(~mask, 0)
token_log_probs = torch.gather(
log_probs,
dim=-1,
index=safe_labels.unsqueeze(-1),
).squeeze(-1)
token_log_probs = token_log_probs * mask
return token_log_probs.sum(dim=-1)
def dpo_loss(
policy_chosen_logps: torch.Tensor,
policy_rejected_logps: torch.Tensor,
ref_chosen_logps: torch.Tensor,
ref_rejected_logps: torch.Tensor,
beta: float,
) -> tuple[torch.Tensor, dict]:
"""
Direct Preference Optimization loss.
Input:
policy_chosen_logps: log pi_theta(chosen)
policy_rejected_logps: log pi_theta(rejected)
ref_chosen_logps: log pi_ref(chosen)
ref_rejected_logps: log pi_ref(rejected)
beta: KL trade-off parameter.
Output:
loss
metrics dictionary
"""
policy_logratios = policy_chosen_logps - policy_rejected_logps
ref_logratios = ref_chosen_logps - ref_rejected_logps
logits = beta * (policy_logratios - ref_logratios)
losses = -F.logsigmoid(logits)
chosen_rewards = beta * (policy_chosen_logps - ref_chosen_logps)
rejected_rewards = beta * (policy_rejected_logps - ref_rejected_logps)
reward_accuracy = (chosen_rewards > rejected_rewards).float().mean()
metrics = {
"reward_accuracy": reward_accuracy.detach().item(),
"chosen_reward": chosen_rewards.mean().detach().item(),
"rejected_reward": rejected_rewards.mean().detach().item(),
"margin": (chosen_rewards - rejected_rewards).mean().detach().item(),
}
return losses.mean(), metrics