Dataloader的num_work污染模型参数
模型的默认参数会dataloder被刷成0
下面的代码看起来只是在获取dataloder的迭代对象,和model毫无关系,但是实际上model里面的weight会被刷掉变成0
arr = []
model = Model()
set = dataset()
dataloder = DataLoader(set, batch_size=1, shuffle=False, num_workers=3)
arr.append(dataloder)
arr.append(model)
x = arr[0].__iter__()
print(model.weight)
首先我们先从最简单的情况看起
tensor = torch.tensor(5).cuda()
storage = tensor.storage()
storage._share_cuda_()
上面的代码定义了一个tensor变量,然后调用_share_cuda_方法,这个方法有什么用我也不知道,看起来要共享内存。但是执行完之后tensor会变成0
上面的情况看起来没什么,毕竟大家也用不到这个代码,但是接下来可就涉及到大家常用的操作了
dataset内的参数被刷
我们先简单定义一个dataset,里面有一个tensor变量。然后我们获取dataloder的迭代对象,这个过程看起来不修改dataset里面的tensor,但是它会被刷成0。由于代码简单,退出的快引发了报错,在复杂的模型里是没有这个错误的,他只会偷偷改变你的参数,不仔细看还以为你设计的模型不好。
class dataset(Dataset):
def __init__(self):
super().__init__()
self.tensor = torch.tensor(5).cuda()
def __getitem__(self, index):
time.sleep(1000)
return 0
def __len__(self):
return 1000
my_set = dataset()
dataloder = DataLoader(my_set, batch_size=10, shuffle=False, num_workers=1)
x = dataloder.__iter__()
print(my_set.tensor)
关于这个bug,我们需要一步一步找到里面出错的地方,我们先进入__iter__里面,然后再进入里面的_get_iterator,再进入_MultiProcessingDataLoaderIter。这里面定义了一个多进程对象w = multiprocessing_context.Process,他有一个方法 w.start(),进去之后经过一层又一层的代码会有一个reduction.dump(process_obj, to_child),这里面其实重载了pickle,搞了个新的dump。如果你的dataset里面有在cuda的tensor那么就会调用reduce_tensor()方法,下面是他的部分代码,这里面就有了刚才提到的storage.share_cuda(), 这个代码非常邪恶,会把你的tensor刷成0,因此执行完之后你的dataset里面的tensor会被刷成0。试想一下一个模型初始参数为0,那输出可想而知。这还不算最坑的,他还有更隐蔽的污染方式。
def reduce_tensor(tensor):
storage = tensor.storage()
if tensor.requires_grad and not tensor.is_leaf:
raise RuntimeError("Cowardly refusing to serialize non-leaf tensor which requires_grad, "
"since autograd does not support crossing process boundaries. "
"If you just want to transfer the data, call detach() on the tensor "
"before serializing (e.g., putting it on the queue).")
check_serializing_named_tensor(tensor)
torch.utils.hooks.warn_if_has_hooks(tensor)
if storage.is_cuda:
(device,
handle,
storage_size_bytes,
storage_offset_bytes,
ref_counter_handle,
ref_counter_offset,
event_handle,
event_sync_required) = storage._share_cuda_()
顺着list刷掉模型参数
这里面定义了一个模型和一个datasset,他们之间没有任何关联,除了在同一个list里面,但即便如此,model里面的weight也会被刷成0,单纯看代码完全想不到 x = arr[0].iter()会把model里面的weight刷掉。尤其是复制模型里面代码层次封装,很难发现这种隐蔽的污染方式。实际上除了list类对象,字典什么的都可以传导这种污染。他不但污染自己的孩子对象,甚至兄弟,父母,舅舅外甥之类的远亲都能污染。
class dataset(Dataset):
def __init__(self):
super().__init__()
self.tensor = torch.tensor(5).cuda()
def __getitem__(self, index):
time.sleep(1000)
return 0
def __len__(self):
return 1000
class Model(nn.Module):
def __init__(self):
super(Model, self).__init__()
self.weight = torch.tensor(5).cuda()
arr = []
model = Model()
set = dataset()
dataloder = DataLoader(set, batch_size=1, shuffle=False, num_workers=3)
arr.append(dataloder)
arr.append(model)
x = arr[0].__iter__()
print(model.weight)
最后提一下解决方法,最简单的就是numwork设置成0,那么他就不会启动多进程,创建w = multiprocessing_context.Process,也就不会有后面的一系列bug。
第二个就是dataset里面不要有张量,或者有张量也不要在cuda,这样 if storage.is_cuda:也会被跳过,就不会有后面的bug了。
第三个就是dataloder最好和其他东西隔离开,隔离之后就不会顺着列表或者其他关系把模型刷了