本节针对含有Matmul高阶API的算子,为排查算子精度问题是否为算子中Matmul高阶API调用方式导致,提供初步的问题定界和定位指导。如未特殊说明,下面均以Atlas A2 训练系列产品/Atlas A2 推理系列产品上的案例为例。
具体排查过程主要有如下六个步骤:
- CPU域调试,观察报错信息;
- Matmul Tiling是否有修改,修改是否合理;
- 算子隐藏Vector计算,仅调用Matmul API,算子功能是否正确;
- 单核执行,算子功能是否正确;
- 排查Matmul API的使用是否正确;
- 用于算子调测的golden脚本是否正确。
详细说明如下:
CPU域调试,观察报错信息
在完成算子代码的开发后,优先通过,调试算子的功能。在CPU域调试时,若编译或执行报错,日志中一般会有明显的报错信息。根据报错信息的提示内容,通常可以快速定位到问题所对应的代码位置。这种方法尤其对DataCopy参数设置错误导致的地址越界、算子Tiling参数设置不正确、其他内存越界访问等基础参数的使用问题,可以快速定位到具体原因。
案例:
以下为matmul算子核函数的代码片段。该段代码实现了根据Global Memory上的A、B矩阵和Tiling信息,计算每个核要使用数据的地址偏移、创建Matmul对象,计算得到Matmul结果。
[object Object]以下为上述代码在CPU域调试时输出的执行结果。以下示例中的路径请以实际情况为准。
[object Object]本案例中的算子有精度问题,于是使用CPU调测该算子功能,CPU运行后,根据报错信息提示的矩阵B的transpose未定义,查看矩阵B的相关设置代码,发现Matmul对象定义时未设置矩阵B的B_TYPE::isTrans,而SetTensorB接口设置了isTransB = true,导致执行报错。所以,此问题的根因为SetTensorB设置的isTransB值与B_TYPE不符。
Matmul Tiling是否有修改,修改是否合理
一般含有Matmul的算子Tiling实现中,通过调用接口获取Matmul Tiling,其数据类型为结构体,这时这组Tiling值是合法的。某些情况下,用户自定义了一组TCubeTiling参数值,或者,基于接口返回的TCubeTiling,自行修改了其中的部分值,这样的修改需要满足参数间的制约条件。
为获取所有Tiling参数值,需要打印Tiling参数相关的日志。设置日志环境变量,获取MatmulTiling参数值。设置环境变量的命令如下:
[object Object]在日志中搜索“MatmulTiling”关键字,参照,检查Tiling取值是否合法。若不满足某条约束条件,需要修改对应的相关参数,使该组TCubeTiling参数值均合法。
[object Object]例如,根据如上打印出的TCubeTiling参数,对照查看各个参数的取值,depthA1的取值应该等于stepM*stepKa或者stepM*stepKa*2,而depthA1的取值为10,既不等于stepM*stepKa=8,也不等于stepM*stepKa*2=16,不满足约束条件,因此需要校正depthA1的值。
算子隐藏Vector计算,仅调用Matmul API,算子功能是否正确
融合算子的代码既包含Matmul API,也包含Vector计算API。通过在算子代码中删除Vector计算API,只保留Matmul API,快速定界是否为Matmul API的错误使用导致了融合算子的精度问题。具体排查过程为:修改算子代码逻辑,删除Vector计算的代码,同步完成golden脚本相应修改,完成适配修改后,,观察算子结果是否正确。若算子结果正确,说明代码中Matmul API的使用方式正确,需要继续排查Vector计算是否正确;反之,若算子结果不正确,需要继续排查Matmul API的使用是否正确。
案例:
以融合算子matmul_leakyrelu为例,执行算子后,出现如下图所示的精度问题。
[object Object]修改算子代码,注释屏蔽LeakyRelu API计算,同时,需要适配修改相应的内存分配和涉及的同步等代码;然后,注释golden脚本中LeakyRelu计算,具体修改示例如下。
以下代码为算子核函数的代码片段。
[object Object]以下代码为golden生成脚本的代码片段。
[object Object]删除LeakyRelu计算后,执行用例,算子结果比对正确,如下所示。
[object Object]由此可确定,算子代码中已正确使用Matmul API,并得到了正确的Matmul API计算结果,需要继续定位LeakyReluCompute函数内LeakyRelu接口使用中存在的问题。
单核执行,算子功能是否正确
验证单核场景下算子的功能是否正确,可以帮助快速定界是Matmul API的计算结果不符合预期,还是算子代码中错误调用Matmul API导致。由于Matmul API内部实现的是单核的计算逻辑,所以单核的计算结果正确,而多核的计算结果错误的情况,说明单核上的Matmul API的使用及计算正确,这时需要排查与多核切分相关的代码逻辑是否正确,比如每个核的输入和输出地址偏移是否正确,每个核上的尾块地址设置是否正确。如果验证单核场景下,算子精度不正确,需要排查Matmul API的使用是否正确,具体可参考。
提示,包含Matmul的算子的Tiling实现中,Matmul的多核Tiling需要使用MultiCoreMatmulTiling构造多核Tiling对象,通过SetDim接口设置Matmul计算所用的核数。注意:这里设置的核数为Matmul计算所用的核数,仅在多核场景下设置,用于计算tiling参数。如下两个案例为MIX模式的算子,SetDim的设置规则请参考。
案例1:多核切分场景,输出地址偏移不正确
以M=512,N=1024,K=512的Matmul为例,MIX模式的算子代码中设置AIC核数为4,AIV核数为8,因为本案例以分离模式为例,所以SetDim设置为AIV核数的取值8。多核场景下执行该算子,计算结果精度错误。
以下为算子Tiling计算的代码片段。
[object Object]以下为算子核函数的代码片段。
[object Object]执行算子,精度校验失败:
[object Object]修改测试脚本和算子Tiling的代码,通过验证单核上的算子执行结果,快速定界。具体如下:
修改算子调测代码,只启动单核,CPU调测代码中将ICPU_RUN_KF宏接口中的numBlocks设置为1(表示一组AIC和AIV);算子的Tiling实现中,设置单核场景,AIC核数为1,AIV核数为2,SetDim设置为AIV核数的取值2。如下代码所示。
以下为调测脚本的代码片段。
[object Object]以下为算子Tiling计算的代码片段。
[object Object]修改为单核场景后,执行算子:
[object Object]从上述比对结果可看出,单核验证结果正确,此时可以定界导致精度的问题与多核逻辑相关。
首先排查多核切分后的输入和输出地址偏移。分析CalcGMOffset函数,定位到矩阵C的偏移地址offsetC计算错误,正确的偏移应该是mCoreIndx * tiling.N * tiling.singleCoreM + nCoreIndx * tiling.singleCoreN。将offsetC修改为正确的偏移地址后,执行算子,结果比对正确。
提示,在上述单核场景的修改验证中,AIC核数为1,AIV核数为2;若想进一步验证,不引入任何多核切分,AIC核数和AIV核数均修改为1,代码修改示例如下:
在核函数中REGIST_MATMUL_OBJ接口后,利用判断代码,BlockIdx不为0的AIV核退出。
以下为算子核函数的代码片段。
[object Object]算子调测脚本的ICPU_RUN_KF中numBlocks和算子Tiling中SetDim的usedCoreNum均设置为1。
以下为算子调测代码片段。
[object Object]以下为算子Tiling计算的代码片段。
[object Object]
案例2:尾块设置不正确
多核场景下,当最后一个核的singleCoreM/singleCoreN/singleCoreK值与前面的核取值不同时,需要在最后一个核上,即尾核,调用SetTail接口,调整singleCoreM/singleCoreN/singleCoreK为实际尾核上的对应取值;若尾核未设置这些参数值,或者设置的参数值大小不正确,也会导致多核精度错误,单核精度正确。
[object Object]以下为算子核函数的代码片段。
[object Object]
[object Object][object Object]排查Matmul API的使用是否正确
经过上述步骤,可定界出是否为Matmul API使用问题。如果由于Matmul API使用错误导致了算子的精度问题,需要根据Matmul各接口的使用说明、约束条件等,检查接口的使用是否正确。
用于算子调测的golden脚本是否正确
算子的golden生成脚本,根据自定义算子的功能逻辑自行实现,用于比对算子执行结果是否正确。因此,该golden脚本的逻辑需要与算子的实现逻辑保持一致,如果golden脚本实现错误,会导致算子计算结果的精度比对失败,这种情况是golden数据不可信。
所以,在算子精度定界定位的过程中,用户需要自行根据自定义算子的逻辑,检查golden脚本的正确性,尤其是对于复杂计算逻辑的算子,需重点排查该项。