diff --git a/BMM/BMM算子优化分析_Release/ASW_Basic分支分析_v1.9.html b/BMM/BMM算子优化分析_Release/ASW_Basic分支分析_v1.9.html deleted file mode 100644 index a61bc06..0000000 --- a/BMM/BMM算子优化分析_Release/ASW_Basic分支分析_v1.9.html +++ /dev/null @@ -1,1179 +0,0 @@ - - -
- - -目标芯片:昇腾 950PR(DAV_3510)。本文为 ASW_Basic 分支的独立分析(v1.9 修订 §5.2 方形结论严格性审视(kL1 耦合的两个 regime)与 §5.10(四方案实现方式、16 对齐平等性修正)),自包含完整推导链。历史:v1.7 新增 §5.10 三策略两两对比;v1.6 新增 §7 程序实现流程图;v1.5 扩充 §6 源码对比、修正 dValue 定义与尾轮重切约束。-
ASW_Basic 是 BMM 的兜底分支——核间切 M/N(或混合切),不做 batch 合并或 K 维切分。本文给出完整的时延建模、核间分配策略分析(证明 B 优先分组在任何场景下都不优于线性映射)、实现方案的逐步推导(含尾轮重切的 Bound 类型分级策略),以及与源码实现的逐维度对比。
-v1.8 修订 §5.10:A1 扩展为两种形态——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_Basic(BatchMatMulV3AswBasicTiling 六阶段:进入判定 → 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 解析模型与平台自适应值得理论吸收。**
BMM 中,当 B ≥ C(batch 数 ≥ AIC 核数)时核间切 B 是免费的(每核独立处理若干 batch,无共享无依赖),由 IterBatch/MergeBatch 承接。当 B < C 或 B ≥ C 但 IterBatch/MergeBatch 条件不满足时,需要切 M/N 来填满所有核——这就是 ASW_Basic。
-ASW_Basic 是实践中最常命中的分支:B 可大可小可等 1,交叉广播也由此承接(对广播侧做 L1/L2 驻留,共享关系与切 M/N 同构)。
-核间: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)_____________↑
-| 符号 | 含义 | 表达式/取值 |
|---|---|---|
| $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:非转置时连续维为 N,dValue = $\text{singleCoreN} \cdot dtype$;转置时连续维为 K,dValue = $k_{L1} \cdot dtype$。dValue 不是两个维度的乘积。
-两级搬移效率阈值:
-同时满足:
-逐条解释:
-| 项 | 含义 | 公式 |
|---|---|---|
| $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。
-总输出块数 $= B \cdot mCnt \cdot nCnt$。并行度约束的正确形式是:
-即总块数至少能填满 C 核。这与 $mCnt \cdot nCnt \ge \lceil C/B \rceil$ 等价(两边同乘 B 后取整)。
-**不应要求 $B \cdot mCnt \cdot nCnt \;\%\; C = 0$**(整除)。整除是充分不必要条件——不整除时产生尾轮,尾轮的处理见 §五。
-每波活跃核所需的 A 行带 + B 列带:
-其中 $sM$ = singleCoreM,$sN$ = singleCoreN,$W$ 为 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——线性映射的工作集是分组方案的一半。
-为什么线性映射工作集更小:
-**场景 I:$mCnt \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$ 个核。线性映射无此问题。线性严格更优。
-B 优先分组在任何场景下都不优于线性映射。 根本原因:
-因此 ASW_Basic 采用 B→M→N 线性映射作为核间分配策略。
-L0C 是 Cube 的累加器,BaseM × BaseN 是每次 Cube 计算的输出 tile。BaseM/N 应尽量把 L0C 用满——L0C 利用率越高,每次 Cube 计算的输出越大,单位计算的启动/排空开销摊得越薄。
-L0C 双缓冲 vs UnitFlag 单缓冲:
-传统做法用 L0C 双缓冲实现 tile 间流水——计算 tile N+1 时,fixpipe 同时写出 tile N:
-但昇腾 950PR 的 Fixpipe 支持 UnitFlag——MMAD 每完成一个 16×16×16 基本块(512B 结果),Fixpipe 立即将其写出,无需等整个 L0C tile 算完。UnitFlag 提供的是 tile 内部的细粒度流水(16×16×16 粒度),替代双缓冲的 tile 间粗粒度流水(BaseM×BaseN 粒度)。
-UnitFlag 单缓冲下,L0C 只需一份 buffer:
-BaseM/N 可放大 $\sqrt{2}$ 倍(如 256→362),mCnt/nCnt 相应减小,L2 重复读率降低(单 batch 块数减少 → 每行 A 被更少的组读取)。
-*时延对比*(单 tile 粒度,BF16 输出):
-| 方案 | tile 大小 | tile 间流水 | tile 内流水 | 单 tile 时延 |
|---|---|---|---|---|
| 双缓冲 | 181×181(32761 元素) | ✓(tile N 写出 ∥ tile N+1 计算) | ✗ | max($T_{comp}$, $T_{write}$) |
| UnitFlag 单缓冲 | 256×256(65536 元素) | ✗ | ✓(16×16×16 粒度) | max($T_{comp}$, $T_{write}$) |
两种方案的稳态时延相同(都是 max($T_{comp}$, $T_{write}$)),但 UnitFlag 单缓冲的 tile 更大 → 总 tile 数更少 → 循环开销更小。但当前 BMM ASW kernel 未启用 UnitFlag(unitFlag = 0,注释 "each l0 only process one block, disable unit flag"),且源码在 baseM=baseN=256 时已自动选 dbL0C=1(256×256×4B×2 > L0C)——即源码已经是单缓冲 + 无 UnitFlag,tile 到顶但无流水交叠。
*建议*:对计算 Bound 的 case 启用 UnitFlag(MMAD 与 Fixpipe 流水并行),可将单 tile 时延从 $T_{comp} + T_{write}$ 降至 $\max(T_{comp}, T_{write})$。对访存 Bound 的 case(MTE2 Bound),UnitFlag 收益小([CANN 文档](https://www.hiascend.com/document/detail/zh/CANNCommunityEdition/920beta1/API/ascendcopapi/atlasascendc_api_07_0003.html):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 = 4:BaseM=512、BaseN=128),则:
-L0A 装满(512×32×2×2 = 64KB)而 L0B 只用了 25%(32×128×2×2 = 16KB)——一半 L0 容量闲置。
-方形 tile(BaseM=BaseN=256):
-L0A 和 L0B 同时装满,且 baseK = 64 是长宽比跟随方案(baseK=32)的 2 倍——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 被迫非方形:
-例:M=128、N=4096 → BaseM=128(M 限制)、BaseN=512(L0C 面积限制)——此时长宽比跟随 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}$):
-核间不切 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 对齐遍历):
-*与源码的差异*:源码默认 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)。
-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 对 L2 重复读的定量影响:
-每行 A 行带 $[\text{singleCoreM}, K]$ 被该行的 $nCnt$ 个列块各读一次,每列 B 列带被 $mCnt$ 个行块各读一次。L2 层的总读取次数:
-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 时才需要分组,此时
-$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 核。
-约束 2——L1 容量(双缓冲下驻留当前 tile 的输入):
-约束 3——搬移效率(dValue 见 §1.4):
-约束 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$ 块,单核总搬入时延:
-(展开验证:每 batch 总搬入 = $(M \cdot nCnt + N \cdot mCnt) \cdot k_{L1} \cdot dtype$——A 矩阵每 batch 被 $nCnt$ 个列块共享读 $nCnt$ 次、B 矩阵被 $mCnt$ 个行块共享读 $mCnt$ 次,与直觉一致。)
-目标函数 $mCnt/M + nCnt/N$ 在 $mCnt = nCnt = 1$ 时取得全局最小值($1/M + 1/N$)——不切分时每 batch 的 A/B 只搬一次,零重复读。因此第一步是尝试最少切分:
-(多切无益:切分越多重复读越多,搬入量单调增大。)
-情形 1:B ≥ C(P = 1)——不切分,$mCnt = nCnt = 1$,$\text{singleCoreM} = M$、$\text{singleCoreN} = N$。tile 跟随 M/N,非方形。前提是 L1 容量与 dValue 满足(约束 2/3);不满足时须 K 分块($k_{L1} < K$)或退化为情形 2。
-情形 2:B < C(P > 1)——必须切分。在 $mCnt \cdot nCnt = P$ 下最小化 $mCnt/M + nCnt/N$:
-——方形(连续松弛下的理论最优)。整数 + 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 的不切分场景。
-*方形结论的严格性审视*(v1.9 补充——回应"SingleCoreM=SingleCoreN 是否从第一性原理严谨"):
-方形推导(式 (mCnt*, nCnt*))有两个隐含假设,逐一审视:
-**假设 1:拉格朗日求导时把 $k_{L1}$ 当常数**。但约束 2 给出 $k_{L1} = \min(K,\; \lfloor L1/(2(sM{+}sN) \cdot dtype) \rfloor_{16})$——$k_{L1}$ 与周长 $(sM+sN)$ 耦合。代入目标函数后分两种 regime:
-面积 $sM \cdot sN = MN/P$ 固定时,$(1/sM + 1/sN) = (sM+sN)/(sM \cdot sN)$ 的分子是周长——均值不等式:周长最小当且仅当方形。此 regime 下方形是严格最优;
-**只与面积 $sM \cdot sN$ 有关,与长宽比无关**——方形不再唯一最优,任何同面积的长宽比时延相同。物理含义:tile 拉长 → 周长增大 → $k_{L1}$ 被 L1 容量压得更小,两者恰好抵消。
-*数值验证*($sM \cdot sN = 65536$ 固定、K=4096、BF16):(256,256) → $k_{L1}$=256、T∝2.000;(512,128) → $k_{L1}$=192、T∝1.875;(1024,64) → $k_{L1}$=112、T∝1.859——L1 主导下时长宽比几乎无关(残差来自 $k_{L1}$ 的 16 对齐取整,非结构性)。同面积、K=64(全载 regime):(256,256) T∝0.500 < (512,128) 0.625 < (1024,64) 1.063——方形严格最优 ✓。
-假设 2:连续松弛。$mCnt^* = \sqrt{PM/N}$ 一般非整数,实际须整数 + 16 对齐 + BaseM/N 整数倍(约束 4)。连续方形只是方向指引,真正的最优由有界枚举 + $T_{MTE2}$ 目标函数决定(§5.2 枚举流程已覆盖所有离散效应)。
-假设 3:dValue 约束对称。实际不对称:B 非转置时 dValue = $sN \cdot dtype \ge 256\text{B}$ 卡 $sN$ 下限,A 的 dValue = $k_{L1} \cdot dtype$ 由 K 向保证、不卡 $sM$。极端长宽比(M ≫ N 或 N ≫ M)时方形可能使 $sN$(或 $sM$)跌破 256B/dtype——此时最优被迫非方形,由枚举的约束 3 过滤自然处理。
-第一性原理的方形直觉:tile 的输出是面积、输入搬运是周长(A 行带 $sM \times K$ + B 列带 $K \times sN$)。等周不等式——面积固定时方形周长最小,即单位输出的输入搬运最少。这就是"方形最优"的物理本质:搬入效率最大化 = 边界/面积比最小化。L1 主导 regime 下该直觉被 $k_{L1}$ 的反向缩放恰好抵消,形状自由度释放。
-结论:方形($sM = sN$)在"K 全载 + 面积固定 + dValue 不卡死"下严格成立;L1 主导时退化为"面积最大化、形状自由";两条都不依赖特殊假设,且都已被 §5.2 的枚举算法自动覆盖——枚举 + 目标函数才是最终裁决者,方形只是其连续极限的解析刻画。§5.10 的尾轮分析沿用"近方形"作为缩放律($g$ 倍面积 ↔ $\sqrt{g}$ 倍周长)的基准形态,非方形分解的实际收益由同一目标函数另行评估(§5.10 例 4 的非方形惩罚即此体现)。
-*SingleCoreM/N 的具体确定过程*(host 端枚举,与尾轮处理联动):
-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$ 目标值):
-*约束 4 过滤*:singleCoreM/singleCoreN 是 BaseM/BaseN 的整数倍;*约束 2 求 $k_{L1}$*:
-*约束 3 过滤*:$k_{L1} \cdot dtype \ge 256\text{B}$ 且 $\text{singleCoreM} \cdot k_{L1} \cdot dtype \ge min\_TileSize$ 且 $k_{L1} \cdot \text{singleCoreN} \cdot dtype \ge min\_TileSize$
-*目标值(§3 时延模型的搬移项)*:
-**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^*$ 越小、越省)
-*完整实例*(B=8、M=N=2048、K=1024、BF16,BaseM=BaseN=256、$min\_TileSize$=16KB):
-| (mCnt, nCnt) | sM × sN | $k_{L1}$(约束 2 上限,16 对齐) | 约束 3 过滤 | $T_{MTE2} \propto (1/sM + 1/sN) \cdot k_{L1}$ |
|---|---|---|---|---|
| (2, 2) | 1024 × 1024 | 64(128B) | ✗ dValue < 256B | — |
| (4, 2) | 512 × 1024 | 80(160B) | ✗ | — |
| (4, 4) | 512 × 512 | 128(256B) | ✓(512×128×2 = 128KB ≥ 16KB) | 0.500 |
| (8, 4) | 256 × 512 | 160(320B) | ✓ | 0.938 |
| (4, 8) | 512 × 256 | 160 | ✓ | 0.938 |
| (8, 8) | 256 × 256 | 256(512B) | ✓ | 2.000 |
$k_{L1}$ 上限公式:$\lfloor 131072/(sM{+}sN) \rfloor$(131072 = L1/(2·dtype) = 512KB/4B)。评估过程展示三个要点:
-*与源码的差异*:源码用 cubeBound 模型 + balanceRate ≥ 0.9 剪枝,不解耦 SingleCoreM/N 与 BaseM/N(baseM=256 已是 SingleCore 级参数,L0 tile 由 stepM/stepN 二次切分),且枚举目标为"算存比/负载均衡帕累托"而非"搬入时延最小"。理论的两层分离使约束链更清晰、目标函数($T_{MTE2}$)有闭式表达——SingleCoreM/N 由 L1 + 并行度 + 搬移效率决定,BaseM/N 由 L0C 决定,各司其职。
-总输出块数 = $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 在 host(CPU)上完成,不占 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$)。
-*重切收益*:
-*占总时延比例*:
-$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 相同:
-**计算 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}$。搬移被掩盖的条件:
-记右端为 $x_{min}$(搬移掩盖下限),则计算 Bound 下的约束为:
-**计算 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}$),再选约束集:
-*例*(C=32、$N_{blk}=40$、$n_{wave}=2$、$r=8$、singleCoreM=singleCoreN=256、$k_{L1}$=128、BF16):
-*切分方向选择*:优先沿 N 切(保持 A 行带完整,L2 中 A 数据不变)。计算 Bound 下若 N 向对齐卡住($\text{singleCoreN}/16 < C/r$),改沿 M 切(A 矩阵 dValue = $k_{L1} \cdot dtype$ 不变,仅受对齐约束 $\text{singleCoreM}/16$)。
-结论:$n_{wave} \le 3$ 且 $r < 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$ 时无尾波)。
-不重切时尾波导致的额外时延(相对于完美整除):
-重切后尾波时延降为 $T_{block}/s^*$,额外时延:
-当 $r \cdot s^* = C$(完美重切)时 $\Delta T_{tail}^{re} = 0$——尾波完全消除。
-切 B(零共享,先试)→ 切 M(右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2)→ 切 N(对称)→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见 Step 8)。
-问题:核间切 M/N 后,同一时刻 C 个核各算一个输出块,它们所需的 A 行块与 B 列块集合就是当前"活跃工作集"。若按行优先顺序朴素分配,一波 C 个块横跨的 A 行、B 列很宽,活跃工作集超过 L2 就回 GM 读(1.6TB/s),重复读代价真实发生。swizzle 要做的就是编排输出块的执行顺序,把每一波核的活跃工作集压到最小。
-做法:把 M 向每 W 个基本块划为一个"窗口",遍历顺序为"窗口内先扫 M、扫满 W 行再进下一列 N;一个窗口扫完再进下一个窗口",且奇数窗口行 N 向反向(蛇形)。效果有二:
-W 怎么取:一波 C 个块的 L2 足迹约为
-由均值不等式,$W + C/W$ 在 $W = \sqrt{C}$ 处取最小——窗口越接近"方形"(W 行 × C/W 列),足迹越小。同时 W 须整除 C,保证每个窗口恰好被整数波核覆盖、窗口边界不把波次切碎。合起来即:
-C=32 时 $\sqrt{32} \approx 5.66$,因子 {1,2,4,8,…} 中不超过它的最大者是 4,故 W=4。
-实例(C=32,W=4,M̃=8,Ñ=8;数字为块的全局执行顺序,一波 32 块):
-窗口0(N 正向) 窗口1(N 蛇形反向)
- ν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)"的说明):蛇形的收益来自"相邻遍历段共享边界数据",要分两种边界看:
-BatchMatMulAswBlock::UpdateBasicIndex:仅 rowIdx 为奇时 n 反向,窗内 m 最快序不反向),正是这个收益结构的直接实现。切的是什么:将 mCnt×nCnt 个基本块划分为若干执行组——每组覆盖输出平面上一个连续矩形区域(若干 singleCoreM × singleCoreN 基本块的集合),使该组所需的 A 行带 + B 列带输入工作集 ≤ L2 可用读入空间;组内所有基本块算完再进下一组,输入只在跨组时换一次。
-为什么需要它:滑窗压缩的只是"同一波"的足迹;若整个工作集超 128MB L2,跨波次复用落空——上一波窗口的 A 行早被挤出,下一波又得回 GM 读。且 L2 是读写共用的:输出经 fixpipe 写出时若驻留 L2(dirty),会压缩读入可用空间;若直写 GM,则占用与读共享的 1.6TB/s 总线。所以 L2 切分必须与写出策略联合决策。记输入总量 $S_{in} = B(MK+KN)\cdot\text{dtype}$,输出总量 $S_{out} = B \cdot MN \cdot outB$。
-两个不变量(一切分析的起点):
-重复读倍率:$r_{in}$ = GM 输入流量 / $S_{in}$。$r_{in} = 1$ 表示每个输入数据从 GM 只读一遍(后续复用全在 L2 命中)——这是 GM 输入流量的下界,L2 管理的全部目标就是让 $r_{in}$ 尽量接近 1。
-先判定写出会不会 Bound。平均写出带宽需求:
-只与 K、outB 有关(K 越小,单位时间输出越密)。例(BF16 输出,C·Q₁₆=432 TFLOPS):K=512 → 844 GB/s;K=256 → 1.69 TB/s,已超 GM 总线——此时任何策略都写出 Bound,L2 缓冲只能削峰(fixpipe 以 5.2TB/s 写 L2 吸收突发),平均速率仍受总线限制,应预期 Fixpipe 成为 $T_{total}$ 的 max 项。
-分场景决策:
-**场景 A:$S_{in} + S_{out} \le L2$(全驻留)**。输入读一遍($r_{in}=1$),输出驻留 L2(dirty)异步回写 GM——写出走 5.2TB/s L2 写口,不与读争,也削平了 GM 写突发。无需切分。
-**场景 B:$S_{in} \le L2$ 但 $S_{in} + S_{out} > L2$(输入能驻留,加上输出超了)。策略:输入驻留、输出直写 GM**。理由链:
-例:B=8、M=N=4096、K=512、BF16——$S_{in}$≈67MB ≤ L2,$S_{out}$≈268MB 直写 GM;T_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 里"。可用的控制手段:
-SetL2CacheHint):标记输入为 allocate(读入 L2)或 non-allocate(直读 GM 不过 L2);标记输出为 non-allocate(直写 GM 不占 L2);执行组的划分:
-*问题*:mCnt×nCnt 个基本块(每块输出 singleCoreM×singleCoreN),按什么粒度分组,使每组的输入工作集 ≤ L2?
-*每组输入工作集*:一组覆盖 M 向 $m_{grp}$ 个基本块、N 向 $n_{grp}$ 个基本块,即覆盖输出区域 $[m_{grp} \cdot \text{singleCoreM},\; n_{grp} \cdot \text{singleCoreN}]$。该区域需要读入的输入:
-*目标*:最小化组数(组数越少,输入从 GM 的重复读次数越少)。每行 A 被 $n_{grp}$ 个组各读一次,每列 B 被 $m_{grp}$ 个组各读一次:
-*求解*:约束 $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 向基本块数与输出平面形状成正比时取到:
-总组数 $= \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 次。
-块内分配用错位分核(对角线分配):线性块号先取 m,n 方向叠加随块号递增的相位偏移,使同一时刻各核落在 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)
-冲突度量与优选规则:
-即同一时刻并发核访问同一 A/B 块的最大冲突数不超过阈值(经验值 6);切分方案中优先选尾波不满载占比小(拖尾 < 一半)的。遍历大方向由 calOrder 决定(0=M 优先、1=N 优先),按形状选共享矩阵更能驻留 L2 的方向。
-补充:若输出会被后续算子立即消费(融合场景),输出驻留 L2 让下游读命中,场景 B/C 的策略反过来;本文按单算子边界分析。
-$\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。
-单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次(L1 全载)。
-tiling 时 usedCoreNum = ⌈P⌉(不强制 C),SingleCoreM/N 在 L0C 容量内取最大($\text{singleCoreM} \times \text{singleCoreN} \times 4\text{B} \le L0C$),每核按标准核内流水(L1→L0→Cube→L0C→Fixpipe)处理自己的输出块;核间无共享无依赖,无需 swizzle 与 L2 切分。降核后 GM 并发搬移核数若 < minCoreNum,带宽利用率上限被压低——这正是降核区 case 时延的瓶颈所在,也是"时延绝对值小、不再继续优化"的定量注脚。
给定 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 使每轮每核恰好一个同样大小的块 | 全部块同大小 |
四种方案的实现方式(v1.9 补充——先讲清工程形态,再做时延对比):
-| 策略 | host 侧 tiling | kernel 侧执行 | tile 参数套数 |
|---|---|---|---|
| A0 | 一套参数(singleCoreM/N、mCnt/nCnt) | 线性映射 index = blockIdx + round × usedCoreNum,尾轮不满载的核 index ≥ totalCnt 直接跳过、空转 | 1 |
| A1a | 主参数 + 尾轮切分参数($s^*$、尾轮小块尺寸) | 最后一轮切换为小块尺寸处理尾轮子块 | 2 |
| A1b | 主参数 + 尾轮 tile($sM_t, sN_t$) | 尾轮区域独立子网格映射,最后一轮切换到小 tile | 2 |
| B | 一套重切后的参数(singleCoreM'/N'、mCnt'/nCnt') | 全程统一 tile,$N_{blk}' = n_{wave} \cdot C$ 整除、无尾轮分支 | 1 |
对齐约束的平等性(v1.9 修正):16 对齐是 Cube 计算粒度(16×16×16 基本块)的硬件要求,A0/A1a/A1b/B 的 SingleCoreM/N——无论重切前还是重切后——都必须是 16 的倍数,无一例外。因此"某策略因 16 对齐产生额外浪费而另一策略没有"的说法不成立:所有策略的重切 tile 都在同一个 16 对齐离散网格上枚举,差异只在枚举空间(A1b 对尾轮子区域、B 对全局 M×N)与 tile 套数。后文例 1 将看到,A1b 允许非方形枚举时与 B 的对齐损失相当。
-A1 的两种形态(v1.8 修订——r > C/2 时 A1 不再退化):
-再按 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}$ 倍,以近方形为基准形态——方形的严格成立条件见 §5.2 的严格性审视:K 全载时方形严格最优、L1 主导时仅面积相关;非方形分解的实际收益由同一目标函数评估):
-| 时延项 | 依赖 | 缩放律 |
|---|---|---|
| $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})$ 为单块主导项):
-A1a 时 $T_{tail} = T_{block}/s^*$(计算 Bound);A1b 凑满 C 核时,尾轮小块面积为 $r/C \cdot sM \cdot sN$,计算 Bound 下:
-(1)A0 vs A1
-A1a 可行时($r \le C/2$):
-$r > C/2$ 时 A1a 的 $s^* = \lfloor C/r \rfloor = 1$ 失效,但 A1b 不退化:尾轮 tile 重选凑满 C 核后:
-合并:
-A0 在计算 Bound 下永不最优($r=0$ 除外)。
-(2)A0 vs B
-B 恒优于 A0(可行性校验:搬移掩盖 $\sqrt{g} \le T_{MMAD}/T_{MTE2}$;分解对齐)。
-(3)A1 vs B
-A1b 凑满核时 $T_{A1b} = T_{MMAD}(n_{wave} - 1 + \rho)$,与 B 的 $T_B = T_{MMAD}(n_{wave} - 1 + r/C)$ 理论时延严格相等——两者都是"总计算量/C"(计算 Bound 下时延与切分方式无关,只要轮轮满载)。
-差异在两个二阶项:
-均值不等式:A1b 周长和恒 ≤ B(等号当 $\rho = 1$,即无尾轮)。搬移少意味着 L2 重复读少、计算 Bound 掩盖余量更大;
-结论(计算 Bound,v1.9 刷新):A1b 与 B 理论时延严格相等(都是总计算量/C);离散 16 对齐后互有胜负(数值依赖,无系统性优劣——对齐约束对两者平等)。结构性差异只有两条:A1b 的搬移周长和恒 ≤ B(均值不等式,连续意义下严格成立),工程上 B 只需一套 tile 参数更简洁。选择依据:工程简洁选 B;追求搬移下限与 L2 重复读下限选 A1b。
-*例 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$)。
-A1b 非方形枚举后与 B 打平(54.6 vs 54.1,差 <1%,纯数值依赖);周长和 A1b $= 32 \times 512 + 32 \times 384 = 28672$ = B $= 64 \times 448 = 28672$(此例恰好持平;连续理论 A1b ≤ B)。相对 A0 均省约 23%。
-*例 2(计算 Bound,$r \mid C$ 完美点)*:M=1536、N=2048、K=4096、BF16。$mCnt=6, nCnt=8$($sM=sN=256$),$N_{blk}=48$、$r=16$。A1a:$s^* = 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(搬移增量小)。
-主导项 $T_{block} = T_{load} = (sM + sN) \cdot k_{L1} \cdot dtype / BW_{pc}$。
-(1)A0 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$:
-受 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}$:
-A1b 恒优于 A0(dValue 允许时:$s_t \cdot dtype = \sqrt{\rho} \cdot sN \cdot dtype \ge 256\text{B}$)。
-(2)A0 vs B
-B 恒优于 A0(dValue 约束 $g \le (sN \cdot dtype/256\text{B})^2$ 满足时)。
-(3)A1 vs B
-比较总搬移量(周长和)。A1b:主轮 $(n_{wave}-1)C$ 块大 tile + 尾轮 C 块 $s\sqrt{\rho}$ tile:
-B:$n_{wave} C$ 块 $s\sqrt{N_{blk}/(n_{wave}C)}$ tile:
-由均值不等式 $S_{A1b} \le S_B$ 恒成立(推导同计算 Bound),且总时延正比于周长和(满载轮聚合带宽相同):
-访存 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 非转置):
-不满足时 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$)。
-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}$ ✓。
-A1b 优 2.2%(周长和 $45056 < 46080$,与均值不等式结论一致)。
-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 | 最优策略 |
|---|---|---|---|---|
| 计算 Bound,$r \le C/2$ 且 $r \mid C$ | A1a 优 | B 优 | A1a = B(选 A1a,搬移少) | A1a |
| 计算 Bound,$r \le C/2$ 且 $r \nmid C$ | A1a 优 | B 优 | B 略优(取整损失) | A1a/B 皆可 |
| 计算 Bound,$r > C/2$ | A1b 优(不再退化) | B 优 | 时延理论相等,离散打平(A1b 周长和 ≤ B;16 对齐对两者平等) | A1b 或 B(工程简洁选 B,搬移下限选 A1b) |
| 访存 Bound,$\sqrt{\rho} \cdot sN \cdot dtype \ge 256$B | A1b 优 | B 优 | A1b 恒优(周长和 ≤ B) | A1b |
| 访存 Bound,$\sqrt{\rho} \cdot sN \cdot dtype < 256$B(dValue 卡死) | A1b 部分凑满 | B 优 | 打平(A1b 退化) | B(若 $g$ 可行) |
| dValue 全面卡死(B 也不可行) | A1a($s_{dv}^* \ge 2$ 时)或 A0 | B 不可行 | — | A1a/A0 |
以下五步闭式计算后查表即得最优策略,无需逐项建模仿真:
-前置计算(全部为 B/M/K/N/dtype 与芯片规格的闭式函数):
-决策表:
-| # | 条件 | 最优策略 | 端到端时延 |
|---|---|---|---|
| 1 | $r = 0$ | A0(无尾轮) | $n_{wave} \cdot T_{block}$ |
| 2 | 计算 Bound,$0 < r \le C/2$ | A1a,$s^* = \lfloor C/r \rfloor$ | $T_{MMAD}(n_{wave} - 1 + 1/s^*)$ |
| 3 | 计算 Bound,$r > C/2$ | A1b($s_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 | 访存 Bound,B 也不可行 | 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 ? ──┬─ 是 ─▶ A1a(s* = ⌊C/r⌋)
- │ └─ 否 ─▶ A1b(s_t = sM√ρ ↓16)或 B
- │ (时延相等;工程简洁选 B)
- │
- └─ 否(访存 Bound)── ρ ≥ ρ_dv ? ──┬─ 是 ─▶ A1b(周长和恒 ≤ B)
- └─ 否 ─▶ B(g ≤ g_dv 时)
- 或 A1a/A0 兜底
-整体结论(v1.8 更新):
-边界说明(带宽模型敏感性):访存 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 对尾轮区域重选 tile($s_t$ 按 16 步进缩小至凑满),选 B 回到阶段 2 枚举(约束 1 改为等式 $B \cdot mCnt' \cdot nCnt' = N_{blk}'$)。
-源码参考 [cann-ops-nn](https://gitcode.com/cann/ops-nn/tree/master/matmul/batch_mat_mul_v3)(DAV_3510/arch35)。以下按实现方案的 Step 0-8 逐维度对比。
-源码(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⌉)。
-源码流程(MatMulV3TilingHelper::GetRebalanceBlock,matmul_v3_tiling_helper.cpp L387-492):
*第 1 步——平台指标*(动态读 platformInfo,适配不同产品形态):
-*第 2 步——cubeBoundEdge 闭式计算*(L418-419):
-其中 $cmr = (M{+}N)/(MN)$、$l2CacheUsage = \max(B \cdot (M{+}N) \cdot K \cdot dtype/L2,\; 1)$。判据:每输出块相对搬运量 $1/baseM + 1/baseN \le edge$ ⟺ 该 tile 处于计算 Bound 侧。
-三项物理含义(第一性推导):
-*第 3 步——枚举与评分*(L448-483):
-为什么源码这么做(设计考虑):cubeBound 是 host 端闭式解析模型,把"该 tile 是否计算 Bound"做成一次不等式比较,替代实测调优;edge 随 shape(K、工作集)和平台(带宽/频率)自适应;双目标帕累托保证解在"算存比"与"负载均衡"之间取平衡。
-与理论最优的优劣判定:
-| 维度 | 理论最优(§5.1) | 源码 | 判定 |
|---|---|---|---|
| tile 面积 | L0C/4B = 65536(单缓冲满) | baseM/baseN 上限 256×256 = 65536 | ✓ 一致 |
| 形状 | 方形优先(L0A=L0B 同时装满、baseK 最大) | 枚举 M/N 独立递减,不强制方形 | ⚠️ 源码可能产出非方形 base(如 256×192),L0B 未满、baseK 被小边限制——可加方形约束改进 |
| baseK | min(L0A/2·BM, L0B/2·BN) 向下 16 对齐 | GetBaseK:K 全载或 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)。
-源码事实:singleCore 与 base 是同一个参数——源码没有分层概念。
-证据链:ResetBaseDav3510(L144-149)置 baseM = baseN = 256、singleCoreM = baseM;CalL1TilingDefault(L77-78)置 singleCoreM = runInfo.baseM、singleCoreN = runInfo.baseN;枚举后 stepM = CeilDiv(singleCoreM, baseM) = 1、stepN = 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、BF16,ASW 承接的 B≥C 且 IterBatch/MergeBatch 不满足的 case):
-结论:当 L1 容量富余(K 小)时,理论的最少切分原则允许 singleCoreM/N 超过 256(上例 512),mCnt/nCnt 减半,GM→L1 总搬入时延(重复读)减半;源码的 singleCore = base ≤ 256 硬上限导致过度切分,L2 重复读翻倍。源码改进方向:引入 SingleCore/Base 分层,SingleCore 由 L1 容量决定(上限解除 L0 约束),stepM/stepN 多轮 L0 计算。这是理论对源码最实质的一条改进建议。
-源码做法:
-块→核映射:UpdateBasicIndex(batch_mat_mul_v3_asw_block_advanced.h)——index = blockIdx + round × usedCoreNum,matIndex = 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):
-GetBalanceRateWithTail(L223-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 维已摊薄尾块效应。判定:理论更优。源码对 BMM 一律不做尾轮拆分(含估算),对小 $n_{wave}$(2~3 轮)的 case 尾轮浪费可达 25%~50% 核时;理论的重切方案 host 端零 NPU 代价、收益可量化(访存 Bound 受 dValue 约束、计算 Bound 可到对齐底线)。理论方案可作为源码改进建议。
-| 维度 | 理论 | 源码 |
|---|---|---|
| 窗口大小 | $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\}$ 完全相同。但源码对细长 shape(mCnt 小或 nCnt≫mCnt)的方形窗假设不成立时,退化为行优先(mainWindow = min(aswWindowLen, mCnt)),未按 shape 长宽比自适应窗形。
| 维度 | 理论 | 源码 |
|---|---|---|
| 启用条件 | $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 |
差异分析:源码有两个额外限制:
-源码中理论要求的"输出直写 GM vs 驻留 L2"的写出策略联合决策(场景 B:$S_{in} \le L2 < S_{in}+S_{out}$)未见显式实现。
-| 维度 | 理论 | 源码 |
|---|---|---|
| 条件 | M ≤ 256,batchA=1,A 驻留 L1,每核 ≥4 轮 | 一致(IsCapable L31-72) |
| A 搬移 | GM→L1 一次搬入 | 一致(Nd2Nz DataCopy 一次搬入) |
| B 搬移 | 每基本块一次 | 一致 |
结论:AL1 全载是理论与源码最吻合的分支。源码注释有笔误("m should be larger than 256" 实为 ≤256),需注意。
-源码的尾轮相关逻辑分布在三处,与理论逐项对比:
-| 机制 | 源码 | 理论(§5.3) | 判定 |
|---|---|---|---|
| 尾块感知评分 | GetBalanceRateWithTail:尾轮按 √ 拆两维估算后计入 rate | $\Delta T_{tail}$ 量化公式 | ≈ 一致(源码是估算、理论是闭式) |
| BMM 尾轮估算 | batchInfo ≠ nullptr 时短路为简单比率,不做拆分估算 | batch 维摊薄尾块效应 | ≈ 一致 |
| 实际重切 | 不做(仅 AL1 全载的 CalcTailBasicBlockAL1Full 沿 N 切尾块逼近满载) | $n_{wave} \le 3$ 且 $r < C$ 时 host 端重切($s^*$ 分块 + 第二套 tiling 参数下发) | ⚠️ 理论更优 |
| 重切约束 | — | 访存 Bound:dValue ≥ 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 参数)。
-| 维度 | 理论 | 源码 |
|---|---|---|
| 实现 | usedCoreNum = ⌈P⌉,其余核闲置 | 未实现——GetNumBlocks() 固定返回 32 |
影响:P < C 的 case(小 M/N、小 B)进入 ASW_Basic 后,所有 32 核参与调度但部分核无实际工作。理论上应只调度 ⌈P⌉ 核。这类 case 的时延绝对值小,但多余核的调度开销(tiling 计算、kernel 启动、上下文切换)是真实存在的。
-| 维度 | 评价 |
|---|---|
| 核间分配 + 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 全载上与理论高度一致,是经实测调优的工程实现。理论分析的价值在于:
-本节给出两张端到端程序实现流程图:从 case 输入(B, M, K, N, dtype 及转置/格式属性)开始,直到所有 tiling & swizzle 参数被清晰计算出来。图 1 严格对应源码实现(每个处理步骤标注所在函数),图 2 对应本文理论最优实现(每个处理步骤标注 §5 的公式出处),§7.3 给出两图差异对照。
-流程图图例约定:[输入/常量]、<处理与判定>、→ 产出参数;分支用 ├─/└─ 表示;✓/✗ 表示通过/拒绝。
源码入口:BatchMatMulV3AswBasicTiling(op_host/op_tiling/arch35/batch_matmul_v3_asw_basic_tiling.cpp,DAV_3510 注册为第 9 优先级模板 ASW_BASIC)。tiling 层依赖 MatMulV3TilingHelper(matmul_v3_tiling_helper.cpp)与 MatMulV3BasicAswtTiling;kernel 层为 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 / aDtypeSize(BF16 取 64)
- → stepM = stepN = 1, singleCoreK = K
- → singleCoreM = baseM, singleCoreN = baseN ← 不分层(本文 §6.3 的过度切分根源)
- → usedCoreNum = 32, dbL0C = 1, iterateOrder = ITER_COL_FIRST
-阶段 2:cubeBound 枚举确定 baseM/baseN/baseK(GetRebalanceBlock)
-[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.85(CUBE_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 → 否则跳过
- ③ cubeBoundCond:curParam ≤ edge 且 curRate > balanceRate
- → 更新解(且 edge = curParam)
- ④ balanceCond:(curParam/curRate) < (cubeBoundParam/balanceRate)
- → 更新解(或比值相等且 curRate 更大)>
- │
- ▼
-<收敛:baseM = min(align16(M), baseM), baseN = min(align16(N), baseN)
- GetBaseK:baseK = 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
-阶段 3:L1 粒度参数(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 ← 仍不分层
-阶段 4:L1 buffer 数与 API 级别(DoOpTiling 尾部)
-<abL1 = baseK·stepKa·(baseM + baseN)·aDtypeSize(hasBias 时加 baseN·biasB)
- l1BufferNum = (abL1 × 4 ≤ L1) ? 4 : 2>
- │
- ▼
-<CheckTensorApiSupport:
- FP32 且 K > 阈值 且 ND 且非连续 → splitK(kernel 内切 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/nTailCnt(CalcTailBasicBlock 尾块拆分)
- mmadParam、l1BufferNum、l0cDB = dbL0C>
- │
- ▼
-<GetTilingDataProcess(MatMulV3TilingData):
- tCubeTiling:singleCoreM/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
-阶段 6:kernel 运行时映射(BatchMatMulAswBlock + BatchMatMulAswKernel::Process)
-<Init:blockBaseM/N = tiling.baseM/baseN
- mCnt = ⌈M/baseM⌉, nCnt = ⌈N/baseN⌉
- mBaseTail = M − (mCnt−1)·baseM, nBaseTail = N − (nCnt−1)·baseN
- totalCnt = B·mCnt·nCnt
- round = ⌈totalCnt/usedCoreNum⌉
- mainWindow = min(aswWindowLen, mCnt)
- mainRow = mCnt/mainWindow − 1
- tailWindow = mCnt − mainRow·mainWindow
- FP32 且 K > 8192 且 B 为 ND:splitKRound = ⌈K/8192⌉(保精度切 K)>
- │
- ▼
-<Process:for j in 0..round−1:
- 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
- UpdateBlockParams:singleCoreM = 尾块 ? mBaseTail : baseM
- singleCoreN = 尾块 ? nBaseTail : baseN
- CalcGMOffset:index 反解 (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 | SetDisableL2cache(totalSize vs L2) |
| iterateOrder | 阶段 1 | ITER_COL_FIRST(列优先) |
| apiLevel | 阶段 4 | TENSOR_LEVEL / BASIC_LEVEL |
| mmadParam | 阶段 5 | HF32 / S8S4 shift |
理论实现按 §5 的推导链组织(§5.1→§5.9),host 端全流程闭式计算,每个判定均可用 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 主体
-阶段 1:BaseM/BaseN/BaseK(§5.1/§5.7,L0 级 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
-阶段 2:SingleCoreM/N 确定(§5.2 完整枚举,本次 v1.5 重写的核心)
-<P = ⌈C/B⌉
- ├─ P = 1(B ≥ 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 > 1(B < C)——有界枚举:
- 枚举空间:mCnt ≤ ⌈M/BaseM⌉、nCnt ≤ ⌈N/BaseN⌉(约束 4 上界)
- 且 B·mCnt·nCnt ≥ C(约束 1)
- 对每个候选执行评估流水线:
- ① 对齐:sM = align16(⌈M/mCnt⌉), sN = align16(⌈N/nCnt⌉)
- ② 约束 4:sM 为 BaseM 整数倍、sN 为 BaseN 整数倍 → 否则剔除
- ③ 约束 2:kL1 = min(K, ⌊L1/(2(sM+sN)·dtype)⌋16)
- ④ 约束 3:kL1·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(零共享,先试)→ 切 M(KN·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 类型:
- 访存 Bound(T_MTE2 ≥ T_MMAD):s_max = min(sN·dtype/256B,
- kL1·sN·dtype/min_TileSize, sN/16)
- 计算 Bound(T_MMAD > T_MTE2):s_max = sN/16
- s* = min(⌊C/r⌋, s_max)
- 切分方向:优先沿 N(A 行带完整、L2 中 A 不变);
- N 向对齐卡住(sN/16 < C/r)改沿 M(A 的 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;否则 B(g≤g_dv)或 A0 兜底>
-阶段 5:swizzle 与错位分核(§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 校验
-阶段 6:L2 分组(§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 Hint(A/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/N);P>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 | C, d ≤ ⌊√C⌋} = 4` |
| calOrder | 阶段 5 | 共享矩阵驻留方向选择 | |
| m_grp, n_grp | 阶段 6 | ⌊D/(2·sM)⌋, ⌊D/(2·sN)⌋ | |
| cacheHint | 阶段 6 | A/B allocate、C non-allocate |
| 步骤 | 源码 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 管理 | SetDisableL2cache(totalSize vs L2 → uncache 标志) | 三场景分组 + 写出联合决策 + Cache Hint/CMO | 源码无执行组划分、无输出直写决策(§6.6) |
| UnitFlag | 未启用 | 计算 Bound 启用(tile 内 512B 块流水) | 流水手段代际差异 |