Files
matmul-analysis/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html
2026-08-26 06:50:59 +00:00

524 lines
54 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>BMM 算子优化分析 v0.94 — 昇腾 950PR</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>BMM 算子优化分析v0.94</h1>
<blockquote>目标芯片:昇腾 950PRDAV_3510。所有分支进入条件只含 case 形状参数B、M、N、K、dtype与芯片规格参数。</blockquote>
<hr>
<h2>一、算子功能与接口说明</h2>
<p>完成带 batch 的矩阵乘:<code>C = A @ B + bias</code></p>
<ul class="tight">
<li>左矩阵 A<code>[BatchA, M, K]</code>dtype典型 ND可带转置</li>
<li>右矩阵 B<code>[BatchB, K, N]</code>dtype典型 ND可带转置</li>
<li>偏置 bias<code>[B, 1, N]</code>,固定 ND可为空</li>
<li>输出 C<code>[BatchC, M, N]</code>BatchC = broadcast(BatchA, BatchB)</li>
</ul>
<hr>
<h2>二、符号与芯片参数约定</h2>
<table><tr><th>符号</th><th>含义</th><th>950PR 取值</th></tr>
<tr><td>C</td><td>AIC 核数aicNum</td><td>32</td></tr>
<tr><td>Q₁₆</td><td>单核 Cube BF16 峰值算力</td><td>486/C ≈ 15.2 TFLOPS</td></tr>
<tr><td>Q_AIV</td><td>AIV 向量求和吞吐64 核合计)</td><td>64×128 fp32/拍×1.65GHz ≈ 13.5 Tops/s</td></tr>
<tr><td>L1</td><td>每核 L1 Buffer</td><td>512KB</td></tr>
<tr><td>L0A / L0B</td><td>每核 L0A / L0B</td><td>64KB / 64KB</td></tr>
<tr><td>L0C</td><td>每核 L0CFP32 累加4B/元素)</td><td>256KB</td></tr>
<tr><td>L2</td><td>L2 Cache 容量</td><td>128MB</td></tr>
<tr><td>W_L2</td><td>L2 读写带宽</td><td>5.2TB/s</td></tr>
<tr><td>W_GM</td><td>GM 带宽(读写共享)</td><td>1.6TB/s</td></tr>
<tr><td>R₁₆</td><td>16bit 对应位宽算存比</td><td>≈607.5 FLOP/元素</td></tr>
<tr><td>dValue</td><td>单数据块内数据连续排布长度</td><td>推荐 256B/512B不建议 &lt;128B</td></tr>
<tr><td>min_TileSize</td><td>确保高带宽利用率的单块搬移数据量最小值</td><td>16KB</td></tr>
<tr><td>min_DatamountPerCore</td><td>确保高带宽利用率的单核搬移数据量最小值</td><td>480KB</td></tr>
<tr><td>minCoreNum</td><td>确保高带宽利用率的并行搬移核数最小值</td><td>≈0.8C = 26</td></tr></table>
<p>以上数值基于 950PR 实测分析总结;对搬移带宽利用率的影响重要性排序为:<b>核数 &gt; 单核搬移总数据量 &gt; 单分块大小 &gt; dValue</b>。不同 NPU 芯片数值可能略有差异,换芯片时逻辑结构不变、只换常数表。</p>
<hr>
<h2>三、最优实现分析</h2>
<h3>3.1 性能模型</h3>
<p>BMM 的执行是核内多级硬件流水的并行——Cube 计算MMAD、GM/L2→L1MTE2、L1→L0MTE1、L0C 写出Fixpipe各流水级时延可被双缓冲相互掩盖</p>
<div class="math">$$
T_{total} = \max\big(T_{MMAD},\; T_{MTE2},\; T_{MTE1},\; T_{Fixpipe}\;[,\;T_{Reduce}]\big)
$$</div>
<p><b>总时延 = 最慢一级流水</b>,优化的关键是对瓶颈级的优化。由此得到设计自由度——<b>瓶颈交换</b>搬移是瓶颈时可牺牲算力冗余计算换搬移效率计算是瓶颈时可牺牲搬移重复读取换计算效率。MergeBatch 是前者的典型ASW_Basic 切 M/N 是后者的典型。</p>
<p>case 固有算存比与 16bit 位宽平衡点:</p>
<div class="math">$$
AI = \frac{2MN}{M+N},\qquad AI_{full} = \frac{2MNK}{MK+KN+MN}
$$</div>
<div class="math">$$
R_{16} = \frac{\text{Cube 峰值算力}}{\text{GM 带宽} / \text{元素字节数}} = \frac{486\ \text{TFLOPS}}{1.6\ \text{TB/s} \,/\, 2\ \text{B}} \approx 607.5\ \text{FLOP/元素}
$$</div>
<p>其中 486 TFLOPS 是乘加各计一次后的标称算力;分母中的 2B 是 16bit 元素字节数,作用是把 GM 带宽折算成元素速率。</p>
<p>$AI &lt; R_{16}$ → 访存 Bound瓶颈在 MTE2反之计算 Bound瓶颈在 MMAD</p>
<h3>3.2 实现本质逻辑</h3>
<p>BMM 的实现本质是把数据分块tile由全部 AIC 核并行 + 串行完成这些分块的计算,再组合成最终结果:</p>
<div class="math">$$
C[B,M,N] = \Big\{\,C[B_u, M_i, N_j] = \sum_k A[B_u, M_i, K_k] \cdot B[B_u, K_k, N_j]\,\Big\}
$$</div>
<p>分块有 4 个维度B、M、N、K。<b>核间怎么分这 4 个维度,就是分支划分的第一性问题</b>(核内分块是第二性问题,属于各分支内部 tiling</p>
<p>四个维度的核间切分特征(后续一切推导的基石):</p>
<table><tr><th>切分维度</th><th>读入特征</th><th>计算特征</th><th>写出特征</th></tr>
<tr><td>切 B</td><td>核间零重复读(每个数据块只被 1 个核读取);<b>核内是否重复读另有条件</b>——若 L1 放不下单 batch 完整的 M、N 维输入K 维可切段放入kL1&lt;K单 batch 计算中切 M 会重复读 B、切 N 会重复读 A</td><td>每个输出块由 1 个核独立完成,无核间依赖</td><td>只写最终结果,无中间结果</td></tr>
<tr><td>切 M / 切 N</td><td>切 M 则同一右矩阵块被多核<b>重复读</b>;切 N 则同一左矩阵块被多核重复读</td><td>每个输出块由 1 个核独立完成,无核间依赖</td><td>只写最终结果,无中间结果</td></tr>
<tr><td>切 K</td><td>每个数据块只被固定的 1 个核读取,零重复读</td><td>每个输出块由<b>多核共同</b>完成,存在核间依赖</td><td><b>有中间结果写出,需核间 Reduce 归约</b></td></tr></table>
<p>差异的根本原因B 维在数学上独立(逐 batch 独立矩阵乘),切 B 核间天然零重复、零依赖K 维有 L0C 累加机制——核内切 K 时多轮 mmad 在 L0C 原地累加、中间结果不出核,一旦切到核间,部分和必须写出 workspace 再归约——<b>切 K 是唯一同时破坏"累加不出核"和"输出独占"的切法</b></p>
<p>4 维的任意非空子集共 $2^4-1=15$ 种切分组合,任何实现方案必属其一(完备):</p>
<table><tr><th></th><th>组合</th><th>共同特征</th><th>优化重心</th></tr>
<tr><td>纯 B</td><td>{B}</td><td>核间零重复读(核内重复读取决于 L1 驻留形态);无中间写出</td><td>搬移效率 / 计算效率</td></tr>
<tr><td>含 M/N 不含 K</td><td>{M},{N},{M,N},{B,M},{B,N},{B,M,N}</td><td>核间可能有重复读</td><td>重复读尽量少L2+swizzle 吸收)+ 搬移/计算效率</td></tr>
<tr><td>含 K</td><td>其余 8 种</td><td>有中间结果写出 + 归约</td><td>计算效率,且归约时延不能成为新瓶颈</td></tr></table>
<p>核间切分价格严格排序cost(切 B) = 0 &lt; cost(切 M/N) ≪ cost(切 K)。切 B 核间免费;切 M/N 的重复读可被 128MB L25.2TB/s vs GM 1.6TB/s+ swizzle 大部分吸收;切 K 的归约流量 ∝ grid_K×输出量且引入核间同步是结构性代价。<b>整条分支决策树就是:按价格从低到高购买并行度,买不够才加价。</b></p>
<h3>3.3 分支推导</h3>
<ol class="tight">
<li><b>问题规约</b>BatchA=1 或 BatchB=1 → 折叠转普通 Matmul转MatmulK=0 / K=1 → Cube 无用,走 AIV 向量通路(特殊分支,与切分正交的前置判断);</li>
<li><b>先买免费的 B</b>B ≥ C 时切 B 可满核。单 batch M×N 够大 → 逐 batch 算IterBatchM×N 太小 → 多 batch 合并成大 tile 算冗余算力换搬移效率MergeBatch</li>
<li><b>B 买不满,加价买 M/N</b>:切 M/N 或混合切,重复读交给 L2 + swizzleASW_Basic</li>
<li><b>B/M/N 都买不满,才买昂贵的 K</b>:核间切 K + 归约StreamK</li>
</ol>
<p>由此得 6 大分支:<b>转Matmul、特殊分支、IterBatch、MergeBatch、ASW_Basic、StreamK</b>。重叠区(如 B≥C 且 M×N 中等时 MergeBatch 与 IterBatch 都合法)由端到端时延模型 $T_{total}$ 仲裁;分支体系保证候选集完备无冗余。</p>
<hr>
<h2>四、可转 Matmul 分支</h2>
<h3>进入分支条件</h3>
<div class="math">$$
BatchA = 1 \;\lor\; BatchB = 1
$$</div>
<p>解释:单边 batch=1 的 BMM 与 Matmul 只差一个维度标签,直接复用 Matmul 的成熟优化体系。</p>
<h3>实现方案</h3>
<ul class="tight">
<li><b>BatchB=1</b>:左矩阵 <code>[B,M,K]</code> 的 batch 维与 M 维在 ND 下内存相邻紧排,直接视图为 <code>[B·M,K]</code>,输出布局逐元素一致——零重排、零 split免费转换</li>
<li><b>BatchA=1</b>:右矩阵折叠为 <code>[K, B·N]</code> 需一次真实转置重排O(B·K·N)),且输出存在置换需 scatter——有代价。A 较小($MK\cdot\text{dtype} \le L1$)时优先 A 常驻 L1、留在 BMM 分支内广播友好形态A 较大时按广播扩展后分别预估"BMM 分支"与"重排+转Matmul"的时延,择优。</li>
</ul>
<hr>
<h2>五、MergeBatch 分支</h2>
<p>核间切 B每核 $b_{core}$ 个 batch核间无同步核内把 b 个 batch 合并计算:$[b,M,K]@[b,K,N] \Rightarrow [bM,K]@[K,bN]=[bM,bN]$BlockTrace 取块对角线得 $[b,M,N]$。交叉项被算出但丢弃(浪费比例 (b1)/b——进入该分支的 case 必然访存 Bound条件 5 保证),浪费的算力被搬移时延掩盖。</p>
<h3>进入分支条件(汇总)</h3>
<p>同时满足b0 = 单次合并计算的 batch 数下限b0 ≥ 2</p>
<ol class="tight">
<li>$BatchA = BatchB \;\land\; b_{core} = B/C \ge 2b_0$</li>
<li>$2 \cdot (b_0 M)(b_0 N) \cdot 4\text{B} \le L0C$</li>
<li>$b_{core} \cdot (MK + KN) \cdot \text{dtype} \ge min\_DatamountPerCore$</li>
<li>$\max(MK,\; KN) \cdot \text{dtype} \ge min\_TileSize$</li>
<li>$\dfrac{2MN}{M+N} &lt; \dfrac{R_{16}}{b_0}$</li>
</ol>
<h3>逐条解释</h3>
<ol class="tight">
<li><b>batch 关系与每核份额</b>:无广播才能逐 batch 对应合并;每核至少分到 $2b_0$ 个 batch——$b_0$ 是合并搬移有收益的最小合并数(取 22 组起步才能构成合并组间乒乓流水(即 $b_{core} \ge 4$)。</li>
</ol>
<p> <b>MergeBatch vs IterBatch 分界建模</b></p>
<p> *执行模型*两分支的核间切分相同——B 维切分到 C 核,每核 $b_{core} = B/C$ 个 batch核内不切 M/N。<b>核心差异在 GM→L1 搬移粒度</b></p>
<ul class="tight">
<li><b>IterBatch</b>:逐 batch 搬移——每 batch 的 $A[M,K]+B[K,N]$ 作为独立 DMA 操作搬入 L1源码 <code>ndNum = curIterBatchL1</code>,多块独立寻址),然后在 L1 内逐 batch 计算</li>
<li><b>MergeBatch</b>:合并搬移——$b_0$ 个 batch 的 $A'[b_0M,K]+B'[K,b_0N]$ 合并为一个大矩阵,作为<b>单次 DMA 操作</b>搬入 L1源码 <code>ndNum = 1</code>,单块连续搬移),然后合并计算</li>
</ul>
<p> *符号*</p>
<table><tr><th>符号</th><th>含义</th><th>表达式</th></tr>
<tr><td>$k_{L1}$</td><td>L1 级 K 分块粒度(双缓冲)</td><td>$\min\big(K,\; L1/(2(M{+}N)\cdot\text{dtype})\big)$</td></tr>
<tr><td>$n_K$</td><td>K 分块数</td><td>$\lceil K/k_{L1} \rceil$</td></tr>
<tr><td>$T_{load}$</td><td>每 K 分块搬移时延</td><td>$k_{L1}(M{+}N)\cdot\text{dtype}/BW_{pc}$</td></tr>
<tr><td>$T_{comp}$</td><td>每 K 分块计算时延</td><td>$2MN \cdot k_{L1}/Q_{16}$</td></tr>
<tr><td>$T_{write}$</td><td>单 batch 输出写回时延</td><td>$MN \cdot outB/W_{GM}$</td></tr>
<tr><td>$T_{cmd}$</td><td>单次 GM→L1 DMA 搬移固定开销</td><td>描述符配置 + 地址生成 + 突发启动</td></tr>
<tr><td>$BW_{pc}$</td><td>单核 GM 带宽份额</td><td>$W_{GM}/C$</td></tr></table>
<p> *端到端时延模型*L0C 双缓冲使 fixpipe 与 Cube 完全交叠,<b>无 batch 边界同步开销</b>。差异仅来自 GM→L1 搬移次数和 drain 暴露:</p>
<div class="math"> $$
T_{iter} = \underbrace{b_{core} \cdot n_K \cdot (T_{load} + T_{cmd})}_{\text{搬移(逐 batch}} + \underbrace{T_{comp} + T_{write}}_{\text{末 batch drain}}
$$</div>
<div class="math"> $$
T_{mb} = \underbrace{\frac{b_{core}}{b_0} \cdot n_K^m \cdot (T_{load}^m + T_{cmd})}_{\text{搬移(合并)}} + \underbrace{b_0(T_{comp} + T_{write})}_{\text{末合并 batch drain}}
$$</div>
<p> <b>K 截断情形</b>$k_{L1} = K$,整个 K 装入 L1 一块):$n_K = n_K^m = 1$$T_{load}^m = b_0 \cdot T_{load}$(合并后数据量 $b_0$ 倍)。</p>
<div class="math"> $$
T_{iter} = b_{core}(T_{load} + T_{cmd}) + T_{comp} + T_{write}
$$</div>
<div class="math"> $$
T_{mb} = \frac{b_{core}}{b_0}(b_0 T_{load} + T_{cmd}) + b_0(T_{comp} + T_{write}) = b_{core} T_{load} + \frac{b_{core}}{b_0}T_{cmd} + b_0(T_{comp} + T_{write})
$$</div>
<div class="math"> $$
\Delta = T_{mb} - T_{iter} = \underbrace{(b_0-1)(T_{comp} + T_{write})}_{\text{drain 惩罚}} - \underbrace{b_{core}\Big(1-\frac{1}{b_0}\Big) T_{cmd}}_{\text{搬移命令节省}}
$$</div>
<p> <b>分界条件</b>MergeBatch 优于 IterBatch 当且仅当 $\Delta &lt; 0$</p>
<div class="math"> $$
b_{core} > \frac{b_0 \cdot (T_{comp} + T_{write})}{T_{cmd}}
$$</div>
<p> 小 MN 时 $T_{comp}$ 小 → 惩罚小 → MergeBatch 更容易赢;大 B 时 $b_{core}$ 大 → 搬移节省多 → MergeBatch 更容易赢。</p>
<p> **$T_{cmd}$ 的物理成因**:每次 GM→L1 DMA 搬移的固定开销,与搬移数据量无关。从源码可直接观察:</p>
<ul class="tight">
<li><code>Nd2NzParams</code> 描述符配置ndNum/nValue/dValue/srcStride 等 7 个字段写入 DMA 寄存器)</li>
<li>地址生成与突发启动</li>
<li>与 L1 buffer 的同步握手(<code>SetFlag&lt;MTE2_MTE1&gt;</code> / <code>WaitFlag&lt;MTE1_MTE2&gt;</code></li>
</ul>
<p> IterBatch 每 batch 需一次完整配置(<code>ndNum = curIterBatchL1</code>多块独立寻址MergeBatch 合并后只需一次配置(<code>ndNum = 1</code>,单块连续搬移)。**$T_{cmd}$ 就是每次 DMA 命令的描述符配置 + 启动延迟**,量级估计为数十 ns。</p>
<p> <b>MergeBatch vs IterBatch 优势总结</b></p>
<table><tr><th>维度</th><th>IterBatch</th><th>MergeBatch</th><th>差异来源</th></tr>
<tr><td>稳态搬移吞吐</td><td>相同</td><td>相同</td><td>总搬移量相同</td></tr>
<tr><td>GM→L1 搬移命令数</td><td>$b_{core}$ 次(每 batch 一次)</td><td>$b_{core}/b_0$ 次(每合并 batch 一次)</td><td>**MergeBatch 少 $b_0$ 倍** ← 核心优势</td></tr>
<tr><td>drain 暴露</td><td>$T_{comp} + T_{write}$</td><td>$b_0(T_{comp} + T_{write})$</td><td>IterBatch 少 $b_0$ 倍 ← 核心劣势</td></tr>
<tr><td>L0C 利用率</td><td>$MN \cdot 4\text{B}$</td><td>$b_0^2 MN \cdot 4\text{B}$</td><td>访存 Bound 下不影响时延</td></tr></table>
<p> 净收益 = 搬移节省 - drain 惩罚 = $b_{core}(1-\frac{1}{b_0})T_{cmd} - (b_0-1)(T_{comp}+T_{write})$。大 B$b_{core}$ 大)且小 MN$T_{comp}$ 小)时 MergeBatch 最优。</p>
<p> <b>L0C 利用率说明</b>:小 MN 时 IterBatch 的 L0C tile$MN \cdot 4\text{B}$)远小于 L0C 容量MergeBatch 合并后更接近满载。但访存 Bound 下计算被搬移掩盖L0C 利用率不影响总时延——<b>不构成 MergeBatch 的优势</b></p>
<ol class="tight">
<li><b>L0C 容量</b>:合并 $b_0$ 个 batch 的输出块 $[b_0M, b_0N]$FP32 累加、双缓冲两份)必须放得下 L0C连最小合并都放不下合并无从谈起。</li>
<li><b>单核搬移总量</b>:单核搬移数据总量低于 min_DatamountPerCore 时GM 带宽利用率上不去(重要性第 2 位的经验约束)。</li>
<li><b>搬移 tile 大小</b>:单 batch 单矩阵的最大连续搬移块须达到 min_TileSize合并是在此之上进一步放大不是替代。</li>
<li><b>访存 Bound</b>:合并把单次计算的算存比放大 $b_0$ 倍后仍须低于 16bit 对应位宽算存比 $R_{16}$,保证瓶颈留在搬移侧,冗余算力被掩盖而非成为新瓶颈。</li>
</ol>
<h3>实现方案</h3>
<p><b>Step 1合并数 bL0C + 算存比双上限)</b></p>
<div class="math">$$
b \le \sqrt{\frac{L0C}{2 \cdot MN \cdot 4\text{B}}},\qquad b < \frac{R_{16}(M+N)}{2MN}
$$</div>
<div class="math">$$
b = \min\big(\text{两上限},\; b_{core}\big),\quad b_{L0} = b
$$</div>
<p>b 尽量取 $b_{core}$ 的因子(每次合并数均匀,负载与功耗更优)。</p>
<p>**Step 2L0 级 K 粒度 $k_{L0}$**</p>
<div class="math">$$
k_{L0} = \min\Big(\frac{L0A}{2\,bM\cdot\text{dtype}},\; \frac{L0B}{2\,bN\cdot\text{dtype}}\Big)\ \text{向下 16 对齐,且 } k_{L0}\cdot\text{dtype} \ge 128\text{B}
$$</div>
<p>解释L0A/L0B 各 64KB、双缓冲两份装入合并后 $bM$ 行($bN$ 列)× $k_{L0}$ 的 fractal末项是 dValue 下限。</p>
<p>**Step 3L1 级 $k_{L1}$、$b_{L1}$**</p>
<div class="math">$$
k_{L1} \ge \min\big(k_{L0\_max},\; 256\text{B}/\text{dtype}\big),\qquad
2\,b_{L1}(M k_{L1} + k_{L1} N)\cdot\text{dtype} \le L1,\qquad
b_{L1} = \min(b_{L1\_max},\; b_{core}) \;\ge\; b
$$</div>
<p>解释:$k_{L1}$ 是 GM→L1 的 K 向粒度,按 dValue 推荐值 256B 取L1 双缓冲两份,每份驻留 $b_{L1}$ 个 batch 的 A/B 各一块;$b_{L1} \ge b$ 保证合并不断供。</p>
<hr>
<h2>六、IterBatch 分支</h2>
<p>核间切 B每核 $b_{core} \ge 1$ 个 batch核间无同步核内逐个 batch 做标准 Matmul 分块计算。无算力浪费、无跨 batch 依赖,是"切 B"最朴素的形态。</p>
<h3>进入分支条件(汇总)</h3>
<p>同时满足:</p>
<ol class="tight">
<li>$BatchA = BatchB \;\land\; b_{core} = \lceil B/C \rceil \ge 1$</li>
<li>$B \bmod C = 0 \;\lor\; B \bmod C \ge minCoreNum$</li>
<li>L1 容量约束四选一Step 为 &gt;1 的整数):</li>
<ul class="tight">
<li>a) $b_{core}=1 \;\land\; (MK+KN)\cdot\text{dtype} \le L1$</li>
<li>b) $b_{core}&gt;1 \;\land\; 2(MK+KN)\cdot\text{dtype} \le L1$</li>
<li>c) $\big(MK + 2\cdot\tfrac{KN}{Step}\big)\cdot\text{dtype} \le \dfrac{L1}{\min(b_{core},\,2)} \;\;\lor\;\; \big(KN + 2\cdot\tfrac{MK}{Step}\big)\cdot\text{dtype} \le \dfrac{L1}{\min(b_{core},\,2)}$</li>
<li>d) $2\cdot\tfrac{K}{Step}(M+N)\cdot\text{dtype} \le L1$</li>
</ul>
<li>c/d 切分后:搬移分块 $\ge min\_TileSize \;\land\; dValue \ge 128\text{B}$</li>
</ol>
<h3>逐条解释</h3>
<ol class="tight">
<li><b>batch 关系与每核份额</b>:无广播;每核至少 1 个 batch。</li>
<li><b>负载均衡</b>:切 B 核间零共享零依赖,唯一系统性风险是负载不均。整除时完全均衡;不整除时尾波活跃核数须 ≥ minCoreNum保证尾波仍有足够核并发搬移重要性第 1 位的约束是核数)。</li>
<li><b>L1 容量——核心要求:单 batch 计算不重复读</b></li>
</ol>
<p> 重复读发生在<b>单 batch 内部</b>L1 放不下单 batch 完整的 M、N 维输入K 维允许切段放入kL1&lt;K核内切 M 会在 K 循环中重复读 B、切 N 会重复读 A。IterBatch 进入与否不由算存比判定——即使 case 是计算 Bound重复读引入的额外搬移也可能把它重新拖回访存 Bound。所以进入条件直接由 L1 驻留形态刻画:</p>
<ul class="tight">
<li><b>a)</b> 每核 1 batch左右矩阵同时驻留 L1零重复读</li>
<li><b>b)</b> 每核多 batchL1 同时放下 2 个 batchbatch 间直接乒乓;</li>
<li><b>c)</b> 一侧驻留 + 对侧切 K$b_{core} \ge 1$ 统一式):驻留侧只搬一次、零重复读;对侧 K 段在预算内双缓冲(故系数 2<b>预算分档由 batch 间流水掩盖决定</b>$b_{core}=1$ 时无 batch 边界、全量 L1 可用;$b_{core} \ge 2$ 时预算减半L1/2另一半用于在计算当前 batch 期间预取下一 batch 的驻留侧——定量分析见下文"batch 间流水掩盖"</li>
<li><b>d)</b> 两侧都切 KA/B 按 K 段成对流水(双缓冲故系数 2无驻留侧、batch 边界天然无缝,是驻留侧放不进 L1/2 时的兜底。</li>
</ul>
<ol class="tight">
<li><b>搬移效率下限</b>:切分把粒度切小,必须守住 min_TileSize 与 dValue否则切分本身把带宽打崩。</li>
</ol>
<h3>batch 间流水掩盖分析c/d 的分工)</h3>
<p>先明确流水结构fixpipe 开 unitflag 后,写出由硬件随路完成(每个 16×16×16 fractal 算完即自动搬出),<b>写出侧不需要软件排流水</b>;要掩盖的只有"读入MTE2: GM→L1↔ 计算Cube"。</p>
<ul class="tight">
<li>**(c) 且 $b_{core}=1$**:每核只有 1 个 batch不存在 batch 边界;驻留侧全程复用,对侧 K 段双缓冲,段间无缝。</li>
<li>**(c) 且 $b_{core} \ge 2$——关键问题:驻留侧在 batch 边界要整体换入,怎么掩盖?** 若不减预算(全量 L1 给当前 batch到 batch 边界时下一 batch 的驻留侧(如 A$MK\cdot\text{dtype}$)必须整体换入,而当前 batch 尾部只剩几个 K 段的计算——掩盖不了整个驻留侧的换入,产生气泡。<b>修正做法</b>:每 batch 只占 L1/2驻留侧 + 对侧 K 段双缓冲都在这一半),另一半 L1 在计算当前 batch 期间预取下一 batch 的驻留侧。上一 batch 做最后一个 K 段时,下一 batch 的驻留侧已就绪、首个 K 段随即搬入——边界无气泡。无气泡的定量条件:驻留侧换入量 $MK\cdot\text{dtype} \le$ 当前 batch 总搬入量 $(MK+KN)\cdot\text{dtype}$,恒成立;访存 Bound 下计算时间 ≥ 搬入时间,尾部窗口必然足够。</li>
<li><b>(d)(两侧都切 K</b>K 段槽位在 batch 间完全同质连续——上一 batch 最后一段计算时搬下一 batch 第一段,天然无缝。当驻留侧单矩阵放不进 L1/2 时 (c) 不成立,由 (d) 接管,代价是两侧都有 K 段级重复读。</li>
</ul>
<p>结论:(c) 统一了原 (c)/(d)——同一族"一侧驻留 + 对侧切 K",预算按 $b_{core}$ 分档($b_{core}=1$ 全量 L1、$b_{core} \ge 2$ 减半以容纳下一 batch 驻留侧预取);驻留侧超 L1/2 时由 (d) 接管。</p>
<h3>实现方案</h3>
<p><b>(a)</b>:每核 1 batch 直接搬入 L1。L1→L0 先看 L0C 能否放下完整单 batch 输出:</p>
<pre><code>if (L0C &gt;= M*N*4B): # L0C 放得下完整输出:不切 M/N只切 K
BaseM = M; BaseN = N
BaseK = min(align(L0A/M, 16), align(L0B/N, 16))
else: # L0C 放不下:按较小维切
if M &lt; N: BaseM = align(M,16); BaseN = floor(L0C/4B / BaseM)
else: BaseN = align(N,16); BaseM = floor(L0C/4B / BaseN)
BaseK = min(floor_align(L0A/BaseM,16), floor_align(L0B/BaseN,16))</code></pre>
<p><b>(b)</b>L1 双 batch 乒乓,核内 GM→L1→L0→Cube→L0C→GM/L2 流水L1→L0 分块同理但各级预算减半L0C/L0A/L0B 按 2 份)。</p>
<p><b>(c)</b>:一侧驻留 + 对侧切 K。假设驻留左矩阵右矩阵搬入的 K 向长度:</p>
<div class="math">$$
k_{L1\_b} = \min\Big(\frac{L1_{budget} - MK\cdot\text{dtype}}{N\cdot\text{dtype}},\; K\Big),\qquad L1_{budget} = \frac{L1}{\min(b_{core},\,2)}
$$</div>
<p>$b_{core} \ge 2$ 时另一半 L1 在计算期间预取下一 batch 的驻留侧,实现 batch 间无气泡衔接fixpipe 开 unitflag。</p>
<p><b>(d)</b>:两侧都切 K 段,$k_{L1}$ 取满足容量与 dValue 的最大值L0C 按 batch 乒乓(各占 L0C/2batch 边界由硬件 fixpipe 自动排空、下一 batch 立即在另一半 L0C 累加。</p>
<hr>
<h2>七、StreamK 分支</h2>
<h3>进入分支条件(汇总)</h3>
<ol class="tight">
<li>$P = \dfrac{B \cdot MN \cdot 4\text{B}}{L0C} \le \dfrac{C}{2}$</li>
<li>$\dfrac{K}{grid_K} \ge \dfrac{256\text{B}}{\text{dtype}}$,其中 $grid_K = \Big\lfloor \dfrac{C}{\lceil P \rceil} \Big\rfloor$</li>
<li>$K &gt; \dfrac{grid_K^{\,2}}{grid_K-1}\cdot\theta_c$$\theta_c = \dfrac{Q_{16}}{2}\Big(\dfrac{8\text{B}}{W_{L2}}+\dfrac{1}{Q_{AIV}}\Big) \approx 12$</li>
<li>工程约束:确定性等级 ≤ 1核间归约顺序不定ND 格式</li>
</ol>
<h3>逐条解释</h3>
<ol class="tight">
<li><b>并行缺口</b>P 以"L0C 满载的输出基本块"为粒度估计不切 K 的最大并行度——基本块按 L0C 最大利用率取($M^t N^t \cdot 4\text{B} = L0C$),免去预先估计 M/N 具体切分。<b>阈值取 C/2 而非 C</b>StreamK 的定义就是 grid_K ≥ 2至少 2 路切 K且同一 batch 内所有输出块共享同一个 grid_Kgrid_K=2 时每块需要 2 个核,总核数需求 = ⌈P⌉ × 2 ≤ C即 P ≤ C/2等号成立时 grid_K=2 恰好填满 C 核)。若 P &gt; C/2切 2 路就超核数2⌈P⌉ &gt; C不切又浪费核——由降核 ASW_Basic 承接更合适。P ≤ C/2 意味着不切 K 时至多一半核有事做K 是唯一剩余的并行维度。</li>
<li><b>单核 K 段下限</b>:每核 K 段内轴连续长度不小于 dValue 推荐值 256BBF16 为 128 元素),保证段内搬移效率不崩。</li>
</ol>
<p> <b>grid_K 取值</b>P 个输出 tile 各需 grid_K 个核,总核数 ⌈P⌉ × grid_K ≤ C → grid_K = ⌊C/⌈P⌉⌋最大化 K 并行度。例P=10, C=32 → grid_K=⌊32/10⌋=3P=5, C=32 → grid_K=⌊32/5⌋=6P=16, C=32 → grid_K=⌊32/16⌋=2。</p>
<ol class="tight">
<li><b>归约代价可接受</b>。StreamK 切 K 引入归约串行尾,收益判据——切 K 后芯片级总时延须小于不切 K降核 ASW</li>
</ol>
<p> <b>降核 ASW 的芯片级→核级映射</b>P &lt; C/2 时 B×M×N 填不满 C 核,以 L0C 满载粒度切为 P 个输出 tile每 tile 尺寸 $M^t N^t = L0C/4\text{B}$$\lceil P \rceil$ 个核各处理一个 tile。芯片级总时延 $T_{alt}$ = 单 tile 流水时延 $T_{pipe}$(所有核并行)。$T_{pipe} = \max(T_{MMAD}^t,\,T_{MTE2}^t)$,其中:</p>
<div class="math"> $$
T_{MMAD}^t = \frac{2 M^t N^t K}{Q_{16}} = \frac{2K}{Q_{16}}\cdot\frac{L0C}{4\text{B}},\qquad
T_{MTE2}^t = \frac{K(M^t+N^t)\cdot\text{dtype}}{BW_{pc}}
$$</div>
<p> <b>StreamK 的芯片级→核级映射</b>:同样 P 个 tile每 tile 由 $grid_K$ 个核共同完成(各算 $K/grid_K$ 段)。流水时延 $T_{pipe}/grid_K$,归约时延 $T_{Reduce}^t$ 串行追加。芯片级总时延:</p>
<div class="math"> $$
T_{SK} = \frac{T_{pipe}}{grid_K} + T_{Reduce}^t
$$</div>
<p> <b>收益判据</b> $T_{SK} &lt; T_{alt}$ ⟺ $T_{Reduce}^t &lt; T_{pipe}\left(1-\dfrac{1}{grid_K}\right)$。等价于 $T_{pipe} &gt; \dfrac{grid_K}{grid_K-1}\cdot T_{Reduce}^t$——α = grid_K/(grid_K-1) 由流水分析导出grid_K=2 时 α=2非经验值。</p>
<p> **$T_{Reduce}^t$ 构成**(每 tile部分和驻留 L2、AIV 归约;$M^t N^t = L0C/4\text{B}$,符号定义见 §二):</p>
<div class="math"> $$
T_{Reduce}^t = \underbrace{\frac{grid_K \cdot M^t N^t \cdot 4\text{B}}{W_{L2}}}_{\text{AIC 写部分和}} + \underbrace{\frac{grid_K \cdot M^t N^t \cdot 4\text{B}}{W_{L2}}}_{\text{AIV 读回}} + \underbrace{\frac{grid_K \cdot M^t N^t}{Q_{AIV}}}_{\text{AIV 求和}} + \underbrace{\frac{M^t N^t \cdot outB}{W_{L2}}}_{\text{写回}}
$$</div>
<p> <b>K 闭式阈值推导</b>——分两种瓶颈情形:</p>
<p> <b>计算 Bound</b>$T_{pipe} = T_{MMAD}^t$),代入判据:</p>
<div class="math"> $$
\frac{2K}{Q_{16}}\cdot\frac{L0C}{4\text{B}}\cdot\frac{grid_K-1}{grid_K} > grid_K\cdot\frac{L0C}{4\text{B}}\Big(\frac{8\text{B}}{W_{L2}}+\frac{1}{Q_{AIV}}\Big) + \frac{L0C}{4\text{B}}\cdot\frac{outB}{W_{L2}}
$$</div>
<p> 两边除以 $L0C/4\text{B}$tile 输出元素数),<b>tile 尺寸消去</b>——K 阈值不依赖 M、N 的具体值:</p>
<div class="math"> $$
K > \frac{grid_K^2}{grid_K-1}\cdot\theta_c,\qquad
\theta_c = \frac{Q_{16}}{2}\Big(\frac{8\text{B}}{W_{L2}}+\frac{1}{Q_{AIV}}\Big)
$$</div>
<p> 代入数值($Q_{16}$=15.2 TFLOPS$W_{L2}$=5.2 TB/s$Q_{AIV}$≈13.5 Tops/s</p>
<div class="math"> $$
\theta_c = \frac{15.2\times10^{12}}{2}\Big(\underbrace{\frac{8}{5.2\times10^{12}}}_{1.54\,\text{ps/元素}} + \underbrace{\frac{1}{13.5\times10^{12}}}_{0.07\,\text{ps/元素}}\Big) = 7.6\times10^{12} \times 1.61\times10^{-12} \approx 12
$$</div>
<p> L2 读写1.54 ps/元素是主导项AIV 求和0.07)仅占 5%。grid_K=2→K&gt;494→K&gt;668→K&gt;112。</p>
<p> <b>访存 Bound</b>$T_{pipe} = T_{MTE2}^t$),同理($M^t N^t/(M^t+N^t)$ 不消去,但阈值远低于计算 Bound</p>
<div class="math"> $$
K > \frac{grid_K^2}{grid_K-1}\cdot\frac{M^t N^t}{M^t+N^t}\cdot\theta_m,\qquad
\theta_m = \frac{BW_{pc}}{\text{dtype}}\Big(\frac{8\text{B}}{W_{L2}}+\frac{1}{Q_{AIV}}\Big) \approx 0.04
$$</div>
<p> <b>访存 Bound 阈值远低于计算 Bound</b>——两情形阈值比值:</p>
<div class="math"> $$
\frac{\theta_m \cdot M^t N^t/(M^t+N^t)}{\theta_c} = \frac{2\cdot BW_{pc}}{Q_{16}\cdot\text{dtype}}\cdot\frac{M^t N^t}{M^t+N^t} = \frac{M^t N^t/(M^t+N^t)}{304}
$$</div>
<p> $M^t N^t/(M^t+N^t)$ 的范围tile 面积 $M^t N^t \le L0C/4\text{B} = 65536$ 元素L0C 容量上限)。由均值不等式 $M^t+N^t \ge 2\sqrt{M^t N^t}$,比值上界为 $\sqrt{M^t N^t}/2 \le \sqrt{65536}/2 = 128$(正方形 tile 取到);极端长宽比($M^t{=}16, N^t{=}4096$)时下界 $\approx 16$。StreamK case 的 tile 通常接近正方形 → 比值 $\sim$ O(64128),上界 128。无论取何值$128/304 \approx 0.42 &lt; 1$——<b>访存 Bound 阈值恒低于计算 Bound</b>。直觉:访存 Bound 时 $T_{pipe} = T_{MTE2}^t &gt; T_{MMAD}^t$,瓶颈时延更大,归约预算更充裕。<b>汇总条件取计算 Bound 阈值</b>(保守,同时覆盖两种情形)。</p>
<p> 注意 θ_c 对 workspace 落点敏感:部分和落 GM 时读写带宽从 5.2TB/s 降到 ~0.64TB/sθ_c 升至约 97。设计时应优先保证 workspace 驻留 L2。</p>
<ol class="tight">
<li><b>工程约束</b>:归约顺序不定引入浮点非确定性,确定性等级 2/3 的业务禁用。</li>
</ol>
<p><b>源码对照</b><code>batch_matmul_v3_basic_streamk_tiling.cpp</code> 中 K 的固定门槛为 <code>CeilAlign(K,256) ≥ max(8192, aicNum×256B/dtype)</code></p>
<ul class="tight">
<li><code>aicNum×256B/dtype</code> = 32×128 = 4096BF16= <b>dValue 下限256B在最大 grid_K=C 下的保障</b>——对应条件 2</li>
<li><code>8192</code> = 32×256 = C×512BBF16= <b>dValue 推荐值512B在最大 grid_K=C 下的保障</b>——同为条件 2取推荐值而非下限。</li>
</ul>
<p>max 取更严格的 8192。修正后的归约阈值θ_c≈12grid_K=32 时 K&gt;396远低于 8192说明 <b>8192 的绑定约束是 dValue条件 2不是归约代价条件 3</b>。源码不动态计算 grid_K用固定阈值同时覆盖条件 2 的最保守情形和条件 3是两条条件的保守合并近似。</p>
<h3>实现方案</h3>
<p><b>Step 0SingleCoreM / SingleCoreN 的确定</b>(每核输出 tile 尺寸)</p>
<p>SingleCoreM × SingleCoreN 是每核每次处理的输出区域。<b>它不受 L0 容量直接约束</b>——L0 容量约束的是 BaseM/BaseN/BaseKL0 级 tile见 Step 5SingleCoreM/N 在 BaseM/N 之上,一个 [SingleCoreM, SingleCoreN] tile 内部由多个 [BaseM, BaseN] L0 tile 组成。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 越多核间并行度越高但单次搬移效率降低L2 重复读率升高</li>
</ul>
<p>*约束链*</p>
<p><b>约束 1——并行度下限</b>:总块数须填满 C 核。</p>
<div class="math">$$
mCnt \times nCnt \ge \Big\lceil \frac{C}{B} \Big\rceil \Rightarrow \frac{M}{\text{singleCoreM}} \times \frac{N}{\text{singleCoreN}} \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
$$</div>
<p>其中 $k_{L1}$ 是 GM→L1 的 K 向粒度,须满足 dValue$k_{L1} \cdot \text{dtype} \ge 256\text{B}$。</p>
<p><b>约束 3——搬移效率</b>:单次 GM→L1 搬移量须达到 min_TileSize</p>
<div class="math">$$
\text{singleCoreM} \cdot k_{L1} \cdot \text{dtype} \ge min\_TileSize,\qquad k_{L1} \cdot \text{singleCoreN} \cdot \text{dtype} \ge min\_TileSize
$$</div>
<p><b>选取策略</b></p>
<p>在约束 1并行度下限和约束 2/3搬移效率之间取平衡。SingleCoreM/N 的长宽比应<b>跟随 M/N 的长宽比</b>$\text{singleCoreM}/\text{singleCoreN} \approx M/N$),使 GM 访问的空间局部性最优。对齐到 16 的倍数Cube 基本块粒度)。</p>
<p>*例*B=8、M=N=2048、K=1024、BF16$\lceil C/B \rceil = 4$,需 $mCnt \times nCnt \ge 4$。取 $mCnt = nCnt = 2$ → singleCoreM = singleCoreN = 1024。约束 2$k_{L1} \le 512\text{KB}/(2 \times 2048 \times 2\text{B}) = 64$ 元素 = 128B恰好满足 dValue 下限。约束 3$1024 \times 64 \times 2 = 128\text{KB} \ge 16\text{KB}$ ✓。</p>
<p>*例*B=2、M=N=4096、K=512、BF16$\lceil C/B \rceil = 16$,需 $mCnt \times nCnt \ge 16$。取 $mCnt = nCnt = 4$ → singleCoreM = singleCoreN = 1024。$k_{L1}$ 同上 = 64 元素。</p>
<p><b>Step 1mCnt / nCnt 与核间分配</b></p>
<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 优先级分配到 C 核。</p>
<p><b>Step 2核间切分维度选择按共享代价从低到高</b>:切 B零共享先试→ 切 M右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2→ 切 N对称→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见 Step 7</p>
<p><b>Step 3swizzle——ASW 滑窗蛇形</b></p>
<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>
<p><b>Step 4L2 分组(工作集超 L2 时)</b></p>
<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>
<p><b>Step 5核内 tiling</b>BaseM/BaseN/BaseK——L0 级 tile受 L0 容量直接约束):$\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>
<p><b>Step 6内部特化参数极限不是独立分支</b>:单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次L1 全载)。</p>
<p><b>Step 7降核模式实现</b>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>
<ul class="tight">
<li><b>K=0</b>无任何计算C = bias 或 0纯 AIV 写值;</li>
<li><b>K=1</b>:退化为逐元素乘 <code>C = A ⊙ B</code>无累加深度Cube 的 16×16×16 粒度浪费 15/16走 AIV 向量通路GM→UB→Mul→GM优于 Cube 通路。触发需 $B \ge 2 \times 64$AIV 核数×2开 UB 乒乓)且单 batch 输入输出能驻留 UB。</li>
</ul>
<hr>
<h2>十、case 遍历:各分支的覆盖区域</h2>
<h3>方法论说明</h3>
<p><b>采样方式</b>B ∈ [1, 2048] 全量遍历2048 个值M/N/K ∈ [1, 10240] 对数网格采样 60 点/维值按指数增长1, 2, 3, 5, 7, 10, 14, 19, 26, 35, ...)。总 case 数 3.2 亿,耗时约 4 分钟。</p>
<p><b>为什么不做全量遍历</b>:全量 = 2048 × 10240³ ≈ 2.2×10¹⁵ 个 case。Python 分类器约需 1400 万小时C 实现约需 6 万小时——完全不可行。对数采样在小值区密集、大值区稀疏,恰好覆盖了分支边界集中的区域。</p>
<p><b>分布依赖测度</b>:对数均匀采样和线性均匀采样给出的分支占比<b>不同</b>。对数采样在小值区密集特殊分支K=0/1、降核 ASWP 小、StreamKK 大但 M/N 小)的占比被放大;线性采样被大 shape 主导M/N/K &gt; 512 占 [1,10240] 的 95%+ASW_Basic 占比显著升高。<b>本文遍历的目的是验证覆盖性(无空洞),不是统计真实工作负载的分布。</b></p>
<h3>分支覆盖统计对数采样B 全量 2048M/N/K 60 点/维,共 3.2 亿 case</h3>
<table><tr><th>分支</th><th>case 数</th><th>占比</th><th>B 范围</th><th>区域特征</th></tr>
<tr><td>ASW_Basic</td><td>1.75 亿</td><td>54.1%</td><td>2 ~ 2048</td><td>通用B&lt;C 且 P≥C或 B≥C 但 L1 四形态不满足M/N 大)</td></tr>
<tr><td>MergeBatch</td><td>6303 万</td><td>19.5%</td><td>97 ~ 2048</td><td>$b_{core}\ge 4$ 且 $MN \le 8192$ 等五条全过</td></tr>
<tr><td>降核 ASW_Basic</td><td>4051 万</td><td>12.6%</td><td>2 ~ 2048</td><td>P&lt;C 且 K 不满足 StreamK 阈值 → 只用 ⌈P⌉ 核</td></tr>
<tr><td>IterBatch</td><td>3804 万</td><td>11.8%</td><td>32 ~ 2048</td><td>B≥C、负载均衡、L1 四形态之一满足</td></tr>
<tr><td>特殊分支</td><td>597 万</td><td>1.9%</td><td>任意</td><td>K=0 / K=1</td></tr>
<tr><td>StreamK</td><td>25 万</td><td>0.08%</td><td>2 ~ 128</td><td>P&lt;C/2 且 K≥8192</td></tr>
<tr><td>转Matmul</td><td>15 万</td><td>0.05%</td><td>B=1</td><td>单边 batch=1</td></tr></table>
<p><b>对照:线性等距采样</b>M/N/K 步长 256B 全量,共 1.3 亿 case下 ASW_Basic 占比升至 92.1%MergeBatch 降至 3.6%——因为线性采样被大 shape 主导。两种测度的结论一致:<b>无空分支、无覆盖空洞</b>,只是占比不同。</p>
<h3>IterBatch 四形态命中分布(对数采样)</h3>
<table><tr><th>形态</th><th>命中数</th><th>占比</th><th>说明</th></tr>
<tr><td>b) 双 batch 乒乓</td><td>2684 万</td><td>70.5%</td><td>最多B 大且单 batch 较小</td></tr>
<tr><td>d) 两侧切 K</td><td>741 万</td><td>19.5%</td><td>次之K 可切段的通用兜底</td></tr>
<tr><td>c) 一侧驻留+对侧切 K</td><td>370 万</td><td>9.7%</td><td>单侧可驻留(含 b_core≥2 的半预算预取档)</td></tr>
<tr><td>a) 单 batch 全驻留</td><td>9 万</td><td>0.2%</td><td>B=C 附近的窄区</td></tr></table>
<h3>典型边界 case</h3>
<table><tr><th>B</th><th>M</th><th>N</th><th>K</th><th>分支</th><th>说明</th></tr>
<tr><td>1</td><td>2048</td><td>2048</td><td>2048</td><td>转Matmul</td><td>单 batch 纯 Matmul</td></tr>
<tr><td>128</td><td>64</td><td>64</td><td>512</td><td><b>MergeBatch</b></td><td>五条全过:$b_{core}$=4MN=4096≤8192单核搬移 512KB≥480KBAI=64&lt;304</td></tr>
<tr><td>128</td><td>64</td><td>64</td><td>256</td><td>IterBatch</td><td>与上行仅 K 不同:单核搬移 256KB &lt; 480KB条件 3 不满足 → 落 IterBatch形态 b</td></tr>
<tr><td>512</td><td>128</td><td>128</td><td>128</td><td>IterBatch</td><td>MN=16384 &gt; 8192MergeBatch 条件 2 不满足 → 落 IterBatch</td></tr>
<tr><td>32</td><td>4096</td><td>4096</td><td>4096</td><td>ASW_Basic</td><td>L1 四形态均不满足M/N 太大),切 M/N</td></tr>
<tr><td>2</td><td>8192</td><td>8192</td><td>1024</td><td>ASW_Basic</td><td>B&lt;CP=2048 ≥ 32</td></tr>
<tr><td>16</td><td>256</td><td>256</td><td>128</td><td>降核 ASW</td><td>P=4 &lt; 32 且 K=128 不满足 StreamK → 用 4 核,其余闲置</td></tr>
<tr><td>4</td><td>128</td><td>128</td><td>10240</td><td>StreamK</td><td>P=0.25 &lt; 32K≥8192</td></tr>
<tr><td>2048</td><td>1024</td><td>1024</td><td>512</td><td>IterBatch</td><td>大 batch形态 b</td></tr>
<tr><td>8</td><td>512</td><td>512</td><td>512</td><td>ASW_Basic</td><td>B&lt;CP=32 恰好满核</td></tr>
<tr><td>64</td><td>64</td><td>64</td><td>8192</td><td>IterBatch</td><td>小 M×N 但 K 大,形态 d</td></tr></table>
<h3>遍历结论</h3>
<ol class="tight">
<li><b>七个分支全部有真实 case 命中</b>无空分支覆盖矩阵无空洞P&lt;C 且 K 小的残余由降核 ASW_Basic 兜底——此时时延绝对值小,调度开销主导,分支选择不敏感);</li>
<li>**MergeBatch 的区域由条件 2$MN \le 8192$)与条件 3min_DatamountPerCore夹出**:小 M×N 且单核搬移量足够的大 batch case两个条件缺一不可。典型分界对照B=128/M=N=64 时 K=256 → 单核搬移 256KB 不达标落 IterBatchK=512 → 512KB 达标进 MergeBatch</li>
<li><b>IterBatch 与 ASW_Basic 的分界就是 L1 四形态是否满足</b>:单 batch 输入 $(MK+KN)\cdot\text{dtype}$ 相对 L1 的比例决定归属——这正是"核内零重复读"原则的定量体现;</li>
<li><b>StreamK 的区域为 P&lt;C/2 且 K≥8192</b>B 小、M/N 小、K 大的"细长" caseC/2 ≤ P &lt; C 且 K 大的 case 由降核 ASW_Basic 承接(切 2 路 K 会超核数,不切又不满核,不如直接用 ⌈P⌉ 核);</li>
<li><b>降核 ASW_Basic 是 P&lt;C 且 K 小区域的理性归宿</b>(占 12.6%):并行度凑不满、切 K 又不划算时,只用 ⌈P⌉ 个核、每核一个 L0C 满载输出块比强行碎切tile 跌破搬移效率下限)更快。</li>
</ol>
</div>
</body>
</html>