在深度学习训练过程中,优化器的选择直接影响显存占用和计算效率。理解SGD和Adam在内存占用上的区别,对于资源受限的场景尤为重要。显存占用的核心差异源于优化算法需要维护的状态变量数量。
关键提示:优化器显存占用主要包含两部分——模型参数本身和优化器状态变量。参数和梯度是所有优化器共有的基础开销,而状态变量才是不同优化器间的差异所在。
以单精度浮点模型为例,每个参数占用4字节显存。假设模型参数量为N,我们来看不同优化器的显存组成:
基础开销(所有优化器共有):
额外状态变量(优化器特有):
标准SGD(随机梯度下降)是最轻量的优化器,其显存占用仅包含:
总显存占用约为2N。这是所有优化器中最节省显存的方案。
当引入动量(Momentum)后,SGD需要额外维护一个动量缓存(momentum_buffer),其维度与模型参数相同。这使得显存占用增加到:
总显存约为3N,比基础SGD增加了50%。
Adam作为自适应学习率优化器,需要维护更多状态信息:
总显存约为4N,是基础SGD的两倍,比带动量的SGD多出33%。
技术细节:Adam的一阶矩(m)类似于动量,记录梯度的指数移动平均;二阶矩(v)则跟踪梯度平方的移动平均,用于自适应调整每个参数的学习率。
| 优化器类型 | 状态变量数量 | 额外显存占用 | 总显存占用 |
|---|---|---|---|
| 纯SGD | 0 | 0 | 2N |
| SGD+Momentum | 1 | N | 3N |
| Adam | 2 | 2N | 4N |
模型规模的影响:
批次大小的影响:
精度设置的影响:
针对Adam的高显存问题,研究者提出了多种改进方案:
AdamW:
Adafactor:
SM3:
通过结合FP16和FP32的混合精度训练,可以显著降低显存需求:
这样可以将显存需求降低约50%,同时保持模型精度。
当显存不足时,可以采用梯度累积:
虽然这会增加训练时间,但允许在有限显存下使用更大的"虚拟"批次。
在实际项目中,我通常会这样决策:
Adam需要维护两个与参数同维度的状态变量(m和v),而SGD-Momentum只需要一个。这两个变量分别用于:
在PyTorch中可以使用以下方法:
python复制import torch
from pynvml import nvmlInit, nvmlDeviceGetHandleByIndex, nvmlDeviceGetMemoryInfo
def print_gpu_usage():
nvmlInit()
handle = nvmlDeviceGetHandleByIndex(0)
info = nvmlDeviceGetMemoryInfo(handle)
print(f"Used GPU memory: {info.used/1024**2:.2f} MB")
虽然Adam通常收敛更快,但这种优势是有代价的:
在PyTorch中,优化器状态是按参数张量存储的。例如:
python复制import torch
import torch.nn as nn
import torch.optim as optim
model = nn.Linear(10, 10)
optimizer = optim.Adam(model.parameters())
# 查看优化器状态
print(optimizer.state_dict()['state'].keys())
对于Adam,每个参数张量会有两个对应的状态张量(m和v)。
TensorFlow的优化器实现略有不同:
近年来出现了多种试图平衡性能和显存占用的新优化器:
这些优化器在显存占用上各有特点,需要根据具体场景选择。