Files
matmul-analysis/BMM/BMM算子优化分析_Release/ASW_Basic分支分析_v1.6.html
2026-08-28 08:27:06 +00:00

954 lines
94 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.6</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.6 新增 §7 程序实现流程图源码与理论最优两张端到端流程图自包含完整推导链。历史v1.5 扩充 §6 源码对比cubeBound 模型完整解析与优劣判定)、修正 dValue 定义与尾轮重切约束。</blockquote>
<h2>摘要</h2>
<p>ASW_Basic 是 BMM 的兜底分支——核间切 M/N或混合切不做 batch 合并或 K 维切分。本文给出完整的时延建模、核间分配策略分析(证明 B 优先分组在任何场景下都不优于线性映射)、实现方案的逐步推导(含尾轮重切的 Bound 类型分级策略),以及与源码实现的逐维度对比。</p>
<p>v1.6 新增 §7 程序实现流程图:<b>从 case 输入B/M/K/N/dtype出发分别给出源码 ASW_Basic<code>BatchMatMulV3AswBasicTiling</code> 六阶段:进入判定 → ResetBase → cubeBound 枚举 → CalL1Tiling → 参数打包 → kernel 滑窗蛇形)与理论最优实现(八阶段:分支判定 → BaseM/N/K → SingleCoreM/N 有界枚举 → 核间分配 → 尾轮重切 → swizzle → L2 分组 → 端到端校验)两张完整流程图</b>每步标注计算公式与产出参数并给出两图差异对照。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 ≥ 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 = 1B ≥ 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 &gt; 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 &gt; 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} &lt; T_{MMAD}$(搬移被计算掩盖),候选间差异失效,此时以尾轮最优者为准</li>
</ol>
<p>*完整实例*B=8、M=N=2048、K=1024、BF16BaseM=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>64128B</td><td>✗ dValue &lt; 256B</td><td></td></tr>
<tr><td>(4, 2)</td><td>512 × 1024</td><td>80160B</td><td></td><td></td></tr>
<tr><td>(4, 4)</td><td>512 × 512</td><td>128256B</td><td>512×128×2 = 128KB ≥ 16KB</td><td>0.500</td></tr>
<tr><td>(8, 4)</td><td>256 × 512</td><td>160320B</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>256512B</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/NbaseM=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 在 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>
<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 &gt; 1$(工作集 $B(M{+}N)K \cdot dtype$ 超过 L2时实际供数带宽部分掉到 HBM$hbmBW &lt; 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、baseMNBufferLimitL0C/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 &lt; 64 且块数 &gt; 核数 → 跳过</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>收尾GetBaseKK 全载或 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 随 shapeK、工作集和平台带宽/频率)自适应;双目标帕累托保证解在"算存比"与"负载均衡"之间取平衡。</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×192L0B 未满、baseK 被小边限制——可加方形约束改进</td></tr>
<tr><td>baseK</td><td>min(L0A/2·BM, L0B/2·BN) 向下 16 对齐</td><td>GetBaseKK 全载或 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、BF16ASW 承接的 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 &lt; 256B 不满足 dValue。<b>K 分块仍不满足则退化</b>$k_{L1} \ge 128$256B⟹ $2 \times 4096 \times 128 \times 2 = 2\text{MB} &gt; 512\text{KB}$ 溢出 ⟹ 不切分不可行,进入情形 2B &lt; 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 = 256L0 上限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上例 512mCnt/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 &lt; 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 &lt; 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 &lt; 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\}$ 完全相同。但源码对细长 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>
<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 &lt; C$ 时 host 端重切($s^*$ 分块 + 第二套 tiling 参数下发)</td><td>⚠️ 理论更优</td></tr>
<tr><td>重切约束</td><td></td><td>访存 BounddValue ≥ 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 &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>七、程序实现流程图(源码 vs 理论最优)</h2>
<p>本节给出两张端到端程序实现流程图:<b>从 case 输入B, M, K, N, dtype 及转置/格式属性)开始,直到所有 tiling &amp; swizzle 参数被清晰计算出来</b>。图 1 严格对应源码实现(每个处理步骤标注所在函数),图 2 对应本文理论最优实现(每个处理步骤标注 §5 的公式出处§7.3 给出两图差异对照。</p>
<p>流程图图例约定:<code>[输入/常量]</code><code>&lt;处理与判定&gt;</code><code>→ 产出参数</code>;分支用 <code>├─/└─</code> 表示;<code>✓/✗</code> 表示通过/拒绝。</p>
<h3>7.1 源码 ASW_Basic 程序实现流程图</h3>
<p>源码入口:<code>BatchMatMulV3AswBasicTiling</code>op_host/op_tiling/arch35/batch_matmul_v3_asw_basic_tiling.cppDAV_3510 注册为第 9 优先级模板 <code>ASW_BASIC</code>。tiling 层依赖 <code>MatMulV3TilingHelper</code>matmul_v3_tiling_helper.cpp<code>MatMulV3BasicAswtTiling</code>kernel 层为 <code>BatchMatMulAswKernel</code> + <code>BatchMatMulAswBlock</code></p>
<p><b>阶段 0进入判定IsCapable</b></p>
<pre><code>[B, M, K, N, dtype, isATrans, isBTrans, hasBias, aFormat/bFormat]
&lt;A、B 非连续转置状态一致?&gt; ──否──▶ 不进入(回落 BASE 模板)
│是
&lt;batchA == batchB四维 batch 全部相等,不支持交叉广播)?&gt; ──否──▶ 不进入
│是
&lt;batchBias ≤ 1 dtype ∈ {FP16, BF16}B 为 NZ 且 C 为 FP32 时报错)&gt; ──否──▶ 不进入
│是
进入 ASW_BASIC</code></pre>
<p><b>阶段 1参数初始化ResetBase → ResetBaseDav3510</b></p>
<pre><code>&lt;ResetBaseDav3510&gt;
→ baseM = 256, baseN = 256"256 is better base"
→ baseK = 128B / aDtypeSizeBF16 取 64
→ stepM = stepN = 1, singleCoreK = K
→ singleCoreM = baseM, singleCoreN = baseN ← 不分层(本文 §6.3 的过度切分根源)
→ usedCoreNum = 32, dbL0C = 1, iterateOrder = ITER_COL_FIRST</code></pre>
<p><b>阶段 2cubeBound 枚举确定 baseM/baseN/baseKGetRebalanceBlock</b></p>
<pre><code>[platformInfo: hbmBW、l2BW、singleCoreComputePower = cube_freq×8]
&lt;算边缘cmr = (M+N)/(M·N), computePower = singleCorePower×32
l2CacheUsage = max(B·(M+N)·K·dtype / L2, 1)
cubeBoundEdge = l2BW/computePower
+ l2CacheUsage·(1 l2BW/hbmBW)·cmr
(1 + l2BW/hbmBW)/K &gt;
&lt;初始最优baseMBest = min(align16(M), 256)
baseNBest = min(align16(N), floor16(L0C/4B/baseMBest))
cubeBoundParamBest = 1/baseMBest + 1/baseNBest
isMemoryBound = cubeBoundParamBest &gt; cubeBoundEdge
fixpBoundEdge = M·N·hbmBW / ((M+N)·l2BW)&gt;
&lt;枚举上界maxBaseM = GetMaxBaseWithLimit(...)L0A 容量/bias 表/kL1 对齐/shape 约束)
maxBaseN = GetMaxBaseWithLimit(...)L0B 容量/bias 表/kL1 对齐/shape 约束)&gt;
&lt;初值baseM = clamp16(min(maxBaseM, 256), ≥16)
baseN = clamp16(min(maxBaseN, floor16(L0C/4B/baseM)), ≥16)
cubeBoundParam = 1/baseM + 1/baseN
cubeBoundEdge = cubeBoundEdge × 0.85CUBE_BOUND_RATIO
balanceRate = GetBalanceRateWithTail(...)&gt;
&lt;双层枚举curBaseM: maxBaseM → 16 步长 baseMAlignUnit
curBaseN: min(maxBaseN, L0C/4B/curBaseM) → 16 步长 baseNAlignUnit
curParam = 1/curBaseM + 1/curBaseN
curRate = GetBalanceRateWithTail(...)
① skip 剪枝balanceRate ≥ 0.9 且 curParam &gt; cubeBoundParam
且 curParam &gt; cubeBoundEdge 且 edge &gt; 0 → 跳过
② FP32 剪枝FP32 且 curParam &lt; edge 且 mCnt×nCnt &gt; 32
时 curBase 须 &gt; 64 → 否则跳过
③ cubeBoundCondcurParam ≤ edge 且 curRate &gt; balanceRate
→ 更新解(且 edge = curParam
④ balanceCond(curParam/curRate) &lt; (cubeBoundParam/balanceRate)
→ 更新解(或比值相等且 curRate 更大)&gt;
&lt;收敛baseM = min(align16(M), baseM), baseN = min(align16(N), baseN)
GetBaseKbaseK = min(align16(K), L0A/2/dtype/max(baseM, baseN))
A 转置且 B 非转置 → floor16否则按 256B 对齐或 {128,64,32,16} 候选
mCore = ⌈M/baseM⌉, nCore = ⌈N/baseN⌉
usedCoreNum = min(B·mCore·nCore, 32)
dbL0C = (baseM·baseN·4B·2 ≤ L0C) ? 2 : 1
ubDB = (baseM·baseN·4B ≤ UB) ? 2 : 1&gt;
→ baseM, baseN, baseK, usedCoreNum, dbL0C, ubDB</code></pre>
<p><b>阶段 3L1 粒度参数CalL1Tiling → CalL1TilingDefault</b></p>
<pre><code>&lt;枚举 stepK = 1 .. min(⌈K/baseK⌉, 8)
kL1 = baseK × stepK
容量约束:(baseM·kL1 + baseN·kL1)·dtype·2 ≤ L1
且 max(两侧)·2·2 ≤ L1
对齐约束K 内轴时 kL1·dtype ≥ 256B单次搬移量 ≥ L1_SINGLE_SIZE_LIMIT&gt;
→ stepKa = stepKb = kL1/baseK
→ depthA1 = depthB1 = stepK × 2双缓冲
→ singleCoreM = baseM, singleCoreN = baseN ← 仍不分层</code></pre>
<p><b>阶段 4L1 buffer 数与 API 级别DoOpTiling 尾部)</b></p>
<pre><code>&lt;abL1 = baseK·stepKa·(baseM + baseN)·aDtypeSizehasBias 时加 baseN·biasB
l1BufferNum = (abL1 × 4 ≤ L1) ? 4 : 2&gt;
&lt;CheckTensorApiSupport
FP32 且 K &gt; 阈值 且 ND 且非连续 → splitKkernel 内切 K 保精度)
BatchMatMulV3 节点且连续且非 splitK → apiLevel = TENSOR_LEVEL
否则 → apiLevel = BASIC_LEVEL&gt;</code></pre>
<p><b>阶段 5参数打包GetTilingKey + GetTilingData</b></p>
<pre><code>&lt;tilingKey = Trans | Model=BASIC | ApiLevel&gt;
&lt;GetTilingDataProcess(BatchMatMulV3BasicTilingData)
usedCoreNum, m = M, n = N, k = K
mL1 = min(align16(M), baseM·stepM)
nL1 = min(align16(N), baseN·stepN)
kL1 = baseK × min(min(stepKb, stepKa), 4)
baseM/baseN/baseK、mTailCnt/nTailCntCalcTailBasicBlock 尾块拆分)
mmadParam、l1BufferNum、l0cDB = dbL0C&gt;
&lt;GetTilingDataProcess(MatMulV3TilingData)
tCubeTilingsingleCoreM/N/K、baseM/N/K、depthA1/B1、
stepM/N、stepKa/Kb、iterateOrder、dbL0C、BatchNum
aswWindowLen = GetAswWindowLen() ← 从 ⌊√32⌋=5 向下找最大因子 → 4
l2CacheDisable = SetDisableL2cache(mL1, kaL1, kbL1, nL1)
← totalSize(=A+B+C) vs L2 → 全/左/右矩阵 L2 uncache 标志&gt;
→ 完整 tilingData 下发 NPU</code></pre>
<p><b>阶段 6kernel 运行时映射BatchMatMulAswBlock + BatchMatMulAswKernel::Process</b></p>
<pre><code>&lt;InitblockBaseM/N = tiling.baseM/baseN
mCnt = ⌈M/baseM⌉, nCnt = ⌈N/baseN⌉
mBaseTail = M (mCnt1)·baseM, nBaseTail = N (nCnt1)·baseN
totalCnt = B·mCnt·nCnt
round = ⌈totalCnt/usedCoreNum⌉
mainWindow = min(aswWindowLen, mCnt)
mainRow = mCnt/mainWindow 1
tailWindow = mCnt mainRow·mainWindow
FP32 且 K &gt; 8192 且 B 为 NDsplitKRound = ⌈K/8192⌉保精度切 K&gt;
&lt;Processfor j in 0..round1
newBlockIdx = GetCurrentBlockIdx()
UpdateBasicIndex(j, newBlockIdx) ← swizzle滑窗 + 蛇形
index = newBlockIdx + j·usedCoreNum (轮次线性映射)
matIndex = index % (mCnt·nCnt)
rowIdx = matIndex / nCnt / mainWindow
主窗行mCntIndex = rowIdx·mainWindow + matIndex % mainWindow
nCntIndex = (matIndex / mainWindow) % nCnt
尾窗行mCntIndex = mainRow·mainWindow + tailIndex % tailWindow
nCntIndex = (tailIndex / tailWindow) % nCnt
蛇形rowIdx 为奇数 → nCntIndex = nCnt 1 nCntIndex
UpdateBlockParamssingleCoreM = 尾块 ? mBaseTail : baseM
singleCoreN = 尾块 ? nBaseTail : baseN
CalcGMOffsetindex 反解 (batch, m, n) 索引 → A/B/C 的 GM 偏移
mm_.SetSingleShape(singleCoreM, singleCoreN, singleShapeK)
mm_.Iterate()核内标准流水L1→L0A/L0B→MMAD→L0C→Fixpipe&gt;</code></pre>
<p><b>源码产出参数汇总</b>(一次 case 的全部 tiling &amp; swizzle 参数):</p>
<table><tr><th>参数</th><th>产出阶段</th><th>公式/取值</th></tr>
<tr><td>baseM, baseN</td><td>阶段 2</td><td>cubeBound 双层枚举(初值 256×256</td></tr>
<tr><td>baseK</td><td>阶段 2</td><td><code>min(align16(K), L0A/2/dtype/max(baseM,baseN))</code></td></tr>
<tr><td>singleCoreM/N</td><td>阶段 1/3</td><td><code>= baseM/baseN</code>(不分层)</td></tr>
<tr><td>singleCoreK</td><td>阶段 1</td><td><code>= K</code></td></tr>
<tr><td>kL1, stepKa, stepKb</td><td>阶段 3</td><td>stepK 枚举(容量 + 256B 对齐)</td></tr>
<tr><td>depthA1, depthB1</td><td>阶段 3</td><td><code>stepK × 2</code></td></tr>
<tr><td>stepM, stepN</td><td>阶段 1</td><td><code>= 1</code></td></tr>
<tr><td>dbL0C, ubDB</td><td>阶段 2</td><td>容量判断 <code>×2 ≤ L0C ? 2 : 1</code></td></tr>
<tr><td>l1BufferNum</td><td>阶段 4</td><td><code>abL1×4 ≤ L1 ? 4 : 2</code></td></tr>
<tr><td>usedCoreNum</td><td>阶段 2</td><td><code>min(B·mCore·nCore, 32)</code></td></tr>
<tr><td>mTailCnt, nTailCnt</td><td>阶段 5</td><td>CalcTailBasicBlock尾块再拆分</td></tr>
<tr><td>aswWindowLen</td><td>阶段 5</td><td><code>⌊√32⌋=5</code> 向下最大因子 <code>→ 4</code></td></tr>
<tr><td>l2CacheDisable</td><td>阶段 5</td><td>SetDisableL2cachetotalSize vs L2</td></tr>
<tr><td>iterateOrder</td><td>阶段 1</td><td>ITER_COL_FIRST列优先</td></tr>
<tr><td>apiLevel</td><td>阶段 4</td><td>TENSOR_LEVEL / BASIC_LEVEL</td></tr>
<tr><td>mmadParam</td><td>阶段 5</td><td>HF32 / S8S4 shift</td></tr></table>
<hr>
<h3>7.2 理论最优实现的程序实现流程图</h3>
<p>理论实现按 §5 的推导链组织§5.1→§5.9host 端全流程闭式计算,每个判定均可用 B/M/K/N/dtype 与芯片规格的闭式表达。</p>
<p><b>阶段 0分支进入与降核判定§二/§5.9</b></p>
<pre><code>[B, M, K, N, dtype]
&lt;P = ⌈C/B⌉最少切分块数
P &lt; C 且 K 不够走 StreamK 切分? ──是──▶ 降核模式§5.9
usedCoreNum = ⌈P⌉singleCoreM×singleCoreN
在 L0C 容量内取最大,无 swizzle/L2 切分
│否
进入 ASW_Basic 主体</code></pre>
<p><b>阶段 1BaseM/BaseN/BaseK§5.1/§5.7L0 级 tile</b></p>
<pre><code>&lt;BaseM = BaseN = ⌊√(L0C/4B)⌋16 = 256方形L0C 单缓冲满载)
例外M &lt; 256 → BaseM = ⌊M⌋16、BaseN = min(65536/BaseM, N)(被迫非方形)
N &lt; 256 对称
L0C 流水选择UnitFlag vs dbL0C 边界判定,见 L0C 流水机制分析 v1.0
计算/写出 Bound 且 2·tile &gt; L0C → UnitFlag 单缓冲tile 上限翻倍)
MTE2 Bound → 单缓冲即可;带变换/多 batch 分区 → 双缓冲
baseK = min(L0A/(2·BaseM·dtype), L0B/(2·BaseN·dtype)) 向下 16 对齐&gt;
→ BaseM, BaseN, baseK, unitFlag/dbL0C</code></pre>
<p><b>阶段 2SingleCoreM/N 确定§5.2 完整枚举,本次 v1.5 重写的核心)</b></p>
<pre><code>&lt;P = ⌈C/B⌉
├─ P = 1B ≥ C
│ mCnt = nCnt = 1, singleCoreM = M, singleCoreN = N
│ kL1 = min(K, ⌊L1/(2(M+N)·dtype)⌋16)
│ 约束 3 检查kL1·dtype ≥ 256B、M·kL1·dtype ≥ min_TileSize、
│ kL1·N·dtype ≥ min_TileSize
│ ✓ → 确定tile 跟随 M/N✗ → 强制切分进入 P&gt;1 流程
└─ P &gt; 1B &lt; C——有界枚举
枚举空间mCnt ≤ ⌈M/BaseM⌉、nCnt ≤ ⌈N/BaseN⌉约束 4 上界)
且 B·mCnt·nCnt ≥ C约束 1
对每个候选执行评估流水线:
① 对齐sM = align16(⌈M/mCnt⌉), sN = align16(⌈N/nCnt⌉)
② 约束 4sM 为 BaseM 整数倍、sN 为 BaseN 整数倍 → 否则剔除
③ 约束 2kL1 = min(K, ⌊L1/(2(sM+sN)·dtype)⌋16)
④ 约束 3kL1·dtype ≥ 256B、sM·kL1·dtype ≥ min_TileSize、
kL1·sN·dtype ≥ min_TileSize → 否则剔除
⑤ 目标T_MTE2 = MN/(C/B)·(1/sM + 1/sN)·kL1·dtype/BWpc
最优选取T_MTE2 最小;并列取 r = B·mCnt·nCnt mod C 最大&gt;
→ singleCoreM, singleCoreN, kL1, mCnt, nCnt, r</code></pre>
<p><b>阶段 3核间分配§5.3/§5.4</b></p>
<pre><code>&lt;usedCoreNum = min(B·mCnt·nCnt, C)(降核场景为 ⌈P⌉
线性映射:块 (b, m, n) 字典序编号,核 i 处理块 i, i+C, i+2C, ...
切分维度选择(共享代价从低到高):
切 B零共享先试→ 切 MKN·dtype ≤ L2 则右矩阵驻留 L2
→ 切 N对称→ 混合切swizzle + L2 切分管理)→ 降核&gt;</code></pre>
<p><b>阶段 4尾轮重切§5.3</b></p>
<pre><code>&lt;n_wave = ⌈B·mCnt·nCnt/C⌉, r = B·mCnt·nCnt mod C
n_wave ≤ 3 且 r &gt; 0 ──否──▶ 不重切r=0 无尾波n_wave≥4 收益 &lt;25%
│是
&lt;判定 Bound 类型:
访存 BoundT_MTE2 ≥ T_MMADs_max = min(sN·dtype/256B,
kL1·sN·dtype/min_TileSize, sN/16)
计算 BoundT_MMAD &gt; T_MTE2s_max = sN/16
s* = min(⌊C/r⌋, s_max)
切分方向:优先沿 NA 行带完整、L2 中 A 不变);
N 向对齐卡住sN/16 &lt; C/r改沿 MA 的 dValue=kL1·dtype 不变)&gt;
→ s*, 尾轮 tiling 变体host 端预处理,零 NPU 代价)</code></pre>
<p><b>阶段 5swizzle 与错位分核§5.5/§5.6</b></p>
<pre><code>&lt;W = max{d : d | C, d ≤ ⌊√C⌋}C=32 → W=4
执行序:窗口内 M 向 W 个基本块最快 → 扫满 W 行进下一列 N
→ 窗口扫完进下一窗口,奇数窗口行 N 向蛇形反转
错位分核:同一波 C 核落对角线上,冲突数
transConflict = max(⌈C/mCnt⌉, ⌈C/nCnt⌉) ≤ 6经验阈值
calOrder按形状选共享矩阵更能驻留 L2 的遍历方向0=M 优先 / 1=N 优先)&gt;
→ W, 遍历顺序, calOrder, transConflict 校验</code></pre>
<p><b>阶段 6L2 分组§5.6,工作集超 L2 时)</b></p>
<pre><code>&lt;S_in = B(MK+KN)·dtype, S_out = B·MN·outB
├─ S_in + S_out ≤ L2 → 场景 A全驻留r_in = 1无需分组
├─ S_in ≤ L2 &lt; S_in + S_out → 场景 B输入驻留 + 输出直写 GM
│ (校验 (S_in+S_out)/T_MMAD ≤ W_GM 总线不爆)
└─ S_in &gt; L2 → 场景 C分组执行
D = L2/(B·K·dtype)
m_grp = ⌊D/(2·sM)⌋, n_grp = ⌊D/(2·sN)⌋
组数 = ⌈mCnt/m_grp⌉ × ⌈nCnt/n_grp⌉
r_in = (n_grp·M + m_grp·N)/(M+N)
软件手段Cache HintA/B allocate、C non-allocate+ CMO
+ 执行顺序(组内 swizzle最主要的 L2 管理手段)&gt;
→ m_grp, n_grp, cacheHint, 执行组划分</code></pre>
<p><b>阶段 7核内参数汇总与端到端校验§3.1</b></p>
<pre><code>&lt;全部参数定稿:
BaseM/BaseN/baseK、singleCoreM/singleCoreN、kL1、mCnt/nCnt、usedCoreNum、
unitFlag/dbL0C、l1BufferNum、s*尾轮、W、calOrder、m_grp/n_grp
端到端时延校验:
T = max(T_MTE2, T_MMAD, T_FIX) + T_drain含尾轮修正 ΔT_tail^re
不满足预期 → 回溯阶段 2 调整 singleCore 候选重新评估&gt;
→ 完整 tiling &amp; swizzle 参数集下发 NPU</code></pre>
<p><b>理论产出参数汇总</b>(与源码参数的对应关系):</p>
<table><tr><th>参数</th><th>产出阶段</th><th>公式/取值</th></tr>
<tr><td>BaseM, BaseN</td><td>阶段 1</td><td><code>⌊√(L0C/4B)⌋16 = 256</code>方形M/N 过小被迫跟随)</td></tr>
<tr><td>baseK</td><td>阶段 1</td><td><code>min(L0A/(2·BaseM·dtype), L0B/(2·BaseN·dtype))</code> ↓16</td></tr>
<tr><td>singleCoreM/N</td><td>阶段 2</td><td>P=1 不切分(=M/NP&gt;1 有界枚举 + T_MTE2 最小</td></tr>
<tr><td>kL1</td><td>阶段 2</td><td><code>min(K, ⌊L1/(2(sM+sN)·dtype)⌋16)</code></td></tr>
<tr><td>mCnt, nCnt</td><td>阶段 2</td><td><code>⌈M/sM⌉</code>, <code>⌈N/sN⌉</code></td></tr>
<tr><td>usedCoreNum</td><td>阶段 0/3</td><td><code>min(B·mCnt·nCnt, C)</code>;降核 <code>⌈P⌉</code></td></tr>
<tr><td>unitFlag/dbL0C</td><td>阶段 1</td><td>Bound 类型 + 容量联合判定</td></tr>
<tr><td>l1BufferNum</td><td>阶段 7</td><td>L1 余量 → 2/4 buffer</td></tr>
<tr><td>s*</td><td>阶段 4</td><td><code>min(⌊C/r⌋, s_max)</code>Bound 分级约束)</td></tr>
<tr><td>W</td><td>阶段 5</td><td>`max{d : d</td><td>C, d ≤ ⌊√C⌋} = 4`</td></tr>
<tr><td>calOrder</td><td>阶段 5</td><td>共享矩阵驻留方向选择</td></tr>
<tr><td>m_grp, n_grp</td><td>阶段 6</td><td><code>⌊D/(2·sM)⌋</code>, <code>⌊D/(2·sN)⌋</code></td></tr>
<tr><td>cacheHint</td><td>阶段 6</td><td>A/B allocate、C non-allocate</td></tr></table>
<hr>
<h3>7.3 两张流程图的差异对照</h3>
<table><tr><th>步骤</th><th>源码 ASW_Basic</th><th>理论最优</th><th>差异本质</th></tr>
<tr><td>进入判定</td><td>转置一致性/等 batch/dtype 白名单</td><td>P 计算 + 广播 + 降核判定</td><td>源码只拦不支持的输入;理论从并行度出发</td></tr>
<tr><td>BaseM/N</td><td>硬编码 256 起 + cubeBound 枚举收缩</td><td>L0C 单缓冲 256 + UnitFlag 流水</td><td>源码无 UnitFlag理论按 Bound 类型选流水手段</td></tr>
<tr><td>baseK</td><td><code>L0A/2/dtype/max(baseM,baseN)</code></td><td><code>min(L0A/(2·BaseM), L0B/(2·BaseN))/dtype</code></td><td>源码取 max 保守(矩形 tile 下另一侧溢出风险);理论方形双侧用满</td></tr>
<tr><td>singleCore</td><td><code>= baseM/baseN</code>(不分层)</td><td>≥ Base独立枚举P=1 不切分)</td><td>源码 K 小时过度切分,搬入时延可达理论 2 倍§6.3</td></tr>
<tr><td>目标函数</td><td>cubeBoundParam + balanceRate 帕累托0.85/0.9 经验常数)</td><td>T_MTE2 闭式最小</td><td>源码多经验常数;理论有闭式表达式与完备性论证</td></tr>
<tr><td>核间分配</td><td><code>usedCoreNum = min(B·mCore·nCore, 32)</code></td><td>同 + 降核模式⌈P⌉</td><td>源码无降核P&lt;C 时仍全核调度)</td></tr>
<tr><td>尾轮</td><td>CalcTailBasicBlock尾块再拆分不重切</td><td>尾轮重切 s*host 零代价Bound 分级约束)</td><td>源码不重切,依赖枚举时选尾波小的组合</td></tr>
<tr><td>swizzle</td><td>aswWindowLen = 4 + 蛇形UpdateBasicIndex</td><td>同公式W 推导自足迹最小化)</td><td>一致§6.5</td></tr>
<tr><td>错位分核</td><td>每轮 <code>GetCurrentBlockIdx</code> 动态取块</td><td>transConflict ≤ 6 + calOrder 显式建模</td><td>理论显式建模冲突上限与遍历方向</td></tr>
<tr><td>L2 管理</td><td>SetDisableL2cachetotalSize vs L2 → uncache 标志)</td><td>三场景分组 + 写出联合决策 + Cache Hint/CMO</td><td>源码无执行组划分、无输出直写决策§6.6</td></tr>
<tr><td>UnitFlag</td><td>未启用</td><td>计算 Bound 启用tile 内 512B 块流水)</td><td>流水手段代际差异</td></tr></table>
<hr>
<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)batch_mat_mul_v3_asw_basic_tiling.cpp入口/IsCapable/DoOpTiling、batch_mat_mul_v3_asw_al1_full_load_basic_tiling.cppAL1 全载特化)</li>
<li>[cann-ops-nn matmul 源码仓](https://gitcode.com/cann/ops-nn/tree/master/matmul/mat_mul_v3)matmul_v3_tiling_helper.cppResetBase/GetRebalanceBlock/CalL1Tiling/GetL0C2Out、matmul_v3_basic_aswt_tiling.cppBasicAswt、matmul_v3_base_tiling_advanced.hGetTilingData/aswWindowLen/SetDisableL2cache</li>
</ol>
</div>
</body>
</html>