DPO 概要
-
DPO(Direct Preference Optimization,直接偏好優化)是由斯坦福大學等研究團隊于2023年提出的一種偏好優化算法,可用于LLM、VLM與MLLM的對齊訓練。
-
算法基于PPO的RLHF基礎上進行了大幅簡化。DPO算法跳過了訓練獎勵模型這一中間過程,直接(Direct)優化策略模型 ——這正是DPO命名中“D(Direct)”的含義所在。
主要流程
-
數據收集: 基于SFT訓練的模型作為推理模型,用戶輸入prompt,模型多次推理,找到好的答案和不好的答案。如果都是不好(rejected)的答案,則人工修改把不好的答案變為好的答案。
標數據收集 -
主要包含兩個基礎模型,策略模型&參考模型(不需要Reward模型)。 在trl強化學習框架中,只需要傳入策略模型,參考模型會復制一份策略模型。
-
策略模型是DPO需要訓練的模型,后用在項目中的模型。策略模型的權重直接復制SFT階段微調模型的權重
-
參考模型是策略模型的幫襯,其權重參數凍結不變。主要兩個作用,其一協助其計算reward loss,其二計算kl正則項,防止其訓練偏移初始SFT模型太遠,由一個β參數控制。
-
-
β參數控制含義
-
較大 beta(如 1.0):放大 reward 或 logp 的差異,使模型更“自信”地傾向于較優樣本,但容易過擬合或 reward 震蕩。
-
較小 beta(如 0.1):差異被壓縮,模型訓練更穩定,但收斂較慢、辨別力較弱。
-
極小 beta(趨近于 0):差異幾乎無效,模型無法區分好壞樣本,退化為隨機訓練
-
-
?整體流程如下:
-
具體流程
DPO訓練流程細節
九個損失函數解析
"loss": 1.8678"rewards/chosen": 42.519317626953125"rewards/rejected": -33.865535736083984"rewards/accuracies": 0.865429699420929"rewards/margins": 76.38734436035156"logps/chosen": -948.4149780273438"logps/rejected": -1285.1175537109375"logits/chosen": 5.363300800323486"logits/rejected": 4.879658222198486
-
logps/chosen和logps/rejected: logps 是模型生成 token 概率,在歸一化后(softmax)取 log 后的值(log prob)。
#1 把 prompt 和 response 拼接起來作為輸入 input = prompt + response from transformers import AutoTokenizer, AutoModelForCausalLM import torch# 加載 tokenizer 和模型 tokenizer = AutoTokenizer.from_pretrained("your-model-name") model = AutoModelForCausalLM.from_pretrained("your-model-name").cuda()# 設置 prompt 和 response prompt = "你今天心情怎么樣?" response = "我今天很開心,太陽出來了,我們一起去玩吧!"# 拼接輸入 full_input = prompt + response encodings = tokenizer(full_input, return_tensors="pt").to("cuda") input_ids = encodings["input_ids"]# 找到 response 的起始位置 prompt_ids = tokenizer(prompt, return_tensors="pt")["input_ids"].to("cuda") response_start = prompt_ids.shape[-1]# 前向推理,獲取 logits with torch.no_grad():outputs = model(**encodings)logits = outputs.logits# 計算 log probabilities log_probs = torch.nn.functional.log_softmax(logits, dim=-1)# 獲取 response 部分 token 的 log probability response_token_ids = input_ids[:, response_start:] response_logits = log_probs[:, response_start - 1:-1, :] # 對應 shift response_logp = torch.gather(response_logits, 2, response_token_ids.unsqueeze(-1)).squeeze(-1)# 平均 log probability(整個 response) logp_response = response_logp.mean()logps_chosen = compute_logp(prompt, chosen, actor_model) logps_rejected = compute_logp(prompt, rejected, actor_model) logps_ref_chosen = compute_logp(prompt, chosen, ref_model) logps_ref_rejected = compute_logp(prompt, rejected, ref_model)
-
logits/chosen和logits/rejected: 模型輸出的raw score(未進行歸一化)求平均
# 模型輸出:logits = [batch_size, seq_len, vocab_size] # 獲取 chosen 的最后一個 token 的 logit: logit_chosen = logits[:, -1, :] # 通常是這個位置 logits/chosen = logit_chosen.mean().item() # 拿出 chosen response 部分的 token 對應的 logit 向量 logits_response = logits[:, prompt_len:, :] # mask 掉 prompt 部分 logits/chosen = logits_response.mean().item()
-
reward 計算方法
chosen_rewards = self.beta * (chosen_logps.to(device) - ref_chosen_logps.to(device)).detach() rejected_rewards = self.beta * (rejected_logps.to(device) - ref_rejected_logps.to(device)).detach() reward_accuracies = (chosen_rewards > rejected_rewards).float() metrics[f"{prefix}rewards/chosen"] = self.accelerator.gather_for_metrics(chosen_rewards).mean().item() metrics[f"{prefix}rewards/rejected"] = self.accelerator.gather_for_metrics(rejected_rewards).mean().item() metrics[f"{prefix}rewards/accuracies"] = self.accelerator.gather_for_metrics(reward_accuracies).mean().item() metrics[f"{prefix}rewards/margins"] = ( self.accelerator.gather_for_metrics(chosen_rewards - rejected_rewards).mean().item()
-
Loss 計算方法
本次默認使用sigmoidlogratios = chosen_logps - rejected_logpsref_logratios = ref_chosen_logps - ref_rejected_logps logratios = logratios.to(self.accelerator.device)ref_logratios = ref_logratios.to(self.accelerator.device)logits = logratios - ref_logratios losses = (-F.logsigmoid(self.beta * logits) * (1 - self.label_smoothing)- F.logsigmoid(-self.beta * logits) * self.label_smoothing ) 其他計算方法如下(后續介紹):"hinge","ipo", "exo_pair","nca_pair","robust","bco_pair", "sppo_hard","aot","apo_down""aot_pair","apo_zero","discopop",
-
關系理解
指標
含義
關系
logits
每個 token 的原始輸出分數(未歸一化)
模型輸出的raw score(未進行歸一化)求平均
logps
所有 token 的 log 概率之和(對 logit softmax 后求 log,token-wise 累加)
來自 logits → softmax → log(prob) → sum over tokens
rewards
在 logp-based reward 情況下,reward 就是 sum(logps)/len(tokens)
eval_rewards/chosen == eval_logps/chosen/len(tokens)
-
主要關注指標
指標名
含義
影響
loss
當前 batch 的 DPO/IPO 損失值
反映訓練是否有效收斂,是否有發散/震蕩
rewards/margins
reward_chosen - reward_rejected 的平均值
反映模型區分正負樣本的能力是否提升
rewards/accuracies
reward_chosen > reward_rejected 的比例
反映偏好判斷正確率是否提高
logs/chosen& logs/rejected
每個 sample 的對數似然總和
趨勢變化判斷 token-level 擬合趨勢
其他思考
1.? logps/chosen是負的合理嗎
logps(y_{chosen}|x})logps(y_{chosen}|x})?是模型對生成chosen回復時,每個token的概率取對數后加總, 由于每一個token的概率?,所以。p(yt,y<t)∈(0,1),所以logp(yt)<0。?所以累加一段文本后,整個logp通常是一個比較大的負值。
2. reward為負值
因為是?rchosen=logπθ(ychosen|x)?,如果沒有額外reward打分模型,則?r=sum(logps)/len(logps)