Files
matmul-analysis/BMM/BMM算子优化分析_Release/ASW_Basic分支分析/ASW_Basic分支分析_v1.2.md

46 KiB
Raw Blame History

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

目标芯片:昇腾 950PRDAV_3510。本文为 ASW_Basic 分支的独立分析自包含完整推导链。v1.2 修正 dValue 定义与尾轮重切约束,新增与源码实现的对比分析。

摘要

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

核心结论:B 优先分组不优于线性映射;尾轮重切在 host 端零代价、n_{wave} \le 3 时应重切且计算 Bound 下 dValue 可放宽;源码的 swizzle/核间分配/AL1 全载与理论高度一致主要差距在降核模式未实现、IsCapable 无并行度校验、尾轮未按 Bound 类型区分重切策略。


一、问题定义与执行模型

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 跌破 128B搬移效率崩塌反而更慢不如只用 ⌈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 的长宽比跟随 SingleCoreM/SingleCoreN进而跟随 M/N对齐 16 的倍数。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 单缓冲上限)
  2. 检查 baseK = \min(L0A/(2 \cdot 256 \cdot dtype),\; L0B/(2 \cdot 256 \cdot dtype)) ≥ 128B/dtype最小高效粒度
  3. 若 baseK 不足,按比例缩小 BaseM/BaseN保持长宽比 ≈ M/N直到 baseK 满足
  4. 若 M ≪ N或反之长宽比跟随 M/NBaseM/BaseN ≈ M/N面积保持 65536

与源码的差异:源码默认 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 越多,并行度越高,但搬移效率降低

约束链

约束 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 边界对齐)。

选取策略:在满足约束 1 的前提下SingleCoreM/N 尽量大(搬移效率和 L2 复用最大化)。长宽比跟随 M/N$\text{singleCoreM}/\text{singleCoreN} \approx M/N$),对齐到 BaseM/BaseN 的整数倍。

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

  1. 从约束 1 得到最小块数:mnCnt_{min} = \lceil C/B \rceil
  2. 枚举 (mCnt, nCnt) 组合,$mCnt \cdot nCnt \ge mnCnt_{min}$,从大到小遍历 singleCoreM = $\lceil M/mCnt \rceil_{16}$、singleCoreN = \lceil N/nCnt \rceil_{16}
  3. 对每个组合检查约束 2/3/4L1 容量、搬移效率、BaseM/N 整数倍)
  4. 计算尾波占比:$r = B \cdot mCnt \cdot nCnt \bmod C$,优先选 r = 0r 大的组合(尾波核数多 → 重切后 s^* 小 → 小块 dValue 有保障)
  5. n_{wave} = \lceil B \cdot mCnt \cdot nCnt / C \rceil \le 3 且 $r > 0$,计算尾轮重切参数 s^* 并评估重切后收益
  6. 选总时延最短的组合:$T_{total} = (n_{wave} - 1) \cdot T_{block} + T_{tail}$,其中 $T_{tail} = T_{block}/s^*$(重切)或 $T_{block}$(不重切)

与源码的差异:源码用 cubeBound 模型 + balanceRate ≥ 0.9 剪枝,不解耦 SingleCoreM/N 与 BaseM/NbaseM=256 已是 SingleCore 级参数L0 tile 由 stepM/stepN 二次切分。理论的两层分离使约束链更清晰——SingleCoreM/N 由 L1 + 并行度 + 搬移效率决定BaseM/N 由 L0C 决定,各司其职。

B=8、M=N=2048、K=1024、BF16$\lceil C/B \rceil = 4$,取 mCnt = nCnt = 2 → singleCoreM = singleCoreN = 1024。BaseM = BaseN = $\lfloor\sqrt{32768}\rfloor_{16} = 176$。SingleCoreM/BaseM = 1024/176 ≈ 5.8 → 取 5整数倍→ singleCoreM = 880。约束 2k_{L1} \le 512\text{KB}/(2 \times 1760 \times 2\text{B}) = 74 → 取 6416 对齐)= 128B ✓。

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 时延的瓶颈所在,也是"时延绝对值小、不再继续优化"的定量注脚。


六、与源码实现的对比

源码参考 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

维度 理论 源码
目标 BaseM × BaseN = L0C/(2×4B) = 32768先把 L0C 用满 baseM=256, baseN=256硬编码 "256 is better base"
长宽比 跟随 M/N 由 cubeBound 模型寻优
baseK min(L0A/2·BaseM, L0B/2·BaseN),无 dValue 要求 baseK = 128B/dtype最小高效粒度

源码的 cubeBound 模型GetRebalanceBlockmatmul_v3_tiling_helper.cpp L387-492


cubeBoundEdge = \frac{l2BW}{computePower} + l2CacheUsage \cdot \Big(1 - \frac{l2BW}{hbmBW}\Big) \cdot cmr - \frac{1 + l2BW/hbmBW}{kValue}

三项含义:① L2 供数速率 ÷ Cube 耗数速率(理论阈值);② 工作集超 L2 时的访存惩罚(抬高 edge倾向更大 tile③ K 向复用修正K 越大 edge 越小,偏 compute bound。乘以 CUBE_BOUND_RATIO=0.85 余量。枚举 baseM/baseN 递减balanceRate ≥ 0.9 剪枝。

差异分析:理论(修正前)按双缓冲取 L0C 满载的一半32768 元素),源码用 256×256=65536 元素——源码的 baseM×baseN 恰好等于 L0C 单缓冲上限256×256×4B = 256KB = L0C。源码的 dbL0C 逻辑:dbL0C = (baseM × baseN × 4B × 2 > L0C) ? 1 : 2——baseM=baseN=256 时 512KB > 256KB自动选 dbL0C=1单缓冲

这说明源码的 baseM/baseN 不是 L0 级 tile 尺寸,而是 SingleCoreM/N 级参数。源码中 L0 级 tile 由 stepM/stepN 二次切分决定per-core tile = baseM × stepM。理论的分层SingleCoreM ≥ BaseM在源码中无显式概念。

UnitFlag 现状BMM ASW kernel 中 unitFlag = 0block_mmad_iterbatch.h L315 注释:"each l0 only process one block, disable unit flag"——MMAD 与 Fixpipe 之间是指令级同步(整个 L0C tile 算完才写出)。若启用 UnitFlagMMAD 每完成一个 16×16×16 块512BFixpipe 立即写出——tile 内部细粒度流水替代 tile 间粗粒度流水,单缓冲即可达到双缓冲的流水效果。对计算 Bound 的 caseUnitFlag 可将单 tile 时延从 T_{comp} + T_{write} 降至 $\max(T_{comp}, T_{write})$。

6.3 SingleCoreM / SingleCoreN 确定§5.2

维度 理论 源码
概念 独立于 L0由 GM→L1 效率 + L2 复用决定 无显式概念per-core tile = baseM × stepM
约束 并行度 + L1 容量 + 搬移效率 + BaseM 整数倍 cubeBound 模型 + balanceRate ≥ 0.9
选取策略 尽量大(搬移效率最大化) 枚举寻优cubeBound + 尾块感知)

关键差异:理论把 SingleCoreM/N 和 BaseM/N 分为两层SingleCoreM ≥ BaseM源码中 baseM=256 实际上已经是 SingleCoreM 级参数。理论的分层更清晰,源码更保守但经实测调优。

6.4 核间分配§5.3

维度 理论 源码
分配策略 B→M→N 线性映射 一致(index = blockIdx + round × usedCoreNum
B 优先分组 证明不优 源码确实采用线性映射
尾轮 不重新切分 一致balanceRate ≥ 0.9 剪枝)

结论:核间分配策略与理论一致。源码的 UpdateBasicIndexbatch_mat_mul_v3_asw_block_advanced.h)实现了 B→M→N 线性映射 + 划窗蛇形。

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

维度 理论 源码
尾轮重切 不重切(收益有限、工程代价真实) 一致balanceRate ≥ 0.9 剪枝)
尾轮量化 \Delta T_{tail}/T_{total} \approx (1-r/C)/n_{wave} GetBalanceRateWithTailL223-246

结论:尾轮处理策略一致。源码通过选择使尾波占比尽量小的 mCnt/nCnt 组合来优化,而非重新切分尾轮。

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 向复用)可以从第一性原理推导。

参考文献

  1. 昇腾 950 NPU 架构白皮书华为技术有限公司2026
  2. cann-ops-nn 源码仓