怎样做网站不花钱,宁波seo服务推广,上海中小企业名录,网站建设大致价格2017文 | 哟林小平知乎先说明一下背景#xff0c;目前正在魔改以下这篇论文的代码#xff1a;https://github.com/QipengGuo/GraphWriter-DGLgithub.com由于每次完成实验需要5个小时#xff08;baseline#xff09;#xff0c;自己的模型需要更久#xff08;2倍#xff09;知乎先说明一下背景目前正在魔改以下这篇论文的代码https://github.com/QipengGuo/GraphWriter-DGLgithub.com由于每次完成实验需要5个小时baseline自己的模型需要更久2倍非常不利于调参和发现问题所以开始尝试使用多卡加速。torch.nn.DataParallel 简称 DPtorch.nn.parallel.DistributedDataParallel 简称DDP一开始采用dp试图加速结果因为dgl的实现每个batch的点都会打包进一个batch从而不可分割而torch.nn.DataParallel的实现是把一个batch切分成更小再加上他的加速性能也不如ddp所以我开始尝试魔改成ddp。另外作者在实现Sampler的时候是继承了torch.utils.data.Sampler这个类的目的在于agenda数据集的文本长度严重不均衡如下为了让模型更快train完把长度相近的文本打包成一个batch温馨提醒torchtext也有相关的类 bucketiterator[1]大概形式如下class BucketSampler(torch.utils.data.Sampler):def __init__(self, data_source, batch_size32):self.data_source data_sourceself.batch_size batch_size def __iter__(self):idxs, lens, batch, middle_batch_size, long_batch_size basesampler(self.data_source , self.batch_size)for idx in idxs:batch.append(idx)mlen max([0][lens[x] for x in batch])#if (mlen100 and len(batch) 32) or (mlen100 and mlen220 and len(batch) 24) or (mlen220 and len(batch)8) or len(batch)32:if (mlen100 and len(batch) self.batch_size) or (mlen100 and mlen220 and len(batch) middle_batch_size) or (mlen220 and len(batch)long_batch_size) or len(batch)self.batch_size:yield batchbatch []if len(batch) 0:yield batchdef __len__(self):return (len(self.data_source)self.batch_size-1)//self.batch_size
这是背景。写bug第一步继承DistributedSampler的漏洞百出我一开始理想当然的把作者的sampler源码crtl-cv下来唯独只改动了这里class DDPBaseBucketSampler(torch.utils.data.distributed.DistributedSampler):
随后就发现了几个问题dataloader不会发包dataloader给每个进程发的是完整的数据按武德来说应该是1/n的数据n为你设置的gpu数量然后我就开始看起了源码[2]很快啊 def __iter__(self) - Iterator[T_co]:if self.shuffle:# deterministically shuffle based on epoch and seedg torch.Generator()g.manual_seed(self.seed self.epoch)indices torch.randperm(len(self.dataset), generatorg).tolist() # type: ignoreelse:indices list(range(len(self.dataset))) # type: ignoreif not self.drop_last:# add extra samples to make it evenly divisiblepadding_size self.total_size - len(indices)if padding_size len(indices):indices indices[:padding_size]else:indices (indices * math.ceil(padding_size / len(indices)))[:padding_size]else:# remove tail of data to make it evenly divisible.indices indices[:self.total_size]assert len(indices) self.total_size# subsampleindices indices[self.rank:self.total_size:self.num_replicas] # 这一步保证每个进程拿到的数据不同assert len(indices) self.num_samplesreturn iter(indices)
这里最关键的问题是是什么呢首先在torch.utils.data.distributed.DistributedSampler里面数据集的变量叫self.dataset而不是data_source其次和torch.utils.data.Sampler要求你_重写__iter__函数不同def __iter__(self) - Iterator[T_co]:raise NotImplementedError
DistributedSampler这个父类里有部分实现如果你没有考虑到这部分就自然会出现每个进程拿到的数据都是all的情况。于是我重写了我的DDPBaseBucketSampler类def basesampler(lens, indices, batch_size):# the magic number comes from the authors codet1 []t2 []t3 []for i, l in enumerate(lens):if (l100):t1.append(indices[i])elif (l100 and l220):t2.append(indices[i])else:t3.append(indices[i])datas [t1,t2,t3]random.shuffle(datas)idxs sum(datas, [])batch []#为了保证不爆卡我们给不同长度的数据上保护锁middle_batch_size min(int(batch_size * 0.75) , 32)long_batch_size min(int(batch_size * 0.5) , 24)return idxs, batch, middle_batch_size, long_batch_sizeclass DDPBaseBucketSampler(torch.utils.data.distributed.DistributedSampler):这里要注意和单GPU的sampler类同步def __init__(self, dataset, num_replicas, rank, shuffleTrue, batch_size32):super(DDPBaseBucketSampler, self).__init__(dataset, num_replicas, rank, shuffle)self.batch_size batch_sizedef __iter__(self):# deterministically shuffle based on epochg torch.Generator()g.manual_seed(self.epoch)#print(here is pytorch code and you can delete it in the /home/lzk/anaconda3/lib/python3.7/site-packages/torch/utils/data)if self.shuffle:indices torch.randperm(len(self.dataset), generatorg).tolist()else:indices list(range(len(self.dataset)))# add extra samples to make it evenly divisibleindices indices[:(self.total_size - len(indices))]assert len(indices) self.total_sizeindices indices[self.rank:self.total_size:self.num_replicas]assert len(indices) self.num_samples# 然后我也要拿到每个数据的长度 (每个rank不同)lens torch.Tensor([len(x) for x in self.dataset])idxs, batch, middle_batch_size, long_batch_size basesampler(lens[indices], indices, self.batch_size)for idx in idxs:batch.append(idx)mlen max([0][lens[x] for x in batch])#if (mlen100 and len(batch) 32) or (mlen100 and mlen220 and len(batch) 24) or (mlen220 and len(batch)8) or len(batch)32:if (mlen100 and len(batch) self.batch_size) or (mlen100 and mlen220 and len(batch) middle_batch_size) or (mlen220 and len(batch)long_batch_size) or len(batch)self.batch_size:yield batchbatch []# print(应该出现2次如果是2个进程的话)if len(batch) 0:yield batchdef __len__(self):return (len(self.dataset)self.batch_size-1)//self.batch_size
后面每个进程终于可以跑属于自己的数据了1/nn进程数量GPU数量单机紧接着问题又来了我发现训练过程正常结束后主进程无法退出mp.spawn()函数。写bug第二步master进程无法正常结束number workers ddp pytorch下无法正常结束。具体表现为mp.spawn传递的函数参数可以顺利运行完但是master进程一直占着卡不退出。一开始我怀疑是sampler函数的分发batch的机制导致的什么意思呢就是由于每个进程拿到的数据不一样各自进程执行sampler类的时候由于我规定了长度接近的文本打包在一起所以可能master进程有一百个iterslave只有80个然后我马上试了一下很快啊▲DDPBucketSampler(torch.utils.data.distributed.DistributedSampler)类迭代函数__iter__▲都能够正常打印证明__iter__函数没有问题发现只有细微的差别并且程序最后都越过了这些print应该不会是batch数量不一致导致的问题。顺便指的一提的是sampler在很早的时候就把batch打包好了加了摧毁进程也于事无补if args.is_ddp:dist.destroy_process_group()print(rank destroy_process_group: , rank)
然后只能点击强制退出File train.py, line 322, in modulemain(args.gpu, args)File /home/lzk/anaconda3/lib/python3.7/site-packages/torch/multiprocessing/spawn.py, line 171, in spawnwhile not spawn_context.join():File /home/lzk/anaconda3/lib/python3.7/site-packages/torch/multiprocessing/spawn.py, line 77, in jointimeouttimeout,File /home/lzk/anaconda3/lib/python3.7/multiprocessing/connection.py, line 920, in waitready selector.select(timeout)File /home/lzk/anaconda3/lib/python3.7/selectors.py, line 415, in selectfd_event_list self._selector.poll(timeout)
TypeError: keyboard_interrupt_handler() takes 1 positional argument but 2 were given
^CError in atexit._run_exitfuncs:
Traceback (most recent call last):File /home/lzk/anaconda3/lib/python3.7/multiprocessing/popen_fork.py, line 28, in pollpid, sts os.waitpid(self.pid, flag)
TypeError: keyboard_interrupt_handler() takes 1 positional argument but 2 were given
代码参考基于Python初探Linux下的僵尸进程和孤儿进程(三)[3]、 Multiprocessing in python blocked[4]很显然是pytorch master进程产生死锁了变成了僵尸进程。再探究发现当我把dataloader的number workers设为0的时候程序可以正常结束。经过我的注释大法后我发现哪怕我把for _i , batch in enumerate(dataloader)内的代码全部注释改为pass程序还是会出现master无法正常结束的情况。所以问题锁定在dataloader身上。参考neroPyTorch DataLoader初探[5]另外一种想法是mp.spawn出现了问题。使用此方式启动的进程只会执行和 target 参数或者 run() 方法相关的代码。Windows 平台只能使用此方法事实上该平台默认使用的也是该启动方式。相比其他两种方式此方式启动进程的效率最低。参考Python设置进程启动的3种方式[6]现在试一下绕开mp.spawn函数用shell脚本实现ddp能不能不报错python -m torch.distributed.launch --nproc_per_node2 --nnodes1 --node_rank0 --master_addr192.168.1.201 --master_port23456 我的文件.py
参数解释nnodes因为是单机多卡所以设为1显然node_rank 只能是0了local_rank:进程在运行的时候会利用args插入local_rank这个参数标识进程序号一番改动后发现问题有所好转最直观的感受是速度快了非常多现在我没有父进程的问题了但还是在运行完所有的程序后无法正常结束此时我的代码运行到上面的代码是main函数2个进程mastersalve都可以越过barrier其中slave顺利结束但是master却迟迟不见踪影这个时候ctrlc终止发现顺着报错路径去torch/distributed/launch.py, line 239找代码def main():args parse_args()# world size in terms of number of processesdist_world_size args.nproc_per_node * args.nnodes# set PyTorch distributed related environmental variablescurrent_env os.environ.copy()current_env[MASTER_ADDR] args.master_addrcurrent_env[MASTER_PORT] str(args.master_port)current_env[WORLD_SIZE] str(dist_world_size)processes []if OMP_NUM_THREADS not in os.environ and args.nproc_per_node 1:current_env[OMP_NUM_THREADS] str(1)print(*****************************************\nSetting OMP_NUM_THREADS environment variable for each process to be {} in default, to avoid your system being overloaded, please further tune the variable for optimal performance in your application as needed. \n*****************************************.format(current_env[OMP_NUM_THREADS]))for local_rank in range(0, args.nproc_per_node):# each processs rankdist_rank args.nproc_per_node * args.node_rank local_rankcurrent_env[RANK] str(dist_rank)current_env[LOCAL_RANK] str(local_rank)# spawn the processesif args.use_env:cmd [sys.executable, -u,args.training_script] args.training_script_argselse:cmd [sys.executable,-u,args.training_script,--local_rank{}.format(local_rank)] args.training_script_argsprocess subprocess.Popen(cmd, envcurrent_env)processes.append(process)for process in processes:process.wait() # 等待运行结束if process.returncode ! 0:raise subprocess.CalledProcessError(returncodeprocess.returncode,cmdcmd)
可恶master和dataloader到底有什么关系哇。。这个问题终于在昨天2020/12/22被解决了说来也好笑左手是graphwriter的ddp实现无法正常退出右手是minst的ddp最小例程可以正常退出于是我开始了删减大法。替换了数据集model然后让dataloader空转都没有发现问题最后一步步逼近知道我把自己的代码这一行注释掉以后终于可以正常结束了def main(args):############################################################print(local_rank : , args.local_rank )if args.is_ddp:dist.init_process_group(backendnccl,init_methodenv://,world_sizeargs.world_size,rankargs.local_rank)############################################################# torch.multiprocessing.set_sharing_strategy(file_system) 万恶之源os.environ[CUDA_VISIBLE_DEVICES] os.environ[CUDA_VISIBLE_DEVICES].split(,)[args.local_rank]args.device torch.device(0) ...
为什么我当时会加上这句话呢因为当时在调试number worker的时候当时年轻以为越大越好所以设置成了number workers cpu.count()发现系统报错说超出了打开文件的最大数量限制。在torch.multiprocessing的设定里共享策略参考pytorch中文文档[7]默认是File descriptor此策略将使用文件描述符作为共享内存句柄。当存储被移动到共享内存中一个由shm_open获得的文件描述符被缓存。当时文档还提到如果你的系统对打开的文件描述符数量有限制并且无法提高你应该使用file_system策略。所以我换成了torch.multiprocessing.set_sharing_strategy(file_system)但是却忽略文档里的共享内存泄露警告。显然或许这不是严重的问题文档里提到也有可能我所说的master进程就是这个torch_shm_manager因为destory进程组始终无法结束0号进程这个BUG结束了真开心期待下一个BUG快快到来。后台回复关键词【入群】加入卖萌屋NLP/IR/Rec与求职讨论群后台回复关键词【顶会】获取ACL、CIKM等各大顶会论文集 [1]bucketiterator (https://pytorch.org/text/stable/data.html#bucketiterator)[2]源码(https://github.com/pytorch/pytorch/blob/master/torch/utils/data/distributed.py)[3]基于Python初探Linux下的僵尸进程和孤儿进程(三)(http://dwz.date/dUmd)[4]Multiprocessing in python blocked (https://stackoverflow.com/questions/13649625/multiprocessing-in-python-blocked)[5]neroPyTorch DataLoader初探 (https://zhuanlan.zhihu.com/p/91521705)[6]Python设置进程启动的3种方式 (http://c.biancheng.net/view/2633.html)[7]pytorch中文文档 (https://pytorch-cn.readthedocs.io/zh/latest/package_references/torch-multiprocessing/)