学习目标理解RLHF三阶段流程的完整架构掌握PPO算法在LLM对齐中的特殊实现学会构建完整的RLHF训练流程理解KL散度约束的重要性一、RLHF完整流程回顾1.1 三阶段架构RLHFReinforcement Learning from Human Feedback通常包含三个阶段┌──────────────────────────────────────────────────────────────┐ │ RLHF Pipeline │ ├──────────────────────────────────────────────────────────────┤ │ │ │ Stage 1: SFT Stage 2: Reward Model Stage 3: RL│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ 预训练模型 │ │ 偏好数据 │ │ PPO训练 │ │ │ ↓ │ │ ↓ │ │ ↓ │ │ │ SFT微调 │────→ │ 奖励模型 │────→ │ 策略优化 │ │ │ (监督学习) │ │ ( pairwise ) │ │ (强化学习) │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ 数十B参数 ~10B参数 数十B参数 │ │ 需要GPU多 相对轻量 需要GPU多 │ └──────────────────────────────────────────────────────────────┘1.2 各阶段的作用阶段输入输出目标SFT预训练模型 指令数据遵循指令的基础模型让模型学会「回答问题」Reward ModelSFT模型 偏好数据奖励模型学习「什么是好回答」RL (PPO)SFT模型 奖励模型对齐后的模型优化「产生高奖励的回答」二、为什么需要RL2.1 SFT的局限性SFT监督微调虽然能让模型学会格式和风格但存在以下问题泛化有限只能模仿训练数据中的模式难以表达复杂偏好有些「好」难以用示例表达分布偏移模型生成的分布与训练数据分布不一致2.2 RL的优势RL通过奖励信号优化能够发现新模式探索训练数据中没有的「好回答」平衡多个目标同时优化有用性、无害性、诚实性处理主观性用奖励信号表达难以显式定义的目标三、KL散度约束3.1 什么是KL散度KL散度Kullback-Leibler Divergence衡量两个分布的差异KL(P || Q) Σ P(x) * log(P(x) / Q(x))在RLHF中我们用它来衡量新策略与**参考策略SFT模型**的差异。3.2 为什么需要KL约束没有KL约束的RL优化会导致「奖励黑客」问题无KL约束的问题 ┌─────────────────────────────────────────────────┐ │ 目标最大化奖励 │ │ │ │ 手段发现奖励模型的漏洞 │ │ 例如 │ │ - 生成重复但看似合理的文本 │ │ - 迎合奖励模型的偏见 │ │ - 产生「安全但无用」的回答 │ └─────────────────────────────────────────────────┘KL约束的作用有KL约束的优化目标 max_π E[r] - β * KL(π || π_ref) 其中 - π当前策略我们要优化的模型 - π_ref参考策略SFT模型 - βKL系数控制约束强度 解释 - 最大化奖励的同时 - 保持与参考策略不要偏离太远3.3 KL系数的选择classKLController:自适应KL系数控制器def__init__(self,target_kl0.1,init_beta0.1):self.target_kltarget_kl self.betainit_beta self.beta_max2.0self.beta_min1e-4defupdate(self,current_kl:float): 根据当前KL值调整beta 如果KL太高 → 降低beta放松约束 如果KL太低 → 提高beta收紧约束 ifcurrent_kl2*self.target_kl:self.beta*1.5# 放宽约束elifcurrent_klself.target_kl/2:self.beta*0.5# 收紧约束# 限制范围self.betamax(self.beta_min,min(self.beta_max,self.beta))returnself.beta四、PPO算法核心4.1 PPO的基本思想PPOProximal Policy Optimization的核心是限制策略更新的幅度普通策略梯度 ∇J E[∇log π(a|s) * A(s,a)] 问题更新步长难以控制可能导致策略崩溃 PPO解决方案 通过裁剪Clipping限制更新幅度 L^CLIP(θ) E[ min(r_t(θ) * A_t, clip(r_t(θ), 1-ε, 1ε) * A_t) ] 其中 - r_t(θ) π_θ(a_t|s_t) / π_θ_old(a_t|s_t) 重要性比率 - clip(x, a, b)将x限制在[a, b]范围内 - ε裁剪参数通常0.24.2 为什么裁剪有效 PPO裁剪的几何解释 假设优势函数A0意味着这个动作是好的 ┌────────────────────────────────────────────────────────┐ │ │ │ 策略比率 r 目标函数L │ │ │ │ r1.5────────────────→ Lr*A │ │ ↑ ↑ │ │ │ │ │ │ │ 裁剪后 │ 裁剪后│ │ ↓ ↓ │ │ r1.2────────────────→ L1.2*A │ │(1ε)(上限)│ │ │ │ 当r1ε时不再增加优化目标 │ │ 防止过度的策略更新 │ └────────────────────────────────────────────────────────┘4.3 PPO在LLM中的特殊实现LLM场景下的PPO与标准RL环境有几个关键区别classLLMPPOConfig:LLM场景下的PPO配置def__init__(self):# 1. 模型配置self.model_namegpt2-mediumself.learning_rate1e-5# 2. PPO特定参数self.ppo_epochs4# 每个batch的PPO更新轮数self.mini_batch_size4# 小批次大小self.gradient_accumulation_steps2# 3. KL约束参数self.kl_coeff0.1# KL惩罚系数self.target_kl0.1# 目标KL值# 4. 裁剪参数self.clip_range_ratio0.2# PPO裁剪范围self.clip_range_value0.2# Value function裁剪# 5. 价值函数self.vf_coef0.5# 价值函数损失系数self.vf_clip_range0.2# 价值函数裁剪范围五、完整RLHF训练流程5.1 数据准备dataclassclassRLHFData:RLHF训练数据prompt:str# 输入promptreference_response:str# 参考回答可选classRLHFDataset(Dataset):RLHF训练数据集def__init__(self,prompts:List[str]):self.promptspromptsdef__len__(self):returnlen(self.prompts)def__getitem__(self,idx)-dict:return{prompt:self.prompts[idx]}5.2 生成rolloutsclassRolloutCollector:收集模型生成的rolloutsdef__init__(self,policy_model,ref_model,reward_model):self.policypolicy_model self.refref_model self.rewardreward_modeltorch.no_grad()defgenerate_rollouts(self,prompts:List[str])-dict:为每个prompt生成rolloutrollouts{query:[],# 原始queryresponse:[],# 生成的responseref_response:[],# 参考模型的responserewards:[],# 奖励分数log_probs:[],# 策略模型的log概率ref_log_probs:[],# 参考模型的log概率values:[]# 价值估计}forpromptinprompts:# 1. 生成responseresponse_ids,log_probsself.policy.generate(prompt)# 2. 计算参考模型的log_probswithtorch.no_grad():ref_log_probsself.ref.compute_log_probs(prompt,response_ids)# 3. 计算奖励rewardself.reward(prompt,response_ids)# 4. 存储rollouts[query].append(prompt)rollouts[response].append(response_ids)rollouts[log_probs].append(log_probs)rollouts[ref_log_probs].append(ref_log_probs)rollouts[rewards].append(reward)returnrollouts5.3 PPO训练步骤classPPOtrainer:PPO训练器def__init__(self,config:LLMPPOConfig):self.configconfig self.kl_controllerKLController(target_klconfig.target_kl)defcompute_advantages(self,rewards:torch.Tensor,values:torch.Tensor)-torch.Tensor:计算GAE优势估计advantages[]gae0gamma1.0# LLM场景通常用gamma1lam0.95# GAE lambda# 从后向前计算fortinreversed(range(len(rewards))):iftlen(rewards)-1:next_value0else:next_valuevalues[t1]deltarewards[t]gamma*next_value-values[t]gaedeltagamma*lam*gae advantages.insert(0,gae)returntorch.tensor(advantages)defppo_loss(self,log_probs:torch.Tensor,old_log_probs:torch.Tensor,advantages:torch.Tensor,ref_log_probs:torch.Tensor)-torch.Tensor:计算PPO裁剪损失# 1. 计算策略比率ratiotorch.exp(log_probs-old_log_probs)# 2. 计算裁剪目标surr1ratio*advantages surr2torch.clamp(ratio,1-self.cfg.clip_range_ratio,1self.cfg.clip_range_ratio)*advantages# 3. PPO损失取较小值policy_loss-torch.min(surr1,surr2).mean()# 4. KL散度损失kl_div(old_log_probs-log_probs).mean()# 5. 总损失total_losspolicy_lossself.cfg.kl_coeff*kl_divreturntotal_lossdeftrain_step(self,rollouts:dict):执行一次PPO训练步骤# 1. 计算奖励和价值rewardstorch.tensor(rollouts[rewards])valuestorch.tensor(rollouts[values])# 需要单独的价值网络# 2. 计算优势advantagesself.compute_advantages(rewards,values)advantages(advantages-advantages.mean())/(advantages.std()1e-8)# 3. PPO更新for_inrange(self.config.ppo_epochs):# 计算当前log_probs实际训练时需要重新计算current_log_probsrollouts[log_probs]# 计算损失lossself.ppo_loss(current_log_probs,rollouts[old_log_probs],advantages,rollouts[ref_log_probs])# 反向传播loss.backward()self.optimizer.step()self.optimizer.zero_grad()returnloss.item()六、完整的RLHF训练循环classRLHFTrainer:完整的RLHF训练器def__init__(self,sft_model,reward_model,config:LLMPPOConfig):self.policysft_model self.ref_policycopy.deepcopy(sft_model)# 参考模型冻结self.rewardreward_model self.ppo_trainerPPOTrainer(config)self.configconfigdeftrain(self,prompts:List[str],num_steps:int):主训练循环rollout_collectorRolloutCollector(self.policy,self.ref_policy,self.reward)forstepinrange(num_steps):# 1. 生成rolloutsrolloutsrollout_collector.generate_rollouts(prompts)# 2. PPO训练lossself.ppo_trainer.train_step(rollouts)# 3. 更新参考模型可选定期同步ifstep%self.config.ref_update_interval0:self.ref_policy.load_state_dict(self.policy.state_dict())# 4. 日志记录ifstep%1000:print(fStep{step}, Loss:{loss:.4f})defsave(self,path:str):保存模型torch.save(self.policy.state_dict(),path)七、简化版DPODirect Preference Optimization7.1 DPO vs PPODPODirect Preference Optimization是一种无需显式训练奖励模型的方法 DPO的核心思想 PPO需要 1. 训练奖励模型 2. 使用奖励模型生成奖励信号 3. PPO优化策略 DPO可以直接从偏好数据优化策略 L_DPO - E_{(x, y_w, y_l) ~ D} [ log σ( β * (log π(y_w|x) - log π(y_l|x)) - β * (log π_ref(y_w|x) - log π_ref(y_l|x)) )] 简化理解为让策略模型更多地偏好y_w更少地偏好y_l classDPOTrainer:DPO训练器def__init__(self,model,beta0.1):self.modelmodel self.betabeta self.ref_modelcopy.deepcopy(model)self.ref_model.requires_grad_(False)defdpo_loss(self,query:torch.Tensor,chosen_response:torch.Tensor,rejected_response:torch.Tensor)-torch.Tensor:计算DPO损失# 1. 计算策略模型的log概率policy_chosenself.model.compute_log_prob(query,chosen_response)policy_rejectedself.model.compute_log_prob(query,rejected_response)# 2. 计算参考模型的log概率withtorch.no_grad():ref_chosenself.ref_model.compute_log_prob(query,chosen_response)ref_rejectedself.ref_model.compute_log_prob(query,rejected_response)# 3. 计算偏好对数几率chosen_logpspolicy_chosen-ref_chosen*self.beta rejected_logpspolicy_rejected-ref_rejected*self.beta# 4. DPO损失loss-torch.log(torch.sigmoid(chosen_logps-rejected_logps)).mean()returnloss7.2 DPO vs PPO对比方面PPODPO需要奖励模型✅❌训练复杂度高低显存需求高4个模型低2个模型训练稳定性需调参相对稳定理论基础强化学习分类/对比学习八、实战技巧8.1 训练稳定性classTrainingStabilizer:训练稳定性工具staticmethoddefgradient_clipping(model,max_norm1.0):梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(),max_norm)staticmethoddefreward_normalization(rewards:torch.Tensor)-torch.Tensor:奖励归一化return(rewards-rewards.mean())/(rewards.std()1e-8)staticmethoddefmoving_average_update(model,target_model,tau0.01):EMA更新参考模型forparam,target_paraminzip(model.parameters(),target_model.parameters()):target_param.data.mul_(1-tau).add_(param.data,alphatau)8.2 常见问题与解决classProblemSolver:常见问题解决# 问题1训练崩溃损失爆炸# 解决降低学习率增加KL系数deffix_loss_explosion():config{learning_rate:1e-6,# 从1e-5降低kl_coeff:0.2# 从0.1增加}returnconfig# 问题2模型退化开始复读# 解决添加重复惩罚defadd_repetition_penalty():penalty_coef0.1# 在生成时应用returnpenalty_coef# 问题3KL值过高# 解决使用渐进式KL调度defkl_scheduler(step,total_steps):# 从0.01线性增加到0.2return0.01(0.2-0.01)*(step/total_steps)九、代码实战使用TRL库fromtransformersimportAutoModelForCausalLM,AutoTokenizerfromtrlimportPPOTrainer,PPOConfigfromtrl.modelsimportAutoModelForCausalLMWithValueHead# 1. 加载模型model_namegpt2modelAutoModelForCausalLMWithValueHead.from_pretrained(model_name)ref_modelAutoModelForCausalLMWithValueHead.from_pretrained(model_name)reward_modelAutoModelForSequenceClassification.from_pretrained(model_name,num_labels1)# 2. 配置ppo_configPPOConfig(model_namemodel_name,learning_rate1e-5,ppo_epochs4,mini_batch_size4,batch_size8)# 3. 初始化PPO训练器ppo_trainerPPOTrainer(configppo_config,modelmodel,ref_modelref_model,reward_modelreward_model,tokenizertokenizer)# 4. 生成rolloutsquery_texts[Hello, how are you?,What is AI?]query_tensors[tokenizer(q,return_tensorspt)forqinquery_texts]# 5. 获取responseresponse_tensorsppo_trainer.generate(query_tensors)# 6. 计算奖励rewards[torch.tensor([1.0])for_inresponse_tensors]# 实际用reward模型# 7. PPO训练statsppo_trainer.step(query_tensors,response_tensors,rewards)print(stats)十、总结10.1 核心要点RLHF三阶段SFT → Reward Model → PPO优化KL约束是防止奖励黑客的关键PPO通过裁剪机制限制策略更新幅度DPO提供了更简单的替代方案10.2 选择建议场景推荐方法计算资源充足PPO 完整RLHF计算资源有限DPO快速原型验证DPO生产环境PPO 充分调参10.3 下一步学习深入理解GAE广义优势估计学习更高级的对齐技术Constitutional AI、DPO变体实践完整的RLHF训练流程习题理论题解释为什么KL约束可以防止奖励黑客实现题使用Hugging Face的TRL库实现一个简单的PPO训练循环。思考题DPO和PPO各有优缺点在什么场景下你会选择使用DPO