4. 重计算(Recomputation)技术:激活值重计算原理、选择性重计算策略、重计算与流水线并行的结合、重计算带来的计算开销

大家好,我是你们的显存优化讲师。今天我们来聊聊重计算技术,也就是常说的激活值重计算。说实话,这个技术是我在训练百亿参数模型时,用得最频繁的「救命稻草」之一。你想想看,当你的显存差那么一点点就爆掉的时候,重计算往往能帮你省下30%-50%的显存。

4.1 激活值重计算原理

先说说基本原理。在神经网络前向传播时,每一层都会产生激活值(activation)。这些激活值需要保留到反向传播时使用。比如一个100层的Transformer,每一层都要存下所有中间结果。显存就是这么被吃掉的。

重计算的思路很简单:我不存了,用的时候再算一遍。具体来说,前向传播时只保留一部分关键节点的激活值(比如每隔几层存一个checkpoint)。反向传播时,如果发现某个激活值没存,就从最近的checkpoint重新计算一遍。

核心思想:用时间换空间

重计算本质上是用额外的计算开销,换取显存空间的释放。典型的trade-off是:
显存节省:30%-50%
计算开销增加:20%-33%

我在项目中遇到过这样的情况:一个72层的GPT模型,如果不做重计算,单卡根本放不下。用了重计算后,显存占用从48GB降到了28GB,虽然训练时间多了15%,但至少能跑了。嗯,这就是典型的「有总比没有好」。

4.2 选择性重计算策略

不是所有层都值得重计算。我个人习惯把层分成三类:

层类型 重计算策略 原因
Attention层 建议重计算 激活值大(QKV矩阵),计算量相对小
FFN层 选择性重计算 激活值中等,计算量大
Embedding层 不重计算 激活值小,计算量也小

为什么会这样?Attention层的QKV矩阵维度是[batch, seq_len, hidden_dim],这个量级非常大。但Attention的计算本身并不重,主要是矩阵乘法。所以重计算Attention的性价比很高。

FFN层就不一样了。FFN的中间维度通常是hidden_dim的4倍,计算量很大。如果你重计算FFN,节省的显存不多,但增加的计算开销却不少。我建议只对FFN的中间激活做重计算,输入输出保留。

我的经验法则:

选择重计算层时,看一个指标:激活值大小 / 计算量。这个比值越大,越值得重计算。我一般设定阈值为0.3,比值超过0.3的层就做重计算。

4.3 重计算与流水线并行的结合

这里有个坑,我曾经踩过。流水线并行本身就有多个stage,每个stage负责几层。如果你在每个stage内部再做重计算,需要特别注意通信和计算的调度。

举个例子,假设流水线有4个stage,每个stage有6层。你决定每3层存一个checkpoint。那么反向传播时,每个stage需要从checkpoint重新计算中间3层的激活值。问题来了:重计算和流水线气泡怎么共存?

我建议的做法是:

  1. 把重计算和流水线调度对齐:让重计算发生在流水线的空闲时间(bubble time)里。这样不会额外增加训练时间。
  2. 使用异步重计算:当前stage在等待前一个stage的梯度时,可以提前开始重计算下一批数据。
  3. 控制checkpoint粒度:流水线stage内部的checkpoint不要太多,否则重计算会打乱流水线的节奏。

避坑指南:

我曾经在一个8卡流水线并行训练中,把每个stage的checkpoint设成了每层一个。结果重计算导致流水线气泡从20%飙升到了45%,训练效率反而下降了。后来我改成每3层一个checkpoint,气泡才回到25%。

4.4 重计算带来的计算开销

重计算不是免费的午餐。我们来算一笔账:

假设一个Transformer层的前向传播需要1个单位时间,反向传播需要2个单位时间。如果不做重计算,总时间是3个单位。如果做全量重计算(每层都重算),前向传播变成2个单位(一次正常前向,一次重计算),反向传播还是2个单位,总时间变成4个单位。计算开销增加了33%。

但实际中,我们很少做全量重计算。选择性重计算通常只增加20%-25%的计算开销。这个代价换来的显存节省,在大多数场景下是值得的。

我记得有一次训练一个130亿参数的模型,显存刚好差5GB。我用了选择性重计算,只重计算了Attention层和FFN的中间激活,显存降了8GB,计算开销只增加了18%。嗯,这个trade-off我很满意。

计算开销的量化分析:

假设模型有L层,每层前向计算量为F,反向计算量为2F:

  • 无重计算:总计算量 = L * (F + 2F) = 3LF
  • 全量重计算:总计算量 = L * (2F + 2F) = 4LF,开销增加33%
  • 选择性重计算(重计算50%的层):总计算量 = L * (1.5F + 2F) = 3.5LF,开销增加17%

最后说一个实用技巧:动态调整重计算策略。我在训练过程中会监控显存使用率和计算时间。如果显存还有余量,就减少重计算层数,提高训练速度。如果显存快爆了,就增加重计算层数。说白了,就是让系统自己找到最优的平衡点。

好了,重计算技术就讲到这里。记住核心:不是所有激活值都值得存,也不是所有层都值得重算。找到那个平衡点,你的训练效率就能最大化。

重计算技术核心逻辑 无重计算 前向传播 存所有激活值 反向传播 使用已存激活值 显存占用:高 计算开销:低 全量重计算 前向传播 不存激活值 重计算 重新前向传播 反向传播 使用重算激活值 显存占用:低 计算开销:高(+33%) 选择性重计算(推荐) 前向传播 存checkpoint(每k层) 反向传播时 从checkpoint重算 反向传播 使用重算的激活值 如何选择重计算层? 激活值大 / 计算量小 → 重计算 激活值小 / 计算量大 → 不重计算 典型节省:显存-40% | 计算+20%

想要更多资料,联系可可