diff --git a/BMM算子优化分析_Release/BMM理论vs源码对比分析_v1.1.html b/BMM算子优化分析_Release/BMM理论vs源码对比分析_v1.1.html new file mode 100644 index 0000000..1247670 --- /dev/null +++ b/BMM算子优化分析_Release/BMM理论vs源码对比分析_v1.1.html @@ -0,0 +1,322 @@ + + +
+ + +基于《BMM 算子优化分析 v0.95》与 ops-nn/matmul/batch_mat_mul_v3 源码(DAV_3510)。
+转Matmul → 特殊分支 → MergeBatch/IterBatch → ASW_Basic → StreamK → 降核ASW
+核心逻辑:按价格从低到高购买并行度——切 B 免费 → 切 M/N 有重复读代价 → 切 K 有归约代价。每个分支有独立的进入条件(B/M/N/K 闭式表达式),case 落入哪个分支由条件判定。
+// batch_matmul_v3_tiling_strategy.h, DAV_3510 优先级
+{K_EQUAL_ZERO(0), TO_MUL(1), STREAM_K(2), MERGE_BATCH(3),
+ ITER_BATCH_BROADCAST(4), ITER_BATCH_BASICAPI(5), ITER_BATCH(6),
+ AL1_FULL_LOAD(7), BL1_FULL_LOAD(8), ASW_BASIC(9), BASE(999)}
+优先级从高到低逐个尝试 IsCapable(),第一个返回 true 的分支胜出。
+| 维度 | 理论 | 源码 |
|---|---|---|
| 选择方式 | 各分支独立条件判定 | 优先级有序匹配,first-match |
| StreamK 位置 | 最后检查(P<C/2 才考虑) | 优先级 3(K=0/K=1 之后立即检查) |
| 转Matmul | 独立分支(BatchA=1∨BatchB=1) | 无独立分支,由广播/ASW 承接 |
| 降核 ASW | 独立分支(P<C 且不满足 StreamK) | 无独立分支,ASW_Basic 内部处理 |
源码优先级顺序的风险:StreamK 优先级高于 MergeBatch/IterBatch。若一个 case 同时满足 StreamK 和 IterBatch 的条件(B>C 且 K≥8192),源码会先进入 StreamK——但理论上 B>C 时切 B 免费,不应优先切 K。不过 StreamK 的 IsCapable 条件(batchC×mCnt×nCnt ≤ aicNum/2)实际上排除了 B>C 的 case,所以优先级顺序在实际中不会造成误判。
理论条件(v0.95 §九):
+源码实现:
+BATCH_MATMUL_INPUT_K_EQUAL_ZERO(优先级 0):
// batch_matmul_v3_k_equal_zero_tiling.cpp IsCapable()
+bool IsCapable() {
+ if (aFormat == NZ || bFormat == NZ) return false; // 不支持 NZ
+ if (hasBias) return false; // 不支持 bias
+ if (kValue != 0) return false; // K=0
+ return true;
+}
+// DoOpTiling: usedCoreNum = aivNum(用 AIV 核)
+BATCH_MATMUL_TO_MUL(优先级 1):K=1 时退化为逐元素乘,用 AIV 核。
对比结论:理论与源码一致。源码实现正确——K=0 和 K=1 都用 AIV 通路,不用 Cube。理论中的"B ≥ 2×64"条件在源码中未显式检查,而是通过 UB 容量隐式限制(ubLimitBatchNum = ubSize / singleBatchSize)。
差异:理论要求"单 batch 输入输出驻留 UB"作为进入条件;源码通过 batchNum = min(singleCoreBatch, ubLimitBatchNum) 自动处理 UB 放不下的情况(分批处理),无需在进入条件中排除。源码更通用,理论更保守。
理论条件(v0.95 §五):
+源码条件(batch_matmul_v3_mergebatch_basicapi_tiling.cpp IsCapable):
bool IsCapable() {
+ if (非连续转置) return false;
+ if (NZ格式) return false;
+ if (hasBias || (FP32 && !HF32)) return false;
+ if (BatchA != BatchB) return false; // 对应理论条件1
+ if (batchC < MIN_BATCH_L0 * aicNum) return false; // B < 128,即 b_core < 4
+ if (alignK < 64 || M > N) return false; // K≥64,M≤N
+ // L0 buffer check with b0=MIN_BATCH_L0=4
+ if (al0Size > L0A || bl0Size > L0B ||
+ tempAlignM * tempAlignN * 4B * 2 > L0C) return false; // 对应理论条件2
+ return true;
+}
+逐条对比:
+| 理论条件 | 源码对应 | 差异分析 |
|---|---|---|
| 1. b_core ≥ 2b₀ | batchC ≥ 128(b_core ≥ 4) | 理论 b₀=2 → b_core ≥ 4;源码 MIN_BATCH_L0=4 → batchC ≥ 128 = b_core ≥ 4。一致(b₀=2 时 b_core ≥ 4 与源码等价) |
| 2. L0C 容量 | L0 buffer check | 理论:2(b₀M)(b₀N)·4B ≤ L0C;源码:tempAlignM × tempAlignN × 4B × 2 ≤ L0C。一致(tempAlignM = b₀×M 对齐) |
| 3. 单核搬移量 ≥ 480KB | 无 | 源码缺少此条件——可能导致小数据量 case 进入 MergeBatch 后 GM 带宽利用率不足 |
| 4. 搬移 tile ≥ 16KB | 无 | 源码缺少此条件——小 tile 时搬移效率无保障 |
| 5. AI < R₁₆/b₀ | 无 | 源码缺少算存比约束——计算 Bound 的 case 进入 MergeBatch 后冗余计算成为瓶颈 |
源码额外条件:alignK ≥ 64(K ≥ 64)和 M ≤ N。理论无此限制。
+Tiling 参数对比:
+| 参数 | 理论 | 源码 |
|---|---|---|
| 合并数 b | b = min(L0C上限, R₁₆上限, b_core) | mergeBatchL0 = min(多项式求解, maxBatchL0, batchNumPerCore) |
| L0 K 粒度 | kL0 = min(L0A/2bM, L0B/2bN) | baseK = min(maxBasek, 64) |
| L1 K 粒度 | kL1 ≥ max(kL0, 256B/dtype) | stepKa × baseK,由 L1/4 容量决定 |
关键差异:源码 baseK ≤ 64(硬编码上限),理论无此限制。64 元素 = 128B BF16 = dValue 下限。这是保守的安全下限。
MergeBatch vs IterBatch 分界:
+结论:源码的 MergeBatch 进入条件偏宽——缺少单核搬移量、tile 大小、算存比三条约束。这导致一些本应走 IterBatch 的 case(如单核搬移量不足、计算 Bound)被错误分配到 MergeBatch。建议补充条件 3/4/5。
+理论条件(v0.95 §六)——四种 L1 驻留形态:
+源码条件(batch_matmul_v3_iterbatch_basicapi_tiling.cpp IsCapable):
bool IsCapable() {
+ if (非连续转置A) return false;
+ if (NZ格式) return false;
+ if (batchBias > 1) return false;
+ if (BatchA != BatchB) return false;
+ if (batchC <= aicNum) return false; // B ≤ 32 不进 IterBatch
+
+ // L1 双缓冲容量检查(形态 b 等价)
+ if ((alignM*alignK + alignK*alignN)*dtype*2 > L1) return false;
+
+ // L0 能放至少 1 个 batch
+ if (!l0CanLoadBatch_) {
+ // 负载均衡检查:balanceRate ≥ 0.8
+ if (balanceRateOfBatch < 0.8) return false;
+ }
+ return true;
+}
+逐条对比:
+| 理论形态 | 源码对应 | 差异分析 |
|---|---|---|
| a) 单 batch 全驻留 | 无独立分支 | 源码不区分 a/b——只要 (MK+KN)×dtype×2 ≤ L1 就进 IterBatch。b_core=1 的 case 被 B>32 条件排除(B≤32 时 batchC≤aicNum,不进 IterBatch) |
| b) 双 batch 乒乓 | ✓ 直接对应 | 源码的 L1 检查就是形态 b 的条件 |
| c) 一侧驻留+对侧切 K | 无 | 源码不实现形态 c——当 (MK+KN)×dtype×2 > L1 时直接退出 IterBatch |
| d) 两侧都切 K | 无 | 源码不实现形态 d |
关键差异:
+batchC <= aicNum 排除了 B ≤ C 的 case。理论上 b_core=1 时形态 a/c 仍可行,但源码将这些 case 交给 ASW/AL1/BL1 分支。balanceRateOfBatch < 0.8 检查(对应理论条件 2 的 B mod C ≥ minCoreNum),但实现方式不同——源码用实际最大 batch 数与平均值的比率,理论用尾波核数。Tiling 参数对比:
+| 参数 | 理论 | 源码 |
|---|---|---|
| iterBatchL1(L1 级 batch 数) | 由 L1 容量决定 | min(L1容量/batch, mmadCount=8, ceil(B/C)) |
| iterBatchL0(L0 级 batch 数) | 由 L0 容量决定 | min(L0A/L0B/L0C容量, iterBatchL1) |
| baseM/baseN/baseK | 由 L0 容量和 dValue 决定 | 由 MatMulV3TilingHelper::ResetBase 统一计算 |
结论:源码的 IterBatch 只覆盖形态 b,且要求 B > C。形态 a/c/d 的缺失意味着:
+理论条件(v0.95 §七):
+源码条件(batch_matmul_v3_basic_streamk_tiling.cpp):
bool IsCapable() {
+ if (deterministicLevel > 1) return false; // 确定性
+ if (BatchA != BatchB) return false; // batch 一致
+ if (FP32 && K > 2000000) return false; // FP32 精度
+
+ // K 阈值:CeilAlign(K,256) ≥ max(8192, aicNum×256B/dtype)
+ if (CeilAlign(K,256) < max(8192, 32×256/2)) return false;
+ // 即 K ≥ 8192(BF16)
+
+ // 并行缺口:batchC × mCnt × nCnt ≤ aicNum/2
+ mCnt = ceil(M/256); nCnt = ceil(N/256); // 256B 粒度
+ if (batchC * mCnt * nCnt > aicNum/2) return false; // B·⌈M/256⌉·⌈N/256⌉ ≤ 16
+ return true;
+}
+逐条对比:
+| 理论条件 | 源码对应 | 差异分析 |
|---|---|---|
| 1. P ≤ C/2 | B·⌈M/256⌉·⌈N/256⌉ ≤ C/2 | 粒度不同:理论用 L0C 满载粒度(M^t·N^t = L0C/4B = 65536 元素),源码用固定 256 元素粒度。理论粒度更粗(65536 vs 65536=256²),实际上一致——⌈M/256⌉·⌈N/256⌉ 就是 L0C 满载 tile 的个数 |
| 2. K/grid_K ≥ 128 元素 | K ≥ 8192(固定阈值) | 源码用固定 8192 = C×256 元素 = dValue 推荐值在最大 grid_K=C 下的保障。理论更精确(动态 grid_K),源码更保守(固定最大 grid_K) |
| 3. K > grid_K²/(grid_K-1)·θ_c | 无 | 源码不归约代价建模,直接用固定 K 阈值覆盖。θ_c≈12 时 grid_K=2 只需 K>49,远低于 8192——源码的条件 3 被条件 2 覆盖,过于保守 |
| 4. 确定性 ≤ 1 | deterministicLevel > 1 排除 | 一致 |
关键差异:
+结论:源码的 StreamK 实现基本合理但过于保守。固定 K≥8192 阈值排除了大量理论上可走 StreamK 的 case(K 在 49~8192 之间)。建议改为动态计算 grid_K 和 K 阈值。
+理论条件(v0.95 §八):
+源码条件(batch_matmul_v3_asw_basic_tiling.cpp IsCapable):
bool IsCapable() {
+ if (A/B 非连续转置不匹配) return false;
+ if (BatchA != BatchB) return false;
+ if (batchBias > 1) return false;
+ if (!FP16/BF16) return false;
+ return true; // 兜底
+}
+进入条件对比与 v1.0 一致——源码兜底定位与理论一致。以下重点对比 tiling 参数确定和 swizzle/L2 管理的实现差异。
+| 维度 | 理论(v0.95) | 源码 |
|---|---|---|
| 目标 | BaseM × BaseN = L0C/(2×4B) = 32768,先把 L0C 用满 | baseM=256, baseN=256(硬编码 "256 is better base") |
| 长宽比 | 跟随 SingleCoreM/N(进而跟随 M/N) | 由 GetRebalanceBlock 的 cubeBound 模型寻优 |
| baseK | min(L0A/2·BaseM, L0B/2·BaseN),仅受 L0 容量约束 | baseK = 128B/dtype(L0A/L0B 最小高效粒度) |
源码的 cubeBound 模型(GetRebalanceBlock,matmul_v3_tiling_helper.cpp L387-492):
差异分析:理论直接取 L0C 满载(32768 元素),源码用 256×256=65536 元素——源码的 baseM×baseN 是理论上限的 2 倍。这说明源码的 baseM/baseN 不是 L0C 满载的 tile 尺寸,而是 SingleCoreM/N 级别的参数。源码中 L0 级 tile 的实际尺寸由后续 stepM/stepN 切分决定。
+| 维度 | 理论(v0.95) | 源码 |
|---|---|---|
| 约束 | 不受 L0 直接约束,由 GM→L1 效率 + L2 复用决定 | 由 baseM/baseN + stepM/stepN 组合决定 |
| 并行度 | mCnt × nCnt ≥ ⌈C/B⌉ | GetRebalanceBlock 中 balanceRate ≥ 0.9 |
| L1 约束 | 2(SM+SN)·kL1·dtype ≤ L1 | CalL1Tiling 中 (aL1+bL1)×2 ≤ L1 |
关键差异:理论把 SingleCoreM/N 和 BaseM/N 分为两层(SingleCoreM ≥ BaseM),源码中 baseM=256 实际上已经是 SingleCoreM 级别的参数。源码没有显式的 SingleCoreM/N 概念——per-core tile 尺寸 = baseM × stepM(或 baseN × stepN)。
+| 维度 | 理论(v0.95) | 源码 | |
|---|---|---|---|
| 窗口大小 | W = max{d : d\ | C, d ≤ ⌊√C⌋},C=32 时 W=4 | mainWindow = GetAswWindowLen(),逻辑相同 |
| 蛇形 | 窗口间 N 向反向 | UpdateBasicIndex:奇数行 n 反向 | |
| 窗内 | 不蛇形(A 行全程驻留 L2,零收益) | 一致 |
结论:swizzle 实现与理论一致。
+| 维度 | 理论(v0.95) | 源码 |
|---|---|---|
| L2 定位 | Cache 非 Buffer,软件可控手段:Cache Hint + CMO + swizzle | enableL2Cache flag + SetL2CacheHint |
| 启用条件 | S_in > L2 时启用分组 | isBigSize(>100MB) && cBatchDimAll<usedCoreNum && transConflict≤6 |
| 分组策略 | 执行组:m_grp × n_grp 块,工作集 ≤ L2 | DoL2CacheAndCalOrderTiling 内部处理 |
| 冲突控制 | transConflict ≤ 6 | 一致 |
差异分析:源码的 L2 管理有两个额外限制:
+| 维度 | 评价 |
|---|---|
| BaseM/BaseN 确定 | 源码用硬编码 256×256 + cubeBound 寻优,理论用 L0C 满载——源码更保守但经实测调优 |
| SingleCoreM/N | 源码无显式概念,由 baseM×stepM 组合——理论的分层更清晰 |
| Swizzle | ✓ 一致 |
| L2 管理 | 源码有额外的 100MB 和 batch 数阈值——工程化的保守限制 |
| 负载均衡 | 源码有 balanceRate ≥ 0.9 + 尾块感知评分——理论未建模,源码更完善 |
理论(v0.95 §八.5):单边无 batch 且该侧矩阵小(M ≤ 256、MK·dtype·2 ≤ L1、对侧每核循环 ≥ 4 轮)时小侧整个常驻 L1。
+源码(batch_matmul_v3_asw_al1_full_load_basic_tiling.cpp IsCapable):
bool IsCapable() {
+ if (非连续转置) return false;
+ if (!FP16/BF16) return false;
+ if (batchA > 1) return false; // A 无 batch
+ if (M > 256) return false; // M ≤ 256
+ if (batchBias > 1) return false;
+ if (A数据量×2 > L1) return false; // A 放得下 L1/2
+ // B 数据量足够:每核至少 4 轮
+ if (B总数据量 < L1×aicNum && B轮数 < 4×aicNum) return false;
+ return true;
+}
+对比:理论与源码高度一致——M ≤ 256、batchA=1、A 驻留 L1、B 每核 ≥ 4 轮。这是理论和源码最吻合的分支。
+理论(v0.95 §八.3/6):P < C 且不满足 StreamK → 只用 ⌈P⌉ 核,其余核闲置。
+源码:无独立的降核分支。ASW_Basic 的 GetNumBlocks() 返回 aicNum(固定 32 核),不降核。
差异:理论上 P < C 时应该降核(只用 ⌈P⌉ 核),但源码始终用全部 32 核。对于 P < C 的 case,部分核分到的输出块为空(idle),但仍参与调度。这可能引入不必要的调度开销。
+建议:源码应在 ASW_Basic 中加入降核逻辑——当 P < C 时 usedCoreNum = ⌈P⌉。
| 维度 | 评价 |
|---|---|
| 分支覆盖完备性 | ✓ 7 个分支覆盖所有 case,无空洞 |
| 分支选择正确性 | ⚠️ 优先级有序匹配基本合理,但 StreamK 优先级偏高(实际不影响) |
| MergeBatch 条件 | ⚠️ 偏宽——缺少搬移量/tile/算存比约束 |
| IterBatch 形态覆盖 | ⚠️ 只覆盖形态 b,缺少 a/c/d |
| StreamK K 阈值 | ⚠️ 固定 8192 过于保守 |
| 降核 ASW | ✗ 未实现 |
| L1 全载特化 | ✓ 与理论高度一致 |
| 特殊分支 | ✓ 与理论一致 |
理论分析的 7 大分支体系在逻辑上完备(15 种切分组合 → 按代价排序 → 6+1 分支),进入条件全部为 B/M/N/K 闭式表达式。与源码对比后确认:
+