Files

100 KiB
Raw Permalink Blame History

ASW_Basic 分支BMM 兜底分支的理论最优实现分析

目标芯片:昇腾 950PRDAV_3510。本文为 ASW_Basic 分支的独立分析v1.8 修订 §5.10A1 扩展为尾轮 tile 重选凑满核形态,新增基于 B/M/K/N 的三策略直接判定表自包含完整推导链。历史v1.7 新增 §5.10 三策略两两对比v1.6 新增 §7 程序实现流程图v1.5 扩充 §6 源码对比、修正 dValue 定义与尾轮重切约束。

摘要

ASW_Basic 是 BMM 的兜底分支——核间切 M/N或混合切不做 batch 合并或 K 维切分。本文给出完整的时延建模、核间分配策略分析(证明 B 优先分组在任何场景下都不优于线性映射)、实现方案的逐步推导(含尾轮重切的 Bound 类型分级策略),以及与源码实现的逐维度对比。

v1.8 修订 §5.10A1 扩展为两种形态——A1a 整数倍切分r≤C/2与 A1b 尾轮 tile 重选凑满核r>C/2 时 tile 按 16 步进缩小凑满 C 核,不再退化为 A0;新增基于 B/M/K/N/dtype 的三策略直接判定表(闭式前置计算 + 决策表);修正 A1 vs B 结论——计算 Bound 下 A1b 与 B 理论时延相等(总计算量/C 守恒),离散 16 对齐使 A1b 尾轮 tile 上取整产生 ≤2% 覆盖浪费;访存 Bound 下周长和均值不等式证明 A1b 恒 ≤ B数值例 2.2%dValue 卡死时退化打平。v1.7 新增 §5.10尾轮处理三策略A0 不重切 / A1 仅尾轮差异化重切 / B 整轮均匀重切)的两两对比与适用分界。计算 Bound 与访存 Bound 下分别推导 A0 vs A1、A0 vs B、A1 vs B 的完整时延公式:计算 Bound 下 B 恒不劣于 A1$\Delta = T_{MMAD}(1/s^* - r/C)$r \mid C 完美重切时等价、r > C/2 时 A1 失效 B 收益达 $T_{MMAD}(1-r/C)$(数值例 23.4%);访存 Bound 下搬移随块周长缩放($\sqrt{g}$A1 收益被 dValue 与 A 行带不缩小双重压缩A1 vs B 的分界为 n_{wave}(1-1/\sqrt{g}) vs $(1-1/s^*)/2$(数值例:n_{wave}=2 时 B 省 14.3%、n_{wave}=4 时临界打平、r=17 时 B 省 6.2%)。**总体结论A0 从来不是最优;r > C/2 时 B 唯一可重切;其余按分界公式判定。**v1.6 新增 §7 程序实现流程图:从 case 输入B/M/K/N/dtype出发分别给出源码 ASW_BasicBatchMatMulV3AswBasicTiling 六阶段:进入判定 → ResetBase → cubeBound 枚举 → CalL1Tiling → 参数打包 → kernel 滑窗蛇形)与理论最优实现(八阶段:分支判定 → BaseM/N/K → SingleCoreM/N 有界枚举 → 核间分配 → 尾轮重切 → swizzle → L2 分组 → 端到端校验)两张完整流程图每步标注计算公式与产出参数并给出两图差异对照。v1.5 扩充 §6 源码对比cubeBound 模型三项的物理推导(工作集超 L2 倾向更小 tile、K 大倾向更大 tile源码 singleCore = base 不分层导致过度切分K 小时搬入时延可达理论 2 倍)、尾轮不实际重切。核心结论:B 优先分组不优于线性映射;理论的最少切分原则 + SingleCore/Base 分层 + 尾轮重切是相对源码的三条实质改进;源码的 cubeBound 解析模型与平台自适应值得理论吸收。


一、问题定义与执行模型

1.1 分支定位

BMM 中,当 B ≥ Cbatch 数 ≥ AIC 核数)时核间切 B 是免费的(每核独立处理若干 batch无共享无依赖由 IterBatch/MergeBatch 承接。当 B < C 或 B ≥ C 但 IterBatch/MergeBatch 条件不满足时,需要切 M/N 来填满所有核——这就是 ASW_Basic。

ASW_Basic 是实践中最常命中的分支B 可大可小可等 1交叉广播也由此承接对广播侧做 L1/L2 驻留,共享关系与切 M/N 同构)。

1.2 执行模型

核间B × mCnt × nCnt 个输出块,按 B→M→N 线性映射分配到 C 核
核内:每核处理若干 [singleCoreM, singleCoreN] 输出块
  └── 每块内部GM→L1→L0→Cube→L0C→Fixpipe 标准流水
       └── K 维不切singleCoreK = K按 kL1 分块搬入 L1

数据流:

GM ──MTE2──> L1 ──MTE1──> L0A/L0B ──MMAD──> L0C ──Fixpipe──> GM
     ↑_____________ L2 Cache读 5.2TB/s_____________↑

1.3 符号定义

符号 含义 表达式/取值
B batch 数 输入参数
M, N, K 矩阵维度 输入参数
C AIC 核数 32
dtype 输入元素字节数 BF16 → 2B
outB 输出元素字节数 BF16 → 2B
L0C L0C 容量/核 256KB
L0A, L0B L0A/L0B 容量/核 各 64KB
L1 L1 容量/核 512KB
L2 L2 容量(共享) 128MB
BW_{pc} 单核 GM 带宽份额 W_{GM}/C = 50 GB/s
Q_{16} 单核 Cube BF16 峰值算力 486/32 ≈ 15.2 TFLOPS
W_{GM} GM 带宽 1.6 TB/s
BW_{L2} L2 读带宽 5.2 TB/s

Tiling 参数

符号 含义 约束层级
\text{BaseM}, \text{BaseN} L0 级 tile 的 M/N 维度 L0C 容量直接约束
baseK L0 级 tile 的 K 维度 L0A/L0B 容量约束
\text{singleCoreM}, \text{singleCoreN} 每核输出 tile 的 M/N 维度 L1 容量 + 并行度 + 搬移效率
k_{L1} GM→L1 的 K 向粒度 dValue ≥ 256B
mCnt, nCnt 单 batch 内 M/N 向块数 \lceil M/\text{singleCoreM} \rceil
W swizzle 窗口宽度 \max\{d : d \mid C, d \le \lfloor\sqrt{C}\rfloor\}

层次关系:$K = \text{singleCoreK} \ge k_{L1} \ge baseK$$\text{singleCoreM} \ge \text{BaseM}$$\text{singleCoreN} \ge \text{BaseN}$。

搬移效率dValue 与 min_TileSize

GM→L1 搬移使用 Nd2Nz DMA每次搬移的关键参数

参数 含义 A 矩阵 [singleCoreM, $k_{L1}$] B 矩阵 [k_{L1}, singleCoreN]
nValue 行数(非连续维) singleCoreM k_{L1}
dValue 每行连续字节数(连续维) k_{L1} \cdot dtype \text{singleCoreN} \cdot dtype
总搬移量 nValue × dValue \text{singleCoreM} \cdot k_{L1} \cdot dtype k_{L1} \cdot \text{singleCoreN} \cdot dtype

dValue 是 ND 排布中连续维的字节数——对 ND 格式的右矩阵 B非转置时连续维为 NdValue = $\text{singleCoreN} \cdot dtype$;转置时连续维为 KdValue = $k_{L1} \cdot dtype$。dValue 不是两个维度的乘积

两级搬移效率阈值:

  1. dValue ≥ 256BDMA 硬件突发下限):每行连续数据量不足 256B 时DMA 突发效率急剧下降;
  2. 总搬移量 ≥ min_TileSize(推荐 16KB单次搬移量太小则带宽利用率不足昇腾 950 NPU 架构白皮书推荐 dValue 256B/512B 对齐)。

二、进入条件

同时满足:

  1. $P = \dfrac{B \cdot MN \cdot 4\text{B}}{L0C} \ge C$(并行度补齐)
  2. 无 batch 结构限制BatchA=BatchB、交叉广播均可典型进入路径$B < C$(切 B 买不满核),或 B \ge C 但 IterBatch/MergeBatch 条件不满足时的兜底
  3. 降核模式P < C 且不满足 StreamK 进入条件 → 只用 \lceil P \rceil 个核,其余核闲置

逐条解释:

  1. 并行度补齐:以 L0C 满载为基本块粒度B×M×N 能切出至少 C 个独立输出块,则切 M/N或混合切并行度够用。切 M/N 的固有代价是共享矩阵被多核重复读,但共享部分驻留 128MB L2 时重复读以 5.2TB/s 命中 L2 而非 1.6TB/s 的 GM代价大部分被吸收。
  2. 兜底性质ASW_Basic 是实践中最常命中的分支——B 可大可小可等 1交叉广播也由此承接对广播侧做 L1/L2 驻留,共享关系与切 M/N 同构)。
  3. 降核模式P < C 且 K 也不够格走 StreamK 时,并行度凑不满核。此时与其强行把 M/N 切得更碎tile 跌破 min_TileSize、dValue 跌破 256B搬移效率崩塌反而更慢不如只用 ⌈P⌉ 个核、每核承担一个完整输出块L0C 满载粒度),其余核闲置。这类 case 的时延绝对值小,继续切分引入的调度与搬移效率损失大于并行收益——降核是理性选择而非偷懒。

三、时延建模

3.1 端到端时延


T = \max(T_{MTE2},\; T_{MMAD},\; T_{FIX}) + T_{drain}
含义 公式
T_{MTE2} GM→L1 搬移时延 r_{in} \cdot S_{in} / (C \cdot BW_{pc})
T_{MMAD} Cube 计算时延 2BMNK / (C \cdot Q_{16})
T_{FIX} 输出写回时延 B \cdot MN \cdot outB / (C \cdot W_{pc})
T_{drain} 末块排空时延 O(T_{comp} + T_{write})

其中 S_{in} = B(MK + KN) \cdot dtype 为输入总量,r_{in} \ge 1 为重复读倍率GM 输入流量 / 输入总量)。r_{in} = 1 表示每字节只从 GM 读一次(后续复用全命中 L2——这是 GM 输入流量的下界

关键观察T_{MMAD}T_{FIX} 与核间分配策略无关——分配策略只影响 $T_{MTE2}$(通过 L2 命中率影响 $r_{in}$)。优化目标:让 r_{in} 尽量接近 1。

3.2 并行度约束的正确形式

总输出块数 $= B \cdot mCnt \cdot nCnt$。并行度约束的正确形式是:


B \cdot mCnt \cdot nCnt \ge C

即总块数至少能填满 C 核。这与 mCnt \cdot nCnt \ge \lceil C/B \rceil 等价(两边同乘 B 后取整)。

不应要求 $B \cdot mCnt \cdot nCnt ;%; C = 0$(整除)。整除是充分不必要条件——不整除时产生尾轮,尾轮的处理见 §五。


四、核间分配策略分析

4.1 两种候选策略

  • B 优先分组C 核分为 B 组,每组 C_g = \lfloor C/B \rfloor\lceil C/B \rceil 核独立处理一个 batch 的 mCnt \times nCnt 个块,组内做 swizzle
  • 线性映射:块按 (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, …

4.2 L2 工作集对比

每波活跃核所需的 A 行带 + B 列带:


WS_{wave} = \big(W \cdot sM + \tfrac{C}{W} \cdot sN\big) \cdot K \cdot dtype

其中 sM = singleCoreMsN = singleCoreNW 为 swizzle 窗口宽度。

维度 B 优先分组 B→M→N 线性映射
每波活跃 batch 数 B 个(每组一个) 1 个(mCnt \cdot nCnt \ge C 时)
每组/每波 swizzle 窗口 W_g = \max\{d \mid d \mid C_g,\; d \le \sqrt{C_g}\} W = \max\{d \mid d \mid C,\; d \le \sqrt{C}\}
每波 L2 工作集 B \cdot (W_g \cdot sM + \frac{C_g}{W_g} \cdot sN) \cdot K \cdot dtype (W \cdot sM + \frac{C}{W} \cdot sN) \cdot K \cdot dtype
C 不整除 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——线性映射的工作集是分组方案的一半

为什么线性映射工作集更小

  1. 更多核参与同一 batch 的 swizzleW = \sqrt{C} > W_g = \sqrt{C/B} → 工作集更接近"方形"下限(均值不等式:W + C/WW = \sqrt{C} 处最小);
  2. 同一时刻只有 1 个 batch 的数据活跃 → L2 只需缓存 1 份 A/B 数据,而非 B 份。

4.3 分场景严格比较

场景 ImCnt \cdot nCnt \ge C 且单波工作集 ≤ L2(最常见)

线性映射:每波 C 个块在同一 batch 内L2 命中率高。B 分组B 个 batch 同时活跃,总工作集 B 倍。线性严格更优。

场景 II$S_{in} \le L2$(全部输入放得下 L2

两种策略的 GM 流量相同($r_{in} = 1$,每字节只读一次)。但 B 分组在 C % B ≠ 0 时负载不均(组间核数不等)。线性不劣于分组。

场景 III$mCnt \cdot nCnt < C$(单 batch 块数不够填满所有核)

线性映射:每波横跨 \lceil C/(mCnt \cdot nCnt) \rceil 个 batch——这些 batch 的数据同时活跃。B 分组:每组 C_g 核但只有 mCnt \cdot nCnt < C_g 个块 → 组内有空闲核。线性映射至少所有核都有活干。线性严格更优。

场景 IV$mCnt \cdot nCnt < C/B$(每组连自己的核都填不满)

B 分组每组空闲 C_g - mCnt \cdot nCnt 个核,总算力浪费 C - B \cdot mCnt \cdot nCnt 个核。线性映射无此问题。线性严格更优。

4.4 结论

B 优先分组在任何场景下都不优于线性映射。 根本原因:

  1. L2 是共享 Cache工作集越小命中率越高——线性映射同一时刻只激活 1 个 batch 的数据,工作集最小;
  2. swizzle 窗口 $W \propto \sqrt{\text{参与核数}}$——更多核参与同一 batch 的 swizzle窗口更大工作集更方形
  3. 线性映射对 B 与 C 的关系无要求B 不整除 C 时无负载不均。

因此 ASW_Basic 采用 B→M→N 线性映射作为核间分配策略。


五、实现方案

5.1 BaseM / BaseN 的确定L0 级 tile先把 L0C 用满)

L0C 是 Cube 的累加器BaseM × BaseN 是每次 Cube 计算的输出 tile。BaseM/N 应尽量把 L0C 用满——L0C 利用率越高,每次 Cube 计算的输出越大,单位计算的启动/排空开销摊得越薄。

L0C 双缓冲 vs UnitFlag 单缓冲

传统做法用 L0C 双缓冲实现 tile 间流水——计算 tile N+1 时fixpipe 同时写出 tile N


\text{BaseM} \times \text{BaseN} = \frac{L0C}{2 \times 4\text{B}} = 32768 \text{ 元素(双缓冲)}

但昇腾 950PR 的 Fixpipe 支持 UnitFlag——MMAD 每完成一个 16×16×16 基本块512B 结果Fixpipe 立即将其写出,无需等整个 L0C tile 算完。UnitFlag 提供的是 tile 内部的细粒度流水16×16×16 粒度),替代双缓冲的 tile 间粗粒度流水BaseM×BaseN 粒度)。

UnitFlag 单缓冲下L0C 只需一份 buffer


\text{BaseM} \times \text{BaseN} = \frac{L0C}{4\text{B}} = 65536 \text{ 元素(单缓冲)}

BaseM/N 可放大 \sqrt{2} 倍(如 256→362mCnt/nCnt 相应减小,L2 重复读率降低(单 batch 块数减少 → 每行 A 被更少的组读取)。

时延对比(单 tile 粒度BF16 输出):

方案 tile 大小 tile 间流水 tile 内流水 单 tile 时延
双缓冲 181×18132761 元素) tile N 写出 ∥ tile N+1 计算) max(T_{comp}, T_{write})
UnitFlag 单缓冲 256×25665536 元素) 16×16×16 粒度) max(T_{comp}, T_{write})

两种方案的稳态时延相同(都是 max(T_{comp}, T_{write})),但 UnitFlag 单缓冲的 tile 更大 → 总 tile 数更少 → 循环开销更小。但当前 BMM ASW kernel 未启用 UnitFlagunitFlag = 0,注释 "each l0 only process one block, disable unit flag"),且源码在 baseM=baseN=256 时已自动选 dbL0C=1256×256×4B×2 > L0C——即源码已经是单缓冲 + 无 UnitFlagtile 到顶但无流水交叠。

建议:对计算 Bound 的 case 启用 UnitFlagMMAD 与 Fixpipe 流水并行),可将单 tile 时延从 T_{comp} + T_{write} 降至 $\max(T_{comp}, T_{write})$。对访存 Bound 的 caseMTE2 BoundUnitFlag 收益小(CANN 文档MTE2 Bound 时 MMAD/FIX 流水可被搬移掩盖)。

BaseM/BaseN 的长宽比应尽量方形(而非跟随 M/N对齐 16 的倍数。推导

1. GM→L1 搬移与 BaseM/N 无关GM→L1 搬移的是 $[\text{singleCoreM}, k_{L1}]$A和 $[k_{L1}, \text{singleCoreN}]$B其 dValue 由 $k_{L1}$A和 $\text{singleCoreN}$B决定——BaseM/N 的长宽比不参与 GM→L1 搬移参数。长宽比跟随 M/N 不会改善 GM→L1 效率。

2. L0A = L0B = 64KB 等容量 → 方形 tile 用满两侧L0A 装 $\text{BaseM} \times baseK$L0B 装 $baseK \times \text{BaseN}$。若长宽比跟随 M/N如 M/N = 4BaseM=512、BaseN=128


baseK = \min\Big(\frac{L0A}{2 \cdot \text{BaseM} \cdot dtype},\; \frac{L0B}{2 \cdot \text{BaseN} \cdot dtype}\Big) = \min(32,\; 128) = 32

L0A 装满512×32×2×2 = 64KBL0B 只用了 25%32×128×2×2 = 16KB——一半 L0 容量闲置。

方形 tileBaseM=BaseN=256


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

L0A 和 L0B 同时装满,且 baseK = 64 是长宽比跟随方案baseK=322 倍——K 维迭代次数减半Cube 的 K 向流水切换和 SetFlag/WaitFlag 同步次数减半L1→L0 搬移本身不是瓶颈,但更少的迭代意味着更少的同步握手和更少的 MMAD 启动/排空轮次)。

3. 方形 tile 是 L0C 面积约束下的最优L0C 约束 \text{BaseM} \times \text{BaseN} \le 65536 只限制面积。在面积固定下,方形使 \min(\text{BaseM}, \text{BaseN}) 最大——baseK 的上限由 \min(\text{BaseM}, \text{BaseN}) 决定(公式见上),所以方形最大化 baseK。

4. 例外:当 M 或 N 小于方形边长时tile 被迫非方形:


\text{BaseM} = \min(\lfloor\sqrt{65536}\rfloor_{16},\; M),\qquad \text{BaseN} = \min\Big(\frac{65536}{\text{BaseM}},\; N\Big) \text{ 向下 16 对齐}

M=128、N=4096 → BaseM=128M 限制、BaseN=512L0C 面积限制)——此时长宽比跟随 M/N 是被迫的M 太小),而非主动选择。

结论BaseM/N 长宽比跟随 M/N 的切分方法站不住脚——它使 L0A/L0B 容量利用率失衡一侧闲置、baseK 减半K 迭代翻倍)、且对 GM→L1 搬移无任何收益。正确做法是方形 tile 优先,仅当 M 或 N 小于方形边长时被迫跟随。

baseK 由 L0A/L0B 容量决定L1→L0 搬移无 dValue 要求dValue 约束的是 GM→L1 的 $k_{L1}$


baseK = \min\Big(\frac{L0A}{2 \cdot \text{BaseM} \cdot \text{dtype}},\; \frac{L0B}{2 \cdot \text{BaseN} \cdot \text{dtype}}\Big) \text{ 向下 16 对齐}

核间不切 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 容量约束)。

BaseM/N 的具体确定过程host 端枚举16 对齐遍历):

  1. 从 BaseM = BaseN = \lfloor\sqrt{L0C/4\text{B}}\rfloor_{16} = 256 开始方形L0C 单缓冲上限L0A/L0B 同时满载)
  2. baseK 取 L0A/L0B 容量允许的最大值baseK = \min(L0A/(2 \cdot \text{BaseM} \cdot dtype),\; L0B/(2 \cdot \text{BaseN} \cdot dtype)) 向下 16 对齐(16 对齐是 Cube K 向粒度要求,不是搬移效率要求——L1→L0 搬移不是瓶颈,可被 GM→L1 或 Cube 计算掩盖。baseK 取大值的意义在 L0 容量利用率和 K 迭代次数,而非搬移效率)
  3. 若 M < 256或 N < 256BaseM = $\lfloor M \rfloor_{16}$(或 BaseN = $\lfloor N \rfloor_{16}$),另一维取 $\min(65536/\text{BaseM},; N)$(或对称)向下 16 对齐——此时 tile 被迫跟随 M/N但这是 M 太小的结果,不是主动选择
  4. 方形 tile 的 L0C 面积利用率:$256 \times 256 / 65536 = 100%$;被迫非方形时面积利用率 = $\text{BaseM} \times \text{BaseN} / 65536$M 或 N 小时必然 < 100%,不可优化)

与源码的差异:源码默认 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)。

5.2 SingleCoreM / SingleCoreN 的确定(每核输出 tile≥ BaseM/N

SingleCoreM × SingleCoreN 是每核每次处理的输出区域,不受 L0 容量直接约束——一个 [SingleCoreM, SingleCoreN] tile 内部由若干 [BaseM, BaseN] L0 tile 组成($\text{SingleCoreM} \ge \text{BaseM}$$\text{SingleCoreN} \ge \text{BaseN}$。SingleCoreM/N 的核心影响是 GM→L1 搬移效率和 L2 重复读率

  • SingleCoreM/N 越大 → 单次 GM→L1 搬移量越大dValue 越有保障L2 中同一份 A 行带/B 列带被更多核复用
  • SingleCoreM/N 越小 → 总块数 mCnt×nCnt 越多,并行度越高,但搬移效率降低

SingleCoreM/N 对 L2 重复读的定量影响

每行 A 行带 [\text{singleCoreM}, K] 被该行的 nCnt 个列块各读一次,每列 B 列带被 mCnt 个行块各读一次。L2 层的总读取次数:


T_{L2} = \big(nCnt \cdot M + mCnt \cdot N\big) \cdot K \cdot dtype

SingleCoreM/N 越小 → $mCnt = \lceil M/\text{singleCoreM} \rceil$、nCnt = \lceil N/\text{singleCoreN} \rceil 越大 → T_{L2} 越大。

但这不直接等于 GM 重复读——L2 命中时重复读由 L2 吸收5.2TB/s不消耗 GM 带宽。GM 重复读倍率 r_{in} 取决于执行组划分§5.6):只有当工作集超 L2 时才需要分组,此时


r_{in} = \frac{\lceil nCnt/n_{grp} \rceil \cdot M + \lceil mCnt/m_{grp} \rceil \cdot N}{M + N}

$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 的变化取决于具体数值——但 L2 带宽不是瓶颈5.2TB/s ≫ GM 1.6TB/s所以 SingleCoreM/N 对性能的主要影响不在 L2 重复读,而在 GM→L1 搬移效率dValue 和 min_TileSize并行度$mCnt \cdot nCnt \ge \lceil C/B \rceil$)之间的权衡。

约束链

约束 1——并行度下限:总块数须填满 C 核。


B \cdot mCnt \cdot nCnt \ge C \iff mCnt \cdot nCnt \ge \Big\lceil \frac{C}{B} \Big\rceil

约束 2——L1 容量(双缓冲下驻留当前 tile 的输入):


2 \cdot (\text{singleCoreM} + \text{singleCoreN}) \cdot k_{L1} \cdot \text{dtype} \le L1,\qquad k_{L1} \cdot \text{dtype} \ge 256\text{B}

约束 3——搬移效率dValue 见 §1.4


\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非转置}}

\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 单次搬移量}}

约束 4——SingleCoreM/N 是 BaseM/N 的整数倍(工程实现要求,保证 L0 tile 边界对齐)。

选取策略最少切分原则 + 切分时方形分配

建模:每 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 块,单核总搬入时延:


T_{MTE2} = \frac{MN}{C/B} \cdot \Big(\frac{mCnt}{M} + \frac{nCnt}{N}\Big) \cdot \frac{k_{L1} \cdot dtype}{BW}

(展开验证:每 batch 总搬入 = $(M \cdot nCnt + N \cdot mCnt) \cdot k_{L1} \cdot dtype$——A 矩阵每 batch 被 nCnt 个列块共享读 nCnt 次、B 矩阵被 mCnt 个行块共享读 mCnt 次,与直觉一致。)

目标函数 mCnt/M + nCnt/NmCnt = nCnt = 1 时取得全局最小值$1/M + 1/N$)——不切分时每 batch 的 A/B 只搬一次,零重复读。因此第一步是尝试最少切分


mCnt \cdot nCnt = \Big\lceil \frac{C}{B} \Big\rceil \triangleq P

(多切无益:切分越多重复读越多,搬入量单调增大。)

情形 1B ≥ CP = 1——不切分,$mCnt = nCnt = 1$$\text{singleCoreM} = M$、$\text{singleCoreN} = N$。tile 跟随 M/N非方形。前提是 L1 容量与 dValue 满足(约束 2/3不满足时须 K 分块($k_{L1} < K$)或退化为情形 2。

情形 2B < CP > 1——必须切分。在 mCnt \cdot nCnt = P 下最小化 $mCnt/M + nCnt/N$


\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}}

\frac{mCnt^*}{nCnt^*} = \frac{M}{N} \Rightarrow \text{singleCoreM} = \text{singleCoreN} = \sqrt{\frac{MN}{P}}

——方形(连续松弛下的理论最优)。整数 + BaseM/N 对齐约束下取离方形最近的可行组合。

数值验证M=2048、N=512、C=32

B P = ⌈C/B⌉ 最优 (mCnt, nCnt) sM × sN 形状
32 1 (1, 1) 2048 × 512 跟随 M/N不切分
16 2 (2, 1) 1024 × 512 2:1整数约束偏离方形
8 4 (4, 1) 512 × 512 方形M/N=4 与 P=4 匹配)
4 8 (4, 2) 512 × 256 2:1

B=32 时不切分 cost = 0.002441 < B=8 时方形 0.003906——切分越少搬入越少,方形只是"被迫切分"时的次优选择。用户反例成立M=2048、N=512、B≥32 时 SingleCoreM=2048、SingleCoreN=512 优于方形 512×512每 batch 零重复读)。

此前推导的错误:把 mCnt \cdot nCnt = P 当作固定等式且未讨论 P=1 的情形——方形结论只适用于 B < C 的强制切分场景,不适用于 B ≥ C 的不切分场景。

SingleCoreM/N 的具体确定过程host 端枚举,与尾轮处理联动):

  1. 计算最少切分 P = \lceil C/B \rceil

  2. 若 P = 1B ≥ C:先试不切分 $mCnt = nCnt = 1$、$\text{singleCoreM} = M$、$\text{singleCoreN} = N$;由约束 2 求 $k_{L1} = \min(K,; \lfloor L1/(2(M{+}N) \cdot dtype) \rfloor_{16})$,检查约束 3k_{L1} \cdot dtype \ge 256\text{B}满足则确定。不满足L1 放不下且 k_{L1} 降无可降)则进入步骤 3 强制切分

  3. 若 P > 1必须切分——完整枚举。枚举不是随意挑几个组合试,而是遍历整个可行空间、每个候选计算搬入时延、取最优

    a. 枚举空间(有界):由约束 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 端遍历开销可忽略

    b. 每个候选的评估流水线(mCnt, nCnt) \to 对齐 \to 约束过滤 \to 目标值):

    
    \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)
    

    约束 4 过滤singleCoreM/singleCoreN 是 BaseM/BaseN 的整数倍;约束 2 求 $k_{L1}$

    
    k_{L1} = \min\Big(K,\; \Big\lfloor \frac{L1}{2 \cdot (\text{singleCoreM} + \text{singleCoreN}) \cdot dtype} \Big\rfloor_{16}\Big)
    

    约束 3 过滤k_{L1} \cdot dtype \ge 256\text{B}\text{singleCoreM} \cdot k_{L1} \cdot dtype \ge min\_TileSizek_{L1} \cdot \text{singleCoreN} \cdot dtype \ge min\_TileSize

    目标值§3 时延模型的搬移项)

    
    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}}
    

    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与切分无关——候选之间的唯一差异在搬入时延§3。约束 2/3/4 只是可行性过滤,不足以选最优:多个可行解的 T_{MTE2} 可差 2 倍§6.3 源码过度切分示例)

    d. 最优选取:通过全部约束的候选中取 T_{MTE2} 最小者;并列时取尾轮块数 r = B \cdot mCnt \cdot nCnt \bmod C 最大者尾轮块越多§5.3 重切的 s^* 越小、越省)

  4. 尾轮修正n_{wave} = \lceil B \cdot mCnt \cdot nCnt / C \rceil \le 3r > 0 时,按 §5.3 计算重切因子 $s^$,尾轮时延 $T_{tail} = T_{block}/s^$;否则 $T_{tail} = T_{block}$r=0 时为 0

  5. 端到端校验$T = \max(T_{MTE2},; T_{MMAD},; T_{FIX}) + T_{tail}$。若选出的候选 $T_{MTE2} < T_{MMAD}$(搬移被计算掩盖),候选间差异失效,此时以尾轮最优者为准

完整实例B=8、M=N=2048、K=1024、BF16BaseM=BaseN=256、$min_TileSize$=16KB

  • $P = \lceil 32/8 \rceil = 4$,进入步骤 3。枚举空间 $mCnt, nCnt \le \lceil 2048/256 \rceil = 8$8 \cdot mCnt \cdot nCnt \ge 32
  • 枚举与评估:
(mCnt, nCnt) sM × sN $k_{L1}$(约束 2 上限16 对齐) 约束 3 过滤 T_{MTE2} \propto (1/sM + 1/sN) \cdot k_{L1}
(2, 2) 1024 × 1024 64128B ✗ dValue < 256B
(4, 2) 512 × 1024 80160B
(4, 4) 512 × 512 128256B 512×128×2 = 128KB ≥ 16KB 0.500
(8, 4) 256 × 512 160320B 0.938
(4, 8) 512 × 256 160 0.938
(8, 8) 256 × 256 256512B 2.000

k_{L1} 上限公式:$\lfloor 131072/(sM{+}sN) \rfloor$131072 = L1/(2·dtype) = 512KB/4B。评估过程展示三个要点

  1. 最少切分 (2,2) 不可行k_{L1} \le 64 元素 = 128B违反 dValue ≥ 256B——若只做约束 1并行度检查会在 (2,2) 上误判达标,必须连同约束 2/3 一起过滤;
  2. 多个候选通过约束(4,4)、(8,4)、(4,8)、(8,8) 都满足约束 2/3/4——约束检查不足以选最优,必须计算目标函数;
  3. 目标函数 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 完美整除、无尾轮

与源码的差异:源码用 cubeBound 模型 + balanceRate ≥ 0.9 剪枝,不解耦 SingleCoreM/N 与 BaseM/NbaseM=256 已是 SingleCore 级参数L0 tile 由 stepM/stepN 二次切分),且枚举目标为"算存比/负载均衡帕累托"而非"搬入时延最小"。理论的两层分离使约束链更清晰、目标函数($T_{MTE2}$有闭式表达——SingleCoreM/N 由 L1 + 并行度 + 搬移效率决定BaseM/N 由 L0C 决定,各司其职。

5.3 mCnt / nCnt 与核间分配(含尾轮处理)


mCnt = \Big\lceil \frac{M}{\text{singleCoreM}} \Big\rceil,\qquad nCnt = \Big\lceil \frac{N}{\text{singleCoreN}} \Big\rceil

总输出块数 = $B \times mCnt \times nCnt$。核间分配采用 B→M→N 线性映射(分析见 §四):块按 (b, m, n) 字典序编号,核 i 处理块 i, i+C, i+2C, …。B 不整除 C 时尾波不满载的核少分一块,无需 B | C。

尾轮处理host 端预处理,零 NPU 开销):

尾轮定义

B \cdot mCnt \cdot nCnt \;\%\; C \neq 0 时,总块数不能被 C 整除,最后一波(尾轮)不满载——只有 B \cdot mCnt \cdot nCnt \;\%\; C 个核有活干,其余核空闲。

尾轮重切:是否值得?

关键前提tiling 在 hostCPU上完成不占 NPU 时间。 重切只需 host 多算一套 tiling 参数下发给 NPU零 NPU 开销。这改变了收益/代价的平衡——重切的代价仅为 host 端多一次枚举,而收益是 NPU 端尾波时延的缩短。

不重切时:尾波 r 个块用 r 个核,每核处理 1 块,时延 = $T_{block}$C - r 核空闲。

重切时:把 r 个块沿 M 或 N 维再切 s 份,变成 r \cdot s 个小块,r \cdot s 个核各处理 1 小块,时延 ≈ $T_{block}/s$(每小块计算量/搬移量/写回量均为原块的 $1/s$)。

重切收益


\Delta T_{saved} = T_{block} \cdot \Big(1 - \frac{1}{s}\Big)

占总时延比例


\frac{\Delta T_{saved}}{T_{total}} \approx \frac{1 - 1/s}{n_{wave}}

$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 时收益显著,应重切。

重切约束的推导:沿 N 切 s 份后,小块变为 $[\text{singleCoreM},; \text{singleCoreN}/s]$。小块须满足两类约束——但约束的严格程度取决于算子是访存 Bound 还是计算 Bound

访存 Bound$T_{MTE2} \ge T_{MMAD}$:搬移是瓶颈,小块的 dValue 和搬移量不能降——否则搬移更慢,总时延反而增加。约束与主 tile 相同:

  1. dValue 下限B 矩阵 dValue = $\text{singleCoreN}_{tail} \cdot dtype \ge 256\text{B}$(沿 N 切时):

s \le \frac{\text{singleCoreN} \cdot dtype}{256\text{B}}
  1. 单次搬移量$k_{L1} \cdot \text{singleCoreN}_{tail} \cdot dtype \ge min_TileSize$

s \le \frac{k_{L1} \cdot \text{singleCoreN} \cdot dtype}{min\_TileSize}
  1. 对齐$\text{singleCoreN}_{tail} \ge 16$

s \le \frac{\text{singleCoreN}}{16}

计算 Bound$T_{MMAD} > T_{MTE2}$搬移被计算掩盖dValue 和搬移量约束可放宽——即使搬移效率降低,只要搬移时间仍 ≤ 计算时间,总时延不变。放宽的定量依据:

小块计算时延 $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}$。搬移被掩盖的条件:


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})}

记右端为 $x_{min}$(搬移掩盖下限),则计算 Bound 下的约束为:


s \le \min\Big(\frac{\text{singleCoreN}}{x_{min}},\; \frac{\text{singleCoreN}}{16}\Big)

计算 Bound 越严重K 越大),x_{min} 越小,s 上限越大——极端情况 $x_{min} \to 16$(对齐底线),$s \le \text{singleCoreN}/16$。

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$。

最优切分因子:先判定 Bound 类型(T_{MMAD} vs $T_{MTE2}$),再选约束集:


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}

C=32、$N_{blk}=40$、$n_{wave}=2$、$r=8$、singleCoreM=singleCoreN=256、$k_{L1}$=128、BF16

  • 访存 BoundK 小dValue $s \le 2$,搬移量 $s \le 4$,对齐 $s \le 16$。$s^* = 2$——dValue 瓶颈16 核满载,尾波减半,总时延节省 25%。
  • 计算 BoundK 大):$x_{min} \approx 1$$s^* = \min(4, 16) = 4$——32 核满载,尾波降为 1/4总时延节省 37.5%。

切分方向选择:优先沿 N 切(保持 A 行带完整L2 中 A 数据不变)。计算 Bound 下若 N 向对齐卡住($\text{singleCoreN}/16 < C/r$),改沿 M 切A 矩阵 dValue = k_{L1} \cdot dtype 不变,仅受对齐约束 $\text{singleCoreM}/16$)。

结论n_{wave} \le 3r < C 时应重切尾轮——host 端零代价NPU 端收益 $T_{block}(1-1/s^*)$。n_{wave} \ge 4 时收益 < 25%,可不重切(通过选择使尾波占比小的 mCnt/nCnt 组合来优化)。

尾轮影响的量化

设总块数 $N_{blk} = B \cdot mCnt \cdot nCnt$,总波数 $n_{wave} = \lceil N_{blk} / C \rceil$,尾波块数 $r = N_{blk} \bmod C$r = 0 时无尾波)。

不重切时尾波导致的额外时延(相对于完美整除):


\Delta T_{tail} = \begin{cases} 0 & r = 0 \\ T_{block} \cdot \big(1 - \frac{r}{C}\big) & r > 0 \end{cases}

重切后尾波时延降为 $T_{block}/s^*$,额外时延:


\Delta T_{tail}^{re} = \frac{T_{block}}{s^*} \cdot \Big(1 - \frac{r \cdot s^*}{C}\Big)

当 $r \cdot s^* = C$(完美重切)时 $\Delta T_{tail}^{re} = 0$——尾波完全消除。


5.4 核间切分维度选择(按共享代价从低到高)

切 B零共享先试→ 切 M右矩阵 KN\cdot\text{dtype} \le L2 则驻留 L2→ 切 N对称→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见 Step 8

5.5 swizzle——ASW 滑窗蛇形

问题:核间切 M/N 后,同一时刻 C 个核各算一个输出块,它们所需的 A 行块与 B 列块集合就是当前"活跃工作集"。若按行优先顺序朴素分配,一波 C 个块横跨的 A 行、B 列很宽,活跃工作集超过 L2 就回 GM 读1.6TB/s重复读代价真实发生。swizzle 要做的就是编排输出块的执行顺序,把每一波核的活跃工作集压到最小。

做法:把 M 向每 W 个基本块划为一个"窗口",遍历顺序为"窗口内先扫 M、扫满 W 行再进下一列 N一个窗口扫完再进下一个窗口",且奇数窗口行 N 向反向(蛇形)。效果有二:

  • 同一波 C 个核的块集中在同一个窗口内 ⇒ 活跃 A 行块只有 W 个、活跃 B 列块只有 C/W 条带;
  • 蛇形反向使相邻窗口行首尾相接——上一窗口末尾的 B 列带与下一窗口开头的 B 列带是同一条,跨窗口切换时工作集增量最小。

W 怎么取:一波 C 个块的 L2 足迹约为


footprint \approx \big(W \cdot M^t K + \tfrac{C}{W} \cdot K N^t\big) \cdot \text{dtype}

由均值不等式,W + C/WW = \sqrt{C} 处取最小——窗口越接近"方形"W 行 × C/W 列),足迹越小。同时 W 须整除 C保证每个窗口恰好被整数波核覆盖、窗口边界不把波次切碎。合起来即


W = \max\{\,d \mid d \mid C,\; d \le \lfloor\sqrt{C}\rfloor\,\}

C=32 时 $\sqrt{32} \approx 5.66$,因子 {1,2,4,8,…} 中不超过它的最大者是 4故 W=4。

实例C=32W=4M̃=8Ñ=8数字为块的全局执行顺序一波 32 块):

窗口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

块 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。

窗内为什么不蛇形(对上例中"块 3=(μ3,ν0) → 块 4=(μ0,ν1) 而非 (μ3,ν1)"的说明):蛇形的收益来自"相邻遍历段共享边界数据",要分两种边界看:

  • 窗内列间边界ν0→ν1相邻两段共享的是同一组 A 行块W 个),它们在整个窗口期间全程驻留 L2,无论按什么顺序扫,工作集不变——窗内蛇形零收益;
  • 窗口行边界窗口0→窗口1A 行整体换血μ0..3 → μ4..7),此时 B 列带的连续性决定换血成本——不蛇形则下一窗口从 ν0 开始LRU 上最久未用、早已被挤出 L2 的冷带),蛇形则延续上一窗口末尾的 ν7最热线带蛇形只标在窗口行号上(源码 BatchMatMulAswBlock::UpdateBasicIndex:仅 rowIdx 为奇时 n 反向,窗内 m 最快序不反向),正是这个收益结构的直接实现。

5.6 L2 分组(工作集超 L2 时)

切的是什么:将 mCnt×nCnt 个基本块划分为若干执行组——每组覆盖输出平面上一个连续矩形区域(若干 singleCoreM × singleCoreN 基本块的集合),使该组所需的 A 行带 + B 列带输入工作集 ≤ L2 可用读入空间;组内所有基本块算完再进下一组,输入只在跨组时换一次。

为什么需要它:滑窗压缩的只是"同一波"的足迹;若整个工作集超 128MB L2跨波次复用落空——上一波窗口的 A 行早被挤出,下一波又得回 GM 读。且 L2 是读写共用的:输出经 fixpipe 写出时若驻留 L2dirty会压缩读入可用空间若直写 GM则占用与读共享的 1.6TB/s 总线。所以 L2 切分必须与写出策略联合决策。记输入总量 $S_{in} = B(MK+KN)\cdot\text{dtype}$,输出总量 $S_{out} = B \cdot MN \cdot outB$。

两个不变量(一切分析的起点):

  • GM 流量下界 $= S_{in} + S_{out}$:输入至少读一遍、输出最终至少要写一遍到 GM与 L2 策略无关;
  • L2 读入可用空间:$L2_{read} = L2 - S_{out}^{resident}$S_{out}^{resident} 为驻留 L2 的输出量)——写出驻留 L2 会压缩读入空间,这是写出策略影响读入复用的通道。

重复读倍率r_{in} = GM 输入流量 / $S_{in}$。r_{in} = 1 表示每个输入数据从 GM 只读一遍(后续复用全在 L2 命中)——这是 GM 输入流量的下界L2 管理的全部目标就是让 r_{in} 尽量接近 1。

先判定写出会不会 Bound。平均写出带宽需求:


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}

只与 K、outB 有关K 越小单位时间输出越密。例BF16 输出C·Q₁₆=432 TFLOPSK=512 → 844 GB/sK=256 → 1.69 TB/s已超 GM 总线——此时任何策略都写出 BoundL2 缓冲只能削峰fixpipe 以 5.2TB/s 写 L2 吸收突发),平均速率仍受总线限制,应预期 Fixpipe 成为 T_{total} 的 max 项。

分场景决策

场景 A$S_{in} + S_{out} \le L2$(全驻留)。输入读一遍($r_{in}=1$),输出驻留 L2dirty异步回写 GM——写出走 5.2TB/s L2 写口,不与读争,也削平了 GM 写突发。无需切分。

场景 BS_{in} \le L2 但 $S_{in} + S_{out} > L2$(输入能驻留,加上输出超了)。策略:输入驻留、输出直写 GM。理由链:

  1. S_{in} \le L2 ⇒ 全部输入可驻留 L2跨波次复用全部命中 ⇒ $r_{in} = 1$GM 输入流量达到下界 $S_{in}$
  2. 输出在本算子内只写不读、零复用收益;若输出也驻留 L2dirty超出 L2 的部分会把输入挤出——被挤出的输入后续得回 GM 重读 ⇒ $r_{in} > 1$GM 流量超出下界;
  3. 故让输出直写 GMfixpipe L0C→GM不占 L2把 128MB 全部留给输入,保住 $r_{in} = 1$——GM 总流量保持下界 $S_{in} + S_{out}$
  4. 代价是输出即刻占用 GM 写带宽(与读共享总线),须校验总线不爆:$(S_{in} + S_{out})/T_{MMAD} \le W_{GM}$。

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 < 1.6TB/s ✓。

场景 C$S_{in} > L2$(输入本身超)。需要分组执行——把 mCnt×nCnt 个基本块划分为若干执行组,每组内所有基本块的输入工作集不超过 L2 可用空间。输出直写 GM不占 L2 读入空间)。

L2 的软件可控手段L2 是 Cache 而非 Buffer软件无法精确控制"哪些数据在 L2 里"。可用的控制手段:

  • Cache HintSetL2CacheHint):标记输入为 allocate读入 L2或 non-allocate直读 GM 不过 L2标记输出为 non-allocate直写 GM 不占 L2
  • CMOCache Maintenance OperationPrefetch/Writeback/Invalidate在关键节点主动管理 L2 内容;
  • 执行顺序swizzle通过编排基本块的执行顺序控制同一时刻的活跃工作集——这是最主要的 L2 管理手段

执行组的划分

问题mCnt×nCnt 个基本块(每块输出 singleCoreM×singleCoreN按什么粒度分组使每组的输入工作集 ≤ L2

每组输入工作集:一组覆盖 M 向 m_{grp} 个基本块、N 向 n_{grp} 个基本块,即覆盖输出区域 $[m_{grp} \cdot \text{singleCoreM},; n_{grp} \cdot \text{singleCoreN}]$。该区域需要读入的输入:


WS_{grp} = B \cdot K \cdot \big(m_{grp} \cdot \text{singleCoreM} + n_{grp} \cdot \text{singleCoreN}\big) \cdot \text{dtype} \;\le\; L2

目标:最小化组数(组数越少,输入从 GM 的重复读次数越少)。每行 A 被 n_{grp} 个组各读一次,每列 B 被 m_{grp} 个组各读一次:


r_{in} = \frac{n_{grp} \cdot M + m_{grp} \cdot N}{M + N}

求解:约束 $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 向基本块数与输出平面形状成正比时取到:


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

总组数 $= \lceil mCnt/m_{grp} \rceil \times \lceil nCnt/n_{grp} \rceil$。

组内 swizzle:每组内部按 ASW 滑窗蛇形执行(窗口 $W = \max{d \mid d \mid C,; d \le \lfloor\sqrt{C}\rfloor}$C=32 时 W=4保证同一波 C 个核的活跃工作集最小。组间切换时输入整体换入——上一组的 A 行带和 B 列带全部失效,从 GM 重新读入下一组的数据。

B=64、M=N=2048、K=1024、BF16、singleCoreM=singleCoreN=256$S_{in} = 64 \times (2048 \times 1024 + 1024 \times 2048) \times 2 = 537\text{MB} > 128\text{MB}$。D = 128\text{MB}/(64 \times 1024 \times 2\text{B}) = 1024 元素。$m_{grp} = \lfloor 1024/(2 \times 256) \rfloor = 2$$n_{grp} = 2$。每组覆盖 [512, 512] 的输出区域,工作集 $= 64 \times 1024 \times (512+512) \times 2 = 128\text{MB} = L2$,恰好装满。总组数 $= (2048/256/2)^2 = 16$。$r_{in} = (2 \times 2048 + 2 \times 2048)/(2048+2048) = 2$——每行 A 被读 2 次,每列 B 被读 2 次。

块内分配用错位分核(对角线分配):线性块号先取 mn 方向叠加随块号递增的相位偏移,使同一时刻各核落在 M×N 平面的不同对角线上——避免多核同一拍并发读同一行 A / 同一列 B 的同一地址同地址并发读会串行化等效带宽打折。例8 核、4×4=16 个基本块k0~k7 为核号):

行优先(不错位):              错位分核(对角线):
      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

冲突度量与优选规则:


transConflict = \max\big(\lceil C / mCnt \rceil,\; \lceil C / nCnt \rceil\big) \le 6

即同一时刻并发核访问同一 A/B 块的最大冲突数不超过阈值(经验值 6切分方案中优先选尾波不满载占比小拖尾 < 一半)的。遍历大方向由 calOrder 决定0=M 优先、1=N 优先),按形状选共享矩阵更能驻留 L2 的方向。

补充:若输出会被后续算子立即消费(融合场景),输出驻留 L2 让下游读命中,场景 B/C 的策略反过来;本文按单算子边界分析。

5.7 核内 tilingBaseM/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。

5.8 内部特化(参数极限,不是独立分支)

单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次L1 全载)。

5.9 降核模式实现

tiling 时 usedCoreNum = ⌈P⌉(不强制 CSingleCoreM/N 在 L0C 容量内取最大($\text{singleCoreM} \times \text{singleCoreN} \times 4\text{B} \le L0C$每核按标准核内流水L1→L0→Cube→L0C→Fixpipe处理自己的输出块核间无共享无依赖无需 swizzle 与 L2 切分。降核后 GM 并发搬移核数若 < minCoreNum带宽利用率上限被压低——这正是降核区 case 时延的瓶颈所在,也是"时延绝对值小、不再继续优化"的定量注脚。

5.10 尾轮处理的三种策略对比与适用分界

给定 B、M、K、N、dtype按 §5.1/§5.2 确定 BaseM/BaseN/SingleCoreM/SingleCoreN 后,总块数 N_{blk} = B \cdot mCnt \cdot nCnt 一般不是 C 的整数倍,尾轮只有 r = N_{blk} \bmod C 个核工作。对尾轮有三种处理策略:

策略 做法 块大小
A0不重切 尾轮 r 核各处理 1 个整块,C-r 核空转 主轮尾轮同大小
A1尾轮差异化处理 尾轮 r 块的区域用更小的 tile 重切,凑满 C 核 主轮整块 + 尾轮小块
B整轮均匀重切 总块数向上取整到 $N_{blk}' = n_{wave} \cdot C$,全局重新枚举 SingleCoreM/N 使每轮每核恰好一个同样大小的块 全部块同大小

A1 的两种形态v1.8 修订——r > C/2 时 A1 不再退化):

  • A1a整数倍切分§5.3 原方案)——尾轮每块沿 N或 Ms^* 份,r \cdot s^* 个小块分给 C 核。受 s^* \le \lfloor C/r \rfloor 整数约束,仅在 r \le C/2 时可用$s^* \ge 2$
  • A1b尾轮 tile 重选凑满核——尾轮 r 个原块覆盖的输出区域(面积 $r \cdot sM \cdot sN$)用更小的 tile (sM_t, sN_t) 重新切分,使尾轮块数凑到 C或最接近 C 的可行值。tile 按 16 对齐步进缩小(如 $sM_t = sM - 16k$),理想值为

s_t^* = sM \cdot \sqrt{\frac{r}{C}} \quad \text{(方形同步缩小,} sM = sN \text{ 时)}

再按 16 对齐下取,使重切块数 $\lceil r \cdot sM \cdot sN / (sM_t \cdot sN_t) \rceil \le C$。r \le C/2 限制——r > C/2 时同样可用,打破 A1a 的整数约束失效问题。

本节在计算 Bound / 访存 Bound 两类场景下,分别对 A0 vs A1、A0 vs B、A1 vs B 三组对比做完整推导,给出适用条件分界。

统一建模

符号:$n_{wave} = \lceil N_{blk}/C \rceil$(总轮次),$r = N_{blk} \bmod C$(尾轮块数),$\rho = r/C$(尾轮占比),s^* 为 A1a 的整数切分因子,g = N_{blk}'/N_{blk} = n_{wave}C/N_{blk} > 1 为 B 的块数放大倍数。

块几何对三类时延的缩放律(块面积缩 g 倍、线性尺寸缩 \sqrt{g} 倍,近方形比例):

时延项 依赖 缩放律
$T_{MMAD}$(单块 Cube 计算) \propto 面积 sM \cdot sN \propto g^{-1}
$T_{MTE2}$(单块 GM→L1 搬入) \propto 周长 (sM + sN) \propto g^{-1/2}
$T_{FIX}$(单块写出) \propto 面积 $\propto g^{-1}$(总量与切分无关)

带宽模型声明:昇腾 950PR 每核 MTE2 带宽上限按 BW_{pc} = W_{GM}/C = 50 GB/s 建模§1.3)——尾轮 r 核聚合带宽仅 $r \cdot BW_{pc}$尾轮搬移不加速。HBM 全局共享池模型的边界说明见本节末。

三策略通用时延式T_{block} = \max(T_{MMAD}, T_{MTE2}, T_{FIX}) 为单块主导项):


T_{A0} = n_{wave} \cdot T_{block},\qquad T_{A1} = (n_{wave}-1) \cdot T_{block} + T_{tail},\qquad T_B = n_{wave} \cdot T_{block}'

A1a 时 $T_{tail} = T_{block}/s^*$(计算 BoundA1b 凑满 C 核时,尾轮小块面积为 $r/C \cdot sM \cdot sN$,计算 Bound 下:


T_{tail}^{A1b} = \frac{r}{C} \cdot T_{block} = \rho \cdot T_{block} \quad \text{(凑满核,理想)}

情形一:计算 Bound$T_{MMAD} > T_{MTE2}$

1A0 vs A1

A1a 可行时($r \le C/2$


\Delta_{A0 \to A1a}^{calc} = T_{MMAD}\Big(1 - \frac{1}{s^*}\Big)

r > C/2 时 A1a 的 s^* = \lfloor C/r \rfloor = 1 失效,但 A1b 不退化:尾轮 tile 重选凑满 C 核后:


\Delta_{A0 \to A1b}^{calc} = T_{MMAD} - \rho \cdot T_{MMAD} = T_{MMAD}\Big(1 - \frac{r}{C}\Big)

合并:


\text{A0 vs A1计算 BoundA1 恒优(} r > 0 \text{} \Delta = \begin{cases} T_{MMAD}(1 - 1/s^*) & r \le C/2 \text{A1a} \\ T_{MMAD}(1 - r/C) & r > C/2 \text{A1b} \end{cases}

A0 在计算 Bound 下永不最优r=0 除外)。

2A0 vs B


\Delta_{A0 \to B}^{calc} = n_{wave} T_{MMAD} - \frac{n_{wave}}{g} T_{MMAD} = T_{MMAD} \cdot \frac{C - r}{C} > 0 \quad (r > 0)

B 恒优于 A0(可行性校验:搬移掩盖 $\sqrt{g} \le T_{MMAD}/T_{MTE2}$;分解对齐)。

3A1 vs B

A1b 凑满核时 $T_{A1b} = T_{MMAD}(n_{wave} - 1 + \rho)$,与 B 的 T_B = T_{MMAD}(n_{wave} - 1 + r/C) 理论时延严格相等——两者都是"总计算量/C"(计算 Bound 下时延与切分方式无关,只要轮轮满载)。

差异在两个二阶项:

  • 搬移量(周长和)A1b 主轮保持大 tile、只缩尾轮B 全局均匀缩小。设方形 tile 边长 $s$A1b 周长和 $= 2sC(n_{wave}-1) + 2s\sqrt{\rho} \cdot C$B 周长和 $= 2s_B \cdot n_{wave} C$$s_B = s\sqrt{N_{blk}/(n_{wave}C)}$)。两边约去 2sC 后比较 n_{wave}-1+\sqrt{\rho} 与 $\sqrt{n_{wave}(n_{wave}-1+\rho)}$

\big(n_{wave}-1+\sqrt{\rho}\big)^2 \le n_{wave}(n_{wave}-1+\rho) \iff 2\sqrt{\rho} \le 1 + \rho \iff (\sqrt{\rho}-1)^2 \ge 0

均值不等式A1b 周长和恒 ≤ B(等号当 $\rho = 1$,即无尾轮)。搬移少意味着 L2 重复读少、计算 Bound 掩盖余量更大;

  • 离散对齐浪费A1b 的 s_t^* = s\sqrt{\rho} 一般不是 16 的倍数,须向上取整到 16 对齐(下取则重切块数 > C、凑不满。上取整使尾轮 tile 略大、覆盖略超尾轮区域——例:$r=17$、s=256 时 $s_t^* = 186.6 \to 192$,尾轮时延 19.9\mu s vs 理想 $18.8\mu s$覆盖浪费约 2%。B 的 s_B 同样须 16 对齐,但全局枚举可在 (mCnt', nCnt') 二维空间选最贴合的分解,对齐损失通常更小。

结论(计算 BoundA1 与 B 理论时延相等A1 搬移少(周长和 ≤ B但须两套 tile 参数、尾轮 L 形区域重切有对齐浪费≤2%)。工程上 B 更简洁;理论搬移效率 A1 略优。若尾轮区域形状不规整L 形)导致 A1b 凑满困难,选 B。

例 1计算 Bound$r > C/2$B=1、M=N=1792、K=4096、BF16、C=32。$mCnt=nCnt=7$$sM=sN=256$$N_{blk}=49$、$n_{wave}=2$、$r=17$、$\rho=0.53$。$T_{MMAD}=35.3\mu s$$T_{MTE2}=5.2\mu s$)。

  • A0$70.6\mu s$
  • A1as^* = \lfloor 32/17 \rfloor = 1 失效;
  • A1b$s_t^* = 256\sqrt{0.53} = 186.6 \to 192$16 对齐上取),尾轮 \lceil 17 \times 256^2/192^2 \rceil = 31 块凑 31 核,尾轮时延 $2 \times 192^2 \times 4096/15.2\text{T} = 19.9\mu s$$T_{A1b} = 35.3 + 19.9 = 55.2\mu s$
  • B$N_{blk}'=64=8\times8$、$sM'=sN'=224$$T_B = 54.1\mu s$。

A1b 与 B 相当55.2 vs 54.1,离散对齐浪费 2%);周长和 A1b = 32 \times 512 + 31 \times 384 = 28288 vs B $= 64 \times 448 = 28672$A1b 搬移少 1.3%)。相对 A0 均省约 23%

例 2计算 Boundr \mid C 完美点)M=1536、N=2048、K=4096、BF16。$mCnt=6, nCnt=8$$sM=sN=256$$N_{blk}=48$、$r=16$。A1as^* = 2 = C/r 完美,$T_{A1a} = 35.3 + 17.7 = 53.0\mu s$B$T_B = 53.0\mu s$。A1a = B$1/s^* = r/C$),选 A1a搬移增量小


情形二:访存 Bound$T_{MTE2} \ge T_{MMAD}$

主导项 $T_{block} = T_{load} = (sM + sN) \cdot k_{L1} \cdot dtype / BW_{pc}$。

1A0 vs A1

A1a$r \le C/2$):尾轮沿 N 切 s^* 份,小块搬移 $(sM + sN/s^) k_{L1} dtype$A 行带不随 s^* 缩小——结构性弱点),方形下 $T_{tail} = T_{load}(1+1/s^)/2$


\Delta_{A0 \to A1a}^{mem} = \frac{T_{load}}{2}\Big(1 - \frac{1}{s^*}\Big)

受 dValue 硬约束 $s^* \le sN \cdot dtype/256\text{B}$BF16、sN=256 时 $s^* \le 2$)。

A1b任意 $r$):尾轮区域用 s_t = s\sqrt{\rho} 的 tile 重切凑满 C 核,尾轮总搬移 $= C \cdot 2s\sqrt{\rho} \cdot k_{L1}$C 核满载聚合带宽 $C \cdot BW_{pc}$


T_{tail}^{A1b} = \frac{2s\sqrt{\rho} \cdot k_{L1} \cdot dtype}{BW_{pc}} = \sqrt{\rho} \cdot T_{load}

\Delta_{A0 \to A1b}^{mem} = T_{load}\big(1 - \sqrt{\rho}\big) > 0 \quad (\rho < 1)

A1b 恒优于 A0dValue 允许时:$s_t \cdot dtype = \sqrt{\rho} \cdot sN \cdot dtype \ge 256\text{B}$)。

2A0 vs B


\Delta_{A0 \to B}^{mem} = n_{wave} T_{load}\Big(1 - \frac{1}{\sqrt{g}}\Big) > 0

B 恒优于 A0dValue 约束 g \le (sN \cdot dtype/256\text{B})^2 满足时)。

3A1 vs B

比较总搬移量周长和。A1b主轮 (n_{wave}-1)C 块大 tile + 尾轮 C 块 s\sqrt{\rho} tile


S_{A1b} = 2sC\big(n_{wave} - 1 + \sqrt{\rho}\big)

Bn_{wave} Cs\sqrt{N_{blk}/(n_{wave}C)} tile


S_B = 2s \cdot n_{wave} C \cdot \sqrt{\frac{N_{blk}}{n_{wave} C}} = 2sC\sqrt{n_{wave}\Big(n_{wave} - 1 + \rho\Big)}

由均值不等式 S_{A1b} \le S_B 恒成立(推导同计算 Bound且总时延正比于周长和满载轮聚合带宽相同


\frac{T_{A1b}^{mem}}{T_B^{mem}} = \frac{n_{wave} - 1 + \sqrt{\rho}}{\sqrt{n_{wave}(n_{wave}-1+\rho)}} \le 1

访存 Bound 下 A1b 恒不劣于 B\rho 小时优势大($\rho=0.1$、n_{wave}=2 时优 11%\rho \to 1 时趋同)。

dValue 修正A1b 凑满要求 $s_t = s\sqrt{\rho} \ge 256\text{B}/dtype$B 非转置):


\sqrt{\rho} \ge \frac{256\text{B}}{sN \cdot dtype} \iff \rho \ge \Big(\frac{256\text{B}}{sN \cdot dtype}\Big)^2

不满足时 A1b 只能缩到 $s_t = 256\text{B}/dtype$dValue 下限),尾轮块数 = r \cdot (sN \cdot dtype/256\text{B})^2 < C 凑不满A1b 退化为部分凑满,此时与 B 打平或略差,选 B若 B 的 g 满足 dValue

访存 Bound 数值实例

例 3\rho 小、dValue 卡死)M=N=1536、K=256、BF16。$mCnt=nCnt=6$、$N_{blk}=36$、$n_{wave}=2$、$r=4$、$\rho=0.125$。$T_{load} = 5.24\mu s$$k_{L1}=256$)。

  • A0$10.5\mu s$
  • A1a$s^* = \min(8, 2, 16) = 2$dValue$T_{A1a} = 5.24 + 3.93 = 9.2\mu s$
  • A1b$s_t^* = 256\sqrt{0.125} = 90.5$dValue 下限 $256\text{B}/2\text{B} = 128$——$90.5 < 128$dValue 卡死,只能 $s_t = 128$,尾轮 4 \times 256^2/128^2 = 16 块(半满载),$T_{tail} = 2.62\mu s$$T_{A1b} = 7.9\mu s$(理论无约束值 5.24 \times 1.354 = 7.1\mu s 达不到);
  • B$N_{blk}'=64$、$sM'=sN'=192$$T_B = 7.9\mu s$。

dValue 卡死后 A1b = B = 7.9μs(打平)。

例 4$r > C/2$、dValue 满足A1b 优)M=N=2304、K=256、BF16。$mCnt=nCnt=9$、$N_{blk}=81$、$n_{wave}=3$、$r=17$、$\rho=0.53$。\sqrt{\rho} \cdot sN \cdot dtype = 0.729 \times 256 \times 2 = 373\text{B} \ge 256\text{B} ✓。

  • A0$15.7\mu s$A1as^* = 1 失效(=A0
  • A1b$s_t = 192$,尾轮 32 块搬移 $32 \times 384 \times 256 \times 2 / (32 \times 50\text{G}) = 3.93\mu s$$T_{A1b} = 2 \times 5.24 + 3.93 = 14.4\mu s$
  • B$N_{blk}'=96$、(8,12) 分解($sM'=288, sN'=192$$T_B = 3 \times 4.92 = 14.7\mu s$。

A1b 优 2.2%(周长和 $45056 < 46080$,与均值不等式结论一致)。


三策略决策总表(保留 v1.7,按 A1 扩展更新)

Bound 判据:$K^* = \dfrac{k_{L1} \cdot dtype \cdot Q_{16}}{2 \cdot BW_{pc}}\Big(\dfrac{1}{sM} + \dfrac{1}{sN}\Big)$K \ge K^* 为计算 Bound。

场景 A0 vs A1 A0 vs B A1 vs B 最优策略
计算 Boundr \le C/2r \mid C A1a 优 B 优 A1a = B选 A1a搬移少 A1a
计算 Boundr \le C/2r \nmid C A1a 优 B 优 B 略优(取整损失) A1a/B 皆可
计算 Boundr > C/2 A1b 优(不再退化) B 优 时延相等A1b 搬移少但离散对齐浪费 ≤2% A1b 或 B工程简洁选 B
访存 Bound$\sqrt{\rho} \cdot sN \cdot dtype \ge 256$B A1b 优 B 优 A1b 恒优(周长和 ≤ B A1b
访存 Bound$\sqrt{\rho} \cdot sN \cdot dtype < 256$BdValue 卡死) A1b 部分凑满 B 优 打平A1b 退化) Bg 可行)
dValue 全面卡死B 也不可行) A1as_{dv}^* \ge 2 时)或 A0 B 不可行 A1a/A0

基于 B/M/K/N/dtype 的直接判定表

以下五步闭式计算后查表即得最优策略,无需逐项建模仿真:

前置计算(全部为 B/M/K/N/dtype 与芯片规格的闭式函数):

  1. 首轮切分§5.2 枚举):得 $mCnt, nCnt, sM, sN, k_{L1}$
  2. 尾轮参数$N_{blk} = B \cdot mCnt \cdot nCnt$$n_{wave} = \lceil N_{blk}/C \rceil$$r = N_{blk} \bmod C$$\rho = r/C$
  3. Bound 判定$K^* = \dfrac{k_{L1} \cdot dtype \cdot Q_{16}}{2 BW_{pc}}\Big(\dfrac{1}{sM} + \dfrac{1}{sN}\Big)$K \ge K^* → 计算 Bound
  4. dValue 可行性$\rho_{dv} = (256\text{B}/(sN \cdot dtype))^2$A1b 访存可行需 $\rho \ge \rho_{dv}$$g_{dv} = (sN \cdot dtype/256\text{B})^2$B 访存可行需 $g \le g_{dv}$
  5. 单块主导项$T_{block} = \max(T_{MMAD}, T_{load})$。

决策表

# 条件 最优策略 端到端时延
1 r = 0 A0无尾轮 n_{wave} \cdot T_{block}
2 计算 Bound0 < r \le C/2 A1as^* = \lfloor C/r \rfloor T_{MMAD}(n_{wave} - 1 + 1/s^*)
3 计算 Boundr > C/2 A1bs_t = sM\sqrt{\rho} ↓16或 B$N_{blk}' = n_{wave}C$ T_{MMAD}(n_{wave} - 1 + \rho)
4 访存 Bound\rho \ge \rho_{dv} A1b T_{load}(n_{wave} - 1 + \sqrt{\rho})
5 访存 Bound$\rho < \rho_{dv}$g \le g_{dv} B T_{load} \cdot n_{wave}/\sqrt{g}
6 访存 BoundB 也不可行 A1a$s^* \ge 2$)或 A0 T_{load}(n_{wave}-1) + T_{load}(1+1/s^*)/2

判定流程图

[B, M, K, N, dtype]
    │
    ▼
<§5.2 首轮枚举 → mCnt, nCnt, sM, sN, kL1>
    │
    ▼
<N_blk, n_wave, r, ρ = r/C>
    │
    ├─ r = 0 ────────────────▶ A0无尾轮
    ▼
<K ≥ K* ?(计算 Bound>
    │
    ├─ 是 ── r ≤ C/2 ? ──┬─ 是 ─▶ A1as* = ⌊C/r⌋
    │                    └─ 否 ─▶ A1bs_t = sM√ρ ↓16或 B
    │                              (时延相等;工程简洁选 B
    │
    └─ 否(访存 Bound── ρρ_dv ? ──┬─ 是 ─▶ A1b周长和恒 ≤ B
                                       └─ 否 ─▶ Bg ≤ g_dv 时)
                                             或 A1a/A0 兜底

整体结论v1.8 更新):

  1. A0 从来不是最优r > 0 时 A1 或 B 严格优A0 只是可行性兜底;
  2. A1 经 A1b 扩展后在 r > C/2 时不再退化:计算 Bound 下 A1b 与 B 理论时延相等(总计算量/C 守恒)、搬移更少(周长和 ≤ B均值不等式访存 Bound 下 A1b 恒不劣于 B
  3. B 的价值:工程简洁(全局一套 tile、无尾轮 L 形区域对齐浪费、dValue 卡死时是 A1b 的替代;
  4. 访存 Bound 的 dValue 是 A1b 的硬约束\rho < (256\text{B}/(sN \cdot dtype))^2 时尾轮 tile 缩不到凑满尺寸A1b 退化为部分凑满,与 B 打平。

边界说明(带宽模型敏感性):访存 Bound 结论依赖"每核 MTE2 带宽上限 $BW_{pc}$"假设。若 HBM 为全局共享池(尾轮 r 核可吃满 $W_{GM}$A0 尾轮搬移时延已是 \rho \cdot T_{load} 接近理想A1b/B 的搬移增量无带宽补偿,结论反转。昇腾 950PR 的 MTE2 为每核独立 DMA 引擎、带宽按核数配平,采用固定份额结论;临界 case 建议实测复核。

A1b 的工程代价:需两套 tile 参数(主轮大 tile + 尾轮小 tile与尾轮区域的边界处理r 个原块的并一般为 L 形按矩形分解重切host 端多一次枚举NPU 侧 kernel 需支持尾轮 tile 尺寸切换。这些复杂度不改变时延结论,但影响实现成本——与 B全局一套 tile的工程权衡如上表。

与流程图的衔接:三策略决策嵌入 §7.2 理论流程图阶段 4——先算 $r$、$\rho$、$g$、$s^*$,按 Bound 类型查决策表;选 A1a 按 §5.3 计算整数切分,选 A1b 对尾轮区域重选 tiles_t 按 16 步进缩小至凑满),选 B 回到阶段 2 枚举(约束 1 改为等式 $B \cdot mCnt' \cdot nCnt' = N_{blk}'$)。



六、与源码实现的对比

源码参考 cann-ops-nnDAV_3510/arch35。以下按实现方案的 Step 0-8 逐维度对比。

6.1 进入条件IsCapable

源码batch_matmul_v3_asw_basic_tiling.cpp IsCapable

// 源码https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3
1. A/B 非连续转置状态一致(混合则拒绝)
2. BatchA == BatchB(广播由 ITER_BATCH_BROADCAST 承接)
3. batchBias <= 1
4. dtype  FP16/BF16
// 无其他条件——兜底
维度 理论 源码 差异
并行度校验 P = B \cdot MN \cdot 4B / L0C \ge C 源码不检查 P靠优先级序靠前分支截胡
降核模式 P < C → usedCoreNum = ⌈P⌉ GetNumBlocks() 固定返回 32 核
batch 结构 无限制 BatchA == BatchB 源码排除广播(由分支 4 承接)

影响源码缺少降核分支。P < C 的 case 进入 ASW_Basic 后,所有 32 核仍参与调度,但部分核无实际工作——引入不必要的调度开销。理论上这些 case 应走降核模式usedCoreNum = ⌈P⌉

6.2 BaseM / BaseN 确定§5.1

源码流程MatMulV3TilingHelper::GetRebalanceBlockmatmul_v3_tiling_helper.cpp L387-492

第 1 步——平台指标(动态读 platformInfo适配不同产品形态


hbmBW = freq \times aicNum \times ddrRate/1024,\qquad l2BW = freq \times aicNum \times l2Rate/1024

computePower = freq \times 8 \times aicNum \; (\text{BF16}),\quad \text{FP32 时 } /16

第 2 步——cubeBoundEdge 闭式计算L418-419


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 向复用修正}}

其中 $cmr = (M{+}N)/(MN)$、$l2CacheUsage = \max(B \cdot (M{+}N) \cdot K \cdot dtype/L2,; 1)$。判据:每输出块相对搬运量 1/baseM + 1/baseN \le edge ⟺ 该 tile 处于计算 Bound 侧。

三项物理含义(第一性推导):

  1. ① 基准:单核计算 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 余量)。

  2. ② 工作集超 L2 时抬高 edge → 倾向更小 tile$l2CacheUsage > 1$(工作集 B(M{+}N)K \cdot dtype 超过 L2时实际供数带宽部分掉到 HBM$hbmBW < l2BW$)。抬 edge 使判定放宽——较小 tile工作集小、L2 命中率高、供数更接近 l2BW也能通过计算 Bound 判定。若坚持大 tile工作集更大、超 L2 更多、供数掉到 HBM反而访存 Bound

  3. ③ K 大时压低 edge → 倾向更大 tileK 大计算量大、天然计算 Bound可接受更大 tile更小搬运量而不触访存 Bound。

第 3 步——枚举与评分L448-483

  • 候选上界由多重约束闭式卡出L0A/L0B 容量minKL0、baseMNBufferLimitL0C/UB 面积、bias table、K 内轴 512B 对齐BMM 固定、shape内存 Bound 时对齐粒度 128、计算 Bound 时 64
  • 枚举 baseM、baseN 从大到小递减,两层剪枝:
    • skipCond:最优解已 balance ≥ 0.9 时,若当前 param 更差tile 更小)且超过 edge访存 Bound→ 跳过param 单调增,后续更差)
    • FP32baseM/baseN < 64 且块数 > 核数 → 跳过
  • 更新评分(帕累托双目标):
    • cubeBoundCondparam \le edge 且 balance 更高 → 更新,并把 edge 收紧为 param单调收敛:进入计算 Bound 区域后只接受同样计算 Bound 且更均衡的解)
    • balanceCond:综合分 param/balanceRate 更小者胜(单位负载均衡率的搬运代价),相等取更均衡者
  • 收尾GetBaseKK 全载或 256B/128B/64B/32B/16 递减、usedCoreNum = min(batch×mCore×nCore, 核数)、dbL0C 按容量置 2/1

为什么源码这么做(设计考虑)cubeBound 是 host 端闭式解析模型,把"该 tile 是否计算 Bound"做成一次不等式比较替代实测调优edge 随 shapeK、工作集和平台带宽/频率)自适应;双目标帕累托保证解在"算存比"与"负载均衡"之间取平衡。

与理论最优的优劣判定

维度 理论最优§5.1 源码 判定
tile 面积 L0C/4B = 65536单缓冲满 baseM/baseN 上限 256×256 = 65536 ✓ 一致
形状 方形优先L0A=L0B 同时装满、baseK 最大) 枚举 M/N 独立递减,不强制方形 ⚠️ 源码可能产出非方形 base如 256×192L0B 未满、baseK 被小边限制——可加方形约束改进
baseK min(L0A/2·BM, L0B/2·BN) 向下 16 对齐 GetBaseKK 全载或 256B/128B/64B/32B/16 递减 ✓ 一致
访存/计算 Bound 判定 §5.2 用粗粒度 Bound 判定(分支级) cubeBound 三项解析模型tile 级) ⚠️ 源码更精细,理论可吸收
负载均衡 §5.3 尾轮重切 balanceRate 尾块感知评分 见 §6.8

结论:两者在 tile 面积上殊途同归(都取 L0C 单缓冲满),理论用"方形优先"推导、源码用枚举寻优。源码的 cubeBound 模型是理论"算存比判定"在 tile 粒度上的精细实现值得理论吸收源码的不足是枚举不保持方形、baseM/N 硬上限 256 使 per-core tile 无法超过 L0 容量(见 §6.3)。

6.3 SingleCoreM / SingleCoreN 确定§5.2

源码事实singleCore 与 base 是同一个参数——源码没有分层概念。

证据链:ResetBaseDav3510L144-149baseM = baseN = 256singleCoreM = baseMCalL1TilingDefaultL77-78singleCoreM = runInfo.baseMsingleCoreN = runInfo.baseN;枚举后 stepM = CeilDiv(singleCoreM, baseM) = 1stepN = 1。即每核 tile = L0 tile = baseM×baseN≤ 256×256,不存在"SingleCore 内部多个 L0 tile 多轮计算"的结构。

源码做法baseM/baseN 由 GetRebalanceBlock 枚举确定§6.2 的 cubeBound 模型 + balanceRate ≥ 0.9 剪枝 + 尾块感知评分),一次枚举同时决定"每核 tile 大小"与"L0 tile 大小"——因为两者不区分。

为什么源码这么做(设计考虑):单一参数简化 tiling 生成与 kernel 实现——kernel 只需处理"每核一个 baseM×baseN tile"的循环,无需 stepM/stepN 多轮嵌套。代价是每核 tile 被 L0 容量(≤ 256×256硬性限制

与理论最优的优劣判定(关键差异):

维度 理论最优§5.2 源码 判定
分层 SingleCoreM/N ≥ BaseM/N 显式两层 singleCore = base单层 ⚠️ 源码是理论的退化特例
SingleCore 上限 L1 容量约束(可远超 256 L0C 面积(≤ 256×256 ⚠️ 差异
mCnt/nCnt 最少切分 ⌈C/B⌉ ⌈M/baseM⌉×⌈N/baseN⌉base ≤ 256 ⚠️ 差异
依据 搬入时延最小化§5.2 拉格朗日) cubeBound + balanceRate 枚举 部分一致

定量示例B=64、M=N=2048、K=512、BF16ASW 承接的 B≥C 且 IterBatch/MergeBatch 不满足的 case

  • 理论:$P = \lceil C/B \rceil = 1$,先试不切分——$mCnt = nCnt = 1$、singleCoreM = 2048、singleCoreN = 2048。L1 检查:2 \times (2048{+}2048) \times k_{L1} \times 2\text{B} \le 512\text{KB}k_{L1} \le 32 元素 = 64B < 256B 不满足 dValue。K 分块仍不满足则退化$k_{L1} \ge 128$256B2 \times 4096 \times 128 \times 2 = 2\text{MB} > 512\text{KB} 溢出 ⟹ 不切分不可行,进入情形 2B < C 式切分)?此时 B ≥ C 但 L1 放不下,实际须切 M/NmCnt \times nCnt \ge \lceil C/B \rceil 不成立——真正约束是 L1$2(mCnt 方向的分块)$… 精确说,理论在 L1 约束下求最小 mCnt×nCnt2 \cdot (\text{singleCoreM} + \text{singleCoreN}) \cdot k_{L1} \cdot dtype \le L1mCnt \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。
  • 源码baseM = baseN = 256L0 上限mCnt = nCnt = ⌈2048/256⌉ = 8、共 64 块/batch、64×64=4096 块。搬入时延 ∝ 8/2048×2 = 0.0078——是理论的 2 倍

结论:当 L1 容量富余K 小)时,理论的最少切分原则允许 singleCoreM/N 超过 256上例 512mCnt/nCnt 减半GM→L1 总搬入时延(重复读)减半;源码的 singleCore = base ≤ 256 硬上限导致过度切分L2 重复读翻倍。源码改进方向:引入 SingleCore/Base 分层SingleCore 由 L1 容量决定(上限解除 L0 约束stepM/stepN 多轮 L0 计算。这是理论对源码最实质的一条改进建议

6.4 mCnt / nCnt 与核间分配§5.3

源码做法


mCnt = \Big\lceil \frac{M}{baseM} \Big\rceil,\qquad nCnt = \Big\lceil \frac{N}{baseN} \Big\rceil

块→核映射:UpdateBasicIndexbatch_mat_mul_v3_asw_block_advanced.h)——index = blockIdx + round × usedCoreNummatIndex = index % (mCnt × nCnt) 解出 batch 内 (m, n)B→M→N 线性字典序,与理论 §4 的线性映射一致;batchIdx = index / (mCnt × nCnt) 天然支持广播。usedCoreNum = min(batch×mCore×nCore, aicNum)GetRebalanceBlock L489——注意源码也有"降核":当 batch×mCnt×nCnt < aicNum 时 usedCoreNum 自动收缩,比 §6.1 所述"无降核"更准确:源码无独立的降核 tiling 模板,但 GetRebalanceBlock 收尾会把 usedCoreNum 压到实际块数。

与理论最优的优劣判定

维度 理论最优 源码 判定
切分数 最少切分mCnt×nCnt = ⌈C/B⌉§5.2 mCnt = ⌈M/baseM⌉baseM ≤ 256 固定 ⚠️ 源码过度切分§6.3 定量:搬入时延 2 倍)
长宽比 B < C 时方形分配(拉格朗日) 由 baseM/baseN 枚举自然产生 ≈ 一致(方形成分由 256×256 上限保证)
核间分配 B→M→N 线性映射 一致
降核 P < C → usedCoreNum = ⌈P⌉§5.9 usedCoreNum = min(batch×m×n, aicNum) ≈ 一致(实现路径不同)

尾轮处理对比(对应 §5.3

  • 源码 GetBalanceRateWithTailL223-246尾块感知的负载均衡率用于枚举评分——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)$。但 BMM 场景batchInfo ≠ nullptr直接短路为简单比率L237-240$rate = (B \cdot MN/C)/((mainRound+1) \cdot baseM \cdot baseN)$)——尾轮不做拆分估算,理由是 batch 维已摊薄尾块效应。
  • 源码不实际重切尾轮,只通过枚举选择尾块占比小的 (baseM, baseN) 组合。
  • 理论§5.3n_{wave} \le 3 时 host 端计算切分因子 s^* 并实际重切尾轮(下发第二套 tiling 参数),收益 $T_{block}(1-1/s^*)$。

判定:理论更优。源码对 BMM 一律不做尾轮拆分(含估算),对小 $n_{wave}$2~3 轮)的 case 尾轮浪费可达 25%~50% 核时;理论的重切方案 host 端零 NPU 代价、收益可量化(访存 Bound 受 dValue 约束、计算 Bound 可到对齐底线)。理论方案可作为源码改进建议。

6.5 Swizzle§5.5

维度 理论 源码
窗口大小 W = \max\{d : d \mid C, d \le \lfloor\sqrt{C}\rfloor\} GetAswWindowLen:逻辑一致
蛇形 窗口行间 N 向反向,窗内不蛇形 一致(rowIdx % 2 != 0 时 n 反向)
尾窗 未提及 有 tailWindow 分支处理 mCnt 不整除窗长

结论swizzle 实现与理论一致。源码的窗长公式与理论 W = \max\{d : d \mid C, d \le \lfloor\sqrt{C}\rfloor\} 完全相同。但源码对细长 shapemCnt 小或 nCnt≫mCnt的方形窗假设不成立时退化为行优先mainWindow = min(aswWindowLen, mCnt)),未按 shape 长宽比自适应窗形。

6.6 L2 管理§5.6

维度 理论 源码
启用条件 S_{in} > L2 时分组 isBigSize(>100MB) && cBatchDimAll < usedCoreNum && transConflict ≤ 6
冲突度量 transConflict ≤ 6 一致
写出策略 S_{in} \le L2 < S_{in}+S_{out} 时输出直写 GM 仅 enableL2Cache flag + SetL2CacheHint无显式输出 non-allocate 决策链
尾波控制 优先选尾波不满载占比小的方案 TAIL_CONFLICT_RATIO = 0.5

差异分析:源码有两个额外限制:

  1. 100MB 阈值:小于 100MB 的 case 不启用 L2 cache 管理——理论上 S_{in} \le L2 = 128 MB 时不需要分组100MB < 128MB 留有余量,合理但是经验值;
  2. cBatchDimAll < usedCoreNumbatch 数小于核数时才启用——B ≥ C 时不做 L2 分块(由 ASW 承接时不分)。

源码中理论要求的"输出直写 GM vs 驻留 L2"的写出策略联合决策(场景 B$S_{in} \le L2 < S_{in}+S_{out}$)未见显式实现。

6.7 AL1 全载特化§5.8

维度 理论 源码
条件 M ≤ 256batchA=1A 驻留 L1每核 ≥4 轮 一致IsCapable L31-72
A 搬移 GM→L1 一次搬入 一致Nd2Nz DataCopy 一次搬入)
B 搬移 每基本块一次 一致

结论AL1 全载是理论与源码最吻合的分支。源码注释有笔误("m should be larger than 256" 实为 ≤256需注意。

6.8 尾轮处理§5.3

源码的尾轮相关逻辑分布在三处,与理论逐项对比:

机制 源码 理论§5.3 判定
尾块感知评分 GetBalanceRateWithTail:尾轮按 √ 拆两维估算后计入 rate \Delta T_{tail} 量化公式 ≈ 一致(源码是估算、理论是闭式)
BMM 尾轮估算 batchInfo ≠ nullptr 时短路为简单比率,不做拆分估算 batch 维摊薄尾块效应 ≈ 一致
实际重切 不做(仅 AL1 全载的 CalcTailBasicBlockAL1Full 沿 N 切尾块逼近满载) n_{wave} \le 3r < C 时 host 端重切(s^* 分块 + 第二套 tiling 参数下发) ⚠️ 理论更优
重切约束 访存 BounddValue ≥ 256B、搬移量 ≥ min_TileSize、16 对齐;计算 Bound仅 16 对齐

判定:源码对 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 参数)。

6.9 降核模式§5.9

维度 理论 源码
实现 usedCoreNum = ⌈P⌉其余核闲置 未实现——GetNumBlocks() 固定返回 32

影响P < C 的 case小 M/N、小 B进入 ASW_Basic 后,所有 32 核参与调度但部分核无实际工作。理论上应只调度 ⌈P⌉ 核。这类 case 的时延绝对值小但多余核的调度开销tiling 计算、kernel 启动、上下文切换)是真实存在的。

6.10 综合评价

维度 评价
核间分配 + swizzle ✓ 与理论一致
AL1 全载特化 ✓ 与理论最吻合
尾轮处理 ✓ 一致(不重切,选小尾波组合)
BaseM/BaseN 分层 ⚠️ 源码无显式 SingleCore/Base 分层baseM=256 实为 SingleCore 级
L2 管理 ⚠️ 比理论保守100MB 阈值 + batch 数限制),缺输出写出策略联合决策
降核模式 ✗ 未实现
经验常数 ⚠️ CUBE_BOUND_RATIO=0.85、balanceRate=0.9、transConflict=6、TAIL_CONFLICT_RATIO=0.5 等无官方文档推导,边界 case 值得实测复核

总体判断:源码的 ASW_Basic 实现在核间分配、swizzle、AL1 全载上与理论高度一致,是经实测调优的工程实现。理论分析的价值在于:

  1. 补齐降核模式——P < C 时 usedCoreNum = ⌈P⌉避免无效核调度
  2. 显式分层——SingleCoreM/N 与 BaseM/N 分开,约束链更清晰;
  3. 写出策略联合决策——S_{in} \le L2 < S_{in}+S_{out} 时输出直写 GM 的显式判断;
  4. 经验常数的理论依据——cubeBound 模型的三项分解L2 供数/溢出惩罚/K 向复用)可以从第一性原理推导。

七、程序实现流程图(源码 vs 理论最优)

本节给出两张端到端程序实现流程图:从 case 输入B, M, K, N, dtype 及转置/格式属性)开始,直到所有 tiling & swizzle 参数被清晰计算出来。图 1 严格对应源码实现(每个处理步骤标注所在函数),图 2 对应本文理论最优实现(每个处理步骤标注 §5 的公式出处§7.3 给出两图差异对照。

流程图图例约定:[输入/常量]<处理与判定>→ 产出参数;分支用 ├─/└─ 表示;✓/✗ 表示通过/拒绝。

7.1 源码 ASW_Basic 程序实现流程图

源码入口:BatchMatMulV3AswBasicTilingop_host/op_tiling/arch35/batch_matmul_v3_asw_basic_tiling.cppDAV_3510 注册为第 9 优先级模板 ASW_BASIC。tiling 层依赖 MatMulV3TilingHelpermatmul_v3_tiling_helper.cppMatMulV3BasicAswtTilingkernel 层为 BatchMatMulAswKernel + BatchMatMulAswBlock

阶段 0进入判定IsCapable

[B, M, K, N, dtype, isATrans, isBTrans, hasBias, aFormat/bFormat]
        │
        ▼
<A、B 非连续转置状态一致?> ──否──▶ 不进入(回落 BASE 模板)
        │是
        ▼
<batchA == batchB四维 batch 全部相等,不支持交叉广播)?> ──否──▶ 不进入
        │是
        ▼
<batchBias ≤ 1  dtype ∈ {FP16, BF16}B 为 NZ 且 C 为 FP32 时报错)> ──否──▶ 不进入
        │是
        ▼
       进入 ASW_BASIC

阶段 1参数初始化ResetBase → ResetBaseDav3510

<ResetBaseDav3510>
 → 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

阶段 2cubeBound 枚举确定 baseM/baseN/baseKGetRebalanceBlock

[platformInfo: hbmBW、l2BW、singleCoreComputePower = cube_freq×8]
        │
        ▼
<算边缘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 >
        │
        ▼
<初始最优baseMBest = min(align16(M), 256)
 baseNBest = min(align16(N), floor16(L0C/4B/baseMBest))
 cubeBoundParamBest = 1/baseMBest + 1/baseNBest
 isMemoryBound = cubeBoundParamBest > cubeBoundEdge
 fixpBoundEdge = M·N·hbmBW / ((M+N)·l2BW)>
        │
        ▼
<枚举上界maxBaseM = GetMaxBaseWithLimit(...)L0A 容量/bias 表/kL1 对齐/shape 约束)
 maxBaseN = GetMaxBaseWithLimit(...)L0B 容量/bias 表/kL1 对齐/shape 约束)>
        │
        ▼
<初值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(...)>
        │
        ▼
<双层枚举curBaseM: maxBaseM → 16 步长 baseMAlignUnit
           curBaseN: min(maxBaseN, L0C/4B/curBaseM) → 16 步长 baseNAlignUnit
    curParam = 1/curBaseM + 1/curBaseN
    curRate  = GetBalanceRateWithTail(...)
    ① skip 剪枝balanceRate ≥ 0.9 且 curParam > cubeBoundParam
                且 curParam > cubeBoundEdge 且 edge > 0 → 跳过
    ② FP32 剪枝FP32 且 curParam < edge 且 mCnt×nCnt > 32
                时 curBase 须 > 64 → 否则跳过
    ③ cubeBoundCondcurParam ≤ edge 且 curRate > balanceRate
                 → 更新解(且 edge = curParam
    ④ balanceCond(curParam/curRate) < (cubeBoundParam/balanceRate)
                 → 更新解(或比值相等且 curRate 更大)>
        │
        ▼
<收敛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>
 → baseM, baseN, baseK, usedCoreNum, dbL0C, ubDB

阶段 3L1 粒度参数CalL1Tiling → CalL1TilingDefault

<枚举 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>
 → stepKa = stepKb = kL1/baseK
 → depthA1 = depthB1 = stepK × 2双缓冲
 → singleCoreM = baseM, singleCoreN = baseN   ← 仍不分层

阶段 4L1 buffer 数与 API 级别DoOpTiling 尾部)

<abL1 = baseK·stepKa·(baseM + baseN)·aDtypeSizehasBias 时加 baseN·biasB
 l1BufferNum = (abL1 × 4 ≤ L1) ? 4 : 2>
        │
        ▼
<CheckTensorApiSupport
 FP32 且 K > 阈值 且 ND 且非连续 → splitKkernel 内切 K 保精度)
 BatchMatMulV3 节点且连续且非 splitK → apiLevel = TENSOR_LEVEL
 否则 → apiLevel = BASIC_LEVEL>

阶段 5参数打包GetTilingKey + GetTilingData

<tilingKey = Trans | Model=BASIC | ApiLevel>
        │
        ▼
<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>
        │
        ▼
<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 标志>
 → 完整 tilingData 下发 NPU

阶段 6kernel 运行时映射BatchMatMulAswBlock + BatchMatMulAswKernel::Process

<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 > 8192 且 B 为 NDsplitKRound = ⌈K/8192⌉保精度切 K>
        │
        ▼
<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>

源码产出参数汇总(一次 case 的全部 tiling & swizzle 参数):

参数 产出阶段 公式/取值
baseM, baseN 阶段 2 cubeBound 双层枚举(初值 256×256
baseK 阶段 2 min(align16(K), L0A/2/dtype/max(baseM,baseN))
singleCoreM/N 阶段 1/3 = baseM/baseN(不分层)
singleCoreK 阶段 1 = K
kL1, stepKa, stepKb 阶段 3 stepK 枚举(容量 + 256B 对齐)
depthA1, depthB1 阶段 3 stepK × 2
stepM, stepN 阶段 1 = 1
dbL0C, ubDB 阶段 2 容量判断 ×2 ≤ L0C ? 2 : 1
l1BufferNum 阶段 4 abL1×4 ≤ L1 ? 4 : 2
usedCoreNum 阶段 2 min(B·mCore·nCore, 32)
mTailCnt, nTailCnt 阶段 5 CalcTailBasicBlock尾块再拆分
aswWindowLen 阶段 5 ⌊√32⌋=5 向下最大因子 → 4
l2CacheDisable 阶段 5 SetDisableL2cachetotalSize vs L2
iterateOrder 阶段 1 ITER_COL_FIRST列优先
apiLevel 阶段 4 TENSOR_LEVEL / BASIC_LEVEL
mmadParam 阶段 5 HF32 / S8S4 shift

7.2 理论最优实现的程序实现流程图

理论实现按 §5 的推导链组织§5.1→§5.9host 端全流程闭式计算,每个判定均可用 B/M/K/N/dtype 与芯片规格的闭式表达。

阶段 0分支进入与降核判定§二/§5.9

[B, M, K, N, dtype]
        │
        ▼
<P = ⌈C/B⌉最少切分块数
 P < C 且 K 不够走 StreamK 切分? ──是──▶ 降核模式§5.9
                                        usedCoreNum = ⌈P⌉singleCoreM×singleCoreN
                                        在 L0C 容量内取最大,无 swizzle/L2 切分
        │否
        ▼
       进入 ASW_Basic 主体

阶段 1BaseM/BaseN/BaseK§5.1/§5.7L0 级 tile

<BaseM = BaseN = ⌊√(L0C/4B)⌋16 = 256方形L0C 单缓冲满载)
 例外M < 256 → BaseM = ⌊M⌋16、BaseN = min(65536/BaseM, N)(被迫非方形)
      N < 256 对称
 L0C 流水选择UnitFlag vs dbL0C 边界判定,见 L0C 流水机制分析 v1.0
   计算/写出 Bound 且 2·tile > L0C → UnitFlag 单缓冲tile 上限翻倍)
   MTE2 Bound → 单缓冲即可;带变换/多 batch 分区 → 双缓冲
 baseK = min(L0A/(2·BaseM·dtype), L0B/(2·BaseN·dtype)) 向下 16 对齐>
 → BaseM, BaseN, baseK, unitFlag/dbL0C

阶段 2SingleCoreM/N 确定§5.2 完整枚举,本次 v1.5 重写的核心)

<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>1 流程
 └─ P > 1B < 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 最大>
 → singleCoreM, singleCoreN, kL1, mCnt, nCnt, r

阶段 3核间分配§5.3/§5.4

<usedCoreNum = min(B·mCnt·nCnt, C)(降核场景为 ⌈P⌉
 线性映射:块 (b, m, n) 字典序编号,核 i 处理块 i, i+C, i+2C, ...
 切分维度选择(共享代价从低到高):
   切 B零共享先试→ 切 MKN·dtype ≤ L2 则右矩阵驻留 L2
   → 切 N对称→ 混合切swizzle + L2 切分管理)→ 降核>

阶段 4尾轮重切§5.3

<n_wave = ⌈B·mCnt·nCnt/C⌉, r = B·mCnt·nCnt mod C
 n_wave ≤ 3 且 r > 0  ──否──▶ 不重切r=0 无尾波n_wave≥4 收益 <25%
        │是
        ▼
<判定 Bound 类型:
 访存 BoundT_MTE2 ≥ T_MMADs_max = min(sN·dtype/256B,
                kL1·sN·dtype/min_TileSize, sN/16)
 计算 BoundT_MMAD > T_MTE2s_max = sN/16
 s* = min(⌊C/r⌋, s_max)
 切分方向:优先沿 NA 行带完整、L2 中 A 不变);
          N 向对齐卡住sN/16 < C/r改沿 MA 的 dValue=kL1·dtype 不变)>
 → s*, 尾轮 tiling 变体host 端预处理,零 NPU 代价)
<三策略判定§5.10 决策表):计算 Bound 且 r≤C/2 → A1a 整数切;
 r>C/2 → A1b 尾轮 tile 重选凑满核s_t = sM√(r/C) ↓16或 B 整轮重切;
 访存 Bound 且 ρρ_dv → A1b否则 Bg≤g_dv或 A0 兜底>

阶段 5swizzle 与错位分核§5.5/§5.6

<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 优先)>
 → W, 遍历顺序, calOrder, transConflict 校验

阶段 6L2 分组§5.6,工作集超 L2 时)

<S_in = B(MK+KN)·dtype, S_out = B·MN·outB
 ├─ S_in + S_out ≤ L2 → 场景 A全驻留r_in = 1无需分组
 ├─ S_in ≤ L2 < S_in + S_out → 场景 B输入驻留 + 输出直写 GM
 │    (校验 (S_in+S_out)/T_MMAD ≤ W_GM 总线不爆)
 └─ S_in > 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 管理手段)>
 → m_grp, n_grp, cacheHint, 执行组划分

阶段 7核内参数汇总与端到端校验§3.1

<全部参数定稿:
 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 候选重新评估>
 → 完整 tiling & swizzle 参数集下发 NPU

理论产出参数汇总(与源码参数的对应关系):

参数 产出阶段 公式/取值
BaseM, BaseN 阶段 1 ⌊√(L0C/4B)⌋16 = 256方形M/N 过小被迫跟随)
baseK 阶段 1 min(L0A/(2·BaseM·dtype), L0B/(2·BaseN·dtype)) ↓16
singleCoreM/N 阶段 2 P=1 不切分(=M/NP>1 有界枚举 + T_MTE2 最小
kL1 阶段 2 min(K, ⌊L1/(2(sM+sN)·dtype)⌋16)
mCnt, nCnt 阶段 2 ⌈M/sM⌉, ⌈N/sN⌉
usedCoreNum 阶段 0/3 min(B·mCnt·nCnt, C);降核 ⌈P⌉
unitFlag/dbL0C 阶段 1 Bound 类型 + 容量联合判定
l1BufferNum 阶段 7 L1 余量 → 2/4 buffer
s* 阶段 4 min(⌊C/r⌋, s_max)Bound 分级约束)
W 阶段 5 `max{d : d
calOrder 阶段 5 共享矩阵驻留方向选择
m_grp, n_grp 阶段 6 ⌊D/(2·sM)⌋, ⌊D/(2·sN)⌋
cacheHint 阶段 6 A/B allocate、C non-allocate

7.3 两张流程图的差异对照

步骤 源码 ASW_Basic 理论最优 差异本质
进入判定 转置一致性/等 batch/dtype 白名单 P 计算 + 广播 + 降核判定 源码只拦不支持的输入;理论从并行度出发
BaseM/N 硬编码 256 起 + cubeBound 枚举收缩 L0C 单缓冲 256 + UnitFlag 流水 源码无 UnitFlag理论按 Bound 类型选流水手段
baseK L0A/2/dtype/max(baseM,baseN) min(L0A/(2·BaseM), L0B/(2·BaseN))/dtype 源码取 max 保守(矩形 tile 下另一侧溢出风险);理论方形双侧用满
singleCore = baseM/baseN(不分层) ≥ Base独立枚举P=1 不切分) 源码 K 小时过度切分,搬入时延可达理论 2 倍§6.3
目标函数 cubeBoundParam + balanceRate 帕累托0.85/0.9 经验常数) T_MTE2 闭式最小 源码多经验常数;理论有闭式表达式与完备性论证
核间分配 usedCoreNum = min(B·mCore·nCore, 32) 同 + 降核模式⌈P⌉ 源码无降核P<C 时仍全核调度)
尾轮 CalcTailBasicBlock尾块再拆分不重切 尾轮重切 s*host 零代价Bound 分级约束) 源码不重切,依赖枚举时选尾波小的组合
swizzle aswWindowLen = 4 + 蛇形UpdateBasicIndex 同公式W 推导自足迹最小化) 一致§6.5
错位分核 每轮 GetCurrentBlockIdx 动态取块 transConflict ≤ 6 + calOrder 显式建模 理论显式建模冲突上限与遍历方向
L2 管理 SetDisableL2cachetotalSize vs L2 → uncache 标志) 三场景分组 + 写出联合决策 + Cache Hint/CMO 源码无执行组划分、无输出直写决策§6.6
UnitFlag 未启用 计算 Bound 启用tile 内 512B 块流水) 流水手段代际差异


参考文献

  1. 昇腾 950 NPU 架构白皮书华为技术有限公司2026
  2. cann-ops-nn 源码仓batch_mat_mul_v3_asw_basic_tiling.cpp入口/IsCapable/DoOpTiling、batch_mat_mul_v3_asw_al1_full_load_basic_tiling.cppAL1 全载特化)
  3. cann-ops-nn matmul 源码仓matmul_v3_tiling_helper.cppResetBase/GetRebalanceBlock/CalL1Tiling/GetL0C2Out、matmul_v3_basic_aswt_tiling.cppBasicAswt、matmul_v3_base_tiling_advanced.hGetTilingData/aswWindowLen/SetDisableL2cache