CANN/asc-devkit核间同步样例
CrossCoreSetFlag和CrossCoreWaitFlag核间同步样例概述本样例首先介绍核间同步接口CrossCoreSetFlag和CrossCoreWaitFlag支持的三种同步模式(详见下表)随后分别在纯 Vector 计算场景以及Cube 与 Vector 融合计算场景这两个实际业务中说明上述三种同步模式的具体使用方法。同步控制模式说明mode 0对于AIC场景同步所有的AIC核直到所有的AIC核都执行到CrossCoreSetFlag时CrossCoreWaitFlag后续的指令才会执行。对于AIV场景同步所有的AIV核直到所有的AIV核都执行到CrossCoreSetFlag时CrossCoreWaitFlag后续的指令才会执行。mode 1单个AI Core内部AIV核之间的同步控制。如果两个AIV核都运行了CrossCoreSetFlagCrossCoreWaitFlag后续的指令才会执行。mode 2在AIC核执行CrossCoreSetFlag之后 两个AIV上CrossCoreWaitFlag后续的指令才会继续执行。两个AIV都执行CrossCoreSetFlag后AIC上CrossCoreWaitFlag后续的指令才能执行。本样例支持的产品及CANN软件版本产品CANN软件版本Ascend 950PR/Ascend 950DT CANN 9.1.0Atlas A3 训练系列产品/Atlas A3 推理系列产品 CANN 9.0.0Atlas A2 训练系列产品/Atlas A2 推理系列产品 CANN 9.0.0目录结构介绍├── cross_core_set_wait_flag │ ├── scripts │ │ ├── gen_data.py // 输入数据和真值数据生成脚本 │ │ └── verify_result.py // 验证输出数据和真值数据是否一致的验证脚本 │ ├── CMakeLists.txt // 编译工程文件 │ ├── data_utils.h // 数据读入写出函数 │ ├── figures // 图示 │ ├── cross_core_set_wait_flag.h // Ascend C样例实现 │ ├── cross_core_set_wait_flag.asc // 调用样例以及结果校验 │ └── README.md // 样例说明文档样例描述SCENARIO_NUM取值业务场景使用的同步模式0纯 Vector 计算场景(16个AIV)mode 0AIV全核同步1纯 Vector 计算场景(2个AIV)mode 12Cube 与 Vector 融合计算场景mode 2AIC等AIV、mode2AIV等AIC、mode0AIC全核同步本样例通过SCENARIO_NUM控制执行分支该变量的不同取值对应不同的业务场景和同步模式。如上表所示当 SCENARIO_NUM取不同值时会分别演示纯 Vector 计算场景和 Cube 与 Vector 融合计算场景下三种同步模式的具体使用方法。计算公式与样例规格SCENARIO_NUM0纯 Vector 计算场景模式0计算公式$$ z \sum_{i0}^{15} (x \times i) $$x 为输入向量全为1i 为每个AIV的BlockIdx取值范围0-15z 为所有AIV计算结果的累加值样例规格样例类型(OpType)CrossCoreSetFlagMode0样例输入nameshapedata typeformatx[32]float32ND样例输出z[32]float32ND核函数名cross_core_set_wait_flag_customSCENARIO_NUM1纯 Vector 计算场景模式1计算公式$$ z (x \times 2) (x \times 3) $$x 为输入向量全为1仅BlockIdx2和3的AIV参与计算z 为这两个AIV计算结果的累加值样例规格样例类型(OpType)CrossCoreSetFlagMode1样例输入nameshapedata typeformatx[32]float32ND样例输出z[32]float32ND核函数名cross_core_set_wait_flag_customSCENARIO_NUM2Cube 与 Vector 融合计算场景计算公式$$ C \text{LeakyRelu}(Cast(A) \times Cast(B)) $$A 为左矩阵形状为[M, K]数据类型为uint8B 为右矩阵形状为[K, N]数据类型为uint8首先将A和B数据类型从uint8转换为half然后执行矩阵乘A × B最后对结果执行LeakyRelu运算C 为最终结果形状为[M, N]数据类型为float32样例规格样例类型(OpType)CrossCoreSetFlagMode2样例输入nameshapedata typeformatisTransa[32, 32]uint8NDfalseb[32, 64]uint8NDfalse样例输出c[32, 64]float32ND-核函数名mmad_custom样例实现1. Cube与Vector融合计算场景1.1 整体逻辑图1Cube与Vector场景融合计算场景整体计算逻辑示意图本样例针对融合算子(配置__mix__(1,2))场景展开每个AI Core内部有1个AIC和2个AIV。配置逻辑核数numBlocks 8对应 8 个 AIC 与 16 个 AIV。如图1所示整体计算逻辑分为三个核心阶段精度转换阶段、分块矩阵乘与原子累加阶段、LeakyRelu运算与结果回写阶段。1.2 精度转换阶段(模式 2单个AI Core内部AIC等AIV)由于GM上左右矩阵的数据类型为uint8,并不满足mmad指令对输入数据类型的要求需要先将GM上的数据搬运到AIV中进行精度转换后才能在AIC中进行分块矩阵乘计算。因此 每个AI Core内部一个AIC 需要等待该AI Core内部其它2 个 AIV 完成数据精度转换后才能开始分块矩阵乘计算。 具体来说将 GM 中左矩阵(A矩阵)数据沿 K 轴切分 8 份分配至 BlockIdx 为偶数的 AIV 中完成 uint8 到 half 的精度转换将 GM 中右矩阵(B矩阵)数据沿 K 轴切分 8 份分配至 BlockIdx 为奇数的 AIV 中完成 uint8 到 half 的精度转换。如图2所示根据以上描述需要使用核间同步模式2。 上述描述对应的代码段如下if (blockIdx % 2 0) { ... AscendC::Cast(castALocal, aLocal, AscendC::RoundMode::CAST_NONE, A_BLOCKS_LENGTH); ... AscendC::DataCopy(AVectorGM, castALocal, A_BLOCKS_LENGTH); } else { ... AscendC::Cast(castBLocal, bLocal, AscendC::RoundMode::CAST_NONE, B_BLOCKS_LENGTH); ... AscendC::DataCopy(BVectorGM, castBLocal, B_BLOCKS_LENGTH); } // 模式2,每一个AICore内部一个AIC等2个AIV AscendC::CrossCoreSetFlag2, PIPE_MTE3(SYNC_AIV_AIC_FLAG);图2精度转换阶段模式2示意图1.3 分块矩阵乘与原子累加阶段(模式 0AIC全核同步)每个 AIC 执行分块矩阵乘计算后开启原子累加求和机制将计算结果搬运至同一块 GM 区域将8个AIC各自分块矩阵乘的结果在GM上进行累加最终得到完整的C矩阵。为了获取正确的C矩阵需要等待8个AIC都完成分块矩阵乘计算并且将结果通过FixPipe搬运到GM上。如图3所示根据以上描述需要使用核间同步模式0AIC全核同步。上述描述对应的代码段如下 $$ C \sum_{i1}^{8} A_i \cdot B_i $$// 模式2,每一个AICore内部AIC等2个AIV AscendC::CrossCoreWaitFlag(SYNC_AIV_AIC_FLAG); CopyIn(a1Local, b1Local); SplitA(a1Local, a2Local); SplitBTranspose(b1Local, b2Local); Compute(a2Local, b2Local, c1Local); CopyOut(c1Local); // 模式0,8个AICore 包含的8个AIC同步 AscendC::CrossCoreSetFlag0, PIPE_FIX(SYNC_AIC_FLAG); AscendC::CrossCoreWaitFlag(SYNC_AIC_FLAG); // 模式2,每一个AICore内部2个AIV等AIC AscendC::CrossCoreSetFlag2, PIPE_FIX(SYNC_AIC_AIV_FLAG);图3分块矩阵乘与原子累加阶段模式0示意图1.4 LeakyRelu与结果回写阶段(模式 2AIC等AIV)每个 AI Core 内部2 个 AIV 需等待 AIC 完成分块矩阵乘及原子累加操作后才能对C矩阵分块执行LeakyRelu运算。 具体来说是对累加得到的C矩阵沿 M 轴切分 16 份分配至 16 个 AIV 中分别执行 LeakyRelu 运算。如图4所示根据以上描述需要使用核间同步模式2单个AI Core内部2个AIV等一个AIC。上述描述对应的代码段如下// 模式2,每一个AICore内部2个AIV等AIC AscendC::CrossCoreWaitFlag(SYNC_AIC_AIV_FLAG); // 进行LeakyRelu运算 float alpha 0.001; ... AscendC::LeakyRelu(reluCLocal, cLocal, alpha, C_AIV_BLOCKS_LENGTH); ...图4LeakyRelu运算与结果回写阶段模式2示意图2. 纯Vector计算场景2.1 模式0和模式1的对比本样例设置NUM_BLOCKS为8(8个AI Core)每一个AI Core内AIC:AIV配比为2即本样例总共起了8个AIC和16个AIVAIV的BlockIdx范围为0~15。 如下图5所示本样例中模式0和模式1计算逻辑几乎相同区别仅在于参与同步的AIV数目模式0时全部的16个AIV参与同步模式1时仅有第二个AI Core中的2个AIV(BlockIdx2和3)参与同步。因此下一节将详细介绍模式0的整体逻辑模式1就不再详细介绍。图5纯AIV场景模式0计算逻辑示意图2.2 模式0的整体逻辑本样例用到的GM分为2块一块用于存储输入数据(initialDataGm)一块用于存储所有AIV的累加结果(atomicResultGm)。 如下图6所示模式0的整体逻辑分为以下步骤 (1) 每个AIV均从initialDataGm上搬运全为1的数据到UB上即为PIPE_MTE2的流水操作。(2) 每个AIV对UB上的数据进行矢量计算通过Muls指令乘以每个核对应的BlockIdx即为PIPE_V的流水操作。(3) 步骤 3 对应代码片段如下atomicResultGm 用于存储 16 个 AIV 全部搬运完成后的累加结果。通过调用 CrossCoreSetFlag 与 CrossCoreWaitFlag 接口实现同步控制确保 16 个 AIV 均完成 PIPE_MTE3 搬运指令后方可执行 CrossCoreWaitFlag 后续指令// UB 到 GM 搬运启用原子累加搬运至 atomicResult 的数据与原值累加后覆盖原值 AscendC::SetAtomicAddfloat(); // DataCopy属于PIPE_MTE3流水操作 AscendC::DataCopy(atomicResultGm, xLocal, this-blockLength); // 当本AIV完成前置PIPE_MTE3(DataCopy)流水操作后通知其他AIV核本AIV已经完成 AscendC::CrossCoreSetFlag0, PIPE_MTE3(0); // 阻塞本AIV继续往下执行指令直到其他AIV全部都完成PIPE_MTE3流水操作才解除阻塞往下执行。 AscendC::CrossCoreWaitFlag(0);上述同步完成之后此时atomicResultGm已经是16个AIV中矢量计算结果的累加值。此时从atomicResultGm搬运到某一个AIV上用DumpTensor进行打印数据检查累加结果是否符合预期最终将该AIV上的结果搬出到atomicResultGm。 如果上一步骤的同步插入不正确那么从atomicResultGm往AIV的搬运的数据可能是部分AIV中矢量计算结果的累加值导致打印和结果以及写回atomicResultGm的结果不准确。if (AscendC::GetBlockIdx() 0) { AscendC::DataCopy(yLocal, atomicResultGm, this-blockLength); // PIPE_MTE2 AscendC::printf( In PrintTensor Process AIV %d , AscendC::GetBlockIdx()); AscendC::DumpTensor(yLocal, AscendC::GetBlockIdx(), this-blockLength); AscendC::DataCopy(atomicResultGm, yLocal, this-blockLength); return; }图6纯AIV场景模式0计算逻辑示意图3. 注意事项3.1 Cube与Vector融合计算场景(1) Cube与Vector融合计算场景中融合算子(配置__mix__(1,2))中需要通过ASCEND_IS_AIV/ ASCEND_IS_AIC进行AIV和AIC核代码的隔离。KernelMmad op; if ASCEND_IS_AIC { op.InitAIC(A, B, c); op.ProcessAIC(); } if ASCEND_IS_AIV { op.InitAIV(a, b, A, B, c); op.ProcessAIV(); }(2) GetBlockIdx(获取当前核的index)在AIC和AIV的取值范围不同其取值与算子设置的逻辑核数和一个AI Core中的AIC与AIV的比例有关。本样例中设置NUM_BLOCKS8、AIC与AIV的比例为1:2因此GetBlockIdx在AIC和AIV的取值范围分别为0-7和0-15。(3) 样例采用静态Tensor编程范式需要手动插入核内同步。另外静态Tensor编程方式中需要开发者手动调用InitSocState()接口初始化全局状态寄存器。3.2 纯Vector计算场景(1) 在使用 CrossCoreSetFlag 与 CrossCoreWaitFlag 核间同步接口时即使是纯 Vector 计算场景核函数也不能使用__vector__ 修饰符。 本样例核函数采用__mix__(1,2) 修饰但在纯 Vector 场景下由于仅执行矢量计算必须通过 ASCEND_IS_AIV 宏确保程序仅在 AIV 核上运行否则会导致程序卡死。if ASCEND_IS_AIV { op.Init(x, z, dataLength); op.Process(); }(2) 模式 1 要求参与同步的 2个AIV 必须属于同一个AI Core否则程序会卡死。本样例中参与同步的两个AIV的 GetBlockIdx2、3 同属第 2 个 AI Core(下标从 1 开始)若将GetBlockIdx改为 3 和 4(分别属于两个不同的AI Core)程序将卡死。(3) 样例采用静态Tensor编程范式需要手动插入核内同步。另外静态Tensor编程方式中需要开发者手动调用InitSocState()接口初始化全局状态寄存器。编译运行在本样例根目录下执行如下步骤编译并执行样例。配置环境变量请根据当前环境上CANN开发套件包的安装方式配置环境变量。source ${install_path}/cann/set_env.sh说明${install_path}为CANN包安装目录未指定安装目录时默认安装至/usr/local/Ascend下。样例执行在本样例目录下执行如下命令。SCENARIO_NUM0 # 设置场景编号取值为0、1、2 mkdir -p build cd build; # 创建并进入build目录 cmake .. -DCMAKE_ASC_ARCHITECTURESdav-2201 -DSCENARIO_NUM$SCENARIO_NUM;make -j; # 编译工程 python3 ../scripts/gen_data.py -scenarioNum $SCENARIO_NUM # 生成测试输入数据 ./demo # 执行编译生成的可执行程序执行样例 python3 ../scripts/verify_result.py ./output/output.bin ./output/golden.bin # 验证输出结果是否正确使用CPU调试或NPU仿真模式时添加-DCMAKE_ASC_RUN_MODEcpu或-DCMAKE_ASC_RUN_MODEsim参数即可。示例如下cmake .. -DCMAKE_ASC_RUN_MODEcpu -DCMAKE_ASC_ARCHITECTURESdav-2201 -DSCENARIO_NUM$SCENARIO_NUM;make -j; # CPU调试模式 cmake .. -DCMAKE_ASC_RUN_MODEsim -DCMAKE_ASC_ARCHITECTURESdav-2201 -DSCENARIO_NUM$SCENARIO_NUM;make -j; # NPU仿真模式注意切换编译模式前需清理 cmake 缓存可在 build 目录下执行rm CMakeCache.txt后重新 cmake。编译选项说明选项可选值说明CMAKE_ASC_RUN_MODEnpu默认、cpu、sim运行模式NPU 运行、CPU调试、NPU仿真CMAKE_ASC_ARCHITECTURESdav-2201默认、dav-3510NPU 架构dav-2201 对应 Atlas A2 训练系列产品/Atlas A2 推理系列产品和 Atlas A3 训练系列产品/Atlas A3 推理系列产品dav-3510 对应 Ascend 950PR/Ascend 950DTSCENARIO_NUM0默认、1、2场景编号0纯Vector模式0、1纯Vector模式1、2CubeVector融合执行结果执行结果如下说明精度对比成功。test pass!创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考