CANN/cannbot-skills Epilogue层扩展开发指南

📅 2026/7/16 17:59:47
CANN/cannbot-skills Epilogue层扩展开发指南
Epilogue 层扩展开发指南【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills适用路径blaze_customCV 融合场景。本文是 Epilogue 设计总入口负责说明 Epilogue 在 CV 中的位置、接口契约、MemBase/RegBase 选择、SplitM、UB 分配、同步设计方法和伪代码生成流程。§1 Epilogue 在 CV 中的位置Epilogue 是 CV 融合算子的 V 部分负责消费 AIC 通过 L0C2UB 写入 UB 的 MatMul 结果。读取额外输入Bias、scale、residual、activation 参数等。执行 Vector 公式链。将结果写回 GM并回复 AIC 可以继续覆盖 UB。Epilogue 不是独立 kernel。它依附于 C 部分的 Kernel / Block / Scheduler 契约尤其依赖UB 中 MatMul 结果的布局。DUAL_DST_SPLIT_M是否启用。CrossCore flag 的轮转策略。§2 三接口合约接口签名职责Initvoid Init(Params, baseM, baseN, ProblemShape)UB 布局、GM 绑定、预发反向 eventGetTensorauto GetTensor()返回 UB TensorAIC 侧CopyL0C2UB的 dstoperator()void operator()(BlockShape, int64_t gmOffset, uint16_t flagId)普通 / MX CV 逐 tile 后处理Paramsstruct Params { ... }额外输入和输出的 GM 地址类型约定BlockShape Shapeint64_t, int64_t, int64_t, int64_t。Grouped CV 使用 context-based Epilogueoperator()签名为void operator()(BlockShape, TileContext, uint16_t flagId)。普通 linear offset Epilogue 不直接兼容 Grouped CV。§3 MemBase vs RegBase 选择Epilogue 类型适用场景特点MemBase只有一个简单 vector 操作且有明确可用的AscendC::LocalTensor API如Mul/Add/Div/Cast中间值通常写 UB代码简单RegBaseGELU / SwiGLU / LayerNorm / 多输入 scale / 多中间值公式链中间值在RegTensor内流转减少 UB 占用和读写默认选择 RegBase。只有当公式足够简单、API 明确且 UB 空间满足时才选择 MemBase。详细设计MemBaseepilogue-membase-design.mdRegBaseepilogue-regbase-design.md§4 SplitM 与 offset 原理普通 CV 与 MX CV 的CopyL0C2UB使用DUAL_DST_SPLIT_MTraitL0C 按 M 维拆给两个 AIV SubBlock。Epilogue 不能把 UB 当成完整blockM × blockN整块处理。CopyL0C2UB Trait 选择TraitDualDstMode适用场景UB 数据分布CopyL0C2UBSplitMTraitDUAL_DST_SPLIT_M__mix__(1,2)标准 CVM 对半分片各 AIV 从各自 UB offset 0 读取CopyL0C2UBNonSplitTraitDUAL_DST_DISABLE单 AIV 调试全量数据在 UB offset 0__mix__(1,2)CV 场景必须使用CopyL0C2UBSplitMTrait。matmul_block_mmad.h参考模板已默认使用 SplitMTrait。GM offset需要 sub-block 偏移基础公式origM blockShapeM halfM ceilDiv(origM, GetTaskRation()) localRows (origM is odd) ? (halfM - GetSubBlockIdx()) : halfM tileM0 gmOffset / N tileN0 gmOffset % N subM0 tileM0 GetSubBlockIdx() * halfM stageM0 subM0 stageRowOffset rowDependentInputOffset stageM0 outputOffset subM0 * N tileN0含义halfM是单个 AIV SubBlock 负责的行数上界。奇数 M 时SubBlock0 处理halfM行SubBlock1 处理halfM - 1行。所有依赖 M 维的额外输入都必须从stageM0计算 GM offset。UB 读取不需要 sub-block 偏移CopyL0C2UBSplitM会硬件自动将 L0C 的 M 行对半切分每个 AIV 从自己的 UB offset 0 开始读取半份数据操作是否需要 SubBlock 偏移公式UB 读取 cLocal_否cLocal_.GetPhyAddr() row * nAlignGM 读取 row-dependent input是stageM0 tileM0 GetSubBlockIdx() * halfM stageRowOffsetGM 写回 output是gmRowOffset subM0 * N tileN0常见错误从 GM offset 公式推断UB 读取也需要加GetSubBlockIdx() * halfM * nAlign偏移。实际上 UB 数据已被硬件自动分片加偏移会导致 V1 跳过自己的数据读到后续 buffer 的垃圾值。localRows0 边界场景当curM为奇数且halfM1时如curM1V1 的localRows 0。此时V1 应 early return不需要做任何 Vector 计算或 GM 写回。CV 同步由 kernel 层MatmulKernelFused统一处理不会因 V1 return 而挂死。§5 UB 空间分配通用方法Epilogue 不重新设计独立 tiling engine而是在 Cube tiling 已确定的前提下消费剩余 UB。设计顺序先保留 L0C2UB 的 MatMul 结果区域。再根据 MemBase / RegBase 和用户公式决定额外需要几份空间。最后计算stageRows/stageSize。通用预算nAlignL0C ceil(baseN / (32/sizeof(L0CDataType))) * (32/sizeof(L0CDataType)) splitMRows ceil(baseM / GetTaskRation()) matmulAreaBytes splitMRows * nAlignL0C * sizeof(L0CDataType) remainBytes TOTAL_UB_SIZE - matmulAreaBytes epilogueBytes sum(extra input buffer, tmp buffer, output staging) epilogueBytes remainBytes关键matmulAreaBytes的行步长是nAlignL0CUB 中的对齐行宽不是L0C cube 边长 16。L0C cube 边长是 L0C 硬件分型粒度与 UB buffer 行步长无关。将nAlignL0C误写为 16 会导致matmulAreaBytes被低估、后续 buffer 重叠到 cLocal_ 数据区。设计原则cLocal_是 MatMul 结果区优先级最高。RegBase 尽量把中间值留在RegTensor只在 UB 中保留输入 staging 和输出 staging。MemBase 中间值通常需要额外 UB tmp buffer。若剩余 UB 不足优先减小baseM/baseN或切换到 RegBase而不是给 V 部分另起一套 tiling。§6 同步设计方法Epilogue 同时受两层同步约束层次机制说明AIC ↔ AIVCrossCoreSetFlag/WaitFlagAIC 写 UB 后通知 AIVAIV 完成后回复 AICAIV 内部SetFlag/WaitFlagHardEvent组织 MTE2→V→MTE3 和反向依赖同步规则以references/fundamentals/blaze-sync-patterns.md为准。最小同步伪代码WaitFlagHardEvent::V_MTE2(ZERO_FLAG); DataCopyPad(extraInputBuf, extraInputGm[offset], ...); SetFlagHardEvent::MTE2_V(ZERO_FLAG); WaitFlagHardEvent::MTE2_V(ZERO_FLAG); WaitFlagHardEvent::MTE3_V(ZERO_FLAG); __VEC_SCOPE__ { // 读 cLocal_ / extra input // 计算 vector 公式 // 写 output staging } SetFlagHardEvent::V_MTE2(ZERO_FLAG); SetFlagHardEvent::V_MTE3(ZERO_FLAG); WaitFlagHardEvent::V_MTE3(ZERO_FLAG); DataCopyPad(outputGm[outputOffset], outputBuf, ...); SetFlagHardEvent::MTE3_V(ZERO_FLAG);§7 Vector 公式到伪代码的设计流程设计 Epilogue 时不应直接写代码而应先从用户公式生成伪代码。步骤把用户公式分解为可执行的 Vector 操作链。判断每个中间值是否必须写 UB还是可以保留在寄存器。对每个操作到对应 skill 查 APIMemBase / AscendC LocalTensor APIascendc-api-best-practicesRegBase APIascendc-regbase-best-practice根据blaze-sync-patterns.md安排同步指令。输出Init/GetTensor/operator()所需信息。输出的伪代码至少应包含额外输入加载顺序。row-dependent offset 公式。Vector 计算链。输出写回顺序。同步指令位置。§8 同步自检清单检查项要求MTE2 - V 正向依赖DataCopyPad后必须存在MTE2_VV - MTE2 反向依赖下一轮覆盖 extra input buffer 前必须存在V_MTE2V - MTE3 正向依赖Vector 计算完成后必须存在V_MTE3MTE3 - V 反向依赖下一轮 V 或 MTE2 使用相关 buffer 前必须存在MTE3_VCrossCore 配对AIC set / AIV waitAIV set / AIC wait 必须成对首轮预发射若首轮要先 Wait 反向依赖必须在Init中预发射尾轮收尾若最后一轮会遗留反向依赖必须在析构或收尾阶段排空§9 常见错误错误后果修复忽略 SplitM 行偏移GM 侧SubBlock1 错位或下半块结果错误用GetTaskRation()/GetSubBlockIdx()计算 GM row offsetUB 读取 cLocal_ 时加 sub-block 偏移V1 读到垃圾值精度完全错误SplitM 已硬件分片从 UB offset 0 读取matmulAreaBytes行步长用 16 而非 nAlignL0CUB buffer 重叠bias/scale 覆写 matmul 结果行步长 nAlignL0C不是 L0C cube 边长 16UB→GM DataCopyPadsrcStride传 bytestail 场景行步长错误UB 侧 stride 是 32B 单位nAlign 对齐时传 0用MTE3_MTE2自 Set 自 Wait无实际同步效果用V_MTE2MTE3_V分别保护不同 buffer缺少 Init 预发射或析构排空首轮 hang 或尾轮 flag 泄漏Init 预发射所有反向 SetFlag析构排空所有 WaitFlag只设计正向依赖不设计反向依赖小 shape PASS大 shape 随机错数或 hang按blaze-sync-patterns.md补全反向依赖把 RegBase 中间值写回 UBUB 压力过大stageRows 降低中间值优先保留在RegTensor在 CV 中重新设计 vector tiling与 C 部分 tile / offset 脱节只复用 Cube tilingV 部分消费剩余 UB【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考