RL 随记
DAPO
-
裁剪偏移
- 提高了 clip 中 (1 - sita, 1+sita)的范围,减少了熵下降的速度。在高熵的状态下可以做更多的探索,高熵就是策略的随机性强,探索能力高,低熵就是随机性低,策略确定性高,偏向利用已知的高奖励动作,探索性低
- clip 的上界会限制 policy 的 exploration,导致 exploitation 的 token 更容易出现,因为这个 clip 是百分比 clip,低概率的 token clip 后的范围会远比高概率的 token clip 后的范围小,从而抑制了低概率 token 的出现
-
动态采样
- 希望每轮训练的时候,数据里正确的数据能够在一个合理的比例,不希望全都是错的或者全都是对的,这样可以显著加快收敛
-
Token 级别的 loss
- 原本 GRPO 对每个回答内部所有 token 的 loss 除以当前 response 的长度,然后同一个 prompt 下所有的 response 做一个平均。
- 对于正样本,一个 group 内两个长度不同的正确 response,各自除以样本自身的长度值 |oi| 后,长度较短的样本会拥有更大的梯度权重,也就是会得到更大的学习奖励,模型会偏向于在正确回答中选择简洁的答案;
- 对于负样本,一个 group 内两个长度不同的错误 response,除以样本自身的长度值 |oi| 后,长度较长的样本会有更小的梯度值,即得到更轻的惩罚,这样会导致模型在错误回答中更倾向于选择篇幅较长的答案。
- DAPO 使用的是,除每个 Prompt 内的所有 response 的 token 总长
- 对比 DR.GRPO:除| oi |,改成除以一个固定值 MAX_TOKEN
-
溢出奖励噪声
- 有一些样本会被截断,然后奖励函数可能会给出错误的奖励。
- 不使用截断的样本,如果超长就丢弃。
VAPO
-
Critic 预训练
- 一开始 critic 可能不准,因为通常是从奖励模型初始化的;早期训练阶段的优势估计误差较大,所以早期的奖励会比较高,然后长度退化,再也没办法变长。
- 使用一个固定策略,用 lambda = 1 离线训练 critic,训练到 loss 够低
-
解耦 GAE
- 用不同的 lambda 训练 Actor 和 Critic,用 lambda = 1 离线训练 critic,让它无偏;用 lambda=0.95 训练 actor,让它快速收敛。
-
自适应 GAE
- 对于长 CoT,它的长度为一百时,奖励只剩下 0.06(0.95 ^ 100),GAE 的计算会变得由可能带有偏差的自举 (bootstrapping) TD 误差所主导,可能并非最优。
- 改成 1 - 1/(a * l) a 为 0.05,超参数;l 是 response 长度
想象一下你在训练一个机器人玩游戏。这个机器人有两个大脑部分:
- 策略大脑 (Policy):负责决定下一步具体该怎么走(比如,向左、向右、开火)。
- 价值大脑 (Value):负责评估当前情况有多好,或者某个动作有多值(比如,这个位置安全吗?得分高吗?)。
总结一下:
这种“解耦”方法,就是让负责评估的“价值大脑”看得更远,获得更准确的长期信息;同时让负责做决策的“策略大脑”看得近一些,保证学习又快又稳。这样分工合作,就能更好地训练机器人。
PPO
Aoi-cn: PPO 的价值模型和策略模型是什么
GitHub Copilot: 好的,我们来分别解释一下 PPO (Proximal Policy Optimization) 中的策略模型和价值模型:
-
策略模型 (Policy Model / Actor):
- 作用:决定在某个状态下应该采取什么动作。它就是智能体(Agent)的“大脑”或“决策者”。
- 输入:环境的当前状态(观察到的信息,Observation)。
- 输出:
- 对于离散动作空间(比如上、下、左、右):输出每个可能动作的概率分布。智能体会根据这个概率分布来随机选择一个动作执行。
- 对于连续动作空间(比如方向盘角度、油门大小):通常输出一个概率分布的参数(例如高斯分布的均值和标准差)。智能体会从这个分布中采样一个具体的动作值。
- 目标:通过学习,调整输出的动作(或动作概率),使得长期累积回报(奖励)最大化。在 PPO 中,它通过优化一个特殊的目标函数来更新,这个目标函数会限制每次策略更新的幅度,防止学“跑偏”,保证学习过程更稳定。
-
价值模型 (Value Model / Critic):
- 作用:评估当前状态有多“好”或多“有价值”。它并不直接决定动作,而是给当前状态打分,预测从这个状态出发,遵循当前的策略,未来能获得多少总回报(奖励)。它像一个“评论家”或“评估师”。
- 输入:环境的当前状态(观察到的信息,Observation)。
- 输出:一个标量值(一个数字),表示对未来累积回报的估计值,通常记为 V(s)。
- 目标:尽可能准确地预测未来回报。它通常通过监督学习的方式进行训练,目标是让它的预测值 V(s) 接近实际观测到的回报(比如使用 GAE 计算出的目标值)。
- 在 PPO 中的关键用途:价值模型的输出 V(s) 对于计算 优势函数 (Advantage Function, A(s, a)) 至关重要。优势函数衡量的是在状态 s 下采取动作 a 比平均水平好多少 (A(s, a) ≈ Q(s, a) - V(s))。这个优势信号会告诉策略模型,哪些动作是“好”的(应该增加选择概率),哪些是“坏”的(应该减少选择概率)。
总结一下:
在 PPO 中,策略模型 (Actor) 负责做动作决策,而价值模型 (Critic) 负责评估状态的好坏,并为策略模型的学习提供关键的指导信号(优势函数)。两者协同工作,共同提升智能体的表现。
Aoi-cn: 在我们的任务中,验证器作为奖励信号的来源。它使用基于规则的答案解析机制,这种机制不太可能表现出对输出长度的偏好。因此,输出长度的减少只能归因于策略优化动态,这主要是由分配给每个 Token 的优势值驱动的。为了进一步探讨这一点,我们绘制了优势值与 Token 位置之间的相关性图,如图 2 所示。这揭示了优势值与 Token 位置之间存在强相关性。Token 的位置越靠前,其优势值的正向偏差就越大。这导致模型偏好靠前的 Token,最终导致观察到的输出长度骤减。为了减轻这种价值初始化偏差,我们引入了价值预训练 (Value-Pretraining)。该方法涉及在预先指定的固定策略下,离线训练价值模型直至收敛。一旦价值模型收敛,它将被用于所有后续的正式实验中。
上面内容中,为什么说有偏差倾向于前面部分的 token?
GitHub Copilot: 好的,我们来分解一下为什么文章说存在偏向前面 Token 的偏差:
-
核心驱动力:优势值 (Advantage Value)
- 在强化学习(特别是 PPO)中,模型学习的关键是“优势值”。简单说,优势值告诉模型,在当前状态下,采取某个动作(在这里是生成某个 Token)比通常情况(平均水平)要好多少。
- 正优势值:意味着这个动作(生成这个 Token)比平均要好,模型会倾向于更多地采取这个动作。
- 负优势值:意味着这个动作比平均要差,模型会倾向于减少采取这个动作。
- 优势值的大小:决定了模型调整策略的力度。越大的正优势值,越会促使模型增加选择该动作的概率。
-
观察到的现象 (如图 2 所示):
- 文章提到,通过分析发现,“Token 的位置越靠前,其优势值的正向偏差就越大”。
- 通俗解释:这意味着,在模型学习过程中计算出来的“好处”(优势值),系统性地给那些出现在输出序列前面的 Token 打了更高的分数。越是早生成的 Token,得到的“奖励信号”(正优势值)就越强。
-
导致的结果:
- 由于模型学习的目标是最大化奖励(通过优势值来指导),当它发现生成靠前的 Token 能获得更高的正优势值时,它就会学习到一种策略:优先生成这些靠前的 Token。
- 反过来说,如果靠后的 Token 得到的优势值相对较低(甚至可能是负的),模型就没有那么大的动力去生成它们。
- 这种对前面 Token 的“偏爱”最终导致模型倾向于生成更短的序列,因为它觉得“早点结束能获得相对更高的累积优势”,或者说“继续往后生成的动力不足”。
-
偏差的来源 (文章推测):
- 文章将这种现象归因于“策略优化动态”,特别是“价值初始化偏差”。
- 通俗解释:这很可能是因为初始的价值模型(在没有预训练的情况下)对状态的价值估计不准确。它可能系统性地高估了生成序列早期部分带来的价值,或者低估了生成完整、长序列的价值。这种不准确的价值估计,导致计算出的优势值也产生了偏差,从而错误地引导策略模型偏爱生成较短的序列。
总结一下:
之所以说有偏差倾向于前面的 Token,是因为在模型学习过程中,计算出的“优势值”(衡量生成该 Token 好坏的指标)系统性地给位置靠前的 Token 打了更高的分数。这使得模型学习到更倾向于生成这些早期的 Token,而生成后期 Token 的动力不足,最终导致输出变短。文章认为这源于价值模型初始估计不准的问题。
Aoi-cn: 通过从一个固定策略(例如 )中采样来持续生成响应,并使用 GAE(其中 ,也称为蒙特卡洛回报)来更新价值模型。这种设置将优化问题转化为一个稳定的梯度下降优化问题,从而确保对价值模型进行更可靠和一致的更新。
这里使用了离线训练,具体是怎么训练?什么叫离线训练,什么叫在线训练?
GitHub Copilot: 好的,我们来详细解释一下这个离线训练过程以及在线/离线训练的区别。
1. 离线训练价值模型(在这个场景下具体怎么做):
- 目标: 单独训练好一个“价值模型”(Critic),让它能准确评估一个“固定策略”(比如已经训练好的 SFT 模型,Supervised Fine-Tuned policy)生成的回答中,各个状态(可以理解为生成到某个词时的状态)的价值。
- 数据生成:
- 使用那个固定不变的策略(比如 SFT 模型)来生成大量的回答样本。这个策略在整个价值模型训练期间不会被更新。它就像一个只会按照固定规则说话的机器人。
- 对于每个生成的回答(一个完整的序列),记录下整个过程:状态(生成到哪个词了)、采取的动作(生成了哪个词)、获得的奖励(这里可能没有即时奖励,但最终会有一个基于规则的验证器给出的整体评价,或者使用 GAE 计算的优势值)。
- 价值计算 (使用 GAE, λ=1):
- GAE (λ=1) 等同于蒙特卡洛回报 (Monte Carlo Return)。这意味着,对于回答序列中的每一个状态(Token 位置),它的“真实价值”被计算为从那个位置开始,一直到回答结束,所有未来获得的(折扣后)奖励的总和。因为验证器是基于规则的,可能在序列结束后才给出一个总评价,这个评价会被反向传播用来计算每个中间状态的蒙特卡洛回报。
- 模型更新:
- 现在我们有了很多 “(状态, 对应的蒙特卡洛回报)” 的数据对。
- 这就变成了一个标准的监督学习问题!我们用这些数据来训练价值模型。
- 输入: 状态 (Token 位置的表示)
- 目标输出: 对应的蒙特卡洛回报 (真实价值)
- 训练方法: 使用梯度下降等优化算法,调整价值模型的参数,让它的预测值尽可能接近目标输出(蒙特卡洛回报)。
- “离线”体现在哪里?
- 价值模型是在一个预先生成(或者持续生成但策略不变)的数据集上进行训练的。
- 生成这些数据的策略是固定的,它不依赖于当前正在训练的价值模型的状态。价值模型学得好不好,并不会反过来影响数据生成的方式。
- 训练过程更像是在“学习历史经验”,而不是“边做边学”。
2. 什么叫离线训练 (Offline Training)?
- 核心思想: 使用一个固定不变的数据集来训练模型(策略模型或价值模型)。这个数据集通常是事先收集好的。
- 数据来源: 可能来自之前的策略、人类演示、或者其他任何方式,关键在于训练开始后,不再与环境进行新的交互来收集数据,或者即使生成新数据,也是用一个固定的行为策略。
- 特点:
- 模型在训练期间不与环境互动。
- 学习完全基于已有的经验。
- 有点像学生只看过去的笔记和录像来学习,而不去亲自做实验或提问。
- 优点: 可以利用大量现有数据,可能更安全(如果在线互动有风险),计算上可能更可控。
- 缺点: 如果数据集不够好或者与实际应用场景偏差太大(分布漂移),模型效果可能不佳。
3. 什么叫在线训练 (Online Training)?
- 核心思想: 模型(智能体)一边与环境互动,一边学习和更新。
- 数据来源: 模型当前的策略被用来与环境互动,收集新的、即时的经验数据。
- 特点:
- 模型持续地与环境互动。
- 使用刚刚获得的经验来更新自己。
- 学习和数据收集是交织在一起的。
- 有点像学生边做实验边学习,根据实验结果马上调整自己的理解。
- 优点: 可以适应环境的变化,探索性强,通常是强化学习的标准范式。
- 缺点: 可能需要大量的交互(样本效率低),探索过程可能有风险,训练过程可能不稳定。
总结:
在这个任务中,价值模型的预训练是离线的,因为它使用一个固定的 SFT 策略生成的数据进行学习,目标是先把它训练好,能准确评估这个固定策略下的状态价值。这与后续的 PPO 在线训练形成对比,在 PPO 训练中,策略模型和价值模型会根据当前策略与环境(或模拟环境)互动产生的新数据进行持续的、交织的更新。
Critic 模型的初始化
Critic 模型的初始化策略很大程度上取决于应用场景,尤其是在大型语言模型(LLM)的 RLHF 中,其做法与传统强化学习有显著不同。
对于大型语言模型(RLHF 场景)
在这种场景下,Critic 模型(也叫 Value Model)本身就是一个大型语言模型。因此,绝对不会随机初始化,因为那样会丢失所有语言能力。其初始化通常采用以下两种方式之一:
-
从监督微调(SFT)后的模型进行初始化(最常见)
- 做法:直接复制一份在 SFER(Supervised Fine-tuning)阶段训练好的模型作为 Critic 模型的初始权重。然后,在其顶部添加一个随机初始化的线性层(即“价值头”),这个头负责将模型输出的隐藏状态向量转换为一个标量(即价值评估分数)。
- 为什么这么做:SFT 模型已经学会了遵循指令并生成符合格式的、有意义的文本。它的内部表征已经对“好的回答”有了一定的理解,这是一个非常强大的起点,可以帮助 Critic 模型更快地学习如何评估状态的价值。
-
从奖励模型(Reward Model)进行初始化
- 做法:直接复制一份已经训练好的奖励模型(Reward Model)的权重作为 Critic 模型的初始权重。同样,也需要替换或重新初始化最后的输出层,使其输出一个价值标量而不是奖励分数。
- 为什么这么做:这是一种非常有效的策略。奖励模型本身就是被训练来“打分”的,它的内部表征已经高度特化,非常适合评估文本的质量。让 Critic 模型从一个已经懂得“好坏”的模型开始学习,可以极大地加速收敛并提升稳定性。
总结(LLM 场景):Critic 模型的“身体”(主干网络)继承自一个已经很强大的预训练模型(SFT 或 RM),只有“头脑”中的一小部分(价值头)是新加的。
对于传统强化学习任务(如游戏、机器人)
在这种场景下,Actor 和 Critic 网络通常是较小的多层感知机(MLP)或卷积网络(CNN)。
- 做法:通常使用标准的神经网络权重初始化方法,如Xavier/Glorot 初始化或He 初始化,进行随机初始化。这些方法旨在使网络在训练开始时各层的激活值和梯度保持在合理的范围内,避免梯度消失或爆炸。在某些架构中,如果 Actor 和 Critic 共享一部分底层网络,那么这部分共享网络只会被初始化一次。
Critic 模型的参数更新过程
Critic 模型的更新过程可以看作是一个在 PPO 主循环内部进行的独立的监督学习任务。其目标是让自己的预测越来越接近“真实的”长期回报。
这个过程可以分解为以下几个步骤:
步骤 1: 数据收集 (Rollout)
- 在 PPO 的每一次迭代中,Actor 模型(当前策略) 与环境(在 LLM 中,环境由奖励模型定义)交互,生成一批经验数据(trajectories)。
- 对于每一个时间步 \(t\),我们都记录下状态 \(s_t\)、Actor 采取的动作 \(a_t\)、以及环境给出的即时奖励 \(r_t\)(在 LLM 中,奖励通常在序列末尾由 Reward Model 给出)。
步骤 2: 计算目标价值 (Target Value)
- 这是最关键的一步。我们需要一个“正确答案”来告诉 Critic 它应该预测多少分。但我们没有真正的正确答案,只能根据已有的数据构建一个尽可能准确的估计,这个估计被称为目标价值 (Target Value) 或 回报 (Return)。
- 计算这个目标价值最先进和常用的方法是广义优势估计 (Generalized Advantage Estimation, GAE)。GAE 通过一个加权平均,巧妙地平衡了单步奖励和 Critic 自身对未来价值的预测,从而在偏差和方差之间取得平衡。
- GAE 会为每个时间步 \(t\) 计算一个优势函数估计 \(\hat{A}_t\)。而我们提供给 Critic 学习的目标价值 \(V_t^{\text{target}}\),可以由这个优势估计和 Critic 当前的价值预测共同构成:\[V_t^{\text{target}} = \hat{A}_t + V_\phi(s_t) \]其中 \(V_\phi(s_t)\) 是 Critic 模型当前对状态 \(s_t\) 的价值预测。这个公式的含义是:目标价值等于“基准价值”加上“比基准好多少(优势)”。
步骤 3: 计算损失函数
- Critic 模型的任务是让自己的预测 \(V_\phi(s_t)\) 尽可能地接近我们刚刚计算出的目标价值 \(V_t^{\text{target}}\)。
- 因此,它的损失函数通常是两者之间的均方误差 (Mean Squared Error, MSE):\[\mathcal{L}_{\text{critic}}(\phi) = \frac{1}{N} \sum_{i=1}^{N} (V_\phi(s_i) - V_i^{\text{target}})^2 \]这里的 \(N\) 是这批数据中的样本总数,\(\phi\) 代表 Critic 模型的可训练参数。
步骤 4: 参数更新
- 计算出损失函数后,我们使用梯度下降来最小化这个损失。
- 具体做法是:计算损失函数 \(\mathcal{L}_{\text{critic}}(\phi)\) 对 Critic 模型参数 \(\phi\) 的梯度 \(\nabla_\phi \mathcal{L}\)。
- 然后使用一个优化器(如Adam或AdamW)来更新参数:\[\phi \leftarrow \phi - \eta \nabla_\phi \mathcal{L}_{\text{critic}}(\phi) \]其中 \(\eta\) 是学习率。
整个更新流程的本质是:Critic 通过观察 Actor 的“实战演练”结果,并结合 GAE 这个“复盘工具”算出的得失,不断修正自己对“局势”(状态)的判断能力。
ROPE
Aoi-cn: 通俗地给我讲解 RoPE 算法
GitHub Copilot: 好的,我们用一个通俗的比喻来理解 RoPE (Rotary Positional Embedding) 算法:
想象一下,你在一个大广场上,有很多个小机器人(代表句子里的词或 Token)。每个机器人本身都有自己的特点(代表词的含义,即 Token Embedding)。
问题: 机器人光有自己的特点还不够,它们需要知道自己在队伍里的位置,以及和其他机器人的相对位置,才能协同工作(比如理解语法结构)。
传统方法(像绝对位置编码):
- 给每个位置(第 1 个、第 2 个、第 3 个...)发一个固定的牌子(一个向量),机器人把这个牌子挂在身上。
- 缺点: 机器人只知道自己是“第 N 个”,但很难直接从牌子上看出“我离第 M 个机器人有多远”。而且,如果队伍变得非常非常长,可能没有准备那么多牌子。
RoPE 的方法(旋转位置编码):
- 核心想法: 不给机器人发固定的牌子,而是根据机器人的位置,让它原地旋转一个特定的角度。
- 怎么转?
- 想象每个机器人的“特点”(Embedding 向量)里有一部分像一个时钟的指针。
- 位置 1 的机器人:指针稍微转动一点点(比如转 10 度)。
- 位置 2 的机器人:指针从原始方向再多转一点(比如转 20 度)。
- 位置
m的机器人:指针从原始方向转动m * 固定的角度。
- 关键点:
- 每个机器人的旋转角度是绝对的(取决于它的位置
m)。 - 但是,当你比较两个机器人(比如位置
m和位置n)时,它们指针方向的差异(角度差),只取决于它们之间的距离n - m,而和它们的具体位置m、n无关!- 比如,位置 2 和位置 5 的指针角度差,跟位置 12 和位置 15 的指针角度差是一样的,因为它们都相隔 3 个位置。
- 每个机器人的旋转角度是绝对的(取决于它的位置
- 好处:
- 自带相对位置信息: 模型在比较两个旋转后的机器人(计算注意力时)时,能天然地感知到它们之间的相对距离。
- 不改变“长度”: 旋转不会改变指针的长度(向量的模长),只是改变方向。这有助于保持模型稳定性。
- 可能更好地处理长序列: 因为它是基于相对关系的,对于没见过的超长队伍,理论上也能比较好地推断相对位置。
总结一下:
RoPE 就像让每个词根据它在句子中的位置,把它自身表示的一部分向量“旋转”一个角度。这个旋转操作巧妙地把绝对位置信息编码成了向量方向的改变,并且使得模型在比较两个词时,能够很容易地察觉到它们之间的相对距离。
Aoi-cn: RoPE 和相对位置编码有什么区别?
GitHub Copilot: RoPE (Rotary Positional Embedding) 和通常所说的“相对位置编码”(Relative Positional Encoding, RPE)都旨在让模型理解 Token 之间的相对位置关系,但它们的实现机制和侧重点有所不同。
可以认为 RoPE 是相对位置编码的一种非常具体且巧妙的实现方式,它与其他常见的相对位置编码方法(如 Transformer-XL 或 T5 中使用的)有显著区别。
以下是它们的主要区别:
-
作用方式和位置:
- RoPE: 在计算注意力分数之前,直接修改 Query (Q) 和 Key (K) 向量本身。它通过将 Q 和 K 向量的一部分视为复数(或二维向量),并根据其绝对位置
m进行旋转。关键在于,旋转后的Q_m和K_n进行点积运算时,结果天然地只依赖于它们的相对位置m-n和原始向量。 - 常见 RPE (如 T5/Transformer-XL): 通常是在计算出初步的注意力分数(Q 和 K 点积之后)之后,或者在计算加权 Value (V) 时,额外加入一个代表相对位置
m-n的偏置项(Bias)或嵌入向量。它不直接修改 Q 和 K 本身来编码位置。
- RoPE: 在计算注意力分数之前,直接修改 Query (Q) 和 Key (K) 向量本身。它通过将 Q 和 K 向量的一部分视为复数(或二维向量),并根据其绝对位置
-
编码内容:
- RoPE: 虽然旋转操作基于绝对位置
m,但其设计使得 QK 点积的交互结果仅与相对位置m-n相关。它没有显式地学习或查找一个代表m-n的向量。 - 常见 RPE: 显式地为不同的相对距离
m-n(通常在一个窗口范围内,比如 -k 到 +k)学习或计算一个专门的位置嵌入向量,然后将这个向量的信息(通常是标量偏置)添加到注意力分数中。
- RoPE: 虽然旋转操作基于绝对位置
-
参数化:
- RoPE: 通常是无参数的(旋转角度由固定的数学公式如正弦/余弦函数确定,尽管基频
theta可以是可学习的超参数),或者参数量很少。 - 常见 RPE: 需要额外的参数来存储不同相对位置的嵌入向量。参数量取决于考虑的相对距离窗口大小。
- RoPE: 通常是无参数的(旋转角度由固定的数学公式如正弦/余弦函数确定,尽管基频
-
对向量范数的影响:
- RoPE: 旋转操作不改变 Q 和 K 向量的范数(长度),只改变方向。
- 常见 RPE: 添加偏置项会直接改变注意力分数的值。
-
长距离外推性:
- RoPE: 由于其数学性质,理论上对于训练时未见过的更长相对距离,其相对位置编码的属性仍然保持,具有较好的外推潜力。
- 常见 RPE: 对于超出预设窗口范围的相对距离,通常需要特殊处理(例如,所有超出距离共享同一个嵌入向量),外推能力可能受限。
总结:
| 特性 | RoPE (Rotary Positional Embedding) | 常见 RPE (如 T5/Transformer-XL) |
|---|---|---|
| 作用位置 | 修改 Q, K 之前 | 修改 Attention Score 之后 或修改 Value |
| 作用方式 | 向量旋转 (乘法操作) | 添加偏置/嵌入 (加法操作) |
| 编码基础 | 基于绝对位置 m 的旋转,交互结果体现相对位置 m-n |
直接编码相对距离 m-n |
| 参数 | 通常无参数或参数很少 | 需要额外参数存储相对位置嵌入 |
| 向量范数 | 不改变 Q, K 范数 | 改变 Attention Score |
| 外推性 | 理论上较好 | 可能受限于预设窗口 |
简单来说,RoPE 是一种更“内嵌”的方式,通过巧妙的向量旋转让 QK 交互自然体现相对位置;而常见的 RPE 则更像是一种“外挂”或“修正”,在注意力计算过程中额外加入代表相对距离的信息。
DPO
- 正常 PPO 需要靠环境来反馈模型,告诉模型他做的对还是做的错,然后给奖励;模型根据奖励来调整策略
- DPO 直接告诉模型怎么做对,怎么做是错的,让模型直接根据这个比率调整策略
def dpo_loss(ref_probs, probs, mask, beta):
# ref_probs 和 probs 都是 shape: (batch_size, seq_len)
# https://github.com/jingyaogong/minimind/issues/298
seq_lengths = mask.sum(dim=1, keepdim=True) # (batch_size, 1)
ref_probs = (ref_probs * mask).sum(dim=1) / seq_lengths.squeeze()
probs = (probs * mask).sum(dim=1) / seq_lengths.squeeze()
# 将 chosen 和 rejected 数据分开
batch_size = ref_probs.shape[0]
chosen_ref_probs = ref_probs[:batch_size // 2]
reject_ref_probs = ref_probs[batch_size // 2:]
chosen_probs = probs[:batch_size // 2]
reject_probs = probs[batch_size // 2:]
pi_logratios = chosen_probs - reject_probs
ref_logratios = chosen_ref_probs - reject_ref_probs
logits = pi_logratios - ref_logratios
loss = -F.logsigmoid(beta * logits)
return loss.mean()
您好!您的这段描述非常精彩,可以说对从 PPO(在 RLHF 场景下)到 DPO 的演进思想抓得非常精准。您的理解在核心上是完全正确的。
为了让这个理解更加系统化,我们来将您的论述拆解并逐一分析,补充一些细节。
1. Critic 的本质:逼近价值函数(Value Function)
您的描述: "critic 模型的本质是尽可能向奖励函数(GAE 结果)靠拢,本质上是向一个包含过程奖励的 reward model 靠拢"
这个说法非常贴切。我们来精确化一下:
- Critic (或称 Value Model) 的目标:在强化学习中,Critic 的目标是学习状态价值函数 V(s),即“在当前状态 s 下,遵循当前策略,未来能获得的总回报的期望值是多少”。它用来评估当前状态有多“好”。
- 训练目标:为了训练 Critic,我们需要一个“真实”的价值目标。在实践中,我们通常使用 GAE (Generalized Advantage Estimation) 的结果作为这个目标。GAE 巧妙地结合了单步奖励和多步回报,能有效降低估算的方差。
- 与 Reward Model (RM) 的关系:在 RLHF (Reinforcement Learning from Human Feedback) 的 PPO 流程中,每一步的即时奖励
r(t)并不是来自传统环境,而是由一个预先训练好的 Reward Model 提供的。GAE 正是利用这些由 RM 产生的奖励序列来计算其目标的。
所以,您的逻辑链是完全正确的:
RM 提供单步奖励 -> GAE 利用这些奖励计算出更稳定的长期价值目标 -> Critic 模型通过监督学习的方式,努力让自己的预测值逼近这个 GAE 目标。
因此,说 Critic 是在向一个“包含过程奖励的 Reward Model”靠拢,是非常形象且正确的理解。
2. Reward Model 的本质:编码人类偏好
您的描述: "而奖励模型本质就是给一些 token 高分数,降低另一些 token 的分数,从而调控 GAE,并使得 Actor 向这个方向靠拢"
这也是一个精准的概括。
- RM 的训练:RM 的训练数据是人类标注的偏好对,例如对于同一个提示(prompt),有两个回答 A 和 B,标注者认为“A 比 B 好”。
- RM 的功能:模型学习一个函数
r(prompt, response),输出一个标量分数。它的目标是让“更好”的回答(A)得分高于“更差”的回答(B)。 - 对 Token 的影响:虽然 RM 通常在整个序列的末尾输出一个总分,但其内部的注意力机制(如 Transformer)实际上是在评估每个 Token 在特定上下文中的贡献。一个“好”的 Token 会让最终得分倾向于变高,反之亦然。
- 驱动 Actor 更新:这个 RM 产生的奖励分数,通过 PPO 算法(其中 GAE 是关键一环)计算出优势(Advantage),告诉 Actor(策略模型)哪些行为(生成哪些 Token)是“好”的,应该增加其概率;哪些是“坏”的,应该降低其概率。
您的第二点描述,准确地揭示了 RM 是如何将抽象的人类偏好转化为具体的、可用于优化策略模型的数值信号的。
3. DPO 的革命:绕过显式建模,直接优化
您的描述: "DPO 发现既然 critic 是基于 reward model 训得,直接就省掉了 critic model,使用偏好对来优化"
这是整个演进过程中的点睛之笔,也是 DPO (Direct Preference Optimization) 的核心洞察。
-
RLHF (PPO) 的复杂性:
- 阶段一 (SFT):监督微调基础模型。
- 阶段二 (RM Training):收集偏好数据,训练一个奖励模型。
- 阶段三 (PPO Training):使用 RM 作为奖励来源,同时训练 Actor 和 Critic,过程复杂且对超参敏感,容易出现训练不稳定的问题。
-
DPO 的洞察:DPO 的作者发现,RLHF 的最终目标其实就是让模型对于“赢家”(winner) 回答
y_w的概率高于“输家”(loser) 回答y_l的概率。整个 RM + PPO + Critic 的复杂流程,都是为了实现这个最终目的。 -
DPO 的做法:
- 绕过 RM 和 Critic:DPO 巧妙地推导出一个数学关系,表明可以通过一个简单的损失函数直接在偏好数据上优化策略模型(Actor)。
- 直接优化:DPO 的损失函数直接作用于偏好对
(y_w, y_l)。它鼓励模型提高y_w的似然概率,同时降低y_l的似然概率。这等价于在隐式地优化一个奖励函数,但你根本不需要去训练这个奖励模型。 - 简化流程:整个流程变成了 SFT -> DPO。省去了训练 RM 和进行复杂 PPO 优化的步骤,更加稳定、高效。
总结对比
| 特性 | RLHF (基于 PPO) | DPO (Direct Preference Optimization) |
|---|---|---|
| 核心思想 | 显式建模人类偏好(RM),然后用强化学习最大化这个奖励。 | 将最大化奖励的目标转化为一个直接在偏好数据上的分类问题。 |
| 所需模型 | 1. Actor (策略模型)<br>2. Critic (价值模型)<br>3. Reward Model (奖励模型)<br>4. Reference Model (SFT 模型,用于计算 KL 散度惩罚) | 1. Policy Model (策略模型)<br>2. Reference Model (SFT 模型,用于计算 KL 散度惩罚) |
| 训练数据 | 偏好数据 (Winner vs. Loser) | 偏好数据 (Winner vs. Loser) |
| 流程 | 复杂的多阶段过程:SFT -> 训练 RM -> PPO 调优 Actor 和 Critic。 | 简化的两阶段过程:SFT -> 直接用 DPO 损失函数调优策略模型。 |
| 优点 | 理论成熟,是 RLHF 的经典范式。 | 简单、稳定,无需训练额外的 RM 和 Critic,避免了 RL 训练的复杂性和不稳定性。 |
| 缺点 | 流程复杂、计算昂贵、训练不稳定。 | 作为一个较新的方法,其理论边界和适用范围仍在探索中。 |
结论:
您的论述是完全正确且深刻的。您准确地把握了从 PPO 到 DPO 的技术演进脉络:认识到 Critic 是 RM 的代理,而 RM 又是偏好数据的代理,DPO 则通过巧妙的数学变换,砍掉了中间的所有代理,建立了一条从人类偏好数据到最终策略模型的直接优化路径。这正是 DPO 方法的精髓所在。
4. DPO 的 loss 函数
好的,我们来详细解析一下 dpo_loss 函数以及它是如何计算 DPO (Direct Preference Optimization) 损失的。
核心目标: DPO 的目标是直接优化语言模型,使其更倾向于生成“偏好”(chosen)的响应,而不是“不偏好”(rejected)的响应,并且这种偏好程度要比一个固定的“参考模型”(reference model)更强。
计算流程分解:
-
输入准备 (在
train_epoch函数中):- 模型输出:
- 使用参考模型 (
ref_model,参数冻结) 对拼接后的输入x(包含 chosen 和 rejected 样本) 进行前向传播,得到ref_logits。 - 使用主模型 (
model,正在训练) 对相同的输入x进行前向传播,得到logits。
- 使用参考模型 (
- 获取目标 Token 的对数概率:
- 调用
logits_to_probs(logits, labels)函数。这个函数做了两件事:F.log_softmax(logits, dim=2): 将模型的原始输出logits(形状[batch*2, seq_len, vocab_size]) 转换为对数概率。对每个位置上的词汇表应用 log_softmax。torch.gather(...): 使用真实的标签labels(形状[batch*2, seq_len]) 作为索引,从log_probs中精确地提取出模型为实际目标 token 分配的对数概率。unsqueeze(2)和squeeze(-1)是为了匹配gather的维度要求。
- 结果
ref_probs和probs的形状都是[batch*2, seq_len],其中batch是原始的批次大小(在拼接前)。ref_probs[i, j]表示参考模型认为第i个样本在第j个位置生成正确 token 的对数概率。probs同理,但来自主模型。
- 调用
- 应用掩码:
ref_probs = ref_probs * maskprobs = probs * mask- 将
mask(形状[batch*2, seq_len], padding 位置为 0, 非 padding 为 1) 乘到概率上。这会将所有 padding 位置的对数概率置为 0,确保它们不参与后续的求和计算。
- 模型输出:
-
dpo_loss函数内部计算:- 输入:
ref_probs,probs,mask(形状[batch*2, seq_len]),beta(超参数, 0.1)。 - 步骤 1: 计算序列有效长度:
seq_lengths = mask.sum(dim=1, keepdim=True): 对每个样本的mask沿着序列长度维度求和,得到每个序列的实际有效 token 数量。keepdim=True保持维度为[batch*2, 1]。
- 步骤 2: 计算每个序列的平均对数概率:
ref_probs = (ref_probs * mask).sum(dim=1) / seq_lengths.squeeze():(ref_probs * mask).sum(dim=1): 对每个序列,将所有有效 token (非 padding) 的对数概率加起来。得到形状[batch*2]的张量,表示每个序列的总对数概率。/ seq_lengths.squeeze(): 除以对应序列的有效长度,得到每个序列的平均对数概率。squeeze()去掉seq_lengths多余的维度。
- 对
probs执行完全相同的操作,得到主模型对每个序列的平均对数概率。 - 意义: 现在
ref_probs[i]代表参考模型生成第i个完整序列(忽略 padding)的平均对数似然,probs[i]代表主模型生成该序列的平均对数似然。
- 步骤 3: 分离 Chosen 和 Rejected 样本:
- 由于输入数据是按
chosen在前rejected在后的顺序拼接的 (torch.cat),这里通过切片操作将它们分开:chosen_ref_probs = ref_probs[:batch_size // 2]reject_ref_probs = ref_probs[batch_size // 2:]chosen_probs = probs[:batch_size // 2]reject_probs = probs[batch_size // 2:]
- 现在我们得到了
chosen样本和rejected样本各自的平均对数概率,分别来自参考模型和主模型。所有这些张量的形状都是[batch]。
- 由于输入数据是按
- 步骤 4: 计算对数概率比 (Log Ratios):
pi_logratios = chosen_probs - reject_probs: 计算主模型 (pi) 对chosen响应和rejected响应的平均对数概率之差。这等价于log(pi(chosen)/pi(rejected))。ref_logratios = chosen_ref_probs - reject_ref_probs: 计算参考模型 (ref) 对chosen响应和rejected响应的平均对数概率之差。这等价于log(ref(chosen)/ref(rejected))。
- 步骤 5: 计算核心 DPO Logits:
logits = pi_logratios - ref_logratios: 这是 DPO 损失的核心部分。它计算的是主模型偏好chosen的程度 (pi_logratios) 与参考模型偏好chosen的程度 (ref_logratios) 之间的差值。- 直观理解: 如果这个
logits值很大,意味着主模型比参考模型更强烈地偏好chosen响应。如果为 0,意味着它们的相对偏好相同。如果为负,意味着主模型反而不如参考模型那样偏好chosen。
- 步骤 6: 应用 Sigmoid 损失:
loss = -F.logsigmoid(beta * logits):beta * logits: 用超参数beta缩放这个差值。beta控制了对偏离参考模型策略的惩罚强度。F.logsigmoid(...): 计算log(sigmoid(beta * logits))。Sigmoid 函数将输入映射到 (0, 1) 区间,可以看作是模型正确排序 (将chosen排在rejected前面,相对于参考模型) 的概率。取对数 (logsigmoid) 是为了数值稳定性和匹配最大似然框架。- 加负号
-: 将最大化对数似然问题转化为最小化损失函数的问题。模型的目标是让beta * logits尽可能大(即主模型比参考模型更偏好 chosen),这样logsigmoid接近 0,损失也就接近 0。
- 步骤 7: 求平均:
return loss.mean(): 计算批次中所有偏好对损失的平均值,作为最终的 DPO 损失。
- 输入:
总结:
DPO 损失通过比较主模型和参考模型对 "chosen" vs "rejected" 响应对数概率的差异,构建了一个衡量主模型相对偏好改进程度的 logits。然后利用 logsigmoid 损失函数来驱动主模型朝着更符合偏好(即让 logits 更大)的方向更新参数。整个过程避免了训练单独的奖励模型,直接在偏好数据上进行优化。
GRPO
好的,我们来逐步解释这个 GRPO (Generative Response Policy Optimization) 目标函数以及它如何用于梯度更新。
目标函数解析:
- \(J_{GRPO}(\theta)\): 这是我们要优化的目标函数,目标是找到最大化这个函数值的策略参数 \(\theta\)。
- \(\mathbb{E}[\dots]\): 表示期望。我们需要从查询分布 \(P(Q)\) 中采样查询 \(q\),并使用旧的策略 \(\pi_{\theta_{old}}\)(即上一次迭代的策略)针对查询 \(q\) 生成 \(G\) 个响应(或轨迹)\(o_i\)。
- \(\frac{1}{G} \sum_{i=1}^G\): 对 \(G\) 个采样的响应求平均。
- \(\frac{1}{|o_i|} \sum_{t=1}^{|o_i|}\): 对每个响应 \(o_i\) 中的所有时间步(token)\(t\) 求平均。\(|o_i|\) 是响应 \(o_i\) 的长度。
- \(\pi_\theta(o_{i,t}|q, o_{i,<t})\): 当前策略 \(\pi_\theta\) 在给定查询 \(q\) 和之前的 token \(o_{i,<t}\) 的条件下,生成 token \(o_{i,t}\) 的概率。
- \(\pi_{\theta_{old}}(o_{i,t}|q, o_{i,<t})\): 旧策略 \(\pi_{\theta_{old}}\) 生成相同 token 的概率。
- \(r_t(\theta) = \frac{\pi_\theta(o_{i,t}|q, o_{i,<t})}{\pi_{\theta_{old}}(o_{i,t}|q, o_{i,<t})}\): 这是重要性采样比率,衡量当前策略和旧策略在这一步生成相同 token 的概率比。
- \(\hat{A}_{i,t}\): 优势函数(Advantage Function)的估计值。它衡量在时间步 \(t\) 生成 token \(o_{i,t}\) 相对于平均情况有多好。通常由奖励模型(Reward Model)的打分减去一个基线(Value Function)得到。
- \(\min\left[ r_t(\theta) \hat{A}_{i,t}, \text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon) \hat{A}_{i,t} \right]\): 这是 PPO 算法中的裁剪替代目标 (Clipped Surrogate Objective)。
- \(\text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\): 将概率比率 \(r_t(\theta)\) 限制在 \([1-\epsilon, 1+\epsilon]\) 的范围内。\(\epsilon\) 是一个小的超参数(例如 0.2)。
- 这个 \(\min\) 操作的目的是限制策略更新的幅度。如果 \(\hat{A}_{i,t} > 0\)(好的 token),目标函数会鼓励增大 \(r_t(\theta)\),但增幅被 \((1+\epsilon) \hat{A}_{i,t}\) 限制。如果 \(\hat{A}_{i,t} < 0\)(坏的 token),目标函数会鼓励减小 \(r_t(\theta)\),但减幅被 \((1-\epsilon) \hat{A}_{i,t}\) 限制。这有助于稳定训练。
- \(- \beta D_{KL}[\pi_\theta || \pi_{ref}]\): KL 散度惩罚项。
- \(D_{KL}[\pi_\theta || \pi_{ref}]\): 计算当前策略 \(\pi_\theta\) 和一个参考策略 \(\pi_{ref}\)(通常是初始的、经过监督微调的模型)之间的 KL 散度。
- \(\beta\) 是控制惩罚强度的系数。
- 这个惩罚项的作用是防止当前策略 \(\pi_\theta\) 偏离参考策略 \(\pi_{ref}\) 太远,有助于保持模型的基础能力(如语言流畅性、遵循指令的能力)或避免模式崩溃。
梯度更新过程 (举例说明):
假设我们的任务是训练一个聊天机器人,使其回答更幽默。
- 采样:
- 从数据集中采样一个查询 \(q\),例如:"讲个关于程序员的笑话"。
- 使用当前的旧策略 \(\pi_{\theta_{old}}\)(比如上一步训练得到的模型)生成一个或多个响应 \(o_i\)。假设生成了一个响应 \(o_1\) = "为什么程序员喜欢黑夜?因为 bug 不喜欢光。" (\(|o_1|=13\) 个 token)。
- 评估:
- 奖励模型: 使用一个预训练的奖励模型来评估响应 \(o_1\) 中每个 token \(o_{1,t}\) 的“幽默程度”。例如,对于 "bug 不喜欢光" 这部分,奖励模型可能给出较高的分数。
- 优势计算: 结合奖励模型分数和可能的值函数(Value Function,预测未来总奖励),计算每个 token \(t\) 的优势值 \(\hat{A}_{1,t}\)。假设 "bug", "不喜欢", "光" 这几个 token 的优势值 \(\hat{A}_{1,t}\) 为正,其他 token 的优势值接近于零或为负。
- 参考策略: 使用初始的 SFT 模型作为 \(\pi_{ref}\)。
- 计算目标函数:
- 对于响应 \(o_1\) 中的每个 token \(t\) (从 "为什么" 到 "。"):
- 计算当前策略 \(\pi_\theta\) 生成该 token 的概率 \(\pi_\theta(o_{1,t}|q, o_{1,<t})\)。
- 获取旧策略 \(\pi_{\theta_{old}}\) 生成该 token 的概率 \(\pi_{\theta_{old}}(o_{1,t}|q, o_{1,<t})\)。
- 计算概率比 \(r_t(\theta) = \frac{\pi_\theta(\dots)}{\pi_{\theta_{old}}(\dots)}\)。
- 计算裁剪后的概率比 \(\text{clip}(r_t(\theta), 1-\epsilon, 1+\epsilon)\)。
- 根据 \(\hat{A}_{1,t}\) 的符号,计算 \(\min[r_t(\theta) \hat{A}_{1,t}, \text{clip}(\dots) \hat{A}_{1,t}]\)。
- 计算当前策略 \(\pi_\theta\) 和参考策略 \(\pi_{ref}\) 在当前上下文 \((q, o_{1,<t})\) 下的 KL 散度 \(D_{KL}[\pi_\theta || \pi_{ref}]\)。
- 将所有 token 的 \(\min[\dots]\) 项求平均,然后减去平均的 \(\beta D_{KL}\) 项。
- 如果采样了多个响应 (\(G > 1\)),则将所有响应的结果再求平均,得到最终的目标函数估计值 \(J_{GRPO}(\theta)\)。
- 对于响应 \(o_1\) 中的每个 token \(t\) (从 "为什么" 到 "。"):
- 梯度计算:
- 使用自动微分库(如 PyTorch 或 TensorFlow)计算目标函数 \(J_{GRPO}(\theta)\) 相对于模型参数 \(\theta\) 的梯度 \(\nabla_\theta J_{GRPO}(\theta)\)。梯度会指示如何调整参数 \(\theta\) 以最大化目标函数。
- 参数更新:
- 使用优化器(如 Adam)根据计算出的梯度更新模型参数:\(\theta_{new} = \theta_{old} + \text{learning\_rate} \times \nabla_\theta J_{GRPO}(\theta)\)。
总结: GRPO 通过采样、评估(计算优势)、计算 PPO 裁剪目标和 KL 散度惩罚,构建一个可微的目标函数。然后通过计算该目标函数关于策略参数的梯度,并使用梯度上升(或梯度下降,如果优化的是负的目标函数)来更新模型,使其生成更符合期望(例如,更高奖励、更接近参考策略)的响应。这个过程会迭代进行,不断优化策略。

浙公网安备 33010602011771号