1.PyTorch 源码分析(一):torch.nn.Module
2.PyTorch源码学习 - (13)模型的保存与加载
3.PyTorch源码学习系列 - 2. Tensor
4.PyTorch 源码解读之 torch.utils.data:解析数据处理全流程
5.PyTorch中torch.nn.Transformer的源码解读(自顶向下视角)
6.PyTorch 源码分析(三):torch.nn.Norm类算子
PyTorch 源码分析(一):torch.nn.Module
nn.Module是PyTorch中最核心和基础的结构,它是操作符/损失函数的基类,同时也是组成各种网络结构的基类(实际上是由多个module组合而成的一个module)。
在Python侧,2.1回调函数注册,2.2 module类定义中,vb获取网页源码有以下几个重点函数:
重点函数一:将模型的参数移动到CUDA上,内部会遍历其子module。
重点函数二:将模型的参数移动到CPU上,内部会遍历其子module。
重点函数三:将模型的参数转化为fp或者fp等,内部会遍历其子module。
重点函数四:forward函数调用。
重点函数五:返回该net的所有layer。
在类图中,PyTorch的算子都是module的子类,包括自定义算子和整网定义。
在C++侧,3.1 module.to("cuda")详细分析中,本质是将module的parameter&buffer等tensor移动到CUDA上,最终调用的是tensor.to(cuda)。
3.2 module.load/save逻辑中,PyTorch模型保存分为两种,一种是纯参数,一种是带模型结构(PyTorch中的模型结构,本质上是由module、sub-module构造的一个计算图)。
parameter、buffer是通过key-value的形式来存储和检索的,key为module的.name,value为存储具体数据的tensor。
InputArchive/OutputArchive的write和read逻辑。
通过Module,PyTorch将op/loss/opt等串联起来,类似于一个计算图。基于PyTorch构建的ResNet等模型,是逐个算子进行计算的,tensor在CPU和GPU之间来回流动,广播系统源码而不是整个计算都在GPU上完成(即中间计算结果不出GPU)。实际上,在进行推理时,可以构建一个计算图,让整个计算图的计算都在GPU上完成,不知道是否可行(如果GPU上有一个CPU就可以完成这个操作,不知道tensorrt是否是这样的操作)。
PyTorch源码学习 - ()模型的保存与加载
在PyTorch源码中,模型的保存与加载是通过`torch.save`和`torch.load`两个核心函数实现的。`torch.save`负责将一个Python对象持久化到磁盘文件,而`torch.load`则用于从磁盘文件中恢复对象。
在具体的实现中,`torch.save`会使用一系列辅助函数如`torch._opener`,`torch._open_zipfile_writer`,`torch._open_zipfile_writer_file`,`torch._open_zipfile_writer_buffer`等来操作文件和流。根据文件或内存缓冲区创建流容器,进行对象的保存。`torch._save`则进一步封装了文件的打开和写入过程,`torch._open_file_like`和`torch._open_file`用于管理文件句柄,`torch._open_buffer_writer`和`torch._open_buffer_reader`则封装了二进制流的读写。
对于模型加载,`torch.load`函数通过`torch._open_zipfile_reader`和`torch._weights_only_unpickler`实现。`torch._weights_only_unpickler`是定制的反序列化器,限制了处理的数据类型,确保安全加载模型权重。`torch._get_restore_location`和`torch.default_restore_location`则用于获取和设置恢复位置,以支持在多设备或分布式环境下的模型加载。
实现中,Python和C++的结合是关键,PyTorch使用`PyBind`实现C++和Python接口的绑定。`torch/_C/ __init__.pyi`用于定义Python中类型信息的模板,`torch/csrc/jit/python/init.cpp`则用于实现JIT(Just-In-Time)编译系统,将C++类对象绑定到Python环境,实现高效的动态编译。
在PyTorch中,Python主要负责管理C++对象,优办源码核心工作包括管理C++对象的生命周期、调用C++方法,以及处理Python层面的逻辑和接口定义。通过这样的结合,PyTorch实现了高性能和易用性的统一,为深度学习模型的开发和应用提供了强大支持。
整体来看,PyTorch的模型保存与加载机制通过精细的文件操作和对象管理,以及Python与C++的高效结合,确保了模型的高效持久化与灵活加载,为深度学习模型的开发与部署提供了坚实的底层支持。
PyTorch源码学习系列 - 2. Tensor
本系列文章同步发布于微信公众号小飞怪兽屋及知乎专栏PyTorch源码学习-知乎(zhihu.com),欢迎关注。
若问初学者接触PyTorch应从何学起,答案非神经网络(NN)或自动求导系统(Autograd)莫属,而是看似平凡却无所不在的张量(Tensor)。正如编程初学者在控制台输出“Hello World”一样,Tensor是PyTorch的“Hello World”,每个初学者接触PyTorch时,都通过torch.tensor函数创建自己的Tensor。
编写上述代码时,我们已步入PyTorch的宏观世界,利用其函数创建Tensor对象。然而,Tensor是如何创建、存储、设计的?今天,让我们深入探究Tensor的微观世界。
Tensor是什么?从数学角度看,Tensor本质上是多维向量。在数学里,数称为标量,一维数据称为向量,二维数据称为矩阵,三维及以上数据统称为张量。维度是衡量事物的方式,例如时间是gdb 源码编译一种维度,销售额相对于时间的关系可视为一维Tensor。Tensor用于表示多维数据,在不同场景下具有不同的物理含义。
如何存储Tensor?在计算机中,程序代码、数据和生成数据都需要加载到内存。存储Tensor的物理媒介是内存(GPU上是显存),内存是一块可供寻址的存储单元。设计Tensor存储方案时,需要先了解其特性,如数组。创建数组时,会向内存申请一块指定大小的连续存储空间,这正是PyTorch中Strided Tensor的存储方式。
PyTorch引入了步伐(Stride)的概念,表示逻辑索引的相对距离。例如,一个二维矩阵的Stride是一个大小为2的一维向量。Stride用于快速计算元素的物理地址,类似于C/C++中的多级指针寻址方式。Tensor支持Python切片操作,因此PyTorch引入视图概念,使所有Tensor视图共享同一内存空间,提高程序运行效率并减少内存空间浪费。
PyTorch将Tensor的物理存储抽象成一个Storage类,与逻辑表示类Tensor解耦,建立Tensor视图和物理存储Storage之间多对一的联系。Storage是声明类,具体实现在实现类StorageImpl中。StorageImp有两个核心成员:Storage和StorageImpl。
PyTorch的Tensor不仅用Storage类管理物理存储,还在Tensor中定义了很多相关元信息,如size、stride和dtype,这些信息都存在TensorImpl类中的sizes_and_strides_和data_type_中。key_set_保存PyTorch对Tensor的layout、device和dtype相关的ipk劫持源码调度信息。
PyTorch创建了一个TensorBody.h的模板文件,在该文件中创建了一个继承基类TensorBase的类Tensor。TensorBase基类封装了所有与Tensor存储相关的细节。在类Tensor中,PyTorch使用代码自动生成工具将aten/src/ATen/native/native_functions.yaml中声明的函数替换此处的宏${ tensor_method_declarations}
Python中的Tensor继承于基类_TensorBase,该类是用Python C API绑定的一个C++类。THPVariable_initModule函数除了声明一个_TensorBase Python类之外,还通过torch::autograd::initTorchFunctions(module)函数声明Python Tensor相关的函数。
torch.Tensor会调用C++的THPVariable_tensor函数,该函数在文件torch/csrc/autograd/python_torch_functions_manual.cpp中。在经过一系列参数检测之后,在函数结束之前调用了torch::utils::tensor_ctor函数。
torch::utils::tensor_ctor在文件torch/csrc/utils/tensor_new.cpp中,该文件包含了创建Tensor的一些工具函数。在该函数中调用了internal_new_from_data函数创建Tensor。
recursive_store函数的核心在于
Tensor创建后,我们需要通过函数或方法对其进行操作。Tensor的方法主要通过torch::autograd::variable_methods和extra_methods两个对象初始化。Tensor的函数则是通过initTorchFunctions初始化,调用gatherTorchFunctions来初始化函数,主要分为两种函数:内置函数和自定义函数。
PyTorch 源码解读之 torch.utils.data:解析数据处理全流程
文@ 目录 0 前言 1 Dataset 1.1 Map-style dataset 1.2 Iterable-style dataset 1.3 其他 dataset 2 Sampler 3 DataLoader 3.1 三者关系 (Dataset, Sampler, Dataloader) 3.2 批处理 3.2.1 自动批处理(默认) 3.2.2 关闭自动批处理 3.2.3 collate_fn 3.3 多进程处理 (multi-process) 4 单进程 5 多进程 6 锁页内存 (Memory Pinning) 7 预取 (prefetch) 8 代码讲解 0 前言 本文以 PyTorch 1.7 版本为例,解析 torch.utils.data 模块在数据处理流程中的应用。 理解 Python 中的迭代器是解读 PyTorch 数据处理逻辑的关键。Dataset、Sampler 和 DataLoader 三者共同构建数据处理流程。 迭代器通过实现 __iter__() 和 __next__() 方法,支持数据的循环访问。Dataset 提供数据获取接口,Sampler 控制遍历顺序,DataLoader 负责加载和批处理数据。 1 Dataset Dataset 包括 Map-style 和 Iterable-style 两种,分别用于索引访问和迭代访问数据。 Map-style dataset 通过实现 __getitem__() 和 __len__() 方法,支持通过索引获取数据。 Iterable-style dataset 实现 __iter__() 方法,适用于随机访问且批次大小依赖于获取数据的场景。 2 Sampler Sampler 用于定义数据遍历的顺序,支持用户自定义和 PyTorch 提供的内置实现。 3 DataLoader DataLoader 是数据加载的核心,支持 Map-style 和 Iterable-style Dataset,提供单多进程处理和批处理等功能。 通过参数配置,如 batch_size、drop_last、collate_fn 等,DataLoader 实现了数据的自动和手动批处理。 4 批处理 3.2.1 自动批处理(默认) DataLoader 默认使用自动批处理,通过参数控制批次生成和样本整理。 3.2.2 关闭自动批处理 关闭自动批处理,允许用户自定义批处理逻辑或处理单个样本。 3.2.3 collate_fn collate_fn 是手动批处理时的关键,用于整理单个样本为批次。 5 多进程 多进程处理通过 num_workers 参数启用,加速数据加载。 6 单进程 单进程模式下,数据加载可能影响计算流程,适用于数据量小且无需多进程的场景。 7 锁页内存 (Memory Pinning) Memory Pinning 技术确保数据在 GPU 加速过程中快速传输,提高性能。 8 代码讲解 通过具体代码分析,展示了 DataLoader 的初始化、迭代和数据获取过程,涉及迭代器、Sampler 和 Dataset 的交互。PyTorch中torch.nn.Transformer的源码解读(自顶向下视角)
torch.nn.Transformer是PyTorch中实现Transformer模型的类,其设计基于论文"Attention is All You Need"。本文尝试从官方文档和代码示例入手,解析torch.nn.Transformer源码。
在官方文档中,对于torch.nn.Transformer的介绍相对简略,欲深入了解每个参数(特别是各种mask参数)的用法,建议参考基于torch.nn.Transformer实现的seq2seq任务的vanilla-transformer项目。
Transformer类实现了模型架构的核心部分,包括初始化和forward函数。初始化时,主要初始化encoder和decoder,其中encoder通过重复堆叠TransformerEncoderLayer实现,decoder初始化类似。forward函数依次调用encoder和decoder,encoder的输出作为decoder的输入。
TransformerEncoder初始化包括设置encoder_layer和num_layers,用于创建重复的encoder层。forward函数则调用这些层进行数据处理,输出编码后的结果。
TransformerEncoderLayer实现了论文中红框部分的结构,包含SelfAttention和FeedForward层。初始化时,主要设置层的参数,forward函数调用这些层进行数据处理。
在实现细节中,可以进一步探索MultiheadAttention的实现,包括初始化和forward函数。初始化涉及QKV的投影矩阵,forward函数调用F.multi_head_attention_forward进行数据处理。
F.multi_head_attention_forward分为三部分:in-projection、scaled_dot_product_attention和拼接变换。in-projection进行线性变换,scaled_dot_product_attention计算注意力权重,拼接变换则将处理后的结果整合。
TransformerDecoder和TransformerDecoderLayer的实现与TransformerEncoder相似,但多了一个mha_block,用于处理多头注意力。
总结,torch.nn.Transformer遵循论文设计,代码量适中,结构清晰,便于快速理解Transformer模型架构。通过自顶向下的解析,可以深入理解其内部实现。
PyTorch 源码分析(三):torch.nn.Norm类算子
PyTorch源码详解(三):torch.nn.Norm类算子深入解析
Norm类算子在PyTorch中扮演着关键角色,它们包括BN(BatchNorm)、LayerNorm和InstanceNorm。1. BN/LayerNorm/InstanceNorm详解
BatchNorm(BN)的核心功能是对每个通道(C通道)的数据进行标准化,确保数据在每个批次后保持一致的尺度。它通过学习得到的gamma和beta参数进行缩放和平移,保持输入和输出形状一致,同时让数据分布更加稳定。 gamma和beta作为动态调整权重的参数,它们在BN的学习过程中起到至关重要的作用。2. Norm算子源码分析
继承关系:Norm类在PyTorch中具有清晰的继承结构,子类如BatchNorm和InstanceNorm分别继承了其特有的功能。
BN与InstanceNorm实现:在Python代码中,BatchNorm和InstanceNorm的实例化和计算逻辑都包含对输入数据的2D转换,即将其分割为M*N的矩阵。
计算过程:在计算过程中,首先计算每个通道的均值和方差,这是这些标准化方法的基础步骤。
C++侧的源码洞察
C++实现中,对于BatchNorm和LayerNorm,代码着重于处理数据的标准化操作,同时确保线程安全,通过高效的数据视图和线程视图处理来提高性能。PyTorch 源码解读之 torch.optim:优化算法接口详解
本文深入解读了 PyTorch 中的优化算法接口 torch.optim,主要包括优化器 Optimizer、学习率调整策略 LRScheduler 及 SWA 相关优化策略。以下为详细内容:
Optimizer 是所有优化器的基类,提供了初始化、更新参数、设置初始学习率等基本方法。在初始化优化器时,需要传入模型的可学习参数和超参数。Optimizer 的核心方法包括:
1. 初始化函数:创建优化器时,需指定模型的可学习参数和超参数,如学习率、动量等。
2. add_param_group:允许为模型的不同可学习参数组设置不同的超参数,以适应不同的学习需求。
3. step:执行一次模型参数更新,需要闭包提供损失函数的梯度信息。
4. zero_grad:在更新参数前,清空参数的梯度信息。
5. state_dict 和 load_state_dict:用于序列化和反序列化优化器的状态,便于保存和加载模型的训练状态。
Optimizer 包括常见的优化器如 SGD、Adagrad、RMSprop 和 Adam,各有特点,适用于不同的应用场景。例如,SGD 适用于简单场景,而 Adam 则在处理大数据集时表现更优。
学习率调节器 lr_scheduler 则负责在训练过程中调整学习率,以适应模型的收敛过程。PyTorch 提供了多种学习率调整策略,如 StepLR、MultiStepLR、ExponentialLR 等,每种策略都有其特点和应用场景,如 StepLR 用于周期性调整学习率,以加速收敛。
SWA(随机权重平均)是一种优化算法,通过在训练过程中计算模型参数的平均值,可以得到更稳定的模型,提高泛化性能。SWA 涉及 AveragedModel 类,用于更新模型的平均参数,以及 update_bn 函数,用于在训练过程中更新批量归一化参数。
总结,torch.optim 提供了丰富的优化算法接口,可以根据模型训练的需求灵活选择和配置,以达到最佳的训练效果和泛化性能。通过深入理解这些优化器和学习率调整策略,开发者可以更有效地训练深度学习模型。
torchvision应用与源码分析
torchvision是PyTorch库中的一部分,用于计算机视觉任务,它包含了一系列的预训练模型和数据集。
一:torchvision应用
在计算机视觉领域,torchvision提供了方便的API,用于加载和处理图像数据,训练模型和进行预测。它通常与PyTorch深度学习框架结合使用,为用户提供了一个完整的框架来开发和部署计算机视觉应用。
二:torchvision源码分析
1. setup.py分析
setup.py是Python包的配置文件,用于描述包的元数据和安装步骤。在torchvision中,setup.py文件被用来编译和安装包的依赖库。
1.1 导入依赖的模块
1.2 从配置文件中获取当前torchvision的版本信息
1.3 获取依赖的torch版本信息
1.4 获取编译扩展信息,然后传递给setup函数,启动编译
1.5 重点:get_extensions分析
在torchvision的setup.py文件中,get_extensions函数是核心部分,它负责编译torchvision自身的源码以及一些第三方库,如jpeg和codec等。
1.5.1 获取ccsrc下面的cpp源码
1.5.2 获取环境变量中配置的编译选项
1.5.3 判断是AMD的HIP还是nVidia的CUDA,来获取到最终的cuda文件
1.5.4:依据环境上是否支持cuda来确定编译扩展
1.5.5 添加扩展
至此,torchvision就将整个版本包编译出来了,会调用torch的cpp和cuda编译扩展(即:通过gcc+nvcc来编译ccsrc下面的源码,而不用torchvision自行再来设置各种编译环境信息了)。
整个编译核心流程总结如下:
2. torchvision新增算子流程
以torchvision.ops.DeformConv2d为例
2.1 基础用法与模型结构
通过Netron工具打开模型结构,可以看到torchvision的deform_conv2d是单独的IR定义的算子
2.2 python侧实现分析
deform_conv2d定义在Python侧,实际上做了参数初始化后,将转交给了C++侧对应的接口
2.3 C++侧分析:torch.ops.torchvision.deform_conv2d
2.3.1 接口定义
2.3.2 接口实现
关键在于这两个接口的注册
算子的具体实现和如何向pytorch完成注册呢?
该算子有C++和CUDA实现方式,C++方式可以在纯CPU版本中运行,cuda实现则依赖于GPU和CUDA
2.3.2.1 C++实现
2.3.2.2 CUDA实现
这种方式实现的算子,trace出来的模型中,为单个算子
总结:自定义算子向torch集成分为两步
三:基于torchvision新增一个算子
实现一个算子:my_add = 2*x + y
3.1 环境准备