diff --git a/BatchMatMulV3算子分支实现分析.html b/BatchMatMulV3算子分支实现分析.html deleted file mode 100644 index 629555e..0000000 --- a/BatchMatMulV3算子分支实现分析.html +++ /dev/null @@ -1,923 +0,0 @@ - - -
- - -BatchMatMulV3 是 CANN ops-nn 仓中批量矩阵乘的主力算子,承载 aclnnBatchMatMul / aclnnBaddbmm / aclnnAddbmm / aclnnEinsum / aclnnBatchMatMulWeightNz 等全部 BMM 类 API。语义为 C[b, m, n] = A[b, m, k] × B[b, k, n](+ 可选 bias),batch 维最多 4 级(总维度 ≤ 6),A/B/C 的 batch 维支持广播。
| 目录 | 内容 | 关键文件 |
|---|---|---|
op_host/op_tiling/ | Host 侧 tiling:分支选择、tiling 参数计算 | batch_mat_mul_v3_tiling.cpp(入口)、batch_mat_mul_v3_base_tiling.cpp(67KB,老路径基类)、arch35/(950 高级 tiling,12 个策略文件) |
op_kernel/ | Device 侧 kernel | arch35/batch_mat_mul_v3.cpp(arch35 入口,编译期 if constexpr 分发树)、batch_mat_mul_v3*.h(老架构 kernel)、arch35/batch_mat_mul_v3_*_advanced.h(各策略 kernel/block scheduler) |
op_host/op_api/ | aclnn API 层 | aclnnBatchMatMul / Baddbmm / Einsum 等 |
依赖:ops-nn matmul/mat_mul_v3、matmul/common/cmct | 公共 tiling 基类、MatmulImpl 高层 API、Cmct/Blaze GEMM 框架 | matmul_v3_tiling_advanced、mat_mul_pingpong_basic、kernel_matmul_iterbatch 等 |
BMM v3 的软件实现遵循昇腾算子的标准两层结构:
-BMM v3 全部分支的设计动机都可以追溯到 950PR(DAV_3510)的以下硬件事实(来源:昇腾950 NPU 架构白皮书表3-1/表4-2、CANN 9.0.0《NPU架构版本351x》文档):
- -| 规格项 | 昇腾 950PR | 对 BMM tiling 的意义 |
|---|---|---|
| AI 子系统 | 36 个(满配),每个 = 1 Cube Core + 2 Vector Core | AIC:AIV = 1:2 是 StreamK、fixpipe 1V2 等 MIX 分支的结构性前提(源码多处硬性要求 aivNum == 2*aicNum) |
| AIC / AIV 核数 | 32(降配 28)/ 64(降配 56),AIC/AIV 分离架构 | |
| Cube 算力 | BF16/FP16 432 TFLOPS(纯 Cube);一拍完成 FP16 16×16×16 | 决定 cubeBound(计算访存比拐点),进而决定 baseM/baseN 寻优 |
| Vector 算力 | FP16/BF16 54 TFLOPS;每拍 256B | K=0/K=1 等低密度计算退到 AIV 更划算的依据 |
| L1 Buffer | 512KB / AIC | L1 全载(AL1/BL1 full load)、iterBatchL1(L1 驻留 batch 数)的容量上界 |
| L0A / L0B | 64KB / AIC(512B 对齐,FRACTAL_NZ/ZN) | baseM×baseK、baseN×baseK 单块上界;mergeBatchL0 合并 batch 数的上界 |
| L0C | 256KB / AIC(较上代增大,白皮书明言"提供更灵活的 Tiling 策略") | fp32 累加:baseM×baseN×4B×DB ≤ 256KB;iterbatch 的 batchOutNum 由它决定 |
| UB | 512KB / AIV(32B 对齐) | AIV 分支(TO_MUL)单轮驻留 batch 数 = UB / 单 batch 字节数 |
| L2 Cache | 128MB(降配 112MB)全局统一 | 滑窗/对角错位 swizzle 的收益来源:让并发核访问的 A/B 块在 L2 命中 |
| 片上内存 | 128GB / 1.6TB/s(PR 降配 1.4TB/s;DT 为 4TB/s) | PR 算存比高 → 偏计算受限 → "减少 HBM 搬运"类分支(L1 全载、iterbatch)收益更大 |
| 数据通路(351x 变化) | 新增 L0C→UB、UB↔L1、AIV UB↔L1 硬通道、SSBuffer 核间通信;删除 GM→L0 直通与 L1→GM | 所有数据必须经 L1 中转;CV 协同(AIC 算 + AIV 后处理)有硬化通路支撑 |
| Fixpipe | L0C→GM/UB 随路量化/转置(NZ2ND 等)、ReLU 融合 | tiling_key 的 L0C2OUT_MODEL 位段(ON_THE_FLY / ND_FIXPIPE_1_1 / 1_2) |
| NDDMA | AI Core 内置 N 维 DMA,≤5 维重排随搬运完成 | TENSOR_LEVEL(张量级 API)分支的硬件基础 |
| 核间同步 | CrossCoreSetFlag/WaitFlag,支持 AIC:AIV = 1:1 / 1:2 | MIX_AIC_1_2 kernel 类型(StreamK、fixpipe 1V2 epilogue)的同步机制 |
// op_host/op_tiling/batch_mat_mul_v3_tiling.cpp
-static ge::graphStatus BatchMatMulV3TilingFunc(gert::TilingContext* context) {
- if (IsAdvancedSocVersion(context)) { // DAV_3510(950) / DAV_RESV(s8s4保留平台)
- return batch_matmul_v3_advanced::BatchMatMulV3Tiling(context).DoTiling(); // arch35 高级 tiling
- }
- return TilingRegistry::GetInstance().DoTilingImpl(context); // 老路径 BatchMatmulV3BaseTiling
-}
-IMPL_OP_OPTILING(BatchMatMulV3)
- .Tiling(BatchMatMulV3TilingFunc)
- .TilingParse<MatmulV3CompileInfo>(TilingPrepareForBatchMatMulV3) // 提取平台规格
- .GenSimplifiedKey(GenSimplifiedKey);
-TilingPrepareForBatchMatMulV3 在编译期从平台信息提取 MatmulV3CompileInfo:aicNum/aivNum、L1/L0A/L0B/L0C/L2/UB 容量、supportL0c2out(fixpipe)、supportL12BtBf16、btSize(1024/4096)、npuArch——所有分支的定量条件都建立在这组平台参数之上,同一套代码因此能适配 950 满配/降配及其他 351x 产品。
定义于 arch35/batch_matmul_v3_tiling_strategy.h:
| 优先级 | strategy 常量 | 策略类 | 一句话定位 | 核类型 |
|---|---|---|---|---|
| 0 | BATCH_MATMUL_INPUT_K_EQUAL_ZERO | BatchMatMulV3KEqZeroTiling | K=0:输出置零 | AIV_ONLY |
| 1 | BATCH_MATMUL_TO_MUL | BatchMatMulV3Matmul2MulTiling | K=1:退化为逐元素乘 | AIV_ONLY |
| 2 | BATCH_STREAM_K | BatchMatMulV3BasicStreamKTiling | K 巨大且 MN 并行度 < 一半核数:切 K | MIX_AIC_1_2 |
| 3 | MERGE_BATCH_BASICAPI | BatchMatMulV3MergeBatchBasicApiTiling | 小 M/N、巨大 batch:多 batch 合并进 L0 | AIC_ONLY |
| 4 | ITER_BATCH_BROADCAST_BASICAPI | BatchMatMulV3IterbatchBroadcastBasicApiTiling | 单边单轴 batch 广播 | AIC_ONLY |
| 5 | ITER_BATCH_BASICAPI | BatchMatMulV3IterBatchBasicApiTiling | batch 相等且 > 核数:L1/L0 多 batch 流水 | AIC_ONLY/MIX_AIC_1_2 |
| 6 | ITER_BATCH | BatchMatMulV3IterBatchTiling | 同上但高阶 API(IterateBatch) | AIC_ONLY |
| 7 | AL1_FULL_LOAD_BASIC | BatchMatMulV3AswAL1FullLoadBasicTiling | A 无 batch 且 M≤256:A 全量常驻 L1 | AIC_ONLY |
| 8 | BL1_FULL_LOAD_BASIC | BatchMatMulV3AswBL1FullLoadBasicTiling | B 无 batch 且 N≤256:B 全量常驻 L1 | AIC_ONLY |
| 9 | ASW_BASIC | BatchMatMulV3AswBasicTiling | 通用基础 API + cubeBound 模型寻优 | AIC_ONLY |
| 999 | BASE | BatchMatMulV3AswTiling | 最终兜底(高阶/张量级 API),IsCapable 恒 true | AIC_ONLY |
DAV_RESV(s8s4 保留平台)仅有:ITER_BATCH_BASICAPI → AL1_FULL_LOAD_BASIC → BL1_FULL_LOAD_BASIC → ASW_BASIC → BASE。
- -BMM 的 case 空间可由 5 个近似正交的特征维度刻画。每个分支恰好占据其中一个特征区域,且这些区域合起来覆盖了全部合法输入——这就是"为什么是这些分支"的系统回答:
-| 特征维度 | 取值区域 | 微架构矛盾 | 对应分支 |
|---|---|---|---|
| ① K 的退化程度 | K=0 | 无计算,Cube 阵列完全无用 | K_EQUAL_ZERO(AIV 清零) |
| K=1 | 无累加深度,mmad 16×16×16 粒度浪费 15/16 | TO_MUL(AIV 向量乘) | |
| ② MN×batch 并行度 vs 核数 | batchC×mCnt×nCnt ≤ aicNum/2 且 K 巨大 | 32 个 AIC 大半闲置,唯一剩余并行维度是 K | STREAM_K(切 K + 部分和归约) |
| ③ batch 量级与 M/N 大小 | M,N 很小(M≤N)、K≥64、batchC ≥ 4×aicNum | 单 batch 基本块喂不饱 L0(如 16×16),mmad 效率极低 | MERGE_BATCH(多 batch 拼成大 L0 块) |
| batchC > aicNum,M/N/K 正常 | 每核要算多个 batch,逐 batch 重复搬 A/B 浪费 MTE | ITER_BATCH_BASICAPI / ITER_BATCH(L1/L0 一次驻留多 batch 流水) | |
| batch ≤ 核数量级,M/N 大 | batch 不构成主并行维度,走 M/N 基本块切分 | ASW_BASIC / BASE | |
| ④ batch 广播形态 | 恰好单边、单轴广播 | 被广播算子的单 batch 数据可被对端多 batch 复用 | ITER_BATCH_BROADCAST(广播侧 L1 驻留一份) |
| ⑤ 单边无 batch 且该边很小 | batchA=1 且 M≤256(B 大) | A 全量可常驻 L1,消除全部重复搬运 | AL1_FULL_LOAD |
| batchB=1 且 N≤256(A 大) | 对称(注:A 不转置时已被 MergeBatchAndMAxis 折叠成 MM) | BL1_FULL_LOAD |
完备性的结构性保证:优先级表尾部是无条件分支(BASE=999 的 IsCapable 恒 true,ASW_BASIC 仅校验 dtype/batch 一致性),因此任何通过 Validate 的输入必然命中某一分支——不存在"所有分支都不收"的 case。而前 10 个分支的关系是"特判 ⊂ 通用":每个特判分支覆盖的 case 落入通用分支也能跑,只是性能更差;优先级顺序保证收益最大的实现先被选中。这与"性能最优分支集必须是完备分支全集子集"的方法论一致:这里完备全集 = 通用 cube 模板(BASE),特判分支是它在各特征区域上的性能占优替代。
-ValidateMatrixBatchInfo 阶段的 MergeBatchAndMAxis() 在 batchB==1 且 A 不转置时直接把 batchA 折叠进 M 轴(mValue = batchA * mValue,batch 置 1),把 BMM 变成单个大 M 的 MatMul——此时后续所有 batch 类分支都不会命中,case 直接落到 ASW/AL1 全载体系。这说明分支选择不是纯"分诊",前面还有一层问题归约。本章对每个分支按统一结构展开:目标 case → 进入条件逐条解析(IsCapable,含每个阈值的取值依据)→ tiling 实现步骤(DoOpTiling 编号步骤,含公式)→ 关键参数的"为什么"分析 → kernel 实现。所有条件表达式、常量、注释均直接引用源码(文件:op_host/op_tiling/arch35/batch_matmul_v3_*_tiling.cpp 及 mat_mul_v3 公共实现),微架构依据标注知识库文档篇目;官方文档没有依据、仅为源码经验值的参数会明确标注。
arch35 tiling 按优先级 0→9→999 短路遍历,每个分支的 IsCapable 是一组"与"条件。整体决策流如下(条件为简化表述,精确表达式见各分支小节):
- -理解各分支前,先建立 950PR 上的两条数据通路模型——所有分支的本质都是在这两条通路上做不同的驻留/复用/并行安排:
- -tiling:arch35/batch_matmul_v3_k_equal_zero_tiling.cpp(68 行);kernel:mat_mul_v3/arch35/mat_mul_input_k_eq_zero_clear_output.h
-K 轴长度为 0 的退化 BMM。语义上 C = 对空 K 维求和 = 全零矩阵,没有任何乘加计算。
-| # | 条件(源码表达式) | 取值依据 / 源码注释 |
|---|---|---|
| 1 | aFormat == FORMAT_FRACTAL_NZ || bFormat == FORMAT_FRACTAL_NZ → 拒绝 | 注释:"The NZ format is not supported in this strategy." 清零 kernel 是纯 AIV vector kernel,只吃 ND 连续布局,NZ 分形格式对它无意义 |
| 2 | hasBias → 拒绝 | 有 bias 时输出应为 bias 广播值而非 0,清零语义不成立 |
| 3 | kValue != 0 → 拒绝 | 本分支唯一触发条件:K==0 |
totalDataAmount = m × n × batchC —— 输出总元素量,作为负载均分指标(本分支不再细分单核任务,kernel 侧按总量均分);usedCoreNum = aivNum —— 用全部 64 个 AIV 而非 32 个 AIC。为什么:清零是纯 vector 写操作,Cube 阵列全程空转;950 的 AIC/AIV 是分离架构、各有独立指令流,用 AIV 做这件事 AIC 可以完全不上电执行;且 AIV 核数是 AIC 的 2 倍,memset 类带宽型任务并行度更高。GetNumBlocks() 同样返回 aivNum。MatMulInputKEqZeroClearOutput:if ASCEND_IS_AIC { return; }(AIC 直接退出);每个 AIV 对均分的输出区间调用 AscendC::InitOutput<DTYPE_Y>(outputGM[...], copyDataAmount, 0) 向 GM 写 0,尾区间用 tailDataCount 处理。数据通路即图 5-2 通路 B 的极简形态(连 UB 都不需要,InitOutput 直接铺 GM)。
为什么要单列这个分支:K=0 在 shape 校验中是合法输入(k∈[0, INT32_MAX]),若不放行会被通用 cube 模板接住——白白建立 GM→L1→L0 流水做"0 深度累加",既浪费 AIC 又拖慢端到端时延。作为计算模式彻底改变的特判,它必须排在最高优先级拦截。tilingKey:BASIC_LEVEL + BATCH_MODEL + Model=K_EQUAL_ZERO + NONE_FULL_LOAD + ON_THE_FLY。
tiling:arch35/batch_matmul_v3_matmul2mul_tiling.cpp(133 行);kernel:batch_mat_mul_v3_matmul2mul_cmct.h + common/cmct/kernel/kernel_batch_matmul_to_mul.h
-K==1 的 BMM:C[m,n] = A[m,0] × B[0,n],矩阵乘退化为列向量⊗行向量的外积,逐元素乘即可,K 维无累加深度。
| # | 条件(源码表达式) | 取值依据(源码注释原文 / 分析) |
|---|---|---|
| 1 | 输入 0/1 为"非连续转置" → 拒绝 | 注释:"Non-contiguous transpose does not support Matmul2Mul." mul kernel 按连续内存寻址,不支持 view 转置的 stride 访问 |
| 2 | aType != bType || aType != cType → 拒绝 | 注释:"Inconsistent a/b/c data types are not supported" 向量路径没有类型提升逻辑,三端必须同型 |
| 3 | NZ 格式 → 拒绝 | 同分支 0,纯 vector kernel 只吃 ND |
| 4 | A/B 四级 batch 任一不等 → 拒绝 | mul 是逐 batch 对应元素相乘,不支持 broadcast(广播 case 留给通用分支的 % 映射) |
| 5 | batchC < 128 → 拒绝 | 注释原文:"batch数大于等于128(AIV CoreNum *2) 才能开pingpong"。128 = aivNum(64) × 2:每核至少分到 2 个 batch,才能一份在算、一份在搬,构成 UB pingpong 流水 |
| 6 | hasBias → 拒绝 | bias 加法未在 mul kernel 实现 |
| 7 | kValue != 1 → 拒绝 | 触发条件:K==1 |
| 8 | n > 32B/dtype && n <= 256B/dtype → 拒绝 | 注释原文:"N>256/DtypeSize才能用满Vector的计算能力,小N走特殊优化"。Vector 一拍处理 256B(054篇):N>256B/dtype 时一次 Mul 指令打满吞吐;N≤32B/dtype(不足一个 32B block)走 DoOpTiling 的小 N 特殊优化公式;中间区 (32B, 256B]/dtype 两头不占,让给其它分支 |
| 9 | n == 1 → 拒绝 | 注释原文:"N=1时部分case劣化"——实测劣化的经验排除项 |
| 10 | (alignM + alignN + alignM×alignN)×dtype > ubSize → 拒绝 | UB 容量校验:单 batch 要同时驻留 A 向量(alignM 元素)、B 行(alignN)、C 外积结果(alignM×alignN),三者放不下 UB(512KB)则该分支不可行。alignNum_=32B/dtype 是 vector 一次访存的最小对齐粒度 |
| 11 | n % (256B/dtype) == 0 → 拒绝 | N 恰为 256B/dtype 整数倍属规整形状,存在更优的规整路径,本策略主动让出(防重复覆盖) |
alignM = CeilAlign(m, 32B/dtype),alignN = CeilAlign(n, 32B/dtype);usedCoreNum = aivNum;singleBatchSize = (m + alignN + m×alignN) × dtypeSize —— 单 batch 占 UB 字节数(A + B + C 三段);小 N 特殊优化:若 alignN ≤ 32B/dtype(不足一条向量指令),改用 (alignM + alignN + alignM×alignN) × dtypeSize,A/C 也按对齐块整块处理;singleCoreBatch = CeilDiv(batchC, aivNum) —— 每核应分 batch 数(负载均衡基准);ubLimitBatchNum = ubSize / singleBatchSize —— UB 容量允许的单轮驻留 batch 数;batchNum = min(singleCoreBatch, ubLimitBatchNum) —— 每核每轮实际处理 batch 数 = 负载均分与 UB 容量的较小者。这个 min 是本分支的核心:batchNum 决定 pingpong 的深度;batchNumLastRound = singleCoreBatch % batchNum(整除时为 batchNum);lastCoreNum = batchC % (batchNum×aivNum) / batchNumLastRound(最后一轮满载核数);batchNumLastRoundTail(最后一个不满载核的 batch 数,恰整除时归并为满载核)——把"总 batch 不能整除 batchNum×核数"的余数精确拆成若干满核 + 一个尾核,kernel 直接按这三参数索引,无需运行时再做除法。Cube 阵列一拍完成 16×16×16(fp16)的 fractal 乘加(054篇)。K=1 时,每次 mmad 的 K 维只有 1/16 被利用——阵列 15/16 的 MAC 空转,还要为此支付 GM→L1→L0A/L0B 的分形搬运、L0C 累加与 fixpipe 写出全链路开销,计算访存比极差。而 AIV 每拍处理 256B 连续数据,外积逐元素乘 + UB 多 batch pingpong 能把 MTE 带宽吃满;64 个 AIV 的聚合向量算力(FP16 54 TFLOPS)对这个计算密度绰绰有余。这就是为什么触发条件里 batchC≥128(开 pingpong)与 N>256B/dtype(打满向量指令)都以"带宽/指令利用率"为判据——该分支的收益模型是纯带宽模型,不是算力模型。
-KernelBatchMatMulToMul(Cmct 框架,ProblemShape 的 k 直接写死 1):if ASCEND_IS_AIC { return; };AIV 按 BlockSchedulerBatchMatMulToMulBuiltIn 给出的 usedCoreNum/singleCoreBatch/batchNum/lastCoreNum 做 batch 组轮询(tileIdx += blockNum),每组由 BlockMmadBuilder<..., BatchMatmulToMul<>> 生成的向量乘 block 处理。通路:GM→UB→Mul→GM,完全绕过 L1/L0/Cube(图 5-2 通路 B)。tilingKey:BASIC_LEVEL + BatchModel=BATCH_MATMUL_TO_MUL(AIV_ONLY)。
tiling:arch35/batch_matmul_v3_basic_streamk_tiling.cpp(165 行);kernel:mat_mul_v3/arch35/mat_mul_streamk.h(MatMulStreamKKernel / MatMulStreamKActKernel)
-K 极大而 batch×M×N 基本块总数填不满 AIC 的场景(典型:小 batch、小 M/N、超长 K)。此时若只沿 batch/M/N 切分,32 个 AIC 大半空转,唯一剩余的并行维度是 K。
-| # | 条件(源码表达式) | 取值依据(注释原文 / 分析) |
|---|---|---|
| 1 | GetDeterministicLevel() > 1 → 拒绝 | 注释原文:"batch一致性控制,当开关等级为2或3时,拒绝切k模板,达到强一致性和batch一致性"。切 K 后多核部分和的归约顺序不定,引入浮点累加非确定性;确定性等级 ≥2 的业务(如训练梯度对比)禁用 |
| 2 | A/B 四级 batch 不等 → 拒绝 | kernel 按等 batch 均摊核数(blocksPerBatch = aicNum/batchC),广播 batch 无法均摊 |
| 3 | aFormat != FORMAT_ND → 拒绝 | 注释:"ND is the only supported format for tensor_a in basic api" |
| 4 | batchBias > 1 → 拒绝 | bias 多 batch 未支持 |
| 5 | 非连续转置 → 拒绝 | 注释:"Non-contiguous transpose does not support StreamK." |
| 6 | aivNum != aicNum × 2 → 拒绝 | 注释:"streamk only support aivNum == aicNum * 2"。归约/fixpipe 后处理依赖 1 AIC : 2 AIV 的固定配比(351x 硬件特征,054篇);非此配比平台直接拒绝 |
| 7 | fp32 非 hf32 且 k > 2,000,000 → 拒绝 | 注释原文:"如果dtype是fp32且k轴大于200万 则走基础模板来保证fp32的精度"、"Due to the requirement of binary accumulation..."。fp32 超长 K 需要二叉树累加保精度,StreamK 的串行归约不满足;200 万是精度劣化可接受的经验上限(源码经验值,文档无依据) |
| 8 | CeilAlign(k,256) < max(8192, aicNum×256B/dtype) → 拒绝 | K 下界双保险:aicNum×256B/dtype 保证"即使全部 AIC 都参与切 K,每核也至少分到 256B(一个 K 内轴搬运块 BASIC_BLOCK_K_256_BYTE)",否则单核 K 段太短、搬运启动开销盖过收益;8192 是绝对收益门槛(元素数)。外层 CeilAlign(k,256) 先把 K 对齐到基本块再比较 |
| 9 | batchC × CeilDiv(m,alignV) × CeilDiv(n,alignV) > aicNum/2 → 拒绝 | ★ 核心判据。alignV=256(fp32 非 hf32 时 32,注释"如果是Fp32 基本块判断要用32")。注释:"判断bmn是否需要已经能切16份及以上"。语义:若 batch×M×N 已能切出超过"核数一半"的块,MN 方向并行度已够(留一半核作均衡余量),无需切 K;反之才进 StreamK。此条件同时保证 DoOpTiling 中 mnCnt ≤ blocksPerBatch/2,使 kCnt ≥ 2(见步骤 4/7 推导) |
ResetBase(DAV_3510 特化:baseM=baseN=256,baseK=128B/dtype,"256 is better base");mCnt = CeilDiv(m, baseM)、nCnt = CeilDiv(n, baseN);blocksPerBatch = aicNum / batchC —— 每个 batch 平均分到的 AIC 核数;if (mCnt > blocksPerBatch/3 && mCnt < blocksPerBatch/2) mCnt = blocksPerBatch/2(nCnt 同理)。推导:由条件 9 已知 mnCnt ≤ bpb/2;若 mCnt 落在 (bpb/3, bpb/2),则 nCnt ≤ (bpb/2)/mCnt < 1.5 即 nCnt 必为 1;收拢后 mnCnt = bpb/2,下一步 kCnt = bpb/mnCnt = 2,恰好把该 batch 的核预算在 M 向和 K 向各分一半,得到规则的 2 路切 K;不收拢则产生零散尾块、负载不均。这是"块数离散化取整到规则分点"的手法;baseM = CeilAlign(CeilDiv(m, mCnt), 16),baseN 同理(16 = cube fractal 粒度,054篇);tailInfo.kCnt = blocksPerBatch / mnCnt —— kCnt 推导:每 batch 的核预算 ÷ MN 已占块数 = 每个输出块还能再切几份 K;由 mnCnt ≤ bpb/2 得 kCnt ≥ 2(至少 2 路切分);singleCoreK = CeilDiv(k, kCnt) —— 每核承担的 K 长度;baseK = min(singleCoreK, FloorAlign(L0A半区/dtype/max(baseM,baseN), alignV)) —— K 基本块取"均分 K"与"L0A 容量上限"较小者。L0A 半区 = 64KB/2(DB 两份,067篇);K 为内轴时(A 不转置或 B 转置)按 128B 对齐(056篇:内轴 128B/256B/512B 对齐发挥带宽),否则按 16 对齐;CalL1Tiling 定 stepK/depthA1/depthB1;if (baseM==baseN && depthB1==2×depthA1) { depthA1×=2; depthB1÷=2; }(注释"depthb1 is less than deptha1")——M、N 块相等时 A/B 应对称,若 B 的 L1 深度恰为 A 两倍则互换,使 depthA1 ≥ depthB1;GetWorkspaceSize() = aicNum × 256×256 × 4B + 20MB:前半是每个 AIC 一块 256×256 fp32 部分和缓冲(对应 baseM×baseN 上限的累加结果——各核的部分和先写 GM workspace,再由归约阶段累加);后半 20MB 是基类默认 RPC 区(注释 "20MB workspace for RPC",核间通信/同步标志位)。fixpipe 选择 GetL0C2OutFlag():n>64 且 n%16≠0 且 m>2 且 m×n≥256 → ND_FIXPIPE_1_2(N 不对齐且输出块足够大时,L0C→GM 直通会成为瓶颈,改走 1 AIC : 2 AIV 的 ND fixpipe 通路分担搬出)。
StreamK 思想源于 GPU 端的 Stream-K 工作中心分解(Osama 等,arXiv:2301.03598,2023):传统 data-parallel GEMM 按输出块切核,当输出块数 < 核数时算力浪费;StreamK 把总工作量(输出块×K 迭代)按"工作量流"均分给所有核,核间通过部分和归约汇合。CANN 公开文档中没有 StreamK 的说明(知识库检索仅命中 catlass/ops-nn 源码),官方文档的切 K 建议是 EnableMultiCoreSplitK + GM 清零 + AtomicAdd(206篇,案例 K=1024 切 2 核后 19.60us→13.70us)。BMM 这里的实现与官方简易切 K 的差异在于:用 workspace 归约而非 AtomicAdd(fp32 部分和写 workspace 再规约,避免 GM 原子加的顺序随机性——这也解释了条件 1 的确定性等级管控),并用 aivNum==2×aicNum 的 MIX 模式让 AIV 承担归约/搬出。进入条件的两条 K 阈值(≥max(8192, aic×256B/dtype) 才切、fp32 ≤200 万)分别守住"切 K 收益下限"与"fp32 精度上限"。
tiling:arch35/batch_matmul_v3_mergebatch_basicapi_tiling.cpp(145 行);kernel:batch_mat_mul_v3_mergebatch_basicapi_cmct.h + common/cmct/kernel/kernel_matmul_merge_batch.h
-M、N 很小(要求 M≤N)、K≥64、batch 巨大的 BMM(如小维度投影层的海量 batch 推理)。单 batch 的基本块太小喂不饱 Cube,把多个 batch 在 M/N 方向"拼接"成大块一次算。
-| # | 条件(源码表达式) | 取值依据(注释原文 / 分析) |
|---|---|---|
| 1 | 非连续转置 / NZ → 拒绝 | 合并 batch 要求物理内存连续(注释:"Non-contiguous transpose does not support MergeBatch.") |
| 2 | hasBias || (fp32 且非 hf32) → 拒绝 | bias 未支持;fp32 不转 hf32 时 L0C 内 fp32 累加块容量减半、合并收益差,直接排除 |
| 3 | A/B 四级 batch 不等 → 拒绝 | 合并要求 A/B batch 完全一致(无广播) |
| 4 | batchC < 4 × aicNum → 拒绝 | 常量 MIN_BATCH_L0=4(.h L46),注释原文:"each aic should process at least 4 batchs"。为什么 4:合并的最小粒度是 4 个 batch(合并 4 个才有"L0 一块装下"的收益),且每个 AIC 至少要分到一份最小合并块 → 总 batch ≥ 4×aicNum |
| 5 | alignK < 64 || m > n → 拒绝 | 注释:"shape check, aligned k shape should be at least 64"。K≥64(fp16 下 128B)是一次 K 内轴搬运的最低收益门槛;m≤n 因为合并沿 N 方向展开(tempAlignN=4×alignN),N 是较大维才有合并收益 |
| 6 | 预计算 tempAlignM / tempAlignN | tempAlignM = CeilAlign(4m, 16);但 isATrans 且 m>1 时 tempAlignM = 4×CeilAlign(m,16)——A 不转置时 4 个 batch 的 M 行内存天然连续,先拼成 4m 再整体对齐(损耗 ≤15 元素);A 转置时每个 batch 的 fractal 独立、M 维须各自对齐 16 再拼接(损耗 ≤4×15),容量校验更保守。tempAlignN = 4×alignN(N 向总是逐 batch 对齐后堆叠) |
| 7 | L0 三容量校验(最小合并粒度 4 下):tempAlignM×minBaseK×dtype×2 > l0ASize 或 tempAlignN×minBaseK×dtype×2 > l0BSize 或 tempAlignM×tempAlignN×4B×2 > l0CSize → 拒绝 | 注释:"l0 buffer check"。L0A 须装下"4 合并 M × 最小 K"的双缓冲;L0B 对称;L0C 须装下"4M×4N"的 fp32 累加双缓冲(64KB/64KB/256KB,950 白皮书表4-2)。连最小合并(4 batch)都放不下,合并就无可行性。minBaseK:K 参与 fractal 对齐(A 转置或 B 不转置)时取 16 倍数,否则取 c0Size=32B/dtype |
batchNumPerCore = CeilDiv(batchC, aicNum);maxBaseK = l0BSize / 4 / alignN / dtype / 2(L0B 要同时容纳 4 个 batch 的 B 块、双缓冲),向下 16 对齐、保底 16;baseK = min(maxBaseK, 64) —— 注释:"threshold of basek is 64"。为什么 64:mergebatch 的收益来自 batch 合并而非 K 深度;baseK=64(fp16 下 128B)已满足 K 内轴最小搬运粒度(056篇),把 baseK 压小可腾出 L0B 容量给更多合并 batch(下一步 maxBatchL0 与 baseK 成反比)——64 是"搬运效率"与"合并度"折中的经验上限(源码经验值);maxBatchL0 = max(l0BSize/baseK/dtype/2 / alignN, 1) —— 已定 baseK 下,L0B 双缓冲能容纳的 N 向总元素 ÷ 单 batch 对齐 N = L0B 容量允许的最大合并 batch 数;mergeBatchL0 = min(CalBatchL0WithPolynomial(l0cElem, m), maxBatchL0, batchNumPerCore) —— 最终合并数取三者最小:L0C 多项式解(容量最优)∩ L0B 上限 ∩ 每核实际 batch 数;baseM = CeilAlign(mergeBatchL0×m, 16)(transA 时逐 batch 对齐再累加)、baseN = mergeBatchL0 × alignN;stepKaMax = min(CeilDiv(batchNumPerCore, mergeBatchL0), l1Size/4/(baseM×baseK×dtype))——L1 均分 4 份(A pingpong 2 份 + B pingpong 2 份),单份容量 l1Size/4 除以单步数据量得容量允许的最大步数,再与 batch 轮数、K 总步数取 min;baseM = alignM, baseN = alignN(单 batch 对齐值)+ mergeBatchL0 字段 —— 注释:"M和N在L1和L0里不切分",切分全部发生在 batch 维,合并倍数由独立字段传给 kernel。注释原文:"多项式求解最佳batchL0"。设合并 x 个 batch,合并后 L0C 输出块为 CeilAlign(x·m,16) × (x·alignN)(fp32)。L0C 单缓冲容量 S = l0CSize/2/4 元素,约束:
CeilAlign(x·m, 16) · x·alignN ≤ S
-令 a = m/16,取整余量 t = 15/16(向上取整的最大余量),则 CeilAlign(x·m,16) ≈ 16(ax + t)
-代入:16(ax + t) · x·alignN ≤ S ⟹ ax² + tx ≤ S/(16·alignN)
-两边乘 a 构造完全平方:(ax)² + t(ax) ≤ aS/(16·alignN) ≜ p
-解正根:ax = √(p + t²/4) − t/2 ≜ y // 与源码 L121 逐符一致
-x = floor( min( p/(ceil(y)·a), ceil(y)/a ) ) // L0C 容量约束 ∩ M 块数自洽,保底 1
-为什么这个根是最优:y 是连续意义下"恰好装满 L0C 单缓冲"的合并块 M 向 16-块数上界——约束取等号时 L0C 利用率 100%:x 再大就溢出、再小则合并度不足(cube 有效算力占比随合并度单调上升)。取整时用 ceil(y) 作实际 M 块数 Y,再同时满足 L0C 容量(x ≤ p/(Y·a))与 M 块数自洽(x·a ≤ Y)两个约束取 min,floor 后保底 1,得到不超过 L0C 容量的最大合法整数合并数。
KernelMatMulMergeBatch + BlockSchedulerMergeBatchBuiltIn:构造时做负载均衡——mainBatchNum = batchC/batchAL1、mainBatchLoop = mainBatchNum/blockNum,余数均摊成"主块/主尾块/最终尾块"三种 tile(前几个核多算一个),GetIterBatchTuple = {batchAL1, batchBL1, batchL0, batchL0}(A/B 在 L1/L0 可载不同 batch 数)。支持 fixpipe 1V2 融合 add/mul epilogue(BlockEpilogueMergeBatch)。tilingKey:BASIC_LEVEL + BatchModel=MERGE_BATCH_MODEL;另有一个细节:transA = isATrans && m>1(注释 "trans_a=true && m==1 equals to trans_a=False",m=1 的转置是退化情形按非转置选 kernel)。
tiling:arch35/batch_matmul_v3_iterbatch_broadcast_basicapi_tiling.cpp(333 行);kernel:batch_mat_mul_v3_iterbatch_broadcast.h(Blaze BatchMatMulIterBatchBroadcastKernel)
-A 或 B 的某一级 batch 维为 1 的单边广播场景(如一份共享权重对多 batch 激活)。广播语义下被广播算子的单 batch 数据可被对端多个 batch 复用——L1 只需驻留一份广播算子,其余空间全部用来流水加载非广播算子的多个 batch,MTE 搬运量按广播倍数减少。
-| # | 条件 | 取值依据(注释原文 / 分析) |
|---|---|---|
| 1 | NZ → 拒绝;任一输入 stride 非连续 → 拒绝 | IsContiguousStride 自实现检查:收集 view 各维 (stride, size),跳过 stride=0(广播维)和 size=1 的维,按 stride 降序排序后验证期望 stride 逐维累乘——保证 L1 迭代搬运的地址连续 |
| 2 | batchBias > 1 → 拒绝 | bias 必须单 batch |
| 3 | hasBroadcastAxis() 必须为真 | 4 级 batch 逐一比对(cpp L92-124):A维==1 且 B维!=1 记 A 广播轴,反之记 B 广播轴。"单边单轴"= 广播轴只在 A 或 B 一侧恰好出现 1 次:双边广播("Dual-side broadcast is not supported.")、多轴广播("Multi-axis broadcast is not supported.")、无广播("No broadcast axis detected, should use ITER_BATCH.")均拒绝。为什么:只有"单边单轴"才能保证 L1 驻留一份广播数据后、迭代轴是单一连续维;双边/多轴广播的复用关系无法用单驻留+单迭代表达,复杂 case 让位给通用 ASW 的 % 广播映射 |
| 4 | CheckNonBroadcastAxisMatch() | 除广播轴外,A/B 其余 3 级 batch 维必须逐维相等(允许两侧都为 1) |
| 5 | batchC ≤ aicNum → 拒绝 | 日志:"batchC <= aicNum, no need for iterbatch." batch 不大于核数时 batch 维并行度不足,无 iterbatch 收益 |
| 6 | CheckL1IterBatch()(cpp L143-206) | 见下方③的 L1 容量推导 |
| 7 | CheckL0IterBatch()(cpp L208-230) | 见下方④ |
sizeAOneBatch = alignM×alignK×dtype、sizeBOneBatch = alignK×alignN×dtype、sizeCOneBatch = alignM×alignN×4B(L0C 内 fp32);(sizeA + sizeB + bias) × 2 ≤ l1Size(A、B、bias 各一份、开 DB 要放得下 512KB L1);innerDimsProduct = 广播轴之后(更内层)所有 batch 维在广播侧的乘积;broadcastSingleBatch = (innerDimsProduct == 1)——含义:广播轴之后的内层维在广播侧全是 1,即广播侧在整个迭代中只对应同一份单 batch 数据,可以永久驻留 L1,循环只在非广播侧上做;iterBatchL1 = (l1Avail − sizeA − bias) / sizeB —— L1 半区先扣驻留的 A 单 batch 和 bias,余下全部装 B 的多个 batch;iterBatchL1 = (l1Avail − bias) / (sizeA + sizeB) —— A、B 必须按 batch 成对进入;iterBatchL1 ≥ 2(注释:"iterBatchL1 expected to be no less than 2"——广播收益的前提是至少 2 个 batch 复用驻留侧);再 min(iterBatchL1, CeilDiv(batchC, aicNum)) 不超过单核平均 batch 数;min(iterBatchL1, 迭代轴长) 向下找能整除迭代轴的最大因子——保证 L1 一次搬运的 batch 段在迭代轴上连续且整除,避免尾批破坏地址连续性(BMM 的 batch 段搬运是 NDDMA 多维整块搬,段边界不对齐迭代轴会产生碎地址)。iterBatchL0A = (l0ASize/2) / sizeAOneBatch; // L0A 半区(DB)可驻留 batch 数,L0B/L0C 同理
-l0CanLoadBatch = min(L0A, L0B, L0C) ≥ 1 // 标准情形:三级半区各放≥1 batch
- || (L0A无DB ≥ 1 && L0B无DB ≥ 1 && L0C半区 > 1) // 放宽:牺牲输入 DB,换 L0C 多攒 batch
- // 源码注释:// try to reduce fixpipe instr
- // —— L0C 多攒几个 batch 的结果再一次 fixpipe 搬出,摊薄 fixpipe 指令开销
-0.8 均衡率(仅当 !l0CanLoadBatch 时执行):
-avgIterBatch = batchC / aicNum // 每核理想平均 batch 数
-actualMaxIterBatch = CeilDiv(CeilDiv(batchC, iterBatchL1), aicNum) × iterBatchL1 // 最忙核实际负载
-balanceRate = avgIterBatch / actualMaxIterBatch < 0.8 → 拒绝
-语义:batch 先按 iterBatchL1 分组、组再均分到核,最忙核要处理"ceil(组数/核数)×iterBatchL1"个 batch(尾组按满组放大)。若 L1 分组导致核间倾斜超过 20%,iterbatch 得不偿失,宁可落到通用分支。为什么只在 L0 载不了多 batch 时检查:L0 顺畅时 iterbatch 的收益足够覆盖一定的倾斜;L0 已打折时再叠加 20% 以上倾斜就不值了。0.8 为源码经验值(defaultBalanceOfBatch)。
-iterBatchL1;iterBatchL0 = max(min(L0A, L0B, L0C, iterBatchL1), 1)(L0 侧迭代不超过 L1 侧);broadcastAxisA/B(无广播侧保持哨兵 4,kernel 据此识别方向);baseM/N/K = alignM/N/K(L0 能整 batch 装,一个 batch 一砖,M/N/K 都不切);| case | 条件 | 固定 | 推导 |
|---|---|---|---|
| 1 | M<N 且 M>K(K 最小) | baseK=alignK | baseM=min(l0A/2/dtype/baseK, alignM);baseN=min(l0B/2/dtype/baseK, alignN) |
| 2 | M<N 且 M≤K(M 最小) | baseM=alignM | baseK=min(l0A/2/dtype/baseM, alignK);baseN=min(l0B/2/dtype/baseK, alignN) |
| 3 | M≥N 且 N>K(K 最小) | baseK=alignK | baseN=min(l0B/2/dtype/baseK, alignN);baseM=min(l0A/2/dtype/baseK, alignM) |
| 4 | M≥N 且 N≤K(N 最小) | baseN=alignN | baseK=min(l0B/2/dtype/baseN, alignK);baseM=min(l0A/2/dtype/baseK, alignM) |
baseN = min(baseN, btSize/2/4)(BT 表半区能容纳的 fp32 元素数);l0CSize/2/4B ÷ 另一维(保证 baseM×baseN 不超 L0C 半区);FloorAlign(max(x,16), 16)(cube fractal 粒度保底)。Blaze 栈 BatchMatMulIterBatchBroadcastKernel(GemmUniversal + BlockSchedulerIterBatchBroadcast + MatmulIterBatchBroadcast<A_BC,B_BC>),scheduler 参数携带 broadcastAxisA/B 与 A/B/C 各 4 维 batch;NZ 等不支持 Blaze 的场景回落通用 BatchMatMulAswKernel(其 CalcGMOffset 的 batchCiIndex % batchAi / % batchBi 天然支持广播映射,只是失去 L1 单份驻留的搬运优化)。tilingKey:TENSOR_LEVEL + ITER_BATCH_BROADCAST_A/B_MODEL + 固定 ON_THE_FLY。
tiling:arch35/batch_matmul_v3_iterbatch_basicapi_tiling.cpp(241 行);kernel:batch_mat_mul_v3_iterbatch_basicapi_cmct.h + common/cmct/kernel/kernel_matmul_iterbatch.h
-A/B batch 完全相等、batchC > aicNum 的普通大 batch BMM——每核要顺序算多个 batch,让 L1/L0 一次驻留多个 batch 形成流水,摊薄每 batch 的搬运与启动开销。
batchC > aicNum;((alignM×alignK + alignK×alignN)×dtype + bias) × 2 ≤ l1Size(单 batch A+B+bias 开 DB 放得下)。对齐细节(源码注释):fp16 或 fp32 的 (m,k)/(n,k) 布局 M/N 按 16 对齐;fp32 的 (k,m)/(k,n) 布局按 8×2 对齐,"for frac combine in loadtol0a/l0b"(L0 装载时的分形合并要求);l0CanLoadBatch = min(L0A半区/sizeA, L0B半区/sizeB, L0C半区/sizeC, L1半区/(sizeA+sizeB)) ≥ 1(带 bias 另需 (btSize/2)/(alignN×4) ≥ 1);不满足时执行与分支 4 相同的 0.8 均衡率检查。constexpr uint64_t mmadCount = 8UL; // cube count which will cause issuequene
-constexpr uint64_t fullCopySize = 64 * 1024UL; // datasize moving once which can use full of bandwith
-依据分析:056篇给出"MTE1 和 MMAD 指令队列深度为 32,队列满会阻塞其他指令下发、引起流水断流"——mmadCount=8 是"L1 一次驻留的 batch 数使连续 mmad 发射不撑爆 issue queue"的工程经验值(具体数字 8 文档无出处,属源码经验值);fullCopySize=64KB 是"单次搬运打满带宽"的粒度(168篇的官方经验值是 16KB,64KB 更保守,同为源码经验值)。if (8 × (单batch A+B 字节数) > 64KB) 则 iterBatchL1 = min(iterBatchL1, 8, CeilDiv(batchC, aicNum))、iterBatchL0 同步钳到 8。逻辑:若 8 个 batch 的搬运量已超 64KB,说明单 batch 足够大、8 次搬运即可打满带宽——再增加驻留 batch 数只增加 issue queue 压力而无带宽收益;反之(单 batch 很小,8 个也填不满 64KB)不钳制,让 L1 尽量多装以摊薄搬运启动开销;min(innerBatch, iterBatchL1) 向下找 innerBatch 的最大因子作为新 iterBatchL1,并把 innerBatch 写进 runInfo 供 kernel 算地址(与分支 4 的整除因子逻辑同构);if (n×bDtype > 256B && n % (256B/bDtype) != 0 && aivNum == 2×aicNum)
- return ND_FIXPIPE_1_2; else return ON_THE_FLY;
-三条件同时满足才启用:① N 单行字节 > 256B;② N 非 256B 对齐(直接 fixpipe 写 GM 会产生非对齐写,056篇:非对齐向上取整补零、效率损失);③ 1C:2V 配比(vector 侧有足够算力消化 fixpipe 输出)。ND_FIXPIPE_1_2 的语义是"1 个 AIC 配 2 个 AIV 做 ND 对齐 fixpipe 后处理"(AIC↔AIV 用 MODE_4 CrossCoreFlag 逐 AIV 握手,087篇/054篇);该枚举无任何 CANN 文档说明,语义以源码为准。Cmct 四层组装 KernelMatMulIterBatch<ProblemShape, BlockMmad, BlockEpilogue, BlockScheduler>:BlockSchedulerIterBatchBuiltIn 的调度粒度是"一组 batch"——GetTileNum = ceil(b/iterBatchL1),GetBlockCoord = {0,0,0, tileIdx×iterBatchL1},GetIterBatchTuple = {iterBatchL1, iterBatchL0, 0, 0}(L1 一次装 iterBatchL1 个 batch、L0 一次装 iterBatchL0 个);核映射为 batch 组轮询。ND_FIXPIPE_1_2 变体(MIX_AIC_1_2)经 BlockEpilogueIterbatch 由 2 个 AIV 配合 1 个 AIC 做后处理(支持 add/mul/relu 融合)。tilingKey:BASIC_LEVEL + SINGLE_BIAS_MODEL + l0C2Out 动态位。
tiling:arch35/batch_matmul_v3_iterbatch_tiling.cpp(143 行);kernel:arch35/batch_mat_mul_v3_iterbatch_kernel_advanced.h(BatchMatMulMultiBatchKernel)
-目标 case 与分支 5 相同(大 batch、batch 相等),区别在于搬移抽象层:分支 5 用基础 API/Cmct(host 手工管理 L1/L0 双缓冲),本分支用高阶 MatmulImpl API 的 IterateBatch(batch 维打进 MM 流水线,DB 由 API 内部管理)。官方文档依据(141篇):"调用一次 IterateBatch,可以计算出多个 singleCoreM × singleCoreN 大小的 C 矩阵"、"当单次 Matmul 输入 shape 较小时,搬运开销占比大,IterateBatch 批量处理可有效提升带宽利用率"。
iterBatch = l1Size / inputSizeOneBatch > 1(不除 DB_SIZE),再与每核 batch 数取 min。
-为什么不除 2(与分支 4/5 的 l1Size/2 对比):batch 维的双缓冲由高阶 API 内部实现(API 在 L1 内对 batch 段做 ping-pong),host 侧只需给出"理论可装多少 batch";DoOpTiling 里两处注释互证——"L1 mm fullLoad, batch 2 DB"(L1 深度 depthA1=stepKa×stepM 不含 batch 因子,batch 的 DB 独立处理)和 "need align to 2 for db in api"(iterBatch 向下对齐 2,保证 API 能拆两份做 DB)。而 basicapi 版 kernel 手工管理 L1 双缓冲,所以 host 必须先除 2 预留;singleCoreM/N/K = 完整 m/n/k(单核内 M/N 不切分,并行维度只有 batch);if (baseM ≥ m && baseN ≥ n) { baseM/N = 对齐整块; isEnableMultiBatch = true; }——L1 基本块能完整覆盖 MN 时才允许多 batch 驻留 L0C;baseK = min(l0A/2/baseM/dtype, l0B/2/baseN/dtype) 向下 16 对齐;singleCoreK = (baseK < singleCoreK || iterBatch ≤ 4) ? singleCoreK / 2 : singleCoreK;
-// 注释:4 avoid issueque
-两个触发条件:① L0 装不下整条 K(baseK<singleCoreK),K 本就要分多次,先把单核 K 减半配合 L1 DB 流水;② iterBatch ≤ 4:batch 迭代次数太少时,cube 在 batch 循环间的指令发射不足以填满 issue queue,减半 K 让 K 循环与 batch 循环重叠、保持 issue queue 有指令可发。阈值 4 与分支 5 的 mmadCount=8 同源(8÷DB2=4 的直觉),均为源码经验值(056篇仅有队列深度 32 的机制描述);stepKa = stepKb = CeilDiv(singleCoreK, baseK);depthA1 = stepKa×stepM(注释 "L1 mm fullLoad, batch 2 DB");iterBatch = FloorAlign(iterBatch, 2)(API 内部 DB 要求偶数);batchOutNum = isEnableMultiBatch ? min( l0CSize / (baseM×baseN×dbL0C×4B), iterBatch ) : 1;
-if (batchOutNum == iterBatch) batchOutNum = iterBatch >> 1; // 恰好相等时再减半
-为什么相等要减半:若 L0C 刚好能把本轮全部 batch 存满,就没有余量做"一半写出、一半继续算"的乒乓;减半后一半容量在计算、一半容量在 fixpipe 搬出,实现输出与计算的流水重叠。这里直接吃到 950 L0C 增大到 256KB 的红利(白皮书 §4.1.1 明言"更大的 L0C Buffer 提供更灵活的 Tiling 策略"——256KB 让更多 batch 的输出能驻留 L0C)。BatchMatMulMultiBatchKernel + BatchMatMulMultiBatchBaseBlock:主循环 LoopTimes = ceil(batchC / (iterBatch×useCoreNum)),每核每轮处理 iterBatch 个 batch;核心是一次 mm_.IterateBatch(cGlobal, 0,0,0, singleASize, singleBSize) 让 MatmulImpl 内部完成多个 batch 的连续 mmad——batch 维打进 MM 流水线,L0C 按 batchOutNum 攒多 batch 再一次写出(SetNBatchOutNum,该接口 CANN 9.0/9.2 文档均未收录,属未文档化高阶接口,语义以源码为准),相对普通分支"一块一 Iterate/GetTensorC"显著减少指令发射与同步开销。GetMultiBatchInfo(loopIndex) 处理尾轮均衡(前几个核多算一个 batch)。tilingKey 只设 trans + SINGLE_BIAS_MODEL(apiLevel 由 key 默认值决定,落 HIGH_LEVEL)。
tiling:arch35/batch_matmul_v3_asw_al1_full_load_basic_tiling.cpp / ..._bl1_...;公共实现:mat_mul_v3/arch35/matmul_v3_basic_aswt_tiling.cpp(DoAL1FullLoad/DoBL1FullLoad);kernel:arch35/batch_mat_mul_v3_asw_{al1,bl1}_full_load_kernel_advanced.h
-A 无 batch(batchA=1)且 M≤256、B 有 batch 且总量大(AL1);镜像地 B 无 batch 且 N≤256(BL1)。典型:小 M 激活 / 小 N 权重(MoE 投影层)被大 batch 对端乘。"全载(full load)"的含义:小的一侧矩阵完整常驻 L1,整个 kernel 只从 GM 搬一次,之后所有 batch、所有 N(或 M)块的计算都从 L1 复用,彻底消除该侧的重复 MTE 搬运。
-| # | 条件 | 取值依据(注释原文 / 分析) |
|---|---|---|
| 1 | 非连续转置 → 拒绝 | 全载要求 GM 一次性 Nd2Nz 大整块搬入,view 转置 stride 不支持 |
| 2 | dtype 限 fp16/bf16(A/B),C 可 fp16/bf16/fp32;非支持类型且 B 为 NZ → 报错 | 基础 API 全载模板的类型约束(fp32 输入只接受 ND) |
| 3 | batchA > 1 → 拒绝 | 注释:"matrix A should not have batch when AL1FullLoad"——A 有 batch 时每核要驻留多份 A,"全载"语义不成立。注意:batchB==1 且 A 不转置的情形已在更早的 MergeBatchAndMAxis() 被折叠成 MM(见 §4),走到这里的是 B 有 batch 的真 BMM |
| 4 | m > 256 → 拒绝 | (源码注释 "m should be larger than 256" 与实际逻辑相反,系注释笔误,实际要求 m≤256)。为什么 256:m≤256 时 A 的 M 向恰好一个 baseM(256) 覆盖,stepM=1,单核 M 向无循环——A 全载后每核只需加载一次 A 且各核间 A 完全共享,全载收益最大 |
| 5 | ★ "值得全载"门槛:alignMatBSize < l1Size×aicNum && batchB×CeilDiv(n,256) < 4×aicNum → 拒绝 | 注释:"each core needs to loop at least 4 batch for MatB"、"总数据量大于2轮 batch大于4轮"。两个子条件: ① B 总量 < 全核 L1 之和:B 小到可摊到所有核各存一份、整体只搬一遍,A 全载没有搬运量优势; ② 每核循环轮数 < 4:A 全载的收益是"A 加载一次、B 流水多轮复用",每核 <4 轮则复用收益抵不过 A 驻留占用的 L1 空间(B 的 stepK/buffer 会被压小)。 取反语义:拒绝条件是"①且②同时成立",即进入全载 ⟺ B 足够大(必然被反复搬运)或 轮数足够多(每核≥4 轮),二者满足其一即值得 |
| 6 | alignMatASize × 2 > l1Size → 拒绝 | 全载可行性:A 全量 16 对齐数据 ×2(L1 上 A 区开 ping-pong 双 buffer 保持流水)≤ 512KB L1,否则物理装不下 |
remainL1Size = l1Size − (aL1Size + biasSize) —— A 全载驻留后剩余的 L1;maxBaseNWithL1 = remainL1 / (baseK×dtype×2)(剩余 L1 给 B 开 DB,每份 baseN×baseK)与 maxBaseNWithL0cDb = l0CSize / (baseM×4B×2)(L0C 双缓冲下 fp32 输出块须装得下),取小并 16 对齐;baseN = min(原baseN, maxBaseN, balanceBaseN);N 为内轴且 baseN>128B/dtype 时向下 128B 对齐(注释 "N内轴时满足128B对齐",056篇内轴对齐粒度);maxStepK = min(CeilDiv(k,baseK), remainL1/(baseK×baseN×2×dtype), 4)——注释 "stepK最大不超过4":限制 L1 上 B 的流水级数,级数过多收益递减且挤压 buffer(4 为源码经验值);maxStepK×baseK 不满足 256B 对齐、且 baseK 翻倍后 L0A 装得下((baseK×2)×baseM×dtype×2 ≤ l0ASize)时:baseK <<= 1 重算。注释:"B矩阵K为内轴,k_bl1不满足256B对齐,尝试baseK放大一倍,提升B矩阵搬运效率"——K 内轴 256B 对齐才能发挥搬运带宽(056篇/950白皮书 §4.3.2 L2 128B Sector);baseN×baseK×stepK×dtype ≥ 48KB(L1_SINGLE_SIZE_LIMIT,注释 "L1搬运量约束"——单次 L1 搬运量阈值;注意:168篇的官方经验值是"单次搬运 16KB 以上发挥带宽最佳",48KB 为源码更保守的经验值)且 K 内轴时 kL1 % (256B/dtype) == 0(注释 "K内轴时约束kL1 256B对齐,发挥带宽能力");都不满足则放大到 maxStepK 兜底;depthA1 = stepM × stepKa(A 全载:深度 = 整个 A 的 M×K 块数,即 A 的所有块都在 L1 有槽位)、depthB1 = stepKb × 2(B 流水 DB);singleCoreM = m(A 全载单核 M 向全覆盖)、singleCoreN = baseN;bL1TensorSize×4 + aL1TensorSize ≤ l1Size 则 B 区开 4 buffer(注释 "l1开2db后依然只使用了一半的空间,则开启4 db。该字段仅在基础api场景生效")。4 buffer 依据:文档无"4 buffer"直接依据(高阶 API 框架 db 只取 1/2;类似思想见 208篇 NBuffer33 模板的 3 份错峰:MTE2 Bound 时错开搬运流水、减少单次搬运量);dbL0C = baseM×baseN×4B×2 ≤ l0CSize ? 2 : 1;usedCoreNum = min(CeilDiv(n, baseN) × batchC, aicNum) —— 并行任务 = N 向条带数 × batchC,batch 维提供主要并行度;tailCnt = nCnt % aicNum 个核有活;把每个尾块沿 N 向再切成 tailInfo.nCnt 份,使尾轮工作核数 tailCnt × nCnt 逼近 aicNum(条件 (nCnt+1)×tailCnt ≤ aicNum);若 N 是 B 内轴还要求切分后每份内轴字节 > 128B(避免内轴过短搬运效率崩塌)。192篇(L1 长驻优化)给出精确核算:L1=512K,左矩阵 992K、右矩阵 16K(fp16),无法同时全载。反例(不长驻):A 分 2 块、B 分 2 块,加载顺序"A1→B1→B2→A2→B1→B2",总搬运 6 次(A×2 + B×4,B 被每个 A 块周期重复搬);正例(16K 的 B 全载长驻):循环内只搬 A1、A2,共 3 次(B×1 + A×2)。BMM 把这一收益再放大 batch 倍——被全载侧的复用次数 ×= batchB,这就是为什么条件 5 用"B 总量 vs 全核 L1 之和"和"每核循环 ≥4 轮"两个尺度衡量复用收益。对带宽相对弱的 950PR(1.6TB/s,DT 为 4TB/s),消除重复搬运的价值更高。
-BatchMatMulAswAL1FullLoadKernel:关键类型重定义 A_TYPE_NEW = MatmulL1GmType<TPosition::TSCM, ...>——告诉 MatmulImpl 该操作数源在 L1(TSCM) 而非 GM。Process 仅两步:① AswAL1FullLoadKernelCopyInA1() 一次性 Nd2Nz DataCopy 把本核 singleCoreM×Ka 的 A 切片搬入 L1(TQue 深度 1、无 DB——只搬一次无需双缓冲);② 主循环与 ASW 相同(复用同一套滑窗 swizzle),但 mm_.SetTensorA(al1Local, ...) 直接给 L1 地址,并 SetOrgShape(singleCoreM, N, Ka)。与普通 ASW 的本质差异:A 的 GM→L1 搬移次数从"每个基本块一次"降为"整个 kernel 一次"。BL1 完全镜像(B 全载、CalCopyBL1 Nd2Nz/Nz2Nz 区分 GM 格式)。tilingKey:Model=BASIC + FullLoad=A_FULL_LOAD / B_FULL_LOAD。
tiling:arch35/batch_matmul_v3_asw_basic_tiling.cpp + batch_matmul_v3_asw_tiling.cpp;公共:mat_mul_v3/arch35/matmul_v3_tiling_helper.cpp(ResetBase/GetRebalanceBlock/CalL1TilingDefault/GetAswWindowLen);kernel:batch_mat_mul_v3_asw_{kernel,block}_advanced.h
-前面 9 个特判都不命中的通用 case:batch 量适中、M/N 够大、无广播或复杂广播。ASW_BASIC 是带约束的通用路径(基础/张量 API + cubeBound 模型寻优),BASE(999) 是无条件兜底(高阶 API 默认参数)。ASW = Adaptive Sliding Window(自适应滑窗),命名佐证见 ops-nn 仓 dual_level_quant_batch_matmul 的 DualLevelQuantBatchMatmulTilingASW(adaptive_sliding_window);滑窗指 kernel 侧块到核映射的"滑窗+蛇形"swizzle(详见第 6 章)。
第 1 步 ResetBase(DAV_3510 默认值及依据):baseM=256、baseN=256(注释 "256 is better base"——DAV_3510 cube 以 256×256×(128B/dtype) 为最优基本块)、baseK=128B/dtype(L0A/L0B 单次载入内轴的最小高效粒度,056篇 128B)、stepM=stepN=1、iterateOrder=列优先、dbL0C=1、singleCoreK=全 K。
-第 2 步 GetRebalanceBlock:cubeBound 解析模型寻优 baseM/baseN(helper.cpp L387-492)——这是通用路径的"基本块寻优器":
-computePower = 单核算力 × aicNum(fp32 时 ÷16);cubeBoundEdge = l2BW/computePower // ① 理论阈值:L2 供数速率 ÷ cube 耗数速率
- + l2CacheUsage × (1 − l2BW/hbmBW) × cmr // ② L2 装不下工作集时的访存惩罚
- − (1 + l2BW/hbmBW) / kValue // ③ K 向复用修正
-cmr = (m+n)/(m×n) l2CacheUsage = max(batch×(m+n)×k×dtype / l2Size, 1.0)
-逐项含义:cube bound 的判据是"每输出块的相对搬运量 1/baseM+1/baseN ≤ edge";①是纯 L2 供数能力下的理论 edge;②当工作集超 L2(l2CacheUsage>1)时按 HBM 与 L2 带宽差抬高 edge(更容易判成 memory bound,倾向更大基本块);③K 越大 A/B 复用越充分,edge 越小(更容易 compute bound)。进入枚举前 edge 先乘 CUBE_BOUND_RATIO=0.85 预留 15% 余量(防实测达不到理论带宽);fixpBoundEdge = m×n×hbmBW / ((m+n)×l2BW),k < fixpBoundEdge(fixpipe bound)时 baseN 强制 256B 对齐(注释 "fixpipe bound场景下,要求baseN是256B对齐,发挥搬出带宽");cubeBoundParam/balanceRate(单位负载均衡率的搬运代价)为综合分,比值差 < EPSILON(1e-9) 时取更均衡者;命中 cubeBoundCond 后 edge 收紧为当前 param,形成单调收敛;totalRound = batch×mCnt×nCnt,主轮数 mainRound = ⌈totalRound/核数⌉−1,尾轮按二维 √ 拆分估计(totalTailSplit 按 √ 开方分两维);尾块拆分后 baseM×baseN/split < 4096(MIN_TATL_BLOCK_SIZE)则无拆分收益;BMM(batchInfo≠)直接用均值/最忙核的简单比率——batch 维已摊薄尾块效应;usedCoreNum = min(batch×mCore×nCore, aicNum);dbL0C/ubDB 按容量置 2/1。第 3 步 CalL1Tiling(CalL1TilingDefault,L45-80):maxStepK = min(CeilDiv(k,baseK), 8)(注释 "Shape约束 && issue queue约束"——stepK≤8 又是 issue queue 经验值);K 内轴对齐单位 512B/dtype(BMM 固定);stepK 从 1 枚举,容量约束 (aL1+bL1)×2 ≤ totalL1 且 max(aL1,bL1)×4 ≤ l1Size(单边开 DB 不得超 L1 一半);更新条件优先级:首个可行解 > 256B 对齐 > 单次搬运 ≥48KB("优先对齐,对齐不了就把单次搬运量做大");得 stepKa=stepKb、depthA1=depthB1=stepK×2。
l1BufferNum = abL1TensorSize×4 ≤ l1Size ? 4 : 2(注释 "l1开2db后依然只使用了一半的空间,则开启4 db");k > 阈值(K>2^28 时阈值 8192,否则 1024;FP32_K_SWITCH_BASE/FP32_SPLIT_K_BASE1/2)判为 splitK 走 BASIC_LEVEL;纯 BatchMatMulV3 节点、连续、非 splitK、未禁用 tensor api → TENSOR_LEVEL(注释 "非切K且连续场景下才允许切换tensor api实现")。fp32 大 K 单核切 K 的依据:fp32 累加对 K 长度敏感,单核内把 K 分段累加(splitKRound)可降低长 K 的累加误差与 L0C 压力。IsCapable 恒 true;DoOpTiling 仅 ResetBase + CalL1Tiling + ubDB 判定——不做 GetRebalanceBlock(用默认 256/256/128B 基块,最保守);apiLevel 兜底为 HIGH_LEVEL(非 BASIC);batchModel = (apiLevel==TENSOR_LEVEL) ? BROADCAST_BATCH_MODEL : BATCH_MODEL——TENSOR_LEVEL 时高阶 API 内建任意 batch 广播支持(枚举注释 "只用于BatchMatMul广播场景"),这保证任意广播形态的 case 都不会漏接(分支 4 只接"单边单轴",其余广播全部汇到这里)。
sqrtNum = sqrt(aicNum);
-for (factor = sqrtNum; factor ≥ 1; --factor)
- if (aicNum % factor == 0) return factor; // 不超过 √aicNum 的最大因子
-为什么这样取:滑窗是 M 向的分组宽度(kernel 侧 mainWindow = min(aswWindowLen, mCnt),窗内 N 向蛇形扫描,见 §6.1)。窗口越接近正方形,同窗口内 A 行块与 B 列块的 L2 足迹越小、并发核访问局部性越好——√aicNum 是"方形"的直觉;取 aicNum 的因子保证整窗被核数均分、窗口边界不碎。例:aicNum=32 → 窗长 4;25 → 5;24 → 4;质数核数退化为 1(退化为普通行优先)。窗长写入 tilingData.aswWindowLen 供 kernel 使用。
-| 参数 / 阈值 | 出现位置 | 取值 | 依据来源 |
|---|---|---|---|
| fractal 16 对齐(baseM/N/K) | 全部分支 | 16 | 文档:054篇 cube 一拍 16×16×16;064篇 一个分形恰好 512B |
| L0A/L0B 512B 对齐 | 全部分支 | 512B | 文档:054篇对齐要求;064篇 分形=512B |
| 内轴 128B / 256B / 512B 对齐 | baseK、stepK、fixpipe 判定 | 128/256/512B | 文档:056篇"搬运拆成 128B/256B/512B,非对齐向上取整";950白皮书 §4.3.2 L2 512B CacheLine/128B Sector;源码注释"BMM 固定内轴 512B 对齐" |
| DB ×2 | 所有容量公式 | 2 | 文档:067篇 InitBuffer 块数=2 的乒乓机制;624篇 db 只取 1/2 |
| batchC ≥ 128(TO_MUL) | 分支1 | 2×aivNum | 源码注释:"batch数大于等于128(AIV CoreNum *2) 才能开pingpong" |
| N>256B/dtype(TO_MUL) | 分支1 | 256B | 源码注释 + 054篇 Vector 每拍 256B |
| K ≥ max(8192, aic×256B/dtype)(StreamK) | 分支2 | 8192 / 256B | 逻辑推断:每核 K 段至少一个 256B 内轴搬运块 + 绝对收益门槛 |
| fp32 K ≤ 200 万(StreamK) | 分支2 | 2×10⁶ | 源码注释(binary accumulation 精度),具体数值无文档依据 |
| batchC×mCnt×nCnt ≤ aicNum/2(StreamK) | 分支2 | 1/2 | 逻辑推断:MN 并行度不足一半核数才切 K;同时保证 kCnt≥2 |
| MIN_BATCH_L0 = 4(mergebatch) | 分支3 | 4 | 源码注释:"each aic should process at least 4 batchs" |
| baseK ≤ 64(mergebatch) | 分支3 | 64 | 源码注释("threshold of basek is 64")+ 推断:压 K 深度换 L0B 合并度 |
| iterBatchL1 ≥ 2(iterbatch 系) | 分支4/5/6 | 2 | 源码注释:广播/驻留收益的前提是至少 2 batch 复用 |
| mmadCount = 8 / iterBatch ≤ 4 | 分支5/6 | 8 / 4 | 源码经验值:注释 "cube count which will cause issuequene";文档仅有 056篇"MTE1/MMAD 队列深度 32"的机制描述,8/4 无官方出处 |
| fullCopySize = 64KB / L1_SINGLE_SIZE_LIMIT = 48KB | 分支5、全载、CalL1Tiling | 64KB / 48KB | 源码经验值:注释 "moving once which can use full of bandwith";168篇官方经验值为 16KB(更宽松) |
| 均衡率 0.8 / balanceRateEdge 0.9 / CUBE_BOUND_RATIO 0.85 | 分支4/5/6、GetRebalanceBlock | 0.8 / 0.9 / 0.85 | 源码经验值(注释仅说明用途,无推导) |
| m/n ≤ 256(全载) | 分支7/8 | 256 = baseM/baseN | 逻辑:单 base 块覆盖全载轴,stepM/stepN=1 |
| "值得全载":总量 ≥ L1×aic 或 轮数 ≥ 4/核 | 分支7/8 | 4 轮 | 源码注释 + 192篇 6→3 次搬运的复用收益模型 |
| 全载数据 ×2 ≤ L1 | 分支7/8 | ×2 | 逻辑:L1 全载区开 ping-pong 双 buffer |
| stepK ≤ 4(全载)/ ≤ 8(通用) | 分支7/8/9 | 4 / 8 | 源码注释:"stepK最大不超过4"、"issue queue约束"(8);4 无进一步文档依据 |
| ASW 窗长 = aicNum 的 ≤√aicNum 最大因子 | 分支9/BASE | √aicNum | 逻辑推断:方形窗 L2 足迹最小 + 因子保证整窗均分 |
| workspace 20MB RPC | 基类 | 20MB | 源码注释:"20MB workspace for RPC"(核间通信区) |
| ND_FIXPIPE_1_1/1_2 启用条件 | 分支5、StreamK、helper | N 非对齐 + 1C:2V | 源码(文档无该枚举说明);机制支撑:054/087篇 fixpipe 随路 NZ2ND、CrossCoreFlag MODE_4 |
BMM v3 中"分块执行顺序 / 哪个分块在哪个核上做"由三套机制实现,分别服务于不同的分支族:
- -核心代码在 BatchMatMulAswBlock::UpdateBasicIndex()(arch35/batch_mat_mul_v3_asw_block_advanced.h):
params_.index = newBlockIdx + roundIdx * usedCoreNum; // 块→核:每轮核号错开
-uint64_t matIndex = params_.index % (params_.mCnt * params_.nCnt); // batch 内 m×n 平面下标
-uint64_t rowIdx = matIndex / params_.nCnt / params_.mainWindow; // 第几个划窗行
-if (rowIdx < params_.mainRow) { // 主划窗区
- params_.mCntIndex = rowIdx * params_.mainWindow + matIndex % params_.mainWindow;
- params_.nCntIndex = (matIndex / params_.mainWindow) % params_.nCnt;
-} else { // 尾划窗区(tailWindow)
- ...
-}
-if (rowIdx % 2 != 0) { // 奇数划窗行反向扫描(蛇形)
- params_.nCntIndex = params_.nCnt - 1 - params_.nCntIndex;
-}
-机制解读:
-index = blockIdx + round×usedCoreNum,第 r 轮核 c 处理第 c + r×usedCoreNum 个基本块,天然错轮;mainWindow = min(aswWindowLen, mCnt) 个 m 块作为一行窗口,遍历顺序是"窗口内先 m 后 n"——同一 A 行窗口内的所有 n 块连续被计算,A 的 GM/L2 数据在一个窗口内被 nCnt 次复用,最大化 L2 命中。窗长由 host 侧 GetAswWindowLen() 计算:取 aicNum 的不超过 sqrt(aicNum) 的最大因子(如 32 核 → 窗长 4,25 核 → 5),写入 tilingData.aswWindowLen。这个选择的直觉是:窗口越接近正方形,同窗口内 A 块与 B 块的 L2 足迹越小、并发核的访问局部性越好;取 aicNum 的因子保证整窗能被核数均分;BatchMatMulCommonBaseBlock::UpdateBasicIndex()(batch_mat_mul_v3_com_base_block.h):
uint64_t newBlockIdx = (GetBlockIdx() + usedCoreNum - params_.blockIdxStart) % usedCoreNum
- + roundIdx * usedCoreNum;
-uint64_t mIdx = newBlockIdx % params_.mCntUse;
-uint64_t nIdx = (newBlockIdx + newBlockIdx / MMLcm(params_.mCntUse, params_.nCntUse)) % params_.nCntUse;
-params_.index = mIdx * params_.nCntUse + nIdx;
-线性 id 先取 m,再用 newBlockIdx / lcm(mCnt,nCnt) 给 n 加相位偏移,使同一时刻各核落在 m×n 平面的不同对角线上,避免多核同时抢同一行 A / 同一列 B 的 GM 流量;配合每个 L2 tile 边界轮转起始核号(blockIdxStart)与 mTile 间蛇形(reverse 翻转 nTile 遍历方向)。
Host 侧 BatchMatmulV3BaseTiling::DoL2CacheAndCalOrderTiling()(batch_mat_mul_v3_base_tiling.cpp)决定核间执行的宏观顺序:
transConflict = max(CeilDiv(usedCoreNum,mCnt), CeilDiv(usedCoreNum,nCnt))——同一时刻并发核访问同一 A/B 块的最大冲突数;nCnt < usedCoreNum < mCnt 时,从 CeilDiv(usedCoreNum,nCnt)×nCnt 递减搜索 newMcnt,直到尾块占比 ≤ 0.5(TAIL_CONFLICT_RATIO)——让第一轮 M 方向无冲突且尾部浪费不超过一半;isBigSize(总数据量 > l2Ratio×100MB)且 cBatchDimAll < usedCoreNum 且 transConflict ≤ 6,命中后写 mTileCntL2/mTileBlock/nTileBlock;CheckandSetDiagonalConflict():不做 L2 分块时,比较"错位分核"与"行优先"的冲突数,错位更优才改写分组;calOrder(父类 matmul_v3_base_tiling.cpp 注释明确):0 = M 优先(行优先),1 = N 优先(列优先),由 isNKMsmallK 判定,kernel 侧 params_.rowOrder = tilingL2.calOrder 控制块派发方向。L1 全载模板直接 mTileCntL2=nTileCntL2=1,不做核间 swizzle(全载后 A/B 已在 L1,无 L2 复用问题)。这些分支的调度粒度是"一组 batch"而非 m×n 基本块,核映射为简单轮询 for (tileIdx = curBlockIdx; tileIdx < tileNum; tileIdx += blockNum),坐标 {0,0,0, tileIdx×iterBatchL1};mergebatch 额外按主块/主尾块/最终尾块做负载均衡。因为并行维度只有 batch,不存在 m×n 平面上的访存冲突问题,无需滑窗/错位。
| 机制 | 所在分支 | 解决的问题 | 核心参数 |
|---|---|---|---|
| 滑窗+蛇形(ASW) | ASW_BASIC / BASE / 全载系(arch35) | m×n 基本块遍历的 L2 复用 | aswWindowLen = aicNum 的 ≤√aicNum 最大因子 |
| 对角线错位 | 老路径通用 kernel | 并发核抢同一行 A / 列 B 的 GM 冲突 | lcm(mCnt,nCnt) 相位偏移 + 起始核轮转 |
| calOrder 行/列优先 + L2 tile 分组 | 老路径 host tiling | 大 shape 下 L2 容量约束的宏观遍历方向 | mTileBlock/nTileBlock/transConflict ≤ 6 |
| batch 组轮询 | iterbatch / mergebatch / matmul2mul | batch 为主并行维度时的均摊 | iterBatchL1 / batchAL1 / 主尾块均摊 |
arch35 路径的 tilingKey 由 BatchMatMulV3TilingKey::GetTilingKey() 生成,7 个位段(声明于 op_kernel/arch35/batch_mat_mul_v3_tiling_key.h,低位到高位):
| 位段 | 位宽 | 取值 | 决定什么 |
|---|---|---|---|
| BATCH_API_LEVEL | 4 bit | 0=HIGH_LEVEL(MatmulImpl 高阶 API)/ 1=BASIC_LEVEL(BasicAPI/Cmct)/ 2=TENSOR_LEVEL(张量级/Blaze) | 用哪一层搬移计算抽象 |
| BATCH_A_TRANS / BATCH_B_TRANS | 2+2 bit | 0/1 | layout/分形方向 |
| BATCH_ITER_MODEL | 4 bit | 0=FOR_BATCH / 1=ITER_BATCH_SINGLE_BIAS / 2=BATCH_MATMUL_TO_MUL / 3=MERGE_BATCH / 4=FOR_FUSED_BATCH / 5=BROADCAST_BATCH / 6=ITER_BATCH_BROADCAST_A / 7=ITER_BATCH_BROADCAST_B | batch 处理模式(≈ 分支 id) |
| BMODEL | 4 bit | 0=BASIC / 1=STREAM_K / 2=K_EQUAL_ZERO | 计算模型特判 |
| BATCH_FULL_LOAD | 4 bit | 0=NONE / 1=A_FULL_LOAD / 2=B_FULL_LOAD / 3=AB_FULL_LOAD | L1 全载模式 |
| BATCH_L0C2OUT_MODEL | 4 bit | 0=ON_THE_FLY / 1=1V1_ND_ALIG_FIXPIPE / 2=1V2_ND_ALIG_FIXPIPE | L0C→GM 输出通路(fixpipe) |
每个合法位段组合通过 ASCENDC_TPL_SEL 宏同时绑定三样东西:kernel 类型(AIC_ONLY / AIV_ONLY / MIX_AIC_1_2)、tiling 结构体(BatchMatMulV3TilingData / ...IterBatchBasicTilingData / ...MergeBatchBasicTilingData / ...ToMulBasicTilingData / MatMulV3KEqZeroBasicTilingData 等)、kernel 模板实例化。编译期为每个 tilingKey 生成确定的 kernel,运行时零分发开销。
kernel 入口是一张编译期 if constexpr 分发树(arch35/batch_mat_mul_v3.cpp),节选主干:
// tilingKey 位段 → kernel 类(节选)
-HIGH_LEVEL + BASIC + NO_FULL_LOAD + FOR_BATCH → BatchMatMulAswKernel + BatchMatMulAswBlock(ASW 主分支)
-BASIC_LEVEL + BASIC + A_FULL_LOAD + FOR_BATCH → MatMulActKernel<..., A_FULL_LOAD_MODE>
-TENSOR_LEVEL+ BASIC + B_FULL_LOAD + FOR_BATCH → Blaze: MatMulBL1FullLoadKernel
-TENSOR/BASIC+ STREAM_K + FOR_BATCH → MatMulStreamKKernel(MIX_AIC_1_2)
-BASIC_LEVEL + BASIC + ITER_BATCH_SINGLE_BIAS → BatchMatMulActIterBatchKernel(Cmct,可带 ND_FIXPIPE_1_2)
-HIGH_LEVEL + BASIC + ITER_BATCH_SINGLE_BIAS → BatchMatMulMultiBatchKernel(IterateBatch 高阶版)
-BASIC_LEVEL + BASIC + MERGE_BATCH → BatchMatMulActMergeBatchKernel(Cmct)
-BASIC_LEVEL + BATCH_MATMUL_TO_MUL(AIV_ONLY) → BatchMatMulToMulActKernel(K=1)
-BASIC_LEVEL + K_EQUAL_ZERO(AIV_ONLY) → MatMulInputKEqZeroClearOutput(K=0)
-TENSOR_LEVEL+ BROADCAST_BATCH → Blaze: BatchMatMulBroadcastKernel
-TENSOR_LEVEL+ ITER_BATCH_BROADCAST_A/B → Blaze: BatchMatMulIterBatchBroadcastKernel
-batchC×mCnt×nCnt ≤ aicNum/2 保证了只有切 K 收益明确的 case 才分流出去,其余留在通用路径。非 DAV_3510/RESV 平台走 BatchMatmulV3BaseTiling::DoLibApiTiling()(batch_mat_mul_v3_base_tiling.cpp),不是短路遍历而是固定函数序列 + flag 覆盖:
对应 kernel(op_kernel/batch_mat_mul_v3.cpp 分发):Common(对角错位 swizzle)、UnAlignCommon(AIV 做 ND2NZ + AIC cube 的 CV 协同,CrossCoreFlag 双缓冲)、AL1/BL1_FULLLOAD(MatmulCallBackFunc 回调式全载)、MultiBatch / MultiBatchOut、MultiBatchL1FullLoad(多 batch Nd2Nz 一次搬 L1 内层复用)、VECTOR_FULLLOAD(BatchMatmulVectorKernel:UB 流水 Mul+ReduceSum 的 GEMV)。
- - -说明:本文所有分支条件、阈值、代码片段均直接引用自上述源码文件;硬件数值均标注来源。资料未覆盖的规格项(MTE 各通路带宽、SSBuffer 容量、芯片频率等)未做编造。
- -