197 lines
14 KiB
HTML
197 lines
14 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>MergeBatch vs IterBatch 分析</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>MergeBatch vs IterBatch:BMM 小 tile 场景的两种实现路径分析</h1>
|
||
<blockquote>目标芯片:昇腾 950PR(DAV_3510)。理论分析基于《BMM 算子优化分析 v0.96》,源码参考 [cann-ops-nn](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3) 实现。</blockquote>
|
||
<hr>
|
||
<h2>一、问题定义</h2>
|
||
<p>BMM(BatchMatMul)中,当 B ≥ C(batch 数 ≥ AIC 核数)时,核间切 B 是免费的——每核独立处理若干 batch,无共享无依赖。核内的两种实现路径:</p>
|
||
<ul class="tight">
|
||
<li><b>IterBatch</b>:逐 batch 处理——每 batch 独立搬入 L1、独立计算、独立写回</li>
|
||
<li><b>MergeBatch</b>:合并处理——把 $b_0$ 个 batch 的 $A[M,K]@B[K,N]$ 合并为 $[b_0M, K]@[K, b_0N]$ 的大矩阵乘,一次搬入 L1、一次计算、一次写回</li>
|
||
</ul>
|
||
<p>核心问题:<b>什么条件下 MergeBatch 优于 IterBatch?</b></p>
|
||
<hr>
|
||
<h2>二、IterBatch 理论最优实现</h2>
|
||
<h3>2.1 执行模型</h3>
|
||
<p>核间切 B,每核 $b_{core} = B/C$ 个 batch。核内逐 batch 做标准 Matmul:</p>
|
||
<pre><code>对每 batch i ∈ [0, b_core):
|
||
GM→L1: 搬入 A_i[M, K] + B_i[K, N](或按 kL1 分块)
|
||
L1→L0→Cube: 计算 C_i[M, N]
|
||
Fixpipe: L0C→GM 写回 C_i</code></pre>
|
||
<h3>2.2 L1 驻留形态</h3>
|
||
<p>IterBatch 按 L1 容量分四种形态(v0.96 §六):</p>
|
||
<table><tr><th>形态</th><th>条件</th><th>特征</th></tr>
|
||
<tr><td>a) 单 batch 全驻留</td><td>$b_{core}=1$,$(MK+KN) \cdot dtype \le L1$</td><td>零重复读</td></tr>
|
||
<tr><td>b) 双 batch 乒乓</td><td>$b_{core}>1$,$2(MK+KN) \cdot dtype \le L1$</td><td>batch 间流水掩盖</td></tr>
|
||
<tr><td>c) 一侧驻留+对侧切 K</td><td>$MK \cdot dtype \le L1/2$(驻留侧)</td><td>驻留侧零重复读</td></tr>
|
||
<tr><td>d) 两侧都切 K</td><td>兜底</td><td>两侧都有 K 段级重复读</td></tr></table>
|
||
<h3>2.3 实现方案</h3>
|
||
<ul class="tight">
|
||
<li><b>L0 tile</b>:BaseM × BaseN 尽量用满 L0C(32768 元素双缓冲),baseK 由 L0A/L0B 容量决定</li>
|
||
<li><b>L1 tile</b>:按形态选择 kL1,须满足 $k_{L1} \cdot dtype \ge 256$B(dValue)</li>
|
||
<li><b>流水</b>:fixpipe 开 unitflag,batch 间由 L0C 双缓冲自动交叠</li>
|
||
</ul>
|
||
<hr>
|
||
<h2>三、MergeBatch 理论最优实现</h2>
|
||
<h3>3.1 执行模型</h3>
|
||
<p>核间切 B(同 IterBatch),核内把 $b_0$ 个 batch 合并计算:</p>
|
||
<pre><code>对每合并组 g ∈ [0, b_core/b0):
|
||
GM→L1: 搬入 A'[b0·M, K] + B'[K, b0·N](单次 DMA,ndNum=1)
|
||
L1→L0→Cube: 计算 C'[b0·M, b0·N]
|
||
Fixpipe: L0C→GM 写回(只取对角块,丢弃交叉项)</code></pre>
|
||
<p>交叉项 $(b_0^2 - b_0) \cdot MN$ 被算出但丢弃——浪费比例 $(b_0-1)/b_0$。进入该分支的 case 必然访存 Bound(条件 5 保证),浪费的算力被搬移时延掩盖。</p>
|
||
<h3>3.2 进入条件</h3>
|
||
<p>同时满足($b_0$ = 最小合并数,取 2):</p>
|
||
<ol class="tight">
|
||
<li>$BatchA = BatchB$ 且 $b_{core} = B/C \ge 2b_0$</li>
|
||
<li>$2(b_0 M)(b_0 N) \cdot 4B \le L0C$(合并后输出放得下 L0C 双缓冲)</li>
|
||
<li>$b_{core}(MK+KN) \cdot dtype \ge min\_DatamountPerCore$(单核搬移量足够)</li>
|
||
<li>$\max(MK, KN) \cdot dtype \ge min\_TileSize$(搬移 tile 不太小)</li>
|
||
<li>$2MN/(M+N) < R_{16}/b_0$(合并后仍访存 Bound)</li>
|
||
</ol>
|
||
<h3>3.3 实现方案</h3>
|
||
<p><b>Step 1:合并数 b₀(L0C + 算存比双上限)</b></p>
|
||
<div class="math">$$
|
||
b_0 = \min\Big(\sqrt{\frac{L0C}{2 \cdot MN \cdot 4B}},\; \frac{R_{16}(M+N)}{2MN},\; b_{core}\Big)
|
||
$$</div>
|
||
<p>**Step 2:L0 级 K 粒度 $k_{L0}$(由 b₀ 和 L0A/L0B 决定)**</p>
|
||
<div class="math">$$
|
||
k_{L0} = \min\Big(\frac{L0A}{2 b_0 M \cdot dtype},\; \frac{L0B}{2 b_0 N \cdot dtype}\Big) \text{ 向下 16 对齐}
|
||
$$</div>
|
||
<p>**Step 3:L1 级 $k_{L1}$(先反推再截断)**</p>
|
||
<p>先令 $b_{L1}^* = b_0$,由 L1 容量反推 $k_{L1}^*$:</p>
|
||
<div class="math">$$
|
||
k_{L1}^* = \frac{L1}{2 \cdot b_0 \cdot (M+N) \cdot dtype}
|
||
$$</div>
|
||
<p>然后取 $k_{L1} = \min(k_{L1}^*,\; K,\; 512B/dtype)$——不超过 K(K 截断),也不超过 dValue 推荐值 512B。</p>
|
||
<p>**Step 4:$b_{L1}$ 最大化**</p>
|
||
<div class="math">$$
|
||
b_{L1} = \min\Big(\frac{L1}{2 \cdot k_{L1} \cdot (M+N) \cdot dtype},\; b_{core}\Big)
|
||
$$</div>
|
||
<p>若 $k_{L1}^* > 512B/dtype$,$k_{L1}$ 被 512B 截断,省出的 L1 空间可容纳更多 batch($b_{L1} > b_0$),提升 batch 间流水深度。</p>
|
||
<hr>
|
||
<h2>四、MergeBatch vs IterBatch 分界分析</h2>
|
||
<h3>4.1 核心差异:GM→L1 搬移粒度</h3>
|
||
<p>从 kernel 侧源码可直接观察:</p>
|
||
<ul class="tight">
|
||
<li><b>IterBatch</b>(<code>block_mmad_iterbatch.h</code> CopyInA1):<code>ndNum = curIterBatchL1</code>(多块独立寻址,<code>srcNdMatrixStride = M*K</code>)</li>
|
||
<li><b>MergeBatch</b>(<code>block_mmad_mergebatch.h</code> CopyInA1):<code>ndNum = 1</code>(合并为单块连续搬移,<code>nValue = b0 × M</code>)</li>
|
||
</ul>
|
||
<p>每次 GM→L1 DMA 搬移有固定开销 $T_{cmd}$(描述符配置 + 地址生成 + 突发启动),与搬移数据量无关。MergeBatch 把 $b_0$ 次搬移合并为 1 次,$T_{cmd}$ 节省 $b_0$ 倍。</p>
|
||
<h3>4.2 分情形分析</h3>
|
||
<p><b>K 截断情形</b>($k_{L1} = K$,整个 K 装入 L1 一块):</p>
|
||
<p>IterBatch 每 batch 一次 GM→L1 搬移;MergeBatch 每合并 batch 一次(覆盖 $b_0$ 个 batch),搬移次数少 $b_0$ 倍:</p>
|
||
<div class="math">$$
|
||
\Delta_K = \underbrace{(b_0-1)(T_{comp} + T_{write})}_{\text{drain 惩罚}} - \underbrace{b_{core}\Big(1-\frac{1}{b_0}\Big) T_{cmd}}_{\text{搬移命令节省}}
|
||
$$</div>
|
||
<p><b>L1 绑定情形</b>($k_{L1} < K$,K 需分块):</p>
|
||
<p>MergeBatch 合并后 tile 更大 → $k_{L1}^m = k_{L1}/b_0$ → $n_K^m = b_0 \cdot n_K$。搬移次数相同($b_{core} \cdot n_K$),每次搬移量也相同——<b>MergeBatch 无搬移优势</b>,只剩 drain 惩罚:</p>
|
||
<div class="math">$$
|
||
\Delta_L = (b_0-1)(T_{comp} + T_{write}) > 0
|
||
$$</div>
|
||
<p><b>L1 绑定情形 MergeBatch 恒劣于 IterBatch</b>。</p>
|
||
<h3>4.3 统一分界条件</h3>
|
||
<div class="math">$$
|
||
\text{MergeBatch 最优} \iff k_{L1} = K \;\land\; b_{core} > \frac{b_0 \cdot (T_{comp} + T_{write})}{T_{cmd}}
|
||
$$</div>
|
||
<p>即 MergeBatch 仅在 <b>K 截断</b>(小 M/N 使 $L1/(2(M+N) \cdot dtype) \ge K$)且 $b_{core}$ 足够大时才优于 IterBatch。</p>
|
||
<table><tr><th>维度</th><th>IterBatch</th><th>MergeBatch</th><th>差异</th></tr>
|
||
<tr><td>稳态搬移吞吐</td><td>相同</td><td>相同</td><td>总搬移量相同</td></tr>
|
||
<tr><td>GM→L1 搬移命令数</td><td>$b_{core}$ 次</td><td>$b_{core}/b_0$ 次</td><td>**MergeBatch 少 $b_0$ 倍** ← 核心优势</td></tr>
|
||
<tr><td>drain 暴露</td><td>$T_{comp} + T_{write}$</td><td>$b_0(T_{comp} + T_{write})$</td><td>IterBatch 少 $b_0$ 倍 ← 核心劣势</td></tr>
|
||
<tr><td>L0C 利用率</td><td>低</td><td>高</td><td>访存 Bound 下不影响时延</td></tr></table>
|
||
<h3>4.4 $T_{cmd}$ 的物理成因</h3>
|
||
<p>每次 GM→L1 DMA 搬移的固定开销包括:Nd2Nz 描述符配置(ndNum/nValue/dValue/srcStride 等 7 个字段写入 DMA 寄存器)、地址生成与突发启动、与 L1 buffer 的同步握手(<code>SetFlag<MTE2_MTE1></code> / <code>WaitFlag<MTE1_MTE2></code>)。量级估计为数十 ns,精确值需实测标定。</p>
|
||
<hr>
|
||
<h2>五、与源码实现的对比</h2>
|
||
<h3>5.1 MergeBatch 源码(<code>batch_matmul_v3_mergebatch_basicapi_tiling.cpp</code>)</h3>
|
||
<p><b>IsCapable 条件</b>:</p>
|
||
<pre><code>// 源码:https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3
|
||
if (batchC < MIN_BATCH_L0 * aicNum) return false; // B < 128,即 b_core < 4
|
||
if (alignK < 64 || M > N) return false; // K ≥ 64,M ≤ N
|
||
if (L0 buffer check with b0=4) return false; // L0C 容量</code></pre>
|
||
<p><b>对比分析</b>:</p>
|
||
<table><tr><th>维度</th><th>理论</th><th>源码</th><th>差异</th></tr>
|
||
<tr><td>合并数 b₀</td><td>min(L0C 上限, 算存比上限, b_core)</td><td>MIN_BATCH_L0 = 4(固定),实际由多项式动态求解</td><td>源码 b₀=4 是硬编码下限,实际值由 L0C 容量动态计算</td></tr>
|
||
<tr><td>b_core 下限</td><td>≥ 2b₀ = 4</td><td>≥ 4(batchC ≥ 128)</td><td>一致</td></tr>
|
||
<tr><td>L0C 约束</td><td>2(b₀M)(b₀N)·4B ≤ L0C</td><td>tempAlignM × tempAlignN × 4B × 2 ≤ L0C</td><td>一致</td></tr>
|
||
<tr><td>搬移量约束</td><td>b_core(MK+KN)·dtype ≥ 480KB</td><td><b>无</b></td><td>源码缺少</td></tr>
|
||
<tr><td>tile 大小约束</td><td>max(MK,KN)·dtype ≥ 16KB</td><td><b>无</b></td><td>源码缺少</td></tr>
|
||
<tr><td>算存比约束</td><td>2MN/(M+N) < R₁₆/b₀</td><td><b>无</b></td><td>源码缺少</td></tr>
|
||
<tr><td>额外约束</td><td>无</td><td>K ≥ 64,M ≤ N</td><td>源码独有</td></tr></table>
|
||
<p><b>结论</b>:源码的 MergeBatch 进入条件<b>偏宽</b>——缺少搬移量、tile 大小、算存比三条约束。可能导致不应进入的 case 被误捕获(如计算 Bound 的 case 进入 MergeBatch 后冗余计算成为瓶颈)。</p>
|
||
<h3>5.2 IterBatch 源码(<code>batch_matmul_v3_iterbatch_basicapi_tiling.cpp</code>)</h3>
|
||
<p><b>IsCapable 条件</b>:</p>
|
||
<pre><code>// 源码:https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3
|
||
if (batchC <= aicNum) return false; // B ≤ 32 不进 IterBatch
|
||
if ((alignM*alignK + alignK*alignN)*dtype*2 > L1) return false; // L1 双缓冲
|
||
if (!l0CanLoadBatch_ && balanceRate < 0.8) return false; // 负载均衡</code></pre>
|
||
<p><b>对比分析</b>:</p>
|
||
<table><tr><th>维度</th><th>理论</th><th>源码</th><th>差异</th></tr>
|
||
<tr><td>L1 形态</td><td>四种(a/b/c/d)</td><td>只覆盖形态 b</td><td>源码缺 a/c/d</td></tr>
|
||
<tr><td>B 下限</td><td>b_core ≥ 1</td><td>B > 32(b_core ≥ 2)</td><td>源码排除了 b_core=1 的形态 a</td></tr>
|
||
<tr><td>负载均衡</td><td>B mod C ≥ minCoreNum</td><td>balanceRate ≥ 0.8</td><td>实现方式不同,目的一致</td></tr></table>
|
||
<p><b>结论</b>:源码的 IterBatch <b>只覆盖形态 b</b>(双 batch 乒乓),且要求 B > C。形态 a/c/d 的 case 由 ASW/AL1/BL1 分支承接。</p>
|
||
<h3>5.3 综合评价</h3>
|
||
<p>理论分析的价值在于:</p>
|
||
<ol class="tight">
|
||
<li><b>分界条件</b>——源码无显式 MergeBatch vs IterBatch 分界,理论给出了 $k_{L1} = K \land b_{core} > b_0(T_{comp}+T_{write})/T_{cmd}$ 的闭式条件</li>
|
||
<li><b>L1 绑定情形</b>——理论证明 MergeBatch 在 L1 绑定时恒劣于 IterBatch,源码未区分</li>
|
||
<li><b>T_cmd 的物理意义</b>——GM→L1 DMA 命令固定开销,是 MergeBatch 优势的本质来源</li>
|
||
</ol>
|
||
<p>源码实现的价值在于:</p>
|
||
<ol class="tight">
|
||
<li><b>工程完备性</b>——负载均衡、格式检查、bias 处理等</li>
|
||
<li><b>cubeBound 模型</b>——经实测调优的 baseM/baseN 寻优</li>
|
||
<li><b>L2 Cache 管理</b>——100MB 阈值 + transConflict 控制</li>
|
||
</ol>
|
||
<hr>
|
||
<h2>参考文献</h2>
|
||
<ol class="tight">
|
||
<li>[昇腾 950 NPU 架构白皮书](https://public-download.obs.cn-east-2.myhuaweicloud.com/ascend/%E6%98%87%E8%85%BE950%20NPU%E6%9E%B6%E6%9E%84%E7%99%BD%E7%9A%AE%E4%B9%A6.pdf),华为技术有限公司,2026</li>
|
||
<li>[CANN AscendC 算子开发——Matmul 高阶 API 使能 UnitFlag](https://www.hiascend.com/document/detail/zh/canncommercial/850/opdevg/Ascendcopdevg/atlas_ascendc_best_practices_10_10003.html),CANN 8.5.0</li>
|
||
<li>[cann-ops-nn 源码仓](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3)</li>
|
||
</ol>
|
||
</div>
|
||
</body>
|
||
</html> |