Skip to content

36. 优化器与训练技巧


一、优化器演进链

Q1: SGD 的核心局限是什么?为什么需要更高级的优化器? ⭐⭐

答:

SGD(随机梯度下降)的更新公式为:

$$\theta_{t+1} = \theta_t - \eta \cdot g_t$$

其中 $g_t = \nabla_\theta L(\theta_t)$ 为当前 mini-batch 的梯度,$\eta$ 为学习率。

SGD 的三大核心局限:

局限具体表现影响
学习率固定所有参数共享同一学习率,无法自适应稀疏特征更新不足,密集特征更新过猛
梯度方向噪声大单 batch 梯度是真实梯度的有偏估计更新方向震荡,收敛路径曲折
鞍点和局部最优在鞍点梯度接近零,更新极慢高维空间鞍点比局部最优更常见
无法利用历史信息每步只看当前梯度,无动量概念穿越狭长山谷时反复震荡

具体例子——学习率问题:

假设特征 A 出现频率 1%,特征 B 出现频率 99%。SGD 对两者使用相同学习率:

  • 特征 A:梯度稀疏,大部分 step 不更新,偶尔大梯度导致跳跃
  • 特征 B:梯度密集,每步都更新,容易过度调整

这直接催生了自适应学习率优化器(AdaGrad、RMSProp、Adam)。


Q2: Momentum 的物理直觉是什么?完整推导其更新公式。 ⭐⭐

答:

物理直觉:

Momentum(动量法)模拟物理学中小球在山坡上滚动的场景:

  • 小球受重力沿坡面加速(梯度方向)
  • 小球具有惯性(历史梯度积累)
  • 摩擦力使速度不会无限增大(衰减系数 $\beta$)

在优化中的含义:

  • 一致的梯度方向 → 动量累积 → 加速(如沿长斜坡加速下滚)
  • 震荡的梯度方向 → 动量相互抵消 → 抑制震荡(如在山谷两壁来回时方向被平均)

完整推导:

引入速度变量 $v_t$,初始 $v_0 = 0$:

$$v_t = \beta \cdot v_{t-1} + g_t$$

$$\theta_{t+1} = \theta_t - \eta \cdot v_t$$

其中 $\beta$ 为动量系数,通常取 0.9。

展开递推:

$$v_t = \beta v_{t-1} + g_t = \beta(\beta v_{t-2} + g_{t-1}) + g_t = \sum_{i=0}^{t} \beta^{t-i} g_i$$

这是一个指数移动平均(EMA),对历史梯度做指数衰减加权:

$$v_t = g_t + \beta g_{t-1} + \beta^2 g_{t-2} + \cdots + \beta^t g_0$$

有效窗口长度: 权重之和 $\sum_{i=0}^{\infty} \beta^i = \frac{1}{1-\beta}$。当 $\beta=0.9$ 时,有效窗口约 10 步。

Nesterov Momentum 改进:

标准 Momentum 先算梯度再更新,Nesterov 先"预更新"再算梯度:

$$v_t = \beta v_{t-1} + \nabla_\theta L(\theta_t - \eta \beta v_{t-1})$$ $$\theta_{t+1} = \theta_t - \eta v_t$$

直觉:先按动量方向走一步"看看",在预判位置算梯度,有"刹车"效果,减少过冲。


Q3: AdaGrad 如何实现自适应学习率?它的问题是什么? ⭐⭐

答:

核心思想: 对每个参数维护其历史梯度平方的累积和,梯度大的参数自动降低学习率,梯度小的参数保持较高学习率。

更新公式:

$$G_t = G_{t-1} + g_t^2 \quad \text{(累积梯度平方和,逐元素)}$$

$$\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{G_t + \epsilon}} \cdot g_t$$

其中 $\epsilon \approx 10^{-8}$ 防止除零。

直觉理解:

  • 对于频繁更新的参数($G_t$ 大)→ 分母大 → 有效学习率小 → 更新步长小
  • 对于稀疏更新的参数($G_t$ 小)→ 分母小 → 有效学习率大 → 更新步长大

这完美解决了 SGD 中稀疏特征的问题。

致命问题——学习率单调递减:

$G_t$ 只增不减(累积和),导致学习率 $\frac{\eta}{\sqrt{G_t + \epsilon}}$ 单调递减

  • 训练后期,$G_t$ 越来越大,学习率趋近于零
  • 模型还没收敛就停止学习了
  • 对于非凸优化问题尤其严重,可能需要持续探索

Q4: RMSProp 如何改进 AdaGrad? ⭐⭐

答:

核心改进: 用**指数移动平均(EMA)**替代 AdaGrad 的累积和,解决学习率单调递减问题。

更新公式:

$$E[g^2]t = \gamma \cdot E[g^2] + (1-\gamma) \cdot g_t^2$$

$$\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{E[g^2]_t + \epsilon}} \cdot g_t$$

其中 $\gamma$ 为衰减率,通常取 0.9 或 0.99。

关键区别:

对比项AdaGradRMSProp
梯度平方的聚合方式累积和(只增不减)指数移动平均(可增可减)
学习率趋势单调递减可自适应调节
适用场景稀疏数据(NLP、推荐)非平稳目标、RNN
历史记忆全部历史等权近期历史权重更大

RMSProp 的局限: 只利用了梯度的二阶矩(方差信息),没有利用一阶矩(动量信息)。这直接引出了 Adam。


Q5: Adam 优化器的完整推导——一阶矩、二阶矩、偏差校正。 ⭐⭐⭐

答:

Adam = Adaptive Moment estimation,融合了 Momentum(一阶矩/动量)和 RMSProp(二阶矩/自适应学习率)。

完整算法:

第一步:计算一阶矩估计(梯度的 EMA,类似 Momentum)

$$m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t$$

第二步:计算二阶矩估计(梯度平方的 EMA,类似 RMSProp)

$$v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2$$

第三步:偏差校正(关键!)

由于 $m_0 = 0, v_0 = 0$,训练初期 $m_t, v_t$ 都偏向零。展开一阶矩:

$$m_t = (1-\beta_1) \sum_{i=1}^{t} \beta_1^{t-i} g_i$$

取期望(假设 $g_i$ 的期望为 $g$):

$$E[m_t] = (1-\beta_1) g \sum_{i=1}^{t} \beta_1^{t-i} = g \cdot (1 - \beta_1^t)$$

因此偏差校正为:

$$\hat{m}_t = \frac{m_t}{1 - \beta_1^t}, \quad \hat{v}_t = \frac{v_t}{1 - \beta_2^t}$$

当 $t$ 较大时,$\beta_1^t \to 0$,校正效果消失;当 $t$ 较小时(如 $t=1$),校正至关重要。

第四步:参数更新

$$\theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}$$

默认超参数: $\beta_1 = 0.9, \beta_2 = 0.999, \epsilon = 10^{-8}$

为什么除以 $\sqrt{\hat{v}_t}$ 是合理的?

更新步长约为 $\eta \cdot \frac{E[g]}{\sqrt{E[g^2]}}$。由柯西-施瓦茨不等式:

$$|E[g]| \leq \sqrt{E[g^2]}$$

所以步长 $\leq \eta$,且当梯度方向一致时接近 $\eta$,当梯度方向多变时自动缩小。

Adam 的直觉总结:

  • 分子 $\hat{m}_t$:平滑的梯度方向(动量,减少震荡)
  • 分母 $\sqrt{\hat{v}_t}$:梯度幅度的归一化(自适应学习率)

Q6: AdamW 和 Adam 有什么区别?为什么 LLM 训练都用 AdamW? ⭐⭐⭐

答:

Adam + L2 正则(原版)的问题:

标准做法是在损失函数中加 L2 正则:$L_{total} = L + \frac{\lambda}{2}||\theta||^2$

梯度变为:$g_t = \nabla L + \lambda \theta_t$

Adam 更新变为:

$$\theta_{t+1} = \theta_t - \eta \cdot \frac{\hat{m}_t(\nabla L + \lambda\theta_t)}{\sqrt{\hat{v}_t(\nabla L + \lambda\theta_t)} + \epsilon}$$

问题: 权重衰减项 $\lambda\theta_t$ 也被自适应学习率缩放了!这意味着:

  • 梯度大的参数,权重衰减被削弱($\sqrt{v_t}$ 大,除以大数)
  • 梯度小的参数,权重衰减被放大

L2 正则在 Adam 中的效果与 SGD 中完全不同,无法有效约束权重。

AdamW 的解耦方案:

将权重衰减从梯度计算中分离出来,直接在参数上操作:

$$m_t = \beta_1 m_{t-1} + (1-\beta_1) \nabla L \quad \text{(只用原始梯度)}$$ $$v_t = \beta_2 v_{t-1} + (1-\beta_2) (\nabla L)^2$$ $$\theta_{t+1} = \theta_t - \eta \left(\frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} + \lambda \theta_t\right)$$

或等价写法:

$$\theta_{t+1} = (1 - \eta\lambda)\theta_t - \eta \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}$$

核心区别: 权重衰减 $\lambda\theta_t$ 不经过 Adam 的自适应缩放,直接乘以学习率 $\eta$。

为什么 LLM 都用 AdamW?

  1. 解耦后权重衰减效果更稳定、可预测
  2. 与学习率调度配合更好(Loshchilov & Hutter 2019 原论文证明)
  3. 正则化效果与 SGD 的 L2 更一致
  4. 几乎所有主流 LLM(GPT、LLaMA、Qwen)都使用 AdamW

Q7: 各主流优化器更新公式对比表。 ⭐⭐

答:

优化器更新公式核心思想适用场景
SGD$\theta \leftarrow \theta - \eta g$最基本梯度下降简单凸问题
SGD+Momentum$v = \beta v + g; \theta \leftarrow \theta - \eta v$累积动量减少震荡CV任务常用
Nesterov$v = \beta v + \nabla L(\theta - \eta\beta v); \theta \leftarrow \theta - \eta v$先预判再修正理论更优
AdaGrad$G \leftarrow G + g^2; \theta \leftarrow \theta - \frac{\eta}{\sqrt{G}+\epsilon}g$累积梯度平方自适应稀疏数据
RMSProp$E[g^2] = \gamma E[g^2] + (1-\gamma)g^2$EMA替代累积和RNN
Adam一阶矩+二阶矩+偏差校正动量+自适应通用默认
AdamW解耦权重衰减Adam + 正确正则化LLM标配
LAMBAdam + 层自适应学习率大batch训练大规模预训练
Lion$\text{sign}(\beta_1 m + (1-\beta_1)g)$ 更新只保留梯度符号Google提出,显存省

显存占用对比(模型参数量 N):

优化器额外状态状态大小
SGD0
SGD+Momentum1个动量N
Adam/AdamW$m$ 和 $v$2N
8-bit Adam量化后的 $m,v$~0.5N

二、学习率调度

Q8: 学习率调度为什么重要?主流调度策略有哪些? ⭐⭐

答:

学习率调度的重要性:

  1. 训练初期: 需要较大的学习率快速下降到损失盆地
  2. 训练后期: 需要较小的学习率精细调整,在最优解附近收敛
  3. 固定学习率的两难困境: 学习率太大无法收敛到最优点,太小初期训练太慢

主流调度策略:

① Step Decay(阶梯衰减):

$$\eta_t = \eta_0 \cdot \gamma^{\lfloor t / T \rfloor}$$

每隔 $T$ 个 epoch 将学习率乘以 $\gamma$(如 0.1)。简单有效,但需要手动设定衰减时机。

② Exponential Decay(指数衰减):

$$\eta_t = \eta_0 \cdot \gamma^t$$

每步都乘以 $\gamma$,衰减更平滑。

③ Cosine Annealing(余弦退火):

$$\eta_t = \eta_{min} + \frac{1}{2}(\eta_{max} - \eta_{min})\left(1 + \cos\left(\frac{t}{T}\pi\right)\right)$$

学习率按余弦曲线从 $\eta_{max}$ 降到 $\eta_{min}$。这是 LLM 训练最常用的策略之一。

优势:

  • 前期衰减慢,充分学习
  • 中后期衰减快,精细调整
  • 平滑无突变,训练更稳定

④ Linear Decay(线性衰减):

$$\eta_t = \eta_0 \cdot (1 - \frac{t}{T})$$

GPT-2、GPT-3 使用的策略。

⑤ Warmup + Decay(预热+衰减):

先线性增大学习率到峰值,再执行衰减。LLM 训练标配。


Q9: Warmup 的作用是什么?为什么 LLM 训练必须 Warmup? ⭐⭐⭐

答:

Warmup 阶段(通常前 1%-5% 的训练步数):

学习率从 0(或很小值)线性增加到设定的峰值 $\eta_{max}$。

为什么需要 Warmup?三大原因:

① Adam 的二阶矩估计不准:

Adam 训练初期 $v_t$ 估计不准(偏差校正后仍有噪声),较大的学习率 + 不准的自适应缩放 = 灾难性的大步更新。Warmup 让模型在小学习率下先积累足够准确的 $m_t, v_t$ 估计。

② 训练初期梯度方差大:

初始权重随机,不同 batch 的梯度方向差异巨大(方差大)。此时大学习率会导致参数剧烈震荡。Warmup 让模型先找到一个相对稳定的优化方向。

③ Layer Norm / Batch Norm 的统计量不稳定:

训练初期统计量(均值、方差)尚未稳定,大步更新会放大不稳定性,导致 loss spike。

LLM 中 Warmup 的典型配置:

  • Warmup steps: 2000 步(GPT-3 用 375M tokens warmup)
  • 通常占总训练步数的 0.1%-2%
  • Warmup 后接 Cosine Decay 到峰值的 10%

不 Warmup 的后果: 训练初期可能出现 loss spike,甚至 loss 发散到 NaN。


Q10: Warmup + Cosine Decay 为什么是 LLM 训练标配? ⭐⭐

答:

完整的学习率曲线:

学习率
  |      /\
  |     /  \
  |    /    \____
  |   /         \___
  |  /              \___
  | /                   \___
  |/                        \___
  +---|---|---|---|---|---|---|--→ step
  0  warmup           cosine decay → ε

为什么这个组合成为标配?

① Warmup 解决初期不稳定(见 Q9)

② Cosine Decay 提供平滑且理论优越的衰减:

  • 起初衰减慢(余弦曲线平缓段),充分探索参数空间
  • 中间衰减加速
  • 末期衰减又放缓,在最优点附近精细调整

③ 与 AdamW 配合良好:

  • Warmup 阶段积累准确的动量估计
  • Cosine Decay 平滑降低学习率,避免阶梯衰减的突变

④ 实验验证:

  • GPT-3、LLaMA、Qwen、DeepSeek 等所有主流 LLM 都使用此策略
  • 消融实验表明比 Step Decay 和 Linear Decay 收敛更优

典型超参数配置(以 LLaMA 为例):

  • Peak LR: 3e-4(7B)→ 1.5e-4(70B),模型越大,学习率越小
  • Warmup steps: 2000
  • Min LR: Peak LR × 0.1(即 cosine 衰减到峰值的 10%)
  • Weight decay: 0.1
  • $\beta_1$ = 0.9, $\beta_2$ = 0.95(注意不是默认的 0.999)

Q11: One Cycle Policy 和 LR Range Test 是什么? ⭐⭐

答:

LR Range Test(学习率范围测试):

由 Leslie Smith 提出,用于找最优学习率。

方法:

  1. 从极小学习率(如 $10^{-7}$)开始
  2. 每个 batch 后线性增大学习率
  3. 记录每个学习率对应的 loss
  4. 画出 loss-LR 曲线

判断标准:

  • Loss 快速下降区域 → 学习率合适范围
  • Loss 最低点 → 最佳学习率
  • Loss 开始上升 → 学习率过大
Loss
  |\
  | \
  |  \___
  |      \___
  |          \____
  |               \/
  +---|---|---|---|---→ LR
  1e-7        1e-2
      ↑最佳LR区间↑

One Cycle Policy:

一个完整的训练周期内的学习率调度:

  1. 上升阶段(~45%训练): 学习率从 $\eta_{min}$ 线性增加到 $\eta_{max}$
  2. 下降阶段(~45%训练): 学习率从 $\eta_{max}$ 余弦衰减到 $\eta_{min}$
  3. 微调阶段(~10%训练): 学习率继续衰减到 $\eta_{min}/10$

优势:

  • 大学习率起到正则化效果(类似 dropout 的噪声注入)
  • 小学习率精细调整
  • 训练速度更快,通常可用更少 epoch 达到同等效果
  • 可以通过 LR Range Test 找到 $\eta_{max}$

在 LLM 中的适用性: One Cycle 主要用于 CV 任务的 fine-tuning。LLM 预训练通常用 Cosine Decay,但在 fine-tuning 阶段 One Cycle 也很有效。


三、梯度裁剪

Q12: 按值裁剪 vs 按范数裁剪的区别?为什么 LLM 训练必须裁剪? ⭐⭐

答:

按值裁剪(Gradient Clipping by Value):

将每个梯度元素裁剪到 $[-c, c]$ 范围内:

$$g_i = \text{clip}(g_i, -c, c) = \max(-c, \min(c, g_i))$$

按范数裁剪(Gradient Clipping by Norm):

计算梯度的全局范数,若超过阈值则等比缩放:

$$g = \begin{cases} g & \text{if } ||g|| \leq c \ \frac{c}{||g||} \cdot g & \text{if } ||g|| > c \end{cases}$$

核心区别:

对比项按值裁剪按范数裁剪
操作对象每个元素独立整体梯度向量
梯度方向改变(不同元素裁剪比例不同)不变(等比缩放)
推荐场景简单场景LLM 训练
PyTorch APItorch.clamptorch.nn.utils.clip_grad_norm_

为什么按范数裁剪更好?

按值裁剪会改变梯度方向!假设梯度 $g = [10, 0.5]$,阈值 $c=5$:

  • 按值裁剪后:$[5, 0.5]$,方向变了
  • 按范数裁剪后:$[5, 0.25]$,方向不变

LLM 为什么必须梯度裁剪?

  1. 梯度爆炸: Transformer 深层网络中梯度可能指数级增长,尤其在长序列上
  2. Loss Spike: 某些 batch 可能产生异常大的梯度,不裁剪会导致参数突变
  3. 训练稳定性: 裁剪提供了"安全网",即使遇到异常梯度也不会破坏训练
  4. 经验法则: 几乎所有 LLM 训练都设置 max_grad_norm = 1.0

PyTorch 使用方式:

python
# 训练循环中
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
optimizer.step()

四、混合精度训练

Q13: FP32、FP16、BF16 的数值范围和精度对比。 ⭐⭐

答:

格式总位数符号位指数位尾数位数值范围精度
FP32321823$\pm 3.4 \times 10^{38}$$\sim 10^{-7}$
FP16161510$\pm 65504$$\sim 10^{-3}$
BF1616187$\pm 3.4 \times 10^{38}$$\sim 10^{-2}$

关键差异:

  • FP16 vs BF16: BF16 复制了 FP32 的指数位(8位),因此数值范围与 FP32 相同,但精度更低(尾数只有7位)
  • FP16 的问题: 数值范围太小,梯度/激活值容易溢出(上溢)或下溢(下溢)
  • BF16 的优势: 范围大,不会溢出,几乎不需要 Loss Scaling

为什么 BF16 成为 LLM 训练标配?

  1. 数值范围: 与 FP32 相同,不会溢出/下溢
  2. 硬件支持: A100、H100 等 GPU 对 BF16 有原生加速(Tensor Core)
  3. 简化训练: 不需要 Loss Scaling(FP16 必须),代码更简洁
  4. 精度足够: LLM 训练中 BF16 精度损失对最终性能影响极小

Q14: 混合精度训练原理是什么?Loss Scaling 如何工作? ⭐⭐⭐

答:

混合精度训练的三个核心技术:

① FP32 主权重(Master Weights):

维护一份 FP32 的模型权重副本,优化器更新在 FP32 精度下进行:

  • 前向/反向传播:FP16/BF16 权重 → 计算速度快
  • 参数更新:FP32 权重 → 精度有保障
FP32 Master Weights ──cast──→ FP16 Weights ──前向/反向──→ FP16 Gradients
                                    ↑                              |
                                    └── FP32 更新 ←──cast── FP32 Gradients

② Loss Scaling(FP16 必须,BF16 通常不需要):

问题: FP16 的梯度值往往在 $[0, 2^{-24}]$ 范围内,低于 FP16 可表示的最小正规数 $2^{-14}$,导致下溢为零。

解决方案: 在反向传播前将 loss 乘以一个缩放因子 $S$(如 1024),使梯度值乘以 $S$ 后进入 FP16 可表示范围,参数更新前再除以 $S$。

$$L' = S \cdot L \quad \Rightarrow \quad \nabla L' = S \cdot \nabla L \quad \Rightarrow \quad g = \nabla L' / S$$

动态 Loss Scaling:

  • 初始缩放因子较大(如 $2^{16}$)
  • 如果连续 N 步没有出现 inf/nan → 缩放因子翻倍
  • 如果出现 inf/nan → 缩放因子减半,跳过本步更新

③ 算子精度控制:

某些运算对精度敏感,需要在 FP32 下执行:

  • Softmax(指数运算)
  • Layer Normalization
  • Loss 计算

PyTorch AMP 自动处理这些。

PyTorch AMP 使用:

python
from torch.cuda.amp import autocast, GradScaler

scaler = GradScaler()
for data, target in dataloader:
    optimizer.zero_grad()
    with autocast(dtype=torch.bfloat16):  # 或 torch.float16
        output = model(data)
        loss = criterion(output, target)
    scaler.scale(loss).backward()
    scaler.unscale_(optimizer)
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    scaler.step(optimizer)
    scaler.update()

显存节省:

组件FP32混合精度
模型参数4N2N + 4N(master) = 6N ≈ 1.5x
梯度4N2N = 0.5x
优化器状态(Adam)8N8N
激活值FP32FP16/BF16 ≈ 0.5x
总计~16N+~12N+ 激活值减半

混合精度主要节省的是激活值显存(通常是大头),这在 batch size 较大时效果显著。


五、分布式训练

Q15: DP 和 DDP 的原理与区别? ⭐⭐

答:

DP(DataParallel)—— 单进程多线程:

主 GPU (GPU 0):
  1. 将模型复制到所有 GPU
  2. 将 mini-batch 分割到各 GPU
  3. 各 GPU 前向传播
  4. 收集所有输出到 GPU 0
  5. GPU 0 计算 loss 并反向传播
  6. 同步梯度到所有 GPU
  7. 更新参数

DP 的问题:

  • GIL 限制: Python 多线程受 GIL 限制,无法真正并行
  • GPU 0 过载: 汇总输出、计算 loss 都在 GPU 0 上,GPU 0 显存和计算都是瓶颈
  • 通信效率低: 参数服务器模式,通信量随 GPU 数增加线性增长
  • 不支持多机

DDP(DistributedDataParallel)—— 多进程:

每个 GPU 一个进程:
  1. 每个进程持有完整模型副本
  2. 每个进程处理独立的 mini-batch(DistributedSampler 确保不重叠)
  3. 各进程独立前向+反向传播
  4. AllReduce 同步梯度(Ring AllReduce)
  5. 各进程独立更新参数(梯度相同 → 参数保持同步)

DDP 优势:

  • 无 GIL 限制,真正并行
  • 无主 GPU 瓶颈
  • Ring AllReduce 通信量恒定,不随 GPU 数增加
  • 支持多机多卡
对比项DPDDP
并行方式单进程多线程多进程
GIL受限不受限
通信模式参数服务器Ring AllReduce
负载均衡不均衡均衡
多机支持不支持支持
效率
推荐不推荐推荐

Q16: DeepSpeed ZeRO 的三个 Stage 分别做了什么? ⭐⭐⭐

答:

问题背景: 训练大模型时,每张卡需要存储:

  • 模型参数:$2\Phi$(FP16,$\Phi$ 为参数量)
  • 梯度:$2\Phi$
  • 优化器状态(Adam):$12\Phi$(FP32 参数副本 4 + 一阶矩 4 + 二阶矩 4)
  • 总计: $16\Phi$ 字节

以 LLaMA-70B 为例($\Phi = 70 \times 10^9$):

  • 单卡需 $16 \times 70 \times 10^9 \approx 1120$ GB 显存
  • 即使用 80GB A100,也需要至少 14 张卡才装得下

ZeRO 通过分片(Partitioning)消除冗余:

Stage 1:优化器状态分片($P_{os}$)

传统: 每张卡存储 4Φ(参数) + 2Φ(梯度) + 12Φ(优化器) = 16Φ
Stage 1: 4Φ + 2Φ + 12Φ/N = 6Φ + 12Φ/N

N=64: 6Φ + 0.19Φ ≈ 6.2Φ(节省 61%)

每张卡只存储 $1/N$ 的优化器状态。更新参数时,AllGather 收集所有分片。

Stage 2:优化器状态 + 梯度分片($P_{os+g}$)

Stage 2: 4Φ + 2Φ/N + 12Φ/N = 4Φ + 14Φ/N

N=64: 4Φ + 0.22Φ ≈ 4.2Φ(节省 74%)

每张卡只存储它负责更新的那些参数对应的梯度。反向传播后 Reduce-Scatter 汇总梯度。

Stage 3:优化器状态 + 梯度 + 参数分片($P_{os+g+p}$)

Stage 3: 4Φ/N + 2Φ/N + 12Φ/N = 16Φ/N

N=64: 0.25Φ(节省 98.4%)

每张卡只存储 $1/N$ 的参数。前向和反向传播时 AllGather 收集所需参数。

Stage分片内容每卡显存通信开销
0(DDP)16Φ
1优化器状态6Φ + 10Φ/N
2优化器+梯度4Φ + 12Φ/N
3优化器+梯度+参数16Φ/N

Stage 3 通信量增加约 50%,但显存节省极大,是训练超大模型的关键。


Q17: 什么是 3D 并行?70B 模型需要多少 GPU? ⭐⭐⭐

答:

3D 并行 = 数据并行 + 张量并行 + 流水线并行

① 数据并行(DP): 不同 GPU 处理不同数据,梯度同步

  • 适合:模型放得下单卡时
  • 扩展:增加数据吞吐量

② 张量并行(TP,Tensor Parallelism): 将单个算子(矩阵乘法)切分到多卡

Y = XW,将 W 按列切分:
GPU 0: Y_0 = X @ W_0  (W 的前半列)
GPU 1: Y_1 = X @ W_1  (W 的后半列)
Y = concat(Y_0, Y_1)
  • Megatron-LM 的做法:MLP 和 Attention 都可以切
  • 适合:同一机器内的 GPU(需要高带宽 NVLink)
  • 通常 2-8 路

③ 流水线并行(PP,Pipeline Parallelism): 将模型按层切分

GPU 0: Layer 0-7
GPU 1: Layer 8-15
GPU 2: Layer 16-23
GPU 3: Layer 24-31
  • 按层分配到不同 GPU,前一个 stage 输出传给下一个
  • 引入 pipeline bubble(流水线气泡),需 micro-batch 填充
  • 适合:跨机器(通信量小,只传激活值和梯度)

3D 并行示例(Megatron-LM 风格):

总 GPU 数 = TP × PP × DP
例: 64 GPU = TP(8) × PP(4) × DP(2)

                  DP 维度
                  ┌─────────────────────────┐
                  │  DP replica 0  │  DP replica 1  │
PP 维度 ┌────────┼────────────────┼────────────────┤
│ Stage 0 │ TP=8 │ GPU 0-7        │ GPU 32-39      │
│ Stage 1 │ TP=8 │ GPU 8-15       │ GPU 40-47      │
│ Stage 2 │ TP=8 │ GPU 16-23      │ GPU 48-55      │
│ Stage 3 │ TP=8 │ GPU 24-31      │ GPU 56-63      │
└─────────┴──────┴────────────────┴────────────────┘

70B 模型需要多少 GPU?

以 LLaMA-70B(70B 参数)为例:

  • 模型参数(FP16):140 GB
  • 优化器状态(Adam FP32):840 GB
  • 梯度(FP16):140 GB
  • 总计约 1120 GB(不含激活值)

配置方案:

方案配置GPU 数说明
ZeRO-3Stage 3 分片8×A100-80G勉强可行,batch size 受限
TP+PPTP=8, PP=1080×A100-80G常用方案
3D 并行TP=8, PP=4, DP=8256×A100-80G训练速度快

实际生产中,70B 模型训练通常使用 128-1024 张 A100/H100,取决于训练时间和预算。


Q18: AllReduce 和 AllGather 通信原语的区别? ⭐⭐

答:

AllReduce:

所有节点各持有数据的一部分,AllReduce 后所有节点得到相同的聚合结果(通常是求和)。

节点0: [a]  ──→  [a+b+c+d]  AllReduce = Reduce + Broadcast
节点1: [b]  ──→  [a+b+c+d]
节点2: [c]  ──→  [a+b+c+d]
节点3: [d]  ──→  [a+b+c+d]

DDP 中的作用: 所有 GPU 的梯度求和/平均,确保所有 GPU 持有相同梯度。

Ring AllReduce 优化:

通信量 = 2 × (N-1)/N × 数据量 ≈ 2 × 数据量(与GPU数无关!)

分两个阶段:Reduce-Scatter(每个节点得到部分结果的聚合)→ AllGather(广播完整结果)。

AllGather:

所有节点各持有一部分数据,AllGather 后所有节点得到完整的数据。

节点0: [a]  ──→  [a, b, c, d]
节点1: [b]  ──→  [a, b, c, d]
节点2: [c]  ──→  [a, b, c, d]
节点3: [d]  ──→  [a, b, c, d]

ZeRO 中的作用: 前向传播前,AllGather 收集所有分片参数。

Reduce-Scatter:

节点0: [a0, a1, a2, a3]  ──→  [a0+b0+c0+d0]
节点1: [b0, b1, b2, b3]  ──→  [a1+b1+c1+d1]
节点2: [c0, c1, c2, c3]  ──→  [a2+b2+c2+d2]
节点3: [d0, d1, d2, d3]  ──→  [a3+b3+c3+d3]

AllReduce = Reduce-Scatter + AllGather


六、数据增强与数据处理

Q19: NLP 和 CV 领域常见的数据增强方法有哪些? ⭐⭐

答:

NLP 数据增强:

方法描述适用场景
同义词替换随机替换非停用词为同义词文本分类
随机插入在随机位置插入随机同义词文本分类
随机交换交换句子中两个词的位置文本分类
随机删除以概率 p 删除句子中的词文本分类
回译(Back Translation)翻译到另一语言再翻回来文本分类、QA
EDA(Easy Data Augmentation)以上四种的组合文本分类
LLM 生成用 ChatGPT/GPT-4 生成改写各种 NLP 任务
Mixup在嵌入空间做线性插值文本分类
对抗训练在嵌入上加小扰动提升鲁棒性

CV 数据增强:

方法描述
Random Crop + Resize随机裁剪后缩放回原尺寸
Random Horizontal Flip随机水平翻转
Color Jitter颜色抖动(亮度、对比度、饱和度)
Random Erasing / Cutout随机遮挡部分区域
Mixup两张图的像素加权混合
CutMix将一张图的区域粘贴到另一张图
AutoAugment用强化学习自动搜索增强策略
RandAugment随机选择增强操作和强度

Q20: 数据清洗和去重为什么重要? ⭐⭐

答:

数据质量 > 数据数量: 研究反复证明,精心清洗的数据比大量脏数据效果更好。

去重的重要性:

  1. 训练集-测试集泄漏: 训练数据中包含测试集数据,导致评估指标虚高
  2. 重复数据的影响:
    • 模型过度记忆重复样本,降低泛化能力
    • 某些 pattern 被过度强化,产生偏见
    • 浪费算力(相同数据反复训练)

去重方法:

方法粒度优点缺点
精确去重(Hash)文档级简单快速无法处理近似重复
MinHash + LSH文档/段落可检测近似重复,可扩展有假阳性
N-gram 去重子串级细粒度计算量大
Embedding 去重语义级检测语义相似计算成本高

LLM 预训练数据清洗流程:

原始数据 → URL过滤 → 语言检测 → 内容过滤(色情/暴力)
         → 规则清洗(HTML标签/乱码) → 去重(MinHash)
         → 质量过滤(困惑度/分类器) → 最终数据集

标签噪声:

  • 来源: 众包标注误差、自动标注错误、模糊样本
  • 影响: 小噪声可起正则化作用;大噪声严重降低模型性能
  • 处理: 标签平滑(Label Smoothing)、置信度学习(Confident Learning)、噪声感知训练

七、过拟合与欠拟合

Q21: 过拟合的 7 种解决方案是什么? ⭐⭐

答:

序号方法原理实施建议
1增加数据量更多数据覆盖更多分布数据增强、数据合成
2正则化(L1/L2/Weight Decay)限制权重大小L2/Weight Decay 最常用,λ=0.01-0.1
3Dropout随机丢弃神经元,集成效应一般 p=0.1-0.5,Transformer 中常用
4Early Stopping在验证集 loss 开始上升时停止用 validation loss 的 patience
5减小模型容量减少参数量减层数/宽度,但 LLM 中不常用
6数据增强人为增加数据多样性NLP: 回译、LLM生成;CV: 翻转裁剪
7Label Smoothing软化标签,降低过度自信$\epsilon=0.1$,防止 logit 趋向无穷

额外方法:

  • Batch Normalization: 有轻度正则化效果(每个 batch 统计量有噪声)
  • Mixup / CutMix: 样本混合增加数据多样性
  • R-Drop: 两次前向的 KL 散度作为正则项

Q22: 欠拟合的 5 种解决方案是什么? ⭐

答:

序号方法原理
1增加模型复杂度更多层、更多参数、更大宽度
2减小正则化强度降低 λ、减少 Dropout
3增加训练时间更多 epoch/step
4调整学习率可能太小或太大
5特征工程提供更有信息量的输入特征

Q23: 早停法的实现和学习曲线分析? ⭐⭐

答:

早停法(Early Stopping)实现:

python
class EarlyStopping:
    def __init__(self, patience=5, min_delta=0.001):
        self.patience = patience
        self.min_delta = min_delta
        self.counter = 0
        self.best_loss = float('inf')
    
    def __call__(self, val_loss):
        if val_loss < self.best_loss - self.min_delta:
            self.best_loss = val_loss
            self.counter = 0
            return False  # 继续训练
        else:
            self.counter += 1
            if self.counter >= self.patience:
                return True  # 停止训练
            return False

学习曲线分析:

Loss
  |  \
  |   \  训练loss        过拟合区间
  |    \______________  ───────↗────
  |     \    ↗────────── 验证loss
  |      \  /
  |       \/
  |       ↑
  |   最佳停止点
  +──────────────────────→ Epoch

常见模式诊断:

训练loss验证loss诊断解决方案
欠拟合增大模型/调学习率
过拟合正则化/数据增强/早停
低且持平良好继续训练
波动大波动大学习率过大/数据噪声降学习率/增大batch

Q24: 大模型会过拟合吗? ⭐⭐⭐

答:

简短回答: 大模型在预训练阶段通常不会过拟合,但在微调阶段会过拟合。

预训练阶段不过拟合的原因:

  1. 数据量巨大: LLaMA-70B 在 1T+ tokens 上训练,远超参数量
  2. Chinchilla Scaling Law: 计算最优配置要求参数量和数据量同比例增长
  3. 正则化效果: AdamW 的权重衰减、Dropout(某些模型)、梯度裁剪都有正则化作用
  4. 隐式正则化: SGD/Adam 的噪声本身就有正则化效果

Chinchilla Scaling Law:

最优训练 tokens 数 ≈ 20 × 模型参数量

模型参数量最优训练 tokens
1B10亿200亿
7B70亿1400亿
70B700亿1.4万亿

微调阶段过拟合:

微调数据通常只有几千到几万条,对于 7B+ 模型:

  • 模型参数量远大于数据量 → 严重过拟合风险
  • 表现:训练 loss 快速下降但评估指标变差

解决方案:

  • LoRA:只微调少量参数,降低过拟合风险
  • 学习率调小:1e-5 ~ 5e-5
  • Early Stopping:监控验证集指标
  • 数据增强:用 LLM 生成更多训练数据

八、训练稳定性

Q25: Loss Spike 的原因和处理方法? ⭐⭐⭐

答:

Loss Spike: 训练过程中 loss 突然急剧增大,然后可能恢复也可能发散。

常见原因:

原因机制频率
异常数据某些 batch 包含异常样本(极长序列、乱码、重复)
梯度爆炸某些层的梯度突然很大
学习率过大参数更新步长超过损失盆地边界
数值溢出FP16 激活值溢出(inf/nan)
注意力坍塌Attention 矩阵退化为近似 one-hot低但严重

处理方法:

① 梯度裁剪(最重要的防线):

python
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)

② 跳过异常 batch:

python
if torch.isnan(loss) or torch.isinf(loss) or loss > threshold:
    optimizer.zero_grad()
    continue  # 跳过此 batch

③ 数据清洗: 移除异常样本(极长文本、乱码、高重复率文本)

④ 降低学习率: 特别是 warmup 结束后的初始学习率

⑤ 从 checkpoint 恢复: loss spike 后回滚到之前的 checkpoint,跳过问题数据后继续

⑥ 使用 BF16 替代 FP16: BF16 数值范围大,不易溢出


Q26: 梯度累积和梯度检查点的作用与实现? ⭐⭐

答:

梯度累积(Gradient Accumulation):

问题: 显存不足以容纳大 batch size,但大 batch 训练更稳定。

解决: 将大 batch 拆成多个 micro-batch,累积梯度后再更新。

python
accumulation_steps = 4  # 逻辑 batch = 4 × micro-batch

for i, (data, target) in enumerate(dataloader):
    loss = model(data, target) / accumulation_steps
    loss.backward()  # 梯度累积,不立即更新
    
    if (i + 1) % accumulation_steps == 0:
        optimizer.step()  # 累积 N 步后才更新
        optimizer.zero_grad()

等价性: 梯度是线性的,$\nabla L = \frac{1}{N}\sum_{i=1}^{N} \nabla L_i$,累积后再除以 N 与大 batch 等价。

注意: loss 要除以 accumulation_steps,否则梯度是正确值的 N 倍。

梯度检查点(Gradient Checkpointing / Activation Checkpointing):

问题: 反向传播需要前向传播保存的中间激活值,占用大量显存。

原理: 前向传播时不保存中间激活值,反向传播时重新计算

标准:   前向 → 保存所有激活 → 反向(用保存的激活)
检查点: 前向 → 只保存检查点激活 → 反向 → 重算中间激活 → 反向

代价: 计算量增加约 33%(前向要重算一次),但显存从 $O(L)$ 降为 $O(\sqrt{L})$,其中 $L$ 为层数。

python
from torch.utils.checkpoint import checkpoint

class TransformerBlock(nn.Module):
    def forward(self, x):
        # 普通模式
        # return self.attention(x) + self.ffn(x)
        # 检查点模式
        return checkpoint(self._forward, x)
    
    def _forward(self, x):
        return self.attention(x) + self.ffn(x)

Q27: Loss 不下降如何排查? ⭐⭐

答:

系统性排查清单:

Loss 不下降
├── 1. Loss 是否是 NaN/Inf?
│   ├── 是 → 学习率过大、数据异常、数值溢出
│   └── 否 → 继续
├── 2. 过拟合一个小 batch 测试
│   ├── 无法过拟合 → 模型/优化器/数据有 bug
│   └── 可以过拟合 → 继续
├── 3. 检查学习率
│   ├── 太大 → loss 震荡不下降
│   ├── 太小 → loss 下降极慢
│   └── 正确 → 继续
├── 4. 检查数据
│   ├── 标签是否正确?
│   ├── 数据加载是否正确(shuffle、tokenization)?
│   └── 数据质量如何?
├── 5. 检查模型
│   ├── 梯度是否流动?(梯度监控)
│   ├── 权重是否更新?(对比前后参数)
│   └── 模型结构是否有 bug?
└── 6. 检查训练配置
    ├── 权重衰减是否过大?
    ├── Dropout 是否过大?
    └── Batch size 是否合理?

关键调试技巧:

python
# 1. 过拟合测试:用少量数据,看 loss 能否降到 0
for _ in range(1000):
    loss = model(small_batch)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()
# 如果 loss 不下降,说明有 bug

# 2. 梯度监控
for name, param in model.named_parameters():
    if param.grad is not None:
        print(f"{name}: grad_norm={param.grad.norm().item()}")

Q28: NaN 出现的常见原因和排查方法? ⭐⭐

答:

NaN 的来源:

来源原因位置
梯度爆炸梯度值溢出 FP16/FP32 范围反向传播
Log 输入零/负值log(0) = -infLoss 计算(如 cross entropy)
除以零1/0 或 softmax 中数值问题各种操作
指数溢出exp(x) 中 x 过大softmax、attention
数据本身输入数据含 NaN/Inf数据加载
权重更新后参数变成 NaN 后传播参数更新

排查方法:

python
# 1. 检查数据
assert not torch.isnan(data).any()
assert not torch.isinf(data).any()

# 2. 开启 anomaly detection
torch.autograd.set_detect_anomaly(True)  # 会打印 NaN 出现的精确位置

# 3. 逐层检查输出
def hook_fn(module, input, output):
    if isinstance(output, torch.Tensor):
        if torch.isnan(output).any():
            print(f"NaN in {module.__class__.__name__}")

for module in model.modules():
    module.register_forward_hook(hook_fn)

# 4. 降低学习率 / 增加 warmup / 减小初始权重

预防措施:

  • 使用 torch.nn.utils.clip_grad_norm_ 裁剪梯度
  • 使用 BF16 而非 FP16(范围更大)
  • Layer Normalization 稳定训练
  • 合理的权重初始化(Xavier/He)
  • 适当的 warmup

九、数学基础补充

Q29: 线性代数核心知识——矩阵运算在深度学习中的应用。 ⭐⭐

答:

矩阵乘法在神经网络中的核心地位:

深度学习的前向传播本质上是一系列矩阵乘法和非线性变换:

$$y = \sigma(Wx + b)$$

关键矩阵运算:

① 矩阵乘法维度分析:

Linear层: Y = XW + b
  X: (batch, d_in)  ×  W: (d_in, d_out)  →  Y: (batch, d_out)

Attention: QK^T / √d_k
  Q: (n, d_k)  ×  K^T: (d_k, n)  →  (n, n)

② 矩阵分解(SVD):

$$W = U \Sigma V^T$$

  • $U, V$ 为正交矩阵,$\Sigma$ 为奇异值对角矩阵
  • 应用: 低秩近似(LoRA 的数学基础)、模型压缩
  • 保留前 $r$ 个最大奇异值:$W \approx U_r \Sigma_r V_r^T$

③ 特征值与特征向量:

$$Av = \lambda v$$

  • PCA 的数学基础: 协方差矩阵的特征向量是主成分方向
  • 理解训练动态: Hessian 矩阵的特征值决定优化景观

④ 矩阵范数:

  • Frobenius 范数:$||W||F = \sqrt{\sum W_{ij}^2}$(权重衰减的目标)
  • 谱范数:$||W||2 = \sigma(W)$(谱归一化、GAN 训练稳定性)

Q30: 概率论基础——贝叶斯、MLE、KL 散度。 ⭐⭐⭐

答:

贝叶斯定理:

$$P(\theta|D) = \frac{P(D|\theta)P(\theta)}{P(D)}$$

  • 后验 $\propto$ 似然 $\times$ 先验
  • 在 ML 中的体现: L2 正则化 = 高斯先验下的 MAP 估计

MLE(最大似然估计):

$$\theta^* = \arg\max_\theta P(D|\theta) = \arg\max_\theta \sum_i \log P(y_i|x_i, \theta)$$

MLE 等价于最小化负对数似然(即交叉熵损失)。

KL 散度(Kullback-Leibler Divergence):

$$D_{KL}(P||Q) = \sum_x P(x) \log \frac{P(x)}{Q(x)} = E_P\left[\log \frac{P(x)}{Q(x)}\right]$$

性质:

  • $D_{KL}(P||Q) \geq 0$(非负性,Gibbs 不等式)
  • $D_{KL}(P||Q) = 0 \iff P = Q$
  • 不对称: $D_{KL}(P||Q) \neq D_{KL}(Q||P)$

在深度学习中的应用:

场景KL 散度的角色
VAE$D_{KL}(q(z
知识蒸馏$D_{KL}(\text
RLHF$D_{KL}(\pi_{\text{old}}
语言模型训练目标等价于最小化与真实分布的 KL 散度

Forward KL vs Reverse KL:

  • Forward KL: $D_{KL}(P||Q)$ — Q 需要覆盖 P 的所有模式(zero-avoiding),倾向于生成多样但不精确的结果
  • Reverse KL: $D_{KL}(Q||P)$ — Q 集中在 P 的某个模式上(zero-forcing),倾向于生成精确但单一的结果

Q31: 信息论基础——熵、交叉熵、互信息。 ⭐⭐

答:

信息熵(Entropy):

$$H(P) = -\sum_x P(x) \log P(x)$$

  • 衡量随机变量的不确定性
  • 均匀分布熵最大(最不确定),确定性分布熵为 0
  • 二元分类中:$H = -p\log p - (1-p)\log(1-p)$

交叉熵(Cross-Entropy):

$$H(P, Q) = -\sum_x P(x) \log Q(x)$$

  • 用分布 Q 编码来自分布 P 的数据所需的平均编码长度
  • 深度学习中分类任务的损失函数

三者关系:

$$H(P, Q) = H(P) + D_{KL}(P||Q)$$

因为 $H(P)$ 是常数(真实标签分布固定),最小化交叉熵等价于最小化 KL 散度。

互信息(Mutual Information):

$$I(X;Y) = H(X) - H(X|Y) = D_{KL}(P(X,Y)||P(X)P(Y))$$

  • 衡量 X 和 Y 之间的依赖关系
  • $I(X;Y) = 0$ 当且仅当 X 和 Y 独立
  • InfoNCE loss(对比学习)本质上是互信息的下界

Q32: Softmax 的数值稳定性问题及解决方案。 ⭐⭐

答:

Softmax 定义:

$$\text{softmax}(z_i) = \frac{e^{z_i}}{\sum_j e^{z_j}}$$

数值问题:

  1. 上溢: 若 $z_i$ 很大(如 $z_i = 1000$),$e^{1000}$ 溢出 FP32 范围 → inf
  2. 下溢: 若 $z_i$ 很小(如 $z_i = -1000$),$e^{-1000}$ 下溢为 0 → 分子/分母为 0
  3. inf/inf = NaN

解决方案——减去最大值:

令 $c = \max_j z_j$,则:

$$\text{softmax}(z_i) = \frac{e^{z_i - c}}{\sum_j e^{z_j - c}}$$

数学上完全等价(分子分母同除以 $e^c$),但数值上:

  • $z_i - c \leq 0$,所以 $e^{z_i-c} \leq 1$,不会上溢
  • 至少有一个 $e^{z_i-c} = e^0 = 1$,分母 $\geq 1$,不会除以零

PyTorch 中已内置此优化:

python
# PyTorch 内部实现(简化版)
def stable_softmax(x):
    x_max = x.max(dim=-1, keepdim=True).values
    exp_x = torch.exp(x - x_max)
    return exp_x / exp_x.sum(dim=-1, keepdim=True)

Log-Softmax 的数值稳定性:

直接计算 $\log(\text{softmax}(z_i))$ 可能因为 softmax 结果接近 0 而得到 $-\infty$。

更好的方法:

$$\log \text{softmax}(z_i) = z_i - c - \log\sum_j e^{z_j - c}$$

使用 logsumexp:$\log\sum_j e^{z_j - c}$ 可用 torch.logsumexp(z - c, dim=-1) 稳定计算。


Q33: 交叉熵损失与 KL 散度的关系。 ⭐⭐

答:

交叉熵定义:

$$H(P, Q) = -\sum_x P(x) \log Q(x)$$

KL 散度定义:

$$D_{KL}(P||Q) = \sum_x P(x) \log \frac{P(x)}{Q(x)} = -H(P) + H(P, Q)$$

因此:

$$H(P, Q) = H(P) + D_{KL}(P||Q)$$

在分类任务中:

  • 真实分布 P 是 one-hot:$P = [0, 0, ..., 1, ..., 0]$
  • 真实分布的熵 $H(P) = 0$(确定性分布)
  • 所以 $H(P, Q) = D_{KL}(P||Q)$

结论:对于 one-hot 标签的分类任务,最小化交叉熵等价于最小化 KL 散度。

为什么用交叉熵而不是直接用 KL 散度?

  1. 交叉熵的梯度更简洁:$\frac{\partial H}{\partial z_i} = Q_i - P_i$(配合 softmax)
  2. $H(P)$ 是常数,不影响优化
  3. 计算上更稳定

梯度推导:

$$\frac{\partial}{\partial z_i} \left[-\sum_k P_k \log Q_k\right] = Q_i - P_i$$

这个梯度极其优美——预测值减去真实值,正是 softmax 输出与 one-hot 标签的误差。


十、优化器与训练面试高频题汇总

Q34: 15 道高频面试题及标准答案。 ⭐⭐⭐

答:


题1:请完整推导 Adam 优化器的更新公式,解释偏差校正的必要性。

标准答案: 参见 Q5。关键点:一阶矩 $m_t = \beta_1 m_{t-1} + (1-\beta_1)g_t$,二阶矩 $v_t = \beta_2 v_{t-1} + (1-\beta_2)g_t^2$。偏差校正 $\hat{m}_t = m_t/(1-\beta_1^t)$ 解决了训练初期 $m_t, v_t$ 偏向零的问题。展开期望 $E[m_t] = g(1-\beta_1^t)$,除以 $(1-\beta_1^t)$ 消除偏差。


题2:AdamW 和 Adam + L2 正则化有什么本质区别?

标准答案: Adam + L2 中,权重衰减项 $\lambda\theta$ 被自适应学习率 $\sqrt{v_t}$ 缩放,导致不同参数的有效衰减强度不同。AdamW 解耦了权重衰减:$\theta_{t+1} = (1-\eta\lambda)\theta_t - \eta\hat{m}_t/(\sqrt{\hat{v}_t}+\epsilon)$,权重衰减直接作用于参数,不经过自适应缩放。


题3:为什么 LLM 训练必须使用 Warmup?

标准答案: 三个原因:①Adam 初期二阶矩估计不准,大 LR 导致灾难性更新;②初始权重随机,梯度方差大,大 LR 导致震荡;③Layer Norm 等统计量初期不稳定。典型 Warmup 占总步数 0.1%-2%。


题4:比较 FP16 和 BF16 的优劣,LLM 训练为什么更倾向 BF16?

标准答案: FP16 范围小(±65504)需要 Loss Scaling,BF16 范围与 FP32 相同(±3.4×10³⁸)不需要。BF16 精度低(7位尾数 vs 10位),但 LLM 训练对精度要求不高。BF16 在 A100+ 有硬件原生支持。


题5:DeepSpeed ZeRO Stage 1/2/3 分别分片了什么?Stage 3 通信量增加多少?

标准答案: Stage 1 分片优化器状态(节省约 60%显存),Stage 2 加分片梯度(节省约 74%),Stage 3 加分片参数(节省约 98%)。Stage 3 需要额外的 AllGather 操作,通信量约增加 50%。


题6:梯度裁剪按值和按范数的区别?为什么按范数更好?

标准答案: 按值裁剪逐元素 clip 到 [-c,c],会改变梯度方向。按范数裁剪当 ||g||>c 时等比缩放 g·c/||g||,保持梯度方向不变。LLM 训练中统一使用 max_norm=1.0 的按范数裁剪。


题7:梯度累积和梯度检查点的原理和代价?

标准答案: 梯度累积:将大 batch 拆成多个 micro-batch 累积梯度后再更新,等价于大 batch 训练,loss 需除以累积步数。梯度检查点:前向不保存中间激活,反向时重算,计算量增加约 33%,显存从 O(L) 降为 O(√L)。


题8:Loss Spike 的常见原因和处理方法?

标准答案: 原因:异常数据、梯度爆炸、学习率过大、数值溢出、注意力坍塌。处理:①梯度裁剪(最重要防线);②跳过异常 batch(检测 NaN/Inf/异常大 loss);③数据清洗;④降低 LR;⑤从 checkpoint 恢复。


题9:为什么最小化交叉熵等价于最小化 KL 散度?

标准答案: $H(P,Q) = H(P) + D_{KL}(P||Q)$。对于 one-hot 标签 $H(P)=0$,所以 $H(P,Q) = D_{KL}(P||Q)$。即使非 one-hot,$H(P)$ 是常数不影响优化,最小化交叉熵等价于最小化 KL 散度。


题10:Softmax 的数值稳定性如何保证?

标准答案: 减去最大值 $c=\max z_j$:$\text{softmax}(z_i) = e^{z_i-c}/\sum e^{z_j-c}$。这保证指数部分 $\leq 1$ 不上溢,分母 $\geq 1$ 不除零。数学上等价(分子分母同除以 $e^c$)。PyTorch 已内置此优化。


题11:3D 并行是什么?如何选择 TP/PP/DP 的维度?

标准答案: 3D 并行 = 张量并行(TP) + 流水线并行(PP) + 数据并行(DP)。TP 适合同机 NVLink 连接的 GPU(2-8路),PP 适合跨机(按层切分),DP 增加数据吞吐。选择原则:先用 TP 放下单层,再用 PP 放下整个模型,最后 DP 提高吞吐。


题12:大模型预训练会过拟合吗?微调呢?

标准答案: 预训练通常不过拟合:数据量巨大(万亿 tokens),Chinchilla Law 要求数据量与参数量同比增长,SGD/Adam 噪声有隐式正则化。微调容易过拟合:数据量小(千级),模型参数远大于数据量。解决:LoRA 降低可训练参数、小 LR、Early Stopping、数据增强。


题13:Cosine Annealing 学习率调度的数学公式和优势?

标准答案: $\eta_t = \eta_{min} + 0.5(\eta_{max}-\eta_{min})(1+\cos(t\pi/T))$。优势:前期衰减慢充分学习,中期衰减加快,末期衰减放缓精细调整,平滑无突变。LLM 配置:Warmup 2000 步 + Cosine 衰减到峰值 10%。


题14:对比 AllReduce、AllGather、Reduce-Scatter 通信原语。

标准答案: AllReduce = Reduce-Scatter + AllGather。AllReduce 所有节点得到相同的聚合结果。AllGather 所有节点收集到完整数据。Reduce-Scatter 每个节点得到聚合结果的一个分片。Ring AllReduce 通信量 ≈ 2×数据量,与节点数无关。


题15:Loss 不下降如何系统性排查?

标准答案: ①检查是否 NaN/Inf;②过拟合小 batch 测试(无法过拟合说明有 bug);③检查 LR(太大震荡,太小慢);④检查数据(标签正确性、数据加载、数据质量);⑤检查模型(梯度是否流动、权重是否更新);⑥检查配置(权重衰减、Dropout、Batch size)。关键技巧:torch.autograd.set_detect_anomaly(True)


本章总结:

核心主题关键考点
优化器演进SGD→Momentum→AdaGrad→RMSProp→Adam→AdamW 的动机和公式
学习率调度Warmup+Cosine Decay 是 LLM 标配,理解每种调度的数学
梯度裁剪按范数裁剪保持方向,max_norm=1.0
混合精度BF16 > FP16,Loss Scaling 原理,显存节省计算
分布式训练DDP vs DP,ZeRO 三个 Stage,3D 并行配置
训练稳定性Loss spike 处理、梯度累积/检查点、NaN 排查
数学基础KL 散度与交叉熵、Softmax 数值稳定、贝叶斯/MLE

LLM 应用 & Agent 开发面试准备