Grpo 训练里面的 π_old π_new 怎么训练更新的
整个过程可以看作是一个由两大阶段组成的循环:A. 数据生成 (Rollout) 和 B. 模型学习 (Learning) 。
第零步:准备工作 (Initialization)
- 加载初始模型:我们加载一个已经经过监督微调(SFT)的模型。这个模型是我们的起点,它的策略我们称之为 π0。
- 加载奖励模型:我们加载两个已经训练好的奖励模型(Reward Models, RM):一个是准确性 RM,另一个是拒答合理性 RM。它们是裁判,负责给后续生成的答案打分。
第一次迭代 (Iteration 1)
阶段 A: 数据生成 (Rollout)
- 生成回答:我们使用当前的策略模型 π0,输入一批全新的提示(Prompts)。模型会为每个 Prompt 生成一个或多个回答(Responses)。
- 记录旧策略概率:在生成这些回答的同时,模型会计算并记录下,以它当前的策略(也就是\pi_0),生成回答中每一个词(token)的概率。这些概率值(通常是 log_probs)会被存储起来,它们是后续计算中
π_old(a|s)的依据。- 计算奖励:将生成的每一个回答,连同原始问题,分别喂给准确性 RM 和拒答合理性 RM。两个 RM 会各自给出一个分数。我们根据这两个分数,通过一个函数(例如加权求和)计算出一个最终的标量奖励值(Reward)。
至此,我们获得了一批完整的训练数据,每一条数据都包含:
(prompt, response, reward, log_probs_from_π_0)。阶段 B: 模型学习 (Learning)
现在,我们将数据生成阶段“冷冻”,进入模型更新阶段。
定义角色:
- 旧策略 **
π_old**:就是刚才生成数据的那个固定不变的策略 π0。它存储的log_probs是计算损失函数时的重要参照物(分母)。- 新策略 **
π_new**:就是我们正在训练、参数即将发生改变的模型。在学习阶段开始的第一瞬间,π_new 和π_old 的参数是完全相同的。内部学习循环 (Epochs) :我们使用刚刚收集到的这批数据,对模型进行多轮(epochs)的训练。在每一轮的每一步(step)中:
计算新策略概率:对于当前批次中的一个回答,
π_new 会重新计算一遍,以它当前的参数,生成这个回答中每个 token 的概率。计算损失函数:此时,损失函数的所有组件都已就绪:
- 重要性采样比率
ρ: 通过 πold(a∣s)πnew(a∣s) 计算得出。分子是π_new刚刚实时算出的概率,分母是我们在阶段 A 记录好的π_old的概率。- 优势函数 (Advantage) :根据阶段 A 中得到的奖励(Reward)计算得出。
- 最终损失 (Loss) :将上述比率和优势函数代入 GRPO/PPO 的裁切目标函数(Clipped Objective Function)中,并可能加上价值函数损失、熵奖励等其他项,计算出最终的总损失。
梯度更新:通过反向传播计算梯度,并更新
π_new的模型参数。完成更新:当内部学习循环(epochs)结束后,
π_new 的参数已经被更新了。这个更新后的模型,我们称之为 π1。第一次迭代到此结束。
第二次及后续迭代 (Iteration 2, 3, ...)
现在,整个流程将重复进行,形成一个大循环:
新的数据生成:我们使用更新后得到的模型 π1****,去执行阶段 A 的操作,为一批新的 Prompts 生成新的回答,并记录下由 π1 计算的概率值,同时由 RM 给出新的奖励。
新的模型学习:进入阶段 B。此时:
- 旧策略 **
π_old**:变成了刚刚生成数据的那个策略 π1。- 新策略 **
π_new**:是我们当前要训练的模型(初始状态和\pi_1一样)。- 我们使用由 π1 生成的新数据和新奖励,重复学习和更新的过程,最终得到一个更优的模型 π2。
这个 “用最新模型生成数据 -> 再用新数据学习模型” 的循环会不断重复,直到模型的性能达到预期或收敛。
总结来说,这个完整的过程就是:用策略 πn**** 生成一批经验数据 → 将 πn 作为
π_old,通过学习这批数据来优化模型 → 得到新策略 πn + 1 → 循环往复。
一个 batch 里面有多个 query 怎么做梯度更新的?
简单来说,整个过程的核心思想是 “分别计算,统一求和(或求平均),一次更新” 。
让我们以您训练 Reward Model 的过程为例,详细分解这个流程:
第一步:数据准备 (构建批次 Batch)
首先,程序会从您的
reject_reward_dataset 数据集中取出 N 条数据,组成一个批次 (Batch) 。在这个例子中,您设置了per_device_train_batch_size: 1,但我们先假设这个值是4 以方便理解(后面会解释gradient_accumulation_steps 的作用)。这个批次现在看起来像这样,包含了 4 个独立的训练样本:
Batch = [ {"question": "Q1", "chosen": "C1", "rejected": "R1"}, {"question": "Q2", "chosen": "C2", "rejected": "R2"}, {"question": "Q3", "chosen": "C3", "rejected": "R3"}, {"question": "Q4", "chosen": "C4", "rejected": "R4"} ]第二步:前向传播 (Forward Pass)
模型(
Qwen2.5-14B-Instruct)会并行地处理这个批次中的所有 4 个样本。GPU 的并行计算能力在这里至关重要。
- 对于 样本 1 (
Q1, C1, R1),模型会分别计算chosen 回答C1 的得分Score(C1) 和rejected 回答R1 的得分Score(R1)。- 同时,对于 样本 2 (
Q2, C2, R2),模型计算Score(C2) 和Score(R2)。- 同样地,并行计算 样本 3 和 样本 4 的得分。
第三步:计算损失 (Loss Calculation)
接下来,对于批次中的每一个样本,都会独立计算一个损失值。对于 Reward Model 训练,常用的损失函数是
log-sigmoid 损失,其目的是让chosen 的得分远高于rejected 的得分。
- 损失 1 (Loss₁) =
f(Score(C1), Score(R1))- 损失 2 (Loss₂) =
f(Score(C2), Score(R2))- 损失 3 (Loss₃) =
f(Score(C3), Score(R3))- 损失 4 (Loss₄) =
f(Score(C4), Score(R4))至此,我们得到了 4 个独立的损失值,每个值都代表了模型在对应那条数据上的“犯错程度”。
第四步:损失聚合 (Loss Aggregation) - 回答您问题的核心
这是关键一步。为了进行一次有效的梯度更新,需要将这一个批次中所有样本的损失值进行聚合,形成一个单一的、代表整个批次平均犯错程度的标量值。
最常见的聚合方式就是计算它们的平均值 (mean) :
Final_Loss = (Loss₁ + Loss₂ + Loss₃ + Loss₄) / 4现在,我们有了一个可以指导模型整体优化的统一目标
Final_Loss。第五步:反向传播 (Backward Pass)
程序会基于这个单一的
Final_Loss 值,执行一次反向传播。
- 计算
Final_Loss 相对于模型中所有可训练参数(权重和偏置)的梯度。- 由于
Final_Loss 是所有样本损失的平均值,所以计算出的梯度也就天然地代表了整个批次数据的平均梯度方向。这意味着它指向一个能让模型在这个批次所有数据上表现都变得更好的“综合优化方向”。第六步:参数更新 (Optimizer Step)
最后,优化器(如 AdamW)使用上一步计算出的聚合梯度,对模型的参数进行一次更新。
New_Weights = Old_Weights - learning_rate * Aggregated_Gradients
关于您配置中的 **
gradient_accumulation_steps: 8**现在我们来解释您配置中一个更重要的细节:梯度累积 (Gradient Accumulation) 。
您设置了
per_device_train_batch_size: 1 和gradient_accumulation_steps: 8。这意味着:
- 物理上的小批次:由于显存限制,每次实际送入 GPU 计算的只有一个样本(batch_size=1)。
- 逻辑上的大批次:但我们希望模拟一个更大的批次(
1 * 8 = 8)来获得更稳定、更准确的梯度。它的工作流程如下:
- 第 1 个样本:送入模型,计算损失
Loss₁,反向传播计算出梯度Grad₁。但是,此时先不更新模型参数! 而是将Grad₁ 累积(暂存)起来。- 第 2 个样本:送入模型,计算
Loss₂,反向传播得到Grad₂。将Grad₂ 加到之前累积的梯度上(Total_Grad = Grad₁ + Grad₂)。仍然不更新模型。- ...
- 第 8 个样本:重复这个过程。当处理完第 8 个样本并累积其梯度后,累积的总梯度
Total_Grad = Grad₁ + ... + Grad₈ 就完成了。- 统一更新:此时,程序会对累积的总梯度进行平均 (
Total_Grad / 8),然后用这个平均后的梯度对模型参数进行一次更新。- 清空累积的梯度,开始下一个循环。
总结:通过梯度累积,您用有限的显存实现了等效于
per_device_train_batch_size * gradient_accumulation_steps = 1 * 8 = 8 的训练效果。多个 query 的梯度是通过在一个累积周期内,将每个 query 独立计算出的梯度进行累加,最后统一求平均并更新模型的方式结合在一起的。
Minibatch?Microbatch?
简单来说,batch 最开始作为重要性采样里面的 π_old,每个 minibatch 更新一次梯度,每个 microbatch 计算损失累加梯度,每个 query 计算损失,microbatch 个损失计算取平均后反向传播计算梯度做累加梯度用。每个 minibatch 训练完之后 π 都会更新为新的 π_new,但是 π_old 是不变的。一个 batch 跑完之后 π_old 才会更新为现在的 π_new。
全局批次 (Global Batch) :
- 这是理论上我们希望在每进行一次模型权重更新时所使用的总样本数量。
- 目的:更大的全局批次通常能提供更稳定、更准确的梯度估计,有助于模型更平滑地收敛,并可以使用更高的学习率。例如,一个大型语言模型的训练可能需要大小为 1024、2048 甚至更大的全局批次。
小批次 (Minibatch) :
- 这是在一次完整的前向和反向传播中,所有参与训练的 GPU 共同处理的样本总数。
- 关系:通常情况下,
Global Batch Size 是Minibatch Size 的整数倍。- 举例:假设我们有 8 个 GPU 参与数据并行训练,每个 GPU 一次处理 16 个样本,那么
Minibatch Size 就是8 * 16 = 128。微批次 (Microbatch) :
- 这是单个 GPU 在一次前向/反向传播中实际处理的最小数据单元。
- 关系:
Minibatch Size 是Microbatch Size 的整数倍。- 目的:它的存在主要是为了将大的计算任务拆解成可以放入单张 GPU 显存的小块。
在训练的时候:
第一阶段:数据收集 (Rollout Phase)
- 我们有一个当前最新的策略模型,我们称之为
π_current。- 我们使用这个
π_current 模型与环境交互,运行多个回合(episodes),直到收集到足够多的数据(例如 2048 个时间步的经验)。- 这个收集到的、包含成千上万条经验的完整数据集,就是您之前语境中的全局批次 (Global Batch) 。
- 一旦数据收集完成,我们就把
π_current 的参数(权重)保存下来,并将其“快照”标记为 **π_old**。关键点:
π_old 在此刻被定义且冻结。它的作用是作为一个固定的参照物,因为它知道在收集数据时,对于某个状态(state)选择某个动作(action)的原始概率是多少。第二阶段:模型更新 (Update Phase)
现在,我们拿着这个巨大的全局批次(Experience Buffer)和冻结的
π_old,开始真正地训练模型。
这个全局批次会被随机打乱,然后切分成很多个小批次 (minibatch) (例如,大小为 64)。
模型会进行多轮(epochs)训练。在每一轮中,它会遍历所有的
minibatch。对于每一个 **
minibatch**:
- 模型(我们称之为正在被优化的
π_new)会计算这个minibatch 中数据的损失。- 在计算损失时,会用到重要性采样的比率:
ratio = π_new(a|s) / π_old(a|s)。- **
π_old** 在这里是固定不变的,它就是第一阶段结束时那个被冻结的“快照”。-
π_new 则是当前正在被优化的模型。- 计算完这个
minibatch 的梯度后,**π_new** 的参数会被更新。接着处理下一个
minibatch,π_old 依然是那个旧的“快照”,而π_new 则是上一步更新后的、更新鲜的模型。当遍历完所有
minibatch 完成一轮(epoch)训练后,π_new 已经被更新了很多次,但π_old 始终未变。在完成所有轮次(epochs)的训练后,这个更新阶段就结束了。最终得到的
π_new 会成为下一个数据收集阶段的π_current。
移除重要性采样会怎样?
每次的 response 要现生成,
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于