精度正常

性能优化的基础是算子运行得到正确的计算结果。评判计算结果正确性需要有一定的评判标准,即使用已知的正确的输出和实际结果进行比较。优化过程中,每次迭代修改后,都需要验证性能优化的新结果是否满足精度评判标准。

下文先介绍几个会影响精度正常的因素:

然后介绍编码过程中需要严格遵守的规则(禁止修改kernel函数参数),防止出现不必要的精度问题。

正确插入同步

正确计算偏移地址

算子使能多核计算时,需要在Tiling的时候确定单核的计算量,Kernel侧根据单核计算量进行地址的偏移。关于正确计算偏移地址的详细介绍可以参考LINK

比如如下样例的分配方案:数据整体长度TOTAL_LENGTH为8 * 2048个元素,平均分配到8个核上运行,每个核上处理的数据大小BLOCK_LENGTH为2048。x + BLOCK_LENGTH * GetBlockIdx()即为单核处理程序中输入x在Global Memory上的内存偏移地址,获取偏移地址后,使用GlobalTensor类的SetGlobalBuffer接口设定该核上Global Memory的起始地址以及长度。具体示意图请参考图1

xGm.SetGlobalBuffer((__gm__ half*)x + BLOCK_LENGTH * GetBlockIdx(), BLOCK_LENGTH);
图1 多核并行处理示意图

浮点数计算

禁止修改kernel函数参数

禁止修改kernel函数参数,不能对函数参数重新进行赋值和修改。例如:FlashAttentionKernel函数定义如下,其参数query、key、tilingData等为指针类型,该指针本身禁止修改。对于算子输入参数,指针指向的内容不可以修改;作为一个例外,算子输出参数,指针指向的内容可以进行修改。特别要强调一下,为了实现静态编译,无论是对tilingData指针本身,还是对tilingData指针指向的内容均禁止修改。

__aicore__ __global__ void FlashAttentionKernel(__gm__ uint8_t* query, __gm__ uint8_t* key, ..., __gm__ uint8_t* attention,..., __gm__ uint8_t* tilingData) {
    ......
}

【反例】

// 对Kernel函数参数重新赋值、对TilingData内容进行修改是不允许的,以下是错误示例
query = tmpQueryPtr;
key = tmpKeyPtr;
tilingData = tmpTilingDataPtr; 
tilingData[0] = 2;

【正例】

// 输入参数仅进行读操作
inputQueryGMTensor.SetGlobalBuffer(query);

// 输出参数attention指针本身是只读,但其指向的内存可以读写
outputAttentionGMTensor.SetGlobalBuffer(attention);
...
DataCopy(outputAttentionGMTensor, outputAttentionLocalTensor, count);