Files
matmul-analysis/BMM/BMM算子优化分析_Release/ASW_Basic分支分析_v1.1.html

435 lines
45 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASW_Basic 分支理论最优实现分析 v1.1</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>目标芯片:昇腾 950PRDAV_3510。本文从《BMM 算子优化分析 v0.98》第八章抽出独立成篇自包含完整推导链。v1.1 新增与源码实现的对比分析。</blockquote>
<h2>摘要</h2>
<p>ASW_Basic 是 BMM 的兜底分支——核间切 M/N或混合切不做 batch 合并或 K 维切分。本文给出完整的时延建模、核间分配策略分析(证明 B 优先分组在任何场景下都不优于线性映射)、实现方案的逐步推导,以及尾轮处理策略。</p>
<p>v1.1 新增 §七:与 [cann-ops-nn](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3) 源码实现的逐维度对比。核心结论:<b>源码的 swizzle、核间分配、AL1 全载与理论高度一致主要差距在降核模式未实现、IsCapable 无并行度校验、以及大量经验常数硬编码。</b></p>
<hr>
<h2>一、问题定义与执行模型</h2>
<h3>1.1 分支定位</h3>
<p>BMM 中,当 B ≥ Cbatch 数 ≥ AIC 核数)时核间切 B 是免费的(每核独立处理若干 batch无共享无依赖由 IterBatch/MergeBatch 承接。当 B &lt; 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──&gt; L1 ──MTE1──&gt; L0A/L0B ──MMAD──&gt; L0C ──Fixpipe──&gt; 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>
<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 &lt; C$(切 B 买不满核),或 $B \ge C$ 但 IterBatch/MergeBatch 条件不满足时的兜底</li>
<li><b>降核模式</b>$P &lt; 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 &lt; C 且 K 也不够格走 StreamK 时,并行度凑不满核。此时与其强行把 M/N 切得更碎tile 跌破 min_TileSize、dValue 跌破 128B搬移效率崩塌反而更慢不如<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} &gt; 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 &lt; C$(单 batch 块数不够填满所有核)**</p>
<p>线性映射:每波横跨 $\lceil C/(mCnt \cdot nCnt) \rceil$ 个 batch——这些 batch 的数据同时活跃。B 分组:每组 $C_g$ 核但只有 $mCnt \cdot nCnt &lt; C_g$ 个块 → <b>组内有空闲核</b>。线性映射至少所有核都有活干。<b>线性严格更优。</b></p>
<p>**场景 IV$mCnt \cdot nCnt &lt; 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 尾轮的定义</h3>
<p>当 $B \cdot mCnt \cdot nCnt \;\%\; C \neq 0$ 时,总块数不能被 C 整除,最后一波(尾轮)不满载——只有 $B \cdot mCnt \cdot nCnt \;\%\; C$ 个核有活干,其余核空闲。</p>
<h3>5.2 尾轮是否重新切分?</h3>
<p><b>不重新切分。</b> 尾轮使用与主轮相同的 SingleCoreM/N、BaseM/N——理由</p>
<ol class="tight">
<li><b>重新切分的收益有限</b>:尾轮只有一波,时延 = 单块时延。即使重新切分把 C 核都用满,单块时延也不会缩短(计算量/核数不变,只是每核分到的块更小,但启动开销不变)</li>
<li><b>重新切分的代价真实</b>:需要额外的 tiling 参数(第二套 SingleCoreM/N/BaseM/N、kernel 分支逻辑、L1 buffer 重新分配——工程复杂度高</li>
<li><b>尾轮占比小</b>:总波数 $= \lceil B \cdot mCnt \cdot nCnt / C \rceil$,尾轮只占 1/总波数。总波数越大,尾轮影响越小</li>
</ol>
<p><b>源码实现</b><code>MatMulV3TilingHelper::GetRebalanceBlock</code>):使用 balanceRate ≥ 0.9 评估负载均衡尾块感知评分GetBalanceRateWithTail。尾轮不重新切分仅通过选择使尾波占比尽量小的 mCnt/nCnt 组合来优化。</p>
<h3>5.3 尾轮影响的量化</h3>
<p>设总块数 $N_{blk} = B \cdot mCnt \cdot nCnt$,总波数 $n_{wave} = \lceil N_{blk} / C \rceil$,尾波块数 $r = N_{blk} \bmod C$$r = 0$ 时无尾波)。</p>
<p>尾波导致的额外时延(相对于完美整除):</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>其中 $T_{block}$ 为单块时延。相对于总时延的比例:</p>
<div class="math">$$
\frac{\Delta T_{tail}}{T_{total}} \approx \frac{1 - r/C}{n_{wave}}
$$</div>
<p>总波数 $n_{wave}$ 越大,尾波影响越小。当 $n_{wave} \ge 4$ 时,尾波影响 &lt; 25%。</p>
<hr>
<h2>六、实现方案</h2>
<h3>Step 0BaseM / 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→362mCnt/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×18132761 元素)</td><td>tile N 写出 ∥ tile N+1 计算)</td><td></td><td>max($T_{comp}$, $T_{write}$)</td></tr>
<tr><td>UnitFlag 单缓冲</td><td>256×25665536 元素)</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=1256×256×4B×2 &gt; L0C——即<b>源码已经是单缓冲 + 无 UnitFlag</b>tile 到顶但无流水交叠。</p>
<p>*建议*:对计算 Bound 的 case 启用 UnitFlagMMAD 与 Fixpipe 流水并行),可将单 tile 时延从 $T_{comp} + T_{write}$ 降至 $\max(T_{comp}, T_{write})$。对访存 Bound 的 caseMTE2 BoundUnitFlag 收益小([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 的长宽比跟随 SingleCoreM/SingleCoreN进而跟随 M/N对齐 16 的倍数。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>
<h3>Step 1SingleCoreM / 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>*约束链*</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></p>
<div class="math">$$
\text{singleCoreM} \cdot k_{L1} \cdot \text{dtype} \ge min\_TileSize,\qquad k_{L1} \cdot \text{singleCoreN} \cdot \text{dtype} \ge min\_TileSize
$$</div>
<p><b>约束 4——SingleCoreM/N 是 BaseM/N 的整数倍</b>(工程实现要求,保证 L0 tile 边界对齐)。</p>
<p>*选取策略*:在满足约束 1 的前提下SingleCoreM/N 尽量大(搬移效率和 L2 复用最大化)。长宽比跟随 M/N$\text{singleCoreM}/\text{singleCoreN} \approx M/N$),对齐到 BaseM/BaseN 的整数倍。</p>
<p>*例*B=8、M=N=2048、K=1024、BF16$\lceil C/B \rceil = 4$,取 $mCnt = nCnt = 2$ → singleCoreM = singleCoreN = 1024。BaseM = BaseN = $\lfloor\sqrt{32768}\rfloor_{16} = 176$。SingleCoreM/BaseM = 1024/176 ≈ 5.8 → 取 5整数倍→ singleCoreM = 880。约束 2$k_{L1} \le 512\text{KB}/(2 \times 1760 \times 2\text{B}) = 74$ → 取 6416 对齐)= 128B ✓。</p>
<h3>Step 2mCnt / 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>
<h3>Step 3核间切分维度选择按共享代价从低到高</h3>
<p>切 B零共享先试→ 切 M右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2→ 切 N对称→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见 Step 8</p>
<h3>Step 4swizzle——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=32W=4M̃=8Ñ=8数字为块的全局执行顺序一波 32 块):</p>
<pre><code>窗口0N 正向) 窗口1N 蛇形反向)
ν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→窗口1A 行整体换血μ0..3 → μ4..7),此时 B 列带的连续性决定换血成本——不蛇形则下一窗口从 ν0 开始LRU 上最久未用、早已被挤出 L2 的冷带),蛇形则延续上一窗口末尾的 ν7最热线带<b>蛇形只标在窗口行号上</b>(源码 <code>BatchMatMulAswBlock::UpdateBasicIndex</code>:仅 <code>rowIdx</code> 为奇时 n 反向,窗内 m 最快序不反向),正是这个收益结构的直接实现。</li>
</ul>
<h3>Step 5L2 分组(工作集超 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 写出时若驻留 L2dirty会压缩读入可用空间若直写 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 TFLOPSK=512 → 844 GB/sK=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$),输出驻留 L2dirty异步回写 GM——写出走 5.2TB/s L2 写口,不与读争,也削平了 GM 写突发。无需切分。</p>
<p>**场景 B$S_{in} \le L2$ 但 $S_{in} + S_{out} &gt; L2$(输入能驻留,加上输出超了)<b>。策略:</b>输入驻留、输出直写 GM**。理由链:</p>
<ol class="tight">
<li>$S_{in} \le L2$ ⇒ 全部输入可驻留 L2跨波次复用全部命中 ⇒ $r_{in} = 1$GM 输入流量达到下界 $S_{in}$</li>
<li>输出在本算子内只写不读、零复用收益;若输出也驻留 L2dirty超出 L2 的部分会把输入挤出——被挤出的输入后续得回 GM 重读 ⇒ $r_{in} &gt; 1$GM 流量超出下界;</li>
<li>故让输出直写 GMfixpipe 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 直写 GMT_MMAD≈318µs总流量速率 (67+268)MB/318µs ≈ 1.05TB/s &lt; 1.6TB/s ✓。</p>
<p>**场景 C$S_{in} &gt; 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 OperationPrefetch/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} &gt; 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>(对角线分配):线性块号先取 mn 方向叠加随块号递增的相位偏移,使同一时刻各核落在 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切分方案中优先选尾波不满载占比小拖尾 &lt; 一半)的。遍历大方向由 calOrder 决定0=M 优先、1=N 优先),按形状选共享矩阵更能驻留 L2 的方向。</p>
<p>补充:若输出会被后续算子立即消费(融合场景),输出驻留 L2 让下游读命中,场景 B/C 的策略反过来;本文按单算子边界分析。</p>
<h3>Step 6核内 tilingBaseM/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>Step 7内部特化参数极限不是独立分支</h3>
<p>单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次L1 全载)。</p>
<h3>Step 8降核模式实现</h3>
<p>tiling 时 <code>usedCoreNum = ⌈P⌉</code>(不强制 CSingleCoreM/N 在 L0C 容量内取最大($\text{singleCoreM} \times \text{singleCoreN} \times 4\text{B} \le L0C$每核按标准核内流水L1→L0→Cube→L0C→Fixpipe处理自己的输出块核间无共享无依赖无需 swizzle 与 L2 切分。降核后 GM 并发搬移核数若 &lt; 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>7.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 &lt;= 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 &lt; 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 &lt; C 的 case 进入 ASW_Basic 后,所有 32 核仍参与调度,但部分核无实际工作——引入不必要的调度开销。理论上这些 case 应走降核模式usedCoreNum = ⌈P⌉</p>
<h3>7.2 BaseM / BaseN 确定Step 0</h3>
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
<tr><td>目标</td><td>BaseM × BaseN = L0C/(2×4B) = 32768先把 L0C 用满</td><td>baseM=256, baseN=256硬编码 "256 is better base"</td></tr>
<tr><td>长宽比</td><td>跟随 M/N</td><td>由 cubeBound 模型寻优</td></tr>
<tr><td>baseK</td><td>min(L0A/2·BaseM, L0B/2·BaseN),无 dValue 要求</td><td>baseK = 128B/dtype最小高效粒度</td></tr></table>
<p><b>源码的 cubeBound 模型</b><code>GetRebalanceBlock</code><code>matmul_v3_tiling_helper.cpp</code> L387-492</p>
<div class="math">$$
cubeBoundEdge = \frac{l2BW}{computePower} + l2CacheUsage \cdot \Big(1 - \frac{l2BW}{hbmBW}\Big) \cdot cmr - \frac{1 + l2BW/hbmBW}{kValue}
$$</div>
<p>三项含义:① L2 供数速率 ÷ Cube 耗数速率(理论阈值);② 工作集超 L2 时的访存惩罚(抬高 edge倾向更大 tile③ K 向复用修正K 越大 edge 越小,偏 compute bound。乘以 CUBE_BOUND_RATIO=0.85 余量。枚举 baseM/baseN 递减balanceRate ≥ 0.9 剪枝。</p>
<p><b>差异分析</b>:理论(修正前)按双缓冲取 L0C 满载的一半32768 元素),源码用 256×256=65536 元素——<b>源码的 baseM×baseN 恰好等于 L0C 单缓冲上限</b>256×256×4B = 256KB = L0C。源码的 dbL0C 逻辑:<code>dbL0C = (baseM × baseN × 4B × 2 &gt; L0C) ? 1 : 2</code>——baseM=baseN=256 时 512KB &gt; 256KB自动选 dbL0C=1单缓冲</p>
<p>这说明源码的 baseM/baseN 不是 L0 级 tile 尺寸,而是 SingleCoreM/N 级参数。源码中 L0 级 tile 由 stepM/stepN 二次切分决定per-core tile = baseM × stepM。理论的分层SingleCoreM ≥ BaseM在源码中无显式概念。</p>
<p><b>UnitFlag 现状</b>BMM ASW kernel 中 <code>unitFlag = 0</code><code>block_mmad_iterbatch.h</code> L315 注释:"each l0 only process one block, disable unit flag"——MMAD 与 Fixpipe 之间是<b>指令级同步</b>(整个 L0C tile 算完才写出)。若启用 UnitFlagMMAD 每完成一个 16×16×16 块512BFixpipe 立即写出——tile 内部细粒度流水替代 tile 间粗粒度流水,单缓冲即可达到双缓冲的流水效果。对计算 Bound 的 caseUnitFlag 可将单 tile 时延从 $T_{comp} + T_{write}$ 降至 $\max(T_{comp}, T_{write})$。</p>
<h3>7.3 SingleCoreM / SingleCoreN 确定Step 1</h3>
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
<tr><td>概念</td><td>独立于 L0由 GM→L1 效率 + L2 复用决定</td><td>无显式概念per-core tile = baseM × stepM</td></tr>
<tr><td>约束</td><td>并行度 + L1 容量 + 搬移效率 + BaseM 整数倍</td><td>cubeBound 模型 + balanceRate ≥ 0.9</td></tr>
<tr><td>选取策略</td><td>尽量大(搬移效率最大化)</td><td>枚举寻优cubeBound + 尾块感知)</td></tr></table>
<p><b>关键差异</b>:理论把 SingleCoreM/N 和 BaseM/N 分为两层SingleCoreM ≥ BaseM源码中 baseM=256 实际上已经是 SingleCoreM 级参数。理论的分层更清晰,源码更保守但经实测调优。</p>
<h3>7.4 核间分配Step 2</h3>
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
<tr><td>分配策略</td><td>B→M→N 线性映射</td><td>一致(<code>index = blockIdx + round × usedCoreNum</code></td></tr>
<tr><td>B 优先分组</td><td>证明不优</td><td>源码确实采用线性映射</td></tr>
<tr><td>尾轮</td><td>不重新切分</td><td>一致balanceRate ≥ 0.9 剪枝)</td></tr></table>
<p><b>结论</b>:核间分配策略与理论一致。源码的 <code>UpdateBasicIndex</code><code>batch_mat_mul_v3_asw_block_advanced.h</code>)实现了 B→M→N 线性映射 + 划窗蛇形。</p>
<h3>7.5 SwizzleStep 4</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\}$ 完全相同。但源码对细长 shapemCnt 小或 nCnt≫mCnt的方形窗假设不成立时退化为行优先<code>mainWindow = min(aswWindowLen, mCnt)</code>),未按 shape 长宽比自适应窗形。</p>
<h3>7.6 L2 管理Step 5</h3>
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
<tr><td>启用条件</td><td>$S_{in} &gt; L2$ 时分组</td><td>isBigSize(&gt;100MB) &amp;&amp; cBatchDimAll &lt; usedCoreNum &amp;&amp; transConflict ≤ 6</td></tr>
<tr><td>冲突度量</td><td>transConflict ≤ 6</td><td>一致</td></tr>
<tr><td>写出策略</td><td>$S_{in} \le L2 &lt; 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 &lt; 128MB 留有余量,合理但是经验值;</li>
<li><b>cBatchDimAll &lt; usedCoreNum</b>batch 数小于核数时才启用——B ≥ C 时不做 L2 分块(由 ASW 承接时不分)。</li>
</ol>
<p>源码中理论要求的"输出直写 GM vs 驻留 L2"的写出策略联合决策(场景 B$S_{in} \le L2 &lt; S_{in}+S_{out}$)未见显式实现。</p>
<h3>7.7 AL1 全载特化Step 7</h3>
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
<tr><td>条件</td><td>M ≤ 256batchA=1A 驻留 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>7.8 尾轮处理</h3>
<table><tr><th>维度</th><th>理论</th><th>源码</th></tr>
<tr><td>尾轮重切</td><td>不重切(收益有限、工程代价真实)</td><td>一致balanceRate ≥ 0.9 剪枝)</td></tr>
<tr><td>尾轮量化</td><td>$\Delta T_{tail}/T_{total} \approx (1-r/C)/n_{wave}$</td><td><code>GetBalanceRateWithTail</code>L223-246</td></tr></table>
<p><b>结论</b>:尾轮处理策略一致。源码通过选择使尾波占比尽量小的 mCnt/nCnt 组合来优化,而非重新切分尾轮。</p>
<h3>7.9 降核模式Step 8</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 &lt; C 的 case小 M/N、小 B进入 ASW_Basic 后,所有 32 核参与调度但部分核无实际工作。理论上应只调度 ⌈P⌉ 核。这类 case 的时延绝对值小但多余核的调度开销tiling 计算、kernel 启动、上下文切换)是真实存在的。</p>
<h3>7.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 &lt; C 时 usedCoreNum = ⌈P⌉避免无效核调度</li>
<li><b>显式分层</b>——SingleCoreM/N 与 BaseM/N 分开,约束链更清晰;</li>
<li><b>写出策略联合决策</b>——$S_{in} \le L2 &lt; 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>