v0.94: 同步HTML
This commit is contained in:
@@ -403,7 +403,7 @@ $$</div>
|
||||
<li><b>窗口行边界</b>(窗口0→窗口1):A 行整体换血(μ0..3 → μ4..7),此时 B 列带的连续性决定换血成本——不蛇形则下一窗口从 ν0 开始(LRU 上最久未用、早已被挤出 L2 的冷带),蛇形则延续上一窗口末尾的 ν7(最热线带)。<b>蛇形只标在窗口行号上</b>(源码 <code>BatchMatMulAswBlock::UpdateBasicIndex</code>:仅 <code>rowIdx</code> 为奇时 n 反向,窗内 m 最快序不反向),正是这个收益结构的直接实现。</li>
|
||||
</ul>
|
||||
<p><b>3、L2 切分(工作集超 L2 时)</b></p>
|
||||
<p><b>切的是什么</b>:L2 切分切的是<b>输出平面</b>——把 M×N 平面切成 mL2TileNum × nL2TileNum 个大矩形块(每块 = 若干基本块的集合),使"该块所需的 A 行带 + B 列带"输入工作集 ≤ L2 可用读入空间;块内所有输出基本块算完再进下一块,输入只在跨块时换一次。</p>
|
||||
<p><b>切的是什么</b>:将 mCnt×nCnt 个基本块划分为若干<b>执行组</b>——每组覆盖输出平面上一个连续矩形区域(若干 singleCoreM × singleCoreN 基本块的集合),使该组所需的 A 行带 + B 列带输入工作集 ≤ L2 可用读入空间;组内所有基本块算完再进下一组,输入只在跨组时换一次。</p>
|
||||
<p><b>为什么需要它</b>:滑窗压缩的只是"同一波"的足迹;若整个工作集超 128MB L2,跨波次复用落空——上一波窗口的 A 行早被挤出,下一波又得回 GM 读。且 L2 是<b>读写共用</b>的:输出经 fixpipe 写出时若驻留 L2(dirty),会压缩读入可用空间;若直写 GM,则占用与读共享的 1.6TB/s 总线。所以 L2 切分必须与写出策略联合决策。记输入总量 $S_{in} = B(MK+KN)\cdot\text{dtype}$,输出总量 $S_{out} = B \cdot MN \cdot outB$。</p>
|
||||
<p><b>两个不变量</b>(一切分析的起点):</p>
|
||||
<ul class="tight">
|
||||
@@ -426,24 +426,30 @@ $$</div>
|
||||
<li>代价是输出即刻占用 GM 写带宽(与读共享总线),须校验总线不爆:$(S_{in} + S_{out})/T_{MMAD} \le W_{GM}$。</li>
|
||||
</ol>
|
||||
<p>例: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 ✓。</p>
|
||||
<p>**场景 C:$S_{in} > L2$(输入本身超)**。必须 L2 切分。输出直写 GM 以最大化 $L2_{read}$。</p>
|
||||
<p><b>mL2 / nL2 的确定方法</b>:</p>
|
||||
<p>*问题*:把 M×N 输出平面切成 mL2 × nL2 个大矩形块,每块内所有输出基本块所需的输入工作集(A 行带 + B 列带,跨全部 B 个 batch)须 ≤ $L2_{read}$。</p>
|
||||
<p>*每块输入工作集*: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$。合计:</p>
|
||||
<p>**场景 C:$S_{in} > L2$(输入本身超)<b>。需要分组执行——把 mCnt×nCnt 个基本块划分为若干</b>执行组**,每组内所有基本块的输入工作集不超过 L2 可用空间。输出直写 GM(不占 L2 读入空间)。</p>
|
||||
<p><b>L2 的软件可控手段</b>:L2 是 Cache 而非 Buffer,软件无法精确控制"哪些数据在 L2 里"。可用的控制手段:</p>
|
||||
<ul class="tight">
|
||||
<li><b>Cache Hint</b>(<code>SetL2CacheHint</code>):标记输入为 allocate(读入 L2)或 non-allocate(直读 GM 不过 L2);标记输出为 non-allocate(直写 GM 不占 L2);</li>
|
||||
<li><b>CMO</b>(Cache Maintenance Operation):Prefetch/Writeback/Invalidate,在关键节点主动管理 L2 内容;</li>
|
||||
<li><b>执行顺序</b>(swizzle):通过编排基本块的执行顺序,控制同一时刻的活跃工作集——<b>这是最主要的 L2 管理手段</b>。</li>
|
||||
</ul>
|
||||
<p><b>执行组的划分</b>:</p>
|
||||
<p>*问题*:mCnt×nCnt 个基本块(每块输出 singleCoreM×singleCoreN),按什么粒度分组,使每组的输入工作集 ≤ L2?</p>
|
||||
<p>*每组输入工作集*:一组覆盖 M 向 $m_{grp}$ 个基本块、N 向 $n_{grp}$ 个基本块,即覆盖输出区域 $[m_{grp} \cdot \text{singleCoreM},\; n_{grp} \cdot \text{singleCoreN}]$。该区域需要读入的输入:</p>
|
||||
<div class="math">$$
|
||||
WS_{block} = B \cdot K \cdot \Big(\frac{M}{mL2} + \frac{N}{nL2}\Big) \cdot \text{dtype} \;\le\; L2_{read}
|
||||
WS_{grp} = B \cdot K \cdot \big(m_{grp} \cdot \text{singleCoreM} + n_{grp} \cdot \text{singleCoreN}\big) \cdot \text{dtype} \;\le\; L2
|
||||
$$</div>
|
||||
<p>*目标*:最小化块数 mL2×nL2(块数越少,输入从 GM 的重复读次数越少)。块间输入重复读倍率:</p>
|
||||
<p>*目标*:最小化组数(组数越少,输入从 GM 的重复读次数越少)。每行 A 被 $n_{grp}$ 个组各读一次,每列 B 被 $m_{grp}$ 个组各读一次:</p>
|
||||
<div class="math">$$
|
||||
r_{in} = \frac{nL2 \cdot MK + mL2 \cdot KN}{MK + KN}
|
||||
r_{in} = \frac{n_{grp} \cdot M + m_{grp} \cdot N}{M + N}
|
||||
$$</div>
|
||||
<p>(每行 A 被 nL2 个块各读一次,每列 B 被 mL2 个块各读一次)。</p>
|
||||
<p>*求解*:约束 $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$ 时取到——<b>L2 切分比例应与输出平面形状成正比</b>。代入约束:</p>
|
||||
<p>*求解*:约束 $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 向基本块数与输出平面形状成正比时取到:</p>
|
||||
<div class="math">$$
|
||||
mL2 = \Big\lceil \frac{2M}{D} \Big\rceil,\qquad nL2 = \Big\lceil \frac{2N}{D} \Big\rceil,\qquad D = \frac{L2_{read}}{B \cdot K \cdot \text{dtype}}
|
||||
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
|
||||
$$</div>
|
||||
<p><b>与 L1 切分的关系</b>:mL2/nL2 与 mL1/nL1 <b>独立</b>——L1 切分由 L0C 容量和 dValue 决定(核内基本块尺寸),L2 切分由 L2 容量和输入工作集决定(块间复用粒度)。两者不要求相等或成倍数关系。工程实现时,L2 块边界应对齐到基本块边界(mL2 整除 mL1、nL2 整除 nL1),避免跨块碎块。</p>
|
||||
<p>*例*(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$。</p>
|
||||
<p>总组数 $= \lceil mCnt/m_{grp} \rceil \times \lceil nCnt/n_{grp} \rceil$。</p>
|
||||
<p><b>组内 swizzle</b>:每组内部按 ASW 滑窗蛇形执行(窗口 $W = \max\{d \mid d \mid C,\; d \le \lfloor\sqrt{C}\rfloor\}$,C=32 时 W=4),保证同一波 C 个核的活跃工作集最小。组间切换时输入整体换入——上一组的 A 行带和 B 列带全部失效,从 GM 重新读入下一组的数据。</p>
|
||||
<p>*例*(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 次。</p>
|
||||
<p>块内分配用<b>错位分核</b>(对角线分配):线性块号先取 m,n 方向叠加随块号递增的相位偏移,使同一时刻各核落在 M×N 平面的不同对角线上——避免多核同一拍并发读同一行 A / 同一列 B 的同一地址(同地址并发读会串行化,等效带宽打折)。例(8 核、4×4=16 个基本块,k0~k7 为核号):</p>
|
||||
<pre><code>行优先(不错位): 错位分核(对角线):
|
||||
n0 n1 n2 n3 n0 n1 n2 n3
|
||||
|
||||
Reference in New Issue
Block a user