UnitFlag 能否完全取代 L0C Double Buffer:时序与数据流分析

This commit is contained in:
2026-08-28 07:35:26 +00:00
parent 2d0047fa53
commit 8fb6c334d5

View File

@@ -0,0 +1,168 @@
# UnitFlag 能否完全取代 L0C Double Buffer —— 昇腾 950PRDAV_3510时序与数据流分析
> 续问:既然 UnitFlag 能在**单个 base 块内部**就把 `max(T_MMAD, T_Fixpipe)` 做满,那 base 块之间是不是就不用考虑 Fixpipe 这一级流水了两者能否叠加UnitFlag 能否**完全取代** L0C 双缓冲?
> 分析方法:以 NPU 数据通路 + 流水线时序图为主线,结合 mat_mul_v3 源码证据dbL0C 决策、enUnitFlag 使能点)与官方约束,逐层论证。
> 配套同仓《UnitFlag与L0C_DoubleBuffer分析.md》基础概念与边界、《3.1_mat_mul_v3源码解析.md》分支与 tiling
---
## 0. 先修正并锁定一个前提事实
> 上一轮文档曾把「普通 ASWT 的 UnitFlag 状态」标记为"待确认"。本轮查实 `GetMDLConfig` 的函数签名后**定死**
```cpp
// 官方 GetMDLConfig 签名(第 7 个位置参数即 enUnitFlag
GetMDLConfig(intrinsicsLimit, batchLoop, doMTE2Preload, isVecND2NZ,
isPerTensor, hasAntiQuantOffset, enUnitFlag=false, ...)
// mat_mul_v3_common.h
constexpr MatmulConfig MM_CFG_NO_PRELOAD =
GetMDLConfig(false, false, 0, false, false, false, /*enUnitFlag=*/true);
```
`MM_CFG_NO_PRELOAD` 的第 7 个实参是 `true`**enUnitFlag 被显式开启**。而 mat_mul_v3 的普通 ASWT、A/B 全载、SplitK 等模板 kernel 的默认模板参数就是 `MM_CFG = MM_CFG_NO_PRELOAD`StreamK 又在自定义搬出回调 `CustomDataCopyOut` 里再显式 `fixpipeParams.unitFlag = MM_FIX_PIPE_UNIT_FLAG(=3)`
**结论**mat_mul_v3 在 950PR 的主路径上,**UnitFlag 默认是开启的**(普通模板经 MM_CFG_NO_PRELOADStreamK 经回调)。这与"MDL 模板默认不使能"并不矛盾——MDL 默认关,但 mat_mul_v3 用 `GetMDLConfig` 把这一位显式置真了。
---
## 1. 两条掩盖路径的时序本质(先把"流水"画清楚)
设 MTE1L1→L0与 MTE2GM→L1已由 L1/L0 的 Double Buffer 掩盖(这是 tiling 的常规动作,与本文讨论的 L0C 无关),则单个 base 块的耗时只需关注 **MMAD 计算****Fixpipe 搬出** 两级:
```
t_mm = baseM·baseN·baseK / R_cube MMAD 计算时间R_cube=8192 FLOP/cycle
t_fp = baseM·baseN·dtypeC / BW_fp Fixpipe 搬出时间dtypeC=输出dtype字节数
```
### 1.1 无 DB 无 UnitFlag —— 完全串行
```
块n: [==== MMAD ====]··等待··[---- Fixpipe ----]
块n+1: [==== MMAD ====]··等待··[---- Fixpipe ----]
周期 T = t_mm + t_fp
```
L0C 只有一份Fixpipe 搬出期间 Cube 不能写 → 块内串行;且当前块 Fixpipe 不结束,下一块 MMAD 不能开始 → 块间也串行。这是最差形态。
### 1.2 只开 L0C Double Buffer —— 块间掩盖
L0C 分两份A/B块 n 在 L0C.A 搬出的同时,块 n+1 在 L0C.B 上算:
```
L0C.A(块n): [==== MMAD ====]········[---- Fixpipe ----]
L0C.B(块n+1): [==== MMAD ====]········[---- Fixpipe ----]
L0C.A(块n+2): [==== MMAD ====]···
周期 T = max(t_mm, t_fp)
```
**关键**DB 让"块 n 的 Fixpipe"与"块 n+1 的 MMAD"重叠,把块间等待消掉。但注意两点:
- **掩盖成立的前提一**`baseM·baseN·4B·2 ≤ L0C`256KB`baseM·baseN ≤ 32768`,否则 L0C 装不下两份DB 开不了。baseM=baseN=256 时 `256·256=65536 > 32768`**恰恰开不了 DB**。
- **掩盖成立的前提二**`t_mm ≥ t_fp`。若 `t_mm < t_fp`(计算快、搬出慢),块 n+1 的 MMAD 很快算完,仍要空等块 n 的 Fixpipe 收尾 → 周期退化为 `T = t_fp`,瓶颈转移到 FixpipeDB 掩盖不充分。
### 1.3 只开 UnitFlag —— 块内 512B 细粒度流水
UnitFlag 把单个 base 块按 512B 切成若干 micro-tileMMAD 每算完一个 512B 就立即触发对应 Fixpipe两者在**同一块内**交错:
```
块n(L0C): [M][M][M]...[M] ← MMAD 按 512B 推进
[F][F][F]...[F] ← Fixpipe 跟随 512B逐段搬出
周期 T ≈ max(t_mm, t_fp) + tail
tail = 最后一个 512B micro-tile 的 Fixpipe 尾开销,可忽略)
```
**关键差异**UnitFlag 不依赖"下一块的 MMAD"来掩盖,它在**当前块内部**就让 Fixpipe 跟着 MMAD 的进度流水走。因此:
- **不要求 L0C 装两份**(不消耗 L0C 容量);
- **不要求 t_mm ≥ t_fp**:无论谁长谁短,都能逼近 `max(t_mm, t_fp)`。即使 `t_mm < t_fp`Fixpipe 也是边算边搬Cube 不停(是 Fixpipe 流水成为瓶颈,而非 Cube 空等)。
---
## 2. 块内能 max 了,块间还需不需要考虑?—— 叠加性论证
这是你追问的核心。我们严格区分两种情形。
### 2.1 情形 AK 不在单块内累加(一次 Iterate 算完一整块 K
这是 mat_mul_v3 最常见的形态:一个 base 块的 `singleCoreK` 完整累加进 L0C算完一次性 Fixpipe 搬出到 GM。
- **开 UnitFlag 后**,单块周期已经是 `≈ max(t_mm, t_fp)`。块 n 的 Fixpipe 段与块 n 的 MMAD 段在时间上**几乎完全重叠**(只差一个 tail
- 此时若再开 L0C DB想做的是"块 n 的 Fixpipe 与块 n+1 的 MMAD 重叠"——但**块 n 的 Fixpipe 已经被块 n 自己的 MMAD 占满了时间窗**,块 n+1 的 MMAD 本可以在块 n 的 Fixpipe 尾段就开始,可 UnitFlag 已经把块 n 的 Fixpipe"摊平"到整个块 n 的计算区间里,块 n+1 能利用的"纯 Fixpipe 空窗"只剩最后一个 tail。
**所以在这个情形下DB 的块间掩盖与 UnitFlag 的块内掩盖争夺的是同一段时间(同一块的 Fixpipe被 UnitFlag 掩盖后DB 几乎无的放矢 → 两者收益不叠加。** 你的判断"开了 UnitFlag上一块的 cube 计算已经和上一块的 fixpipe 重叠了,就没法再和下一块的 cube 重叠"在 K 内一次累加场景下**成立**。
### 2.2 情形 BK 在单块内分片累加(多次 Iterate 累加进同一 L0C
`singleCoreK` 很大、被切成多个 `baseK` 分片时,同一 base 块要做多轮 `MMAD→部分结果留在 L0C`**Fixpipe 只在最后一次累加完成后才整体搬出**。
- 这里 UnitFlag 的约束就暴露了:**官方明确「使能 UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」**。也就是说,**L0C 上做多分片 K 累加的场景UnitFlag 用不了**。
- 此时若 Fixpipe 搬出与后续计算存在串行,**只能靠 L0C DB 做块间掩盖**(一块在累加,另一块的成品在搬出)。
**所以 UnitFlag 的使用边界里明确排除了「L0C K 累加」这一类场景,而这恰恰是多分片大 K 的常见形态 → 这类场景 UnitFlag 无法取代 DB。**
### 2.3 叠加性结论
| 场景 | UnitFlag | L0C DB | 能否叠加 | 主导掩盖 |
|---|---|---|---|---|
| K 一次累加t_mm≥t_fp能开DB | 块内掩盖 | 块间掩盖 | **收益不叠加**(同一段 Fixpipe | 任一即可UnitFlag 更省 L0C |
| K 一次累加t_mm<t_fp | 块内掩盖Cube不停 | 掩盖不足退化 t_fp | DB 无效UnitFlag 主导 | **UnitFlag** |
| K 一次累加baseM·baseN>32768 | 块内掩盖 | **开不了** | DB 不可用UnitFlag 主导 | **UnitFlag** |
| K 分片累加(大 K | **不支持**多次Iterate一次输出 | 块间掩盖 | UnitFlag 不可用DB 主导 | **L0C DB** |
---
## 3. UnitFlag 能否完全取代 L0C DB
**不能完全取代。** 结合上面论证与 NPU 架构约束,分三层说清:
### 3.1 从掩盖能力看UnitFlag 掩盖域 ⊇ DB在它能用的场景内
在「K 一次累加、C 输出 GM、Norm/IBShare/MDL 模板」这个 UnitFlag 的合法域内:
- DB 达到 `max(t_mm,t_fp)` 需要**两个前提**L0C 装得下两份 + t_mm≥t_fp
- UnitFlag 达到 `≈max(t_mm,t_fp)` **几乎无条件**(块内 512B 流水),还省下 L0C 容量(这份容量可用来把 baseM/baseN 做大,减少重复读)。
所以**在 UnitFlag 合法域内DB 相对它没有独立价值,可以被取代**。你的直觉在这一层是对的。
### 3.2 从合法域看UnitFlag 有硬约束DB 是兜底
UnitFlag 的官方约束(缺一不可):
1. **模板限制**:仅 Norm / IBShare / MDL 三模板;
2. **流水互斥**:使能后**不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水**
3. **累加限制**:使能 + L0C 累加时,**不支持多次 Iterate、一次 GetTensorC 输出**
4. **收益前提**:仅当 MMAD 与 Fixpipe 串行且未被 MTE2 等其他流水掩盖时才有收益。
凡是落在这些约束之外的场景(最典型就是 §2.2 的**多分片 K 在 L0C 累加**UnitFlag 直接不可用,**只能退回到 L0C DB** 做块间掩盖。
### 3.3 从 mat_mul_v3 实际看:默认已开 UnitFlagDB 是「补位」而非「主选」
- mat_mul_v3 主路径 `MM_CFG_NO_PRELOAD` 已把 enUnitFlag 置真StreamK 又在回调里显式 `unitFlag=3`**UnitFlag 是该算子的默认掩盖手段**
- dbL0C 的判据 `baseM·baseN ≤ 32768` 与"最优 base=256×256"天然冲突256×256 开不了 DB——这说明在 950PR 上,**设计者的倾向就是靠 UnitFlag 做块内掩盖DB 只在 base 块被迫做小(如矩形 256×128或落在 UnitFlag 约束之外时补位**。
**最终判断**UnitFlag 在其合法域内可以取代 L0C DB且更优但因「模板/流水互斥/累加」三重硬约束,它**无法覆盖全部场景**L0C DB 作为不依赖这些约束的块间掩盖手段,是 UnitFlag 失效场景下的必要补充,两者是**主从互补**关系,而非简单的"新替旧"。
---
## 4. 对本项目性能建模与最优方案的直接推论
1. **建模时 Fixpipe 项要分两种掩盖**:在性能模型 `T_total = max(T_MMAD, T_MTE2, T_MTE1, T_FIXPIPE)` 中,`T_FIXPIPE` 的实际暴露量应写成
- 开 UnitFlag合法域内`T_FIXPIPE_exposed ≈ tail`512B 尾开销)→ 基本可以忽略Fixpipe 通常不构成独立瓶颈;
- 不开 UnitFlag或不可用`T_FIXPIPE_exposed = max(0, t_fp - t_mm)`DB 掩盖后的残余),且仅当 `baseM·baseN ≤ 32768` 时 DB 才生效,否则 `T_FIXPIPE_exposed = t_fp`(全暴露)。
2. **最优 tiling 的取舍被简化**:既然 UnitFlag 默认开baseM/baseN 的搜索**不必再为"能否开 L0C DB"让步**,可以更激进地逼近 256×256重复读最少。原先"为开 DB 而把 baseN 砍半"的权衡在 UnitFlag 合法域内不成立。这是对 §3.2 `GetRebalanceBlock` 的一处实质修正点。
3. **StreamK 的 K 分片场景要单独建模**StreamK/大 K 分片累加命中 UnitFlag 的"多次 Iterate 一次输出"约束,其 Fixpipe 掩盖机制与普通 ASWT 不同,性能模型需为这类 case 单列。
---
## 附录:关键证据索引
| 证据 | 位置 |
|---|---|
| GetMDLConfig 签名第7参=enUnitFlag | `昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/631_..._GetMDLConfig.md` |
| enUnitFlag 默认值与约束 | `昇腾NPU知识库/CANN商用版9.0.0/02_API参考/AscendC_API/626_..._MatmulConfig.md` |
| UnitFlag 512B 细粒度同步定义 | `昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md` |
| MM_CFG_NO_PRELOAD 显式开 enUnitFlag | `ops-nn/matmul/mat_mul_v3/op_kernel/mat_mul_v3_common.h:72` |
| StreamK 回调显式 unitFlag=3 | `ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_kernel.h:36-37` |
| StreamK K 分片累加L0C→workspace→AIV | `ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_stream_k_block.h``mat_mul_stream_k_kernel.h:188-202` |
| dbL0C 判据 baseM·baseN≤32768 | `ops-nn/matmul/mat_mul_v3/op_host/op_tiling/arch35/matmul_v3_tiling_helper.cpp:490` |