activation_offload.py — 激活值 CPU 卸载¶
文件路径: verl/utils/activation_offload.py
文件概述¶
activation_offload.py 实现了前向传播中保存的激活值(saved for backward)的 CPU 卸载机制。在反向传播时再从 CPU 加载回 GPU。这样可以大幅降低 GPU 显存峰值。
背景知识¶
训练神经网络时,前向传播会保存中间激活值以供反向传播使用。对于大型 Transformer 模型,这些激活值占据大量 GPU 显存。激活卸载将它们暂时移到 CPU 内存,用时再取回。
相比激活检查点(gradient checkpointing,丢弃激活并在反向传播时重算),激活卸载不增加计算量,但增加了 CPU-GPU 通信。
核心类详解¶
1. CpuOffloadHookWithOffloadHandler — 上下文管理器¶
class CpuOffloadHookWithOffloadHandler:
def __enter__(self):
torch._C._autograd._push_saved_tensors_default_hooks(
self.on_save_for_backward,
self.on_get_saved_tensor
)
def on_save_for_backward(self, tensor):
# 前向传播保存张量时,交给 handler 决定是否卸载
return self.offload_handler.tensor_push(tensor)
def on_get_saved_tensor(self, saved_state):
# 反向传播取回张量时,从 handler 恢复
return self.offload_handler.tensor_pop(saved_state)
利用 PyTorch 的 saved_tensors_hooks 机制,拦截所有 autograd 保存的张量。
2. SynchronizedGroupOffloadHandler — 同步卸载¶
class SynchronizedGroupOffloadHandler(OffloadHandler):
@staticmethod
def offload(src_tensor, pin_memory=True):
cpu_backup = torch.empty(..., device="cpu", pin_memory=pin_memory)
cpu_backup.copy_(src_tensor, non_blocking=True)
return (src_tensor.device, cpu_backup)
@staticmethod
def reload(state, non_blocking=None):
dev, cpu_backup = state
return cpu_backup.to(dev, non_blocking=non_blocking)
将张量复制到 CPU 固定内存(pinned memory),使用 non_blocking=True 实现异步传输。
3. AsyncDoubleBufferGroupOffloadHandler — 异步双缓冲卸载¶
class AsyncDoubleBufferGroupOffloadHandler(SynchronizedGroupOffloadHandler):
def __init__(self, num_offload_group, num_model_group, ...):
self.d2h_stream = get_torch_device().Stream() # Device to Host 流
self.h2d_stream = get_torch_device().Stream() # Host to Device 流
使用独立的 CUDA stream 进行 D2H(设备到主机)和 H2D(主机到设备)传输,与计算 stream 重叠。"双缓冲"意味着同时有两组激活在 GPU 上(当前层 + 预取的下一组)。
4. 按层分组卸载¶
class GroupCommitFunction(torch.autograd.Function):
"""标记层边界,触发一组激活的卸载/恢复"""
@staticmethod
def forward(ctx, tensor, cpu_offload_handler):
cpu_offload_handler.on_group_commit_forward()
return tensor
每个 Transformer 层结束时调用 group_commit,触发该层激活的批量卸载。
5. 入口函数¶
def enable_activation_offloading(model, strategy, enable_ckpt=False):
"""为模型启用激活卸载"""
layers = [] # 找到所有 FSDP 包装的层
get_layers(model)
# 创建 handler 并包装每层的 forward 方法
handler = ActivationHandler(context, sync_func, tensor_filter, enable_ckpt)
for layer in layers:
handler.wrap_module_forward_method(module)
工作流程图¶
前向传播:
Layer 0 forward → 激活卸载到 CPU (d2h_stream)
Layer 1 forward → Layer 0 卸载完成, Layer 1 激活卸载
...
反向传播:
Layer N backward ← 从 CPU 预取 Layer N-1 激活 (h2d_stream)
Layer N-1 backward ← 激活已就绪
...
核心类/函数列表¶
| 类/函数 | 说明 |
|---|---|
CpuOffloadHookWithOffloadHandler |
卸载上下文管理器 |
SynchronizedGroupOffloadHandler |
同步卸载 |
AsyncDoubleBufferGroupOffloadHandler |
异步双缓冲卸载 |
GroupCommitFunction |
层边界标记 |
ActivationHandler |
层级激活管理 |
enable_activation_offloading() |
启用入口 |
FSDPParameterFilter |
过滤模型参数(不卸载参数本身) |
与其他模块的关系¶
- 依赖
device.py获取 CUDA stream - 依赖
fsdp_utils.py检测 FSDP 层 - 被 Actor worker 在训练时启用
小结¶
activation_offload.py 实现了高效的异步双缓冲激活卸载,通过将前向传播的中间激活值暂存到 CPU,显著降低 GPU 显存需求,同时通过异步传输尽量隐藏通信开销。