Last updated on October 6, 2026 pm
本文用于整理大语言模型训练与推理中的基础知识、常见方法及其背后的原理,并持续补充相关内容。
大模型算法面试中,Transformer 手写题通常不是要求从头复现完整的 LLaMA,而是检查是否真正理解几个核心组件:
Scaled Dot-product Attention 与 Causal Mask;
Multi-head Attention 中的 Shape 变换;
RoPE;
RMSNorm;
SwiGLU;
Pre-Norm、Residual Connection 与完整 Transformer Block;
Next-token Prediction 的 Shift。
下面使用 PyTorch 分组件实现一个最小 Decoder-only Transformer。代码重点是结构与 Tensor Shape,不包含 FlashAttention、Tensor Parallel、KV Cache 和混合精度等工程优化。
1 2 3 4 5 import mathimport torchimport torch.nn as nnimport torch.nn.functional as F
1.1 RMSNorm
设 RMSNorm 的输入为:
X ∈ R B × T × D , X\in\mathbb R^{B\times T\times D},
X ∈ R B × T × D ,
其中:
B B B :Batch Size;
T T T :Sequence Length;
D D D :Hidden Size;
X b , t , : ∈ R D X_{b,t,:}\in\mathbb R^D X b , t , : ∈ R D :第 b b b 个样本中第 t t t 个 Token 的 Hidden Vector。
RMSNorm 对每个 Token 的 Hidden Dimension 单独计算均方根。也就是说,固定 b , t b,t b , t ,只在最后一维 j = 1 , … , D j=1,\ldots,D j = 1 , … , D 上求均值:
RMS ( X b , t , : ) = 1 D ∑ j = 1 D X b , t , j 2 + ϵ . \operatorname{RMS}(X_{b,t,:})
=
\sqrt{
\frac{1}{D}
\sum_{j=1}^{D}X_{b,t,j}^{2}
+\epsilon
}.
RMS ( X b , t , : ) = D 1 j = 1 ∑ D X b , t , j 2 + ϵ .
然后对该 Token 的每个 Hidden Dimension 做缩放:
Y b , t , j = w j X b , t , j RMS ( X b , t , : ) , Y_{b,t,j}
=
w_j
\frac{X_{b,t,j}}
{\operatorname{RMS}(X_{b,t,:})},
Y b , t , j = w j RMS ( X b , t , : ) X b , t , j ,
其中:
w ∈ R D w\in\mathbb R^D w ∈ R D :可学习的缩放参数;
w j w_j w j :第 j j j 个 Hidden Dimension 对应的参数;
ϵ \epsilon ϵ :防止分母为零的常数;
Y ∈ R B × T × D Y\in\mathbb R^{B\times T\times D} Y ∈ R B × T × D :RMSNorm 的输出。
因此 RMSNorm:
不在 Batch Dimension B B B 上统计;
不在 Sequence Dimension T T T 上统计;
只在每个 Token 内部的 Hidden Dimension D D D 上统计;
输入和输出 Shape 都是 ( B , T , D ) (B,T,D) ( B , T , D ) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class RMSNorm (nn.Module): def __init__ (self, hidden_size, eps=1e-6 ): super ().__init__() self .weight = nn.Parameter(torch.ones(hidden_size)) self .eps = eps def forward (self, x ): mean_square = x.pow (2 ).mean(dim=-1 , keepdim=True ) inv_rms = torch.rsqrt(mean_square + self .eps) return x * inv_rms * self .weight
与 LayerNorm 相比,RMSNorm 不计算并减去均值,只根据均方根控制向量尺度,因此计算更简单。
1.2 RoPE
Multi-head Attention 拆分 Head 后,Query 和 Key 的 Shape 为:
Q , K ∈ R B × H × T × D h , Q,K\in
\mathbb R^{B\times H\times T\times D_h},
Q , K ∈ R B × H × T × D h ,
其中:
B B B :Batch Size;
H H H :Attention Head 数量;
T T T :Sequence Length;
D h D_h D h :每个 Head 的维度,要求是偶数;
Q b , h , m , : Q_{b,h,m,:} Q b , h , m , : :第 b b b 个样本、第 h h h 个 Head 在位置 m m m 的 Query;
K b , h , n , : K_{b,h,n,:} K b , h , n , : :第 b b b 个样本、第 h h h 个 Head 在位置 n n n 的 Key。
RoPE 不把 Position Embedding 加到 Token Hidden State 上,而是根据位置分别旋转 Q Q Q 和 K K K 。它把 Head Dimension 中相邻的两个维度看作一个二维向量:
[ Q m , 2 i Q m , 2 i + 1 ] , i = 0 , 1 , … , D h 2 − 1. \begin{bmatrix}
Q_{m,2i}\\
Q_{m,2i+1}
\end{bmatrix},
\qquad
i=0,1,\ldots,\frac{D_h}{2}-1.
[ Q m , 2 i Q m , 2 i + 1 ] , i = 0 , 1 , … , 2 D h − 1.
第 i i i 对维度使用的角频率定义为:
ω i = b − 2 i D h , \omega_i
=
b^{-\frac{2i}{D_h}},
ω i = b − D h 2 i ,
其中 b b b 是 RoPE Base,通常取 10000 10000 10000 。位置 m m m 对应的旋转角度为:
θ m , i = m ω i . \theta_{m,i}=m\omega_i.
θ m , i = m ω i .
二维旋转矩阵为:
R ( θ m , i ) = [ cos θ m , i − sin θ m , i sin θ m , i cos θ m , i ] . R(\theta_{m,i})
=
\begin{bmatrix}
\cos\theta_{m,i} & -\sin\theta_{m,i}\\
\sin\theta_{m,i} & \cos\theta_{m,i}
\end{bmatrix}.
R ( θ m , i ) = [ cos θ m , i sin θ m , i − sin θ m , i cos θ m , i ] .
因此旋转后的 Query 为:
[ Q m , 2 i ′ Q m , 2 i + 1 ′ ] = R ( θ m , i ) [ Q m , 2 i Q m , 2 i + 1 ] . \begin{bmatrix}
Q'_{m,2i}\\
Q'_{m,2i+1}
\end{bmatrix}
=
R(\theta_{m,i})
\begin{bmatrix}
Q_{m,2i}\\
Q_{m,2i+1}
\end{bmatrix}.
[ Q m , 2 i ′ Q m , 2 i + 1 ′ ] = R ( θ m , i ) [ Q m , 2 i Q m , 2 i + 1 ] .
展开后:
Q m , 2 i ′ = Q m , 2 i cos θ m , i − Q m , 2 i + 1 sin θ m , i , Q m , 2 i + 1 ′ = Q m , 2 i sin θ m , i + Q m , 2 i + 1 cos θ m , i . \begin{aligned}
Q'_{m,2i}
&=
Q_{m,2i}\cos\theta_{m,i}
-
Q_{m,2i+1}\sin\theta_{m,i},\\
Q'_{m,2i+1}
&=
Q_{m,2i}\sin\theta_{m,i}
+
Q_{m,2i+1}\cos\theta_{m,i}.
\end{aligned}
Q m , 2 i ′ Q m , 2 i + 1 ′ = Q m , 2 i cos θ m , i − Q m , 2 i + 1 sin θ m , i , = Q m , 2 i sin θ m , i + Q m , 2 i + 1 cos θ m , i .
Key 使用完全相同的方式旋转;Value 不使用 RoPE。输入和输出 Shape 均保持为 ( B , H , T , D h ) (B,H,T,D_h) ( B , H , T , D h ) 。
为什么旋转以后包含相对位置?
下面省略 Batch 和 Head 下标,并用 R m R_m R m 表示位置 m m m 上所有二维旋转矩阵组成的 Block-diagonal Matrix。
位置 m m m 的 Query 与位置 n n n 的 Key 做点积时:
( R m Q m ) T ( R n K n ) = Q m T R m T R n K n = Q m T R n − m K n . \begin{aligned}
(R_mQ_m)^T(R_nK_n)
&=
Q_m^TR_m^TR_nK_n\\
&=
Q_m^TR_{n-m}K_n.
\end{aligned}
( R m Q m ) T ( R n K n ) = Q m T R m T R n K n = Q m T R n − m K n .
因为二维旋转矩阵满足:
R m T R n = R n − m , R_m^TR_n=R_{n-m},
R m T R n = R n − m ,
所以旋转后 Query–Key 的点积不仅与内容有关,还与相对位置 n − m n-m n − m 有关。这就是 RoPE 能把相对位置信息注入 Attention Score 的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 def rotate_half (x ): """ 将最后一维中相邻的两个数 (x_0, x_1) 变成 (-x_1, x_0),相当于旋转 90°。 x: (..., head_dim) """ x_even = x[..., 0 ::2 ] x_odd = x[..., 1 ::2 ] rotated = torch.stack((-x_odd, x_even), dim=-1 ) return rotated.flatten(start_dim=-2 )def build_rope_cache (seq_len, head_dim, device, base=10000.0 ): """ 返回: cos, sin: (1, 1, seq_len, head_dim) 前两个 1 用于在 Batch 和 Head Dimension 上广播。 """ assert head_dim % 2 == 0 pair_index = torch.arange( head_dim // 2 , device=device, dtype=torch.float32, ) frequencies = base ** (-2 * pair_index / head_dim) positions = torch.arange( seq_len, device=device, dtype=torch.float32, ) angles = positions[:, None ] * frequencies[None , :] angles = torch.repeat_interleave(angles, repeats=2 , dim=-1 ) cos = angles.cos()[None , None , :, :] sin = angles.sin()[None , None , :, :] return cos, sindef apply_rope (q, k ): """ q, k: (batch_size, num_heads, seq_len, head_dim) 返回: 旋转后的 q, k,Shape 保持不变。 """ seq_len = q.size(-2 ) head_dim = q.size(-1 ) cos, sin = build_rope_cache(seq_len, head_dim, q.device) cos = cos.to(dtype=q.dtype) sin = sin.to(dtype=q.dtype) q = q * cos + rotate_half(q) * sin k = k * cos + rotate_half(k) * sin return q, k
1.3 Scaled Dot-product Attention
Attention 的输入 Shape 为:
Q , K , V ∈ R B × H × T × D h , Q,K,V\in
\mathbb R^{B\times H\times T\times D_h},
Q , K , V ∈ R B × H × T × D h ,
其中 H H H 是 Attention Heads 数量,D h = D / H D_h=D/H D h = D / H 是每个 Head 的维度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def scaled_dot_product_attention (q, k, v ): """ q, k, v: (batch_size, num_heads, seq_len, head_dim) """ head_dim = q.size(-1 ) seq_len = q.size(-2 ) scores = q @ k.transpose(-2 , -1 ) scores = scores / math.sqrt(head_dim) causal_mask = torch.triu( torch.ones( seq_len, seq_len, device=q.device, dtype=torch.bool , ), diagonal=1 , ) scores = scores.masked_fill(causal_mask, float ("-inf" )) attention_weights = F.softmax(scores, dim=-1 ) output = attention_weights @ v return output
除以 D h \sqrt{D_h} D h 是为了防止 Head Dimension 增大时点积幅度过大,导致 Softmax 过早饱和。Causal Mask 则保证 Decoder 只能看到当前位置及其之前的 Tokens。
1.4 Multi-head Self-Attention
Multi-head Attention 的核心手写点是:
一次线性映射得到 Q , K , V Q,K,V Q , K , V ;
把 Hidden Dimension 拆成多个 Heads;
每个 Head 独立计算 Attention;
合并 Heads 并做输出投影。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class MultiHeadSelfAttention (nn.Module): def __init__ (self, hidden_size, num_heads ): super ().__init__() assert hidden_size % num_heads == 0 self .num_heads = num_heads self .head_dim = hidden_size // num_heads self .qkv_proj = nn.Linear( hidden_size, 3 * hidden_size, bias=False , ) self .out_proj = nn.Linear( hidden_size, hidden_size, bias=False , ) def _split_heads (self, x ): batch_size, seq_len, hidden_size = x.shape x = x.reshape( batch_size, seq_len, self .num_heads, self .head_dim, ) return x.transpose(1 , 2 ) def forward (self, x ): batch_size, seq_len, hidden_size = x.shape q, k, v = self .qkv_proj(x).chunk(3 , dim=-1 ) q = self ._split_heads(q) k = self ._split_heads(k) v = self ._split_heads(v) q, k = apply_rope(q, k) output = scaled_dot_product_attention(q, k, v) output = output.transpose(1 , 2 ).contiguous() output = output.view(batch_size, seq_len, hidden_size) return self .out_proj(output)
1.5 SwiGLU Feed-forward Network
SwiGLU 使用一条分支产生内容,另一条分支经过 SiLU 后充当 Gate:
SwiGLU ( x ) = W d o w n [ SiLU ( W g a t e x ) ⊙ ( W u p x ) ] . \operatorname{SwiGLU}(x)
=
W_{\mathrm{down}}
\left[
\operatorname{SiLU}(W_{\mathrm{gate}}x)
\odot
(W_{\mathrm{up}}x)
\right].
SwiGLU ( x ) = W down [ SiLU ( W gate x ) ⊙ ( W up x ) ] .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class SwiGLU (nn.Module): def __init__ (self, hidden_size, intermediate_size ): super ().__init__() self .gate_proj = nn.Linear( hidden_size, intermediate_size, bias=False , ) self .up_proj = nn.Linear( hidden_size, intermediate_size, bias=False , ) self .down_proj = nn.Linear( intermediate_size, hidden_size, bias=False , ) def forward (self, x ): gated = F.silu(self .gate_proj(x)) * self .up_proj(x) return self .down_proj(gated)
现代 Decoder-only LLM 通常使用 Pre-Norm:先做 RMSNorm,再进入 Attention/FFN,最后加 Residual。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class TransformerBlock (nn.Module): def __init__ ( self, hidden_size, num_heads, intermediate_size, ): super ().__init__() self .attn_norm = RMSNorm(hidden_size) self .attn = MultiHeadSelfAttention( hidden_size, num_heads, ) self .ffn_norm = RMSNorm(hidden_size) self .ffn = SwiGLU( hidden_size, intermediate_size, ) def forward (self, x ): x = x + self .attn(self .attn_norm(x)) x = x + self .ffn(self .ffn_norm(x)) return x
Residual Connection 为:
x ← x + f ( Norm ( x ) ) , x\leftarrow x+f(\operatorname{Norm}(x)),
x ← x + f ( Norm ( x )) ,
既保留原始信息,也为深层网络提供更直接的梯度传播路径。
1.7 组装最小 Decoder-only Language Model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class TinyDecoderLM (nn.Module): def __init__ ( self, vocab_size, hidden_size, num_heads, intermediate_size, num_layers, ): super ().__init__() self .token_embedding = nn.Embedding( vocab_size, hidden_size, ) self .layers = nn.ModuleList( [ TransformerBlock( hidden_size, num_heads, intermediate_size, ) for _ in range (num_layers) ] ) self .final_norm = RMSNorm(hidden_size) self .lm_head = nn.Linear( hidden_size, vocab_size, bias=False , ) self .lm_head.weight = self .token_embedding.weight def forward (self, input_ids ): x = self .token_embedding(input_ids) for layer in self .layers: x = layer(x) x = self .final_norm(x) logits = self .lm_head(x) return logits
模型输出 logits 的 Shape 为 ( B , T , ∣ V ∣ ) (B,T,|\mathcal V|) ( B , T , ∣ V ∣ ) 。训练 Next-token Prediction 时,需要错开一位:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def next_token_loss (logits, input_ids, attention_mask=None ): """ 位置 t 的 Logits 预测位置 t+1 的 Token。 """ pred_logits = logits[:, :-1 , :] labels = input_ids[:, 1 :].clone() if attention_mask is not None : labels[attention_mask[:, 1 :] == 0 ] = -100 return F.cross_entropy( pred_logits.reshape(-1 , pred_logits.size(-1 )), labels.reshape(-1 ), ignore_index=-100 , )
面试手写时,最容易出错的是:
忘记 Attention 除以 D h \sqrt{D_h} D h ;
Causal Mask 方向写反;
( B , T , D ) (B,T,D) ( B , T , D ) 拆 Head 后没有变成 ( B , H , T , D h ) (B,H,T,D_h) ( B , H , T , D h ) ;
RoPE 应用于 Q , K Q,K Q , K ,而不是 V V V ;
Next-token Prediction 忘记 Shift;
transpose 后直接 view,忘记先调用 contiguous()。
二、什么是大模型后训练?
大模型的训练通常可以粗略分为三个阶段:
Pre-training(预训练) :在海量文本上做 Next-token Prediction,学习语言、知识和一定的推理能力。
Supervised Fine-tuning,SFT(监督微调) :使用“指令—回答”数据训练模型,使其学会遵循指令和使用目标输出格式。
Post-training(后训练) :进一步利用偏好、规则或环境反馈优化模型,使其回答更符合人类偏好,或者在数学、代码等任务上取得更高的最终正确率。
大模型 RL 后训练的核心目标是:训练信号不再只是“模仿参考答案中的每个 Token”,而是利用 Reward 直接优化模型自己生成的结果。
SFT 与 RL 的区别可以概括为:
SFT:模仿给定 Demonstration RL:优化模型自己生成的 Outcome \boxed{
\text{SFT:模仿给定 Demonstration}
\qquad
\text{RL:优化模型自己生成的 Outcome}
}
SFT :模仿给定 Demonstration RL :优化模型自己生成的 Outcome
同一道数学题可能有多条正确推理路径。SFT 通常提高某一条参考回答的似然,而 RL 可以让模型自行生成不同的 Response,再根据最终答案是否正确给予 Reward。因此,具有可自动验证结果的数学、代码和形式化证明任务尤其适合 RL。
三、统一符号:把语言模型看作策略
3.1 Prompt、Response 与 Token
记:
x x x :输入模型的 Prompt;
y = ( y 1 , y 2 , … , y T ) y=(y_1,y_2,\ldots,y_T) y = ( y 1 , y 2 , … , y T ) :模型生成的完整 Response;
T T T :Response 的 Token 数量;
y t y_t y t :Response 中第 t t t 个 Token;
y < t = ( y 1 , … , y t − 1 ) y_{<t}=(y_1,\ldots,y_{t-1}) y < t = ( y 1 , … , y t − 1 ) :生成 y t y_t y t 之前的所有 Response Tokens;
V \mathcal V V :模型的 Vocabulary(词表);
θ \theta θ :Policy Model 的可训练参数。
自回归语言模型在第 t t t 步给出下一个 Token 的概率分布:
π θ ( ⋅ ∣ x , y < t ) . \pi_\theta(\cdot\mid x,y_{<t}).
π θ ( ⋅ ∣ x , y < t ) .
其中 π θ \pi_\theta π θ 表示由参数 θ \theta θ 决定的策略。选中 Token y t y_t y t 的概率为:
π θ ( y t ∣ x , y < t ) . \pi_\theta(y_t\mid x,y_{<t}).
π θ ( y t ∣ x , y < t ) .
一条完整 Response 的条件概率是各 Token 条件概率的乘积:
π θ ( y ∣ x ) = ∏ t = 1 T π θ ( y t ∣ x , y < t ) . \pi_\theta(y\mid x)
=
\prod_{t=1}^{T}
\pi_\theta(y_t\mid x,y_{<t}).
π θ ( y ∣ x ) = t = 1 ∏ T π θ ( y t ∣ x , y < t ) .
实际计算通常使用 Log Probability:
log π θ ( y ∣ x ) = ∑ t = 1 T log π θ ( y t ∣ x , y < t ) , \log\pi_\theta(y\mid x)
=
\sum_{t=1}^{T}
\log\pi_\theta(y_t\mid x,y_{<t}),
log π θ ( y ∣ x ) = t = 1 ∑ T log π θ ( y t ∣ x , y < t ) ,
因为求和比大量小概率相乘更稳定。
3.2 LLM 生成如何对应强化学习?
一条 Response 的生成可以看作一个有限长度的 Markov Decision Process(MDP):
State :
s t = ( x , y < t ) , s_t=(x,y_{<t}),
s t = ( x , y < t ) ,
即 Prompt 加上已经生成的 Tokens。
Action :
a t = y t , a_t=y_t,
a t = y t ,
即选择下一个 Token。
Policy :
π θ ( a t ∣ s t ) = π θ ( y t ∣ x , y < t ) . \pi_\theta(a_t\mid s_t)
=
\pi_\theta(y_t\mid x,y_{<t}).
π θ ( a t ∣ s t ) = π θ ( y t ∣ x , y < t ) .
State Transition :将新 Token 追加到上下文中:
s t + 1 = ( x , y ≤ t ) . s_{t+1}=(x,y_{\le t}).
s t + 1 = ( x , y ≤ t ) .
Reward :可以是每一步的即时 Reward r t r_t r t ,也可以只在完整回答结束时给出 Terminal Reward。
在数学正确性这类 Outcome Reward 中,常见设置是:
r 1 = ⋯ = r T − 1 = 0 , r T = R ( x , y ) . r_1
=\cdots=
r_{T-1}=0,
\qquad
r_T=R(x,y).
r 1 = ⋯ = r T − 1 = 0 , r T = R ( x , y ) .
这里 R ( x , y ) R(x,y) R ( x , y ) 是完整回答的得分,例如答案正确为 1 1 1 、错误为 0 0 0 。
3.3 Return、Value、Q Value 与 Advantage
从第 t t t 步开始的折扣累计回报称为 Return:
G t = ∑ l = 0 T − t γ l r t + l , G_t=
\sum_{l=0}^{T-t}
\gamma^l r_{t+l},
G t = l = 0 ∑ T − t γ l r t + l ,
这里使用 G t G_t G t 是强化学习中的常见记号约定:小写 r t r_t r t 表示第 t t t 步的即时 Reward,而 G t G_t G t 表示从第 t t t 步开始累积得到的 Return。字母本身没有强制规定,也有资料将 Return 记作 R t R_t R t ;本文使用 G t G_t G t ,是为了与单步 Reward r t r_t r t 以及整条回答的评分函数 R ( x , y ) R(x,y) R ( x , y ) 区分。
其中 γ ∈ [ 0 , 1 ] \gamma\in[0,1] γ ∈ [ 0 , 1 ] 是 Discount Factor。LLM 的一条 Response 通常是有限长度序列,而且主要关注最终结果,因此实践中经常取 γ \gamma γ 接近或等于 1 1 1 。
状态价值函数表示:从状态 s t s_t s t 出发,继续按照当前策略生成,预期能获得多少 Return:
V π ( s t ) = E π [ G t ∣ s t ] . V^\pi(s_t)
=
\mathbb E_\pi[G_t\mid s_t].
V π ( s t ) = E π [ G t ∣ s t ] .
动作价值函数表示:在状态 s t s_t s t 选择动作 a t a_t a t 后继续按照当前策略生成,预期能获得多少 Return:
Q π ( s t , a t ) = E π [ G t ∣ s t , a t ] . Q^\pi(s_t,a_t)
=
\mathbb E_\pi[G_t\mid s_t,a_t].
Q π ( s t , a t ) = E π [ G t ∣ s t , a t ] .
Advantage 衡量某个动作相对于当前状态下“平均表现”好多少:
A π ( s t , a t ) = Q π ( s t , a t ) − V π ( s t ) . A^\pi(s_t,a_t)
=
Q^\pi(s_t,a_t)-V^\pi(s_t).
A π ( s t , a t ) = Q π ( s t , a t ) − V π ( s t ) .
A t > 0 A_t>0 A t > 0 :这个 Token 选择比当前策略通常会做出的选择更好,应提高其概率;
A t < 0 A_t<0 A t < 0 :这个选择比平均水平更差,应降低其概率;
A t ≈ 0 A_t\approx0 A t ≈ 0 :这个选择没有明显优于或劣于 Baseline。
四、在线 RL 后训练的完整流程
在线(Online)表示训练数据中的 Response 由当前或最近版本的 Policy 在训练过程中不断生成,而不是完全来自一个固定数据集。
完整 Pipeline 可以表示为:
Prompt Sampling → Policy Rollout → Reward → Advantage → Policy Update → Evaluation \boxed{
\text{Prompt Sampling}
\rightarrow
\text{Policy Rollout}
\rightarrow
\text{Reward}
\rightarrow
\text{Advantage}
\rightarrow
\text{Policy Update}
\rightarrow
\text{Evaluation}
}
Prompt Sampling → Policy Rollout → Reward → Advantage → Policy Update → Evaluation
4.1 第一步:采样 Prompt
从 Prompt Dataset 中采样一批输入:
x 1 , x 2 , … , x B ∼ D p r o m p t , x_1,x_2,\ldots,x_B\sim D_{\mathrm{prompt}},
x 1 , x 2 , … , x B ∼ D prompt ,
其中:
D p r o m p t D_{\mathrm{prompt}} D prompt 是 Prompt 的数据分布;
B B B 是一个 Rollout Batch 中的 Prompt 数量。
数据可以来自数学、问答、选择题和代码等多个任务。多任务训练时,任务采样比例会直接改变不同任务对总梯度的贡献。
4.2 第二步:Policy Rollout
使用生成当前数据的 Behavior Policy π θ o l d \pi_{\theta_{\mathrm{old}}} π θ old 为每个 Prompt 生成回答:
y i ∼ π θ o l d ( ⋅ ∣ x i ) . y_i\sim\pi_{\theta_{\mathrm{old}}}(\cdot\mid x_i).
y i ∼ π θ old ( ⋅ ∣ x i ) .
如果每个 Prompt 生成 G G G 条 Response,则记为:
y i , 1 , y i , 2 , … , y i , G . y_{i,1},y_{i,2},\ldots,y_{i,G}.
y i , 1 , y i , 2 , … , y i , G .
Rollout 时需要确定:
Sampling Temperature;
Top-p p p 或 Top-k k k ;
每个 Prompt 的 Rollout 数 G G G ;
Max Response Length;
Stop Tokens。
探索强度太低时,多个 Response 可能几乎完全相同;探索强度太高时,又可能生成大量无意义文本。
4.3 第三步:计算 Reward
每条 Response 得到 Reward:
R i , j = R ( x i , y i , j ) . R_{i,j}=R(x_i,y_{i,j}).
R i , j = R ( x i , y i , j ) .
常见 Reward 来源包括:
Rule-based / Verifiable Reward :使用答案匹配、单元测试、编译器或定理验证器自动判断;
Reward Model :训练一个模型预测人类更喜欢哪种回答;
AI Feedback :使用更强模型充当 Judge;
Process Reward :对中间推理步骤评分;
Format Reward :检查是否满足规定格式。
从理论上看,Reward Function 是一个将 Prompt 与 Response 映射为标量的函数:
R : X × Y → R , R:\mathcal X\times\mathcal Y\rightarrow\mathbb R,
R : X × Y → R ,
其中 X \mathcal X X 和 Y \mathcal Y Y 分别表示 Prompt Space 与 Response Space。R ( x , y ) R(x,y) R ( x , y ) 越大,表示回答 y y y 在 Prompt x x x 下越符合优化目标。
例如,对于答案可以自动验证的任务,可以使用二值 Outcome Reward:
R ( x , y ) = 1 [ extract ( y ) = a ∗ ] , R(x,y)
=
\mathbf 1[\operatorname{extract}(y)=a^*],
R ( x , y ) = 1 [ extract ( y ) = a ∗ ] ,
其中 1 [ ⋅ ] \mathbf 1[\cdot] 1 [ ⋅ ] 是示性函数,条件成立时为 1 1 1 ,否则为 0 0 0 ;extract ( y ) \operatorname{extract}(y) extract ( y ) 表示从回答中提取最终答案;a ∗ a^* a ∗ 是标准答案。
4.4 第四步:估计 Advantage
不同算法主要在这里产生分歧:
PPO :训练 Critic 估计 V ϕ ( s t ) V_\phi(s_t) V ϕ ( s t ) ,再通过 GAE 计算 Token-level Advantage;
GRPO / DAPO :不训练 Critic,而是用同一 Prompt 下多个 Response 的相对 Reward 构造 Group-relative Advantage;
REINFORCE / RLOO :使用完整 Return,并配合不同形式的 Baseline 降低方差。
其中 ϕ \phi ϕ 表示 Critic 的参数,与 Actor 参数 θ \theta θ 相互独立。
4.5 第五步:更新 Policy
利用 Advantage 更新 Actor。Policy Gradient 的核心直觉是:
A t > 0 ⇒ 提高动作 a t 的概率 \boxed{
A_t>0\Rightarrow
\text{提高动作 }a_t\text{ 的概率}
}
A t > 0 ⇒ 提高动作 a t 的概率
A t < 0 ⇒ 降低动作 a t 的概率 \boxed{
A_t<0\Rightarrow
\text{降低动作 }a_t\text{ 的概率}
}
A t < 0 ⇒ 降低动作 a t 的概率
PPO、GRPO 和 DAPO 都会限制一次更新的幅度,避免新 Policy 相对生成数据的 Old Policy 变化过大。
4.6 第六步:同步与评测
完成参数更新后,将当前策略作为下一轮生成数据的 Behavior Policy:
θ o l d ← θ . \theta_{\mathrm{old}}\leftarrow\theta.
θ old ← θ .
然后使用更新后的 Policy 重新 Rollout、计算 Reward 并继续优化,由此形成 Online RL 的迭代闭环。训练过程中还需要在独立 Validation Set 上定期评测,判断策略的真实任务能力是否随优化目标一起提高。
五、训练中到底有哪些模型?
不同算法需要的模型数量不同。先区分以下角色。
5.1 Actor / Policy Model
Actor 就是实际生成回答并接受更新的语言模型:
π θ ( y t ∣ x , y < t ) . \pi_\theta(y_t\mid x,y_{<t}).
π θ ( y t ∣ x , y < t ) .
训练完成后,最终部署的一般也是 Actor。
5.2 Critic / Value Model
Critic 预测每个 State 的价值:
V ϕ ( s t ) ∈ R . V_\phi(s_t)\in\mathbb R.
V ϕ ( s t ) ∈ R .
它通常由 Transformer Backbone 加 Scalar Value Head 构成。Critic 不负责生成 Token,也不是 Reward Model。它的任务只是给 Actor 提供更稳定的 Advantage Baseline。
5.3 Reward Model
Reward Model 接收 Prompt 和完整 Response,输出一个标量分数:
r ψ ( x , y ) ∈ R , r_\psi(x,y)\in\mathbb R,
r ψ ( x , y ) ∈ R ,
其中 ψ \psi ψ 是 Reward Model 的参数。Reward Model 表示回答质量,而 Critic 表示“从当前生成状态继续生成的预期回报”,两者含义不同。
5.4 Old Policy
Old Policy π θ o l d \pi_{\theta_{\mathrm{old}}} π θ old 是生成当前 Rollout 数据的 Behavior Policy。PPO 类算法需要比较新旧 Policy:
π θ ( a t ∣ s t ) π θ o l d ( a t ∣ s t ) . \frac{\pi_\theta(a_t\mid s_t)}
{\pi_{\theta_{\mathrm{old}}}(a_t\mid s_t)}.
π θ old ( a t ∣ s t ) π θ ( a t ∣ s t ) .
实现中不一定长期保存一份独立 Old Model,常见做法是保存 Rollout 时动作的 old_log_probs。
5.5 Reference Policy
Reference Policy π r e f \pi_{\mathrm{ref}} π ref 通常是冻结的 SFT Model,用于约束 Actor 不要偏离初始模型过远:
D K L ( π θ ∥ π r e f ) . D_{\mathrm{KL}}
(\pi_\theta\Vert\pi_{\mathrm{ref}}).
D KL ( π θ ∥ π ref ) .
Reference Policy 与 Old Policy 不是一回事:
Old Policy 会随训练迭代更新,用来构造 Importance Ratio;
Reference Policy 通常长期冻结,用来提供 KL 锚点;
如果算法没有使用 KL,Reference Policy 可以完全不加载。
六、从 Policy Gradient 开始理解
6.1 最基本的目标
希望最大化模型生成回答的期望 Reward:
J ( θ ) = E x ∼ D , y ∼ π θ ( ⋅ ∣ x ) [ R ( x , y ) ] . J(\theta)
=
\mathbb E_{x\sim D,\ y\sim\pi_\theta(\cdot\mid x)}
[R(x,y)].
J ( θ ) = E x ∼ D , y ∼ π θ ( ⋅ ∣ x ) [ R ( x , y )] .
其中 D D D 是 Prompt 分布。
利用 Log-derivative Trick,可以得到 REINFORCE 形式的梯度:
∇ θ J ( θ ) = E [ R ( x , y ) ∇ θ log π θ ( y ∣ x ) ] . \nabla_\theta J(\theta)
=
\mathbb E
\left[
R(x,y)
\nabla_\theta\log\pi_\theta(y\mid x)
\right].
∇ θ J ( θ ) = E [ R ( x , y ) ∇ θ log π θ ( y ∣ x ) ] .
展开到 Token:
∇ θ J ( θ ) = E [ ∑ t = 1 T R ( x , y ) ∇ θ log π θ ( y t ∣ x , y < t ) ] . \nabla_\theta J(\theta)
=
\mathbb E
\left[
\sum_{t=1}^{T}
R(x,y)
\nabla_\theta
\log\pi_\theta(y_t\mid x,y_{<t})
\right].
∇ θ J ( θ ) = E [ t = 1 ∑ T R ( x , y ) ∇ θ log π θ ( y t ∣ x , y < t ) ] .
这说明:如果一条回答 Reward 高,就提高其中已采样 Tokens 的概率;如果 Reward 低,就降低这些 Tokens 的概率。
6.2 为什么不直接使用 Reward?
绝对 Reward 没有考虑题目难度。例如两道题分别得到 0.6 0.6 0.6 :
对简单题而言,模型通常能得到 0.9 0.9 0.9 ,那么 0.6 0.6 0.6 其实较差;
对困难题而言,模型通常只能得到 0.1 0.1 0.1 ,那么 0.6 0.6 0.6 已经很好。
因此引入 Baseline:
A t = G t − b ( s t ) . A_t=G_t-b(s_t).
A t = G t − b ( s t ) .
只要 Baseline 不依赖当前采样动作,它不会改变 Policy Gradient 的期望,但可以降低方差。PPO 使用 Learned Critic:
b ( s t ) = V ϕ ( s t ) . b(s_t)=V_\phi(s_t).
b ( s t ) = V ϕ ( s t ) .
6.3 TD Residual 与 GAE
从 Bellman 关系理解 TD Residual
对于当前策略 π \pi π ,真实状态价值满足 Bellman Expectation Equation:
V π ( s t ) = E π [ r t + γ V π ( s t + 1 ) ∣ s t ] . V^\pi(s_t)
=
\mathbb E_\pi
\left[
r_t+\gamma V^\pi(s_{t+1})
\mid s_t
\right].
V π ( s t ) = E π [ r t + γ V π ( s t + 1 ) ∣ s t ] .
也就是说,一个 State 的价值应等于“执行下一步后获得的即时 Reward”与“下一个 State 的折扣价值”之和的期望。
实际训练中并不知道真实的 V π V^\pi V π ,只能让 Critic V ϕ V_\phi V ϕ 对它进行近似。对于一次真实采样到的 Transition:
( s t , a t , r t , s t + 1 ) , (s_t,a_t,r_t,s_{t+1}),
( s t , a t , r t , s t + 1 ) ,
可以用:
r t + γ V ϕ ( s t + 1 ) r_t+\gamma V_\phi(s_{t+1})
r t + γ V ϕ ( s t + 1 )
作为 V ϕ ( s t ) V_\phi(s_t) V ϕ ( s t ) 的一步 Bootstrap Target。这里的 Bootstrap 指:目标中使用了模型自身对下一个 State 的 Value 估计,而不是必须等到整条轨迹结束后再计算完整 Return。
两者之差称为 Temporal Difference Residual(TD 残差):
δ t = r t + γ V ϕ ( s t + 1 ) − V ϕ ( s t ) . \delta_t
=
r_t
+\gamma V_\phi(s_{t+1})
-V_\phi(s_t).
δ t = r t + γ V ϕ ( s t + 1 ) − V ϕ ( s t ) .
其中:
s t s_t s t :生成第 t t t 个 Token 前的 State;
a t a_t a t :在 s t s_t s t 下选择的 Token;
r t r_t r t :执行 a t a_t a t 后得到的即时 Reward;
s t + 1 s_{t+1} s t + 1 :追加该 Token 后的新 State;
V ϕ ( s t ) V_\phi(s_t) V ϕ ( s t ) :Critic 对当前 State Value 的估计;
γ \gamma γ :Discount Factor。
TD Residual 可以理解为一次 Transition 带来的“超出预期程度”:
δ t > 0 \delta_t>0 δ t > 0 :实际得到的“即时 Reward + 下一状态价值”高于 Critic 原本对 s t s_t s t 的估计;
δ t < 0 \delta_t<0 δ t < 0 :这一步之后的结果低于预期;
如果 Critic 已经准确,在给定 s t s_t s t 的条件下,δ t \delta_t δ t 的期望应接近 0 0 0 。
但只用:
A ^ t = δ t \hat A_t=\delta_t
A ^ t = δ t
估计 Advantage 时,只显式观察一步 Transition,虽然方差较低,却会比较依赖 Critic 对 V ϕ ( s t + 1 ) V_\phi(s_{t+1}) V ϕ ( s t + 1 ) 的估计。另一方面,如果直接使用完整 Monte Carlo Return:
A ^ t = G t − V ϕ ( s t ) , \hat A_t=G_t-V_\phi(s_t),
A ^ t = G t − V ϕ ( s t ) ,
就能利用整条轨迹的真实 Reward,但长序列上的方差通常更大,导致梯度估计更容易受单次采样结果影响、训练波动更明显,并且需要更多样本取平均。GAE 的目的就是在这两个极端之间进行折中。
GAE 的定义
Generalized Advantage Estimation(GAE)将当前及未来的 TD Residual 加权求和:
A ^ t G A E = ∑ l = 0 T − t ( γ λ ) l δ t + l , \hat A_t^{\mathrm{GAE}}
=
\sum_{l=0}^{T-t}
(\gamma\lambda)^l\delta_{t+l},
A ^ t GAE = l = 0 ∑ T − t ( γλ ) l δ t + l ,
将前几项展开:
A ^ t G A E = δ t + γ λ δ t + 1 + ( γ λ ) 2 δ t + 2 + ⋯ . \hat A_t^{\mathrm{GAE}}
=
\delta_t
+\gamma\lambda\delta_{t+1}
+(\gamma\lambda)^2\delta_{t+2}
+\cdots.
A ^ t GAE = δ t + γλ δ t + 1 + ( γλ ) 2 δ t + 2 + ⋯ .
其中:
l l l :从当前时间步向后移动的步数;
γ ∈ [ 0 , 1 ] \gamma\in[0,1] γ ∈ [ 0 , 1 ] :控制未来 Reward 的折扣;
λ ∈ [ 0 , 1 ] \lambda\in[0,1] λ ∈ [ 0 , 1 ] :控制未来 TD Residual 参与当前 Advantage 的程度;
T T T :Response 的最后一个生成时间步。
实际实现通常从序列末尾向前递推:
A ^ t = δ t + γ λ A ^ t + 1 . \hat A_t
=
\delta_t
+\gamma\lambda\hat A_{t+1}.
A ^ t = δ t + γλ A ^ t + 1 .
对于 Episode 结束后的 Terminal State,规定:
V ϕ ( s T + 1 ) = 0 , A ^ T + 1 = 0. V_\phi(s_{T+1})=0,
\qquad
\hat A_{T+1}=0.
V ϕ ( s T + 1 ) = 0 , A ^ T + 1 = 0.
因此可以先算最后一个 Token 的 A ^ T \hat A_T A ^ T ,再依次计算 A ^ T − 1 , … , A ^ 1 \hat A_{T-1},\ldots,\hat A_1 A ^ T − 1 , … , A ^ 1 ,时间复杂度为 O ( T ) O(T) O ( T ) ,不需要对每个位置重复计算后续和。
λ \lambda λ 如何控制 Bias-Variance Trade-off?
当 λ = 0 \lambda=0 λ = 0 时:
A ^ t = δ t . \hat A_t=\delta_t.
A ^ t = δ t .
此时只使用一步 TD 信息,方差较低,但高度依赖 Critic 的 Bootstrap 估计,因此 Bias 可能较大。
当 λ \lambda λ 接近 1 1 1 时:
A ^ t ≈ δ t + γ δ t + 1 + γ 2 δ t + 2 + ⋯ . \hat A_t
\approx
\delta_t+\gamma\delta_{t+1}
+\gamma^2\delta_{t+2}+\cdots.
A ^ t ≈ δ t + γ δ t + 1 + γ 2 δ t + 2 + ⋯ .
在完整 Episode 且 Terminal State Value 为 0 0 0 时,中间的 Value 项会发生 Telescope Cancellation,最终近似:
A ^ t ≈ G t − V ϕ ( s t ) . \hat A_t
\approx
G_t-V_\phi(s_t).
A ^ t ≈ G t − V ϕ ( s t ) .
此时更接近 Monte Carlo Advantage,Bias 较小,但会引入更多未来随机性,方差通常更大。
因此,GAE 可以理解为对不同长度的 n n n -step Advantage Estimator 进行指数加权:较小的 λ \lambda λ 更相信短期 Bootstrap,较大的 λ \lambda λ 更相信完整轨迹的实际结果。
Terminal Reward 如何向前传递?
LLM Reasoning 常只有最终 Outcome Reward:
r 1 = ⋯ = r T − 1 = 0 , r T = R ( x , y ) . r_1=\cdots=r_{T-1}=0,
\qquad
r_T=R(x,y).
r 1 = ⋯ = r T − 1 = 0 , r T = R ( x , y ) .
为了看清传播过程,暂时假设 Critic 在所有位置的初始预测都是 0 0 0 ,并取 γ = 1 \gamma=1 γ = 1 。此时:
δ T = R ( x , y ) , δ 1 = ⋯ = δ T − 1 = 0. \delta_T=R(x,y),
\qquad
\delta_1=\cdots=\delta_{T-1}=0.
δ T = R ( x , y ) , δ 1 = ⋯ = δ T − 1 = 0.
通过 GAE 递推:
A ^ t = λ T − t R ( x , y ) . \hat A_t
=
\lambda^{T-t}R(x,y).
A ^ t = λ T − t R ( x , y ) .
因此,最终 Reward 可以沿着 Response 从后向前影响更早的 Tokens;距离结尾越远,信号会经过越多次 λ \lambda λ 衰减。实际训练中 Critic 不为零,每一步的 Advantage 还会受到相邻 State Value 差异的影响。
这解释了 GAE 如何完成数值上的 Credit Assignment,但它并没有获得每一步推理是否正确的直接标签。只有 Terminal Reward 时,PPO 仍然无法真正识别“具体哪一个推理步骤正确或错误”;要获得这种监督,需要 Process Reward 或中间步骤验证器。
Value Target
得到 Advantage 后,Critic 的回归目标可以写成:
G ^ t = A ^ t + V ϕ ( s t ) . \hat G_t
=
\hat A_t+V_\phi(s_t).
G ^ t = A ^ t + V ϕ ( s t ) .
这里 G ^ t \hat G_t G ^ t 是由 GAE 构造的 Value Target。在更新 Critic 时,右侧用于构造 Target 的数值会停止梯度传播,然后最小化:
L v a l u e ( ϕ ) = E t [ ( V ϕ ( s t ) − G ^ t ) 2 ] . L_{\mathrm{value}}(\phi)
=
\mathbb E_t
\left[
\left(
V_\phi(s_t)-\hat G_t
\right)^2
\right].
L value ( ϕ ) = E t [ ( V ϕ ( s t ) − G ^ t ) 2 ] .
Actor 使用 A ^ t \hat A_t A ^ t 判断已采样 Token 应该被提高还是降低概率;Critic 则使用 G ^ t \hat G_t G ^ t 学习更准确的 Value Baseline。二者使用同一批 Rollout,但优化目标和参数彼此不同。
七、PPO:使用 Critic 的稳定 On-policy 更新
PPO(Proximal Policy Optimization)的核心是:利用当前 Rollout 学习,但限制新 Policy 一次不要偏离生成这些数据的 Old Policy 太远。
7.1 Importance Ratio
Rollout 完成后,每个已采样动作都有一个固定的 Advantage A ^ t \hat A_t A ^ t :
A ^ t > 0 \hat A_t>0 A ^ t > 0 :这个动作比预期好,应提高它的概率;
A ^ t < 0 \hat A_t<0 A ^ t < 0 :这个动作比预期差,应降低它的概率。
Advantage 只负责评价动作好坏,本身不包含待更新参数 θ \theta θ 。要更新 Policy,需要让动作的概率参与目标。对于一个状态 s s s ,希望最大化 Current Policy 下的期望 Advantage:
J s ( θ ) = ∑ a π θ ( a ∣ s ) A ^ o l d ( s , a ) . J_s(\theta)
=
\sum_a
\pi_\theta(a\mid s)
\hat A^{\mathrm{old}}(s,a).
J s ( θ ) = a ∑ π θ ( a ∣ s ) A ^ old ( s , a ) .
这个式子就是“动作被 Current Policy 选中的概率 × 动作的好坏”。最大化它时,正 Advantage 动作的概率会提高,负 Advantage 动作的概率会降低。
但现有样本不是由 Current Policy 生成的,而是来自 Old Policy。为了使用这批旧样本,将上式改写为:
∑ a π θ ( a ∣ s ) A ^ o l d ( s , a ) = ∑ a π θ o l d ( a ∣ s ) π θ ( a ∣ s ) π θ o l d ( a ∣ s ) A ^ o l d ( s , a ) . \sum_a\pi_\theta(a\mid s)\hat A^{\mathrm{old}}(s,a)
=
\sum_a\pi_{\theta_{\mathrm{old}}}(a\mid s)
\frac{\pi_\theta(a\mid s)}
{\pi_{\theta_{\mathrm{old}}}(a\mid s)}
\hat A^{\mathrm{old}}(s,a).
a ∑ π θ ( a ∣ s ) A ^ old ( s , a ) = a ∑ π θ old ( a ∣ s ) π θ old ( a ∣ s ) π θ ( a ∣ s ) A ^ old ( s , a ) .
因此定义概率比:
ρ t ( θ ) = π θ ( a t ∣ s t ) π θ o l d ( a t ∣ s t ) . \rho_t(\theta)
=
\frac{
\pi_\theta(a_t\mid s_t)
}{
\pi_{\theta_{\mathrm{old}}}(a_t\mid s_t)
}.
ρ t ( θ ) = π θ old ( a t ∣ s t ) π θ ( a t ∣ s t ) .
于是可以使用 Old Policy 的样本估计 Current Policy 的目标:
J s u r r o g a t e ( θ ) = E ( s t , a t ) ∼ π θ o l d [ ρ t ( θ ) A ^ t ] . J_{\mathrm{surrogate}}(\theta)
=
\mathbb E_{(s_t,a_t)\sim\pi_{\theta_{\mathrm{old}}}}
\left[
\rho_t(\theta)\hat A_t
\right].
J surrogate ( θ ) = E ( s t , a t ) ∼ π θ old [ ρ t ( θ ) A ^ t ] .
这里 ρ t \rho_t ρ t 与 A ^ t \hat A_t A ^ t 的关系很简单:
ρ t = 1 \rho_t=1 ρ t = 1 :新旧 Policy 给该 Token 的概率相同;
ρ t > 1 \rho_t>1 ρ t > 1 :新 Policy 提高了该 Token 的概率;
ρ t < 1 \rho_t<1 ρ t < 1 :新 Policy 降低了该 Token 的概率。
当 A ^ t > 0 \hat A_t>0 A ^ t > 0 时,最大化 ρ t A ^ t \rho_t\hat A_t ρ t A ^ t 会推动 ρ t \rho_t ρ t 增大,也就是提高该 Token 的概率;
当 A ^ t < 0 \hat A_t<0 A ^ t < 0 时,ρ t A ^ t \rho_t\hat A_t ρ t A ^ t 是负数。减小 ρ t \rho_t ρ t 会让它更接近 0 0 0 、从而使目标变大,也就是降低该 Token 的概率。
所以 ρ t A ^ t \rho_t\hat A_t ρ t A ^ t 不是“概率已经提高后,再把更新幅度放大”,而是把 Current Policy 对该动作的相对概率与动作好坏结合起来,形成一个可以对 θ \theta θ 优化的目标。
7.2 PPO Clipped Objective
问题是,如果一直最大化 ρ t A ^ t \rho_t\hat A_t ρ t A ^ t :
正 Advantage 会持续推动 ρ t \rho_t ρ t 增大;
负 Advantage 会持续推动 ρ t \rho_t ρ t 减小。
而 A ^ t \hat A_t A ^ t 只是根据一批 Old Policy Rollout 得到的估计,可能有噪声。不能因为一次判断就把 Token 概率改得过多,所以 PPO 只允许这批数据推动 Policy 变化一小段范围。
PPO 的目标为:
J P P O ( θ ) = E t [ min ( ρ t ( θ ) A ^ t , clip ( ρ t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] . J_{\mathrm{PPO}}(\theta)
=
\mathbb E_t
\left[
\min
\left(
\rho_t(\theta)\hat A_t,\,
\operatorname{clip}
(\rho_t(\theta),1-\epsilon,1+\epsilon)\hat A_t
\right)
\right].
J PPO ( θ ) = E t [ min ( ρ t ( θ ) A ^ t , clip ( ρ t ( θ ) , 1 − ϵ , 1 + ϵ ) A ^ t ) ] .
其中:
ϵ > 0 \epsilon>0 ϵ > 0 控制允许的相对概率变化范围;
clip ( ρ t , 1 − ϵ , 1 + ϵ ) \operatorname{clip}(\rho_t,1-\epsilon,1+\epsilon) clip ( ρ t , 1 − ϵ , 1 + ϵ ) 将 ρ t \rho_t ρ t 截到该区间内;
min 在未裁剪项和裁剪项中选择更小、更保守的一个。
现在重新理解“提高到 Old Policy 的 1 + ϵ 1+\epsilon 1 + ϵ 倍”。因为:
π θ ( a t ∣ s t ) = ρ t π θ o l d ( a t ∣ s t ) , \pi_\theta(a_t\mid s_t)
=
\rho_t\,
\pi_{\theta_{\mathrm{old}}}(a_t\mid s_t),
π θ ( a t ∣ s t ) = ρ t π θ old ( a t ∣ s t ) ,
所以 ρ t = 1 + ϵ \rho_t=1+\epsilon ρ t = 1 + ϵ 表示:在当前这轮参数更新中,该 Token 的概率已经变成旧概率的 1 + ϵ 1+\epsilon 1 + ϵ 倍。
若 A ^ t > 0 \hat A_t>0 A ^ t > 0 ,提高概率是正确方向;但当 ρ t > 1 + ϵ \rho_t>1+\epsilon ρ t > 1 + ϵ 后,裁剪项保持不变,这个样本不再奖励继续提高概率;
若 A ^ t < 0 \hat A_t<0 A ^ t < 0 ,降低概率是正确方向;但当 ρ t < 1 − ϵ \rho_t<1-\epsilon ρ t < 1 − ϵ 后,裁剪项保持不变,这个样本不再奖励继续降低概率。
注意,这不是把概率硬性锁在边界上,而只是让当前样本在越过边界后不再提供更多收益。
min 用来处理错误方向。PPO 在最大化目标,因此取较小值意味着:
如果 Policy 朝正确方向走得过远,选择裁剪项,停止额外奖励;
如果 Policy 朝错误方向变化,选择更差的未裁剪项,仍然保留纠正它的梯度。
因此,PPO Clipping 的作用可以概括为:限制一次更新从旧 Rollout 中获得的收益,但不掩盖错误方向带来的损失。
7.3 Critic Loss
Critic 回归 Value Target:
L v a l u e ( ϕ ) = E t [ ( V ϕ ( s t ) − G ^ t ) 2 ] . L_{\mathrm{value}}(\phi)
=
\mathbb E_t
\left[
\left(
V_\phi(s_t)-\hat G_t
\right)^2
\right].
L value ( ϕ ) = E t [ ( V ϕ ( s t ) − G ^ t ) 2 ] .
实际 PPO 常对 Value Update 也进行 Clipping,避免 Critic 一次变化过大。
7.4 Entropy Bonus 与 KL
Policy Entropy 为:
H ( π θ ( ⋅ ∣ s t ) ) = − ∑ a ∈ V π θ ( a ∣ s t ) log π θ ( a ∣ s t ) . \mathcal H(\pi_\theta(\cdot\mid s_t))
=
-
\sum_{a\in\mathcal V}
\pi_\theta(a\mid s_t)
\log\pi_\theta(a\mid s_t).
H ( π θ ( ⋅ ∣ s t )) = − a ∈ V ∑ π θ ( a ∣ s t ) log π θ ( a ∣ s t ) .
Entropy 高表示分布更分散、探索更强;Entropy 过低可能发生 Entropy Collapse,模型过早变得确定。
PPO/RLHF 还经常加入相对 Reference Policy 的 KL 惩罚:
J ( θ ) = E [ R ] − β D K L ( π θ ∥ π r e f ) , J(\theta)
=
\mathbb E[R]
-
\beta
D_{\mathrm{KL}}
(\pi_\theta\Vert\pi_{\mathrm{ref}}),
J ( θ ) = E [ R ] − β D KL ( π θ ∥ π ref ) ,
其中 β ≥ 0 \beta\ge0 β ≥ 0 是 KL Coefficient。它是可选设计,并非所有 PPO 训练都必须加载 Reference Model。
7.5 PPO 的完整训练过程
一次 PPO Iteration 可以分为四个阶段。记 Rollout Batch 中有 B B B 个 Prompt,第 b b b 个 Prompt 为 x b x_b x b ;用 m b , t ∈ { 0 , 1 } m_{b,t}\in\{0,1\} m b , t ∈ { 0 , 1 } 标记第 t t t 个位置是否为有效 Response Token。
阶段一:采集 Rollout
先固定当前 Actor 的参数快照 θ o l d \theta_{\mathrm{old}} θ old ,再从 Prompt Dataset 采样:
x 1 , … , x B ∼ D p r o m p t . x_1,\ldots,x_B\sim D_{\mathrm{prompt}}.
x 1 , … , x B ∼ D prompt .
Old Policy 为每个 Prompt 生成一条 Response:
y b = ( y b , 1 , … , y b , T b ) ∼ π θ o l d ( ⋅ ∣ x b ) . y_b=(y_{b,1},\ldots,y_{b,T_b})
\sim
\pi_{\theta_{\mathrm{old}}}(\cdot\mid x_b).
y b = ( y b , 1 , … , y b , T b ) ∼ π θ old ( ⋅ ∣ x b ) .
生成过程中需要保存每个已采样 Token 的 Old Log Probability:
ℓ b , t o l d = log π θ o l d ( y b , t ∣ x b , y b , < t ) . \ell_{b,t}^{\mathrm{old}}
=
\log\pi_{\theta_{\mathrm{old}}}
(y_{b,t}\mid x_b,y_{b,<t}).
ℓ b , t old = log π θ old ( y b , t ∣ x b , y b , < t ) .
这一阶段最终得到 Prompt、Response、Response Mask 和 Old Log Probability。之后即使 Actor 参数发生变化,ℓ b , t o l d \ell_{b,t}^{\mathrm{old}} ℓ b , t old 也保持不变,因为它记录的是“生成这批数据时的 Policy”。
阶段二:为 Rollout 构造训练信号
Reward Function 或 Reward Model 先给完整 Response 打分:
R b = R ( x b , y b ) . R_b=R(x_b,y_b).
R b = R ( x b , y b ) .
如果使用 Terminal Outcome Reward,则只在最后一个有效 Token 放置 R b R_b R b ,前面位置的即时 Reward 为 0 0 0 。
接着,更新前的 Critic 对每个状态:
s b , t = ( x b , y b , < t ) s_{b,t}=(x_b,y_{b,<t})
s b , t = ( x b , y b , < t )
预测 Value。记这次固定预测为:
v b , t = V ϕ o l d ( s b , t ) . v_{b,t}=V_{\phi_{\mathrm{old}}}(s_{b,t}).
v b , t = V ϕ old ( s b , t ) .
利用 Reward 和 v b , t v_{b,t} v b , t 计算:
δ b , t = r b , t + γ v b , t + 1 − v b , t , \delta_{b,t}
=
r_{b,t}
+\gamma v_{b,t+1}
-v_{b,t},
δ b , t = r b , t + γ v b , t + 1 − v b , t ,
再从 Response 末尾向前递推:
A ^ b , t = δ b , t + γ λ A ^ b , t + 1 . \hat A_{b,t}
=
\delta_{b,t}
+\gamma\lambda\hat A_{b,t+1}.
A ^ b , t = δ b , t + γλ A ^ b , t + 1 .
最后构造 Critic 的监督目标:
G ^ b , t = A ^ b , t + v b , t . \hat G_{b,t}
=
\hat A_{b,t}+v_{b,t}.
G ^ b , t = A ^ b , t + v b , t .
至此,一条用于 PPO 更新的训练样本包含:
输入:x b x_b x b 与 y b y_b y b ;
Rollout 时的记录:ℓ b , t o l d \ell_{b,t}^{\mathrm{old}} ℓ b , t old ;
Actor 的监督信号:A ^ b , t \hat A_{b,t} A ^ b , t ;
Critic 的监督信号:G ^ b , t \hat G_{b,t} G ^ b , t ;
用于忽略 Padding 的 m b , t m_{b,t} m b , t 。
这些量在随后使用这批 Rollout 训练时全部固定,不参与梯度传播。
阶段三:使用 Mini Batch 更新 Actor 和 Critic
将 B B B 条轨迹 Shuffle 后拆成 Mini Batches。对于每个 Mini Batch,Current Actor 使用最新参数 θ \theta θ 重新计算同一批 Tokens 的 Log Probability:
ℓ b , t n e w = log π θ ( y b , t ∣ x b , y b , < t ) . \ell_{b,t}^{\mathrm{new}}
=
\log\pi_\theta
(y_{b,t}\mid x_b,y_{b,<t}).
ℓ b , t new = log π θ ( y b , t ∣ x b , y b , < t ) .
因此 Importance Ratio 为:
ρ b , t ( θ ) = exp ( ℓ b , t n e w − ℓ b , t o l d ) . \rho_{b,t}(\theta)
=
\exp\left(
\ell_{b,t}^{\mathrm{new}}
-\ell_{b,t}^{\mathrm{old}}
\right).
ρ b , t ( θ ) = exp ( ℓ b , t new − ℓ b , t old ) .
Actor 最小化 PPO Loss:
L a c t o r ( θ ) = − 1 ∑ b , t m b , t ∑ b , t m b , t min ( ρ b , t A ^ b , t , clip ( ρ b , t , 1 − ϵ , 1 + ϵ ) A ^ b , t ) . \begin{aligned}
L_{\mathrm{actor}}(\theta)
=-
\frac{1}{\sum_{b,t}m_{b,t}}
\sum_{b,t}m_{b,t}
\min\Big(
&\rho_{b,t}\hat A_{b,t},\\
&\operatorname{clip}
(\rho_{b,t},1-\epsilon,1+\epsilon)
\hat A_{b,t}
\Big).
\end{aligned}
L actor ( θ ) = − ∑ b , t m b , t 1 b , t ∑ m b , t min ( ρ b , t A ^ b , t , clip ( ρ b , t , 1 − ϵ , 1 + ϵ ) A ^ b , t ) .
Current Critic 使用最新参数 ϕ \phi ϕ 重新预测 V ϕ ( s b , t ) V_\phi(s_{b,t}) V ϕ ( s b , t ) ,并拟合阶段二中固定的 Value Target:
L v a l u e ( ϕ ) = ∑ b , t m b , t ( V ϕ ( s b , t ) − G ^ b , t ) 2 ∑ b , t m b , t . L_{\mathrm{value}}(\phi)
=
\frac{
\sum_{b,t}m_{b,t}
\left(
V_\phi(s_{b,t})-\hat G_{b,t}
\right)^2
}{
\sum_{b,t}m_{b,t}
}.
L value ( ϕ ) = ∑ b , t m b , t ∑ b , t m b , t ( V ϕ ( s b , t ) − G ^ b , t ) 2 .
Actor 与 Critic 使用同一批轨迹,但分别计算 Loss、反向传播,并由各自的 Optimizer 更新参数。
阶段四:同步 Policy,开始下一轮
一批 Rollout 可以被 Shuffle 并训练一个或多个 PPO Epochs。完成预定更新后,丢弃这批 Rollout,并将更新后的 Actor 作为下一轮 Behavior Policy:
θ o l d ← θ . \theta_{\mathrm{old}}\leftarrow\theta.
θ old ← θ .
然后重新采样 Prompt、生成 Response,并重复上述过程。旧 Rollout 不能无限复用,因为 Current Policy 与 Old Policy 的差距会越来越大,数据也会越来越 Off-policy。
7.6 PPO 核心与面试手写代码
PPO 面试中最常见的手写内容包括:
Masked Mean 与 Advantage Whitening;
GAE 反向递推;
从 Causal LM Logits 中取出实际 Token 的 Log Probability;
Importance Ratio 与 PPO Clipped Loss。
下面将这些代码集中在一起,并省略文本生成和分布式训练。开始计算前,已经获得:
阶段一保存的 old_log_probs 和 response_mask;
阶段二得到的 rewards 和旧 Critic values。
所有 Tensor 的形状均为 ( B , T ) (B,T) ( B , T ) ,其中 B B B 是轨迹数量,T T T 是 Padding 后的 Response 长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 import torchdef masked_mean (x, mask ): """忽略 Padding,只对有效 Response Tokens 求平均。""" return (x * mask).sum () / mask.sum ().clamp_min(1.0 )@torch.no_grad() def compute_gae (rewards, values, response_mask, gamma=1.0 , lam=0.95 ): """ 对应阶段二:由 Reward 和旧 Critic Value 计算 GAE 与 Value Target。 rewards: 每个 Token 的即时 Reward 只有 Outcome Reward 时,通常仅最后一个有效位置非零 values: 阶段二中旧 Critic 给出的固定 V(s_t) response_mask: 有效 Response Token 为 1,Padding 为 0 返回: advantages: Actor 更新使用的 A_hat_t returns: Critic 回归使用的 G_hat_t """ batch_size, seq_len = rewards.shape advantages = torch.zeros_like(rewards) last_advantage = torch.zeros( batch_size, device=rewards.device, dtype=rewards.dtype ) for t in reversed (range (seq_len)): if t == seq_len - 1 : next_value = torch.zeros_like(last_advantage) next_valid = torch.zeros_like(last_advantage) else : next_value = values[:, t + 1 ] next_valid = response_mask[:, t + 1 ] delta = ( rewards[:, t] + gamma * next_value * next_valid - values[:, t] ) last_advantage = ( delta + gamma * lam * next_valid * last_advantage ) advantages[:, t] = last_advantage advantages = advantages * response_mask returns = (advantages + values) * response_mask return advantages, returnsdef ppo_losses ( new_log_probs, old_log_probs, advantages, new_values, returns, response_mask, clip_eps=0.2 , ): """ 对应阶段三:计算一个 Mini Batch 的 Actor/Critic Loss。 new_log_probs: Actor 当前参数重新计算的 Log Probability old_log_probs: 阶段一保存的 Old Policy Log Probability advantages: 阶段二得到的 A_hat_t new_values: Critic 当前参数重新计算的 V(s_t) returns: 阶段二得到的固定 Value Target G_hat_t """ old_log_probs = old_log_probs.detach() advantages = advantages.detach() returns = returns.detach() ratio = torch.exp(new_log_probs - old_log_probs) surrogate_1 = ratio * advantages surrogate_2 = torch.clamp( ratio, 1.0 - clip_eps, 1.0 + clip_eps ) * advantages actor_objective = torch.minimum(surrogate_1, surrogate_2) actor_loss = -masked_mean(actor_objective, response_mask) value_error = (new_values - returns) ** 2 critic_loss = masked_mean(value_error, response_mask) return actor_loss, critic_loss
阶段三先将 Rollout Batch 切成 Mini Batches。对于每个 Mini Batch,Actor 重新计算 new_log_probs,Critic 重新计算 new_values,然后分别更新:
1 2 3 4 5 6 7 8 9 actor_optimizer.zero_grad() actor_loss.backward() actor_optimizer.step() critic_optimizer.zero_grad() critic_loss.backward() critic_optimizer.step()
这段代码只展示 PPO 的核心数学结构。完整实现还通常包含 Advantage Whitening、Value Clipping、Entropy Bonus、KL Penalty、Gradient Clipping 和分布式 Mask 处理。
从 Logits 取出实际 Token 的 Log Probability
Causal LM 在第 t t t 个位置输出的 Logits 用于预测第 t + 1 t+1 t + 1 个 Token。PPO 不需要保存整个 Vocabulary 的概率,只需要取出轨迹中实际 Token 对应的 Log Probability:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import torch.nn.functional as Fdef get_token_log_probs (logits, input_ids ): """ logits: (batch_size, seq_len, vocab_size) input_ids: (batch_size, seq_len) 返回每个实际 Next Token 的 Log Probability, 形状为 (batch_size, seq_len - 1)。 """ next_token_logits = logits[:, :-1 , :] next_tokens = input_ids[:, 1 :] log_probs = F.log_softmax(next_token_logits, dim=-1 ) selected_log_probs = torch.gather( log_probs, dim=-1 , index=next_tokens.unsqueeze(-1 ), ) return selected_log_probs.squeeze(-1 )
对于 Prompt–Response 拼接序列,再从结果中切出 Response 对应的位置,就得到 PPO 使用的 Token-level Log Probability。
带 Mask 的 Advantage Whitening
不同 Batch 的 Advantage 尺度可能不同,通常会在有效 Response Tokens 上做归一化:
1 2 3 4 5 6 7 8 9 def masked_whiten (x, mask, eps=1e-8 ): """ 只使用有效 Response Tokens 计算均值和方差, Padding 位置不参与统计,输出也保持为 0。 """ mean = masked_mean(x, mask) variance = masked_mean((x - mean) ** 2 , mask) whitened = (x - mean) / torch.sqrt(variance + eps) return whitened * mask
因此,PPO 常见的手写重点可以归纳为:
根据 Reward 和 Value 反向递推 GAE;
从 Causal LM Logits 中 Gather 实际 Token 的 Log Probability;
使用 exp(new_log_prob - old_log_prob) 计算 Importance Ratio;
实现 PPO 的 minimum + clip Actor Loss;
使用 Mask 排除 Prompt 和 Padding;
对有效 Tokens 做 Advantage Whitening。
7.7 PPO 的优势与局限
优势:
Learned Critic 可以跨 Prompt 和跨样本学习 Value Baseline;
GAE 能提供随 Token/State 变化的 Advantage;
Clipping 能控制更新幅度,工程上相对稳定;
同一批 Rollout 可以通过 Mini Batch 和多个 PPO Epoch 重复利用。
局限:
需要额外训练 Critic,显存和计算成本较高;
Actor、Critic、Old Log Prob、Reference Model 之间的数据流复杂;
Critic 训练不好时,Advantage 估计反而可能产生偏差;
On-policy 数据很快过期,不能无限重复使用旧 Rollout;
Rollout 往往比梯度更新更耗时。
八、GRPO:用组内相对 Reward 代替 Critic
GRPO(Group Relative Policy Optimization)最关键的变化是:删除 Critic,使用同一 Prompt 下多个 Response 的相对 Reward 作为 Baseline。
8.1 Group Rollout
对一个 Prompt x x x ,Old Policy 生成 G G G 条回答:
y 1 , y 2 , … , y G ∼ π θ o l d ( ⋅ ∣ x ) . y_1,y_2,\ldots,y_G
\sim
\pi_{\theta_{\mathrm{old}}}(\cdot\mid x).
y 1 , y 2 , … , y G ∼ π θ old ( ⋅ ∣ x ) .
得到对应 Reward:
R 1 , R 2 , … , R G . R_1,R_2,\ldots,R_G.
R 1 , R 2 , … , R G .
计算组内均值和标准差:
R ˉ = 1 G ∑ i = 1 G R i , \bar R
=
\frac{1}{G}
\sum_{i=1}^{G}R_i,
R ˉ = G 1 i = 1 ∑ G R i ,
σ R = 1 G ∑ i = 1 G ( R i − R ˉ ) 2 . \sigma_R
=
\sqrt{
\frac{1}{G}
\sum_{i=1}^{G}
(R_i-\bar R)^2
}.
σ R = G 1 i = 1 ∑ G ( R i − R ˉ ) 2 .
第 i i i 条 Response 的 Group-relative Advantage 为:
A ^ i = R i − R ˉ σ R + ε n u m , \hat A_i
=
\frac{
R_i-\bar R
}{
\sigma_R+\varepsilon_{\mathrm{num}}
},
A ^ i = σ R + ε num R i − R ˉ ,
其中 ε n u m \varepsilon_{\mathrm{num}} ε num 是防止除零的数值稳定项。通常同一 Response 内的所有 Tokens 共享同一个 A ^ i \hat A_i A ^ i :
A ^ i , t = A ^ i . \hat A_{i,t}=\hat A_i.
A ^ i , t = A ^ i .
8.2 GRPO Objective
定义第 i i i 条 Response 中第 t t t 个 Token 的 Importance Ratio:
ρ i , t ( θ ) = π θ ( y i , t ∣ x , y i , < t ) π θ o l d ( y i , t ∣ x , y i , < t ) . \rho_{i,t}(\theta)
=
\frac{
\pi_\theta(y_{i,t}\mid x,y_{i,<t})
}{
\pi_{\theta_{\mathrm{old}}}(y_{i,t}\mid x,y_{i,<t})
}.
ρ i , t ( θ ) = π θ old ( y i , t ∣ x , y i , < t ) π θ ( y i , t ∣ x , y i , < t ) .
经典 GRPO 的 Sample-level Objective 可以写为:
J G R P O ( θ ) = E [ 1 G ∑ i = 1 G 1 ∣ y i ∣ ∑ t = 1 ∣ y i ∣ min ( ρ i , t A ^ i , clip ( ρ i , t , 1 − ϵ , 1 + ϵ ) A ^ i ) − β D K L ] . \begin{aligned}
J_{\mathrm{GRPO}}(\theta)
=
\mathbb E
\Bigg[
&\frac{1}{G}
\sum_{i=1}^{G}
\frac{1}{|y_i|}
\sum_{t=1}^{|y_i|}
\min\Big(
\rho_{i,t}\hat A_i,\\
&\operatorname{clip}
(\rho_{i,t},1-\epsilon,1+\epsilon)\hat A_i
\Big)
-
\beta D_{\mathrm{KL}}
\Bigg].
\end{aligned}
J GRPO ( θ ) = E [ G 1 i = 1 ∑ G ∣ y i ∣ 1 t = 1 ∑ ∣ y i ∣ min ( ρ i , t A ^ i , clip ( ρ i , t , 1 − ϵ , 1 + ϵ ) A ^ i ) − β D KL ] .
这里:
∣ y i ∣ |y_i| ∣ y i ∣ 是第 i i i 条 Response 的 Token 数;
先对每条 Response 内部的 Tokens 求平均,再对 G G G 条 Response 求平均;
KL 项是否启用取决于具体实现;很多 Reasoning RL 实现会取 β = 0 \beta=0 β = 0 。
8.3 GRPO 的完整训练过程
记一个 Rollout Batch 包含 B B B 个 Prompt,每个 Prompt 生成 G G G 条 Response。一次 GRPO Iteration 包括:
采样 Prompt
x 1 , … , x B ∼ D p r o m p t . x_1,\ldots,x_B\sim D_{\mathrm{prompt}}.
x 1 , … , x B ∼ D prompt .
为每个 Prompt 生成一组 Response
对第 b b b 个 Prompt:
y b , 1 , … , y b , G ∼ π θ o l d ( ⋅ ∣ x b ) . y_{b,1},\ldots,y_{b,G}
\sim
\pi_{\theta_{\mathrm{old}}}(\cdot\mid x_b).
y b , 1 , … , y b , G ∼ π θ old ( ⋅ ∣ x b ) .
Rollout 时保存每个 Token 的 Old Log Probability。与 PPO 不同,GRPO 需要同一 Prompt 的多条回答,以构造组内 Baseline。
计算每条 Response 的 Reward
R b , i = R ( x b , y b , i ) , i = 1 , … , G . R_{b,i}
=
R(x_b,y_{b,i}),
\qquad
i=1,\ldots,G.
R b , i = R ( x b , y b , i ) , i = 1 , … , G .
在每个 Prompt 的组内计算 Advantage
先计算第 b b b 组 Reward 的均值和标准差:
R ˉ b = 1 G ∑ i = 1 G R b , i , \bar R_b
=
\frac{1}{G}
\sum_{i=1}^{G}R_{b,i},
R ˉ b = G 1 i = 1 ∑ G R b , i ,
σ b = 1 G ∑ i = 1 G ( R b , i − R ˉ b ) 2 . \sigma_b
=
\sqrt{
\frac{1}{G}
\sum_{i=1}^{G}
(R_{b,i}-\bar R_b)^2
}.
σ b = G 1 i = 1 ∑ G ( R b , i − R ˉ b ) 2 .
再得到第 i i i 条 Response 的相对 Advantage:
A ^ b , i = R b , i − R ˉ b σ b + ε n u m . \hat A_{b,i}
=
\frac{
R_{b,i}-\bar R_b
}{
\sigma_b+\varepsilon_{\mathrm{num}}
}.
A ^ b , i = σ b + ε num R b , i − R ˉ b .
同一 Response 中的 Tokens 通常共享该值:
A ^ b , i , t = A ^ b , i . \hat A_{b,i,t}=\hat A_{b,i}.
A ^ b , i , t = A ^ b , i .
GRPO 在这里不调用 Critic,也不计算 GAE 或 Value Target。
计算 Current Policy 的 Probability Ratio
对 Response y b , i y_{b,i} y b , i 中第 t t t 个 Token:
ρ b , i , t ( θ ) = π θ ( y b , i , t ∣ x b , y b , i , < t ) π θ o l d ( y b , i , t ∣ x b , y b , i , < t ) . \rho_{b,i,t}(\theta)
=
\frac{
\pi_\theta(y_{b,i,t}\mid x_b,y_{b,i,<t})
}{
\pi_{\theta_{\mathrm{old}}}(y_{b,i,t}\mid x_b,y_{b,i,<t})
}.
ρ b , i , t ( θ ) = π θ old ( y b , i , t ∣ x b , y b , i , < t ) π θ ( y b , i , t ∣ x b , y b , i , < t ) .
更新 Actor
将 B × G B\times G B × G 条 Response Shuffle 并切成 Mini Batches。训练时最小化:
L G R P O ( θ ) = − J G R P O ( θ ) , L_{\mathrm{GRPO}}(\theta)
=
-J_{\mathrm{GRPO}}(\theta),
L GRPO ( θ ) = − J GRPO ( θ ) ,
其中 J G R P O J_{\mathrm{GRPO}} J GRPO 是上一节给出的 Clipped Objective。如果启用了 Reference Policy,还会同时计算 KL Penalty;若 β = 0 \beta=0 β = 0 ,则不需要该项。
同步 Policy 并重新 Rollout
完成当前数据上的一个或多个更新 Epoch 后:
θ o l d ← θ , \theta_{\mathrm{old}}\leftarrow\theta,
θ old ← θ ,
再使用新 Policy 生成下一批 Group Rollout。
整个过程与 PPO 最本质的区别发生在第四步:
PPO:Critic + GAE GRPO:Group Reward Normalization . \text{PPO:Critic + GAE}
\qquad
\text{GRPO:Group Reward Normalization}.
PPO : Critic + GAE GRPO : Group Reward Normalization .
如果同一组内所有 Reward 都相同,则 R b , i − R ˉ b = 0 R_{b,i}-\bar R_b=0 R b , i − R ˉ b = 0 ,该 Prompt 的所有 Advantage 都为 0 0 0 ,不会产生有效的 Policy Gradient。
8.4 GRPO 为什么不需要 Critic?
PPO 的 Baseline 来自 Learned Value:
A ^ t ≈ G t − V ϕ ( s t ) . \hat A_t\approx G_t-V_\phi(s_t).
A ^ t ≈ G t − V ϕ ( s t ) .
GRPO 的 Baseline 来自同一 Prompt 下多个回答的平均 Reward:
A ^ i ≈ R i − R ˉ . \hat A_i
\approx
R_i-\bar R.
A ^ i ≈ R i − R ˉ .
因此 GRPO 用更多 Rollout 计算换掉了 Critic:
PPO:Learned Critic Baseline GRPO:Group-relative Baseline \boxed{
\text{PPO:Learned Critic Baseline}
\qquad
\text{GRPO:Group-relative Baseline}
}
PPO : Learned Critic Baseline GRPO : Group-relative Baseline
8.5 GRPO 的优势
不需要训练 Critic,减少模型状态、Value Forward 和 Value Update 的开销;
同一 Prompt 内做相对比较,能一定程度上抵消题目难度差异;
很适合具有可验证 Outcome Reward 的数学和代码任务;
Pipeline 比 PPO 更简单。
8.6 GRPO 的问题
全对或全错时没有有效信号
如果:
R 1 = R 2 = ⋯ = R G , R_1=R_2=\cdots=R_G,
R 1 = R 2 = ⋯ = R G ,
则:
R i − R ˉ = 0 ⇒ A ^ i = 0. R_i-\bar R=0
\quad\Rightarrow\quad
\hat A_i=0.
R i − R ˉ = 0 ⇒ A ^ i = 0.
简单题全部正确、难题全部错误时,这一组 Rollout 几乎不产生 Policy Gradient。
Group Size 有限导致 Baseline 噪声
G G G 较小时,R ˉ \bar R R ˉ 和 σ R \sigma_R σ R 只是基于少量样本的局部估计。多任务、难度跨度较大的数据中,Group Baseline 可能具有较高方差。
Rollout 成本增加
为了得到组内比较,每个 Prompt 必须生成多条 Response。Critic 成本下降了,但生成成本增加了。
Response-level Advantage 不等于过程监督
同一 Response 内所有 Tokens 常共享一个 Advantage。模型知道整条回答相对较好或较差,但不知道具体哪一步推理正确。
九、DAPO:面向 Long-CoT 的 GRPO 改进
DAPO(Decoupled Clip and Dynamic sAmpling Policy Optimization)建立在 Group-relative Advantage 和 Rule-based Outcome Reward 之上。它针对大规模 Long Chain-of-Thought RL 中的训练稳定性和效率问题,引入四项关键技术:
Clip-Higher + Dynamic Sampling + Token-level Loss + Overlong Reward Shaping \boxed{
\text{Clip-Higher}
+
\text{Dynamic Sampling}
+
\text{Token-level Loss}
+
\text{Overlong Reward Shaping}
}
Clip-Higher + Dynamic Sampling + Token-level Loss + Overlong Reward Shaping
DAPO 原始方案还移除了 KL 项,允许 Reasoning Policy 更自由地偏离初始模型。
9.1 Clip-Higher:解耦上下 Clip Bound
普通 PPO/GRPO 使用对称区间:
[ 1 − ϵ , 1 + ϵ ] . [1-\epsilon,1+\epsilon].
[ 1 − ϵ , 1 + ϵ ] .
DAPO 将其改为:
[ 1 − ϵ l o w , 1 + ϵ h i g h ] , [1-\epsilon_{\mathrm{low}},
1+\epsilon_{\mathrm{high}}],
[ 1 − ϵ low , 1 + ϵ high ] ,
并设置:
ϵ h i g h > ϵ l o w . \epsilon_{\mathrm{high}}>
\epsilon_{\mathrm{low}}.
ϵ high > ϵ low .
对应目标中的裁剪项为:
clip ( ρ i , t , 1 − ϵ l o w , 1 + ϵ h i g h ) . \operatorname{clip}
\left(
\rho_{i,t},
1-\epsilon_{\mathrm{low}},
1+\epsilon_{\mathrm{high}}
\right).
clip ( ρ i , t , 1 − ϵ low , 1 + ϵ high ) .
为什么上界需要更大?假设一个低概率探索 Token 的旧概率只有 0.01 0.01 0.01 。若 ϵ = 0.2 \epsilon=0.2 ϵ = 0.2 ,一次更新中可获得目标收益的概率上界只有:
0.01 ( 1 + 0.2 ) = 0.012. 0.01(1+0.2)=0.012.
0.01 ( 1 + 0.2 ) = 0.012.
它很难获得明显提升。提高 ϵ h i g h \epsilon_{\mathrm{high}} ϵ high 可以给正 Advantage、低概率的探索 Tokens 更大的上升空间,缓解 Entropy Collapse。
下界仍保持较保守,是为了避免某些 Tokens 的概率被迅速压到接近零,造成探索空间收缩。
9.2 Dynamic Sampling:只保留有组内差异的 Prompt
对于二值正确性 Reward:
一组全部正确:组内 Advantage 全为 0 0 0 ;
一组全部错误:组内 Advantage也全为 0 0 0 ;
只有同时存在正确与错误 Response 时,Group-relative Advantage 才有区分度。
DAPO 在生成阶段 Oversample,并过滤满足以下情况的 Prompt Group:
# { correct responses } = 0 \#\{\text{correct responses}\}=0
# { correct responses } = 0
或:
# { correct responses } = G . \#\{\text{correct responses}\}=G.
# { correct responses } = G .
直到 Batch 中填满:
0 < # { correct responses } < G 0<
\#\{\text{correct responses}\}
<G
0 < # { correct responses } < G
的有效 Prompt。
这里的 Dynamic 指每轮为了凑齐有效训练 Batch,实际需要生成的 Prompt 数量会变化 ,不是根据任务 Reward 在线调整多任务数据比例。
它提高了梯度更新中的有效样本比例,但不能凭空解决极难任务:如果模型对某一类题几乎永远答错,可能需要大量 Oversampling,甚至根本无法填满有效 Batch。这时仍需要 Curriculum、SFT Cold Start 或更稠密的 Reward。
9.3 Token-level Policy Gradient Loss
原始 GRPO 常先对每条 Response 的 Tokens 求平均,再对 Response 求平均:
J s a m p l e = 1 G ∑ i = 1 G 1 ∣ y i ∣ ∑ t = 1 ∣ y i ∣ ℓ i , t . J_{\mathrm{sample}}
=
\frac{1}{G}
\sum_{i=1}^{G}
\frac{1}{|y_i|}
\sum_{t=1}^{|y_i|}
\ell_{i,t}.
J sample = G 1 i = 1 ∑ G ∣ y i ∣ 1 t = 1 ∑ ∣ y i ∣ ℓ i , t .
其中 ℓ i , t \ell_{i,t} ℓ i , t 表示第 i i i 条 Response 中第 t t t 个 Token 的 Clipped Policy Objective。
这种聚合方式让每条 Response 的总权重相同,因此长回答中的每个 Token 权重更小。
DAPO 改为在整个 Batch 的所有有效 Tokens 上直接取平均:
J t o k e n = 1 ∑ i = 1 G ∣ y i ∣ ∑ i = 1 G ∑ t = 1 ∣ y i ∣ ℓ i , t . J_{\mathrm{token}}
=
\frac{1}{
\sum_{i=1}^{G}|y_i|
}
\sum_{i=1}^{G}
\sum_{t=1}^{|y_i|}
\ell_{i,t}.
J token = ∑ i = 1 G ∣ y i ∣ 1 i = 1 ∑ G t = 1 ∑ ∣ y i ∣ ℓ i , t .
这样每个 Token 的基础权重相同,长 Response 会对总梯度产生更大影响。对于 Long-CoT:
高质量长推理中的模式可以获得充分学习;
低质量长文本中的重复、乱码等模式也能受到更充分惩罚。
这并不表示长回答一定更好,而是在 Loss Aggregation 时不再先将每条 Response 强行归一成相同总权重。
9.4 Overlong Reward Shaping
生成通常存在最大长度 L max L_{\max} L m a x 。如果 Response 达到上限后被截断,直接将其视为完全错误会引入噪声:它可能具有合理推理,只是尚未来得及输出最终答案。
设:
L = ∣ y ∣ L=|y| L = ∣ y ∣ :实际 Response 长度;
L max L_{\max} L m a x :硬长度上限;
L b u f L_{\mathrm{buf}} L buf :Overlong Buffer 长度;
L s t a r t = L max − L b u f L_{\mathrm{start}}=L_{\max}-L_{\mathrm{buf}} L start = L m a x − L buf :开始惩罚的长度;
α > 0 \alpha>0 α > 0 :最大长度惩罚。
可以定义线性惩罚:
R l e n ( L ) = { 0 , L ≤ L s t a r t , − α L − L s t a r t L b u f , L s t a r t < L ≤ L max . R_{\mathrm{len}}(L)
=
\begin{cases}
0,
&L\le L_{\mathrm{start}},\\[4pt]
-\alpha
\dfrac{L-L_{\mathrm{start}}}
{L_{\mathrm{buf}}},
&L_{\mathrm{start}}<L\le L_{\max}.
\end{cases}
R len ( L ) = ⎩ ⎨ ⎧ 0 , − α L buf L − L start , L ≤ L start , L start < L ≤ L m a x .
最终 Reward 为:
R f i n a l = R t a s k + R l e n . R_{\mathrm{final}}
=
R_{\mathrm{task}}
+R_{\mathrm{len}}.
R final = R task + R len .
Response 越接近硬上限,惩罚越强,从而提前告诉模型应该收敛并给出答案,而不是等到真正截断时突然给予不连续的惩罚。
另一种处理是直接 Mask 掉被截断样本的 Loss,即 Overlong Filtering。但这会浪费数据;Soft Overlong Punishment 提供了更连续的训练信号。
9.5 DAPO 的整体目标
忽略长度 Reward 的具体写法后,DAPO 的核心 Policy Objective 可以写为:
J D A P O ( θ ) = E [ 1 ∑ i ∣ y i ∣ ∑ i = 1 G ∑ t = 1 ∣ y i ∣ min ( ρ i , t A ^ i , clip ( ρ i , t , 1 − ϵ l o w , 1 + ϵ h i g h ) A ^ i ) ] , \begin{aligned}
J_{\mathrm{DAPO}}(\theta)
=
\mathbb E\Bigg[
\frac{1}{\sum_i|y_i|}
\sum_{i=1}^{G}
\sum_{t=1}^{|y_i|}
\min\Big(
&\rho_{i,t}\hat A_i,\\
&\operatorname{clip}
(\rho_{i,t},
1-\epsilon_{\mathrm{low}},
1+\epsilon_{\mathrm{high}})
\hat A_i
\Big)
\Bigg],
\end{aligned}
J DAPO ( θ ) = E [ ∑ i ∣ y i ∣ 1 i = 1 ∑ G t = 1 ∑ ∣ y i ∣ min ( ρ i , t A ^ i , clip ( ρ i , t , 1 − ϵ low , 1 + ϵ high ) A ^ i ) ] ,
并要求参与训练的 Prompt Group 具有非零 Reward 方差。
可以把它理解为:
DAPO = GRPO-style Advantage + 更适合 Long-CoT 的训练 Recipe \boxed{
\text{DAPO}
=
\text{GRPO-style Advantage}
+
\text{更适合 Long-CoT 的训练 Recipe}
}
DAPO = GRPO-style Advantage + 更适合 Long-CoT 的训练 Recipe
十、veRL:大模型 RL 后训练框架
10.1 veRL 解决什么问题?
veRL(Volcano Engine Reinforcement Learning)是面向大模型 RL 后训练的分布式训练框架,也是 HybridFlow 的开源实现。PPO、GRPO 和 DAPO 回答的是“模型应该怎样更新”,veRL 回答的则是“如何让这套算法在多机多卡上真正运行起来”。
一次在线 RL 训练不只有 Forward 和 Backward,还需要反复执行:
1 2 3 4 5 6 取 Prompt → 生成 Response → 计算 Reward → 计算 Advantage → 更新模型 → 用新模型继续生成
这条链涉及多种模型和计算引擎:
Actor 负责训练,需要保存 Gradient 和 Optimizer State;
Rollout Engine 负责生成,需要 KV Cache 和高吞吐推理;
PPO 还需要 Critic 计算 Value;
KL 正则可能需要冻结的 Reference Policy;
Reward 可能来自规则、代码执行器或 Reward Model。
它们使用的并行方式、显存和执行时机都不相同。veRL 的作用,就是把这些角色组织成一条完整的数据流,并管理分布式执行、GPU 分配、Batch 传递以及 Actor 到 Rollout Engine 的权重同步。
veRL 也提供 SFT 等能力,但其核心优势仍然是 PPO、GRPO 等包含在线 Rollout 的 RL 训练。
10.2 先看 veRL 的整体运行结构
理解 veRL 时,可以先只记住三层:
1 2 3 4 5 6 7 8 9 10 算法控制层(Algorithm Control / Driver) RayPPOTrainer ↓ 调用 WorkerGroup 分布式执行层(Distributed Workers) ActorRollout WorkerGroup / Critic WorkerGroup Ref WorkerGroup(可选)/ Reward Model WorkerGroup(可选) ↓ Worker 调用具体 Backend 计算引擎层(Execution Engine / Backend) Training Engine:FSDP / FSDP2 / Megatron Rollout Engine:vLLM / SGLang
第一层:Trainer / Driver 决定执行顺序
RayPPOTrainer 通常运行在 Driver Process 中。它维护 PPO 的全局控制流,决定什么时候生成、什么时候计算 Reward、什么时候更新 Critic 和 Actor。
Driver 本身不执行大模型的分布式 Forward 或 Backward。它更像调度者:准备 Batch,调用对应角色,接收结果,再把结果交给下一阶段。
第二层:Worker / WorkerGroup 执行某个模型角色
Actor、Rollout、Critic 等角色由远程 Workers 执行。多个共同运行一个分布式模型的 Workers 组成一个 WorkerGroup。
例如,Driver 调用:
1 actor_rollout_wg.generate_sequences(batch)
表示让 Actor/Rollout WorkerGroup 使用多张 GPU 生成 Response。Driver 只发起一次调用,WorkerGroup 内部会完成数据分发、分布式计算和结果收集。
第三层:Engine / Backend 完成真正的模型计算
Worker 内部再调用具体计算引擎:
Actor/Critic Training 可以使用 FSDP、FSDP2 或 Megatron;
Rollout Generation 可以使用 vLLM 或 SGLang。
因此可以把三层的职责概括为:
1 2 3 Trainer:下一步做什么? WorkerGroup:由哪些进程和 GPU 来做? Engine:模型具体怎样并行计算?
HybridFlow 所谓 Hybrid,主要体现在它既保留了清晰的集中式算法控制流,又允许每个模型角色在内部使用自己的分布式执行方式。
10.3 把算法角色映射到 GPU
上一节说明了程序由 Trainer 调用 WorkerGroup。接下来还要解决两个问题:需要创建哪些 WorkerGroup,以及它们分别放在哪些 GPU 上。
Role:需要哪些计算角色?
Role 是算法中的逻辑角色:
ActorRollout:训练 Policy,并使用 Policy 生成 Response;
Critic:PPO 中的 Value Model;
RefPolicy:计算 KL 时使用的冻结 Reference Policy;
RewardModel:使用模型打分时的 Reward Model。
不是每次训练都需要全部角色。例如 PPO 需要 Actor/Rollout 和 Critic,但 Reference Policy、Reward Model 都是可选的;GRPO 一般不需要 Critic。
veRL 会根据配置把这些 Role 映射为相应的 WorkerGroup。理解时重点关注每个角色的输入和输出,不需要死记具体的 Worker Class 名称。
ResourcePool:这些角色放在哪些 GPU 上?
ResourcePool 表示一组可供 Worker 使用的 GPU。Role 到 ResourcePool 的 Mapping 决定每个角色部署在哪里。
如果多个角色映射到同一个 ResourcePool,它们会共享同一批 GPU,并在不同训练阶段分时运行,这称为 Colocation。例如 Rollout 结束后释放或休眠 KV Cache,再使用这些 GPU 更新 Actor。
如果给 Rollout 和 Training 分配不同 Resource Pools,则两者可以独立运行,减少显存切换和资源竞争,但需要更多 GPU,也要承担跨资源池同步权重的开销。
DataProto:阶段之间传递什么?
不同 WorkerGroup 之间不直接传递零散变量,而是通过统一的 DataProto 传递一个 Batch。它包含:
Tensor Batch:input_ids、responses、old_log_probs 等;
Non-tensor Batch:原始文本、数据来源、Ground Truth 等;
Meta Info:动态 Batch、Timing 等控制信息。
可以把 DataProto 理解成贯穿一次 RL Iteration 的数据包:Rollout 向其中加入 Response,Critic 加入 Value,Reward Function 加入 Reward,Advantage Estimator 再加入 Advantage 和 Return。后面的 PPO 流程会具体说明这些字段怎样逐步产生。
10.4 从启动脚本到训练循环
运行一个 veRL PPO 脚本时,程序首先完成初始化:
读取 Hydra 配置,确定模型、数据、算法和集群参数;
启动 Ray,获得当前节点上的 GPU 资源;
加载 Dataset、Tokenizer 和 Reward Function;
根据配置确定需要 Actor/Rollout、Critic、Reference、Reward Model 中的哪些角色;
将这些 Role 映射到 ResourcePool,并初始化对应的 WorkerGroup;
构造 RayPPOTrainer,调用 fit() 进入训练循环。
这部分只是在“搭建舞台”。真正的数据流发生在 RayPPOTrainer.fit() 中:它每次从 DataLoader 取得一批 Prompt,让各个 WorkerGroup 按照 PPO 顺序处理这批数据。
10.5 一次 PPO 在 veRL 中如何执行?
这一节只跟踪一批 Prompt,重点看每一步由谁执行、返回什么,以及结果怎样交给下一步。
第一步:取出 Prompt
DataLoader 从训练集中取出一批样本。每条样本主要包含两部分:
给模型的问题,例如一道数学题或一段用户指令;
计算 Reward 所需的信息,例如标准答案、测试用例或任务类型。
Tokenizer 将文本 Prompt 转换成 Token IDs,并通过 Padding 把同一 Batch 补到相同长度。最终得到的不是新的训练结果,只是一批已经整理好、可以送入模型生成的 Prompt。
第二步:生成 Response
Rollout Worker 接收上一阶段整理好的 Prompt,使用当前 Policy 生成答案。底层通常由 vLLM 或 SGLang 执行高吞吐推理。
如果每个 Prompt 采样 G G G 次,就会得到 G G G 条不同的 Response。每个“Prompt + Response”组成一条完整轨迹。
这一步的主要输出就是一批模型答案,同时还会记录每条答案的有效长度,避免后续把补齐长度使用的 Padding 当成模型真正生成的内容。
第三步:计算 Reward
Trainer 将每条 Prompt、生成的 Response 和标准答案等验证信息交给 Reward Function 或 Reward Model。
例如,数学题可以检查最终答案,代码题可以运行测试用例,主观问答可以由 Reward Model 打分。这一步通常为每条完整 Response 返回一个标量,表示这条答案整体有多好。
为了与后续按 Token 计算的 GAE 对齐,veRL 会把这个分数放在 Response 的最后一个有效 Token 上。如果启用了 KL Reward,还会减去 Actor 偏离 Reference Policy 的惩罚。
第四步:确定这批数据的 Old Log Probability
这批 Response 是由更新前的 Policy 生成的。为了进行 PPO 更新,需要知道旧策略当时认为每个已生成 Token 有多大概率。
Training Actor 会重新读取固定的 Prompt–Response,不再生成新答案,而是一次性计算旧策略赋予每个 Response Token 的 Log Probability,这就是 Teacher-forcing Forward。
之所以由 Training Actor 重新计算,而不直接使用 Rollout Engine 返回的概率,是因为生成与训练可能分别使用 vLLM/SGLang 和 FSDP/Megatron。重新计算可以让 Old Log Probability 与后面 Current Log Probability 使用一致的模型计算路径,减少不同引擎造成的数值偏差。
这些旧概率在整轮 PPO 更新中保持不变,后面会与更新中的 Current Actor 概率比较,计算 Importance Ratio。
第五步:补齐 PPO 所需的其他量
如果启用了 KL,Reference Policy 会读取同一批 Prompt–Response,计算自己赋予每个 Response Token 的概率。将它与 Actor 的概率比较,就能衡量 Actor 偏离 Reference Policy 的程度。
Critic 也会读取这些完整轨迹。与 Actor 预测“下一个 Token 是什么”不同,Critic 为每个生成位置预测“从这里继续生成,最终预计能得到多少回报”,也就是 Value。
至此,每条轨迹已经有了实际 Reward、旧策略概率和 Critic 对未来回报的预测。
第六步:计算 Advantage 与 Return
Trainer 将实际 Reward 与 Critic 预测的 Value 交给 GAE,产生两种训练信号:
Advantage:这次实际生成相对于 Critic 原本预期好多少,用于训练 Actor;
Return:Critic 后续应该拟合的目标,用于训练 Critic。
简单来说,这一步把“模型最终得了多少分”转换成 Actor 和 Critic 各自可以直接学习的目标。具体计算公式对应前文的 GAE。
第七步:先更新 Critic
Trainer 把完整轨迹和刚刚算出的 Return 交给 Critic WorkerGroup。
Critic 重新预测每个生成位置的 Value,并与 Return 比较,计算 Value Loss,然后反向传播更新 Critic。这个阶段只让 Critic 的估值更加准确,不会直接改变 Actor 的生成概率。
第八步:再更新 Actor
Trainer 再把固定轨迹、旧策略概率和 Advantage 交给 Actor WorkerGroup。Actor 会:
重新计算 Current Actor 对这些 Response Tokens 的概率;
与生成数据时的旧策略概率比较,构造 Importance Ratio;
结合 Advantage 计算 PPO Clipped Objective;
执行 Backward 和 Optimizer Step。
这里不会重新生成 Response,而是在同一批固定 Tokens 上更新 Actor。
同一批 Rollout 可以重复训练多轮,但作为比较基准的旧策略概率始终不变。
第九步:同步新权重,开始下一轮
Actor 更新后,新权重需要进入 Rollout Engine。下一批 Prompt 必须由更新后的 Policy 生成,否则 Rollout Policy 会一直停留在旧版本。
veRL 将更新后的 Actor 权重同步给 Rollout Engine。Trainer 随后记录 Reward、KL、Clip Fraction、Value Loss 和各阶段耗时,并按配置执行 Validation 或保存 Checkpoint,再读取下一批 Prompt。
因此,一次 veRL PPO Iteration 的主线是:
1 2 3 4 5 6 7 8 9 Prompt → Rollout 得到 Response → Reward → Old Log Probability → Critic Value → GAE → 更新 Critic → 更新 Actor → 同步权重后继续 Rollout
对应到代码,RayPPOTrainer.fit() 的核心调用关系可以简化为下面的伪代码。这里省略了 DataProto 合并、分布式调度和日志等细节:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 for prompt_batch in train_dataloader: rollout_batch = actor_rollout_wg.generate_sequences(prompt_batch) rewards = reward_fn(rollout_batch) old_log_probs = actor_rollout_wg.compute_log_prob(rollout_batch) if use_reference_policy: ref_log_probs = ref_policy_wg.compute_ref_log_prob(rollout_batch) values = critic_wg.compute_values(rollout_batch) train_batch = compute_advantage( rollout_batch, rewards=rewards, values=values, old_log_probs=old_log_probs, ) critic_wg.update_critic(train_batch) actor_rollout_wg.update_actor(train_batch)
若运行 GRPO,整体控制流基本相同,但会去掉 Critic 的 Value 计算和更新,并将 GAE 换成同一 Prompt 多条 Response 的 Group-relative Advantage。
10.6 配置文件应该怎么看?
veRL 使用层级配置。阅读一个 PPO 启动脚本时,可以按以下顺序理解。
Data
1 2 3 4 5 data.train_files data.val_files data.train_batch_size data.max_prompt_length data.max_response_length
train_batch_size 表示每个 Rollout Step 的全局 Prompt 数。若 rollout.n=G,最终生成的轨迹数通常为:
num trajectories = train batch size × G . \text{num trajectories}
=
\text{train batch size}\times G.
num trajectories = train batch size × G .
Model 与 Actor
1 2 3 4 5 6 actor_rollout_ref.model.path actor_rollout_ref.actor.strategy actor_rollout_ref.actor.optim.lr actor_rollout_ref.actor.ppo_mini_batch_size actor_rollout_ref.actor.ppo_epochs actor_rollout_ref.actor.clip_ratio
strategy 决定 Training Backend;
ppo_mini_batch_size 是全局 Mini Batch Size;
ppo_epochs 决定一批 Rollout 被重复训练多少轮;
clip_ratio 对应 PPO 的 ϵ \epsilon ϵ 。
Rollout
1 2 3 4 5 6 actor_rollout_ref.rollout.name actor_rollout_ref.rollout.n actor_rollout_ref.rollout.tensor_model_parallel_size actor_rollout_ref.rollout.gpu_memory_utilization actor_rollout_ref.rollout.temperature actor_rollout_ref.rollout.top_p
name 选择 vLLM、SGLang 等 Rollout Backend;
n 表示每个 Prompt 生成几条 Response;
tensor_model_parallel_size 决定一个 Rollout Replica 使用多少张 GPU;
gpu_memory_utilization 表示 Rollout Engine 可以使用的 GPU 显存比例,影响模型权重、Activation 和 KV Cache 的可用显存预算。
Critic 与 Algorithm
1 2 3 4 5 6 7 critic.model.path critic.optim.lr critic.ppo_mini_batch_size algorithm.adv_estimator algorithm.gamma algorithm.lam algorithm.use_kl_in_reward
PPO 通常使用 adv_estimator=gae 并启用 Critic;GRPO 通常使用 adv_estimator=grpo,不需要 Critic。
Trainer
1 2 3 4 5 6 trainer.n_gpus_per_node trainer.nnodes trainer.total_epochs trainer.test_freq trainer.save_freq trainer.logger
这些参数控制 Cluster 规模、训练轮数、验证、Checkpoint 和日志。
10.7 Train Batch、Mini Batch 与 Micro Batch
这三个 Batch Size 位于不同层级:
data.train_batch_size:一次 Rollout 取多少个 Prompt;
ppo_mini_batch_size:一次 Optimizer Step 使用多少条 Trajectories;
micro_batch_size_per_gpu:每张 GPU 单次 Forward/Backward 实际处理多少条 Trajectories。
假设:
1 2 3 4 5 train_batch_size = 128 rollout.n = 1 ppo_mini_batch_size = 16 4 GPUs micro_batch_size_per_gpu = 1
那么 128 条 Rollout 会拆成 8 个 Mini Batches。每个 Mini Batch 全局有 16 条轨迹,平均每张 GPU 处理 4 条;每卡再以 Micro Batch 1 连续处理并累积梯度,最后完成该 Mini Batch 的 Optimizer Step。
Micro Batch 主要影响峰值显存与吞吐,不应改变算法上的有效 Mini Batch。veRL 还支持 Dynamic Batch:
1 2 use_dynamic_bsz = True ppo_max_token_len_per_gpu = ...
它不再只按“样本条数”切 Batch,而是限制每张 GPU 一次处理的最大 Token 数,更适合 Response 长度差异很大的 RL Rollout。
10.8 并行策略如何选择?
Training Backend
FSDP/FSDP2 :按参数切分模型状态,适合常见 Hugging Face 模型和中等规模训练;
Megatron-LM :支持更完整的 Tensor/Pipeline/Sequence Parallel,适合更大模型。
Rollout Tensor Parallel
rollout.tensor_model_parallel_size=TP 表示一个 Rollout Replica 由 T P TP TP 张 GPU 协同运行。
较大 TP:降低每卡模型权重和 KV Cache 压力,但跨卡通信更多、Replica 数更少;
较小 TP:可以启动更多 Data-parallel Rollout Replicas,通常吞吐更高,但每卡显存压力更大。
因此能放下模型时,Rollout 不一定是 TP 越大越好。
Sequence Parallel
长上下文训练可以使用 Ulysses Sequence Parallel,把 Sequence 维度切到多张 GPU。相关配置通常是:
1 ulysses_sequence_parallel_size
它主要缓解长序列 Activation 和 Attention 的显存压力,但会增加通信。
10.9 显存与性能调优
定位性能问题时,应先区分发生在哪个阶段。
Rollout OOM 或吞吐低
重点检查:
max_prompt_length 与 max_response_length;
rollout.n;
tensor_model_parallel_size;
gpu_memory_utilization;
max_num_batched_tokens;
max_num_seqs;
KV Cache 是否在角色切换时正确释放或休眠。
Actor/Critic Update OOM
重点检查:
PPO Micro Batch;
Dynamic Batch 的 Max Tokens per GPU;
Gradient Checkpointing;
Remove Padding;
FSDP Parameter/Optimizer Offload;
Sequence Parallel Size;
Model、Gradient、Optimizer State 与 Activation 的显存占用。
GPU 利用率低
常见原因包括:
Response 长度差异导致 Straggler;
Micro Batch 太小;
Rollout TP 太大、Replica 太少;
生成阶段 max_num_batched_tokens 太低;
Worker 之间存在同步等待;
Reward Function 或外部 Sandbox 成为 CPU/Network Bottleneck。
veRL 的 Timing Metrics 会将 gen、reward、old_log_prob、values、adv、update_actor 和 update_critic 分开记录,应先找到耗时最大的阶段,再调整对应配置。
10.10 阅读 veRL 源码的主线
阅读 veRL 源码时,可以沿以下顺序:
PPO 入口:配置、Dataset、Reward、Role Mapping;
RayPPOTrainer.init_workers():WorkerGroup 和模型如何初始化;
RayPPOTrainer.fit():整个算法控制流;
generate_sequences():Rollout Backend;
compute_advantage():GAE、GRPO 等 Advantage Estimator;
update_actor():Mini/Micro Batch 与 PPO Loss;
update_critic():Value Loss;
DataProto:各阶段传递的数据字段;
Sharding Manager:Actor Training 与 Rollout Engine 如何同步权重。
不必死记所有文件名,重点是能回答:
算法控制流在哪里?
每个分布式模型由哪个 WorkerGroup 执行?
一个字段在 DataProto 的哪一步产生、被谁消费?
Actor 更新后,权重如何进入 Rollout Engine?
当前 OOM 或性能瓶颈属于 Rollout、Forward 还是 Backward?
十一、PPO、GRPO、DAPO 的关系
11.1 PPO 与 GRPO
两者都使用:
Online Rollout;
Importance Ratio;
Clipped Policy Objective;
Reward 驱动的策略更新。
主要区别是 Advantage Baseline:
PPO:Critic + GAE \boxed{
\text{PPO:Critic + GAE}
}
PPO : Critic + GAE
GRPO:同 Prompt 多 Response 的组内相对 Reward \boxed{
\text{GRPO:同 Prompt 多 Response 的组内相对 Reward}
}
GRPO :同 Prompt 多 Response 的组内相对 Reward
PPO 节省同 Prompt 的多次采样,但需要训练 Critic;GRPO 去掉 Critic,但依赖多个 Rollout 形成可靠的 Group Baseline。
11.2 GRPO 与 DAPO
DAPO 继承了 GRPO 的 Group-relative Advantage,但针对 Long-CoT 做了系统改进:
对称 Clip 改成 Clip-Higher;
随机 Prompt Batch 改成过滤零方差 Group 的 Dynamic Sampling;
Sample-level Loss 改成 Token-level Loss;
对硬截断改成平滑的 Overlong Reward Shaping;
原始 DAPO Recipe 移除了 KL。
因此 DAPO 不是完全不同的理论框架,更像是围绕 GRPO-style RL 构建的一套大规模 Reasoning RL Recipe。
十二、实践中最容易忽略的问题
12.1 Reward Hacking
Policy 优化的是给定 Reward,而不是开发者心中真正想要的能力。如果 Reward Function 存在漏洞,模型可能学会钻漏洞。
例如只检查最终答案字符串时,模型可能碰巧输出正确答案,但推理过程完全错误。解决思路包括:
改进答案解析和等价性判断;
使用隐藏测试用例;
同时检查格式、过程与最终结果;
定期人工审查高 Reward 样本;
在训练外使用独立 Evaluation。
12.2 Sparse Reward
如果困难题几乎全部得到 0 0 0 :
P ( R > 0 ) ≈ 0 , P(R>0)\approx0,
P ( R > 0 ) ≈ 0 ,
就没有足够的正向信号。可以使用:
SFT Cold Start;
Curriculum Learning;
增加每个 Prompt 的 Rollout 数;
Process / Partial Reward;
将任务分解成更容易的子任务。
12.3 Entropy Collapse
Policy 过早变得确定时,多条 Rollout 几乎一致,探索能力下降。应监控:
H ( π θ ) , \mathcal H(\pi_\theta),
H ( π θ ) ,
并结合 Sampling Temperature、Entropy Bonus、Clip Range 和 DAPO Clip-Higher 调整。
Entropy 也不是越大越好。过高可能表示输出随机、无法收敛;目标是在探索与利用之间取得平衡。
12.4 长度偏差与截断
长 Response 会带来:
更高 Rollout 延迟;
更大的 KV Cache;
更多训练激活值;
更高的截断概率;
Sample-level 与 Token-level Loss 权重差异。
因此应同时监控:
Mean Response Length;
Max-length Hit Rate;
Truncation Rate;
正确与错误样本各自的长度分布;
Reward 与长度的相关性。
12.5 Prompt 分布偏移
训练与最终评测的 Prompt Template 不一致时,表现可能下降。变化可能来自:
System Prompt;
是否要求输出推理过程;
最终答案格式;
Few-shot Examples;
中英文指令;
Stop Tokens 与 Chat Template。
稳妥做法是让训练 Prompt 覆盖可能的评测格式,并单独做 Prompt Robustness Evaluation,而不是只在单一 Template 上报告结果。
12.6 多任务 Mixture 与 Task Interference
多任务目标可写为:
J ( θ ) = ∑ k = 1 K w k J k ( θ ) , J(\theta)
=
\sum_{k=1}^{K}
w_kJ_k(\theta),
J ( θ ) = k = 1 ∑ K w k J k ( θ ) ,
其中:
K K K :任务数量;
J k J_k J k :第 k k k 个任务的目标;
w k w_k w k :该任务在总目标中的权重。
改变采样比例,本质上会改变各任务对总梯度的贡献。比例选择不应只看哪个任务当前 Reward 最低,而应看:
各任务独立 Held-out Validation;
Reward / Accuracy 的改善趋势;
是否已经 Plateau;
增加训练预算后的边际收益;
是否存在一个任务提升、另一个任务持续退化。
如果两个任务梯度满足:
g i T g j < 0 , g_i^Tg_j<0,
g i T g j < 0 ,
说明它们的更新方向存在冲突,可能出现 Task Interference。这时除了调 Mixture,还可以考虑更小的 Learning Rate、分阶段训练或 Multi-task Gradient Balancing。
12.7 Online RL 的 Off-policy 程度
Rollout 来自 π θ o l d \pi_{\theta_{\mathrm{old}}} π θ old ,更新的是 π θ \pi_\theta π θ 。如果同一批数据训练太多轮,新旧 Policy 差距会越来越大:
π θ ≉ π θ o l d . \pi_\theta
\not\approx
\pi_{\theta_{\mathrm{old}}}.
π θ ≈ π θ old .
Importance Sampling 的方差增大,大量 Tokens 会被 Clip。因此应结合:
PPO Epochs;
Learning Rate;
Clip Fraction;
Approximate KL;
Rollout 更新频率;
判断是否过度重复使用旧数据。
十三、总结
理解大模型 RL 后训练,可以抓住两条主线。
第一条是统一 Pipeline:
Prompt → Rollout → Reward → Advantage → Policy Update . \text{Prompt}
\rightarrow
\text{Rollout}
\rightarrow
\text{Reward}
\rightarrow
\text{Advantage}
\rightarrow
\text{Policy Update}.
Prompt → Rollout → Reward → Advantage → Policy Update .
第二条是不同算法如何构造 Advantage 并稳定更新 Policy:
PPO:Critic GRPO / DAPO:Group Reward \text{PPO:Critic}
\qquad
\text{GRPO / DAPO:Group Reward}
PPO : Critic GRPO / DAPO : Group Reward
PPO 通过 Critic 与 GAE 提供较稳定的 Advantage,但需要额外训练 Value Model;GRPO 用同一 Prompt 下的组内 Reward 比较删除 Critic,但依赖多次 Rollout 和有效的组内 Reward 方差;DAPO 则进一步针对 Long-CoT 中的探索、有效采样、Loss 聚合和截断问题改进 GRPO-style Training。
最终选择哪种方法,取决于 Reward 是否可验证、Rollout 预算、显存限制、任务难度以及 Critic 能否提供可靠的 Value Estimation。
参考资料
Attention Is All You Need
RoFormer: Enhanced Transformer with Rotary Position Embedding
Root Mean Square Layer Normalization
GLU Variants Improve Transformer
LLaMA: Open and Efficient Foundation Language Models
Proximal Policy Optimization Algorithms
High-Dimensional Continuous Control Using Generalized Advantage Estimation
DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models
DAPO: An Open-Source LLM Reinforcement Learning System at Scale
HybridFlow: A Flexible and Efficient RLHF Framework
veRL GitHub Repository
veRL Documentation