Ascend C 同步指令编码指导【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills适用架构DAV_3510Ascend 950本文档覆盖核内流水事件同步SetFlag/WaitFlag、核间同步CrossCoreSetFlag/CrossCoreWaitFlag、全流水栅栏PipeBarrier的编码规范与常见陷阱。1. 同步机制总览1.1 三种同步机制对比机制粒度适用场景性能开销SetFlag/WaitFlagHardEvent精确到两个 pipe 之间双缓冲流水、buffer 生命周期管理低硬件事件寄存器CrossCoreSetFlag/WaitFlagAIC↔AIV 核间MIX 模板中 Cube 与 Vector 的 tile 级同步中跨核信号PipeBarrierpipe_t排空单个 pipe 或全部 pipe调试、核间同步前强制排空高全流水停顿1.2 硬件流水线架构AIVVector 核GM ──MTE2──▶ UB ──V──▶ UB ──MTE3──▶ GM │ │ (额外输入加载) (计算)AICCube 核GM ──MTE2──▶ L1 ──MTE1──▶ L0A/L0B ──M──▶ L0C ──FIX──▶ UB/GM │ │ │ │ (A/B 加载) (L1→L0) (MMAD) (量化/Cast)关键原则DataCopy/DataCopyPad 是异步 DMA立即返回。必须通过同步机制确保消费者在读数据前生产者的搬运已完成。2. HardEvent 事件全表与选取指南2.1 事件枚举按核类型分组AIC 专用事件在 AIV 上调用会被编译器静默跳过HardEvent生产者 pipe消费者 pipe用途MTE1_MTE2MTE1MTE2MTE1 用完 L1 bufferMTE2 可覆盖MTE2_MTE1MTE2MTE1MTE2 加载 L1 完成MTE1 可读取MTE1_MMTE1MMTE1 加载 L0 完成Cube 可计算M_MTE1MMTE1Cube 用完 L0MTE1 可重新加载M_FIXMFIXCube 累加完成FixPipe 可读取 L0CFIX_MFIXMFixPipe 读完 L0CCube 可重新累加MTE2_MMTE2MMTE2 写 UB 完成Cube 可读取M_MTE2MMTE2Cube 用完 UBMTE2 可覆盖AIV 专用事件在 AIC 上调用会被编译器静默跳过HardEvent生产者 pipe消费者 pipe用途MTE2_VMTE2VMTE2 加载 UB 完成Vector 可计算V_MTE2VMTE2Vector 用完 UBMTE2 可覆盖V_MTE3VMTE3Vector 计算完成MTE3 可写回 GMMTE3_VMTE3VMTE3 写完Vector 可读取MTE3_MTE2MTE3MTE2MTE3 写回完成MTE2 可覆盖 UBMTE2_MTE3MTE2MTE3MTE2 加载完成MTE3 可读取V_VVV等同PipeBarrierPIPE_V通用事件HardEvent说明V_V内部实现为PipeBarrierPIPE_V排空 Vector pipeFIX_FIX内部实现为PipeBarrierPIPE_FIX排空 FixPipeS_V/V_SScalar ↔ Vector 同步S_MTE2/MTE2_SScalar ↔ MTE2 同步S_MTE3/MTE3_SScalar ↔ MTE3 同步2.2 事件选取决策表给定生产者操作完成后消费者才能开始的需求查表选取事件生产者 ↓ \ 消费者 →MTE1MTE2M (Cube)V (Vector)MTE3FIXMTE1—MTE1_MTE2MTE1_M—MTE1_MTE3MTE1_FIXMTE2MTE2_MTE1—MTE2_MMTE2_VMTE2_MTE3MTE2_FIXM (Cube)M_MTE1M_MTE2—M_V—M_FIXV (Vector)V_MTE1V_MTE2—V_VV_MTE3—MTE3MTE3_MTE1MTE3_MTE2—MTE3_V—FIX_MTE3FIXFIX_MTE1FIX_MTE2FIX_M—FIX_MTE3FIX_FIX注表中—表示该组合在常规流水中不出现。并非所有组合都有实际硬件支持。3. SetFlag/WaitFlag 配对规则3.1 核心原则生产者操作如 DataCopyPad ↓ SetFlagEvent(eventID) ← 标记我完成了 ↓ ... 硬件异步推进... ↓ WaitFlagEvent(eventID) ← 阻塞等待生产者完成 ↓ 消费者操作如 Vector 计算规则Set 在生产者之后Wait 在消费者之前同一 eventID 的完整生命周期Set → Wait为一个周期。Wait 返回后该 eventID 才可复用Set 和 Wait 必须使用相同的 HardEvent 类型和 eventID3.2 构造预发 / 析构排空Init-Drain 模式双缓冲流水或单缓冲但多轮生产/消费中第一轮迭代的 Wait 没有上一轮可等。解决方案构造函数预发所有 slot 的 SetFlag析构函数排空所有 slot 的 WaitFlag。class BlockMmad { BlockMmad() { // 预发标记所有 L1 slot 为空闲可写 SetFlagHardEvent::MTE1_MTE2(0); // slot 0 SetFlagHardEvent::MTE1_MTE2(1); // slot 1 // 预发标记所有 L0 slot 为空闲可写 SetFlagHardEvent::M_MTE1(0); SetFlagHardEvent::M_MTE1(1); } ~BlockMmad() { // 排空等待所有 in-flight DMA 完成 WaitFlagHardEvent::MTE1_MTE2(0); WaitFlagHardEvent::MTE1_MTE2(1); WaitFlagHardEvent::M_MTE1(0); WaitFlagHardEvent::M_MTE1(1); } };3.3 双缓冲循环中的配对for (uint64_t iter 0; iter totalIter; iter) { uint64_t bufId iter 0x1; // 交替 0, 1 // ── 生产者侧MTE2 加载 L1── WaitFlagHardEvent::MTE1_MTE2(bufId); // 等 MTE1 用完此 slot CopyGM2L1(l1Buffer[bufId], ...); // MTE2 写入 L1 SetFlagHardEvent::MTE2_MTE1(bufId); // 通知 MTE1 可读取 // ── 消费者侧MTE1 读取 L1 → L0── WaitFlagHardEvent::MTE2_MTE1(bufId); // 等 MTE2 加载完成 CopyL12L0(l0Buffer[...], l1Buffer[bufId]); SetFlagHardEvent::MTE1_MTE2(bufId); // 通知 MTE2 可覆盖 }4. 正向依赖与反向依赖4.1 概念类型方向含义示例正向依赖数据流方向数据准备好了你可以用MTE2_VMTE2 搬完 UBV 可计算反向依赖buffer 回收方向我用完了你可以覆盖V_MTE3V 算完MTE3 可写回MTE3_MTE2MTE3 写回完MTE2 可覆盖 UBMTE1_MTE2MTE1 读完 L1MTE2 可覆盖FIX_MFixPipe 读完 L0CCube 可重新累加4.2 多轮计算中反向依赖的必要性单轮计算中buffer 只使用一次不存在覆盖问题。多轮计算每核处理多个 tile中buffer 被循环复用必须通过反向依赖保护轮次 N: MTE2 写 UB[0] → V 读 UB[0] → MTE3 写回 GM 轮次 N1: MTE2 写 UB[0] → V 读 UB[0] → MTE3 写回 GM ↑ 如果轮次 N 的 MTE3 尚未完成MTE2 覆盖 UB[0] → 数据损坏 需要 MTE3_MTE2 反向 barrier 保护。4.3 遗漏反向依赖的症状场景现象原因小 shape单轮PASSbuffer 不复用无冲突大 shape多轮crash 或数据错乱上一轮 V 未计算完本轮 MTE2 覆盖同一 buffer确定性测试两次运行结果不同时序竞争V 完成时间随调度波动4.4 epilogue 循环中的正确 barrier 位置for (int64_t mOff 0; mOff halfM; mOff stageRows) { // ── 反向 barrier等上一轮 V 计算完成, // 注意首轮SetFlag需要在Init中预发射此处伪代码虽不作展示但不能遗漏 WaitFlagHardEvent::V_MTE2(ZERO_FLAG); // ── 加载 (M,1)/(M,N) 额外输入每 stage 不同行── DataCopyPad(pertokenBuf, pertokenGM[start mOff], ...); // ── 正向 barrier等 MTE2 加载完成 ── SetFlagHardEvent::MTE2_V(ZERO_FLAG); WaitFlagHardEvent::MTE2_V(ZERO_FLAG); // ── 反向 barrierV等上一轮 MTE3 完成后才能开始计算, // 注意首轮SetFlag需要在Init中预发射此处伪代码虽不作展示但不能遗漏 WaitFlagHardEvent::MTE3_V(ZERO_FLAG); // ── V 计算 ── __VEC_SCOPE__ { ... } // ── 反向 barrier通知下一轮 MTE2 可以开始 ── // 注意尾轮WaitFlag需要在析构函数中完成此处伪代码虽不作展示但不能遗漏 SetFlagHardEvent::V_MTE2(ZERO_FLAG); // ── 正向 barrier等 V 计算完成 ── SetFlagHardEvent::V_MTE3(ZERO_FLAG); WaitFlagHardEvent::V_MTE3(ZERO_FLAG); // ── MTE3 写回 GM ── DataCopyPad(outputGM[offset], bf16Out, ...); // ── 反向 barrier通知下一轮 V 可以开始 ── // 注意尾轮WaitFlag需要在析构函数中完成此处伪代码虽不作展示但不能遗漏 SetFlagHardEvent::MTE3_V(ZERO_FLAG); }关键V_MTE2barrier 放在循环内、MTE2 操作之前WaitFlag, V 完成后SetFlag注意首轮尾轮和中间轮次的区别。5. EventID 管理5.1 取值范围架构eventID 数量可用值DAV_3510 / V20080~7EVENT_ID0~EVENT_ID7旧架构40~3// 编译器内置枚举 typedef enum { EVENT_ID0 0, EVENT_ID1, EVENT_ID2, EVENT_ID3, EVENT_ID4, EVENT_ID5, EVENT_ID6, EVENT_ID7, // V200 } event_t;5.2 双缓冲 eventID 分配 0x1模式最常用uint64_t pingPong 0; for (...) { uint64_t bufId pingPong 0x1; // 交替 0, 1 WaitFlagEvent(bufId); // ... 操作 buffer[bufId] ... SetFlagEvent(bufId); pingPong; } (N-1)模式N 缓冲 ringN 必须为 2 的幂uint64_t l1BufId abL1LoopCnt_ (l1BufNum_ - 1); // l1BufNum4 → 3XOR toggle等价于 0x1aL1BufferID_ aL1BufferID_ ^ 1; // 0→1, 1→05.3 不同事件类型共享 eventID 空间关键陷阱同一核上不同 HardEvent 类型共享同一组硬件 eventID 寄存器。MTE1_MTE2的 ID0 和M_MTE1的 ID0 是同一个物理寄存器。解决方案用偏移量隔离不同事件类型的 eventID。// L1 双缓冲MTE1_MTE2 使用 ID 0,1 SetFlagHardEvent::MTE1_MTE2(0); SetFlagHardEvent::MTE1_MTE2(1); // L0 双缓冲M_MTE1 使用 ID 6,7偏移 6 避免与 L1 冲突 constexpr uint16_t L0_FLAG_OFFSET 6; SetFlagHardEvent::M_MTE1(0 L0_FLAG_OFFSET); SetFlagHardEvent::M_MTE1(1 L0_FLAG_OFFSET);5.4 L1_EVENT_ID_OFFSETA/B 矩阵独立 eventIDA 矩阵和 B 矩阵的 L1 加载可以独立进行。使用偏移量分离 eventID 避免假依赖constexpr uint16_t L1_EVENT_ID_OFFSET 2; // A 矩阵使用 ID 0,1 WaitFlagHardEvent::MTE1_MTE2(abL1EventID_ 0x1); // B 矩阵使用 ID 2,3 WaitFlagHardEvent::MTE1_MTE2((abL1EventID_ 0x1) L1_EVENT_ID_OFFSET);5.5 TPipe 动态分配当使用 TPipe 管理事件时框架自动分配/释放auto eventId GetTPipePtr()-AllocEventIDHardEvent::MTE3_MTE2(); // 分配 // ... 使用 eventId ... GetTPipePtr()-ReleaseEventIDHardEvent::MTE3_MTE2(eventId); // 释放内部实现使用位图sff0找首个空闲位sbitset1标记占用sbitset0释放。6. PipeBarrier 使用指南6.1 语义PipeBarrierpipe_t()排空指定 pipe 的所有 in-flight 操作阻塞直到该 pipe 完全空闲。6.2 pipe_t 枚举pipe_t含义说明PIPE_SScalar pipe标量计算PIPE_VVector pipe向量计算PIPE_MMatrix pipeCube 矩阵乘PIPE_MTE1L1→L0 搬运PIPE_MTE2GM→L1/UB 搬运PIPE_MTE3UB→GM 搬运PIPE_FIXFixPipeL0C→UB/GMdav-3510PIPE_ALL所有 pipe全流水停顿6.3 架构差异架构PIPE_MTE3 on AICPIPE_SPIPE_V on AICDAV_3510no-op不支持no-opDAV_5102no-op支持no-op6.4 使用场景场景推荐原因调试同步问题PipeBarrierPIPE_ALL()快速验证是否为同步问题高性能流水不用PipeBarrier用 SetFlag/WaitFlag 精确同步6.5 与 SetFlag/WaitFlag 的选择// ❌ 性能差PipeBarrier 全流水停顿 DataCopyPad(x, gm, size); PipeBarrierPIPE_ALL(); Compute(x); // ✅ 性能好SetFlag/WaitFlag 精确同步 DataCopyPad(x, gm, size); SetFlagHardEvent::MTE2_V(eventId); WaitFlagHardEvent::MTE2_V(eventId); Compute(x);7. 核间同步CrossCoreSetFlag / CrossCoreWaitFlag7.1 API 签名template uint8_t modeId, pipe_t pipe __aicore__ inline void CrossCoreSetFlag(uint16_t flagId); template uint8_t modeId 0, pipe_t pipe PIPE_S __aicore__ inline void CrossCoreWaitFlag(uint16_t flagId);7.2 MODE 4 语义modeId 4为 intra-block 模式用于同一 block 内 AIC↔AIV 配对核间同步。底层调用set_intra_block(pipe, flagId)/wait_intra_block(pipe, flagId)。7.3 PIPE 参数选择操作推荐 PIPE原因AIC SetFlag通知 AIV 数据就绪PIPE_FIXFixPipe 写完 UB 后才可通知AIV WaitFlag等待 AIC 数据PIPE_VVector pipe 等待阻塞后续 V 计算AIV SetFlag通知 AIC 消费完成PIPE_MTE3MTE3 写回 GM 后才可通知AIC WaitFlag等待 AIV 消费完PIPE_FIXFixPipe pipe 等待阻塞下一 tile 的 L0C→UB7.4 FLAG_ID_MAX 与双 AIV1:2 CV 比例下1 个 AIC 配 2 个 AIV。使用FLAG_ID_MAX 16偏移分离两个 AIV 的 flag 空间// AIC 通知 AIV0 CrossCoreSetFlagMODE4, PIPE_FIX(AIC_SYNC_AIV_FLAG countId); // AIC 通知 AIV1 CrossCoreSetFlagMODE4, PIPE_FIX(AIC_SYNC_AIV_FLAG countId FLAG_ID_MAX); // AIC 等待 AIV0 CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_SYNC_AIC_FLAG countId); // AIC 等待 AIV1 CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_SYNC_AIC_FLAG countId FLAG_ID_MAX);7.5 count 轮换机制硬件 flag 寄存器有限16 个多 tile 场景需要轮换constexpr int16_t COUNT_ID_MAX 15; // 每个 flag slot 服务 15 个 tile constexpr int16_t COUNT_FLAG 3; // 3 个 flag slot 循环 countId count / COUNT_ID_MAX % COUNT_FLAG; // 产生序列0,0,...,0(15次), 1,1,...,1(15次), 2,2,...,2(15次), 0,0,...flag 地址分布方向AIV0AIV1AIV→AIC5, 6, 7轮换21, 22, 23轮换AIC→AIV8, 9, 10轮换24, 25, 26轮换7.6 构造/析构预发排空模式class KernelMatmulMixFixpipeOpti { KernelMatmulMixFixpipeOpti() { if ASCEND_IS_AIV { // 预发让 AIC 第一轮 Wait 直接通过 CrossCoreSetFlagMODE4, PIPE_MTE3(AIV_SYNC_AIC_FLAG); // ping CrossCoreSetFlagMODE4, PIPE_MTE3(AIV_SYNC_AIC_FLAG 1); // pong } } ~KernelMatmulMixFixpipeOpti() { if ASCEND_IS_AIC { // 排空等所有 AIV 完成最后一轮 CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_SYNC_AIC_FLAG); CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_SYNC_AIC_FLAG FLAG_ID_MAX); CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_SYNC_AIC_FLAG 1); CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_SYNC_AIC_FLAG 1 FLAG_ID_MAX); } } };7.7 空闲核处理空闲核必须在 return 前发送 flag否则对端永久等待if ASCEND_IS_AIC { if (curBlockIdx realBlockNum) { // 空闲 AIC 必须通知 AIV CrossCoreSetFlagMODE4, PIPE_FIX(AIC_SYNC_AIV_FLAG); CrossCoreSetFlagMODE4, PIPE_FIX(AIC_SYNC_AIV_FLAG FLAG_ID_MAX); return; } }8. 常见挂死问题与排查8.1 挂死分类表#现象根因修复D1AIC hang循环结束后未 drain 最后一轮 AIV→AIC WaitFlag循环后/析构中补 WaitFlagD2AIV hang空闲 AIC 未发 flag 就 returnreturn 前补 CrossCoreSetFlagD3大 shape crash小 shape PASS缺少 MTE3_MTE2 反向 barrier循环内 MTE2 操作前加 MTE3_MTE2D4全 shape hangSetFlag/WaitFlag eventID 不匹配检查两侧 flagId 计算一致D5大 shape 数据错乱非确定性反向同步设置错误检查 V_MTE2 MTE3_V 等反向同步是否设置正确D6L1 双缓冲 hang构造时未预发 MTE1_MTE2构造函数 SetFlag 所有 slotD7L0C ping-pong deadlockl0cDB1 和 l0cDB2 同步模式混用统一 L0C 同步模式D8EnQue/DeQue hang队列空等或满等检查 Alloc/Free/EnQue/DeQue 配对8.2 排查流程程序卡死/超时 │ ├─ [1] 检查 plog 定位卡死位置 │ └─ grep timeout\|hang\|deadlock plog │ ├─ [2] 判断确定性 │ └─ 同输入跑两次比对 │ ├─ diff ≠ 0 → 竞争查 D5PipeBarrier / CV 同步 │ └─ diff 0 → 确定性错误查 D3/D4/D6/D7 │ ├─ [3] 隔离触发维度 │ └─ 单独放大 M/N/K │ ├─ 仅大 shape 触发 → D3反向 barrier或 D5WAR 竞争 │ └─ 全 shape 触发 → D4eventID 不匹配或 D6预发缺失 │ └─ [4] 检查 CV 同步完整性 └─ Set/Wait 数量是否匹配 ├─ AIC Set 数 AIV Wait 数 ├─ AIV Set 数 AIC Wait 数 └─ 空闲核是否发送了 flag9. 速查表9.1 Matmul 双缓冲同步模板// ── L1 双缓冲GM→L1→L0── // 构造预发 SetFlagMTE1_MTE2(0); SetFlagMTE1_MTE2(1); // 循环 WaitFlagMTE1_MTE2(bufId); // 等 MTE1 释放 L1 slot CopyGM2L1(...); // MTE2 写 L1 SetFlagMTE2_MTE1(bufId); // 通知 MTE1 可读取 WaitFlagMTE2_MTE1(bufId); // 等 MTE2 写完 CopyL12L0(...); // MTE1 读 L1→L0 SetFlagMTE1_MTE2(bufId); // 通知 MTE2 可覆盖 // ── L0 双缓冲L1→L0→MMAD── // 构造预发注意 eventID 偏移避免与 L1 冲突 SetFlagM_MTE1(OFFSET 0); SetFlagM_MTE1(OFFSET 1); // 循环 WaitFlagM_MTE1(OFFSET bufId); // 等 Cube 用完 L0 CopyL12L0(...); // MTE1 写 L0 SetFlagMTE1_M(OFFSET bufId); // 通知 Cube 可计算 WaitFlagMTE1_M(OFFSET bufId); // 等 MTE1 写完 Mmad(...); // Cube 计算 SetFlagM_MTE1(OFFSET bufId); // 通知 MTE1 可覆盖 // ── 析构排空所有 slot ── WaitFlagMTE1_MTE2(0); WaitFlagMTE1_MTE2(1); WaitFlagM_MTE1(OFFSET 0); WaitFlagM_MTE1(OFFSET 1);9.2 AIV Epilogue 同步模板for (int64_t mOff 0; mOff halfM; mOff stageRows) { // ── 反向 barrier等上一轮 V 计算完成, // 注意首轮SetFlag需要在Init中预发射此处伪代码虽不作展示但不能遗漏 WaitFlagHardEvent::V_MTE2(ZERO_FLAG); // ── 加载 (M,1)/(M,N) 额外输入每 stage 不同行── DataCopyPad(pertokenBuf, pertokenGM[start mOff], ...); // ── 正向 barrier等 MTE2 加载完成 ── SetFlagHardEvent::MTE2_V(ZERO_FLAG); WaitFlagHardEvent::MTE2_V(ZERO_FLAG); // ── 反向 barrierV等上一轮 MTE3 完成后才能开始计算, // 注意首轮SetFlag需要在Init中预发射此处伪代码虽不作展示但不能遗漏 WaitFlagHardEvent::MTE3_V(ZERO_FLAG); // ── V 计算 ── __VEC_SCOPE__ { ... } // ── 反向 barrier通知下一轮 MTE2 可以开始 ── // 注意尾轮WaitFlag需要在析构函数中完成此处伪代码虽不作展示但不能遗漏 SetFlagHardEvent::V_MTE2(ZERO_FLAG); // ── 正向 barrier等 V 计算完成 ── SetFlagHardEvent::V_MTE3(ZERO_FLAG); WaitFlagHardEvent::V_MTE3(ZERO_FLAG); // ── MTE3 写回 GM ── DataCopyPad(outputGM[offset], bf16Out, ...); // ── 反向 barrier通知下一轮 V 可以开始 ── // 注意尾轮WaitFlag需要在析构函数中完成此处伪代码虽不作展示但不能遗漏 SetFlagHardEvent::MTE3_V(ZERO_FLAG); }9.3 CV 核间同步模板// ── AIC 侧 ── if (enableCVSync) { countId count / COUNT_ID_MAX % COUNT_FLAG; CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_FLAG countId); // 等 AIV0 CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_FLAG countId 16); // 等 AIV1 } blockMmadOp(...); // 含 CopyL0C2UB count; countId count / COUNT_ID_MAX % COUNT_FLAG; CrossCoreSetFlagMODE4, PIPE_FIX(AIC_FLAG countId); // 通知 AIV0 CrossCoreSetFlagMODE4, PIPE_FIX(AIC_FLAG countId 16); // 通知 AIV1 // ── AIV 侧 ── count; countId count / COUNT_ID_MAX % COUNT_FLAG; CrossCoreWaitFlagMODE4, PIPE_V(AIC_FLAG countId); // 等 AIC epilogueOp(...); CrossCoreSetFlagMODE4, PIPE_MTE3(AIV_FLAG countId); // 通知 AIC // ── AIC drain循环后── if (enableCVSync) { countId count / COUNT_ID_MAX % COUNT_FLAG; CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_FLAG countId); CrossCoreWaitFlagMODE4, PIPE_FIX(AIV_FLAG countId 16); }9.4 核内生产-消费流水同步的基本原则任何流水如果产出的数据被其他流水使用必须同时设置正向同步和反向流水正向流水设置在于生产流水和反向流水之间反向同步的WaitFlag在生产流水之前SetFlag在消费流水之后。反向同步设置时首轮SetFlag在Init中预发射尾轮WaitFlag在析构函数中完成。伪代码如下class SomeClass { __aicore__ void Init () { // ... 其他Init函数逻辑 SetFlagHardEvent::消费流水_生产流水(id); // 预发射首轮SetFlag } __aicore__ void operator() () { // ... 其他逻辑代码 // 反向等待消费流水完成,必须设置, 否则会导致多tile轮次计算间有读写竞争 WaitFlagHardEvent::消费流水_生产流水(id); { // ... 生产流水代码 } // 正向同步 SetFlagHardEvent::生产流水_消费流水(id); WaitFlagHardEvent::生产流水_消费流水(id); { // ... 消费流水代码 } // 反向通知下一轮生产流水可以开始,必须设置不设置会导致多tile轮次计算间有读写竞争 SetFlagHardEvent::消费流水_生产流水(id); // ... 其他逻辑代码 } __aicore__ ~SomeClass() { // ... 其他析构函数逻辑代码 // 析构函数中设置最后一轮反向等待的WaitFlag WaitFlagHardEvent::消费流水_生产流水(id); } };重要同步代码和流水代码的位置必须绑定不允许同步代码在循环外但流水代码在循环内。当修改流水代码位置时必须将对应的同步代码位置一起修改.SetFlag和WaitFlag的数量必须匹配先Set后Wait。尤其是涉及循环的情况需要仔细验证SetFlag和WaitFlag的数量是否匹配。9.5 DataCopyPad stride 单位速查DataCopyExtParams的srcStride/dstStride单位因搬运方向不同方向srcStridedstStride说明GM → UBbytes32 字节单位srcStride GM 行间 gap bytesdstStride UB 行间 gap 以 32B 为单位UB → GM32 字节单位bytessrcStride UB 行间 gap 以 32B 为单位dstStride GM 行间 gap bytes关键规则当 UB 行按nAlign对齐排布时nAlign ceil(N / (32/sizeof(T))) * (32/sizeof(T))保证nAlign * sizeof(T)是 32 的倍数UB 侧 stride 恒等于 0直接传0即可。// GM→UBdstStrideUB 侧传 0 DataCopyExtParams cp{nRows, rowBytes, gmRowGap, 0, 0}; // UB→GMsrcStrideUB 侧传 0 DataCopyExtParams outParams{nRows, rowBytes, 0, gmRowGap, 0};常见错误将 UB 侧 stride 按 bytes 计算如(nAlign - N) * sizeof(T)。虽然对齐场景下结果恰好为 0 不暴露但 tail 窄 dtype如 bf16场景会导致行步长错误。【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考