CS336 学习笔记

课程网站:https://cs336.stanford.edu/

CS336 学习笔记

Tokenization

文本的本质: Unicode字符串。

语言模型会对token序列建立概率分布。Tokenizer是完成encode和decode的类。

Token麻烦的点:

  • 有时单词和空格+单词是不同的token
  • 开头的单词和句中的单词token是不一样的
  • 如何tokenize数字?

压缩比 = 字符串的字节数 / token数量

提升压缩比会增加效率,过度增加压缩比会造成sparsity的问题。词表数量增加-单个词出现的频率下降->数据稀疏->embedding层学习能力不充分->泛化差

如何构建分词器?

  1. 按照Unicode编码?词表巨大;有的字符利用率低,压缩比不理想。
  2. 首先将String转成字节。同上。
  3. 按照空格或正则来切:词表巨大,可能是无限的。

Byte Pair Encoding (BPE)

让罕见的序列拆分成多个token,同时让常见的序列拆分成一个token

基本思路:使用原始文本训练分词器

初始:每个字节都是一个token。

循环:将出现频率最高的相邻token对合并起来(原来的不会删除)。使用新的token更新整个训练语料库。重新统计pair

边界:直到词表达到设定的大小

BPE 训练算法

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
算法:BPE训练

输入:
训练语料 C
目标词表大小 V_target

输出:
词表 V
合并规则 M

1. 将训练语料 C 拆分为基本符号序列
2. 使用所有基本符号初始化词表 V
3. 初始化合并规则 M ← 空

4. 当 |V| < V_target 时,重复:

5. 统计 C 中所有相邻 Token 对的出现频率

6. 找到出现频率最高的 Token 对 (a, b)

7. 创建新 Token:
new_token ← 合并(a, b)

8. 将 C 中所有相邻的 (a, b)
替换为 new_token

9. 将 new_token 加入词表 V

10. 将合并规则 (a, b) 加入 M

11. 结束循环

12. 返回 V, M

BPE 编码算法

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
算法:BPE编码

输入:
待编码文本 x
合并规则 M

输出:
Token序列 T

1. 将文本 x 拆分为基本符号,得到 Token 序列 T

2. 重复:

3. 找出 T 中所有相邻的 Token 对

4. 从这些 Token 对中,
找出存在于合并规则 M 中的 Token 对

5. 如果不存在可以合并的 Token 对:
结束循环

6. 根据合并规则 M 的优先级,
选择优先级最高的 Token 对 (a, b)

7. 将 T 中相邻的 (a, b)
合并成新的 Token ab

8. 结束循环

9. 返回 T

扩展方法:

  • 只遍历重要的合并规则
  • 处理特殊token
  • 将文本分解为块,对每个块应用分词器

展望

未来可能不需要Tokenization,而是实现从字节开始的端到端。

但是仍然需要满足:

transformer依赖某种序列的chunks运行。

Chunks应该是大小可变的。并非所有字节都同等重要。


Resource Accounting

要回答的问题:在给定的资源(计算量与内存)下,可以训练最好的模型是什么。

Memory Accounting

Tensor Memory

tensor是储存数据的基本单元。

1
2
3
a = torch.zeros(4) # vector
b = torch.zeros(4, 8) # matrix
c = torch.zeros(4, 8, 2) # rank-3 tensor

通常储存浮点数

  • float32: fp32 单精度,用于传统科学计算,可训练小模型
  • float64: 双精度
  • float16: 半精度,动态范围小,训练时不稳定
  • bfloat16:动态范围和float32一样。

混合精度训练:

  • 参数、激活值、梯度:bf16
  • 优化器状态:fp32
  • Pytorch的AMP库可以实现

甚至有fp8,fp4

Compute Counting

Tensor Einops

Einops 是用来操作张量的库,命名了每个维度。

Ensum

Ensum 爱因斯坦求和约定:一套统一的方法完成很多tensor运算。

约定:

  • 字母表示Tensor的维

  • 输入中存在、但输出中消失的维度,会被求和。因此:"i,i->"\(= \sum_i a_ib_i\)

  • 输出中字母顺序决定输出Tensor的维度顺序

  • 可以用...前面所有剩余的维度

    1
    2
    3
    x = np.random.rand(2, 3, 4, 5)  # 想把最后两维 4和5 转置为 5和4
    y = np.einsum('...ij->...ji', x)
    print(y.shape) # (2, 3, 5, 4)

矩阵乘法:

1
torch.einsum("ik,kj->ij", A, B)

使用的三个步骤:

  1. 给输入维度贴标签

    Q → b h q d

    K → b h k d

  2. 看输出

    output → b h q k

  3. 寻找消失的字母

常用einsum

运算 einsum
向量求和 "i->"
向量逐元素乘法 "i,i->i"
向量点积 "i,i->"
外积 "i,j->ij"
矩阵转置 "ij->ji"
矩阵所有元素求和 "ij->"
每行求和 "ij->i"
每列求和 "ij->j"
矩阵乘法 "ik,kj->ij"
Batch 矩阵乘法 "bik,bkj->bij"
Reduce

对消失的维度做:

1
2
3
4
5
6
"sum"     # 求和
"mean" # 平均
"max" # 最大值
"min" # 最小值
"prod" # 连乘
reduce(x, "b s h -> b h", "mean") # 例子
Rearrange

重新排列、拆分或合并 Tensor 的维度,但不会对数据进行求和等计算。

顺序:括号中从左到右排列,右边的维度变化最快。

1
2
rearrange(x, "batch seq hidden -> (batch seq) hidden") # 合并
rearrange(x, "batch seq (head d) -> batch head seq d", head=8) #拆分

Tensor Operations Flops

FLOP:浮点运算,主要是加法乘法

FLOPs 浮点运算次数

FLOP/s (FLOPS): 浮点运算性能

H100的性能:1979 teraFLOP/s(实际的要除以2)

对于 (B,D) @ (D,K) 的矩阵乘法,\(FLOPs = 2*B*D*K\)(乘一次加一次)

另一种理解方式:\(FLOPs = 2(tokens)(parameters)\)

MFU (Model Flops utilization): 实际FLOPS/承诺FLOPS(除二过的)

FLOPS由硬件和数据类型决定

Arithmetic Intensity

计算时间决定因素:

  • 加速器的速度 (FLOP/s)
  • 内存带宽 (bytes/s)

二者可以并行

bottleneck:

  • Memory-bound
  • Compute-bound

arithmetic intensity = flops / bytes

acclelerator intensity = flop/s / bytes / s

Roofline polts:横轴是计算密度,纵轴是加速器峰值计算能力。

训练时的计算量

反向传播的计算量:

\(h_1.grad = h_2.grad \times W_2^T\)

\(W_2.grad = h_1^Th_2.grad\)

是前向传播的两倍

总计算量:

Forward pass: 2 (# data points) (# parameters) FLOPs

Backward pass: 4 (# data points) (# parameters) FLOPs

Total: 6 (# data points) (# parameters) FLOPs

在上下文不长时对Transformer也使用

结论

当模型一共L层,每层权重\(W\in \mathbb{R}^{D\times D}\),一次处理B个token,则有

\[ flops = 6BN,where N = D^2L \]

减少内存积累的方法:

  • 梯度积累 gradient accumulation:
  • 激活检查点 activation checkpoint:将部分没保存的激活重新计算(\(\sqrt L\) 个检查点比较好)

Architectures

大模型结构:

输入文字

tokenID

Enbedding词向量(可训练)

很多层 Transformer Block:

  • RMSNorm (缩放参数可训练)
  • 多头自注意力机制
    • 生成KQV(\(W_{QKV}\)可训练)
    • 对QK做RoPE
    • 计算注意力分数
    • Causal Mask
    • 加权汇总V
  • 残差连接
  • RMSNorm (缩放参数可训练)
  • MLP (权重可训练,偏置一般不使用)
  • 残差连接

最终归一化

LM Head (参数可训练)

Logits

采样或选最大值

得到下一个Token

新Token加到最后面

重复这个过程

归一化

RMS Norm没有明显的表达能力损失,不需要计算均值中间量更少,运算更快,减少了内存搬运。 \[ \operatorname{RMS}(x) = \sqrt{\frac{1}{d}\sum_{i=1}^{d}x_i^2+\varepsilon} \]

\[ \mathrm{RMSNorm}(x) = \frac{x}{\operatorname{RMS}(x)} \odot \gamma \]

  • \(x\):输入向量(Hidden State)
  • \(d\):Hidden Size
  • \(\varepsilon\):防止除零的小常数(不可训练)
  • \(\gamma\):可训练的缩放参数(与 Hidden Size 等长)
  • \(\odot\):逐元素乘法

现代模型使用Prenorm,保证残差主干干净

\[ x_{l+1} = x_l + F(\text{Norm}(x_l)) \]

偏置项

实践证明可以去掉减少内存搬运,避免不稳定

激活函数

现代模型使用:SwiGLU/GeGLU \[ \text{GeGLU}(x) = (x W) \otimes \text{GELU}(x V ) \]

\[ \text{SwiGLU}(x) = \text{Swish}(x V) \otimes (x W) \]

\[ \text{Swish}_1(x) = x \cdot \text{sigmoid}(x) = \frac{x}{1 + e^{-x}} \]

\[ \text{GELU}(x) = x \cdot P(X \le x) = x \cdot \Phi(x) \]

如果要给MLP加上门控结构,需要把原来的W的输出维度缩小到原来的2/3:

原来的参数量:

  • 升维层 \(W_1\) 参数量:\(d \times h_{\text{old}}\)
  • 降维层 \(W_2\) 参数量:\(h_{\text{old}} \times d\)
  • 总参数量:\(2 \cdot d \cdot h_{\text{old}}\)

加门的参数量:

  • 路径 A (\(W_{\text{up}}\)) 参数量:\(d \times h_{\text{new}}\)

  • 路径 B (\(W_{\text{gate}}\)) 参数量:\(d \times h_{\text{new}}\)

  • 降维层 (\(W_{\text{down}}\)) 参数量:\(h_{\text{new}} \times d\)

  • 总参数量:\(3 \cdot d \cdot h_{\text{new}}\)

保持参数量不变:\[h_{\text{new}} = \frac{2}{3} h_{\text{old}}\]

RoPE

传统的绝对位置编码的问题:无法自然表示Token之间的相对距离。

旋转位置编码,通过在二维空间中旋转向量来注入位置信息。

注入 token 位置信息,让模型识别token的绝对位置、相对距离、前后顺序。

二维向量旋转: \[ R(\theta)=\begin{bmatrix}\cos\theta & -\sin\theta\\\sin\theta & \cos\theta\end{bmatrix} \]

\[ x_1'=x_1\cos\theta-x_2\sin\theta,\quad x_2'=x_1\sin\theta+x_2\cos\theta \]

高维向量旋转:将高维向量两两拆分为二维小组,每组独立执行旋转。

位置 m 处,第 i 组的旋转角度:\(m\theta_i, \theta_i = \frac{1}{10000^{2i/d}}\) ,其中 d 为单头注意力维度

\(\theta_i\)较小:旋转角度随位置m变化缓慢,捕捉长距离语义依赖。

\(\theta_i\)较大:旋转角度岁位置变化速度快,捕捉相邻词之间的局部依赖。

实现:

\[f(\mathbf{x}, m) = \mathbf{x} \odot \cos(m\Theta) + \tilde{\mathbf{x}} \odot \sin(m\Theta)\]

\[ \tilde{\mathbf{x}} = [-x_2, x_1, -x_4, x_3, \dots, -x_d, x_{d-1}] \] 优势:

作用在单向量上,便于 KV Cache 缓存;表达相对位置。

长文本外推性好,配合NTK-Aware Scaling 和 YaRN,只对低频维度压缩,扩展上下文长度的代价更小。

随着相对距离 \(\vert{}m - n\vert{}\) 增加内积的期望值会自动衰减,即词汇关联度降低,符合语言学规律。

Parallel Layers

传统串行:

\[ y = x + \text{MLP}(\text{LayerNorm}(x + \text{Attention}(\text{LayerNorm}(x)))) \]

并行: \[ y = x + \text{MLP}(\text{LayerNorm}(x)) + \text{Attention}(\text{LayerNorm}(x)) \]

可能导致残差相加时的方差积累稍快,主流仍然是用串行,保证数值稳定性。

超参数

一些经验法则

Feedforward

\(d_{ff}=4d_{model}\),算上GLU之后的比率是2.67左右,也可以取3.5来侧重MLP。

Head Dim

\(d_{head} \approx d_{model}/n_{head}\)

Aspect ratio

\(d_{model}/n_{layer} \approx 100\),根据系统利用率选择

词汇表大小:多语言模型10万左右。

Regularization

大模型基本不用担心过拟合。

常用方法:

Weight decay并不是在控制过拟合,它本质上是在于优化器相互作用让优化变得更好。

稳定性问题

很核心的问题

不希望出现毛刺

softmax可能是问题关键,因为有指数运算,会有除零。softmax存在于

  • 输出端
  • 注意力归一化的时候

解决方法:使用z-loss方法

模型对词表中每个 token 输出一个 logit。用\(U_r(x)\) 表示:输入为 \(x\) 时,第 \(r\) 个 token 的 logit。

它对应的概率为: \[ P(r\mid x) = \frac{e^{U_r(x)}}{Z(x)} \] 其中: \[ Z(x)=\sum_{r'=1}^{|V|}e^{U_{r'}(x)} \] 取对数后: \[ \log P(r\mid x) = U_r(x)-\log Z(x) \] \(\log Z(x)\)的数字可能极大或极小,带来不稳定。

Softmax具有整体平移不改变概率的性质,因此:在loss中加入一项\(\alpha \log ^2 (Z(x_i))\),使logZ 接近0。

QK Norm

在Attention里QK相乘之前分别先LayerNorm。保证注意力机制稳定性。

Logit soft-capping

(不太常用)

在softmax之前加上

核心公式是: \[ z_{\text{new}} = c\cdot\tanh\left(\frac{z}{c}\right) \] 其中:

  • \(z\):原始 logit
  • \(c\):soft cap上限尺度
  • \(z_{\text{new}}\):压缩后的 logit

只有当 \(|z|\) 接近或超过 \(c\) 时,压缩才明显。

正常范围保持原样,异常大值才被压住。

注意力机制的改进

GQA / MQA

GQA:Grouped-Query Attention,分组查询注意力,几个Q头共享1个KV

MQA:Multi-Query Attention,多查询注意力,所有Q头共享1个KV,表达能力损失比较大

减少KV头数量,保留较多的Q头

目的,在推理时接生KV Cache的大小和带宽

滑动窗口注意力

交替使用滑动窗口注意力和全局注意力

Architecture 进阶

Attention的线性复杂度替换

去掉softmax,利用结合律: \[ (QK^T)V=Q(K^TV) \]

\[ Q,K,V\in\mathbb{R}^{L\times d_{\text{head}}} \]

复杂度从\(n^2d_k+n^2d_v\)变成\(2nd_vd_k\)

推理时:将 \(K^TV\) 看成状态

假设逐 token 处理: \[ q_t,k_t,v_t\in\mathbb{R}^{d} \] 定义状态: \[ S_t=\sum_{i=1}^{t}k_i v_i^T \] 其中: \[ S_t\in\mathbb{R}^{d_k\times d_v} \] 它可以递推更新: \[ S_t=S_{t-1}+k_t v_t^T \] 然后当前 token 的输出为: \[ y_t=q_t^TS_t \] 这种方法的优势是把随历史长度增长的计算和缓存,变成固定大小的状态。训练时还是矩阵形式较快。

Mamba-2

带有输入相关衰减机制的递推线性注意力。

普通Linear Attention的问题:旧信息不断堆积,内容互相干扰,状态越来越拥挤。

加入遗忘门 \(\gamma_t\)\[ S_t=\gamma_tS_{t-1}+k_tv_t^T \] 其中: \[ \gamma_t=f(x_t) \] 表示 \(\gamma_t\) 是根据当前输入 \(x_t\) 计算出来的。

它通常可以理解为一个介于 0 和 1 之间的衰减或遗忘系数。

输出部分

Mamba-2 输出: \[ y_t=q_t^TS_t+v_t^TD \] 前半部分: \[ q_t^TS_t \] 是从历史状态中读取信息。

后半部分: \[ v_t^TD \] 是一条直接通路,即使信息还没有通过状态传播,当前 token 也能直接影响当前输出。

Gated DeltaNet

公式: \[ S_t= \gamma_t \left(I-\beta_tk_tk_t^T\right)S_{t-1} + \beta_tk_tv_t^T \] 输出: \[ y_t=q_t^TS_t \] 其中: \[ \gamma_t=f(x_t),\qquad \beta_t=f(x_t) \] 两个门都由当前输入决定。

这个式子可以拆成三步理解:

整体遗忘:\(\gamma_tS_{t-1}\)这部分与 Mamba-2 类似

定向擦除:

所以可以记住: \[ kk^Tx=\text{x 在 k 方向上的分量} \] 前提是: \[ \|k\|=1 \] 也就是 \(k\) 已经归一化。 \[ (I-kk^T)x=\text{沿k的横向分量被删掉,x中垂直于k的部分被保留} \] 写入新信息:\(\beta_tk_tv_t^T\)

改写公式:

暂时忽略 \(\gamma_t\),有: \[ S_t= \left(I-\beta_tk_tk_t^T\right)S_{t-1} + \beta_tk_tv_t^T \] 展开: \[ S_t = S_{t-1} - \beta_tk_tk_t^TS_{t-1} + \beta_tk_tv_t^T \] 提取公共项: \[ S_t= S_{t-1} + \beta_tk_t \left( v_t^T-k_t^TS_{t-1} \right) \] 括号中的: \[ v_t^T-k_t^TS_{t-1} \] 就是: \[ \text{目标 value}-\text{当前记忆预测出的 value} \] 也就是一个误差项

因此更新规则可以理解为: \[ \text{新状态} = \text{旧状态} + \text{学习率}\times \text{key}\times \text{预测误差} \] 这和在线梯度下降非常相似,所以叫 Delta Rule

Linear层占比越大,性能下降越多。

Deepseek Sparse Attention

思想:在所有的token中使用选择器选出一个子集并在其中计算Full Attention

  1. Lightning Indexer

耗时极短,但理论复杂度仍为二次方。模型不再直接进行高维的 Query 和 Key 内积,而是通过一个Lightning Indexer,将两者投影到一个极低的维度 \(d_{idx}\),且通常使用 FP8 等低精度硬件加速。它的目标是快速评估出当前 Query 与过去所有 Token 的粗略相关性得分。

时间复杂度\(O(L^2 \cdot d_{idx})\)。尽管由于 \(d_{idx}\) 极小且使用了低精度计算,常数项被压缩到了极致,但因为每个 Query 依然要遍历所有 \(L\) 个历史 Token,其理论时间复杂度依然是 \(O(L^2)\)

  1. Top-k Selection

纯内存与排序操作、对于当前的每一个 Query,系统会基于上一步生成的得分矩阵,利用快速排序算法(如 Radix Top-k),动态筛选出得分最高的 \(k\) 个 Token 索引(例如 \(k=2048\))。

时间复杂度:针对 \(L\) 个 Query,在 \(L\) 个候选项中找出前 \(k\) 个最大值的复杂度一般在 \(O(L^2)\) 级别。这一步主要涉及内存寻址和排序操作,几乎不占用核心的矩阵乘法算力。

  1. Sparse Attention Update

系统只提取那 \(k\) 个被选中的 Token 的高精度、高维度的 Key 和 Value,与当前的 Query 进行标准的 Attention 计算并输出结果。

时间复杂度\(O(L \cdot k \cdot d_{head})\)。传统稠密注意力在这一步的复杂度高达 \(O(L^2 \cdot d)\),而 DSA 通过限制参与计算的数量,将这一步的复杂度降到了与上下文长度 \(L\) 成正比的线性关系

Mixture of Experts

Routing Function

常用:Top-k

1. \[ \mathbf h_t^l = \sum_{i=1}^{N} \left( g_{i,t}\operatorname{FFN}_i(\mathbf u_t^l) \right) + \mathbf u_t^l \]

这里:

  • \(t\):第 \(t\) 个 token
  • \(l\):第 \(l\)
  • \(\mathbf u_t^l\):这个 token 进入 MoE 层之前的 hidden state
  • \(N\):专家总数
  • \(\operatorname{FFN}_i\):第 \(i\) 个专家,本质上是一个独立的 FFN
  • \(g_{i,t}\):第 \(t\) 个 token 分配给第 \(i\) 个专家的权重
  • \(\mathbf h_t^l\):MoE 层输出
  • 最后的 \(+\mathbf u_t^l\):残差连接

每个被选中的专家都处理一次 token,然后把各专家的结果按照路由权重加权求和,最后再加回原输入。

2.

\[ s_{i,t} = \operatorname{Softmax}_i \left( {\mathbf u_t^l}^{T}\mathbf e_i^l \right) \]

它表示:计算 token 与每个专家之间的匹配程度。

\(\mathbf e_i^l\)是专家向量。每个专家都有一个可训练的路由向量: \[ \mathbf e_i^l \in \mathbb R^{d_{\text{model}}} \] token 的 hidden state 也是: \[ \mathbf u_t^l \in \mathbb R^{d_{\text{model}}} \] 二者做点积: \[ {\mathbf u_t^l}^{T}\mathbf e_i^l \] 得到一个标量。

这个标量越大,就意味着:当前 token 的表示与第 \(i\) 个专家的路由向量越匹配。

3. \[ g_{i,t} = \begin{cases} s_{i,t}, & s_{i,t} \in \operatorname{TopK} \left( \{s_{j,t}\mid 1\leq j\leq N\},K \right) \\[4pt] 0, & \text{otherwise} \end{cases} \] 对当前 token 计算它对所有专家的分数 \(s_{i,t}\)

找出分数最高的 \(K\) 个专家

保留这些专家的分数

其他专家的权重全部设成 0

两种顺序:Softmax 在 Top-K 前还是后

Shared Experts

总是启用一些专家,不受router的影响。

Training MoE (Load Balancing)

问题:Gate 不可导

Stochastic Approximations:在点积方程中加入噪声。

Heuristic balancing losses:平衡探索和利用,防止专家现象崩溃在loss加一个惩罚项。

Per-expert balancing

Per-device balancing

Per-expert Bias

趋势:Top-k+均衡负载 / 辅助损失函数 支持不可微操作。

并行方法

  • Data
  • Model
  • Model and Data
  • Expert and Data
  • Expert, Model and Data

MoE可以转化成稀疏矩阵乘法加速

MoE 稳定性

z-loss保证softmax稳定

微调:困难,容易过拟合

Upcycling:从dense模型得到MoE模型。(少见)


GPU

参考:

  • Horace He’s blog
  • CUDA Mode group
  • TPU (and now GPU) book

CPU设计目标:尽量低延迟->大Control 小ALU

GPU设计目标:大吞吐量->大量轻量级核心,大ALU

内存模型:

现阶段主要瓶颈在内存上。

要避免落在斜坡区域

Control Divergence:尽量避免分支(if)

Low Precision Computation

低精度计算是在大模型训练和推理中,用 FP16、BF16、FP8、INT8、INT4 等格式替代部分 FP32 计算。

主要收益:

  • 降低显存占用
  • 减少显存带宽压力
  • 提高 Tensor Core 吞吐量
  • 降低训练和推理成本

核心思想:在可接受的误差范围内,用更少的比特完成计算。

常见格式:

格式 主要特点 常见用途
FP32 精度高、开销大 累加、敏感操作
FP16 精度较高、范围较小 混合精度训练
BF16 范围接近 FP32 大模型训练
FP8 更快、更省显存,需要缩放 高性能训练和推理
INT8 压缩明显 推理
INT4/FP4 压缩极强、误差较大 显存受限推理

FP16 与 BF16 的区别:

  • FP16:尾数更多,精度更细
  • BF16:指数更多,动态范围更大,训练更稳定

为什么能加速?

Transformer 的主要开销是矩阵乘法

低精度数据:

  • 占用更少存储空间
  • 从显存读取更快
  • 一次可传输更多元素
  • Tensor Core 每次可执行更多乘加操作

但只有 GPU 和 Kernel 原生支持对应格式时,才能获得明显加速。

混合精度计算

实际不会让所有操作都使用低精度,而是低精度乘法+高精度累加如:

\[ \text{FP16}\times\text{FP16} \rightarrow \text{FP32 累加} \]

原因是矩阵乘法包含大量求和,低精度累加容易产生误差。

Softmax、RMSNorm、梯度归约等敏感操作通常保留 FP32

Loss Scaling

FP16 动态范围较小,小梯度可能下溢为 0。

解决方法是先放大 Loss:L'=SL

使梯度同步放大,参数更新前再除以 S。

BF16 动态范围较大,通常不需要 Loss Scaling。

量化

INT8、INT4 不能直接表示小数,需要使用缩放因子:

\[ q=\operatorname{round}\left(\frac{x}{s}\right) \]

\[ \hat{x}=sq \] 常见方式:

  • Weight-only:只量化权重
  • W8A8:权重和激活值都量化
  • KV Cache 量化:降低长上下文推理显存

缩放粒度越细,通常误差越小:

Per-tensor → Per-channel → Per-group → Per-block

训练与推理选择

场景 常见选择
大模型训练 BF16
新型 GPU 高性能训练 FP8
普通推理 FP16 / BF16
显存受限推理 INT8 / INT4
长上下文推理 低精度 KV Cache

总结

低精度存储与乘法 动态缩放 高精度累加

位宽越低: - 显存越小 - 带宽需求越低 - 理论速度越快 - 数值误差越大

低精度计算的本质,是在模型精度、显存占用和计算速度之间取得平衡。

Operator Fusion

将数个调用融合成一个CUDA调用

Recomputation

不保存激活值,在需要的时候重新计算这些激活值。

Memory Coalescing and DRAM

读取在内存中连续的数据代价较小

Tiling

思想:将重复访问的数据分块后放到Shared Memory中。

矩阵乘法案例(默认能整除):

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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <cuda_runtime.h>

#include <iostream>

constexpr int N = 4;
constexpr int TILE_SIZE = 2;

__global__ void tiledMatMul(
const float* A,
const float* B,
float* C
) {
__shared__ float As[TILE_SIZE][TILE_SIZE];
__shared__ float Bs[TILE_SIZE][TILE_SIZE];

int row = blockIdx.y * TILE_SIZE + threadIdx.y;
int col = blockIdx.x * TILE_SIZE + threadIdx.x;

float sum = 0.0f;

// K 维度被分成 N / TILE_SIZE 个 Tile
for (int tile = 0; tile < N / TILE_SIZE; ++tile) {

// 每个线程加载 A Tile 中的一个元素
As[threadIdx.y][threadIdx.x] =
A[row * N + tile * TILE_SIZE + threadIdx.x];

// 每个线程加载 B Tile 中的一个元素
Bs[threadIdx.y][threadIdx.x] =
B[(tile * TILE_SIZE + threadIdx.y) * N + col];

// 确保整个 Tile 已经加载到 Shared Memory
__syncthreads();

// 使用当前 Tile 计算部分结果
for (int k = 0; k < TILE_SIZE; ++k) {
sum += As[threadIdx.y][k]
* Bs[k][threadIdx.x];
}

// 确保所有线程使用完当前 Tile,
// 才允许下一轮覆盖 Shared Memory
__syncthreads();
}

// 所有 Tile 计算结束后,写入 Global Memory
C[row * N + col] = sum;
}

void printMatrix(const float* matrix) {
for (int row = 0; row < N; ++row) {
for (int col = 0; col < N; ++col) {
std::cout << matrix[row * N + col] << "\t";
}
std::cout << '\n';
}
}

int main() {
float h_A[N * N] = {
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12,
13, 14, 15, 16
};

float h_B[N * N] = {
1, 0, 1, 0,
0, 1, 0, 1,
1, 0, 1, 0,
0, 1, 0, 1
};

float h_C[N * N] = {};

float* d_A = nullptr;
float* d_B = nullptr;
float* d_C = nullptr;

size_t bytes = N * N * sizeof(float);

cudaMalloc(&d_A, bytes);
cudaMalloc(&d_B, bytes);
cudaMalloc(&d_C, bytes);

cudaMemcpy(
d_A,
h_A,
bytes,
cudaMemcpyHostToDevice
);

cudaMemcpy(
d_B,
h_B,
bytes,
cudaMemcpyHostToDevice
);

dim3 blockSize(TILE_SIZE, TILE_SIZE);
dim3 gridSize(
N / TILE_SIZE,
N / TILE_SIZE
);

tiledMatMul<<<gridSize, blockSize>>>(
d_A,
d_B,
d_C
);

cudaDeviceSynchronize();

cudaMemcpy(
h_C,
d_C,
bytes,
cudaMemcpyDeviceToHost
);

std::cout << "Matrix A:\n";
printMatrix(h_A);

std::cout << "\nMatrix B:\n";
printMatrix(h_B);

std::cout << "\nMatrix C = A * B:\n";
printMatrix(h_C);

cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);

return 0;
}

FlashAttention

对KQV矩阵乘法分块

Online Softmax

Kernels

Parallelism

多GPU加速

多GPU:数据可能在另一个GPU上

关键:减少数据搬运开销

节点可能不止一个

方法:replication/sharding

分布式通信与计算

Collective Operations

Collective:指定通用的通信模式

Rank:一个特定的设备

World size:设备数量

操作:

  • broadcast, scatter, gather, reduce (foundations)
  • All-gather, reduce-scatter, all-reduce (workhorse) 大模型训练常用
  • All-to-all MoE常用

Broadcast: 从rank0 复制到所有rank

scatter:散射,将一个大张量从一处分散到多处

本身不直接使用,reduce-scatter的基础

Gather:scatter的逆向操作

Reduce:将所有tensor通过归约操作集中在rank0上

All-gather: 对将gather结果放在所有rank上

Reduce-scatter: 在每个维度上进行reduce操作,将结果scatter出去

应用场景:在反向传播后,将不同数据分片中的梯度数据加起来,并分布式存储。

All-reduce: reduce-scatter+all-gather

应用场景:ZeRO/FSDP

All-to-all:指定每个rank如何向另一个rank发送消息

应用场景:

  • MoE中,将token路由到expert
  • 可以看成一次矩阵转置
  • 处理负载不均的情况

总结:

All代表目标是所有设备

scatter是gather的逆向操作

Reduce是聚合操作(sum, min, max)

硬件

传统结构

现代结构:

典型配置:

一个node 8GPU,使用Nvlink和NvSwitch 1.8TB/s

一个pod 256个node,infonoband 0.05TB/s

N pods per cluster, Ethernet

问题:Ethernet发送数据要经过CPU

Remote Direct Memory Access技术,允许一个GPU直接读写另一个GPU的内存

Advancement

GB200/300 NVL72

RoCE

NCCL:底层通信库

PyTorch 分布式编程

库:torch.distributed spawn

分布式训练

Data paralleism

把数据拆开,每个GPU负责一部分

将矩阵分成world size份,按照batch切分

Loss需要在rank之间通过all-reduce取平均

Tensor paralleism

将参数矩阵纵向切分,运算完以后all-gathering拼接

每个 rank 存储全部数据

Pipline paralleism

按照layer划分MLP,每个GPU负责几个layer

切分batch,否则会造成计算和传输重叠度不高。

并行起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
时间 →

GPU0:
mb0 mb1 mb2 mb3


GPU1:
mb0 mb1 mb2 mb3


GPU2:
mb0 mb1 mb2 mb3


GPU3:
mb0 mb1 mb2 mb3

总结

如何选择?

对比项 Data Parallel (DDP) Tensor Parallel (TP) Pipeline Parallel (PP)
切分对象 数据(batch) 模型参数/张量 模型层
每个 GPU 保存 完整模型 部分参数 部分层
每个 GPU 输入 不同数据 相同数据 micro-batch 流入
主要通信内容 梯度 激活值、梯度 激活值、梯度
通信频率 每个训练 step 一次(backward 后) 每层 forward/backward 多次 每个 micro-batch 在 stage 间传递
通信量 中等(≈模型参数大小) 高(≈层间 activation 大小,频繁) 中等(≈hidden activation 大小)
通信模式 all_reduce all_gather / reduce_scatter / all_reduce send/recv(点对点)
对网络要求 中等 最高 中等,适合分布式训练
最适合的 GPU 连接 PCIe / NVLink 均可 强依赖 NVLink / 高速互联 需要稳定低延迟通信
计算并行粒度 样本级 矩阵级 层级
扩展效率 高(容易扩展) GPU 数增加后通信瓶颈明显 GPU 数增加后 pipeline bubble 增大
实现复杂度
典型用途 多 GPU 加速训练 单模型太大,单卡放不下 超深超大的模型
代表技术 PyTorch DDP Megatron Tensor Parallel GPipe / PipeDream

超大规模集群训练

GPU是通过树形结构连接

TPU使用球形网络结构连接

Data parallelism

Naive parallelism: 按照B划分,将所有梯度加起来平均,问题:内存占用过大,优化器状态占大头

ZeRO:将优化器状态分散到不同的GPU上。

方法 核心思想
DDP 每个 GPU 保存完整模型,最后同步梯度
ZeRO-1 模型和梯度不变,只把 optimizer 状态分摊
ZeRO-2 在 ZeRO-1 基础上,再把梯度分摊
ZeRO-3 连模型参数也分摊,需要时临时 gather

ZeRO-1:

  • 每个rank计算全部梯度
  • 将梯度reduce-scatter,每个rank只拿一部分
  • 每台rank更新
  • All gather更新所有参数

内存节省没有导致更多通信开销

ZeRO-2:

  • 现在无法计算完整梯度?沿着计算图,每当我计算出一层梯度,就发送一个梯度

ZeRO-3(FSDP):

  • 平时每个rank只保存模型参数、梯度、优化器状态的一部分

  • 计算时,临时将这一层的完整参数拼出来,之后立即释放内存

  • 要将通信和计算的开销重叠起来:

数据并行消耗批次大小的问题

全局批次增加有瓶颈

单个GPU批次太少效率低

更细致的思路

之前传递的是参数,现在传递激活值

Pipline 并行:

零气泡方法:

反向传播中:

  • 计算输入/激活梯度:很关键,立刻
  • 计算参数梯度:不着急,有空闲再做

拆开两部分。

Tensor 并行:

MLP,QKV,升维矩阵:按列切分

降维矩阵、注意力输出:按行切分

归一化、路由器:完整复制

适用于节点内部并行

对比:不会出现流水线气泡,通信开销更大。

内存使用: \[ M_{\text{layer}} =sbh\left(34+5\frac{as}{h}\right) =34sbh+5abs^2 \] 其中:

  • \(s\):序列长度
  • \(b\):microbatch size
  • \(h\):hidden size
  • \(a\):attention head 数量

加入Tensor Parallel后: \[ M_{\text{layer, per GPU}} =sbh\left(10+\frac{24}{t}+5\frac{as}{ht}\right) \] 其中:

  • \(s\):序列长度
  • \(b\):microbatch size
  • \(h\):hidden size
  • \(a\):attention head 数
  • \(t\):Tensor Parallel 大小

假设:FP16/BF16 activation 为 2 字节,dropout mask 为 1 字节。

Sequence Parallel思路:把 TP 中原本在每张 GPU 上重复保存的LayerNorm、Dropout、Residual 等activation,沿 sequence/token 维切分。

Expert Parallel

优先选EP不是TP,TP削弱GPU利用率。

可以与DP,TP结合

Context Parallel

在Attention计算期间,也把长序列沿token/context维度分布到多张 GPU 上,而不是让每张GPU都保存完整序列。

总结

Scaling Law

问题:如何用小规模实验预测大模型的性能,并据此决定模型大小、数据量、计算预算和超参数。

性能随规模呈幂律变化

语言模型的测试损失通常可近似写成: \[ L(X)=L_\infty+A X^{-\alpha} \] 其中 \(X\) 可以是数据量 \(D\)、参数量 \(N\) 或训练计算量 \(C\)

在 log-log 坐标下,损失与规模近似呈直线关系。

联合考虑模型和数据时,常用形式为: \[ L(N,D)=L_\infty+A N^{-\alpha}+B D^{-\beta} \] 原因:

均值估计: \[ \mathbb E[(\hat\mu-\mu)^2]=\frac{\sigma^2}{n} \] 取对数后就是斜率为 \(-1\) 的直线。更复杂的非参数学习中,误差可能按 \(n^{-1/d}\) 衰减,其中 \(d\) 可理解为任务或数据的有效维度。

结论:模型性能虽有边际收益递减,但在一定范围内具有较强的可预测性,可以先训练一组小模型,再外推大模型表现。

数据的影响

增加独立、高质量数据通常能降低损失;

但数据分布、领域比例和质量会改变曲线的位置。

在特定设定下,数据组成主要改变截距,而幂律斜率相对稳定。

有限数据被反复训练时,每个重复 token 的边际价值会下降。

结论:数据策略应随训练规模动态变化,而不是始终固定一种过滤或混合比例。

Scaling Law 也是模型工程工具

通过一组较小模型,可以比较不同方案的整条扩展曲线,而不必直接训练多个超大模型:

  • 架构与优化器:比较 Transformer/LSTM、Adam/SGD 等方案的斜率与截距。
  • 深度和宽度:从一层增加到多层收益明显,但在合理形状范围内,模型总规模往往比具体宽深比更重要。
  • 参数并非等价:Embedding 参数、活跃参数与 MoE 的总参数对性能和计算的贡献不同。
  • Batch size:存在临界批量;超过该点后,并行度继续增加,但样本效率显著下降。目标损失越低,合适的临界批量通常越大。
  • 学习率:普通参数化下,最佳学习率可能随宽度变化;\(\mu\)P/\(\mu\)Transfer 通过尺度感知的初始化和参数化,使小模型上调好的超参数更稳定地迁移到大模型。

典型流程是:训练小模型,拟合扩展曲线,检查外推误差,选择大模型方案。

固定算力下模型做多大、数据用多少

对稠密 Transformer,训练计算量可粗略看作: \[ C\propto ND \] 所以固定预算下,模型过大而数据不足会“欠训练”;模型过小而训练过久,又会浪费大量数据。

Chinchilla 研究则发现,在其研究范围内,计算最优策略近似为: \[ N_{\text{opt}}\propto C^{0.5},\qquad D_{\text{opt}}\propto C^{0.5} \] 即模型扩大一倍时,训练 token 数也应大致扩大一倍。

经验值:约20token/参数

训练最优不等于部署最优

Chinchilla 主要优化的是固定预训练算力下的损失。实际产品还需考虑推理成本、延迟、显存和调用次数。

当模型会被大量调用时,往往值得提前投入更多训练算力,训练一个参数更小、token 更多的模型,以降低长期推理成本。

局限与正确使用方式

Scaling Law :局部经验规律和预算规划工具,不是物理定律。

新架构、数据处理或训练算法可能改变截距甚至斜率。

预训练loss往往较平滑,但下游准确率、推理能力和“涌现”行为可能更不稳定。

不能跨越完全不同的数据分布、模型架构或训练制度盲目外推。

拟合时应报告置信区间,并保留中等规模实验检验外推结果。

优化目标应包含训练、推理、存储、延迟和数据成本,而不只是最低训练 loss。

总结:scaling law用可控的小实验回答“在给定资源和使用场景下,模型、数据与计算应该如何分配”。

大模型推理优化

LLM 自回归推理,受到显存带宽和KV Cache限制。

思路:

  • 减少每生成一个Token需要从显存读取的数据量
  • 让一次权重读取服务更多请求

指标

  • TTFT (Time to First Token):用户提交请求后,首个Token出现的实践
  • 单Token延迟/TPOT:后续生成Token的速度
  • 吞吐量:系统每秒为所有请求生成的Token总数

Transformer 回顾

推理的阶段

Prefill: 处理输入的提示词

一次并行处理整个Prompt,为每层保存历史Token的Key、Value

特点:

  • 可以沿着序列维度并行

  • 矩阵乘法规模较大

  • 通常是compute-bound

  • 主要决定TTFT

Decode:逐Token生成

每一步只能在前一个Token生成后继续,天然串行。

KV Cache避免重复计算历史Token,但每一步仍然要读取:

  • 模型权重
  • 当前请求全部历史Token的KV Cache

特点:Memory-bound

原因:

算术强度定义为: \[ I = \frac{\text{FLOPs}}{\text{读写字节数}} \] 算术强度高,倾向于受计算能力限制;算术强度低,倾向于受显存带宽限制。

对于 MLP 矩阵乘法,在隐藏维度远大于 Token Batch 时: \[ I_{\text{MLP}}\approx B T \] 其中 \(B\) 是请求数,\(T\) 是本轮同时计算的 Token 数。

  • Prefill:\(T\) 较大,容易充分复用权重。
  • Decode:\(T=1\),只能通过批处理多个请求提高利用率。

对于使用 KV Cache 的 Attention: \[ I_{\text{attention}} = \frac{ST}{S+T} \] 其中 \(S\) 是 历史长度。

  • Prefill:\(T=S\),因此 \(I\approx S/2\)
  • Decode:\(T=1\),长上下文下 \(I\approx1\)

更重要的是,每个请求拥有独立的 KV Cache,因此增加 Batch 并不能显著提高 Decode Attention 的数据复用率。这使得解码阶段的 Attention 几乎始终受显存带宽限制。

一个实用的粗略模型是: \[ \text{单步延迟} \gtrsim \frac{\text{参数大小}+B\times\text{单请求 KV Cache}} {\text{显存带宽}} \] Batch 增大时,参数读取成本被更多请求分摊,吞吐量提高;

但 KV Cache 随 Batch 线性增长,所以收益最终递减。

优化方法

缩小 KV Cache

KV Cache大小约为KV Cache 大小近似为: \[ 2\times \text{精度字节数}\times \text{上下文长度}\times \text{KV 头数}\times \text{头维度}\times \text{层数} \] 主要方法:

MQA:所有 Query 头共享一组 K/V,缓存最小,但可能损失质量。

GQA:若干 Query 头共享一组 K/V,是 MHA 与 MQA 的折中。GQA 论文表明,它能获得接近 MHA 的质量以及接近 MQA 的推理效率。

MLA:先将 K/V 压缩到低维潜在向量,推理时只缓存压缩表示。DeepSeek-V2的方法。

跨层共享 KV:不同 Transformer 层共享部分 K/V。

局部或混合注意力:部分层只关注滑动窗口,限制缓存随上下文增长;再穿插全局注意力维持长程能力。

总结:用更少的历史状态,近似保留足够的信息。

量化

减少每次读取的字节数,将 BF16 权重或缓存降低到 FP8、INT8、INT4,可以

降低参数和 KV Cache 占用;减少显存传输;容纳更大的 Batch;改善延迟和吞吐量。

主要路线:

QAT:训练时模拟量化误差,效果通常更稳,但成本高。

PTQ:训练后使用校准数据量化,更便宜。

GPTQ、AWQ:识别更敏感的权重或通道,为其保留更高精度。

量化并不保证线性加速,实际收益还取决于硬件是否具有对应的低精度计算单元、反量化开销以及 Kernel 实现。

剪枝与蒸馏

直接缩小模型的方法

流程:

  • 使用校准数据来评估层、Attention 头或隐藏维度的重要性
  • 删除不重要的结构
  • 使用原模型作为教师,对小模型进行蒸馏修复

剪枝减少参数读取和计算,但通常会损失能力;

蒸馏用于恢复性能;

优化的是整个模型成本,而不仅仅是 KV Cache。

投机解码

小模型起草,大模型批改。

大模型特点:

  • 大模型逐个生成 Token 很慢;
  • 大模型并行验证一串Token更快。

流程:

  • 小Draft Model一次性提出若干候选Token
  • Target Model 并行计算这些位值的概率
  • 按修正后的拒绝采样规则接收或拒绝
  • 若拒绝,则从残差分布重新采样

在算法正确实现的前提下,最终样本分布与直接使用 Target Model 解码完全一致。

属于无损加速,而不是简单接受小模型的近似输出。

收益取决于 Draft Model 的速度及其与 Target Model 的一致程度。

连续批处理

处理动态请求。

真实服务的请求具有不规则性:

  • 到达时间不同;
  • Prompt 和输出长度不同;
  • 有的请求提前结束;
  • 有的请求共享系统提示词或其他前缀。

静态 Batch 必须等待最慢请求结束,造成大量空闲。

Continuous Batching 改为按解码迭代调度:

  • 每生成一步就重新整理Batch;
  • 已完成请求立即移除;
  • 新请求及时加入;
  • Attention按各序列分别处理;
  • MLP等非Attention 操作可将不同长度序列拼接后统一计算。

其目标是持续保持 GPU 忙碌,同时避免让短请求等待长请求。

PagedAttention

像操作系统管理内存一样管理 KV Cache。

传统做法:按最大输出长度预留连续显存。问题:

  • 内部碎片:请求提前结束,预留空间没有使用;

  • 外部碎片:空闲显存被分割成不连续的小块;

  • 相同前缀被重复存储。

PagedAttention将KV Cache分成固定大小的块:

逻辑上连续,物理上可以不连续;

按实际增长逐块分配;

相同 Prompt 前缀可共享物理块;

通过块级Copy-on-Write支持从同一 Prompt 生成多个候选答案。

工程实践

场景 优先目标 典型策略
聊天、代码补全 低 TTFT、低 Token 延迟 小 Prefill Batch、连续批处理、量化、投机解码
离线数据生成 最大吞吐、最低成本 大 Batch、模型副本并行、低精度推理
长上下文 控制 KV Cache GQA/MLA、局部注意力、PagedAttention
多轮 Agent 前缀复用、动态调度 Prefix Cache、Radix/Paged Cache、连续批处理
本地或边缘设备 内存容量与单请求延迟 INT4/INT8、小模型、CPU/GPU 专用 Kernel

Scaling Law 2

问题:给定算力预算,怎么平衡模型规模,训练数据量,架构,超参数,使得性能达到最优,同时降低试错成本。

扩展模型主要面临的困难:

  • 模型变宽、变深后,初始化和架构超参数是否仍然合适
  • 学习率、批大小、优化器等是否随规模变化
  • 为拟合 Chinchilla 式 scaling law,需要训练大量模型,实验成本很高

固定算力时,模型参数量和训练 token 应共同增长,而不是只增加参数。

原论文实验发现,两者大致应以相近速度扩展;这解释了为什么一些超大但训练数据不足的模型并非算力最优。

用 μP 稳定扩展

MiniCPM 的方法是:

  1. 使用 μP 初始化和参数化;
  2. 固定模型宽深比例,再整体扩大规模;
  3. 在小模型上拟合最优学习率、batch 和 token/参数比例;
  4. 使用WSD学习率日程降低数据 scaling 实验成本。

WSD

Warmup:学习率升高;

Stable:长时间保持稳定;

Decay:最后约一小段进行衰减。

DeepSeek

直接拟合超参数 scaling

  • 假设大多数 Transformer 架构参数可以跨规模保持
  • 用小规模网格实验估计最优学习率和 batch
  • 用 IsoFLOP 分析确定模型与数据规模
  • 使用分段学习率日程降低重复训练成本

优化器

不同优化器可能具有:

  • 不同的最佳学习率和weight decay

  • 不同的 batch scaling

  • 随模型增大而变化的相对优势

  • 不同的单步计算和通信开销

模型评测

困惑度

评估模型是否学会了数据分布。对测试序列 \(D=(x_1,\ldots,x_N)\),困惑度可写为: \[ \mathrm{PPL}(D)=\exp\left(-\frac1N\sum_{t=1}^{N}\log p(x_t\mid x_{<t})\right) \] 困惑度越低,说明模型给真实文本分配的概率越高。

优势:计算便宜、连续平滑,非常适合监控预训练、比较训练配置和研究缩放规律。

考试型基准

选择题或短答案,优点是难度可控、答案明确、评分便宜。

随着旧基准逐渐饱和,评估不断转向更多选项、更专业、更难和多模态的问题。

对话型评估

人类两两比较:如 Chatbot Arena,让用户在匿名回答中选择更好者,再拟合相对排名。

LLM 作为裁判:如 AlpacaEval、WildBench,使用参考标准或评分 rubric 自动判断。

智能体评估

SWE-Bench、TerminalBench、MLE-Bench

要求系统阅读代码、调用工具、运行程序、修改文件并反复纠错,最终通过单元测试或任务结果评分。

Agent=模型+提示与流程+工具+记忆+上下文管理

推理、安全、真实工作

ARC-AGI尝试用新颖的抽象任务降低事实记忆的影响,观察模型能否从少量示例归纳规则。

HarmBench、AIR-Bench 和越狱测试衡量有害行为、拒绝策略及其鲁棒性。

GDPVal、MedHELM 等让专业人士提供更接近真实职业或临床环境的任务,以提高生态效度。

排行榜可信度

  • 数据污染,测试集混入训练集
  • 基准会饱和
  • 题目或评分器本身不完善
  • 应提供多模板结果、重复实验和置信区间

设计评估框架

五个问题:

  • 目的:采购决策、科研比较、产品迭代,风险治理等
  • 评估对象:训练方法、基础模型、对话系统、智能体等
  • 任务真实性:测试分布是否代表目标用户、语言、领域和失败成本?
  • 裁判可靠性:能否使用程序验证、专家判断或清晰rubric?是否检查了裁判偏差?
  • 结果稳健性:是否报告成本、延迟、准确率、校准、鲁棒性、安全性,以及分组表现和统计不确定性?

总结

好的评估应当:

  • 与实际决策目标一致;
  • 明确区分方法、模型和系统;
  • 同时覆盖能力、成本、可靠性与风险;
  • 尽量贴近真实任务,又控制污染和隐私;
  • 报告误差、方差和失败案例,而不只报告排行榜名次。

Data

大模型训练数据:在线内容 → 抓取或数据转储 → 文本转换 → 过滤 → 去重 → 数据配比 → 训练数据

模型差距来源:

  • 数据来源
  • 清洗规则
  • 数据配比

数据来源

常见来源包括:

  • Common Crawl:规模最大,但噪声、重复、垃圾页面很多。Common Crawl 提供的 WARC 保存原始网页响应,WET 保存抽取后的纯文本;WET 更方便,但会丢失图片、表格、版式和部分结构。
  • Wikipedia:结构清晰、知识密度高,但内容范围和写作风格比较单一。
  • GitHub:代码、提交记录、Issue、PR 等;需处理许可证、分叉仓库、自动生成代码和恶意文件。
  • arXiv、PubMed:适合科学知识,但 PDF/LaTeX 转换复杂。
  • 书籍、新闻、论坛、Stack Exchange:语言质量或任务形式较好,但版权与访问限制更突出。
  • 合成数据:由强模型生成答案、推理过程或任务,主要用于中期训练和后训练。

数据集方法的演进

直接选择优质来源:BERT 使用 Wikipedia 和 BooksCorpus;GPT-2 的 WebText 用 Reddit 链接的点赞数作为质量代理。

规则清洗与领域组合:C4 使用标点、句子数量、语言识别和关键词规则;The Pile 则组合论文、代码、书籍、问答等多个领域。

模型化质量筛选:GPT-3、LLaMA、DCLM 等训练分类器,判断网页是否类似 Wikipedia、教材或高质量问答。

趋势:从凭经验制定规则逐渐走向定义好数据,再训练模型寻找相似数据。

数据处理Pipline

  1. 转换

原始数据通常是 HTML、PDF 或代码仓库,而不是干净文本。

转换需要:

  • 去除导航栏、广告、页脚等模板内容;
  • 尽量保留标题、段落、公式、表格和代码结构;
  • 对扫描 PDF 进行 OCR;
  • 避免把菜单、引用列表或乱码当成正文。

这是有损过程。解析工具的选择也可能影响最终模型能力。

  1. 过滤

将问题抽象为:已知少量目标数据T和海量原始数据R,从R中找出类似T的子集。

常见评分方式:

  • 生成式模型:score(x) = p_T(x)
  • 分类器:score(x) = p(T | x)
  • 人工规则:长度、符号比例、语言概率、敏感词等。

应用包括语言识别、教育价值判断、数学或代码数据筛选、毒性过滤。

过滤并非越严格越好:严格过滤提高平均质量,却会减少数量和多样性。训练预算越大,通常越需要保留部分质量稍低但提供新信息的数据。

  1. 去重

重复数据会浪费计算、增加记忆和隐私风险,还可能造成训练集与评测集泄漏。

  • 精确去重:对规范化文本计算哈希,只保留一个副本。
  • 近似去重:将文档表示为 n-gram 集合,用 Jaccard 相似度衡量重合程度:

\[ J(A,B)=\frac{|A\cap B|}{|A\cup B|} \]

MinHash 满足:

\[ P[h(A)=h(B)]=J(A,B) \]

再用 LSH 将多个 MinHash 分为b个 band、每个包含r个哈希。相似度为s的两个文档成为候选重复项的概率为:

\[ P(\text{collision})=1-(1-s^r)^b \]

增大 (r) 会让匹配更严格,增大 (b) 会提高召回率。

实验表明,去重可以减少约 10 倍的训练文本复现,同时保持或改善困惑度,并使评测更可信。

数据配比

假设训练源包括网页、Wikipedia 和代码,需要决定每个来源的采样概率 \(p_s\)

简单方案包括:

  • 均匀采样;
  • 按数据量成比例采样;
  • 人工设置比例。

关键问题:小型优质数据可能被重复过多。某来源实际训练轮数约为:

\[ \text{epochs}*s=\frac{p_s,N*{\text{train}}}{N_s} \]

若高质量数据只有 10B token,却在 1T token 训练中占 50%,它会被重复约 50 次,容易过拟合。

解决方法:

  • UniMax:均衡来源,但限制每个来源最多重复多少轮。

  • RegMix:用多种配比训练小模型,拟合“配比 → 评测性能”的回归模型,再预测大模型的较优配比。

  • 模拟重复:在小规模实验中按比例缩小各数据源,使重复程度接近最终大规模训练。

数据配比本质上也是一个需要实验验证的超参数。

后训练

后训练数据通常更像真实任务或评测题:

  1. 定义任务、提示和交互环境;
  2. 让强教师模型生成一个或多个回答;
  3. 使用规则、判分器或真实执行结果进行验证;
  4. 选取高质量轨迹进行 SFT 或强化学习。

任务可以是:

  • 完全合成;
  • 真实环境加合成任务;
  • 真实的人类问题、GitHub PR 或错误修复记录。

OpenThoughts 的实验表明,教师是否“适合教”、任务来源和多次采样策略都很重要;更强的模型未必总是更好的教师。

总结

好数据:

  • 来源可追踪
  • 转换损失小
  • 筛选目标明确
  • 重复少
  • 配比合理
  • 与最终希望模型具备的能力相匹配

Mid-training、SFT 与 RLHF

框架

预训练:让模型获得广泛的语言、知识和推理能力,但它学习的是互联网文本如何延续,而不是怎样成为有用的助手。

典型训练流程:

预训练 、中期训练、监督微调(SFT)、偏好优化(PPO/DPO/RLVR)

三类阶段作用不同:

  • 预训练:学习知识和通用能力。
  • SFT:教模型按照指定格式调用已有能力。
  • 偏好优化:在多个合理回答中,提高人类更偏好答案的概率。

InstructGPT的经典流程:

  • 收集示范回答进行 SFT
  • 再收集回答排序、训练奖励模型
  • 最后进行强化学习
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
          预训练模型


SFT


SFT / Policy Model

┌───────┴────────┐
│ │
产生多个回答 人类排序
│ │
└───────┬────────┘

Reward Model


PPO 等 RL


对齐后的模型

监督微调 SFT

目标:

给定指令x和理想回答y,最小化回答 token 的负对数似然:

\[ -\mathbb E_{(x,y)} \sum_t \log \pi_\theta(y_t\mid x,y_{<t}) \]

实际训练通常只计算 assistant 回答部分的损失,不要求模型预测 system 和 user 消息。

SFT 本质上是行为模仿,即让模型模仿数据中的回答方式。

SFT 数据的作用

不同数据集具有明显不同的行为风格:

  • FLAN 偏短答案和传统 NLP 任务
  • Alpaca、OpenAssistant 更详细
  • 新数据集包含多轮对话、工具调用和智能体轨迹;
  • 安全数据教模型拒绝、解释风险或提供安全替代方案。

因此,SFT 数据中的长度、列表、引用、语气和工具格式都会成为模型行为的一部分。

需要警惕:

  • 人类和模型裁判经常偏爱更长、更有条理的答案;
  • 偏好分数上升不一定意味着事实性或推理能力提升;
  • 训练集中的虚假引用可能教会模型生成看似可信的引用

SFT 更适合行为学习,而非知识注入

经验:SFT 最擅长教模型如何使用已有知识,而不是可靠地写入大量新知识。

研究发现,模型学习与预训练知识一致的样本较快,而学习全新事实较慢;强行学会这些事实后,还可能增加对其他问题的幻觉倾向。

实践上:

  • 新知识优先放入预训练或中期训练;
  • 经常更新的知识适合使用检索增强;
  • SFT 重点训练格式、任务策略、工具协议、安全行为和回答风格。

少量但针对性强的数据就可能显著改变行为,尤其是安全性和指令遵循;但长尾能力仍然需要更广的数据覆盖。

Mid-training

当指令数据规模很大时,可以将其提前混入预训练数据:

  1. 在网页、代码等通用数据上预训练;
  2. 在后期混入高质量知识、指令和领域数据;
  3. 最后再进行较短的纯 SFT。

这种方法兼顾规模与控制,可以减少只在小型指令数据上训练造成的灾难性遗忘。它也说明预训练、Mid-training 与 SFT 的边界并非绝对,而是数据质量和训练目标逐渐变化的连续过程。

偏好优化

人们能够写出的回答,不一定等于他们最喜欢的回答。这被称为“生成—价值差距”:

  • 写出一篇优秀答案很难;
  • 在两个答案中选出较好的一个通常更容易。

可以对同一提示生成多个回答,让标注者选择:\(x,y_w,y_l\),其中\(y_w\) 是优选回答,\(y_l\) 是较差回答。

奖励模型通常采用Bradley-Terry形式:

\[ \sigma\big(r_\phi(x,y_w)-r_\phi(x,y_l)\big) \]

它将人类偏好压缩为一个标量奖励。

偏好数据的问题

RLHF 的瓶颈往往不是算法,而是“谁的偏好被收集、如何收集”。

主要影响因素包括:

  • 标注指南是否清楚;
  • 标注者是否具有足够专业知识;
  • 标注者的人口、文化和价值分布;
  • 工资、时间压力及劳动伦理;
  • 是否认真核查事实;
  • 是否偷偷使用模型完成标注;
  • 回答长度、语气和排版等混杂变量。

AI 也可以充当评审者,即 RLAIF。它成本低、可扩展,但会继承教师模型的偏见和错误。例:Constitutional AI

PPO:显式奖励模型与在线强化学习

PPO 式 RLHF 大致优化:

\[ \beta D_{\mathrm{KL}} (\pi_\theta\Vert\pi_{\mathrm{ref}}) \]

其中:

  • \(r_\phi\):奖励模型;
  • \(\pi_{\mathrm{ref}}\):通常是 SFT 模型;
  • KL 项:防止策略偏离原模型过远;
  • \(\beta\):控制优化强度。

流程是:

  1. 当前模型生成回答;
  2. 奖励模型打分;
  3. PPO 根据奖励更新模型;
  4. 重复生成和更新。

优点是可以在线探索新回答;缺点是需要奖励模型、价值模型、在线采样和复杂的稳定性控制,工程成本较高。

DPO

直接使用偏好对。DPO 将带 KL 约束的 RLHF 最优策略写成闭式形式,把奖励表示为策略与参考策略的对数概率比,最终直接优化:

\[ \log\frac{\pi_\theta(y_l\mid x)} {\pi_{\mathrm{ref}}(y_l\mid x)} \]

直观上,它会:

  • 增加优选回答 \(y_w\) 的相对概率;
  • 降低劣选回答 \(y_l\) 的相对概率;
  • 通过参考模型限制策略漂移。

DPO 不需要显式训练奖励模型,也不需要在线 rollout,因而更容易实现和稳定训练。

但 DPO 并非始终优于 PPO:

  • DPO 依赖固定的离线偏好数据,难以探索数据覆盖之外的行为;
  • PPO 可以利用当前模型新生成的数据;
  • 两者表现高度依赖数据、模型、超参数和评测设置。

RLHF 的主要失败模式

奖励过度优化

奖励模型只是人类偏好的近似。当模型被过度优化后,代理奖励可能继续升高,而真实质量开始下降,即 Goodhart 定律:当一个指标成为优化目标时,它就不再是一个可靠指标。

这一现象不仅存在于 PPO,也存在于 Best-of-N 和离线偏好优化中。常见缓解方法:

  • KL 约束和早停;
  • 独立的人类评测与保留集;
  • 多个奖励模型或规则交叉验证;
  • 持续收集当前策略产生的新偏好数据;
  • 使用可验证奖励代替纯主观评分

模式坍塌

偏好优化可能让模型过度集中于少数高奖励风格,例如:

  • 所有回答都很长;
  • 频繁使用列表和总结;
  • 不确定时也表现得非常自信
  • 输出多样性和概率校准下降

现代后训练趋势

现代开放流程通常组合多种方法:

  • SFT 建立基本格式和行为
  • DPO 学习主观偏好
  • RLVR 使用代码测试、数学答案等可验证奖励
  • 迭代生成新数据并重新训练

总结

  • SFT是模仿学习理想回答看起来怎样
  • RLHF是优化:提高更受偏好的回答的概率
  • Mid-training 负责衔接规模与质量
  • DPO 简单稳定,PPO 能在线探索,二者没有绝对优劣
  • 数据来源、标注者与评测器本身就是模型目标的一部分
  • 奖励只能近似真实目标,过度优化必然带来风险
  • 后训练的核心是把预训练能力转化为可控、可用、可验证的行为
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
海量互联网文本


┌───────────────┐
│ Pretraining │
│ 学习语言和知识 │
└───────────────┘


Base Model

│ 指令数据

┌───────────────┐
│ SFT │
│ 学习如何回答 │
└───────────────┘


Instruct Model

│ 偏好 / 可验证奖励

┌─────────────────────────┐
│ Preference / RL │
│ RLHF / DPO / RLVR / GRPO│
└─────────────────────────┘


最终 Chat / Reasoning Model
强化学习概念 语言模型中的对应物
Agent 正在训练的语言模型
Environment 问题数据集、验证器、代码执行器或工具环境
初始状态 提示词 / 问题 \(q\)
中间状态 问题加已生成的 token 前缀 \(q,o_{<t}\)
动作 选择下一个 token;Agent 场景还包括工具调用
Policy 模型给下一个 token 的概率分布
Trajectory 一段完整回答,或一串多轮工具交互
Reward 答案正确、测试通过、格式合规等分数
Episode 终点 回答结束或任务完成/失败
Rollout 模型实际生成的一条回答或执行轨迹

PPO 细节

后训练:RLVR

强化学习概念回顾

强化学习的循环:观察状态,采取动作,环境反馈,获得奖励,更新策略。

Policy:\(\pi_\theta(a\mid s)\),表示在状态 \(s\) 下,参数为 \(\theta\) 的策略选择动作 \(a\) 的概率。策略为什么是概率而不是one-hot?探索:如果永远选择当前看起来最好的动作,就可能永远发现不了更好的选择。可优化性:可以逐渐增加高奖励动作的概率,而不是每次把决定完全推翻。

Episode: 一次从开始到结束的完整尝试

Trajectory:episode中经历的状态和动作序列。在LLM中,一次完整回答可以看成一条trajectory。

Sampling:按照概率随机选择一个动作。

语言模型是Policy

给定文本前缀,它会为给定文本前缀,它会为词表中每个下一个 token 分配概率。完整回答的概率是每一步 token 概率的乘积: \[ \pi_\theta(o\mid q) =\prod_{t=1}^{T}\pi_\theta(o_t\mid q,o_{<t}). \]

由于很多小概率相乘不便计算,实际常使用对数。乘法在取对数后变成加法:

\[ \log \pi_\theta(o\mid q) =\sum_{t=1}^{T}\log \pi_\theta(o_t\mid q,o_{<t}). \]

概率在 0 到 1 之间,所以它的对数通常不大于 0;越接近 0,表示原概率越接近 1、动作越可能发生。

Reward:某一步立即收到的分数

Return:指从当前时刻开始,未来奖励的累计值: \[ G_t=r_t+\gamma r_{t+1}+\gamma^2r_{t+2}+\cdots. \]

\(\gamma\in[0,1]\) 叫折扣因子。\(\gamma\) 接近0更重视眼前奖励。

困难:

LLM 推理常只有回答结束时才知道答案是否正确。例如模型写了500个token做判断题,最后验证器给出1分或0分。这叫稀疏的终局奖励。

Credit assignment:如果最终答案错了,究竟是第20个token的思路选择错了,还是第480个token的计算出错了?把最终结果归因到先前动作的问题叫 credit assignment。

模型训练的目标:\(J(\theta)=\mathbb E_{\tau\sim\pi_\theta}[R(\tau)]\),意思是按照当前策略生成许多轨迹 \(\tau\),希望它们的平均奖励最大。

梯度 \(\nabla_\theta J(\theta)\) 可以理解为一个方向:参数朝这个方向稍微移动,期望奖励上升最快。于是做梯度上升:\(\theta\leftarrow\theta+\eta\nabla_\theta J(\theta)\),其中 \(\eta\) 是学习率。

问题:答案检查器或代码测试通常不可为。技巧: \[ \nabla_\theta J(\theta) =\mathbb E\left[ R(\tau)\nabla_\theta\log\pi_\theta(\tau) \right] \] 记住行为即可:

一条轨迹的reward为较大正数:提高其已选中动作的概率

reward为负:降低动作概率

reward的绝对值越大:更新力度越大

单纯reward为 0”不一定代表惩罚;若没有 baseline,它可能只意味着没有更新。减去 baseline 后,它才可能变成负 advantage。

通常机器学习代码做梯度下降,因此在目标前加负号:\(L(\theta)=-J(\theta)\)

只看原始奖励会有很大噪声。同样得到1分,在一道极难题上可能非常优秀,在一道送分题上却只是正常表现。因此需要判断“比通常预期好多少”。

状态价值 \(V(s)\) \[ V^\pi(s)=\mathbb E_\pi[G_t\mid s_t=s]. \]

含义:从状态 \(s\) 出发,之后继续按策略 \(\pi\) 行动,平均能获得多少回报。

动作价值 \(Q(s,a)\) \[ Q^\pi(s,a)=\mathbb E_\pi[G_t\mid s_t=s,a_t=a]. \]

含义:在状态 \(s\) 先做动作 \(a\),之后继续按策略行动,平均能得到多少回报。

优势函数 \(A(s,a)\) \[ A^\pi(s,a)=Q^\pi(s,a)-V^\pi(s). \]

含义:这个动作相对于该状态下的平均水平好多少。

  • \(A>0\):比平常好,应该增加概率。
  • \(A<0\):比平常差,应该降低概率。
  • \(A\approx 0\):表现和预期差不多,不必大改。

在 actor-critic 方法里:

  • Actor 是做动作的策略模型。
  • Critic 是估计 \(V(s)\) 或 advantage 的价值模型。

PPO 通常使用 critic;GRPO 的核心简化就是不训练 critic,直接拿“同一道题的其他回答”充当比较基准。

GRPO 的核心思路:不用价值模型预测 \(b\),而是对同一个问题生成多条回答,用它们的平均奖励作为 baseline。

这样节省 critic 的显存和训练成本,但组内归一化会引入难度与长度偏差

探索与利用:探索尝试不确定的新答案或新推理路线;利用更多使用当前已知的高成功率路线。

Entropy:常用来衡量策略的随机程度。Entropy高分布更分散,探索更多。

Rollout:让当前模型实际生成一次完整回答或完成一次环境交互。

On-policy:用当前策略或非常接近当前策略的模型生成数据,并立刻用这些数据更新模型。PPO和标准GRPO主要是on-policy

Off-policy:使用较旧的策略或其他策略生成的数据训练当前模型。可以重复利用数据。但数据分布与当前策略不同更新时容易产生偏差或不稳定。

Importance ratio

PPO 会记录数据由旧策略生成时的概率,再计算

\[ \rho_t(\theta)= \frac{\pi_\theta(a_t\mid s_t)} {\pi_{\theta_{\text{old}}}(a_t\mid s_t)}. \]

  • \(\rho_t=1\):新旧策略给这个动作的概率相同。
  • \(\rho_t>1\):新策略更偏爱这个动作。
  • \(\rho_t<1\):新策略更不愿选择这个动作。

PPO 对这个比率做裁剪,是为了防止模型根据同一批数据一次改变太多。

KL divergence:用来衡量两个概率分布相差多大。LLM 后训练常比较当前模型与参考模型:\(D_{\mathrm{KL}}(\pi_\theta\|\pi_{\mathrm{ref}}).\)

KL 小:当前模型仍接近原来的语言能力和表达习惯;KL 大:模型为了拿奖励发生了明显改变,可能开始输出怪异、重复或投机内容。

因此常把目标写成:\(\text{任务奖励}-\beta\times\text{KL 惩罚}.\) \(\beta\) 越大,模型越保守;越小,模型越敢为了任务分数偏离参考策略。

熵:衡量一个策略有多不确定;KL:离参考模型有多远。

PPO

直接策略梯度可能一步走太远,导致新策略和采样数据对应的旧策略差异过大。TRPO 用一个显式 KL 约束限制更新,但求解复杂。PPO 用更容易实现的裁剪目标近似“不要偏离旧策略太远”。

定义 token 级重要性比率:

\[ \rho_t(\theta) =\frac{\pi_\theta(o_t\mid q,o_{<t})} {\pi_{\theta_{\text{old}}}(o_t\mid q,o_{<t})}. \]

PPO 的核心目标是:

\[ L^{\text{clip}}(\theta) =\mathbb E_t\left[ \min\left( \rho_t A_t, \operatorname{clip}(\rho_t,1-\epsilon,1+\epsilon)A_t \right) \right]. \]

直觉:

  • \(A_t>0\):希望提高这个动作的概率,但不让比率无限增大。
  • \(A_t<0\):希望降低这个动作的概率,但不让比率一步暴跌。
  • min + clip 给过大的策略变化设置收益上限,减少灾难性更新。

Adventage

PPO 常训练一个 critic \(V_\psi(s_t)\),并使用 Generalized Advantage Estimation:

\[ \delta_t=r_t+\gamma V_\psi(s_{t+1})-V_\psi(s_t), \]

\[ \hat A_t^{\text{GAE}(\gamma,\lambda)} =\sum_{l=0}^{\infty}(\gamma\lambda)^l\delta_{t+l}. \]

  • \(\lambda\to 0\):更依赖一步 TD,方差低但偏差较高。
  • \(\lambda\to 1\):更接近 Monte Carlo return,偏差低但方差较高。
  • 对只有终局奖励的 LLM 场景,价值函数要给每个前缀估值,训练本身并不轻松。

常把参考模型 \(\pi_{\text{ref}}\) 作为锚点,在每个 token 加入 KL 相关惩罚:

\[ r_t =r_\phi(q,o_{\le t}) -\beta\log \frac{\pi_\theta(o_t\mid q,o_{<t})} {\pi_{\text{ref}}(o_t\mid q,o_{<t})}. \]

通常主要任务奖励在最后一个 token 才出现,前面 token 主要承担 KL 惩罚。作用是限制模型远离初始 SFT 策略,缓和 reward hacking、语言退化与训练发散。

PPO成本

典型 PPO-RLHF 训练同时涉及:

组件 作用
Policy / actor 被更新的语言模型
Old policy 计算重要性比率
Reference model 计算 KL 约束
Reward model / verifier 给回答打分
Value model / critic 估计 baseline 与 advantage

再加上on-policy rollout、训练/推理框架切换、长CoT导致的负载不均衡,系统复杂度和显存成本都很高。

GRPO

用组内相对奖励替代critic

核心步骤:对同一个问题 \(q\),从旧策略采样 \(G\) 个回答,计算\(\{o_1,\ldots,o_G\}\sim\pi_{\theta_{\text{old}}}(\cdot\mid q)\)得到奖励 \(r_1,\ldots,r_G\),再计算组内标准化 advantage: \[ \hat A_i =\frac{r_i-\operatorname{mean}(r_1,\ldots,r_G)} {\operatorname{std}(r_1,\ldots,r_G)+\varepsilon_{\text{num}}}. \]

之后仍使用类似 PPO 的 clipped ratio,并常加入相对参考模型的 KL 正则。

流程:

  • 每道题生成一组回答
  • 评分
  • 归一化得到advantage
  • 计算token级重要性比率、clipped loss和KL
  • 更新策略

偏差

题目难度偏差:中心化 \(r_i-\bar r\) 后除以同组标准差,导致组内方差小的题会被放大,题目之间的更新权重因随机 rollout 而变化,若组内全对或全错基本没有学习信号。

回答长度偏差

对正advantage,较短的正确回答每 token 获得更强的总有效推动。对负advantage,较长的错误回答因除数更大而被惩罚得更轻。这会人为偏向正确回答更短、错误回答更长。

Dr. GRPO

去掉组内标准差归一化,避免题目级难度重加权。

不再按每条回答自身长度归一化,而用全局固定常数(如最大生成长度)缩放,避免回答级长度重加权。

leave-one-out \[ b_i=\frac{1}{G-1}\sum_{j\ne i}r_j, \qquad A_i=r_i-b_i \] \(b_i\) 不含第 \(i\) 条回答自己的奖励,所以在给定问题和其他样本后,它与第 \(i\) 个动作采样独立。若使用包含自身的组均值,则梯度期望会多一个 \((G-1)/G\) 的缩放;可用 \(G/(G-1)\) 修正。

GRPO PPO 对比

项目 PPO GRPO
Baseline 学习的价值函数 \(V_\psi\) 同题多回答的组内平均奖励
Advantage 常用 GAE,随 token 变化 原始版本通常同一回答所有 token 共用一个组相对分数
Critic 需要 不需要
显存/调参 较高 较低
Rollout 在线采样 在线采样,且每题需多条回答
主要风险 critic 不准、实现复杂 组内方差、长度偏差、题目重加权

RLVR

与传统 RLHF 的差别

维度 RLHF RLVR
奖励来源 学习到的人类偏好代理 外部验证器或明确规则
典型任务 写作、对话风格、安全偏好 数学、代码、形式证明、可执行任务
扩展难点 人类标注和奖励模型偏差 验证器覆盖范围、任务生成和执行成本
主要风险 奖励模型过优化 验证漏洞、稀疏奖励、投机通过测试

Outcome reward与process reward

  • Outcome reward:只看最终答案/任务是否完成。便宜、客观、容易规模化,但credit assignment粗糙。
  • Process reward model:对中间步骤评分,可能提供更密集监督,但标注、校准和防reward hacking更难。

可靠RLVR需要同时设计:

  • 隔离且可复现的执行环境
  • 不泄漏 oracle patch / 隐藏测试
  • 对工具调用格式、未完成轨迹、超长轨迹的惩罚
  • 轨迹审计与持续更新的反投机规则