融合算子是指将多个独立的“小算子”融合起来成为一个“大算子”,多个小算子的功能和大算子的功能等价,大算子的性能优于独立的小算子。可以根据具体算法的实现自由融合Vector、Cube算子以达到性能上的收益。
比如对于LLM大模型中最核心的一个融合算子Flash Attention, 其核心实现如下图。图中的MatMul算子(Cube)、Scale算子(Vector)、Mask算子(Vector)、SoftMax算子(Vector)融合为一个大的算子Flash Attention。
当对算子的性能要求较高时,可以通过融合算子编程的方式,将矢量算子和矩阵算子进行融合,通过一个算子kernel函数来承载,由此来获得性能上的收益。下图展示了独立矢量算子和矩阵算子、Mix融合算子的执行耗时对比,由此可以看出为什么开发Mix融合算子会带来性能上的收益。
除了有效提升算子性能,充分发挥AI处理器的算力,融合算子还有如下优势:
总之,融合算子是一种优化计算的有效手段,可以提高计算效率和内存利用率,优化数据流,简化代码实现。
Ascend C提供融合算子的编程范式,方便开发者基于该范式表达融合算子的数据流,快速实现自己的融合算子。
融合算子数据流指融合算子的输入输出在各存储位置间的流向。以一个典型的Cube和Vector融合算子为例,逻辑位置间的数据流向如下图所示:
整个过程的示例代码如下(伪代码):
template<typename aType, typename bType, typename cType, typename biasType> __aicore__ inline void MatmulLeakyKernel<aType, bType, cType, biasType>::Process() { // 步骤1:初始化一个MatMul对象,将输入数据从Global Memory搬运到Cube核上。 uint32_t computeRound = 0; REGIST_MATMUL_OBJ(&pipe, GetSysWorkSpacePtr(), matmulObj); matmulObj.Init(&tiling); matmulObj.SetTensorA(aGlobal); matmulObj.SetTensorB(bGlobal); matmulObj.SetBias(biasGlobal); while (matmulObj.template Iterate<true>()) { // 步骤2:进行MatMul内部的计算。 // 步骤3:将MatMul的计算结果搬运到Vector核上。 reluOutLocal = reluOutQueue_.AllocTensor<cType>(); matmulObj.template GetTensorC<true>(reluOutLocal, false, true); // 步骤4:进行Vector矢量计算。 AscendC::LeakyRelu(reluOutLocal, reluOutLocal, (cType)alpha, tiling.baseM * tiling.baseN); reluOutQueue_.EnQue(reluOutLocal); // 步骤5:将输出结果搬运到Global Memory上 reluOutQueue_.DeQue<cType>(); ... AscendC::DataCopy(cGlobal[startOffset], reluOutLocal, copyParam); reluOutQueue_.FreeTensor(reluOutLocal); computeRound++; } matmulObj.End(); }