赞
踩
AI与传统编译器
至于TVM,现在有很多框架(TF,Pytorch),然后会部署到不同平台(CPU、GPU、TPU),神经网络编译器呢就是把不同框架里写的东西编译成一样的格式再生成到某一平台的代码
再来看传统编译器(更偏向于LLVM),现在有许多语言(C、ObjC、C++),也有许多平台(x86、arm),编译器做的就是把不同语言编译到同样的中间代码再生成某一平台的代码
这两个就是把前端的表示进行统一再生成硬件相关的程序,只不过一个前端表示的是神经网络,一个是大家都熟悉的代码,结构类似但实际内部工作大相径庭
传统编译器:输入高级语言输出低级语言
神经网络编译器:输入计算图/算子,输出低级语言
相同点是都做了类似语言转换的工作
不同点
传统编译器解决的主要问题是降低编程难度,其次是优化程序性能
神经网络编译器解决的主要问题是优化程序性能,其次是降低编程难度
问题
神经网络编译器,编译时考虑神经网络有关的特性来优化程序。
程序优化,解释器vs编译器,JVM,JIT,llvm, Halide,TensorFlow, XLA, ONNX, TVM, MLIR
AI编译器和传统编译器的本质是一样的,都是一类能够将不同的编程语言所表达code进行转换的program。这也是AI编译器之所以被称之为“编译器”的原因。
两者的联系
因为AI编译器出现的比较晚,所以在设计的时候往往会借鉴传统编译器的思路:
• 两者的理念比较类似。两者都力求通过一种更加通用,更加自动化的方式进行程序优化和代码生成,从而降低手工优化的effort。
• 两者的软件结构比较类似。一般都分成前端,IR,后端等模块。其中前端负责讲不同的语言的描述转换成统一的IR表述,后端通常会对IR表示进行优化,最终生成可执行的code。IR层用来解耦前端和后端,降低集成的effort。
• 两者的优化方式比较类似。通常编译器都会对code进行一系列的优化,从而提高performance或者减少memory footprint等。AI编译器和传统编译器都是通过在IR上面,run各种各样的pass,进行优化的。而且,AI编译器往往还会借鉴传统编译器中的一些pass,比如constant folding, dead code elimination等
• AI编译器通常会依赖于传统编译器。AI编译器在IR上面,对model进行优化之后,通常会有lowering的过程,将优化后的high-level IR转换成传统编译器的low-level IR,然后依赖传统编译器,做最终的机器码生成。
两者的区别
两者最根本的区别是应用场景的区别:
• AI编译器是把一个深度学习模型转换成executable。这里可以把一个深度学习模型理解成一段用DSL(Domain Specific Language)描述的code,而executable就是一段用硬件能理解的机器码描述的code。这正好能对应到compiler的定义。
• 传统编译器是把一段用高级语言编写的code转换成executable。这里的高级语言可能是C/C++等。这也能够对应到compiler的定义。
应用场景的区别导致了两者在设计上不同:
• 两者的IR表达层次有区别。AI编译器一般会有一套high-level的IR,用来更抽象的描述深度学习模型中常用的high-level的运算,比如convolution,matmul等。而传统编译器的IR更偏low-level,用于描述一些更加基本的运算,比如load,store,arithmetic等。有了high-level的IR,AI编译器在描述深度学习模型的时候会更加方便。
• 两者的优化策略有区别。AI编译器因为是面向AI领域的,在优化时,可以引入更多领域特定的先验知识,从而进行更加high-level,更加aggressive的优化。比如说:
o AI编译器可以在high-level的IR上面做operator fusion等,而传统编译器在做类似的loop fusion的时候往往更加保守。
o AI编译器可以降低计算的精度,比如int8, bf16等,因为深度学习模型对计算精度不那么敏感。但传统编译器一般不会做这种优化。
对神经网络优化,尽量减少逻辑判断,一算到底是最好的。对内存要尽可能优化,降低内存占用。
神经网就是一组矩阵计算。神经网编译器就是将这组计算针对平台尽可能加速。
编译神经网络,把一张张计算图编译成cpu的gpu的,或者是某些专用的AI计算设备,比如google 的TPU的指令集合。具体说来就是先来个图剪枝,再来个拓扑序遍历计算图,一边遍历,一边映射为中间表示。
后面从中间表示到指令集就大同小异了。
神经网络编译器大概有TVM/Glow/TensorRT/TensorComprehension/XLA/Tiramisu。这些针对的都是神经网络模型推理阶段的优化,从神经网络模型到机器代码的编译。一般过程是 神经网络模型->图优化->中间代码生成(例如Halide)->中间代码优化(例如TC/Tiramisu使用多面体模型进行变换)->机器代码。编译的是神经网络的模型,优化的是网络模型本身,各层数据数据存储的方式(如分块存储,nchw,nhcw),各个算子(如mlp,conv)的计算方式(如向量化,分块)等等。
传统编译器(GCC,Clang这些)的编译范围更广,是从源代码到机器代码的编译,输入是一段完整的代码,经过了词法分析,语法分析,语义分析,中间代码生成,优化,最后到机器代码。
联系:
首先是神经网络编译器,从中间代码到机器代码的过程,可能就对应了传统编译器的整个编译过程,比如Halide->机器代码
然后,目标都是都要针对目标处理器进行的优化。无论是什么代码/模型,最后的优化,就是如何最大化利用硬件,比如cache的命中率,计算速度啥的,最终目标都是生成好的机器代码。
神经网络编译器,可以对应用做很多很强的假设,主要以嵌套循环的计算为主,所以可以针对性的进行优化。
传统编译器的前端也非常厚重,都是以编程语言为输入来生成IR的。而神经网络编译器的主要问题,还是性能优化和适配,所以基本都不做前端,直接用代码手动构造IR。
针对deep learning的编译器,把应用限制在tensor operator上,做domain specific optimization。传统编译器面向的程序更加general。前者更偏上层,只需要考虑deep models,流行的deep models基本算子就卷积和矩阵乘,后者更偏底层。
以TVM和LLVM举例,TVM拿到模型的计算图,先用Relay做一下图切分,算子融合,conv-bn-relu之类的,也有人做multiple conv fusion,这一步是graph-level的优化;之后再到算子层面,现在的deep compiler侧重于循环优化,这部分在传统编译器里研究的很多,即使是deep learning领域,能做的domain specific的优化也没多少,auto tuning做的主要还是tiling的参数 (AutoTVM / FlexTensor (ASPLOS 2020) / Ansor (OSDI 2020))。做完operator-level的优化,TVM IR转成LLVM IR,再借助LLVM的各种后端生成可执行代码。
要部署一个模型,后端可以选择使用手调库,比如厂商库,MKLDNN, CuDNN,某些厂商的,或者第三方的Blas库,算子库,比如阿里的MNN;另外一条路就是选择deep compilers,做代码生成。
先说deep compiler的缺点。首先编译器能做的工作比较有限,实际的部署,要考虑到模型设计,模型压缩之类的。另外因为比较偏上层,代码生成部分交给了black-box compiler, 很难做到汇编级的调优,能在tuning中避免shared memory bank conflicts,但是,并不能优化掉register bank conflicts,在现有的DSL中也缺乏底层的表达,相比于某些手调库,最终性能不太行。比如说,某些人专门做Winograd Conv的优化,性能都快接近理论极限了 (ppopp 2020)。能想到的缺点都非常细节,觉得未来很容易解决,比如GPU的prefetch,现在TVM里面,用prefetch怎么选它的size和offset基本都会导致性能变差。
但是,手调库的缺点更加明显,除了耗费人力外,做的优化也是general的,无法cover到具体的input configuration。即使是针对某些input,选择调用不同的kernel,也非常有限。比如MKL-DNN,CuDNN虽然是厂商库,代表了手调的state-of-the-art,可能对3 * 3的卷积做了特殊优化,但对于某些大的feature map或者大的kernel size性能就很差。在某个具体网络上,通过auto-tuning,超过MKL-DNN和CuDNN并不难。AMD的就更不用说了,性能太差了,针对CUDA做的调优,用hipify工具转到ROCm上,性能都强。
自动调优最重要的是调优之后的性能,其次是调优的时间。
对TVM了解比较深,对其他的deep compiler了解不多。至少相比于主流框架Torch/TensorFlow来看,当然考虑了这些框架用的底层库,在某个网络上,比如ResNet-18,针对Input大小为(1, 3, 224, 224)做调优,超过还不算太难。因为做的就是inference optimization,实际部署模型的时候,input size都是运行时不再变的,所以这条路可行。
调优时间上,Ansor调一个网络大概一天左右,比较短了。Facebook有工作做贪心搜索,能把调优时间降到一分钟以内,最终性能也不算差。
如果指的是针对神经网络的编译器,相对传统编译器最大的不同,引入了multi-level IR。
传统编译器里分为前端,优化和后端,其中前端和语言打交道,后端和机器打交道,现代编译器的的前端和后端分的很开,共同桥梁就是IR。IR可以说是一种胶水语言,注重逻辑,去掉了平台相关的各种特性,这样为了支持一种新语言或新硬件都会非常方便。
由于神经网络结构领域的特殊性,这类编译器不光要解决跨平台,还有解决对神经网络本身的优化问题,原先一层的IR就显得远远不够,原因在于如果设计一个方便硬件优化的low level的语言,几乎很难从中推理一些NN中高阶的概念进行优化。比如说LLVM,很难把一连串的循环理解成卷积。一个完善的High level IR,至少需要包括对计算图的表示(DAG, let binding),满足对tensor和operator的支持。
神经网络编译器或者深度学习编译器(下称 DL 编译器),属于一种领域特定编译器,专门用于将神经网络的训练/推理部署到 CPU、GPU、NPU 上。与传统的编译器有着类似的结构,有很多共用的部分,同时也有自己的侧重点。
关于 DL 编译器,更多谈一下 edge 端 DL 编译器。
参考链接:
https://www.zhihu.com/question/396105855
参考文献:
[1] Tensorflow. https://github.com/tensorflow/tensorflow
[2] MindSpore. https://gitee.com/mindspore/mindspore
[3] Tvm. https://tvm.apache.org/
[4] XLA. https://www.tensorflow.org/xla
[5]TensorComprehensions.https://github.com/facebookresearch/TensorComprehensions
[6] Li, Mingzhen and Liu, Yi, etc. The deep learning compiler: A comprehensive survey. IEEE Transactions on Parallel and Distributed Systems. 2020
[7] Polyhedral Compilation. https://polyhedral.info. Accessed February 4, 2020.
[8] Zheng, Lianmin, etc. Ansor: Generating high-performance tensor programs for deep learning. Symposium on Operating Systems Design and Implementation. 2020
[9] MindAKG. https://gitee.com/mindspore/akg
[10] ^TASO: Optimizing Deep Learning Computation with Automatic Generation of Graph Substitutions https://cs.stanford.edu/~zhihao/papers/sosp19.pdf
[11] ^abEquality Saturation for Tensor Graph Superoptimization https://arxiv.org/pdf/2101.01332.pdf
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。