设想你是一名登山爱好者,为了清晨在山顶看日出,背着一只 10 升的小包出发。山路上有 24 个观景台,每个台子的风景都值得拍照。可向导叮嘱:下山时每到一处岔路口,都要翻看对应的照片才能辨清方向。于是你每过一个观景台就拍一张高清大图,一张张塞进背包。拍到第 6 个台子,背包已经鼓得拉不上拉链——而山路才走了四分之一。 这就是深度神经网络训练时的真实困境:模型把前向传播每一层算出的“中间激活值”都留在显存里,留给反向传播算梯度时当“路标”使用。层数越多、序列越长、batch 越大,显存就越吃紧,许多大模型甚至还没开始训练,就弹出了"out of memory"。 梯度检查点(Gradient Checkpointing)提供的,正是老登山客的智慧:不必每个观景台都拍照,只在少数几个关键位置留影;遇到岔路发现没照片时,就退回最近的有照片的台子,重走那段路,现场补拍。虽然多花了一些脚程(计算),但背包(显存)一下子轻了一大半。
对 AI 从业者而言,梯度检查点是突破“显存墙”的必备技能。掌握它,你就能在有限的 GPU 上训练更大的模型,读懂大模型训练框架中那些隐形设计,从容应对训练 OOM 与效率权衡。
目录
可视化演示
展示梯度检查点三维度:前向保存与反向重计算流程(仅checkpoint层保存激活其余反向时重计算内存从O(n)降至O(√n))、显存对比(标准训练激活占55%检查点降至12%总显存减少约50%LLaMA 65B batch从1提升至4-5)、选择性检查点策略(按计算-内存比决策高计算低成本层设为checkpoint低计算高内存层重算),三模式切换理解用计算换内存的核心机制。
本节配有交互动效(进入视口后自动加载,约 3KB)。
'+C1+'
显存瓶颈:背包爆满
设想你背着一只 10 升的小包去登山,目标是清晨在山顶看日出。向导反复叮嘱:下山时每到一个岔路口,都要翻看对应的风景照才能找对路。你生怕漏掉任何信息,于是经过 24 个观景台时都拍一张高清大图,一张张往包里塞。第 6 个台子还没到,背包就鼓得再也装不下一瓶水。
神经网络训练也有类似的下山之路。前向传播从输入层一路计算到输出层,每一层都会产生“中间激活值”——它是反向传播计算梯度时必不可少的“路标”。深度学习框架为了避免重复计算,默认把这些激活值全部保留在显存里,相当于把每张照片都背在身上。模型越深、序列越长、批量越大,显存压力就越大,训练常常还没开始就报"out of memory"。
若网络有 N 层、每层激活占用 m_layer 字节,激活占用的显存为:
24 层网络每层激活 2MB,一次前向就要在显存里堆 48MB 的“照片”;真实的大模型动辄上百层、每层激活数百 MB,背包自然瞬间爆满。所以,想轻装上山,只能选择少带照片。
- 层数 N神经网络中需要保存激活值的层数。N 越大,显存中需要常驻的激活值“份数”越多。
- 单层激活体积 m_layer每一层前向传播产生的中间结果大小,与隐藏维度、序列长度直接相关。
- 总激活显存 M_fullN 与 m_layer 相乘,得到全部中间激活占用的显存总量,相当于“每层一张照片”的总重量。
核心原理:隔站拍照
老登山客的办法很简单:不在每个观景台都拍照,只在每隔几个台子选一个“补给兼摄影点”。比如 24 站只拍第 4、8、12、16、20、24 站的风景。遇到岔路,就回到最近的照片点,重走那一段,到岔路时再看一眼路况——虽然多走几步,但背包一下子轻了 75%。
梯度检查点正是这个思路。前向传播时只保存少数几层的激活值,这些位置叫作“检查点”(Checkpoint),其余中间激活直接丢弃。反向传播需要某一层的激活时,从最近的检查点重新执行这一段前向计算,现场“补拍”一张。这样既保留了“路标”,又不必把全部照片背在身上。
设网络共 N 层,均匀分成 k 段,只存 k 个检查点;反向时每段临时重算,激活峰值不超过一段的长度:
当 k = √N 时取最小值:M_ckpt ≈ 2√N × m_layer。显存占用从 O(N) 降到了 O(√N)。
以 24 层、每层 2MB 为例:全部保存需要 48MB,而取 k≈5 时,检查点方案只需约 20MB。隔站拍照,让一只 10 升的小背包也能装下 24 站的“路书”。
- 检查点总数 k我们总共保存 k 份激活,每份大小为 m_layer,这部分是“已拍好的照片”。
- 段内重算临时激活N/k 是每段包含的层数,反向重算段内激活时,缓存里最多出现一段的激活量。
- 最优分段数 k = √N对 k 求极值可知,当 k 等于 √N 时两者之和最小。
- 最小显存 2√N × m_layer代入 k = √N,得到检查点方案的最优显存规模。
检查点策略:补给站选址
隔几站拍一次?拍得太密,背包依然很重;拍得太疏,来回折返的腿脚受不了。有经验的向导会先看地形:陡坡、岔路多的地方多拍几张,平直大道少拍甚至不拍。检查点的位置,本质上是一个“补给站选址”问题。
朴素做法是均匀分段,把 N 层等分成 k 段,每段长度 S = N/k,简单且容易实现。进阶做法是考虑每层的计算密度与激活体积:激活特别大的层(比如注意力层、大分辨率特征图)优先设为检查点,因为“省下”的显存最多;而重算成本低的层(如激活函数、归一化)也适合作为检查点,因为补拍代价小。
工程上常用动态规划或贪心搜索来求解最优分段。假设各段长度为 L₁, L₂, ……, L_k,重算子层数越多代价越高,优化目标可写为:
好的选址策略,是均匀打底、关键层优先、搜索兜底。就像老向导知道每条山路的脾气,优秀的调度器能把“少拍照、多跑腿”的平衡点拿捏得恰到好处。
- 段长 L_i第 i 段包含的层数,段长决定了一次重算需要跑多远。
- 平方项 L_i²/2重算子层数的代价不能简单叠加,它随段长近似平方增长,体现出“越长的回头路越累”。
- 求和符号把所有段的折返代价加总,得到整个网络的反向重算总成本。
- 约束条件所有段长加起来必须等于总层数 N,选址不能凭空多出或漏掉楼层。
时间开销:来回折返的代价
隔站拍照不是免费的午餐。每退回一个摄影点重走一段路,都要额外花时间。如果照片拍得太少,你可能会在同一个坡上来回爬三趟,日出都过了还没到山顶。梯度检查点同样要付出“重计算”的时间成本。
原本一次训练迭代包含一次前向传播和一次反向传播。前向计算量记为 F,反向传播大约是前向的两倍(经验值 2F),那么基准迭代成本是 3F。启用检查点后,反向传播每遇到一段没有照片的路,都要从最近检查点补跑一次该段前向;近似来看,整网前向被“重跑”了一半到一遍,额外增加约 F 的计算量:
在显存受限的 GPU 上,这 33% 的时间开销往往远比“模型根本无法训练”更划算。也是为什么大模型训练普遍愿意接受这份学费。
- 前向量 F一次前向传播的浮点计算总量,是模型的“单程爬山”成本。
- 反向量 2F反向传播要计算梯度,通常约为前向的两倍,相当于下山的体力消耗。
- 额外前向 +F检查点方案在反向期间至少补跑一次前向,所以总成本多出一个 F。
- 比值 4/3 ≈ 1.33总时间约为原来的 1.33 倍,即训练时间增加约 33%。
工程实践:PyTorch 落地
有了策略,还得有趁手的工具。就像买好了登山杖和摄影绳,照着步骤操作就能上路。PyTorch 把梯度检查点做成了开箱即用的接口,不需要自己发明轮子。
核心接口是 torch.utils.checkpoint.checkpoint。它接受一个网络模块和输入,在前向传播时只保留输入,不保存中间激活;反向传播时,它会在内部自动重新执行一次该模块的前向,把激活“补拍”出来,再用于梯度计算。
启用检查点后,理论上节省的显存为:
实际使用时,把模型中每个 Transformer Block 整体包进 checkpoint 即可,例如 output = checkpoint(block, x)。框架还提供了 torch.utils.checkpoint.checkpoint_sequential,适合顺序模型直接按段划分。需要注意的是,启用后显存变量明显下降,但训练日志里的耗时也会相应上升——这正是前几节反复强调的取舍。
- 省下的层数份数 N − 2√N全存需要 N 份激活,检查点方案只需约 2√N 份,差额就是省下的显存“份数”。
- 单层激活 m_layer每份激活的体积,把“份数”换算成实际字节数,就是能省下的显存。
高级优化:老驴友的省力心得
资深向导登山时从不蛮干:下坡不走回头路、把器材捆在一起背、天快亮时集中赶路。梯度检查点同样有一整套“省力组合拳”,让省出来的显存进一步放大。
其一是算子融合。把相邻的重算子(如卷积+激活、注意力+归一化)合并成一个算子,前向时不必把中间结果显式写出,重算时的临时显存也会变小。其二是混合检查点:显存大头通常集中在少数大激活层(如注意力矩阵、高分辨率特征图),只对约三分之一的大层设置检查点,其他小激活层照常保留,往往能得到 70% 以上的显存节省,而重算开销远低于均匀分段。
混合策略的显存模型为:
其中 𝒞 是设置了检查点的层集合,S_max 是未设置检查点的连续段中的最大段长。
配合内存调度(把参数或优化器状态暂存到 CPU,用时再取回),这套组合拳让现代大模型训练几乎把梯度检查点当作默认标配。LLaMA、GPT 等大规模预训练,无一不在用它把单位 GPU 的吞吐压榨到极限。
- 集合 𝒞被选中做检查点的层集合,通常优先放激活体积大的层。
- 检查点占用之和对集合中每一层 m_i 求和,得到所有检查点照片的总空间。
- 最大未检查点段 S_max没有设置检查点的连续段里,最长的一段长度,它决定了重算时的临时显存峰值。
应用边界:雪山与险峰
隔站拍照不是万能钥匙。雪山上步距大、掉头难,如果折返距离太长,不如多带几张照片。梯度检查点也有明确的适用边界:当计算极度紧张、训练时长成为首要矛盾时,33% 的额外开销就变得难以接受;当模型本身不大、显存绰绰有余时,它更是多此一举。
一个实用的判据是:检查点启用后,激活显存、参数显存、梯度显存三者之和必须不超过设备显存:
梯度检查点是显存应急预案,而非银弹。理解它的边界,才能在大模型训练中做出清醒的权衡:何时用它救命,何时配合其他并行手段,何时干脆放弃。把这套“隔站拍照”的智慧用在正确的地方,你就能在显存的悬崖边,稳稳地走向更大的模型。
- 激活显存 M_ckpt启用检查点后中间激活占用的峰值显存。
- 参数显存 M_param模型权重本身占用的空间,与是否检查点无关。
- 梯度显存 M_grad反向传播累积的梯度所占空间,同样不可省略。
- 设备显存上限 M_limitGPU 的总显存容量。三者和超过上限,模型依然装不下。
AI 应用场景
大语言模型预训练
阶段:模型训练 / 显存优化
解决的问题:GPT、LLaMA 等大模型训练时,激活值动辄占用数十甚至上百 GB 显存,传统方式下单卡根本放不下。梯度检查点把激活显存从 O(N) 降到 O(√N),让数百亿参数模型可以在有限的 A100/H100 集群上启动训练。
具体案例:某团队预训练 130 亿参数的 LLaMA 规模模型,原本每个 Transformer 层激活需要 4.2GB,启用 torch.utils.checkpoint 后单层激活降到 1.1GB,配合混合精度,总算力不变的情况下把 batch size 提升了 2 倍,训练吞吐反而更优。
长序列视觉 Transformer
阶段:模型推理 / 微调适配
解决的问题:视频理解、高分辨率遥感影像等任务,输入序列极长,注意力矩阵的激活值呈平方级爆炸。传统方法要么切短序列损失信息,要么爆显存。梯度检查点让长序列微调成为可能。
具体案例:某安防团队微调 ViT-Large 处理 4096 帧视频,激活显存超 80GB;将每 4 层设一个检查点后,显存降至 26GB,单张 A100 即可完成微调,代价仅是训练时间增加约 35%。
云上多卡训练成本优化
阶段:训练基础设施 / 成本控制
解决的问题:云 GPU 按小时计费,模型太大导致必须租用更高配实例或更多卡。梯度检查点能以 33% 的时间换 50% 以上的显存,帮助团队用更少的卡跑同一个模型,总成本反而下降。
具体案例:某创业公司在 4×A10 上训练 7B 对话模型,启用混合检查点 + 梯度累积后,从 8 卡降为 4 卡,训练时长仅增加 28%,月度 GPU 账单减少约 45%。
Python 演示
# 梯度检查点:前向激活显存模拟器def simulate_full(num_layers, act_size_mb): total = 0 for i in range(num_layers): total += act_size_mb return total def simulate_checkpoint(num_layers, act_size_mb, num_segments): seg_len = num_layers / num_segments checkpoints = num_segments * act_size_mb temp_acts = seg_len * act_size_mb return checkpoints + temp_acts def main(): L = 24 m = 2.0 full_mem = simulate_full(L, m) print("[模拟] 24 层网络,每层激活 2MB") print("不启用梯度检查点: 显存 = %.1f MB" % full_mem) print("启用梯度检查点:") for k in [1, 2, 4, 5, 6, 8, 12, 24]: mem = simulate_checkpoint(L, m, k) ratio = mem / full_mem * 100 print(" 分段 %2d: 显存 = %5.1f MB 为原来的 %4.1f%%" % (k, mem, ratio)) if __name__ == "__main__": main()
for i in range(num_layers)模拟框架逐层把激活值留在显存里,一层累加一层。total += act_size_mb每层的激活体积累加进总显存,体现线性增长。seg_len = num_layers / num_segments平均每段包含的层数,决定反向重算的临时显存峰值。checkpoints = num_segments * act_size_mb检查点照片本身占用的空间,分段越多照片越多。temp_acts = seg_len * act_size_mb反向补拍一段时临时占用的激活空间,段越长临时显存越大。ratio = mem / full_mem * 100算出检查点方案相当于原始显存的百分比,一眼看出最优分段在中间。
常见误区
误区我以为梯度检查点像是“压缩照片”,省了显存,拍出来的“路标”变模糊了,所以模型精度应该会下降。
正解梯度检查点并不会丢弃任何信息,只是把中间激活“推迟”到反向传播时重新计算。重算出来的激活值与原来完全一致(数值上可能有浮点级微小差异,但不影响训练结果)。它改变的是显存与时间的分配,而不是数据精度。
为何常见:人们容易把“省显存”和“省精度”混为一谈,看到名字里有“检查点”就联想到模型压缩或量化。
误区既然分段能省显存,那我就把每层都设成检查点,分 24 段,显存应该省到极限吧?
正解分段数 k 有一个最优值。从公式 M_ckpt = k·m + (N/k)·m 可以看出,k 太大时检查点本身占用的显存会上升;k 太小则重算临时显存增加。最优在 k ≈ √N 附近。比如 24 层时 k=5 最省,k=24 时显存反而接近全保存的水平。
为何常见:只看到“间隔保存”的直觉,忽略了检查点本身也要占空间,重算段也有临时开销,于是滑向极端取值。
误区都是省显存的手段,梯度检查点和梯度累积(Gradient Accumulation)应该是同一种东西吧?
正解两者解决的问题完全不同。梯度累积是把大 batch 拆成小 batch,先攒梯度再更新,省的是“一次前向的临时激活峰值”;梯度检查点则通过“不存中间激活、反向重算”来省显存。两者可以叠加使用,但原理和收益各有侧重。
为何常见:二者都出现在“内存优化”教程里,名字里都有“梯度”,初学者容易混为一谈。
误区既然能省这么多显存,为什么不全开?感觉它是零成本的优化技巧。
正解梯度检查点不是零成本,它每次反向都要补跑一段前向,整体训练时间约增加 33%~40%。它是一笔“用时间换空间”的买卖,在显存紧张时物超所值,但在计算极度紧张或显存充裕的场景下并不划算。
为何常见:教程往往重点讲显存收益,对时间开销一笔带过,让初学者误以为它没有任何代价。
面试题
Q1. 梯度检查点主要靠哪种方式节省显存?
- A.减少模型参数数量
- B.不保存全部中间激活← 正确答案
- C.降低训练计算精度
- D.缩小训练数据集规模
答案:B。梯度检查点只保存少数检查点层的激活值,其余中间激活在反向时重算,本质是“少存照片、需要时补拍”。它不改变参数量、精度或数据规模,所以 B 正确,A、C、D 均与机制无关。
Q2. 启用梯度检查点后,训练时间大约会额外增加多少?
- A.约增加 10%
- B.约增加 33%← 正确答案
- C.约增加 100%
- D.约增加 3%
答案:B。前向计算量记为 F,反向约为 2F,基准总成本 3F。检查点方案反向时补跑一次前向,总成本变为 4F,因此时间开销约为 (4F-3F)/3F ≈ 33%。选 B。
Q3. 24 层网络、每层激活 2MB,采用均匀分段的最优检查点策略时,激活显存约为多少?
- A.大约 20MB← 正确答案
- B.大约 48MB
- C.大约 24MB
- D.大约 96MB
答案:A。最优分段数 k = √24 ≈ 5,代入公式 M_ckpt ≈ 2√N × m = 2×4.9×2 ≈ 19.6MB,约 20MB。48MB 是不启用检查点的值,C、D 均不是正确代入结果。
Q4. 以下哪些场景适合使用梯度检查点?
- A.大模型预训练显存紧张← 正确答案
- B.长序列 Transformer 训练← 正确答案
- C.计算极紧张且不可让步
- D.追求极致训练速度不考虑显存
答案:AB。大模型预训练和长序列训练都是激活显存爆炸的典型场景,检查点能以 33% 时间换大量显存,A、B 正确。C 和 D 场景下时间成本是主要矛盾,不适合用时间换空间。
Q5. 反向传播时,梯度检查点如何获得被丢弃的中间激活?
- A.从根节点重算整个网络
- B.从最近检查点前向重算← 正确答案
- C.从目标层反向到检查点
- D.从随机权重重新初始化
答案:B。需要某层激活时,框架会从距离该层最近的检查点开始,重新执行这一段的前向传播,“现场补拍”出所需的中间激活。从根节点重算整个网络太浪费,反向重算方向也错了,随机初始化更是毫无意义。
Q6. PyTorch 中实现梯度检查点的原生接口是?
- A.优化器 Adam 接口
- B.官方 checkpoint 接口← 正确答案
- C.全连接 Linear 层
- D.随机种子 seed 接口
答案:B。torch.utils.checkpoint.checkpoint 是 PyTorch 提供的梯度检查点接口,传入模块和输入即可自动完成“少存激活、反向重算”。Adam 是优化器、Linear 是网络层、seed 是随机控制,均与检查点无关。
Q7. 设计混合检查点策略时,哪些层更适合设为检查点?
- A.激活体积大的层优先级高← 正确答案
- B.必须所有层都设置检查点
- C.重算成本低的层优先设置← 正确答案
- D.算子融合无法与之配合使用
答案:AC。大激活层省显存收益最大,重算成本低的层(如轻量激活层)补拍代价小,二者都适合设为检查点,A、C 正确。混合策略正是“选择性设置”,不是所有层都设,B 错;算子融合常与检查点搭配以进一步降低重算开销,D 错。
Q8. 梯度检查点与梯度累积的关系是?
- A.两者本质完全相同
- B.梯度累积节省的是优化器侧显存← 正确答案
- C.梯度累积节省的是激活显存
- D.检查点会改变梯度值
答案:B。梯度累积把小 batch 的梯度累加后再更新,主要降低优化器更新频率与梯度缓冲区的峰值压力,不是省激活显存,B 正确。检查点与梯度累积是两种独立手段,A 错;梯度累积并不直接省激活显存,C 错;检查点不改变梯度值,D 错。
Q9. 对于忽略参数和梯度的纯激活显存,检查点方案在最优分段下的显存复杂度是?
- A.k=1 时内存最小
- B.k=N 时内存最小
- C.k=√N 时内存最小← 正确答案
- D.k=2N 时内存最小
答案:C。M_ckpt = k·m + (N/k)·m,对 k 求导令其为零可得 k = √N,此时两项相等,总和最小。k=1 时重算临时显存巨大,k=N 时检查点本身占满显存,k=2N 已超出合理范围。
Q10. 关于梯度检查点,下列说法正确的有?
- A.数值与不检查点基本一致← 正确答案
- B.训练时间约增三成← 正确答案
- C.能减少模型参数量
- D.显存由线性降为开方← 正确答案
答案:ABD。梯度检查点通过重算得到与原来一致的激活,数值基本一致,A 正确;时间代价约为 33%,B 正确;显存复杂度从 O(N) 降到 O(√N),D 正确。它不改变参数数量,C 错误。