Pytorch Tutorial
文章目录
pytorch 学习笔记
不理解的地方,一个是关于 nn包搭建网络的部分。
- pytorch 和torch 比较
编程语言: pytorch 采用python语言,实际上使用c语言和c++ 做接口 torch 采用lua,使用c语言和lua 语言做接口 (lua 语言相当于一个小型加强版的c语言,支持类和面向对象) 依赖库: pytorch 和 torch 框架的区别和联系: pytorch 可调用python强大的第三方库,比如 opencv torch 可调用 lua 库函数,目前 lua库函数没有python多 pytorch 依赖库多于 torch 效率: python 的debug 功能比lua 强大,所以pytorch 效率高于torch 模型和中间变量的关系: pytorch 中中间变量都存在计算图中,所以model 共享中间变量 torch 的中间变量在每一个模块中,所以想要调用其他模块的参数就必须复制这个模块然后再调用
总结 pytorch可以说是torch 的python版本,并增加了很多新功能
- 常用的框架比较
tensorflow 背后是google, mxnet 是Amazon,pytorch背后是Facebook 每个框架都有各自的有点,比如tensorflow的工程能力很强,Theano特别适合科研等等 keras是一个很高层的结构,它的后端支持theano和tensorflow,它本质上并不是一个框架,只是对框架的操作做了一个封装,你在写keras的时候其实是对其后端进行调用,相当于你还是在tensorflow或者theano上跑程序,只不过你把你的语言交给keras处理了一下变成tensorflow听得懂的语言,然后再交给tensorflow处理,这样的后果当然方便你构建网络,方便定义模型做训练,极快的构建你的想法,工程实现很强,但是这样也有一个后果,那就是细节你没有办法把控,训练过程高度封装,导致你没有办法知道里面的具体细节,以及每个参数的具体细节,使得调试和研究变得很困难。
- pytorch的思想
PyTorch 的构建者表明,PyTorch 的哲学是解决当务之急,也就是说即时构建和运行我们的计算图。这恰好适合 Python 的编程方法,因为我们不需等待整个代码都被写入才能知道是否起作用。我们很容易运行部分代码,并实时检查它。
PyTorch 是一个基于 Python 的库,旨在为深度学习提供一个灵活的开发平台。PyTorch 的工作流程非常接近于 Python 的科学计算库 NumPy。那么为什么我们需要使用 PyTorch 构建深度学习模型?以下作者根据实际经验提供了三个理由:
- 便于使用的 API:它的使用如同 Python 那样简单。
- 支持 Python:正如上文所述,PyTorch 可以平滑地与 Python 数据科学栈相结合。它与 NumPy 一样简单,甚至我们都感觉不出它们的区别。
- 动态计算图:PyTorch 不再采用特定的函数预定义计算图,而是提供构建动态计算图的框架,甚至我们可以在运行时修正它们。这种动态框架在我们不知道所构建的神经网络需要多少内存时非常有用。 其它一些使用 PyTorch 的优点还有多 GPU 支持、自定义数据加载器和极简的预处理过程等。
在讨论 PyTorch 的各个组件前,我们需要了解它的工作流。PyTorch 使用一种称之为 imperative / eager 的范式,即每一行代码都要求构建一个图以定义完整计算图的一个部分。即使完整的计算图还没有完成构建,我们也可以独立地执行这些作为组件的小计算图,这种动态计算图被称为**「define-by-run」**方法。
PyTorch 提供了 CPU 张量和 GPU 张量,并且极大地加速了计算的速度。 从张量的构建与运行就能体会到 PyTorch 相比 TensorFLow 需要声明张量、初始化张量要简洁地多。以下语句将随机初始化一个 5×3 的二维张量,因为 PyTorch 是一种动态图,所以它声明和真实赋值是同时进行的。
|
|
若我们希望随机初始化的张量服从某些分布,那么我们可以直接对张量对象使用一些方法。如下初始化的张量将服从均匀分布:
|
|
PyTorch 同样支持广播(Broadcasting)操作,一般它会隐式地把一个数组的异常维度调整到与另一个算子相匹配的维度以实现维度兼容。
如下,我们定义了两个 GPU 张量,并对这两个张量执行矩阵乘法。当然,我们也可以如下所示将 CPU 张量转换为 GPU 张量。
|
|
AutoGrad 模块
TensorFlow、Caffe 和 CNTK 等大多数框架都是使用的静态计算图,开发者必须建立或定义一个神经网络,并重复使用相同的结构来执行模型训练。改变网络的模式就意味着我们必须从头开始设计并定义相关的模块。
PyTorch 使用的技术为自动微分(automatic differentiation),这个是用来自动求解微分的模块。在这种机制下,系统会有一个 Recorder 来记录我们执行的运算,然后再反向计算对应的梯度。这种技术在构建神经网络的过程中十分强大,因为我们可以通过计算前向传播过程中参数的微分来节省时间。
从概念上讲, Autograd
对数据记录记录了一个有向无环图(DAG), 叫做计算图,用来表示它的计算过程。沿着计算图应用链式求导法则就可以求出其梯度。Autograd
包中有两个核心包: torch.Tensor
和torch.Function
, 默认某个 tensor的属性是 .requires_grad
为true,当计算完成的时候可以调用 .backward()
来自动计算所有的梯度,针对这个tensor 可以在 .grad
属性中去查看。
设置一个张量不跟踪历史记录的方法:
- 调用
.detach()
将其从计算历史中分离出来 - 使用
torch.no_grad()
包裹代码块,那么在该代码块中的计算都不会计算梯度。使用的情况是, 在评估阶段(predict)阶段。 - 设置某个tensor 的属性为
Required_grad =False
Function
类,每一个tensor都有一个.grad_fn
属性指向一个 Function
,表示如何得到了当期的tensor。如果是用户自己创建的张量tensor,那么 grad_fn is None
。
.requires_grad
具有传递性,比如说 $x_1, x_2, \dots, x_n$ 中某一个满足 required_grad =True
,那么这个时候由这些tensor表示tensor 的属性都是true
。
最优化模块 torch.optim 是实现神经网络中多种优化算法的模块,它目前已经支持大多数一般的方法,所以我们不需要从头构建优化算法。以下展示了使用 Adam 优化器的基本代码:
|
|
我们一般可以使用 torch.nn 包构建神经网络,下面提供了一些 API 的表达及意义:
- 线性层- nn.Linear、nn.Bilinear
- 卷积层 - nn.Conv1d、nn.Conv2d、nn.Conv3d、nn.ConvTranspose2d
- 非线性激活函数- nn.Sigmoid、nn.Tanh、nn.ReLU、nn.LeakyReLU
- 池化层 - nn.MaxPool1d、nn.AveragePool2d
- 循环网络 - nn.LSTM、nn.GRU
- 归一化 - nn.BatchNorm2dDropout - nn.Dropout、nn.Dropout2d
- 嵌入 - nn.Embedding
- 损失函数 - nn.MSELoss、nn.CrossEntropyLoss、nn.NLLLoss
张量
|
|
相同点 / 不同点 第一个区别是,所有的操作在张量操作需要有_后缀。例如,add在此处无用,使用add_是可用的。
|
|
零索引
|
|
下一个小的区别是所有的功能现在都不是驼峰命名了。例如indexAdd现在调用index_add_
CUDA传感器在pytorch中很好并且很容易,并将CUDA张量从CPU转移到GPU将保留其基础类型。
|
|
基本类型 Tensor的基本数据类型有五种:
- 32位浮点型:torch.FloatTensor。 (默认)
- 64位整型:torch.LongTensor。
- 32位整型:torch.IntTensor。
- 16位整型:torch.ShortTensor。
- 64位浮点型:torch.DoubleTensor。
numpy 和 tensor 之间的相互转换
使用numpy 方法将tensor 转换成 ndarray
|
|
numpy转化为Tensor
|
|
一般情况下可以使用.cuda方法将tensor移动到gpu,这步操作需要cuda设备支持
|
|
|
|
使用.cpu 将tensor 转换成cpu
|
|
如果我们有多GPU的情况,可以使用to方法来确定使用那个设备,这里只做个简单的实例:
|
|
下一章介绍PyTorch的自动求导机制
- 变量
- 梯度
从0.4起, Variable 正式合并入Tensor类, 通过Variable嵌套实现的自动微分功能已经整合进入了Tensor类中。虽然为了代码的兼容性还是可以使用Variable(tensor)这种方式进行嵌套, 但是这个操作其实什么都没做。所以,以后的代码建议直接使用Tensor类进行操作,因为官方文档中已经将Variable设置成过期模块。要想通过Tensor类本身就支持了使用autograd功能,只需要设置.requries_grad=True。 Variable类中的的grad和grad_fn属性已经整合进入了Tensor类中
每个变量都有两个标志:requires_grad
和volatile
。它们都允许从梯度计算中精细地排除子图,并可以提高效率。
requires_grad
如果有一个单一的输入操作需要梯度,它的输出也需要梯度。相反,只有所有输入都不需要梯度,输出才不需要。如果其中所有的变量都不需要梯度进行,后向计算不会在子图中执行。
这个标志特别有用,当您想要冻结部分模型时,或者您事先知道不会使用某些参数的梯度。例如,如果要对预先训练的CNN进行优化,只要切换冻结模型中的requires_grad标志就足够了,直到计算到最后一层才会保存中间缓冲区,其中的仿射变换将使用需要梯度的权重并且网络的输出也将需要它们。
volatile
纯粹的inference模式下推荐使用volatile,当你确定你甚至不会调用.backward()时。它比任何其他自动求导的设置更有效——它将使用绝对最小的内存来评估模型。volatile也决定了require_grad is False。
volatile不同于require_grad的传递。如果一个操作甚至只有有一个volatile的输入,它的输出也将是volatile。Volatility比“不需要梯度”更容易传递——只需要一个volatile的输入即可得到一个volatile的输出,相对的,需要所有的输入“不需要梯度”才能得到不需要梯度的输出。使用volatile标志,您不需要更改模型参数的任何设置来用于inference。创建一个volatile的输入就够了,这将保证不会保存中间状态。
参考官方教程
PyTorch 基础 : 神经网络包nn和优化器optm
pytorch 学习笔记
- pytorch 的核心主要是提供了两个主要的功能:
- n维tensor,类似numpy,但可以运行在GPU 上
Numpy是科学计算的通用框架;它对计算图形、深度学习或梯度一无所知。Tensor张量是pytorch 中最基本的概念。PyTorch张量可以利用GPU加速其数字计算。要在GPU上运行PyTorch Tensor,请在构造Tensor时使用
device
参数将Tensor放置在GPU上。 - 自动微分,用于构建和训练神经网络 使用自动微分来自动计算神经网络中的反向通过。
-
在搭建网络的过程中,网络中的正向传播定义为一个 computational graph计算图: 图中的节点为张量,边为从输入张量产生输出张量的函数,然后通过改图进行反向传播,可以轻松计算梯度。默认张量中的参数
require_grad=True
, 那么在反向传播的时候,x.grad
将是另一个张量, 它保持了x
相对于某个标量值的梯度。如果在训练神经网络的时候,比如通常不想要更新步骤中向后传播(形成计算图),那么这个时候可以使用torch.no_grad()
上下文管理器来防止构建计算图。 -
pytorch 中的计算图很想 tensorflow 中的计算图,但是两者不同在于前者是动态图,后者是静态图。在tensorflow 中,如果定义了一个计算图,然后一遍遍计算相同的图,可能将不同的输入数据提供给图。在pytorch 中,每一个前向传播都定义了一个新的计算图。静态图的优势,可以预先优化图,比如说融合某些图的操作,分布式之类的。而动态图,入门比较简单,方便debug。
-
pytorch中常见的包:
- nn 定义了一组模块,包括神经网络层和有用的损失函数
- optim(优化器),优化算法的思想, 比如说 adagrad, rmsprop, adam
|
|
|
|
pytorch 中的梯度计算是累积的,这个在训练 RNN 时候很方便。
|
|
-
使用 custom nn module(这种是经常用到,搭建自己的nn 网络) 通过子类nn.Module并定义一个
forwad
输入来定义自己的模块,该前向接收输入张量并使用其他模块或在张量上的其他自动转换操作产生输出张量。 -
control flow and weight sharing 这个权值共享在RNN 中使用比较多,但是不是很多呀,多看例子把~
-
pytorch 中
model.train()
和model.eval()
的区别
在图像中影响比较大。如果模型中出现了BatchNormalization 和Dropout,必须要区分训练和验证模式。两种模式在计算上是不同的。当 eval 的时候,模型会自动把 BN和DropOut固定住,使用已经训练好的值,否则容易出现异常的结果。
- contiguous 关键词
contiguous 使用空间换取时间,保证语义上相邻的元素在内存上也是连续的。这样访问的时候,可以减少cpu 对内存的请求的次数。
torch.Tensor
和 torch.tensor
的区别
In Pytorch
torch.Tensor
is the main tensor class. So all tensors are just instances oftorch.Tensor
When you call
torch.Tensor()
you will get an empty tensor without any data 是一个类on ontrast
torch.tensor
is a function which results a tensor. 是一个函数
tensor 和 variable 的区别
tensor and variable are a class provided by Pytorch. Accordign to the official Pytorch document. Both classes are a multi-dimensional matrix containing elements of a single data type (类似 numpy 中的 array)
The difference between Tensor and Variable is that the Variable is a wrapper of Tensor(当初主要目的是 variable 进行包含了自动求导,tensor 没有自动求导的功能)
however, as of now the Variable class has been deprecated and we are no longer bother between Variable and Tensor since the Autograd also supports Tensor.
replay 重放
可视化
PyTorch是Torch框架的表亲,Torch是基于lua开发的,在Facebook公司里被广泛使用。然而,PyTorch的出现并不是为了支持流行语言而对Torch进行简单的包装,它被重写和定制出来是为了得到更快的速度和本地化。
tensorboardx
Visdom是Facebook在2017年发布的一款针对PyTorch的可视化工具
方案: pytorch 使用替代品tensorbordx
Pytorch框架也有自己的可视化软件–Visdom,但貌似不是很好用。所以是可以用tensorboardx 来进行运行的。
|
|
注意numpy的版本要对应,否则会报错,如果不匹配,那就进行更新或者新建虚拟环境了!
- Loss可视化 最常见的可视化就是loss曲线作图
|
|
- 输入图片和标签的可视化
|
|
- 单通道特征图的可视化
|
|
pytorch最被人诟病的就是可视化问题和部署问题
https://shenxiaohai.me/2018/10/23/pytorch-tutorial-TensorBoard/
https://www.jianshu.com/p/429eb27855a0
可视化工具 visdom
|
|
使用 以下命令在本地启动服务器
|
|
然后输入 http://localhost:8097
model.eval() 和 model.no_grad() 的区别
model.eval()
- 在train模式,dropout层会按照设定的参数p设置保留激活单元的概率(保留概率=p,比如keep_prob=0.8),batchnorm层会继续计算数据的mean和var并进行更新
- 在val模式下,dropout层会让所有的激活单元都通过,而batchnorm层会停止计算和更新mean和var,直接使用在训练阶段已经学出的mean和var值
- model.eval()不会影响各层的gradient计算行为,即gradient计算和存储与training模式一样,只是不进行反向传播(backprobagation)
model.no_grad()
|
|
- 用于停止autograd模块的工作,起到加速和节省显存的作用(具体行为就是停止gradient计算,从而节省了GPU算力和显存)
- 不会影响dropout和batchnorm层的行为
所以在部署的时候,需要两者搭配起来进行使用,这样可以处理 dropout 和 BN 的情况,也可减少显存的使用。
These two have different goals: model.eval() will notify all your layers that you are in eval mode, that way, batchnorm or dropout layers will work in eval mode instead of training mode. torch.no_grad() impacts the autograd engine and deactivate it. It will reduce memory usage and speed up computations but you won’t be able to backprop (which you don’t want in an eval script). eval()和train 的不同在于 BN 和DropOut 机制的是否使用,前者是不使用,后者是使用。 no_grad() 是不进行反向传播,主要是用来减少内存和加快训练的。
Dropout works as a regularization for preventing overfitting during training. It randomly zeros the elements of inputs in Dropout layer on forward call. It should be disabled during testing ( model.eval() ) since you may want to use full model (no element is masked)
- argparse模块中的action参数
用argparse模块让python脚本接收参数时,对于True/False类型的参数,向add_argument方法中加入参数action=‘store_true’/‘store_false’。 顾名思义,store_true就代表着一旦有这个参数,做出动作“将其值标为True”,也就是没有时,默认状态下其值为False。反之亦然,store_false也就是默认为True,一旦命令中有此参数,其值则变为False。
pytorch 中指定gpuid的两种方式
PyTorch 默认是使用从0 开始的GPU,如果 0正在使用,那么需要指定其他GPU。有三种方式进行指定:
- 直接在终端设定
|
|
- 在python 代码中指定
|
|
- 使用函数 set_device
|
|
官方建议使用前两种方式,即设置 CUDA_VISIBLE_DEVICES
pytorch 中的 squeeze 函数
|
|
多维张量本质上就是一个变换,如果维度是 1 ,那么,1 仅仅起到扩充维度的作用,而没有其他用途,因而,在进行降维操作时,为了加快计算,是可以去掉这些 1 的维度。
PyTorch 单机多卡操作总结:分布式DataParallel,混合精度,Horovod
为了实现多GPU训练,我们必须想一个办法在多个GPU上分发数据和模型,并且协调训练过程。
单机多卡的方法:
1、nn.DataParallel 简单方便的 nn.DataParallel
2、torch.distributed 使用 torch.distributed 加速并行训练
3、apex 使用 apex 再加速。
NVIDIA显卡机器型号
通常 NVIDIA 显卡的名字会是这样的一串 RTX 2080 Super
。第一串英文字母系列从低到高:GT GTX RTX 位端越高价格越高;第二串是当年推出的显卡,2019年 有 20 和16 两个系列, 2020年推出的是以 30 开头,后面 80
表示等级,数字越大等级越高;最后是后缀的英文,通常是 Ti 和 Super,其中 Ti 表示加强版,Super 表示升级版。
20年 NVIDIA (30 系列)
显卡型号 | 核心代号 | 制造工艺 (nm) | 流处理器/RT核心/Tensor核心 | 核心频率 (MHz) | 加速频率 (MHz) | 显存位宽 (-bit) | 显存容量 | 显存频率 (GHz) | 整卡功耗 (W) |
---|---|---|---|---|---|---|---|---|---|
RTX 3090 | GA102-300 | 8nm | 10496/82/328 | 1395 | 1695 | 384 | 24GB GDDR6X | 19.5 | 350 |
RTX 3080 Ti | GA102-225 | 8nm | 10240/80/320 | 1365 | 1665 | 384 | 12GB GDDR6X | 19 | 350 |
RTX 3080 | GA102-200/202 | 8nm | 8704/68/272 | 1440 | 1710 | 320 | 10GB GDDR6X | 19 | 320 |
RTX 3070 Ti | GA104-400 | 8nm | 6144/48/96 | 1575 | 1770 | 256 | 8GB GDDR6X | 19 | 290 |
RTX 3070 | GA104-300/302 | 8nm | 5888/46/184 | 1500 | 1725 | 256 | 8GB GDDR6 | 14 | 220 |
RTX 3060 Ti | GA104-200/202 | 8nm | 4864/38/152 | 1410 | 1665 | 256 | 8GB GDDR6 | 14 | 200 |
RTX 3060 | GA106-300/302 | 8nm | 3584/28/112 | 1320 | 1777 | 192 | 12GB GDDR6 | 15 | 170 |
在这里,我直接贴出 Nvidia 家各主流GPU的一些参数。以及拥有 TensorCore 的GPU列表: 就目前而言,基本就只有V100 和 TITAN V 系列是支持 TensorCore 计算的。
分布式计算调研
https://yangkky.github.io/2019/07/08/distributed-pytorch-tutorial.html
I like to implement my models in Pytorch because I find it has the best balance between control and ease of use of the major neural-net frameworks. Pytorch has two ways to split models and data across multiple GPUs:
nn.DataParallel
andnn.DistributedDataParallel
.nn.DataParallel
is easier to use (just wrap the model and run your training script). However, because it uses one process to compute the model weights and then distribute them to each GPU during each batch, networking quickly becomes a bottle-neck and GPU utilization is often very low. Furthermore,nn.DataParallel
requires that all the GPUs be on the same node and doesn’t work with Apex for mixed-precision training.自己基于 pytorch 实现 balance control and ease of use of the major neural-net
DP 的问题有两个:网络通信称为瓶颈;GPU 使用率很低。DP 要求所有 GPU都在same node
Multiprocessing with
DistributedDataParallel
duplicates the model across multiple GPUs, each of which is controlled by one process. (A process is an instance of python running on the computer; by having multiple processes running in parallel, we can take advantage of procressors with multiple CPU cores. If you want, you can have each process control multiple GPUs, but that should be obviously slower than having one GPU per process. It’s also possible to have multiple worker processes that fetch data for each GPU, but I’m going to leave that out for the sake of simplicity.) The GPUs can all be on the same node or spread across multiple nodes. (A node is one “computer,” including all of its CPUs and GPUs. If you’re using AWS, a node is one EC2 instance.) Every process does identical tasks, and each process communicates with all the others. Only gradients are passed between the processes/GPUs so that network communication is less of a bottleneck.这个 example 没有实现每个进程中有多个 num_workers,一个进程对应的是一个 GPU。实现的是单个进程对应单个 num_worker
DDP 中只是传递 梯度,所以对于通信的要求低一些。
During training, each process loads its own minibatches from disk and passes them to its GPU. Each GPU does its own forward pass, and then the gradients are all-reduced across the GPUs. Gradients for each layer do not depend on previous layers, so the gradient all-reduce is calculated concurrently with the backwards pass to futher alleviate the networking bottleneck. At the end of the backwards pass, every node has the averaged gradients, ensuring that the model weights stay synchronized.
DDP 中前向是各自单独进行,后向传播是得到一个 averaged gradient。
All this requires that the multiple processes, possibly on multiple nodes, are synchronized and communicate. Pytorch does this through its
distributed.init_process_group
function. This function needs to know where to find process 0 so that all the processes can sync up and the total number of processes to expect. Each individual process also needs to know the total number of processes as well as its rank within the processes and which GPU to use. It’s common to call the total number of processes the world size. Finally, each process needs to know which slice of the data to work on so that the batches are non-overlapping. Pytorch providesnn.utils.data.DistributedSampler
to accomplish this.pytorch 中使用
distributed.init_process_group
来控制进程之间的通信; 使用nn.utils.data.DistributedSampler
来控制数据的分配。
|
|
Initialize the process and join up with the other processes. This is “blocking,” meaning that no process will continue until all processes have joined. I’m using the
nccl
backend here because the pytorch docs say it’s the fastest of the available ones. Theinit_method
tells the process group where to look for some settings. In this case, it’s looking at environment variables for theMASTER_ADDR
andMASTER_PORT
, which we set withinmain
. I could have set theworld_size
there as well asWORLD_SIZE
, but I’m choosing to set it here as a keyword argument, along with the global rank of the current process.进程启动和通信
如果是多 node,那么需要使用多个 terminal 一块进行启动。目前来说我只是在单个node 上进行实验。
To run this on, say, 4 nodes with 8 GPUs each, we need 4 terminals (one on each node). On node 0 (as set by line 13 in main
):
|
|
Then, on the other nodes:
|
|
pytorch 提供两种方法在多 GPU 上切分数据和模型
- dataParallel
- distributedataparallel
DataParallel更易于使用。不过,通信是瓶颈,GPU利用率通常很低,而且不支持分布式。DistributedDataParallel支持模型并行和多进程,单机/多机都可以,是分布训练。
混合精度(mixed precision)知识点
默认深度学习模型训练过程中使用的都是 fp32(pytorch 中 float 就是 fp32, python 中 float 是 fp64)。apex 是NVIDIA 的一个pytorch 扩展,用于支持混合精度训练和分布式训练。自 pytorch1.6 开始,已经内置了 torch.cuda.amp,采用自动混合精度训练不再需要加载第三方 NVIDIA 的apex 库。
fp16 半精度
半精浮点数是一种计算机使用的二级制浮点数数据类型,使用 2 字节(16位)存储。
为什么要使用 fp16
- 减少显存占用。由于 FP16 的内存占用只有 FP32 的一半,所以可以节省一半的显存空间。
- 加快训练和推理的计算。除了能减少内存的使用,还能节省模型训练时间,在大部分的测试中,基于 FP16 的加速方法,能够带来多一倍的加速体验。
- 张量核心的普及(NVIDIA tensor core),低精度计算是未来深度学习的一个重要趋势。
但是,FP16 带来以下两个问题:
- 溢出错误:由于 FP16 的动态范围比 FP32 狭窄很多,所以在计算中很容易出现上溢出和下溢出,溢出之后就会出现
nan
问题。在深度学习中,由于激活函数的梯度往往比权重梯度小,所以更加容易出现下溢出问题。 - 舍入误差:当梯度小鱼当前区间内的最小间隔,那么这次梯度更新就可能失败。
解决问题的办法:混合精度训练 + 动态损失放大
- 混合精度训练(mixed precision):在内存中使用 FP16 做存储和乘法从而加速计算,使用 FP32 做累加避免舍入误差。
在内存中用 FP16作存储和乘法从而加速计算,用 fp32 作累加避免舍入误差。混合精度训练的策略有效避免了舍入误差。在pytorch1.6的AMP上下文中,以下操作中Tensor会被自动转化为半精度浮点型torch.HalfTensor:
|
|
- 损失放大 (loss scaling):即使使用了混合精度训练,还是会存在无法收敛的情况,原因是激活梯度的值太小,造成了下溢出(underflow)。损失放大的思路:
- 反向传播前,将损失变化(dLoss)手动增大 $2^k$倍,因此反向传播时得到的中间变量(激活函数梯度)则不会溢出;
- 反向传播后,将权重梯度缩$2^k$ 倍,恢复正常值。
AMP(automatic mixed precision)自动混合精度包括:
- 自动: tensor 的 dtype 类型可以自动变化,框架按需自动调整 tensor的 dtype
- 混合精度:包括 torch.FloatTensor 和 torch.HalfTensor
pytorch 1.6 的新包: torch.cuda.amp, 是 NVIDIA 开发人员贡献到 pytorch 中的,只有支持 tensor core 的 cuda 硬件才能享受 AMP 带来的优势。 tensor core 是一种矩阵乘累加的计算单元,每个 tensor core 时针执行 64 个浮点混合精度操作(fp16 矩阵相乘 和 fp32 累加)
如果 pytorch1.5 之前的版本,可以使用 NVIDIA 的三方包 apex.amp
,下面给出了一个样例。
|
|
这个方式相对来说是更加容易的。
其中只有一个opt_level
需要用户自行配置:
O0
:纯FP32训练,可以作为accuracy的baseline;O1
:混合精度训练(推荐使用),根据黑白名单自动决定使用FP16(GEMM, 卷积)还是FP32(Softmax)进行计算。O2
:“几乎FP16”混合精度训练,不存在黑白名单,除了Batch norm,几乎都是用FP16计算。O3
:纯FP16训练,很不稳定,但是可以作为speed的baseline;
安装
|
|
如果是 pytorch 1.6 及其以上的版本,那么可以使用自带的接口: autocast
和 Gradscaler
目前 autocast
已经调通,使用 AMP 之后的结果,训练时候的显存占用从原先的 11G 下降到了 6 GB。
所以这个 AMP 还是很厉害的一项优化技术。
还不知道 Gradscaler
如何进行使用。
好像只有等到 使用 autocast 报错之后,然后才需要使用 gradscaler 去调整
方法一: 使用 dataparallel
|
|
DataParallel 会自动帮我们将数据切分 load 到相应 GPU,将模型复制到相应 GPU,进行正向传播计算梯度并汇总
缺点:
单进程计算模型的权重,然后分发到其他的 GPU上,所以如果是多机器多卡,那么网络通信就成为了一个瓶颈。所以当使用 nn.DataParallel
时候,尽量保证是单机器多卡。(GPU负载不均衡,通常是第一个GPU 的使用率更高一些)
另外这种方式不支持混合精度训练。
方法二:使用 torch.distributed 加速并行训练
DataParallel:单进程控制多 GPU。
DistributedDataParallel:多进程控制多 GPU。
和单进程训练不同的是,多进程训练需要注意以下事项:
- 在喂数据的时候,一个batch被分到了好几个进程,每个进程在取数据的时候要确保拿到的是不同的数据(
DistributedSampler
); - 要告诉每个进程自己是谁,使用哪块GPU(
args.local_rank
); - 在做BatchNormalization的时候要注意同步数据。
同步BN
现有的标准 Batch Normalization 因为使用数据并行(Data Parallel),是单卡的实现模式,只对单个卡上对样本进行归一化,相当于减小了批量大小(batch-size)(详见BN工作原理部分)。对于比较消耗显存的训练任务时,往往单卡上的相对批量过小,影响模型的收敛效果。之前在我们在图像语义分割的实验中,Jerry和我就发现使用大模型的效果反而变差,实际上就是BN在作怪。跨卡同步 Batch Normalization 可以使用全局的样本进行归一化,这样相当于‘增大‘了批量大小,这样训练效果不再受到使用 GPU 数量的影响。最近在图像分割、物体检测的论文中,使用跨卡BN也会显著地提高实验效果,所以跨卡 BN 已然成为竞赛刷分、发论文的必备神器。
|
|
3 使用 apex (混合精度)训练
Apex 是 NVIDIA 开源的用于混合精度训练和分布式训练库。Apex 对混合精度训练的过程进行了封装,改两三行配置就可以进行混合精度的训练,从而大幅度降低显存占用,节约运算时间。此外,Apex 也提供了对分布式训练的封装,针对 NVIDIA 的 NCCL 通信库进行了优化。
(这个是需要硬件支持的,目前只有 Tesla V100 和 TTITAN V 系列支持)
判断GPU 是否支持 FP16, 支持 tensor core 的GPU (2080 ti, titan, tesla),不支持的(Pascal 系列)
|
|
|
|
如果只是做测试,那么 transformers.ToTensor() 就足够了
|
|
pin_memory 是锁页内存,创建 DataLoader 时候,设置 pin_memory=True
, 这样将内存中的 tensor转义到 GPU显存中更快一些。
主机中的内存有两种存在方式,一种是锁页,一种是不锁页,锁页内存存放的内容在任何情况下都不会和主机中的虚拟内存(虚拟内存就是硬盘)进行交换。而不锁页内存在主机内存不足的情况下,数据会存放到虚拟内存中。
显卡中的显存全部都是锁页内存。
所以在内存比较充足的情况下,建议加上这个参数,这样可以加快速度。
参考文献
介绍PyTorch的简单示例 LEARNING PYTORCH WITH EXAMPLES
Horovod
Uber 开源的分布式深度学习框架, 最初是面向 tensorflow 的分布式训练框架, 现在也支持pytorch 等多个深度学习框架. 所以这个是跨平台的分布式训练工具, 兼容 tensorflow, keras 和 pytorch.
按照并行方法, 分布式训来年一般分为数据并行和模型并行两种:
- 模型并行, 分布式中的不同GPU 负责网络模型的不同部分. 例如,神经网络模型的不同网络层被分配到不同的 GPU, 或者同一层内部的不同参数被分配到不同 GPU
- 数据并行, 不同的GPU 有同一个模型的多个副本, 每个GPU 分配到不同的数据, 然后将所有 GPU 的计算按照哦阿某种方式合并.
注意,上述中的不同 GPU 可以是同一个机器上的多个 GPU, 也可以是不同机器上的GPU.
当然也有数据并行和模型并行的混合模式. 模型并行各个部分存在一定的依赖, 在实际训练中用得不多. 而数据并行,各个部分独立,在实际中更加常用, 提速效果也更好.
数据并行会涉及到各个 GPU 之间同步模型参数, 一般分为同步更新和异步更新. 同步更新需要等到所有 GPU 的梯度计算完成, 再统一计算新权重, 然后所有 GPU 同步数值后, 才进行下一轮的计算. 异步更新, 每个 gpu 梯度计算完后, 无需等待其他 gpu 的梯度计算, 即可进行下一轮的计算. 同步更新有等待, 异步更新基本上没有等待, 但异步更新涉及到梯度过时等更复杂问题.
无论是单机多卡, 还是多机多卡, 都是分布式运算.
使用
在单机4 卡上进行训练
|
|
在四机器,每个机器 4卡上进行训练, 只需要在一个机器上执行命令
|
|
horovod 只支持同步更新的数据并行. 模型并行和异步更新的数据并行, 应该是不支持的.
显存占用 85% 左右就行, 不要占满, 偶尔有些操作会上蹿, 一点余量都不给容易 OOM. memory-usage 不是关键, 关键是 GPU-util 拉满才是王道, 毕竟后者才是实打实的计算.
对比
Horovod 安装有点麻烦. DDP 一时爽,迁移火葬场.
由于 pytorch 已经实现了分布式训练, 所以可以不选择 horovod.
在学习的时候, 可以参考官方的 readme 和相应的 github repo, 比较好的项目, 一般都是有 examples
这样的东西.
https://github.com/horovod/horovod/tree/master/examples
可以进一步看看这个.
拷贝
(1)tensor.clone()
返回一个完全一样的 tensor,新的 tensor 开辟新的内存,但是仍保留在计算图中。
clone 操作在不共享数据内存的同时,支持梯度回溯,所以常用在神经网络中某个单元需要重复使用的场景下。
|
|
(2) tensor.detach()
从计算图中脱离
返回的新的 tensor 和原来的 tensor 共享数据内存,但不涉及计算,即
requires_grad =False
。因为是共享同一内存,对其中一个tensor 执行原地操作(比如resize_
,set_
) 则会引发错误。
detach 操作在共享内存,脱离计算图,所以常用在神经网络中仅需要利用张量数值,而不需要追踪梯度的场景下。
(3)tensor.clone().detach() 和 tensor.detach().clone()
两者的结果是一样的,即返回的 tensor 和原 tensor 在梯度上和数据上都没有任何关系,一般使用前者。
(4)copy_()
Tensor.``copy_
(src, non_blocking=False) → Tensor
Copies the elements from src
into self
tensor and returns self
.
The src
tensor must be broadcastable with the self
tensor. It may be of a different data type or reside on a different device.
-
Parameters
src (Tensor) – the source tensor to copy fromnon_blocking (bool) – if
True
and this copy is between CPU and GPU, the copy may occur asynchronously with respect to the host. For other cases, this argument has no effect.
inplace 的操作
支持 broadcast
文章作者 jijeng
上次更新 2019-02-01