模型转换涉及对模型的结构和参数进行重新表示。在进行模型转换时,通常需要理解模型的计算图结构,并根据目标格式的要求对其进行调整和转换,可能包括添加、删除或修改节点、边等操作,以确保转换后的计算图能够正确地表示模型的计算流程。
本文主要介绍自定义计算图的方法以及模型转换的流程和细节。
转换模块架构分为转换模块和图优化,中间的 IR(Intermediate Representation)用来承载,把不同的 AI 框架对接到同一个 IR。
有了 IR,我们就可以很方便地做各种图优化工作,图优化是对计算图进行优化,以提高模型的计算效率和性能。通过对计算图进行算子融合、算子替换等各种优化技术的应用,可以减少冗余计算、提高并行性、减少内存占用等,从而加速训练和推理过程。
无论是 AI 框架还是推理引擎,其计算图都是由基本数据结构张量(Tensor)和基本运算单元算子构成的。
基本数据结构张量(Tensor): 在机器学习领域内将多维数据称为张量,使用秩来表示张量的轴数或维度。标量为零秩张量,包含单个数值,没有轴;向量为一秩张量,拥有一个轴;拥有 RGB 三个通道的彩色图像即为三秩张量,包含三个轴。Tensor 中的元素类型可以为:int, float, string 等。下图所示的 Tensor 形状(Shape)为3, 2, 5。
基本运算单元算子(Operator): 算子是构成神经网络的基本计算单元,对张量数据进行加工处理,实现了多种机器学习中常用的计算逻辑,包括数据转换、条件控制、数学运算等。
算子通常由最基本的代数算子组成,并根据深度学习结构组合形成复杂算子。常见的算子包括数学运算(如加法、乘法)、数据变换(如转置、reshape)、条件控制(如 if-else)等。下图是一些常见的算子:
N 个输入张量经过算子的计算产生 M 个输出张量。举例来说,一个基本的加法算子可以接受两个输入张量,并将它们按元素进行相加,生成一个输出张量。而一个更复杂的卷积算子可能包含多个输入张量(如输入数据和卷积核),并输出一个张量,通过卷积运算实现特征提取。
复杂的神经网络结构通常由多个算子组合而成,每个算子在计算图中都执行特定的操作,并将结果传递给下一个算子。
AI 框架: 如 TensorFlow、PyTorch 等,是开发和训练机器学习模型的软件环境。这些框架提供了一套丰富的工具和库,使得研究人员和开发人员能够更加便捷地构建、训练和部署模型。
通过这些框架,用户可以定义复杂的神经网络结构、实现高效的数值计算,并进行自动化的梯度计算和优化。除此之外,AI 框架还提供了模型的可视化工具、数据预处理工具、以及各种预训练模型和组件,使得构建和调试神经网络模型的过程更加高效和便捷。
计算图: 神经网络模型的一种表达方式。现代机器学习模型的拓扑结构日益复杂,需要机器学习框架对模型算子的执行依赖关系、梯度计算以及训练参数进行快速高效的分析,便于优化模型结构、制定调度执行策略以及实现自动化梯度计算,从而提高机器学习框架训练复杂模型的效率。
为了兼顾编程的灵活性和计算的高效性,设计了基于计算图的机器学习框架。计算图明确了各个算子之间的依赖关系,使得模型的计算过程能够被清晰地描述和理解。在 AI 框架中,计算图被用来表示模型的前向传播过程,即输入数据经过各种操作和层次的处理,最终生成输出结果。通过计算图,框架可以对模型的结构进行各种优化,例如算子融合、常量折叠和内存优化等,从而提高模型的执行效率。
在实际应用中,计算图可以是静态的(如 TensorFlow 的静态计算图),也可以是动态的(如 PyTorch 的动态图)。静态计算图在构建时就已经确定了计算过程,适合于生产环境中的高效执行;动态计算图则在运行时构建,提供了更大的灵活性和易用性。
先来回顾一下推理引擎的概念:
推理引擎是用于执行模型推理任务的软件组件或系统。一旦模型训练完成,推理引擎被用来部署模型并在真实环境中进行推理,即根据输入数据生成预测结果。推理引擎通常会优化推理过程,以提高推理速度和效率。
推理引擎本身也可以认为是一个基础软件,它提供了一组 API 用于在特定平台(如 CPU、GPU 和 VPU)上进行推理任务。(注:执行推理任务时模型已稳定无需训练,服务于真实数据进行推理预测。)
英特尔的 OpenVINO 这样定义推理引擎:
(OpenVINO)推理引擎是一组 C++库,提供通用 API,可在您选择的平台(CPU、GPU 或 VPU)上提供推理解决方案。使用推理引擎 API 读取中间表示(IR)、设置输入和输出格式并在设备上执行模型。虽然 C++库是主要实现,但 C 库和 Python bindings(通过 Python 调用 C/C++ 库)也可用。
计算图是实现高效推理和跨平台部署的关键。计算图的标准化表示使得推理引擎能够在不同硬件平台上进行高效部署。在推理过程中,模型通常会被转换为一种中间表示(IR)。这种表示形式能够抽象出模型的计算过程,使得模型能够在不同硬件平台上高效执行。推理引擎通过分析计算图,能够识别和优化常见的算子模式。例如,连续的算子可以进行融合,减少计算开销和内存访问次数,从而提高推理速度。
推理引擎还可以通过计算图精确管理内存的分配和使用,减少内存碎片和重复数据拷贝。同时,计算图明确了各个算子的执行顺序和依赖关系,推理引擎可以据此进行高效的任务调度,最大化利用多核处理器和并行计算资源。
AI 框架计算图与推理引擎计算图在多个方面存在差异:
通常神经网络都可以看成一个计算图,而推理可以理解成数据从计算图起点到终点的过程。为了在推理引擎中自定义一个高效的计算图,可以通过 Protobuf 或者 FlatBuffers 定义计算图的整体流程:
Tensor 数据存储格式: 定义了一个名为 DataType 的枚举类型,它包含了几种常见的数据类型,如浮点型(float)、双精度浮点型(double)、32 位整型(int32)、8 位无符号整型(uint8)等。每个数据类型都有一个与之对应的整数值来表示,例如 DT_FLOAT 对应整数值 1,DT_DOUBLE 对应整数值 2,以此类推。
代码语言:txt
// 定义 Tensor 的数据类型
enum DataType : int {
DT_INVALID = 0,
DT_FLOAT = 1,
DT_DOUBLE = 2,
DT_INT32 = 3,
DT_UINT8 = 4,
DT_INT16 = 5,
DT_INT8 = 6,
// ...
}
Tensor 数据内存排布格式: 即张量在内存中的存储顺序,不同的框架和算法可能使用不同的数据排布格式来表示张量数据。
代码语言:txt
// 定义 Tensor 数据排布格式
enum DATA_FORMAT : byte {
ND,
NCHW,
NHWC,
NC4HW4,
NC1HWC0,
UNKNOWN,
// ...
}
Tensor 张量的定义: 定义了一个名为 Tensor 的数据结构,用于表示张量(Tensor)的一些属性,如形状(shape)、数据排布格式(dataFormat)和数据类型(dataType)等。
代码语言:txt
// 定义 Tensor
table Tensor {
// shape
dims: [int];
dataFormat: DATA_FORMAT;
// data type
dataType: DataType = DT_FLOAT;
// extra
// ...
}
算子的定义与张量不同,因为要对接到不同的 AI 框架,同一个算子在不同 AI 框架里的定义可能不同。所以在推理引擎中,对每一个算子都要有独立的定义。
算子列表: 算子是构成神经网络计算图的基本单元,每个算子代表了一种特定的计算操作,如卷积、池化、全连接等。算子数量建议控制在 200-300 个之间,基本上能够覆盖到 95%的场景。Pytorch 中有 1200 多个算子,TensorFlow 中有 1500 多个算子,但推理引擎有时可能不需要这么多算子。每个算子实现时,可能有好几个 kernel,这会影响推理引擎的大小。
代码语言:txt
// 算子列表
enum OpType : int {
Const,
Concat,
Convolution,
ConvolutionDepthwise,
Deconvolution,
DeconvolutionDepthwise,
MatMul,
// ...
}
算子公共属性和特殊算子列表: 不同的算子可能需要不同的属性和参数来进行操作,通过使用联合体的方式,可以在统一的数据结构中存储这些信息,并根据具体的算子类型来选择使用合适的成员。
代码语言:txt
// 算子公共属性和参数
union OpParameter {
Axis,
shape,
Size,
WhileParam,
IfParam,
LoopParam,
// ...
}
算子的基础定义: 每个 Op 对象包含了算子的输入输出索引、类型、参数和名称等信息,通过这些信息可以清晰地表示和描述算子的功能和作用。
代码语言:txt
// 算子基本定义
table Op {
inputIndexes: [int];
outputIndexes: [int];
main: OpParameter;
type: OpType;
name: string;
// ...
}
定义网络模型: 表示网络模型的定义,存储网络模型的以下信息:
代码语言:txt
// 网络模型定义
table Net {
name: string;
inputName: [string];
outputName: [string];
oplists: [Op];
sourceType: NetSource;
// Subgraphs of the Net.
subgraphs: [SubGraph];
// ...
}
对于一些分类的网络,可能没有子图。但在具体实现过程中,遇到 if-else、while 或者 for 等语句时,就会拆分成子图。
定义网络模型子图: 表示子图的概念,并存储子图的输入输出信息、张量名称和节点信息。子图的定义与上面的网络模型定义是相似的,但子图的信息相较于整个图更少。
代码语言:txt
// 子图概念的定义
table SubGraph {
// Subgraph unique name.
name: string;
inputs: [int];
outputs: [int];
// All tensor names.
tensors: [string];
// Nodes of the subgraph.
nodes: [Op];
}
下面使用 FlatBuffers 定义一个简单的神经网络结构,其中包含了卷积层和池化层操作:
代码语言:txt
namespace MyNet;
table Pool {
padX:int;
padY:int;
// ...
}
table Conv {
kernelX:int = 1;
kernelY:int = 1;
// ...
}
union OpParameter {
Conv,
Pool,
}
enum OpType : int {
Conv,
Pool,
}
table Op {
type: OpType;
parameter: OpParameter;
name: string;
inputIndexes: [int];
outputIndexes: [int];
}
table Net {
oplists: [Op];
tensorName: [string];
}
root_type Net;
声明了一个名为MyNet
的命名空间,用于组织以下定义的数据结构:
table Pool { ... }
:定义了一个名为 Pool
的表,表中包含了 padX 和 padY 两个整数字段,用于表示池化层的填充参数。table Conv { ... }
:定义了一个名为 Conv
的表,表中包含了 kernelX 和 kernelY 两个整数字段,用于表示卷积层的卷积核尺寸。union OpParameter { ... }
:定义了一个联合体(union),包含了 Conv 和 Pool 两种类型。enum OpType : int { ... }
定义了一个枚举类型 OpType
,包含了 Conv 和 Pool 两种算子类型。table Op { ... }
是算子的基础定义,包含了 type
字段,用于表示算子类型(属于 Conv 还是 Pool),parameter
字段,用于表示算子的参数,name
字段表示算子的名称,inputIndexes
和 outputIndexes
分别表示算子的输入和输出索引。table Net { ... }
定义了网络模型,包含 oplits
字段,表示网络中的算子列表,tensorName
字段表示张量的名称。root_type Net
将 Net 表声明为根类型,表示 FlatBuffers 序列化和反序列化时的入口点。本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。