本篇在之前Halide基础上,是说编程者要写高性能程序,需要费尽心思的写优化的schedule,这里提出了一个算法,扩展之前的边界分析,自动化产生优化的调度策略,就不需要人为写了 之前的研究方向有:
自动化的tuning:很多是暴力搜索,需要实际跑到硬件上来测时间,费时间限制搜索空间:比如Darkroom,限制一些条件,变成整数线性规划问题多面体模型一个Halide程序可以表示为无环有向图,边表示数据依赖,但即使有数据依赖,也没有规定前后计算的流程
类似上一篇论文,这里还是举了3x3卷积的例子,给出了三种调度方式。第一种需要分配比较大的空间来存放中间结果,数据局部性最差;第二种压根不存放中间结果,这就会有冗余计算,虽然数据局部性最好;第三种用overlapped tiling的方式
这个FPGA的数据复用还不一样,是面向CPU的,为了让数据还保留在cache里,这样重新读这个数据的时候就快得多; 对于一个矩阵乘法,如果这个矩阵很大的话,可能就不能都放到cache里,这就需要tiling了
编译器动过interval analysis来确定循环的边界,确定中间数组的大小;这个边界在后面的自动调度算法中也需要 它从output函数开始,根据函数依赖链,在无环有向图中一步一步确定 如果这个边界不好自动判断,那就需要编程者显式声明
这节主要讲自动化产生Halide调度的算法,原理是把整个程序分成几组,每一组内部可以独立的利用数据局部性原理进行优化,比如说tiling 要求是算法必须已知各个循环的边界,不能自己判断的话就要显式声明
算法采用聚类的思想,一开始每个函数的分散的
枚举所有可能的merging的可能性对于每一个merging,估计性能的提升然后选择性能提升最大的merging如果性能不在提升的话,终止merging其实还是立足于cache,有一个明确的cache的大小,寻找合适的tile大小,保证cache miss次数是最小的
主要就是明确tile的尺寸,对于n维的循环,可以看作一个n维的多面体,在数据可以复用的方向上拉长。 这需要编译器明确分配临时缓冲区的大小 若果中间临时存储大小超过了cache大小,或者tile太大并行度太小,这些都会被舍弃掉
inline有好处也有坏处,会导致冗余计算,所以要评估出这部分的代价。consumer可能不止一个,所有冗余计算可能不止一处,所有的consumer的代价都要加上
刚刚讲了确定tiling大小和inline,还要做的优化是
reorder输出函数的循环,看如何实现最大的input locality尝试unroll和向量化最里面的循环 之后就可以让编译器产生机器代码了过去自动化优化代码的局限性有:
只考虑了搜索空间的子集:只是用了一些特定的调度模板或者习语用了特别的搜索算法,有局限性手工设计的cost model不足够准确本篇论文提出的方法,是自动优化halide程序的算法,第一个大幅超过人工手写代码的性能,主要贡献有:
参数化的搜索空间通用的搜索算法:利用率回溯法和粗粒度改进利用了符号分析和机器学习的cost model一个鲁棒的可以训练cost model的方法:通过大量的随机程序来训练,更通用利用采样和benchmarking(autotuning)来提高性能可以平衡编译时间和程序性能:程序编译时间可任意设置,从最快的贪婪算法到最优算法之间可以插值halide程序分成算法和调度两部分,算法又称pipeline,可以用有向无环图表示;调度则表示成一个确定的loop nest。loop nest主要由两部分决定:
intra-stage order:包括arbitrary dimension order和n维作用域的tiling,而且任意循环都可以并行或者展开做cross-stage granularity:at what granularity each stage should be computed,比如inline,root,sliding window或者line-buffered一个loop nest是由这几个决定的:
a nested set of n维tiling计算和存储的粒度循环的每层tiling是展开、用并行的线程还是SIMD首先要遍历出所有的可行的调度选择,我们是从最后的结果开始,从后往前加计算的一级,每一级会有计算和存储的粒度,以及tiling的策略
每次保留最好的k个结果,同时又减枝策略,排除掉一些不合适的情况
这里基于halide符号分析工具提取的特征,用一个小型的神经网络来预测runtime
特征可以分成
算法决定的特征调度决定的特征