CANN/cannbot-skills分组矩阵乘实现注意事项

📅 2026/7/10 20:26:41
CANN/cannbot-skills分组矩阵乘实现注意事项
Grouped Matmul — 分组矩阵乘含 MoE 融合实现注意事项【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills导航设计选型见 design skill kernels/grouped-matmul.mdDevice 调用基础见 architecture/02-device-calling.md。 参考 example02_grouped_matmul_slice_m、07_*_per_token_dequant_moe、10_*_per_token_dequant_multistage_workspace。Grouped Matmul 处理多组独立C_i A_i × B_i。本文聚焦实现期易踩的非显然坑——尤其是含融合 epiloguedequant / SwiGLU / per-token 量化时的同步、调度与调试。结构性选型请先读设计侧文档。1. 骨架要点项目写法DispatchPolicyMmadAtlasA2PreloadAsync纯分组/MmadAtlasA2PreloadAsyncWithCallback融合性能优先Kernel分组 kernelslice_m/k/QuantMatmulMultiStageWorkspace系量化融合逐组 offsetA_i起址 Σ_{ji} M_j × Kslice_mB_i/C_i同理host tiling 或 device GroupList 计算Workspace指针透传GM_ADDR userWs workspace;hand-launch 直调禁GetUserWorkspace见 rules.md Δ42. ★ 跨 PIPE 同步栅栏高频坑编译不报错融合算子在自定义 Tile / Epilogue 里手动编排多 PIPE 时PipeBarrierPIPE_V只保证 V-V 排序不跨 PIPE。凡跨 PIPE 的数据依赖必须配齐对应方向的SetFlag/WaitFlag否则会读到「半成品」中间结果——且编译期完全不报错。跨 PIPE 同步事件对照表数据依赖需要的同步典型场景V 算完 → MTE3 存出SetFlag/WaitFlagPIPE_V, PIPE_MTE3SwiGLU/激活算完的结果存进 workspaceMTE2 搬入 → V 计算SetFlag/WaitFlagPIPE_MTE2, PIPE_V从 GM 搬入数据后做矢量运算MTE3 存出 → MTE2 复用同 bufferSetFlag/WaitFlagPIPE_MTE3, PIPE_MTE2双 buffer 轮转存完才能覆盖V-V 之间PipeBarrierPIPE_V仅同 PIPE 排序不能替代上面任何一条典型事故模式V→MTE3 缺失现象量化输出几乎全部饱和到 int8 极值但同一算子产出的量化 scale 数值精确编译无任何报错。根因Pass-1 把中间结果 tile 存到 workspace 的存出指令PIPE_MTE3在矢量后处理PIPE_V写的中间 UB buffer退役之前就读了它——二者间只有PipeBarrierPIPE_V缺 V→MTE3 同步。MTE3 把半成品中间结果存进了 workspacePass-2 读回量化自然全错。解法存储中间结果前加SetFlag/WaitFlagPIPE_V, PIPE_MTE3并把上一次存储的守护WaitFlagPIPE_MTE3, PIPE_V上提到条带循环顶部保证覆盖前上一轮已存完。「部分正确」的迷惑性双输出里一个对scale一个错量化结果往往是某条数据路径的同步缺失而非整体逻辑错——应按数据路径而非按输出排查。scale 对是因为它走的是另一条已正确同步的核内累积路径。3. ★ 跨 N-block 行归约两趟 epilogue 写法设计背景见 design kernels/grouped-matmul.md Step 4 模式 B。per-token 动态量化等行归约后处理per-block 轮转 workspace 只能看到单个 N-block必须改 row-block 调度 两趟。调度按 M-row-block 切分同一 row-block 的全部 N-block 由同一核顺序处理使整行归约成为核内局部归约A2 无 fp32 HBM 原子 max必须核内做。for each M-row-block 分给本核: # Pass-1遍历该 row-block 的全部 N-block for nb in N-blocks: S_tile matmul dequant SwiGLU(...) # fp32 中间结果 copy S_tile - S_workspace[rowBlockM, nb区段] # (a) 落盘★需 V→MTE3 同步 ReduceMax(|S_tile|) - 累积到 UB 常驻 rowMaxAbs[rowBlockM] # (b) 核内跨 N-block Max # Pass-2整行同一 scale Q_scale rowMaxAbs / 127 # 写出 for nb in N-blocks: read S_workspace[rowBlockM, nb区段] # ★需 MTE3→MTE2 守护 Q round(S / Q_scale) # 整行同一 Q_scale 量化 copy Q - 输出关键点rowMaxAbs必须UB 常驻、跨 N-block 累积不是每 N-block 各自算。Pass-1 落盘 S 与 Pass-2 读回之间靠 S workspace 中转——这正是 §2 V→MTE3 / MTE3→MTE2 同步的来源。S workspace 按 per-core 复用单核rowBlockM × N × fp32以典型 N 量级估算约数 MB/核、全局数十 MB。量级写进 host tiling 的 workspaceSize。4. ★ 中间结果 dump 比对融合算子调试方法论融合算子输出错误时别在最终输出层猜逻辑。优先 dump 关键中间 workspace如落盘的中间结果 S与 golden 逐元素比对快速二分定位是「计算错」还是「搬运/同步错」。做法在自定义 Tile/Epilogue 加一个标准可选 dump 钩子如-DCATLASS_DUMP_中间量编译开关把 device 端中间 workspace dump 出来用脚本与 golden 逐元素 diff。实战收益上面 §2 的事故正是 dump 中间结果后由逐元素 diff 出现大偏差锁定「存储环节出错」而非量化逻辑补上同步后中间结果回到位精确diff 近 0。没有 dump 的话会在量化逻辑里反复空转。建议把CATLASS_DUMP_*做成自定义 Tile/Epilogue 的标准可选钩子交付时关闭。5. 平台能力A2实现约束A2 (2201)无 L0C→UB 的 CV 直通路径Cube 结果只能经HBM workspace中转给 Vector不能照搬example 65的 UB ping/pong那是 950 专属。A2 双 C region / 多 stage 的 AIC→AIV 同步走 example 10 的「HBM workspace CrossCoreFlag」骨架。锁定参考 example 前先核对其原生平台详见 design Step 5。6. ★ 性能对标基准融合算子无同形态 example 时Reviewer 默认口径是「与 catlass同形态 example基线差距 30%」。但融合 grouped matmulGMMdequantSwiGLU量化等在catlass/examples/里没有完全同形态的样例——硬套单一 matmul example 会低估真实工作量漏掉 dequant/激活/量化/行归约让「达标」失去意义。此时按下表降级选取基准。对标基准优先级优先级基准适用口径1首选同语义 aclnn / 竞品算子实测 Task Duration存在功能等价的官方/竞品融合算子catlass 实现 ≤ 竞品 × 1.0~1.3视融合深度2最近 example 拆解相加分组 matmul example epilogue 部分各自 Task Duration 估算求和无竞品但能拆出可跑的子算子融合实现应优于朴素串接之和体现融合收益3Cube 理论上限 / MTE 带宽上限无任何可跑对照实测 vs 理论上限的达成率与平台无关最硬4同算子历史最优PRE/POST 调优迭代调优阶段每轮只要求不回退同语义对标的操作要点确认功能等价竞品算子的数学公式、I/O dtype/layout、量化粒度per-token / per-channel须与本算子一致否则不可直接比时延。例如 A8W8 GMMSwiGLUper-token 量化须找同为 A8W8、同 SwiGLU、同 per-token 的竞品。同 shape 同 groupList用竞品 example 里的M/K/N/E与 groupList 分布跑出竞品基线再用完全相同的 shape跑 catlass 实现避免 shape 不一致导致的伪对比。★ 对齐竞品的格式/约束硬条件竞品常对权重格式、dtype、轴长有硬约束如某 A8W8 GMM 强制权重FRACTAL_NZ、N≤上限、K上限。对标前先核对这些约束——若要与竞品同条件比时延本算子实现需对齐到相同格式/约束如权重也用 NZ而不是用不同格式跑完再去折算差异折算会引入扯皮且不可比。⚠️ 格式/dtype 未写明时必须向上游追问权重 ND 还是 NZ、量化粒度、轴长上限等是需求项不是开发期可自行假定的。用户/设计未写明时按 design 流程向上游追问确认禁止臆测一种格式实现后再倒推对标口径。改格式ND↔NZ属算子设计变更须回到 DESIGN.md 由 Architect 决策不在开发期擅自切换。对标产物归档把竞品基线与 catlass 实测一并写入docs/perf/round_NNN/注明竞品来源算子名 版本/commit、shape、groupList、双方权重格式便于 Reviewer 复核。 选基准本身就是设计结论DESIGN 里若已锁定「对标某 aclnn 算子」开发期直接复用其 example 跑基线若设计未指定按上表优先级降级并在 PLAN.md 记录所选基准与理由。7. ★ 小 M 核空转rowBlockM的双重角色陷阱与专用调度变体教训来源row-block 调度的 per-token 量化 GMM大 shape 达标、唯独小 MM 小到 row-block 数 核数严重不达标实测比值 1.7×。这是行归约调度§3在小规模下的必然副作用设计期极易漏判。7.1 根因rowBlockM同时是「核调度单元」和「BlockMmad 的 M-tile」§3 的 row-block 调度里rowBlockML1TileShape::M典型 128身兼两职核调度单元totalRowBlocks Σ CeilDiv(M_i, rowBlockM)个 row-block 按 round-robin 摊到各核。BlockMmad 的 M 方向 tile每个 row-block 就是一次 matmul 的 M 分块。当totalRowBlocks 核数如 M1024、rowBlockM128 ⇒ 仅 8 个 row-block、20 核 ⇒12 核空转cube_time0。Task Duration 由最慢核决定空转核不干活等价于用 8 核跑时延自然远差于铺满 20 核的竞品。7.2 ⚠️ 反直觉陷阱「缩小rowBlockM铺满核」会更慢已证伪直觉解法是把rowBlockM从 128 缩到 32使CeilDiv(M, rowBlockM) ≥ 核数、铺满所有核。实测反而更慢29%因为两个角色耦合不可分缩小rowBlockM同时缩小了 BlockMmad 的 M-tile ⇒ block 数翻数倍 ⇒每个 block 都要重新从 GM 串流主导的 B 权重B 加载 ×N 膨胀⇒ MTE2/MTE1/scalar 全线上涨、aic_cube_ratio从 0.76 崩到 0.38cube 被喂不饱、饿死。多用 12 个核补不回每核数倍的访存/开销膨胀。结论调度粒度 计算块粒度时「缩粒度铺满核」与「保 cube/访存效率」互斥。小 M 核空转是design_issue需换调度/分块不是 Step 6 能调优掉的。7.3 正解小-M 专用调度变体N-split 全网格 原子-free 跨核行归约对齐竞品做法竞品小-M 常切到专用 kernel 变体。要点切 N 不切 MM-tile 恒 128B 每列只加载一次、零膨胀、cube 不饿死改为在N 方向split让全网格(rowBlock × nBlock)round-robin 铺满所有核对齐 example 10 骨架。判据充要host tiling 期schedMode (totalRowBlocks coreNum nBlocks 1) ? SMALL_M : LARGE_M用复合 TilingKey如key schedMode * K dtypeKey分流SMALL_M 走独立新 Kernel 文件。与行归约§3的冲突及解法N-split 后整行的 N-block 散落多核而 per-token max 是行归约、A2 无 fp32 HBM 原子 max。用两阶段原子-free 归约全程限定AIV 域AIC 只产 C-region 不参与握手阶段 A各核算 S 落全局 S workspace 写私有 partial-max 槽互不重叠、免原子AIV 域CrossCoreBarrier→ 阶段 Breducer 跨 laneMax合并整行 max → 写 Q_scale → barrier → 各核用整行同一 scale 量化。partial-max 槽按 nBlock 索引不是 coreIdxslot 宽度 nBlocks × subblockNumAIV 双 subblock 各占槽免竞争。workspace 变化S workspace 从 §3 的 per-core 复用改为按totalRowBlocks全驻留跨 barrier 供别核读回寻址式r_global × L1.M × nHalf新增 partial-max 私有槽区。★partial-max 区 host 端 launch 前必须aclrtMemset清零aclrtMalloc不保证零页reducer 对未写 lane 行做 Max 依赖中性元 0脏页会致该行量化精度错——只清 partial-max 区C/S 由算法写满不必清。大-M 零回退SMALL_M 是独立 Kernel 文件大 shape 恒选 LARGE_M 物理不进入既有 row-block 路径一字不改BlockEpilogue 只增公有方法不动既有槽位/UB_STAGES——用结构性隔离保证零回退避免 round_004 那种改同一 kernel 参数需 env 守护的脆弱做法。7.4CrossCoreBarrier是核类型域内栅栏易错catlass/include/catlass/arch/cross_core_sync.hpp里 barrier flag 由g_coreType编译期选取AIV 与 AIC 用不同 flag、不互通。所以两阶段归约链必须全程限定 AIV 域触发 barrierAIC 侧不能参与同一 barrier 握手否则半侧挂死。参考纯 AIV 域先例#ifdef __DAV_VEC__包住整段归约。7.5 ★ 复用同一 HardEvent 的 SetFlag/WaitFlag 计数净平衡新增方法小-M partial-max 导出踩坑教训来源小-M N-split 的 partial-max 导出函数复用了主 Pass 的MTE3_V事件队列导致一类只在低-K / 小规模偶发、被误判成时序竞态的确定性量化错——实际连错三次根因才定位。坑小-M 专用 kernel 里partial-max 导出函数写私有槽的那步复用了主计算 Pass 的 per-strip HardEvent如eventQMTE3VList的MTE3_V做 SetFlag/WaitFlag。主 Pass 的 strip 循环末尾会遗留一个悬挂SetFlagMTE3_V1本应由「下一 strip」顶部 WaitFlag 消费但末 strip 无下一迭代。导出函数进入时计数已 1 → 它的每个WaitFlag都释放在上一次陈旧 set上 → 每次导出存储晚一拍被等待 → 顶部 strip 的 partial-max 存储要等到下一个 cell的主 Pass而那时ResetRowMaxPIPE_V Duplicate已覆盖单一内驻的 rowMaxAbs → 顶 strip 导出被丢弃 → reducer 少计高行 max → 量化 scale 偏小 → int8 输出错但 scale 之外一切正确极具迷惑性。低-K 时 AIC 喂 cell 快、丢弃几乎必然高-K 给晚到存储足够余量常能赶上 →伪装成「K 相关时序竞态」。修复不是加大栅栏是让复用事件的整条链计数净平衡——导出函数 strip 循环前加一个WaitFlagMTE3_V消费上游遗留的悬挂 token、循环后补一个SetFlagMTE3_V重新武装下一 cell 主 Pass strip[0] 期望的 1。两条标量指令架构零改动。两个决定性诊断手段凡「小规模/低-K 偶发、大规模消失」的正确性问题必先做PipeBarrierPIPE_ALL二分法在疑似点前加一次全流水 drain。若仍 FAIL ⇒ 缺陷能扛过完整 drain不是时序竞态必是确定性 accounting事件计数 / 地址偏移 / buffer 覆盖问题——别再往「加同步」方向空转。若 PASS 才是真跨 PIPE 同步缺失回 §2 配对向 SetFlag/WaitFlag。byte-identical shape 翻转即确定性信号完全相同的 shape 仅换 group-list 就 PASS↔FAIL不是竞态证据竞态应随机、跨 rep 抖动而是「跨 cell/跨块的确定性链在不同边界上净平衡与否」的差异——指向 accounting bug。用中间量 device dump如 partial-max 逐 lane 探针MISSING_LANES逐 lane/逐行比对 golden直接看「哪条 lane/哪个 strip 的数据丢了」。强制核对任何「复用另一循环/另一 Pass 的 HardEvent」的写法必须把整条链的SetFlag/WaitFlag计数在跨迭代、跨 cell 边界上核平尤其末迭代遗留的悬挂 token 会被下一复用者错配一拍。新开一个独立 event 队列往往比复用更安全。强制规则规则说明跨 PIPE 同步凡跨 PIPE 数据依赖必配SetFlag/WaitFlagPipeBarrierPIPE_V不跨 PIPE§2行归约调度per-token 量化等行归约用 row-block 调度 两趟 epilogue禁 per-block 轮转直接归约§3Δ4workspace 指针透传userWs workspace禁GetUserWorkspace/SetSysWorkspaceForceΔ10SwiGLU 等配对列门控按[M,H]调度见 rules.md Δ10测试覆盖必须含N L1TileShape::N的实网 shape暴露跨 N-block bug小 M shapetotalRowBlocks 核数暴露核空转§7性能对标融合算子无同形态 example 时按 §6 优先级选基准首选同语义 aclnn/竞品禁无基准空跑小 M 调度row-block 调度的 GMM 小 M 核空转是 design_issue禁缩rowBlockM铺核cube 饿死§7.2须小-M 专用变体N-split AIV 域原子-free 归约§7.3partial-max 清零小-M N-split 的 partial-max workspace 区 host 端 launch 前必须aclrtMemset清零§7.3复用 HardEvent 计数平衡复用另一循环/Pass 的 HardEvent 做 SetFlag/WaitFlag 时须核对整条链跨迭代/跨 cell 的 set/wait 计数净平衡末迭代悬挂 token 会被下一复用者错配一拍宁可新开独立 event 队列§7.5偶发正确性诊断「小规模/低-K 偶发、大规模消失」的正确性问题先用PipeBarrierPIPE_ALL二分「时序竞态 vs 确定性 accounting」drain 后仍 FAIL ⇒ 非竞态byte-identical shape 换 group-list 翻转 PASS/FAIL ⇒ 确定性 accounting非竞态§7.5【免费下载链接】cannbot-skillsCANNBot 是面向 CANN 开发的用于提升开发效率的系列智能体本仓库为其提供可复用的 Skills 模块。项目地址: https://gitcode.com/cann/cannbot-skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考