• 中国精品科技期刊
  • CCF推荐A类中文期刊
  • 计算领域高质量科技期刊T1类
高级检索

面向昇腾处理器的高性能同步原语自动插入方法

李帅江, 张馨元, 赵家程, 田行辉, 石曦予, 徐晓忻, 崔慧敏

李帅江, 张馨元, 赵家程, 田行辉, 石曦予, 徐晓忻, 崔慧敏. 面向昇腾处理器的高性能同步原语自动插入方法[J]. 计算机研究与发展. DOI: 10.7544/issn1000-1239.202440093
引用本文: 李帅江, 张馨元, 赵家程, 田行辉, 石曦予, 徐晓忻, 崔慧敏. 面向昇腾处理器的高性能同步原语自动插入方法[J]. 计算机研究与发展. DOI: 10.7544/issn1000-1239.202440093
Li Shuaijiang, Zhang Xinyuan, Zhao Jiacheng, Tian Xinghui, Shi Xiyu, Xu Xiaoxin, Cui Huimin. Automatic Insertion of High-Performance Synchronization Primitives for Ascend Processors[J]. Journal of Computer Research and Development. DOI: 10.7544/issn1000-1239.202440093
Citation: Li Shuaijiang, Zhang Xinyuan, Zhao Jiacheng, Tian Xinghui, Shi Xiyu, Xu Xiaoxin, Cui Huimin. Automatic Insertion of High-Performance Synchronization Primitives for Ascend Processors[J]. Journal of Computer Research and Development. DOI: 10.7544/issn1000-1239.202440093
李帅江, 张馨元, 赵家程, 田行辉, 石曦予, 徐晓忻, 崔慧敏. 面向昇腾处理器的高性能同步原语自动插入方法[J]. 计算机研究与发展. CSTR: 32373.14.issn1000-1239.202440093
引用本文: 李帅江, 张馨元, 赵家程, 田行辉, 石曦予, 徐晓忻, 崔慧敏. 面向昇腾处理器的高性能同步原语自动插入方法[J]. 计算机研究与发展. CSTR: 32373.14.issn1000-1239.202440093
Li Shuaijiang, Zhang Xinyuan, Zhao Jiacheng, Tian Xinghui, Shi Xiyu, Xu Xiaoxin, Cui Huimin. Automatic Insertion of High-Performance Synchronization Primitives for Ascend Processors[J]. Journal of Computer Research and Development. CSTR: 32373.14.issn1000-1239.202440093
Citation: Li Shuaijiang, Zhang Xinyuan, Zhao Jiacheng, Tian Xinghui, Shi Xiyu, Xu Xiaoxin, Cui Huimin. Automatic Insertion of High-Performance Synchronization Primitives for Ascend Processors[J]. Journal of Computer Research and Development. CSTR: 32373.14.issn1000-1239.202440093

面向昇腾处理器的高性能同步原语自动插入方法

基金项目: 国家重点研发计划项目(2022ZD0116316);国家自然科学基金重点项目(62232015);中国科学院计算技术研究所创新课题(E361010, E261110).
详细信息
    作者简介:

    李帅江: 1998年生. 博士研究生,CCF学生会员. 主要研究方向为异构编程

    张馨元: 1994年生. 硕士,工程师. 主要研究方向为异构编译

    赵家程: 1989年生. 博士,副研究员. 主要研究方向为异构编程、机器学习系统和领域定制架构编译基础设施

    田行辉: 1989年生. 硕士. 主要研究方向为AI编译器

    石曦予: 1996年生. 硕士,工程师. 主要研究方向为异构编程

    徐晓忻: 1986年生. 硕士. 主要研究方向为AI系统优化

    崔慧敏: 1979年生. 博士,研究员. 主要研究方向为编译器构建、编译优化和异构计算

    通讯作者:

    赵家程(zhaojiacheng@ict.ac.cn

  • 中图分类号: TP314

Automatic Insertion of High-Performance Synchronization Primitives for Ascend Processors

Funds: This work was supported by the National Key Research and Development Program of China (2022ZD0116316), the Key Program of the National Natural Science Foundation of China (62232015), and the Innovation Funding of Institute of Computing Technology, Chinese Academy of Sciences (E361010, E261110).
More Information
    Author Bio:

    Li Shuaijiang: born in 1998. PhD candidate, Student member of CCF. His main research interest includes heterogeneous programming

    Zhang Xinyuan: born in 1994. Master, engineer. Her main research interest includes heterogeneous compile

    Zhao Jiacheng: born in 1989. PhD, associate professor. His main research interests include heterogeneous programming, machine learning systems, and compiler infrastructure for domain specific architectures

    Tian Xinghui: born in 1989. Master. His main research interest includes AI compiler

    Shi Xiyu: born in 1996. Master, engineer. Her main research interest includes heterogeneous programming

    Xu Xiaoxin: born in 1986. Master. His main research interest includes AI system optimization

    Cui Huimin: born in 1979. PhD, professor. Her main research interests include compiler construction, compiler optimization and heterogeneous computing

  • 摘要:

    指令级并行(instruction level parallism,ILP)是处理器体系结构研究的经典难题. 以昇腾为代表的领域定制架构将更多的流水线细节暴露给上层软件,由编译器/程序员显式控制流水线之间的同步来优化ILP,但是流水线之间的物理同步资源是有限的,限制了ILP的提升. 针对这一问题,提出一种面向昇腾处理器的高性能同步原语自动插入方法,通过引入“虚拟同步资源”的抽象将同步原语的插入和物理同步资源的选择进行解耦. 首先提出了一种启发式算法在复杂的控制流图上进行虚拟同步原语的插入,随后通过虚拟同步原语合并等技术,将虚拟同步资源映射到有限数量的物理同步资源上,并同时在满足程序正确性与严苛硬件资源限制的前提下,根据指令间的偏序关系删除程序中冗余的同步原语. 使用指令级与算子级基准测试程序在昇腾910A平台上的实验表明,该方法自动插入同步原语的程序在保证正确性的基础上,整体性能与专家程序员手动插入同步原语接近或持平.

    Abstract:

    Instruction-Level Parallelism (ILP) is a classic challenge in processor architecture research. Domain-specific architectures, such as the Ascend processor, expose more pipeline details to upper-layer software, and compilers/programmers explicitly control the synchronization between pipelines to optimize ILP. However, the physical synchronization resources between pipelines are limited, which limits the improvement of ILP. To address this issue, a high-performance automatic synchronization primitive insertion method for the Ascend processor is proposed. By introducing the abstraction of "virtual synchronization resources," this method decouples the insertion of synchronization primitives from the selection of physical synchronization resources. Firstly, a heuristic algorithm is proposed to insert virtual synchronization primitives in complex control flow graphs. Then, a significant number of virtual synchronization resources are mapped to an extremely limited number of physical synchronization resources through virtual synchronization primitive merging and other techniques. At the same time, redundant synchronization primitives in the program are removed based on the partial order relationship between instructions, while ensuring program correctness and stringent hardware resource constraints. Experiments on the Ascend 910A platform using instruction-level and operator-level benchmark programs show that the programs with automatically inserted synchronization primitives achieve performance comparable to or on par with those manually inserted by expert programmers, while ensuring correctness.

  • 对指令级并行(instruction level parallism,ILP)的追求是体系结构软硬件研究的永恒问题. 通用CPU由微架构承担了挖掘ILP的核心职责. 具体来说,通用CPU的微架构设计了众多复杂的控制逻辑,通过乱序执行[1]、分支预测[2]、计分板动态调度[3]、寄存器重命名[4]等技术来在充分挖掘ILP的基础上保证程序的执行不会违反指令间的数据/控制依赖,从而在提升性能的同时保证程序正确性.

    但是随着以深度学习为代表的人工智能技术在图像处理[5-6]、自然语言处理[7-8]、蛋白质结构预测[9-10]等技术领域的成功,人工智能应用开始爆炸式发展,同时驱动芯片进步的摩尔定律的终结,使得传统的CPU愈发难以满足各类AI应用不断增大的算力需求[11],由此催生了各类领域定制架构用于加速AI计算,如通用GPU(general purpose GPU,GPGPU)(如基于SIMT架构的英伟达 GPU[12],AMD GPU[13]和海光深度计算处理器(deep computing unit,DCU)[14]),专用的神经网络处理器(neural processing unit,NPU)(如寒武纪的MLU(machine learning unit)[15]和华为昇腾[16])等.

    与CPU不同,领域定制架构为了利用有限的芯片面积最大化算力,将指令级并行挖掘的职责留给了软件来负责. 具体来说,和传统的通用处理器类似,领域定制架构的单核上通常集成了多种功能不同的流水线单元,如直接内存访问(direct memory access,DMA)流水线、张量计算流水线、向量计算流水线等,但是和传统的通用处理器不同,领域定制架构不同的流水线单元有相对独立的指令队列,队列之间(或者说流水线之间)微架构不提供执行序的保证,而在传统的通用处理器当中,微架构则会通过计分板动态调度等技术保证流水线之间的数据/控制依赖的正确性. 为此,在领域定制架构当中,微架构通常会暴露更多的流水线间同步接口,由上层软件(编译器或者最终开发者)直接对流水线间的执行顺序进行编程,解决必要的数据/控制依赖,从而通过软硬件协同的方式兼顾完备性和效率,但是也大幅提升了软件的负担.

    以昇腾系列处理器为例,其内部有6个独立的执行单元:标量计算单元(scalar unit)、向量计算单元(vector unit)、矩阵计算单元(cube unit)以及3个数据搬运单元,分别完成其对应的计算、存储等功能,每个执行单元有一条独立的指令队列. 该6个执行单元之间是无序的,需要程序员或编译器手动插入显式同步原语来确保不同执行单元之间按照依赖关系执行[17-18].

    尽管当前的GPGPU如英伟达GPU和AMD GPU也面临相同的问题,但是微架构暴露的流水线同步接口具有巨大的差异,从而使得这些技术难以直接适用于昇腾架构. 具体来说,英伟达的GPGPU微架构提供了一种依赖寄存器形式的流水线同步方法,由PTXAS编译器在生成体系结构相关的汇编过程当中根据指令依赖关系为每条指令设置对应的依赖寄存器,来进行依赖关系的管理. 而AMD GPU则提供了一种计数器形式的同步原语. 而正如上文所言,昇腾架构的底层同步原语与通用GPGPU的存在着巨大的差异,昇腾架构提供了一种基于set-wait(也称post-wait)抽象的流水线间同步方法,硬件提供set/wait指令,可以指定1对执行单元之间形成“锁”的关系,以此来进行不同执行单元间的同步.

    当前,英伟达与AMD GPU已实现编译器自动插入同步原语,与处理器协同调度来维护指令间的数据依赖. 而昇腾处理器采用的set-wait同步原语在硬件上的自动插入问题仍未解决. 因此,程序员在昇腾处理器上进行算子编程时需要手动插入同步原语[17-18],更容易出现错误. 然而手动插入同步原语需要编程人员对底层硬件有足够深入的了解,才能保证程序的正确性与性能,大大增加了编程难度和工作量:高性能算子库为了获得较好的性能,往往需要数月的时间来对每个算子进行专门调优,阻碍了新算法的发展[19]. 因此,如何在昇腾处理器上编译器自动插入同步原语,以实现和处理器的协同调度来保证指令间数据依赖,成为昇腾处理器算子编程亟待解决的问题.

    我们深入研究发现,当前在昇腾处理器上实现同步原语的自动插入需要面临以下3个挑战:

    挑战1. 复杂的程序控制流图与执行路径. 同步语句post-wait需要根据指令间的数据依赖严格地一一对应,但是复杂的控制流图与执行路径使得为依赖的指令插入严格对应的post-wait变得极为挑战:既要保证依赖指令间的数据依赖得到满足,又要求post-wait同步指令一一配对.

    挑战2. 严苛的同步资源限制. 在昇腾架构中,post-wait同步原语需要置位/释放指定的硬件资源(信号量/标志位),而这些硬件物理资源的数量是极为有限的. 这一问题对于昇腾架构异常突出,AMD GPU采用的计数器同步原语不限制同步的资源数量,英伟达GPU采用的依赖寄存器同步原语,每条指令都可以设置或等待一定数量的依赖寄存器,限制较为宽松. 而在现实昇腾应用中,同时活跃的需要编译器维护的依赖关系可高达数十个,软件依赖与硬件同步资源二者间数量级的巨大差距给自动的同步方法带来了严苛的挑战.

    挑战3. 如何在满足上述限制的同时尽可能提升性能. 同步原语的自动插入不仅要保证正确性,还应该尽可能提升硬件利用效率. 而冗余的同步原语会导致过多非必要的阻塞,降低硬件单元的执行效率. 因此需要在满足硬件资源限制与程序正确性的同时,尽可能去除冗余同步原语,以提升性能.

    为了解决上述挑战,本文提出了一种面向昇腾处理器的高性能同步原语自动插入方法. 我们提出“虚拟同步资源”的概念,将昇腾处理器上的同步原语插入问题解耦为3部分:1)虚拟同步原语插入;2)冗余虚拟同步原语删除;3)物理同步资源分配. 我们基于LLVM实现了该方法,并在昇腾910A处理器上进行了实验,实验表明,该自动插入方法在保证正确性的基础上,整体性能与专家程序员手动插入同步原语接近或持平.

    本文的主要贡献包括4个方面:

    1)提出“虚拟同步资源”概念,并基于该概念实现了为异构处理器上的程序进行虚拟同步原语插入,解决了复杂的控制流图和执行路径所带来的一系列问题;

    2)通过同步原语合并等技术,解决了大量虚拟同步资源到极有限数量的物理同步资源的映射问题;

    3)在满足程序正确性与严苛硬件资源限制的同时,根据指令间的偏序关系对程序中冗余的同步原语进行删除,进一步提升了硬件的利用效率;

    4)基于LLVM实现了该方法,并在昇腾910A平台上进行了实验,首次实现了昇腾平台上同步原语的自动化插入.

    昇腾处理器是华为公司推出的面向人工智能场景的领域定制架构处理器,其由AI CPU与AI Core共同组成片上系统(system of chip,SOC),其中AI Core负责神经网络运算,提供绝大部分算力. 通过配置不同产品中AI Core的数量(1~32),可以满足云-边-端不同场景对AI算力的需求. 例如,昇腾910处理器包含32个AI Core,FP16算力达256 TFLOPS,INT8算力为512 TOPS;配备32GB HBM(high bandwidth memory),带宽达1 200GBps. 其SOC架构如图1所示:

    图  1  昇腾910架构[16]
    Figure  1.  Ascend 910 architecture

    AI Core内部结构如图2所示. 在存储方面,AI Core内部由L1(L1 buffer),L0A(L0A buffer),L0B(L0B buffer),L0C(L0C buffer),UB(unified buffer)等多个不同功能和层次的存储单元组成:L1大小为1MB,为一块较大的数据中转区,可暂存一些反复使用的数据,减少访问片外内存的次数,要求512B对齐;L0A与L0B大小均为64KB,分别存放矩阵计算单元的输入左矩阵与右矩阵,要求512B对齐;L0C大小为256KB,用于存放矩阵计算单元的输出数据,进行累加乘计算时,也作为输入数据的一部分,要求512B对齐;UB大小为256KB,为统一缓冲区,存放向量与标量计算的输入与输出数据,要求32B对齐[18].

    图  2  AI Core架构[16]
    Figure  2.  AI Core architecture

    在计算方面,AI Core包含矩阵计算单元、向量计算单元、标量计算单元3部分:矩阵计算单元负责矩阵乘计算,其每次执行完成1个fp16精度的16×16与16×16的矩阵乘;向量计算单元负责激活函数等非矩阵乘计算;标量计算单元负责循环控制、分支判断、地址计算以及基本的算术运算等功能. 其中,矩阵计算单元算力最强,提供了AI Core的绝大部分算力,但也最不灵活,只能进行矩阵乘计算;标量计算单元算力最弱,但其计算最灵活;向量计算单元则介于二者之间[18].

    计算与存储单元的联系:矩阵计算单元的左操作数来自L0A,右操作数来自L0B,矩阵乘结果与中间结果放在L0C;向量计算单元所有源数据与计算结果都要求位于UB中;标量计算单元的操作数可以存放在寄存器与UB中. 由于复杂的对应关系,AI Core内部有3个专门的存储转换引擎(memory transfer engine,MTE)单元进行数据传输与搬运:MTE1负责从L1传输数据到L0A/L0B/UB,或者用SPR初始化L0A/L0B;MTE2负责L2/HBM/DDR到L1/L0A/L0B/UB;MTE3负责UB到L2/HBM/DDR [18].

    3个计算单元与3个MTE单元一起,共6个独立的执行单元,每个单元对应一条指令队列,共6条独立的指令队列,如表1所示. 同一指令队列内部顺序执行,不同指令队列之间乱序执行. 硬件不保证不同队列间的同步与数据依赖关系,仅提供同步原语. 因此,需要程序员或编译器向程序中插入同步指令来确保不同队列间的数据依赖关系.

    表  1  6个指令队列[18]
    Table  1.  Six Instruction Queues
    队列缩写含义队列内指令功能
    S标量指令队列标量计算
    V向量指令队列向量计算
    M矩阵指令队列矩阵乘计算
    MTE1MTE1指令队列L1到L0A/L0B/UB,或者用
    SPR初始化L0A/L0B
    MTE2MTE2指令队列L2/HBM/DDR到L1/L0A/L0B/UB
    MTE3MTE3指令队列UB到L2/HBM/DDR
    下载: 导出CSV 
    | 显示表格

    图3所示,昇腾处理器采用set-wait(也称post-wait)形式的同步原语. 硬件提供set/wait指令,可以指定1对执行单元之间形成“锁”的关系:当set指令之前的所有读写操作都完成之后,set指令执行,将硬件中对应的标志位设置为1;另一个执行单元执行到对应的wait指令时,如果发现对应标志位为0,该执行单元的后续指令将一直被阻塞;如果发现对应标志位为1,则将对应标志位设置为0,同时开始执行后续指令.

    图  3  昇腾处理器同步原语示例
    Figure  3.  An example of Ascend processor synchronization primitives

    为便于陈述,下文将标志位称为ID,“物理ID”即表示“物理标志位”.

    图3所示, src1指令与dst1指令之间有依赖关系(虚线所示),abcd为指令中的变量,必须等load a执行结束,b = a + 5才能开始执行. src1由访存单元(假设为pipe1)执行,dst1由计算单元(假设为pipe2)执行,因此需要在src1后插入set(pipe1,pipe2,0)指令,在dst1前插入wait(pipe1,pipe2,0)指令,表示使用第0个标志位. 下一对src2与dst2指令就使用第1个标志位,以此类推.

    多样的计算单元、复杂的多级内存层次与数据传输单元,加剧了同步原语自动插入的困难性. 在算子编程方面,昇腾提供了多种方式.

    TBE(tensor boost engine) TIK(tensor iterator kernel)与TBE DSL(domain-specific language)调用封装好的API接口编程,以避免处理同步原语,但其调用封装接口进行编程的形式层次相对较高,限制了算子编程的灵活性,不利于昇腾芯片极致性能的挖掘. CCE(cube-based computing engine)是华为推出的类C编程语言,相比TBE TIK与TBE DSL更接近底层,但其需要程序员手动插入同步原语[17];Ascend C是昇腾2023年新发布的算子编程语言,将向量算子计算过程分为CopyIn,Compute,CopyOut 这3个步骤,矩阵算子计算过程分为CopyIn,Split,Compute,Aggregate,CopyOut 这5个过程[18],其本质上依然是通过程序员手动划分计算过程,来控制数据传输、数据计算等执行单元间的同步,算子编程的复杂度依然较大.

    关于2个执行单元间物理ID的数量,华为没有公开资料说明,但通过分析华为Ascend C编程语言对各种算子生成的.cce文件可知,.cce文件中同步语句使用到的物理ID,范围均在0~3之间,因此可判断,昇腾处理器硬件上任意2个执行单元间物理ID数量为4(如果物理ID数量大于4,Ascend C生成的.cce文件就会使用到更多的物理ID,而不是只使用0~3这4个物理ID).

    因此,由于复杂的控制流图与执行路径、严苛的硬件资源限制、对性能的要求,昇腾处理器的同步原语自动插入问题目前仍未解决,增大了算子编程的难度和工作量.

    CPU上多线程程序间显式同步原语的插入是一个经典问题,之前已经有较多研究. 但据我们所知,目前尚无面向异构处理器的post-wait硬件同步原语自动插入研究.

    Nicolau等人[20]探索了CPU上多线程程序之间的post-wait同步原语放置,通过代码移动等技术来优化post和wait同步原语的放置,增强具有迭代依赖关系的循环的线程级并行性. Zhai等人[21]使用基于CFG的分析来确定在哪里适当地放置wait和signal同步语句.

    在CPU多线程间同步原语消除上,Nicolau等人[22]提出根据迭代之间的依赖关系,消除CPU上多线程间同步的冗余wait语句. Tseng[23]提出了消除屏障(barrier)同步的方法. Chen等人[24]提出了消除DOACROSS循环中冗余同步的技术. 随后,Alrdrich等人[25]、Bogda等人[26]提出了在Java中消除不必要同步的技术. Han等人[27]也提出了一种新的解决方案,使用fork-join模型来减少多线程系统中所需的同步总数. 而本文是根据指令间的偏序关系,同时消除冗余的硬件post与wait指令.

    作为当前的主流AI加速器之一,GPU上同步语句的插入已经有较多研究. Li等人[28]在GPU上构建了生产者-消费者(即post-wait)同步原语,用于线程块中协作线程之间的细粒度数据同步;Liu等人[29]通过数据依赖分析来为GPU程序插入同步语句,并消除冗余同步语句. Moses等人[30]通过分析同步语句前后的指令的内存行为,判断该同步语句是否必要,实现同步语句的移动与冗余消除;Sorensen等人[31]实现了跨工作组(work-group)而不仅仅是在1个工作组内的GPU 同步指令. 然而,上述这些工作是针对GPU的,由于硬件架构差异较大,硬件同步模型不同,所以上述工作的方法并不适用于昇腾处理器.

    综上所述,先前的研究大多关注于CPU上多线程之间,或GPU上不同线程间的同步问题,昇腾处理器由于其硬件架构、并行模型等方面与传统CPU及GPU有巨大差异,面临:1)苛刻的硬件资源限制;2)复杂的程序控制流图与执行路径;3)满足前述限制的同时尽可能提升性能等挑战,因此现有方法无法解决昇腾处理器中post-wait原语的自动插入问题.

    本文提出的同步原语高效自动插入方法探索并首次实现了昇腾处理器上硬件同步原语的自动插入,以降低编程人员开发负担,同时通过优化技术充分尽可能减少硬件单元阻塞,充分发挥硬件的算力. 本节首先介绍了方法的整体结构. 然后分别介绍方法各步,包括虚拟同步原语插入,冗余同步原语删除,物理同步资源分配.

    该同步原语自动插入方法由3阶段构成,如图4所示. 该方法的输入是未插入同步原语的中间表示程序. 同时,为了解耦问题的复杂度,我们在处理过程中引入了“虚拟硬件资源”的概念,在插入过程中可以使用无限多个“虚拟硬件资源”,对应地,使用“虚拟硬件资源”的同步原语称为“虚拟同步原语”. 在插入同步原语之前,首先会根据数据流方程,对输入程序进行依赖分析,得到程序中指令间存在的读后写(write after read,WAR)、写后读(read after write,RAW)以及写后写(write after write,WAW)3种数据依赖关系.

    图  4  自动插入过程总览
    Figure  4.  Overview of automatic insertion process

    得到数据依赖关系后,第1阶段解决插入的正确性问题. 首先对程序控制流图进行兄弟结点插入、循环规范化等处理,使程序结构满足后续处理过程的要求;然后向程序中初步插入虚拟同步原语;最后处理程序中的分支与循环结构,解决控制流图和执行路径带来的复杂性,满足程序的正确性:1)post与wait原语两两配对;2)程序指令间的依赖关系得到保证.

    插入虚拟同步原语后,为了在确保程序正确性的基础上尽可能优化性能,第2阶段根据指令间的偏序关系,去除第1阶段插入的冗余同步原语.

    第3阶段负责将虚拟ID映射到硬件资源所支持的有限数量的物理ID上,以得到最终的结果. 与虚拟寄存器到物理寄存器的映射不同,在物理寄存器容纳不下虚拟寄存器时,可以选择将一些物理寄存器中的值溢出(spill)到内存(或cache)中. 在本文中,虚拟ID映射到物理ID时,如果同时活跃的虚拟ID数大于物理ID数,无法像物理寄存器那样溢出. 所以第3阶段首先对程序中的虚拟ID进行合并处理,使得在任意程序点,同时活跃的虚拟ID数目都不超过N. 然后使用图着色算法将虚拟ID分配到物理ID,完成整体流程.

    由于数据流分析算法已经有许多比较成熟的研究,因此使用数据流分析得到依赖关系的过程不在本文讨论范围. 本节其他小节将依次介绍虚拟同步原语插入、冗余同步原语删除、物理ID分配等内容.

    为了将控制流图及执行路径的复杂性与物理资源的限制这2个问题尽可能解耦,降低问题复杂度,我们借鉴了编译优化中寄存器分配过程的“虚拟寄存器”概念,引入了“虚拟ID(虚拟硬件资源)”. 其与物理硬件资源的不同在于,虚拟硬件资源有无限多个,没有物理硬件资源那样严苛的限制,以在插入同步原语时不用考虑硬件资源限制问题.

    在插入同步原语之前,首先需要处理程序的分支和循环等结构,插入一些空的基本块(basicblock,BB),使程序控制流图变成“规范化”形式:

    1)所有分支结构都有then和else这2个分支,即为if-then-else的形式;

    2)所有循环结构都满足规范形式:

    ① 有1个前置头结点(preheader);

    ② 只有1个回边(backedge);

    ③ 循环只有1个出口基本块(exit block),且其被循环头结点(loop header)支配.

    传统控制流图规范化着重于控制流图的特例——循环结构的处理,如LLVM中的loop-simplify pass把不规整的循环规范化为标准的循环简化形式(loop simplify form)[32];而本文的方法关注更加通用的控制流:一方面,像LLVM一样,对循环进行处理;另一方面,需要把if-else结构规范化为if-else-then结构,以保证程序在任意执行路径上,set与wait语句都能一一匹配.

    图5(a)所示,为处理前的if-then形式的分支结构,通过规范化处理之后,变为图5(b)所示的if-then-else分支结构.

    图  5  分支结构规范化示例
    Figure  5.  An example of normalization of branch structure

    图6(a)所示为处理前的循环结构,通过规范化处理之后,变为图6(b)所示结构,加入了前置头结点(基本块G),插入了1个退出基本块(基本块H),使得循环只有1个退出基本块.

    图  6  循环结构规范化示意
    Figure  6.  An example of normalization of loop structure

    规范化循环结构主要包括为循环结构添加前置结点和出口基本块. 规范化分支结构是指为分支结构中的一个基本块添加兄弟结点;兄弟结点是指没有前驱和后继关系的多个基本块,并且在控制流图中具有相同支配结点和后支配结点的基本块. 对控制流图进行规范化的过程如图7所示.

    图  7  规范化控制流图过程
    Figure  7.  The process of normalizing the control flow graph

    根据数据流分析得到的结果,可得到程序中所有的依赖指令对〈src,dst〉,其中dst指令依赖于src指令,即必须src先执行完,dst才能执行.

    遍历〈src,dst〉指令对,依次对每对指令初步插入虚拟同步指令. 该步骤插入的同步指令使用虚拟ID,按照遍历的顺序,对应流水线的虚拟ID从0开始递增使用.

    1)若src指令和dst指令不在循环内,则直接在src之后插入set指令,在dst之前插入wait指令. 如图8所示,set和wait为初步插入的同步原语.

    图  8  初步插入同步原语示例
    Figure  8.  An example of preliminary insertion of synchronization primitives

    2)若src指令或dst指令至少有1个在循环内,则需找到要插入set和wait的最外层循环,根据二者最外层循环之间的关系(包含与否),来判断set与wait的插入位置(set是否要外提到外层循环的出口基本块,wait是否要外提到外层循环的前置头结点,即preheader)中.

    图9所示为一种控制流图示例. 图9中src指令在循环L0内,且dst指令不在src所在的最外层循环L中(如:dst指令不在循环内,或dst指令在循环L1内,但循环L1不在循环L内),可以在最外层循环L的出口基本块中插入src指令对应的set指令.

    图  9  src/dst指令在循环中示例
    Figure  9.  An example of src/dst instructions in a loop

    外提set的原因在于:如果紧跟着在src后插入set指令,则程序在每次执行循环L0或者最外层循环L时,都会执行1次该处的set,将该指令中ID标志位进行置位,这种连续多次置位同一个标志位的行为会导致硬件出错,因此需要将该set指令放到最外层循环L外,例如在最外层循环L的出口基本块中插入该set.

    同样地,在与上述情况对应的一些情况下,wait需要外提到外层循环的前置头结点(即preheader)中,以避免硬件报错.

    在根据上述方法为依赖指令对初步插入虚拟同步原语之后,还需要对部分指令对插入额外的虚拟同步原语. 以确保在程序执行的任何路径上,set与wait都可以两两对应,从而保证程序正常执行. 而由于控制流图和执行路径的复杂性,额外虚拟同步原语的插入是自动插入同步原语过程中面临的最大挑战之一.

    图8所示,在初步插入同步原语后,若程序执行路径为A ->C->D,则路径上的set与wait相互配对;若执行路径为A->B->D,则该路径上就只有set,没有与之配对的wait,导致硬件错误. 因此,应该在基本块B中插入1个额外的wait指令,以保证配对.

    值得注意的是,额外虚拟同步原语与前述为〈src,dst〉指令对所初步插入的虚拟同步原语所使用的虚拟ID相同. 具体地,可以分为如下3种情况为指令对插入额外的虚拟同步原语.

    1)src指令和dst指令之间具有跨迭代依赖关系.

    跨迭代依赖关系是指,在执行第i+1次循环迭代的过程中,dst指令的执行依赖于之前迭代(如第i次,第i-1次等)的src指令的结果;其中,i为大于或等于1的正整数.

    图10(a)所示,该情况下若不插入额外虚拟同步原语,在第1次执行该循环的过程中,在执行到wait指令时,由于在之前未执行set指令,wait指令中虚拟ID对应的标志位没有被置位,会导致程序一直等待无法继续执行. 因此需要在该循环的preheader中插入1条set指令.

    图  10  src与dst指令存在跨迭代依赖关系示例
    Figure  10.  An example of cross-iteration dependency relationship between src and dst instructions

    如果仅在循环体中插入上述set指令,则在执行最后一次循环的过程中,执行完循环内的set指令之后,会将对应的标志位置位,此时程序跳出循环,该标志位没有被释放,会一直处于置位状态,导致程序执行出错. 因此需要在该循环的退出基本块再插入1条wait指令. 插入额外的虚拟同步原语之后,如图10(b)所示.

    2)src指令和dst指令之间互不可达.

    src指令和dst指令之间互不可达是指不考虑循环多次迭代的情况下,src指令到达不了dst指令,且dst指令到达不了src指令. 例如src指令和dst指令位于判断语句之后的不同分支中,在执行一次循环的过程中只会执行src指令或dst指令中的1个.

    图11(a)所示,在这种情况下如果不插入额外的虚拟同步原语,当相邻2次循环都执行同一分支时,就会执行连续的set指令或连续的wait指令.

    图  11  src与dst指令互不可达示例
    Figure  11.  An example of mutually unreachable src and dst instructions

    因此,如果src指令和dst指令互不可达,则在紧邻着set指令之前再插入1条wait指令;在紧邻着wait指令之后再插入1条set指令. 如图11(b)所示.

    另外,图11所示的示例中,src指令和dst指令也是跨迭代依赖关系,会在其所在的循环的前置头结点(preheader)中插入1条set指令,在循环的出口基本块中插入1条wait指令. 如果没有本步骤,在进入循环时,如果第一次迭代执行左侧分支,就会出现连续执行set指令的情况;在最后一次循环退出前执行右侧的分支,就会出现连续执行wait指令的情况.

    3)在执行完前述2种情况后,该阶段会处理其他的由控制流结构引入的同步原语不配对问题,如图12(a)所示.

    图  12  其他由控制流结构引入的同步原语不配对示例
    Figure  12.  An example of other unpaired synchronization primitives introduced by control flow structures

    该步骤遍历各个基本块,对于访问的当前基本块A,需要根据A的各个前驱中的同步原语序列长度和当前基本块的入口状态,确定是否需要在各个前驱中插入额外的同步原语,以及插入set还是wait指令.

    其中,某个前驱基本块preA的序列长度是指,从程序入口处到preA的路径上,所有的set原语和wait等待指令的数量;当前基本块的入口状态是指,当前基本块的所有前驱中,序列长度最大的值.

    对于当前基本块A,如果A具有多个前驱,首先获取其各个前驱的序列长度,并据此得到A的入口状态. 然后对于其中任意1个前驱preA,如果preA的序列长度对2取余的余数mod2(preA)与A的入口状态对2取余的余数mod2(A)相等,则确定不需要在preA中插入额外的同步原语.

    若mod2(preA)与mod2(A)不相等;则确定需要在preA中插入set指令或wait指令. 在该情况下,具体地,若mod2(A)≠0,则需要在preA中插入1条额外set指令;若mod2(A)=0,则在preA中插入1条额外wait指令.

    之所以选择对2取余,是因为只有2种同步语句set与wait,二者是相互匹配的(可以理解为set与wait两两抵消),所以这里选择对2取余来比较,而不是整个同步原语序列的长度.

    图12(b)即为处理之后的结果.

    虽然上述同步原语插入方法相比朴素的插入方法已经尽可能减少了执行单元的阻塞,但依然可能存在过多的/冗余的同步原语,导致过多的阻塞,降低硬件的利用率,导致程序性能下降. 因此,在保证正确性的基础上,本阶段根据指令间的偏序关系删除冗余的post-wait同步原语,以进一步提升处理器利用率.

    对某一流水线对中插入的虚拟同步原语进行去冗余时,需要遍历所有的虚拟ID组合,根据不同虚拟ID对应的虚拟同步原语之间的偏序关系进行去冗余. 其中,如果该流水线对中插入了n个虚拟同步原语,即:虚拟ID范围是0~n-1,则虚拟ID的组合的数量是nn-1)个. 例如,如果该流水线对中插入了3个虚拟ID,分别是0,1,2,则虚拟ID的所有(IDA,IDB)组合数量为3×(3-1)=6个,分别为:(0,1),(0,2),(1,0),(1,2),(2,0),(2,1). 即:需要遍历这些组合来判断某对set-wait原语是否冗余.

    对于任意1组虚拟ID的组合(IDA,IDB),其对应的同步指令分别为:set(pipe1,pipe2,A),wait(pipe1,pipe2,A);set(pipe1,pipe2,B),wait(pipe1,pipe2,B)(以下简称setA,waitA,setB,waitB);对应的依赖指令对分别为:〈srcA,dstA〉,〈srcB,dstB〉.

    分为如下3种情况进行去冗余,分别如图13(a)~(c)所示. (偏序关系在这里指指令之间的支配与后支配关系,如下文所述. )

    图  13  冗余同步原语的3种情况
    Figure  13.  Three cases of redundant synchronization primitives

    1)srcA与srcB相同.

    此时,判断waitA与waitB之间是否存在支配关系. 所谓支配关系是指:从src指令出发,到IDB对应的所有wait指令(包括初步插入的与额外插入的),路径上都存在1个waitA指令,则认为waitA支配waitB. 如图13(a)所示.

    如果waitA支配waitB,则IDB即为冗余的,可以被消除,其对应的set与wait指令可以被删除. 其背后的逻辑是:如果waitA支配waitB,意味着从src到waitB的所有路径上,都必然会经过1个waitA,也即:waitB执行时,waitA必然已经执行过了,即src指令必然执行结束了,所以dstB必然可以执行了,因此IDB对应的虚拟set/wait指令可以删除,以减少对物理资源分配阶段的压力,从而避免引入更多阻塞,进而提升性能.

    2)dstA与dstB相同.

    此时,判断setA与setB之间是否存在后支配关系. 类似地,所谓后支配关系是指:从所有setB出发,到dst指令,路径上都存在1个setA,则认为setA后支配setB. 如图13(b)所示.

    如果setA后支配setB,则IDB即为冗余的,可以被消除,其对应的set与wait指令可以被删除. 其原因同上所述.

    3)如果src指令与dst指令都不相同.

    此时判断setA是否后支配setB,且同时waitA支配waitB,若是,则IDB是冗余的,可以被消除,其对应的set与wait指令可以被删除. 如图13(c)所示. 原因同上所述.

    由于昇腾处理器硬件上每对流水线对之间可用的标志位(ID)只有4个,而上述过程中在每对流水线对之间用到的虚拟ID至多可达到数十个,这中间存在数量级的差距是同步原语自动插入面对的最大挑战之一.

    该问题与虚拟寄存器到物理寄存器的分配问题有较大差距:1)寄存器分配问题中,如果同时活跃的虚拟寄存器数量大于物理寄存器数量,可以选择将某些物理寄存器中的值溢出到内存(或cache)中,但在本问题中,硬件同步资源不能被溢出,是一直被占用的;2)寄存器分配场景中,可供分配的物理寄存器数量一般有十数个甚至数十个,但在本问题中,硬件上每对流水线对之间可用的物理ID只有4个,大大增加了虚拟ID到物理ID的分配难度.

    因此,本文分2步解决该问题:1)合并虚拟ID,使得在任意程序点,同时活跃的虚拟ID数目不超过4;2)基于图着色算法将虚拟ID分配到物理ID上.

    该部分以逆后序(reverse post order,RPO)遍历程序中的基本块及其中的指令,遍历过程中记录当前活跃的虚拟ID数量. 发现当前活跃的ID数大于4时,就会开始对当前活跃的ID进行合并.

    优先从当前活跃的跨迭代的ID中选择合并,如对于IDA与IDB:IDA与IDB对应的额外set与wait指令需要位于同一个循环的前置头结点(preheader)与出口基本块(exit block). 判断IDA与IDB能否合并(以下依然简称IDA与IDB对应的同步指令分别为setA,waitA,setB,waitB;对应的依赖指令对分别为〈srcA,dstA〉,〈srcB,dstB〉):

    1)首先判断waitA能否支配waitB,或者反之. 如果可以,被支配的一方的wait指令可以删掉.

    2)在此基础上,判断setA能否后支配setB,或者反之. 如果可以,被后支配的一方的set指令可以删掉. 如:判断的结果是,删除waitA与setB,则对应的处理是:从程序中删除waitA与setB,删除IDB位于前置头和出口基本块中的额外set与wait指令.

    3)最后将waitB指令中的IDB,替换成IDA. 至此,从跨迭代的ID中选择IDA与IDB,完成了合并,合并之后的指令使用IDA,即IDB被删除了. 如图14所示,IDA与IDB合并.

    图  14  合并虚拟ID示例
    Figure  14.  An example of merging virtual IDs

    若跨迭代的ID中没有能合并的,则从当前活跃的非跨迭代的ID中选择合并. 任意1对同时活跃的非跨迭代ID都可以进行合并.

    需要注意的是,该步骤需要对同时活跃的虚拟ID进行合并,相当于在原本没有关系的2对指令间引入了额外的数据依赖,可能导致硬件执行单元出现额外的阻塞,降低程序性能. 如图14所示,原本dstB指令只需等待srcB指令执行结束就可开始执行,但在经过上述步骤合并IDA与IDB之后,dstB指令需要等待之后的srcA指令执行结束,才能开始执行,相当于在srcA与dstB之间引入了额外的依赖,使dstB指令提前阻塞了从srcB到srcA指令间的这段时间.

    因此,2.3节中的算法可以删除冗余的虚拟同步原语,从而减小对本阶段的压力,避免引入更多额外数据依赖,进而提升性能.

    该步骤执行结束后,在任意程序点,同时活跃的虚拟 ID数都≤4,也即一定可以分到4个实ID上.

    该部分以逆后序遍历程序中的基本块及其中的指令. 遇到1个set或wait指令,首先查看其对应的虚拟IDA是否已经被分配了实ID. 如果没有,则对该虚拟IDA进行实ID分配:

    1)首先检查,4个物理ID是否已经全部被分配出去了. 如果没有,则从还未被分配出去的物理ID中选择1个进行分配.

    2)如果都已经分配出去,则通过着色算法,遍历当前同时活跃的虚拟ID,得到当前虚拟IDA可以使用的物理ID有哪些,未被它们使用的物理ID,就是当前虚拟IDA可以使用的物理ID. 得到候选的可以使用的物理ID后,从这些候选物理ID中,选择1个分配给当前虚拟IDA:

    ① 判断候选者中是否存在这样的实ID,上一个使用它的虚拟IDB,从它对应的任意1个waitB指令,到IDA的任意1个setA指令,都有happen-before关系(即“发生在前”或“先行发生”关系,A happen-before B保证了A操作的结果对B操作可见,且A的执行顺序排在B之前)或不可达. 这里waitB happen-before setA,保证了IDB的所有waitB指令,都会在IDA的setA指令之前执行,也即:不会出现连续的对同一物理ID的set(这将导致处理器报错),因为在setB与setA之间,一定有1个waitB指令. 因此,如果存在这样的物理ID,直接分配该物理ID.

    ② 如果不存在,则挑选候选物理ID中的第1个, 找到上一次使用该物理ID的虚拟IDB,并找到IDB所有的waitB,紧邻其后插入1对反向的set和wait,以此来保证IDB与IDA之间的happen-before关系. 然后选择该物理ID进行分配.

    至此,为当前处理的虚拟IDA分配了一个实ID. 循环遍历完所有的虚拟ID后,可得到虚拟ID到实ID的映射. 然后根据该映射,将同步原语中的虚拟ID替换为实ID即可. 即完成了昇腾处理器上算子程序的同步原语插入.

    本节评估了本文提出的同步原语自动插入方法的正确性与性能.

    本文基于LLVM 8.0实现了昇腾处理器上的同步原语自动插入方法,测试环境为华为Atlas 800服务器(型号:9000),其CPU为4×鲲鹏920,操作系统为EulerOS 2.8,带有8张昇腾910A加速卡. 昇腾910A包含32个AI Core,FP16算力达256 TFLOPS,INT8算力为512 TFLOPS;配备32GB HBM,带宽达1200GBps. 实验在单张昇腾910A加速卡上进行. 在正式运行测试前均进行预热(warmup),并运行多次取数据平均值.

    本节使用微基准测试、单算子(包含矩阵类、向量类以及融合算子)基准程序进行测试. 对微基准测试,分别采用本文提出的方法自动插入同步原语,与使用CCE语言手动插入同步原语的方式进行比较. CCE语言手动插入的同步原语是由华为公司经验丰富的算子开发工程师完成的,并进行了针对性的同步插入的调优(约1~2人周/算子),用来保证比较的公平性. 对单算子测试,由于CCE语言需要手动处理的同步原语过于复杂,故采用TBE进行编程. 由于TBE是调用封装好的API接口进行神经网络算子编程的,故相比CCE编程少了很多灵活性.

    微基准测试覆盖了昇腾处理器上常见的向量类指令、矩阵类指令等;单算子基准测试程序覆盖了深度学习中大部分常见算子,如向量类算子LeakyRelu,LayerNorm,Softmax,Tanh等;矩阵类算子Matmul等,以及融合算子AttnScore.

    出于性能考虑,通常编译器在处理device代码调用时都会内联,如AMD ROCm[33]等,CCE也采用相同的内联策略,算子程序中的函数调用会被内联,再进行后续处理;因此只需要处理内联后的程序,即程序内已无函数调用的情况,不涉及不同函数间的依赖关系. 故测试程序中不包含算子中有函数调用的情况,不影响实验完整性.

    在昇腾处理器中,由于标量单元的运算能力很弱,是设计用来负责程序的流程控制(如循环控制、分支判断等),以及矩阵/向量等指令的地址和参数计算;因此出于性能考虑,一般使用向量与矩阵单元而不用标量单元来编写算子. 故实验中未对标量算子进行性能测试,不影响实验的完整性.

    经验证,微基准测试与单算子基准测试中,所有自动插入方法的执行结果均与基线程序(手动插入与TBE算子)相同,验证了自动插入方法在不同场景下的正确性.

    测试程序中矩阵乘(Matmul)等矩阵类算子最为复杂,其控制流图、执行路径等的规模与复杂度在常见算子均较为复杂. 在虚拟同步资源映射方面,矩阵乘算子2个执行单元间需要的虚拟ID最多达到了24个. 即:对于矩阵乘算子,自动插入方法将24个虚拟ID映射到了4个物理ID上,由此证明本文提出的自动插入方法,解决了大量虚拟同步资源到极有限数量的物理同步资源的映射问题,可以有效处理复杂程序的同步语句自动插入问题.

    本文中的性能,可视为算子执行时间的倒数,性能比可视为算子执行时间比例的倒数. 例如对某算子,自动插入方法的执行时间为T1,手动插入方法的时间为T2,则自动插入相比手动插入的时间比为T1/T2,性能比为T2/T1. 具体来说,实验中所描述的性能,指的是通过统计算子在昇腾处理器上执行的时间(单位为μs)所反映出来的性能. 如:自动插入方法相比手动插入的执行时间更短,则认为自动插入方法的性能更好. 由于对于同一算子,2种方法的测试数据是相同的,因此执行时间的差距背后,就是硬件利用效率的差距:同步语句的插入质量更好,硬件中不同执行单元间的阻塞就会越少,硬件利用效率就会更高,最终执行时间就会更短.

    微基准测试中各算子的执行时间(单位为μs)如图15所示. 在微基准测试中,本文自动插入方法的平均性能为手动插入方法的1.01倍.

    图  15  微基准测试的执行时间
    Figure  15.  Execution time of micro-benchmark test

    在单算子基准测试中,由于不同算子的执行时间相差较大,为了展示清晰,我们对数据做了归一化处理,以TBE的执行时间为基准,TBE与自动插入方法算子的执行时间如图16图17所示. 单算子基准测试的具体执行时间(单位为μs)如表2所示.

    图  16  向量类算子的归一化执行时间
    Figure  16.  Normalized execution time of vector operators
    图  17  矩阵与融合类算子的归一化执行时间
    Figure  17.  Normalized execution time of Cube and fusion operators
    表  2  单算子基准测试执行时间 μs
    Table  2.  Execution Time of Single Operator Benchmark Test
    算子类型 算子名称 TBE 自动插入
    向量 Cast 2.81 4.12
    Muls 3.6 3.35
    Sub 4.09 2.96
    GatherV2 326.45 335.5
    BroadcastTo 4.11 3.88
    Add 53.83 55.29
    LayerNorm 243.02 291.48
    Transpose 30.49 31.65
    RealDiv 306.41 308.87
    Softmax 1 674.98 1 867.39
    Gelu 804.64 683.12
    StridedSlice 4.57 5.8
    Tanh 4.44 5.88
    ClipByValue 1.92 1.88
    LogSoftmax 4 5.91
    OnesLike 1.44 2.35
    NLLLoss 2.44 4.11
    NLLLossGrad 5.04 4.52
    LogSoftmaxGrad 3.74 5.58
    LayerNormGrad 71.11 93.77
    GeluGrad 1 377.39 1 458.49
    SoftmaxGrad 931.54 1 394.93
    EmbeddingDenseGrad 427.18 476.86
    ApplyAdam 10 989.29 11 052.46
    maxpool3dgrad 28.8 25.7
    BNInferenceD 109 115
    LeakyRelu 111 116
    LeakyReluGrad 4 466 4437
    矩阵 MatMul 321.79 623.69
    BatchMatMul 252.27 584.46
    融合 AttnScore 570 536
    下载: 导出CSV 
    | 显示表格

    在单算子基准测试中,本文的自动插入方法性能平均可达TBE算子的0.89倍,同时在BroadcastTo,Gelu,maxpool3dgrad等算子上,自动插入方法可达到比TBE算子更好的性能.

    这是因为TBE算子调用的是经过专家高度优化的算子API实现,因此其平均性能比本文自动插入方法略高;但也同样因为调用算子API,阻碍了一些可能的常见优化如算子融合等的实施,因此本文提出的自动插入方法在一些算子上性能要好于TBE算子程序,最高可达到TBE的1.38倍.

    同时我们注意到,矩阵类算子Matmul与BatchMatmul的性能不佳,这是因为矩阵类算子的复杂度极高,且厂商往往投入极大的资源专门优化Matmul类算子库,集成了很多大量专家经验与领域知识,因此本文的自动插入方法相对性能较差.

    在融合算子AttnScore上,我们的自动插入方法相比于TBE调用封装算子的方法实现了1.07倍的加速,证明了相比于TBE,我们的自动插入方法可结合更多优化手段,以实现更好的性能目标.

    这里假设程序控制流图中的结点数为M,每个流水线对的依赖指令对(即虚拟ID)数量为N.

    本文提出的自动插入方法由3部分组成:

    1)虚拟同步原语插入.

    ① 规范化控制流图:遍历程序中所有的循环与if-else结构,按照规则进行处理即可. 故只需扫描2遍控制流图;时间复杂度为O(M);

    ② 初步插入同步原语:对每个流水线对,遍历所有的依赖指令对〈src,dst〉,对每个指令对,按照规则处理即可;时间复杂度为O(N);

    ③ 插入额外同步原语:对每个指令对,根据规则判断并找到需要插入额外同步原语的位置并插入即可,时间复杂度为O(N);遍历基本块处理其他情况,时间复杂度为O(M);该步骤的时间复杂度为O(N + M);

    2)冗余同步原语删除. 对每个流水线对,遍历所有虚拟ID的组合,组合数量为N(N-1),对每对虚拟ID组合,根据规则进行判断与去冗余即可,时间复杂度为O(N2);

    3)物理ID分配.

    ① 合并虚拟ID:遍历程序控制流图O(M),同时对活跃ID进行合并,最多进行N-4次合并O(N);每次合并的时间复杂度为O(1):合并时,处理当前活跃的5个ID,根据规则进行处理即可. 故该步骤时间复杂度为O(M + N);

    ② 图着色算法分配物理ID:由于合并虚拟ID已经确保任意程序点同时活跃的虚拟ID数≤4,一定可以分配到物理ID上,该步骤面临的问题复杂度相比传统图着色问题大为简化;故本文采用启发式算法,遍历程序控制流图处理所有虚拟ID,对于遇到的虚拟ID,按照简化后的启发式规则进行处理即可,因此该步骤时间复杂度为O(M+N);

    综上所述,整个算法的时间复杂度为O(N + M) + O(N2). 从实验中的基准测试程序可知,每个流水线对中依赖指令对的数量至多为数十个(即N的大小);同时遍历控制流图是编译优化中非常频繁的操作,算子程序的规模也属于正常程序的大小.

    传统编译器中常见的算法,如线性扫描寄存器分配算法,如果使用平衡二叉树来搜索插入点,则时间复杂度为O(V × log R);若线性搜索插入点时,最坏情况下会导致O(V × R)的时间复杂度;其中V为待分配的变量数,R为可用于分配的寄存器数[34]. 考虑到实际场景中算子程序中MN的数量要比传统程序中的VR小很多,因此实际执行时,本文方法的时间开销与编译器中常见的算法相持平. 综上所述,本文的自动插入方法不会引入过多的额外时间开销.

    在生成的代码量方面,本文的方法会为每对依赖指令对初步插入2条同步原语,并视情况插入一定数量的额外同步原语(由2.2.3节可知,额外同步原语最多为5条);同时冗余同步原语删除、虚拟ID合并都会删除一定数量的同步原语;而手动插入同步原语时,对每个依赖指令对也需要插入至少2条同步指令(因为要优化性能,手动插入也需要一定数量的额外同步原语,只不过因为可以利用程序语义知识,插入的额外同步原语数量会比本文的方法少);因此鉴于依赖指令对的数量,相比手动插入方法,本文的方法并不会对生成的代码量带来明显增加.

    总的来说,本文提出的自动插入方法在保证正确性的基础上,整体性能与专家程序员手动插入同步原语接近或持平,在编程灵活性、提高开发效率以及发挥硬件极端性能之间得到了一种平衡,大大方便了程序员进行算子编程,为进一步的深度优化打开了空间.

    本文提出了面向昇腾处理器的高性能同步原语自动插入方法. 通过“虚拟同步资源”的抽象解耦了同步原语插入和物理同步资源分配2个难题,通过自动插入虚拟同步原语,基于偏序关系消除冗余同步原语以及合并虚拟同步原语、基于图着色算法分配物理同步资源分配等技术,实现了昇腾处理器上同步原语的自动插入. 实验表明,本文提出的自动插入方法在保证正确性的基础上,整体性能可接近或持平专家程序员手动插入同步原语的水平.

    同时,本文的方法也存在可进一步完善之处,如对矩阵类复杂算子的优化尚有不足,未来将尝试更多的优化策略,进一步提升性能. 另外,虽然本文是在昇腾处理器平台上进行的实验,但我们相信本文提出的方法同样适用于其他采用post-wait硬件同步原语的异构处理器,因此未来可以拓展到其他平台进行实验.

    作者贡献声明:李帅江设计并实现算法、撰写论文;张馨元、石曦予协助设计实现了部分算法,设计实验方案,并进行实验对比分析;田行辉和徐晓忻协助进行实验对比分析;赵家程和崔慧敏对算法设计提出了关键性意见,并审阅与修订论文.

  • 图  1   昇腾910架构[16]

    Figure  1.   Ascend 910 architecture

    图  2   AI Core架构[16]

    Figure  2.   AI Core architecture

    图  3   昇腾处理器同步原语示例

    Figure  3.   An example of Ascend processor synchronization primitives

    图  4   自动插入过程总览

    Figure  4.   Overview of automatic insertion process

    图  5   分支结构规范化示例

    Figure  5.   An example of normalization of branch structure

    图  6   循环结构规范化示意

    Figure  6.   An example of normalization of loop structure

    图  7   规范化控制流图过程

    Figure  7.   The process of normalizing the control flow graph

    图  8   初步插入同步原语示例

    Figure  8.   An example of preliminary insertion of synchronization primitives

    图  9   src/dst指令在循环中示例

    Figure  9.   An example of src/dst instructions in a loop

    图  10   src与dst指令存在跨迭代依赖关系示例

    Figure  10.   An example of cross-iteration dependency relationship between src and dst instructions

    图  11   src与dst指令互不可达示例

    Figure  11.   An example of mutually unreachable src and dst instructions

    图  12   其他由控制流结构引入的同步原语不配对示例

    Figure  12.   An example of other unpaired synchronization primitives introduced by control flow structures

    图  13   冗余同步原语的3种情况

    Figure  13.   Three cases of redundant synchronization primitives

    图  14   合并虚拟ID示例

    Figure  14.   An example of merging virtual IDs

    图  15   微基准测试的执行时间

    Figure  15.   Execution time of micro-benchmark test

    图  16   向量类算子的归一化执行时间

    Figure  16.   Normalized execution time of vector operators

    图  17   矩阵与融合类算子的归一化执行时间

    Figure  17.   Normalized execution time of Cube and fusion operators

    表  1   6个指令队列[18]

    Table  1   Six Instruction Queues

    队列缩写含义队列内指令功能
    S标量指令队列标量计算
    V向量指令队列向量计算
    M矩阵指令队列矩阵乘计算
    MTE1MTE1指令队列L1到L0A/L0B/UB,或者用
    SPR初始化L0A/L0B
    MTE2MTE2指令队列L2/HBM/DDR到L1/L0A/L0B/UB
    MTE3MTE3指令队列UB到L2/HBM/DDR
    下载: 导出CSV

    表  2   单算子基准测试执行时间 μs

    Table  2   Execution Time of Single Operator Benchmark Test

    算子类型 算子名称 TBE 自动插入
    向量 Cast 2.81 4.12
    Muls 3.6 3.35
    Sub 4.09 2.96
    GatherV2 326.45 335.5
    BroadcastTo 4.11 3.88
    Add 53.83 55.29
    LayerNorm 243.02 291.48
    Transpose 30.49 31.65
    RealDiv 306.41 308.87
    Softmax 1 674.98 1 867.39
    Gelu 804.64 683.12
    StridedSlice 4.57 5.8
    Tanh 4.44 5.88
    ClipByValue 1.92 1.88
    LogSoftmax 4 5.91
    OnesLike 1.44 2.35
    NLLLoss 2.44 4.11
    NLLLossGrad 5.04 4.52
    LogSoftmaxGrad 3.74 5.58
    LayerNormGrad 71.11 93.77
    GeluGrad 1 377.39 1 458.49
    SoftmaxGrad 931.54 1 394.93
    EmbeddingDenseGrad 427.18 476.86
    ApplyAdam 10 989.29 11 052.46
    maxpool3dgrad 28.8 25.7
    BNInferenceD 109 115
    LeakyRelu 111 116
    LeakyReluGrad 4 466 4437
    矩阵 MatMul 321.79 623.69
    BatchMatMul 252.27 584.46
    融合 AttnScore 570 536
    下载: 导出CSV
  • [1]

    Tomasulo R M. An efficient algorithm for exploiting multiple arithmetic units[J]. IBM Journal of research and Development, 1967, 11(1): 25−33 doi: 10.1147/rd.111.0025

    [2]

    Smith J E. A study of branch prediction strategies[C] //Proc of the 8th annual Symp on Computer Architecture. New York: ACM, 1981: 135−148

    [3] Thornton J E. Parallel operation in the control data 6600[C] //Proc of the Fall Joint Computer Conf,Part II:Very High Speed Computer Systems. New York:ACM,1964:33−40(没有届
    [4]

    Moudgill M, Pingali K, Vassiliadis S. Register renaming and dynamic speculation: an alternative approach[C] //Proc of the 26th Annual Int Symp on Microarchitecture. Los Alamitos, CA: IEEE Computer Society, 1993: 202−213

    [5]

    Krizhevsky A, Sutskever I, Hinton G E. Imagenet classification with deep convolutional neural networks[C] //Proc of the 25th Int Conf on Neural Information Processing Systems - Volume 1. New York: Curran Associates Inc, 2012: 1097 - 1105

    [6] 张蕊,李锦涛. 基于深度学习的场景分割算法研究综述[J]. 计算机研究与发展,2020,57(4):859−875 doi: 10.7544/issn1000-1239.2020.20190513

    Zhang Rui, Li Jintao. A survey on algorithm research of scene parsing based on deep learning[J]. Journal of Computer Research and Development, 2020, 57(4): 859−875 (in Chinese) doi: 10.7544/issn1000-1239.2020.20190513

    [7]

    Brown T B, Mann B, Ryder N, et al. Language models are few-shot learners[C] //Proc of the 34th Int Conf on Neural Information Processing Systems. New York: Curran Associates Inc, 2020: 1877 - 1901

    [8]

    Vaswani A, Shazeer N, Parmar N, et al. Attention is all you need[C] //Proc of the 31st Int Conf on Neural Information Processing Systems. New York: Curran Associates Inc, 2017: 6000 - 6010

    [9]

    Jumper J, Evans R, Pritzel A, et al. Highly accurate protein structure prediction with AlphaFold[J]. Nature, 2021, 596(7873): 583−589 doi: 10.1038/s41586-021-03819-2

    [10] 李洪顺,于华,宫秀军. 一种只利用序列信息预测RNA结合蛋白的深度学习模型[J]. 计算机研究与发展,2018,55(1):93−101 doi: 10.7544/issn1000-1239.2018.20160508

    Li Hongshun, Yu Hua, Gong Xiujun. A deep learning model for predicting RNA-binding proteins only from primary sequences[J]. Journal of Computer Research and Development, 2018, 55(1): 93−101 (in Chinese) doi: 10.7544/issn1000-1239.2018.20160508

    [11]

    OpenAI. AI and compute[EB/OL]. [2024-02-01]. https://openai.com/ research/ai-and-compute

    [12]

    Nvidia. NVIDIA tensor cores[EB/OL]. [2024-02-01]. https://www.nvidia.com/en-us/data-center/tensor-cores/

    [13]

    AMD. AMD Instinct™ MI300 series accelerators[EB/OL]. [2024-02-01]. https://www.amd.com/en/products/accelerators/instinct/mi300.html

    [14] 海光. 海光深度计算处理器[EB/OL]. [2024-02-01]. https://www.hygon.cn/product/accelerator

    Hygon. Hygon deep computing unit[EB/OL]. [2024-02-01]. https://www.hygon.cn/product/accelerator (in Chinese)

    [15]

    Chen Tianshi, Du Zidong, Sun Ninghui, et al. DianNao: A small-footprint high-throughput accelerator for ubiquitous machine-learning[C] //Proc of the 19th Int Conf on Architectural Support for Programming Languages and Operating Systems. New York: ACM, 2014: 269–284

    [16]

    Liao Heng, Tu Jiajin, Xia Jing, et al. Ascend: A scalable and unified architecture for ubiquitous deep neural network computing [C] //Proc of the 27th IEEE Int Symp on High-Performance Computer Architecture (HPCA). Piscataway, NJ: IEEE, 2021: 789−801

    [17]

    Liao Heng, Tu Jiajin, Xia Jing, et al. DaVinci: A scalable architecture for neural network computing[C/OL] //Proc of the 31st Hot Chips Symp. Piscataway, NJ: IEEE, 2019 [2024-02-01]. https://www.old.hotchips.org/hc31/HC31_1.11_Huawei.Davinci.HengLiao_v4.0.pdf

    [18]

    Huawei. CANN 8.0. RC1. alpha001 [EB/OL]. [2024-02-01]. https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/80RC1alpha001/devguide/devguide/devguide_0001.html

    [19]

    Barham P, Isard M. Machine learning systems are stuck in a rut[C] //Proc of the 17th Workshop on Hot Topics in Operating Systems. Berkeley, CA: USENIX Association, 2019: 177−183

    [20]

    Nicolau A, Li Guangqiang, Kejariwal A. Techniques for efficient placement of synchronization primitives[C] //Proc of the 14th ACM SIGPLAN Symp on Principles and Practice of Parallel Programming. New York: ACM, 2009: 199−208

    [21]

    Zhai A, Steffan J G, Colohan C B, et al. Compiler and hardware support for reducing the synchronization of speculative threads[J]. ACM Transactions on Architecture and Code Optimization, 2008, 5(1): 1−33

    [22]

    Nicolau A, Li Guangqiang, Veidenbaum A V, et al. Synchronization optimizations for efficient execution on multi-cores[C] //Proc of the 23rd Int Conf on Supercomputing. New York: ACM, 2009: 169−180

    [23]

    Tseng C W. Compiler optimizations for eliminating barrier synchronization[J]. ACM SIGPLAN Notices, 1995, 30(8): 144−155 doi: 10.1145/209937.209952

    [24]

    Chen Dingkai, Yew P C. Redundant synchronization elimination for DOACROSS loops[J]. IEEE Transactions on Parallel and Distributed Systems, 1999, 10(5): 459−470 doi: 10.1109/71.770138

    [25]

    Aldrich J, Chambers C, Sirer E G, et al. Static analyses for eliminating unnecessary synchronization from java programs[C] //Proc of the 6th Int Symp on Static Analysis. Berlin: Springer, 1999: 19–38

    [26]

    Bogda J, Hölzle U. Removing unnecessary synchronization in Java [C] //Proc of the 14th ACM SIGPLAN Conf on Object-Oriented Programming, Systems, Languages, and Applications. New York: ACM, 1999: 35−46

    [27]

    Han H, Tseng C W. Compile-time synchronization optimizations for software DSMs[C] //Proc of the 1st Merged Int Parallel Processing Symp and Symp on Parallel and Distributed Processing. Piscataway, NJ: IEEE, 1998: 662−669

    [28]

    Li Ang, van den Braak G J, Corporaal H, et al. Fine-grained synchronizations and dataflow programming on GPUs[C] //Proc of the 29th ACM on Int Conf on Supercomputing. New York: ACM, 2015: 109−118

    [29]

    Liu Lifeng, Liu Meilin, Wang Chongjun, et al. Compile-time automatic synchronization insertion and redundant synchronization elimination for GPU kernels[C] //Proc of the 22nd Int Conf on Parallel and Distributed Systems. Piscataway, NJ: IEEE, 2016: 826−834

    [30]

    Moses W S, Ivanov I R, Domke J, et al. High-performance gpu-to-cpu transpilation and optimization via high-level parallel constructs[C] //Proc of the 28th ACM SIGPLAN Annual Symp on Principles and Practice of Parallel Programming. New York: ACM, 2023: 119−134

    [31]

    Sorensen T, Donaldson A F, Batty M, et al. Portable inter-workgroup barrier synchronisation for GPUs[C] //Proc of the 31st ACM SIGPLAN Int Conf on Object-Oriented Programming, Systems, Languages, and Applications. New York: ACM, 2016: 39−58

    [32]

    LLVM. Loop-simplify: Canonicalize natural loops[EB/OL]. [2024-03-20]. https://llvm.org/docs/Passes.html#loop-simplify-canonicalize-natural-loops

    [33]

    AMD. Register pressure in AMD CDNA2™ GPUs[EB/OL]. [2024-03-20]. https://gpuopen.com/learn/amd-lab-notes/amd-lab-notes-register-pressure-readme

    [34]

    Poletto M, Sarkar V. Linear scan register allocation[J]. ACM Transactions on Programming Languages and Systems (TOPLAS), 1999, 21(5): 895−913 doi: 10.1145/330249.330250

图(17)  /  表(2)
计量
  • 文章访问数:  21
  • HTML全文浏览量:  1
  • PDF下载量:  13
  • 被引次数: 0
出版历程
  • 收稿日期:  2024-02-02
  • 修回日期:  2025-01-19
  • 录用日期:  2025-03-02
  • 网络出版日期:  2025-03-02

目录

/

返回文章
返回