14 KiB
UnitFlag 与 L0C Double Buffer 的适用边界分析 —— 昇腾 950PR(DAV_3510)
关注点:L0C Buffer 的 Double Buffer(dbL0C)与 Fixpipe 的 UnitFlag 都能用来「掩盖 Fixpipe 搬出耗时」,两者是否等价、是否可以互相替代?mat_mul_v3 中各自的实际使用场景与边界是什么? 参考:
昇腾950PR/昇腾950PR架构解读.md、官方文档《Matmul高阶API开启UnitFlag》《MatmulConfig》、源码ops-nn/matmul/mat_mul_v3/(op_host/op_tiling + op_kernel/arch35)。 结论先行:两者掩盖的对象不同、不互为替代;mat_mul_v3 中 L0C DB 与 UnitFlag 独立存在、可叠加,且 UnitFlag 在该算子里只在 StreamK 的自定义搬出回调里显式开启。
0. 问题重述与先验共识
昇腾 950PR 上,Matmul 的标准数据流是:
GM ──MTE2──► L1 ──MTE1──► L0A/L0B ──MMAD──► L0C ──Fixpipe──► GM(C)
其中 L0C 的输出(Fixpipe 搬出)与 Cube 的计算(MMAD)之间存在「谁先谁后」的问题。用户观察到的两条掩盖路径:
- L0C Double Buffer(dbL0C=2):L0C 分两份,当前一份在 MMAD 计算,另一份在 Fixpipe 搬出,用「计算-搬出」重叠掩盖 Fixpipe。
- UnitFlag(细粒度同步):MMAD 每算完 512B 就立即触发 Fixpipe 搬出该 512B,用「指令级流水」掩盖 Fixpipe。
问题:既然 UnitFlag 能这么细粒度地掩盖,L0C 还需要开 Double Buffer 吗? 两者的适用边界在哪?
1. 两条掩盖路径的原理性拆解
1.1 L0C Double Buffer 掩盖的是什么
L0C DB 的目标是:当前 base 块的 Fixpipe 搬出 与 下一个 base 块的 MMAD 计算 并行。它的掩盖对象是「base 块与 base 块之间的 Fixpipe 串行等待」。
- 时序(dbL0C=2,方块为 base 块):
L0C 块0: MMAD ──► Fixpipe 搬出
L0C 块1: MMAD ──► Fixpipe 搬出
L0C 块2: MMAD ──► Fixpipe 搬出
时间轴: ────────┬───────┬───────┬──►
块0搬出与块1计算重叠 块1搬出与块2计算重叠
- 关键点:没有 L0C DB 时,当前 base 块必须等 Fixpipe 完全搬出才能开始下一个 base 块的 MMAD(L0C 只有一份,搬出期间不能写入);有 L0C DB 时,下一个 base 块的 MMAD 可以立即在另一份 L0C 上开始,当前块的 Fixpipe 与之并行。
1.2 UnitFlag 掩盖的是什么
UnitFlag 的目标是:单个 base 块内部,MMAD 计算 与 Fixpipe 搬出 的细粒度并行。它的掩盖对象是「单个 base 块内部的 MMAD-Fixpipe 串行等待」。
-
官方定义(《Matmul高阶API开启UnitFlag》):UnitFlag 为 MMAD 计算指令和 FIXPIPE 搬运指令提供基于内存访问的 512B 细粒度同步,使计算与搬运流水并行。未开启时,FIXPIPE 要等整条 MMAD 指令算完才搬出;开启后,MMAD 每算完 512B 数据,FIXPIPE 立即搬出该 512B。
-
时序(单个 base 块内):
未开 UnitFlag: MMAD(整个base块) ──────► Fixpipe(整个base块)
开 UnitFlag: MMAD[512B] ─► Fixpipe[512B]
MMAD[512B] ─► Fixpipe[512B] ...
(512B 粒度交错,计算与搬出重叠)
- 用户观察「最多剩一个小小的 16×16 尾巴 Fixpipe 输出不可掩盖」是对的:最后一个 512B 块算完后,它的 Fixpipe 没有后续 MMAD 可重叠,是尾开销。但相比整个 base 块的 Fixpipe 串行等待,这个尾开销很小。
1.3 两者的关系:掩盖对象不同,可叠加
| 维度 | 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 收益小。
2. mat_mul_v3 中两者的实际使用场景
2.1 L0C Double Buffer 的使用场景(Host tiling 决策)
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。
2.2 UnitFlag 的使用场景(Device kernel 开启)
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 模板默认不使能),源码没有显式强制开启。
3. 适用条件边界:什么时候用哪个
3.1 L0C Double Buffer 的适用边界
| 条件 | 说明 |
|---|---|
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 耗时相当。
3.2 UnitFlag 的适用边界(官方文档明确约束)
| 约束 | 内容 |
|---|---|
| 模板限制 | 仅支持 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 混合搬出。
3.3 两者的叠加与互斥判断
从官方约束与源码实现看:
- 可叠加:L0C DB 是 Host 层的 buffer 划分,UnitFlag 是 Device 层的指令同步,两者作用于不同层级,不冲突。
isA2B2Shared字段说明里甚至建议「开启 A2B2 共享时同时设 enUnitFlag=true」。 - 不互斥,但有先后:baseM=baseN=256 时 L0C 开不了 DB(容量不够),此时若 Fixpipe 是瓶颈,只能靠 UnitFlag 在块内掩盖;baseM·baseN≤32768 时 DB 可开,块间掩盖由 DB 负责,块内细粒度掩盖仍可由 UnitFlag 补充。
- StreamK 的特殊性:StreamK 的搬出是「L0C→workspace→AIV 累加→GM」,不是直接 L0C→GM,且用自定义回调显式开了 UnitFlag——因为 StreamK 的部分和搬出频繁、且与计算强相关,细粒度同步收益大。
4. 对用户问题的直接回答
Q1:mat_mul_v3 中是否存在 L0C 上开 double buffer 的场景?
存在。Host 层 dbL0C 在 baseM·baseN·4B·2 ≤ L0C(256KB)(即 baseM·baseN ≤ 32768)时置 2,普通 ASWT、A/B 全载、StreamK(复用同一引擎)都会走到。但当最优 base 块是 256×256 时,dbL0C=1(开不了)。
Q2:是否存在开 UnitFlag 特性的场景?
存在,且显式开启点唯一:StreamK 分支的自定义搬出回调 CustomDataCopyOut(mat_mul_stream_k_kernel.h),在 params->enUnitFlag 为真时设 fixpipeParams.unitFlag = 3U。普通 ASWT 分支的 UnitFlag 状态取决于 MM_CFG_NO_PRELOAD(GetMDLConfig)的默认值——官方口径 MDL 默认不使能,因此普通 ASWT 路径上 UnitFlag 大概率是关的,需对照 GetMDLConfig 实际参数确认。
Q3:L0C DB 和 UnitFlag 的适用边界?
- L0C DB:掩盖「base 块之间」的 Fixpipe 串行,要求 L0C 能容两份 base 块(baseM·baseN≤32768),适合多 base 块、MMAD 与 Fixpipe 耗时相当的场景。
- UnitFlag:掩盖「base 块内部」的 MMAD-Fixpipe 串行,512B 细粒度,适合单块 MMAD 快、Fixpipe 慢的场景;但受模板(Norm/IBShare/MDL)、流水互斥(不能同时 CO1→GM 和 A1→GM)、累加方式(不能多次 Iterate 一次输出)约束,MDL 默认关。
Q4:UnitFlag 这么好,是不是没必要开 L0C DB 了?
不是。两者掩盖对象不同:L0C DB 掩盖块间、UnitFlag 掩盖块内。在「单块 MMAD 长、多块循环」的场景,L0C DB 已能很好掩盖,UnitFlag 收益小;在「单块 MMAD 短、Fixpipe 长」的场景,L0C DB 掩盖不住(下一块 MMAD 很快算完又要等当前块搬出),必须靠 UnitFlag。两者是正交互补,不是替代关系。
5. 对 mat_mul_v3 的观察与后续建议
- UnitFlag 在普通 ASWT 路径的默认状态需确认:
MM_CFG_NO_PRELOAD = GetMDLConfig(..., true)的最后一个参数是否真是 enUnitFlag、MDL 默认值是否生效,建议对照GetMDLConfigAPI 文档核实。若普通 ASWT 默认未开 UnitFlag,而 StreamK 开了,两者在 Fixpipe 掩盖上的行为不一致,值得在性能模型里区分。 - dbL0C 与 baseM/baseN 的耦合:
dbL0C直接由 baseM·baseN 决定,但 baseM/baseN 的搜索(GetRebalanceBlock)目标是 CubeBound + 负载均衡,没有把「dbL0C 能否开」纳入目标函数。当 256×256(开不了 DB)与 256×128(能开 DB)性能接近时,模型可能选前者而牺牲 DB 收益——这是一个潜在的 tiling 改进点。 - 性能模型需区分两种掩盖机制:后续 Phase 1 的
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 |