diff --git a/chapter_data_processing/extension.md b/chapter_data_processing/extension.md index 4ae9574..89d7ac1 100644 --- a/chapter_data_processing/extension.md +++ b/chapter_data_processing/extension.md @@ -76,7 +76,7 @@ outputs = pipe.run() :width:`800px` :label:`distributed_data_preprocess_based_on_3rd_party_software` -该方案虽然再业内被广泛使用,却面临着三个问题: +该方案虽然在业内被广泛使用,却面临着三个问题: - 由于数据处理和数据训练采用不同的框架,使得用户为此常常需要在两个不同的框架中编写不同语言的程序,增加了用户的使用负担。 diff --git a/chapter_data_processing/program_model.md b/chapter_data_processing/program_model.md index f340beb..a528aa7 100644 --- a/chapter_data_processing/program_model.md +++ b/chapter_data_processing/program_model.md @@ -35,7 +35,7 @@ val count = ones.reduce(_+_) 主流机器学习系统中的数据模块同样也采用了类似的编程抽象,如TensorFlow的数据模块tf.data :cite:`murray2021tf`, 以及MindSpore的数据模块MindData等。接下来我们以MindData的接口设计为例子来介绍如何面向机器学习这个场景设计好的编程抽象来帮助用户方便的构建模型训练中多种多样的数据处理流水线。 -MindData是机器学习系统MindSpore的数据模块,主要负责完成机器学习模型训练中的数据预处理任务,MindData的向用户提供的核心编程抽象为基于Dataset(数据集)的变换处理。这里的Dataset是一个数据帧的概念(Data +MindData是机器学习系统MindSpore的数据模块,主要负责完成机器学习模型训练中的数据预处理任务,MindData向用户提供的核心编程抽象为基于Dataset(数据集)的变换处理。这里的Dataset是一个数据帧的概念(Data Frame),即一个Dataset为一个多行多列,且每一列都有列名的关系数据表。 ![MindSpore @@ -56,8 +56,8 @@ Dataset示例](../img/ch07/7.2/dataset_table.png) | prefetch | 从存储介质中预取数据集 | | project | 从Dataset数据表中选择一些列用于接下来的处理 | | zip | 将多个数据集合并为一个数据集 | -| repeat | 多轮次训练中,重复整个数据流水多次。 | -| create_dict_iterator | 对数据集创建一个返回字典类型数据的迭代器。 | +| repeat | 多轮次训练中,重复整个数据流水多次 | +| create_dict_iterator | 对数据集创建一个返回字典类型数据的迭代器 | | ... | ... | 上述描述了数据集的接口抽象,而对数据集的具体操作实际上是由具体的数据算子函数定义。为了方便用户使用,MindData对机器学习领域常见的数据类型及其常见数据处理需求都内置实现了丰富的数据算子库。针对视觉领域,MindData提供了常见的如Decode(解码)、Resize(缩放)、RandomRotation(随机旋转)、Normalize(正规化)以及HWC2CHW(通道转置)等算子;针对文本领域,MindData提供了Ngram、NormalizeUTF8、BertTokenizer等算子;针对语音领域,MindData提供了TimeMasking(时域掩盖)、LowpassBiquad(双二阶滤波器)、ComplexNorm(归一化)等算子;这些常用算子能覆盖用户的绝大部分需求。 @@ -102,7 +102,7 @@ dataset = dataset.map(input_columns="label", operations=onehot_op) 有了基于数据集变换的编程抽象、以及针对机器学习各种数据类型的丰富变换算子支持,我们可以覆盖用户绝大部分的数据处理需求。然而由于机器学习领域本身进展快速,新的数据处理需求不断涌现,可能会有用户想要使用的数据变换算子没有被数据模块覆盖支持到的情况发生。为此我们需要设计良好的用户自定义算子注册机制,使得用户可以方便在构建数据处理流水线时使用自定义的算子。 -机器学习场景中,用户的开发编程语言以Python为主,所以我们可以认为用户的自定义算子更多情况下实际上是一个Python函数或者Python类。数据模块支持自定义算子的难度主要由数据模块对计算的调度实现方式有关系,比如Pytorch的dataloader的计算调度主要在Python层面实现,得益于Python语言的灵活性,在dataloader的数据流水中插入自定义的算子相对来说比较容易;而像TensorFlow的tf.data以及MindSpore的MindData的计算调度主要在C++层面实现,这使得数据模块想要灵活的在数据流中插入用户定义的Python算子变得较为有挑战性。接下来我们以MindData中的算子自定义算子注册使用实现为例子展开讨论这部分内容。 +机器学习场景中,用户的开发编程语言以Python为主,所以我们可以认为用户的自定义算子更多情况下实际上是一个Python函数或者Python类。数据模块支持自定义算子的难度主要与数据模块对计算的调度实现方式有关系,比如Pytorch的dataloader的计算调度主要在Python层面实现,得益于Python语言的灵活性,在dataloader的数据流水中插入自定义的算子相对来说比较容易;而像TensorFlow的tf.data以及MindSpore的MindData的计算调度主要在C++层面实现,这使得数据模块想要灵活的在数据流中插入用户定义的Python算子变得较为有挑战性。接下来我们以MindData中的自定义算子注册使用实现为例子展开讨论这部分内容。 ![MindData的C层算子和Python层算子](../img/ch07/7.2/operation.png) diff --git a/chapter_data_processing/summary.md b/chapter_data_processing/summary.md index 7918724..0a77fff 100644 --- a/chapter_data_processing/summary.md +++ b/chapter_data_processing/summary.md @@ -1,3 +1,3 @@ ## 章节总结 -本章我们围绕着易用性、高效性和保序性三个维度展开研究如何设计实现机器学习系统中的数据预处理模块。在易用性维度我们重点探讨了数据模块的编程模型,通过借鉴历史上优秀的并行数据处理系统的设计经验,我们认为基于描述数据集变换的编程抽象较为适合作为数据模块的编程模型,在具体的系统实现中,我们不仅要在上述的编程模型的基础上提供足够多内置算子方便的用户的数据预处理编程,同时还要考虑如何支持用户方便的使用自定义算子。在高效性方面,我们从数据读取和计算两个方面分别介绍了特殊文件格式设计和计算并行架构设计。我们也使用我们在前几章中学习到的模型计算图编译优化技术来优化用户的数据预处理计算图,以进一步的达到更高的数据处理吞吐率。机器学习场景中模型对数据输入顺序敏感,于是衍生出来保序性这一特殊性质,我们在本章中对此进行了分析并通过MindSpore中的Connector的特殊约束实现来展示真实系统实现中如何确保保序性。最后,我们也针对部分情况下单机CPU数据预处理性能的问题,介绍了当前基于异构处理加速的纵向扩展方案,和基于分布式数据预处理的横向扩展方案,我们相信读者学习了本章后能够对机器学习系统中的数据模块有深刻的认知,也对数据模块未来面临的挑战有所了解。 +本章我们围绕着易用性、高效性和保序性三个维度展开研究如何设计实现机器学习系统中的数据预处理模块。在易用性维度我们重点探讨了数据模块的编程模型,通过借鉴历史上优秀的并行数据处理系统的设计经验,我们认为基于描述数据集变换的编程抽象较为适合作为数据模块的编程模型,在具体的系统实现中,我们不仅要在上述的编程模型的基础上提供足够多内置算子方便用户的数据预处理编程,同时还要考虑如何支持用户方便的使用自定义算子。在高效性方面,我们从数据读取和计算两个方面分别介绍了特殊文件格式设计和计算并行架构设计。我们也使用我们在前几章中学习到的模型计算图编译优化技术来优化用户的数据预处理计算图,以进一步的达到更高的数据处理吞吐率。机器学习场景中模型对数据输入顺序敏感,于是衍生出来保序性这一特殊性质,我们在本章中对此进行了分析并通过MindSpore中的Connector的特殊约束实现来展示真实系统实现中如何确保保序性。最后,我们也针对部分情况下单机CPU数据预处理性能的问题,介绍了当前基于异构处理加速的纵向扩展方案,和基于分布式数据预处理的横向扩展方案,我们相信读者学习了本章后能够对机器学习系统中的数据模块有深刻的认知,也对数据模块未来面临的挑战有所了解。 diff --git a/chapter_distributed_training/overview.md b/chapter_distributed_training/overview.md index a9e294b..e2cce5d 100644 --- a/chapter_distributed_training/overview.md +++ b/chapter_distributed_training/overview.md @@ -44,7 +44,7 @@ Algorithm)的设计思想:由于每个计算节点只需要负责更小的 - **提升系统性能**:使用分布式训练,往往可以带来训练性能的巨大提升。一个分布式训练系统往往用以下这个指标来衡量性能:到达目标精度所需的时间(time-to-accuracy)。这个指标由两个参数决定: 一个数据周期所需的完成时间,以及一个数据周期模型所提升的精度。通过持续增加并行处理节点,我们可以将数据周期的完成时间不断变短,最终显著减少到达目标精度所需的时间。 -- **经济性(Economy)**:使用分布式训练,我们也可以进一步减少训练及其模型所需的成本。受限于单节点散热的上限,单节点的算力越高,起所需的散热硬件成本也更高。因此,在提供同等的算力的条件下,组合多个计算节点是一个更加经济高效的方式。这促使需要云服务商(如亚马逊和微软等)更加注重给用户提供成本高效的分布式机器学习系统。 +- **经济性(Economy)**:使用分布式训练,我们也可以进一步减少训练及其模型所需的成本。受限于单节点散热的上限,单节点的算力越高,其所需的散热硬件成本也更高。因此,在提供同等的算力的条件下,组合多个计算节点是一个更加经济高效的方式。这促使云服务商(如亚马逊和微软等)需要更加注重给用户提供成本高效的分布式机器学习系统。 - **抵御硬件故障**:分布式训练系统同时能有效提升抵御硬件故障的能力。机器学习训练集群往往由商用硬件(Commodity - Hardware)组成,这类硬件(例如说,磁盘和网卡)运行一定周期就会产生故障。而仅使用单个硬件进行训练的话,那么一个硬件的故障就会造成整个训练的任务的失败。通过将这个训练任务又多个硬件共同完成,即使一个硬件故障了,我们也可以通过将这个硬件上相应的计算子任务转移给其余硬件,继续完成训练,从而避免训练任务的失败。 \ No newline at end of file + Hardware)组成,这类硬件(例如说,磁盘和网卡)运行一定周期就会产生故障。而仅使用单个硬件进行训练的话,那么一个硬件的故障就会造成整个训练的任务的失败。通过将这个训练任务由多个硬件共同完成,即使一个硬件故障了,我们也可以通过将这个硬件上相应的计算子任务转移给其余硬件,继续完成训练,从而避免训练任务的失败。 \ No newline at end of file diff --git a/chapter_distributed_training/parameter_servers.md b/chapter_distributed_training/parameter_servers.md index 8e6fddd..f3f7135 100644 --- a/chapter_distributed_training/parameter_servers.md +++ b/chapter_distributed_training/parameter_servers.md @@ -31,7 +31,7 @@ request)将平均梯度发送到参数服务器,参数服务器更新本地 在以上的参数服务器架构中,机器学习集群拥有者可以灵活的根据梯度计算所需要算力配置合理数量的训练服务器。他们也可以根据参数的数量配置大部分的稀疏参数(Sparse parameters)在参数服务器中,仅留下小部分的密集参数(Dense -parameters)在训练服务器中。密集参数和稀疏参数的核心区别是:稀疏参数在每一步训练不一定都会被用到,他们需要根据当前训练小批量来决定。而密集参数每一步训练都需要用到。因此为了频繁从参数服务器中拉取,密集参数往往会存储在训练服务器中。 +parameters)在训练服务器中。密集参数和稀疏参数的核心区别是:稀疏参数在每一步训练不一定都会被用到,他们需要根据当前训练小批量来决定。而密集参数每一步训练都需要用到。因此为了避免频繁从参数服务器中拉取,密集参数往往会存储在训练服务器中。 ### 数据副本 diff --git a/chapter_distributed_training/summary.md b/chapter_distributed_training/summary.md index 3d83295..1ef2913 100644 --- a/chapter_distributed_training/summary.md +++ b/chapter_distributed_training/summary.md @@ -4,7 +4,7 @@ - 分布式训练系统的设计往往遵循"分而治之"的设计思路。 -- 利用分布式训练系统,人们可以显著提升性能性能,经济性,并且帮助抵御硬件故障。 +- 利用分布式训练系统,人们可以显著提升性能,经济性,并且帮助抵御硬件故障。 - 分布式训练系统可以通过数据并行增加设备来提升算力。 diff --git a/chapter_model_deployment/model_converter_and_optimizer.md b/chapter_model_deployment/model_converter_and_optimizer.md index b2be8c9..cd92372 100644 --- a/chapter_model_deployment/model_converter_and_optimizer.md +++ b/chapter_model_deployment/model_converter_and_optimizer.md @@ -4,7 +4,7 @@ 前面我们提到过,不同的训练框架(Tensorflow、PyTorch、MindSpore、MXNet、CNTK等)都定义了自己的模型的数据结构,推理系统需要将它们转换到统一的一种数据结构上。Open Neural Network Exchange(ONNX)正是为此目的而设计的。ONNX支持广泛的机器学习运算符集合,并提供了不同训练框架的转换器,例如TensorFlow模型到ONNX模型的转换器、PyTorch模型到ONNX模型的转换器等。 模型转换本质上是将模型这种结构化的数据,从一种数据结构转换为另一种数据结构的过程。进行模型转换首先要分析两种数据结构的异同点,然后针对结构相同的数据做搬运;对于结构相似的数据做一一映射;对于结构差异较大的数据则需要根据其语义做合理的数据转换;更进一步如果两种数据结构上存在不兼容,则模型转换无法进行。ONNX的一个优势就在于其强大的表达能力,从而大多数业界框架的模型都能够转换到ONNX的模型上来而不存在不兼容的情况. -模型可以抽象为是一种图,从而模型的数据结构可以解构为以下两个要点: +模型可以抽象为一种图,从而模型的数据结构可以解构为以下两个要点: - 模型拓扑表达:从图的角度来说,就是图的边;从模型的角度来说,就是模型中的数据流和控制流等,模型数据流和控制流的定义又可以引申出子图的表达形式、模型输入输出的表达形式、控制流结构的表达形式等。比如Tensorflow1.x中的控制流表达为一种有环图,通过Enter、Exit、Switch、LoopCond、NextIteration等算子来解决成环,而ONNX通过Loop,If等算子来表达控制流,从而避免引入了有环,所以在将Tensorflow1.x的控制流模型转化为ONNX模型时,需要将Tensorflow模型中的控制流图结构融合成ONNX的While或者If算子。 @@ -35,7 +35,7 @@ $$\pmb{Y_{conv}}=\pmb{W_{conv}}*\pmb{X_{conv}}+\pmb{B_{conv}}$$ 这里我们不需要理解公式 :eqref:`ch08-equ-conv_equation`中每个变量的含义,只需要注意到一点,该公式是$\pmb{Y_{conv}}$关于$\pmb{X_{conv}}$的,其他符号均表示常量。 -Batchnorm算子的计算过程如公式 :eqref:`equ:bn-equation`所示。 +Batchnorm算子的计算过程如公式 :eqref:`ch08-equ-bn_equation`所示。 $$\pmb{Y_{bn}}=\gamma\frac{\pmb{X_{bn}}-\mu_{\mathcal{B}}}{\sqrt{{\sigma_{\mathcal{B}}}^{2}+\epsilon}}+\beta$$ :eqlabel:`ch08-equ-bn_equation` @@ -71,7 +71,7 @@ $$\pmb{Y_{bn}}=\pmb{A}*\pmb{X_{conv}}+\pmb{B}$$ :width:`500px` :label:`ch08-fig-bn_replace` -如 :numref:`ch08-fig-bn_replace`,我们以Batchnorm算子替换成Scale算子为例,阐述算子替换的原理。我们直接将Batchnorm的计算公式 :eqref:`ch08-equ-replace_scale`进行分解,并将常量合并简化,Batchnorm的计算公式可以写成: +如 :numref:`ch08-fig-bn_replace`,我们以Batchnorm算子替换成Scale算子为例,阐述算子替换的原理。我们直接将Batchnorm的计算公式 :eqref:`ch08-equ-bn_equation`进行分解,并将常量合并简化,Batchnorm的计算公式可以写成: $$\pmb{Y_{bn}}=scale*\pmb{X_{bn}}+offset$$ :eqlabel:`ch08-equ-replace_scale` diff --git a/chapter_model_deployment/model_inference.md b/chapter_model_deployment/model_inference.md index c96f275..cda73f6 100644 --- a/chapter_model_deployment/model_inference.md +++ b/chapter_model_deployment/model_inference.md @@ -123,17 +123,17 @@ ARMv8系列的CPU上有32个NEON寄存器v0-v31,如 :numref:`ch08-fig-register 卷积计算归根到底是矩阵乘法,两个二维矩阵相乘的时间复杂度是$O(n^3)$。我们可以使用Winograd来降低矩阵乘法的复杂度。 -以一维卷积运算为例,记为F(m,r),其中,m代表输出的个数,r为卷积核的个数。输入为$d=[d_0 \ d_0 \ d_2 \ d_3]$,卷积核为$g=[g_0 \ g_0 \ g_2]^T$,该卷积计算可以写成矩阵形式如公式 :eqref:`ch08-equ-conv_matmul_one_dimension`所示,需要6次乘法和4次加法。 +以一维卷积运算为例,记为F(m,r),其中,m代表输出的个数,r为卷积核的个数。输入为$d=[d_0 \ d_1 \ d_2 \ d_3]$,卷积核为$g=[g_0 \ g_1 \ g_2]^T$,该卷积计算可以写成矩阵形式如公式 :eqref:`ch08-equ-conv_matmul_one_dimension`所示,需要6次乘法和4次加法。 $$F(2, 3)= -\left[ \begin{matrix} d_0 & d_0 & d_2 \\ d_1 & d_2 & d_3 \end{matrix} \right] \left[ \begin{matrix} g_0 \\ g_1 \\ g_2 \end{matrix} \right]= +\left[ \begin{matrix} d_0 & d_1 & d_2 \\ d_1 & d_2 & d_3 \end{matrix} \right] \left[ \begin{matrix} g_0 \\ g_1 \\ g_2 \end{matrix} \right]= \left[ \begin{matrix} y_0 \\ y_1 \end{matrix} \right]$$ :eqlabel:`ch08-equ-conv_matmul_one_dimension` 可以观察到,卷积运算转换为矩阵乘法时输入矩阵中存在着重复元素$d_1$和$d_2$,因此,卷积转换的矩阵乘法相对一般的矩阵乘有了优化空间。可以通过计算中间变量$m_0-m_3$得到矩阵乘的结果,见公式 :eqref:`ch08-equ-conv-2-winograd`: $$F(2, 3)= -\left[ \begin{matrix} d_0 & d_0 & d_2 \\ d_1 & d_2 & d_3 \end{matrix} \right] \left[ \begin{matrix} g_0 \\ g_1 \\ g_2 \end{matrix} \right]= +\left[ \begin{matrix} d_0 & d_1 & d_2 \\ d_1 & d_2 & d_3 \end{matrix} \right] \left[ \begin{matrix} g_0 \\ g_1 \\ g_2 \end{matrix} \right]= \left[ \begin{matrix} m_0+m_1+m_2 \\ m_1-m_2+m_3 \end{matrix} \right]$$ :eqlabel:`ch08-equ-conv-2-winograd` @@ -153,7 +153,7 @@ m_2=(d_1-d_3)*g_2 计算过程写成矩阵形式如公式 :eqref:`ch08-equ-winograd-matrix`所示,其中,⊙为对应位置相乘,A、B、G都是常量矩阵。这里写成矩阵计算是为了表达清晰,实际使用时,按照公式 :eqref:`ch08-equ-winograd-param`手写展开的计算速度更快。 $$\mathbf{Y}=\mathbf{A^T}(\mathbf{G}g)*(\mathbf{B^T}d)$$ -:label:`ch08-equ-winograd-matrix` +:eqlabel:`ch08-equ-winograd-matrix` $$\mathbf{B^T}= \left[ \begin{matrix} 1 & 0 & -1 & 0 \\ 0 & 1 & 1 & 0 \\ 0 & -1 & 1 & 0 \\ 0 & 1 & 0 & -1 \end{matrix} \right]$$