模型能加载,为什么一训练就放不下?
上一课主要拆解推理时的权重、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。这个下降来自本例的参数和步长,不能由此推出训练每一步的损失都必然下降。
神经网络中间层的输出称为激活。反向常需要前向保存的激活,所以训练峰值可能出现在反向期间。梯度累积是刻意跨微批次保留梯度;平常忘记清零则是错误。两种情况都看见梯度增加,但更新语义完全不同。
小批次累积,必须保持同一个平均目标
一个 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 如何分摊这些状态;下面可先切换阶段,看“哪一项被分摊”如何影响账面大小,不能把计算值当成可部署容量。
计算器采用的假设
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 手算承担不同目的,不应期待每步数值相同。下一课用它检验训练恢复。
把一次更新与内存账都留下
- 复算单参数例,把学习率改成 0.05,写出预测、损失、梯度、新权重和新损失。
- 陌生微批次分别含 2、1 条样本,平均梯度为 3、9。写出总体平均梯度;解释为什么两份均值直接平均不对。
- 为 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 课把视线移到输入队列和持久存储:既要让设备不空等,也要在进程退出后从同一个训练位置继续。
留下本课的产出
本课产出:训练状态内存账与更新自检。写下条件、证据、结果和仍不确定的事。保存不代表自动通过,下面的状态由你按验收条件自检。
记录只保存在当前浏览器。