0%

论文阅读[精读]-The Deep Learning Compiler: A Comprehensive Survey

​ 这是我读的第一篇深度学习编译方向的论文,是一篇survey工作。由于是第一篇,那基本上就得逐字逐句慢慢看了,又因为是survey,那二三十页是跑不了了。因此我为了加深印象,干脆就同步更新一下笔记,就当做”把论文翻成中文“了。

摘要

  • 学术界提出了XLA, TVM等深度学习编译技术
  • DL compiler输入模型,输出适用于不同平台的、优化后的代码
  • 本篇工作聚焦于multi-level IR,前后端的优化技术。
  • 提出了对这些技术的作者的点评,未来的改进方向
  • 这是第一篇深度学习编译优化的综述文章

Introduction

DL硬件分类:

  • 软硬件联合设计的 general-prupose 硬件
    • 线性代数库BLAS支持DL运算
    • 还有MKL-DNN,cuDNN等专门的加速库
    • 还有TensorRT等加速库
  • 专为DL设计的硬件
    • 类似的加速库
  • 从生物脑科学获得灵感的神经元硬件

库的更新经常赶不上硬件更新的速度,因此很难利用好DL硬件的算力

DL编译器

  • 优化 模型定义-> 实现代码 这一过程

  • 算子合并、层合并等方法

  • 是层次性的。

本篇工作的贡献:

  • 归纳了前端、多层IR、后端级别的优化方法
  • 分裂归纳了已有的DL编译器
  • 定量对比了不同编译器在CNN的效率提升(end-to-end和单层粒度下)
  • 提出了DL编译器的未来方向: dynamic shape and pre-/post-processing, advanced auto-tuning, polyhedral model, subgraph partitioning, quantization, unified optimizations, differentiable programming and privacy protection

Background

DL框架对比

  • tensorflow:其他语言支持最广泛:C ++, Python, Java, Go, R, and Haskell。TensorFlow Lite为Android设计。Keria作为tensorflow的前端。eager-mode类似pytorch的动态计算图
  • keria:高层神经网络库,快速构建model。兼容scikit-learn等库。由于过度封装,增加算子、获取底层信息很难。
  • Pytorch:动态框架,按行执行。合并了Caffe2。FastAI 是pytorch的高层封装,借鉴了keria。
  • Caffe/Caffe2,MXNet,CNTK,PaddlePaddle略过
  • ONNX:别的框架模型可以转换成ONNX模型,以便于交互(使用pytorchAPI)。

深度学习硬件

  • General-purpose Hardware:类似NVIDIA-GPU。同时上层有cuDNN等支持,软硬件联合开发。

  • Dedicated Hardware:A100,cloud service provider(google TPU)等等

    • TPU包含:
      • Matrix Multiplier Unit (MXU),
      • Unified Buffer (UB),
      • and Activation Unit (AU)
    • TPU由host硬件 进行CISC指令集的驱动
    • TPU可以直接把矩阵视为单元,而不是vector或者标量集
  • Neuromorphic Hardware:IBM’s TrueNorth and Intel’s Loihi.

    • 神经元可以同时存储和处理数据,存储区和运算区在一起,没有专门存储区。
    • 距离大规模应用很遥远
    • 探索rapid, life-long learning。

FPGA

cpu/gpu泛用性强,但是费电;AISC省电,但是转为某任务设计。编程FPGA是一个折中,把模型部署在FPGA上。

High-Level Synthesis (HLS) programming model帮助用户编辑FPGA,不用写很多verilog。

FGPA应用的问题:

  • AI模型用框架描述,而不是c/c++
  • DL特有的优化方式很难推广到FPGA

hardware-specific code generator:输入DL模型,输出HLS/verilog/VHDL代码,再编译成bitstream烧录:

  • The processor architecture:目标FPGA和正常处理器类似。
  • The streaming architecture:目标FPGA是个流水线。快,瓶颈是FPGA内的memory不够

DL编译器结构

正常编译器分为前端和后端。中间由IR联系起来,IR分为多个层次(multi-level IR)。

前端基于高层IR做硬件无关的优化;后端基于低层IR,做硬件专有的优化、代码生成、编译。

  • High-level IR: 定义计算和控制流,硬件无关。目标是得到程序的控制流和数据依赖性,提供图级别的优化。

  • Low-level IR:足够细粒度。需要支持后端的第三方工具链。

  • 前端: 从node-level,block-level, dataflow-level优化模型,生成 graph-IR.

  • 后端:输入high-level IR,输出 low-level IR。 可以直接把high-level IR转成LLVM IR等第三方工具链支持输入做优化,也可以通过已有的硬件结构、模型结构做自己的特殊优化

DL编译器的关键因素

High-level IR

High-level IR计算图的表示

图的表示法不同,优化方向也不同:

  • DAG图:有向无环图,点表示算子,边表示tensor。可以快速分析数据依赖。优点是方便优化,缺点是图上的节点、边语义不明确
  • Let-binding-based IR:对每一个变量”let“建立节点,语义明确。它对每一个let都算结果,建立一个map。每个表示通过查表找到结果

TVM等方法借鉴两种表示,博才两家之长。

tensor计算的表示法:

  • Function-based:Glow, nGraph and XLA采用,提供一些封装的算子。tensor计算视为一种函数
  • lambda expression: 用lambda函数表达,不需要声明新函数。TVM采用此方法,需要先计算输出的形状。
  • Einstein notation:比lambda表达式更简单。算子需要相关联,可交换,方便并行化。

Graph-IR 的实现

数据(tensor)的表示:

  • Placeholder:标记tensor的shape,有这个就有shape。

  • Unknown (Dynamic) shape representation:允许某一维的大小未知

  • Data layout:逻辑地址到内存分片的映射。

    • TVM and Glow把获取data layer作为一个专门的算子,这样不需要专门实现这个方法,更方便优化。
    • XLA把这个视为后端硬件的一个约束
    • Relay and MLIR要求tensor在type中描述layout
  • Bound inference:推断迭代器的上下界。TVM中iterator建立一个dag图,点代表迭代器,边代表运算。根节点的shapes of placeholders被确定,就能递归处理。

graph-IR需要支持很多算子:代数算子、tensor算子、control flow算子。举例子:

  • broadcast:放宽一般算子对形状的依赖性。
  • control flow:if或者while
  • Derivative:自动求导。
  • Customized operators:自定义算符。
    • Glow中定义算符需要实现多层的封装
    • TVM、TC只需实现一个implementation

不同编译器有不同的 graph-IR 实现,但有一定的相似之处。需要注意,graph-IR一定要是硬件无关的。

Low-level IR

实现

不同的实现

  • Halide-based IR:设计哲学是计算和规划分离。可以试多种规划,选一个最好的。原始方法需要形状确定,TVM改进了它:
    • 取消LLVM的依赖
    • 重构project module
    • 提高复用性,方便自定义算子
    • 保证每个变量只有一个定义位置
  • Polyhedral-based IR:视为多面体。loop的大小更加灵活,方便采用polyhedral transformations优化方法。有多种优化器:isl,Omega,PIP,Polylib,PPL
  • Other unique IR: 他们用一些自定义的优化方法,然后编译成LLVM IR
    • GLOW:包含declare和program两种操作。用@in,@out,@inout帮助分析内存优化的时机
    • MLIR:受到LLVM影响。用dialect提供对别的IR的抽象,包含:TensorFlow IR, XLA HLO IR, experimental polyhedral IR, LLVM IR, TensorFlow Lite。 自定义dialect很简单,方便开发者适配新硬件。
    • HLO IR of XLA:同时是high/low-level IR。足够细粒度,提供硬件级的优化,生成 LLVM IR

代码生成

绝大多数都是生成LLVM IR,接下来通过LLVM进行多种优化。需要提供几种优化:

  • 循环转换
  • 提供目标硬件的额外信息

前端的优化

前端进行计算图的优化,和硬件实现无关。前端的优化称为pass,通过多次遍历图,每轮进行不同的操作。一旦模型被import、转换成graph,前端可以获取各个地方的shape。

Node-level optimizations

  • 消除不必要的节点:比如sum(1,0),Nop Elimination
  • Zero-dim-tensor elimination:消除0维向量,或者消除某个维度为0的向量

Block-level optimizations

  • 消除单位运算+0,$*1$
  • 用简单运算符替代复杂运算符
  • 预计算常数

DL运算符也可以优化,比如:

  • optimization of computation order:$A^T B^T = (BA)^T$

  • optimization of node combination:比如把多个transpose消成一个

  • optimization of ReduceMean nodes:用AVGPool代替ReduceMean

  • Operator fusion:算子合并。难点是如何合并包含多个reshape,boardcast,reduce等节点的算子。

  • Operator sink:消除可以消除的节点

Dataflow-level optimizations

  • Common sub-expression elimination (CSE):预计算可以计算的控制流。
  • Dead code elimination (DCE):如果结果不用,就是dead,不需要存中间结果。经常在上面的优化以后出现
  • Static memory planning,对于内存受限的机器很重要:
    • in-place memory sharing:复用输入和输出的memory
    • Standard memory sharing:在不覆盖的情况下,复用前面用到的存储空间
  • Layout transformation:
    • 计算出tensor最好的存储形式,然后在计算图增加layout transformation节点。
    • 最优方式随tensor计算公式不同,硬件不同而不同
    • 速度提升明显

后端的优化

Hardware-specific Optimization

一种方式是编译到 LLVM IR,另一种方式是定义自己的优化方式,用模型的信息。举5个例子:

  • Hardware intrinsic mapping:编译成硬件已经优化过的一些kernel。

  • Memory allocation and fetching:针对硬件上不同存储的延迟不同做优化

  • Memory latency hiding:等内存延迟的时候干别的,好多硬件自己实现了,但TPU没有。TVM通过虚拟线程解决这事

  • Loop oriented optimizations:Halide和LLVM实现过了。

    • Loop fusion: 把多个边界一样、没有依赖的循环放到一起
    • Sliding windows:直到需要数据时再计算
    • Tiling:把一个循环拆成多重循环,更好的局部性
    • Loop reordering:重新排列循环顺序。让空间局部的循环们放在一起
    • Loop unrolling:循环展开,更好地做指令级并行。
  • Parallelization:更好的支持线程级并行等,提高硬件利用率。需要额外的、模型的知识,更难开发。

Auto-tuning

由于各种优化很多,搜索空间很大,需要自动搜索优化方式。TVM, TC,XLA都支持这个。包含四个要素:

  • Parameterization:搜索参数时需要知道的一些已有参数
    • Data and target:tensor的形状,GPU上各个memory的延迟、大小
    • Optimization options:优化方法有什么,对应什么超参。TC,XLA支持把超参参数化,比如batch_size。
  • Cost model:评价优化方法的模型
    • Black-box model:只关心运行时间,对内部情况不关心。TC采用
    • ML-based cost model: 用ML模型衡量现在情况有多好。TVM,XLA采用
    • Pre-defined cost model:预先定义好。搜索更快,但是定义很难,和DL model有关。
  • Searching technique:如何搜索
    • Initialization and searching space determination:随机/特定初始化。TVM允许用户决定搜索空间
    • Genetic algorithm (GA) :类似蚁群算法,TC采用
    • Simulated annealing algorithm (SA):模拟退火。TVM采用
    • Reinforcement learning (RL): Chameleon采用(基于TVM开发)
  • Acceleration:如何加速搜索过程
    • Parallelization:

    • Configuration reuse:复用上次的结果,如果某些局部的参数和上次一样。

      Optimized Kernel Libraries

硬件预定义好了一些快速的计算库(比如cuDNN)。后端可以调用这些库

  • 调用库带来巨大的性能提升
  • 但调用库需要符合库的调用约定,可能破坏最佳控制流
  • 库函数对编译器是黑盒,可能影响优化,不能做operator fusion等。

已有编译器的分类

通过上面已经讲过的不同方面的优化方法,可以梳理、归纳、分类一下现在比较火的DL编译器。总体结果如下图:

评测

这一部分评测了一下已有的一些编译器的性能,具体实验就略过了,对学习DL编译优化知识没什么帮助。

结论和未来方向

这一部分归纳梳理了一下DL编译未来的发展空间:

  • Dynamic shape and pre/post processing:

    • 一方面类似NLP,只有运行时才能知道输入的形状。另一方面,模型结构本身可能也会变化。
    • 随着模型增大,模型的加载时间可能成为瓶颈。已有还没有触及
  • Advanced auto-tuning:受限于时间,现有优化都是在找一些局部最优。但局部最优的组合大概率不是全局最优

    • 目前ML方法还有潜力,可以考虑在auto-tuning中进一步应用,而不只是cost model。
  • Polyhedral model:在auto-tuning中引入多面体模型

    • 可以复用之前结果
    • 可以减少搜索空间
    • 挑战是如何在稀疏情况下应用之。
  • Subgraph partitioning:把计算图拆分成不同子图。可以把不同子图分布并行到异质的设备上。

  • Quantization:增强量子化可以在编译时进一步提升优化的空间,挑战是:

    • 如何简单的增量开发
    • 量子化操作如何和其他的优化步骤交互。
  • Unified optimizations:如何同时采用不同编译器的优化,已由编译器大多聚焦于一些方面的优化。MLIR通过dialect某种程度上可以复用不同编译器的优化。

  • Differentiable programming:让编译器支持可微程序

  • Privacy protection:能不能在中间输出层加噪音,保护隐私。

  • Training support:已有编译方法专注于部署。