Files
matmul-analysis/Matmul/UnitFlag与L0C_DoubleBuffer分析.html

162 lines
18 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>
<p>昇腾 950PR 上Matmul 的标准数据流是:</p>
<pre><code>GM ──MTE2──► L1 ──MTE1──► L0A/L0B ──MMAD──► L0C ──Fixpipe──► GM(C)</code></pre>
<p>其中 L0C 的输出Fixpipe 搬出)与 Cube 的计算MMAD之间存在「谁先谁后」的问题。用户观察到的两条掩盖路径</p>
<ol>
<li><strong>L0C Double BufferdbL0C=2</strong>L0C 分两份,当前一份在 MMAD 计算,另一份在 Fixpipe 搬出,用「计算-搬出」重叠掩盖 Fixpipe。</li>
<li><strong>UnitFlag细粒度同步</strong>MMAD 每算完 512B 就立即触发 Fixpipe 搬出该 512B用「指令级流水」掩盖 Fixpipe。</li>
</ol>
<p>问题:既然 UnitFlag 能这么细粒度地掩盖,<strong>L0C 还需要开 Double Buffer 吗?</strong> 两者的适用边界在哪?</p>
<hr/>
<h2>1. 两条掩盖路径的原理性拆解</h2>
<h3>1.1 L0C Double Buffer 掩盖的是什么</h3>
<p>L0C DB 的目标是:<strong>当前 base 块的 Fixpipe 搬出 与 下一个 base 块的 MMAD 计算 并行</strong>。它的掩盖对象是「<strong>base 块与 base 块之间</strong>的 Fixpipe 串行等待」。</p>
<ul>
<li>时序dbL0C=2方块为 base 块):</li>
</ul>
<pre><code>L0C 块0: MMAD ──► Fixpipe 搬出
L0C 块1: MMAD ──► Fixpipe 搬出
L0C 块2: MMAD ──► Fixpipe 搬出
时间轴: ────────┬───────┬───────┬──►
块0搬出与块1计算重叠 块1搬出与块2计算重叠</code></pre>
<ul>
<li>关键点:<strong>没有 L0C DB 时</strong>,当前 base 块必须等 Fixpipe 完全搬出才能开始下一个 base 块的 MMADL0C 只有一份,搬出期间不能写入);<strong>有 L0C DB 时</strong>,下一个 base 块的 MMAD 可以立即在另一份 L0C 上开始,当前块的 Fixpipe 与之并行。</li>
</ul>
<h3>1.2 UnitFlag 掩盖的是什么</h3>
<p>UnitFlag 的目标是:<strong>单个 base 块内部MMAD 计算 与 Fixpipe 搬出 的细粒度并行</strong>。它的掩盖对象是「<strong>单个 base 块内部</strong>的 MMAD-Fixpipe 串行等待」。</p>
<ul>
<li>官方定义《Matmul高阶API开启UnitFlag》UnitFlag 为 MMAD 计算指令和 FIXPIPE 搬运指令提供<strong>基于内存访问的 512B 细粒度同步</strong>使计算与搬运流水并行。未开启时FIXPIPE 要等整条 MMAD 指令算完才搬出开启后MMAD 每算完 512B 数据FIXPIPE 立即搬出该 512B。</li>
</ul>
<ul>
<li>时序(单个 base 块内):</li>
</ul>
<pre><code>未开 UnitFlag: MMAD(整个base块) ──────► Fixpipe(整个base块)
开 UnitFlag: MMAD[512B] ─► Fixpipe[512B]
MMAD[512B] ─► Fixpipe[512B] ...
512B 粒度交错,计算与搬出重叠)</code></pre>
<ul>
<li>用户观察「最多剩一个小小的 16×16 尾巴 Fixpipe 输出不可掩盖」是对的:最后一个 512B 块算完后,它的 Fixpipe 没有后续 MMAD 可重叠,是尾开销。但相比整个 base 块的 Fixpipe 串行等待,这个尾开销很小。</li>
</ul>
<h3>1.3 两者的关系:掩盖对象不同,可叠加</h3>
<table><thead><tr><th>维度</th><th>L0C Double Buffer</th><th>UnitFlag</th></tr></thead><tbody>
<tr><td>掩盖对象</td><td>base 块<strong>之间</strong>的 Fixpipe 串行</td><td>base 块<strong>内部</strong>的 MMAD-Fixpipe 串行</td></tr>
<tr><td>粒度</td><td>base 块级(一份 L0C 搬出时另一份在算)</td><td>512B 指令级</td></tr>
<tr><td>触发方式</td><td>Host tiling 决策dbL0C=1/2</td><td>Device kernel 里 enUnitFlag=true</td></tr>
<tr><td>资源成本</td><td>L0C 容量翻倍占用</td><td>无额外 Buffer但受模板/格式约束</td></tr>
</tbody></table>
<p><strong>关键结论</strong>:两者不互为替代,而是<strong>掩盖不同层级的串行</strong>。一个 base 块的完整耗时可以拆解为:</p>
<pre><code>T_base_block ≈ max(T_MMAD_base, T_MTE1_base, T_MTE2_base) + T_Fixpipe_base_tail</code></pre>
<ul>
<li><code>T_MMAD_base</code>:该 base 块的 MMAD 计算时间;</li>
<li><code>T_Fixpipe_base_tail</code>Fixpipe 搬出该 base 块的时间中<strong>无法被掩盖</strong>的部分。</li>
</ul>
<p><strong>开 L0C DB</strong> 的效果:下一个 base 块的 MMAD 可以并行启动,把「当前块 Fixpipe」的时间<strong>藏进下一个块的 MMAD 里</strong>。但如果当前块的 MMAD 本身就比 Fixpipe 短(计算快、搬出慢),下一个块的 MMAD 很快算完,又要等当前块的 Fixpipe → <strong>L0C DB 掩盖不住「计算远快于搬出」的场景</strong></p>
<p><strong>开 UnitFlag</strong> 的效果:把当前块内部的 MMAD 与 Fixpipe 交错,<strong>Fixpipe 不再等整个 base 块算完</strong>,而是边算边搬。即使当前块 MMAD 很快Fixpipe 也能跟着 MMAD 的进度流水走,把大部分搬出时间藏进 MMAD 里。</p>
<p>所以:<strong>L0C DB 掩盖「块间」串行UnitFlag 掩盖「块内」串行;在「单块 MMAD 短、Fixpipe 长」的场景L0C DB 掩盖不住,必须靠 UnitFlag在「单块 MMAD 长、Fixpipe 短」的场景L0C DB 已足够UnitFlag 收益小。</strong></p>
<hr/>
<h2>2. mat_mul_v3 中两者的实际使用场景</h2>
<h3>2.1 L0C Double Buffer 的使用场景Host tiling 决策)</h3>
<p>mat_mul_v3 的 L0C DB 由 Host tiling 层计算 <code>runInfo.dbL0C</code>,写入 tiling data 的 <code>dbL0C</code>/<code>l0cDB</code> 字段Device 侧消费。源码中 <code>dbL0C</code> 的设置点:</p>
<table><thead><tr><th>设置点</th><th>文件</th><th>条件</th><th>结果</th></tr></thead><tbody>
<tr><td>① 初始默认</td><td><code>matmul_v3_tiling_helper.cpp:134</code>ResetBaseDefault</td><td></td><td><code>dbL0C = DB_OFF_SIZE = 1</code>(默认关)</td></tr>
<tr><td>② 主搜索后</td><td><code>matmul_v3_tiling_helper.cpp:490</code>GetRebalanceBlock 末尾)</td><td><code>baseM·baseN·4B·2 ≤ L0C(256KB)</code></td><td>满足则 <code>dbL0C=2</code>,否则 1</td></tr>
<tr><td>③ A 全载</td><td><code>matmul_v3_basic_aswt_tiling.cpp:203</code>DoAL1FullLoad</td><td>同上</td><td>同上</td></tr>
<tr><td>④ B 全载</td><td><code>matmul_v3_basic_aswt_tiling.cpp:274</code>DoBL1FullLoad</td><td>同上</td><td>同上</td></tr>
</tbody></table>
<p>核心条件统一为:</p>
<pre><code>dbL0C = (baseM · baseN · DATA_SIZE_FP32 · DB_SIZE ≤ L0C容量) ? 2 : 1</code></pre>
<p>代入 950PR 数值L0C=256KB=262144BDATA_SIZE_FP32=4BDB_SIZE=2</p>
<pre><code>baseM·baseN·4·2 ≤ 262144 → baseM·baseN ≤ 32768 = 2^15</code></pre>
<p>baseM=baseN=256 时:<code>256·256 = 65536 &gt; 32768</code><strong>dbL0C=1</strong></p>
<p>baseM=256、baseN=128 时:<code>256·128 = 32768</code><strong>dbL0C=2</strong></p>
<p><strong>物理含义</strong>baseM=baseN=256 的 FP32 累加块恰好填满整个 L0C256KB开不了 DB只有把 baseN或 baseM砍到一半L0C 才能容纳两份,才能开 DB。这与「baseM=baseN=256 是重复读最少的最优解」存在张力:<strong>base 块取 256×256 时L0C 被占满,无法用 DB 掩盖 Fixpipe</strong></p>
<p><strong>使用场景总结</strong>mat_mul_v3 中L0C DB 在<strong>绝大多数分支</strong>都可能开启(普通 ASWT、A/B 全载、StreamK 复用同一引擎),但<strong>前提都是 baseM·baseN ≤ 32768</strong>。当最优 base 块是 256×256 时dbL0C 强制为 1。</p>
<h3>2.2 UnitFlag 的使用场景Device kernel 开启)</h3>
<p>mat_mul_v3 中 UnitFlag 由 kernel 侧的 <code>MatmulConfig.enUnitFlag</code> 或自定义搬出回调的 <code>fixpipeParams.unitFlag</code> 控制。检索结果:</p>
<ul>
<li><strong>全局默认配置</strong><code>mat_mul_v3_common.h</code> 定义 <code>MM_CFG_NO_PRELOAD = GetMDLConfig(..., true)</code>(最后一个参数即 enUnitFlag。多个 kernelMatMulBasicKernel 等)默认 <code>MM_CFG = MM_CFG_NO_PRELOAD</code>。但 <code>GetMDLConfig</code> 是 MDL 模板,<strong>官方文档明确「MDL 下 enUnitFlag 默认不使能」</strong>,且 GetMDLConfig 的具体默认参数需查 API 文档——<code>MM_CFG_NO_PRELOAD</code> 的 enUnitFlag 实际生效值取决于 <code>GetMDLConfig</code> 的实现,不能直接断言 mat_mul_v3 所有路径都开了 UnitFlag。</li>
</ul>
<ul>
<li><strong>唯一显式开启点</strong><code>mat_mul_stream_k_kernel.h:36-37</code>StreamK 的自定义搬出回调 <code>CustomDataCopyOut</code></li>
</ul>
<pre><code>if (params-&gt;enUnitFlag) {
fixpipeParams.unitFlag = MM_FIX_PIPE_UNIT_FLAG; // 使能unitflag的参数 3U
}</code></pre>
<p>这里 <code>params-&gt;enUnitFlag</code> 来自上层传入的搬出参数,<code>unitFlag = 3U</code> 直接写进 <code>FixpipeParamsC310</code></p>
<p><strong>使用场景总结</strong>mat_mul_v3 中 UnitFlag 的<strong>显式、确定开启</strong>只发生在 <strong>StreamK 分支的自定义搬出回调</strong>StreamK 的部分和需要从 L0C 搬到 GM workspace再由 AIV 累加,搬出路径特殊)。普通 ASWT/A/B 全载分支是否开 UnitFlag取决于 <code>GetMDLConfig</code> 的默认值官方口径MDL 模板默认不使能),源码没有显式强制开启。</p>
<hr/>
<h2>3. 适用条件边界:什么时候用哪个</h2>
<h3>3.1 L0C Double Buffer 的适用边界</h3>
<table><thead><tr><th>条件</th><th>说明</th></tr></thead><tbody>
<tr><td><code>baseM·baseN ≤ 32768</code>FP32 累加)</td><td>硬约束L0C 必须能容纳两份 base 块</td></tr>
<tr><td>存在「块间 Fixpipe 串行」</td><td>即 base 块数 &gt; 1多 base 块循环)</td></tr>
<tr><td>Fixpipe 耗时与 MMAD 相当或更短</td><td>若 Fixpipe 远快于 MMAD开不开 DB 都掩盖得住;若 Fixpipe 远慢DB 掩盖不住(需 UnitFlag</td></tr>
<tr><td>无 L0C 累加冲突</td><td>若 base 块需要在 L0C 上做 K 方向累加split-K 等DB 语义复杂</td></tr>
</tbody></table>
<p><strong>典型受益场景</strong>baseM=256、baseN=128 的矩形 base 块,多 base 块循环MMAD 与 Fixpipe 耗时相当。</p>
<h3>3.2 UnitFlag 的适用边界(官方文档明确约束)</h3>
<table><thead><tr><th>约束</th><th>内容</th></tr></thead><tbody>
<tr><td>模板限制</td><td>仅支持 <strong>Norm、IBShare、MDL</strong> 三个模板(<code>MatmulConfig.enUnitFlag</code> 字段说明)</td></tr>
<tr><td>流水互斥</td><td>使能 UnitFlag 时,<strong>不支持同时存在 CO1(L0C)→GM 和 A1(L1)→GM 两种搬出流水</strong></td></tr>
<tr><td>累加限制</td><td>使能 UnitFlag + L0C 累加时,<strong>不支持多次 Iterate 计算、一次 GetTensorC 输出</strong></td></tr>
<tr><td>收益前提</td><td>仅当 MMAD 与 FIXPIPE <strong>串行且未被其他流水掩盖</strong>时收益大;若已被 MTE2 Bound 掩盖,收益很小</td></tr>
<tr><td>默认状态</td><td>Norm/IBShare 默认使能,<strong>MDL 默认不使能</strong>(需显式 enUnitFlag=true</td></tr>
</tbody></table>
<p><strong>典型受益场景</strong>:单个 base 块内 MMAD 很快、Fixpipe 慢(如小 K、大 MN且模板是 Norm/IBShare/MDL、无 L1→GM 混合搬出。</p>
<h3>3.3 两者的叠加与互斥判断</h3>
<p>从官方约束与源码实现看:</p>
<ul>
<li><strong>可叠加</strong>L0C DB 是 Host 层的 buffer 划分UnitFlag 是 Device 层的指令同步,两者作用于不同层级,不冲突。<code>isA2B2Shared</code> 字段说明里甚至建议「开启 A2B2 共享时同时设 enUnitFlag=true」。</li>
<li><strong>不互斥,但有先后</strong>baseM=baseN=256 时 L0C 开不了 DB容量不够此时若 Fixpipe 是瓶颈,只能靠 UnitFlag 在块内掩盖baseM·baseN≤32768 时 DB 可开,块间掩盖由 DB 负责,块内细粒度掩盖仍可由 UnitFlag 补充。</li>
<li><strong>StreamK 的特殊性</strong>StreamK 的搬出是「L0C→workspace→AIV 累加→GM」不是直接 L0C→GM且用自定义回调显式开了 UnitFlag——因为 StreamK 的部分和搬出频繁、且与计算强相关,细粒度同步收益大。</li>
</ul>
<hr/>
<h2>4. 对用户问题的直接回答</h2>
</blockquote>
<p>存在。Host 层 <code>dbL0C</code><code>baseM·baseN·4B·2 ≤ L0C(256KB)</code>(即 baseM·baseN ≤ 32768时置 2普通 ASWT、A/B 全载、StreamK复用同一引擎都会走到。但当最优 base 块是 256×256 时dbL0C=1开不了</p>
</blockquote>
<p>存在,且<strong>显式开启点唯一</strong>StreamK 分支的自定义搬出回调 <code>CustomDataCopyOut</code><code>mat_mul_stream_k_kernel.h</code>),在 <code>params-&gt;enUnitFlag</code> 为真时设 <code>fixpipeParams.unitFlag = 3U</code>。普通 ASWT 分支的 UnitFlag 状态取决于 <code>MM_CFG_NO_PRELOAD</code><code>GetMDLConfig</code>)的默认值——官方口径 MDL 默认不使能,因此普通 ASWT 路径上 UnitFlag 大概率是关的,需对照 <code>GetMDLConfig</code> 实际参数确认。</p>
</blockquote>
<ul>
<li><strong>L0C DB</strong>掩盖「base 块之间」的 Fixpipe 串行,要求 L0C 能容两份 base 块baseM·baseN≤32768适合多 base 块、MMAD 与 Fixpipe 耗时相当的场景。</li>
<li><strong>UnitFlag</strong>掩盖「base 块内部」的 MMAD-Fixpipe 串行512B 细粒度,适合单块 MMAD 快、Fixpipe 慢的场景但受模板Norm/IBShare/MDL、流水互斥不能同时 CO1→GM 和 A1→GM、累加方式不能多次 Iterate 一次输出约束MDL 默认关。</li>
</ul>
</blockquote>
<p>不是。两者掩盖对象不同:<strong>L0C DB 掩盖块间、UnitFlag 掩盖块内</strong>。在「单块 MMAD 长、多块循环」的场景L0C DB 已能很好掩盖UnitFlag 收益小;在「单块 MMAD 短、Fixpipe 长」的场景L0C DB 掩盖不住(下一块 MMAD 很快算完又要等当前块搬出),必须靠 UnitFlag。两者是正交互补不是替代关系。</p>
<hr/>
<h2>5. 对 mat_mul_v3 的观察与后续建议</h2>
<ol>
<li><strong>UnitFlag 在普通 ASWT 路径的默认状态需确认</strong><code>MM_CFG_NO_PRELOAD = GetMDLConfig(..., true)</code> 的最后一个参数是否真是 enUnitFlag、MDL 默认值是否生效,建议对照 <code>GetMDLConfig</code> API 文档核实。若普通 ASWT 默认未开 UnitFlag而 StreamK 开了,两者在 Fixpipe 掩盖上的行为不一致,值得在性能模型里区分。</li>
<li><strong>dbL0C 与 baseM/baseN 的耦合</strong><code>dbL0C</code> 直接由 baseM·baseN 决定,但 baseM/baseN 的搜索(<code>GetRebalanceBlock</code>)目标是 CubeBound + 负载均衡没有把「dbL0C 能否开」纳入目标函数。当 256×256开不了 DB与 256×128能开 DB性能接近时模型可能选前者而牺牲 DB 收益——这是一个潜在的 tiling 改进点。</li>
<li><strong>性能模型需区分两种掩盖机制</strong>:后续 Phase 1 的 <code>T_total = max(...)</code> 模型里Fixpipe 项应区分「块间掩盖DB」和「块内掩盖UnitFlag否则对 Fixpipe Bound 场景会高估或低估。</li>
</ol>
<hr/>
<h2>附录:关键证据索引</h2>
<table><thead><tr><th>证据</th><th>位置</th></tr></thead><tbody>
<tr><td>UnitFlag 定义与约束</td><td><code>昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._Matmul高阶API开启UnitFlag.md</code>、商用版 9.0.0 <code>203_..._使能UnitFlag.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>dbL0C 设置条件</td><td><code>ops-nn/matmul/mat_mul_v3/op_host/op_tiling/arch35/matmul_v3_tiling_helper.cpp:490</code></td></tr>
<tr><td>dbL0C 在全载分支</td><td><code>.../matmul_v3_basic_aswt_tiling.cpp:203,274</code></td></tr>
<tr><td>UnitFlag 显式开启</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>MM_CFG_NO_PRELOAD 定义</td><td><code>ops-nn/matmul/mat_mul_v3/op_kernel/mat_mul_v3_common.h:72</code></td></tr>
</tbody></table></body></html>