文章目录
- Tiling技术
- Loop Tiling
- AI推理中的Tiling
- 参考
- 流水线技术
- 指令周期
- 参考
Tiling技术
Tiling(平铺)是一种将大的问题或数据集分解为较小的子问题或子数据集的技术,目的是提高数据局部性和缓存利用率,从而提升程序性能。
在CUDA编程中,Tiling常涉及利用共享内存和循环分块。共享内存可被一个线程块内的所有线程访问,可以减少全局内存访问次数,Tiling过将数据分割成小块并重复利用这些块中的数据来实现这一点,从而最大化了共享内存的效益;循环分块则将大循环分解为小循环,减少内存访问冲突,提高内存访问局部性。在 CPU 中,Tiling 用于优化矩阵乘法,将输入矩阵分成小块进行乘法运算,以减少内存访问次数,提高缓存命中率。
Loop Tiling
在该二重循环中,元素A[i]会在内层循环中被连续访问M次,元素B[j]则是每隔N个元素再次被访问。如果内部循环跨度比较大,即M数值比较大,当启动下一次外部循环时,之前访问过的B[j]已经移除缓存了。比如说,cache大小为10,N为5,M为12(大于cache的size即可),当i=1,j=10时,cache刚好存储了B[1]到B[10];当j=11时,由于cache中没有B[11],所以需要从内存中读入,同时按照最久未访问策略将B[1]移除缓存,但是当外层循环加一即i=2时,内层循环重新遍历,此时j=1,但是此时cache中并没有B[1],未命中,需要再次从内存中读入。这样A的未命中率就是N/cache_size,B的未命中率就是(NM)/cache_size。
按照这种访问策略,要想提供效率就是要提供cache的命中率。数据移出cache的策略是操作系统控制的,开发者角度可以修改的就是数据的遍历顺序以提升命中率从而提升计算效率,Loop Tiling的工作就在于此。
Tiling的作用就是解决数据未命中的问题。将数据分割成若干个块(tile),保证在下一次访问时数据还在cache中。一般的做法就是先从内层循环开始分割。原因上面也分析过了,外层循环动一下,内层循环要动一轮,如果先分外层循环,一次要遍历的数据还是很多,包含整个内部循环所涉及的数据。假设切分的大小为T,则之前的循环改写如下。为了保证遍历的正确性,此时内层循环需要放在最外层,但是这样会多次移动A,影响A的命中率。
此时A的未命中率为(M/T)N/cache_size,B的未命中率为M/T * T/cache_size,总的未命中率为MN/(Tcache_size) + M/cache_size。如果沿着外层循环切割,总的未命中率为MN/(Tcache_size) + N/cache_size,则实际的差别主要在于内外层循环的大小。
二维和更高纬度的计算分析过程类似。
AI推理中的Tiling
由于Ai core的Local Memory的存储容量无法完整的容纳下算子的输入与输出,因此需要先搬运一部分输入进行计算然后搬出,再搬运下一部分输入进行计算,直到得到完整的最终结果,这个数据切分、分块计算的过程称之为Tiling。根据算子的shape等信息来确定数据切分算法相关参数(比如每次搬运的块大小,以及总共循环多少次)的计算程序,称之为Tiling实现。**Tiling实现完成后,获取到的Tiling切分算法相关参数,会传递给kernel侧,用于指导并行数据的切分。**大致步骤可以分为1)宿主机端获取硬件信息,2)获取输入数据维度和类型信息,3)kernel侧解析tiling参数并执行。
切分可分为核间切分和核内切分两块内容。核间切分是将数据分配给NPU的多个Aicore;核内切分是将分配到某个核计算的数据进行分批处理。
参考
http://www.coreui.cn/news/411154.html
https://zhuanlan.zhihu.com/p/292539074
https://zhuanlan.zhihu.com/p/685761148
流水线技术
目前,并行技术主要分三种类别:多处理器、资源共享即cpu分时和时间重叠即流水线技术。流水线技术的特点是将整个过程分为若干个子过程(段,stage),每个子过程由一个专门的功能部件来实现。每个段的所用时间应该尽可能相等,其中耗时最长的就是流水线的瓶颈。每两个段之间会有一个缓冲寄存器叫做锁存器,用于在两个段之间传递数据,同时也起到隔离电路的作用。
指令周期
单周期处理机模型:一个周期完成一个指令(每个周期是等长的),指令长度可能不一样,会造成很大的浪费
多周期处理机模型:将一个指令的完成划分成若干个周期来实现
经典五段流水线
1)取指(IF)
从程序计数器PC中取出地址,从存储器中取出指令并放入指令寄存器IR中,PC指向下一条指令。
2)译码(ID)
对执行进行译码,从IR的寄存器地址访问通用寄存器组获取操作数。
3)执行(EX)
ALU对上个周期中准备好的操作数进行运算操作。
4)存储器访问(MEM)
load指令根据上一个周期计算出的有效地址从存储器中读出的相应的数据;store把指定的数据写入这个有效地址对应的存储单元。
5)写回(WB)
把结果写入通用寄存器组。对于ALU运算来说,这个结果来自ALU,而对于load指令来说,这个结果来自存储器。
参考
https://www.cnblogs.com/CorePower/p/CorePower.html
https://blog.csdn.net/weixin_40064300/article/details/124415855