190 lines
24 KiB
HTML
190 lines
24 KiB
HTML
<!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 —— 昇腾 950PR(DAV_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 —— 昇腾 950PR(DAV_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_PRELOAD,StreamK 经回调)。这与"MDL 模板默认不使能"并不矛盾——MDL 默认关,但 mat_mul_v3 用 <code>GetMDLConfig</code> 把这一位显式置真了。</p>
|
||
<hr/>
|
||
<h2>1. 两条掩盖路径的时序本质(先把"流水"画清楚)</h2>
|
||
<p>设 MTE1(L1→L0)与 MTE2(GM→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 让"块 n 的 Fixpipe"与"块 n+1 的 MMAD"重叠,把块间等待消掉。但注意两点:</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 > 32768</code>,<strong>恰恰开不了 DB</strong>。</li>
|
||
<li><strong>掩盖成立的前提二</strong>:<code>t_mm ≥ t_fp</code>。若 <code>t_mm < t_fp</code>(计算快、搬出慢),块 n+1 的 MMAD 很快算完,仍要空等块 n 的 Fixpipe 收尾 → 周期退化为 <code>T = t_fp</code>,瓶颈转移到 Fixpipe,DB 掩盖不充分。</li>
|
||
</ul>
|
||
<h3>1.3 只开 UnitFlag —— 块内 512B 细粒度流水</h3>
|
||
<p>UnitFlag 把单个 base 块按 512B 切成若干 micro-tile,MMAD 每算完一个 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 不依赖"下一块的 MMAD"来掩盖,它在<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 < t_fp</code>,Fixpipe 也是边算边搬,Cube 不停(是 Fixpipe 流水成为瓶颈,而非 Cube 空等)。</li>
|
||
</ul>
|
||
<hr/>
|
||
<h2>2. 块内能 max 了,块间还需不需要考虑?—— 叠加性论证</h2>
|
||
<p>这是你追问的核心。我们严格区分两种情形。</p>
|
||
<h3>2.1 情形 A:K 不在单块内累加(一次 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,想做的是"块 n 的 Fixpipe 与块 n+1 的 MMAD 重叠"——但<strong>块 n 的 Fixpipe 已经被块 n 自己的 MMAD 占满了时间窗</strong>,块 n+1 的 MMAD 本可以在块 n 的 Fixpipe 尾段就开始,可 UnitFlag 已经把块 n 的 Fixpipe"摊平"到整个块 n 的计算区间里,块 n+1 能利用的"纯 Fixpipe 空窗"只剩最后一个 tail。</li>
|
||
</ul>
|
||
<p><strong>所以在这个情形下,DB 的块间掩盖与 UnitFlag 的块内掩盖争夺的是同一段时间(同一块的 Fixpipe),被 UnitFlag 掩盖后,DB 几乎无的放矢 → 两者收益不叠加。</strong> 你的判断"开了 UnitFlag,上一块的 cube 计算已经和上一块的 fixpipe 重叠了,就没法再和下一块的 cube 重叠"在 K 内一次累加场景下<strong>成立</strong>。</p>
|
||
<h3>2.2 情形 B:K 在单块内分片累加(多次 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<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>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 实际看:默认已开 UnitFlag,DB 是「补位」而非「主选」</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> 与"最优 base=256×256"天然冲突(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>关系,而非简单的"新替旧"。</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 < kSliceNum; kSlice++) { // 多次 Iterate
|
||
mm.Iterate(); // 每片 K 的部分结果累加进同一块 L0C(L0C 累加)
|
||
}
|
||
mm.GetTensorC(c); // 只在最后一次性搬出(一次输出)</code></pre>
|
||
<p><strong>为什么 UnitFlag 与这种形态冲突</strong>:UnitFlag 的硬件机制是「MMAD 每产出 512B 成品,就立即触发 Fixpipe 把这段搬走」。但 L0C 累加形态下,前面若干次 Iterate 的结果是<strong>部分和,还必须留在 L0C 里等后续 K 片继续累加</strong>,不能被搬走。一旦开 UnitFlag,MMAD 算出 512B 就被 Fixpipe 立即搬出,L0C 里就保不住部分和、后续累加无从谈起——两者在"这 512B 到底是驻留还是立即搬走"上直接矛盾,故硬件/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>"多次 Iterate、一次 GetTensorC",因此常规路径不触碰这条约束,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>:官方约束里没有任何一条把 "一次 Iterate 的 K 上限" 与 UnitFlag 挂钩。<strong>UnitFlag 约束的不是 K 的大小,而是"累加形态"</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 的固有累加语义,不是"多次 Iterate 一次输出",也不违反 UnitFlag——因为整个 singleCoreK 是一次 Iterate 内部完成的)。</li>
|
||
<li>换句话说:一次 Iterate 内部,Cube 会在 L0C 上做 <code>singleCoreK/baseK</code> 次累加,这在 UnitFlag 下是允许的;UnitFlag 禁止的是"<strong>跨多次 Iterate</strong> 的累加 + 只在最后一次输出"。</li>
|
||
</ul>
|
||
<p><strong>结论</strong>:<strong>没有"一次 Iterate 最大 K 受 UnitFlag 限制"这回事</strong>。一次 Iterate 的 K 上限由 L1/L0 容量与 stepK 流水决定(例如 950PR 上 <code>maxStepK ≤ 8</code>、<code>baseK ≈ 128B/dtype</code>),UnitFlag 只管"算出的 512B 何时搬出",不管 K 多大。把这条约束理解成"K 不能超过某值"是误读——它限的是"累加跨几次 Iterate",不是"K 的绝对大小"。</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)。即搬出通路的"源头"必须统一为 L0C,不能 L0C 和 L1 混着当搬出源。</p>
|
||
<p><strong>「L1 能直接写回 GM 吗?」——分架构看,这正是关键点</strong>:</p>
|
||
<ul>
|
||
<li><strong>在 950PR(351x 架构)上:不能。</strong> 950 白皮书明确,351x 相对上代<strong>删除了 <code>L1→GM</code> 的回写通路</strong>(同时也删除了 <code>GM→L0A/L0B</code> 的直达通路),一切矩阵数据的搬出都必须经 <code>L0C→Fixpipe→GM</code>。所以<strong>在 950PR 上根本不存在"L1→GM"这条物理通路</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>:这条约束的语义是"搬出源必须唯一(只能是 L0C)",防止 UnitFlag 的细粒度同步在"两种不同源头的搬出"上产生时序混乱。在 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 用 MDL(GetMDLConfig)→ <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 排除的是"<strong>在 L0C 里做跨 Iterate 驻留累加</strong>"这一类形态——而这种形态在 mat_mul_v3 里被刻意用"GM 原子加 / workspace + AIV 累加"规避了。这进一步支撑了 §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>不必再为"能否开 L0C DB"让步</strong>,可以更激进地逼近 256×256(重复读最少)。原先"为开 DB 而把 baseN 砍半"的权衡在 UnitFlag 合法域内不成立。这是对 §3.2 <code>GetRebalanceBlock</code> 的一处实质修正点。</li>
|
||
<li><strong>真正需要单独建模的 K 累加形态</strong>:需要警惕的不是 mat_mul_v3 的主路径,而是"<strong>在 L0C 里做跨 Iterate 驻留累加</strong>"这一类形态(核内 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> |