From 1412964a0ec37c35ad9ab68934fa64d9a65db3c9 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 28 Aug 2026 07:52:40 +0000 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=20UnitFlag=20=E4=B8=89?= =?UTF-8?q?=E6=9D=A1=E5=AE=98=E6=96=B9=E7=BA=A6=E6=9D=9F=E7=9A=84=E9=80=90?= =?UTF-8?q?=E6=9D=A1=E8=80=83=E6=8D=AE=EF=BC=88=E5=90=AB=E4=B9=89/?= =?UTF-8?q?=E5=87=BA=E5=A4=84/950PR=E5=AE=9E=E9=99=85=E7=94=9F=E6=95=88?= =?UTF-8?q?=E6=80=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Matmul/UnitFlag能否取代L0C_DoubleBuffer.md | 81 +++++++++++++++++++++- 1 file changed, 79 insertions(+), 2 deletions(-) diff --git a/Matmul/UnitFlag能否取代L0C_DoubleBuffer.md b/Matmul/UnitFlag能否取代L0C_DoubleBuffer.md index 21f8e86..64ebef0 100644 --- a/Matmul/UnitFlag能否取代L0C_DoubleBuffer.md +++ b/Matmul/UnitFlag能否取代L0C_DoubleBuffer.md @@ -145,13 +145,86 @@ UnitFlag 的官方约束(缺一不可): --- +### 3.4 对官方三条约束的逐条考据(答疑) + +针对三条容易被误读的官方约束,逐一给出原文出处、准确含义、以及在 mat_mul_v3 / 950PR 上的实际适用性。 + +#### 3.4.1 「UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」——具体是什么约束? + +**官方原文出处**(两处,措辞略异,商用版更完整): + +- 昇腾社区官网 CANN 社区版 → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → **《Matmul 高阶 API 开启 UnitFlag》**,约束条件第 3 条: + > 开启 UnitFlag 功能时,若同时开启 **L0C 累加** 功能,不支持**多次计算、一次输出**。 + > (注:该页面在线渲染时 "L0C 累加" 一词在部分版本缺失,仅显示"若同时开启功能",以下方商用版为准。) +- 昇腾社区官网 CANN 商用版 → 同名章节 **《Matmul 高阶 API 使能 UnitFlag》**,约束条件第 3 条(完整表述): + > 使能 UnitFlag 功能时,若同时使能 **L0C 累加功能**,不支持**多次 Iterate 计算、一次 GetTensorC 输出**。 + +**准确含义拆解**:这条约束针对的是 **「同一个 base 块,K 方向分多片,在 L0C 上驻留并多次累加,最后才一次性 GetTensorC 搬出」** 的编程形态。即: + +``` +// 被禁止的形态:K 在 L0C 内分片累加,最后一次才搬出 +for (kSlice = 0; kSlice < kSliceNum; kSlice++) { // 多次 Iterate + mm.Iterate(); // 每片 K 的部分结果累加进同一块 L0C(L0C 累加) +} +mm.GetTensorC(c); // 只在最后一次性搬出(一次输出) +``` + +**为什么 UnitFlag 与这种形态冲突**:UnitFlag 的硬件机制是「MMAD 每产出 512B 成品,就立即触发 Fixpipe 把这段搬走」。但 L0C 累加形态下,前面若干次 Iterate 的结果是**部分和,还必须留在 L0C 里等后续 K 片继续累加**,不能被搬走。一旦开 UnitFlag,MMAD 算出 512B 就被 Fixpipe 立即搬出,L0C 里就保不住部分和、后续累加无从谈起——两者在"这 512B 到底是驻留还是立即搬走"上直接矛盾,故硬件/API 不允许叠加。 + +**mat_mul_v3 是否踩这条约束?——不踩(常规路径)**。看 `mat_mul_asw_kernel.h:140-141`: + +```cpp +mm_.Iterate(); +mm_.GetTensorC(cGlobal_[...], enAtomic || kIndex != 0); // 每次 Iterate 紧跟一次 GetTensorC +``` + +mat_mul_v3 的普通 ASWT 是 **「一次 Iterate → 一次 GetTensorC」一一配对**(即使在 splitKRound 循环里,也是 Iterate 与 GetTensorC 成对出现,用 `enAtomic || kIndex!=0` 在 GM 侧做原子加,而非在 L0C 里驻留累加)。它**不是**"多次 Iterate、一次 GetTensorC",因此常规路径不触碰这条约束,UnitFlag 可用。 + +**真正会踩这条的场景**:把 split-K 的**核内**部分和放在 L0C 里驻留累加(L0C 累加),或 `intraBlockPartSum`(两 AIV 结果在 L0C 累加)这类显式 L0C 累加特性。StreamK 之所以要绕道 workspace 由 AIV 累加、而不是在 L0C 里累加,正是为了避开这条。 + +#### 3.4.2 一次 Iterate 最大支持的 K 是多少? + +**先澄清概念**:官方约束里没有任何一条把 "一次 Iterate 的 K 上限" 与 UnitFlag 挂钩。**UnitFlag 约束的不是 K 的大小,而是"累加形态"**(见上一条)。一次 Iterate 能算多大的 K,由 **L0C 之外的资源(L1/L0A/L0B 容量、stepKa/stepKb 流水深度、baseK)** 决定,与 UnitFlag 无关: + +- 单次 Iterate 的 K 由 `singleCoreK` 决定,而 `singleCoreK` 在 L1 内按 `stepKa·baseK`(A 侧)/ `stepKb·baseK`(B 侧)分多片流水喂给 Cube,**每片的中间结果在同一 L0C 上原地累加**(这是 MMAD 的固有累加语义,不是"多次 Iterate 一次输出",也不违反 UnitFlag——因为整个 singleCoreK 是一次 Iterate 内部完成的)。 +- 换句话说:一次 Iterate 内部,Cube 会在 L0C 上做 `singleCoreK/baseK` 次累加,这在 UnitFlag 下是允许的;UnitFlag 禁止的是"**跨多次 Iterate** 的累加 + 只在最后一次输出"。 + +**结论**:**没有"一次 Iterate 最大 K 受 UnitFlag 限制"这回事**。一次 Iterate 的 K 上限由 L1/L0 容量与 stepK 流水决定(例如 950PR 上 `maxStepK ≤ 8`、`baseK ≈ 128B/dtype`),UnitFlag 只管"算出的 512B 何时搬出",不管 K 多大。把这条约束理解成"K 不能超过某值"是误读——它限的是"累加跨几次 Iterate",不是"K 的绝对大小"。 + +#### 3.4.3 「不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水」——是什么意思?L1 能直接写回 GM 吗? + +**官方原文出处**:同上两篇《Matmul 高阶 API(使能/开启)UnitFlag》,约束条件第 2 条: +> 使能 UnitFlag 功能时,不支持算子内同时存在 **CO1(L0C) 搬出到 Global Memory** 和 **A1(L1) 搬出到 Global Memory** 的两种流水。 + +**这条的字面意思**:UnitFlag 开启时,同一个算子里不能**同时**出现两类搬出流水——一类是 L0C→GM(结果搬出,走 Fixpipe),另一类是 L1(A1)→GM(从 L1 直接把数据写回 GM)。即搬出通路的"源头"必须统一为 L0C,不能 L0C 和 L1 混着当搬出源。 + +**「L1 能直接写回 GM 吗?」——分架构看,这正是关键点**: + +- **在 950PR(351x 架构)上:不能。** 950 白皮书明确,351x 相对上代**删除了 `L1→GM` 的回写通路**(同时也删除了 `GM→L0A/L0B` 的直达通路),一切矩阵数据的搬出都必须经 `L0C→Fixpipe→GM`。所以**在 950PR 上根本不存在"L1→GM"这条物理通路**,这条约束对 950PR 而言**自然满足、不会触发**——它更像是从支持 L1→GM 的老架构(如部分 A2/A3 代际)沿用过来的兼容性约束。 +- **mat_mul_v3 在 950PR 上的搬出源**:普通 ASWT 的结果一律走 `L0C→Fixpipe→GM(C)`(`mm_.GetTensorC(cGlobal...)`),没有任何 L1→GM 的直接搬出 → 符合该约束。 + +**小结**:这条约束的语义是"搬出源必须唯一(只能是 L0C)",防止 UnitFlag 的细粒度同步在"两种不同源头的搬出"上产生时序混乱。在 950PR 上由于 L1→GM 通路已被硬件删除,这条约束**自动成立**,不会成为限制 mat_mul_v3 开 UnitFlag 的因素。 + +#### 3.4.4 三条约束在 mat_mul_v3 / 950PR 上的实际生效性汇总 + +| 官方约束 | 准确含义 | 950PR 上是否实质限制 mat_mul_v3 | +|---|---|---| +| 仅 Norm/IBShare/MDL 模板 | UnitFlag 只在三个高阶模板下有效 | mat_mul_v3 用 MDL(GetMDLConfig)→ **满足** | +| 不能同时有 CO1→GM 与 A1→GM 两种搬出 | 搬出源必须唯一为 L0C | 351x 已删 L1→GM 通路 → **自动满足,无实质限制** | +| L0C 累加时不支持多次 Iterate 一次输出 | 禁止跨 Iterate 的 L0C 驻留累加 + 末次才搬出 | mat_mul_v3 常规路径是 Iterate/GetTensorC 一一配对 → **不触碰,UnitFlag 可用**;仅显式 L0C 累加(核内 split-K 驻留 / intraBlockPartSum)才触碰 | +| MTE2 Bound 已掩盖时收益小 | Fixpipe 已被搬运流水掩盖时无需再开 | 这是**收益**问题不是**正确性**问题,不影响可用性 | + +**由此可得更精确的判断**:mat_mul_v3 在 950PR 上常规路径(含普通 ASWT、A/B 全载、以及把 K 分片放到 GM workspace 由 AIV 累加的 StreamK)**都满足 UnitFlag 的可用条件且默认已开启**;真正被 UnitFlag 排除的是"**在 L0C 里做跨 Iterate 驻留累加**"这一类形态——而这种形态在 mat_mul_v3 里被刻意用"GM 原子加 / workspace + AIV 累加"规避了。这进一步支撑了 §3.3 的结论:**在 950PR 上 UnitFlag 的合法域覆盖了 mat_mul_v3 的全部主路径,L0C DB 的补位空间被压缩得很小**。 + +--- + ## 4. 对本项目性能建模与最优方案的直接推论 1. **建模时 Fixpipe 项要分两种掩盖**:在性能模型 `T_total = max(T_MMAD, T_MTE2, T_MTE1, T_FIXPIPE)` 中,`T_FIXPIPE` 的实际暴露量应写成 - 开 UnitFlag(合法域内):`T_FIXPIPE_exposed ≈ tail`(512B 尾开销)→ 基本可以忽略,Fixpipe 通常不构成独立瓶颈; - 不开 UnitFlag(或不可用):`T_FIXPIPE_exposed = max(0, t_fp - t_mm)`(DB 掩盖后的残余),且仅当 `baseM·baseN ≤ 32768` 时 DB 才生效,否则 `T_FIXPIPE_exposed = t_fp`(全暴露)。 2. **最优 tiling 的取舍被简化**:既然 UnitFlag 默认开,baseM/baseN 的搜索**不必再为"能否开 L0C DB"让步**,可以更激进地逼近 256×256(重复读最少)。原先"为开 DB 而把 baseN 砍半"的权衡在 UnitFlag 合法域内不成立。这是对 §3.2 `GetRebalanceBlock` 的一处实质修正点。 -3. **StreamK 的 K 分片场景要单独建模**:StreamK/大 K 分片累加命中 UnitFlag 的"多次 Iterate 一次输出"约束,其 Fixpipe 掩盖机制与普通 ASWT 不同,性能模型需为这类 case 单列。 +3. **真正需要单独建模的 K 累加形态**:需要警惕的不是 mat_mul_v3 的主路径,而是"**在 L0C 里做跨 Iterate 驻留累加**"这一类形态(核内 split-K 驻留 / intraBlockPartSum 等)。这类形态被 UnitFlag 约束排除,只能靠 L0C DB 做块间掩盖。mat_mul_v3 的 StreamK 用 workspace + AIV 累加规避了它,因此主路径不受影响;但若未来引入 L0C 驻留累加的新模板,性能模型需为其单列 Fixpipe 掩盖方式(回到 DB 主导)。 --- @@ -159,10 +232,14 @@ UnitFlag 的官方约束(缺一不可): | 证据 | 位置 | |---|---| +| UnitFlag 三条约束官方原文(在线) | 昇腾社区官网 → CANN → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → 《Matmul 高阶 API 开启 UnitFlag》(社区版)/ 《Matmul 高阶 API 使能 UnitFlag》(商用版)。社区版直达链接:见任务输入所给 UnitFlag.md 页面 | +| UnitFlag 三条约束官方原文(本地) | `昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md`(第19/21行)、`昇腾NPU知识库/CANN商用版9.0.0/01_AscendC算子开发/203_..._使能UnitFlag.md`(第21/23行,表述最完整) | | GetMDLConfig 签名(第7参=enUnitFlag) | `昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/631_..._GetMDLConfig.md` | | enUnitFlag 默认值与约束 | `昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/626_..._MatmulConfig.md` | | UnitFlag 512B 细粒度同步定义 | `昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md` | +| 351x 删除 L1→GM / GM→L0 直达通路 | `昇腾NPU知识库/00_硬件/昇腾950_NPU架构白皮书.txt`(CV融合/数据通路章节);同仓 `昇腾950PR/昇腾950PR架构解读.md` §5.1 | | MM_CFG_NO_PRELOAD 显式开 enUnitFlag | `ops-nn/matmul/mat_mul_v3/op_kernel/mat_mul_v3_common.h:72` | +| 普通 ASWT Iterate/GetTensorC 一一配对 | `ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_asw_kernel.h:140-141` | | StreamK 回调显式 unitFlag=3 | `ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_kernel.h:36-37` | -| StreamK K 分片累加(L0C→workspace→AIV) | `ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_block.h`、`mat_mul_stream_k_kernel.h:188-202` | +| StreamK K 分片走 workspace + AIV 累加 | `ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_kernel.h:188-259`、`mat_mul_stream_k_block.h` | | dbL0C 判据 baseM·baseN≤32768 | `ops-nn/matmul/mat_mul_v3/op_host/op_tiling/arch35/matmul_v3_tiling_helper.cpp:490` |