diff --git a/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html b/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html index 9a9ed76..06adc05 100644 --- a/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html +++ b/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html @@ -344,6 +344,30 @@ $$

max 取更严格的 8192。修正后的归约阈值(θ_c≈12,grid_K=32 时 K>396)远低于 8192,说明 8192 的绑定约束是 dValue(条件 2),不是归约代价(条件 3)。源码不动态计算 grid_K,用固定阈值同时覆盖条件 2 的最保守情形和条件 3,是两条条件的保守合并近似。

实现方案

+

Step 0:singleCoreM / singleCoreN 的确定(每核输出基本块尺寸)

+

singleCoreM × singleCoreN 是 ASW_Basic 的基石参数——它决定了每核每次计算的输出 tile 大小,进而决定 mCnt/nCnt、swizzle 窗口、L2 执行组等所有后续参数。确定逻辑按约束链推导:

+

*约束 1——L0C 容量*(输出 tile 必须放得下 L0C 双缓冲):

+
$$ +\text{singleCoreM} \times \text{singleCoreN} \times 4\text{B} \times 2 \le L0C \Rightarrow \text{singleCoreM} \times \text{singleCoreN} \le 32768 \text{ 元素} +$$
+

*约束 2——L0A/L0B 容量*(输入 tile 决定 baseK 上限):

+
$$ +baseK \le \min\Big(\frac{L0A}{2 \cdot \text{singleCoreM} \cdot \text{dtype}},\; \frac{L0B}{2 \cdot \text{singleCoreN} \cdot \text{dtype}}\Big) +$$
+

*约束 3——dValue*:$baseK \cdot \text{dtype} \ge 256\text{B}$(BF16 为 128 元素)。

+

*约束 4——L1 容量*(双缓冲下驻留输入):

+
$$ +2 \cdot (\text{singleCoreM} + \text{singleCoreN}) \cdot k_{L1} \cdot \text{dtype} \le L1 +$$
+

*约束 5——搬移效率*:$\text{singleCoreM} \cdot K \cdot \text{dtype} \ge min\_TileSize$ 且 $K \cdot \text{singleCoreN} \cdot \text{dtype} \ge min\_TileSize$。

+

*选取策略*:在约束 1 的上界(32768 元素)内,singleCoreM/singleCoreN 的长宽比应跟随 M/N 的长宽比——$\text{singleCoreM}/\text{singleCoreN} \approx M/N$,使 GM 访问的空间局部性最优(减少跨行/跨列的 strided 访问)。同时对齐到 16 的倍数(Cube 基本块粒度)。

+

*例*:M=N=2048,方形 → singleCoreM=singleCoreN=$\lfloor\sqrt{32768}\rfloor_{16}$=176。约束 2:$baseK \le 16384/176 = 93$ → 取 80(16 对齐)。约束 3:$80 \times 2 = 160\text{B} \ge 128\text{B}$ ✓。

+

*例*:M=4096、N=512,长条 → singleCoreM=256、singleCoreN=128($256 \times 128 = 32768$ 恰好满载 L0C/2)。约束 2:$baseK \le \min(16384/256, 16384/128) = \min(64, 128) = 64$。

+

Step 1: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 优先级分配到 C 核。

核间组织:先按 B/M/N 切出输出块,剩余核预算折成 K 向份数:

$$ blocksPerBatch = \Big\lfloor \frac{C}{B} \Big\rfloor,\qquad @@ -372,8 +396,8 @@ $$
  • 降核模式:P < C 且 K 也不够格走 StreamK 时,并行度凑不满核。此时与其强行把 M/N 切得更碎(tile 跌破 min_TileSize、dValue 跌破 128B,搬移效率崩塌,反而更慢),不如只用 ⌈P⌉ 个核、每核承担一个完整输出块(L0C 满载粒度),其余核闲置。这类 case 的时延绝对值小,继续切分引入的调度与搬移效率损失大于并行收益——降核是理性选择而非偷懒。
  • 实现方案

    -

    1、核间切分维度选择(按共享代价从低到高):切 B(零共享,先试)→ 切 M(右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2)→ 切 N(对称)→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见第 6 条)。

    -

    2、swizzle:ASW 滑窗蛇形

    +

    Step 2:核间切分维度选择(按共享代价从低到高):切 B(零共享,先试)→ 切 M(右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2)→ 切 N(对称)→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见 Step 7)。

    +

    Step 3:swizzle——ASW 滑窗蛇形

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

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

    -

    3、L2 切分(工作集超 L2 时)

    +

    Step 4:L2 分组(工作集超 L2 时)

    切的是什么:将 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$。

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

    @@ -466,9 +490,9 @@ transConflict = \max\big(\lceil C / mCnt \rceil,\; \lceil C / nCnt \rceil\big) \ $$

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

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

    -

    4、核内 tiling:$M^t N^t \cdot 4\text{B} \cdot DB \le L0C$;$M^t K^t \cdot \text{dtype} \cdot 2 \le L0A$、$K^t N^t \cdot \text{dtype} \cdot 2 \le L0B$;内轴按 dValue 256B/512B 对齐;L1 按容量开双缓冲,余量充足开 4 buffer。

    -

    5、内部特化(参数极限,不是独立分支):单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次(L1 全载)。

    -

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

    +

    Step 5:核内 tiling:$\text{singleCoreM} \times \text{singleCoreN} \times 4\text{B} \times DB \le L0C$;$\text{singleCoreM} \times k_{L0} \times \text{dtype} \times 2 \le L0A$、$k_{L0} \times \text{singleCoreN} \times \text{dtype} \times 2 \le L0B$;内轴按 dValue 256B/512B 对齐;L1 按容量开双缓冲,余量充足开 4 buffer。

    +

    Step 6:内部特化(参数极限,不是独立分支):单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次(L1 全载)。

    +

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


    九、特殊分支