这里是PyTorch中常用代码段的一份整理,并且持续更新,初稿在参考资料1的基础上进行编辑补充,方便使用时查阅。
x#本文需要用到以下包import collectionsimport osimport shutilimport tqdmimport numpy as npimport PIL.Imageimport torchimport torchvision
xxxxxxxxxximport torchimport torch.nn as nnimport torchvisionprint(torch.__version__) #PyTorch versionprint(torch.version.cuda) #Corresponding CUDA versionprint(torch.backends.cudnn.version()) #Corresponding cuDNN versionprint(torch.cuda.get_device_name(0)) #GPU type在硬件设备(CPU、GPU)不同时,完全的可复现性无法保证,即使随机种子相同。但是,在同一个设备上,应该保证可复现性。具体做法是,在程序开始的时候固定torch的随机种子,同时也把numpy的随机种子固定。
xxxxxxxxxxtorch.manual_seed(0) #为CPU设置种子用于生成随机数,以使得结果是确定的torch.cuda.manual_seed(0) #为当前GPU设置随机种子torch.cuda.manual_seed_all(0) #如果使用多个GPU,为所有的GPU设置种子np.random.seed(0)torch.backends.cudnn.benchmark = True #cuDNN benchmark模式,会提升计算速度,但是由于计算中有随机性,每次网络前馈结果略有差异。torch.backends.cudnn.deterministic = True #避免这种结果波动指定程序运行在特定GPU卡上。
在命令行指定环境变量
xxxxxxxxxxCUDA_VISIBLE_DEVICES=0,1 python train.py或在代码中指定
xxxxxxxxxx如果只需要一张显卡# Device configurationdevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')如果需要指定多张显卡os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'清除GPU存储 有时Ctrl+C中止运行吼GPU存储没有及时释放,需要手动清空。在PyTorch内部可以
xxxxxxxxxxtorch.cuda.empty_cache()或在命令行
xxxxxxxxxx#先使用ps找到程序的PID,再使用kill结束该进程ps aux | grep pythonkill -9 [pid]#或直接重置没有被清空的GPUnvidia-smi --gpu-reset -i [gpu_id]
我一般在使用多GPU的时候, 会喜欢使用os.environ['CUDA_VISIBLE_DEVICES']来限制使用的GPU个数, 例如我要使用第0和第3编号的GPU, 那么只需要在程序中设置:
xxxxxxxxxxos.environ['CUDA_VISIBLE_DEVICES'] = '0,3'1.单机多卡 DataParallel (DP) : Parameter Server模式,一张卡位reducer,实现也超级简单,一行代码。
xxxxxxxxxx#模型if torch.cuda.is_available(): model.cuda()if torch.cuda.device_count() > 1: print("Let's use", torch.cuda.device_count(), "GPUs!") # 就这一行 model = nn.DataParallel(model) #数据 inputs = inputs.cuda()labels = labels.cuda()
2.多机多卡
DistributeDataParallel (DDP) : All-Reduce模式,本意是用来分布式训练,但是也可用于单机多卡。
xxxxxxxxxximport torch.multiprocessing as mp#import torch.distributed as distfrom apex.parallel import DistributedDataParallel as DDPfrom apex import amp1.初始化后端
xxxxxxxxxxtorch.distributed.init_process_group(backend="nccl")2.模型并行化
xxxxxxxxxxmodel=torch.nn.parallel.DistributedDataParallel(model)最小例程与解释
☞训练一个MNIST分类的简单卷积网络。
xxxxxxxxxxparser.add_argument('-n', '--nodes', default=1, type=int, metavar='N')parser.add_argument('-g', '--gpus', default=1, type=int, help='number of gpus per node') parser.add_argument('-nr', '--nr', default=0, type=int, help='ranking within the nodes')args = parser.parse_args()train(0, args)#训练是这么定义的def train(gpu, args):#上述代码中肯定有一些我们还不需要的额外的东西(例如gpu和节点的数量),但是将整个框架放置到位是很有帮助的。之后在命令行输入 python src/mnist.py -n 1 -g 1 -nr 0就可以在一个结点上的单个GPU上训练啦~☞加上MultiProcessing
(我们需要一个脚本,用来启动一个进程的每一个GPU。每个进程需要知道使用哪个GPU,以及它在所有正在运行的进程中的阶序(rank)。而且,我们需要在每个节点上运行脚本。)
xxxxxxxxxx args = parser.parse_args() #########################################################14 args.world_size = args.gpus * args.nodes #15 os.environ['MASTER_ADDR'] = '10.57.23.164' #16 os.environ['MASTER_PORT'] = '8888' #17 mp.spawn(train, nprocs=args.gpus, args=(args,)) # #########################################################3.Apex混合精度训练
(在2的基础上)
xxxxxxxxxxfrom apex import ampmodel, optimizer = amp.initialize(model, optimizer, opt_level="O1") # 这里是“欧一”,不是“零一”with amp.scale_loss(loss, optimizer) as scaled_loss: scaled_loss.backward()最近Apex更新了API,只需上述三行代码即可实现混合精度加速。
DP是基于Parameter server的算法,负载不均衡的问题比较严重,有时在模型较大的时候(比如bert-large),reducer的那张卡会多出3-4g的显存占用。
nn.DataParallel使用起来更加简单(通常只要封装模型然后跑训练代码就ok了)。但是在每个训练批次(batch)中,因为模型的权重都是在 一个进程上先算出来 然后再把他们分发到每个GPU上,所以网络通信就成为了一个瓶颈,而GPU使用率也通常很低。
官方建议用新的DDP,采用all-reduce算法,本来设计主要是为了多机多卡使用,但是单机上也能用。
nn.DistributedDataParallel进行Multiprocessing可以在多个gpu之间复制该模型,每个gpu由一个进程控制。每个进程都执行相同的任务,并且每个进程与所有其他进程通信。只有梯度会在进程/GPU之间传播,这样网络通信就不至于成为一个瓶颈了。
注:训练过程中,每个进程从磁盘加载自己的小批(minibatch)数据,并将它们传递给自己的GPU。每个GPU都做它自己的前向计算,然后梯度在GPU之间全部约简。每个层的梯度不仅仅依赖于前一层,因此梯度全约简与并行计算反向传播,进一步缓解网络瓶颈。在反向传播结束时,每个节点都有平均的梯度,确保模型权值保持同步(synchronized)。
混合精度训练,即组合浮点数 (FP32)和半精度浮点数 (FP16)进行训练,允许我们使用更大的batchsize,并利用NVIDIA张量核进行更快的计算。
我们只需要修改 train 函数即可
xxxxxxxxxxtensor.type() #Data typetensor.size() #Shape of tensor. tensor.dim() #Number of dimensionsxxxxxxxxxx#Set default tensor type. Float in PyTorch is much faster than double.torch.set_default_tensor_type(torch.FloatTensor) #or torch.cuda.FloatTensor#Type conversions.tensor = tensor.cuda()tensor = tensor.cpu()tensor = tensor.float()tensor = tensor.long()除了CharTensor,其他所有CPU上的张量都支持转换为numpy格式然后转换回来。
xxxxxxxxxx#torch.Tensor → np.ndarray.ndarray = tensor.cpu().numpy()#np.ndarray → torch.Tensor.tensor = torch.from_numpy(ndarray).float()tensor = torch.from_numpy(ndarray.copy()).float() #If ndarray has negative stride.PyTorch中的张量默认采用N×C×H×W的顺序,并且数据范围在[0, 1],需要进行转置和规范化。
xxxxxxxxxx# torch.Tensor → PIL.Image.image = PIL.Image.fromarray(torch.clamp(tensor*255, min=0,max=255).byte().permute(1,2,0).cpu().numpy())image = torchvision.transforms.functional.to_pil_image(tensor) #Equivalently way# PIL.Image → torch.Tensor.path = r'./figure.jpg' #r'string'是raw_string用来防止\ 自动转义的tensor = torch.from_numpy(np.asarray(PIL.Image.open(path))).permute(2,0,1).float()/255tensor = torchvision.transforms.functional.to_tensor(PIL.Image.open(path)) #Equivalently wayxxxxxxxxxx# np.ndarray → PIL.Image.image = PIL.Image.fromarray(ndarray.astype(np.unit8))# PIL.Image → np.ndarray.ndarray = np.asarray(PIL.Image.open(path))
https://www.zhangshengrong.com/p/Ap1Zp295N0/
GPU → CPU
xxxxxxxxxxmodel.load_state_dict(torch.load('model.pth', map_location='cpu'))或
xxxxxxxxxxmodel.load_state_dict(torch.load('model.pth', map_location=lambda storage, loc: storage))CPU → GPU
xxxxxxxxxxmodel.load_state_dict(torch.load('model.pth', map_location=lambda storage, loc: storage.cuda(1)))# 加载到GPU1中GPU→GPU
xxxxxxxxxxmodel.load_state_dict(torch.load('model.pth', map_location={'cuda:1':'cuda:0'}))多GPU → CPU 保存了模型nn.DataParallel,该模型将模型存储在该模型中module,而现在您正试图加载模型DataParallel。您可以nn.DataParallel在网络中暂时添加一个加载目的,也可以加载权重文件,创建一个没有module前缀的新的有序字典,然后加载它。
xxxxxxxxxx解决方案有两个:1:此时的训练加入torch.nn.DataParallel()即可。2:创建一个没有module.的新字典,即将原来字典中module.删除掉。解决方案1:model = torch.nn.DataParallel(model)cudnn.benchmark = True解决方案2:# original saved file with DataParallelstate_dict = torch.load('myfile.pth')# create new OrderedDict that does not contain `module.`from collections import OrderedDictnew_state_dict = OrderedDict()for k, v in state_dict.items(): name = k[7:] # remove `module.` new_state_dict[name] = v# load paramsmodel.load_state_dict(new_state_dict)解决方案3:model.load_state_dict({k.replace('module.',''):v for k,v in torch.load('myfile.pth').items()})