v1.0 同步HTML
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>L0C 流水机制:UnitFlag 与 Double Buffer 对比分析 v1.0</title>
|
||||
<script>
|
||||
MathJax = {
|
||||
tex: {
|
||||
inlineMath: [['$','$']],
|
||||
displayMath: [['$$','$$']],
|
||||
tags: 'ams',
|
||||
processEscapes: true
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
<style>
|
||||
:root{--ink:#1f2933;--muted:#5f6b7a;--accent:#0b6bcb;--accent2:#0e9f6e;--line:#d9e2ec;--code-bg:#f4f6f9}
|
||||
*{box-sizing:border-box}
|
||||
body{font-family:"PingFang SC","Microsoft YaHei","Helvetica Neue",Arial,sans-serif;color:var(--ink);background:#eef2f6;margin:0;line-height:1.8}
|
||||
.page{max-width:1020px;margin:0 auto;padding:32px 44px 80px;background:#fff;box-shadow:0 0 24px rgba(0,0,0,.06)}
|
||||
h1{font-size:28px;border-bottom:3px solid var(--accent);padding-bottom:12px;margin-top:8px}
|
||||
h2{font-size:22px;margin-top:44px;border-left:6px solid var(--accent);padding-left:12px;color:#0b3d73}
|
||||
h3{font-size:18px;margin-top:30px;color:#0b3d73;border-bottom:1px dashed var(--line);padding-bottom:6px}
|
||||
h4{font-size:16px;margin-top:20px;color:#123}
|
||||
table{border-collapse:collapse;width:100%;margin:14px 0;font-size:14px}
|
||||
th,td{border:1px solid var(--line);padding:7px 10px;text-align:left;vertical-align:top}
|
||||
th{background:#eaf2fb;color:#0b3d73}
|
||||
tr:nth-child(even) td{background:#f8fafc}
|
||||
code,pre{font-family:"JetBrains Mono",Consolas,Menlo,monospace;font-size:13px}
|
||||
code{background:var(--code-bg);padding:1px 5px;border-radius:4px;color:#9d2c5e}
|
||||
pre{background:var(--code-bg);border:1px solid var(--line);border-radius:8px;padding:14px;overflow-x:auto;line-height:1.55}
|
||||
pre code{background:none;color:#243447;padding:0}
|
||||
blockquote{background:#eafaf3;border-left:5px solid var(--accent2);padding:10px 16px;border-radius:0 8px 8px 0;margin:14px 0}
|
||||
.math{background:#fafbfc;border:1.5px solid #c3d6ee;border-radius:10px;padding:10px 22px;margin:14px 0;overflow-x:auto}
|
||||
ul.tight li,ol.tight li{margin:3px 0}
|
||||
hr{border:none;border-top:1px solid var(--line);margin:24px 0}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>L0C 流水机制对比分析:UnitFlag 与 Double Buffer</h1>
|
||||
<p><b>版本</b>:v1.0</p>
|
||||
<p><b>日期</b>:2026-08-28</p>
|
||||
<p><b>范围</b>:昇腾 950PR Cube 核,BatchMatMulV3 算子数据流场景</p>
|
||||
<p><b>定位</b>:回答"UnitFlag 这么好,为什么还需要 L0C 双缓冲(dbL0C=2)"——从机制、时延模型、源码事实三个层面给出两者的适用边界。</p>
|
||||
<hr>
|
||||
<h2>摘要</h2>
|
||||
<p>UnitFlag 与 L0C 双缓冲解决的是同一个问题:<b>MMAD(Cube 计算写 L0C)与 Fixpipe(L0C 读出发送 GM)之间的流水掩盖</b>,但机制完全不同——双缓冲用"两块 buffer 交替"实现 tile 间粗粒度流水,UnitFlag 用"512B 块级标志位"实现 tile 内细粒度流水。本文先分别解析两种机制,再建立三方案(单缓冲串行 / 双缓冲 / UnitFlag)时延模型对比,最后结合 batch_mat_mul_v3 源码事实给出适用边界判定链。</p>
|
||||
<p>核心结论:</p>
|
||||
<ol class="tight">
|
||||
<li><b>稳态时延</b>:双缓冲与 UnitFlag 都达到 $\max(T_{MMAD}, T_{FIX})$,两者打平;</li>
|
||||
<li><b>UnitFlag 的真实优势</b>:省掉第二份 buffer → tile 上限从 $L0C/2$ 翻倍到 $L0C$ → L2 重复读减少、切分 padding 减少、尾巴从"1 个 tile"缩到"1 个 512B 块";</li>
|
||||
<li><b>dbL0C=2 仍存在的原因</b>:源码未启用 UnitFlag(试验特性);且 UnitFlag 有硬约束——Mmad/Fixpipe 块划分须对齐、Fixpipe 带 NZ2ND 等变换时要求 Mmad N 向优先、多 batch 输出分区(NBatchOut)场景管理复杂;MTE2 Bound 场景两者都不需要;</li>
|
||||
<li><b>边界判定</b>:MTE2 Bound → 单缓冲无 UnitFlag;需掩盖且 tile 已超 $L0C/2$ → UnitFlag 是唯一手段;需掩盖且带变换/多 batch 分区 → 双缓冲;其余重叠区 → UnitFlag 单缓冲大 tile 更优。</li>
|
||||
</ol>
|
||||
<hr>
|
||||
<h2>一、问题背景:两个机制解决同一问题</h2>
|
||||
<p>Cube 核执行 matmul 的数据流(单 batch、单核视角):</p>
|
||||
<pre><code>GM --MTE2--> L1(A,B) --MTE1--> L0A/L0B --Cube(MMAD)--> L0C --Fixpipe--> GM</code></pre>
|
||||
<p>其中 L0C 是 Cube 的累加器:MMAD 沿 K 迭代<b>累加写</b> L0C,K 迭代全部完成后 L0C 中才有完整结果,随后 Fixpipe 把 L0C <b>读出发送</b> GM。</p>
|
||||
<p>设单 tile 的计算时延 $T_{MMAD}$(全部 K 迭代的 MMAD 时间)与写出时延 $T_{FIX}$(Fixpipe 搬出整个 tile 的时间)。若两者<b>串行</b>,单 tile 时延为 $T_{MMAD} + T_{FIX}$。问题的本质是:L0C 只有一份 buffer 时,Fixpipe 读 tile N 与 MMAD 写 tile N+1 争用同一块地址空间,无法并行;要让两者并行,要么给两份空间(双缓冲),要么让读写同步粒度足够细、允许在同一块空间上"追尾"(UnitFlag)。</p>
|
||||
<p>由此立即可见:<b>双缓冲与 UnitFlag 是同一问题的两个解,在"能否掩盖"上等价,差异在"用什么代价掩盖"以及各自的约束条件</b>。</p>
|
||||
<hr>
|
||||
<h2>二、UnitFlag 机制详解(官方语义)</h2>
|
||||
<p>依据官方文档 [UnitFlag-Mmad计算关键特性说明](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/latest/API/ascendcopapi/docs/zh/api/SIMD-API/%E5%9F%BA%E7%A1%80API/cube_compute_TensorAPI/Mmad%E8%AE%A1%E7%AE%97%E5%85%B3%E9%94%AE%E7%89%B9%E6%80%A7%E8%AF%B4%E6%98%8E/UnitFlag.md)(CANN 9.2.0-beta.1,试验特性)。</p>
|
||||
<h3>2.1 机制:512B 块级标志位</h3>
|
||||
<p>开启 UnitFlag 后,<b>L0C Buffer 中每个 512B 内存块</b>有一个单元标志位,指示该块当前"可读"还是"可写":</p>
|
||||
<table><tr><th>操作</th><th>标志位语义</th><th>执行完成后的标志位</th></tr>
|
||||
<tr><td>写操作(Mmad),unitFlag=2</td><td>flag=0 直接写;flag=1 等待至 0</td><td>保持 0(块仍被占用)</td></tr>
|
||||
<tr><td>写操作(Mmad),unitFlag=3</td><td>flag=0 直接写;flag=1 等待至 0</td><td>置 1(写完,可读)</td></tr>
|
||||
<tr><td>读操作(Copy/Fixpipe),unitFlag=2</td><td>flag=1 直接读;flag=0 等待至 1</td><td>保持 1(块仍被占用)</td></tr>
|
||||
<tr><td>读操作(Copy/Fixpipe),unitFlag=3</td><td>flag=1 直接读;flag=0 等待至 1</td><td>置 0(读完,可写)</td></tr></table>
|
||||
<p>语义归纳:<b>unitFlag=2 是"保持占用",unitFlag=3 是"写完/读完释放"</b>。同步粒度从"整个 L0C tile"细化为"512B 块"。</p>
|
||||
<h3>2.2 两种标准使用模式</h3>
|
||||
<p>官方文档给出两个典型模式:</p>
|
||||
<p><b>模式一:K 迭代累积后一次搬出</b>(示例 A 128×1024、B 1024×128,baseK=128 迭代 8 次,8 次 MMAD 对应 1 次 Fixpipe):</p>
|
||||
<ul class="tight">
|
||||
<li>前 7 次 MMAD:unitFlag=2(累加中间结果,块保持"占用"状态,Fixpipe 不可读——因为块里还不是最终结果);</li>
|
||||
<li>第 8 次 MMAD:unitFlag=3(K 迭代完成,块置"可读");</li>
|
||||
<li>Fixpipe:unitFlag=3(读完后块置"可写",供下一个 tile 复用)。</li>
|
||||
</ul>
|
||||
<p><b>模式二:单次 MMAD 分多次搬出</b>(MMAD 结果 128×256,沿 N 分两次 Fixpipe 搬出):</p>
|
||||
<ul class="tight">
|
||||
<li>MMAD:unitFlag=3;</li>
|
||||
<li>每次 Fixpipe:unitFlag=3(读完释放)。</li>
|
||||
</ul>
|
||||
<h3>2.3 流水如何形成:跨 tile 的 512B 追尾</h3>
|
||||
<p>结合 §一 的数据流,UnitFlag 的流水发生在 <b>K 迭代完成后的输出块流</b>上:</p>
|
||||
<ol class="tight">
|
||||
<li>tile N 的最后一条 MMAD 沿 K 迭代逐 512B 块完成 → 每块 flag 置 1;</li>
|
||||
<li>Fixpipe 逐块读出发送 GM → 每块读完 flag 置 0;</li>
|
||||
<li>tile N+1 的 MMAD 逐块追着写(该块 flag 已置 0 才可写)→ 又逐块置 1 → 循环。</li>
|
||||
</ol>
|
||||
<p>于是 Fixpipe 与 MMAD 在<b>同一份 L0C 上以 512B 块为粒度追尾并行</b>,等效于 512B 粒度的乒乓。这回答了"为什么单缓冲也能流水":互斥的单位从 tile 缩小到 512B 块,读写只需错开一个块即可并行。</p>
|
||||
<h3>2.4 官方文档明确列出的约束</h3>
|
||||
<ol class="tight">
|
||||
<li><b>Mmad 与 Copy 接口须同步开启</b> unitFlag,才能正常生效;</li>
|
||||
<li><b>建议 Mmad 计算数据量与 Fixpipe 搬出数据量保持一致</b>——若 MMAD 算 128×128 而 Fixpipe 只搬 64×64,可能导致执行异常,需通过 ResetL0CState 接口重置 L0C 状态。即<b>两者的块划分必须对齐</b>;</li>
|
||||
<li><b>Fixpipe 使能 NZ2ND 或 ChannelMerge 等 layout 变换时,MMAD 计算方向须设为 N 方向优先</b>;无变换时 M 方向优先。即<b>带变换的 Fixpipe 数据通路对 MMAD 有额外方向约束</b>;</li>
|
||||
<li>连续多条指令操作同一块空间时,前 n-1 条 unitFlag=2、最后一条 unitFlag=3 的"占用链"必须完整;</li>
|
||||
<li>该特性标注为<b>试验特性</b>。</li>
|
||||
</ol>
|
||||
<hr>
|
||||
<h2>三、L0C 双缓冲(dbL0C)机制与 BMM 源码事实</h2>
|
||||
<h3>3.1 机制:tile 间粗粒度乒乓</h3>
|
||||
<p>dbL0C=2 时 L0C 分为两份 buffer:MMAD 在 buffer 0 算 tile N 的同时,Fixpipe 从 buffer 1 搬出 tile N-1。同步单位是<b>整个 tile</b>(baseM×baseN 级),互斥只在 tile 交替处发生,tile 内部不需要任何块级标志。代价是 L0C 有效容量减半:</p>
|
||||
<div class="math">$$S_{tile} = \text{baseM} \times \text{baseN} \times 4\text{B} \le L0C/2$$</div>
|
||||
<h3>3.2 源码事实:dbL0C 决策是纯容量判断</h3>
|
||||
<p>[batch_mat_mul_v3 源码](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3)(op_host/op_tiling)中 dbL0C 的赋值在 AL1FullLoadTiling / BL1FullLoadTiling / arch35 ASW tiling 三处,公式完全一致:</p>
|
||||
<pre><code>tilingData.dbL0C = (baseM * baseN * 4 * NUM_TWO > l0CSize) ? 1 : NUM_TWO; // 4 is data size within l0c</code></pre>
|
||||
<p>即:<b>tile 的 2 倍放得进 L0C 就开双缓冲,放不进就单缓冲</b>——只看容量,不看 bound 类型。</p>
|
||||
<p>配合另一处源码事实——普通 BMM 路径(DoCommonTiling → TuneBaseMKN → CalcBaseMN)对 tile 的约束:</p>
|
||||
<pre><code>baseN = std::min(LastPower2(32768UL / baseM), bestBaseN); // L0C大小限制baseM * baseN <= 32768</code></pre>
|
||||
<p>$32768 \text{ 元素} \times 4\text{B} = 128\text{KB} = L0C/2$。<b>普通 BMM 路径从根上就把 tile 限制在 L0C 一半以内,即默认按双缓冲规划</b>——这解释了为什么 batch_mat_mul_v3 中大量场景 dbL0C=2:不是某种 bound 分析的结果,而是 CalcBaseMN 的 tile 上限本身就是双缓冲容量,容量公式自然回 2。</p>
|
||||
<p>而 ASW 路径(arch35/batch_matmul_v3_asw_al1_full_load_basic_tiling.cpp 等)经 ResetBaseDav3510 置 baseM=baseN=256 后,$256 \times 256 \times 4\text{B} \times 2 = 512\text{KB} > 256\text{KB} = L0C$,容量公式自动退化 dbL0C=1。</p>
|
||||
<h3>3.3 多 batch 输出(NBatchOut)对 dbL0C 的依赖</h3>
|
||||
<p>DoMultiBatchOutTiling 中:</p>
|
||||
<pre><code>uint64_t batchOutCnt = compileInfo_.l0CSize / (baseN * baseM * dbL0C * 4);
|
||||
bool isNBatchOut = batchOutCnt > 1UL && batchInfo_.batchC > 1000UL;</code></pre>
|
||||
<p>多 batch 输出场景(batchC > 1000 的小矩阵 BMM)把 L0C 划分为 batchOutCnt 个分区,每分区放一个 batch 的输出 tile,<b>dbL0C 直接参与分区数计算</b>。此时 L0C 的语义从"同一 tile 的双缓冲"变为"多 batch 输出的多分区",tile 间流水靠 batch 间交错实现,UnitFlag 的块级标志在多分区边界上会引入跨 batch 的管理复杂度。</p>
|
||||
<h3>3.4 源码未启用 UnitFlag</h3>
|
||||
<p>本工作区保存的 batch_mat_mul_v3 源码副本中,所有 MMAD/Fixpipe 调用均无 unitFlag 参数——该试验特性在 BMM 算子中未启用。即<b>源码在 dbL0C=1 的场景(ASW 大 tile)下,MMAD 与 Fixpipe 是 tile 级串行的</b>(靠 MTE2 Bound 掩盖,见 §5)。</p>
|
||||
<hr>
|
||||
<h2>四、三方案时延建模对比</h2>
|
||||
<h3>4.1 符号定义</h3>
|
||||
<p>设单核处理一个输出 tile(baseM×baseN,FP32 输出 4B,K 维全量迭代):</p>
|
||||
<table><tr><th>符号</th><th>定义</th></tr>
|
||||
<tr><td>$T_{MMAD}$</td><td>单 tile 全部 K 迭代的 Cube 计算时延</td></tr>
|
||||
<tr><td>$T_{FIX}$</td><td>单 tile 的 Fixpipe 写出时延</td></tr>
|
||||
<tr><td>$T_{MTE2}$</td><td>单 tile 的 GM→L1 搬入时延(与 L0C 方案无关)</td></tr>
|
||||
<tr><td>$n_{tile}$</td><td>单核处理的 tile 总数</td></tr>
|
||||
<tr><td>$Q_{core}$</td><td>单核 Cube 计算速率(FLOP/s)</td></tr>
|
||||
<tr><td>$W_{pc}$</td><td>单核 Fixpipe 写 GM 带宽(B/s)</td></tr></table>
|
||||
<div class="math">$$T_{MMAD} = \frac{2 \cdot \text{baseM} \cdot \text{baseN} \cdot K}{Q_{core}},\qquad T_{FIX} = \frac{\text{baseM} \cdot \text{baseN} \cdot 4\text{B}}{W_{pc}}$$</div>
|
||||
<h3>4.2 方案一:单缓冲串行(dbL0C=1,无 UnitFlag)</h3>
|
||||
<p>L0C 只有一份 buffer 且无块级标志:Fixpipe 必须等整个 tile 算完,且 Fixpipe 读 tile N 期间 MMAD 不能写 tile N+1(同一地址)。完全串行:</p>
|
||||
<div class="math">$$T_1 = n_{tile} \cdot (T_{MMAD} + T_{FIX})$$</div>
|
||||
<h3>4.3 方案二:双缓冲(dbL0C=2)</h3>
|
||||
<p>tile N 计算与 tile N-1 写出并行,稳态单 tile 时延 $\max(T_{MMAD}, T_{FIX})$,另有首 tile 计算启动与尾 tile 写出排空各一次:</p>
|
||||
<div class="math">$$T_2 = n_{tile}' \cdot \max(T_{MMAD}', T_{FIX}') + T_{MMAD}' + T_{FIX}'$$</div>
|
||||
<p>其中带撇量为双缓冲下的参数:tile 上限减半($\text{baseM}' \times \text{baseN}' \le 32768$),$n_{tile}'$ 相应增大。</p>
|
||||
<h3>4.4 方案三:UnitFlag 单缓冲</h3>
|
||||
<p>Fixpipe 以 512B 块粒度追尾 MMAD,稳态同样 $\max(T_{MMAD}, T_{FIX})$,排空尾巴仅为<b>最后 1 个 512B 块</b>的 Fixpipe 时间 $\tau_{blk}$:</p>
|
||||
<div class="math">$$T_3 = n_{tile} \cdot \max(T_{MMAD}, T_{FIX}) + \tau_{blk},\qquad \tau_{blk} = \frac{512\text{B}}{W_{pc}}$$</div>
|
||||
<h3>4.5 稳态等价性论证</h3>
|
||||
<p>双缓冲 tile 减半后 $T_{MMAD}', T_{FIX}'$ 各减半(两者都与 baseM×baseN 成正比),$n_{tile}'$ 翻倍(忽略对齐 padding 时),故稳态项:</p>
|
||||
<div class="math">$$n_{tile}' \cdot \max(T_{MMAD}', T_{FIX}') = 2 n_{tile} \cdot \frac{1}{2}\max(T_{MMAD}, T_{FIX}) = n_{tile} \cdot \max(T_{MMAD}, T_{FIX})$$</div>
|
||||
<p><b>稳态时延两者严格相同</b>。差异仅在:①首尾尾巴(双缓冲 $T_{MMAD}' + T_{FIX}'$ 一个 tile 量级 vs UnitFlag $\tau_{blk}$ 一个 512B 块量级);②tile 上限导致的切分 padding 与 L2 重复读(§4.6)。</p>
|
||||
<h3>4.6 数值实例(问题 → 思路 → 公式 → 实例)</h3>
|
||||
<p><b>场景</b>:M=N=2048、K=512、单 batch、BF16 输入、FP32 输出直写 GM。芯片参数:$Q_{core} = 486\text{TFLOPS}/32 = 15.2\text{TFLOPS}$,$W_{pc} = 1.6\text{TB/s}/32 = 50\text{GB/s}$(假设均摊,数量级估计),L0C=256KB。</p>
|
||||
<p><b>tile 选择</b>:</p>
|
||||
<ul class="tight">
|
||||
<li>单缓冲:$256 \times 256 = 65536$ 元素 × 4B = 256KB,恰好占满 L0C;</li>
|
||||
<li>双缓冲:上限 $32768$ 元素,取 $181 \times 181 = 32761$($\le 32768$,128KB × 2 = 256KB 恰好)。</li>
|
||||
</ul>
|
||||
<p><b>单 tile 时延</b>:</p>
|
||||
<div class="math">$$
|
||||
T_{MMAD} = \frac{2 \times 256 \times 256 \times 512}{15.2 \times 10^{12}} = 4.41\,\mu s,\qquad T_{FIX} = \frac{256 \times 256 \times 4}{50 \times 10^9} = 5.24\,\mu s
|
||||
$$</div>
|
||||
<div class="math">$$
|
||||
T_{MMAD}' = \frac{2 \times 181 \times 181 \times 512}{15.2 \times 10^{12}} = 2.21\,\mu s,\qquad T_{FIX}' = \frac{181 \times 181 \times 4}{50 \times 10^9} = 2.62\,\mu s
|
||||
$$</div>
|
||||
<p><b>tile 数与 padding</b>:</p>
|
||||
<ul class="tight">
|
||||
<li>单缓冲:$n_{tile} = (2048/256)^2 = 64$,零 padding;</li>
|
||||
<li>双缓冲:$n_{tile}' = \lceil 2048/181 \rceil^2 = 12^2 = 144$,padding 后实际覆盖 $144 \times 181^2 / 2048^2 = 1.125$,<b>12.5% 计算与写出虚增</b>。</li>
|
||||
</ul>
|
||||
<p><b>三方案总时延</b>:</p>
|
||||
<div class="math">$$
|
||||
T_1 = 64 \times (4.41 + 5.24) = 618\,\mu s
|
||||
$$</div>
|
||||
<div class="math">$$
|
||||
T_2 = 144 \times \max(2.21, 2.62) + 2.21 + 2.62 = 382\,\mu s
|
||||
$$</div>
|
||||
<div class="math">$$
|
||||
T_3 = 64 \times \max(4.41, 5.24) + \frac{512}{50 \times 10^9} = 335.4\,\mu s + 10\,ns
|
||||
$$</div>
|
||||
<p><b>解读</b>:</p>
|
||||
<ol class="tight">
|
||||
<li>串行 → 双缓冲/UnitFlag:618 → 382/335 μs,掩盖收益巨大(该场景 $T_{FIX} > T_{MMAD}$ 写出 Bound);</li>
|
||||
<li>双缓冲 vs UnitFlag:稳态相同,但 UnitFlag 因 tile 翻倍获得<b>零 padding</b>(省 42μs,12%)和<b>可忽略的尾巴</b>(10ns vs 双缓冲首尾 4.8μs);</li>
|
||||
<li>若场景改为计算 Bound(K=2048、M=N=512),可算得 $T_1 = 92\mu s$、$T_2 = 79.6\mu s$、$T_3 = 70.7\mu s$——差异同样来自双缓冲的 padding 虚增。</li>
|
||||
</ol>
|
||||
<p><b>tile 大小的连带效应(决定性差异)</b>:UnitFlag 的 tile 上限是双缓冲的 2 倍。按最少切分原则,tile 越大 → mCnt×nCnt 越小 → 每行 A/B 的 L2 重复读越少 → $T_{MTE2}$ 越小。这正是 UnitFlag 相对双缓冲的<b>结构性优势</b>,而不是稳态流水上的优势。</p>
|
||||
<hr>
|
||||
<h2>五、为什么 batch_mat_mul_v3 中还有 dbL0C=2 场景</h2>
|
||||
<p>综合 §二~§四,逐条回答:</p>
|
||||
<ol class="tight">
|
||||
<li><b>源码压根没启用 UnitFlag</b>(§3.4)。试验特性未被生产代码采用,dbL0C 是源码中唯一的流水手段;</li>
|
||||
<li><b>dbL0C 决策不分析 bound</b>:容量公式回 2 的唯一条件是 tile ≤ L0C/2。普通 BMM 路径 CalcBaseMN 强制 tile ≤ 32768 元素(§3.2),所以这些场景<b>必然</b> dbL0C=2——这是"按双缓冲规划 tile"的设计惯性,而非"该场景双缓冲更优"的结论;</li>
|
||||
<li><b>多 batch 输出分区(NBatchOut)</b>:batchC>1000 的小矩阵场景 L0C 被划分为多 batch 输出区(§3.3),分区边界与 UnitFlag 块级标志的管理会互相干扰,batch 间交错已提供粗粒度流水,UnitFlag 收益边际小;</li>
|
||||
<li><b>Fixpipe 带布局变换</b>:NZ 输出 / ND2NZ on-the-fly 等变换要求 MMAD N 方向优先(§2.4 约束 3),与默认 M 方向优先的 BMM 数据流冲突,双缓冲无此约束;</li>
|
||||
<li><b>Mmad/Fixpipe 块划分对齐约束</b>(§2.4 约束 2):BMM 的 Fixpipe 搬出粒度随 epilogue、bias、batch stride 变化,强制与 MMAD 块对齐会限制 tiling 灵活性,双缓冲下 Fixpipe 可搬任意子区域;</li>
|
||||
<li><b>MTE2 Bound 场景两者都不需要</b>:$T_{MTE2} > T_{MMAD}, T_{FIX}$ 时瓶颈在 GM→L1 搬入,MMAD/Fixpipe 串行也被搬移掩盖,单缓冲 dbL0C=1 即可——这正是 ASW 大 tile 路径的现状。</li>
|
||||
</ol>
|
||||
<hr>
|
||||
<h2>六、适用边界判定链</h2>
|
||||
<p>设 $\beta = \max(T_{MMAD}, T_{FIX})$ 为需掩盖的瓶颈项,$S_{tile} = \text{baseM} \times \text{baseN} \times 4\text{B}$ 为 tile 字节数。判定从 bound 类型开始:</p>
|
||||
<p><b>判定 1:是否 MTE2 Bound</b></p>
|
||||
<div class="math">$$T_{MTE2} \ge \beta \;\Longrightarrow\; \text{单缓冲 dbL0C=1,不开 UnitFlag}$$</div>
|
||||
<p>瓶颈在搬入,MMAD/Fixpipe 串行被掩盖,双缓冲白白浪费一半 L0C 容量,UnitFlag 无额外收益。<b>注意</b>:源码 dbL0C 容量公式在此场景恰好正确(tile 大 → 容量放不下两份 → 自动 1),是"容量判断与 bound 判断殊途同归"的巧合,不能倒果为因认为源码做了 bound 分析。</p>
|
||||
<p>**判定 2:需要掩盖($T_{MTE2} < \beta$)时,双缓冲是否可行**</p>
|
||||
<div class="math">$$2 \cdot S_{tile} \le L0C \;\Longleftrightarrow\; \text{双缓冲可行}$$</div>
|
||||
<p><b>判定 3:UnitFlag 是否可行</b>(§2.4 约束映射到 BMM 场景)</p>
|
||||
<table><tr><th>约束</th><th>不满足的 BMM 场景</th></tr>
|
||||
<tr><td>MMAD 与 Fixpipe 块划分对齐</td><td>Fixpipe 带 epilogue 链 / bias / batch stride 导致搬出粒度与 512B 块错位</td></tr>
|
||||
<tr><td>带 NZ2ND/ChannelMerge 时 MMAD 须 N 优先</td><td>NZ 输出、ND2NZ on-the-fly 变换场景</td></tr>
|
||||
<tr><td>单 tile 分区语义简单</td><td>多 batch 输出 NBatchOut 分区场景</td></tr>
|
||||
<tr><td>试验特性可接受</td><td>生产代码保守策略</td></tr></table>
|
||||
<p><b>判定 4:分支选择</b></p>
|
||||
<table><tr><th>条件</th><th>结论</th></tr>
|
||||
<tr><td>$T_{MTE2} \ge \beta$</td><td>dbL0C=1,无 UnitFlag(源码 ASW 现状)</td></tr>
|
||||
<tr><td>$T_{MTE2} < \beta$ 且 $2 S_{tile} > L0C$</td><td>双缓冲不可行 → <b>UnitFlag 是唯一流水手段</b>;不用则接受 $T_{MMAD}+T_{FIX}$ 串行</td></tr>
|
||||
<tr><td>$T_{MTE2} < \beta$ 且 $2 S_{tile} \le L0C$ 且 UnitFlag 不可行</td><td>双缓冲(源码普通 BMM 路径现状)</td></tr>
|
||||
<tr><td>$T_{MTE2} < \beta$ 且 $2 S_{tile} \le L0C$ 且 UnitFlag 可行</td><td>两者都可,<b>UnitFlag 单缓冲大 tile 更优</b>(tile 翻倍 → L2 重复读↓、padding↓、尾巴↓)</td></tr></table>
|
||||
<p>重叠区(末行)是唯一"可以选"的区域,其余区域由约束唯一决定。重叠区中 UnitFlag 相对双缓冲的优势全部来自 tile 上限翻倍(§4.6),若 tiling 本身无 padding 且 L2 重复读不敏感,两者差异缩至尾巴量级。</p>
|
||||
<hr>
|
||||
<h2>七、结论与对 BMM 优化的建议</h2>
|
||||
<ol class="tight">
|
||||
<li><b>UnitFlag 不能完全取代双缓冲</b>:MTE2 Bound 场景两者都不需要;带 NZ2ND 变换、NBatchOut 多分区、块划分无法对齐的场景 UnitFlag 不可行,双缓冲是唯一手段;</li>
|
||||
<li><b>源码 dbL0C=2 是设计惯性而非最优选择</b>:CalcBaseMN 把 tile 上限锁死在 L0C/2(双缓冲容量),导致普通 BMM 路径 tile 全部偏小、切分偏多。若按 UnitFlag 单缓冲规划(tile 上限 L0C),tile 可放大 2 倍,L2 重复读与 padding 同步下降;</li>
|
||||
<li><b>BMM 优化建议</b>(按优先级):</li>
|
||||
<ul class="tight">
|
||||
<li>计算/写出 Bound + tile 已超 L0C/2 的 ASW 场景:<b>启用 UnitFlag</b>,将单 tile 时延从 $T_{MMAD}+T_{FIX}$ 降为 $\max(T_{MMAD}, T_{FIX})$——这是当前源码最大的直接收益点(此前分析已指出 ASW kernel 未启用 UnitFlag);</li>
|
||||
<li>计算/写出 Bound + 小 tile + 无变换/单 batch:<b>tile 放大 + UnitFlag</b>,替代 dbL0C=2;</li>
|
||||
<li>NZ 输出 / NBatchOut / 块划分无法对齐:<b>维持双缓冲</b>;</li>
|
||||
<li>MTE2 Bound:维持单缓冲,不必引入 UnitFlag 复杂度;</li>
|
||||
</ul>
|
||||
<li><b>工程落地顺序</b>:先在无变换、单 batch、块划分规整的 ASW 路径启用(约束最少、收益最大),验证后再向带变换场景扩展。</li>
|
||||
</ol>
|
||||
<hr>
|
||||
<h2>参考文献</h2>
|
||||
<ol class="tight">
|
||||
<li>[UnitFlag-Mmad计算关键特性说明(CANN 9.2.0-beta.1 官方文档)](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/latest/API/ascendcopapi/docs/zh/api/SIMD-API/%E5%9F%BA%E7%A1%80API/cube_compute_TensorAPI/Mmad%E8%AE%A1%E7%AE%97%E5%85%B3%E9%94%AE%E7%89%B9%E6%80%A7%E8%AF%B4%E6%98%8E/UnitFlag.md)</li>
|
||||
<li>[batch_mat_mul_v3 源码仓(CANN ops-nn)](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3):op_host/op_tiling/batch_mat_mul_v3_base_tiling.cpp(CalcBaseMN、AL1FullLoadTiling/BL1FullLoadTiling 的 dbL0C 赋值、DoMultiBatchOutTiling)、op_host/op_tiling/arch35/batch_matmul_v3_asw_al1_full_load_basic_tiling.cpp</li>
|
||||
<li>昇腾 950PR 规格:L0C 256KB/核、L1 512KB/核、Cube 486 TFLOPS@BF16(32 核均摊 15.2 TFLOPS/核)、GM 带宽 1.6TB/s(32 核均摊 50GB/s,估计值)</li>
|
||||
</ol>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user