中文 英语

编译和优化神经网络

低功耗,提高性能的推理。

受欢迎程度

边缘推理引擎通常运行一个精简的实时引擎,该引擎解释神经网络模型,在运行时调用内核。但是,只要用例允许,可以通过预编译模型并直接运行它来获得更高的性能,而不需要解释。

在编译时,优化是可能的,而在解释时是不可用的。通过自动量化、合并节点和内核,以及在可行的情况下将变量绑定为常数,可以实现显著的效率改进。这意味着推理将以更少的能量运行得更快。

“经营神经网络在边缘设备上高效地实现良好的性能有两个重大挑战——处理计算密集型卷积和操纵大量数据,”微软公司机器学习组产品营销总监史蒂夫·斯蒂尔(Steve Steel)说手臂.“为了实现平衡的系统设计,这两个挑战都必须解决。”

一个典型的发动机
系统在边缘拥有数据中心没有的资源限制。虽然电力对所有人工智能推理都很重要,但在边缘领域,尤其是在电池可能驱动设备的地方,它更为重要。内存和计算能力也非常有限,这为提高推理引擎的效率创造了机会。Tensilica AI产品的产品营销总监Suhas Mitra说:“如果你在设计物联网设备或类似性质的东西,网络都是事先知道的。节奏.“如果你的模型是先验的,你就可以提前通过工具链。”

一个典型的配置是让推理引擎运行一个有限版本的机器学习框架,比如TensorFlow Lite。该引擎采用神经网络模型的相对抽象版本,并解释所需的执行,调用执行各种功能的称为“内核”的小型自包含程序。卷积是由核运行的。激活函数可以是另一个内核。运行时解释器在处理网络时调用内核。

但是解释给整个问题增加了一层计算。除了进行实际的推断之外,还需要进行额外的计算,以将模型的高层简化为特定的执行指令。这需要时间和精力。

在开发模型时,解释是有用的,因为它会经常变化。拥有一个可以接受任何模型并运行它的通用引擎可以节省时间和精力。但是一旦进入生产环境,一些人发现将模型编译成本地代码比运行解释器更有好处。

解释对于可能运行在各种平台上的应用程序也有意义。安卓智能手机就是一个例子。虽然它们共享Android的共性,但底层硬件——处理器、加速器、内存和硬件架构的其他关键部分——在每部手机中可能是不同的。

在这种情况下,编译没有意义。您需要为每个手机型号进行编译,这是不切实际的。解释让你了解所有的变体。Cadence公司Tensilica vision和AI DSP IP产品管理组总监Pulin Desai表示:“整个想法是,你应该能够运行任何应用程序,你必须能够运行它。“这就是内部网络API提供的灵活性。”

当工作负载没有被锁定时,解释也可能是必要的。Steel说:“是预编译还是解释机器学习工作负载将取决于用例,一些边缘设备可能两者都做。”“如果预先知道ML工作负载,那么就处理器周期和能源使用而言,预编译和部署二进制文件通常会提供最有效的执行。”

即使有解释,在使用新加载的模型进行第一次推断之前,也可以做一定程度的准备工作。米特拉说:“我可以理解不同的层次和图表是什么,我可以优化它们,但它没有达到预先编译时优化的程度。”这种准备不必为每一个推论重复。“你可以先做第一级的准备,然后再进行多层的执行,以摊销成本。”

编译的明显好处是,现在可以直接用处理器的本机语言执行模型。这消除了开销层并减少了运行的代码量。一个模型可以在几秒钟或几分钟内编译完成,但这不是一个特别耗时的过程。

当然,如果推理引擎不涉及处理器,那么就没有解释选项。的首席执行官Geoff Tate说:“编译或解释的概念只适用于基于处理器的引擎Flex Logix.“使用基于fpga的引擎,每个模型都被编译。它在硬件上运行的事实使它更快、更高效。”

然而,除了消除处理开销之外,编译时优化还可以简化模型并进一步提高性能。“它从你的高级图表开始,”Markus Levy说NXP.“然后它创建了一个低级的中间表示,这或多或少是目标不可知的。所以这是对模型本身的优化。”

Facebook已经通过Glow项目承担了这样的任务,这是一种开源方法,可以“降低”神经网络图的抽象级别。(Glow一词来源于“降低图形”)。包括Cadence和NXP在内的许多公司都与Glow进行了合作,既为开源项目做出了贡献,也为各自的特定平台编写了优化。

图1:神经网络编译过程的说明。来源:节奏

Glow使用模型的静态表示,可以一次性加载。Facebook软件工程经理罗曼·莱文斯坦(Roman Levenstein)说:“它使用一个静态分配的块,在任何给定的时间点上,所有张量都有已知的偏移量。”“这样就可以在运行时节省大量动态内存分配,而且不需要任何操作系统或服务。”

这反映了这样一个事实,即项目最初专注于数据中心计算,在那里可以免费获得大量内存。边缘应用的情况可能不太一样,可能有更多的机会进一步改进。但关键是内存可以静态分配,并且可以以灵活的方式使用。

Cadence指出,内存必须在某些时候分配,但算法可能无法控制这一点。“我们简单地给你一个API,然后说,‘给我一个指向包含输入的blob的指针,’这将是一个摄像头输入或其他东西,”Mitra说。“‘告诉我你想让我把输出放在哪里。然后给我一些舞台或工作空间。’”

使用这些指针,不需要分配任何新的内存。由开发人员来分配这些区域,他们可以静态地或动态地进行分配——例如,使用malloc。

Arm指出,一些应用程序不能依赖于已知的工作负载。“如何分配取决于设备,”斯蒂尔说。“例如,基于Arm Cortex-M CPU并运行简单RTOS的嵌入式设备通常被设计为执行相对简单的固定ML任务,并预先分配内存。而基于Arm Cortex-A CPU并运行平台操作系统(如具有多个并发ML应用程序的Linux)的设备将由操作系统动态分配内存。”

优化模型
神经网络讨论中经常提到两种优化方法。一个是修剪,它描述了寻找权重很小的连接的过程,以至于可以在不显著影响模型精度的情况下删除连接。据说这样的连接在被移除时就被修剪掉了。

图2:剪枝去除对结果影响最小的连接。资料来源:Bryon Moyer/Semiconductor Engineering

第二个常被引用的优化是层融合,其中模型的两个连续层将它们的内核组合在一起,这样看起来在一层中工作的实际上在两层中工作。

图3:层融合可以导致对单个融合层遍历一次而不是两次,每个层一次。资料来源:Bryon Moyer/Semiconductor Engineering

但是还有许多其他的优化机会,无论是在图级还是在较低的指令级。因为运行时解释器必须能够处理抛出给它们的任何网络,所以它们有许多可以设置的参数——比如输入的数量、矩阵的大小和选择的激活。但对于任何给定的静态模型,这些都不是变量。它们是已知的量。通过消除容纳任何神经网络的需要,只关注一个特定的网络,可以获得很高的效率。

高级优化侧重于图。Facebook的研究科学家Jordan Fix说:“我们只是把这张图当作一个节点的海洋,我们要对它进行优化。”低级优化关注低级代码。LLVM是在相对较低的级别上使用代码建立中间表示(IR)的一种方法,同时仍然与硬件无关。

也就是说,一些优化可以利用特定的平台特征——加速器、特殊指令、内存结构——因此,高级和低级优化可以一般地进行,也可以针对特定的后端进行。

高级优化
Glow执行的一项优化是合并共享相同输入的全连接节点。Fix说:“一般来说,合并密集线性代数以使用更大的尺寸通常是有益的,这样我们就可以更好地利用向量化/数据并行执行架构。”

有些平台的矩阵运算符更喜欢特定大小的矩阵,而这可能与正在处理的矩阵的大小不一致。Fix说:“如果32×32乘法能最好地优化或利用它,而且我们有一些非常细的矩阵正在相乘,我们就有可能将矩阵乘法合并在一起,从而更好地利用我们所运行的硬件。”他还指出,如果两个这样的瘦矩阵乘以相同的大矩阵,那么它们可以简单地堆叠瘦矩阵(而不是像下图所示的那样创建“岛屿”)。

图4:如果一个特定的引擎能更有效地处理8 x 8的矩阵,那么更小的矩阵可以连接成一个8 x 8的矩阵,一次乘法可以取代两次乘法。资料来源:Bryon Moyer/Semiconductor Engineering

量化是任何模型的重要步骤。Levy说:“一旦你量化了(浮点数到整数),你的内存容量马上就减少了4倍。”量化可以通过在模型的抽象版本上运行工作负载并创建各种值的直方图来自动实现。如果直接使用,则最小值和最大值将建立动态范围。对于给定的位量化,较宽的动态范围比较窄的动态范围所提供的内部分辨率要低。如果外边缘反映了异常值,那么用户可以选择缩小范围以提供更好的分辨率。

但这种量化可以与其他函数相互作用,可能会使它们变得多余。Fix说:“一些模型/操作符在内部使用裁剪作为操作符的一部分。”“在Glow中,如果后端没有Logit的专门实现,我们将把它‘降低’为一系列更简单的节点,在这种情况下,我们创建一个Clip节点作为降低过程的一部分。得益于此,我们能够应用一般查找Clip节点的优化。例如,如果Logit之前的节点是ReLU,那么我们可以通过合并ReLU和Clip来优化它。”

类似地,如果量化是以一种只包含正数的方式完成的,那么剪辑和ReLU函数也是多余的。Fix说:“假设你有一个卷积,其中可能的值是-5到5,然后你在图中有一个ReLU。”“我们可以删除ReLU并将卷积更改为最小范围为零,因为无论如何我们最终都会切断所有这些负值。因此,我们对手术产生的量化值有了更好的分辨率,而且我们也不需要进行ReLU。”

也有矩阵需要转置的情况。Fix说:“如果我们在图中看到一个权重被转置,那么我们可以在编译时转置它,而不必在运行时这样做。”这些将是编译时已知的常数矩阵。

“还有另一种相关的优化,其中图可能包含应用于给定张量的转置操作链,而不需要是常数,”Levenstein指出。这种转置链可能是其他图变换的结果。这种转置操作链的净效果通常可以表示为一个转置操作。”

这些高级优化中的一些(虽然不是全部)可能会影响模型的准确性。例如,量化和剪枝明确地从图中删除信息,假设所删除的信息对推断结果的影响最小。但事实可能并非如此。

为了确保这一点,必须在运行模型时进行选定的优化,以确保精度足够高。Cadence公司的Mitra说:“我们设置了不同的旋钮,并努力确保精度损失不会超过1%。”“工具链会进行这种搜索,并在运行时为你提供数字,告诉你这些优化的精度数字是多少。”这可能涉及使用不同优化的实验设计,以找到在执行时间、功耗和占用空间方面提供最佳节省的组合,同时保持所需的精度水平。

或者,再培训可能是必要的。提前知道模型将如何量化有助于避免再训练,因为最初的训练可以在量化的前提下完成。Levy说:“我们一直试图建议客户进行量化意识培训,因为这有助于保持准确性。”

低级优化
在较低层次上工作可以带来更具体的现实世界概念。Levenstein说:“当我们把这个高级图形降低到指令流中时,我们就引入了内存的概念。”“对于每个输入,或者对于图中的每个唯一张量,我们创建一个内存缓冲区和指令。他们现在的手术有副作用。”

一个可用的选项是缓冲区共享。张量缓冲区是预先分配的,但张量的生命期对这些缓冲区的使用方式有影响。“如果我们看到两个张量的生存期不重叠,我们就可以为它们重用相同的内存块,”Levenstein说。在这种方式下,缓冲区并不需要在处理过程中存在的每个张量——只需要同时活动的数量。

也可以通过操纵张量的生存期来增加缓冲区共享的机会。如果一个特定的张量被早使用和晚使用,那么在使用之间,缓冲区仍然被占用和空闲。“我们不希望这个张量存活这么长时间,这样我们就可以尽快重用它的内存,”Levenstein说。“如果我们能证明它们不会改变整体结果的语义,我们可能会移动一些计算。”

现在有一个例外——所有的权重都被静态地分配为常数张量。如果模型将用于多个连续的推断,这是一种有效的方法,只要有足够的可用内存。这意味着这些权重永远不需要从存储中获取。它们总是在适当的位置。

但如果内存供应不足,则有机会重新使用重量存储。随着处理在网络的各个层中移动,与第一层相关的权重将不再相关,并且它们的存储可以被后续层重用。如果小心操作,这些缓冲区可以在使用之前急切地重新加载,至少隐藏了获取新权重时的一些延迟。Levenstein说:“我们目前使用的模型在一开始就得到了所有的权重,但是如果需要的话(重用模型缓冲区)可以很容易地完成,所以这不是架构上的限制。”然而,这涉及到更多的数据移动,这可以增加功率。

另一种技术被称为“内核堆叠”。如果几个内核连续操作,就有机会产生显著的效率,因为这些是张量操作——通常是嵌套循环。“如果你有一系列与元素相关的操作,比如乘法和加法,然后可能是一个ReLU,那么,通常,所有这些操作都有独立的内核,你可以按顺序调用它们,”Levenstein说。“这是可行的。问题是,就性能而言,它并不理想。如果你有大张量,它会浪费你的缓存,因为你会多次迭代每个张量,所以我们做循环融合。”

而不是做多个循环,每个循环有一个操作,这些循环可以组合成一个循环,其中所有的核函数在张量的每个项上连续执行。所有数据仍然在缓存中。当循环移动到另一个条目时,前面的条目完成,不再需要它们的缓存值。

图5:同一个张量上的单独循环可能需要在每个循环上重新加载缓存。通过将它们组合到一个循环中,对于给定的迭代,所有数据都保持在本地。资料来源:Bryon Moyer/Semiconductor Engineering

这也是张量的已知维数有很大价值的地方。“我们静态地知道[张量]的形状,”Levenstein说。对于必须处理抛出给它的任何张量的通用引擎,循环和其他操作的边界仍然作为参数,并且有代码来验证操作期间发生的值。“在卷积核内部,你有六到七个实现卷积的循环。他们有边界检查之类的东西,”他说。

但是,如果张量的大小事先已知,对于特定的模型来说是这样,那么这些变量就可以用常数代替,验证和其他相关代码就可以省去。“如果你将精确的边界替换为常数,那么LLVM在向量化这些内核方面做得相当不错。”

图6:通过用常数限制替换可变循环限制,验证和可能的其他代码可以被删除。资料来源:Bryon Moyer/Semiconductor Engineering

节省代码的另一个来源涉及硬件平台构建者提供的库。这些库提供了对针对该平台优化的函数的访问。但并不是每个模型都使用库中的每个函数。通常只使用一个小子集。然而,整个库通常会链接到项目中,包括未使用的部分。

“Resnet作为著名的计算机视觉网络的一个例子,主要使用卷积、智能移动和其他一些操作,”Levenstein说。“但我们(在库中)也有40或50个可用的内核用于其他操作。现在,因为我们将它们放在同一个LLVM IR模块中,它将看到它们中的大多数都不涉及,并且它只会剥离它们的代码,“在不影响操作的情况下缩小代码库的大小。

还可以执行其他经典的软件优化技术,如死代码和公共子表达式消除,以清理算法。除了Glow提供的,Cadence还提供了大量这样的代码级优化。

使其与平台相关
通过在LLVM级别执行这些操作,可以在一般情况下完成这些操作,同时可能针对平台特征(如最佳矩阵乘法大小)进行调优。此时,只要目标硬件支持LLVM, IR就可以一直编译到机器代码。事实上,“我们可以将网络编译成一个独立的目标文件,”Levenstein指出。

但是不同的体系结构也可能有它们自己可以使用的特定技巧和优化。如何进行这些操作可能会影响其他优化的顺序。因此,Glow是足够模块化的,为特定硬件编写后端时,可以添加、删除甚至重新排序默认的优化,让平台制造商有机会将他们的硬件发挥到最佳效果。Levenstein说:“有一个默认的顺序,可以进行正常的Glow流程,但后端可以自由地重新定义它。”

Mitra表示了认同。“人们建立自己的工具链,将模型分解成更小的部分,”他说。“然后他们进行图形优化,然后他们找出后端是什么,然后为后端进行优化。”他提到修剪是一个例子。“我们为自己的硬件做修剪逻辑,因为我们知道什么是最适合的。所以我们不需要像现在这样使用它们的剪枝。”

一组结果
恩智浦与Glow合作,针对微控制器进行优化。该公司使用TensorFlow Lite和Glow收集了一些数据,并在优化了他们的mcu后端后,将开箱即用的Glow与Glow进行了比较。最后,NXP比较了不同的mcu -一些仅使用CMSIS-NN库,另一些使用Tensilica HIFi4 DSP。

编译后的模型使用了优化的CMSIS-NN库,运行速度比TensorFlow Lite实现快3.1倍。对于编译版本,NXP使用CMSIS-NN库优化的Glow实现的运行速度约为使用未优化的Glow的两倍。而使用HiFi4 DSP则比TensorFlow Lite提高了15倍。

图7:使用Glow编译后的性能改进。来源:NXP

至于功率,列维指出,它对功率的影响不太大,但确实会影响能量。“更快地完成推理就等于节省能源。更少的内存传输等于节省能源,”他说。

Glow绝不是唯一可用的编译选项。还有Apache TVM、TensorFlow mlr和微软的ELL。NXP选择Glow是因为它更成熟,也更灵活。但在所有这些方面的持续发展可能会使编译的神经网络模型更容易访问,从而实现更好的边缘推断。



留下回复


(注:此名称将公开显示)

Baidu