本文首发自 HyperAI超神经微信公众号~

咱们好我是来自希姆核算的淡孝强,今日我将和三位搭档一起来给咱们共享怎么在 TVM 上支撑 NPU。

DSA 编译器处理的实质问题便是不同的模型需求布置到硬件上,利用各种笼统层级的优化手段,使得模型尽量打满芯片,也便是要紧缩气泡。关于怎么去调度,Halide 描绘的调度三角形是这个问题的实质。

DSA 编译器要处理的首要问题是什么?首要咱们笼统一个 DSA 的架构,如图所示,habana、Ascend 以及 IPU 上都是这个笼统架构的实例化。一般核算核里每个核有向量、标量以及张量的核算单元。从指令的操作和数据粒度来看,不少 DSA 或许倾向于运用相对粗粒度的指令,例如二维三维的向量和张量的指令,也有不少硬件运用细粒度的指令,例如一维的 SIMD 和 VLIW。指令间的依赖,有一些是经过显式的接口露出让软件来操控,有的是硬件自己操控。内存是多级内存,大多是 scratchpad memory。并行有各种粒度和维度的并行,比方 stream 并行、cluster 并行、多核并行以及不同核算部件之间的流水并行。

希姆计算:基于 TVM 的 DSA AI 编译器构建
要支撑这样一类架构,从编译器开发者的视点看,是从上述体系结构几个方面对 AI 编译器提出不同的需求,这部分后边咱们会展开。

从用户的视点看,首要要有一个稳定和泛化的编译器,尽量多的模型或许算子都能够编译成功,别的,用户希望编译器能够供给一个可编程的界面来进行算法和算子的自定义,以确保能够独立展开一些要害算法的立异作业。最终,关于相似咱们这样的团队或许友商也会重视:怎么用 TVM 构建 AI 编译器,比方怎样办理自研和开源的 TVM 代码,怎么建立一套高效 CI 等等。这便是咱们今日要共享的内容,下面由我的搭档来讲编译优化的部分。

希姆核算王成科:DSA 的编译优化流程

本部分为希姆核算工程师王成科现场共享。

首要介绍一下希姆编译实践的全体流程概况。

针对方才说到的架构特性,咱们依据 TVM 数据结构构建了自研的优化 pass 再加上对 TVM 的复用,组成了一个新模式完成:tensorturbo。

希姆计算:基于 TVM 的 DSA AI 编译器构建

咱们看到一个比较经典的 DSA 架构,一般会供给一些高效、定制矩阵以及向量层的多核核算中心,拥有一个与之相配合的多层缓存机制,一起也会供给可并行运转的多模组履行单元。相应的咱们需求处理以下问题:

  • 对数据核算进行切分、高效绑核以及定制指令的高效向量化;

  • 精密办理有限的片上缓存,在不同的缓存等级上做相应的数据预取;

  • 优化多模组履行的多级流水线,力求得到一个比较好的加快比。

    希姆计算:基于 TVM 的 DSA AI 编译器构建

这儿赤色部分(上图)显现的是整个流程里对 TVM 复用比较高的部分,在 relay 上完成的图层相关比较通用的优化能够直接复用,别的复用程度比较高的是依据 TensorIR 和 custom LLIR 的算子完成部分。像咱们方才说到的跟硬件特性相关的定制优化,则需求更多自研作业。

首要咱们来看在图层上自研的一项作业。

希姆计算:基于 TVM 的 DSA AI 编译器构建
重视最左面这张比较典型的核算流图,能够看到从上到下,全体对缓存的占用及对核算的占用都在不断削减,出现倒金字塔的状况。关于前半部分,模型规划较大时,咱们需求着重处理片上缓存驻留的问题;然后半部分,在模型规划比较小的时分,需求处理核算单元利用率较低的问题。假如简略调整模型规划,比方调整 batch size ,较小的 batch size 能够得到较低的 latency ,而相应的 throughput 会有所下降;同样较大的 batch size 会导致 latency 较高,可是有或许进步全体 throughput。

那么咱们就能够用图调度来处理这个问题。首要,允许一个比较大的 batch size 输入,确保全程对核算的利用率比较高,然后对整图做一个存储剖析,加上切分和调度战略,使得模型的前半部分成果能够更好地缓存在片上,一起完成核算中心利用率较高的成果。实践来看全体能够完成 latency 和 throughput 都表现较好成果(具体能够重视 OSDI 23 希姆文章:Effectively Scheduling Computational Graphs of Deep Neural Networks toward Their Domain-Specific Accelerators,6 月份可获取链接检查)。

下面介绍别的一个软流水的加快作业。

希姆计算:基于 TVM 的 DSA AI 编译器构建
重视右上图,完成了一个比较 native 的四级流水线,但显着不是一个高效的流水线。一般高效的流水线,应该是经过几次迭代后,四个履行单元都能够同步并行起来,那么这需求做一些作业,包含 L1 及 L0 上的切分、L1 上跨层的数据预取以及 L0 层级上的 double buffer 操作。经过这些作业咱们能够完成像右下图所展现的,加快比较高的流水线。

由此,也会引入一个新的问题,比方当多个履行单元对缓存的一起读写并发数要高于其时缓存可支撑的并发数时,就会产生竞争,这个问题会导致访存功率成倍下降,也便是 Bank Conflict 问题。对此,咱们会在编译时静态地对流水线进行模拟,提取抵触目标,结合 cost model 对分配地址进行交换和平移,能够极大地下降该问题的影响。

希姆计算:基于 TVM 的 DSA AI 编译器构建
有了各种 pass 之后,能够以一个简略的 Top-Down 方法把它们组合起来,沿着左图中黑色流程,就得到了一个功能上可行的编译 pipeline。可是实践中发现许多问题,包含思远说到的 pass 与 pass 之间的相互影响、短少交互逻辑,图层与算子之间短少交流逻辑等。能够看到左图中赤色部分指示的流程,实践中发现每个途径或许它们的组合都会导致编译失利。怎么让其鲁棒性更强?希姆在每个或许失利的 pass 中供给一个反馈途径,在图层和算子之间引入了交互逻辑,进行预剖析、 prelower 操作,一起在要点部分引入一些迭代调优机制,终究得到一个泛化性较高且调优能力比较强的全体 pipeline 完成。

咱们也留意到,上述作业中对数据结构的改造以及相关规划思想与现在 TVM Unity 规划有较多相似之处,咱们也等待 Relax 能够带来更多或许性。

这儿展现的是希姆在编译流程中愈加细节的 pass,从左到右便是逐层递减的进程,其间赤色部分是对 TVM 复用比较高的,越挨近硬件特性部分会有更多的定制 pass。

下面持续对其间的部分模块进行具体介绍。

希姆计算:基于 TVM 的 DSA AI 编译器构建

希姆核算刘飞:DSA 的向量化和张量化

本部分为希姆核算工程师刘飞现场共享。

这个章节将展开介绍希姆向量化和张量化作业。从指令粒度考虑,指令粒度越粗,越挨近 Tensor IR 的多层 loop 表达,所以向量化张量化难度越小,相反,指令粒度越细,难度也就越大,咱们的 NPU 指令,支撑一维/二维/三维的 tensor 数据核算。希姆也考虑过原生 TVM tensorize 进程,但考虑到 Compute Tensorize 对杂乱表达能力有限,例如对 if condition 这种杂乱表达式做 Tensorized 就比较困难,并且做 Tensorized 向量化后,无法 schedule。

别的其时 TensorIR Tensorize 在开发当中,不能满足开发需求,所以希姆供给了自己的一套指令向量化流程,咱们称之为指令发射。这套流程下咱们支撑了大约 120 条 Tensor 指令,包含各种维度的指令等。

咱们的指令流程大约分为三个模块:

  • 发射前的优化处理。对循环轴的改换,为指令发射供给更多的发射条件和或许;

  • 指令发射模块。剖析循环的成果和信息,挑选一个最优的指令生成方法;

  • 指令发射后的模块。对指定发射处理失利之后处理,确保在 CPU 上正确履行。

    希姆计算:基于 TVM 的 DSA AI 编译器构建
    下面是指令发射前的优化和处理模块,都是由一组优化 pass 组成,其间 IfPromotion 是把阻碍循环轴发射的 if 句子尽量外提,PreProcess 是把没有对应指令的 operator 做拆分处理,LoopShift 是对循环轴边界为归一化,LoopCallapse 是对接连的循环轴作尽或许的合并,LoopPartition 是做 if 相关的循环轴拆分,还有 LoopFission 是对循环内多个 store 句子的分裂。

从这个比如能够看到,起先 IR 是不能发射任何指令的,经过优化后,最终能够发射两条 Tensor 指令且一切的循环轴都能够发射指令。

希姆计算:基于 TVM 的 DSA AI 编译器构建
再便是指令发射模块。首要,指令发射模块会循环剖析循环中的结构,从中获取 Optype、dtype、bufferAcess 等信息,有这些信息之后,指令辨认会辨认出来循环轴或许会发射哪几种指令。由于一种 IR 结构或许对应多种 NPU 指令,所以咱们会把一切或许发射的指令都辨认出来,由 VectorEngine 查找引擎去依据指令的 alignment、reshape 等一系列信息去查找每种指令发射的或许性,最终再由 CostModel 做核算,找到最优发射方法进行发射。
希姆计算:基于 TVM 的 DSA AI 编译器构建
最终便是指令发射后处理模块。首要是对指令发射失利的 tir 做处理,确保其能在 CPU 上正确运转。还有一些特别指令,希姆需求在算法前端打一些符号,指令发射模块经过这些符号加上自己的 IR 剖析,正确地发射相应的指令。
希姆计算:基于 TVM 的 DSA AI 编译器构建
以上是希姆整个 DSA 张量化和向量化的流程,咱们也在一些方向上做了探究,比方微内核的方案,也是最近评论比较火热的方向。它的基本思想是把一个核算进程分成两层,一层用组合微内核的方法去拼接,另一种用查找的方法去寻觅,终究把两层的成果做拼接,挑选一个最优成果。这样其优势是充分利用硬件资源的一起,下降查找的杂乱度,进步查找功率。
希姆计算:基于 TVM 的 DSA AI 编译器构建
希姆计算:基于 TVM 的 DSA AI 编译器构建
希姆也在微内核上做了相关探究,但考虑到微内核方案与现在的处理方案相比,并没有在功能等方面有较大提高,所以现在希姆在微内核方向还属于探究阶段。

希姆核算袁晟:DSA 的自定义算子

本部分为希姆核算工程师袁晟现场共享。

首要,咱们知道算子开发现在碰到了四个大问题:

  • 需求支撑的神经网络算子许多,进行归类后根底算子有 100 多个;

  • 由于硬件架构不停迭代,相应指令以及算子参与的逻辑都需求进行变更;

  • 功能考虑。算子交融(local memory, share memory)以及我前边说到的图算信息传递(切分等);

  • 算子需求开放给用户,用户只能进入软件进行自定义算子。

我首要分成了以下三个方面介绍。首要是图算子,图算子是依据 relay api,把它裁剪成根底的语言算子。

以下图为例:

希姆计算:基于 TVM 的 DSA AI 编译器构建
第二是元算子,所谓的元算子是依据 TVM Topi 用 compute/schedule 描绘算子算法逻辑和循环改换相关逻辑。咱们在开发算子时,会发现许多算子的 schedule 是能够复用的,依据这种状况下,希姆供给了一套相似 schedule 的模板。现在,咱们把算子分成许多类,依据这些类,新的算子就会许多复用 schedule 模板。

接下来是一个比较杂乱的算子,依据 NPU 的状况下,咱们会发现 topk、nms 等带操控流的算法,带许多标量核算,现在用 compute/schedule 很难描绘,为处理这个问题,希姆供给一个相似 library 库。相当于在 library 库先编译杂乱的逻辑,然后经过跟 IR Builder 结合的方法,把整个算子的逻辑输出。

希姆计算:基于 TVM 的 DSA AI 编译器构建
接下来是算子的切分。关于 NPU,相对 GPU 和 CPU 状况下,TVM 每条指令都会操作接连内存块,一起会有 memory size 约束。一起,在这种状况下,查找空间不大。依据这些问题,希姆供给了处理办法,首要,会有一个候选集,把可行的解题放到候选集里,其次,对可行性进行解释,首要考虑功能要求以及 NPU 指令约束,最终,会引入 cost function,其间会考虑算子特征以及或许用到的核算单元特征。

再接下来对算子开发比较有挑战性的便是交融算子。现在面对两个爆破性问题,榜首不知道怎么将自己的算子和其他算子组合,第二个能够看到 NPU 里有许多 memory 层级,出现爆破式 memory 层级的交融。希姆 LLB 会有 shared memory 和 local memory 等交融的组合,依据这种状况下,咱们也供给一个主动生成结构,先依据图层给的调度信息,刺进数据搬移操作,再依据 schedule 里 master op 和 salve op 提炼 schedule info,最终依据其时指令的约束等问题做一个后处理。

希姆计算:基于 TVM 的 DSA AI 编译器构建
最终首要展现希姆支撑的算子。ONNX 算子大约是 124 个,现在支撑的大约是 112 个,占比 90.3%,一起希姆有一套随机测验,能够测验大质数、交融组合以及一些 pattern 交融组合。

总结

本部分为希姆核算工程师淡孝强现场共享。

这是希姆依据 TVM 搭的 CI,这上面跑了 200 多个模型以及十分多的单元测验。MR 不抢 CI 资源的状况下,提交一个代码需求 40 多分钟。核算量很大,大约挂了 20 多张自研核算卡以及一些 CPU 机器。

希姆计算:基于 TVM 的 DSA AI 编译器构建
总结,这是希姆的架构图,如下所示:
希姆计算:基于 TVM 的 DSA AI 编译器构建
作用来看,功能得到很大提高,别的主动生成与另一个手写模型的团队对标的话,基本上能够到达他们的 90% 以上。
希姆计算:基于 TVM 的 DSA AI 编译器构建
这是希姆代码的状况,左面是 TVM 和自研代码怎么办理,TVM 是作为 third_party 里的数据结构来运用,希姆有自己的 source 和 python 的东西,假如咱们需求对 TVM 进行更改,就在 patch 文件夹中对 TVM 进行改动。这儿有三个原则:

  • 大部分运用自研的 pass,也自研了 Custom module;

  • patch 会约束少修正 TVM 源代码,能 upstream 就及时 upstream;

  • 定时跟 TVM 社区做同步,更新最新代码到仓库中。

整个代码量也如上图所示。

总结:

  • 咱们依据 TVM 端到端支撑希姆一代二代芯片;

  • 依据 relay 和 tir 完成一切的编译优化需求;

  • 依据 tir 完成了 100+ 条向量张量指令的主动生成;

  • 依据 TVM 完成了自定义算子方案;

  • 模型一代支撑 160+,二代现已使能 20+;

  • 模型功能挨近手写极致。

Q&A

Q1:我对交融算子比较感兴趣,它怎么跟 TVM 的 tir 结合?

A1:关于右图,同一个算级,榜首,假如算子有两个 input 一个 output,那算子形态就有 27 种。第二,各式各样的算子联接时,scope 有或许是三个之一,所以咱们不会假设有固定 pattern。那么怎么在 TVM 上完成?首要依据图层调度,决定前后 add 和中间 scope 在哪里,图层是一个十分杂乱的进程,输出的成果是决定算子存在于哪个缓存以及可用缓存有多少。有了这个调度的成果,咱们在算子层进行主动交融算子生成,比方咱们依据 scope 信息进行主动刺进数据搬移的操作,完成数据流的构建。

schedule info 里面和 TVM 原生的机制很相似,交融进程中需求考虑每个 member scope 所用的大小,所以这儿便是 TVM 原生的东西,咱们只是用了一个特别的结构,将其集成到这儿,让它主动化。

do schedule 在此根底上,把开发者所需求的 schedule 做出来,或许也会有一些后处理。

Q2:便利泄漏 CostModel 更多细节吗?cost function 是依据算子层面的 feature 还是依据硬件层面的特性结合规划的?

A1:大思路现已在这了,首要生成一个候选集,生成进程跟 NPL 结构相关,然后会有剪枝的进程,考虑指令约束以及后边的优化,多核、double buffer 等,最终有一个 cost function 对其进行排序。

咱们知道优化套路实质是怎么把数据搬移隐藏在核算中,无非是对操作照此标准进行模拟,最终核算价值。

Q3:除了 TVM 支撑的默许交融规矩,希姆有没有产生新的交融规矩,比方在核算图层结合不同硬件定制的特有交融。

A3:关于交融,实践上有两个层次,榜首,buffer,第二,loop 交融。TVM 交融方法实践上是针对后一种。希姆基本沿用你说的 TVM 交融 pattern 的套路,可是做了一些约束。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。