用反向重计算减少训练激活占用
PyTorch 的非重入 activation checkpoint 在反向传播需要中间张量时重新执行对应前向片段,以额外计算换取更低的激活存储需求。
工程问题#
训练时大量中间激活必须活到反向传播,可能成为显存瓶颈;并非所有中间结果都需要一直保存。
实际实现#
checkpoint 用 saved_tensors_hooks 把原本要保存的中间张量替换为 holder,保留重新执行所需输入。反向解包 holder 时触发 recompute_fn,恢复所需张量;非重入实现默认支持所需张量齐备后提前停止重计算。
处理流程#
- 保存重放所需输入及相应执行状态
- 前向过程为待保存张量留下 holder,而非长期保留每个激活
- 反向首次请求这些张量时重新运行前向片段
- 拿到所需激活后继续计算梯度
源码片段#
固定版本 · 原文摘录1117 if not frame.is_recomputed[gid]:1118 ctx = frame.input_saver.grad_fn1119 args = ctx.get_args(ctx.saved_tensors)11201121 try:1122 with _recomputation_hook(1123 weakref.ref(frame), gid1124 ), torch.autograd.enable_grad():1125 frame.recompute_fn(*args)1126 except _StopRecomputationError:1127 pass1128 frame.is_recomputed[gid] = True1129 frame.check_recomputed_tensors_match(gid)首次解包时获取保存的输入,开启梯度记录并调用 recompute_fn;捕获提前停止信号后记录已完成重计算,并校验重计算张量的元数据。该校验不是一般意义上的数值完全相等证明。
1500 def recompute_fn(*inputs):1501 kwargs, *args = inputs1502 # This will be called later during recomputation. This wrapping enables1503 # the necessary global state to be captured.1504 rng_devices = []1505 if preserve_rng_state and had_device_in_fwd:1506 rng_devices = fwd_devices1507 with torch.random.fork_rng(1508 devices=rng_devices, enabled=preserve_rng_state, device_type=device_type1509 ):1510 if preserve_rng_state:1511 torch.set_rng_state(fwd_cpu_state)1512 if had_device_in_fwd:1513 set_device_states(fwd_devices, fwd_device_states, device_type=device_type)15141515 device_autocast_ctx = torch.amp.autocast(重计算包装器在配置允许时恢复前向的 CPU 和相关设备 RNG 状态,使 dropout 等随机运算的重放保持相容;设备状态覆盖仍有实现边界。
片段展示按需触发重计算与随机状态恢复;holder 的建立、autocast 上下文和实际函数调用也在同一源文件。
收益与取舍#
工程分析带来的收益
- 减少选定计算区域为反向保存的中间激活
- 能在显存受限时扩大可训练模型或批次的选择空间
付出的代价
- 额外前向计算与状态保存会增加训练开销;实际收益依分段和张量大小而定
- 重放时的函数行为必须与原前向相容,副作用、可变全局状态或未覆盖的设备随机状态可能破坏等价性
可比较的选择
保存全部激活省去重计算,但活跃激活的内存峰值更高。
激活卸载到主存用传输与主存容量换显存,需要权衡带宽、同步和恢复延迟。
版本与适用范围#
对应 PyTorch v2.5.1 的 use_reentrant=False 路径;这里的 checkpoint 指训练激活重计算,不是把模型参数写入磁盘的 checkpoint,也不代表所有 torch.compile 后端的执行方式。
参考来源#
源码、官方文档与原始论文torch/utils/checkpoint.py · checkpoint / _checkpoint_hook / _checkpoint_without_reentrant_generator
相关实现与比较#
4 个相关案例HNSW 从稀疏上层快速定位区域,再在底层扩大候选搜索。
借助计算依赖图与使用者计数,清理既不影响输出也无副作用的节点。
Linux BPF 验证器的 check_cfg 使用显式指令栈执行非递归 DFS,检查跳转目标、可达性及控制流结构。
LLVM 的 legacy 调用图 pass 管理器使用 Tarjan SCC 迭代器,将相互递归的函数作为一组,并按被调用方优先的顺序处理分组。