CS336 学习笔记
课程网站:https://cs336.stanford.edu/
CS336 学习笔记
Tokenization
文本的本质: Unicode字符串。
语言模型会对token序列建立概率分布。Tokenizer是完成encode和decode的类。
Token麻烦的点:
- 有时单词和空格+单词是不同的token
- 开头的单词和句中的单词token是不一样的
- 如何tokenize数字?
压缩比 = 字符串的字节数 / token数量
提升压缩比会增加效率,过度增加压缩比会造成sparsity的问题。词表数量增加-单个词出现的频率下降->数据稀疏->embedding层学习能力不充分->泛化差
如何构建分词器?
- 按照Unicode编码?词表巨大;有的字符利用率低,压缩比不理想。
- 首先将String转成字节。同上。
- 按照空格或正则来切:词表巨大,可能是无限的。
Byte Pair Encoding (BPE)
让罕见的序列拆分成多个token,同时让常见的序列拆分成一个token
基本思路:使用原始文本训练分词器
初始:每个字节都是一个token。
循环:将出现频率最高的相邻token对合并起来(原来的不会删除)。使用新的token更新整个训练语料库。重新统计pair
边界:直到词表达到设定的大小
BPE 训练算法
1 | 算法:BPE训练 |
BPE 编码算法
1 | 算法:BPE编码 |
扩展方法:
- 只遍历重要的合并规则
- 处理特殊token
- 将文本分解为块,对每个块应用分词器
展望
未来可能不需要Tokenization,而是实现从字节开始的端到端。
但是仍然需要满足:
transformer依赖某种序列的chunks运行。
Chunks应该是大小可变的。并非所有字节都同等重要。
Resource Accounting
要回答的问题:在给定的资源(计算量与内存)下,可以训练最好的模型是什么。
Memory Accounting
Tensor Memory
tensor是储存数据的基本单元。
1 | a = torch.zeros(4) # vector |
通常储存浮点数
- 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
3x = 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) |
使用的三个步骤:
给输入维度贴标签
Q → b h q d
K → b h k d
看输出
output → b h q k
寻找消失的字母
常用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 | "sum" # 求和 |
Rearrange
重新排列、拆分或合并 Tensor 的维度,但不会对数据进行求和等计算。
顺序:括号中从左到右排列,右边的维度变化最快。
1 | rearrange(x, "batch seq hidden -> (batch seq) hidden") # 合并 |
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
- 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)\)。
- Top-k Selection
纯内存与排序操作、对于当前的每一个 Query,系统会基于上一步生成的得分矩阵,利用快速排序算法(如 Radix Top-k),动态筛选出得分最高的 \(k\) 个 Token 索引(例如 \(k=2048\))。
时间复杂度:针对 \(L\) 个 Query,在 \(L\) 个候选项中找出前 \(k\) 个最大值的复杂度一般在 \(O(L^2)\) 级别。这一步主要涉及内存寻址和排序操作,几乎不占用核心的矩阵乘法算力。
- 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 |
|
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 | 时间 → |
总结
如何选择?
| 对比项 | 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 的方法是:
- 使用 μP 初始化和参数化;
- 固定模型宽深比例,再整体扩大规模;
- 在小模型上拟合最优学习率、batch 和 token/参数比例;
- 使用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
- 转换
原始数据通常是 HTML、PDF 或代码仓库,而不是干净文本。
转换需要:
- 去除导航栏、广告、页脚等模板内容;
- 尽量保留标题、段落、公式、表格和代码结构;
- 对扫描 PDF 进行 OCR;
- 避免把菜单、引用列表或乱码当成正文。
这是有损过程。解析工具的选择也可能影响最终模型能力。
- 过滤
将问题抽象为:已知少量目标数据T和海量原始数据R,从R中找出类似T的子集。
常见评分方式:
- 生成式模型:
score(x) = p_T(x); - 分类器:
score(x) = p(T | x); - 人工规则:长度、符号比例、语言概率、敏感词等。
应用包括语言识别、教育价值判断、数学或代码数据筛选、毒性过滤。
过滤并非越严格越好:严格过滤提高平均质量,却会减少数量和多样性。训练预算越大,通常越需要保留部分质量稍低但提供新信息的数据。
- 去重
重复数据会浪费计算、增加记忆和隐私风险,还可能造成训练集与评测集泄漏。
- 精确去重:对规范化文本计算哈希,只保留一个副本。
- 近似去重:将文档表示为 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:用多种配比训练小模型,拟合“配比 → 评测性能”的回归模型,再预测大模型的较优配比。
模拟重复:在小规模实验中按比例缩小各数据源,使重复程度接近最终大规模训练。
数据配比本质上也是一个需要实验验证的超参数。
后训练
后训练数据通常更像真实任务或评测题:
- 定义任务、提示和交互环境;
- 让强教师模型生成一个或多个回答;
- 使用规则、判分器或真实执行结果进行验证;
- 选取高质量轨迹进行 SFT 或强化学习。
任务可以是:
- 完全合成;
- 真实环境加合成任务;
- 真实的人类问题、GitHub PR 或错误修复记录。
OpenThoughts 的实验表明,教师是否“适合教”、任务来源和多次采样策略都很重要;更强的模型未必总是更好的教师。
总结
好数据:
- 来源可追踪
- 转换损失小
- 筛选目标明确
- 重复少
- 配比合理
- 与最终希望模型具备的能力相匹配
Mid-training、SFT 与 RLHF
框架
预训练:让模型获得广泛的语言、知识和推理能力,但它学习的是互联网文本如何延续,而不是怎样成为有用的助手。
典型训练流程:
预训练 、中期训练、监督微调(SFT)、偏好优化(PPO/DPO/RLVR)
三类阶段作用不同:
- 预训练:学习知识和通用能力。
- SFT:教模型按照指定格式调用已有能力。
- 偏好优化:在多个合理回答中,提高人类更偏好答案的概率。
InstructGPT的经典流程:
- 收集示范回答进行 SFT
- 再收集回答排序、训练奖励模型
- 最后进行强化学习
1 | 预训练模型 |
监督微调 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
当指令数据规模很大时,可以将其提前混入预训练数据:
- 在网页、代码等通用数据上预训练;
- 在后期混入高质量知识、指令和领域数据;
- 最后再进行较短的纯 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\):控制优化强度。
流程是:
- 当前模型生成回答;
- 奖励模型打分;
- PPO 根据奖励更新模型;
- 重复生成和更新。
优点是可以在线探索新回答;缺点是需要奖励模型、价值模型、在线采样和复杂的稳定性控制,工程成本较高。
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 | 海量互联网文本 |
| 强化学习概念 | 语言模型中的对应物 |
|---|---|
| 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 / 隐藏测试
- 对工具调用格式、未完成轨迹、超长轨迹的惩罚
- 轨迹审计与持续更新的反投机规则