diff --git a/Matmul/3_源码对比/3.1_mat_mul_v3源码解析.md b/Matmul/3_源码对比/3.1_mat_mul_v3源码解析.md index 35de11c..b17ad02 100644 --- a/Matmul/3_源码对比/3.1_mat_mul_v3源码解析.md +++ b/Matmul/3_源码对比/3.1_mat_mul_v3源码解析.md @@ -1,40 +1,39 @@ # mat_mul_v3 算子源码分析 —— 分支决策与 tiling/swizzle 实现 -> 目标芯片:昇腾 950PR(NpuArch = DAV_3510,32 AIC / 64 AIV,1.65GHz) -> 源码位置:`昇腾NPU知识库/代码仓/ops-nn/matmul/mat_mul_v3/`(op_host 为 tiling 层,op_kernel 为 device 层) -> 算子接口:`aclnnMatmul` / `aclnnAddmm` / `aclnnMm` 的后端,是最常用 Matmul 实现(32 个 matmul 变体中的核心) -> 配套参考:同仓 `昇腾950PR/昇腾950PR架构解读.md`(硬件规格)、官方性能建模文档 `matmul_story/docs/matmul_performance.md` +> 目标芯片:昇腾 950PR(NpuArch = DAV_3510,32 AIC / 64 AIV,1.65GHz,GM 128GB/1.6TB/s,L2 128MB/5.2TB/s) +> 源码位置:`昇腾NPU知识库/代码仓/ops-nn/matmul/mat_mul_v3/`(op_host 为 Host tiling 层,op_kernel 为 Device kernel 层) +> 算子接口:`aclnnMatmul` / `aclnnAddmm` / `aclnnMm` 后端,最常用 Matmul 实现 +> 配套参考:同仓 `昇腾950PR/昇腾950PR架构解读.md`、官方建模文档 `matmul_story/docs/matmul_performance.md` --- ## 0. 源码结构总览 -mat_mul_v3 采用 **Host Tiling(CPU)+ Device Kernel(NPU)** 两层结构,中间通过 **tiling key(位域编码)** 传递决策结果: +mat_mul_v3 采用 **Host Tiling(CPU)+ Device Kernel(NPU)** 两层结构,中间以 **tiling key(位域编码)+ tiling data(参数结构体)** 传递决策: ``` -┌─────────────────────────── Host(CPU 侧,算子启动前)───────────────────────────┐ -│ MatMulV3Tiling::DoTiling() │ -│ └─ MMTilingRegistry 按 priority 顺序尝试各 tiling 类(策略模式) │ -│ ├─ MatMulV3KEqZeroTiling (priority 0) │ -│ ├─ MatMulV3ToMulTiling (priority 1) │ -│ ├─ MatMulV3ToVectorTiling (priority 2) │ -│ ├─ MatMulV3BasicStreamKTiling (priority 3) │ -│ ├─ MatMulV3BasicAswtTiling (priority 4) │ -│ └─ MatMulV3AswTiling (priority 999, 兜底) │ -│ 每个类的 IsCapable() 判定是否满足条件,满足则 DoOpTiling() 计算 tiling 参数,│ -│ 编码成 tiling key + tiling data 下发给 device。 │ -└─────────────────────────────────────────────────────────────────────────────────┘ - │ tiling key + tiling data +┌─────────────────────────── Host(CPU 侧,算子启动前执行一次)───────────────────────────┐ +│ MatMulV3Tiling::DoTiling() │ +│ └─ MMTilingRegistry::DoTilingImpl() 按 priority 依次尝试各 tiling 类(策略模式) │ +│ ├─ MatMulV3KEqZeroTiling (priority 0) │ +│ ├─ MatMulV3ToMulTiling (priority 1) │ +│ ├─ MatMulV3ToVectorTiling (priority 2) │ +│ ├─ MatMulV3BasicStreamKTiling (priority 3) │ +│ ├─ MatMulV3BasicAswtTiling (priority 4) │ +│ └─ MatMulV3AswTiling (priority 999, 兜底) │ +│ 每个类:IsCapable() 判条件 → DoOpTiling() 算参数 → 编码 tiling key + tiling data │ +└─────────────────────────────────────────────────────────────────────────────────────────┘ + │ tiling key(分支选择结果)+ tiling data(参数) ▼ -┌─────────────────────────── Device(NPU 侧,每核执行)───────────────────────────┐ -│ mat_mul_v3() 入口:if constexpr 按 (ApiLevel, FullLoad, Model, L0C2Out) 分发 │ -│ ├─ MatMulInputKEqZeroClearOutput K==0 清零 │ -│ ├─ MatMulToMulActKernel / MatMulToVectorActKernel 退化向量乘 │ -│ ├─ MatMulStreamKKernel / MatMulStreamKSplitKKernel StreamK 切 K │ -│ ├─ MatMulBasicKernel / MatMulBasicSplitKKernel ASWT 基础模板 │ -│ ├─ MatMulAL1FullLoadKernel / MatMulBL1FullLoadKernel A/B 全载 │ -│ └─ MatMulFixpipeOptiTensorKernel Fixpipe 随路搬出优化 │ -└─────────────────────────────────────────────────────────────────────────────────┘ +┌─────────────────────────── Device(NPU 侧,每个核独立执行)────────────────────────────┐ +│ mat_mul_v3() 入口:if constexpr 按 (ApiLevel, FullLoad, Model, L0C2Out) 四元组分发 │ +│ ├─ MatMulInputKEqZeroClearOutput K==0 清零 │ +│ ├─ MatMulToMulActKernel / MatMulToVectorActKernel 退化向量乘 │ +│ ├─ MatMulStreamKKernel / MatMulStreamKSplitKKernel StreamK 切 K │ +│ ├─ MatMulBasicKernel / MatMulBasicSplitKKernel ASWT 基础模板 │ +│ ├─ MatMulAL1FullLoadKernel / MatMulBL1FullLoadKernel A/B 全载 │ +│ └─ MatMulFixpipeOptiTensorKernel Fixpipe 随路搬出优化 │ +└─────────────────────────────────────────────────────────────────────────────────────────┘ ``` **关键枚举**(`mat_mul_v3_tiling_key_public.h`): @@ -44,7 +43,31 @@ mat_mul_v3 采用 **Host Tiling(CPU)+ Device Kernel(NPU)** 两层结构 | MatMulV3Model | BASIC=0 / STREAM_K=1 / K_EQUAL_ZERO=2 / TO_MUL=3 / TO_MULTI_MUL=4 / SLICE=5 / BASIC_SPLIT_K=6 / SK_SPLIT_K=7 | 主计算模型 | | MatMulV3FullLoad | NONE=0 / A_FULL_LOAD=1 / B_FULL_LOAD=2 / AB_FULL_LOAD=3 | L1 全载模式 | | MatMulV3L0C2Out | ON_THE_FLY=0 / ND_FIXPIPE_1_1=1 / ND_FIXPIPE_1_2=2 | L0C 输出方式 | -| MatMulV3ApiLevel | HIGH=0 / BASIC=1 / TENSOR=2 | 编程接口层级(kernel 用 BASIC/TENSOR) | +| MatMulV3ApiLevel | HIGH=0 / BASIC=1 / TENSOR=2 | 编程接口层级(kernel 实际用 BASIC/TENSOR) | + +**贯穿全文的符号约定(维度标注)**: + +| 符号 | 维度 | 含义 | +|---|---|---| +| `M, N, K` | 元素个数 | 矩阵 `C[M,N] = A[M,K] · B[K,N]` 的三维 | +| `dtypeA, dtypeB` | 字节/元素 | A、B 的 dtype 字节数(FP16/BF16=2,FP32=4) | +| `baseM, baseN, baseK` | 元素个数 | L0 分块尺寸(L0A/L0B 一次装的数据块),对齐 16 | +| `singleCoreM/N/K` | 元素个数 | 单核负责的 M/N/K 范围(= base 或更大) | +| `mL1, nL1, kL1` | 元素个数 | L1 中的分块尺寸(= singleCore 或 base,见各分支) | +| `stepKa, stepKb` | 无量纲(个数) | K 方向 L1 分片步数(K 在 L1 中切几段) | +| `depthA1, depthB1` | 无量纲(个数) | L1 流水深度(A/B 在 L1 中缓存的 tile 层数) | +| `mCnt, nCnt` | 无量纲(个数) | M/N 方向 base 块个数 `mCnt=⌈M/baseM⌉` | +| `aicNum, aivNum` | 个 | AIC 核数=32、AIV 核数=64 | + +**关键硬件规格(原理性推导的输入,来自 950PR 架构解读)**: + +| 规格 | 数值 | 推导用途 | +|---|---|---| +| Cube 每拍 MAC 数 | 16×16×16 = 4096 MAC = 8192 FLOP | 单 AIC BF16 算力 = 1.65GHz × 8192 ≈ **13.5 TFLOPS** | +| L0A / L0B / L0C / L1 / UB | 64KB / 64KB / 256KB / 512KB / 512KB | 分块尺寸上限(`baseM·baseN·4B ≤ L0C` → baseM=baseN=256 上限) | +| MTE1 / MTE3 | 256 BPC ≈ 422.4 GB/s(单 AIC) | 核内 L1→L0、L0C→GM 搬移耗时 | +| GM / L2 带宽 | 1.6 TB/s / 5.2 TB/s | MTE2 综合带宽(重复读走 L2 才喂饱 Cube) | +| 单 AIC 分摊 HBM | 1.6TB/s ÷ 32 ≈ 50 GB/s | 算术强度要求 = 13.5T/50G ≈ **270 FLOP/Byte**(带宽极紧张) | --- @@ -56,199 +79,416 @@ mat_mul_v3 采用 **Host Tiling(CPU)+ Device Kernel(NPU)** 两层结构 { NpuArch::DAV_3510, {K_EQUAL_ZERO, TO_MUL, TO_MULTI_MUL, BASIC_STREAM_K, BASIC_ASWT} } ``` -各 tiling 类按 priority 注册(`MM_REGISTER_TILING_TEMPLATE`),`MMTilingRegistry::DoTilingImpl` 按优先级逐个 `IsCapable()` + `DoTiling()`,**第一个成功者胜出**。 +`MMTilingRegistry::DoTilingImpl` 按 priority 顺序逐个 `IsCapable() + DoTiling()`,**第一个成功者胜出**(每个 tiling 类的 `DoTiling` 返回失败则尝试下一优先级)。 -| Priority | 分支 | 触发条件(IsCapable) | Device Kernel | -|---|---|---|---| -| 0 | **K_EQUAL_ZERO** | 无 bias 且 `K == 0` | MatMulInputKEqZeroClearOutput | -| 1 | **TO_MUL** | 高精度FP32(`isForceGrpAccForFp32`) ∧ 非slice ∧ (M==1 ∨ N==1) ∧ A/B 均 FP32 | MatMulToMulActKernel | -| 2 | **TO_MULTI_MUL** | 高精度FP32 ∧ 非slice ∧ `!ATrans ∧ BTrans` ∧ 无bias ∧ A/B 均 FP32 | MatMulToVectorActKernel | -| 3 | **BASIC_STREAM_K** | deterministic≤1 ∧ A为ND ∧ 非slice ∧ aivNum==2·aicNum ∧ (SK 或 DPSK 条件) | MatMulStreamKKernel / StreamKSplitK / StreamKActKernel | -| 4 | **BASIC_ASWT** | 无条件(主分支) | MatMulBasicKernel 等(含 fullLoad/fixpipe 子分支) | -| 999 | **BASE(ASW)** | 无条件(最终兜底,老 Cmct 接口) | MatMulActKernel | +| Priority | 分支 | Model 枚举 | 触发条件(IsCapable) | Device Kernel | +|---|---|---|---|---| +| 0 | K_EQUAL_ZERO | K_EQUAL_ZERO(2) | 无 bias 且 `K==0` | MatMulInputKEqZeroClearOutput | +| 1 | TO_MUL | TO_MUL(3) | 高精度FP32 ∧ 非slice ∧ (M==1 ∨ N==1) ∧ A/B 均 FP32 | MatMulToMulActKernel | +| 2 | TO_MULTI_MUL | TO_MULTI_MUL(4) | 高精度FP32 ∧ 非slice ∧ `!ATrans∧BTrans` ∧ 无bias ∧ FP32 | MatMulToVectorActKernel | +| 3 | BASIC_STREAM_K | STREAM_K(1)/SK_SPLIT_K(7) | deterministic≤1 ∧ A为ND ∧ 非slice ∧ aivNum==2·aicNum ∧ (SK∨DPSK) | MatMulStreamKKernel 等 | +| 4 | BASIC_ASWT | BASIC(0)/SLICE(5)/BASIC_SPLIT_K(6) | 无条件(主分支) | MatMulBasicKernel 等(含全载/fixpipe 子分支) | +| 999 | BASE(ASW) | BASIC(0) | 无条件(最终兜底,老 Cmct 接口) | MatMulActKernel | -> 补充:BASIC_ASWT 还注册了 `DAV_RESV` 平台(supportMmadS8S4),但 DAV_RESV 优先级只有 BASIC_ASWT 一项。本文聚焦 950PR(DAV_3510)。 +> BASIC_ASWT 也注册了 `DAV_RESV` 平台(supportMmadS8S4),其优先级仅 BASIC_ASWT 一项。本文聚焦 950PR(DAV_3510)。 --- ## 2. 各分支详细分析 +> **本章与第 3、4 章的关系**(先交代,避免混淆): +> - **本章(what / why)**:回答"给定 case 会命中哪个分支?该分支的数据流长什么样?为什么这么设计(硬件原理)?"。每个分支的 tiling 参数计算依赖第 3 章的算法,核间编排依赖第 4 章的 swizzle。 +> - **第 3 章(how:参数计算引擎)**:`ResetBase / GetRebalanceBlock / GetBaseK / CalL1Tiling` 是 **ASW 系分支(BASIC_ASWT、BASE)共用的 tiling 参数计算引擎**,StreamK 也复用其中 `ResetBase/GetBaseK/CalL1Tiling`。它回答"baseM/baseN/baseK/singleCore 这些数具体怎么算出来的"。 +> - **第 4 章(how:核间编排)**:`BlockSchedulerAswtBuiltIn`(ASWT)与 `BlockSchedulerStreamKBuiltIn`(StreamK)回答"二维 MN 块流如何映射到一维核索引流、执行顺序如何蛇形滑动、尾轮怎么重切"。它是第 3 章算出的参数在 device 侧的消费方式。 + +--- + ### 2.1 K_EQUAL_ZERO(priority 0) -**条件**:`!hasBias && kValue == 0`。 +**触发条件**:`!hasBias && K == 0`。 + +**原理**:K=0 时 `C[i,j] = 0`,输出恒为全零矩阵。若走 Cube,MMAD 对空 K 做无效计算;而 64 个 AIV 是天然的并行批量写单元,直接对 `C` 的 `M×N` 个元素清零即可。 **实现**(`matmul_v3_k_equal_zero_tiling.cpp` + `mat_mul_input_k_eq_zero_clear_output.h`): -- `totalDataAmount = M * N`,`usedCoreNum = aivNum`(64 个 AIV)。 -- Device 侧 `MatMulInputKEqZeroClearOutput` 直接用 AIV 对输出 `C` 清零,完全不启动 Cube。 -**评价**:K=0 是退化空矩阵乘,输出恒为 0。用 AIV 批量清零、不浪费 Cube 计算单元,实现正确且高效。适用面窄(仅 K==0 且无 bias)。 - -### 2.2 TO_MUL(priority 1) - -**条件**:`isForceGrpAccForFp32`(op_impl_mode_enum == 0x4 高精度)∧ 非 slice ∧ (M==1 ∨ N==1) ∧ A/B 均 FP32。 - -**实现**(`matmul_v3_to_mul_tiling.cpp` + `mat_mul_to_mul_cmct.h`): -- 矩阵乘退化为**向量乘**(M==1 或 N==1 时本质是点积/向量乘),全部由 AIV 完成,不占 Cube。 -- tiling:`baseMN`、`baseK` 由 UB 容量(`ubSize/sizeof(float)`)反推;`loopK = ceil(K/baseK)`;区分 `dataCopyMode`(判断内外轴是否连续搬运)。 -- `usedCoreNum = min(tileNum, aivNum)` 分核。 - -**评价**:M==1 或 N==1 时 Cube 的 16×16×16 分形利用率极低,改用 AIV 向量乘是正确决策。局限:仅覆盖高精度 FP32 模式(`isForceGrpAccForFp32`),FP16/BF16 的 M==1/N==1 场景走不到这里。 - -### 2.3 TO_MULTI_MUL(priority 2) - -**条件**:高精度FP32 ∧ 非slice ∧ `!ATrans && BTrans` ∧ 无 bias ∧ A/B 均 FP32。 - -**实现**(`matmul_v3_to_multi_mul_tiling.cpp` + `mat_mul_to_multi_mul_cmct.h`): -- 同样退化为 AIV 向量乘,但针对 `!ATrans && BTrans` 的特定转置组合(A 行主序、B 列主序,恰好内积方向连续)。 -- `CalcBasicBlock()`:按 mCore/nCore 的核数分配调整 baseM/baseN,使 M/N 方向的核分配均衡(`while baseN >= 2*baseM ...` 启发式均衡)。 - -**评价**:与 TO_MUL 同类,但更窄(限定转置组合、不支持 bias)。两个分支共同说明:**当矩阵乘退化到 M==1 或 N==1 时,源码选择绕开 Cube 走 AIV**,规避 Cube 16 对齐分形粒度的浪费。 - -### 2.4 BASIC_STREAM_K(priority 3)—— StreamK / DPSK - -**IsCapable 前置条件**:`deterministicLevel ≤ 1`(切 K 核间累加顺序不确定,强一致性场景禁用)∧ A 为 ND ∧ 非 slice ∧ `aivNum == 2·aicNum`。 - -**SK 模式条件**(`CheckStreamKSKTilingDav3510`): ``` -align(K, 256) >= max(8192, aicNum·256B) / dtypeSize -mCnt · nCnt <= aicNum / 2 // MN 用 base 块切的份数不超过核数一半 +totalDataAmount = M · N // 需清零的元素总数(元素个数) +usedCoreNum = aivNum // 64 个 AIV 并行 ``` -- 语义:**M/N 太小、块数不够分满核,但 K 足够大** → 把 K 切成多份分给多个核,每核算一段 K 后跨核累加。 -- FP32 且非 hf32 时 base 块对齐单位退化为 32(`BLOCK_BYTE_SIZE`)。 -**DPSK 模式条件**(`CheckStreamKDPSKTilingDav3510`): +Device 侧 `MatMulInputKEqZeroClearOutput(biasGM, cGM, tilingData)`:每个 AIV 负责一段连续的 `C` 地址,一次搬移清零。数据流极简: + ``` -M % 256 == 0 且 N % 256 == 0 -K >= max(8192, aicNum·128B) / dtypeSize -totalMNCnt >= aicNum 且 totalMNCnt % aicNum != 0 且 余数 <= aicNum/2 +AIV ──(写0)──► GM(C) // 无 GM 读、无 Cube 参与 ``` -- 语义:**主轮能分满核(DP),但尾轮有 M/N 剩余块无法分满** → 尾轮用 SK 方式(切 K)提前执行,让 AIV 累加时 AIC 继续算下一轮(数据并行)。 -**tiling 核心**(`DoOpTiling`): -- `singleCoreK = ceil(K / kCnt)`,`kCnt` 由核数与 MN 块数的关系决定; -- `baseK = min(singleCoreK, L0A容量约束)`; -- `workspace = aicNum · 256·256·4B + RPC·MB`(**核间累加工作区**); -- `GetL0C2Out`:N 不对齐且大 N 时选 `ND_FIXPIPE_1_2`; -- model:FP32 且 `singleCoreK >= FP32_SPLIT_K_THRESHOLD` → `SK_SPLIT_K`(单核内再切 K 保精度),否则 `STREAM_K`。 +**评价**:正确且高效(不浪费 Cube、不产生 GM 读流量)。适用面窄(仅 K==0 且无 bias)。 -**Device swizzle**(`block_scheduler_streamk.h`): -- `mL1 = baseM`、`nL1 = baseN`(StreamK 中 L1 只装一个 base 块); -- `tileNum = DP部分MN块数 + 尾轮SK部分MN块数·skKTileNum`; -- `CheckIsSkScene(tileIdx)` 判定当前块属于 DP 主轮(K 不分片)还是 SK 尾轮(K 分片); -- 同样应用 SWAT 窗口 + 蛇形扫描(见 §4)。 +--- -**评价**: -- **优点**:解决"MN 块数不足分满核、但 K 大"这一 ASWT 无法高效处理的场景,大幅提升瘦高形状(大 K)的算力利用率;DPSK 让尾轮 SK 与 AIC 计算重叠,减少核空闲。 -- **缺点**:① 需要 workspace 做跨核累加,**额外 GM 写+读流量**,在 950PR 带宽紧张下成本不低;② 累加顺序不确定 → `deterministicLevel>1` 时整分支禁用;③ 仅支持 A 为 ND;④ DPSK 要求 M/N 严格 256 对齐,形状不齐时退化为纯 SK 或走 ASWT。 +### 2.2 TO_MUL(priority 1)—— M==1 或 N==1 的 FP32 退化向量乘 -### 2.5 BASIC_ASWT(priority 4)—— 主分支 +**触发条件**:`isForceGrpAccForFp32`(op_impl_mode_enum==0x4 高精度 FP32)∧ 非 slice ∧ (M==1 ∨ N==1) ∧ dtypeA==dtypeB==FP32。 -**IsCapable**:无条件返回 true。其 `DoOpTiling` 先调用父类 `MatMulV3AswTiling::DoOpTiling()` 完成基础 tiling(baseM/baseN/baseK/singleCore),再按顺序做子决策: +**原理(结合硬件)**:Cube 的 MMAD 指令最小分形是 **16×16×16**。当 `M==1` 时,MMAD 的 M 维 16 个 lane 只能用 1 个 → **Cube 算力利用率仅 1/16 ≈ 6%**。此时 `[1,K]·[K,N]` 本质是**一个 K 维向量与 N 个 K 维向量的点积**,用 AIV 的向量乘加(`Mul+ReduceSum`)天然适合,且 64 个 AIV 的 FP32 算力 27 TFLOPS 足够覆盖。 + +**实现**(`matmul_v3_to_mul_tiling.cpp` + `mat_mul_to_mul_cmct.h`):数据流为「GM→UB→AIV 向量乘→UB→GM」,全程不走 Cube: + +``` +GM(A[M=1,K]) ──MTE2──► UB ──AIV向量乘加──► UB ──MTE3──► GM(C[N]) +GM(B[K,N]) ──MTE2──► UB +``` + +tiling 参数由 **UB 容量**(`ubSize/4` 个 float)反推: + +``` +tmpMN = ⌈(M·N) / aivNum⌉ // 每核摊到的 MN 元素数(元素个数) +baseMN = min(Align(tmpMN, 对齐), BASE_MN) // MN 方向分块(元素个数) +baseK = (ubCount - baseMN - biasCount)/5/baseMN // K 方向分块,受 UB 容量约束(元素个数) +loopK = ⌈K / baseK⌉ // K 循环次数(无量纲) +usedCoreNum = min(⌈M·N/baseMN⌉, aivNum) // 实际核数 +``` + +`dataCopyMode` 区分「K 内轴连续((K,N)/(K,M) 布局)」还是「MN 内轴连续」,决定 baseK/baseMN 的取舍顺序。 + +**评价**:对退化场景绕开 Cube 走 AIV 正确。局限:① 仅覆盖高精度 FP32,**FP16/BF16 的 M==1/N==1 无退化分支、仍走 Cube**(§6 改进点);② TO_MULTI_MUL 不支持 bias。 + +--- + +### 2.3 TO_MULTI_MUL(priority 2)—— 特定转置组合的 FP32 退化向量乘 + +**触发条件**:高精度FP32 ∧ 非slice ∧ `!ATrans ∧ BTrans` ∧ 无bias ∧ FP32。 + +**原理**:与 TO_MUL 同(退化向量乘),但锁定 `!ATrans∧BTrans`:A 行主序(K 连续)、B 列主序(K 连续),**内积方向 K 两侧都连续**,向量乘访存顺序、无需 gather。 + +**实现**(`matmul_v3_to_multi_mul_tiling.cpp` + `mat_mul_to_multi_mul_cmct.h`):同样「GM→UB→AIV 向量乘→UB→GM」。核数分配用 `CalcBasicBlock()` 启发式均衡: + +``` +mCore = ⌈M/baseM⌉, nCore = ⌈N/baseN⌉ +if (mCore·nCore < aivNum) CalcBasicBlock() // 块数不足分满核时重调 baseM/baseN +while (baseN ≥ 2·baseM ∧ nCore < aivNum/2): // N 侧块太多 → nCore·=2, baseN=⌈N/nCore⌉ +while (baseM ≥ 2·baseN ∧ mCore < aivNum/2): // M 侧块太多 → mCore·=2, baseM=⌈M/mCore⌉ +usedCoreNum = min(mCore·nCore, aivNum) +``` + +**评价**:与 TO_MUL 同族但更窄(限转置、无 bias)。共同原则:**矩阵乘退化到 M==1 或 N==1 时,绕开 Cube 走 AIV**,规避 Cube 16 对齐分形浪费。 + +--- + +### 2.4 BASIC_STREAM_K(priority 3)—— 切 K 满核 + +#### 2.4.0 解决的问题(硬件原理) + +**核心矛盾**:baseM=baseN=256 时,若 M、N 较小(如 M=N=256),则 `mCnt·nCnt = 1×1 = 1` 个 MN 块,但 `aicNum=32` → **只有 1 个核工作、31 核空闲**,算力利用率 1/32。950PR「算力/带宽比极高」架构下,大 K 瘦高形状(如 `[256, 8192]×[8192, 256]`)是典型 case:MN 方向分不满核,但 K 很大、总计算量足够喂饱 32 核。 + +**解法**:把 K 轴切成 `kCnt` 份分给多个核,每个核算 `M×N×singleCoreK` 的**部分和**,最后跨核累加。有效并行度变为 `mCnt·nCnt·kCnt`。 + +``` +普通 ASWT(M=N=256, K=8192,kCnt=1): + ┌────────────────────────────────────────────┐ + │ 核0: [256×256] × [256×8192] (算完整 K) │ ← 只有 1 核,31 核空闲 + └────────────────────────────────────────────┘ + +StreamK(切 K,kCnt=32): + ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ + │ 核0: K[0:256] │ │ 核1: K[256:512]│ ... │ 核31: K[...] │ ← 32 核各算一段 K + └───────┬───────┘ └───────┬───────┘ └───────┬───────┘ + └──────────────────┴──────────────────────────┘ + ▼ 核间累加(workspace,AIV 顺序累加) + C[M,N] = Σ_k 部分和 +``` + +#### 2.4.1 分支结构(含 3 个子模式) + +**IsCapable 前置条件**(`MatMulV3BasicStreamKTiling::IsCapable`): + +``` +deterministicLevel ≤ 1 // 切 K 核间累加顺序不确定,强一致性场景禁用整分支 +∧ aFormat == ND // 左矩阵必须 ND +∧ 非 slice // 不支持非连续视图 +∧ aivNum == 2·aicNum // 必须 AIC:AIV = 1:2 +∧ (CheckStreamKSKTiling() ∨ CheckStreamKDPSKTiling()) +``` + +**子模式 ①:SK(纯切 K)** —— `CheckStreamKSKTilingDav3510`: + +``` +align(K, 256) >= max(8192, aicNum·256B) / dtypeA // K 足够大 +mCnt · nCnt <= aicNum / 2 // MN 块数不超过核数一半 +``` + +语义:M/N 小到「MN 块数 ≤ 核数一半」,K 大 → 全 K 切分,每核算一段 K。 +注:FP32 且非 hf32 时,`alignValue` 从 256 退化为 32(`BLOCK_BYTE_SIZE`)。 + +**子模式 ②:DPSK(数据并行 StreamK)** —— `CheckStreamKDPSKTilingDav3510`: + +``` +M % 256 == 0 且 N % 256 == 0 // MN 严格 256 对齐 +K >= max(8192, aicNum·128B) / dtypeA // K 足够大 +totalMNCnt >= aicNum // MN 块数能分满核(主轮) +totalMNCnt % aicNum != 0 // 但有尾轮余数 +(totalMNCnt % aicNum) <= aicNum/2 // 余数不超过核数一半 +``` + +语义:**主轮 MN 块数 ≥ 核数(DP 满核、K 不分片),尾轮剩余 MN 块 < 核数** → 尾轮把 K 切开分给空闲核,且提前执行让 AIV 累加与 AIC 下一轮计算重叠(数据并行)。 + +**子模式 ③:SK_SPLIT_K**:SK/DPSK 基础上,若 `FP32 ∧ singleCoreK ≥ FP32_SPLIT_K_THRESHOLD`,则单核内再切 K(`splitSingleK`,保 FP32 累加精度),model 由 `STREAM_K` 升级为 `SK_SPLIT_K`。 + +#### 2.4.2 tiling 参数推导(`DoOpTiling`) + +``` +baseM, baseN // 由 ResetBase + 后续逻辑给出(§3.2),通常 256 +mCnt = ⌈M/baseM⌉, nCnt = ⌈N/baseN⌉ +totalMNCnt = mCnt · nCnt + +if (totalMNCnt <= aicNum/2): // 纯 SK 模式 + 调整 mCnt/nCnt 到 [aicNum/3, aicNum/2] 区间 // 避免切得过碎 + baseM = Align(⌈M/mCnt⌉, 16); baseN = Align(⌈N/nCnt⌉, 16) + kCnt = ⌊aicNum / totalMNCnt⌋ // 每个 MN 块分配多少核切 K + singleCoreK = ⌈K / kCnt⌉ // 单核 K 分片(元素个数) +else: // DPSK 模式(尾轮切 K) + kCnt = aicNum / (totalMNCnt % aicNum) + singleCoreK = ⌈K / kCnt⌉ + kCnt = ⌈K / singleCoreK⌉ // 回算实际切分数 + +// baseK:受 L0A 容量约束(§3.3) +kValueMax = ⌊L0A/DB/dtypeA / max(baseM, baseN)⌋ (按 128B/dtype 或 16 对齐) +baseK = min(singleCoreK, kValueMax) + +CalL1Tiling(...) // 复用 §3.4 计算 stepKa/stepKb +workspace = aicNum·256·256·4B + 20·MB // 核间累加工作区(字节) +``` + +**L0C2Out**:N 大且 N 不对齐(`N>64 ∧ N%16≠0 ∧ M>2 ∧ M·N≥256`)→ `ND_FIXPIPE_1_2`,否则 `ON_THE_FLY`(同 §2.5.4 的 fixpipe 逻辑)。 + +#### 2.4.3 Device 数据流(`block_scheduler_streamk.h` + StreamK kernel) + +StreamK 的 L1 只装一个 base 块(`mL1=baseM, nL1=baseN`),核间通过 workspace 累加: + +``` +DP 主轮(每核一个 MN 块,算完整 K): + GM(A块) ─MTE2─► L1 ─MTE1─► L0A ─┐ + ├─MMAD─► L0C ─Fixpipe─► GM(C块) // 直接写结果 + GM(B块) ─MTE2─► L1 ─MTE1─► L0B ─┘ + +SK 尾轮(每核一个 MN 块的一段 K): + GM(A块) ─► L1 ─► L0A ─┐ + ├─MMAD─► L0C ─Fixpipe─► workspace(部分和) // 写部分和 + GM(B块) ─► L1 ─► L0B ─┘ + 然后 AIV 从 workspace 顺序累加(Atomic/确定性累加)─► GM(C块) +``` + +`CheckIsSkScene(tileIdx)` 判定当前核索引属于 DP 轮还是 SK 轮:`ceil((tileIdx+1)/usedCoreNum) == ceil(tileNum/usedCoreNum)`(最后一轮是 SK)。 + +#### 2.4.4 评价 + +- **优点**:① 解决"MN 块数不足分满核、但 K 大"的场景,大 K 瘦高形状算力利用率可从 1/32 提升到接近满核;② DPSK 让尾轮 SK 与 AIC 计算重叠,减少核空闲。 +- **缺点**:① workspace 核间累加带来**额外 GM 写+读流量**(部分和写出 + AIV 读回),950PR 带宽紧张下成本不低;② 累加顺序不确定 → `deterministicLevel>1` 整分支禁用;③ 仅 A 为 ND;④ DPSK 要求 M/N 严格 256 对齐;⑤ FP32 切 K 需额外 `splitSingleK` 保精度,复杂度高。 + +--- + +### 2.5 BASIC_ASWT(priority 4)—— 主分支(含 4 个子分支) + +**IsCapable**:无条件返回 true(除命中 StreamK 场景外的绝大多数 case 都落在这里)。 + +`DoOpTiling` 先调用父类 `MatMulV3AswTiling::DoOpTiling()` 完成基础 tiling(`ResetBase→GetRebalanceBlock→CalcTailBasicBlock→CalL1Tiling`,即 §3 的完整流程),得到 baseM/baseN/baseK 后,再按顺序做**子分支决策**: ``` DoOpTiling(): - isSlice_ = IsSelfNonContiguous() // 非连续 3D slice - l0C2Out_ = GetL0C2Out() // 是否走 fixpipe 优化 - if (!isSlice_ && CheckAL1FullLoad()) → DoAL1FullLoad() // A 全载 L1 - elif (!isSlice_ && CheckBL1FullLoad()) → DoBL1FullLoad() // B 全载 L1 - elif (l0C2Out_ == ON_THE_FLY) → 普通场景,按 L1 剩余容量均分 stepK + isSlice_ = IsSelfNonContiguous() // 子分支④:非连续 3D slice + l0C2Out_ = GetL0C2Out() // 子分支③:fixpipe 优化判定 + if (!isSlice_ && CheckAL1FullLoad()) → DoAL1FullLoad() // 子分支①:A 全载 + elif (!isSlice_ && CheckBL1FullLoad()) → DoBL1FullLoad() // 子分支②:B 全载 + elif (l0C2Out_ == ON_THE_FLY) → 普通场景,L1 剩余容量均分 stepK else → fixpipe 优化场景 - CheckFp32SplitK() // FP32 大 K → BASIC_SPLIT_K - CheckApiLevelAndModel() // tensor/basic api + CheckFp32SplitK() // FP32 大 K → BASIC_SPLIT_K + CheckApiLevelAndModel() // BASIC / TENSOR api ``` -**A 全载条件**(`CheckAL1FullLoad`): -- `l0C2Out == ON_THE_FLY`(不叠加 fixpipe); -- 非 CubeBound(`cubeBoundParam > cubeBoundEdge`,即 MTE2 是瓶颈、重复读代价高); -- `nCnt > aicNum`(N 方向块数多于核数,存在跨核重复读 A); -- 排除"Fixp Bound 多轮"(`K<=128 && mCnt!=1`); -- 整个 A(M×K)+ bias ≤ **3/4 L1**。 +ASWT 的**标准数据流**(所有子分支共享的骨架,`mat_mul_asw_kernel.h` 的 `mm_.Iterate()`): -**B 全载条件**(`CheckBL1FullLoad`):对称(非 CubeBound ∧ `mCnt > aicNum` ∧ B+bias ≤ 3/4 L1)。 - -**A 全载实现**(`DoAL1FullLoad`):整个 A 常驻 L1,B 按 `baseN` 分块流式搬入;`singleCoreM = M`(不再分 M)、`singleCoreN = baseN`;`baseN` 取 min(原值, L1剩余容量上限, L0C双缓冲上限, 负载均衡值);`stepKb` 由 B 的 L1 搬移量 + 256B 对齐约束确定;`l1BufferNum` 判断能否 4-buffer。 - -**B 全载实现**(`DoBL1FullLoad`):对称(整个 B 常驻 L1,A 流式)。 - -**L0C2Out(fixpipe 优化)条件**(`GetL0C2OutDav3510`): ``` -isValidMKN = K<=256 && M>=256 -isMultiRound = mCnt·nCnt > aicNum -isUnalignedN = (N·cDtypeSize % 128 != 0) && (N·cDtypeSize > 256) -fixpipeBound = isValidMKN && isMultiRound && isUnalignedN +GM(A块) ─MTE2─► L1(A) ─MTE1─► L0A ─┐ + ├─ MMAD(16×16×16) ─► L0C ─Fixpipe─► GM(C块) +GM(B块) ─MTE2─► L1(B) ─MTE1─► L0B ─┘ + (MTE2 重复读可命中 L2,5.2TB/s;L1 用 Double Buffer 掩盖搬移延迟) ``` -满足且 `aivNum == 2·aicNum` → FP16/BF16 选 `ND_FIXPIPE_1_1`,FP32 选 `ND_FIXPIPE_1_2`。 -**Device kernel 分发**(`mat_mul_v3.cpp` 的 `if constexpr`):按 (ApiLevel, FullLoad, Model, L0C2Out) 组合映射到 `MatMulBasicKernel` / `MatMulAL1FullLoadKernel` / `MatMulBL1FullLoadKernel` / `MatMulFixpipeOptiTensorKernel` / `MatMulBasicSplitKKernel`(均基于 Blaze::Gemm 模板库)。 +--- -**评价**: -- **优点**:① 通用性强,覆盖绝大多数 shape;② SWAT 提升 L2 命中率;③ A/B 全载在"一侧重复读多、另一侧能装进 L1"时显著减少 GM→L1 流量;④ fixpipe 优化在特定小 K 大 M 场景让搬出与计算并行;⑤ 尾轮负载均衡减少尾轮算力浪费。 -- **缺点**:① baseM/baseN 的选择是**启发式搜索**(§3 的 `cubeBoundParam/balanceRate` 权衡),不是严格最优解;② A/B 全载只支持"整个矩阵常驻 L1"一种粒度,且限定非 CubeBound、ND 场景;③ fixpipe 条件苛刻(K≤256 且 M≥256 且 N 不对齐),覆盖面窄;④ 未区分 GM 读写带宽,`GetHbmBW` 用统一换算值。 +#### 2.5.1 子分支①:A 全载 L1(A_FULL_LOAD) + +**触发条件**(`CheckAL1FullLoad`): + +``` +l0C2Out == ON_THE_FLY // 不与 fixpipe 叠加 +∧ cubeBoundParam > cubeBoundEdge // 非 CubeBound(MTE2 是瓶颈,重复读代价高) +∧ nCnt > aicNum // N 方向块数多于核数 → 同一 A 块被跨 N 的核重复读 +∧ !(K <= 128 && mCnt != 1) // 排除 Fixp Bound 多轮 +∧ (M·K·dtypeA + bias) <= 3/4·L1 // 整个 A + bias 能装进 3/4 L1(384KB) +``` + +**原理(结合硬件)**:当 `nCnt > aicNum`,同一列(固定 m)的 A 块会被 `nCnt` 个核重复读。若 A 的 `M×K` 整个能装进 L1,则把它**常驻 L1**,只流式搬 B → **A 的重复读从 GM/L2 级别降为 0**,只剩 B 的单次读 + A 的单次读。对 950PR 的 1.6TB/s 带宽是直接省流。 + +``` +A 全载数据流(A 常驻 L1,B 按 baseN 流式): + GM(A[M×K]) ─MTE2─► L1(A, 常驻, 不再搬出) + │每次只搬 B 块: + GM(B[K×baseN]) ─MTE2─► L1(B) ─MTE1─► L0B ─┐ + L1(A) ─MTE1─► L0A ────────────────────────┼─MMAD─► L0C ─Fixpipe─► GM(C) +``` + +**tiling**(`DoAL1FullLoad`): + +``` +singleCoreM = M // M 不再分核(A 常驻,M 方向一块) +singleCoreN = baseN // N 方向按 baseN 分 +aL1Size = M·K·dtypeA // A 全载字节数 +remainL1 = L1 - (aL1Size + bias) // L1 剩余容量 +maxBaseN = min(remainL1/(baseK·dtypeB·DB), L0C/(baseM·4B·DB)) // 受 L1 剩余 + L0C 双缓冲约束 +baseN = min(baseN, maxBaseN, ⌈N/aicNum⌉对齐16) // 三者取小 +stepKa = ⌈K/baseK⌉, stepM = ⌈M/baseM⌉ // K、M 方向 L1 分片 +stepKb = 由 B 的 L1 搬移量 + 256B 对齐确定 // B 侧 K 分片 +l1BufferNum = (4·B搬移量 + aL1Size + bias > L1) ? 2 : 4 // 能否 4-buffer +dbL0C = (baseM·baseN·4B·2 <= L0C) ? 2 : 1 // L0C 双缓冲 +usedCoreNum = min(nCore·batchNum, aicNum) +``` + +--- + +#### 2.5.2 子分支②:B 全载 L1(B_FULL_LOAD) + +与 A 全载完全对称:`!CubeBound ∧ mCnt > aicNum ∧ (K·N·dtypeB + bias) <= 3/4·L1` 时,整个 B 常驻 L1,A 按 baseM 流式。 + +``` +B 全载数据流(B 常驻 L1,A 按 baseM 流式): + GM(B[K×N]) ─MTE2─► L1(B, 常驻) + GM(A[baseM×K]) ─MTE2─► L1(A) ─MTE1─► L0A ─┐ + L1(B) ─MTE1─► L0B ────────────────────────┼─MMAD─► L0C ─Fixpipe─► GM(C) +``` + +tiling 对称:`singleCoreN=N`、`singleCoreM=baseM`,`baseM = min(baseM, 剩余容量上限, L0C上限, ⌈M/aicNum⌉)`。 + +--- + +#### 2.5.3 子分支③:Fixpipe 随路搬出优化(ND_FIXPIPE_1_1 / 1_2) + +**触发条件**(`GetL0C2OutDav3510`): + +``` +isValidMKN = (K <= 256) ∧ (M >= 256) // 计算量小、M 大 +isMultiRound = (mCnt·nCnt > aicNum) // 多轮(搬出流水与计算重叠才有意义) +isUnalignedN = (N·cDtypeSize % 128 != 0) ∧ (N·cDtypeSize > 256) // N 不对齐 128B +fixpipeBound = isValidMKN ∧ isMultiRound ∧ isUnalignedN +→ 满足且 aivNum == 2·aicNum: + FP16/BF16 → ND_FIXPIPE_1_1 FP32 → ND_FIXPIPE_1_2 +``` + +**原理(结合硬件)**:小 K 大 M 场景,MMAD 计算时间短,`T_FIXPIPE = M·N·dtypeC / BW_fixp` 可能超过 `T_MMAD`,成为 **Fixpipe Bound**。950PR 的 Fixpipe 随路硬化(L0C→GM 直接量化/排布转换)但**对 N 不对齐(非 128B 对齐)的搬出效率低**。`ND_FIXPIPE_1_1/1_2` 用 AIV 辅助做搬出对齐(1V1 或 1V2 的 AIC:AIV 配比),配合 `SetMMLayoutTransform(true)`(fixp 用 N 搬出)+ UnitFlag(512B 粒度同步),让 Fixpipe 与 Cube 并行、搬出对齐。 + +``` +普通(ON_THE_FLY): MMAD ──────► 等算完 ──Fixpipe──► GM (串行) +Fixpipe 优化: MMAD(512B) ─► 立即随路 Fixpipe ─► GM (并行,粒度 512B) +``` + +--- + +#### 2.5.4 子分支④:非连续 Slice(SLICE model) + +**触发条件**:`IsSelfNonContiguous(context)` —— self 是 view 且 storageShape 为 1D、self 3D / mat2 2D(createView with TensorV2 场景)。此时 model=SLICE,`MatMulBasicKernel` 用 `sliceM/srcNdStride` 做非连续搬移(NDDMA 多维重排),`GetBlockShape` 对 sliceM 对齐。 + +--- + +#### 2.5.5 评价 + +- **优点**:① 通用性强,覆盖绝大多数 shape;② SWAT 提升 L2 命中(§4);③ A/B 全载在"一侧重复读多、另一侧能装进 L1"时显著减少 GM→L1 流量;④ fixpipe 优化在特定小 K 大 M 场景让搬出与计算并行;⑤ 尾轮负载均衡(§3.5)减少尾轮算力浪费。 +- **缺点**:① baseM/baseN 是**启发式搜索**(§3.2 的 `cubeBoundParam/balanceRate` 权衡),非严格最优;② A/B 全载只支持"整个矩阵常驻 L1"一种粒度,A/B 稍大于 3/4 L1 时直接放弃(存在优化断档,§6);③ fixpipe 条件苛刻(K≤256 ∧ M≥256 ∧ N 不对齐),覆盖面窄;④ GM 带宽未区分读写(§6)。 + +--- ### 2.6 BASE(priority 999)—— 老 Cmct 接口兜底 **实现**(`matmul_v3_asw_tiling.cpp` + `mat_mul_asw_kernel.h` / `mat_mul_asw_block.h`): -- `DoOpTiling`: `ResetBase` → `GetRebalanceBlock` → `CalcTailBasicBlock` → `CalL1Tiling`; -- Device 侧走 `MatMulActKernel`(老 Cmct 接口),核内 `mm_.Iterate()` 执行标准 `GM→L1→L0A/L0B→Cube→L0C→Fixpipe` 流水;`SetMMLayoutTransform(true)` 让 Fixpipe 用 N 搬出实现 Cube 与 Fixpipe 并行; -- 块索引计算(`MatmulAswBlock::UpdateBasicIndex`)同样实现 SWAT 窗口 + 蛇形扫描 + 尾轮重切。 +- `DoOpTiling`: `ResetBase → GetRebalanceBlock → CalcTailBasicBlock → CalL1Tiling`(与 BASIC_ASWT 基础 tiling 完全相同的引擎,§3); +- Device 走 `MatMulActKernel`(老 Cmct 接口),核内 `mm_.Iterate()` 执行标准 `GM→L1→L0A/L0B→Cube→L0C→Fixpipe` 流水;`SetMMLayoutTransform(true)` 让 Fixpipe 用 N 搬出实现 Cube 与 Fixpipe 并行; +- 块索引(`MatmulAswBlock::UpdateBasicIndex`)同样实现 SWAT 窗口 + 蛇形扫描 + 尾轮重切(§4)。 -**评价**:作为最终兜底保证正确性,逻辑与 BASIC_ASWT 基础 tiling 一致,但**没有**全载 / fixpipe / StreamK 等新优化,性能上限低于主分支。 +**与 BASIC_ASWT 的区别**:两者共用同一套 tiling 引擎和 swizzle,区别只在 **Device 接口代际**——BASE 走老 Cmct 接口(无全载/fixpipe/StreamK 的 kernel 模板),BASIC_ASWT 走新 Blaze 接口(支持全载/fixpipe/SplitK)。实际运行时 BASIC_ASWT(priority 4)几乎总是先命中,BASE 仅在注册表异常时兜底。 + +**评价**:保证正确性;性能上限低于主分支(缺全载/fixpipe/SplitK 等新优化)。 --- -## 3. 核心 tiling 算法(决定 baseM/baseN/baseK 与单核形状) +## 3. 核心 tiling 算法(ASW 系分支共用的参数计算引擎) -### 3.1 ResetBase(初始值,`matmul_v3_tiling_helper.cpp`) +> 本节是 §2.5(BASIC_ASWT)、§2.6(BASE)的 tiling 参数来源,§2.4(StreamK)复用其中 ResetBase/GetBaseK/CalL1Tiling。所有函数在 `matmul_v3_tiling_helper.cpp`。 + +### 3.1 ResetBase(初始值) ```cpp -// ResetBaseDefault(DAV_3510 在此基础上改 baseM) -usedCoreNum = aicNum; // 32 -baseM = 256; baseN = 256; // 950PR 的 base 块 -baseK = 128B / dtypeSize; // FP16=64, FP32=32 -iterateOrder = ITER_COL_FIRST; // 列优先 -singleCoreK = K; singleCoreM/N = baseM/N; +// ResetBaseDav3510(950PR) +usedCoreNum = aicNum = 32 +baseM = 256; baseN = 256 // 950PR 的 base 块(上代为 128) +baseK = 128B / dtypeA // FP16/BF16=64 元素,FP32=32 元素 +iterateOrder = ITER_COL_FIRST // 列优先遍历 +singleCoreK = K; singleCoreM/N = baseM/N ``` +**为什么 baseM=baseN=256**:L0C=256KB,FP32 累加 `256×256×4B = 256KB` 恰好填满 L0C。baseM=baseN=256 是"L0C 能容纳的最大方块",也是重复读最少的配置(`重复读 ∝ 1/baseM + 1/baseN`,见 §3.2 的 `cubeBoundParam`)。950PR 的 L0C 比上代(128KB)大一倍,所以 baseM 从 128 升到 256。 + +**为什么 baseK = 128B/dtype**:GM→L1 高效搬移要求单次 dValue ≥ 128B(连续列数据量)。K 方向对齐 128B 保证 MTE2 搬移效率。 + ### 3.2 GetRebalanceBlock(baseM/baseN 最优搜索,核心) -这是整个 tiling 最关键的函数,分两步: +分两步: -**① Roofline 判 CubeBound**: -```cpp -hbmBW = freq · 32核 · 31B/拍 / 1024 // ≈ 1.6TB/s -l2BW = freq · 32核 · 100B/拍 / 1024 // ≈ 5.2TB/s -singleCoreComputePower = freq · 8 // ≈ 13.2 TFLOPS(单核 BF16) +**① Roofline 判 CubeBound**(`cubeBoundEdge` 计算): + +``` +hbmBW = freq · 32核 · 31B/拍 / 1024 ≈ 1.6 TB/s +l2BW = freq · 32核 · 100B/拍 / 1024 ≈ 5.2 TB/s +singleCoreComputePower = freq · 8 ≈ 13.2 TFLOPS(单核 BF16) computePower = singleCoreComputePower · aicNum -cmr = (M+N)/(M·N) // 临界算术强度相关量 -cubeBoundEdge = (l2BW/computePower) + l2CacheUsage·(1 - l2BW/hbmBW)·cmr - - (1 + l2BW/hbmBW)/K -cubeBoundParam = 1/baseM + 1/baseN +cmr = (M+N) / (M·N) // 临界算术强度相关量(1/元素) +l2CacheUsage = max(batch·(M+N)·K·dtypeA / L2, 1) // L2 占用倍率(无量纲) +cubeBoundEdge = (l2BW/computePower) + + l2CacheUsage·(1 - l2BW/hbmBW)·cmr + - (1 + l2BW/hbmBW)/K +cubeBoundParam = 1/baseM + 1/baseN // 重复读因子(1/元素) // Cube Bound 条件:cubeBoundParam <= cubeBoundEdge ``` -**② 搜索最优 (baseM, baseN)**:在 `maxBaseM × maxBaseN` 解空间内双重循环,对每个候选算: -- `curCubeBoundParam = 1/curBaseM + 1/curBaseN` -- `curBalanceRate`(尾轮负载均衡率,`GetBalanceRateWithTail`) -- 目标:优先满足 CubeBound 且 balanceRate 更高;否则综合 `cubeBoundParam/balanceRate` 评选。 +物理含义:`cubeBoundParam` 是当前 tiling 的 MTE2 重复搬移强度,`cubeBoundEdge` 是"恰好 Cube Bound"的临界强度。`cubeBoundParam ≤ cubeBoundEdge` ⇔ MTE2 不成为瓶颈。这与官方 `matmul_performance.md` 的 MTE2 Bound 条件 `BW_mte2 ≥ (1/baseN + 1/baseM)·dtype·16·16·16·核数·频率` 等价。 -`maxBaseM/maxBaseN` 由 `GetMaxBaseWithLimit` 计算,受 L0A/L0C/L1/bias table/K 对齐多重约束。 +**② 搜索最优 (baseM, baseN)**:在 `maxBaseM × maxBaseN` 解空间双重循环,每个候选算: -### 3.3 GetBaseK - -```cpp -maxBaseK = L0A_SIZE / DB_SIZE / dtypeSize / max(baseM, baseN) -// 优先 K 全载进 L0A;否则按 256B 对齐;再退 128/64/32/16 +``` +curCubeBoundParam = 1/curBaseM + 1/curBaseN +curBalanceRate = GetBalanceRateWithTail(...) // 尾轮负载均衡率(无量纲 0~1) +目标:优先满足 CubeBound 且 balanceRate 更高; + 否则综合 (curCubeBoundParam/curBalanceRate) 评选(值小者优) ``` -### 3.4 CalL1Tiling(K 方向 L1 分片,`CalL1TilingDefault`) +`maxBaseM/maxBaseN` 由 `GetMaxBaseWithLimit` 计算,受 L0A/L0C/L1/bias table/K 对齐多重约束。搜索步长 `baseMAlignUnit/baseNAlignUnit` 与转置、fixp bound 相关(16 / 32 / 64 / 128B 对齐)。 + +### 3.3 GetBaseK(baseK 约束) ```cpp -isKInner = !ATrans || BTrans -maxStepK = min(ceil(K/baseK), 8) // K 方向 L1 分片数上限 8 -// 遍历 stepK:满足 (aL1+bL1)·DB <= L1 且 K 256B 对齐 且 单次搬移量约束 -stepKa = stepKb = resKL1 / baseK -depthA1 = stepKa · DB; depthB1 = stepKb · DB +maxBaseK = L0A_SIZE/DB_SIZE/dtypeA / max(baseM, baseN) // 受 L0A 容量约束(元素个数) +// K 能全载进 L0A 则取 K 对齐值;否则按 256B 对齐;再退候选 {128,64,32,16} +``` + +### 3.4 CalL1Tiling(K 方向 L1 分片) + +```cpp +isKInner = !ATrans || BTrans // K 是否内轴 +maxStepK = min(⌈K/baseK⌉, 8) // K 方向 L1 分片数上限 8 +遍历 stepK(1..maxStepK): + 满足 (aL1+bL1)·DB <= L1 且 K 256B 对齐 且 单次搬移量约束 +stepKa = stepKb = resKL1 / baseK // K 方向 L1 分片步数(无量纲) +depthA1 = stepKa·DB; depthB1 = stepKb·DB // L1 流水深度(tile 层数) +singleCoreM = baseM; singleCoreN = baseN ``` ### 3.5 CalcTailBasicBlock(尾轮重切) @@ -256,35 +496,62 @@ depthA1 = stepKa · DB; depthB1 = stepKb · DB ```cpp tailCnt = (mCnt·nCnt > aicNum) ? (mCnt·nCnt % aicNum) : 0 // 尾轮把 base 块在 M/N 方向重切 mTailCnt×nTailCnt 份, -// 使尾轮也尽量填满核,且保持搬移效率(128B 对齐约束) +// 使尾轮也尽量填满核,且保持 128B 搬移对齐 +while ((mTailCnt+1)·nTailCnt·tailCnt <= aicNum ∧ 搬移对齐) mTailCnt++ ``` --- ## 4. Swizzle 编排(SWAT 窗口 + 蛇形扫描) -ASWT 与 StreamK 共用的 swizzle 核心(`block_scheduler_aswt.h` 的 `UpdateMNTileIdx`、`mat_mul_asw_block.h` 的 `UpdateBasicIndex`): +> 本节是 §2.5(ASWT)、§2.4(StreamK)的核间编排实现,回答"tiling 参数算好后,二维 MN 块如何映射到一维核索引、执行顺序如何"。ASWT 在 `block_scheduler_aswt.h`,StreamK 在 `block_scheduler_streamk.h`。 + +### 4.1 ASWT 的 SWAT 蛇形滑动(`UpdateMNTileIdx`) ``` -mainWindow = min(4, mTileNum) // 固定窗口 4 行(WINDOW_LEN=4) -mainRow = mTileNum / mainWindow - 1 -tailWindow = mTileNum - mainRow · mainWindow +mainWindow = min(4, mCnt) // 固定窗口 4 个 base 块(WINDOW_LEN=4) +mainRow = mCnt / mainWindow - 1 +tailWindow = mCnt - mainRow·mainWindow -rowIdx = tileIdx / nTileNum / mainWindow -if (rowIdx < mainRow): - mTileIdx = rowIdx·mainWindow + tileIdx % mainWindow // 窗口内 M 小步滑动 - nTileIdx = (tileIdx / mainWindow) % nTileNum // N 方向连续滑动 -else: - // 尾窗口特殊处理 -if (rowIdx % 2 != 0): - nTileIdx = nTileNum - 1 - nTileIdx // 蛇形:奇数行 N 反向 +rowIdx = tileIdx / nCnt / mainWindow +if (rowIdx < mainRow): // 主窗口区 + mTileIdx = rowIdx·mainWindow + tileIdx % mainWindow // 窗口内 M 小步滑动 + nTileIdx = (tileIdx / mainWindow) % nCnt // N 方向连续滑动 +else: // 尾窗口区(M 剩余不足一窗) + mTileIdx = mainRow·mainWindow + tailIndex % tailWindow + nTileIdx = (tailIndex / tailWindow) % nCnt +if (rowIdx % 2 != 0): // 蛇形:奇数行 N 反向扫描 + nTileIdx = nCnt - 1 - nTileIdx ``` -**SWAT 语义**:把 M 轴按窗口(默认 4 个 base 块)分组,窗口内沿 N 连续滑动、M 小步滑动,使相邻核访问的数据在空间上邻近 → 最大化 L2 命中;奇数行 N 反向扫描(蛇形)让相邻轮的首尾块空间相邻,进一步提升 L2 复用。这是官方 `matmul_performance.md` 中 SWAT(Slide Window Adaptive Template)的落地实现。 +**物理含义(结合硬件)**:把 M 轴按窗口(4 个 base 块)分组,窗口内沿 N 连续滑动、M 小步滑动,使**相邻核访问的数据在 GM 地址上空间邻近** → 最大化 L2 命中(重复读走 5.2TB/s 的 L2 而非 1.6TB/s 的 GM)。奇数行 N 反向(蛇形)让相邻两轮的首尾块空间相邻,进一步复用 L2。示意图: -**尾轮重切**(`GetBlockShape`):最后一轮把单个 base 块在 M/N 方向再切 `mTailCnt×nTailCnt` 份分给更多核,`blockIdx % tailCnt` 决定每核拿哪个子块,消除尾轮算力浪费。 +``` +M方向 base 块(窗口=4) +┌──────────────────────────────────────────┐ +│ 窗口0: 核0→N0, 核1→N1, 核2→N2, 核3→N3 (N 连续) │ +│ 窗口1: 核4→N3, 核5→N2, 核6→N1, 核7→N0 (蛇形反向) │ +│ ... │ +└──────────────────────────────────────────┘ + (每行窗口内 N 方向连续滑动 → 相邻核读相邻 A 块 → L2 命中) +``` -**StreamK 的 swizzle**(`block_scheduler_streamk.h`):在 SWAT 窗口基础上叠加 **DP 主轮(K 不分片)+ SK 尾轮(K 分片)** 判定(`CheckIsSkScene`),主轮每核一个 MN 块算完整 K,尾轮把剩余 MN 块切 K 分多核、由 AIV 在 workspace 上确定性累加。 +### 4.2 尾轮重切(`GetBlockShape`) + +最后一轮把单个 base 块在 M/N 方向再切 `mTailCnt × nTailCnt` 份分给更多核,`blockIdx % tailCnt` 决定每核拿哪个子块,消除尾轮"核数远大于剩余块数"的算力浪费(配合 §3.5)。 + +### 4.3 StreamK 的 DP/SK 编排(`block_scheduler_streamk.h`) + +在 SWAT 蛇形基础上叠加 **DP 主轮 + SK 尾轮** 判定: + +``` +tileNum = (mCnt·nCnt - tailMNTileNum) + tailMNTileNum·skKTileNum·batch + └──────── DP 部分(每核一个MN块) ┘ └───── SK 尾轮(每MN块切K) ─────┘ +CheckIsSkScene(tileIdx) = (⌈(tileIdx+1)/usedCoreNum⌉ == ⌈tileNum/usedCoreNum⌉) +// DP 轮:kTileNum=1(K 不分片);SK 轮:kTileNum=skKTileNum(K 分片) +``` + +DP 主轮每核一个 MN 块算完整 K(数据并行),SK 尾轮把剩余 MN 块切 K 分给空闲核(提前执行,AIV 累加与 AIC 下一轮重叠)。 --- @@ -293,24 +560,24 @@ if (rowIdx % 2 != 0): | 分支 | 适用场景 | 优点 | 缺点 / 局限 | |---|---|---|---| | K_EQUAL_ZERO | K==0 且无 bias | 极简,AIV 清零不浪费 Cube | 仅空矩阵乘 | -| TO_MUL | 高精度 FP32 且 M==1 或 N==1 | 避开 Cube 分形浪费,AIV 向量乘 | 仅 FP32 高精度模式 | -| TO_MULTI_MUL | 高精度 FP32 且 !ATrans∧BTrans 且 M/N 任意 | 同上 + 转置组合下的内积连续 | 更窄(限转置组合、无 bias) | -| BASIC_STREAM_K | M/N 小、K 大(K≥8192) | 切 K 用满核;DPSK 尾轮与计算重叠 | workspace 额外带宽;累加顺序不确定;仅 A 为 ND;DPSK 要求 256 对齐 | -| BASIC_ASWT | 通用主分支 | SWAT 提 L2 命中;A/B 全载减重复读;fixpipe 并行搬出;尾轮均衡 | baseM/N 启发式搜索非严格最优;全载/fixpipe 场景窄 | +| TO_MUL | 高精度 FP32 且 M==1/N==1 | 避开 Cube 分形浪费,AIV 向量乘 | 仅 FP32 高精度 | +| TO_MULTI_MUL | 高精度 FP32 且 !ATrans∧BTrans | 同上 + 内积方向连续 | 更窄(限转置、无 bias) | +| BASIC_STREAM_K | M/N 小、K 大(≥8192) | 切 K 满核;DPSK 尾轮与计算重叠 | workspace 额外带宽;累加顺序不确定;仅 A 为 ND;DPSK 要求 256 对齐 | +| BASIC_ASWT | 通用主分支 | SWAT 提 L2 命中;A/B 全载减重复读;fixpipe 并行搬出;尾轮均衡 | baseM/N 启发式非最优;全载/fixpipe 场景窄;GM 带宽不分读写 | | BASE(ASW) | 最终兜底 | 保证正确性 | 老接口,无全载/fixpipe/StreamK 优化 | --- ## 6. 初步观察到的可改进点(衔接任务 3.2) -在通读源码过程中,已浮现若干值得深挖的改进线索,留待后续对照性能模型严格论证: +在通读源码过程中浮现的改进线索,留待后续用性能模型严格论证: -1. **baseM/baseN 搜索目标是启发式的**:`GetRebalanceBlock` 用 `cubeBoundParam/balanceRate` 复合指标剪枝,而非直接代入 §3.2 的 `T_total = max(T_MMAD, T_MTE2, T_MTE1, T_FIXPIPE)` 精确评估。理论上可用性能模型对候选解做精确打分。 +1. **baseM/baseN 搜索是启发式的**:`GetRebalanceBlock` 用 `cubeBoundParam/balanceRate` 复合指标剪枝,而非直接代入 `T_total = max(T_MMAD, T_MTE2, T_MTE1, T_FIXPIPE)` 精确评估。理论上可用性能模型对候选解精确打分。 2. **SWAT 窗口固定为 4**(`WINDOW_LEN=4`):未根据 L2 容量、shape、核数自适应调窗。窗口大小直接影响 L2 命中率与重复读量的权衡。 -3. **A/B 全载只有"整个矩阵常驻"一种粒度**:没有"部分驻留"(多个 base 块驻留 L1 的中间态),在 A/B 稍大于 3/4 L1 时直接放弃全载,存在优化断档。 -4. **fixpipe 优化覆盖窄**:仅 `K≤256 ∧ M≥256 ∧ N 不对齐` 场景触发,其它 Fixpipe Bound 场景(如更小 K)未覆盖。 +3. **A/B 全载只有"整个矩阵常驻"一种粒度**:没有"部分驻留"(多个 base 块驻留 L1 的中间态),A/B 稍大于 3/4 L1 时直接放弃,存在优化断档。 +4. **fixpipe 优化覆盖窄**:仅 `K≤256 ∧ M≥256 ∧ N 不对齐` 触发,其它 Fixpipe Bound 场景未覆盖。 5. **GM 带宽未区分读写**:`GetHbmBW` 统一按 `32核·31B/拍` 换算,未区分读/写共享 1.6TB/s 的竞争,可能高估有效带宽。 -6. **FP32 高精度(isForceGrpAccForFp32)的退化分支覆盖不全**:M==1/N==1 的 FP16/BF16 场景无对应 AIV 退化分支,仍走 Cube。 +6. **M==1/N==1 的 FP16/BF16 无退化分支**:TO_MUL/TO_MULTI_MUL 仅覆盖 FP32 高精度,FP16/BF16 的退化场景仍走 Cube(1/16 利用率)。 --- @@ -322,6 +589,7 @@ if (rowIdx % 2 != 0): | Host | `op_host/op_tiling/arch35/matmul_tiling_registry.h` | 策略注册与 DoTilingImpl 调度 | | Host | `op_host/op_tiling/arch35/matmul_v3_tiling_advanced.cpp` | 主入口 + 各 Phase | | Host | `op_host/op_tiling/arch35/matmul_v3_tiling_helper.cpp` | ResetBase/GetRebalanceBlock/CalL1Tiling/GetL0C2Out | +| Host | `op_host/op_tiling/arch35/matmul_v3_common_advanced.h` | 常量与数据结构定义 | | Host | `op_host/op_tiling/arch35/matmul_v3_basic_streamk_tiling.cpp` | StreamK/DPSK 条件与 tiling | | Host | `op_host/op_tiling/arch35/matmul_v3_basic_aswt_tiling.cpp` | ASWT 子分支(全载/fixpipe) | | Host | `op_host/op_tiling/arch35/matmul_v3_{k_equal_zero,to_mul,to_multi_mul,asw}_tiling.cpp` | 其余分支 | @@ -333,3 +601,4 @@ if (rowIdx % 2 != 0): | Device | `op_kernel/arch35/mat_mul_asw_block.h` / `mat_mul_asw_kernel.h` | 老接口 ASW 块调度与主循环 | | Device | `op_kernel/arch35/mat_mul_{al1,bl1}_full_load.h` | A/B 全载 kernel 模板 | | Device | `op_kernel/arch35/mat_mul_streamk.h` / `mat_mul_fixpipe.h` / `mat_mul_basic_split_k.h` | StreamK/Fixpipe/SplitK 模板 | +