Files
matmul-analysis/BMM/BMM算子优化分析_Release/BMM算子优化分析_v0.7_讲解PPT.html

263 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.0">
<title>BMM算子优化分析 — 讲解PPT</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:"PingFang SC","Microsoft YaHei",sans-serif;background:#0f172a;color:#e2e8f0;overflow:hidden}
.slide{display:none;width:100vw;height:100vh;padding:60px 80px;flex-direction:column;justify-content:center}
.slide.active{display:flex}
.slide h1{font-size:42px;color:#38bdf8;margin-bottom:16px}
.slide h2{font-size:32px;color:#818cf8;margin-bottom:24px}
.slide h3{font-size:24px;color:#c4b5fd;margin:20px 0 12px}
.slide p{font-size:20px;line-height:1.7;margin:8px 0}
.slide ul,.slide ol{font-size:19px;line-height:1.8;margin-left:28px}
.slide li{margin:6px 0}
.slide .tag{display:inline-block;background:#1e293b;border:1px solid #334155;border-radius:6px;padding:2px 10px;font-size:16px;margin:3px;color:#94a3b8}
.slide .eq{font-family:monospace;font-size:18px;color:#fbbf24;background:#1e293b;padding:4px 10px;border-radius:6px}
.slide table{font-size:17px;border-collapse:collapse;margin:12px 0;width:100%}
.slide th,.slide td{border:1px solid #334155;padding:8px 14px;text-align:left}
.slide th{background:#1e293b;color:#818cf8}
.slide td{background:#0f172a}
.slide .hl{color:#fbbf24}
.slide .ct{color:#38bdf8}
.slide .sub{color:#94a3b8;font-size:16px}
.slide .note{color:#f87171;font-size:16px;margin-top:16px}
.nav{position:fixed;bottom:24px;right:32px;display:flex;gap:12px;z-index:100}
.nav button{background:#1e293b;color:#e2e8f0;border:1px solid #334155;border-radius:8px;padding:10px 20px;font-size:16px;cursor:pointer}
.nav button:hover{background:#334155}
.nav .pg{font-size:15px;color:#94a3b8;align-self:center;min-width:60px}
.code{font-family:monospace;font-size:16px;background:#1e293b;padding:12px 18px;border-radius:8px;margin:10px 0;white-space:pre;line-height:1.5;color:#e2e8f0}
.dot{color:#38bdf8;font-weight:bold}
</style>
</head>
<body>
<div class="nav">
<span class="pg" id="pg">1/14</span>
<button onclick="go(-1)"></button>
<button onclick="go(1)"></button>
</div>
<!-- Slide 1: Title -->
<div class="slide active" id="s1">
<h1>BMM 算子最优实现分析</h1>
<p style="font-size:28px;color:#c4b5fd">昇腾 950PR — BatchMatMulV3</p>
<p class="sub">v0.7|基于 CANN 源码 + 950PR 白皮书 + 架构规格的系统推导</p>
<p class="sub" style="margin-top:30px">这份分析靠谱吗?→ 最后一页有验证</p>
</div>
<!-- Slide 2: 问题引入 -->
<div class="slide" id="s2">
<h2>1. 什么是 BMM 算子?</h2>
<p>Batch MatMul对 batch 维的每个索引独立做一次矩阵乘</p>
<div class="eq">C[b, m, n] = Σₖ A[b, m, k] · B[b, k, n] + bias</div>
<p style="margin-top:16px">输入:左矩阵 <span class="hl">[BatchA, M, K]</span>、右矩阵 <span class="hl">[BatchB, K, N]</span></p>
<p>输出:<span class="hl">[BatchC, M, N]</span>batch 维支持广播</p>
<p style="margin-top:20px"><b>核心挑战</b>B、M、N、K 四个维度都可能很大或很小,必须在<span class="hl">32 个 AIC 核</span>上高效并行完成计算</p>
<p class="sub">一个 case 由 (B, M, N, K, dtype, 转置, 广播形态) 完全决定</p>
</div>
<!-- Slide 3: 硬件背景 -->
<div class="slide" id="s3">
<h2>2. 硬件基础:昇腾 950PR</h2>
<table>
<tr><th>规格</th><th>数值</th><th>对 tiling 的意义</th></tr>
<tr><td>AIC / AIV 核数</td><td class="hl">32 / 641:2</td><td>核间并行度上限 C=32</td></tr>
<tr><td>Cube 算力 BF16</td><td class="hl">486 TFLOPS</td><td>算存比分子</td></tr>
<tr><td>GM 带宽</td><td class="hl">1.6 TB/s读写共享</td><td>访存 Bound 分母</td></tr>
<tr><td>L2 Cache</td><td class="hl">128MB / 5.2 TB/s</td><td>重复读取的吸收层</td></tr>
<tr><td>L1 / L0A / L0B / L0C</td><td class="hl">512KB / 64KB / 64KB / 256KB</td><td>核内分块的容量约束</td></tr>
</table>
<p style="margin-top:16px"><b>关键关系</b>L25.2TB/s→ L1512KB→ L0A/B64KB→ Cube16×16×16 一拍)→ L0C256KB→ Fixpipe → GM</p>
<p class="sub">GM 带宽 1.6TB/s 相对 L2 的 5.2TB/s 是瓶颈——数据复用就是一切</p>
</div>
<!-- Slide 4: 性能模型 -->
<div class="slide" id="s4">
<h2>3. 什么叫"最优"</h2>
<div class="eq">T_total = max(T_MMAD, T_MTE2, T_MTE1, T_Fixpipe [, T_Reduce])</div>
<p style="margin-top:16px"><b>总时延 = 流水线最慢的一级</b></p>
<p>核内多级流水并行Cube 计算MMAD、GM→L1 搬移MTE2、L1→L0 搬移MTE1、L0C 写出Fixpipe</p>
<p style="margin-top:16px"><b class="hl">瓶颈交换</b>:搬移是瓶颈时,可牺牲算力换搬移效率;计算是瓶颈时,可牺牲搬移换计算效率</p>
<p style="margin-top:12px">算存比:<span class="eq">AI = 2MN/(M+N)</span> FLOP/元素</p>
<p>16bit 平衡点:<span class="eq">R₁₆ = 486 TFLOPS / (1.6TB/s ÷ 2B) ≈ 607.5</span> FLOP/元素</p>
<p>AI < R 访存 Bound瓶颈在搬移AI > R₁₆ → 计算 Bound瓶颈在 Cube</p>
</div>
<!-- Slide 5: 分块本质 -->
<div class="slide" id="s5">
<h2>4. 分块计算的本质4 维可切</h2>
<p>BMM 的实现 = 把数据按 B、M、N、K 切块,由 32 个 AIC 核并行 + 串行完成</p>
<p style="margin-top:16px"><b class="hl">核间怎么分这 4 个维度,就是分支划分的第一性问题</b></p>
<table>
<tr><th>切分维度</th><th>读入特征</th><th>计算特征</th><th>写出特征</th></tr>
<tr><td><span class="hl">切 B</span></td><td>核间零重复读</td><td>无核间依赖</td><td>无中间结果</td></tr>
<tr><td><span class="ct">切 M / 切 N</span></td><td>共享矩阵被多核重复读</td><td>无核间依赖</td><td>无中间结果</td></tr>
<tr><td><span class="note">切 K</span></td><td>零重复读</td><td>多核共同完成,有依赖</td><td>需核间归约</td></tr>
</table>
<p style="margin-top:16px">4 维任意非空子集共 <span class="hl">15 种</span>切分组合 → 完备枚举</p>
<p class="sub">为什么切 K 最贵L0C 累加机制:核内切 K 时多轮 mmad 在 L0C 原地累加不出核;切到核间,部分和必须写出 workspace 再归约</p>
</div>
<!-- Slide 6: 价格表 -->
<div class="slide" id="s6">
<h2>5. 切分维度的"价格表"</h2>
<div class="eq">cost(切 B) = 0 < cost( M/N) cost( K)</div>
<p style="margin-top:20px"><b class="hl">切 B 免费</b>零重复读、零依赖、零中间写出——BMM 语义就是逐 batch 独立</p>
<p><b class="ct">切 M/N 廉价</b>:共享矩阵被重复读,但 128MB L25.2TB/s吸收大部分代价</p>
<p><b class="note">切 K 昂贵</b>:归约流量 ∝ 切 K 份数 × 输出量,且引入核间同步——结构性代价</p>
<p style="margin-top:24px"><b>整条分支决策树就是一句话:</b></p>
<p style="font-size:26px;color:#fbbf24;text-align:center">按价格从低到高购买并行度,买不够才加价</p>
</div>
<!-- Slide 7: 分支推导 -->
<div class="slide" id="s7">
<h2>6. 从价格表到 7 大分支</h2>
<div class="code">第0层K=0/1 → AIV 向量通路Cube 无用)
BatchA=1 或 BatchB=1 → 折叠转普通 Matmul
第1层B ≥ 32切 B 可满核)
M×N 大 → IterBatch逐 batch 算)
M×N 小 → MergeBatch多 batch 合并成大 tile 算)
第2层B < 32P = B·MN·4B/L0C 32
ASW_Basic M/N 补并行重复读交 L2+swizzle 吸收
第3层P < 32B/M/N 都填不满核
K StreamK K付归约代价
K 降核 ASW只用 P 个核其余闲置</div>
<p style="margin-top:12px">7 大路径:<span class="tag">特殊(AIV)</span><span class="tag">转Matmul</span><span class="tag">IterBatch</span><span class="tag">MergeBatch</span><span class="tag">ASW_Basic</span><span class="tag">StreamK</span><span class="tag">降核ASW</span></p>
</div>
<!-- Slide 8: MergeBatch -->
<div class="slide" id="s8">
<h2>7. MergeBatch — 多 batch 合并计算</h2>
<p><b>思路</b>:单 batch M×N 太小时,把 b 个 batch 拼成 [bM,K]@[K,bN] 大矩阵,</p>
<p>算完取块对角线得各 batch 结果。交叉项被丢弃,浪费比例 (b1)/b</p>
<p style="margin-top:12px"><b class="hl">为什么允许浪费?</b> 进这个分支的 case 必然是访存 Bound瓶颈在搬移</p>
<p>不是在计算——浪费的算力被搬移时延掩盖,用闲置算力换搬移效率</p>
<p style="margin-top:16px"><b>进入条件精要</b></p>
<ul>
<li>每核 batch 数 ≥ 42 组 × 合并下限 b₀=2组间乒乓</li>
<li>合并块输出 ≤ L0Cb₀²·MN·4B·2 ≤ 256KB</li>
<li>单核搬移总量 ≥ 480KB保证 GM 带宽利用率)</li>
<li>搬移 tile ≥ 16KB单次搬移效率下限</li>
<li>2MN/(M+N) &lt; R₁₆/b₀访存 Bound保证浪费被掩盖</li>
</ul>
<p class="sub">为何不允许 b_core=2合并后每核仅 1 组,与 IterBatch(b_core=2) 总搬移时延相同,但多 50% 冗余计算——无收益</p>
<p class="sub">典型B=128, M=N=64, K=512 → MergeBatchK=256 → 单核搬移不够 480KB → IterBatch</p>
</div>
<!-- Slide 9: IterBatch -->
<div class="slide" id="s9">
<h2>8. IterBatch — 逐 batch 计算</h2>
<p><b>思路</b>:核间切 B核内逐个 batch 做标准 Matmul。无浪费、无跨 batch 依赖</p>
<p style="margin-top:12px"><b>进入条件精要</b>B ≥ 32负载均衡L1 四形态之一满足</p>
<p style="margin-top:10px"><b class="hl">关键:能否进 IterBatch 不由算存比判定</b></p>
<p>即使计算 BoundL1 放不下完整 M、N 维输入 → 单 batch 内部重复读 → 额外搬移可能把算子重新拖回访存 Bound</p>
<p style="margin-top:10px"><b>L1 四形态</b></p>
<ul>
<li>a) 单 batch 全驻留b_core=1 且 MK+KN ≤ L1</li>
<li>b) 双 batch 乒乓b_core>1 且 2(MK+KN) ≤ L1</li>
<li>c) 一侧驻留+对侧切 K预算 L1 或 L1/2b_core≥2 时半区预取下一 batch 驻留侧)</li>
<li>d) 两侧切 K无缝兜底驻留侧超 L1/2 时接管)</li>
</ul>
</div>
<!-- Slide 10: StreamK -->
<div class="slide" id="s10">
<h2>9. StreamK — K 维核间切分</h2>
<p><b>思路</b>B/M/N 都填不满核时,把 K 切给多个核各算一段,再归约</p>
<p style="margin-top:12px"><b>进入条件</b>P = B·MN·4B/L0C &lt; 16=C/2K/grid_K ≥ 128 元素dValue 256B</p>
<p class="sub">阈值取 C/2grid_K≥2 时每块需 2 核P×2 ≤ C 才放得下</p>
<p><b>归约代价</b>(按实现流程推导):</p>
<div class="code">第1步grid_K 个 AIC 各算 K/grid_K 段L0C 原地累加
第2步fixpipe 写部分和到 workspace驻留 L2走 5.2TB/s 写口)
第3步AIV 从 L2 读回各段部分和到 UB向量求和写回
AIV 独立硬件,数据流 GM/L2→UB→AIV→UB→L2/GM</div>
<p style="margin-top:10px">归约时延 = 写部分和 + AIV 读回 + AIV 求和 + 写回,四段之和</p>
<p>收益判据T_Reduce &lt; T_pipe(11/grid_K)T_pipe = max(T_MTE2, T_MMAD)</p>
<p>⟺ T_pipe &gt; grid_K/(grid_K1)·T_Reduceα=grid_K/(grid_K1)grid_K=2 时 α=2</p>
<p class="sub">StreamK case 多为访存 BoundAI &lt; R₁₆T_pipe = T_MTE2 是瓶颈</p>
<p style="margin-top:8px"><span class="eq">K &gt; grid_K²/(grid_K1) × 12</span> grid_K=2→K>494→K>668→K>112</p>
<p class="sub">源码 8192 = C×512B = dValue 推荐值在最大 grid_K=C 下的保障条件2非归约代价条件3阈值仅 ~50~400</p>
</div>
<!-- Slide 11: ASW 1 -->
<div class="slide" id="s11">
<h2>10. ASW_Basic — 通用框架swizzle</h2>
<p><b>思路</b>:不切 K切 B/M/N 任意组合。最常命中的分支35.2%</p>
<p style="margin-top:12px"><b class="hl">swizzle 滑窗蛇形</b>:编排输出块的执行顺序,压缩每一波核的活跃工作集</p>
<p><b>窗口怎么取</b>M 向每 W 块划窗W = C 的 ≤ √C 的最大因子32 核 → W=4</p>
<p>一波核的 L2 足迹 ≈ (W·M^t·K + C/W·K·N^t)·dtypeW=√C 时最小</p>
<p><b>蛇形只在窗口行边界</b>:奇数窗口行 N 方向反向,使上一窗口末尾的 B 列带延续到下一窗口LRU 热线),跨窗切换几乎零增量。窗内不蛇形——窗内 W 个 A 行块全程驻留 L2扫序无关。</p>
<p style="margin-top:10px"><b>对比</b>:朴素行优先(Ñ=16 时)一波足迹 18 单位;滑窗 12 单位 → 缩小 1/3</p>
</div>
<!-- Slide 12: ASW 2 -->
<div class="slide" id="s12">
<h2>11. ASW_Basic — L2 切分 & 写出策略</h2>
<p><b>切的是输出平面</b>:把 M×N 切成大矩形块,每块的输入工作集 ≤ L2 可用空间</p>
<p style="margin-top:12px"><b class="hl">L2 是读写共用的</b>:输出驻留 L2 会压缩读入空间 → 写与读要联合决策</p>
<p style="margin-top:8px"><b>写出 Bound 先判定</b>BW_out = C·Q₁₆·outB/(2K)K=256 时已超 GM 总线</p>
<p style="margin-top:8px"><b>三场景</b></p>
<ul>
<li><b>A</b>S_in+S_out ≤ L2 → 全驻留,输出走 L2 异步回写(削峰)</li>
<li><b>B</b>S_in ≤ L2 < S_in+S_out 输入驻留保住 r_in=1输出直写 GM不占 L2</li>
<li><b>C</b>S_in > L2 → 必须切分,输出直写 GM块内错位分核对角线同地址冲突 4→2</li>
</ul>
<p class="sub">r_in = 1 含义:每个输入从 GM 只读一遍,后续复用全在 L2 命中</p>
</div>
<!-- Slide 13: 遍历验证 -->
<div class="slide" id="s13">
<h2>12. case 遍历验证20736 个 case 全覆盖</h2>
<p>B∈[1,2048]、M,N,K∈[1,10240] 对数采样,按进入条件严格分类</p>
<table>
<tr><th>分支</th><th>占比</th><th>B 范围</th></tr>
<tr><td>ASW_Basic</td><td class="hl">35.2%</td><td>2~2048</td></tr>
<tr><td>IterBatch</td><td>21.9%</td><td>32~2048</td></tr>
<tr><td>降核 ASW</td><td>15.4%</td><td>2~128</td></tr>
<tr><td>特殊(AIV)</td><td>8.3%</td><td>任意</td></tr>
<tr><td>MergeBatch</td><td>8.1%</td><td>128~2048</td></tr>
<tr><td>转Matmul</td><td>7.6%</td><td>B=1</td></tr>
<tr><td>StreamK</td><td>3.5%</td><td>2~128</td></tr>
</table>
<p style="margin-top:12px"><b class="hl">7 个分支全部有真实 case 命中,无覆盖空洞</b></p>
<p class="sub">降核 ASW 是 P&lt;C 且 K 小区域的理性归宿:并行度凑不满,碎切反而不如少用核</p>
</div>
<!-- Slide 14: 靠谱吗? -->
<div class="slide" id="s14">
<h2>这份分析靠谱吗?</h2>
<h3>✅ 根基扎实</h3>
<ul>
<li><b>源码级验证</b>:分支条件与 CANN ops-nn batch_mat_mul_v3 源码逐条对照arch35 路径MergeBatch/IterBatch/StreamK/ASW 的 IsCapable 条件均能在源码中找到对应,差异处已标注并给出理由</li>
<li><b>硬件规格准确</b>950PR 架构白皮书表3-1/表4-2+ CANN 9.0 AscendC 编程文档054/056/192篇等为所有参数L1 512KB、L0C 256KB、dValue 256B、GM 1.6TB/s提供依据</li>
<li><b>数学推导闭环</b>15 种切分组合完备枚举 → 价格表(免费/廉价/昂贵)→ 7 分支体系,极小性有反例表格验证</li>
</ul>
<h3>⚠️ 需要注意</h3>
<ul>
<li><b>经验常数</b>min_TileSize=16KB、480KB、均衡率 0.8、CUBE_BOUND_RATIO 0.85 等)为该芯片档实测值,换芯片需重新标定</li>
<li><b>重叠区</b>MergeBatch vs IterBatch 在 M×N 中段)的归属由端到端时延模型 $T_{total}$ 精确仲裁,文档条件给出的是"主场"</li>
<li><b>StreamK θ≈12</b> 基于 L2 5.2TB/s 推导,若 workspace 落 GM 则升至 ~97源码 8192 门槛由 dValue 推荐值主导</li>
</ul>
<p style="margin-top:16px;font-size:24px;text-align:center;color:#38bdf8"><b>整体评价:逻辑体系完整,推导自洽,可指导工程实现</b></p>
</div>
<script>
let cur = 0;
const slides = document.querySelectorAll('.slide');
const N = slides.length;
function go(d) {
slides[cur].classList.remove('active');
cur = ((cur + d) % N + N) % N;
slides[cur].classList.add('active');
document.getElementById('pg').textContent = `${cur+1}/${N}`;
}
document.addEventListener('keydown', e => { if (e.key === 'ArrowRight') go(1); if (e.key === 'ArrowLeft') go(-1); });
</script>
</body>
</html>