#优化器深度解析附录(新增:设计动机、数学推导与系统对比)

前面的第九批优化器题库已经覆盖了"每个优化器是什么"和"怎么用",但面试中最能拉开差距的追问往往是:"为什么要这样设计?""如果从零设计一个优化器,你会怎么思考?""这几个优化器本质区别在哪?"

本节从设计演进核心数学工程选型三个维度,把优化器从"背诵公式"提升到"理解原理"。


#一、优化器设计演进:为什么从 SGD 走到 AdamW

#1. SGD 的核心问题与 Momentum 的解决思路

SGD 的更新规则

theta_{t+1} = theta_t - lr * g_t

其中 g_t 是当前 mini-batch 的梯度。

SGD 的问题

  • 震荡:在峡谷型 loss surface(一个维度很陡、另一个维度很平)中,SGD 会在陡坡上来回震荡,在平坡上进展缓慢。
  • 噪声敏感:每次更新只依赖当前 batch 的梯度,噪声大时方向不稳定。
  • 统一学习率:所有参数共享同一个 lr,但不同参数的梯度尺度可能差异很大(如 embedding 层和深层 FFN)。

Momentum 的设计动机:把物理中的"动量"概念引入优化。不再只看当前梯度,而是把历史梯度做指数滑动平均,形成"速度":

v_t = beta * v_{t-1} + g_t
theta_{t+1} = theta_t - lr * v_t

为什么这样设计有效? 想象一个球滚下山:

  • 在峡谷两侧来回震荡时,横向速度会相互抵消,纵向速度会累积;
  • 最终球会沿着山谷底部平稳前进,而不是在两侧来回反弹。

这就是 Momentum 的核心价值:用历史信息的累积来平滑更新方向,减少震荡


#2. 从 Momentum 到 RMSProp:自适应学习率的直觉

Momentum 解决了"方向不稳定",但没有解决"不同参数需要不同学习率"。

RMSProp 的洞察:如果某个参数的历史梯度一直很大,说明这个参数可能在一个"很陡"的方向上,应该减小它的学习率;反之,如果某个参数的历史梯度一直很小,应该增大它的学习率。

RMSProp 更新规则

v_t = beta * v_{t-1} + (1 - beta) * g_t^2
theta_{t+1} = theta_t - lr * g_t / (sqrt(v_t) + eps)

这里 v_t 是梯度平方的滑动平均(二阶矩估计),用它来做逐维度的归一化:

  • 梯度大的维度:sqrt(v_t) 大,更新步长被压缩;
  • 梯度小的维度:sqrt(v_t) 小,更新步长被放大。

面试追问:为什么用梯度平方而不是梯度绝对值?

  • 平方对大梯度惩罚更重,对小梯度更宽容,这和优化中常见的"先把大梯度方向压下来"的直觉一致。另外,平方和后续的 Adam 设计一脉相承。

#3. Adam 的合成:Momentum + RMSProp

Adam 的设计动机:Momentum 在"方向平滑"上很好,RMSProp 在"自适应步长"上很好,能不能把两者结合起来?

Adam 更新规则(带 bias correction):

m_t = beta1 * m_{t-1} + (1 - beta1) * g_t       # 一阶矩(动量)
v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2     # 二阶矩(自适应)

m_hat_t = m_t / (1 - beta1^t)                   # bias correction
v_hat_t = v_t / (1 - beta2^t)                   # bias correction

theta_{t+1} = theta_t - lr * m_hat_t / (sqrt(v_hat_t) + eps)

bias correction 的必要性:因为 m_tv_t 初始化为 0,前几步的估计会系统性地偏小。除以 (1 - beta^t) 补偿这个偏差,让早期估计更准确。

具体数值beta1=0.9, beta2=0.999

  • beta1=0.9 表示约看最近 10 步的梯度平均;
  • beta2=0.999 表示约看最近 1000 步的梯度平方平均。

为什么 beta2 远大于 beta1? 因为二阶矩(梯度平方)变化更慢、需要更长记忆,而一阶矩(梯度方向)需要更快响应。


#4. AdamW 的关键修正:Decoupled Weight Decay

问题的根源:在标准 Adam 中,如果直接把 L2 正则 lambda * theta 加到梯度里:

g_t = grad(loss) + lambda * theta_t
theta_{t+1} = theta_t - lr * m_hat_t / (sqrt(v_hat_t) + eps)

这个 lambda * theta_t 会被二阶矩 v_hat_t 缩放。结果是:

  • 历史梯度大的参数,v_hat_t 大,L2 正则的实际效果被压缩;
  • 历史梯度小的参数,v_hat_t 小,L2 正则的实际效果被放大。

L2 正则的语义被扭曲了! 你想让所有参数都按同样比例衰减,但 Adam 的自适应机制让不同参数的衰减强度完全不同。

AdamW 的修正:把 weight decay 从梯度更新中彻底解耦

# 先按 Adam 规则更新(只用数据梯度)
theta_t' = theta_t - lr * m_hat_t / (sqrt(v_hat_t) + eps)
# 再单独做一次参数收缩(和自适应无关)
theta_{t+1} = theta_t' - lr * lambda * theta_t

这样 lambda 的效果是明确的:所有参数都按 lr * lambda 的比例收缩,不会被二阶矩扭曲。

为什么这在大模型训练中特别重要?

  • 大模型参数数量级差异大(embedding 层可能有几十亿参数,某些小矩阵只有几百万);
  • 如果 weight decay 强度随参数乱飘,调参会变成噩梦;
  • AdamW 让正则化"可控、可解释、可独立调参"。

#二、核心优化器对比表

维度 SGD Momentum RMSProp Adam AdamW
状态量 0 1x (速度) 1x (二阶矩) 2x (一阶+二阶) 2x (一阶+二阶)
自适应 是(逐维) 是(逐维) 是(逐维)
平滑性 有(动量) 有(一阶矩) 有(一阶矩)
weight decay L2=decay L2=decay L2=decay L2(扭曲) 解耦 decay
适用场景 简单凸问题 震荡严重的 loss 非平稳梯度 通用默认 大模型默认
调参难度 中高

面试时的快速记忆法

  • SGD = 盲人摸象(只看当前梯度);
  • Momentum = 滚雪球(历史方向有惯性);
  • RMSProp = 因人而异(不同参数不同步长);
  • Adam = 又稳又自适应(动量 + 自适应);
  • AdamW = 正则化也干净(解耦 decay)。

#三、大模型场景下的优化器选型决策树

模型能否放入单卡显存(含 optimizer state)?
├─ 是 ───────────────────────────────────────────┐
│  └─ 数据质量高、训练稳定?                       │
│     ├─ 是 ─> AdamW(默认选择)                 │
│     └─ 否 ─> 尝试 Lion(更轻量,可能更稳)     │
└─ 否(optimizer state 爆显存)───────────────────┘
   ├─ 模型 < 20B ─> 8-bit AdamW / Adafactor
   ├─ 模型 20B-70B ─> ZeRO-1/2 + AdamW
   └─ 模型 > 70B ─> ZeRO-3/FSDP + 考虑 Adafactor / 8-bit

关键决策点

  1. 显存够吗? optimizer state 是 AdamW 最大的代价(2-3x 参数显存)。如果不够,要么换省状态优化器,要么上 ZeRO/FSDP。
  2. 训练稳定吗? AdamW 的默认参数(lr=1e-4~3e-4, beta1=0.9, beta2=0.95, weight_decay=0.1)在大多数大模型预训练中已经很稳。如果还不稳,检查数据质量和梯度裁剪。
  3. 追求极致效率? Lion、Adafactor 等新优化器在特定场景下可能更好,但工程生态不如 AdamW 成熟。

#四、常见面试深挖追问与应答

Q1: 为什么 Adam 前期收敛快但泛化不一定最好?

  • Adam 的自适应机制让它在早期快速找到 loss 下降方向;
  • 但自适应也可能"过度拟合"训练集梯度的局部统计,找到尖锐极小值;
  • SGD(调得好)更容易找到平坦极小值,泛化更好;
  • AdamW 通过解耦 weight decay 部分缓解了这个矛盾

Q2: 如果让你从头设计一个优化器,你会怎么思考?

  • 第一步:定义问题。是震荡?是不同参数尺度不均?是显存不够?是收敛慢?
  • 第二步:选择工具。需要动量?需要自适应?需要省状态?
  • 第三步:验证。在小数据集上快速实验,看 loss 曲线和梯度范数。
  • 第四步:调参。学习率、warmup、weight decay、gradient clipping 的组合。

Q3: Adam 的 bias correction 在早期有多重要?

  • t=1 时,m_1 = (1-beta1)*g_1,只有真实均值的 0.1 倍(若 beta1=0.9);
  • 除以 (1-0.9^1) = 0.1 后,估计恢复为 g_1,完全无偏;
  • 如果没有 bias correction,前几十步的更新会系统性地偏小,可能导致训练初期"假死"。

Q4: 学习率 warm-up 和优化器是什么关系?

  • 不是优化器本身的特性,但和优化器配合至关重要;
  • Adam/AdamW 早期 v_t 很小(因为还没积累足够梯度平方),如果没有 warm-up,lr / sqrt(v_t) 会异常大,导致早期更新爆炸;
  • warm-up 让学习率从零缓慢上升,给二阶矩足够时间积累稳定估计。

#五、优化器状态显存占用速算表

AdamW + BF16 训练为例,每 1B 参数的显存占用:

组件 计算 每 1B 参数
参数(BF16) 1B × 2 字节 2 GB
梯度(BF16) 1B × 2 字节 2 GB
一阶矩 m(FP32) 1B × 4 字节 4 GB
二阶矩 v(FP32) 1B × 4 字节 4 GB
合计 12 GB

如果加 FP32 master weights(mixed precision):再加 4 GB,总计 16 GB / 1B 参数

这就是为什么:

  • 7B 模型全参数训练,单卡至少需要 7 × 12 = 84 GB 显存(这还不含激活);
  • 必须使用 ZeRO/FSDP/8-bit optimizer 来切分或压缩 optimizer state。