在调试项目的时候,碰到显存不够,查阅了很多资料、方法,记录一下,以及一些自己的理解:
1、很多博客说如下方法,反正我是没啥用,或者说微乎其微
del tmp
torch.cuda.empty_cache()
2、每次迭代前,优化器记得清空,一般很少人会犯这个错误
optimizer.zero_grad()
的作用是清除所有可训练的torch.Tensor
的梯度
optimizer.zero_grad()
但是我看有的代码中会将model清空,这种情况一般是只是用该model做一个推理,不记录该model产生的梯度信息时适用
model.zero_grad()
的作用是将所有模型参数的梯度置为0一般常规做法都会将model的parameters放到optimizer中,在进行optimizer.zero_grad()的时候其实就相同于执行了model.zero_grad()
但是有的时候放进optimizer中的不是model的parameters,这时可以使用
model.zero_grad()
model.zero_grad()
3、关于with torch.no_grad()和requires_grad,见博客:什么时候该用with torch.no_grad()?什么时候该用.requires_grad ==False?_loss中的变量是不是都需要requires grad-CSDN博客
1、requires_grad 表达的含义是,这一参数是否保留(或者说持有,即在前向传播完成后,是否在显存中记录这一参数的梯度,而非立即释放)梯度,等待优化器执行optim.step()更新参数。
- 当requires_grad = True,则在前向计算后保留梯度,用于optimizer更新参数。但如果没有在optimizer中注册参数,那么即便保存了梯度也无法更新参数。(这时可能会产生一些参数的显存消耗)
- 当requires_grad = False,则不保留梯度,因此即便在optimizer中注册了参数,也没有梯度可以用来更新参数,因此参数不变。不过不影响梯度继续反向传播。
2、一般在测试的时候使用with torch.no_grad()可以禁用梯度计算,如果你的某段代码明确不需要参与更新,可以使用。
3、我的代码使用场景是data经过net1和net2得到loss,但是不想更新net1和net2,但是loss依赖于net1和net2的计算,这时用requires_grad。如果使用with torch.no_grad()就阻断了loss流动。
4、需要注意的是,一旦设置了某个网络A的参数requires_grad为False,使用torch.autograd.grad(loss, A_parameters)计算梯度的时候会出错,因为网络A没有保存梯度信息。
5、总之就是,requires_grad 在不计算梯度的同时保留梯度图的计算,不影响反向传播,而with torch.no_grad()是会禁止梯度计算并且中断梯度计算图。
4、重置优化器状态,感觉没啥用
optimizer.state = collections.defaultdict(dict)
这里有个注意的点是,Adam的显存是SGD的两倍多。
5、使用amp混合计算,这个确实可以有效降低显存,格式如下
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()
for epoch in epochs:
for input, target in data:
optimizer.zero_grad()
with autocast():
output = model(input)
loss = loss_fn(output, target)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
使用混合计算要注意可能会出现溢出情况,进而导致梯度出现Nan,具体参考文章:解决pytorch半精度amp训练nan问题_混合精度训练模型输出nan-CSDN博客
scaler的大小在每次迭代中动态估计,为了尽可能减少梯度underflow,scaler应该更大;但太大,半精度浮点型又容易overflow(变成inf或NaN).所以,动态估计原理就是在不出现if或NaN梯度的情况下,尽可能的增大scaler值。在每次scaler.step(optimizer)中,都会检查是否有inf或NaN的梯度出现:
1.如果出现inf或NaN,scaler.step(optimizer)会忽略此次权重更新(optimizer.step()),并将scaler的大小缩小(乘上backoff_factor);
2.如果没有出现inf或NaN,那么权重正常更新,并且当连续多次(growth_interval指定)没有出现inf或NaN,则scaler.update()会将scaler的大小增加(乘上growth_factor)。
6、 梯度累积,变相的扩大batchsize
for i, (images, target) in enumerate(train_loader):
# 1. input output
images = images.cuda(non_blocking=True)
target = torch.from_numpy(np.array(target)).float().cuda(non_blocking=True)
outputs = model(images) # 前向传播
loss = criterion(outputs, target) # 计算损失
# 2. backward
loss.backward() # 反向传播,计算当前梯度
# 3. update parameters of net
if ((i+1)%accumulation)==0:
# optimizer the net
optimizer.step() # 更新网络参数
optimizer.zero_grad() # reset grdient # 清空过往梯度
7、其实到最后发现,有时候不是显存不够,是自己代码问题,我最后只能调小batchsize解决的。
8、检查计算图大小,这个没办法,只能调小batchsize
在前向传播过程中,数据从输入节点开始,通过一系列操作节点,最终生成输出节点。每个操作节点会保存其操作所需的所有输入数据和生成的输出数据的引用。这样,整个数据流动过程形成了一个有向图,即计算图。