深度学习编译器 学习笔记 0x01

深度学习编译器 学习笔记 0x01


一、简介

本文内容围绕以论文解析的角度,来理解如何利用卷积神经网络来优化加速GPUs.欢迎大佬来喷
论文来源:https://doi.org/10.1145/3337821.3337839

为了实现更短的延迟、减轻网络连接到云的负担以及保护用户隐私等多种原因,现代深度学习应用迫切需要推动在边缘设备上进行模型推理。卷积神经网络(CNN)1是应用最广泛的模型族之一。

考虑到CNN模型的高计算复杂度,在边缘设备的集成GPU上执行它们是有利的,这些GPU是普遍存在的,并且比伴随的CPU具有更大的功率和更好的能量效率。然而,由于其体系结构和编程接口的多样性,在集成GPU上高效地编程是具有挑战性的。本文提出了一种在边缘集成
GPU上执行CNN模型推理的端到端解决方案,该方案使用统一的IR来表示和优化来自多个供应商的集成GPU上的视觉特定算子。
以及利用基于机器学习的调度搜索方案来优化计算密集型运算符(如卷积)。
我们的解决方案甚至为不适合或不方便在GPU上运行的操作员提供了后备机制。

理解:

不同芯片厂家提供的SOC中集成GPU差异很大,没法形成一个通用的方案

1.首先对深度学习模型进行训练
2.利用预先训练的深度学习模型对输入数据进行推理
3.直接在边缘设备上执行模型推理

边缘集成的GPU虽然通常比服务器侧的分立GPU弱得多,
能够提供比伴随的CPU更高的FLOP2。然而,在实践中,由于更容易的可编程性和更灵活的可移植性,边缘的大多数模型推理是在CPU上执行的跨越不同的SOC

理解:
对边缘CPU进行模型推理相对比较灵活容易,但是GPU性能比CPU的要好很多,注意这里加了一个限制,对于同一SOC下的CPU及GPU,并且CPU在温度过高时,会受到功率限制导致的性能降低

与GPU相比,CPU上的执行时间不太稳定。除了可能的功率调节之外,设备的操作系统
通常具有在CPU上定期运行的多个进程(例如守护进程),这不可避免地导致CPU资源争用,并因此导致模型推理持续时间的高变化

理解:守护进程就是后台一直在跑的进程,会有资源争抢的情况出现

但众所周知,这种解决方案不灵活且容易出错,而且需要大量繁琐的工程工作。此外, 这些库主要关注计算密集型运算符(如卷积)

理解: computationally intensive operators 计算密集型运算符
程序系统大部分在做计算、逻辑判断、循环导致cpu占用率很高的情况,称之为计算密集型;频繁网络传输、读取硬盘及其他io设备称之为io密集型

此外, 这些运算符的输入长度可能明显大于 GPU的处理器数量,因此需要同步。同时,将应用程序绑定到第三方库极大地延迟了软件开发周期。例如,当发明新的深度学习 模型并引入新的操作符时,开发人员必须等待供应商库支持将新型部署到生产中的新功能。正如我们将在评估中展示的那样, 许多最先进的CNN模型缺乏来自供应商库的现成的优化实现。
为了克服这些限制,本文提出了一个统一的端到端堆栈来部署 和优化CNN模型,以便在Intel、ARM和NVIDIA的主流集成GPU上进 行高效推理。

理解:
深度学习模型在训练过程中,从输入端(输入数据)到输出端会得到一个预测结果,与真实结果相比较会得到一个误差,这个误差会在模型中的每一层传递(反向传播),每一层的表示都会根据这个误差来做调整,直到模型收敛或达到预期的效果才结束,这是端到端的。
就是developer只用关注输入端和输出端,至于模型内部的实现,不去管。


二、背景

1.集成GPUs

如下内容讨论如何在边缘设备的集成GPU上优化CNN模型推理。主流的集成GPU由英特尔、ARM和NVIDIA生产。这些GPU通过环形互连连接到同一SoC内的其他代理(如CPU内核),并与CPU内核共享 主内存。集成GPU维护多个级别的分层缓存以减少数据延迟,通常包括寄存器文件、L1和L2缓存。有效的计算模式将主要使用存 储在寄存器文件中的数据,并隐藏从更远的存储器检索数据的等待时间。

集成GPU使用其计算单元处理计算,
1.在Intel Graphics中称为execution units(EU),
2.在ARM Mali GPU中称为shader cores(SC),
3.在NVIDIA GPU中称为stream multiprocessors (SM)

每个计算单元协调一定数量的硬件线程,并且每个线程拥有一定数量的寄存器文件。根据集成GPU的生成和级别,计算单元的数量会有所不同。大多数现代集成GPU支持算法ISA中的SIMD指令。一个有效的计算模式应 该保持所有的所有计算单元的可用线程大部分时间都处于忙碌状态,并尽可能利用SIMD指令。

理解:

  1. ISA(instruction set architecture)指令集,
    例如 MOV 传送字或字节.
  2. SIMD:
      (Single Instruction Multiple Data,单指令多数据流)能够复制多个操作数,并把它们打包在大型寄存器的一组指令集,例:3DNow!、SSE。以同步方式,在同一时间内执行同一条指令。

指令集并行性,编译原理 P451每一行对应一个时钟周期
1.获取指令 IF fetch
2.解码 ID decode
3.执行运算 EX execute
4.获取内存 MEM
5.回写结果 WB write-back
可以看到在每个时钟周期只执行了一次获取内存操作,并且只执行了一次运算,所以并不会出现同时读写问题

现代集成GPU是一种大规模并行处理器,支持同时运行数百个硬件调度线程。这些线程被组织成块(OpenCL:Workgroups),
并且硬件将线程块调度到硬件核心(CUDA:流式多处理器, OpenCL:计算单元)上。NVIDIA GPU大约有16个核心,每个核心都包含32宽SIMD处理器(CUDA:CUDA核心,OpenCL:SIMD单元),
以锁步方式运行32个线程。GPU还具有每个线程寄存器、每个块 共享内存(每个工作组本地内存)和所有线程可访问的片外全局
DRAM的内存层次结构。CUDA程序(“内核”)在SIMT(单指令多线程)编程模型下指定每个块的块数和线程数。GPU实现通常在执行期间启动多个内核。

内核本质上是一个函数, 其可以被实例化为多个实例以处理由块索引指定的不同数据。它们通过同时运行许多内核实例来实现并行化,每个内核实例称为一个工作项。在集成 GPU中,工作项对应于SIMD条目,由CUDA核心(CUDA术语)或虚拟线程(Intel Graphics中的OpenCL术语)处理。因此,Warp(CUDA术 语)或硬件线程(Intel Graphics中的OpenCL术语)同时处理多个 工作项,从而实现SIMD矢量化。CUDA和OpenCL的编程模型适用于神 经网络的计算模式。高效的GPU程序应该
1)每个内核有足够的工作来保持所有硬件核心忙碌(负载平衡);
2)努力减少线程发散(当 相邻线程在不同方向上分支时);
3)以访问大连续块中的存储器为目标,以最大化所实现的存储器带宽(合并);
4)最小化CPU和GPU之间通信。设计实现所有这些目标的实现是一项重大挑战。

理解:

  1. 说白了就是一堆小学生去算1+1=?这样的问题。

  2. 这里多看一下关于分支预测的问题,https://zhuanlan.zhihu.com/p/150707004
    对于无序数组统计和有序数组统计,执行起来速度差距有多大?性能差距足足有6倍
    将单个指令切分成多个阶段,大致分为取指(fetch),译码(decode),执行(execute),回写(write-back),一条指令不必等上一条完全执行完成就可以开始执行了,就好比工厂中的流水线pipeline,可以大大提升指令执行的吞吐率。现代CPU实际上不止4个阶段,像intel和arm的处理器基本上都有十多个阶段,流水线吞吐率的优势更加明显。

    分支预测的思路也很简单,既然依赖的数据还没算出来,那我就猜一个结果,然后提前开始执行指令,当然也不是随机猜测,现代CPU的预测思路是看前几次预测的结果,就好比前天下雨、昨天也下雨,那我可以简单粗暴的认为今天也下雨,具体细节见文末参考资料。思路很简单,但效果却出奇的好,从Wikipedia的数据我们可以知道现代CPU的分支预测准确率可以到90%以上。
    既然准确率不是100%,就意味着有失败的时候。如果CPU发现预测错误会把所有预测之后的指令执行结果全部抛弃,然后从预测分支那重新开始执行,相当于很多指令白跑了,预测失败的代价很高,正常一条指令执行需要10-20个指令周期,预测失败的话可能额外多出30-40个指令周期。
    回到我们上面的测试代码,我准备的数据是100w个从0-100w之间的数,然后统计小于50w的数的个数。无序的情况下相当于会有50%的可能性分支预测失败,有序情况下100w次预测只会有一次失败,分支预测失败就是产生性能差距的原因。

    但额外做排序带来的性能损失远超过分支预测失败带来的性能损失 if-else 是先赋值再运算,为了节省时间,分支预测会先猜测运行 if 还是 else 并继续运行 (默认是if),若猜对则因并行运算而节省时间,若猜错则因消除运算而耗费时间。
    三目运算符是先运算再赋值,遇到选择支时停止并行并判断条件。
    通常情况下如果选用if-else一定要把概率大的放前面,如果无法选择出概率则选用三目运算符

  3. 分支延时产生原因《编译原理》P451

  4. 局部性原理 是指处理器在访问某些数据时短时间内存在重复访问,说白了就是想加速大概率事件
    https://www.cnblogs.com/xindoo/p/11303906.html

    • 时间局部性(Temporal locality):
      如果某个信息这次被访问,那它有可能在不久的未来被多次访问。时间局部性是空间局部性访问地址一样时的一种特殊情况。这种情况下,可以把常用的数据加cache来优化访存。
    • 空间局部性(Spatial locality):   如果某个位置的信息被访问,那和它相邻的信息也很有可能被访问到。 这个也很好理解,我们大部分情况下代码都是顺序执行,数据也是顺序访问的。
    • 内存局部性(Memory locality): 访问内存时,大概率会访问连续的块,而不是单一的内存地址,其实就是空间局部性在内存上的体现。目前计算机设计中,都是以块/页为单位管理调度存储,其实就是在利用空间局部性来优化性能。
    • 分支局部性(Branch locality)
      这个又被称为顺序局部性,计算机中大部分指令是顺序执行,顺序执行和非顺序执行的比例大致是5:1,即便有if这种选择分支,其实大多数情况下某个分支都是被大概率选中的,于是就有了CPU的分支预测优化。
    • 等距局部性(Equidistant locality)
      等距局部性是指如果某个位置被访问,那和它相邻等距离的连续地址极有可能会被访问到,它位于空间局部性和分支局部性之间。 举个例子,比如多个相同格式的数据数组,你只取其中每个数据的一部分字段,那么他们可能在内存中地址距离是等距的,这个可以通过简单的线性预测就预测是未来访问的位置。

此外,英特尔还扩展了OpenCL驱动程序,以支持其 硬件平台的一些特殊功能。例如,英特尔扩展OpenCL将同一硬件线程的工作项目组织为一个subgroup,该子组共享硬件线程的相同寄存器文件。这些高性能的库主要针对优化计算密集型的张量算子,如卷积。

2.边缘CNN模型推理

由于两个原因,有效地完成这一任务是具有挑战性的。首 先,CNN模型由大量计算密集的卷积运算组成。为集成GPU完全优化
它们并不是一件小事。一方面,集成GPU的架构在供应商之间差异很 大;也就是说,一个GPU上的优化解决方案可能根本不适用于其他GPU。另一方面,具有不同数据输入形状的卷积可能需要不同的优化 方案。对于所有可能的卷积工作负载,没有灵丹妙药。因此,应根据具体情况对卷积进行优化。 第二,在目标检测和分割模型中有一些特定于视觉的算子,例如
NMS和ROIalign。这些运算符通常不需要密集的计算,但需要GPU本 身不适合处理的控制流逻辑。例如,SSD产生大量预测以实现位置、
比例和纵横比的更多覆盖。然后,NMS操作员扫描这些预测,以删除 指向同一对象的重复项,最后按置信度分数对更新后的预测进行排序。对于这种类型的运营商,通常没有高性能的实现,甚至没有在 集成的GPU(如英特尔图形)上实现。它严重阻碍了有效执行相应模 型的部署。

理解: 各个硬件差异非常大,有的需要的是处理控制流逻辑,对于需要大量调度的地方,GPU的优势并不能被很好的发挥。

3.现有统一优化解决方案

我们的解决方案建立在开源深度学习编译器堆栈TVM之上。它设计用于跨多个硬件平台(包括CPU、GPU和专用加速器)编译和优化深度学习模型推理。具体来说,继承自Halide,TVM利用统一的IR将不同集成GPU的优化方案降低到CUDA或OpenCL,以便在这些设备上生成代码。然而,许多常用的CNN模型,例如SSD和YOLO, 由于缺少一些特定于视觉的操作符而不能完全支持 。即使对于那些算子被TVM覆盖的模型,CNN模型推理在主流集成GPU上的端到端性能通常也不具有吸引力,因为涉及控制逻辑的算子没有针对GPU进行仔细调整,并且计算密集型张量算子的调度方案也没有得到彻底探索。
我们的解决方案以多种方式扩展了原来的TVM。对于需要非直接控制流逻辑的特定于视觉的操作符,我们通过TVM的统一IR在优化方式,充分利用GPU的可用计算资源,适用于不同厂商提供的集成GPU。对于像卷积这样的计算密集型算子,我们求助于基于机器学习的方法来为不同GPU上的不同卷积工作负载自动搜索好的优化方案。此外,为了便于操作员回退到CPU以更轻松地执行,我们启用了对原始TVM堆栈的异构执行。
tvm框架

理解: 这里的工作相当于为国产芯片做一个类似于Cuda的东西。

三、方法论

本节重点介绍我们使用统一优化方法来获得高性能和广泛模型覆盖的方法。我们的优化由两部分组成。首先,我们将讨论如何在集成GPU上优化特定于视觉的操作符,这在现有的工作中并不是微不足道的,但在很大程度上被忽略了。第3.1.1节介绍了GPU优化,第3.1.2节提供了另一种方法,以便在需要时退回对CPU不友好的GPU操作符。 其次,我们提出了在计算密集型张量算子(如卷积)上获得性能增益的见解。第3.2节首先介绍了英特尔图形卷积的优化策略,这在文献中没有得到很好的研究,然后讨论了两种基于机器学习的技术,以探索实现更好性能的优化空间。如第2.3节所述,我们的解决方案基于TVM,并进行了大量改进。 工作管道的概述如上图所示。

理解: 该用GPU的时候用GPU,该用CPU算的时候用CPU算,以及张量算子有性能增益的地方。

1.视觉专用算子

集成GPU的优化。目标检测模型在边缘应用中被广泛使用。
这些模型是从基于CNN的图像分类模型扩展而来的,分类后,视觉特定算子的数量。这些Operator通常用于建议感兴趣的区域并进行排序
相应地。特定于视觉的运算符不需要太多计算,但需要在集成 GPU上优化性能。这是因为这些操作符通常涉及非直接的控制逻辑,
其要求GPU的计算单元内的线程在小心处理的情况下发散。因此, 实际上很难完全在集成GPU上运行对象检测模型,如SSD和YOLO。当前的解决方案最多只能以次优性能运行。本小节描述了我们如何优 化视觉特定操作符,这些操作符本质上导致集成GPU上缺乏对CNN模型的支持
排序,本质上是ArgSort,是CNN模型中常见的运算符, 如SSD。基本上,argsort将排序后的索引号分配给列表中的每一项3。在CUDA中,离散GPU有快速高效的ArgSort实现。 然而,英特尔和ARM的集成GPU的OpenCL版本尚未推出。众所周知,在GPU上实现高性能排序非常困难,因为它需要不符合GPU并行特性的条件分支。例如,在SSD模型中经常使用的NMS操作符包含对 小数据块进行排序的排序操作,其中每个小数据块的输入大小可能不同。换句话说,对于输入数据的每个维度,需要对不同数量 的元素进行排序。如果实施不当,此过程可能会导致分支分歧。 GPU不是被设计为在小的不平衡问题上有效地运行,这些问题对 性能有很大的损害

理解:

argsort
import numpy as np
a = np.array([[8,7],
              [9,1]])
index = np.argsort(a) #默认按行升序,返回排序后的索引值
print(index)    #返回索引值

这里引入了新名词Warp
Warp是SM的基本执行单元。一个warp包含32个并行thread,这32个thread执行于SMIT模式。也就是说所有thread执行同一条指令,并且每个thread会使用各自的data执行该指令。
block可以是一维二维或者三维的,但是,从硬件角度看,所有的thread都被组织成一维,每个thread都有个唯一的ID
一个warp中的线程必然在同一个block中,如果block所含线程数目不是warp大小的整数倍,那么多出的那些thread所在的warp中,会剩余一些inactive的thread,也就是说,即使凑不够warp整数倍的thread,硬件也会为warp凑足,只不过那些thread是inactive状态,需要注意的是,即使这部分thread是inactive的,也会消耗SM资源

分支分歧 branch divergence
CPU有复杂的硬件设计可以很好的做分支预测,即预测应用程序会走哪个path。如果预测正确,那么CPU只会有很小的消耗。和CPU对比来说,GPU就没那么复杂的分支预测了(CPU和GPU这方面的差异的原因不是我们关心的,了解就好,我们关心的是由这差异引起的问题)。
这样我们的问题就来了,因为所有同一个warp中的thread必须执行相同的指令,那么如果这些线程在遇到控制流语句时,如果进入不同的分支,那么同一时刻除了正在执行的分支之外,其余分支都被阻塞了,十分影响性能。这类问题就是warp divergence。

针对这个问题,人们提出了线程交换的优化方法,其主要思想为:将具有相同分支行为(即执行相同分支路径)的线程放在同一个warp中,这样warp中的线程就可以并行执行,从而达到减少Branch Divergence、提高性能的目的。

为了解决这个问题,我们建议将输入数组展平, 每个维度上的细粒度排序问题变成了粗粒度。然而,这也给我们带来了两个挑战:负载平衡和编程简单性。我们使用分段排序来优化 CNN模型的argsort,如图2所示。请注意,尽管我们的实现使用了与之前的分段排序工作和合并排序工作类似的思想,但它并不局限于NVIDIA GPU,而是使用统一程序在具有不同架构的不同集成GPU上高效运行。想法如下。首先,将数据展平到一个数组中,并存储每个段的起始索引。其次,将展平的数组划分为相等的长度,而不是对每个单独的可变长度段进行排序。第三,在每个段上完成块排序以保持它们的顺序。最后,我们执行一系列合并操作。每次迭代使协作块大小加倍,直到所有元素都被排序。例如,在图2中, 黑线和绿线代表不同的线段。线的长度表示线段的大小。红色垂直线表示活动界面。我们将展平的数组分割成大小相等的块。为了便于说明,我们假设总共有五个线程核。每个线程对一个块进行操作。
因此,在每个块内进行局部排序后,我们进行合并排序。在Coop 2 中,两个线程协同工作以合并两个块。在Coop 4中,四个线程一起工作以合并四个块。在Coop 8中,所有五个块由协同工作的所有线程核合并。并且该算法是有效的,因为只有跨越活动修改两个输入列表之间的接口。
分段排序

理解:

  1. 这里首先将数组平展成一维,然后将其分割成一个个的段
  2. 对每个段进行排序
  3. 然后再进行合并排序

可以看一下这篇文章的动画,讲的比较清晰O(n log n)
https://blog.csdn.net/qq_20011607/article/details/82351225?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_paycolumn_v3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1.pc_relevant_paycolumn_v3&utm_relevant_index=2

疑问 : 在Coop 2 中,两个线程协同工作来合并两个块?

前缀和。前缀和(扫描)是CPU上的一种简单的顺序算法。但 是GPU并没有针对顺序计算进行优化,特别是当工作项具有数据依赖性时。文献[13,30,35]中的许多算法都对NVIDIA GPU (CUDA专用)进行了优化扫描,但没有一种算法针对所有集成GPU。基于Hillis和Steele[15]的经典并行扫描,我们提出了一 种面向CUDA和OpenCL集成GPU的高效前缀和

理解:
数据依赖,两个运算写写或者写读同一个变量值

我们的解决方案是以三阶段方式设计的:向上扫描、扫描和向下 扫描。给定n个输入元素,在Hillis和Steele方法的合作扫描中,需要logn个通道来完成扫描。在传递D中,将元素I2 d 添加到元素I。虽 然与CPU顺序算法相比,该算法具有N个log N操作,但它将延迟从N
减少到log N。然而,输入的数量通常比设备中的内核数量大得多。 因此,简单地应用前面提到的方法是低效的,因为它可能触发每一遍的全局同步。我们利用寄存器阻塞来避免全局同步的需要,并减 少数据移动。寄存器分块技术将多个元素分配给一个处理器,这些元素按顺序处理。然后,在没有全局同步的情况下,使用Hillis和 Steele的方法通过并行扫描来处理来自所有核心的归约结果。 图3示出了我们的解决方案的示例。假设阵列中有5个并行处理器和18个元素,我们为每个处理器分配4个元素,最后一个除外。对于上扫描步骤,扫描计算在每个处理器(颜色块)内按顺序进行,并在所有处 理器上并行进行。在获得每个处理器的分段缩减结果(红色粗体数字)后,我们对所有处理器进行并行扫描。对于向下扫描步骤,将 扫描结果并行添加回相应的处理器(数字颜色与处理器块的颜色匹配)。

事实证明,尽管我们已经设法做到了这一点,但在集成GPU上为目 标模型实现所有特定于视觉的操作符仍然非常耗时。实际上,在SoC中处理非GPU友好操作符的常见做法是将这些操作符退回到CPU。下 一节将讨论如何启用此功能。

理解: 其实就是有些操作符用CPU更好,需要回退

2.Fallback

虽然集成GPU已经显示出其在以合理的功率预算产生有竞争力的性能方面的有效性,但在某些情况下,我们可能无
法在集成GPU上完全执行整个模型。首先,GPU的灵活性和可编程 性通常不如其配套的CPU。因此,一些需要密集控制逻辑的视觉
特定操作符(例如排序)本质上在GPU上比在CPU上更难实现。如 第3.1.1节所述,可以花费更多的工程精力为每个GPU供应商编写这些运算符的GPU版本,并为它们调整性能。一个更有效的替代 方案是将这些运算符退回到CPU,以实现更简单、更统一的实现,但让其他计算密集型部件在GPU上运行。由于以下三个原因,这是一个合理的设计。 首先,这类操作符通常不需要太多的计算,因此不需要利用大量的并行性。其次,这些算子通常出现在神经网络的前/后处理部分, 而这并不是性能关键部分,因此在CPU上执行它们并不会带来太大的性能损失。第三,在实践中,由于上述原因,回退所需的数据传输量并不大,并且跨GPU和CPU设备的来回数据移动/复制并不多。此外,共享内存的广泛使用也方便了同一片SoC上GPU和CPU之间的数据传输。 最近的英特尔图形处理器甚至具有专用的共享本地内存,以支持在OpenCL工作组内的内核实例之间共享程序员管理的数据。 似乎需要一种复杂的算法来智能地将正确的算子放在正确的设备上。然而,
事实证明,当我们试图在集成GPU上调度尽可能多的算子时,一个简单的启发式算法对于CNN模型就足够了,只留下几个对CPU不友好的GPU。中使用标准图遍历技术来实现此启发式算法

理解: 其实整篇文章说的几件事

  1. 现在兼容性不好,对于不同的硬件并没有一个统一的解决方案
  2. 有些地方需要用CPU去算,有些地方需要用GPU去算,场景不一样
  3. 对某些已经集成的库的算法做了一定的优化

两遍方式,具有在GPU上执行的已知操作符的列表。在第一遍中,一旦在列表中匹配图形节点的设备属性,我们就将其标记为GPU。否则,其设备属性将标记为CPU。在完成第一遍时,执行第二遍以在分配给不同设备的任何两个直接连接的节点之间插入数据复制算子。
我们的实验结果证实,回退方法只会导致可忽略的性能下降。
例如,完全在AWS DeepLens的集成GPU上运行一个样本的SSD模型推断(由ResNet支持)需要1010.23毫秒,而涉及排序到CPU的NMS操作符需要1015.14毫秒,导致开销小于0.5%。值得注意的是,在实验中,我们 在集成的GPU上运行了整个模型推理。然而,我们的后备机制使新运营商能够尽早采用新模式。

3.计算密集型运算符

英特尔图形优化注意事项。虽然ARM和NVIDIA的集成GPU 最近吸引了许多研究兴趣[1,7,11],但将成功的优化技巧扩展到英特尔图形是一项挑战。据我们所知,几乎没有任何已发表的工作提供有关各种CNN型号的英特尔图形性能优化方法的详细见解。
由于英特尔图形的主内存和末级高速缓存被视为全局内存,而寄存器文件和L1到L3高速缓存是本地的,
因此提高性能的直观解决方案是将尽可能多的数据存储在本地内存中。然而,这种方案通常不会导致预期的结果,因为最接近的存储器,即通用寄存器文件(GRF),比其他存储器起着更关键的作用。如果使用不当,即使数据保存在本地缓存中,表演不会吸引人。
执行单元可以有效地使用GRF来执行SIMD计算。更重要的是,执行单元的SIMD浮点单元能够将多个128位
寄存器组合在一起以形成宽得多的寄存器,从而可以并行计算多达 八个32位浮点值。将工作分配给 OpenCL 内核单元中的 Intel Graphics是一种可行的方法,可以最大限度地利用有效利用寄存器文件的SIMD单元。 如第2.1节所述,对于Intel Graphics,硬件线程由多个虚拟线程 组成,这些虚拟线程构建了一个共享该硬件线程的4KB GRF的子组。
此扩展旨在允许子组中的工作项在不使用本地内存和工作组屏障的 情况下共享数据,并利用专门的硬件来加载和存储数据块。在英特尔OpenCL扩展中,有一些原语,如intel_subroupblock_read、
intel_subroup_block_write和intel_subgroup_shuffle,它们支持在GRF中读取/写入数据,并在硬件线程的工作项目之间进行广播。 多个子组组成一个组,其中使用多个硬件线程同时处理子组。
OpenCL程序通常包含多个组。OpenCL将组和虚拟线程组织在三维空间中。 属于英特尔定义的同一子组的虚拟线程应位于同一组内的同一维度中。

理解:

Single instruction, multiple data (SIMD)是并行计算机的一类(按照Flynn分类法)。它描述了具有多个处理元素(multiple processing elements)的计算机,可以在多个数据点(data points)上同时(simultaneously)完成相同的操作。这种机器利用数据级并行(data level parallelism)(不是并发, concurrency): 在某一时刻,只有一个指令,但有多个同时发生的(simultaneous)计算(computations),也即并行计算。SIMD特别适用于一些常见的任务,例如:调整数字图像的对比度,或者调整数字音频的音量。大部分现代CPU设计都包含了SIMD指令,来提高多媒体使用的性能。不要把SIMD和SIMT搞混了,前者在data level工作,后者要利用线程。

SIMT(Single Instruction Multi Thread)例如在GPU上开32个线程同时做一件事

通过考虑Intel Graphics上OpenCL的上述特性,我们提出了启发式算法来优化这些GPU上的深度学习算子

理解:
启发式算法,在一个合理的求解资源范围内(合理的时间,合理的内存开销等)求得一个较为满意的解。目前主要包括邻域搜索和群体仿生两大类。

4.卷积优化

Conv2D是我们需要关注的最耗时的算子。作为管道的概述,我们编写了一个优化的计划模板(第3.2.2节),然后使用AutoTVM[6]和Graph Tuner[26]来搜索不同 工作负载的最佳计划(第3.2.3节)。我们自适应地调整主模板, 并在有性能改进空间时修改搜索。
如[26]中所述,我们使用空间打包,即循环平铺,以在卷积计算 期间最大限度地重用内存。循环平铺将循环的迭代空间分割为较小的块,以确保循环中使用的数据在重用时保留在缓存中。循环嵌套可以被划分为具有合理平铺大小的多个较小的块。这些分块中的每一个都能够完美地适合高速缓存,以便充分利用空间局部性。对于 Intel Graphics,我们还充分利用了子组的优势,将卷积核安排到子组的GRF中。

未完,后续会做成传送门
待学习知识点:

  • SIMD
  • 指令级并行性
  • 后缀数组 Suffix Array

  1. 卷积神经网络: https://www.zhihu.com/question/52668301 ↩︎

  2. FLOP: 对性能非常重要的指标是计算吞吐量。计算吞吐量的常用度量是 Floating-Point Operations ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值