1.ONNX-Runtime一本通:综述&使用&源码分析(持续更新)
2.PyTorch 源码分析(一):torch.nn.Module
3.TVM 自底向上(二):TIR 的源码概念和编译原理
4.TVM 自底向上(三):TE 的概念和编译原理
5.PyTorch源码学习系列 - 2. Tensor
6.Pytorch深入剖析 | 1-torch.nn.Module方法及源码
ONNX-Runtime一本通:综述&使用&源码分析(持续更新)
ONNX-Runtime详解:架构概览、实践与源码解析
ONNX-Runtime作为异构模型运行框架,源码其核心机制是源码先对原始ONNX模型进行硬件无关的图优化,之后根据支持的源码硬件选择相应的算子库,将模型分解为子模型并发在各个平台执行。源码它提供同步模式的源码bio溯源码在哪计算支持,暂不包括异步模式。源码ORT(onnx-runtime缩写)是源码主要组件,包含了图优化(graph transformer)、源码执行提供者(EP)等关键模块。源码
EP是源码执行提供者,它封装了硬件特有的源码内存管理和算子库,可能只支持部分ONNX算子,源码但ORT的源码CPU默认支持所有。ORT统一定义了tensor,源码但EP可有自定义,需提供转换接口。每个推理会话的run接口支持多线程,要求kernel的compute函数是并发友好的。
ORT具有后向兼容性,能运行旧版本ONNX模型,并支持跨平台运行,包括Windows、Linux、macOS、iOS和Android。安装和性能优化是实际应用中的重要步骤。
源码分析深入到ORT的核心模块,如框架(内存管理、tensor定义等)、图结构(构建、排序与修改)、优化器(包括RewriteRule和GraphTransformer),以及平台相关的功能如线程管理、文件操作等。Session是推理流程的管理核心,构造函数初始化模型和线程池,load负责模型反序列化,initialize则进行图优化和准备工作。
ORT中的执行提供者(EP)包括自定义实现和第三方库支持,如TensorRT、CoreML和SNPE。其中,ORT与CoreML和TensorRT的集成通过在线编译,将ONNX模型传递给这些框架进行计算。ORT通过统一的接口管理元框架之上的算子库,但是把网站源码导出否支持异构运算(如SNPE与CPU库的混合)仍有待探讨。
总结来说,ONNX-Runtime处理多种模型格式,包括原始ONNX和优化过的ORT模型,以适应多平台和多设备需求。它通过复杂的架构和优化技术,构建了可扩展且高效的推理软件栈,展示了flatbuffer在性能和体积方面的优势。
附录:深入探讨ORT源码编译过程的细节。
PyTorch 源码分析(一):torch.nn.Module
nn.Module是PyTorch中最核心和基础的结构,它是操作符/损失函数的基类,同时也是组成各种网络结构的基类(实际上是由多个module组合而成的一个module)。
在Python侧,2.1回调函数注册,2.2 module类定义中,有以下几个重点函数:
重点函数一:将模型的参数移动到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是否是这样的操作)。
TVM 自底向上(二):TIR 的概念和编译原理
在深入探讨TVM中的编译过程与中间表示(IR)时,特别是TIR(Tensor IR)的概念及其编译原理,本节将重点聚焦于如何将神经网络模型转化为硬件源代码,以帮助读者更深入地理解这一复杂过程,并找到学习TVM的乐趣。
TIR是TVM中最接近目标硬件的数据结构,无论前端模型(如pytorch、tensorflow或ONNX)经过了哪些转换,最终在被编译为目标后端代码前,都需要被转化为TIR。TVM的编译流程中,TIR起着核心作用,其位置如图所示。
在TIR的实现中,抽象语法树(AST)扮演着关键角色。AST是一种通用的数据结构,用于表示任何编程语言的语法结构。它由节点组成,每个节点代表一种语言元素,如变量、函数调用或控制结构。在TIR中,AST为编译为不同硬件(如C++、CUDA、LLVM IR等)的代码提供了一个通用的结构。
通过将AST转换为源代码(CodeGen过程),TIR能够解决神经网络推理计算中遇到的两个主要问题:首先,它能够表示深度学习算子(如卷积、池化、ReLU)和控制结构(如min、max、if-else),这些算子和控制结构都基于基本的数学运算。其次,TIR的通用性使得加速逻辑可以被抽象化并应用于各种硬件架构,从而实现跨平台的加速。
TVM中的关键概念包括:IRModule、PrimFunc和CodeGen。IRModule是完达山奶粉溯源码TVM中最小的编译单元,用于封装待编译的TIR和其他中间表示。PrimFunc封装了完整的AST,作为IRModule的API,对应生成.so库的函数入口。CodeGen负责将AST转换为目标硬件的源代码,本质上是一个树遍历迭代器。
TVMScript提供了一种简化TIR AST开发的方法,它利用Python AST(Python的语法树实现,如Cython使用),允许直接使用Python语法编写AST,从而简化了TIR的开发流程。TVMScript还支持双向转换,即可以从TIR AST生成TVMScript,也可以从TVMScript解析回TIR AST。
通过调用tvm.build函数,可以将IRModule编译为目标代码并运行,该过程根据所选的目标(如CPU、GPU或LLVM IR)选择适当的CodeGen。对于不同的目标,CodeGen过程涉及将TIR AST转换为目标硬件的源代码,然后使用相应的编译器生成可执行文件。例如,对于C++目标,CodeGen过程包括TIR到C++源代码的转换,而CUDA目标则涉及TIR到CUDA源代码的转换。
最后,本节概述了使用TVMScript编写TIR AST和调用适当CodeGen编译为源代码的完整流程,并强调了其他相关章节的内容。通过了解这些概念和原理,读者能够更深入地理解TVM编译过程的内在机制,从而为探索和应用TVM提供坚实的基础。
TVM 自底向上(三):TE 的概念和编译原理
在探讨 TVM 自底向上系列文章的第三篇章节中,我们深入探讨了 Tensor Expression (TE) 的概念及其编译原理。前文已介绍了 TVM 中最接近目标硬件源代码的 IR 表示,即 Tensor IR (TIR)。本文将聚焦 TE,它是位于 Relay IR/TOPI 和 TIR 之间的抽象层次。
TE 的核心作用之一是提供除 TVMScript 外的另一种方式来构建 TIR AST。TE 通过抽象程度更高的表达式,允许用户以更灵活的方式编写计算逻辑,但 TE 本身无法直接编译为硬件源代码。相反,它需要经过一个称为“lowering”的过程,将 TE 表达式转换为 TIR 的 Primitive Function,之后 TIR 的编译流程得以进行。
TE 在 TVM 编译流程中的现在时间源码位置如下图所示。首先,使用 TE 来构建计算图,通过调用 `te.create_prim_func` 函数将 TE 表达式转换为 TIR 的 `PrimFunc`。然后,这些 `PrimFunc` 被嵌入到一个新的 `IRModule` 中,该 `IRModule` 再被编译为目标硬件代码。
以官方文档 Blitz Course to TensorIR 提供的示例为例,使用 TE 实现了一个与前文使用 TVMScript 实现相同的 TIR AST。TE 方法通过 `te.Tensor` 对象构建计算图,随后调用 `te.create_prim_func` 将这些对象转换为 TIR 的 `PrimFunc`。最终,将这些 `PrimFunc` 嵌入到 `IRModule` 中,该模块可被编译为目标硬件执行代码。
在 TE 中,`tvm.te.Tensor` 是计算图中的数据块,类似于神经网络中的 feature map。TE 提供了两种主要的 `Tensor` 类型:`tvm.te.placeholder` 和 `tvm.te.compute`。`placeholder` 用于计算图的输入节点,而 `compute` 则用于定义计算 Tensor 的操作,基于传入的 lambda 表达式计算结果。
`PrimExpr` 是 TE 中用于表示 AST 的概念,它是通过 lambda 表达式将 Tensor 和运算转换而成。`PrimExpr` 支持各种数学运算符,并且其内部结构通过 `tir.expr.ProducerLoad` 对象实现,使得 `PrimExpr` 能够自然地表示 AST。通过 `te.compute` 调用将 `PrimExpr` 实例化为 `tvm.te.Tensor`,进一步构建计算图。
`Operation` 是 TE 中的抽象类,它封装了特定的计算逻辑,例如 `te.compute` 实例化时所对应的计算操作。通过 `Operation`,TE 实现了对计算图中操作的精细控制,包括输入和输出 Tensor 的管理。
TE 的另一个关键功能是 `te.create_prim_func`,该函数将 TE 表达式转换为 TIR 的 `PrimFunc`。这一过程包括构建一个 Graph,其中包含了所有 Operation 的输出和输入 Tensor 的信息,从而实现 TE 到 TIR 的转换。
综上所述,TE 作为 TVM 中的高级抽象层,提供了更灵活的计算图构建方式,通过与 TIR 的结合实现了高效的目标硬件编译。TE 的引入不仅丰富了 TVM 的编程模型,还为实现不同硬件加速策略提供了更多可能。
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本质上是多维向量。在数学里,数称为标量,一维数据称为向量,二维数据称为矩阵,三维及以上数据统称为张量。维度是衡量事物的方式,例如时间是一种维度,销售额相对于时间的关系可视为一维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相关的调度信息。
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深入剖析 | 1-torch.nn.Module方法及源码
torch.nn.Module是神经网络模型的基础类,大部分自定义子模型(如卷积、池化或整个网络)均是其子类。torch.nn.Parameter是继承自torch.tensor的子类,用以表示可训练参数。定义Module时,可以使用个内置方法,例如add_module用于添加子模块,children和named_children用于获取子模块,modules和named_modules用于获取所有模块,register_parameter用于注册参数,parameters和named_parameters用于获取参数,get_parameter用于获取指定参数等。Module还支持数据格式转换,如float、double、half和bfloat,以及模型的设备移动,如cpu、cuda和xpu。训练模式调整可以通过train和eval方法实现。模型参数的梯度可以使用zero_grad方法清零。
模型的前向传播由forward方法定义,而apply方法允许应用特定函数到模型的所有操作符上。模型状态可以通过state_dict和load_state_dict方法进行保存和加载,常用于保存模型参数。此外,模型可以设置为训练模式或评估模式,影响特定模块如Dropout和BatchNorm的行为。
在PyTorch中,hook方法用于在前向和反向传播过程中捕获中间变量。注册hook时,可以使用torch.Tensor.register_hook针对张量注册后向传播函数,torch.nn.Module.register_forward_hook针对前向传播函数,torch.nn.Module.register_forward_pre_hook用于在前向传播之前修改输入张量,以及torch.nn.Module.register_backward_hook用于捕获中间层的梯度输入和输出。
通过这些方法,开发者可以灵活地调整、监控和优化神经网络模型的行为,从而实现更高效、更精确的模型训练和应用。利用hook方法,用户可以访问中间变量、修改输入或输出,以及提取特征图的梯度,为模型的定制化和深入分析提供了强大的工具。
Pytorch中的Dataset和DataLoader源码深入浅出
构建Pytorch中的数据管道是许多机器学习项目的关键步骤,尤其是当处理复杂的数据集时。本篇文章将深入浅出地解析Pytorch中的Dataset和DataLoader源码,旨在帮助你理解和构建高效的数据管道。
如果你在构建数据管道时遇到困扰,比如设计自定义的collate_fn函数不知从何入手,或者数据加载速度成为训练性能瓶颈时无法优化,那么这篇文章正是你所需要的。通过阅读本文,你将能够达到对Pytorch中的Dataset和DataLoader源码的深入理解,并掌握构建数据管道的三种常见方式。
首先,我们来了解一下Pytorch中的Dataset和DataLoader的基本功能和工作原理。
Dataset是一个类似于列表的数据结构,具有确定的长度,并能通过索引获取数据集中的元素。而DataLoader则是一个实现了__iter__方法的可迭代对象,能够以批量的形式加载数据,控制批量大小、元素的采样方法,并将批量结果整理成模型所需的输入形式。此外,DataLoader支持多进程读取数据,提升数据加载效率。
构建数据管道通常只需要实现Dataset的__len__方法和__getitem__方法。对于复杂的数据集,可能还需要自定义DataLoader中的collate_fn函数来处理批量数据。
深入理解Dataset和DataLoader的原理有助于你构建更加高效的数据管道。获取一个批量数据的步骤包括确定数据集长度、抽样出指定数量的元素、根据元素下标获取数据集中的元素,以及整理结果为两个张量。在这一过程中,数据集的长度由Dataset的__len__方法确定,元素的抽样方法由DataLoader的sampler和batch_sampler参数控制,元素获取逻辑在Dataset的__getitem__方法中实现,批量结果整理则由DataLoader的collate_fn函数完成。
Dataset和DataLoader的源码提供了灵活的控制和优化机制,如调整batch大小、控制数据加载顺序、选择采样方法等。以下是一些常用的Dataset和DataLoader功能的实现方式:
使用Dataset创建数据集的方法有多种,包括基于Tensor创建数据集、根据目录创建数据集以及创建自定义数据集等。通过继承torch.utils.data.Dataset类,你可以轻松地创建自定义数据集。
DataLoader的函数签名较为简洁,主要参数包括dataset、batch_size、shuffle、num_workers、pin_memory和drop_last等。在构建数据管道时,只需合理配置这些参数即可。对于复杂结构的数据集,可能还需要自定义collate_fn函数来处理批量数据的特殊需求。
总的来说,通过深入理解Dataset和DataLoader的原理,你可以更高效地构建数据管道,优化数据加载流程,从而提升机器学习项目的训练效率和性能。无论是处理简单的数据集还是复杂的数据结构,遵循上述原则和方法,你都能够构建出高效且易于维护的数据管道。
torcy7中的含有两个括号的函数THTenser_(random)(a ,b)是怎么回事
在深入探讨Torcy7中的THTensor_(random)(a, b)函数之前,我们先了解一下Pytorch,一个被广泛应用于人工智能训练的框架,它的起源是C语言编写的torch软件包。在研究torch7源码时,我们发现了一种特殊的函数定义方式,即在函数前加上两个括号。这似乎违背了C语言的基本规则,因为通常来说,函数定义只能包含一个括号。
面对这一疑惑,让我们一起揭开谜底。其实,这里并没有违反C语言的规则,而是巧妙地运用了宏定义(#define)这一C语言特性。在源码中,我们看到对THTensor_(NAME)的宏定义,通过这种方式,THTensor_(random)被解释为一个函数名称。
宏定义后,函数实际上被定义为:
这种定义方式的目的是为了方便开发者批量修改函数名称。传统的修改方式需要逐个修改每个引用点,耗时且容易出错。通过宏定义,开发者只需修改一处定义,就能同时更新所有引用,大大提高了开发效率和代码维护性。
总的来说,THTensor_(random)(a, b)的定义并非违反C语言规则,而是巧妙地利用了宏定义这一特性,为代码的修改和维护带来了极大的便利。这种设计不仅符合C语言的规范,也体现了编程中的灵活性和效率追求。