Files
matmul-analysis/Matmul/UnitFlag能否取代L0C_DoubleBuffer.html

190 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>UnitFlag 能否完全取代 L0C Double Buffer —— 昇腾 950PRDAV_3510时序与数据流分析</title>
<style>
body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; max-width: 980px; margin: 40px auto; padding: 0 24px; line-height: 1.75; color: #1f2328; background:#fff; }
h1 { font-size: 28px; border-bottom: 2px solid #d0d7de; padding-bottom: 12px; }
h2 { font-size: 22px; border-bottom: 1px solid #d0d7de; padding-bottom: 8px; margin-top: 36px; }
h3 { font-size: 18px; margin-top: 28px; }
code { background:#f6f8fa; padding: 2px 6px; border-radius: 4px; font-family: "SF Mono", Consolas, monospace; font-size: 0.9em; color:#c7254e; }
pre { background:#f6f8fa; padding: 16px; border-radius: 8px; overflow-x:auto; }
pre code { background:none; padding:0; color:#24292e; }
table { border-collapse: collapse; width: 100%; margin: 16px 0; font-size: 14px; }
th, td { border: 1px solid #d0d7de; padding: 8px 12px; text-align: left; vertical-align: top; }
th { background:#f6f8fa; font-weight: 600; }
blockquote { border-left: 4px solid #d0d7de; margin: 16px 0; padding: 4px 16px; color:#57606a; background:#f6f8fa; }
hr { border:none; border-top:1px solid #d0d7de; margin: 24px 0; }
li { margin: 4px 0; }
strong { color:#0a3069; }
</style>
</head><body><h1>UnitFlag 能否完全取代 L0C Double Buffer —— 昇腾 950PRDAV_3510时序与数据流分析</h1>
</blockquote>
<hr/>
<h2>0. 先修正并锁定一个前提事实</h2>
</blockquote>
<pre><code>// 官方 GetMDLConfig 签名(第 7 个位置参数即 enUnitFlag
GetMDLConfig(intrinsicsLimit, batchLoop, doMTE2Preload, isVecND2NZ,
isPerTensor, hasAntiQuantOffset, enUnitFlag=false, ...)
// mat_mul_v3_common.h
constexpr MatmulConfig MM_CFG_NO_PRELOAD =
GetMDLConfig(false, false, 0, false, false, false, /*enUnitFlag=*/true);</code></pre>
<p><code>MM_CFG_NO_PRELOAD</code> 的第 7 个实参是 <code>true</code><strong>enUnitFlag 被显式开启</strong>。而 mat_mul_v3 的普通 ASWT、A/B 全载、SplitK 等模板 kernel 的默认模板参数就是 <code>MM_CFG = MM_CFG_NO_PRELOAD</code>StreamK 又在自定义搬出回调 <code>CustomDataCopyOut</code> 里再显式 <code>fixpipeParams.unitFlag = MM_FIX_PIPE_UNIT_FLAG(=3)</code></p>
<p><strong>结论</strong>mat_mul_v3 在 950PR 的主路径上,<strong>UnitFlag 默认是开启的</strong>(普通模板经 MM_CFG_NO_PRELOADStreamK 经回调)。这与&quot;MDL 模板默认不使能&quot;并不矛盾——MDL 默认关,但 mat_mul_v3 用 <code>GetMDLConfig</code> 把这一位显式置真了。</p>
<hr/>
<h2>1. 两条掩盖路径的时序本质(先把&quot;流水&quot;画清楚)</h2>
<p>设 MTE1L1→L0与 MTE2GM→L1已由 L1/L0 的 Double Buffer 掩盖(这是 tiling 的常规动作,与本文讨论的 L0C 无关),则单个 base 块的耗时只需关注 <strong>MMAD 计算</strong><strong>Fixpipe 搬出</strong> 两级:</p>
<pre><code>t_mm = baseM·baseN·baseK / R_cube MMAD 计算时间R_cube=8192 FLOP/cycle
t_fp = baseM·baseN·dtypeC / BW_fp Fixpipe 搬出时间dtypeC=输出dtype字节数</code></pre>
<h3>1.1 无 DB 无 UnitFlag —— 完全串行</h3>
<pre><code>块n: [==== MMAD ====]··等待··[---- Fixpipe ----]
块n+1: [==== MMAD ====]··等待··[---- Fixpipe ----]
周期 T = t_mm + t_fp</code></pre>
<p>L0C 只有一份Fixpipe 搬出期间 Cube 不能写 → 块内串行;且当前块 Fixpipe 不结束,下一块 MMAD 不能开始 → 块间也串行。这是最差形态。</p>
<h3>1.2 只开 L0C Double Buffer —— 块间掩盖</h3>
<p>L0C 分两份A/B块 n 在 L0C.A 搬出的同时,块 n+1 在 L0C.B 上算:</p>
<pre><code>L0C.A(块n): [==== MMAD ====]········[---- Fixpipe ----]
L0C.B(块n+1): [==== MMAD ====]········[---- Fixpipe ----]
L0C.A(块n+2): [==== MMAD ====]···
周期 T = max(t_mm, t_fp)</code></pre>
<p><strong>关键</strong>DB 让&quot;块 n 的 Fixpipe&quot;&quot;块 n+1 的 MMAD&quot;重叠,把块间等待消掉。但注意两点:</p>
<ul>
<li><strong>掩盖成立的前提一</strong><code>baseM·baseN·4B·2 ≤ L0C</code>256KB<code>baseM·baseN ≤ 32768</code>,否则 L0C 装不下两份DB 开不了。baseM=baseN=256 时 <code>256·256=65536 &gt; 32768</code><strong>恰恰开不了 DB</strong></li>
<li><strong>掩盖成立的前提二</strong><code>t_mm ≥ t_fp</code>。若 <code>t_mm &lt; t_fp</code>(计算快、搬出慢),块 n+1 的 MMAD 很快算完,仍要空等块 n 的 Fixpipe 收尾 → 周期退化为 <code>T = t_fp</code>,瓶颈转移到 FixpipeDB 掩盖不充分。</li>
</ul>
<h3>1.3 只开 UnitFlag —— 块内 512B 细粒度流水</h3>
<p>UnitFlag 把单个 base 块按 512B 切成若干 micro-tileMMAD 每算完一个 512B 就立即触发对应 Fixpipe两者在<strong>同一块内</strong>交错:</p>
<pre><code>块n(L0C): [M][M][M]...[M] ← MMAD 按 512B 推进
[F][F][F]...[F] ← Fixpipe 跟随 512B逐段搬出
周期 T ≈ max(t_mm, t_fp) + tail
tail = 最后一个 512B micro-tile 的 Fixpipe 尾开销,可忽略)</code></pre>
<p><strong>关键差异</strong>UnitFlag 不依赖&quot;下一块的 MMAD&quot;来掩盖,它在<strong>当前块内部</strong>就让 Fixpipe 跟着 MMAD 的进度流水走。因此:</p>
<ul>
<li><strong>不要求 L0C 装两份</strong>(不消耗 L0C 容量);</li>
<li><strong>不要求 t_mm ≥ t_fp</strong>:无论谁长谁短,都能逼近 <code>max(t_mm, t_fp)</code>。即使 <code>t_mm &lt; t_fp</code>Fixpipe 也是边算边搬Cube 不停(是 Fixpipe 流水成为瓶颈,而非 Cube 空等)。</li>
</ul>
<hr/>
<h2>2. 块内能 max 了,块间还需不需要考虑?—— 叠加性论证</h2>
<p>这是你追问的核心。我们严格区分两种情形。</p>
<h3>2.1 情形 AK 不在单块内累加(一次 Iterate 算完一整块 K</h3>
<p>这是 mat_mul_v3 最常见的形态:一个 base 块的 <code>singleCoreK</code> 完整累加进 L0C算完一次性 Fixpipe 搬出到 GM。</p>
<ul>
<li><strong>开 UnitFlag 后</strong>,单块周期已经是 <code>≈ max(t_mm, t_fp)</code>。块 n 的 Fixpipe 段与块 n 的 MMAD 段在时间上<strong>几乎完全重叠</strong>(只差一个 tail</li>
<li>此时若再开 L0C DB想做的是&quot;块 n 的 Fixpipe 与块 n+1 的 MMAD 重叠&quot;——但<strong>块 n 的 Fixpipe 已经被块 n 自己的 MMAD 占满了时间窗</strong>,块 n+1 的 MMAD 本可以在块 n 的 Fixpipe 尾段就开始,可 UnitFlag 已经把块 n 的 Fixpipe&quot;摊平&quot;到整个块 n 的计算区间里,块 n+1 能利用的&quot;纯 Fixpipe 空窗&quot;只剩最后一个 tail。</li>
</ul>
<p><strong>所以在这个情形下DB 的块间掩盖与 UnitFlag 的块内掩盖争夺的是同一段时间(同一块的 Fixpipe被 UnitFlag 掩盖后DB 几乎无的放矢 → 两者收益不叠加。</strong> 你的判断&quot;开了 UnitFlag上一块的 cube 计算已经和上一块的 fixpipe 重叠了,就没法再和下一块的 cube 重叠&quot;在 K 内一次累加场景下<strong>成立</strong></p>
<h3>2.2 情形 BK 在单块内分片累加(多次 Iterate 累加进同一 L0C</h3>
<p><code>singleCoreK</code> 很大、被切成多个 <code>baseK</code> 分片时,同一 base 块要做多轮 <code>MMAD→部分结果留在 L0C</code><strong>Fixpipe 只在最后一次累加完成后才整体搬出</strong></p>
<ul>
<li>这里 UnitFlag 的约束就暴露了:<strong>官方明确「使能 UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」</strong>。也就是说,<strong>L0C 上做多分片 K 累加的场景UnitFlag 用不了</strong></li>
<li>此时若 Fixpipe 搬出与后续计算存在串行,<strong>只能靠 L0C DB 做块间掩盖</strong>(一块在累加,另一块的成品在搬出)。</li>
</ul>
<p><strong>所以 UnitFlag 的使用边界里明确排除了「L0C K 累加」这一类场景,而这恰恰是多分片大 K 的常见形态 → 这类场景 UnitFlag 无法取代 DB。</strong></p>
<h3>2.3 叠加性结论</h3>
<table><thead><tr><th>场景</th><th>UnitFlag</th><th>L0C DB</th><th>能否叠加</th><th>主导掩盖</th></tr></thead><tbody>
<tr><td>K 一次累加t_mm≥t_fp能开DB</td><td>块内掩盖</td><td>块间掩盖</td><td><strong>收益不叠加</strong>(同一段 Fixpipe</td><td>任一即可UnitFlag 更省 L0C</td></tr>
<tr><td>K 一次累加t_mm&lt;t_fp</td><td>块内掩盖Cube不停</td><td>掩盖不足(退化 t_fp</td><td>DB 无效UnitFlag 主导</td><td><strong>UnitFlag</strong></td></tr>
<tr><td>K 一次累加baseM·baseN&gt;32768</td><td>块内掩盖</td><td><strong>开不了</strong></td><td>DB 不可用UnitFlag 主导</td><td><strong>UnitFlag</strong></td></tr>
<tr><td>K 分片累加(大 K</td><td><strong>不支持</strong>多次Iterate一次输出</td><td>块间掩盖</td><td>UnitFlag 不可用DB 主导</td><td><strong>L0C DB</strong></td></tr>
</tbody></table>
<hr/>
<h2>3. UnitFlag 能否完全取代 L0C DB</h2>
<p><strong>不能完全取代。</strong> 结合上面论证与 NPU 架构约束,分三层说清:</p>
<h3>3.1 从掩盖能力看UnitFlag 掩盖域 ⊇ DB在它能用的场景内</h3>
<p>在「K 一次累加、C 输出 GM、Norm/IBShare/MDL 模板」这个 UnitFlag 的合法域内:</p>
<ul>
<li>DB 达到 <code>max(t_mm,t_fp)</code> 需要<strong>两个前提</strong>L0C 装得下两份 + t_mm≥t_fp</li>
<li>UnitFlag 达到 <code>≈max(t_mm,t_fp)</code> <strong>几乎无条件</strong>(块内 512B 流水),还省下 L0C 容量(这份容量可用来把 baseM/baseN 做大,减少重复读)。</li>
</ul>
<p>所以<strong>在 UnitFlag 合法域内DB 相对它没有独立价值,可以被取代</strong>。你的直觉在这一层是对的。</p>
<h3>3.2 从合法域看UnitFlag 有硬约束DB 是兜底</h3>
<p>UnitFlag 的官方约束(缺一不可):</p>
<ol>
<li><strong>模板限制</strong>:仅 Norm / IBShare / MDL 三模板;</li>
<li><strong>流水互斥</strong>:使能后<strong>不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水</strong></li>
<li><strong>累加限制</strong>:使能 + L0C 累加时,<strong>不支持多次 Iterate、一次 GetTensorC 输出</strong></li>
<li><strong>收益前提</strong>:仅当 MMAD 与 Fixpipe 串行且未被 MTE2 等其他流水掩盖时才有收益。</li>
</ol>
<p>凡是落在这些约束之外的场景(最典型就是 §2.2 的<strong>多分片 K 在 L0C 累加</strong>UnitFlag 直接不可用,<strong>只能退回到 L0C DB</strong> 做块间掩盖。</p>
<h3>3.3 从 mat_mul_v3 实际看:默认已开 UnitFlagDB 是「补位」而非「主选」</h3>
<ul>
<li>mat_mul_v3 主路径 <code>MM_CFG_NO_PRELOAD</code> 已把 enUnitFlag 置真StreamK 又在回调里显式 <code>unitFlag=3</code><strong>UnitFlag 是该算子的默认掩盖手段</strong></li>
<li>dbL0C 的判据 <code>baseM·baseN ≤ 32768</code>&quot;最优 base=256×256&quot;天然冲突256×256 开不了 DB——这说明在 950PR 上,<strong>设计者的倾向就是靠 UnitFlag 做块内掩盖DB 只在 base 块被迫做小(如矩形 256×128或落在 UnitFlag 约束之外时补位</strong></li>
</ul>
<p><strong>最终判断</strong>UnitFlag 在其合法域内可以取代 L0C DB且更优但因「模板/流水互斥/累加」三重硬约束,它<strong>无法覆盖全部场景</strong>L0C DB 作为不依赖这些约束的块间掩盖手段,是 UnitFlag 失效场景下的必要补充,两者是<strong>主从互补</strong>关系,而非简单的&quot;新替旧&quot;</p>
<hr/>
<h3>3.4 对官方三条约束的逐条考据(答疑)</h3>
<p>针对三条容易被误读的官方约束,逐一给出原文出处、准确含义、以及在 mat_mul_v3 / 950PR 上的实际适用性。</p>
<h4>3.4.1 「UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」——具体是什么约束?</h4>
<p><strong>官方原文出处</strong>(两处,措辞略异,商用版更完整):</p>
<ul>
<li>昇腾社区官网 CANN 社区版 → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → <strong>《Matmul 高阶 API 开启 UnitFlag》</strong>,约束条件第 3 条:</li>
</ul>
</blockquote>
<ul>
<li>昇腾社区官网 CANN 商用版 → 同名章节 <strong>《Matmul 高阶 API 使能 UnitFlag》</strong>,约束条件第 3 条(完整表述):</li>
</ul>
</blockquote>
<p><strong>准确含义拆解</strong>:这条约束针对的是 <strong>「同一个 base 块K 方向分多片,在 L0C 上驻留并多次累加,最后才一次性 GetTensorC 搬出」</strong> 的编程形态。即:</p>
<pre><code>// 被禁止的形态K 在 L0C 内分片累加,最后一次才搬出
for (kSlice = 0; kSlice &lt; kSliceNum; kSlice++) { // 多次 Iterate
mm.Iterate(); // 每片 K 的部分结果累加进同一块 L0CL0C 累加)
}
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>mat_mul_v3 是否踩这条约束?——不踩(常规路径)</strong>。看 <code>mat_mul_asw_kernel.h:140-141</code></p>
<pre><code>mm_.Iterate();
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><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>
<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>
<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>换句话说:一次 Iterate 内部Cube 会在 L0C 上做 <code>singleCoreK/baseK</code> 次累加,这在 UnitFlag 下是允许的UnitFlag 禁止的是&quot;<strong>跨多次 Iterate</strong> 的累加 + 只在最后一次输出&quot;</li>
</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>
<h4>3.4.3 「不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水」——是什么意思L1 能直接写回 GM 吗?</h4>
<p><strong>官方原文出处</strong>同上两篇《Matmul 高阶 API使能/开启UnitFlag》约束条件第 2 条:</p>
</blockquote>
<p><strong>这条的字面意思</strong>UnitFlag 开启时,同一个算子里不能<strong>同时</strong>出现两类搬出流水——一类是 L0C→GM结果搬出走 Fixpipe另一类是 L1(A1)→GM从 L1 直接把数据写回 GM。即搬出通路的&quot;源头&quot;必须统一为 L0C不能 L0C 和 L1 混着当搬出源。</p>
<p><strong>「L1 能直接写回 GM 吗?」——分架构看,这正是关键点</strong></p>
<ul>
<li><strong>在 950PR351x 架构)上:不能。</strong> 950 白皮书明确351x 相对上代<strong>删除了 <code>L1→GM</code> 的回写通路</strong>(同时也删除了 <code>GM→L0A/L0B</code> 的直达通路),一切矩阵数据的搬出都必须经 <code>L0C→Fixpipe→GM</code>。所以<strong>在 950PR 上根本不存在&quot;L1→GM&quot;这条物理通路</strong>,这条约束对 950PR 而言<strong>自然满足、不会触发</strong>——它更像是从支持 L1→GM 的老架构(如部分 A2/A3 代际)沿用过来的兼容性约束。</li>
<li><strong>mat_mul_v3 在 950PR 上的搬出源</strong>:普通 ASWT 的结果一律走 <code>L0C→Fixpipe→GM(C)</code><code>mm_.GetTensorC(cGlobal...)</code>),没有任何 L1→GM 的直接搬出 → 符合该约束。</li>
</ul>
<p><strong>小结</strong>:这条约束的语义是&quot;搬出源必须唯一(只能是 L0C&quot;,防止 UnitFlag 的细粒度同步在&quot;两种不同源头的搬出&quot;上产生时序混乱。在 950PR 上由于 L1→GM 通路已被硬件删除,这条约束<strong>自动成立</strong>,不会成为限制 mat_mul_v3 开 UnitFlag 的因素。</p>
<h4>3.4.4 三条约束在 mat_mul_v3 / 950PR 上的实际生效性汇总</h4>
<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>不能同时有 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>MTE2 Bound 已掩盖时收益小</td><td>Fixpipe 已被搬运流水掩盖时无需再开</td><td>这是<strong>收益</strong>问题不是<strong>正确性</strong>问题,不影响可用性</td></tr>
</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>
<hr/>
<h2>4. 对本项目性能建模与最优方案的直接推论</h2>
<ol>
<li><strong>建模时 Fixpipe 项要分两种掩盖</strong>:在性能模型 <code>T_total = max(T_MMAD, T_MTE2, T_MTE1, T_FIXPIPE)</code> 中,<code>T_FIXPIPE</code> 的实际暴露量应写成</li>
</ol>
<ul>
<li>开 UnitFlag合法域内<code>T_FIXPIPE_exposed ≈ tail</code>512B 尾开销)→ 基本可以忽略Fixpipe 通常不构成独立瓶颈;</li>
<li>不开 UnitFlag或不可用<code>T_FIXPIPE_exposed = max(0, t_fp - t_mm)</code>DB 掩盖后的残余),且仅当 <code>baseM·baseN ≤ 32768</code> 时 DB 才生效,否则 <code>T_FIXPIPE_exposed = t_fp</code>(全暴露)。</li>
</ul>
<ol>
<li><strong>最优 tiling 的取舍被简化</strong>:既然 UnitFlag 默认开baseM/baseN 的搜索<strong>不必再为&quot;能否开 L0C DB&quot;让步</strong>,可以更激进地逼近 256×256重复读最少。原先&quot;为开 DB 而把 baseN 砍半&quot;的权衡在 UnitFlag 合法域内不成立。这是对 §3.2 <code>GetRebalanceBlock</code> 的一处实质修正点。</li>
<li><strong>真正需要单独建模的 K 累加形态</strong>:需要警惕的不是 mat_mul_v3 的主路径,而是&quot;<strong>在 L0C 里做跨 Iterate 驻留累加</strong>&quot;这一类形态(核内 split-K 驻留 / intraBlockPartSum 等)。这类形态被 UnitFlag 约束排除,只能靠 L0C DB 做块间掩盖。mat_mul_v3 的 StreamK 用 workspace + AIV 累加规避了它,因此主路径不受影响;但若未来引入 L0C 驻留累加的新模板,性能模型需为其单列 Fixpipe 掩盖方式(回到 DB 主导)。</li>
</ol>
<hr/>
<h2>附录:关键证据索引</h2>
<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>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>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>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>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>StreamK 回调显式 unitFlag=3</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_kernel.h:36-37</code></td></tr>
<tr><td>StreamK K 分片走 workspace + AIV 累加</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_kernel.h:188-259</code><code>mat_mul_stream_k_block.h</code></td></tr>
<tr><td>dbL0C 判据 baseM·baseN≤32768</td><td><code>ops-nn/matmul/mat_mul_v3/op_host/op_tiling/arch35/matmul_v3_tiling_helper.cpp:490</code></td></tr>
</tbody></table></body></html>