昇腾 950PR 上,Matmul 的标准数据流是:
GM ──MTE2──► L1 ──MTE1──► L0A/L0B ──MMAD──► L0C ──Fixpipe──► GM(C)
其中 L0C 的输出(Fixpipe 搬出)与 Cube 的计算(MMAD)之间存在「谁先谁后」的问题。用户观察到的两条掩盖路径:
问题:既然 UnitFlag 能这么细粒度地掩盖,L0C 还需要开 Double Buffer 吗? 两者的适用边界在哪?
L0C DB 的目标是:当前 base 块的 Fixpipe 搬出 与 下一个 base 块的 MMAD 计算 并行。它的掩盖对象是「base 块与 base 块之间的 Fixpipe 串行等待」。
L0C 块0: MMAD ──► Fixpipe 搬出
L0C 块1: MMAD ──► Fixpipe 搬出
L0C 块2: MMAD ──► Fixpipe 搬出
时间轴: ────────┬───────┬───────┬──►
块0搬出与块1计算重叠 块1搬出与块2计算重叠
UnitFlag 的目标是:单个 base 块内部,MMAD 计算 与 Fixpipe 搬出 的细粒度并行。它的掩盖对象是「单个 base 块内部的 MMAD-Fixpipe 串行等待」。
未开 UnitFlag: MMAD(整个base块) ──────► Fixpipe(整个base块)
开 UnitFlag: MMAD[512B] ─► Fixpipe[512B]
MMAD[512B] ─► Fixpipe[512B] ...
(512B 粒度交错,计算与搬出重叠)
| 维度 | L0C Double Buffer | UnitFlag |
|---|---|---|
| 掩盖对象 | base 块之间的 Fixpipe 串行 | base 块内部的 MMAD-Fixpipe 串行 |
| 粒度 | base 块级(一份 L0C 搬出时另一份在算) | 512B 指令级 |
| 触发方式 | Host tiling 决策(dbL0C=1/2) | Device kernel 里 enUnitFlag=true |
| 资源成本 | L0C 容量翻倍占用 | 无额外 Buffer,但受模板/格式约束 |
关键结论:两者不互为替代,而是掩盖不同层级的串行。一个 base 块的完整耗时可以拆解为:
T_base_block ≈ max(T_MMAD_base, T_MTE1_base, T_MTE2_base) + T_Fixpipe_base_tail
T_MMAD_base:该 base 块的 MMAD 计算时间;T_Fixpipe_base_tail:Fixpipe 搬出该 base 块的时间中无法被掩盖的部分。开 L0C DB 的效果:下一个 base 块的 MMAD 可以并行启动,把「当前块 Fixpipe」的时间藏进下一个块的 MMAD 里。但如果当前块的 MMAD 本身就比 Fixpipe 短(计算快、搬出慢),下一个块的 MMAD 很快算完,又要等当前块的 Fixpipe → L0C DB 掩盖不住「计算远快于搬出」的场景。
开 UnitFlag 的效果:把当前块内部的 MMAD 与 Fixpipe 交错,Fixpipe 不再等整个 base 块算完,而是边算边搬。即使当前块 MMAD 很快,Fixpipe 也能跟着 MMAD 的进度流水走,把大部分搬出时间藏进 MMAD 里。
所以:L0C DB 掩盖「块间」串行,UnitFlag 掩盖「块内」串行;在「单块 MMAD 短、Fixpipe 长」的场景,L0C DB 掩盖不住,必须靠 UnitFlag;在「单块 MMAD 长、Fixpipe 短」的场景,L0C DB 已足够,UnitFlag 收益小。
mat_mul_v3 的 L0C DB 由 Host tiling 层计算 runInfo.dbL0C,写入 tiling data 的 dbL0C/l0cDB 字段,Device 侧消费。源码中 dbL0C 的设置点:
| 设置点 | 文件 | 条件 | 结果 |
|---|---|---|---|
| ① 初始默认 | matmul_v3_tiling_helper.cpp:134(ResetBaseDefault) | 无 | dbL0C = DB_OFF_SIZE = 1(默认关) |
| ② 主搜索后 | matmul_v3_tiling_helper.cpp:490(GetRebalanceBlock 末尾) | baseM·baseN·4B·2 ≤ L0C(256KB) | 满足则 dbL0C=2,否则 1 |
| ③ A 全载 | matmul_v3_basic_aswt_tiling.cpp:203(DoAL1FullLoad) | 同上 | 同上 |
| ④ B 全载 | matmul_v3_basic_aswt_tiling.cpp:274(DoBL1FullLoad) | 同上 | 同上 |
核心条件统一为:
dbL0C = (baseM · baseN · DATA_SIZE_FP32 · DB_SIZE ≤ L0C容量) ? 2 : 1
代入 950PR 数值(L0C=256KB=262144B,DATA_SIZE_FP32=4B,DB_SIZE=2):
baseM·baseN·4·2 ≤ 262144 → baseM·baseN ≤ 32768 = 2^15
baseM=baseN=256 时:256·256 = 65536 > 32768 → dbL0C=1(关)。
baseM=256、baseN=128 时:256·128 = 32768 → dbL0C=2(开)。
物理含义:baseM=baseN=256 的 FP32 累加块恰好填满整个 L0C(256KB),开不了 DB;只有把 baseN(或 baseM)砍到一半,L0C 才能容纳两份,才能开 DB。这与「baseM=baseN=256 是重复读最少的最优解」存在张力:base 块取 256×256 时,L0C 被占满,无法用 DB 掩盖 Fixpipe。
使用场景总结:mat_mul_v3 中,L0C DB 在绝大多数分支都可能开启(普通 ASWT、A/B 全载、StreamK 复用同一引擎),但前提都是 baseM·baseN ≤ 32768。当最优 base 块是 256×256 时,dbL0C 强制为 1。
mat_mul_v3 中 UnitFlag 由 kernel 侧的 MatmulConfig.enUnitFlag 或自定义搬出回调的 fixpipeParams.unitFlag 控制。检索结果:
mat_mul_v3_common.h 定义 MM_CFG_NO_PRELOAD = GetMDLConfig(..., true)(最后一个参数即 enUnitFlag)。多个 kernel(MatMulBasicKernel 等)默认 MM_CFG = MM_CFG_NO_PRELOAD。但 GetMDLConfig 是 MDL 模板,官方文档明确「MDL 下 enUnitFlag 默认不使能」,且 GetMDLConfig 的具体默认参数需查 API 文档——MM_CFG_NO_PRELOAD 的 enUnitFlag 实际生效值取决于 GetMDLConfig 的实现,不能直接断言 mat_mul_v3 所有路径都开了 UnitFlag。mat_mul_stream_k_kernel.h:36-37(StreamK 的自定义搬出回调 CustomDataCopyOut):if (params->enUnitFlag) {
fixpipeParams.unitFlag = MM_FIX_PIPE_UNIT_FLAG; // 使能unitflag的参数 3U
}
这里 params->enUnitFlag 来自上层传入的搬出参数,unitFlag = 3U 直接写进 FixpipeParamsC310。
使用场景总结:mat_mul_v3 中 UnitFlag 的显式、确定开启只发生在 StreamK 分支的自定义搬出回调里(StreamK 的部分和需要从 L0C 搬到 GM workspace,再由 AIV 累加,搬出路径特殊)。普通 ASWT/A/B 全载分支是否开 UnitFlag,取决于 GetMDLConfig 的默认值(官方口径:MDL 模板默认不使能),源码没有显式强制开启。
| 条件 | 说明 |
|---|---|
baseM·baseN ≤ 32768(FP32 累加) | 硬约束:L0C 必须能容纳两份 base 块 |
| 存在「块间 Fixpipe 串行」 | 即 base 块数 > 1(多 base 块循环) |
| Fixpipe 耗时与 MMAD 相当或更短 | 若 Fixpipe 远快于 MMAD,开不开 DB 都掩盖得住;若 Fixpipe 远慢,DB 掩盖不住(需 UnitFlag) |
| 无 L0C 累加冲突 | 若 base 块需要在 L0C 上做 K 方向累加(split-K 等),DB 语义复杂 |
典型受益场景:baseM=256、baseN=128 的矩形 base 块,多 base 块循环,MMAD 与 Fixpipe 耗时相当。
| 约束 | 内容 |
|---|---|
| 模板限制 | 仅支持 Norm、IBShare、MDL 三个模板(MatmulConfig.enUnitFlag 字段说明) |
| 流水互斥 | 使能 UnitFlag 时,不支持同时存在 CO1(L0C)→GM 和 A1(L1)→GM 两种搬出流水 |
| 累加限制 | 使能 UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出 |
| 收益前提 | 仅当 MMAD 与 FIXPIPE 串行且未被其他流水掩盖时收益大;若已被 MTE2 Bound 掩盖,收益很小 |
| 默认状态 | Norm/IBShare 默认使能,MDL 默认不使能(需显式 enUnitFlag=true) |
典型受益场景:单个 base 块内 MMAD 很快、Fixpipe 慢(如小 K、大 MN),且模板是 Norm/IBShare/MDL、无 L1→GM 混合搬出。
从官方约束与源码实现看:
isA2B2Shared 字段说明里甚至建议「开启 A2B2 共享时同时设 enUnitFlag=true」。存在。Host 层 dbL0C 在 baseM·baseN·4B·2 ≤ L0C(256KB)(即 baseM·baseN ≤ 32768)时置 2,普通 ASWT、A/B 全载、StreamK(复用同一引擎)都会走到。但当最优 base 块是 256×256 时,dbL0C=1(开不了)。
存在,且显式开启点唯一:StreamK 分支的自定义搬出回调 CustomDataCopyOut(mat_mul_stream_k_kernel.h),在 params->enUnitFlag 为真时设 fixpipeParams.unitFlag = 3U。普通 ASWT 分支的 UnitFlag 状态取决于 MM_CFG_NO_PRELOAD(GetMDLConfig)的默认值——官方口径 MDL 默认不使能,因此普通 ASWT 路径上 UnitFlag 大概率是关的,需对照 GetMDLConfig 实际参数确认。
不是。两者掩盖对象不同:L0C DB 掩盖块间、UnitFlag 掩盖块内。在「单块 MMAD 长、多块循环」的场景,L0C DB 已能很好掩盖,UnitFlag 收益小;在「单块 MMAD 短、Fixpipe 长」的场景,L0C DB 掩盖不住(下一块 MMAD 很快算完又要等当前块搬出),必须靠 UnitFlag。两者是正交互补,不是替代关系。
MM_CFG_NO_PRELOAD = GetMDLConfig(..., true) 的最后一个参数是否真是 enUnitFlag、MDL 默认值是否生效,建议对照 GetMDLConfig API 文档核实。若普通 ASWT 默认未开 UnitFlag,而 StreamK 开了,两者在 Fixpipe 掩盖上的行为不一致,值得在性能模型里区分。dbL0C 直接由 baseM·baseN 决定,但 baseM/baseN 的搜索(GetRebalanceBlock)目标是 CubeBound + 负载均衡,没有把「dbL0C 能否开」纳入目标函数。当 256×256(开不了 DB)与 256×128(能开 DB)性能接近时,模型可能选前者而牺牲 DB 收益——这是一个潜在的 tiling 改进点。T_total = max(...) 模型里,Fixpipe 项应区分「块间掩盖(DB)」和「块内掩盖(UnitFlag)」,否则对 Fixpipe Bound 场景会高估或低估。| 证据 | 位置 |
|---|---|
| UnitFlag 定义与约束 | 昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._Matmul高阶API开启UnitFlag.md、商用版 9.0.0 203_..._使能UnitFlag.md |
| enUnitFlag 字段与默认值 | 昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/626_..._MatmulConfig.md |
| dbL0C 设置条件 | ops-nn/matmul/mat_mul_v3/op_host/op_tiling/arch35/matmul_v3_tiling_helper.cpp:490 |
| dbL0C 在全载分支 | .../matmul_v3_basic_aswt_tiling.cpp:203,274 |
| UnitFlag 显式开启 | ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_kernel.h:36-37 |
| MM_CFG_NO_PRELOAD 定义 | ops-nn/matmul/mat_mul_v3/op_kernel/mat_mul_v3_common.h:72 |