安装
Anaconda
.condarc
的channels
是有顺序(优先级)的:
channels:
- pytorch
- conda-forge
- main
conda install
会从上往下找所需的包,找到了就会安装
如果main放在pytorch前面,就算按照官网的命令安装GPU版,也会装成CPU版,因为main里面只有CPU版的pytorch
要安装GPU版,把pytorch仓库放在main前面即可
后端(backends)检查
cuDNN
版本:torch.backends.cudnn.version()
是否可用:torch.backends.cudnn.is_available()
是否启用:torch.backends.cudnn.enabled
CUDA
查看pytorch是否为支持cuda的版本:torch.backends.cuda.is_built()
Returns whether PyTorch is built with CUDA support. Note that this doesn’t necessarily mean CUDA is available; just that if this PyTorch binary were run a machine with working CUDA drivers and devices, we would be able to use it. 返回True只能表示该pytorch(版本)可以支持cuda,实际能不能用cuda还要看系统是否安装了cuda环境
torch.save
后缀格式
首先讲讲保存模型或权重参数的后缀格式,权重参数和模型参数的后缀格式一样,pytorch中最常见的模型保存使用 .pt 或者是 .pth 作为模型文件扩展名。还有其他的保存数据的格式为.t7或者.pkl格式。t7文件是沿用torch7中读取模型权重的方式,而pth文件是python中存储文件的常用格式,而在keras中则是使用.h5文件 。
要保存多个组件,请在字典中组织它们并使用
torch.save()
来序列化字典。PyTorch 中常见的保存checkpoint 是使用 .tar 文件扩展名。
小结:
保存模型用.pt
保存Checkpoint(包含更多、更完整的信息,用于继续训练)用.tar
显存
参数量与计算量没有绝对的关系,例如比较fc与conv,前者参数量大,但计算量小,后者参数量小,但计算量大。
谷歌提出的MobileNet就以增大参数量为代价换取更小的计算量(空间换时间),从而提升运行速度。
显存占用 = 模型参数 + 计算产生的中间变量
-
模型参数
与输入无关,模型初始化后就固定了
-
中间变量
梯度与动量(optimizer反向传播时用到)
GPU并行下的错误定位
错误如下:
/opt/conda/conda-bld/pytorch_1607370172916/work/aten/src/ATen/native/cuda/ScatterGatherKernel.cu:312: operator(): block: [716,0,0], thread: [17,0,0] Assertion `idx_dim >= 0 && idx_dim < index_size && "index out of bounds"` failed.
/opt/conda/conda-bld/pytorch_1607370172916/work/aten/src/ATen/native/cuda/ScatterGatherKernel.cu:312: operator(): block: [716,0,0], thread: [28,0,0] Assertion `idx_dim >= 0 && idx_dim < index_size && "index out of bounds"` failed.
/opt/conda/conda-bld/pytorch_1607370172916/work/aten/src/ATen/native/cuda/ScatterGatherKernel.cu:312: operator(): block: [291,0,0], thread: [63,0,0] Assertion `idx_dim >= 0 && idx_dim < index_size && "index out of bounds"` failed.
CUDA error: device-side assert triggered
在运行命令前加上`CUDA_LAUNCH_BLOCKING=1,强制同步执行,从而显示出正确的错误定位。参考
您可以通过设置环境变量强制进行同步计算
CUDA_LAUNCH_BLOCKING=1
。这在 GPU 上发生错误时非常方便。(使用异步执行时,直到实际执行操作后才会报告此类错误,因此堆栈跟踪不会显示请求的位置。)参考
当模型在GPU上运行的时候其实是没办法显示出真正导致错误的地方的(按照PyTorch Dev的说法:“Because of the asynchronous nature of cuda, the assert might not point to a full correct stack trace pointing to where the assert was triggered from.”即这是CUDA的特性,他们也没办法),所以可以通过将模型改成在CPU上运行来检查出到底是哪里出错(因为CPU模式下会有更加细致的语法/程序检查)。但是当训练网络特别大的时候,这个方法通常是不可行的,转到CPU上训练的话可能会花费很长时间[1]。参考
本例中真正的错误位置与异步的提示位置差了一行,加上CUDA_LAUNCH_BLOCKING=1
后,显示了正确的位置。
错误原因是标签(target, gt)中出现了负值,从而导致数组越界。
nn.Transformer
https://pytorch.org/docs/master/nn.html#transformer-layers
设置默认GPU
- 官方不推荐使用
torch.cuda.set_device(device)
,详见文档:
Usage of this function is discouraged in favor of
device
. In most cases it’s better to useCUDA_VISIBLE_DEVICES
environmental variable.
-
torch.cuda.device(device)
:Context-manager that changes the selected device. -
os.environ['CUDA_VISIBLE_DEVICES'] = '3'
通过环境变量来设置(官方推荐)
尝试后发现:
- 方法1会在每张GPU上都创建一个进程,仅有实际使用的卡有显存占用,其他卡显存占用0
- 方法3设置环境变量,一切正常,不会生成额外进程(看来还是要按官方建议来)
卷积的bias
卷积后接BN的话,bias就不起作用(公式推导),设为False,减少计算量
Tensor的复制
需要用=
的时候,都加上.clone()
gather & scatter_
gather
函数声明:torch.gather(input, dim, index, out=None, sparse_grad=False) → Tensor
一句话解释:按照index –> 从input中找值 –> 替换掉index的值(生成大小跟index一样的新张量)
size限制:
- 除了dim维外(apart from dimension dim),index的size大小不得超过input(想象把index矩阵盖到input上,按dim方向挑选值。当dim维超过时,只是会选到重复元素;而其他维超过了,找不到对应向量,就不知道从哪选值)
output的size与index一致
同时看input和index中dim那一维的向量(比如二维矩阵:dim=0就是纵向量,dim=1就是横向量),从input中按照index来找值,找到的值填回index的对应位置,生成大小跟index一样的新张量
如dim=1,则根据向量index[i, :, j, ...]
的元素值,在向量a[i, :, j, ...]
中找值,找到的值填回index。想象把index盖到input上,
dim=1
按行看,以中间的行为例,计算方式如下所示:(其余行同理)
import torch
a = torch.arange(1., 10.).view(3,3)
print(a)
# tensor([[1., 2., 3.],
# [4., 5., 6.],
# [7., 8., 9.]])
index = torch.LongTensor([
[1, 1, 1, 1, 1],
[2, 0, 1, 2, 0],
[1, 2, 0, 0, 2],
])
# output = torch.gather(input=a, dim=1, index=index)
a.gather(dim=1, index=index) # 与上一行等价
# tensor([[2., 2., 2., 2., 2.],
# [6., 4., 5., 6., 4.],
# [8., 9., 7., 7., 9.]])
pytorch中有很多类似的等价写法:func(input=a, **kwargs)
等价于a.func(**kwargs)
官方计算公式:
out[i][j][k] = input[index[i][j][k]][j][k] # if dim == 0
out[i][j][k] = input[i][index[i][j][k]][k] # if dim == 1
out[i][j][k] = input[i][j][index[i][j][k]] # if dim == 2
scatter_
函数声明:scatter_(dim, index, src) → Tensor
一句话解释:将src的值 –> 根据index(用于确定位置) –> 写到self里面(覆盖 or 替换self中相应位置的值)
公式:
self[index[i][j][k]][j][k] = src[i][j][k] # if dim == 0
self[i][index[i][j][k]][k] = src[i][j][k] # if dim == 1
self[i][j][index[i][j][k]] = src[i][j][k] # if dim == 2
size限制:
- 除了dim维外(apart from dimension dim),index的size不得超过self(想象把index矩阵盖到self上,dim维超过了,只是会在同一位置重复填值,后填的值覆盖先填的;而其他维超过了,找不到对应向量,就不知道往哪填值)
- index的size不得超过src(想象把 index覆盖到src上,被覆盖的区域就会填到self里面)
output的size与self一致
个人认为理解这两个函数的难点在于:理解dim的含义
这里dim的含义与gather函数是相同的:可以将一个dim维的向量(行向量 or 列向量)当作独立子任务来操作。即选定一个dim维的向量,执行一套完整的计算流程(见一句话解释),再处理dim维的下一个向量
scatter_的计算过程与gather类似,都是围绕dim进行的,如上一段所诉。示例代码如下:
import torch
a = torch.zeros(3, 3)
print(f'a:\n{a}')
# tensor([[0., 0., 0.],
# [0., 0., 0.],
# [0., 0., 0.]])
src = torch.arange(1., 16.).view(3,5)
print(f'src:\n{src}')
# tensor([[ 1., 2., 3., 4., 5.],
# [ 6., 7., 8., 9., 10.],
# [11., 12., 13., 14., 15.]])
index = torch.LongTensor([
[1, 1, 1, 1],
[2, 0, 1, 2],
[2, 0, 1, 2],
])
a.scatter_(dim=1, index=index, src=src)
print(f'a:\n{a}')
# tensor([[ 0., 4., 0.],
# [ 7., 8., 9.],
# [12., 13., 14.]])
以第一行为例:
- 把1(src)写到张量a的1(index)位置
- 把2(src)写到张量a的1(index)位置(覆盖)
- 把3(src)写到张量a的1(index)位置(覆盖)
- 把4(src)写到张量a的1(index)位置(覆盖)
- 最终张量a的1位置就是4(后面的覆盖了前面的)
总结
不同
-
gather:从self里面提取出需要的元素,组成新的tensor
scatter_:替换/修改 self 中的某些元素
-
gather:将self中(被选中的)零散的元素聚集起来形成新的tensor
scatter_:将集中在src中的元素分散到self中
相似
dim的含义:都可以将dim维上每个向量当作独立子任务来执行一遍完整的操作(gather/scatter_)
参考
[1] pytorch.gather/scatter_的用法
[2] 3分钟理解 pytorch 的 gather 和 scatter
expand
函数声明:expand(*sizes) → Tensor
,属于类:torch.Tensor
将大小为1的维扩展至size指定的大小(原来大小不为1的维不能扩展)
size
为-1
表示保持该维大小不变
不会生成分配新的内存,只是在原Tensor上创建一个新的视图(?)
kernel_size=1的卷积
kernel_size=1
时,一维二维三维卷积有什么区别?
冻结参数
如何冻结BN:关于pytorch的BN,在训练的模型上增添新模块[只训练新模块]
apply
apply(fn: Callable[Module, None]) → T
,属于类:torch.nn.Module
Applies
fn
recursively to every submodule (as returned by.children()
) as well as self.