Hook函数机制:不改变主体,实现额外的功能,像一个挂件一样;由于 PyTorch 是基于动态图实现的,因此在一次迭代运算结束后,一些中间变量(如非叶子节点的梯度和特征图)会被释放掉。在这种情况下想要提取和记录这些中间变量,就需要使用 Hook 函数。
功能: 注册一个反向传播 hook 函数,仅输入一个参数,为张量的梯度。
功能: 注册 module 的前向传播的hook函数,可用于获取输入数据。
功能: 注册 module 的反向传播的hook函数,可用于获取梯度。
功能: 注册 module 的前向传播hook函数,可用于获取中间的 feature map。
import torch
x = torch.randn(size = (4,3,5,6))
x = torch.tensor([[1.], [2], [3]])
print(x.nelement())
结果是:3
综述:PyTorch显存机制分析
PyTorch在进行深度学习训练的时候,有4大部分的显存开销,分别是模型参数(parameters),模型参数的梯度(gradients),优化器状态(optimizer states)以及中间激活值(intermediate activations) 或者叫中间结果(intermediate results)。
(1) No Nvidia-smi
nvidia-smi 来看 pytorch 的显存占用,盯着跳来跳去的torch缓存区分析真的累。
PyTorch是有缓存区的设置的,意思就是一个Tensor就算被释放了,进程也不会把空闲出来的显存还给GPU,而是等待下一个Tensor来填入这一片被释放的空间。
有什么好处?进程不需要重新向GPU申请显存了,运行速度会快很多,有什么坏处?他不能准确地给出某一个时间点具体的Tensor占用的显存,而是显示的已经分配到的显存和显存缓冲区之和。这也是令很多人在使用PyTorch时对显存占用感到困惑的罪魁祸首。
(2) torch.cuda is all you need
在分析PyTorch的显存时候,一定要使用torch.cuda里的显存分析函数torch.cuda.memory_allocated()和torch.cuda.max_memory_allocated(),前者可以精准地反馈当前进程中Torch.Tensor所占用的GPU显存,后者则可以告诉我们到调用函数为止所达到的最大的显存占用字节数。
还有torch.cuda.memory_reserved()则是查看当前进程所分配的显存缓冲区是多少的。
memory_allocated+memory_reserved就等于nvidia-smi中的值。
在PyTorch中,显存是按页为单位进行分配的,这可能是CUDA设备的限制。就算我们只想申请4字节的显存,CUDA也会为我们分配512字节或者1024字节的空间。
在PyTorch中,只要一个Tensor对象在后续不会再被使用,那么PyTorch就会自动回收该Tensor所占用的显存,并以缓冲区的形式继续占用显存。
要是实在看缓冲区不爽的话,也可以用torch.cuda.empty_cache()把它归零,但是程序速度会变慢
torch.clamp(a, 0, 255)
如果a是一张图像数据张量,限制其取值范围在[0, 255]之间。
当a中有元素低于0,则该元素被置为0;
当a中有元素超过255,则该元素取值被置为255;
算子内部进行的操作:
算子内部进行的操作:
torch.jit是PyTorch的即时编译器(Just-In-Time Compiler,JIT)模块。这个模块存在的意义是把PyTorch的动态图转换成可以优化和序列化的静态图,其主要工作原理是通过输入预先定义好的张量,追踪整个动态图的构建过程,得到最终构建出来的动态图,然后转换为静态图(通过中间表示,即IntermediateRepresentation,来描述最后得到的图)。通过JIT得到的静态图可以被保存,并且被PyTorch其他的前端(如C++语言的前端)支持。
另外,JIT也可以用来生成其他格式的神经网络描述文件,如ONNX。需要注意的一点是,torch.jit支持两种模式,即脚本模式(ScriptModule)和追踪模式(Tracing)。前者和后者都能构建静态图,区别在于前者支持控制流,后者不支持,但是前者支持的神经网络模块比后者少,比如脚本模式不支持torch.nn.GRU(详细的描述可以参考PyTorch官方提供的JIT相关的文档)。
mkldnn是intel开发的开源项目,针对cpu上运行神经网络做了一些优化
将tensor的维度换位。
import torch
import numpy as np
a=np.array([[[1,2,3],[4,5,6]]])
unpermuted=torch.tensor(a)
print(unpermuted.size()) # ——> torch.Size([1, 2, 3])
permuted=unpermuted.permute(2,0,1)
print(permuted.size()) # ——> torch.Size([3, 1, 2])
PyTorch专用编译器:跨三大操作系统,单击几下完成任务,还带教程
pytorch中使用TensorRT
某些Tensor操作(如transpose、permute、narrow、expand、view)与原Tensor是共享内存中的数据,不会改变底层数组的存储,但原来在语义上相邻、内存里也相邻的元素在执行这样的操作后,在语义上相邻,但在内存不相邻,即不连续了(is not contiguous)。如果想要变得连续使用contiguous方法,则会重新开辟一块内存空间保证数据是在内存中是连续的。
使用reshape()方法直接对底层数据进行了更改相当于先后调用了view()方法和contiguous方法。
踩坑:一开始我被contiguous的字面意思“连续的”所误导了,以为在内存中不是存在一块连续的地址,其实tensor中的数据还是存在内存中的一块区域里,只是布局问题。
Pytorch中contiguous()函数理解
PyTorch中的contiguous
1.tensorflow的数据形式:[B, H, W, C]
2.pytorch的数据形式:[B, C, H, W]
3.c++ opencv数据只有resize形式[W, H],输出[H,W,C],矩阵初始化也是H,W
1.希望保持一部分的网络参数不变,只对其中一部分的参数进行调整;
2.或者只训练部分分支网络,并不让其梯度对主网络的梯度造成影响,
1)detach()与detach_()
在x->y->z传播中,对y进行detach(),依然可以反向传播到x;对y进行detach_(),就把x->y->z切成两部分:x和y->z,x就无法接受到后面传过来的梯度
2)detach()和data
共同点:x.data(x.detach()) 返回和 x 的相同数据 tensor, 这个新的tensor和原来的tensor(即x)是共用数据的,一者改变,另一者也会跟着改变,且require s_grad = False
不同点: x.data 不能被 autograd 追踪求微分,
pytorch的两个函数 .detach() .detach_() 的作用和区别
requires_grad: 如果需要为张量计算梯度,则为True,否则为False。Variable变量的requires_grad的属性默认为False,若一个节点requires_grad被设置为True,那么所有依赖它的节点的requires_grad都为True。
grad_fn: 梯度函数,用来记录变量是怎么来的,方便计算梯度,y = x*3,grad_fn记录了y由x计算的过程。
grad:当执行完了backward()之后,通过x.grad查看x的梯度值。
torch.mm两个矩阵(NxM)和(MxP)相乘得(NxP);
torch.bmm两个batch矩阵(BxNxM)和(BxMxP)相乘得(BxNxP)
add_()表示原地in-place操作
数据不需要计算梯度,也不会进行反向传播。
在该模块下,所有计算得出的tensor(如下面的w)的requires_grad都自动设置为False。
即使一个tensor(命名为x)的requires_grad = True,在with torch.no_grad计算,由x得到的新tensor(w)requires_grad也为False,且grad_fn也为None,即不会对w求导。
import torch
x = torch.randn(2, 5, requires_grad=True)
y = torch.randn(2, 5, requires_grad=True)
z = torch.randn(2, 5, requires_grad=True)
with torch.no_grad():
w = x + y + z
print(w.requires_grad)
print(w.grad_fn)
print(x)
输出:
False
None
tensor([[-0.0348, -0.8959, 0.3981, 0.0469, 0.5354],
[-0.5925, -0.3968, 0.8008, -1.7377, 1.8987]], requires_grad=True)
1、driver API更为底层一些,对一般用户不是很友好,而runtime API把driver API包装了一些,将很多原本需要代码编写的步骤自动完成了,因此会更加容易上手。
2、最大的一个不同就是使用runtime API需要NVCC编译器,而使用driver API不用。
cuda 的driver API 和 runtime API