# BMM 算子优化分析(v0.98) > 目标芯片:昇腾 950PR(DAV_3510)。所有分支进入条件只含 case 形状参数(B、M、N、K、dtype)与芯片规格参数。 --- ## 一、算子功能与接口说明 完成带 batch 的矩阵乘:`C = A @ B + bias`。 * 左矩阵 A:`[BatchA, M, K]`,dtype,典型 ND,可带转置 * 右矩阵 B:`[BatchB, K, N]`,dtype,典型 ND,可带转置 * 偏置 bias:`[B, 1, N]`,固定 ND,可为空 * 输出 C:`[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 芯片数值可能略有差异,换芯片时逻辑结构不变、只换常数表。 --- ## 三、最优实现分析 ### 3.1 性能模型 BMM 的执行是核内多级硬件流水的并行——Cube 计算(MMAD)、GM/L2→L1(MTE2)、L1→L0(MTE1)、L0C 写出(Fixpipe),各流水级时延可被双缓冲相互掩盖: $$ T_{total} = \max\big(T_{MMAD},\; T_{MTE2},\; T_{MTE1},\; T_{Fixpipe}\;[,\;T_{Reduce}]\big) $$ **总时延 = 最慢一级流水**,优化的关键是对瓶颈级的优化。由此得到设计自由度——**瓶颈交换**:搬移是瓶颈时可牺牲算力(冗余计算)换搬移效率;计算是瓶颈时可牺牲搬移(重复读取)换计算效率。MergeBatch 是前者的典型,ASW_Basic 切 M/N 是后者的典型。 case 固有算存比与 16bit 位宽平衡点: $$ AI = \frac{2MN}{M+N},\qquad AI_{full} = \frac{2MNK}{MK+KN+MN} $$ $$ R_{16} = \frac{\text{Cube 峰值算力}}{\text{GM 带宽} / \text{元素字节数}} = \frac{486\ \text{TFLOPS}}{1.6\ \text{TB/s} \,/\, 2\ \text{B}} \approx 607.5\ \text{FLOP/元素} $$ 其中 486 TFLOPS 是乘加各计一次后的标称算力;分母中的 2B 是 16bit 元素字节数,作用是把 GM 带宽折算成元素速率。 $AI < R_{16}$ → 访存 Bound(瓶颈在 MTE2);反之计算 Bound(瓶颈在 MMAD)。 ### 3.2 实现本质逻辑 BMM 的实现本质是:把数据分块(tile),由全部 AIC 核并行 + 串行完成这些分块的计算,再组合成最终结果: $$ C[B,M,N] = \Big\{\,C[B_u, M_i, N_j] = \sum_k A[B_u, M_i, K_k] \cdot B[B_u, K_k, N_j]\,\Big\} $$ 分块有 4 个维度:B、M、N、K。**核间怎么分这 4 个维度,就是分支划分的第一性问题**(核内分块是第二性问题,属于各分支内部 tiling)。 四个维度的核间切分特征(后续一切推导的基石): | 切分维度 | 读入特征 | 计算特征 | 写出特征 | |---|---|---|---| | 切 B | 核间零重复读(每个数据块只被 1 个核读取);**核内是否重复读另有条件**——若 L1 放不下单 batch 完整的 M、N 维输入(K 维可切段放入,kL1 0 $$ **L1 绑定情形 MergeBatch 恒劣于 IterBatch**——合并只放大了 drain 暴露,没有换来搬移命令节省。 **统一分界条件**: $$ \text{MergeBatch 最优} \;\Longleftrightarrow\; k_{L1} = K \;\land\; b_{core} > \frac{b_0 \cdot (T_{comp} + T_{write})}{T_{cmd}} $$ 即 MergeBatch 仅在 **K 截断**($k_{L1} = K$,小 M/N 使 $L1/(2(M{+}N)\cdot\text{dtype}) \ge K$)且 $b_{core}$ 足够大时才优于 IterBatch。小 MN 时 $T_{comp}$ 小且 $k_{L1}$ 大(容易 K 截断)→ MergeBatch 最容易赢;大 B 时 $b_{core}$ 大 → 搬移节省多 → MergeBatch 更容易赢。 **$T_{cmd}$ 的物理成因**:每次 GM→L1 DMA 搬移的固定开销,与搬移数据量无关。从源码可直接观察: - `Nd2NzParams` 描述符配置(ndNum/nValue/dValue/srcStride 等 7 个字段写入 DMA 寄存器) - 地址生成与突发启动 - 与 L1 buffer 的同步握手(`SetFlag` / `WaitFlag`) IterBatch 每 batch 需一次完整配置(`ndNum = curIterBatchL1`,多块独立寻址);MergeBatch 合并后只需一次配置(`ndNum = 1`,单块连续搬移)。**$T_{cmd}$ 就是每次 DMA 命令的描述符配置 + 启动延迟**,量级估计为数十 ns。 **MergeBatch vs IterBatch 优势总结**: | 维度 | IterBatch | MergeBatch | 差异来源 | |---|---|---|---| | 稳态搬移吞吐 | 相同 | 相同 | 总搬移量相同 | | GM→L1 搬移命令数 | $b_{core}$ 次(每 batch 一次) | $b_{core}/b_0$ 次(每合并 batch 一次) | **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_{cmd} - (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 的优势**。 2. **L0C 容量**:合并 $b_0$ 个 batch 的输出块 $[b_0M, b_0N]$(FP32 累加、双缓冲两份)必须放得下 L0C;连最小合并都放不下,合并无从谈起。 3. **单核搬移总量**:单核搬移数据总量低于 min_DatamountPerCore 时,GM 带宽利用率上不去(重要性第 2 位的经验约束)。 4. **搬移 tile 大小**:单 batch 单矩阵的最大连续搬移块须达到 min_TileSize;合并是在此之上进一步放大,不是替代。 5. **访存 Bound**:合并把单次计算的算存比放大 $b_0$ 倍后仍须低于 16bit 对应位宽算存比 $R_{16}$,保证瓶颈留在搬移侧,冗余算力被掩盖而非成为新瓶颈。 ### 实现方案 **Step 1:合并数 b(L0C + 算存比双上限)** $$ b \le \sqrt{\frac{L0C}{2 \cdot MN \cdot 4\text{B}}},\qquad b < \frac{R_{16}(M+N)}{2MN} $$ $$ b_0 = \min\big(\text{两上限},\; b_{core}\big) $$ b₀ 尽量取 $b_{core}$ 的因子(每次合并数均匀,负载与功耗更优)。 **Step 2:L0 级 K 粒度 $k_{L0}$(由 b₀ 和 L0A/L0B 决定)** $$ k_{L0} = \min\Big(\frac{L0A}{2\,b_0 M\cdot\text{dtype}},\; \frac{L0B}{2\,b_0 N\cdot\text{dtype}}\Big)\ \text{向下 16 对齐} $$ 解释:L0A/L0B 各 64KB、双缓冲两份,装入合并后 $b_0 M$ 行($b_0 N$ 列)× $k_{L0}$ 的 fractal。L1→L0 搬移无 dValue 要求(dValue 约束的是 GM→L1 的 $k_{L1}$)。 **Step 3:L1 级 $k_{L1}$、$b_{L1}$** 先令 $b_{L1}^* = b_0$(最低合并数保障),由 L1 容量反推 $k_{L1}^*$: $$ k_{L1}^* = \frac{L1}{2 \cdot b_0 \cdot (M+N) \cdot \text{dtype}} $$ (L1 双缓冲两份,每份驻留 $b_0$ 个 batch 的 $A[b_0 M, k_{L1}^*]+B[k_{L1}^*, b_0 N]$)。 然后取 $k_{L1} = \min(k_{L1}^*,\; K,\; 512\text{B}/\text{dtype})$——不超过 K(K 截断),也不超过 dValue 推荐值 512B(搬移效率拐点,更大不带来额外收益)。 最后由 $k_{L1}$ 和 L1 容量确定 $b_{L1}$(力求尽量大,提升 batch 间流水深度): $$ b_{L1} = \min\Big(\frac{L1}{2 \cdot k_{L1} \cdot (M+N) \cdot \text{dtype}},\; b_{core}\Big) $$ $b_{L1} \ge b_0$ 保证合并不断供。若 $k_{L1}^* > 512\text{B}/\text{dtype}$(L1 充裕),$k_{L1}$ 被 512B 截断,省出的 L1 空间可容纳更多 batch($b_{L1} > b_0$),提升 batch 间流水掩盖能力。 --- ## 六、IterBatch 分支 核间切 B(每核 $b_{core} \ge 1$ 个 batch,核间无同步);核内逐个 batch 做标准 Matmul 分块计算。无算力浪费、无跨 batch 依赖,是"切 B"最朴素的形态。 ### 进入分支条件(汇总) 同时满足: 1. $BatchA = BatchB \;\land\; b_{core} = \lceil B/C \rceil \ge 1$ 2. $B \bmod C = 0 \;\lor\; B \bmod C \ge minCoreNum$ 3. L1 容量约束,四选一(Step 为 >1 的整数): * a) $b_{core}=1 \;\land\; (MK+KN)\cdot\text{dtype} \le L1$ * b) $b_{core}>1 \;\land\; 2(MK+KN)\cdot\text{dtype} \le L1$ * c) $\big(MK + 2\cdot\tfrac{KN}{Step}\big)\cdot\text{dtype} \le \dfrac{L1}{\min(b_{core},\,2)} \;\;\lor\;\; \big(KN + 2\cdot\tfrac{MK}{Step}\big)\cdot\text{dtype} \le \dfrac{L1}{\min(b_{core},\,2)}$ * d) $2\cdot\tfrac{K}{Step}(M+N)\cdot\text{dtype} \le L1$ 4. c/d 切分后:搬移分块 $\ge min\_TileSize \;\land\; dValue \ge 128\text{B}$ ### 逐条解释 1. **batch 关系与每核份额**:无广播;每核至少 1 个 batch。 2. **负载均衡**:切 B 核间零共享零依赖,唯一系统性风险是负载不均。整除时完全均衡;不整除时尾波活跃核数须 ≥ minCoreNum,保证尾波仍有足够核并发搬移(重要性第 1 位的约束是核数)。 3. **L1 容量——核心要求:单 batch 计算不重复读**。 重复读发生在**单 batch 内部**:L1 放不下单 batch 完整的 M、N 维输入(K 维允许切段放入,kL1= 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 向长度: $$ k_{L1\_b} = \min\Big(\frac{L1_{budget} - MK\cdot\text{dtype}}{N\cdot\text{dtype}},\; K\Big),\qquad L1_{budget} = \frac{L1}{\min(b_{core},\,2)} $$ $b_{core} \ge 2$ 时另一半 L1 在计算期间预取下一 batch 的驻留侧,实现 batch 间无气泡衔接;fixpipe 开 unitflag。 **(d)**:两侧都切 K 段,$k_{L1}$ 取满足容量与 dValue 的最大值;L0C 按 batch 乒乓(各占 L0C/2),batch 边界由硬件 fixpipe 自动排空、下一 batch 立即在另一半 L0C 累加。 --- ## 七、StreamK 分支 ### 进入分支条件(汇总) 1. $P = \dfrac{B \cdot MN \cdot 4\text{B}}{L0C} \le \dfrac{C}{2}$ 2. $\dfrac{K}{grid_K} \ge \dfrac{256\text{B}}{\text{dtype}}$,其中 $grid_K = \Big\lfloor \dfrac{C}{\lceil P \rceil} \Big\rfloor$ 3. $K > \dfrac{grid_K^{\,2}}{grid_K-1}\cdot\theta_c$,$\theta_c = \dfrac{Q_{16}}{2}\Big(\dfrac{8\text{B}}{W_{L2}}+\dfrac{1}{Q_{AIV}}\Big) \approx 12$ 4. 工程约束:确定性等级 ≤ 1(核间归约顺序不定);ND 格式 ### 逐条解释 1. **并行缺口**:P 以"L0C 满载的输出基本块"为粒度估计不切 K 的最大并行度——基本块按 L0C 最大利用率取($M^t N^t \cdot 4\text{B} = L0C$),免去预先估计 M/N 具体切分。**阈值取 C/2 而非 C**:StreamK 的定义就是 grid_K ≥ 2(至少 2 路切 K),且同一 batch 内所有输出块共享同一个 grid_K;grid_K=2 时每块需要 2 个核,总核数需求 = ⌈P⌉ × 2 ≤ C,即 P ≤ C/2(等号成立时 grid_K=2 恰好填满 C 核)。若 P > C/2,切 2 路就超核数(2⌈P⌉ > C),不切又浪费核——由降核 ASW_Basic 承接更合适。P ≤ C/2 意味着不切 K 时至多一半核有事做,K 是唯一剩余的并行维度。 2. **单核 K 段下限**:每核 K 段内轴连续长度不小于 dValue 推荐值 256B(BF16 为 128 元素),保证段内搬移效率不崩。 **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。 3. **归约代价可接受**。StreamK 切 K 引入归约串行尾,收益判据——切 K 后芯片级总时延须小于不切 K(降核 ASW)。 **降核 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)$,其中: $$ T_{MMAD}^t = \frac{2 M^t N^t K}{Q_{16}} = \frac{2K}{Q_{16}}\cdot\frac{L0C}{4\text{B}},\qquad T_{MTE2}^t = \frac{K(M^t+N^t)\cdot\text{dtype}}{BW_{pc}} $$ **StreamK 的芯片级→核级映射**:同样 P 个 tile,每 tile 由 $grid_K$ 个核共同完成(各算 $K/grid_K$ 段)。流水时延 $T_{pipe}/grid_K$,归约时延 $T_{Reduce}^t$ 串行追加。芯片级总时延: $$ T_{SK} = \frac{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}$,符号定义见 §二): $$ T_{Reduce}^t = \underbrace{\frac{grid_K \cdot M^t N^t \cdot 4\text{B}}{W_{L2}}}_{\text{AIC 写部分和}} + \underbrace{\frac{grid_K \cdot M^t N^t \cdot 4\text{B}}{W_{L2}}}_{\text{AIV 读回}} + \underbrace{\frac{grid_K \cdot M^t N^t}{Q_{AIV}}}_{\text{AIV 求和}} + \underbrace{\frac{M^t N^t \cdot outB}{W_{L2}}}_{\text{写回}} $$ **K 闭式阈值推导**——分两种瓶颈情形: **计算 Bound**($T_{pipe} = T_{MMAD}^t$),代入判据: $$ \frac{2K}{Q_{16}}\cdot\frac{L0C}{4\text{B}}\cdot\frac{grid_K-1}{grid_K} > grid_K\cdot\frac{L0C}{4\text{B}}\Big(\frac{8\text{B}}{W_{L2}}+\frac{1}{Q_{AIV}}\Big) + \frac{L0C}{4\text{B}}\cdot\frac{outB}{W_{L2}} $$ 两边除以 $L0C/4\text{B}$(tile 输出元素数),**tile 尺寸消去**——K 阈值不依赖 M、N 的具体值: $$ K > \frac{grid_K^2}{grid_K-1}\cdot\theta_c,\qquad \theta_c = \frac{Q_{16}}{2}\Big(\frac{8\text{B}}{W_{L2}}+\frac{1}{Q_{AIV}}\Big) $$ 代入数值($Q_{16}$=15.2 TFLOPS,$W_{L2}$=5.2 TB/s,$Q_{AIV}$≈13.5 Tops/s): $$ \theta_c = \frac{15.2\times10^{12}}{2}\Big(\underbrace{\frac{8}{5.2\times10^{12}}}_{1.54\,\text{ps/元素}} + \underbrace{\frac{1}{13.5\times10^{12}}}_{0.07\,\text{ps/元素}}\Big) = 7.6\times10^{12} \times 1.61\times10^{-12} \approx 12 $$ 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): $$ K > \frac{grid_K^2}{grid_K-1}\cdot\frac{M^t N^t}{M^t+N^t}\cdot\theta_m,\qquad \theta_m = \frac{BW_{pc}}{\text{dtype}}\Big(\frac{8\text{B}}{W_{L2}}+\frac{1}{Q_{AIV}}\Big) \approx 0.04 $$ **访存 Bound 阈值远低于计算 Bound**——两情形阈值比值: $$ \frac{\theta_m \cdot M^t N^t/(M^t+N^t)}{\theta_c} = \frac{2\cdot BW_{pc}}{Q_{16}\cdot\text{dtype}}\cdot\frac{M^t N^t}{M^t+N^t} = \frac{M^t N^t/(M^t+N^t)}{304} $$ $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。 4. **工程约束**:归约顺序不定引入浮点非确定性,确定性等级 2/3 的业务禁用。 **源码对照**:`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 向份数: $$ blocksPerBatch = \Big\lfloor \frac{C}{B} \Big\rfloor,\qquad grid_K = \frac{blocksPerBatch}{mCnt \cdot nCnt} $$ 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$ 个核的部分和经归约(Fixpipe 模式或 AIV)求和为最终结果。 --- ## 八、ASW_Basic 分支 ASW_Basic 是 BMM 的**兜底分支**——核间切 M/N(或混合切),不做 batch 合并或 K 维切分。它是实践中最常命中的分支:B 可大可小可等 1,交叉广播也由此承接。 ### 进入分支条件(汇总) 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 的时延绝对值小,继续切分引入的调度与搬移效率损失大于并行收益——降核是理性选择而非偷懒。 ### 时延建模与核间分配策略 ASW_Basic 核间切 M/N(不切 K),每核处理若干 [singleCoreM, singleCoreN] 输出块。端到端时延: $$ T = \max(T_{MTE2},\; T_{MMAD},\; T_{FIX}) + T_{drain} $$ 其中 $T_{MMAD} = 2BMNK/(C \cdot Q_{16})$ 和 $T_{FIX} = B \cdot MN \cdot outB/(C \cdot W_{pc})$ 与分配策略无关——**分配策略只影响 $T_{MTE2}$**(通过 L2 命中率影响有效 GM 带宽)。 $T_{MTE2}$ 展开: $$ T_{MTE2} = \frac{S_{in}^{GM}}{C \cdot BW_{pc}},\qquad S_{in}^{GM} = r_{in} \cdot S_{in} $$ $r_{in}$ 为重复读倍率(GM 输入流量 / 输入总量 $S_{in} = B(MK{+}KN) \cdot dtype$),$r_{in} \ge 1$,下界 $r_{in} = 1$ 表示每字节只从 GM 读一次(后续复用全命中 L2)。**分配策略的优化目标就是让 $r_{in}$ 尽量接近 1。** #### B 优先分组 vs B→M→N 线性映射 两种候选分配策略: - **B 优先分组**:C 核分为 B 组,每组 $C_g = C/B$ 核独立处理一个 batch 的 $mCnt \times nCnt$ 个块,组内做 swizzle - **线性映射**:块按 (b, m, n) 字典序编号,核 i 处理块 i, i+C, i+2C, …,所有核在同一 batch 内做 swizzle *L2 工作集对比*(每波活跃核所需的 A 行带 + B 列带): | 维度 | 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 | 组间核数不等 → 负载不均 | 无影响(总块数任意) | ($sM$ = singleCoreM,$sN$ = singleCoreN,dtype = 输入元素字节数) *数值例*(C=32、B=4、sM=sN=256、K=1024、BF16):分组 $C_g=8$、$W_g=2$,总工作集 = $4 \times (2 \times 256 + 4 \times 256) \times 1024 \times 2 = 12\text{MB}$;线性 $W=4$,工作集 = $(4 \times 256 + 8 \times 256) \times 1024 \times 2 = 6\text{MB}$——**线性映射的工作集是分组方案的一半**。 *为什么线性映射工作集更小*: 1. **更多核参与同一 batch 的 swizzle** → $W = \sqrt{C} > W_g = \sqrt{C/B}$ → 工作集更接近"方形"下限(均值不等式:$W + C/W$ 在 $W = \sqrt{C}$ 处最小); 2. **同一时刻只有 1 个 batch 的数据活跃** → L2 只需缓存 1 份 A/B 数据,而非 B 份。 #### 分场景严格比较 **场景 I:$mCnt \cdot nCnt \ge C$ 且单波工作集 ≤ L2**(最常见) 线性映射:每波 C 个块在同一 batch 内,L2 命中率高。B 分组:B 个 batch 同时活跃,总工作集 $B$ 倍。**线性严格更优。** **场景 II:$mCnt \cdot nCnt \ge C$ 且 $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$ 个核,总算力浪费 $B \cdot (C_g - mCnt \cdot nCnt) = C - B \cdot mCnt \cdot nCnt$ 个核。线性映射无此问题。**线性严格更优。** #### 结论 **B 优先分组在任何场景下都不优于线性映射。** 根本原因是: 1. L2 是共享 Cache,工作集越小命中率越高——线性映射同一时刻只激活 1 个 batch 的数据,工作集最小; 2. swizzle 窗口 $W \propto \sqrt{\text{参与核数}}$——更多核参与同一 batch 的 swizzle,窗口更大,工作集更方形; 3. 线性映射对 B 与 C 的关系无要求,B 不整除 C 时无负载不均。 因此 ASW_Basic 采用 **B→M→N 线性映射**作为核间分配策略。 ### 实现方案 **Step 0:BaseM / BaseN 的确定**(L0 级 tile,先把 L0C 用满) L0C 是 Cube 的累加器,BaseM × BaseN 是每次 Cube 计算的输出 tile。BaseM/N 应**尽量把 L0C 用满**——L0C 利用率越高,每次 Cube 计算的输出越大,单位计算的启动/排空开销摊得越薄: $$ \text{BaseM} \times \text{BaseN} = \frac{L0C}{2 \times 4\text{B}} = 32768 \text{ 元素} $$ (双缓冲两份,FP32 4B/元素)。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 容量约束)。 **Step 1:SingleCoreM / SingleCoreN 的确定**(每核输出 tile,$\ge$ 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 核。 $$ mCnt \times 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——搬移效率**: $$ \text{singleCoreM} \cdot k_{L1} \cdot \text{dtype} \ge min\_TileSize,\qquad k_{L1} \cdot \text{singleCoreN} \cdot \text{dtype} \ge min\_TileSize $$ **约束 4——SingleCoreM/N 是 BaseM/N 的整数倍**(工程实现要求,保证 L0 tile 边界对齐)。 *选取策略*:在满足约束 1 的前提下,SingleCoreM/N 尽量大(搬移效率和 L2 复用最大化)。长宽比跟随 M/N($\text{singleCoreM}/\text{singleCoreN} \approx M/N$),对齐到 BaseM/BaseN 的整数倍。 *例*(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。约束 2:$k_{L1} \le 512\text{KB}/(2 \times 1760 \times 2\text{B}) = 74$ → 取 64(16 对齐)= 128B ✓。 **Step 2: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。 **Step 3:核间切分维度选择(按共享代价从低到高)**:切 B(零共享,先试)→ 切 M(右矩阵 $KN\cdot\text{dtype} \le L2$ 则驻留 L2)→ 切 N(对称)→ 混合切(靠 swizzle + L2 切分管理)→ 降核(见 Step 8)。 **Step 4: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/W$ 在 $W = \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=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)"的说明):蛇形的收益来自"相邻遍历段共享边界数据",要分两种边界看: * **窗内列间边界**(ν0→ν1):相邻两段共享的是同一组 A 行块(W 个),它们在整个窗口期间**全程驻留 L2**,无论按什么顺序扫,工作集不变——窗内蛇形零收益; * **窗口行边界**(窗口0→窗口1):A 行整体换血(μ0..3 → μ4..7),此时 B 列带的连续性决定换血成本——不蛇形则下一窗口从 ν0 开始(LRU 上最久未用、早已被挤出 L2 的冷带),蛇形则延续上一窗口末尾的 ν7(最热线带)。**蛇形只标在窗口行号上**(源码 `BatchMatMulAswBlock::UpdateBasicIndex`:仅 `rowIdx` 为奇时 n 反向,窗内 m 最快序不反向),正是这个收益结构的直接实现。 **Step 5: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$。 **两个不变量**(一切分析的起点): * 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 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**。理由链: 1. $S_{in} \le L2$ ⇒ 全部输入可驻留 L2,跨波次复用全部命中 ⇒ $r_{in} = 1$(GM 输入流量达到下界 $S_{in}$); 2. 输出在本算子内只写不读、零复用收益;若输出也驻留 L2(dirty),超出 L2 的部分会把输入挤出——被挤出的输入后续得回 GM 重读 ⇒ $r_{in} > 1$,GM 流量超出下界; 3. 故让输出直写 GM(fixpipe 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 直写 GM;T_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 Hint**(`SetL2CacheHint`):标记输入为 allocate(读入 L2)或 non-allocate(直读 GM 不过 L2);标记输出为 non-allocate(直写 GM 不占 L2); - **CMO**(Cache Maintenance Operation):Prefetch/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 次。 块内分配用**错位分核**(对角线分配):线性块号先取 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) ``` 冲突度量与优选规则: $$ 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 的策略反过来;本文按单算子边界分析。 **Step 6:核内 tiling**(BaseM/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。 **Step 7:内部特化(参数极限,不是独立分支)**:单边无 batch 且该侧矩阵小($M \le 256$、$MK\cdot\text{dtype}\cdot 2 \le L1$、对侧每核循环 ≥4 轮)时小侧整个常驻 L1、只搬一次(L1 全载)。 **Step 8:降核模式实现**:tiling 时 `usedCoreNum = ⌈P⌉`(不强制 C),SingleCoreM/N 在 L0C 容量内取最大($\text{singleCoreM} \times \text{singleCoreN} \times 4\text{B} \le L0C$),每核按标准核内流水(L1→L0→Cube→L0C→Fixpipe)处理自己的输出块;核间无共享无依赖,无需 swizzle 与 L2 切分。降核后 GM 并发搬移核数若 < minCoreNum,带宽利用率上限被压低——这正是降核区 case 时延的瓶颈所在,也是"时延绝对值小、不再继续优化"的定量注脚。 --- ## 九、特殊分支 * **K=0**:无任何计算,C = bias 或 0,纯 AIV 写值; * **K=1**:退化为逐元素乘 `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。 --- ## 十、case 遍历:各分支的覆盖区域 ### 方法论说明 **采样方式**: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 占比显著升高。**本文遍历的目的是验证覆盖性(无空洞),不是统计真实工作负载的分布。** ### 分支覆盖统计(对数采样,B 全量 2048,M/N/K 60 点/维,共 3.2 亿 case) | 分支 | case 数 | 占比 | B 范围 | 区域特征 | |---|---|---|---|---| | ASW_Basic | 1.75 亿 | 54.1% | 2 ~ 2048 | 通用:B 8192,MergeBatch 条件 2 不满足 → 落 IterBatch | | 32 | 4096 | 4096 | 4096 | ASW_Basic | L1 四形态均不满足(M/N 太大),切 M/N | | 2 | 8192 | 8192 | 1024 | ASW_Basic | B