diff --git a/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html b/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html index 167f5cf..9a9ed76 100644 --- a/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html +++ b/BMM算子优化分析_Release/BMM算子优化分析_v0.94.html @@ -403,7 +403,7 @@ $$
BatchMatMulAswBlock::UpdateBasicIndex:仅 rowIdx 为奇时 n 反向,窗内 m 最快序不反向),正是这个收益结构的直接实现。3、L2 切分(工作集超 L2 时)
-切的是什么:L2 切分切的是输出平面——把 M×N 平面切成 mL2TileNum × nL2TileNum 个大矩形块(每块 = 若干基本块的集合),使"该块所需的 A 行带 + B 列带"输入工作集 ≤ 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$。
两个不变量(一切分析的起点):
例: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$(输入本身超)**。必须 L2 切分。输出直写 GM 以最大化 $L2_{read}$。
-mL2 / nL2 的确定方法:
-*问题*:把 M×N 输出平面切成 mL2 × nL2 个大矩形块,每块内所有输出基本块所需的输入工作集(A 行带 + B 列带,跨全部 B 个 batch)须 ≤ $L2_{read}$。
-*每块输入工作集*:L2 块 $(i,j)$ 覆盖 M 向 $M/mL2$ 行、N 向 $N/nL2$ 列。该块需要读入的 A 数据 = $B \times (M/mL2) \times K \times dtype$,B 数据 = $B \times K \times (N/nL2) \times dtype$。合计:
+**场景 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}]$。该区域需要读入的输入:
*目标*:最小化块数 mL2×nL2(块数越少,输入从 GM 的重复读次数越少)。块间输入重复读倍率:
+*目标*:最小化组数(组数越少,输入从 GM 的重复读次数越少)。每行 A 被 $n_{grp}$ 个组各读一次,每列 B 被 $m_{grp}$ 个组各读一次:
(每行 A 被 nL2 个块各读一次,每列 B 被 mL2 个块各读一次)。
-*求解*:约束 $M/mL2 + N/nL2 \le D$(其中 $D = L2_{read}/(B \cdot K \cdot \text{dtype})$),最小化 $nL2 \cdot M + mL2 \cdot N$。由拉格朗日乘子法,最优在 $mL2/nL2 = M/N$ 时取到——L2 切分比例应与输出平面形状成正比。代入约束:
+*求解*:约束 $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 向基本块数与输出平面形状成正比时取到:
与 L1 切分的关系:mL2/nL2 与 mL1/nL1 独立——L1 切分由 L0C 容量和 dValue 决定(核内基本块尺寸),L2 切分由 L2 容量和输入工作集决定(块间复用粒度)。两者不要求相等或成倍数关系。工程实现时,L2 块边界应对齐到基本块边界(mL2 整除 mL1、nL2 整除 nL1),避免跨块碎块。
-*例*(B=64、M=N=2048、K=1024、BF16、$L2_{read}$=128MB):$D = 128\text{MB}/(64 \times 1024 \times 2\text{B}) = 1024$。$mL2 = \lceil 2 \times 2048/1024 \rceil = 4$,$nL2 = 4$。切 4×4=16 块,每块工作集 $64 \times 1024 \times (512+512) \times 2 = 128\text{MB} = L2_{read}$,恰好装满。重复读倍率 $r_{in} = (4 \times 2048 \times 1024 + 4 \times 1024 \times 2048)/(2048 \times 1024 + 1024 \times 2048) = 4$。
+总组数 $= \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