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

136 lines
14 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/>
<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>StreamK 的 K 分片场景要单独建模</strong>StreamK/大 K 分片累加命中 UnitFlag 的&quot;多次 Iterate 一次输出&quot;约束,其 Fixpipe 掩盖机制与普通 ASWT 不同,性能模型需为这类 case 单列。</li>
</ol>
<hr/>
<h2>附录:关键证据索引</h2>
<table><thead><tr><th>证据</th><th>位置</th></tr></thead><tbody>
<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>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>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 分片累加L0C→workspace→AIV</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_block.h</code><code>mat_mul_stream_k_kernel.h:188-202</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>