解决多次forward导致的RuntimeError: Expected to mark a variable ready only once.

在修改BEVDet时遇到RuntimeError,由于两次forward_train操作导致。问题根源在于backbone的with_cp设置。关闭with_cp可以解决,此设置原本用于减少GPU显存占用。使用torch.utils.checkpoint的目的是在forward时不存储中间激活值,而在backward时重新计算并计算梯度。两次forward创建的计算图无法在一次backward中处理。

背景:

最近在修改BEVDet,由于任务需要进行两次forward_train操作,遇到了RuntimeError: Expected to mark a variable ready only once.报错,原因很明显,对网络的某些部分进行了两次计算图的标注,在backward时出现问题。之前在2d上做类似的事情并没有碰到这样的问题。

解决方式

1.参考这位老哥的解决方式网页链接,但我发现这么写会导致后期任务出现问题,且对bevdet的数据已经进行了split操作,再次进行sup和student的数据结合,会增加程序运行时间。
2.在github issue 上发现类似的问题网页链接,经过实践发现只需要关闭backbone的with_cp即可。

究其根本

单纯的解决问题没有任何意义。
mmdetection中提到,在 backbone 中设置 with_cp=True。 这使用 PyTorch 中的 sublinear strategy 来降低 backbone 占用的 GPU 显存。

import torch.utils.checkpoint 
该 `RuntimeError` 错误通常在 PyTorch 分布式训练(DDP)中出现,错误原因主要有以下两点: 1. 在 `forward` 函数外使用了模块参数,需要确保模型参数不会在多个并发的前向 - 反向传播过程中共享。若模块图在训练循环中不发生变化,可尝试使用 `_set_static_graph()` 作为解决办法。 2. 在多个可重入的反向传播过程中重复使用了参数。例如,使用多个 `checkpoint` 函数包装模型的同一部分,会导致同一组参数在不同的可重入反向传播过程中被多次使用,从而多次标记一个变量为就绪状态。DDP 默认不支持此类用例,若模块图在迭代过程中不发生变化,可尝试使用 `_set_static_graph()` 作为解决办法。 以下是具体的解决方法: ### 确保模型参数不共享 检查代码中是否在 `forward` 函数外部使用了模型参数,保证在多个并发的前向 - 反向传播过程中不共享模型参数。可以通过仔细审查代码,确保所有参数的使用都在 `forward` 函数内部进行。 ### 使用 `_set_static_graph()` 若模块图在训练循环或迭代过程中不发生变化,可以尝试使用 `_set_static_graph()` 方法。示例代码如下: ```python import torch import torch.distributed as dist import torch.multiprocessing as mp import torch.nn as nn import torch.optim as optim from torch.nn.parallel import DistributedDataParallel as DDP def setup(rank, world_size): os.environ['MASTER_ADDR'] = 'localhost' os.environ['MASTER_PORT'] = '12355' # initialize the process group dist.init_process_group("nccl", rank=rank, world_size=world_size) def cleanup(): dist.destroy_process_group() class ToyModel(nn.Module): def __init__(self): super(ToyModel, self).__init__() self.net1 = nn.Linear(10, 10) self.relu = nn.ReLU() self.net2 = nn.Linear(10, 5) def forward(self, x): return self.net2(self.relu(self.net1(x))) def demo_basic(rank, world_size): setup(rank, world_size) # create model and move it to GPU with id rank model = ToyModel().to(rank) ddp_model = DDP(model, device_ids=[rank]) # 设置静态图 ddp_model._set_static_graph() loss_fn = nn.MSELoss() optimizer = optim.SGD(ddp_model.parameters(), lr=0.001) optimizer.zero_grad() outputs = ddp_model(torch.randn(20, 10).to(rank)) labels = torch.randn(20, 5).to(rank) loss_fn(outputs, labels).backward() optimizer.step() cleanup() def run_demo(demo_fn, world_size): mp.spawn(demo_fn, args=(world_size,), nprocs=world_size, join=True) if __name__ == "__main__": n_gpus = torch.cuda.device_count() assert n_gpus >= 2, f"Requires at least 2 GPUs to run, but got {n_gpus}" world_size = n_gpus run_demo(demo_basic, world_size) ``` ### 避免重复使用 `checkpoint` 函数 检查代码中是否使用了多个 `checkpoint` 函数包装模型的同一部分,若有,调整代码结构,避免同一组参数在不同的可重入反向传播过程中被多次使用。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值