修正:补充官方约束出处,澄清高阶API约束与底层unitFlag=2/3机制的两层区别,纠正K分段累加论断

This commit is contained in:
2026-08-28 08:14:45 +00:00
parent 5316494138
commit a5a35cae83

View File

@@ -95,15 +95,16 @@ L0C.A(块n+2): [==== MMAD ====]···
<li>UnitFlag 达到 <code>≈max(t_mm,t_fp)</code> <strong>几乎无条件</strong>(块内 512B 流水),还省下 L0C 容量(这份容量可用来把 baseM/baseN 做大,减少重复读)。</li> <li>UnitFlag 达到 <code>≈max(t_mm,t_fp)</code> <strong>几乎无条件</strong>(块内 512B 流水),还省下 L0C 容量(这份容量可用来把 baseM/baseN 做大,减少重复读)。</li>
</ul> </ul>
<p>所以<strong>在 UnitFlag 合法域内DB 相对它没有独立价值,可以被取代</strong>。你的直觉在这一层是对的。</p> <p>所以<strong>在 UnitFlag 合法域内DB 相对它没有独立价值,可以被取代</strong>。你的直觉在这一层是对的。</p>
<h3>3.2 从合法域看UnitFlag 有约束DB 是兜底</h3> <h3>3.2 从合法域看UnitFlag 有约束DB 是兜底</h3>
<p>UnitFlag 的官方约束(缺一不可):</p> </blockquote>
<p><strong>高阶 API 层官方约束</strong>(出处:昇腾官网 → CANN → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → 《Matmul 高阶 API 开启 UnitFlag》社区版 / 《Matmul 高阶 API 使能 UnitFlag》商用版&quot;约束条件&quot;一节,逐字摘录):</p>
<ol> <ol>
<li><strong>模板限制</strong>Norm / IBShare / MDL 三模板</li> <li><strong>模板限制</strong>UnitFlag 功能仅支持 <strong>NormIBShare、MDL</strong>模板</li>
<li><strong>流水互斥</strong>使能后<strong>不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水</strong></li> <li><strong>流水互斥</strong>开启 UnitFlag 功能时,不支持算子内<strong>同时存在 L0C BufferCO1搬出到 Global Memory 和 L1 BufferA1搬出到 Global Memory 的两种流水</strong></li>
<li><strong>累加限制</strong>使能 + L0C 累加时,<strong>不支持多次 Iterate、一次 GetTensorC 输出</strong></li> <li><strong>累加限制</strong>开启 UnitFlag 功能时,若同时开启 <strong>L0C 累加</strong> 功能,不支持<strong>多次 Iterate 计算、一次 GetTensorC 输出</strong></li>
<li><strong>收益前提</strong>:仅当 MMAD 与 Fixpipe 串行且未被 MTE2 等其他流水掩盖时才有收益</li> <li><strong>收益前提</strong>:仅当 MMAD 流水与 FIXPIPE 流水<strong>串行执行且未被其他流水掩盖</strong>(比如 MTE2 Bound开启 UnitFlag 才有收益;否则收益很小</li>
</ol> </ol>
<p>凡是落在这些约束之外的场景(最典型就是 §2.2 的<strong>多分片 K 在 L0C 累加</strong>UnitFlag 直接不可用,<strong>只能退回 L0C DB</strong> 做块间掩盖。</p> <p>这四条是官方文档明确写出的原文(第 2、3 条在 §3.4 逐条考据)。落在高阶 API 约束之外的场景UnitFlag 不可用,<strong>只能退回 L0C DB</strong> 做块间掩盖。</p>
<h3>3.3 从 mat_mul_v3 实际看:默认已开 UnitFlagDB 是「补位」而非「主选」</h3> <h3>3.3 从 mat_mul_v3 实际看:默认已开 UnitFlagDB 是「补位」而非「主选」</h3>
<ul> <ul>
<li>mat_mul_v3 主路径 <code>MM_CFG_NO_PRELOAD</code> 已把 enUnitFlag 置真StreamK 又在回调里显式 <code>unitFlag=3</code><strong>UnitFlag 是该算子的默认掩盖手段</strong></li> <li>mat_mul_v3 主路径 <code>MM_CFG_NO_PRELOAD</code> 已把 enUnitFlag 置真StreamK 又在回调里显式 <code>unitFlag=3</code><strong>UnitFlag 是该算子的默认掩盖手段</strong></li>
@@ -113,35 +114,52 @@ L0C.A(块n+2): [==== MMAD ====]···
<hr/> <hr/>
<h3>3.4 对官方三条约束的逐条考据(答疑)</h3> <h3>3.4 对官方三条约束的逐条考据(答疑)</h3>
<p>针对三条容易被误读的官方约束,逐一给出原文出处、准确含义、以及在 mat_mul_v3 / 950PR 上的实际适用性。</p> <p>针对三条容易被误读的官方约束,逐一给出原文出处、准确含义、以及在 mat_mul_v3 / 950PR 上的实际适用性。</p>
<h4>3.4.1 「UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」——具体是什么约束?</h4> <h4>3.4.1 「UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」——具体是什么约束?它与&quot;K 分段累加&quot;是什么关系?</h4>
<p><strong>官方原文出处</strong>(两处,措辞略异,商用版更完整</p> <p><strong>官方原文出处</strong>高阶 API 层,两处):</p>
<ul> <ul>
<li>昇腾社区官网 CANN 社区版 → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → <strong>《Matmul 高阶 API 开启 UnitFlag》</strong>,约束条件第 3 条:</li> <li>昇腾社区官网 CANN 社区版 → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → <strong>《Matmul 高阶 API 开启 UnitFlag》</strong>,约束条件第 3 条:</li>
</ul> </ul>
</blockquote> </blockquote>
<ul> <ul>
<li>昇腾社区官网 CANN 商用版 → 同名章节 <strong>《Matmul 高阶 API 使能 UnitFlag》</strong>,约束条件第 3 条(完整表述):</li> <li>昇腾社区官网 CANN 商用版 → 同名章节 <strong>《Matmul 高阶 API 使能 UnitFlag》</strong>,约束条件第 3 条(表述更完整</li>
</ul> </ul>
</blockquote> </blockquote>
<p><strong>准确含义拆解</strong>:这条约束针对的是 <strong>「同一个 base 块K 方向分多片,在 L0C 上驻留并多次累加,最后才一次性 GetTensorC 搬出」</strong> 的编程形态。即:</p> <p><strong>这是&quot;高阶 APIIterate/GetTensorC&quot;层的约束,不是&quot;基础 APIMmad/Copy&quot;层的限制</strong>——这是理解的关键,也是本节要澄清的核心误区。</p>
<pre><code>// 被禁止的形态K 在 L0C 内分片累加,最后一次才搬出 <p><strong>它约束的准确形态</strong>:用<strong>高阶 API</strong> 编程时,同一个 base 块如果「<code>mm.Iterate()</code> 多次K 分片在 L0C 驻留累加)、最后只 <code>mm.GetTensorC()</code> 一次(一次输出)」,且此时打开了 L0C 累加功能,那么高阶 API 内部的 UnitFlag 封装无法正确处理 → 不允许。即:</p>
for (kSlice = 0; kSlice &lt; kSliceNum; kSlice++) { // 多次 Iterate <pre><code>// 高阶 API 下被禁止的形态
mm.Iterate(); // 每片 K 的部分结果累加进同一块 L0CL0C 累加) for (kSlice = 0; kSlice &lt; kSliceNum; kSlice++) {
mm.Iterate(); // 多次 Iterate部分和在 L0C 驻留累加
} }
mm.GetTensorC(c); // 只在最后一次搬出(一次输出)</code></pre> mm.GetTensorC(c); // 只在最后一次搬出</code></pre>
<p><strong>为什么 UnitFlag 与这种形态冲突</strong>UnitFlag 的硬件机制是「MMAD 每产出 512B 成品,就立即触发 Fixpipe 把这段搬走」。但 L0C 累加形态下,前面若干次 Iterate 的结果是<strong>部分和,还必须留在 L0C 里等后续 K 片继续累加</strong>,不能被搬走。一旦开 UnitFlagMMAD 算出 512B 就被 Fixpipe 立即搬出L0C 里就保不住部分和、后续累加无从谈起——两者在&quot;这 512B 到底是驻留还是立即搬走&quot;上直接矛盾,故硬件/API 不允许叠加。</p> <p><strong>但是——底层基础 APIMmad/Copy 指令)完全支持 K 分段累加,且能做出很好的流水掩盖。</strong> 这是用户指出的关键点官方《UnitFlag —— Mmad 计算关键特性说明》(基础 API/矩阵计算 Tensor API有明确机制与实例逐字核实如下</p>
<p><strong>mat_mul_v3 是否踩这条约束?——不踩(常规路径)</strong>。看 <code>mat_mul_asw_kernel.h:140-141</code></p> <p><strong>底层机制</strong>:开启 UnitFlag 后L0C Buffer 按 <strong>512B 内存块</strong>划分每块配一个「单元标志位」unit-flag指示该块可读/可写。<code>Mmad</code>(写)与 <code>Copy</code>/<code>Fixpipe</code>(读)通过 <code>unitFlag</code> 参数控制</p>
<table><thead><tr><th>unitFlag 取值</th><th>语义(写=Mmad / 读=Fixpipe</th></tr></thead><tbody>
<tr><td><strong>2</strong></td><td>指令执行后<strong>不改变</strong>标志位:写操作写完保持&quot;占用&quot;,让后续 Mmad 还能继续写这块(用于 K 累加的中间片);读操作读完保持&quot;可读&quot;,让后续 Fixpipe 还能继续读</td></tr>
<tr><td><strong>3</strong></td><td>指令执行后<strong>翻转</strong>标志位Mmad 写完置&quot;可读&quot;(放给 Fixpipe 搬Fixpipe 读完置&quot;可写&quot;(放还给 Mmad</td></tr>
</tbody></table>
<p><strong>官方 K 分段累加实例</strong>原文A=128×1024、B=1024×128沿 K 轴迭代,每次迭代 K 长 128共 8 次 Mmad 对应 1 次 Fixpipe</p>
<ul>
<li><strong>前 7 次 Mmad 设 <code>unitFlag=2</code></strong>:写入后把标志位<strong>始终保持 0</strong>,保证后续 Mmad 能继续写入同一块 L0C Buffer即在同一块 L0C 上做 K 累加);</li>
<li><strong>最后 1 次 Mmad 设 <code>unitFlag=3</code></strong>:写入后把标志位置 1保证 Fixpipe 可以读取 L0C Buffer</li>
<li><strong>Fixpipe 设 <code>unitFlag=3</code></strong>:读取后把标志位置 0保证后续 Mmad 接口可以顺利写入。</li>
</ul>
<p><strong>结论修正</strong>:所以&quot;UnitFlag 支持 K 分段累加、最后一次搬出&quot;<strong>基础 API 层是成立的、且是官方推荐用法</strong>。用户描述的&quot;以 16×K×16 为粒度,算完一段立即让 Fixpipe 搬出对应 16×16 到 GM、同时开始下一段 16×K×16 计算&quot;的掩盖方式,正是 UnitFlag 在块内流水 + K 分段场景下的正确用法,<strong>确实能有很好的掩盖</strong></p>
<p><strong>它与高阶 API 约束的边界</strong>:高阶 API 的 <code>mm.Iterate()</code> 一次调用内部会完成 <code>singleCoreK</code> 的全部 K 分片流水L0C 原地累加是 MMAD 固有语义,这一步不受约束影响);高阶约束禁的只是&quot;<strong>跨多次 <code>Iterate()</code></strong> 且最后才一次 <code>GetTensorC()</code>&quot;这种由用户在<strong>高阶层面</strong>组织的驻留累加。换言之:</p>
<ul>
<li><strong>底层 Mmad 级</strong><code>unitFlag=2/3</code> 可自由编排 K 分段累加 → 支持;</li>
<li><strong>高阶 Iterate 级</strong><code>Iterate</code> 内部 K 分片(一次 Iterate 完成)→ 支持;<code>Iterate</code> 多次 + 末次 <code>GetTensorC</code> → 不支持。</li>
</ul>
<p><strong>mat_mul_v3 是否踩高阶约束?——不踩(常规路径)</strong>。看 <code>mat_mul_asw_kernel.h:140-141</code></p>
<pre><code>mm_.Iterate(); <pre><code>mm_.Iterate();
mm_.GetTensorC(cGlobal_[...], enAtomic || kIndex != 0); // 每次 Iterate 紧跟一次 GetTensorC</code></pre> mm_.GetTensorC(cGlobal_[...], enAtomic || kIndex != 0); // 每次 Iterate 紧跟一次 GetTensorC</code></pre>
<p>mat_mul_v3 的普通 ASWT 是 <strong>「一次 Iterate → 一次 GetTensorC」一一配对</strong>(即使在 splitKRound 循环里也是 Iterate 与 GetTensorC 成对出现,用 <code>enAtomic || kIndex!=0</code> 在 GM 侧原子加,而非 L0C 驻留累加)。它<strong>不是</strong>&quot;多次 Iterate、一次 GetTensorC&quot;,因此常规路径不触碰这条约束UnitFlag 可用。</p> <p>mat_mul_v3 的普通 ASWT 是 <strong>「一次 Iterate → 一次 GetTensorC」一一配对</strong>(即便 splitKRound 循环里也是成对出现,用 <code>enAtomic || kIndex!=0</code> 在 GM 侧原子加,而非 L0C 驻留累加)。它不是&quot;多次 Iterate、一次 GetTensorC&quot;,因此常规路径不触碰高阶约束UnitFlag 可用。真正会触碰的是 <code>intraBlockPartSum</code>(两 AIV 结果在 L0C 累加)这类显式 L0C 驻留累加特性——StreamK 绕道 workspace + AIV 累加正是为了避开它。</p>
<p><strong>真正会踩这条的场景</strong>:把 split-K 的<strong>核内</strong>部分和放在 L0C 里驻留累加L0C 累加),或 <code>intraBlockPartSum</code>(两 AIV 结果在 L0C 累加)这类显式 L0C 累加特性。StreamK 之所以要绕道 workspace 由 AIV 累加、而不是在 L0C 里累加,正是为了避开这条。</p>
<h4>3.4.2 一次 Iterate 最大支持的 K 是多少?</h4> <h4>3.4.2 一次 Iterate 最大支持的 K 是多少?</h4>
<p><strong>先澄清概念</strong>:官方约束里没有任何一条把 &quot;一次 Iterate 的 K 上限&quot; 与 UnitFlag 挂钩。<strong>UnitFlag 约束的不是 K 的大小,而是&quot;累加形态&quot;</strong>(见上一条)。一次 Iterate 能算多大的 K<strong>L0C 之外的资源L1/L0A/L0B 容量、stepKa/stepKb 流水深度、baseK</strong> 决定,与 UnitFlag 无关:</p> <p><strong>先澄清概念</strong>:官方约束里没有任何一条把 &quot;一次 Iterate 的 K 上限&quot; 与 UnitFlag 挂钩。<strong>UnitFlag 约束的不是 K 的大小,而是&quot;累加由谁、在哪个层级组织&quot;</strong>(见上一条)。一次 Iterate 能算多大的 K<strong>L0C 之外的资源L1/L0A/L0B 容量、stepKa/stepKb 流水深度、baseK</strong> 决定,与 UnitFlag 无关:</p>
<ul> <ul>
<li>单次 Iterate 的 K 由 <code>singleCoreK</code> 决定,<code>singleCoreK</code> 在 L1 内按 <code>stepKa·baseK</code>A 侧)/ <code>stepKb·baseK</code>B 侧)分多片流水喂给 Cube<strong>每片的中间结果在同一 L0C 上原地累加</strong>这是 MMAD 固有累加语义,不是&quot;次 Iterate 一次输出&quot;不违反 UnitFlag——因为整个 singleCoreK 是一次 Iterate 内部完成的)。</li> <li>单次高阶 <code>Iterate()</code> 的 K 由 <code>singleCoreK</code> 决定,<code>singleCoreK</code> 在 L1 内按 <code>stepKa·baseK</code>A 侧)/ <code>stepKb·baseK</code>B 侧)分多片流水喂给 Cube<strong>每片的中间结果在同一 L0C 上原地累加</strong>MMAD 固有累加语义,&quot;次 Iterate 内部&quot;,不违反高阶约束)。</li>
<li>换句话说:一次 Iterate 内部Cube 会在 L0C 上做 <code>singleCoreK/baseK</code> 次累加,这在 UnitFlag 下是允许的UnitFlag 禁止的是&quot;<strong>跨多次 Iterate</strong> 的累加 + 只在最后一次输出&quot;</li> <li>若单次 <code>Iterate()</code> 装不下整个 KK 超过 L1/L0 一次能流水的范围),高阶层会拆成多次 <code>Iterate()</code>;此时只要不打开&quot;L0C 驻留累加 + 末次才 GetTensorC&quot;就不违反约束mat_mul_v3 用 GM 原子加规避)</li>
</ul> </ul>
<p><strong>结论</strong><strong>没有&quot;一次 Iterate 最大 K 受 UnitFlag 限制&quot;这回事</strong>。一次 Iterate 的 K 上限由 L1/L0 容量与 stepK 流水决定(例如 950PR 上 <code>maxStepK ≤ 8</code><code>baseK ≈ 128B/dtype</code>UnitFlag 只管&quot;算出的 512B 何时搬出&quot;,不管 K 多大。把这条约束理解成&quot;K 不能超过某值&quot;是误读——它限的是&quot;累加跨几次 Iterate&quot;,不是&quot;K 的绝对大小&quot;</p> <p><strong>结论</strong><strong>没有&quot;一次 Iterate 最大 K 受 UnitFlag 限制&quot;这回事</strong>。一次 Iterate 的 K 上限由 L1/L0 容量与 stepK 流水决定(例如 950PR 上 <code>maxStepK ≤ 8</code><code>baseK ≈ 128B/dtype</code>UnitFlag 只管&quot;算出的 512B 何时搬出&quot;,不管 K 多大。把这条约束理解成&quot;K 不能超过某值&quot;是误读——它限的是&quot;累加跨几次 Iterate 的组织方式&quot;,不是&quot;K 的绝对大小&quot;</p>
<h4>3.4.3 「不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水」——是什么意思L1 能直接写回 GM 吗?</h4> <h4>3.4.3 「不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水」——是什么意思L1 能直接写回 GM 吗?</h4>
<p><strong>官方原文出处</strong>同上两篇《Matmul 高阶 API使能/开启UnitFlag》约束条件第 2 条:</p> <p><strong>官方原文出处</strong>同上两篇《Matmul 高阶 API使能/开启UnitFlag》约束条件第 2 条:</p>
</blockquote> </blockquote>
@@ -156,10 +174,28 @@ mm_.GetTensorC(cGlobal_[...], enAtomic || kIndex != 0); // 每次 Iterate 紧
<table><thead><tr><th>官方约束</th><th>准确含义</th><th>950PR 上是否实质限制 mat_mul_v3</th></tr></thead><tbody> <table><thead><tr><th>官方约束</th><th>准确含义</th><th>950PR 上是否实质限制 mat_mul_v3</th></tr></thead><tbody>
<tr><td>仅 Norm/IBShare/MDL 模板</td><td>UnitFlag 只在三个高阶模板下有效</td><td>mat_mul_v3 用 MDLGetMDLConfig<strong>满足</strong></td></tr> <tr><td>仅 Norm/IBShare/MDL 模板</td><td>UnitFlag 只在三个高阶模板下有效</td><td>mat_mul_v3 用 MDLGetMDLConfig<strong>满足</strong></td></tr>
<tr><td>不能同时有 CO1→GM 与 A1→GM 两种搬出</td><td>搬出源必须唯一为 L0C</td><td>351x 已删 L1→GM 通路 → <strong>自动满足,无实质限制</strong></td></tr> <tr><td>不能同时有 CO1→GM 与 A1→GM 两种搬出</td><td>搬出源必须唯一为 L0C</td><td>351x 已删 L1→GM 通路 → <strong>自动满足,无实质限制</strong></td></tr>
<tr><td>L0C 累加时不支持多次 Iterate 一次输出</td><td>禁止跨 Iterate 的 L0C 驻留累加 + 末次才搬出</td><td>mat_mul_v3 常规路径是 Iterate/GetTensorC 一一配对 → <strong>不触碰UnitFlag 可用</strong>;仅显式 L0C 累加(核内 split-K 驻留 / intraBlockPartSum才触碰</td></tr> <tr><td>L0C 累加时不支持多次 Iterate 一次输出</td><td>高阶层禁止&quot;跨多次 Iterate 的 L0C 驻留累加 + 末次才搬出&quot;(底层 Mmad 级 unitFlag=2/3 仍支持 K 分段累加)</td><td>mat_mul_v3 常规路径是 Iterate/GetTensorC 一一配对 → <strong>不触碰UnitFlag 可用</strong>;仅显式 L0C 驻留累加( intraBlockPartSum才触碰</td></tr>
<tr><td>MTE2 Bound 已掩盖时收益小</td><td>Fixpipe 已被搬运流水掩盖时无需再开</td><td>这是<strong>收益</strong>问题不是<strong>正确性</strong>问题,不影响可用性</td></tr> <tr><td>MTE2 Bound 已掩盖时收益小</td><td>Fixpipe 已被搬运流水掩盖时无需再开</td><td>这是<strong>收益</strong>问题不是<strong>正确性</strong>问题,不影响可用性</td></tr>
</tbody></table> </tbody></table>
<p><strong>由此可得更精确的判断</strong>mat_mul_v3 在 950PR 上常规路径(含普通 ASWT、A/B 全载、以及把 K 分片放到 GM workspace 由 AIV 累加的 StreamK<strong>都满足 UnitFlag 的可用条件且默认已开启</strong>;真正被 UnitFlag 排除的是&quot;<strong>在 L0C 里做跨 Iterate 驻留累加</strong>&quot;这一类形态——而这种形态在 mat_mul_v3 里被刻意&quot;GM 原子加 / workspace + AIV 累加&quot;规避了。这进一步支撑 §3.3 的结论:<strong>在 950PR 上 UnitFlag 的合法域覆盖了 mat_mul_v3 的全部主路径L0C DB 的补位空间被压缩得很小</strong></p> <p><strong>由此可得更精确的判断</strong>mat_mul_v3 在 950PR 上常规路径(含普通 ASWT、A/B 全载、以及把 K 分片放到 GM workspace 由 AIV 累加的 StreamK<strong>都满足 UnitFlag 的可用条件且默认已开启</strong>高阶 API 真正排除的是&quot;<strong>多次 Iterate + 末次一次 GetTensorC</strong>&quot;的组织方式——而 mat_mul_v3 用&quot;GM 原子加 / workspace + AIV 累加&quot;规避了。这支撑 §3.3 的结论:<strong>在 950PR 上 UnitFlag 的合法域覆盖了 mat_mul_v3 的全部主路径L0C DB 的补位空间被压缩得很小</strong></p>
<hr/>
<h3>3.5 两个层级的澄清:为什么&quot;高阶约束说不能 K 分段一次输出&quot;,而&quot;底层文档却举例 K 分段累加&quot;</h3>
<p>这是最容易混淆、也最关键的一点,单独澄清。</p>
<p><strong>两个层级的对象不同</strong></p>
<table><thead><tr><th>层级</th><th>编程接口</th><th>UnitFlag 的控制量</th><th>对 K 分段累加的支持</th></tr></thead><tbody>
<tr><td>基础 API指令级</td><td><code>Mmad</code> / <code>Copy</code>/<code>Fixpipe</code> 指令</td><td>指令参数 <code>unitFlag = 2/3</code>,逐 512B 块控标志位</td><td><strong>支持</strong>:前 n1 次 Mmad 设 2保持占用、续写累加末次 Mmad 设 3释放给 Fixpipe官方明确给出 K 分 8 次迭代的实例</td></tr>
<tr><td>高阶 API对象级</td><td><code>Matmul.Iterate()</code> / <code>GetTensorC()</code></td><td><code>MatmulConfig.enUnitFlag = true</code>,由库内部统一编排</td><td><strong>受限</strong><code>Iterate</code> 内部的 K 分片流水(一次 Iterate 完成)支持;但&quot;<strong>多次 Iterate + 末次一次 GetTensorC</strong>&quot;这种高阶层组织的驻留累加不支持</td></tr>
</tbody></table>
<p><strong>为什么高阶层反而受限</strong>:高阶 <code>Matmul</code> 对象把&quot;K 分片、L0C 驻留、何时搬出&quot;都封装在 <code>Iterate()/GetTensorC()</code> 内部UnitFlag 的标志位编排也由库统一管理。当用户用高阶接口做&quot;多次 Iterate 累加 + 一次 GetTensorC&quot;时,库无法在一次 <code>GetTensorC</code> 里反推出前面若干次 Iterate 各自该用什么 unitFlag 时序,于是干脆禁止这种组合。而<strong>基础 API 层把 unitFlag 的控制权直接交给用户</strong>,用户可以自己排&quot;前 n1 次设 2、末次设 3&quot;,所以能做更灵活的 K 分段累加流水。</p>
<p><strong>对用户设想的直接回答</strong>:你描述的&quot;以 16×K×16 为粒度,每段算完立即让 Fixpipe 搬出对应 16×16 到 GM、同时开始下一段计算&quot;——<strong>在基础 API 层完全成立,且就是官方推荐用法</strong>(前段 unitFlag=2 保持占用、末段 unitFlag=3 释放)。这种&quot;K 分段 + 边算边搬&quot;能同时做到:① 块内 512B 流水掩盖;② K 分段累加。它比单纯 L0C DB 更省 L0C 容量、掩盖更细。</p>
<p><strong>但要强调一个前提</strong>:这种灵活编排走的是<strong>基础 APIMmad/Copy + 手写 unitFlag=2/3</strong>,不是 mat_mul_v3 用的高阶 <code>Matmul</code> 对象。mat_mul_v3 用高阶 API 换来的是开发效率与正确性保证,代价就是受高阶约束(不能多次 Iterate 一次输出)。如果要做你说的这种极致 K 分段流水,需要下沉到基础 API 层手写 Mmad/Copy 序列——这正是 catlass类 CUTLASS 模板库)一层在做的事。</p>
<p><strong>&quot;UnitFlag 能否完全取代 L0C DB&quot;的最终修正</strong></p>
<ul>
<li><strong>mat_mul_v3高阶 API现状</strong>下:主路径默认开 UnitFlagK 一次 Iterate 完成Fixpipe 已被块内 512B 流水掩盖,<strong>L0C DB 价值很小</strong>(且 256×256 时本就开不了 DB。DB 仅在 base 块被迫做小256×128 等)时偶发有用。</li>
<li><strong>基础 API / catlass 层</strong>UnitFlag 用 unitFlag=2/3 可覆盖 K 分段累加场景,掩盖能力进一步逼近&quot;完全取代 DB&quot;</li>
<li><strong>仍不能完全取代的根本原因</strong>:不在&quot;K 能不能分段&quot;(底层能),而在于 <strong>UnitFlag 依赖 L0C 块被&quot;写满即搬&quot;,它服务的是&quot;计算结果要尽快流出 L0C&quot;的场景</strong>;而 L0C DB 的本质是&quot;<strong>用双倍 L0C 容量换时间上的重叠</strong>&quot;,它服务的是&quot;想让 base 块更大、减少重复读&quot;的场景。当 L0C 容量成为 tiling 瓶颈(想把 baseM·baseN 做大但放不下两份UnitFlag不占额内容量反而比 DB 更优——这一层上 UnitFlag 甚至优于 DB。只有当场景落到 UnitFlag 高阶约束之外(如必须用多次 Iterate 驻留累加DB 才成为唯一选择。</li>
</ul>
<p><strong>一句话总结</strong>:在 950PR + mat_mul_v3 的语境里,<strong>UnitFlag 已经基本取代 L0C DB 作为 Fixpipe 掩盖的主手段</strong>DB 退化为&quot;高阶 API 约束之外(多次 Iterate 驻留累加)&quot;&quot;L0C 装得下两份、想换更大 base 块&quot;这两类边角场景的兜底。</p>
<hr/> <hr/>
<h2>4. 对本项目性能建模与最优方案的直接推论</h2> <h2>4. 对本项目性能建模与最优方案的直接推论</h2>
<ol> <ol>
@@ -176,11 +212,12 @@ mm_.GetTensorC(cGlobal_[...], enAtomic || kIndex != 0); // 每次 Iterate 紧
<hr/> <hr/>
<h2>附录:关键证据索引</h2> <h2>附录:关键证据索引</h2>
<table><thead><tr><th>证据</th><th>位置</th></tr></thead><tbody> <table><thead><tr><th>证据</th><th>位置</th></tr></thead><tbody>
<tr><td>UnitFlag 三条约束官方原文(在线)</td><td>昇腾社区官网 → CANN → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → 《Matmul 高阶 API 开启 UnitFlag》社区版/ 《Matmul 高阶 API 使能 UnitFlag》商用版。社区版直达链接:见任务输入所给 UnitFlag.md 页面</td></tr> <tr><td>高阶 API UnitFlag 约束(在线)</td><td>昇腾社区官网 → CANN → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → 《Matmul 高阶 API 开启 UnitFlag》社区版/《Matmul 高阶 API 使能 UnitFlag》商用版&quot;约束条件&quot;一节</td></tr>
<tr><td>UnitFlag 三条约束官方原文(本地)</td><td><code>昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md</code>第19/21行<code>昇腾NPU知识库/CANN商用版9.0.0/01_AscendC算子开发/203_..._使能UnitFlag.md</code>第21/23行表述最完整</td></tr> <tr><td>高阶 API UnitFlag 约束(本地)</td><td><code>昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md</code>第15-21行<code>昇腾NPU知识库/CANN商用版9.0.0/01_AscendC算子开发/203_..._使能UnitFlag.md</code>(第17/21/23行表述最完整</td></tr>
<tr><td><strong>基础 API UnitFlag 机制unitFlag=2/3、K 分段累加实例,在线)</strong></td><td>昇腾社区官网 → CANN → API 参考 → Ascend C API → SIMD API → 基础 API → 矩阵计算Tensor API→ Mmad 计算关键特性说明 → <strong>《UnitFlag》</strong>用户所给链接hiascend.com/.../Mmad计算关键特性说明/UnitFlag.md</td></tr>
<tr><td>基础 API UnitFlag 机制(本地缓存)</td><td><code>昇腾NPU/.tmp/unitflag_raw2.txt</code>(已抓取的页面正文,含 128×1024 八次迭代实例原文)</td></tr>
<tr><td>GetMDLConfig 签名第7参=enUnitFlag</td><td><code>昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/631_..._GetMDLConfig.md</code></td></tr> <tr><td>GetMDLConfig 签名第7参=enUnitFlag</td><td><code>昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/631_..._GetMDLConfig.md</code></td></tr>
<tr><td>enUnitFlag 默认值与约束</td><td><code>昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/626_..._MatmulConfig.md</code></td></tr> <tr><td>enUnitFlag 默认值与约束</td><td><code>昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/626_..._MatmulConfig.md</code></td></tr>
<tr><td>UnitFlag 512B 细粒度同步定义</td><td><code>昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md</code></td></tr>
<tr><td>351x 删除 L1→GM / GM→L0 直达通路</td><td><code>昇腾NPU知识库/00_硬件/昇腾950_NPU架构白皮书.txt</code>CV融合/数据通路章节);同仓 <code>昇腾950PR/昇腾950PR架构解读.md</code> §5.1</td></tr> <tr><td>351x 删除 L1→GM / GM→L0 直达通路</td><td><code>昇腾NPU知识库/00_硬件/昇腾950_NPU架构白皮书.txt</code>CV融合/数据通路章节);同仓 <code>昇腾950PR/昇腾950PR架构解读.md</code> §5.1</td></tr>
<tr><td>MM_CFG_NO_PRELOAD 显式开 enUnitFlag</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/mat_mul_v3_common.h:72</code></td></tr> <tr><td>MM_CFG_NO_PRELOAD 显式开 enUnitFlag</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/mat_mul_v3_common.h:72</code></td></tr>
<tr><td>普通 ASWT Iterate/GetTensorC 一一配对</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_asw_kernel.h:140-141</code></td></tr> <tr><td>普通 ASWT Iterate/GetTensorC 一一配对</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_asw_kernel.h:140-141</code></td></tr>