优化器与训练技巧 入门

梯度下降 primer 用一行字收尾:w ← w − η · ∇L。 但真正的 LLM 训练是这一行外面包了四层实战机械装置 —— 每一层都是「模型真能收敛」的必需品。四个短主题:SGD → Adam → AdamW(大家都在用的优化器);学习率调度(warmup + 余弦衰减);混合精度训练(fp16 / bf16); 以及分布式训练(data / tensor / pipeline parallel)。 读完这一篇,你以后看每一份训练脚本顶上的那一堆配置,都看得懂了。

01

SGD → Adam → AdamW

每个参数自适应步长,加上「让 LLM 训练真正能跑」的那个权重衰减修法。

梯度下降 primer §1 介绍过普通 SGD —— w ← w − η · ∇L。 在简单问题上能用。在现代深度网络上它留下了大量优化空间: 全局一个学习率,它必须小到能容纳最陡的方向,意味着对平坦方向太小, 意味着收敛慢。深度学习里的优化器历史,就是一段「绕过这条约束」的长故事。

第一招:动量(momentum)。维护一个过去梯度的滑动平均,沿那个方向走。 噪声被平滑掉、在一致方向上加速。还是一个全局学习率,但优化器把它用得更好了。

第二招:Adam(Kingma 和 Ba,2014)—— 自适应矩估计。 保留两个滑动平均:梯度的一阶矩 m_t、梯度平方的二阶矩 v_t。 前者就是动量。后者告诉你每个参数的梯度有多「变」。把步长除以 √v_t, 每个参数就有了自己的有效学习率。

Adam,第 t 步更新:
  m_t  =  β₁ · m_{t-1} + (1 − β₁) · g_t          ← 一阶矩
  v_t  =  β₂ · v_{t-1} + (1 − β₂) · g_t²         ← 二阶矩
  m̂_t  =  m_t / (1 − β₁ᵗ)                        ← 偏置修正
  v̂_t  =  v_t / (1 − β₂ᵗ)
  w    ←  w  −  η · m̂_t / (√v̂_t + ε)

典型值:β₁ = 0.9、β₂ = 0.95–0.999、ε = 1e-8

核心思路 —— 近期梯度大的参数,有效步长被缩小;近期梯度小的参数,有效步长被放大。 在细长的损失面上,陡的方向被缩、平的方向被放,于是直接奔向最小值,不再来回震。

SGD vs Adam,在一个细长的山谷里损失地形最小值start细长的「碗」。沿 y 陡、沿 x 缓。优化从右上角起步。
1 / 3
Adam 给每个参数维护一个滑动尺度(二阶矩 v),再用它去除。陡的方向自动缩小步长、缓的方向自动放大。

第三招:AdamW(Loshchilov 和 Hutter,2017)。 Adam 加上「正确做权重衰减」的方法。原版 Adam 想把 L2 正则化混进梯度里 —— 但 L2 也会被 √v_t 缩放,意味着每个参数实际被衰减多少 取决于它的梯度方差,一团乱。AdamW 把两件事解耦:先做 Adam 更新,然后单独把权重收缩 η · λ · w:

AdamW,第 t 步更新:
  w  ←  w  −  η · m̂_t / (√v̂_t + ε)  −  η · λ · w
                                       ─────────
                                       权重衰减
                                       与自适应缩放解耦

改动很微妙,但后果很大。AdamW 在规模上泛化更好,每一个现代 LLM 的训练都用它。 PyTorch、JAX、HuggingFace 在做 Transformer 训练时,默认优化器都是 AdamW。

2026 年大家真在用什么:

  • AdamW:几乎做什么都用它。LLM 预训练、微调、大多数其它深度学习任务的默认。
  • 带动量的 SGD 在某些图像分类任务上(某些设置的 ResNet、ViT)依然胜出 —— 那种损失面够友好,自适应方法没帮助、甚至会损害泛化。
  • Lion、Sophia、Adafactor、Shampoo、Muon —— 一批新优化器, 号称在内存或收敛上能赢 AdamW。截至 2026 年,生产里还没有谁能完全替掉 AdamW, 但这一方向一直在试新东西。

关于 Adam 的内存代价说一句:它每个参数都要多带两个 fp32 张量(m v)。一个 7B 模型,光优化器状态就 56 GB,加上 28 GB 的 fp32 权重 —— 训练侧光优化器这一边就 84 GB。硬件 primer §2 讲了为什么这是硬约束。 Adafactor、8-bit AdamW 这类「省内存优化器」就是为解决这件事而生的。

在 Transformer 里:每个块的每一个参数,用的都是β₁ = 0.9β₂ = 0.95ε = 1e-8、 权重衰减 λ ≈ 0.1 的 AdamW,再配上一个学习率调度(§2)。 这一句话就描述了 GPT-3、LLaMA、PaLM、Claude、Gemini —— 几乎 2020 到 2026 年 所有旗舰 LLM 的优化器。这套配方主导得如此彻底, 「AdamW」基本就是「现代 LLM 训练」的同义词。

02

学习率调度

开头 warmup,结尾 decay。没人用恒定 η 训 LLM。

AdamW 给你每个参数的步长。剩下的问题是:全局步长 η 在训练过程中怎么变? 事实是:恒定 η 是错的。每一个现代 LLM 都用两阶段的调度 —— 开头warmup、后面decay

Warmup。从 η = 0 起,在前 ~0.1–2% 的训练步内线性升到峰值。为啥? Adam 的二阶矩 v_t 需要时间积累梯度大小的统计; 要是第一步就上满 η,这时 v_t 完全不靠谱、产出的更新巨大。 Warmup 给优化器状态一点时间稳下来,然后再让全速步落地。 跳过 warmup 是经典「loss 第 5 步爆 NaN」错误。

Decay。Warmup 之后,把 η 在剩下的训练里降下去,最终降到很小 (常见 10% 峰值,有时直接到 0)。直觉:训练早期想要大步,找对邻域; 训练晚期想要小步,把模型在最小值附近打磨。常用的 decay 形状有几种:

  • 余弦衰减(cosine) —— η_t = η_peak · 0.5 · (1 + cos(π · t/T))。 平滑、没有突变。LLM 默认。
  • 线性衰减 —— 从 η_peak 直线降到 η_end。微调时常见。
  • 逆平方根 —— η_t = η_peak / √t。最早的 Transformer 论文用的就是这个;现在用得少了。
  • WSD(Warmup-Stable-Decay) —— 大部分时间维持在 η_peak, 训练末尾再急速衰减。当你不知道最终训练 budget 时很有用(可以以后再继续训、再衰减)。
warmup + cosine decay —— LLM 默认调度坐标轴学习率训练步数 →peak空坐标轴。真实训练用的不是恒定 η。
1 / 3
从 0 起跑,给 Adam 的方差估计一点稳下来的时间;升到峰值;然后余弦衰减回去,让模型最后能用小步长去微调。

一次 Transformer 预训练里最常见的数值:

峰值 LR:        3e-4 到 1e-3   (越大的模型越小)
Warmup 步数:    500 – 2000     (总步数的 0.1–2%)
总步数:         10k – 几百万    (取决于数据量)
衰减形状:       余弦衰减到峰值的 10%
最低 LR:        3e-5 到 1e-4

关于规模的一条实用事实:模型越大,最优峰值 LR 越小。7B 模型最佳大约在η ≈ 3e-4;70B 模型要降到 η ≈ 5e-5 附近。 「Maximal Update Parameterization」(μP)这条研究路线给了规模化规则, 让你能在小模型上调超参、然后预测大模型的正确 LR。

在 Transformer 里:训练脚本里的调度通常就放在每步都被调的get_lr() 里。最重要的超参就是峰值 LR;第二重要的是 warmup 长度; 第三是衰减形状。任何主要 LLM 实验室出的 model card 或技术报告, 都会告诉你这三个数字。要是你的训练发散了,LR 调度是第一个该查的地方。

03

混合精度训练

激活值用 2 字节浮点。数学不坏、模型照训、GPU 快一倍。

硬件 primer §2 解释了为什么 VRAM 重要。混合精度训练是大家用得最多的单项优化: 把网络的大部分东西从 4 字节的 fp32 换成 2 字节的浮点(fp16 或 bf16)。 结果是显存减半、在现代 tensor-core GPU 上吞吐量大致翻倍。

登场人物:

  • fp32 —— 32 比特 IEEE 754。1 符号 + 8 指数 + 23 尾数。 范围 ~10⁻³⁸ 到 10³⁸,精度约 7 位十进制数。所有数值计算的安全默认,但贵 —— 每个数 4 字节。
  • fp16 —— 16 比特半精度。1 + 5 + 10。 范围 ~6×10⁻⁵ 到 ~6.5×10⁴,精度约 3 位十进制数。显存减半、 在 Tensor Core 硬件上吞吐量翻倍。但是:范围窄意味着梯度可能下溢到 0、 激活可能溢到 inf。
  • bf16(brain float)—— 也是 16 比特,1 + 8 + 7。 指数和 fp32 一样多(所以没有溢出 / 下溢问题),但尾数只有 7 比特 (精度约 2 位十进制数)。Google 为 ML 设计的;现在所有主流硬件平台 (A100、H100、TPU、MI300X)上做大模型训练的默认。
  • fp8 —— 8 比特浮点。两个变种(E4M3 和 E5M2)。H100 及更新的硬件 可以用 fp8 跑矩阵乘做进一步加速;推理用得多,训练也越来越多。在 OCP MX 标准里 被规范化。
fp32 / fp16 / bf16 —— 同一个数,更少的比特fp32 —— 完整精度fp32182332 bits4 字节 · 安全、慢fp1616 bitsbf1616 bitsfp32。1+8+23 = 32 比特。范围大、精度高,每个数 4 字节。
1 / 3
混合精度训练:激活和梯度走 fp16 或 bf16 以提速,但优化器用一份 fp32 的「主拷贝」权重做更新,保证稳定。

「混合」里的「混合」很重要。不是什么都丢进 fp16/bf16 —— 有些操作需要更高精度。标准配方:

  • 权重、激活、梯度 —— fp16 或 bf16。前向和反向都在低精度里跑。 加速从这里来。
  • 权重的「主拷贝」 —— fp32。优化器状态和权威的权重值保留全精度。 更新时,先在 fp16/bf16 里算更新量,再把它叠到 fp32 主拷贝上。
  • 归约(求和、求均值,尤其是跨很多个值的)—— fp32。 很多小低精度数累加起来很快丢精度;输入是 bf16 时归约也通常在 fp32 里做。
  • Loss scaling —— 只用于 fp16。反向之前把 loss 乘上一个大常数 (比如 1024)把梯度大小推出 fp16 的下溢区,反向之后再把梯度除回去。 bf16 不需要这一步(指数范围跟 fp32 匹配),这也是它能胜出的最主要原因。

实际代码里你几乎不用手写这些。PyTorch 的 autocast 和 AMP 自动处理类型转换:

# PyTorch —— 用 bf16 autocast
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
    out = model(x)
    loss = criterion(out, y)
loss.backward()                    # 梯度自动是 bf16
optimizer.step()                   # 更新 fp32 主拷贝

实际能换来什么,在 H100 上训一个 7B Transformer 的具体数字:

  • 显存少 ~50%。激活和优化器二阶张量都省。 可以把 batch size 或上下文长度翻倍。
  • tflops 多 ~2 倍。H100 在稠密矩阵乘上 fp16/bf16 标称 989 TFLOPS vs fp32 的 67 TFLOPS —— 真实差距 10× —— 这就是 2026 年「用 fp32 训练」基本绝迹的原因。
  • 最终 loss 相同(在噪声范围内)。模型功能上跟 fp32 训出来的一样, 只是训得更快。

在 Transformer 里:每一个现代 LLM 的参考实现默认都用 bf16 训练。 推理常常走得更远,到 fp8、int8、甚至 4 比特量化。 你下一篇要读的 Transformer(以及你 2026 年可能要去微调的任何 LLM) 几乎肯定是「bf16 + fp32 主拷贝权重」 —— 这套组合到了这一步,已经是无聊的默认了。

04

分布式训练

DP × TP × PP —— 把模型切到几百张 GPU 的三个独立轴。

一个 70B 参数的 bf16 模型,光权重就 140 GB —— 比单卡 H100 的 80 GB 还大。 训练还要加梯度、优化器状态、激活。硬件 primer §2 已经看过 7B 训练能超 H100; 70B 及更大就保证是多卡了。那「一个模型切到多张 GPU 上」到底怎么切? 三个正交技术,通常组合使用。

1. 数据并行(DP)。每张 GPU 都有一份完整模型。 把 batch 拆给各 GPU。每张 GPU 在自己那份 batch 上做前向 + 反向、得到本地梯度。 然后 all-reduce 把所有 GPU 的梯度平均一下,每张卡的权重以相同方式更新。 简单、容易扩到很多 GPU —— 要求整模型在每张 GPU 上能装下。

DP 的变种把训练状态本身也切片,不只是切数据。ZeRO(微软)和FSDP(PyTorch)把优化器状态、梯度、权重在 DP 组里切片, 按需 gather 和 scatter。这能把单卡显存大幅减少,同时保留「每张卡一份模型」的心智模型。 现在大多数训练脚本默认就用 FSDP。

2. 张量并行(TP)。当模型本身一张卡装不下时, 把每一层的权重矩阵切到多张卡上。一个 4096 × 4096 的权重矩阵切给 4 张卡, 就成了 4 个 4096 × 1024 的块。前向反向都要在每一层做 all-to-all 通信 (每张卡算一部分矩阵乘,然后聚合)。通信代价巨大,所以 TP 一般只在单机内做、 靠 NVLink(≥ 600 GB/s 单向),不会跨节点去走 InfiniBand。

3. 流水并行(PP)。把模型纵向切 —— GPU 0 拿第 1–8 层、 GPU 1 拿第 9–16 层,以此类推。GPU 0 的激活流到 GPU 1、再流到 GPU 2…… 朴素做法下, GPU 会闲等前驱完成 —— 这就是「流水气泡」。真实实现会用micro-batching: 把 batch 切成更小的块流过去,让所有阶段都忙。 当 TP 也不够时(模型大到单层都在单机里装不下),就上 PP。

把一个 4 层的模型切到 4 张 GPU 上数据并行(DP)GPU 0L1L2L3L4batch 0GPU 1L1L2L3L4batch 1GPU 2L1L2L3L4batch 2GPU 3L1L2L3L4batch 3数据并行。每张 GPU 都装完整模型,batch 切成 4 份分给各卡。
1 / 3
三个独立的轴。真实的 LLM 训练会叠加它们 —— 一次 1024 卡训练可能是 32-way DP × 8-way TP × 4-way PP。

真实 LLM 训练把它们组合用:

  • 2D(DP + TP)。到 10B 量级常用。多节点 DP、节点内 TP。 每个节点持有一份(切片后的)完整模型,节点之间靠 all-reduce 协调。
  • 3D(DP + TP + PP)。70B+ 模型的标配。想象一次 1024 卡训练: 32-way DP × 8-way TP × 4-way PP。每个轴在不同维度切工作量; 总数 = 32 · 8 · 4 = 1024。
  • 序列并行(Sequence parallel)。相对较新:除了上面那些, 再把序列维度切到多张 GPU 上。在超长上下文场景中,沿序列轴的激活占主导, 这一招很重要。
  • 专家混合(MoE)。另一种「并行」 —— 把不同 token 路由到不同的 专家子网络。每个专家住在一张卡上,所以模型可以巨大、但每次前向只激活几个专家。 能让你拥有「1T 参数」级模型,而每 token 成本相当于一个 80B 模型。

承载这一切的硬件:

  • 节点内:8 张 GPU 通过 NVLink/NVSwitch 互连,单向 ~600+ GB/s。 TP 通信代价能扛得住。
  • 节点间:InfiniBand 或 RoCE,~200–400 GB/s。够 DP 的 all-reduce, 太慢做精细的 TP。
  • 软件:PyTorch FSDP、NVIDIA Megatron-LM、DeepSpeed、 JAX 加 Mesh + GSPMD、Maxtext。栈各有不同,底下都是这三个轴。

在 Transformer 里:你下一篇要读的同一个架构,可以从 1 张 GPU (装下 1B 模型)一路扩到 16 张(70B 模型 TP + PP)、再到几千张 (前沿 LLM 3D 并行 + MoE)。「从『单卡能跑』到『2.5 万张 H100 训练 6 个月』」 这条路上做的所有工程,基本就是这一节的内容。 任何旗舰模型发布背后大部分工程努力都在这里,不在架构里。

前置知识铺完了。下一篇 primer:Transformer 本身 —— 终于到了。