文中大部分为GPT生成,并由我个人理解后添加了注解。
个人理解:
上图中:
- PPO-clip Loss指的是用了clip的Actor loss(含kl散度罚项),用来鼓励actor模型在不过度偏离ref模型以及old actor模型的前提下,对齐人类偏好;
- LM Loss指的是(交叉)熵损失函数,用来促进actor生成更多样的输出;
- MSE Loss指的是critic loss,用来促进critic的价值打分value更接近真实收益return(在不过度偏离old critic的前提下)。
但是实际上,一般:
下面仅给出一般情况下的理解
一般来说:
Actor Loss
PPO Loss
是Proximal Policy Optimization(PPO)算法设计的整体损失函数。
包括:
PPO 中的 Actor Loss 具体形式:
PPO 的 Actor Loss 有一个特殊设计,叫做 Clipped Objective,如下:
L^{\text{CLIP}}(\theta) = \mathbb{E}_t \left[ \min \left( r_t(\theta) \hat{A}_t,\ \text{clip}(r_t(\theta), 1 - \epsilon, 1 + \epsilon)\hat{A}_t \right) \right]
其中:
这个裁剪形式就是 PPO 特有的 Actor Loss。
总结关系如下表:
Actor Loss | PPO Loss | |
---|---|---|
指代 | 策略网络的损失 | PPO算法的总损失 |
是否包含Critic | ❌ | ✅(通常包括 critic 和 entropy) |
数学形式 | 常用 Clipped Objective | 通常是:Actor Loss + Critic Loss + Entropy Bonus |
是否特指 PPO | ❌(其他算法也有) | ✅(是PPO特有设计) |
所以:
ppo中,actor基于prompt生成response,以及response所选出的各token id对应的概率,也就是logprobs,
我们将prompt + response拼成一个完整序列送给ref进行前向(注意ref模型只做一次前向,而不是生成!),
然后获取actor生成的response中的各token id所对应的ref给出的概率,作为ref_logprobs,
进而与logprobs做kl散度计算来约束actor的更新。
在 PPO 中,actor 的 response 会和 prompt 拼接成一个完整的序列,送入 reference model(也就是 pre‑trained LM 或 SFT model,简称 ref)进行一次前向传播,用来获取每个 token 的 log-probabilities。这些就是所谓的 ref_logprobs。
流程总结:
拼接序列
把 prompt(环境状态)和 actor 生成的 response(模型行为)拼接成一整段文本序列。
丢给 reference model 前向计算
用这个完整序列在 ref 模型上前向计算,从而获得 response 部分每个 token 的概率(用 log-prob 表示)。
注意!
这里相当于用actor生成的response中的各token的token id,去索引ref做完前向之后对应位置上的token id所对应的概率!
记录 token log‑probabilities
得到的 log-probabilities 就是 ref_logprobs,对应 actor 输出的每个 token。
用于 KL 惩罚和计算 reward
PPO 的 reward 包括两个部分:一是 reward model 打分,二是 KL penalty —— 即 actor logprobs 与 ref logprobs 差值,防止偏离太远。
问题回答:
个人理解:
在更小一层的PPO epochs循环中,我们更新了new Actor和new Critic,
然后当这个PPO epochs结束之后,我们立刻将new Actor的参数赋给了old Actor, 然后将new Critic的参数赋给了old Critic
(虽然实际上我们不会保存old actor和old critic的参数,因为太占空间了,事实上我们保存的是old模型的经验的快照,在ppo epochs中我们直接更新actor和critic!)
(此外critic其实没有新旧之说,我们仅保存大循环开始时critic对response的打分序列values,用这个values来clip后续critic的更新)
(具体见后续问题)
下面用清晰的步骤和小粒度对齐,说明 PPO 中 newActor、newCritic 和 oldActor(旧策略)之间的更新顺序与流程:
小循环 & 大循环中的更新逻辑:
大循环:多次 iterations(或 epochs)
每个 iteration 包括采样、更新等完整步骤。
(快照old模型)
小循环(单次 iteration 里的 PPO epochs)
使用同一批体验数据,进行多轮梯度更新。
(更新new模型,在ppo epochs全部跑完后将new模型赋给old模型)
每次 iteration 的完整流程细节:
采样阶段(Sampling)
使用当前策略 actor θₖ 与环境交互,收集 trajectories,并记录动作 log prob。这 log prob 即代表 “旧策略” 在采样时做出的判断。
计算 Advantage & Return
利用当前 critic φₖ (可以理解为old critic,实际上就是大循环开始时的critic模型)估计值value,以及reward的打分(含kl散度),
结合奖励信号计算 TD‑error 或 GAE→得出 Aₜ, Rₜ。
快照旧策略
将当前 actor θₖ 复制一份为 θ_old,此时就固定为旧策略,用于后续多个 PPO epoch 中计算比值 rₜ(medium.com)。
训练小循环(多个 PPO epochs)
对同一批数据,执行 K 个 epoch 的小批训练(batch):
Actor 更新(newActor θ):计算 clipped surrogate loss,用 θ_old 的 log‑prob 比较当前策略,进行梯度上升。
Critic 更新(newCritic φ):用均方误差最小化 V_φ(sₜ) 与 Return 的差距,同步调整参数。
顺序:
同一个 batch 上,多数实现是 先 actor,再 critic 或者 分开独立 optimize,
但都是 多轮使用相同 θ_old 的旧策略。
结束本次 iteration
当所有 epoch 完成:
总结一句话:
在一个 iteration 内:
小循环中:不断更新 newActor 和 newCritic,但“旧策略 θ_old”保持不变;
小循环结束时:整个 newActor(和 newCritic)替代旧参数,成为下一轮 iteration 的 actor 和 critic。
回答问题:
“也就是说在更小一层的循环中,我们更新了newActor和newCritic, 然后当这个PPO epochs结束之后,我们立刻将newActor赋给了oldActor, 然后将newCritic赋给了oldCritic, 是这样吗?”
完全正确:
个人理解:
实际上我们并没有保存Old Actor和Old Critic的权重,
而是我们将它们所生成的一些旧经验用快照冻结, 然后保存到了一个buffer里面,
然后我们在ppo Epochs里面持续更新原来的Actor和Critic。
许多 PPO 的实现并不保存完整的 oldActor、oldCritic 网络参数,
而是通过 将旧策略生成的经验(如 log π_old)冻结并缓存到 buffer,
在多个 epoch 内反复使用这些旧经验来训练,不断更新原来的 Actor 和 Critic。
下面详细说明这种关键机制。
PPO 中对“旧策略”处理的两种方式:
最直观的做法:保存完整网络快照
常见高效实现:只缓存 旧策略的 log‑prob
在采样时,actor 输出时同时保存这个动作在当时策略下的 log π_old 到 buffer。
在多个 PPO epoch 中,无需再调用一个旧策略网络,
只需 从 buffer 中读取旧的 log π_old,
并与当前策略生成的新 log π_new 作对比,计算比例:
r _ t =exp(log π _{ new} (a _ t ∣s _ t )−log π _{ old} )
这就等效于“冻结旧策略”的效果,但效率更高。
reddit 上也指出:
“after collecting data using the old policy, we do k epochs update, so the policy in each epoch is the new policy compared to the old log probabilities saved in Replay Buffer”(reddit.com)
即:用 buffer 冻结旧 log‑prob,更新时直接对比,不需要保存旧模型。
总结与对照表:
方法 | 是否存网络快照 | 使用旧策略方式 | 优点 | 缺点 |
---|---|---|---|---|
快照网络 | ✅ | 每 epoch 调用旧模型 | 精准、逻辑清晰 | 存储和计算量大 |
缓存 log‑prob | ❌ | buffer 里读取旧 log‑prob | 高效、简洁,常用 | 不保留网络状态,只保留行为统计 |
回答问题:
“我们并没有保存 OldActor 和 OldCritic 的权重,而是将旧经验快照冻结到 buffer,然后持续更新 Actor 和 Critic是吗?”
完全正确,且是 PPO 常见且推荐的实现方式。
详细、清晰地给出ref、old actor、old critic、reward、new actor、new critic获得的输入和输出
(其中你需要明确给出reward的输出相当于给非结尾的token不附加reward模型打分,给response结尾附加reward模型打分,但是response每个token位置都有加入kl散度约束来作为该token的reward的一部分)
本问题的重点在于reward模型的输出究竟是一个序列还是一个标量)
个人理解:
reward模型接收prompt + response作为输入,
然后我们给response处的各token打分,
在这里我们要求reward模型给response非结尾处的token都打0分(无reward奖励),
只给response结尾处附上一个整体的reward打分。
实际上reward模型是可以做到逐token打分的,这里只是只取最后一个token生成的打分值(也就是对应整体的打分)而已!
这样,目前的rewards其实相当于一个[0, 0, ..., reward_score]的序列。
然后我们为rewards加上actor和ref模型之间的kl散度,
这样就得到了我们最终期望的rewards序列(也就是获得了response中每个token对应的reward打分)。
这样的设计公式如下:
"select * from blocks where id='20250624002126-0jv0w6s'"
目的是:
- 当 时,我们更加关心Actor是否有在Ref的约束下生产token
- 当 时,我们不仅关心Actor是否遵从了Ref的约束,也关心真正的即时收益
具体详见:(3)重新设计 R_{t}
以下是基于标准 RLHF + PPO 实现(以 TRL PPOTrainer 为例)的详细流程图,说明每个模型:reference model(ref)、old actor、old critic、reward model、new actor、new critic 在训练中的输入与输出,以及如何将 KL 散度约束 与 reward 模型评分 融入 token‑级别的 reward:
一、 采集阶段(Rollout):
模块 | 输入 | 输出 |
---|---|---|
old actor(即当前策略) | Prompt(x) | Response tokens y = (y₁…yᴺ),及其 log‑probs π_old(yₜ |
reward model | Prompt+Response 完整序列 (x,y) | reward_score = r(x,y)(一个 scalar,仅在整个 response 结尾计算一次) |
二、风控与评价阶段(Ref 前向 & Critic):
reference model(ref)
old critic(value/value head)
输入:prompt + response 序列(x, y)
输出:每个 time‑step 的估值 V_old(x, y_ 此处下标小于t的解释详见:7. 为什么PPO 中,critic 的输入使用的是当前时刻之前的上下文 (x, y_{
三、构造 token‑级 rewards + advantage:(重要)
对每个 token t:
reward from reward model:
KL penalty(每个 token 都计算):
综合 token reward:
随后使用 gamma 折扣累计至各步,得到 return-to-go R̂ₜ。
再计算优势:
Aₜ = R̂ₜ − V_old(x, y_ 四、PPO 更新阶段: 在一个批次内,使用下面步骤更新 actor 和 critic: 新 actor(new policy π_new) 输入:相同 prompt,生成整个 batch 的 y 并记录新 log‑probs π_new(yₜ) 使用 PPO-clip 目标函数更新: 其中 (en.wikipedia.org)。 新 critic(new value V_new) 流程总结图: 总结要点: 以下是一个层级清晰、结构明确的 RLHF + PPO 流程,帮助梳理每个阶段各模型的输入输出及它们之间的关系: 第 1 层:数据采集(Rollout) 目的:使用旧策略生成回应并打标签。 输入:Prompt 模型:旧 actor() 模型:奖励模型(Reward model) 第 2 层:评价与打分 目的:为每个 token 打分 —— 引入 KL 惩罚及计算 critic 的 baseline。 模型:Reference model(冻结 LM) 模型:旧 critic(Value model) 输入:每个 token 的生成上下文 此处下标小于t的解释详见:7. 为什么PPO 中,critic 的输入使用的是当前时刻之前的上下文 (x, y_{ 输出:每个 time‑step 的状态价值估计 第 3 层:构造 Token‑级 Reward & Advantage 目的:根据模型输出构建 token‑级 reward,并计算 advantage。 KL 惩罚(每个 token): Reward 分配: 累积折扣收益(Return-to-go): 优势估计(Aₜ): 第 4 层:PPO 更新(Actor 和 Critic) 目的:依据 PPO 的目标函数更新策略与价值网络。 新 actor() 输入:原 batch 的 prompt 序列 输出:新 log-probs 损失函数(PPO-Clip + KL penalty): L_{\text{actor}} = \mathbb{E}_t \left[ \min \Big( r_t A_t,\; \text{clip}(r_t,1-\epsilon,1+\epsilon) A_t \Big) \right] - \beta\,\mathbb{E}_t[\text{kl}_t] 其中 新 critic() 小结: 个人理解: 如图中所示也可以看出,其实左边的value模型接收的输入是t时刻的状态,但是实际上一共有1~T个时刻! 所以我们可以理解为,这样写是要求value模型必须是一个causal因果模型, value只能依赖当前状态(prompt + 已生成的可见的前文)去给当前状态打分,而不能看到未来的信息!因为它的任务是预估未来的信息。 但是在实际操作中,我们已经获得了prompt + actor给出的完整response, 因此我们在操作上,就是将整个序列塞给value作为输入! 不过我们又做了一个causal mask + teacher forcing的事情来让value打分。 其中: 在 PPO 中,critic 的输入使用的是当前时刻之前的上下文 (x, y_ 为什么是 y_ 总结: 批量对 prompt+付出的每个位置 y_ 再po一次第七个问题的个人理解: 个人理解: 如图中所示也可以看出,其实左边的value模型接收的输入是t时刻的状态,但是实际上一共有1~T个时刻! 所以我们可以理解为,这样写是要求value模型必须是一个causal因果模型, value只能依赖当前状态(prompt + 已生成的可见的前文)去给当前状态打分,而不能看到未来的信息!因为它的任务是预估未来的信息。 但是在实际操作中,我们已经获得了prompt + actor给出的完整response, 因此我们在操作上,就是将整个序列塞给value作为输入! 不过我们又做了一个causal mask + teacher forcing的事情来让value打分。 其中: 问题: 但是我们不是一次性生成了所有prompt + response的吗?所以具体critic只用y_ 是的,你的理解完全正确 ——在 PPO 中,新旧 critic 的输入方式可以通过 teacher forcing + causal mask 实现,只看历史 tokens 而不泄露未来信息。 为什么只用 而不包括 ? 实现方式:只需一次前向,使用 teacher forcing + causal mask 因为你在 rollout 阶段已经生成了整个响应 sequence ,所以 critic 只要: 这就是典型的 teacher forcing。不需要手动循环 t,只需一个 batch forward + causal mask,即可获得所有 time-step 的 value 输出。 小结: TRL/PPOTrainer 就是这样在一次 batch 数据前向中完成多时刻的 critic 估值的,无需显式循环,非常高效。
prompt x
├─▶ old actor ─▶ response y, actor log‑probs
拼接完整序列 (x,y)
├─▶ reward model ─▶ reward_score at token N
├─▶ reference model ─▶ ref log‑probs for every token
每个 token 的生成上下文 (x,y_{
6. 给出清晰明确分层的ppo流程
7. 为什么PPO 中,critic 的输入使用的是当前时刻之前的上下文 (x, y_{
在 RL 中,每个 time-step 的状态表示环境当前状况,即当前所见的历史信息。在文本生成中,step 的状态是 prompt 和已经生成的 tokens ,即 (x, y_
critic 的目标是预测从这个状态开始,到未来所有奖励的期望值 。如果 critic 知道了后续 token(甚至结尾 token),它就“偷看未来”了,估值会过高,违反 causal consistency。
critic 项用来计算优势 。这里的 应严格基于 当前时刻可见的上下文,这样 才是合理的差值(ar5iv.labs.arxiv.org)。
8. 在ppo实际操作中如何实现critic 的输入仅使用当前时刻之前的上下文 (x, y_{