Deep Learning Compiler 之自我理解

Deep Learning Compiler 之自我理解

前序

接触DL算起来也有几年了,但是一直在外围,未真正深入。因工作关系,主要接触的是DL的inference架构以及ASIC的Soc部分,零散又琐碎。

后将DL compiler作为个人的兴趣方向,认为DL编译器在整个DL的业务流程中,处于承上启下的地位。读了些论文以及代码,但依然缺少系统性。

近一年前接触MLIR,当时只是粗略了解,以及在nGraph上玩了玩MLIR,仅此而已。后发现公司的DL编译器有融合MLIR的计划,MLIR有一统江湖的趋势,又花了些时间看MLIR在实现一个具体compiler时的作用。

就想着花些时间做个小总结,写给自己看的,毕竟底子真薄。不定期更新,有兴趣了就来。

编译器狂想

完全是个编译器门外汉,抛开AST、SSA、三地址码等专业术语,就以外行的眼光来朴素的看看什么是编译器。

编译器的产生是将编程语言转换为机器码的天然需求;而编程语言则是对自然语言的逻辑抽象。将想法或设计思路以文字的形式表达出来,然后由程序员将之转换为编程语言,接着编译器将其转换为机器码。

所以站在程序员的角度看,编译器就是将由编程语言表达式在不改变内在逻辑的前提下转换为机器码表达式,就是个convert的过程。在各种转换过程中,就涉及到计算机科学的方方面面。而编译原理和计算机体系架构在编译器的实现中有具体的体现。

而llvm的横空出世,带来了除GCC之外另一个阵营。后来者没有历史包袱,可以采用最新的技术和全新的架构,带来了颠覆性的编译器实现思路。llvm所提出的frontend、IR,、backend结构,可以极大的利用已有的backend,只需实现自己的frontedn即可。可以轻松的自己DIY一个自己的编译器。而这种可扩展性影响力也波及到了DL编译器领域。

DL编译器

Deep Learning的兴起,不仅带来了各种DL的framework,可以说每个大厂都有自己的一套*;还带了各种新处理器,既有在原CPU中添加计算加速器这种新瓶装老酒,也有GPU这种老瓶装老酒,更有专为DL而生的各种ASIC。

百花齐放带来DL繁荣的同时,也带来了互通的问题。同时随着DL的发展,各种graph结构和算子层出不穷,需要能否在各种后端运行,带来了所谓的XX爆炸现象。

Deep Learning Compiler 之自我理解

“TASO: Optimizing Deep Learning with Automatic Generation of Graph Substitutions”

依靠优化工程师去重复造*有着巨大的工作量,且优化工程师也是稀缺资源。

人类的进步是因为会制造工具、使用工具,于是DL编译器顺其自然的登场。要解决的问题是将以graph表示的DNN最终转化为各种硬件后端的runtime。

“The-Deep-Learning-Compiler–A-Comprehensive-Survey-v4”这篇综述对主流的DL编译器都有覆盖,且各自的技术重点也有提及。主流的DL framework一般都有自己的编译器。
Deep Learning Compiler 之自我理解
而下图则是对各家编译器采用技术的overview,可见也明显的分为frontend和backend。一般将与硬件无关的转换和优化放在frontend,而将与硬件有关的转换和优化放在各个硬件平台的backend中。
Deep Learning Compiler 之自我理解
以OpenVino为例,有一个统一的frontend,针对CPU、GPU、VPU等又有各自的plugin(及backend)。

但是frontend/backend和hardware-independent/hardware-dependent 是否就是一一对应的,这就难说了。

什么是DL编译器

回归本源,DL编译器就是个转换工具,不仅功能性要完备,更重要的是性能上和handcraft要有可比性。好像如果DL编译器的性能达到handcraft的85%(不是准确数字,不深究)以上就算个不错的工具了。

抛开优化不谈,只说编译器的基本功能,则要先看看DNN是怎么表示的的。
基本上来说,DNN可视为计算图(computer graph)或数据流图(data flow)。
把一个很大的计算式子分layer或stage逐步计算。
Deep Learning Compiler 之自我理解
一般的DNN编译过程如下图所示:
Deep Learning Compiler 之自我理解

An In-depth Comparison of Compilers for Deep Neural Networks on Hardware

但DNN有其特殊之处,以常见的convolution为例,在计算时将它视为一个整体conv还是当作矩阵乘或向量乘对待?这就和backend有关了,对于CPU,没有conv加速器,只能将conv转为矩阵乘等,在运行时调用对应的kernel;而对于ASIC,一般都有conv加速器,需保持conv的整体性,将conv整体喂给加速器。

这样会带来一个问题,backend有时会需要比较原始的graph结构,但是这些information可能会在frontend阶段被丢失或优化掉。在选定后端之后,需要选择一个合适的前端来满足特定的要求。

实践出真知,Pytorch转ONNX详解中描述的ONNX和Caffe的operation颗粒度差异,会影响后端可优化手段的多少。如果后端是ASIC,可见Caffe是更优的选择。

DL编译器的融合

各家编译器受llvm的影响,通过引入IR,最大程度的利用已有的各种转换方法和优化方法。

Deep Learning Compiler 之自我理解

“MLIR:Multi-Level Intermediate Representation Compiler Infrastructure”

其实llvm中的概念在DL编译器中很常见。比如OpenVino的演进就挺有代表性,之初没有pass的概念,一条路走到底;后期做代码重构,引入了frontend,backend以及pass,清晰了流程和架构。

而公司为一款ASIC做的编译器更是将pass概念发展到极致,把编译转换的各个步骤都抽象为pass,然后由xml脚本来定义pass的执行次序。

虽然这两款编译器没有明确提出IR的概念,但是在graph的转换过程中,仍会定义自己的graph表示方式,然后基于此presentation做优化并向硬件的运行时抽象转换。

而公司除了OpenVino之外,还有nGraph和PlaidML,主要都是面向edge inference的,整合是必然的,最终都统一在OpenVino下。nGraph成了OpenVino的前端表示,并利用nGraph已有的一些优化pass;而PlaidML则成为了一个Plugin,专门面向CPU/GPU后端。

编译器相关之间借鉴和参考,因门槛太高而举步维艰。Google看不下了,提出了MLIR(Multi-Level Intermediate Representation),呼吁大家使用MLIR做*,别再自己造了;且MLIR这个*可以从这个大厂走到那个大厂,可同行无阻。

MLIR的最厉害之处在于多种IR可以在MLIR的框架下共存;high -level IR和low-level IR可以在同一个IR文件中体现。

MLIR的文件游戏玩得好,将DL编译器常见的parten match, rewrite, Operation definition等工作由DSL描述,然后由相关工具自动生成C++语言。从而可以将工程师从琐碎的工作中解放出来,可以专注graph本身的优化。

MLIR中的语句很*,可以只declare而不define;即可以指定某OP是legal的,但可能在某个阶段其根本就没有对应的implemention。就和在代码中引用lib库中的函数或symbol类似,至于库中是否有对应的实现,只有在runtime时才会知晓,报错或正常通过。

MLIR只是一个compiler infrastructure,出发点就是开放,让更多的人使用。所以架构上的可扩展性和灵活性是毋庸置疑的。至于MLIR能统一多大的江湖,再看。

DL编译器的优化方法

不同的阶段有不同有不同的优化方法。在后端一般是Auto scheduling(Polyhedral) 和Manual scheduling(Halide)。

MLIR自带Polyhedral的Dialect。但好像用得比较多的还是cost-function + search space。公司针对ASIC的编译器就是采用的后者。

暂停,不想写了。后续有兴趣了再更新。

上一篇:设置GPU及显存大小


下一篇:HAproxy简介