Pytorch翻车记录:单卡改多卡踩坑记!


文 | 哟林小平@知乎

先说明一下背景,目前正在魔改以下这篇论文的代码:

https://github.com/QipengGuo/GraphWriter-DGLgithub.com

由于每次完成实验需要5个小时(baseline),自己的模型需要更久(2倍),非常不利于调参和发现问题,所以开始尝试使用多卡加速。

torch.nn.DataParallel ==> 简称 DP

torch.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_size=32):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 (mlen<100 and len(batch) == 32) or (mlen>100 and mlen<220 and len(batch) >= 24) or (mlen>220 and len(batch)>=8) or len(batch)==32:if (mlen<100 and len(batch) == self.batch_size) or (mlen>100 and mlen<220 and len(batch) >= middle_batch_size) or (mlen>220 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), generator=g).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 author's codet1 = []t2 = []t3 = []for i, l in enumerate(lens):if (l<100):t1.append(indices[i])elif (l>100 and l<220):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, shuffle=True, batch_size=32):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), generator=g).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 (mlen<100 and len(batch) == 32) or (mlen>100 and mlen<220 and len(batch) >= 24) or (mlen>220 and len(batch)>=8) or len(batch)==32:if (mlen<100 and len(batch) == self.batch_size) or (mlen>100 and mlen<220 and len(batch) >= middle_batch_size) or (mlen>220 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/n,n=进程数量=GPU数量,单机)

紧接着问题又来了,我发现训练过程正常结束后,主进程无法退出mp.spawn()函数。

写bug第二步,master进程无法正常结束

number workers ddp pytorch下无法正常结束。具体表现为,mp.spawn传递的函数参数可以顺利运行完,但是master进程一直占着卡,不退出。一开始我怀疑是sampler函数的分发batch的机制导致的,什么意思呢?就是由于每个进程拿到的数据不一样,各自进程执行sampler类的时候,由于我规定了长度接近的文本打包在一起,所以可能master进程有一百个iter,slave只有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 <module>main(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 jointimeout=timeout,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身上。参考:nero:PyTorch DataLoader初探[5]

另外一种想法是,mp.spawn出现了问题。使用此方式启动的进程,只会执行和 target 参数或者 run() 方法相关的代码。Windows 平台只能使用此方法,事实上该平台默认使用的也是该启动方式。相比其他两种方式,此方式启动进程的效率最低。参考:Python设置进程启动的3种方式[6]

现在试一下,绕开mp.spawn函数,用shell脚本实现ddp,能不能不报错:

python -m torch.distributed.launch --nproc_per_node=2 --nnodes=1 --node_rank=0 --master_addr="192.168.1.201" --master_port=23456 我的文件.py

参数解释:

  • nnodes:因为是单机多卡,所以设为1,显然node_rank 只能是0了

  • local_rank:进程在运行的时候,会利用args插入local_rank这个参数标识进程序号

一番改动后,发现问题有所好转,最直观的感受是速度快了非常多!!现在我没有父进程的问题了,但还是在运行完所有的程序后,无法正常结束:

此时我的代码运行到:

上面的代码是main函数,2个进程(master,salve)都可以越过barrier,其中slave顺利结束,但是master却迟迟不见踪影:

这个时候ctrl+c终止,发现:

顺着报错路径去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("*****************************************\n""Setting 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 process's 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, env=current_env)processes.append(process)for process in processes:process.wait() # 等待运行结束if process.returncode != 0:raise subprocess.CalledProcessError(returncode=process.returncode,cmd=cmd)

可恶,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(backend='nccl',init_method='env://',world_size=args.world_size,rank=args.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]nero:PyTorch 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/)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/479293.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用docker部署flask项目

前言 本次部署是把2个项目、mysql、redis、uwsgi封装在一个容器中&#xff0c;ngnix封装在一个容器中 实际应用中最好是&#xff1a; 项目和uwsgi封装在一个容器中mysql单独封装&#xff0c;可能还要读写分离&#xff0c;主从同步等redis单独封装&#xff0c;可能还要读写分离&…

论文浅尝 | DRUM:一种端到端的可微的知识图谱上的规则学习方法

论文笔记整理&#xff1a;张文&#xff0c;浙江大学在读博士&#xff0c;研究方向为知识图谱的表示学习&#xff0c;推理和可解释。现有的多数链接预测方法都不能处理新的实体&#xff0c;并且多为黑盒方法&#xff0c;使得其预测结果无法解释。本文提出了一种新的端到端的可微…

WSDM Cup 2019自然语言推理任务获奖解题思路

WSDM&#xff08;Web Search and Data Mining&#xff0c;读音为Wisdom&#xff09;是业界公认的高质量学术会议&#xff0c;注重前沿技术在工业界的落地应用&#xff0c;与SIGIR一起被称为信息检索领域的Top2。 刚刚在墨尔本结束的第12届WSDM大会传来一个好消息&#xff0c;由…

LeetCode 637. 二叉树的层平均值(层次遍历queue)

1. 题目 给定一个非空二叉树, 返回一个由每层节点平均值组成的数组. 输入:3/ \9 20/ \15 7 输出: [3, 14.5, 11] 解释: 第0层的平均值是 3, 第1层是 14.5, 第2层是 11. 因此返回 [3, 14.5, 11].2. 解题 queue按层遍历&#xff0c;每次进入循环记录队列长度n class So…

AI框架你只会调包,这种想法很危险!

深度学习神经网络正步入成熟&#xff0c;而深度学习框架目前众多&#xff0c;大都可以在图像识别、手写识别、视频识别、语音识别、目标识别和自然语言处理等诸多领域大显身手。深度学习框架平台占据人工智能产业生态的核心地位&#xff0c;具有统领产业进步节奏、带动终端场景…

Docker镜像大小

都说容器大法好&#xff0c;可是假设没有Docker镜像&#xff0c;Docker该是多无趣啊。 是否还记得第一个接触Docker的时候&#xff0c;你从Docker Hub下拉的那个镜像呢&#xff1f;在那个处女镜像的基础上。你执行了容器生涯的处女容器。镜像的基石作用已经非常明显。在Docker…

论文浅尝 | 机器阅读理解中常识知识的显式利用

论文笔记整理&#xff1a;吴林娟&#xff0c;天津大学硕士&#xff0c;自然语言处理方向。链接&#xff1a;https://arxiv.org/pdf/1809.03449.pdf动机机器阅读理解(MRC)和人类进行阅读理解之间还存在差距&#xff0c;作者认为主要体现在对于数据的需求和噪声鲁棒性上&#xff…

深度学习在美团配送ETA预估中的探索与实践

1.背景 ETA&#xff08;Estimated Time of Arrival&#xff0c;“预计送达时间”&#xff09;&#xff0c;即用户下单后&#xff0c;配送人员在多长时间内将外卖送达到用户手中。送达时间预测的结果&#xff0c;将会以”预计送达时间”的形式&#xff0c;展现在用户的客户端页面…

在AndroidStudio中配置Gradle进行 “动态编译期间,根据远程服务器地址 ,生成多类安装包”

原文地址&#xff1a;http://www.cnblogs.com/vir56k/p/4763810.html 需求&#xff1a; 在产品开发中&#xff0c;经常需要发布各个版本&#xff0c;每个版本的服务器地址有不同的服务器地址。比如 开发 服务器使用 192.168.1.232服务器&#xff0c;测试 服务器使用 192.168.1.…

中科院博士整理的机器学习算法知识手册,完整 PDF 开放下载!

分享一份中科院博士总结的机器学习算法知识手册&#xff0c;文章已打包成压缩文件&#xff0c;感兴趣的同学可下载交流。文件包含了&#xff1a;机器学习基础&#xff0c;监督学习方法、非监督学习方法&#xff0c;Python数据科学和深度学习等相关知识&#xff0c;若初学者在自…

论文浅尝 | 通过共享表示和结构化预测进行事件和事件时序关系的联合抽取

论文笔记整理&#xff1a;邓淑敏&#xff0c;浙江大学在读博士&#xff0c;研究方向为低资源条件下知识图谱自动化构建关键技术研究。Rujun Han, Qiang Ning, Nanyun Peng. Joint Event and Temporal Relation Extractionwith Shared Representations and Structured Predictio…

研发团队资源成本优化实践

背景 工程师主要面对的是技术挑战&#xff0c;更关注技术层面的目标。研发团队的管理者则会把实现项目成果和业务需求作为核心目标。实际项目中&#xff0c;研发团队所需资源&#xff08;比如物理机器、内存、硬盘、网络带宽等&#xff09;的成本&#xff0c;很容易被忽略&…

抖音算法推荐机制详解(科普向)

本文转载自公众号“夕小瑶的卖萌屋”&#xff0c;专业带逛互联网算法圈的神操作 -----》我是传送门 关注后&#xff0c;回复以下口令&#xff1a; 回复【789】 &#xff1a;领取深度学习全栈手册&#xff08;含NLP、CV海量综述、必刷论文解读&#xff09; 回复【入群】&#xf…

论文浅尝 | 融合多粒度信息和外部语言知识的中文关系抽取

论文笔记整理&#xff1a;吴涵&#xff0c;天津大学硕士&#xff0c;研究方向&#xff1a;自然语言处理Paper:https://www.aclweb.org/anthology/P19-1430/Code:https://github.com/thunlp/Chinese_NRE引入中文NER问题在很大程度上取决于分词的效果&#xff0c;所以在中文NER问…

智能写作

6.2万字报告剖析「智能写作」全貌&#xff0c;从落地产品看NLP商业化突破&#xff1a;

论文拒稿的评价可以有多狠?

文 | 自然卷知乎自古严师出高徒&#xff0c;“天将降大任于是人也&#xff0c;必先苦其心志&#xff0c;劳其筋骨……”。然而&#xff0c;这并不能作为审稿人走向“键盘侠”之路的理由&#xff01;让我们这些卑微投稿人心里真是好凉凉&#xff01;ಥ_ಥ 大家来看看&#xff0c…

全链路压测自动化实践

背景与意义 境内度假是一个低频、与节假日典型相关的业务&#xff0c;流量在节假日较平日会上涨五到十几倍&#xff0c;会给生产系统带来非常大的风险。因此&#xff0c;在2018年春节前&#xff0c;我们把整个境内度假业务接入了全链路压测&#xff0c;来系统性地评估容量和发现…

论文浅尝 | GMNN: Graph Markov Neural Networks

论文笔记整理&#xff1a;吴锐&#xff0c;东南大学硕士研究生&#xff0c;研究方向为自然语言处理来源&#xff1a;ICML 2019链接&#xff1a;http://proceedings.mlr.press/v97/qu19a/qu19a.pdf问题定义弱监督下的在关系数据中的对象分类。形式化地来说&#xff0c;给定一个图…

五笔字根表口诀的通俗易懂讲解

五笔字根表口诀的通俗易懂讲解 从上面五笔学习导图&#xff0c;我们不难看出&#xff0c;五笔只包含两个部分&#xff0c;一是独立字。二是字根字。 &#xff11; 先来讲解独立字。 独立字就是不用字根就可以输出的汉字&#xff0c;独立字包含一级简码&#xff0c;按键字两种。…

论文浅尝 | 基于知识图谱难度可控的多跳问题生成

论文笔记整理&#xff1a;谭亦鸣&#xff0c;东南大学博士生&#xff0c;研究兴趣&#xff1a;知识图谱问答。来源&#xff1a;ISWC 2019链接&#xff1a;https://link.springer.com/content/pdf/10.1007%2F978-3-030-30793-6_22.pdf本文提出一个end2end神经网络模型以知识图谱…