梯度下降 入门

训练每一个现代模型的优化器。5 个短小主题:那一行更新规则、ML 里调得最多的超参数(学习率)、 谁也绕不开的变种(基于小批量的 SGD)、让每个新人都头大的词汇(epoch / iteration / step), 以及由这四个概念拼起来的训练循环。

01

更新规则

一行公式,重复几十亿次。

想象一颗弹珠停在山坡上。重力把它往下拽 —— 垂直于等高线,顺着最陡的方向往下滚。梯度下降就是这颗弹珠。弹珠是一个参数向量,山坡是损失函数的曲面, 「往下」是梯度的反方向。走一小步,重新看清自己在哪,再走一步。 整套算法就是这样 —— 你听过的每一个现代模型,训练时干的都是这件事。

一行数学把它写完:

θ ← θ − η · ∇L(θ)

四个符号,每个都有实义:

  • θ —— 参数向量。对一个 Transformer 就是几十亿个权重; 对监督学习 primer 里那条房价线,就只是 (w, b)
  • L(θ) —— 损失函数。每一组 θ 对应一个标量,越小越好。 我们不直接优化损失,而是沿着它梯度的反方向走。
  • ∇L(θ) —— 梯度(微积分 primer §4):和 θ 同形状的向量, 指向上升最陡的方向。前面的负号把它翻成下降最陡。
  • η(eta)—— 学习率:每一步走多大。这是 §2 的主角。

负号干了大部分活。梯度本身指向上山;我们要的是下山,所以朝它的反方向走。 如果我们顺着梯度走 —— 同样的公式但是加号 —— 那弹珠就往山上滚,损失炸掉,训练永远不收敛。 手写优化器时,忘记这个负号是最常见的 bug;框架把它藏在了优化器里,但它就在每一次更新里。

θLL(θ) = (θ − 3)² + 0.5 · η = 0.3θ = -1.00 · L = 16.50从 θ = −1 起步。L = 16.5,离极小点 θ = 3 还很远。
1 / 4
θ ← θ − η · ∇L 里的那个负号,正是把「上山的梯度」翻转成「下山的步」的关键。

弹珠这个比喻基本是准确的,只有一点不一样:真实模型里的山坡住在一个画不出来的百万维空间里。 上面那张一维图 —— 一个抛物线损失盆 + 一个 marker 往下走 —— 就是最好的直觉; 往高维扩展,算法的形状一点都不会变。每个维度有自己的梯度分量, 弹珠在所有维度上同时迈步。

早早提一个细节:梯度的方向是确定的,但大小取决于损失曲面有多陡。 陡坡产生长梯度,平地产生短梯度。把它跟 η 相乘后, 步长 η · ‖∇L‖ 在「训练还有很多要学」时大,在「快收敛」时小 —— 自适应是直接长在公式里的。

梯度下降不保证找到全局最小值 —— 它只找到一个极小值 (或者卡在鞍点、平台)。对监督学习 primer §3 那个凸的房价 MSE, 只有一个极小,梯度下降一定能找到。对深度神经网络,损失曲面有海量局部极小, 但现代深度学习的一个惊人经验事实是:大多数局部极小,几乎跟全局最小一样好。 所以实践中,我们跑梯度下降、收下到哪算哪,模型通常就能用。

在 Transformer 里:这一条更新规则,在每一步训练里都跑一次。 前向传播算损失;反向传播(微积分 primer §3)同时算出几十亿个参数各自的 ∇L;优化器把更新应用上去。本 primer 后面四节, 全都是为了让这一次更新 —— 以及包在外面的循环 —— 真的能在工业规模上跑起来。

02

学习率(Learning Rate)

ML 里影响最大的一个旋钮。

θ ← θ − η · ∇L 里的 η,就是 学习率。 它只控制一件事:优化器每次更新走多大一步。听起来无聊;实际上它是整个 ML 里 被调得最多的超参数。一个糟糕的 η,能把一个本来完全没问题的模型给毁掉。

三种情况:

  • 太小。弹珠一点点往极小点蹭。训练慢得受不了,GPU 烧着钱、进展却看不见。 症状:训练损失在下降,但缓慢而稳定,曲线似乎永远不会拐进平台。
  • 刚刚好。弹珠麻利地滚到底部、停下来。训练损失先下降很快, 然后随着梯度变小弯成平台。这就是论文里那条「能用」的训练曲线长的样子。
  • 太大。弹珠冲过极小点,落到盆的另一侧,反弹回来 —— 甚至可能比一开始还高。损失开始震荡,或者更惨,直接炸成 NaN,训练崩了。
θLL(θ) = (θ − 3)² + 0.5η = 0.02 · 太小η = 0.02 —— 太小。8 步之后 marker 几乎还在 θ = −1 没动;真要走到 θ = 3 得几百步。
1 / 3
同一个损失盆、同一个起点、各跑 8 步 —— 只有 η 不一样。

η 主要靠经验。没有闭式公式,因为合适的取值取决于模型架构、batch size、 参数初始化、损失曲面的形状 —— 这些每个项目都不一样。工作流是这样的:

  • 先扫几个数量级。如果 η = 1e-3 能用, 再试 3e-43e-3。 连一个数量级都没扫完,别在第 4 位小数上较劲。
  • 看训练损失曲线。炸了就降 η; 慢悠悠下坡就抬 η。最快的第一遍训练曲线, 是那条「差点炸但没炸」的曲线。
  • LR finder。fast.ai 推火的一招:跑一段迷你训练, 把 η 从极小线性拉到极大;选刚好在「损失开始炸」前面那个 η。 便宜、出奇地有效。

实践中 η 通常不是常数 —— 几乎所有现代论文都用 学习率调度:η 是训练步数的函数。反复出现的两段:

  • Warmup(预热)。η = 0 起步,在前几百到几千步里 线性拉到目标值。为什么?参数刚从随机初始化出来时,损失曲面是雷区, 早期的大步可能把训练打进一个坏区域。warmup 给模型一段时间, 让它先找到一个平滑的盆地,优化器再开始正式迈步。
  • Decay(衰减)。warmup 之后,在剩下的训练里逐渐降低 η。常见的形状有余弦(沿半个余弦曲线平滑下降)、线性(直线下降)、阶梯(在固定的里程碑处下降 10 倍)。 目的都一样:早期还有明显工作要做时大步走;后期为了在极小点附近做细致收敛, 就把步长缩小。

典型的 Transformer 训练曲线把两段拼起来:前 2000 步从 η = 0 warmup 到3e-4,后面余弦衰减回 3e-5。每篇论文都会写自己的 schedule, 因为这个 schedule 真的会显著影响结果。

η 相互作用的两个相关参数:

  • batch size。更大的 batch 给出更准的梯度估计 (概率统计 primer §4:方差按 1/n 缩); 梯度更准,你就敢迈更大的步。一条常见的拇指法则: 「batch size 翻倍,η 大致也可以翻倍」 —— 不过对超大 batch 这条会失效。
  • Momentum / Adam。带动量(momentum)或者按参数自适应缩放 (Adam、RMSProp)的优化器,实际上把「裸的 η · ∇L」步长又调了一下。 SGD 上「合适」的 η,在 Adam 上几乎都不合适 —— Adam 在「学得好」的坐标上 实际步长会小很多,所以它的 η 可以设大一些。

在 Transformer 里:GPT 系列的峰值 η 经典落在 1e-46e-4;PaLM、Chinchilla 论文给出的也是类似量级。 前 ~0.4% 步 warmup + 余弦衰减到峰值的 0.1×, 是 Transformer 文献里事实上的标准调度。一旦一次训练莫名其妙地停滞或者爆炸, 大家第一个要改的就是学习率 schedule。

03

随机梯度下降(SGD)

用一小块数据估计梯度 —— 便宜、有噪声,而且是现代每个优化器实际做的事。

第 1 节把梯度写成 ∇L(θ),其中 L整个数据集上的损失。 对一个在几百万、几十亿样本上训练的模型,这是一句方便的谎言。 你不可能每一步都在整份数据上算梯度 —— 把 Common Crawl 走一遍就要几周。 真实的训练在「作弊」:每一步只在数据的一小块上估计梯度。 便宜。有噪声。普及。

按块大小,有三个变种:

  • 批量梯度下降。在整个训练集上算 ∇L,然后走一步。 「老实」的那个版本。每一步慢、每一步贵,对大数据集来说毫无可行性。
  • 严格意义的随机梯度下降(SGD)。单个样本上算 ∇L、走一步、重复。 每一步最快。梯度估计噪声大得离谱 —— 每个样本都把参数往它自己想要的方向拽 —— 但平均下来噪声会抵消,你依然在下山。1951 年就有的概念,「SGD」的原型。
  • 小批量(mini-batch)SGD。在一小组样本上算 ∇L、走一步、重复。 这一组的大小叫 batch size:常见是 32、64、256; 在现代 LLM 训练里则是「每个 batch 4M 个 token」(也就是几千条序列)。 兼顾两者 —— 2026 年说「SGD」,几乎都是指它。
θLL(θ) = (θ − 3)² + 0.5batch = 整个数据集全批量 GD:每个梯度都精确。marker 沿一条平滑、单调的路径下到盆地。
1 / 3
同样的步数(14)、同样的 η —— 只有梯度的噪声不一样。batch size 控制这个噪声。

为什么小批量赢了?两个原因,都值得搞清楚。

计算效率。现代 GPU 算一个 batch 样本的梯度和算一个样本的梯度, 墙钟时间差不多 —— 硬件是 SIMD 并行的,一个样本浪费了大半芯片。 把 batch 从 1 翻倍到 2,吞吐量几乎也翻倍。从 256 翻到 512 通常也更快, 但可能只提速 30% —— 最后 GPU 饱和,batch 再大就纯粹增加时间。 最优点取决于硬件。

梯度质量。单个样本算出的梯度,是对「真实(全数据集)梯度」的一个有噪声估计 —— 相当于一个随机意见。把 n 个样本平均一下,噪声方差就缩小 n 倍 (概率统计 primer §4 又出现了)。所以 batch=256 的每个坐标比 batch=1 的噪声 小 √256 = 16×。梯度更平滑 → 可以走更大的步而不至于冲过去, 通常这个收益超过多花的算力。

小批量带来的噪声并不全是开销 —— 有时反倒是好事。尖锐的局部极小对噪声不稳定; 带噪声的梯度下降倾向于跳过这种点,停在更平的区域 —— 这种区域往往泛化更好。 有一小段文献认为「一点噪声对训练是好事」,这也是为什么哪怕全批量很便宜, 大家也未必愿意用纯批量梯度下降。

每篇论文都会出现的几个 batch 相关的实操点:

  • 每个 epoch 都 shuffle。不打乱的话,模型每一轮看到的 mini-batch 顺序都一样, 它会记住「我是在第几个 batch」。每轮训练前都要打乱训练集。
  • 梯度累积(gradient accumulation)。想要逻辑上 4096 的 batch, 但 GPU 一次只装得下 256?那就在 256 个样本上算梯度、把它累积起来、重复 16 次,然后才走一次更新。优化器看到的是 4096 的梯度;硬件每时每刻只持有 256。
  • 线性缩放法则。batch size 翻倍,学习率(大致)也可以翻倍 —— 梯度更准,就敢迈更大一步。在一个「临界 batch size」之前都成立,这个临界值取决于模型和问题; 超过它后收益递减。

在 Transformer 里:现代 LLM 用极大的 batch size —— 以百万级 token 而不是几百个样本来计算 —— 但永远是通过「小批量 + 梯度累积」来实现的,绝不是真的对一百万个 token 一次性做前向 + 反向。 训练从头到尾本质都是小批量;过去 70 年里变化的只是「mini」的大小。 论文里看到「batch size 4M」时,那是几千张 GPU 上、几十步累积加起来的总和。

04

Epoch · Iteration · Step

三个词都在描述「训练进行了多少」 —— 意思并不一样。

小批量 SGD 把训练变成了一道计数题。数据集切成块,每一块产出一次更新,更新慢慢累积。 描述这种累积有三个词,而且经常被混用 —— 哪怕在论文里也如此。 把它们钉清楚,就是这一小节全部要做的事。

  • Step(有时叫 iteration) —— 一次更新规则的应用。 一次前向、一次反向、一次 optimizer.step()。训练的原子。 PyTorch 打出「step 12345」,或者 HuggingFace 显示 global_step=… 时, 指的就是一次权重更新。
  • Iteration —— 通常就是 step 的同义词。一些框架(Keras、早期 TF) 说「iteration」,另一些说「step」,论文里两个混着用。 除非上下文特别说明,把它们当一个意思。
  • Epoch —— 完整地把训练数据集过一遍。数据集有 50,000 个样本、 batch size 是 100,那么一个 epoch 就是 50,000 / 100 = 500 步。

一行公式记账:

每个 epoch 的 step 数 = 数据集大小 / batch size

12 个样本 · batch size = 3 · 每个 epoch 4 个 batchepoch1step1训练集(每个 epoch 都重新打乱)01234567891011batch 1batch 2batch 3batch 4Epoch 1, step 1:优化器处理第一个 3 个样本的 batch。
1 / 9
12 个样本 ÷ 每 batch 3 个 = 每个 epoch 4 步。step 跨 epoch 累加;数据集在每个 epoch 之间重新打乱。

为什么明明同一件事要三个词?因为它们其实回答了三个略微不同的问题:

  • 优化器跑了多久?step —— 也就是「更新被应用过多少次」, 学习率 schedule 也按这个对齐。
  • 模型看过多少数据?epoch —— 「训练集被过了 7 遍」。 想数据覆盖度、想过拟合风险时,用这个。
  • 这一次训练花了多少算力?step × batch size × 每样本成本。 或者 epoch × 数据集大小 × 每样本成本。答案一样,只是拆法不同。

另一个常见但容易忽视的术语:

  • 训练总 token 数(或者训练样本数)—— 现代 LLM 论文的头条数字。 GPT-3 训了大约 3000 亿 token;Chinchilla 是 1.4 万亿;现代前沿模型 10-15 万亿。 这一项就是 step × batch_size(以 token 计) —— 在大规模训练里这是最干净的 「训练量」单位,因为「数据集」和「epoch」的边界已经模糊 (前沿 LLM 经常训练时间不到一个 epoch,反正数据集已经去重 + 精心策展过,而不是反复转圈)。

这些术语容易踩坑的几个地方:

  • 「训了几个 epoch?」只对那种有限的、要被反复过一遍的数据集才有意义。 对一个在 Common Crawl 上训的 LLM,你训练时间还不到一个 epoch —— 数据集太大,根本来不及重复。问「几个 epoch」是问错单位了,应该问「多少 token」。
  • 「训 100 步」没有 batch size 就毫无意义。batch=1 的 100 步和 batch=4096 的 100 步,差着十万八千里。报告时永远把两个一起说。
  • 「Loss vs steps」vs「loss vs epochs」这类图。论文里看到「loss vs steps」时, 要在脑子里把它换算成数据。100,000 步的平滑下降曲线,可能意味着 200 个 epoch (小数据集 + 反复过)、也可能意味着 0.1 个 epoch(大数据集 + 几乎全是新数据) —— 对泛化的故事完全不同。

在 Transformer 里:训练日志通常按 step 记账 (学习率 schedule、checkpoint 周期都按它对齐),按 token 报告 (「在 2T token 上训练」)。Epoch 几乎不出现 —— 前沿 LLM 数据集大到让模型过一遍就要 几百万美金 GPU 时间。论文里看到「300B tokens」时,那才是算力的真相, epoch 和 step 都是围绕它推导出来的记账数字。

05

训练循环

算 loss、算梯度、更新参数 —— 重复一百万次。

本 primer 里所有的概念,最后都拼进一个四行循环里,反复跑到损失够低、或者预算耗尽。 PyTorch 伪代码:

  for batch in dataloader:
      logits = model(batch.inputs)         # 前向传播
      loss   = loss_fn(logits, batch.targets)   # 一个标量
      loss.backward()                      # 反向传播,填好每个 .grad
      optimizer.step()                     # θ ← θ − η · ∇L
      optimizer.zero_grad()                # 把 .grad 清零

五行 Python,就是一个 Transformer 的全部训练算法。你读到的每篇论文、看到的每张模型卡片, 都源自这套循环跑到预算花完为止。其他一切 —— 分布式训练、混合精度、ZeRO、FlashAttention、 梯度 checkpoint —— 都是工程基础设施,目的就是让这个循环在更大的集群上跑得更快。

逐行解释三条:

  • loss.backward() 是微积分 primer §3 —— 链式法则被应用到 PyTorch 前向时建好的计算图上。它把每个参数的 ∂L/∂θ 写到那个参数的 .grad 属性里。
  • optimizer.step() 读每个 .grad、 套上 §1 的更新规则(加上优化器自带的那些扩展:动量、Adam 的逐坐标缩放、权重衰减), 把结果写回参数张量。
  • zero_grad() 是会让每个人踩一次的记账细节。 默认情况下 .grad 在多次反向传播之间是累加的 —— 这其实是个特性,正是它让 §3 里的梯度累积成为可能。但只要你忘记清零, 下一步用的就是「上一 batch + 当前 batch」的梯度,不是当前的。 多数代码要么显式调 zero_grad(),要么用 optimizer.zero_grad(set_to_none=True)

看着这个循环跑起来,是训练最有成就感的部分。损失曲线在几百万步里一路下降, 就是这整套 primer 数学的可见回报:

01000steplossone forward + backward + update per stepStep 0:训练开始。损失就是随机初始化给出来的那个数 —— 通常很高。
1 / 4
每一次现代训练都会画出这种形状的曲线 —— 先一段急速下降,然后一段长尾。

几乎每条训练曲线都有两段:

  • 初期快速下降。前几百步,模型从随机初始化里跑出来、学会一些最基础的结构, 损失就在量级上往下掉。在 log-scale 上看,几乎是一根垂直的下降。
  • 长平台。容易的红利吃完后,损失下降变得非常慢 —— 一条幂律形状的长尾。训练里大部分时间都耗在这一段,一点一点抠最后那些性能。 这条曲线看起来像训练停了,其实还在掉,只是慢。

真实项目里你会往循环里加的一堆东西:

  • 验证评估。每隔几千步,在留出的验证集上算一次损失(数据 primer §3)。 如果验证损失开始往上爬而训练损失还在掉,你过拟合了(监督学习 primer §4)—— 该停了,或者加强正则化。
  • Checkpoint。每 N 步把模型 + 优化器状态保存一次到磁盘。 崩溃、断电、代码改错 —— 有 checkpoint 在,恢复起来都容易得多。 前沿 LLM 训练每隔几百步就写一个 checkpoint。
  • 日志。损失、学习率、梯度范数、吞吐量(tokens/秒 或 样本/秒)、 GPU 利用率、偶尔抽个样本输出。训练循环没仪表盘根本是黑盒; Weights & Biases、TensorBoard 这种工具,就是为了让它可观测而存在。
  • 梯度裁剪。把梯度的范数封顶到某个最大值(常用 1.0), 防止一个出了 bug 的 mini-batch 把几周的训练毁掉。三行代码,救过无数次训练。
  • 混合精度训练。前向、反向大部分用 16 位浮点跑; 优化器内部的累加器保留 32 位。约 2 倍提速,2 倍省显存, 只要记得给 loss 做缩放避免下溢,几乎是免费的。

在 Transformer 里:上面这个循环,就是 GPT、LLaMA、Claude、Mistral、Gemini —— 每个现代 LLM 的训练循环。区别只在规模:几千张 GPU 各自跑数据并行版的同一个循环, 每一步同步梯度平均;序列是几千个 token 而不是单个样本;「batch size」通过梯度累积 以百万 token 计。循环本身的算术,跟笔记本上的 PyTorch 完全一样。就是这个循环,跑几百万次,撑起了整个现代 AI。