目标芯片:昇腾 950PR(DAV_3510)。所有分支进入条件只含 case 形状参数(B、M、N、K、dtype)与芯片规格参数。
完成带 batch 的矩阵乘:C = A @ B + bias。
[BatchA, M, K],dtype,典型 ND,可带转置[BatchB, K, N],dtype,典型 ND,可带转置[B, 1, N],固定 ND,可为空[BatchC, M, N],BatchC = broadcast(BatchA, BatchB)| 符号 | 含义 | 950PR 取值 |
|---|---|---|
| C | AIC 核数(aicNum) | 32 |
| Q₁₆ | 单核 Cube BF16 峰值算力 | 486/C ≈ 15.2 TFLOPS |
| Q_AIV | AIV 向量求和吞吐(64 核合计) | 64×128 fp32/拍×1.65GHz ≈ 13.5 Tops/s |
| L1 | 每核 L1 Buffer | 512KB |
| L0A / L0B | 每核 L0A / L0B | 64KB / 64KB |
| L0C | 每核 L0C(FP32 累加,4B/元素) | 256KB |
| L2 | L2 Cache 容量 | 128MB |
| W_L2 | L2 读写带宽 | 5.2TB/s |
| W_GM | GM 带宽(读写共享) | 1.6TB/s |
| R₁₆ | 16bit 对应位宽算存比 | ≈607.5 FLOP/元素 |
| dValue | 单数据块内数据连续排布长度 | 推荐 256B/512B,不建议 <128B |
| min_TileSize | 确保高带宽利用率的单块搬移数据量最小值 | 16KB |
| min_DatamountPerCore | 确保高带宽利用率的单核搬移数据量最小值 | 480KB |
| minCoreNum | 确保高带宽利用率的并行搬移核数最小值 | ≈0.8C = 26 |
以上数值基于 950PR 实测分析总结;对搬移带宽利用率的影响重要性排序为:核数 > 单核搬移总数据量 > 单分块大小 > dValue。不同 NPU 芯片数值可能略有差异,换芯片时逻辑结构不变、只换常数表。
BMM 的执行是核内多级硬件流水的并行——Cube 计算(MMAD)、GM/L2→L1(MTE2)、L1→L0(MTE1)、L0C 写出(Fixpipe),各流水级时延可被双缓冲相互掩盖:
总时延 = 最慢一级流水,优化的关键是对瓶颈级的优化。由此得到设计自由度——瓶颈交换:搬移是瓶颈时可牺牲算力(冗余计算)换搬移效率;计算是瓶颈时可牺牲搬移(重复读取)换计算效率。MergeBatch 是前者的典型,ASW_Basic 切 M/N 是后者的典型。
case 固有算存比与 16bit 位宽平衡点:
其中 486 TFLOPS 是乘加各计一次后的标称算力;分母中的 2B 是 16bit 元素字节数,作用是把 GM 带宽折算成元素速率。
$AI < R_{16}$ → 访存 Bound(瓶颈在 MTE2);反之计算 Bound(瓶颈在 MMAD)。
BMM 的实现本质是:把数据分块(tile),由全部 AIC 核并行 + 串行完成这些分块的计算,再组合成最终结果:
分块有 4 个维度:B、M、N、K。核间怎么分这 4 个维度,就是分支划分的第一性问题(核内分块是第二性问题,属于各分支内部 tiling)。
四个维度的核间切分特征(后续一切推导的基石):
| 切分维度 | 读入特征 | 计算特征 | 写出特征 |
|---|---|---|---|
| 切 B | 核间零重复读(每个数据块只被 1 个核读取);核内是否重复读另有条件——若 L1 放不下单 batch 完整的 M、N 维输入(K 维可切段放入,kL1<K),单 batch 计算中切 M 会重复读 B、切 N 会重复读 A | 每个输出块由 1 个核独立完成,无核间依赖 | 只写最终结果,无中间结果 |
| 切 M / 切 N | 切 M 则同一右矩阵块被多核重复读;切 N 则同一左矩阵块被多核重复读 | 每个输出块由 1 个核独立完成,无核间依赖 | 只写最终结果,无中间结果 |
| 切 K | 每个数据块只被固定的 1 个核读取,零重复读 | 每个输出块由多核共同完成,存在核间依赖 | 有中间结果写出,需核间 Reduce 归约 |
差异的根本原因:B 维在数学上独立(逐 batch 独立矩阵乘),切 B 核间天然零重复、零依赖;K 维有 L0C 累加机制——核内切 K 时多轮 mmad 在 L0C 原地累加、中间结果不出核,一旦切到核间,部分和必须写出 workspace 再归约——切 K 是唯一同时破坏"累加不出核"和"输出独占"的切法。
4 维的任意非空子集共 $2^4-1=15$ 种切分组合,任何实现方案必属其一(完备):
| 组 | 组合 | 共同特征 | 优化重心 |
|---|---|---|---|
| 纯 B | {B} | 核间零重复读(核内重复读取决于 L1 驻留形态);无中间写出 | 搬移效率 / 计算效率 |
| 含 M/N 不含 K | {M},{N},{M,N},{B,M},{B,N},{B,M,N} | 核间可能有重复读 | 重复读尽量少(L2+swizzle 吸收)+ 搬移/计算效率 |
| 含 K | 其余 8 种 | 有中间结果写出 + 归约 | 计算效率,且归约时延不能成为新瓶颈 |
核间切分价格严格排序:cost(切 B) = 0 < cost(切 M/N) ≪ cost(切 K)。切 B 核间免费;切 M/N 的重复读可被 128MB L2(5.2TB/s vs GM 1.6TB/s)+ swizzle 大部分吸收;切 K 的归约流量 ∝ grid_K×输出量且引入核间同步,是结构性代价。整条分支决策树就是:按价格从低到高购买并行度,买不够才加价。
由此得 6 大分支:转Matmul、特殊分支、IterBatch、MergeBatch、ASW_Basic、StreamK。重叠区(如 B≥C 且 M×N 中等时 MergeBatch 与 IterBatch 都合法)由端到端时延模型 $T_{total}$ 仲裁;分支体系保证候选集完备无冗余。
解释:单边 batch=1 的 BMM 与 Matmul 只差一个维度标签,直接复用 Matmul 的成熟优化体系。
[B,M,K] 的 batch 维与 M 维在 ND 下内存相邻紧排,直接视图为 [B·M,K],输出布局逐元素一致——零重排、零 split,免费转换;[K, B·N] 需一次真实转置重排(O(B·K·N)),且输出存在置换需 scatter——有代价。A 较小($MK\cdot\text{dtype} \le L1$)时优先 A 常驻 L1、留在 BMM 分支内(广播友好形态);A 较大时按广播扩展后分别预估"BMM 分支"与"重排+转Matmul"的时延,择优。核间切 B(每核 $b_{core}$ 个 batch,核间无同步);核内把 b 个 batch 合并计算:$[b,M,K]@[b,K,N] \Rightarrow [bM,K]@[K,bN]=[bM,bN]$,BlockTrace 取块对角线得 $[b,M,N]$。交叉项被算出但丢弃(浪费比例 (b−1)/b)——进入该分支的 case 必然访存 Bound(条件 5 保证),浪费的算力被搬移时延掩盖。
同时满足(b0 = 单次合并计算的 batch 数下限,b0 ≥ 2):
MergeBatch vs IterBatch 分界建模:
*执行模型*:两分支的核间切分相同——B 维切分到 C 核,每核 $b_{core} = B/C$ 个 batch;核内不切 M/N。差异在核内 K 维处理:
*符号*:
| 符号 | 含义 | 表达式 |
|---|---|---|
| $k_{L1}$ | L1 级 K 分块粒度(双缓冲) | $\min\big(K,\; L1/(2(M{+}N)\cdot\text{dtype})\big)$ |
| $n_K$ | K 分块数 | $\lceil K/k_{L1} \rceil$ |
| $T_{load}$ | 每 K 分块搬移时延 | $k_{L1}(M{+}N)\cdot\text{dtype}/BW_{pc}$ |
| $T_{comp}$ | 每 K 分块计算时延 | $2MN \cdot k_{L1}/Q_{16}$ |
| $T_{write}$ | 单 batch 输出写回时延 | $MN \cdot outB/W_{GM}$ |
| $T_{bd}$ | batch 边界固定开销 | fixpipe 启动握手(详见下文物理成因) |
| $BW_{pc}$ | 单核 GM 带宽份额 | $W_{GM}/C$ |
*端到端时延模型*:对每核执行过程建模。稳态流水(unitflag 交叠 batch 间 drain/startup)加上 batch 边界固定开销 $T_{bd}$(每边界一次):
其中 MergeBatch 合并后 $k_{L1}^m = \min(K,\; k_{L1}/b_0)$——L1 绑定($k_{L1}<K$)时 $k_{L1}^m = k_{L1}/b_0$;K 截断($k_{L1}=K$)时 $k_{L1}^m = K$ 不减半。以 L1 绑定为例。
条件 5 保证合并后仍访存 Bound($T_{load} > b_0 T_{comp}$),两式相减:
分界条件(MergeBatch 优于 IterBatch 当且仅当 $\Delta < 0$):
penalty 两种情形的含义:drain 暴露 = 末(合并)batch 最后一个 K 分块的计算时延 + 输出写回时延——这部分没有下一块搬移可交叠,是流水线的 drain 尾部。
小 MN 时 $T_{comp}$ 小 → penalty 小 → MergeBatch 更容易赢;大 B 时 $b_{core}$ 大 → 边界节省多 → MergeBatch 更容易赢。条件 1($b_{core} \ge 2b_0$)是该分界在典型 $T_{bd}$ 下的保守近似,精确边界依赖 $T_{bd}$ 实测标定。
**$T_{bd}$ 的物理成因**(batch 边界处硬件必须完成的固定动作):
从 IterBatch 的 kernel 侧源码(cmct/block/block_mmad_iterbatch.h)可直接观察到 batch 边界的 Set/Wait 同步序列:
// 当前 batch 最后一个 K 分块结束后:
SetFlag<M_FIX>(l0cEventID_ & 0x1); // Cube → fixpipe: "L0C 可以排空"
WaitFlag<M_FIX>(l0cEventID_ & 0x1); // 等 fixpipe 确认开始 ← 串行握手,不可掩盖
CopyOut(cTensor, c1Local_[...]); // fixpipe 排空 L0C
SetFlag<FIX_M>(l0cEventID_ & 0x1); // fixpipe → Cube: "L0C 已空"
// 下一 batch 第一个 K 分块:
WaitFlag<FIX_M>(l0CEventID_ & 0x1); // 等 L0C 可用(被 L0C 双缓冲掩盖)
$T_{bd}$ 由三个部分组成:
WaitFlag<M_FIX>):fixpipe 从收信号到启动排空的延迟——地址/步长寄存器重配置(每 batch 输出写往 GM 不同区域)。串行、不可掩盖,是 $T_{bd}$ 的主要成分;WaitFlag<FIX_M>):L0C 双缓冲——Cube 写一半、fixpipe 读另一半。被双缓冲掩盖,不贡献 $T_{bd}$;证据来源:
| 证据 | 文件 | 内容 |
|---|---|---|
| MMAD→Fixpipe 同步开销存在 | CANN社区版9.2.0-beta.1/02_API参考/AscendC_API/180_..._UnitFlag.md | "未开启UnitFlag功能时,MMAD和FIXPIPE是指令级别的同步……流水串行";实测 7.3% 改善(M=128,N=30720,K=64) |
| 编译器默认插入同步指令 | CANN社区版9.2.0-beta.1/01_AscendC算子开发/048_..._算子编译基本用法.md | "默认会插入内存同步指令……这些同步指令会带来性能开销" |
| 同步机制是架构级关注点 | 00_硬件/昇腾950_NPU架构白皮书.pdf §4.1.6 | 新增 BufferID 同步机制替代 set_flag/wait_flag——"降低了同步的复杂度" |
注:非融合场景下 IterBatch/MergeBatch 仅使用 AIC(Cube),AIV 不参与(kernel_matmul_iterbatch.h line 295-297:if (!enableFusion) { if ASCEND_IS_AIV { return; } }),故不引用 AIV 侧同步文献。
$T_{bd}$ ≈ fixpipe 启动握手延迟(寄存器写入 + DMA 启动),量级估计为数十 ns。知识库未给出精确数值,需实测标定。
这些开销与 batch 的计算量无关,是每次 batch 切换的固定成本。大 batch 时被大量计算分摊,占比可忽略;小 batch 时每个 batch 绝对时延短,边界开销占比显著。MergeBatch 把 $b_0$ 个 batch 并为一次大矩阵计算,边界数降 $b_0$ 倍——这是 MergeBatch 的结构性收益来源。
MergeBatch vs IterBatch 优势总结:
| 维度 | IterBatch | MergeBatch | 差异来源 |
|---|---|---|---|
| 稳态搬移吞吐 | 相同 | 相同 | 总搬移量、每块大小、总块数相同 |
| batch 边界开销 | $b_{core} \cdot T_{bd}$ | $b_{core}/b_0 \cdot T_{bd}$ | **MergeBatch 少 $b_0$ 倍** ← 核心优势 |
| drain 暴露 | $T_{comp} + T_{write}$ | $b_0(T_{comp} + T_{write})$ | IterBatch 少 $b_0$ 倍 ← 核心劣势 |
| L0C 利用率 | $MN \cdot 4\text{B}$ | $b_0^2 MN \cdot 4\text{B}$ | 访存 Bound 下不影响时延 |
净收益 = 边界节省 - drain 惩罚 = $b_{core}(1-\frac{1}{b_0})T_{bd} - (b_0-1)(T_{comp}+T_{write})$。大 B($b_{core}$ 大)且小 MN($T_{comp}$ 小)时 MergeBatch 最优。
L0C 利用率说明:小 MN 时 IterBatch 的 L0C tile($MN \cdot 4\text{B}$)远小于 L0C 容量,MergeBatch 合并后更接近满载。但访存 Bound 下计算被搬移掩盖,L0C 利用率不影响总时延——不构成 MergeBatch 的优势。
Step 1:合并数 b(L0C + 算存比双上限)
b 尽量取 $b_{core}$ 的因子(每次合并数均匀,负载与功耗更优)。
**Step 2:L0 级 K 粒度 $k_{L0}$**
解释:L0A/L0B 各 64KB、双缓冲两份,装入合并后 $bM$ 行($bN$ 列)× $k_{L0}$ 的 fractal;末项是 dValue 下限。
**Step 3:L1 级 $k_{L1}$、$b_{L1}$**
解释:$k_{L1}$ 是 GM→L1 的 K 向粒度,按 dValue 推荐值 256B 取;L1 双缓冲两份,每份驻留 $b_{L1}$ 个 batch 的 A/B 各一块;$b_{L1} \ge b$ 保证合并不断供。
核间切 B(每核 $b_{core} \ge 1$ 个 batch,核间无同步);核内逐个 batch 做标准 Matmul 分块计算。无算力浪费、无跨 batch 依赖,是"切 B"最朴素的形态。
同时满足:
重复读发生在单 batch 内部:L1 放不下单 batch 完整的 M、N 维输入(K 维允许切段放入,kL1<K)时,核内切 M 会在 K 循环中重复读 B、切 N 会重复读 A。IterBatch 进入与否不由算存比判定——即使 case 是计算 Bound,重复读引入的额外搬移也可能把它重新拖回访存 Bound。所以进入条件直接由 L1 驻留形态刻画:
先明确流水结构:fixpipe 开 unitflag 后,写出由硬件随路完成(每个 16×16×16 fractal 算完即自动搬出),写出侧不需要软件排流水;要掩盖的只有"读入(MTE2: GM→L1)↔ 计算(Cube)"。
结论:(c) 统一了原 (c)/(d)——同一族"一侧驻留 + 对侧切 K",预算按 $b_{core}$ 分档($b_{core}=1$ 全量 L1、$b_{core} \ge 2$ 减半以容纳下一 batch 驻留侧预取);驻留侧超 L1/2 时由 (d) 接管。
(a):每核 1 batch 直接搬入 L1。L1→L0 先看 L0C 能否放下完整单 batch 输出:
if (L0C >= M*N*4B): # L0C 放得下完整输出:不切 M/N,只切 K
BaseM = M; BaseN = N
BaseK = min(align(L0A/M, 16), align(L0B/N, 16))
else: # L0C 放不下:按较小维切
if M < N: BaseM = align(M,16); BaseN = floor(L0C/4B / BaseM)
else: BaseN = align(N,16); BaseM = floor(L0C/4B / BaseN)
BaseK = min(floor_align(L0A/BaseM,16), floor_align(L0B/BaseN,16))
(b):L1 双 batch 乒乓,核内 GM→L1→L0→Cube→L0C→GM/L2 流水;L1→L0 分块同理,但各级预算减半(L0C/L0A/L0B 按 2 份)。
(c):一侧驻留 + 对侧切 K。假设驻留左矩阵,右矩阵搬入的 K 向长度:
$b_{core} \ge 2$ 时另一半 L1 在计算期间预取下一 batch 的驻留侧,实现 batch 间无气泡衔接;fixpipe 开 unitflag。
(d):两侧都切 K 段,$k_{L1}$ 取满足容量与 dValue 的最大值;L0C 按 batch 乒乓(各占 L0C/2),batch 边界由硬件 fixpipe 自动排空、下一 batch 立即在另一半 L0C 累加。
grid_K 取值:P 个输出 tile 各需 grid_K 个核,总核数 ⌈P⌉ × grid_K ≤ C → grid_K = ⌊C/⌈P⌉⌋(最大化 K 并行度)。例:P=10, C=32 → grid_K=⌊32/10⌋=3;P=5, C=32 → grid_K=⌊32/5⌋=6;P=16, C=32 → grid_K=⌊32/16⌋=2。
降核 ASW 的芯片级→核级映射:P < C/2 时 B×M×N 填不满 C 核,以 L0C 满载粒度切为 P 个输出 tile(每 tile 尺寸 $M^t N^t = L0C/4\text{B}$),$\lceil P \rceil$ 个核各处理一个 tile。芯片级总时延 $T_{alt}$ = 单 tile 流水时延 $T_{pipe}$(所有核并行)。$T_{pipe} = \max(T_{MMAD}^t,\,T_{MTE2}^t)$,其中:
StreamK 的芯片级→核级映射:同样 P 个 tile,每 tile 由 $grid_K$ 个核共同完成(各算 $K/grid_K$ 段)。流水时延 $T_{pipe}/grid_K$,归约时延 $T_{Reduce}^t$ 串行追加。芯片级总时延:
收益判据 $T_{SK} < T_{alt}$ ⟺ $T_{Reduce}^t < T_{pipe}\left(1-\dfrac{1}{grid_K}\right)$。等价于 $T_{pipe} > \dfrac{grid_K}{grid_K-1}\cdot T_{Reduce}^t$——α = grid_K/(grid_K-1) 由流水分析导出(grid_K=2 时 α=2),非经验值。
**$T_{Reduce}^t$ 构成**(每 tile,部分和驻留 L2、AIV 归约;$M^t N^t = L0C/4\text{B}$,符号定义见 §二):
K 闭式阈值推导——分两种瓶颈情形:
计算 Bound($T_{pipe} = T_{MMAD}^t$),代入判据:
两边除以 $L0C/4\text{B}$(tile 输出元素数),tile 尺寸消去——K 阈值不依赖 M、N 的具体值:
代入数值($Q_{16}$=15.2 TFLOPS,$W_{L2}$=5.2 TB/s,$Q_{AIV}$≈13.5 Tops/s):
L2 读写(1.54 ps/元素)是主导项,AIV 求和(0.07)仅占 5%。grid_K=2→K>49;4→K>66;8→K>112。
访存 Bound($T_{pipe} = T_{MTE2}^t$),同理($M^t N^t/(M^t+N^t)$ 不消去,但阈值远低于计算 Bound):
访存 Bound 阈值远低于计算 Bound——两情形阈值比值:
$M^t N^t/(M^t+N^t)$ 的范围:tile 面积 $M^t N^t \le L0C/4\text{B} = 65536$ 元素(L0C 容量上限)。由均值不等式 $M^t+N^t \ge 2\sqrt{M^t N^t}$,比值上界为 $\sqrt{M^t N^t}/2 \le \sqrt{65536}/2 = 128$(正方形 tile 取到);极端长宽比($M^t{=}16, N^t{=}4096$)时下界 $\approx 16$。StreamK case 的 tile 通常接近正方形 → 比值 $\sim$ O(64–128),上界 128。无论取何值,$128/304 \approx 0.42 < 1$——访存 Bound 阈值恒低于计算 Bound。直觉:访存 Bound 时 $T_{pipe} = T_{MTE2}^t > T_{MMAD}^t$,瓶颈时延更大,归约预算更充裕。汇总条件取计算 Bound 阈值(保守,同时覆盖两种情形)。
注意 θ_c 对 workspace 落点敏感:部分和落 GM 时读写带宽从 5.2TB/s 降到 ~0.64TB/s,θ_c 升至约 97。设计时应优先保证 workspace 驻留 L2。
源码对照:batch_matmul_v3_basic_streamk_tiling.cpp 中 K 的固定门槛为 CeilAlign(K,256) ≥ max(8192, aicNum×256B/dtype)。
aicNum×256B/dtype = 32×128 = 4096(BF16)= dValue 下限(256B)在最大 grid_K=C 下的保障——对应条件 2;8192 = 32×256 = C×512B(BF16)= dValue 推荐值(512B)在最大 grid_K=C 下的保障——同为条件 2,取推荐值而非下限。max 取更严格的 8192。修正后的归约阈值(θ_c≈12,grid_K=32 时 K>396)远低于 8192,说明 8192 的绑定约束是 dValue(条件 2),不是归约代价(条件 3)。源码不动态计算 grid_K,用固定阈值同时覆盖条件 2 的最保守情形和条件 3,是两条条件的保守合并近似。
核间组织:先按 B/M/N 切出输出块,剩余核预算折成 K 向份数:
mCnt、nCnt 收拢为 blocksPerBatch 的因子(避免碎核尾块);由条件 1 知 $mCnt \cdot nCnt \le blocksPerBatch/2$,故 $grid_K \ge 2$。归约组内核 c 负责 K 段 $[cK/grid_K,\; (c{+}1)K/grid_K)$。
核内流水:对自己的 K 段做标准分块流水(MTE2→L1→L0→mmad),段内多轮在 L0C 原地累加;段完部分和经 Fixpipe 写出。
归约:
参数搜索:$grid_K$ 从 2 起按 2 的幂递增,取同时满足条件 2/3 的最小值;都不满足则退为降核 ASW_Basic。
1、核间切分维度选择(按共享代价从低到高):切 B(零共享,先试)→ 切 M(右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2)→ 切 N(对称)→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见第 6 条)。
2、swizzle:ASW 滑窗蛇形
问题:核间切 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 最快序不反向),正是这个收益结构的直接实现。3、L2 切分(工作集超 L2 时)
切的是什么:L2 切分切的是输出平面——把 M×N 平面切成 mL2TileNum × nL2TileNum 个大矩形块(每块 = 若干基本块的集合),使"该块所需的 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$(输入本身超)**。必须 L2 切分。输出直写 GM 以最大化 $L2_{read}$,切分数满足
每块输入工作集 ≤ $L2_{read}$,逐块计算——块内滑窗复用充分,块间只发生一次性换入。块内分配用错位分核(对角线分配):线性块号先取 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 的方向。例:B=64、M=N=2048、K=1024、BF16——$S_{in}$≈537MB > L2,输出直写($L2_{read}$=128MB),切 3×2=6 块(每块输入约 89MB ≤ 128MB);总流量约 1.07GB,T_MMAD≈1.27ms,速率 844GB/s < 1.6TB/s ✓。
补充:若输出会被后续算子立即消费(融合场景),输出驻留 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 时延的瓶颈所在,也是"时延绝对值小、不再继续优化"的定量注脚。
C = A ⊙ B,无累加深度,Cube 的 16×16×16 粒度浪费 15/16,走 AIV 向量通路(GM→UB→Mul→GM)优于 Cube 通路。触发需 $B \ge 2 \times 64$(AIV 核数×2,开 UB 乒乓)且单 batch 输入输出能驻留 UB。采样方式:B ∈ [1, 2048] 全量遍历(2048 个值),M/N/K ∈ [1, 10240] 对数网格采样 60 点/维(值按指数增长:1, 2, 3, 5, 7, 10, 14, 19, 26, 35, ...)。总 case 数 3.2 亿,耗时约 4 分钟。
为什么不做全量遍历:全量 = 2048 × 10240³ ≈ 2.2×10¹⁵ 个 case。Python 分类器约需 1400 万小时,C 实现约需 6 万小时——完全不可行。对数采样在小值区密集、大值区稀疏,恰好覆盖了分支边界集中的区域。
分布依赖测度:对数均匀采样和线性均匀采样给出的分支占比不同。对数采样在小值区密集,特殊分支(K=0/1)、降核 ASW(P 小)、StreamK(K 大但 M/N 小)的占比被放大;线性采样被大 shape 主导(M/N/K > 512 占 [1,10240] 的 95%+),ASW_Basic 占比显著升高。本文遍历的目的是验证覆盖性(无空洞),不是统计真实工作负载的分布。
| 分支 | case 数 | 占比 | B 范围 | 区域特征 |
|---|---|---|---|---|
| ASW_Basic | 1.75 亿 | 54.1% | 2 ~ 2048 | 通用:B<C 且 P≥C;或 B≥C 但 L1 四形态不满足(M/N 大) |
| MergeBatch | 6303 万 | 19.5% | 97 ~ 2048 | $b_{core}\ge 4$ 且 $MN \le 8192$ 等五条全过 |
| 降核 ASW_Basic | 4051 万 | 12.6% | 2 ~ 2048 | P<C 且 K 不满足 StreamK 阈值 → 只用 ⌈P⌉ 核 |
| IterBatch | 3804 万 | 11.8% | 32 ~ 2048 | B≥C、负载均衡、L1 四形态之一满足 |
| 特殊分支 | 597 万 | 1.9% | 任意 | K=0 / K=1 |
| StreamK | 25 万 | 0.08% | 2 ~ 128 | P<C/2 且 K≥8192 |
| 转Matmul | 15 万 | 0.05% | B=1 | 单边 batch=1 |
对照:线性等距采样(M/N/K 步长 256,B 全量,共 1.3 亿 case)下 ASW_Basic 占比升至 92.1%,MergeBatch 降至 3.6%——因为线性采样被大 shape 主导。两种测度的结论一致:无空分支、无覆盖空洞,只是占比不同。
| 形态 | 命中数 | 占比 | 说明 |
|---|---|---|---|
| b) 双 batch 乒乓 | 2684 万 | 70.5% | 最多:B 大且单 batch 较小 |
| d) 两侧切 K | 741 万 | 19.5% | 次之:K 可切段的通用兜底 |
| c) 一侧驻留+对侧切 K | 370 万 | 9.7% | 单侧可驻留(含 b_core≥2 的半预算预取档) |
| a) 单 batch 全驻留 | 9 万 | 0.2% | B=C 附近的窄区 |
| B | M | N | K | 分支 | 说明 |
|---|---|---|---|---|---|
| 1 | 2048 | 2048 | 2048 | 转Matmul | 单 batch 纯 Matmul |
| 128 | 64 | 64 | 512 | MergeBatch | 五条全过:$b_{core}$=4,MN=4096≤8192,单核搬移 512KB≥480KB,AI=64<304 |
| 128 | 64 | 64 | 256 | IterBatch | 与上行仅 K 不同:单核搬移 256KB < 480KB,条件 3 不满足 → 落 IterBatch(形态 b) |
| 512 | 128 | 128 | 128 | IterBatch | MN=16384 > 8192,MergeBatch 条件 2 不满足 → 落 IterBatch |
| 32 | 4096 | 4096 | 4096 | ASW_Basic | L1 四形态均不满足(M/N 太大),切 M/N |
| 2 | 8192 | 8192 | 1024 | ASW_Basic | B<C,P=2048 ≥ 32 |
| 16 | 256 | 256 | 128 | 降核 ASW | P=4 < 32 且 K=128 不满足 StreamK → 用 4 核,其余闲置 |
| 4 | 128 | 128 | 10240 | StreamK | P=0.25 < 32,K≥8192 |
| 2048 | 1024 | 1024 | 512 | IterBatch | 大 batch,形态 b |
| 8 | 512 | 512 | 512 | ASW_Basic | B<C,P=32 恰好满核 |
| 64 | 64 | 64 | 8192 | IterBatch | 小 M×N 但 K 大,形态 d |