把一个训练步算完整

从前向、反向和更新理解训练状态、数值、batch 与累积。

第 03 单元 · 约 70 分钟(含练习) · 产出:训练状态内存账与更新自检

从哪里接上:第 02 课的内存与执行模型

模型能加载,为什么一训练就放不下?

上一课主要拆解推理时的权重、KV 与工作区。训练还会增加梯度、优化器状态与激活。现在你调用一次训练,峰值却超过权重大小几倍。先别把多出来的空间都叫缓存:训练要记住中间结果,求出每个参数该往哪里改,还可能保存更新的历史。它们有不同的生命周期,也有不同的节省办法。

参数是模型要学习的数;前向计算用参数把输入变成预测;损失把预测与目标的差异变成一个数。反向计算根据链式法则,求损失对参数的导数,这个导数叫梯度。优化器最后依据梯度修改参数。反向得到梯度,不等于参数已经更新。工程上必须区分读取一批数据、完成一次反向和完成一次优化器更新,才能解释吞吐、步数与恢复点。

先用一个参数,把一次更新算完整

模型取最小的形式 预测 ŷ=w×x,没有偏置。令损失 L=(ŷ−y)²/2;前面的二分之一只是为了让导数简洁。梯度为 g=(w×x−y)×x。本例使用不带动量、不带权重衰减的随机梯度下降 SGD,更新规则是 w新=w旧−η×g,η 是学习率。

从 w=1 开始

样本 x=2、目标 y=4,学习率 η=0.1。前向得到 ŷ=2、L=2;反向得到 g=(2−4)×2=−4;更新得到 w=1−0.1×(−4)=1.4。用同一样本重新前向,ŷ=2.8、L=0.72。这个下降来自本例的参数和步长,不能由此推出训练每一步的损失都必然下降。

前向、反向与更新不是同一个动作w为1时前向预测2并保存中间结果;反向求出梯度负4;优化器用学习率0.1更新到1.4,然后清理本次梯度进入下一步。前向:ŷ=2,L=2保留反向需要的结果→反向:g=−4此时 w 仍为 1→更新:w=1.4完成一次参数更新下一步使用新参数;旧梯度不能无意留到下一次更新。训练中的“步”要写清:微批次、反向次数,还是优化器更新次数。
图 03.1 · 将三个时刻分开,才能判断参数、梯度和中间结果何时存在。

神经网络中间层的输出称为激活。反向常需要前向保存的激活,所以训练峰值可能出现在反向期间。梯度累积是刻意跨微批次保留梯度;平常忘记清零则是错误。两种情况都看见梯度增加,但更新语义完全不同。

小批次累积,必须保持同一个平均目标

一个 batch 是一次用于估计梯度的样本集合;显存放不下时,可以把它拆成多个 microbatch(微批次),分别前向、反向,最后只更新一次。两条等权样本在 w=1 时分别是 (1,2)、(3,6),梯度为 −1 和 −9。整个 batch 的平均梯度是 −5,SGD 更新为 1.5。逐个微批次计算时,每份梯度除以 2 后累加,也得到 −5。

如果直接把两个梯度相加再更新,会得到 w=2,等价于改变了这一步的尺度。如果每个微批次都更新,则第二条样本使用的已是新参数,结果也不再等价。只有参数在累积期间不变、目标归一化一致,才有这个等价关系。不同大小的微批次要按样本数加权;变长序列若目标是有效 token 的均值,就应按有效 token 数归一化,不能盲目除以微批次数。

累积减少同时保留的激活,却不会自动减少每个副本的权重和优化器状态。它也不保证与大 batch 完全同轨迹:批内统计、随机操作和浮点求和顺序都可能带来差异。验证时先比较一次更新,而不是等一整轮训练结束再猜原因。

“每参数 16 bytes”是一份有条件的账

Adam 优化器会维护梯度的一阶和二阶历史估计,用来调整更新。采用一份特定的混合精度教学配置:低精度参数 2B、低精度梯度 2B、FP32(32 位浮点)主参数副本 4B、Adam 两份状态各 4B,合计 16B/参数。主副本用于较高精度的更新。不是所有实现都维护同样的副本,梯度格式、优化器和分片方式变化后必须重列账目。

尚未计入激活、通信缓冲、工作区与碎片
项目每参数字节为什么存在
低精度参数、梯度2+2计算使用的值与待更新方向
FP32 主副本4本配置的参数更新副本
Adam 一阶、二阶状态4+4跨更新保留历史

10⁹ 参数的这些常驻状态合计 16GB,约 14.901GiB;再加假设的每卡激活 2GiB,得到 16.901GiB。这里的激活只是输入估计值,不由参数个数直接推导。第 05 课会解释 ZeRO 如何分摊这些状态;下面可先切换阶段,看“哪一项被分摊”如何影响账面大小,不能把计算值当成可部署容量。

默认完整状态 14.901 GiB,加激活 2 GiB,合计约 16.901 GiB;未包含临时聚合和运行时峰值。
计算器采用的假设

P 为参数数目,D 为数据并行度。阶段 0/1/2/3 每卡常驻字节依次是 16P、(4+12/D)P、(2+14/D)P、16P/D;除以 2³⁰ 再加每卡激活。默认阶段 3 约 5.725GiB,但通信、临时收集参数及重计算仍会改变实际峰值。不要把激活也自动除以 D。

从第一次不一致的位置开始排障

预测已不一致,先查输入、初始权重和数值格式;预测相同但梯度不同,查损失求和/平均及梯度是否清零;梯度相同但更新不同,查学习率、优化器状态与更新次数。loss 突然变成非有限数时,先保留出错 batch 和状态,再定位溢出或非法输入;换低精度或一味缩小学习率不能代替定位。

本课实验包用标准库实现小型线性回归,不是 GPU 框架。先读 实验说明并下载代码包。其中模型有 w、b 两个参数,目标为 y=3x+2,优化器带动量:保留过去梯度的加权累积,参与本次更新。它与上面的单参数纯 SGD 手算承担不同目的,不应期待每步数值相同。下一课用它检验训练恢复。

把一次更新与内存账都留下

  1. 复算单参数例,把学习率改成 0.05,写出预测、损失、梯度、新权重和新损失。
  2. 陌生微批次分别含 2、1 条样本,平均梯度为 3、9。写出总体平均梯度;解释为什么两份均值直接平均不对。
  3. 为 20 亿参数建立本课的完整状态账,另计 3GiB 激活;列出两个仍未纳入的峰值来源。
参考与验收

学习率 0.05 时旧预测 2、旧损失 2、梯度 −4,新 w=1.2,新损失 1.28。陌生微批次应取 (2×3+1×9)/3=5,不是 6。20 亿参数状态约 29.802GiB,加激活为 32.802GiB。验收要能指出归一化分母、更新发生的时刻,以及通信缓冲或临时张量等漏项;只交计算器截图不够。

上述不等大小微批次的平均梯度是多少?

读公式,核对更新语义

打开 PyTorch SGD 文档,先找学习率、动量和权重衰减在更新式中的位置,再把本课手算对应到动量与衰减均为零的情况。读后写出“加入动量,恢复时为什么只保存 w 不够”,不要把一个优化器公式推广成所有训练实现。

下一步:同一个更新,能否喂得上、续得上?

你已经知道更新依赖哪些状态。第 04 课把视线移到输入队列和持久存储:既要让设备不空等,也要在进程退出后从同一个训练位置继续。

留下本课的产出

本课产出:训练状态内存账与更新自检。写下条件、证据、结果和仍不确定的事。保存不代表自动通过,下面的状态由你按验收条件自检。

记录只保存在当前浏览器。

按验收条件自检