670 lines
74 KiB
HTML
670 lines
74 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>ASW_Basic 分支理论最优实现分析 v1.5</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>ASW_Basic 分支:BMM 兜底分支的理论最优实现分析</h1>
|
||
<blockquote>目标芯片:昇腾 950PR(DAV_3510)。本文为 ASW_Basic 分支的独立分析(v1.5 扩充 §6 源码对比:cubeBound 模型完整解析与优劣判定),自包含完整推导链。v1.5 修正 dValue 定义与尾轮重切约束,新增与源码实现的对比分析。</blockquote>
|
||
<h2>摘要</h2>
|
||
<p>ASW_Basic 是 BMM 的兜底分支——核间切 M/N(或混合切),不做 batch 合并或 K 维切分。本文给出完整的时延建模、核间分配策略分析(证明 B 优先分组在任何场景下都不优于线性映射)、实现方案的逐步推导(含尾轮重切的 Bound 类型分级策略),以及与源码实现的逐维度对比。</p>
|
||
<p>v1.5 扩充 §6 源码对比:cubeBound 模型三项的物理推导(工作集超 L2 倾向更小 tile、K 大倾向更大 tile)、<b>源码 singleCore = base 不分层导致过度切分</b>(K 小时搬入时延可达理论 2 倍)、尾轮不实际重切。核心结论:<b>B 优先分组不优于线性映射;理论的最少切分原则 + SingleCore/Base 分层 + 尾轮重切是相对源码的三条实质改进;源码的 cubeBound 解析模型与平台自适应值得理论吸收。</b></p>
|
||
<hr>
|
||
<h2>一、问题定义与执行模型</h2>
|
||
<h3>1.1 分支定位</h3>
|
||
<p>BMM 中,当 B ≥ C(batch 数 ≥ AIC 核数)时核间切 B 是免费的(每核独立处理若干 batch,无共享无依赖),由 IterBatch/MergeBatch 承接。当 B < C 或 B ≥ C 但 IterBatch/MergeBatch 条件不满足时,需要切 M/N 来填满所有核——这就是 ASW_Basic。</p>
|
||
<p>ASW_Basic 是实践中最常命中的分支:B 可大可小可等 1,交叉广播也由此承接(对广播侧做 L1/L2 驻留,共享关系与切 M/N 同构)。</p>
|
||
<h3>1.2 执行模型</h3>
|
||
<pre><code>核间:B × mCnt × nCnt 个输出块,按 B→M→N 线性映射分配到 C 核
|
||
核内:每核处理若干 [singleCoreM, singleCoreN] 输出块
|
||
└── 每块内部:GM→L1→L0→Cube→L0C→Fixpipe 标准流水
|
||
└── K 维不切(singleCoreK = K),按 kL1 分块搬入 L1</code></pre>
|
||
<p>数据流:</p>
|
||
<pre><code>GM ──MTE2──> L1 ──MTE1──> L0A/L0B ──MMAD──> L0C ──Fixpipe──> GM
|
||
↑_____________ L2 Cache(读 5.2TB/s)_____________↑</code></pre>
|
||
<h3>1.3 符号定义</h3>
|
||
<table><tr><th>符号</th><th>含义</th><th>表达式/取值</th></tr>
|
||
<tr><td>$B$</td><td>batch 数</td><td>输入参数</td></tr>
|
||
<tr><td>$M, N, K$</td><td>矩阵维度</td><td>输入参数</td></tr>
|
||
<tr><td>$C$</td><td>AIC 核数</td><td>32</td></tr>
|
||
<tr><td>$dtype$</td><td>输入元素字节数</td><td>BF16 → 2B</td></tr>
|
||
<tr><td>$outB$</td><td>输出元素字节数</td><td>BF16 → 2B</td></tr>
|
||
<tr><td>$L0C$</td><td>L0C 容量/核</td><td>256KB</td></tr>
|
||
<tr><td>$L0A, L0B$</td><td>L0A/L0B 容量/核</td><td>各 64KB</td></tr>
|
||
<tr><td>$L1$</td><td>L1 容量/核</td><td>512KB</td></tr>
|
||
<tr><td>$L2$</td><td>L2 容量(共享)</td><td>128MB</td></tr>
|
||
<tr><td>$BW_{pc}$</td><td>单核 GM 带宽份额</td><td>$W_{GM}/C = 50$ GB/s</td></tr>
|
||
<tr><td>$Q_{16}$</td><td>单核 Cube BF16 峰值算力</td><td>486/32 ≈ 15.2 TFLOPS</td></tr>
|
||
<tr><td>$W_{GM}$</td><td>GM 带宽</td><td>1.6 TB/s</td></tr>
|
||
<tr><td>$BW_{L2}$</td><td>L2 读带宽</td><td>5.2 TB/s</td></tr></table>
|
||
<p>*Tiling 参数*:</p>
|
||
<table><tr><th>符号</th><th>含义</th><th>约束层级</th></tr>
|
||
<tr><td>$\text{BaseM}, \text{BaseN}$</td><td>L0 级 tile 的 M/N 维度</td><td>L0C 容量直接约束</td></tr>
|
||
<tr><td>$baseK$</td><td>L0 级 tile 的 K 维度</td><td>L0A/L0B 容量约束</td></tr>
|
||
<tr><td>$\text{singleCoreM}, \text{singleCoreN}$</td><td>每核输出 tile 的 M/N 维度</td><td>L1 容量 + 并行度 + 搬移效率</td></tr>
|
||
<tr><td>$k_{L1}$</td><td>GM→L1 的 K 向粒度</td><td>dValue ≥ 256B</td></tr>
|
||
<tr><td>$mCnt, nCnt$</td><td>单 batch 内 M/N 向块数</td><td>$\lceil M/\text{singleCoreM} \rceil$</td></tr>
|
||
<tr><td>$W$</td><td>swizzle 窗口宽度</td><td>$\max\{d : d \mid C, d \le \lfloor\sqrt{C}\rfloor\}$</td></tr></table>
|
||
<p>层次关系:$K = \text{singleCoreK} \ge k_{L1} \ge baseK$,$\text{singleCoreM} \ge \text{BaseM}$,$\text{singleCoreN} \ge \text{BaseN}$。</p>
|
||
<p><b>搬移效率:dValue 与 min_TileSize</b></p>
|
||
<p>GM→L1 搬移使用 Nd2Nz DMA,每次搬移的关键参数:</p>
|
||
<table><tr><th>参数</th><th>含义</th><th>A 矩阵 [singleCoreM, $k_{L1}$]</th><th>B 矩阵 [$k_{L1}$, singleCoreN]</th></tr>
|
||
<tr><td>nValue</td><td>行数(非连续维)</td><td>singleCoreM</td><td>$k_{L1}$</td></tr>
|
||
<tr><td>dValue</td><td>每行连续字节数(连续维)</td><td>$k_{L1} \cdot dtype$</td><td>$\text{singleCoreN} \cdot dtype$</td></tr>
|
||
<tr><td>总搬移量</td><td>nValue × dValue</td><td>$\text{singleCoreM} \cdot k_{L1} \cdot dtype$</td><td>$k_{L1} \cdot \text{singleCoreN} \cdot dtype$</td></tr></table>
|
||
<p><b>dValue 是 ND 排布中连续维的字节数</b>——对 ND 格式的右矩阵 B:非转置时连续维为 N,dValue = $\text{singleCoreN} \cdot dtype$;转置时连续维为 K,dValue = $k_{L1} \cdot dtype$。<b>dValue 不是两个维度的乘积</b>。</p>
|
||
<p>两级搬移效率阈值:</p>
|
||
<ol class="tight">
|
||
<li><b>dValue ≥ 256B</b>(DMA 硬件突发下限):每行连续数据量不足 256B 时,DMA 突发效率急剧下降;</li>
|
||
<li><b>总搬移量 ≥ min_TileSize</b>(推荐 16KB):单次搬移量太小则带宽利用率不足([昇腾 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)推荐 dValue 256B/512B 对齐)。</li>
|
||
</ol>
|
||
<hr>
|
||
<h2>二、进入条件</h2>
|
||
<p>同时满足:</p>
|
||
<ol class="tight">
|
||
<li>$P = \dfrac{B \cdot MN \cdot 4\text{B}}{L0C} \ge C$(并行度补齐)</li>
|
||
<li>无 batch 结构限制(BatchA=BatchB、交叉广播均可);典型进入路径:$B < C$(切 B 买不满核),或 $B \ge C$ 但 IterBatch/MergeBatch 条件不满足时的兜底</li>
|
||
<li><b>降核模式</b>:$P < C$ 且不满足 StreamK 进入条件 → 只用 $\lceil P \rceil$ 个核,其余核闲置</li>
|
||
</ol>
|
||
<p>逐条解释:</p>
|
||
<ol class="tight">
|
||
<li><b>并行度补齐</b>:以 L0C 满载为基本块粒度,B×M×N 能切出至少 C 个独立输出块,则切 M/N(或混合切)并行度够用。切 M/N 的固有代价是共享矩阵被多核重复读,但共享部分驻留 128MB L2 时重复读以 5.2TB/s 命中 L2 而非 1.6TB/s 的 GM,代价大部分被吸收。</li>
|
||
<li><b>兜底性质</b>:ASW_Basic 是实践中最常命中的分支——B 可大可小可等 1,交叉广播也由此承接(对广播侧做 L1/L2 驻留,共享关系与切 M/N 同构)。</li>
|
||
<li><b>降核模式</b>:P < C 且 K 也不够格走 StreamK 时,并行度凑不满核。此时与其强行把 M/N 切得更碎(tile 跌破 min_TileSize、dValue 跌破 256B,搬移效率崩塌,反而更慢),不如<b>只用 ⌈P⌉ 个核</b>、每核承担一个完整输出块(L0C 满载粒度),其余核闲置。这类 case 的时延绝对值小,继续切分引入的调度与搬移效率损失大于并行收益——降核是理性选择而非偷懒。</li>
|
||
</ol>
|
||
<hr>
|
||
<h2>三、时延建模</h2>
|
||
<h3>3.1 端到端时延</h3>
|
||
<div class="math">$$
|
||
T = \max(T_{MTE2},\; T_{MMAD},\; T_{FIX}) + T_{drain}
|
||
$$</div>
|
||
<table><tr><th>项</th><th>含义</th><th>公式</th></tr>
|
||
<tr><td>$T_{MTE2}$</td><td>GM→L1 搬移时延</td><td>$r_{in} \cdot S_{in} / (C \cdot BW_{pc})$</td></tr>
|
||
<tr><td>$T_{MMAD}$</td><td>Cube 计算时延</td><td>$2BMNK / (C \cdot Q_{16})$</td></tr>
|
||
<tr><td>$T_{FIX}$</td><td>输出写回时延</td><td>$B \cdot MN \cdot outB / (C \cdot W_{pc})$</td></tr>
|
||
<tr><td>$T_{drain}$</td><td>末块排空时延</td><td>$O(T_{comp} + T_{write})$</td></tr></table>
|
||
<p>其中 $S_{in} = B(MK + KN) \cdot dtype$ 为输入总量,$r_{in} \ge 1$ 为重复读倍率(GM 输入流量 / 输入总量)。$r_{in} = 1$ 表示每字节只从 GM 读一次(后续复用全命中 L2)——<b>这是 GM 输入流量的下界</b>。</p>
|
||
<p><b>关键观察</b>:$T_{MMAD}$ 和 $T_{FIX}$ 与核间分配策略无关——**分配策略只影响 $T_{MTE2}$**(通过 L2 命中率影响 $r_{in}$)。优化目标:让 $r_{in}$ 尽量接近 1。</p>
|
||
<h3>3.2 并行度约束的正确形式</h3>
|
||
<p>总输出块数 $= B \cdot mCnt \cdot nCnt$。并行度约束的正确形式是:</p>
|
||
<div class="math">$$
|
||
B \cdot mCnt \cdot nCnt \ge C
|
||
$$</div>
|
||
<p>即总块数至少能填满 C 核。这与 $mCnt \cdot nCnt \ge \lceil C/B \rceil$ 等价(两边同乘 B 后取整)。</p>
|
||
<p>**不应要求 $B \cdot mCnt \cdot nCnt \;\%\; C = 0$**(整除)。整除是充分不必要条件——不整除时产生尾轮,尾轮的处理见 §五。</p>
|
||
<hr>
|
||
<h2>四、核间分配策略分析</h2>
|
||
<h3>4.1 两种候选策略</h3>
|
||
<ul class="tight">
|
||
<li><b>B 优先分组</b>:C 核分为 B 组,每组 $C_g = \lfloor C/B \rfloor$ 或 $\lceil C/B \rceil$ 核独立处理一个 batch 的 $mCnt \times nCnt$ 个块,组内做 swizzle</li>
|
||
<li><b>线性映射</b>:块按 (b, m, n) 字典序编号(block 0 = (0,0,0),block 1 = (0,0,1),…,block $mCnt \cdot nCnt$ = (1,0,0),…),核 i 依次处理块 i, i+C, i+2C, …</li>
|
||
</ul>
|
||
<h3>4.2 L2 工作集对比</h3>
|
||
<p>每波活跃核所需的 A 行带 + B 列带:</p>
|
||
<div class="math">$$
|
||
WS_{wave} = \big(W \cdot sM + \tfrac{C}{W} \cdot sN\big) \cdot K \cdot dtype
|
||
$$</div>
|
||
<p>其中 $sM$ = singleCoreM,$sN$ = singleCoreN,$W$ 为 swizzle 窗口宽度。</p>
|
||
<table><tr><th>维度</th><th>B 优先分组</th><th>B→M→N 线性映射</th></tr>
|
||
<tr><td>每波活跃 batch 数</td><td>B 个(每组一个)</td><td>1 个($mCnt \cdot nCnt \ge C$ 时)</td></tr>
|
||
<tr><td>每组/每波 swizzle 窗口</td><td>$W_g = \max\{d \mid d \mid C_g,\; d \le \sqrt{C_g}\}$</td><td>$W = \max\{d \mid d \mid C,\; d \le \sqrt{C}\}$</td></tr>
|
||
<tr><td>每波 L2 工作集</td><td>$B \cdot (W_g \cdot sM + \frac{C_g}{W_g} \cdot sN) \cdot K \cdot dtype$</td><td>$(W \cdot sM + \frac{C}{W} \cdot sN) \cdot K \cdot dtype$</td></tr>
|
||
<tr><td>C 不整除 B</td><td>组间核数不等 → 负载不均</td><td>无影响(总块数任意)</td></tr></table>
|
||
<p><b>数值例</b>(C=32、B=4、sM=sN=256、K=1024、BF16):分组 $C_g=8$、$W_g=2$(8 的最大因子 ≤ √8≈2.83),总工作集 = $4 \times (2 \times 256 + 4 \times 256) \times 1024 \times 2 = 12$ MB;线性 $W=4$,工作集 = $(4 \times 256 + 8 \times 256) \times 1024 \times 2 = 6$ MB——<b>线性映射的工作集是分组方案的一半</b>。</p>
|
||
<p><b>为什么线性映射工作集更小</b>:</p>
|
||
<ol class="tight">
|
||
<li><b>更多核参与同一 batch 的 swizzle</b> → $W = \sqrt{C} > W_g = \sqrt{C/B}$ → 工作集更接近"方形"下限(均值不等式:$W + C/W$ 在 $W = \sqrt{C}$ 处最小);</li>
|
||
<li><b>同一时刻只有 1 个 batch 的数据活跃</b> → L2 只需缓存 1 份 A/B 数据,而非 B 份。</li>
|
||
</ol>
|
||
<h3>4.3 分场景严格比较</h3>
|
||
<p>**场景 I:$mCnt \cdot nCnt \ge C$ 且单波工作集 ≤ L2**(最常见)</p>
|
||
<p>线性映射:每波 C 个块在同一 batch 内,L2 命中率高。B 分组:B 个 batch 同时活跃,总工作集 $B$ 倍。<b>线性严格更优。</b></p>
|
||
<p>**场景 II:$S_{in} \le L2$(全部输入放得下 L2)**</p>
|
||
<p>两种策略的 GM 流量相同($r_{in} = 1$,每字节只读一次)。但 B 分组在 C % B ≠ 0 时负载不均(组间核数不等)。<b>线性不劣于分组。</b></p>
|
||
<p>**场景 III:$mCnt \cdot nCnt < C$(单 batch 块数不够填满所有核)**</p>
|
||
<p>线性映射:每波横跨 $\lceil C/(mCnt \cdot nCnt) \rceil$ 个 batch——这些 batch 的数据同时活跃。B 分组:每组 $C_g$ 核但只有 $mCnt \cdot nCnt < C_g$ 个块 → <b>组内有空闲核</b>。线性映射至少所有核都有活干。<b>线性严格更优。</b></p>
|
||
<p>**场景 IV:$mCnt \cdot nCnt < C/B$(每组连自己的核都填不满)**</p>
|
||
<p>B 分组每组空闲 $C_g - mCnt \cdot nCnt$ 个核,总算力浪费 $C - B \cdot mCnt \cdot nCnt$ 个核。线性映射无此问题。<b>线性严格更优。</b></p>
|
||
<h3>4.4 结论</h3>
|
||
<p><b>B 优先分组在任何场景下都不优于线性映射。</b> 根本原因:</p>
|
||
<ol class="tight">
|
||
<li>L2 是共享 Cache,工作集越小命中率越高——线性映射同一时刻只激活 1 个 batch 的数据,工作集最小;</li>
|
||
<li>swizzle 窗口 $W \propto \sqrt{\text{参与核数}}$——更多核参与同一 batch 的 swizzle,窗口更大,工作集更方形;</li>
|
||
<li>线性映射对 B 与 C 的关系无要求,B 不整除 C 时无负载不均。</li>
|
||
</ol>
|
||
<p>因此 ASW_Basic 采用 <b>B→M→N 线性映射</b>作为核间分配策略。</p>
|
||
<hr>
|
||
<h2>五、实现方案</h2>
|
||
<h3>5.1 BaseM / BaseN 的确定(L0 级 tile,先把 L0C 用满)</h3>
|
||
<p>L0C 是 Cube 的累加器,BaseM × BaseN 是每次 Cube 计算的输出 tile。BaseM/N 应<b>尽量把 L0C 用满</b>——L0C 利用率越高,每次 Cube 计算的输出越大,单位计算的启动/排空开销摊得越薄。</p>
|
||
<p><b>L0C 双缓冲 vs UnitFlag 单缓冲</b>:</p>
|
||
<p>传统做法用 L0C 双缓冲实现 tile 间流水——计算 tile N+1 时,fixpipe 同时写出 tile N:</p>
|
||
<div class="math">$$
|
||
\text{BaseM} \times \text{BaseN} = \frac{L0C}{2 \times 4\text{B}} = 32768 \text{ 元素(双缓冲)}
|
||
$$</div>
|
||
<p>但昇腾 950PR 的 Fixpipe 支持 <b>UnitFlag</b>——MMAD 每完成一个 16×16×16 基本块(512B 结果),Fixpipe 立即将其写出,无需等整个 L0C tile 算完。UnitFlag 提供的是 <b>tile 内部的细粒度流水</b>(16×16×16 粒度),替代双缓冲的 <b>tile 间粗粒度流水</b>(BaseM×BaseN 粒度)。</p>
|
||
<p>UnitFlag 单缓冲下,L0C 只需一份 buffer:</p>
|
||
<div class="math">$$
|
||
\text{BaseM} \times \text{BaseN} = \frac{L0C}{4\text{B}} = 65536 \text{ 元素(单缓冲)}
|
||
$$</div>
|
||
<p>BaseM/N 可放大 $\sqrt{2}$ 倍(如 256→362),mCnt/nCnt 相应减小,<b>L2 重复读率降低</b>(单 batch 块数减少 → 每行 A 被更少的组读取)。</p>
|
||
<p>*时延对比*(单 tile 粒度,BF16 输出):</p>
|
||
<table><tr><th>方案</th><th>tile 大小</th><th>tile 间流水</th><th>tile 内流水</th><th>单 tile 时延</th></tr>
|
||
<tr><td>双缓冲</td><td>181×181(32761 元素)</td><td>✓(tile N 写出 ∥ tile N+1 计算)</td><td>✗</td><td>max($T_{comp}$, $T_{write}$)</td></tr>
|
||
<tr><td>UnitFlag 单缓冲</td><td>256×256(65536 元素)</td><td>✗</td><td>✓(16×16×16 粒度)</td><td>max($T_{comp}$, $T_{write}$)</td></tr></table>
|
||
<p>两种方案的稳态时延相同(都是 max($T_{comp}$, $T_{write}$)),但 UnitFlag 单缓冲的 tile 更大 → 总 tile 数更少 → 循环开销更小。<b>但当前 BMM ASW kernel 未启用 UnitFlag</b>(<code>unitFlag = 0</code>,注释 "each l0 only process one block, disable unit flag"),且源码在 baseM=baseN=256 时已自动选 dbL0C=1(256×256×4B×2 > L0C)——即<b>源码已经是单缓冲 + 无 UnitFlag</b>,tile 到顶但无流水交叠。</p>
|
||
<p>*建议*:对计算 Bound 的 case 启用 UnitFlag(MMAD 与 Fixpipe 流水并行),可将单 tile 时延从 $T_{comp} + T_{write}$ 降至 $\max(T_{comp}, T_{write})$。对访存 Bound 的 case(MTE2 Bound),UnitFlag 收益小([CANN 文档](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/920beta1/API/ascendcopapi/atlasascendc_api_07_0003.html):MTE2 Bound 时 MMAD/FIX 流水可被搬移掩盖)。</p>
|
||
<p>BaseM/BaseN 的长宽比<b>应尽量方形</b>(而非跟随 M/N),对齐 16 的倍数。<b>推导</b>:</p>
|
||
<p><b>1. GM→L1 搬移与 BaseM/N 无关</b>:GM→L1 搬移的是 $[\text{singleCoreM}, k_{L1}]$(A)和 $[k_{L1}, \text{singleCoreN}]$(B),其 dValue 由 $k_{L1}$(A)和 $\text{singleCoreN}$(B)决定——BaseM/N 的长宽比不参与 GM→L1 搬移参数。<b>长宽比跟随 M/N 不会改善 GM→L1 效率。</b></p>
|
||
<p><b>2. L0A = L0B = 64KB 等容量 → 方形 tile 用满两侧</b>:L0A 装 $\text{BaseM} \times baseK$,L0B 装 $baseK \times \text{BaseN}$。若长宽比跟随 M/N(如 M/N = 4:BaseM=512、BaseN=128),则:</p>
|
||
<div class="math">$$
|
||
baseK = \min\Big(\frac{L0A}{2 \cdot \text{BaseM} \cdot dtype},\; \frac{L0B}{2 \cdot \text{BaseN} \cdot dtype}\Big) = \min(32,\; 128) = 32
|
||
$$</div>
|
||
<p>L0A 装满(512×32×2×2 = 64KB)而 <b>L0B 只用了 25%</b>(32×128×2×2 = 16KB)——一半 L0 容量闲置。</p>
|
||
<p>方形 tile(BaseM=BaseN=256):</p>
|
||
<div class="math">$$
|
||
baseK = \min\Big(\frac{64\text{KB}}{2 \cdot 256 \cdot 2\text{B}},\; \frac{64\text{KB}}{2 \cdot 256 \cdot 2\text{B}}\Big) = 64
|
||
$$</div>
|
||
<p>L0A 和 L0B <b>同时装满</b>,且 baseK = 64 是长宽比跟随方案(baseK=32)的 <b>2 倍</b>——K 维迭代次数减半,Cube 的 K 向流水切换和 SetFlag/WaitFlag 同步次数减半(L1→L0 搬移本身不是瓶颈,但更少的迭代意味着更少的同步握手和更少的 MMAD 启动/排空轮次)。</p>
|
||
<p><b>3. 方形 tile 是 L0C 面积约束下的最优</b>:L0C 约束 $\text{BaseM} \times \text{BaseN} \le 65536$ 只限制面积。在面积固定下,方形使 $\min(\text{BaseM}, \text{BaseN})$ 最大——baseK 的上限由 $\min(\text{BaseM}, \text{BaseN})$ 决定(公式见上),所以方形最大化 baseK。</p>
|
||
<p><b>4. 例外</b>:当 M 或 N 小于方形边长时,tile 被迫非方形:</p>
|
||
<div class="math">$$
|
||
\text{BaseM} = \min(\lfloor\sqrt{65536}\rfloor_{16},\; M),\qquad \text{BaseN} = \min\Big(\frac{65536}{\text{BaseM}},\; N\Big) \text{ 向下 16 对齐}
|
||
$$</div>
|
||
<p>例:M=128、N=4096 → BaseM=128(M 限制)、BaseN=512(L0C 面积限制)——此时长宽比跟随 M/N 是<b>被迫的</b>(M 太小),而非主动选择。</p>
|
||
<p><b>结论</b>:BaseM/N 长宽比跟随 M/N 的切分方法<b>站不住脚</b>——它使 L0A/L0B 容量利用率失衡(一侧闲置)、baseK 减半(K 迭代翻倍)、且对 GM→L1 搬移无任何收益。正确做法是<b>方形 tile 优先</b>,仅当 M 或 N 小于方形边长时被迫跟随。</p>
|
||
<p>baseK 由 L0A/L0B 容量决定(L1→L0 搬移无 dValue 要求,dValue 约束的是 GM→L1 的 $k_{L1}$):</p>
|
||
<div class="math">$$
|
||
baseK = \min\Big(\frac{L0A}{2 \cdot \text{BaseM} \cdot \text{dtype}},\; \frac{L0B}{2 \cdot \text{BaseN} \cdot \text{dtype}}\Big) \text{ 向下 16 对齐}
|
||
$$</div>
|
||
<p>核间不切 K 时 K 维度层次关系:$K = \text{singleCoreK} \ge k_{L1} \ge baseK$——$k_{L1}$ 是 GM→L1 的 K 向粒度(须 $k_{L1} \cdot \text{dtype} \ge 256\text{B}$),baseK 是 L1→L0 的 K 向粒度(仅受 L0A/L0B 容量约束)。</p>
|
||
<p>*BaseM/N 的具体确定过程*(host 端枚举,16 对齐遍历):</p>
|
||
<ol class="tight">
|
||
<li>从 BaseM = BaseN = $\lfloor\sqrt{L0C/4\text{B}}\rfloor_{16} = 256$ 开始(方形,L0C 单缓冲上限,L0A/L0B 同时满载)</li>
|
||
<li>baseK 取 L0A/L0B 容量允许的最大值:baseK = $\min(L0A/(2 \cdot \text{BaseM} \cdot dtype),\; L0B/(2 \cdot \text{BaseN} \cdot dtype))$ 向下 16 对齐(<b>16 对齐是 Cube K 向粒度要求,不是搬移效率要求</b>——L1→L0 搬移不是瓶颈,可被 GM→L1 或 Cube 计算掩盖。baseK 取大值的意义在 L0 容量利用率和 K 迭代次数,而非搬移效率)</li>
|
||
<li>若 M < 256(或 N < 256),BaseM = $\lfloor M \rfloor_{16}$(或 BaseN = $\lfloor N \rfloor_{16}$),另一维取 $\min(65536/\text{BaseM},\; N)$(或对称)向下 16 对齐——此时 tile 被迫跟随 M/N,但这是 M 太小的结果,不是主动选择</li>
|
||
<li>方形 tile 的 L0C 面积利用率:$256 \times 256 / 65536 = 100\%$;被迫非方形时面积利用率 = $\text{BaseM} \times \text{BaseN} / 65536$(M 或 N 小时必然 < 100%,不可优化)</li>
|
||
</ol>
|
||
<p>*与源码的差异*:源码默认 baseM=baseN=256(硬编码 "256 is better base"),再由 cubeBound 模型(L2 供数能力 + 溢出惩罚 + K 向复用)枚举收缩。理论直接从 L0C 容量出发,cubeBound 的三项修正可从第一性原理推导:①L2 供数速率 vs Cube 耗数速率 → 访存 Bound 时应缩 tile;②工作集超 L2 → 增大 tile 减少重复读;③K 越大越偏 compute bound → 可放大 tile。两者方向一致,源码多了实测调优的余量(CUBE_BOUND_RATIO=0.85)。</p>
|
||
<h3>5.2 SingleCoreM / SingleCoreN 的确定(每核输出 tile,≥ BaseM/N)</h3>
|
||
<p>SingleCoreM × SingleCoreN 是每核每次处理的输出区域,<b>不受 L0 容量直接约束</b>——一个 [SingleCoreM, SingleCoreN] tile 内部由若干 [BaseM, BaseN] L0 tile 组成($\text{SingleCoreM} \ge \text{BaseM}$,$\text{SingleCoreN} \ge \text{BaseN}$)。SingleCoreM/N 的核心影响是 <b>GM→L1 搬移效率和 L2 重复读率</b>:</p>
|
||
<ul class="tight">
|
||
<li>SingleCoreM/N 越大 → 单次 GM→L1 搬移量越大,dValue 越有保障,L2 中同一份 A 行带/B 列带被更多核复用</li>
|
||
<li>SingleCoreM/N 越小 → 总块数 mCnt×nCnt 越多,并行度越高,但搬移效率降低</li>
|
||
</ul>
|
||
<p><b>SingleCoreM/N 对 L2 重复读的定量影响</b>:</p>
|
||
<p>每行 A 行带 $[\text{singleCoreM}, K]$ 被该行的 $nCnt$ 个列块各读一次,每列 B 列带被 $mCnt$ 个行块各读一次。L2 层的总读取次数:</p>
|
||
<div class="math">$$
|
||
T_{L2} = \big(nCnt \cdot M + mCnt \cdot N\big) \cdot K \cdot dtype
|
||
$$</div>
|
||
<p>SingleCoreM/N 越小 → $mCnt = \lceil M/\text{singleCoreM} \rceil$、$nCnt = \lceil N/\text{singleCoreN} \rceil$ 越大 → $T_{L2}$ 越大。</p>
|
||
<p>但这不直接等于 GM 重复读——L2 命中时重复读由 L2 吸收(5.2TB/s),不消耗 GM 带宽。**GM 重复读倍率 $r_{in}$ 取决于执行组划分**(§5.6):只有当工作集超 L2 时才需要分组,此时</p>
|
||
<div class="math">$$
|
||
r_{in} = \frac{\lceil nCnt/n_{grp} \rceil \cdot M + \lceil mCnt/m_{grp} \rceil \cdot N}{M + N}
|
||
$$</div>
|
||
<p>$m_{grp}$、$n_{grp}$ 由 L2 容量约束反推($m_{grp} \cdot \text{singleCoreM} + n_{grp} \cdot \text{singleCoreN} \le D$,$D = L2/(B \cdot K \cdot dtype)$)。SingleCoreM/N 减小时 $m_{grp}$、$n_{grp}$ 按比例增大,$\lceil mCnt/m_{grp} \rceil$ 和 $\lceil nCnt/n_{grp} \rceil$ 的变化取决于具体数值——<b>但 L2 带宽不是瓶颈</b>(5.2TB/s ≫ GM 1.6TB/s),所以 SingleCoreM/N 对性能的主要影响不在 L2 重复读,而在 <b>GM→L1 搬移效率</b>(dValue 和 min_TileSize)和<b>并行度</b>($mCnt \cdot nCnt \ge \lceil C/B \rceil$)之间的权衡。</p>
|
||
<p>*约束链*:</p>
|
||
<p><b>约束 1——并行度下限</b>:总块数须填满 C 核。</p>
|
||
<div class="math">$$
|
||
B \cdot mCnt \cdot nCnt \ge C \iff mCnt \cdot nCnt \ge \Big\lceil \frac{C}{B} \Big\rceil
|
||
$$</div>
|
||
<p><b>约束 2——L1 容量</b>(双缓冲下驻留当前 tile 的输入):</p>
|
||
<div class="math">$$
|
||
2 \cdot (\text{singleCoreM} + \text{singleCoreN}) \cdot k_{L1} \cdot \text{dtype} \le L1,\qquad k_{L1} \cdot \text{dtype} \ge 256\text{B}
|
||
$$</div>
|
||
<p><b>约束 3——搬移效率</b>(dValue 见 §1.4):</p>
|
||
<div class="math">$$
|
||
\underbrace{k_{L1} \cdot \text{dtype} \ge 256\text{B}}_{\text{A 矩阵 dValue}},\qquad \underbrace{\text{singleCoreN} \cdot \text{dtype} \ge 256\text{B}}_{\text{B 矩阵 dValue(非转置)}}
|
||
$$</div>
|
||
<div class="math">$$
|
||
\underbrace{\text{singleCoreM} \cdot k_{L1} \cdot \text{dtype} \ge min\_TileSize}_{\text{A 单次搬移量}},\qquad \underbrace{k_{L1} \cdot \text{singleCoreN} \cdot \text{dtype} \ge min\_TileSize}_{\text{B 单次搬移量}}
|
||
$$</div>
|
||
<p><b>约束 4——SingleCoreM/N 是 BaseM/N 的整数倍</b>(工程实现要求,保证 L0 tile 边界对齐)。</p>
|
||
<p>*选取策略*:<b>最少切分原则 + 切分时方形分配</b>。</p>
|
||
<p>*建模*:每 batch 有 $mCnt \cdot nCnt$ 块,每块的 GM→L1 搬入(A 块 $[\text{singleCoreM}, k_{L1}]$ + B 块 $[k_{L1}, \text{singleCoreN}]$)时延为 $(\text{singleCoreM} + \text{singleCoreN}) \cdot k_{L1} \cdot dtype / BW$。每核处理 $B \cdot mCnt \cdot nCnt / C$ 块,单核总搬入时延:</p>
|
||
<div class="math">$$
|
||
T_{MTE2} = \frac{MN}{C/B} \cdot \Big(\frac{mCnt}{M} + \frac{nCnt}{N}\Big) \cdot \frac{k_{L1} \cdot dtype}{BW}
|
||
$$</div>
|
||
<p>(展开验证:每 batch 总搬入 = $(M \cdot nCnt + N \cdot mCnt) \cdot k_{L1} \cdot dtype$——A 矩阵每 batch 被 $nCnt$ 个列块共享读 $nCnt$ 次、B 矩阵被 $mCnt$ 个行块共享读 $mCnt$ 次,与直觉一致。)</p>
|
||
<p>目标函数 $mCnt/M + nCnt/N$ 在 $mCnt = nCnt = 1$ 时取得<b>全局最小值</b>($1/M + 1/N$)——<b>不切分时每 batch 的 A/B 只搬一次,零重复读</b>。因此第一步是<b>尝试最少切分</b>:</p>
|
||
<div class="math">$$
|
||
mCnt \cdot nCnt = \Big\lceil \frac{C}{B} \Big\rceil \triangleq P
|
||
$$</div>
|
||
<p>(多切无益:切分越多重复读越多,搬入量单调增大。)</p>
|
||
<p><b>情形 1:B ≥ C(P = 1)</b>——不切分,$mCnt = nCnt = 1$,$\text{singleCoreM} = M$、$\text{singleCoreN} = N$。<b>tile 跟随 M/N,非方形</b>。前提是 L1 容量与 dValue 满足(约束 2/3);不满足时须 K 分块($k_{L1} < K$)或退化为情形 2。</p>
|
||
<p><b>情形 2:B < C(P > 1)</b>——必须切分。在 $mCnt \cdot nCnt = P$ 下最小化 $mCnt/M + nCnt/N$:</p>
|
||
<div class="math">$$
|
||
\frac{\partial}{\partial mCnt}\Big(\frac{mCnt}{M} + \frac{P}{mCnt \cdot N}\Big) = 0 \Rightarrow mCnt^* = \sqrt{\frac{P \cdot M}{N}},\; nCnt^* = \sqrt{\frac{P \cdot N}{M}}
|
||
$$</div>
|
||
<div class="math">$$
|
||
\frac{mCnt^*}{nCnt^*} = \frac{M}{N} \Rightarrow \text{singleCoreM} = \text{singleCoreN} = \sqrt{\frac{MN}{P}}
|
||
$$</div>
|
||
<p>——<b>方形</b>(连续松弛下的理论最优)。整数 + BaseM/N 对齐约束下取离方形最近的可行组合。</p>
|
||
<p>*数值验证*(M=2048、N=512、C=32):</p>
|
||
<table><tr><th>B</th><th>P = ⌈C/B⌉</th><th>最优 (mCnt, nCnt)</th><th>sM × sN</th><th>形状</th></tr>
|
||
<tr><td>32</td><td>1</td><td>(1, 1)</td><td>2048 × 512</td><td>跟随 M/N(不切分)</td></tr>
|
||
<tr><td>16</td><td>2</td><td>(2, 1)</td><td>1024 × 512</td><td>2:1(整数约束偏离方形)</td></tr>
|
||
<tr><td>8</td><td>4</td><td>(4, 1)</td><td>512 × 512</td><td>方形(M/N=4 与 P=4 匹配)</td></tr>
|
||
<tr><td>4</td><td>8</td><td>(4, 2)</td><td>512 × 256</td><td>2:1</td></tr></table>
|
||
<p>B=32 时不切分 cost = 0.002441 < B=8 时方形 0.003906——<b>切分越少搬入越少,方形只是"被迫切分"时的次优选择</b>。用户反例成立:M=2048、N=512、B≥32 时 SingleCoreM=2048、SingleCoreN=512 优于方形 512×512(每 batch 零重复读)。</p>
|
||
<p>*此前推导的错误*:把 $mCnt \cdot nCnt = P$ 当作固定等式且未讨论 P=1 的情形——方形结论只适用于 B < C 的强制切分场景,不适用于 B ≥ C 的不切分场景。</p>
|
||
<p>*SingleCoreM/N 的具体确定过程*(host 端枚举,与尾轮处理联动):</p>
|
||
<ol class="tight">
|
||
<li>计算最少切分 $P = \lceil C/B \rceil$</li>
|
||
<li><b>若 P = 1(B ≥ C)</b>:先试不切分 $mCnt = nCnt = 1$、$\text{singleCoreM} = M$、$\text{singleCoreN} = N$;由约束 2 求 $k_{L1} = \min(K,\; \lfloor L1/(2(M{+}N) \cdot dtype) \rfloor_{16})$,检查约束 3($k_{L1} \cdot dtype \ge 256\text{B}$ 等);满足则确定。不满足(L1 放不下且 $k_{L1}$ 降无可降)则进入步骤 3 强制切分</li>
|
||
<li><b>若 P > 1(必须切分)——完整枚举</b>。枚举不是随意挑几个组合试,而是<b>遍历整个可行空间、每个候选计算搬入时延、取最优</b>:</li>
|
||
</ol>
|
||
<p> <b>a. 枚举空间(有界)</b>:由约束 4 给出上界 $mCnt \le \lceil M/\text{BaseM} \rceil$、$nCnt \le \lceil N/\text{BaseN} \rceil$(SingleCore 不能小于 Base),约束 1 给出 $B \cdot mCnt \cdot nCnt \ge C$。候选数最多 $\lceil M/\text{BaseM} \rceil \times \lceil N/\text{BaseN} \rceil$(如 M=N=4096、Base=256 时 256 个),host 端遍历开销可忽略</p>
|
||
<p> <b>b. 每个候选的评估流水线</b>($(mCnt, nCnt) \to$ 对齐 $\to$ 约束过滤 $\to$ 目标值):</p>
|
||
<div class="math"> $$
|
||
\text{singleCoreM} = \text{Align}_{16}\Big(\Big\lceil \frac{M}{mCnt} \Big\rceil\Big),\qquad \text{singleCoreN} = \text{Align}_{16}\Big(\Big\lceil \frac{N}{nCnt} \Big\rceil\Big)
|
||
$$</div>
|
||
<p> *约束 4 过滤*:singleCoreM/singleCoreN 是 BaseM/BaseN 的整数倍;*约束 2 求 $k_{L1}$*:</p>
|
||
<div class="math"> $$
|
||
k_{L1} = \min\Big(K,\; \Big\lfloor \frac{L1}{2 \cdot (\text{singleCoreM} + \text{singleCoreN}) \cdot dtype} \Big\rfloor_{16}\Big)
|
||
$$</div>
|
||
<p> *约束 3 过滤*:$k_{L1} \cdot dtype \ge 256\text{B}$ 且 $\text{singleCoreM} \cdot k_{L1} \cdot dtype \ge min\_TileSize$ 且 $k_{L1} \cdot \text{singleCoreN} \cdot dtype \ge min\_TileSize$</p>
|
||
<p> *目标值(§3 时延模型的搬移项)*:</p>
|
||
<div class="math"> $$
|
||
T_{MTE2} = \frac{MN}{C/B} \cdot \Big(\frac{1}{\text{singleCoreM}} + \frac{1}{\text{singleCoreN}}\Big) \cdot \frac{k_{L1} \cdot dtype}{BW_{pc}}
|
||
$$</div>
|
||
<p> **c. 为什么目标函数是 $T_{MTE2}$**:$T_{MMAD} = 2BMNK/(C \cdot Q_{16})$ 与 $T_{FIX} = B \cdot MN \cdot outB/(C \cdot W_{pc})$ 只依赖全量 B/M/N/K,<b>与切分无关</b>——候选之间的唯一差异在搬入时延(§3)。约束 2/3/4 只是可行性过滤,<b>不足以选最优</b>:多个可行解的 $T_{MTE2}$ 可差 2 倍(§6.3 源码过度切分示例)</p>
|
||
<p> <b>d. 最优选取</b>:通过全部约束的候选中取 $T_{MTE2}$ 最小者;并列时取尾轮块数 $r = B \cdot mCnt \cdot nCnt \bmod C$ 最大者(尾轮块越多,§5.3 重切的 $s^*$ 越小、越省)</p>
|
||
<ol class="tight">
|
||
<li><b>尾轮修正</b>:$n_{wave} = \lceil B \cdot mCnt \cdot nCnt / C \rceil \le 3$ 且 $r > 0$ 时,按 §5.3 计算重切因子 $s^*$,尾轮时延 $T_{tail} = T_{block}/s^*$;否则 $T_{tail} = T_{block}$(r=0 时为 0)</li>
|
||
</ol>
|
||
<ol class="tight">
|
||
<li><b>端到端校验</b>:$T = \max(T_{MTE2},\; T_{MMAD},\; T_{FIX}) + T_{tail}$。若选出的候选 $T_{MTE2} < T_{MMAD}$(搬移被计算掩盖),候选间差异失效,此时以尾轮最优者为准</li>
|
||
</ol>
|
||
<p>*完整实例*(B=8、M=N=2048、K=1024、BF16,BaseM=BaseN=256、$min\_TileSize$=16KB):</p>
|
||
<ul class="tight">
|
||
<li>$P = \lceil 32/8 \rceil = 4$,进入步骤 3。枚举空间 $mCnt, nCnt \le \lceil 2048/256 \rceil = 8$,$8 \cdot mCnt \cdot nCnt \ge 32$</li>
|
||
<li>枚举与评估:</li>
|
||
</ul>
|
||
<table><tr><th>(mCnt, nCnt)</th><th>sM × sN</th><th>$k_{L1}$(约束 2 上限,16 对齐)</th><th>约束 3 过滤</th><th>$T_{MTE2} \propto (1/sM + 1/sN) \cdot k_{L1}$</th></tr>
|
||
<tr><td>(2, 2)</td><td>1024 × 1024</td><td>64(128B)</td><td>✗ dValue < 256B</td><td>—</td></tr>
|
||
<tr><td>(4, 2)</td><td>512 × 1024</td><td>80(160B)</td><td>✗</td><td>—</td></tr>
|
||
<tr><td>(4, 4)</td><td>512 × 512</td><td>128(256B)</td><td>✓(512×128×2 = 128KB ≥ 16KB)</td><td>0.500</td></tr>
|
||
<tr><td>(8, 4)</td><td>256 × 512</td><td>160(320B)</td><td>✓</td><td>0.938</td></tr>
|
||
<tr><td>(4, 8)</td><td>512 × 256</td><td>160</td><td>✓</td><td>0.938</td></tr>
|
||
<tr><td>(8, 8)</td><td>256 × 256</td><td>256(512B)</td><td>✓</td><td>2.000</td></tr></table>
|
||
<p> $k_{L1}$ 上限公式:$\lfloor 131072/(sM{+}sN) \rfloor$(131072 = L1/(2·dtype) = 512KB/4B)。评估过程展示三个要点:</p>
|
||
<ol class="tight">
|
||
<li><b>最少切分 (2,2) 不可行</b>:$k_{L1} \le 64$ 元素 = 128B,违反 dValue ≥ 256B——若只做约束 1(并行度)检查会在 (2,2) 上误判达标,必须连同约束 2/3 一起过滤;</li>
|
||
<li><b>多个候选通过约束</b>:(4,4)、(8,4)、(4,8)、(8,8) 都满足约束 2/3/4——<b>约束检查不足以选最优</b>,必须计算目标函数;</li>
|
||
<li>**目标函数 $T_{MTE2}$ 定最优**:拉格朗日方形点 (4,4) 的搬入时延 0.500 最小(比 (8,4) 省 47%、比 (8,8) 省 75%)。注意此处 $k_{L1}$ 是变量(K=1024 未截断、由 L1 容量决定),故 $T_{MTE2} \propto (1/sM + 1/sN) \cdot k_{L1}$ 而非 §5.2 的简化式 $(1/sM + 1/sN)$。最终 (4,4):总块数 $8 \times 16 = 128$,$r = 128 \bmod 32 = 0$ 完美整除、无尾轮</li>
|
||
</ol>
|
||
<p>*与源码的差异*:源码用 cubeBound 模型 + balanceRate ≥ 0.9 剪枝,不解耦 SingleCoreM/N 与 BaseM/N(baseM=256 已是 SingleCore 级参数,L0 tile 由 stepM/stepN 二次切分),且枚举目标为"算存比/负载均衡帕累托"而非"搬入时延最小"。理论的两层分离使约束链更清晰、目标函数($T_{MTE2}$)有闭式表达——SingleCoreM/N 由 L1 + 并行度 + 搬移效率决定,BaseM/N 由 L0C 决定,各司其职。</p>
|
||
<h3>5.3 mCnt / nCnt 与核间分配(含尾轮处理)</h3>
|
||
<div class="math">$$
|
||
mCnt = \Big\lceil \frac{M}{\text{singleCoreM}} \Big\rceil,\qquad nCnt = \Big\lceil \frac{N}{\text{singleCoreN}} \Big\rceil
|
||
$$</div>
|
||
<p>总输出块数 = $B \times mCnt \times nCnt$。核间分配采用 B→M→N 线性映射(分析见 §四):块按 (b, m, n) 字典序编号,核 i 处理块 i, i+C, i+2C, …。B 不整除 C 时尾波不满载的核少分一块,无需 B | C。</p>
|
||
<p><b>尾轮处理</b>(host 端预处理,零 NPU 开销):</p>
|
||
<p><b>尾轮定义</b></p>
|
||
<p>当 $B \cdot mCnt \cdot nCnt \;\%\; C \neq 0$ 时,总块数不能被 C 整除,最后一波(尾轮)不满载——只有 $B \cdot mCnt \cdot nCnt \;\%\; C$ 个核有活干,其余核空闲。</p>
|
||
<p><b>尾轮重切:是否值得?</b></p>
|
||
<p><b>关键前提:tiling 在 host(CPU)上完成,不占 NPU 时间。</b> 重切只需 host 多算一套 tiling 参数下发给 NPU,零 NPU 开销。这改变了收益/代价的平衡——重切的代价仅为 host 端多一次枚举,而收益是 NPU 端尾波时延的缩短。</p>
|
||
<p><b>不重切时</b>:尾波 $r$ 个块用 $r$ 个核,每核处理 1 块,时延 = $T_{block}$,$C - r$ 核空闲。</p>
|
||
<p><b>重切时</b>:把 $r$ 个块沿 M 或 N 维再切 $s$ 份,变成 $r \cdot s$ 个小块,$r \cdot s$ 个核各处理 1 小块,时延 ≈ $T_{block}/s$(每小块计算量/搬移量/写回量均为原块的 $1/s$)。</p>
|
||
<p>*重切收益*:</p>
|
||
<div class="math">$$
|
||
\Delta T_{saved} = T_{block} \cdot \Big(1 - \frac{1}{s}\Big)
|
||
$$</div>
|
||
<p>*占总时延比例*:</p>
|
||
<div class="math">$$
|
||
\frac{\Delta T_{saved}}{T_{total}} \approx \frac{1 - 1/s}{n_{wave}}
|
||
$$</div>
|
||
<p>$n_{wave} = 2$、$s = C/r$ 时:$r=16$ → 收益 25%;$r=8$ → 收益 37.5%。$n_{wave} = 3$ 时:$r=16$ → 16.7%;$r=8$ → 25%。**$n_{wave} \le 3$ 时收益显著,应重切。**</p>
|
||
<p>*重切约束的推导*:沿 N 切 $s$ 份后,小块变为 $[\text{singleCoreM},\; \text{singleCoreN}/s]$。小块须满足两类约束——<b>但约束的严格程度取决于算子是访存 Bound 还是计算 Bound</b>。</p>
|
||
<p>**访存 Bound($T_{MTE2} \ge T_{MMAD}$)**:搬移是瓶颈,小块的 dValue 和搬移量不能降——否则搬移更慢,总时延反而增加。约束与主 tile 相同:</p>
|
||
<ol class="tight">
|
||
<li><b>dValue 下限</b>:B 矩阵 dValue = $\text{singleCoreN}_{tail} \cdot dtype \ge 256\text{B}$(沿 N 切时):</li>
|
||
</ol>
|
||
<div class="math">$$
|
||
s \le \frac{\text{singleCoreN} \cdot dtype}{256\text{B}}
|
||
$$</div>
|
||
<ol class="tight">
|
||
<li><b>单次搬移量</b>:$k_{L1} \cdot \text{singleCoreN}_{tail} \cdot dtype \ge min\_TileSize$:</li>
|
||
</ol>
|
||
<div class="math">$$
|
||
s \le \frac{k_{L1} \cdot \text{singleCoreN} \cdot dtype}{min\_TileSize}
|
||
$$</div>
|
||
<ol class="tight">
|
||
<li><b>对齐</b>:$\text{singleCoreN}_{tail} \ge 16$:</li>
|
||
</ol>
|
||
<div class="math">$$
|
||
s \le \frac{\text{singleCoreN}}{16}
|
||
$$</div>
|
||
<p>**计算 Bound($T_{MMAD} > T_{MTE2}$)**:搬移被计算掩盖,dValue 和搬移量约束可放宽——即使搬移效率降低,只要搬移时间仍 ≤ 计算时间,总时延不变。放宽的定量依据:</p>
|
||
<p>小块计算时延 $T_{comp}^{tail} = 2 \cdot \text{singleCoreM} \cdot \text{singleCoreN}_{tail} \cdot K / Q_{16}$,搬移时延 $T_{load}^{tail} = (\text{singleCoreM} + \text{singleCoreN}_{tail}) \cdot k_{L1} \cdot dtype / BW_{pc}$。搬移被掩盖的条件:</p>
|
||
<div class="math">$$
|
||
T_{load}^{tail} \le T_{comp}^{tail} \iff \text{singleCoreN}_{tail} \ge \frac{\text{singleCoreM} \cdot k_{L1} \cdot dtype \cdot Q_{16}}{BW_{pc} \cdot (2 \cdot \text{singleCoreM} \cdot K - k_{L1} \cdot dtype \cdot Q_{16} / BW_{pc})}
|
||
$$</div>
|
||
<p>记右端为 $x_{min}$(搬移掩盖下限),则计算 Bound 下的约束为:</p>
|
||
<div class="math">$$
|
||
s \le \min\Big(\frac{\text{singleCoreN}}{x_{min}},\; \frac{\text{singleCoreN}}{16}\Big)
|
||
$$</div>
|
||
<p>**计算 Bound 越严重(K 越大),$x_{min}$ 越小,$s$ 上限越大**——极端情况 $x_{min} \to 16$(对齐底线),$s \le \text{singleCoreN}/16$。</p>
|
||
<p>*例*(singleCoreM=singleCoreN=256、$k_{L1}$=128、K=1024、BF16):$x_{min} = 256 \times 128 \times 2 \times 15.2 \times 10^{12} / (50 \times 10^9 \times (2 \times 256 \times 1024 - 128 \times 2 \times 15.2 \times 10^{12} / 50 \times 10^9)) \approx 1$——几乎无约束,$s \le 256/16 = 16$。</p>
|
||
<p>*最优切分因子*:先判定 Bound 类型($T_{MMAD}$ vs $T_{MTE2}$),再选约束集:</p>
|
||
<div class="math">$$
|
||
s^* = \min\Big(\Big\lfloor \frac{C}{r} \Big\rfloor,\; s_{max}\Big),\qquad s_{max} = \begin{cases} \min\big(\frac{\text{singleCoreN} \cdot dtype}{256\text{B}},\; \frac{k_{L1} \cdot \text{singleCoreN} \cdot dtype}{min\_TileSize},\; \frac{\text{singleCoreN}}{16}\big) & \text{访存 Bound} \\ \frac{\text{singleCoreN}}{16} & \text{计算 Bound} \end{cases}
|
||
$$</div>
|
||
<p>*例*(C=32、$N_{blk}=40$、$n_{wave}=2$、$r=8$、singleCoreM=singleCoreN=256、$k_{L1}$=128、BF16):</p>
|
||
<ul class="tight">
|
||
<li><b>访存 Bound</b>(K 小):dValue $s \le 2$,搬移量 $s \le 4$,对齐 $s \le 16$。$s^* = 2$——dValue 瓶颈,16 核满载,尾波减半,总时延节省 25%。</li>
|
||
<li><b>计算 Bound</b>(K 大):$x_{min} \approx 1$,$s^* = \min(4, 16) = 4$——32 核满载,尾波降为 1/4,总时延节省 37.5%。</li>
|
||
</ul>
|
||
<p>*切分方向选择*:优先沿 N 切(保持 A 行带完整,L2 中 A 数据不变)。计算 Bound 下若 N 向对齐卡住($\text{singleCoreN}/16 < C/r$),改沿 M 切(A 矩阵 dValue = $k_{L1} \cdot dtype$ 不变,仅受对齐约束 $\text{singleCoreM}/16$)。</p>
|
||
<p><b>结论</b>:$n_{wave} \le 3$ 且 $r < C$ 时应重切尾轮——host 端零代价,NPU 端收益 $T_{block}(1-1/s^*)$。$n_{wave} \ge 4$ 时收益 < 25%,可不重切(通过选择使尾波占比小的 mCnt/nCnt 组合来优化)。</p>
|
||
<p><b>尾轮影响的量化</b></p>
|
||
<p>设总块数 $N_{blk} = B \cdot mCnt \cdot nCnt$,总波数 $n_{wave} = \lceil N_{blk} / C \rceil$,尾波块数 $r = N_{blk} \bmod C$($r = 0$ 时无尾波)。</p>
|
||
<p><b>不重切时</b>尾波导致的额外时延(相对于完美整除):</p>
|
||
<div class="math">$$
|
||
\Delta T_{tail} = \begin{cases} 0 & r = 0 \\ T_{block} \cdot \big(1 - \frac{r}{C}\big) & r > 0 \end{cases}
|
||
$$</div>
|
||
<p><b>重切后</b>尾波时延降为 $T_{block}/s^*$,额外时延:</p>
|
||
<div class="math">$$
|
||
\Delta T_{tail}^{re} = \frac{T_{block}}{s^*} \cdot \Big(1 - \frac{r \cdot s^*}{C}\Big)
|
||
$$</div>
|
||
<p>当 $r \cdot s^* = C$(完美重切)时 $\Delta T_{tail}^{re} = 0$——尾波完全消除。</p>
|
||
<hr>
|
||
<h3>5.4 核间切分维度选择(按共享代价从低到高)</h3>
|
||
<p>切 B(零共享,先试)→ 切 M(右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2)→ 切 N(对称)→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见 Step 8)。</p>
|
||
<h3>5.5 swizzle——ASW 滑窗蛇形</h3>
|
||
<p><b>问题</b>:核间切 M/N 后,同一时刻 C 个核各算一个输出块,它们所需的 A 行块与 B 列块集合就是当前"活跃工作集"。若按行优先顺序朴素分配,一波 C 个块横跨的 A 行、B 列很宽,活跃工作集超过 L2 就回 GM 读(1.6TB/s),重复读代价真实发生。<b>swizzle 要做的就是编排输出块的执行顺序,把每一波核的活跃工作集压到最小。</b></p>
|
||
<p><b>做法</b>:把 M 向每 W 个基本块划为一个"窗口",遍历顺序为"窗口内先扫 M、扫满 W 行再进下一列 N;一个窗口扫完再进下一个窗口",且奇数窗口行 N 向反向(蛇形)。效果有二:</p>
|
||
<ul class="tight">
|
||
<li>同一波 C 个核的块集中在同一个窗口内 ⇒ 活跃 A 行块只有 W 个、活跃 B 列块只有 C/W 条带;</li>
|
||
<li>蛇形反向使相邻窗口行首尾相接——上一窗口末尾的 B 列带与下一窗口开头的 B 列带是同一条,跨窗口切换时工作集增量最小。</li>
|
||
</ul>
|
||
<p><b>W 怎么取</b>:一波 C 个块的 L2 足迹约为</p>
|
||
<div class="math">$$
|
||
footprint \approx \big(W \cdot M^t K + \tfrac{C}{W} \cdot K N^t\big) \cdot \text{dtype}
|
||
$$</div>
|
||
<p>由均值不等式,$W + C/W$ 在 $W = \sqrt{C}$ 处取最小——窗口越接近"方形"(W 行 × C/W 列),足迹越小。同时 W 须整除 C,保证每个窗口恰好被整数波核覆盖、窗口边界不把波次切碎。合起来即:</p>
|
||
<div class="math">$$
|
||
W = \max\{\,d \mid d \mid C,\; d \le \lfloor\sqrt{C}\rfloor\,\}
|
||
$$</div>
|
||
<p>C=32 时 $\sqrt{32} \approx 5.66$,因子 {1,2,4,8,…} 中不超过它的最大者是 4,故 W=4。</p>
|
||
<p><b>实例</b>(C=32,W=4,M̃=8,Ñ=8;数字为块的全局执行顺序,一波 32 块):</p>
|
||
<pre><code>窗口0(N 正向) 窗口1(N 蛇形反向)
|
||
ν0 ν1 ν2 … ν7 ν0 ν1 … ν7
|
||
μ0 0 4 8 … 28 μ4 60 56 … 32
|
||
μ1 1 5 9 … 29 μ5 61 57 … 33
|
||
μ2 2 6 10 … 30 μ6 62 58 … 34
|
||
μ3 3 7 11 … 31 μ7 63 59 … 35</code></pre>
|
||
<p>块 31 = (μ3, ν7),块 32 = (μ4, ν7)——相邻两个块共用同一条 B 列带(ν7),窗口切换几乎零增量。对比朴素行优先(Ñ=16 时):一波横跨 2 个 A 行块 + 16 条 B 列带,足迹 (2·M^t + 16·N^t)·K·dtype;滑窗为 (4·M^t + 8·N^t)·K·dtype——M^t≈N^t 时足迹缩小 1/3。</p>
|
||
<p><b>窗内为什么不蛇形</b>(对上例中"块 3=(μ3,ν0) → 块 4=(μ0,ν1) 而非 (μ3,ν1)"的说明):蛇形的收益来自"相邻遍历段共享边界数据",要分两种边界看:</p>
|
||
<ul class="tight">
|
||
<li><b>窗内列间边界</b>(ν0→ν1):相邻两段共享的是同一组 A 行块(W 个),它们在整个窗口期间<b>全程驻留 L2</b>,无论按什么顺序扫,工作集不变——窗内蛇形零收益;</li>
|
||
<li><b>窗口行边界</b>(窗口0→窗口1):A 行整体换血(μ0..3 → μ4..7),此时 B 列带的连续性决定换血成本——不蛇形则下一窗口从 ν0 开始(LRU 上最久未用、早已被挤出 L2 的冷带),蛇形则延续上一窗口末尾的 ν7(最热线带)。<b>蛇形只标在窗口行号上</b>(源码 <code>BatchMatMulAswBlock::UpdateBasicIndex</code>:仅 <code>rowIdx</code> 为奇时 n 反向,窗内 m 最快序不反向),正是这个收益结构的直接实现。</li>
|
||
</ul>
|
||
<h3>5.6 L2 分组(工作集超 L2 时)</h3>
|
||
<p><b>切的是什么</b>:将 mCnt×nCnt 个基本块划分为若干<b>执行组</b>——每组覆盖输出平面上一个连续矩形区域(若干 singleCoreM × singleCoreN 基本块的集合),使该组所需的 A 行带 + B 列带输入工作集 ≤ L2 可用读入空间;组内所有基本块算完再进下一组,输入只在跨组时换一次。</p>
|
||
<p><b>为什么需要它</b>:滑窗压缩的只是"同一波"的足迹;若整个工作集超 128MB L2,跨波次复用落空——上一波窗口的 A 行早被挤出,下一波又得回 GM 读。且 L2 是<b>读写共用</b>的:输出经 fixpipe 写出时若驻留 L2(dirty),会压缩读入可用空间;若直写 GM,则占用与读共享的 1.6TB/s 总线。所以 L2 切分必须与写出策略联合决策。记输入总量 $S_{in} = B(MK+KN)\cdot\text{dtype}$,输出总量 $S_{out} = B \cdot MN \cdot outB$。</p>
|
||
<p><b>两个不变量</b>(一切分析的起点):</p>
|
||
<ul class="tight">
|
||
<li>GM 流量下界 $= S_{in} + S_{out}$:输入至少读一遍、输出最终至少要写一遍到 GM,与 L2 策略无关;</li>
|
||
<li>L2 读入可用空间:$L2_{read} = L2 - S_{out}^{resident}$($S_{out}^{resident}$ 为驻留 L2 的输出量)——写出驻留 L2 会压缩读入空间,这是写出策略影响读入复用的通道。</li>
|
||
</ul>
|
||
<p><b>重复读倍率</b>:$r_{in}$ = GM 输入流量 / $S_{in}$。$r_{in} = 1$ 表示每个输入数据从 GM 只读一遍(后续复用全在 L2 命中)——这是 GM 输入流量的下界,L2 管理的全部目标就是让 $r_{in}$ 尽量接近 1。</p>
|
||
<p><b>先判定写出会不会 Bound</b>。平均写出带宽需求:</p>
|
||
<div class="math">$$
|
||
BW_{out} = \frac{S_{out}}{T_{MMAD}} = \frac{B \cdot MN \cdot outB}{2BMNK\,/\,(C \cdot Q_{16})} = \frac{C \cdot Q_{16} \cdot outB}{2K}
|
||
$$</div>
|
||
<p>只与 K、outB 有关(K 越小,单位时间输出越密)。例(BF16 输出,C·Q₁₆=432 TFLOPS):K=512 → 844 GB/s;K=256 → 1.69 TB/s,已超 GM 总线——此时<b>任何策略都写出 Bound</b>,L2 缓冲只能削峰(fixpipe 以 5.2TB/s 写 L2 吸收突发),平均速率仍受总线限制,应预期 Fixpipe 成为 $T_{total}$ 的 max 项。</p>
|
||
<p><b>分场景决策</b>:</p>
|
||
<p>**场景 A:$S_{in} + S_{out} \le L2$(全驻留)**。输入读一遍($r_{in}=1$),输出驻留 L2(dirty)异步回写 GM——写出走 5.2TB/s L2 写口,不与读争,也削平了 GM 写突发。无需切分。</p>
|
||
<p>**场景 B:$S_{in} \le L2$ 但 $S_{in} + S_{out} > L2$(输入能驻留,加上输出超了)<b>。策略:</b>输入驻留、输出直写 GM**。理由链:</p>
|
||
<ol class="tight">
|
||
<li>$S_{in} \le L2$ ⇒ 全部输入可驻留 L2,跨波次复用全部命中 ⇒ $r_{in} = 1$(GM 输入流量达到下界 $S_{in}$);</li>
|
||
<li>输出在本算子内只写不读、零复用收益;若输出也驻留 L2(dirty),超出 L2 的部分会把输入挤出——被挤出的输入后续得回 GM 重读 ⇒ $r_{in} > 1$,GM 流量超出下界;</li>
|
||
<li>故让输出直写 GM(fixpipe L0C→GM,不占 L2),把 128MB 全部留给输入,保住 $r_{in} = 1$——GM 总流量保持下界 $S_{in} + S_{out}$;</li>
|
||
<li>代价是输出即刻占用 GM 写带宽(与读共享总线),须校验总线不爆:$(S_{in} + S_{out})/T_{MMAD} \le W_{GM}$。</li>
|
||
</ol>
|
||
<p>例:B=8、M=N=4096、K=512、BF16——$S_{in}$≈67MB ≤ L2,$S_{out}$≈268MB 直写 GM;T_MMAD≈318µs,总流量速率 (67+268)MB/318µs ≈ 1.05TB/s < 1.6TB/s ✓。</p>
|
||
<p>**场景 C:$S_{in} > L2$(输入本身超)<b>。需要分组执行——把 mCnt×nCnt 个基本块划分为若干</b>执行组**,每组内所有基本块的输入工作集不超过 L2 可用空间。输出直写 GM(不占 L2 读入空间)。</p>
|
||
<p><b>L2 的软件可控手段</b>:L2 是 Cache 而非 Buffer,软件无法精确控制"哪些数据在 L2 里"。可用的控制手段:</p>
|
||
<ul class="tight">
|
||
<li><b>Cache Hint</b>(<code>SetL2CacheHint</code>):标记输入为 allocate(读入 L2)或 non-allocate(直读 GM 不过 L2);标记输出为 non-allocate(直写 GM 不占 L2);</li>
|
||
<li><b>CMO</b>(Cache Maintenance Operation):Prefetch/Writeback/Invalidate,在关键节点主动管理 L2 内容;</li>
|
||
<li><b>执行顺序</b>(swizzle):通过编排基本块的执行顺序,控制同一时刻的活跃工作集——<b>这是最主要的 L2 管理手段</b>。</li>
|
||
</ul>
|
||
<p><b>执行组的划分</b>:</p>
|
||
<p>*问题*:mCnt×nCnt 个基本块(每块输出 singleCoreM×singleCoreN),按什么粒度分组,使每组的输入工作集 ≤ L2?</p>
|
||
<p>*每组输入工作集*:一组覆盖 M 向 $m_{grp}$ 个基本块、N 向 $n_{grp}$ 个基本块,即覆盖输出区域 $[m_{grp} \cdot \text{singleCoreM},\; n_{grp} \cdot \text{singleCoreN}]$。该区域需要读入的输入:</p>
|
||
<div class="math">$$
|
||
WS_{grp} = B \cdot K \cdot \big(m_{grp} \cdot \text{singleCoreM} + n_{grp} \cdot \text{singleCoreN}\big) \cdot \text{dtype} \;\le\; L2
|
||
$$</div>
|
||
<p>*目标*:最小化组数(组数越少,输入从 GM 的重复读次数越少)。每行 A 被 $n_{grp}$ 个组各读一次,每列 B 被 $m_{grp}$ 个组各读一次:</p>
|
||
<div class="math">$$
|
||
r_{in} = \frac{n_{grp} \cdot M + m_{grp} \cdot N}{M + N}
|
||
$$</div>
|
||
<p>*求解*:约束 $m_{grp} \cdot \text{singleCoreM} + n_{grp} \cdot \text{singleCoreN} \le D$(其中 $D = L2/(B \cdot K \cdot \text{dtype})$),最小化 $n_{grp} \cdot M + m_{grp} \cdot N$。最优在组内 M/N 向基本块数与输出平面形状成正比时取到:</p>
|
||
<div class="math">$$
|
||
m_{grp} = \Big\lfloor \frac{D}{2 \cdot \text{singleCoreM}} \Big\rfloor,\qquad n_{grp} = \Big\lfloor \frac{D}{2 \cdot \text{singleCoreN}} \Big\rfloor
|
||
$$</div>
|
||
<p>总组数 $= \lceil mCnt/m_{grp} \rceil \times \lceil nCnt/n_{grp} \rceil$。</p>
|
||
<p><b>组内 swizzle</b>:每组内部按 ASW 滑窗蛇形执行(窗口 $W = \max\{d \mid d \mid C,\; d \le \lfloor\sqrt{C}\rfloor\}$,C=32 时 W=4),保证同一波 C 个核的活跃工作集最小。组间切换时输入整体换入——上一组的 A 行带和 B 列带全部失效,从 GM 重新读入下一组的数据。</p>
|
||
<p>*例*(B=64、M=N=2048、K=1024、BF16、singleCoreM=singleCoreN=256):$S_{in} = 64 \times (2048 \times 1024 + 1024 \times 2048) \times 2 = 537\text{MB} > 128\text{MB}$。$D = 128\text{MB}/(64 \times 1024 \times 2\text{B}) = 1024$ 元素。$m_{grp} = \lfloor 1024/(2 \times 256) \rfloor = 2$,$n_{grp} = 2$。每组覆盖 $[512, 512]$ 的输出区域,工作集 $= 64 \times 1024 \times (512+512) \times 2 = 128\text{MB} = L2$,恰好装满。总组数 $= (2048/256/2)^2 = 16$。$r_{in} = (2 \times 2048 + 2 \times 2048)/(2048+2048) = 2$——每行 A 被读 2 次,每列 B 被读 2 次。</p>
|
||
<p>块内分配用<b>错位分核</b>(对角线分配):线性块号先取 m,n 方向叠加随块号递增的相位偏移,使同一时刻各核落在 M×N 平面的不同对角线上——避免多核同一拍并发读同一行 A / 同一列 B 的同一地址(同地址并发读会串行化,等效带宽打折)。例(8 核、4×4=16 个基本块,k0~k7 为核号):</p>
|
||
<pre><code>行优先(不错位): 错位分核(对角线):
|
||
n0 n1 n2 n3 n0 n1 n2 n3
|
||
m0 k0 k1 k2 k3 m0 k0 k4 . .
|
||
m1 k4 k5 k6 k7 m1 . k1 k5 .
|
||
m2 . . . . m2 . . k2 k6
|
||
m3 . . . . m3 k7 . . k3
|
||
|
||
同一波:A 行带 m0 被 k0~k3 同读 同一波:每行带、每列带
|
||
(4 路同地址冲突) 最多 2 核同读(冲突 4→2)</code></pre>
|
||
<p>冲突度量与优选规则:</p>
|
||
<div class="math">$$
|
||
transConflict = \max\big(\lceil C / mCnt \rceil,\; \lceil C / nCnt \rceil\big) \le 6
|
||
$$</div>
|
||
<p>即同一时刻并发核访问同一 A/B 块的最大冲突数不超过阈值(经验值 6);切分方案中优先选尾波不满载占比小(拖尾 < 一半)的。遍历大方向由 calOrder 决定(0=M 优先、1=N 优先),按形状选共享矩阵更能驻留 L2 的方向。</p>
|
||
<p>补充:若输出会被后续算子立即消费(融合场景),输出驻留 L2 让下游读命中,场景 B/C 的策略反过来;本文按单算子边界分析。</p>
|
||
<h3>5.7 核内 tiling(BaseM/BaseN/BaseK——L0 级 tile,受 L0 容量直接约束)</h3>
|
||
<p>$\text{BaseM} \times \text{BaseN} \times 4\text{B} \times DB \le L0C$;$\text{BaseM} \times k_{L0} \times \text{dtype} \times 2 \le L0A$、$k_{L0} \times \text{BaseN} \times \text{dtype} \times 2 \le L0B$;内轴按 dValue 256B/512B 对齐。SingleCoreM/N 内部按 BaseM/BaseN 进一步切分为 L0 tile 逐个计算。L1 按容量开双缓冲,余量充足开 4 buffer。</p>
|
||
<h3>5.8 内部特化(参数极限,不是独立分支)</h3>
|
||
<p>单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次(L1 全载)。</p>
|
||
<h3>5.9 降核模式实现</h3>
|
||
<p>tiling 时 <code>usedCoreNum = ⌈P⌉</code>(不强制 C),SingleCoreM/N 在 L0C 容量内取最大($\text{singleCoreM} \times \text{singleCoreN} \times 4\text{B} \le L0C$),每核按标准核内流水(L1→L0→Cube→L0C→Fixpipe)处理自己的输出块;核间无共享无依赖,无需 swizzle 与 L2 切分。降核后 GM 并发搬移核数若 < minCoreNum,带宽利用率上限被压低——这正是降核区 case 时延的瓶颈所在,也是"时延绝对值小、不再继续优化"的定量注脚。</p>
|
||
<hr>
|
||
<h2>六、与源码实现的对比</h2>
|
||
<p>源码参考 [cann-ops-nn](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3)(DAV_3510/arch35)。以下按实现方案的 Step 0-8 逐维度对比。</p>
|
||
<h3>6.1 进入条件(IsCapable)</h3>
|
||
<p><b>源码</b>(<code>batch_matmul_v3_asw_basic_tiling.cpp</code> IsCapable):</p>
|
||
<pre><code>// 源码:https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3
|
||
1. A/B 非连续转置状态一致(混合则拒绝)
|
||
2. BatchA == BatchB(广播由 ITER_BATCH_BROADCAST 承接)
|
||
3. batchBias <= 1
|
||
4. dtype 限 FP16/BF16
|
||
// 无其他条件——兜底</code></pre>
|
||
<table><tr><th>维度</th><th>理论</th><th>源码</th><th>差异</th></tr>
|
||
<tr><td>并行度校验</td><td>$P = B \cdot MN \cdot 4B / L0C \ge C$</td><td><b>无</b></td><td>源码不检查 P,靠优先级序靠前分支截胡</td></tr>
|
||
<tr><td>降核模式</td><td>$P < C$ → usedCoreNum = ⌈P⌉</td><td><b>无</b></td><td>GetNumBlocks() 固定返回 32 核</td></tr>
|
||
<tr><td>batch 结构</td><td>无限制</td><td>BatchA == BatchB</td><td>源码排除广播(由分支 4 承接)</td></tr></table>
|
||
<p><b>影响</b>:源码缺少降核分支。P < C 的 case 进入 ASW_Basic 后,所有 32 核仍参与调度,但部分核无实际工作——引入不必要的调度开销。理论上这些 case 应走降核模式(usedCoreNum = ⌈P⌉)。</p>
|
||
<h3>6.2 BaseM / BaseN 确定(§5.1)</h3>
|
||
<p><b>源码流程</b>(<code>MatMulV3TilingHelper::GetRebalanceBlock</code>,<code>matmul_v3_tiling_helper.cpp</code> L387-492):</p>
|
||
<p>*第 1 步——平台指标*(动态读 platformInfo,适配不同产品形态):</p>
|
||
<div class="math">$$
|
||
hbmBW = freq \times aicNum \times ddrRate/1024,\qquad l2BW = freq \times aicNum \times l2Rate/1024
|
||
$$</div>
|
||
<div class="math">$$
|
||
computePower = freq \times 8 \times aicNum \; (\text{BF16}),\quad \text{FP32 时 } /16
|
||
$$</div>
|
||
<p>*第 2 步——cubeBoundEdge 闭式计算*(L418-419):</p>
|
||
<div class="math">$$
|
||
edge = \underbrace{\frac{l2BW}{computePower}}_{\text{① L2 满速供数基准}} + \underbrace{l2CacheUsage \cdot \Big(1-\frac{l2BW}{hbmBW}\Big) \cdot cmr}_{\text{② L2 工作集超容惩罚}} - \underbrace{\frac{1 + l2BW/hbmBW}{kValue}}_{\text{③ K 向复用修正}}
|
||
$$</div>
|
||
<p>其中 $cmr = (M{+}N)/(MN)$、$l2CacheUsage = \max(B \cdot (M{+}N) \cdot K \cdot dtype/L2,\; 1)$。判据:<b>每输出块相对搬运量</b> $1/baseM + 1/baseN \le edge$ ⟺ 该 tile 处于计算 Bound 侧。</p>
|
||
<p>三项物理含义(第一性推导):</p>
|
||
<ol class="tight">
|
||
<li><b>① 基准</b>:单核计算 $2MNK$ FLOP 时长为 $2MNK/Q_{16}$,搬运 $(M{+}N)K \cdot dtype$ 字节时长为 $(M{+}N)K \cdot dtype/l2BW$。计算 Bound ⟺ $2MNK/Q_{16} \ge (M{+}N)K \cdot dtype/l2BW$ ⟺ $1/M + 1/N \le 2 \cdot l2BW/(Q_{16} \cdot dtype)$——与 ① 同构($2/dtype$ 常数吸收进后面的 CUBE_BOUND_RATIO 余量)。</li>
|
||
</ol>
|
||
<ol class="tight">
|
||
<li><b>② 工作集超 L2 时抬高 edge → 倾向更小 tile</b>:$l2CacheUsage > 1$(工作集 $B(M{+}N)K \cdot dtype$ 超过 L2)时实际供数带宽部分掉到 HBM($hbmBW < l2BW$)。抬 edge 使判定放宽——较小 tile(工作集小、L2 命中率高、供数更接近 l2BW)也能通过计算 Bound 判定。<b>若坚持大 tile,工作集更大、超 L2 更多、供数掉到 HBM,反而访存 Bound</b>。</li>
|
||
</ol>
|
||
<ol class="tight">
|
||
<li><b>③ K 大时压低 edge → 倾向更大 tile</b>:K 大计算量大、天然计算 Bound,可接受更大 tile(更小搬运量)而不触访存 Bound。</li>
|
||
</ol>
|
||
<p>*第 3 步——枚举与评分*(L448-483):</p>
|
||
<ul class="tight">
|
||
<li>候选上界由多重约束闭式卡出:L0A/L0B 容量(minKL0)、baseMNBufferLimit(L0C/UB 面积)、bias table、K 内轴 512B 对齐(BMM 固定)、shape;内存 Bound 时对齐粒度 128、计算 Bound 时 64</li>
|
||
<li>枚举 baseM、baseN 从大到小递减,两层剪枝:</li>
|
||
<ul class="tight">
|
||
<li><b>skipCond</b>:最优解已 balance ≥ 0.9 时,若当前 param 更差(tile 更小)且超过 edge(访存 Bound)→ 跳过(param 单调增,后续更差)</li>
|
||
<li><b>FP32</b>:baseM/baseN < 64 且块数 > 核数 → 跳过</li>
|
||
</ul>
|
||
<li>更新评分(帕累托双目标):</li>
|
||
<ul class="tight">
|
||
<li><b>cubeBoundCond</b>:$param \le edge$ 且 balance 更高 → 更新,并把 edge 收紧为 param(<b>单调收敛</b>:进入计算 Bound 区域后只接受同样计算 Bound 且更均衡的解)</li>
|
||
<li><b>balanceCond</b>:综合分 $param/balanceRate$ 更小者胜(单位负载均衡率的搬运代价),相等取更均衡者</li>
|
||
</ul>
|
||
<li>收尾:GetBaseK(K 全载或 256B/128B/64B/32B/16 递减)、usedCoreNum = min(batch×mCore×nCore, 核数)、dbL0C 按容量置 2/1</li>
|
||
</ul>
|
||
<p><b>为什么源码这么做(设计考虑)</b>:cubeBound 是 <b>host 端闭式解析模型</b>,把"该 tile 是否计算 Bound"做成一次不等式比较,替代实测调优;edge 随 shape(K、工作集)和平台(带宽/频率)自适应;双目标帕累托保证解在"算存比"与"负载均衡"之间取平衡。</p>
|
||
<p><b>与理论最优的优劣判定</b>:</p>
|
||
<table><tr><th>维度</th><th>理论最优(§5.1)</th><th>源码</th><th>判定</th></tr>
|
||
<tr><td>tile 面积</td><td>L0C/4B = 65536(单缓冲满)</td><td>baseM/baseN 上限 256×256 = 65536</td><td>✓ 一致</td></tr>
|
||
<tr><td>形状</td><td>方形优先(L0A=L0B 同时装满、baseK 最大)</td><td>枚举 M/N 独立递减,不强制方形</td><td>⚠️ 源码可能产出非方形 base(如 256×192),L0B 未满、baseK 被小边限制——可加方形约束改进</td></tr>
|
||
<tr><td>baseK</td><td>min(L0A/2·BM, L0B/2·BN) 向下 16 对齐</td><td>GetBaseK:K 全载或 256B/128B/64B/32B/16 递减</td><td>✓ 一致</td></tr>
|
||
<tr><td>访存/计算 Bound 判定</td><td>§5.2 用粗粒度 Bound 判定(分支级)</td><td>cubeBound 三项解析模型(tile 级)</td><td>⚠️ 源码更精细,理论可吸收</td></tr>
|
||
<tr><td>负载均衡</td><td>§5.3 尾轮重切</td><td>balanceRate 尾块感知评分</td><td>见 §6.8</td></tr></table>
|
||
<p><b>结论</b>:两者在 tile 面积上殊途同归(都取 L0C 单缓冲满),理论用"方形优先"推导、源码用枚举寻优。源码的 cubeBound 模型是理论"算存比判定"在 tile 粒度上的精细实现,值得理论吸收;源码的不足是枚举不保持方形、baseM/N 硬上限 256 使 per-core tile 无法超过 L0 容量(见 §6.3)。</p>
|
||
<h3>6.3 SingleCoreM / SingleCoreN 确定(§5.2)</h3>
|
||
<p><b>源码事实:singleCore 与 base 是同一个参数——源码没有分层概念。</b></p>
|
||
<p>证据链:<code>ResetBaseDav3510</code>(L144-149)置 <code>baseM = baseN = 256</code>、<code>singleCoreM = baseM</code>;<code>CalL1TilingDefault</code>(L77-78)置 <code>singleCoreM = runInfo.baseM</code>、<code>singleCoreN = runInfo.baseN</code>;枚举后 <code>stepM = CeilDiv(singleCoreM, baseM) = 1</code>、<code>stepN = 1</code>。即<b>每核 tile = L0 tile = baseM×baseN(≤ 256×256)</b>,不存在"SingleCore 内部多个 L0 tile 多轮计算"的结构。</p>
|
||
<p><b>源码做法</b>:baseM/baseN 由 GetRebalanceBlock 枚举确定(§6.2 的 cubeBound 模型 + balanceRate ≥ 0.9 剪枝 + 尾块感知评分),一次枚举同时决定"每核 tile 大小"与"L0 tile 大小"——因为两者不区分。</p>
|
||
<p><b>为什么源码这么做(设计考虑)</b>:单一参数简化 tiling 生成与 kernel 实现——kernel 只需处理"每核一个 baseM×baseN tile"的循环,无需 stepM/stepN 多轮嵌套。代价是<b>每核 tile 被 L0 容量(≤ 256×256)硬性限制</b>。</p>
|
||
<p><b>与理论最优的优劣判定</b>(关键差异):</p>
|
||
<table><tr><th>维度</th><th>理论最优(§5.2)</th><th>源码</th><th>判定</th></tr>
|
||
<tr><td>分层</td><td>SingleCoreM/N ≥ BaseM/N 显式两层</td><td>singleCore = base,单层</td><td>⚠️ 源码是理论的退化特例</td></tr>
|
||
<tr><td>SingleCore 上限</td><td>L1 容量约束(可远超 256)</td><td>L0C 面积(≤ 256×256)</td><td>⚠️ 差异</td></tr>
|
||
<tr><td>mCnt/nCnt</td><td>最少切分 ⌈C/B⌉</td><td>⌈M/baseM⌉×⌈N/baseN⌉,base ≤ 256</td><td>⚠️ 差异</td></tr>
|
||
<tr><td>依据</td><td>搬入时延最小化(§5.2 拉格朗日)</td><td>cubeBound + balanceRate 枚举</td><td>部分一致</td></tr></table>
|
||
<p><b>定量示例</b>(B=64、M=N=2048、K=512、BF16,ASW 承接的 B≥C 且 IterBatch/MergeBatch 不满足的 case):</p>
|
||
<ul class="tight">
|
||
<li>理论:$P = \lceil C/B \rceil = 1$,先试不切分——$mCnt = nCnt = 1$、singleCoreM = 2048、singleCoreN = 2048。L1 检查:$2 \times (2048{+}2048) \times k_{L1} \times 2\text{B} \le 512\text{KB}$ ⟹ $k_{L1} \le 32$ 元素 = 64B < 256B 不满足 dValue。<b>K 分块仍不满足则退化</b>:$k_{L1} \ge 128$(256B)⟹ $2 \times 4096 \times 128 \times 2 = 2\text{MB} > 512\text{KB}$ 溢出 ⟹ 不切分不可行,进入情形 2(B < C 式切分)?此时 B ≥ C 但 L1 放不下,实际须切 M/N:按 $mCnt \times nCnt \ge \lceil C/B \rceil$ 不成立——真正约束是 L1:$2(mCnt 方向的分块)$… 精确说,理论在 L1 约束下求最小 mCnt×nCnt:$2 \cdot (\text{singleCoreM} + \text{singleCoreN}) \cdot k_{L1} \cdot dtype \le L1$ 与 $mCnt \cdot nCnt$ 的联合。取 $k_{L1}=128$:$\text{singleCoreM} + \text{singleCoreN} \le 512\text{KB}/(2 \times 128 \times 2) = 1024$。方形分配 ⟹ singleCoreM = singleCoreN = 512、mCnt = nCnt = 4、共 16 块/核分配 64×16=1024 块到 32 核。搬入时延 ∝ mCnt/M + nCnt/N = 4/2048×2 = 0.0039。</li>
|
||
<li>源码:baseM = baseN = 256(L0 上限),mCnt = nCnt = ⌈2048/256⌉ = 8、共 64 块/batch、64×64=4096 块。搬入时延 ∝ 8/2048×2 = 0.0078——<b>是理论的 2 倍</b>。</li>
|
||
</ul>
|
||
<p><b>结论</b>:当 L1 容量富余(K 小)时,理论的最少切分原则允许 singleCoreM/N 超过 256(上例 512),mCnt/nCnt 减半,GM→L1 总搬入时延(重复读)减半;源码的 singleCore = base ≤ 256 硬上限导致<b>过度切分</b>,L2 重复读翻倍。源码改进方向:引入 SingleCore/Base 分层,SingleCore 由 L1 容量决定(上限解除 L0 约束),stepM/stepN 多轮 L0 计算。这是理论对源码<b>最实质的一条改进建议</b>。</p>
|
||
<h3>6.4 mCnt / nCnt 与核间分配(§5.3)</h3>
|
||
<p><b>源码做法</b>:</p>
|
||
<div class="math">$$
|
||
mCnt = \Big\lceil \frac{M}{baseM} \Big\rceil,\qquad nCnt = \Big\lceil \frac{N}{baseN} \Big\rceil
|
||
$$</div>
|
||
<p>块→核映射:<code>UpdateBasicIndex</code>(<code>batch_mat_mul_v3_asw_block_advanced.h</code>)——<code>index = blockIdx + round × usedCoreNum</code>,<code>matIndex = index % (mCnt × nCnt)</code> 解出 batch 内 (m, n),B→M→N 线性字典序,与理论 §4 的线性映射一致;<code>batchIdx = index / (mCnt × nCnt)</code> 天然支持广播。usedCoreNum = min(batch×mCore×nCore, aicNum)(GetRebalanceBlock L489)——<b>注意源码也有"降核"</b>:当 batch×mCnt×nCnt < aicNum 时 usedCoreNum 自动收缩,比 §6.1 所述"无降核"更准确:源码无<b>独立的降核 tiling 模板</b>,但 GetRebalanceBlock 收尾会把 usedCoreNum 压到实际块数。</p>
|
||
<p><b>与理论最优的优劣判定</b>:</p>
|
||
<table><tr><th>维度</th><th>理论最优</th><th>源码</th><th>判定</th></tr>
|
||
<tr><td>切分数</td><td>最少切分:mCnt×nCnt = ⌈C/B⌉(§5.2)</td><td>mCnt = ⌈M/baseM⌉,baseM ≤ 256 固定</td><td>⚠️ 源码过度切分(§6.3 定量:搬入时延 2 倍)</td></tr>
|
||
<tr><td>长宽比</td><td>B < C 时方形分配(拉格朗日)</td><td>由 baseM/baseN 枚举自然产生</td><td>≈ 一致(方形成分由 256×256 上限保证)</td></tr>
|
||
<tr><td>核间分配</td><td>B→M→N 线性映射</td><td>一致</td><td>✓</td></tr>
|
||
<tr><td>降核</td><td>P < C → usedCoreNum = ⌈P⌉(§5.9)</td><td>usedCoreNum = min(batch×m×n, aicNum)</td><td>≈ 一致(实现路径不同)</td></tr></table>
|
||
<p><b>尾轮处理对比</b>(对应 §5.3):</p>
|
||
<ul class="tight">
|
||
<li>源码 <code>GetBalanceRateWithTail</code>(L223-246):尾块感知的负载均衡率用于<b>枚举评分</b>——mainRound = ⌈totalRound/C⌉-1,尾轮按 √ 拆两维估算(totalTailSplit = FloorDiv(usedCoreNum, 尾块数)),$tailRound = 1/(\sqrt{t} \times (\sqrt{t}+offset-1))$,$rate = (MN/C)/((mainRound+tailRound) \cdot baseM \cdot baseN)$。<b>但 BMM 场景(batchInfo ≠ nullptr)直接短路为简单比率</b>(L237-240:$rate = (B \cdot MN/C)/((mainRound+1) \cdot baseM \cdot baseN)$)——尾轮不做拆分估算,理由是 batch 维已摊薄尾块效应。</li>
|
||
<li>源码<b>不实际重切尾轮</b>,只通过枚举选择尾块占比小的 (baseM, baseN) 组合。</li>
|
||
<li>理论(§5.3):$n_{wave} \le 3$ 时 host 端计算切分因子 $s^*$ 并实际重切尾轮(下发第二套 tiling 参数),收益 $T_{block}(1-1/s^*)$。</li>
|
||
</ul>
|
||
<p><b>判定</b>:理论更优。源码对 BMM 一律不做尾轮拆分(含估算),对小 $n_{wave}$(2~3 轮)的 case 尾轮浪费可达 25%~50% 核时;理论的重切方案 host 端零 NPU 代价、收益可量化(访存 Bound 受 dValue 约束、计算 Bound 可到对齐底线)。理论方案可作为源码改进建议。</p>
|
||
<h3>6.5 Swizzle(§5.5)</h3>
|
||
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
|
||
<tr><td>窗口大小</td><td>$W = \max\{d : d \mid C, d \le \lfloor\sqrt{C}\rfloor\}$</td><td><code>GetAswWindowLen</code>:逻辑一致</td></tr>
|
||
<tr><td>蛇形</td><td>窗口行间 N 向反向,窗内不蛇形</td><td>一致(<code>rowIdx % 2 != 0</code> 时 n 反向)</td></tr>
|
||
<tr><td>尾窗</td><td>未提及</td><td>有 tailWindow 分支处理 mCnt 不整除窗长</td></tr></table>
|
||
<p><b>结论</b>:swizzle 实现与理论一致。源码的窗长公式与理论 $W = \max\{d : d \mid C, d \le \lfloor\sqrt{C}\rfloor\}$ 完全相同。但源码对细长 shape(mCnt 小或 nCnt≫mCnt)的方形窗假设不成立时,退化为行优先(<code>mainWindow = min(aswWindowLen, mCnt)</code>),未按 shape 长宽比自适应窗形。</p>
|
||
<h3>6.6 L2 管理(§5.6)</h3>
|
||
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
|
||
<tr><td>启用条件</td><td>$S_{in} > L2$ 时分组</td><td>isBigSize(>100MB) && cBatchDimAll < usedCoreNum && transConflict ≤ 6</td></tr>
|
||
<tr><td>冲突度量</td><td>transConflict ≤ 6</td><td>一致</td></tr>
|
||
<tr><td>写出策略</td><td>$S_{in} \le L2 < S_{in}+S_{out}$ 时输出直写 GM</td><td>仅 enableL2Cache flag + SetL2CacheHint,无显式输出 non-allocate 决策链</td></tr>
|
||
<tr><td>尾波控制</td><td>优先选尾波不满载占比小的方案</td><td>TAIL_CONFLICT_RATIO = 0.5</td></tr></table>
|
||
<p><b>差异分析</b>:源码有两个额外限制:</p>
|
||
<ol class="tight">
|
||
<li><b>100MB 阈值</b>:小于 100MB 的 case 不启用 L2 cache 管理——理论上 $S_{in} \le L2 = 128$ MB 时不需要分组,100MB < 128MB 留有余量,合理但是经验值;</li>
|
||
<li><b>cBatchDimAll < usedCoreNum</b>:batch 数小于核数时才启用——B ≥ C 时不做 L2 分块(由 ASW 承接时不分)。</li>
|
||
</ol>
|
||
<p>源码中理论要求的"输出直写 GM vs 驻留 L2"的写出策略联合决策(场景 B:$S_{in} \le L2 < S_{in}+S_{out}$)未见显式实现。</p>
|
||
<h3>6.7 AL1 全载特化(§5.8)</h3>
|
||
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
|
||
<tr><td>条件</td><td>M ≤ 256,batchA=1,A 驻留 L1,每核 ≥4 轮</td><td>一致(IsCapable L31-72)</td></tr>
|
||
<tr><td>A 搬移</td><td>GM→L1 一次搬入</td><td>一致(Nd2Nz DataCopy 一次搬入)</td></tr>
|
||
<tr><td>B 搬移</td><td>每基本块一次</td><td>一致</td></tr></table>
|
||
<p><b>结论</b>:AL1 全载是理论与源码<b>最吻合的分支</b>。源码注释有笔误("m should be larger than 256" 实为 ≤256),需注意。</p>
|
||
<h3>6.8 尾轮处理(§5.3)</h3>
|
||
<p>源码的尾轮相关逻辑分布在三处,与理论逐项对比:</p>
|
||
<table><tr><th>机制</th><th>源码</th><th>理论(§5.3)</th><th>判定</th></tr>
|
||
<tr><td>尾块感知评分</td><td><code>GetBalanceRateWithTail</code>:尾轮按 √ 拆两维估算后计入 rate</td><td>$\Delta T_{tail}$ 量化公式</td><td>≈ 一致(源码是估算、理论是闭式)</td></tr>
|
||
<tr><td>BMM 尾轮估算</td><td>batchInfo ≠ nullptr 时<b>短路为简单比率</b>,不做拆分估算</td><td>batch 维摊薄尾块效应</td><td>≈ 一致</td></tr>
|
||
<tr><td>实际重切</td><td><b>不做</b>(仅 AL1 全载的 <code>CalcTailBasicBlockAL1Full</code> 沿 N 切尾块逼近满载)</td><td>$n_{wave} \le 3$ 且 $r < C$ 时 host 端重切($s^*$ 分块 + 第二套 tiling 参数下发)</td><td>⚠️ 理论更优</td></tr>
|
||
<tr><td>重切约束</td><td>—</td><td>访存 Bound:dValue ≥ 256B、搬移量 ≥ min_TileSize、16 对齐;计算 Bound:仅 16 对齐</td><td>—</td></tr></table>
|
||
<p><b>判定</b>:源码对 BMM 的 ASW 路径不做尾轮重切,仅通过枚举选择尾块占比小的组合(balanceRate ≥ 0.9 剪枝)。对于 $n_{wave} = \lceil B \cdot mCnt \cdot nCnt/C \rceil \le 3$ 的 case,理论的重切方案可节省 $T_{block}(1-1/s^*)$ 的尾波时延($n_{wave}=2$、$r=8$ 时总时延省 25%),且 host 端零 NPU 代价。源码改进建议:在枚举收尾后增加尾轮重切判定($n_{wave} \le 3$ 时计算 $s^*$ 并生成第二套 tiling 参数)。</p>
|
||
<h3>6.9 降核模式(§5.9)</h3>
|
||
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
|
||
<tr><td>实现</td><td>usedCoreNum = ⌈P⌉,其余核闲置</td><td><b>未实现</b>——GetNumBlocks() 固定返回 32</td></tr></table>
|
||
<p><b>影响</b>:P < C 的 case(小 M/N、小 B)进入 ASW_Basic 后,所有 32 核参与调度但部分核无实际工作。理论上应只调度 ⌈P⌉ 核。这类 case 的时延绝对值小,但多余核的调度开销(tiling 计算、kernel 启动、上下文切换)是真实存在的。</p>
|
||
<h3>6.10 综合评价</h3>
|
||
<table><tr><th>维度</th><th>评价</th></tr>
|
||
<tr><td>核间分配 + swizzle</td><td>✓ 与理论一致</td></tr>
|
||
<tr><td>AL1 全载特化</td><td>✓ 与理论最吻合</td></tr>
|
||
<tr><td>尾轮处理</td><td>✓ 一致(不重切,选小尾波组合)</td></tr>
|
||
<tr><td>BaseM/BaseN 分层</td><td>⚠️ 源码无显式 SingleCore/Base 分层,baseM=256 实为 SingleCore 级</td></tr>
|
||
<tr><td>L2 管理</td><td>⚠️ 比理论保守(100MB 阈值 + batch 数限制),缺输出写出策略联合决策</td></tr>
|
||
<tr><td>降核模式</td><td>✗ 未实现</td></tr>
|
||
<tr><td>经验常数</td><td>⚠️ CUBE_BOUND_RATIO=0.85、balanceRate=0.9、transConflict=6、TAIL_CONFLICT_RATIO=0.5 等无官方文档推导,边界 case 值得实测复核</td></tr></table>
|
||
<p><b>总体判断</b>:源码的 ASW_Basic 实现在核间分配、swizzle、AL1 全载上与理论高度一致,是经实测调优的工程实现。理论分析的价值在于:</p>
|
||
<ol class="tight">
|
||
<li><b>补齐降核模式</b>——P < C 时 usedCoreNum = ⌈P⌉,避免无效核调度;</li>
|
||
<li><b>显式分层</b>——SingleCoreM/N 与 BaseM/N 分开,约束链更清晰;</li>
|
||
<li><b>写出策略联合决策</b>——$S_{in} \le L2 < S_{in}+S_{out}$ 时输出直写 GM 的显式判断;</li>
|
||
<li><b>经验常数的理论依据</b>——cubeBound 模型的三项分解(L2 供数/溢出惩罚/K 向复用)可以从第一性原理推导。</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-ops-nn 源码仓](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3)</li>
|
||
</ol>
|
||
</div>
|
||
</body>
|
||
</html> |