Files
matmul-analysis/BMM算子优化分析_Release/ASW_Basic分支分析_v1.4.html
2026-08-27 09:40:15 +00:00

572 lines
60 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.4</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。本文为 ASW_Basic 分支的独立分析自包含完整推导链。v1.4 修正 dValue 定义与尾轮重切约束,新增与源码实现的对比分析。</blockquote>
<h2>摘要</h2>
<p>ASW_Basic 是 BMM 的兜底分支——核间切 M/N或混合切不做 batch 合并或 K 维切分。本文给出完整的时延建模、核间分配策略分析(证明 B 优先分组在任何场景下都不优于线性映射)、实现方案的逐步推导(含尾轮重切的 Bound 类型分级策略),以及与源码实现的逐维度对比。</p>
<p>核心结论:**B 优先分组不优于线性映射;尾轮重切在 host 端零代价、$n_{wave} \le 3$ 时应重切且计算 Bound 下 dValue 可放宽;源码的 swizzle/核间分配/AL1 全载与理论高度一致主要差距在降核模式未实现、IsCapable 无并行度校验、尾轮未按 Bound 类型区分重切策略。**</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>
<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非转置时连续维为 NdValue = $\text{singleCoreN} \cdot dtype$;转置时连续维为 KdValue = $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 &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 跌破 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} &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 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→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 的长宽比<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 = 4BaseM=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>方形 tileBaseM=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=128M 限制、BaseN=512L0C 面积限制)——此时长宽比跟随 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 &lt; 256或 N &lt; 256BaseM = $\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 小时必然 &lt; 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>情形 1B ≥ CP = 1</b>——不切分,$mCnt = nCnt = 1$$\text{singleCoreM} = M$、$\text{singleCoreN} = N$。<b>tile 跟随 M/N非方形</b>。前提是 L1 容量与 dValue 满足(约束 2/3不满足时须 K 分块($k_{L1} &lt; K$)或退化为情形 2。</p>
<p><b>情形 2B &lt; CP &gt; 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 &lt; 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 &lt; C 的强制切分场景,不适用于 B ≥ C 的不切分场景。</p>
<p>*SingleCoreM/N 的具体确定过程*host 端枚举,与尾轮处理联动):</p>
<ol class="tight">
<li>计算最少切分 $P = \lceil C/B \rceil$</li>
<li><b>若 P = 1</b>$mCnt = nCnt = 1$$\text{singleCoreM} = M$、$\text{singleCoreN} = N$;检查约束 2L1 容量)与约束 3dValue满足则确定不满足则 K 分块($k_{L1} = \lfloor L1/(2(M{+}N) \cdot dtype) \rfloor_{16}$,须 $k_{L1} \cdot dtype \ge 256\text{B}$),仍不满足则进入步骤 3</li>
<li><b>若 P &gt; 1</b>:枚举 $(mCnt, nCnt)$$mCnt \cdot nCnt \ge P$,从 $\sqrt{PM/N}$ 附近的整数开始(方形优先),检查约束 2/3/4L1 容量、搬移效率、BaseM/N 整数倍)</li>
<li>计算尾波占比:$r = B \cdot mCnt \cdot nCnt \bmod C$,优先选 $r = 0$ 或 $r$ 大的组合</li>
<li>若 $n_{wave} = \lceil B \cdot mCnt \cdot nCnt / C \rceil \le 3$ 且 $r &gt; 0$,计算尾轮重切参数 $s^*$ 并评估重切后收益</li>
<li>选总时延最短的组合:$T_{total} = (n_{wave} - 1) \cdot T_{block} + T_{tail}$,其中 $T_{tail} = T_{block}/s^*$(重切)或 $T_{block}$(不重切)</li>
</ol>
<p>*与源码的差异*:源码用 cubeBound 模型 + balanceRate ≥ 0.9 剪枝,不解耦 SingleCoreM/N 与 BaseM/NbaseM=256 已是 SingleCore 级参数L0 tile 由 stepM/stepN 二次切分。理论的两层分离使约束链更清晰——SingleCoreM/N 由 L1 + 并行度 + 搬移效率决定BaseM/N 由 L0C 决定,各司其职。</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>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 在 hostCPU上完成不占 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} &gt; 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 &lt; C/r$),改沿 M 切A 矩阵 dValue = $k_{L1} \cdot dtype$ 不变,仅受对齐约束 $\text{singleCoreM}/16$)。</p>
<p><b>结论</b>$n_{wave} \le 3$ 且 $r &lt; C$ 时应重切尾轮——host 端零代价NPU 端收益 $T_{block}(1-1/s^*)$。$n_{wave} \ge 4$ 时收益 &lt; 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=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>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 写出时若驻留 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>5.7 核内 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>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>(不强制 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>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 &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>6.2 BaseM / BaseN 确定§5.1</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>6.3 SingleCoreM / SingleCoreN 确定§5.2</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>6.4 核间分配§5.3</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>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\}$ 完全相同。但源码对细长 shapemCnt 小或 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} &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>6.7 AL1 全载特化§5.8</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>6.8 尾轮处理§5.3</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>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 &lt; 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 &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>