修正:补充官方约束出处,澄清高阶API约束与底层unitFlag=2/3机制的两层区别,纠正K分段累加论断

This commit is contained in:
2026-08-28 08:14:43 +00:00
parent afb91797b3
commit 5316494138

View File

@@ -125,16 +125,22 @@ UnitFlag 把单个 base 块按 512B 切成若干 micro-tileMMAD 每算完一
所以**在 UnitFlag 合法域内DB 相对它没有独立价值,可以被取代**。你的直觉在这一层是对的。
### 3.2 从合法域看UnitFlag 有约束DB 是兜底
### 3.2 从合法域看UnitFlag 有约束DB 是兜底
UnitFlag 的官方约束(缺一不可):
> **重要前提UnitFlag 有两层官方文档,约束范围不同,必须分开看**
> - **高阶 API 层**`AscendC::Matmul` 对象 + `MatmulConfig.enUnitFlag`约束见《Matmul 高阶 API 开启 UnitFlag》
> - **基础 API 层**`Mmad`/`Copy` 指令 + 指令参数 `unitFlag`机制与取值见《UnitFlag —— Mmad 计算关键特性说明》。
>
> mat_mul_v3 走**高阶 API**`mm_.Iterate()` / `mm_.GetTensorC()`),所以高阶 API 层的约束对它直接生效;基础 API 层是底层硬件机制,用来理解约束的来由。两层的"能不能做 K 分段累加"答案**不一样**(详见 §3.4.1、§3.5)。
1. **模板限制**:仅 Norm / IBShare / MDL 三模板;
2. **流水互斥**:使能后**不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水**
3. **累加限制**:使能 + L0C 累加时,**不支持多次 Iterate、一次 GetTensorC 输出**
4. **收益前提**:仅当 MMAD 与 Fixpipe 串行且未被 MTE2 等其他流水掩盖时才有收益。
**高阶 API 层官方约束**(出处:昇腾官网 → CANN → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → 《Matmul 高阶 API 开启 UnitFlag》社区版 / 《Matmul 高阶 API 使能 UnitFlag》商用版"约束条件"一节,逐字摘录):
凡是落在这些约束之外的场景(最典型就是 §2.2 的**多分片 K 在 L0C 累加**UnitFlag 直接不可用,**只能退回到 L0C DB** 做块间掩盖
1. **模板限制**UnitFlag 功能仅支持 **Norm、IBShare、MDL** 三个模板
2. **流水互斥**:开启 UnitFlag 功能时,不支持算子内**同时存在 L0C BufferCO1搬出到 Global Memory 和 L1 BufferA1搬出到 Global Memory 的两种流水**。
3. **累加限制**:开启 UnitFlag 功能时,若同时开启 **L0C 累加** 功能,不支持**多次 Iterate 计算、一次 GetTensorC 输出**。
4. **收益前提**:仅当 MMAD 流水与 FIXPIPE 流水**串行执行且未被其他流水掩盖**(比如 MTE2 Bound开启 UnitFlag 才有收益;否则收益很小。
这四条是官方文档明确写出的原文(第 2、3 条在 §3.4 逐条考据)。落在高阶 API 约束之外的场景UnitFlag 不可用,**只能退回 L0C DB** 做块间掩盖。
### 3.3 从 mat_mul_v3 实际看:默认已开 UnitFlagDB 是「补位」而非「主选」
@@ -149,47 +155,65 @@ UnitFlag 的官方约束(缺一不可):
针对三条容易被误读的官方约束,逐一给出原文出处、准确含义、以及在 mat_mul_v3 / 950PR 上的实际适用性。
#### 3.4.1 「UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」——具体是什么约束?
#### 3.4.1 「UnitFlag + L0C 累加时,不支持多次 Iterate 计算、一次 GetTensorC 输出」——具体是什么约束?它与"K 分段累加"是什么关系?
**官方原文出处**(两处,措辞略异,商用版更完整
**官方原文出处**高阶 API 层,两处):
- 昇腾社区官网 CANN 社区版 → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → **《Matmul 高阶 API 开启 UnitFlag》**,约束条件第 3 条:
> 开启 UnitFlag 功能时,若同时开启 **L0C 累加** 功能,不支持**多次计算、一次输出**。
> (注:该页面在线渲染时 "L0C 累加" 一词在部分版本缺失,仅显示"若同时开启功能",以下方商用版为准。)
- 昇腾社区官网 CANN 商用版 → 同名章节 **《Matmul 高阶 API 使能 UnitFlag》**,约束条件第 3 条(完整表述):
- 昇腾社区官网 CANN 商用版 → 同名章节 **《Matmul 高阶 API 使能 UnitFlag》**,约束条件第 3 条(表述更完整):
> 使能 UnitFlag 功能时,若同时使能 **L0C 累加功能**,不支持**多次 Iterate 计算、一次 GetTensorC 输出**。
**准确含义拆解**:这条约束针对的是 **「同一个 base 块K 方向分多片,在 L0C 上驻留并多次累加,最后才一次性 GetTensorC 搬出」** 的编程形态。即:
**这是"高阶 APIIterate/GetTensorC"层的约束,不是"基础 APIMmad/Copy"层的限制**——这是理解的关键,也是本节要澄清的核心误区。
```
// 被禁止的形态K 在 L0C 内分片累加,最后一次才搬出
for (kSlice = 0; kSlice < kSliceNum; kSlice++) { // 多次 Iterate
mm.Iterate(); // 每片 K 的部分结果累加进同一块 L0CL0C 累加)
**它约束的准确形态**:用**高阶 API** 编程时,同一个 base 块如果「`mm.Iterate()` 多次K 分片在 L0C 驻留累加)、最后只 `mm.GetTensorC()` 一次(一次输出)」,且此时打开了 L0C 累加功能,那么高阶 API 内部的 UnitFlag 封装无法正确处理 → 不允许。即:
```cpp
// 高阶 API 下被禁止的形态
for (kSlice = 0; kSlice < kSliceNum; kSlice++) {
mm.Iterate(); // 多次 Iterate部分和在 L0C 驻留累加
}
mm.GetTensorC(c); // 只在最后一次搬出(一次输出)
mm.GetTensorC(c); // 只在最后一次搬出
```
**为什么 UnitFlag 与这种形态冲突**UnitFlag 的硬件机制是「MMAD 每产出 512B 成品,就立即触发 Fixpipe 把这段搬走」。但 L0C 累加形态下,前面若干次 Iterate 的结果是**部分和,还必须留在 L0C 里等后续 K 片继续累加**,不能被搬走。一旦开 UnitFlagMMAD 算出 512B 就被 Fixpipe 立即搬出L0C 里就保不住部分和、后续累加无从谈起——两者在"这 512B 到底是驻留还是立即搬走"上直接矛盾,故硬件/API 不允许叠加。
**但是——底层基础 APIMmad/Copy 指令)完全支持 K 分段累加,且能做出很好的流水掩盖。** 这是用户指出的关键点官方《UnitFlag —— Mmad 计算关键特性说明》(基础 API/矩阵计算 Tensor API有明确机制与实例逐字核实如下
**mat_mul_v3 是否踩这条约束?——不踩(常规路径)**。看 `mat_mul_asw_kernel.h:140-141`
**底层机制**:开启 UnitFlag 后L0C Buffer 按 **512B 内存块**划分每块配一个「单元标志位」unit-flag指示该块可读/可写。`Mmad`(写)与 `Copy`/`Fixpipe`(读)通过 `unitFlag` 参数控制
| unitFlag 取值 | 语义(写=Mmad / 读=Fixpipe |
|---|---|
| **2** | 指令执行后**不改变**标志位:写操作写完保持"占用",让后续 Mmad 还能继续写这块(用于 K 累加的中间片);读操作读完保持"可读",让后续 Fixpipe 还能继续读 |
| **3** | 指令执行后**翻转**标志位Mmad 写完置"可读"(放给 Fixpipe 搬Fixpipe 读完置"可写"(放还给 Mmad |
**官方 K 分段累加实例**原文A=128×1024、B=1024×128沿 K 轴迭代,每次迭代 K 长 128共 8 次 Mmad 对应 1 次 Fixpipe
- **前 7 次 Mmad 设 `unitFlag=2`**:写入后把标志位**始终保持 0**,保证后续 Mmad 能继续写入同一块 L0C Buffer即在同一块 L0C 上做 K 累加);
- **最后 1 次 Mmad 设 `unitFlag=3`**:写入后把标志位置 1保证 Fixpipe 可以读取 L0C Buffer
- **Fixpipe 设 `unitFlag=3`**:读取后把标志位置 0保证后续 Mmad 接口可以顺利写入。
**结论修正**:所以"UnitFlag 支持 K 分段累加、最后一次搬出"在**基础 API 层是成立的、且是官方推荐用法**。用户描述的"以 16×K×16 为粒度,算完一段立即让 Fixpipe 搬出对应 16×16 到 GM、同时开始下一段 16×K×16 计算"的掩盖方式,正是 UnitFlag 在块内流水 + K 分段场景下的正确用法,**确实能有很好的掩盖**。
**它与高阶 API 约束的边界**:高阶 API 的 `mm.Iterate()` 一次调用内部会完成 `singleCoreK` 的全部 K 分片流水L0C 原地累加是 MMAD 固有语义,这一步不受约束影响);高阶约束禁的只是"**跨多次 `Iterate()`** 且最后才一次 `GetTensorC()`"这种由用户在**高阶层面**组织的驻留累加。换言之:
- **底层 Mmad 级**`unitFlag=2/3` 可自由编排 K 分段累加 → 支持;
- **高阶 Iterate 级**`Iterate` 内部 K 分片(一次 Iterate 完成)→ 支持;`Iterate` 多次 + 末次 `GetTensorC` → 不支持。
**mat_mul_v3 是否踩高阶约束?——不踩(常规路径)**。看 `mat_mul_asw_kernel.h:140-141`
```cpp
mm_.Iterate();
mm_.GetTensorC(cGlobal_[...], enAtomic || kIndex != 0); // 每次 Iterate 紧跟一次 GetTensorC
```
mat_mul_v3 的普通 ASWT 是 **「一次 Iterate → 一次 GetTensorC」一一配对**(即使在 splitKRound 循环里也是 Iterate 与 GetTensorC 成对出现,用 `enAtomic || kIndex!=0` 在 GM 侧原子加,而非 L0C 驻留累加)。它**不是**"多次 Iterate、一次 GetTensorC",因此常规路径不触碰这条约束UnitFlag 可用。
**真正会踩这条的场景**:把 split-K 的**核内**部分和放在 L0C 里驻留累加L0C 累加),或 `intraBlockPartSum`(两 AIV 结果在 L0C 累加)这类显式 L0C 累加特性。StreamK 之所以要绕道 workspace 由 AIV 累加、而不是在 L0C 里累加,正是为了避开这条。
mat_mul_v3 的普通 ASWT 是 **「一次 Iterate → 一次 GetTensorC」一一配对**(即便 splitKRound 循环里也是成对出现,用 `enAtomic || kIndex!=0` 在 GM 侧原子加,而非 L0C 驻留累加)。它不是"多次 Iterate、一次 GetTensorC",因此常规路径不触碰高阶约束UnitFlag 可用。真正会触碰的是 `intraBlockPartSum`(两 AIV 结果在 L0C 累加)这类显式 L0C 驻留累加特性——StreamK 绕道 workspace + AIV 累加正是为了避开它。
#### 3.4.2 一次 Iterate 最大支持的 K 是多少?
**先澄清概念**:官方约束里没有任何一条把 "一次 Iterate 的 K 上限" 与 UnitFlag 挂钩。**UnitFlag 约束的不是 K 的大小,而是"累加形态"**(见上一条)。一次 Iterate 能算多大的 K**L0C 之外的资源L1/L0A/L0B 容量、stepKa/stepKb 流水深度、baseK** 决定,与 UnitFlag 无关:
**先澄清概念**:官方约束里没有任何一条把 "一次 Iterate 的 K 上限" 与 UnitFlag 挂钩。**UnitFlag 约束的不是 K 的大小,而是"累加由谁、在哪个层级组织"**(见上一条)。一次 Iterate 能算多大的 K**L0C 之外的资源L1/L0A/L0B 容量、stepKa/stepKb 流水深度、baseK** 决定,与 UnitFlag 无关:
- 单次 Iterate 的 K 由 `singleCoreK` 决定,`singleCoreK` 在 L1 内按 `stepKa·baseK`A 侧)/ `stepKb·baseK`B 侧)分多片流水喂给 Cube**每片的中间结果在同一 L0C 上原地累加**这是 MMAD 固有累加语义,不是"多次 Iterate 一次输出"不违反 UnitFlag——因为整个 singleCoreK 是一次 Iterate 内部完成的)。
- 换句话说:一次 Iterate 内部Cube 会在 L0C 上做 `singleCoreK/baseK` 次累加,这在 UnitFlag 下是允许的UnitFlag 禁止的是"**跨多次 Iterate** 的累加 + 只在最后一次输出"
- 单次高阶 `Iterate()` 的 K 由 `singleCoreK` 决定,`singleCoreK` 在 L1 内按 `stepKa·baseK`A 侧)/ `stepKb·baseK`B 侧)分多片流水喂给 Cube**每片的中间结果在同一 L0C 上原地累加**MMAD 固有累加语义,属"一次 Iterate 内部",不违反高阶约束)。
- 若单次 `Iterate()` 装不下整个 KK 超过 L1/L0 一次能流水的范围),高阶层会拆成多次 `Iterate()`;此时只要不打开"L0C 驻留累加 + 末次才 GetTensorC"就不违反约束mat_mul_v3 用 GM 原子加规避)
**结论****没有"一次 Iterate 最大 K 受 UnitFlag 限制"这回事**。一次 Iterate 的 K 上限由 L1/L0 容量与 stepK 流水决定(例如 950PR 上 `maxStepK ≤ 8``baseK ≈ 128B/dtype`UnitFlag 只管"算出的 512B 何时搬出",不管 K 多大。把这条约束理解成"K 不能超过某值"是误读——它限的是"累加跨几次 Iterate",不是"K 的绝对大小"。
**结论****没有"一次 Iterate 最大 K 受 UnitFlag 限制"这回事**。一次 Iterate 的 K 上限由 L1/L0 容量与 stepK 流水决定(例如 950PR 上 `maxStepK ≤ 8``baseK ≈ 128B/dtype`UnitFlag 只管"算出的 512B 何时搬出",不管 K 多大。把这条约束理解成"K 不能超过某值"是误读——它限的是"累加跨几次 Iterate 的组织方式",不是"K 的绝对大小"。
#### 3.4.3 「不允许同时存在 CO1(L0C)→GM 与 A1(L1)→GM 两种搬出流水」——是什么意思L1 能直接写回 GM 吗?
@@ -211,10 +235,37 @@ mat_mul_v3 的普通 ASWT 是 **「一次 Iterate → 一次 GetTensorC」一一
|---|---|---|
| 仅 Norm/IBShare/MDL 模板 | UnitFlag 只在三个高阶模板下有效 | mat_mul_v3 用 MDLGetMDLConfig**满足** |
| 不能同时有 CO1→GM 与 A1→GM 两种搬出 | 搬出源必须唯一为 L0C | 351x 已删 L1→GM 通路 → **自动满足,无实质限制** |
| L0C 累加时不支持多次 Iterate 一次输出 | 禁止跨 Iterate 的 L0C 驻留累加 + 末次才搬出 | mat_mul_v3 常规路径是 Iterate/GetTensorC 一一配对 → **不触碰UnitFlag 可用**;仅显式 L0C 累加(核内 split-K 驻留 / intraBlockPartSum才触碰 |
| L0C 累加时不支持多次 Iterate 一次输出 | 高阶层禁止"跨多次 Iterate 的 L0C 驻留累加 + 末次才搬出"(底层 Mmad 级 unitFlag=2/3 仍支持 K 分段累加) | mat_mul_v3 常规路径是 Iterate/GetTensorC 一一配对 → **不触碰UnitFlag 可用**;仅显式 L0C 驻留累加( intraBlockPartSum才触碰 |
| MTE2 Bound 已掩盖时收益小 | Fixpipe 已被搬运流水掩盖时无需再开 | 这是**收益**问题不是**正确性**问题,不影响可用性 |
**由此可得更精确的判断**mat_mul_v3 在 950PR 上常规路径(含普通 ASWT、A/B 全载、以及把 K 分片放到 GM workspace 由 AIV 累加的 StreamK**都满足 UnitFlag 的可用条件且默认已开启**真正被 UnitFlag 排除的是"**在 L0C 里做跨 Iterate 驻留累加**"这一类形态——而这种形态在 mat_mul_v3 里被刻意用"GM 原子加 / workspace + AIV 累加"规避了。这进一步支撑 §3.3 的结论:**在 950PR 上 UnitFlag 的合法域覆盖了 mat_mul_v3 的全部主路径L0C DB 的补位空间被压缩得很小**。
**由此可得更精确的判断**mat_mul_v3 在 950PR 上常规路径(含普通 ASWT、A/B 全载、以及把 K 分片放到 GM workspace 由 AIV 累加的 StreamK**都满足 UnitFlag 的可用条件且默认已开启**高阶 API 真正排除的是"**多次 Iterate + 末次一次 GetTensorC**"的组织方式——而 mat_mul_v3 用"GM 原子加 / workspace + AIV 累加"规避了。这支撑 §3.3 的结论:**在 950PR 上 UnitFlag 的合法域覆盖了 mat_mul_v3 的全部主路径L0C DB 的补位空间被压缩得很小**。
---
### 3.5 两个层级的澄清:为什么"高阶约束说不能 K 分段一次输出",而"底层文档却举例 K 分段累加"
这是最容易混淆、也最关键的一点,单独澄清。
**两个层级的对象不同**
| 层级 | 编程接口 | UnitFlag 的控制量 | 对 K 分段累加的支持 |
|---|---|---|---|
| 基础 API指令级 | `Mmad` / `Copy`/`Fixpipe` 指令 | 指令参数 `unitFlag = 2/3`,逐 512B 块控标志位 | **支持**:前 n1 次 Mmad 设 2保持占用、续写累加末次 Mmad 设 3释放给 Fixpipe官方明确给出 K 分 8 次迭代的实例 |
| 高阶 API对象级 | `Matmul.Iterate()` / `GetTensorC()` | `MatmulConfig.enUnitFlag = true`,由库内部统一编排 | **受限**`Iterate` 内部的 K 分片流水(一次 Iterate 完成)支持;但"**多次 Iterate + 末次一次 GetTensorC**"这种高阶层组织的驻留累加不支持 |
**为什么高阶层反而受限**:高阶 `Matmul` 对象把"K 分片、L0C 驻留、何时搬出"都封装在 `Iterate()/GetTensorC()` 内部UnitFlag 的标志位编排也由库统一管理。当用户用高阶接口做"多次 Iterate 累加 + 一次 GetTensorC"时,库无法在一次 `GetTensorC` 里反推出前面若干次 Iterate 各自该用什么 unitFlag 时序,于是干脆禁止这种组合。而**基础 API 层把 unitFlag 的控制权直接交给用户**,用户可以自己排"前 n1 次设 2、末次设 3",所以能做更灵活的 K 分段累加流水。
**对用户设想的直接回答**:你描述的"以 16×K×16 为粒度,每段算完立即让 Fixpipe 搬出对应 16×16 到 GM、同时开始下一段计算"——**在基础 API 层完全成立,且就是官方推荐用法**(前段 unitFlag=2 保持占用、末段 unitFlag=3 释放)。这种"K 分段 + 边算边搬"能同时做到:① 块内 512B 流水掩盖;② K 分段累加。它比单纯 L0C DB 更省 L0C 容量、掩盖更细。
**但要强调一个前提**:这种灵活编排走的是**基础 APIMmad/Copy + 手写 unitFlag=2/3**,不是 mat_mul_v3 用的高阶 `Matmul` 对象。mat_mul_v3 用高阶 API 换来的是开发效率与正确性保证,代价就是受高阶约束(不能多次 Iterate 一次输出)。如果要做你说的这种极致 K 分段流水,需要下沉到基础 API 层手写 Mmad/Copy 序列——这正是 catlass类 CUTLASS 模板库)一层在做的事。
**对"UnitFlag 能否完全取代 L0C DB"的最终修正**
-**mat_mul_v3高阶 API现状**下:主路径默认开 UnitFlagK 一次 Iterate 完成Fixpipe 已被块内 512B 流水掩盖,**L0C DB 价值很小**(且 256×256 时本就开不了 DB。DB 仅在 base 块被迫做小256×128 等)时偶发有用。
-**基础 API / catlass 层**UnitFlag 用 unitFlag=2/3 可覆盖 K 分段累加场景,掩盖能力进一步逼近"完全取代 DB"。
- **仍不能完全取代的根本原因**:不在"K 能不能分段"(底层能),而在于 **UnitFlag 依赖 L0C 块被"写满即搬",它服务的是"计算结果要尽快流出 L0C"的场景**;而 L0C DB 的本质是"**用双倍 L0C 容量换时间上的重叠**",它服务的是"想让 base 块更大、减少重复读"的场景。当 L0C 容量成为 tiling 瓶颈(想把 baseM·baseN 做大但放不下两份UnitFlag不占额内容量反而比 DB 更优——这一层上 UnitFlag 甚至优于 DB。只有当场景落到 UnitFlag 高阶约束之外(如必须用多次 Iterate 驻留累加DB 才成为唯一选择。
**一句话总结**:在 950PR + mat_mul_v3 的语境里,**UnitFlag 已经基本取代 L0C DB 作为 Fixpipe 掩盖的主手段**DB 退化为"高阶 API 约束之外(多次 Iterate 驻留累加)"和"L0C 装得下两份、想换更大 base 块"这两类边角场景的兜底。
---
@@ -232,11 +283,12 @@ mat_mul_v3 的普通 ASWT 是 **「一次 Iterate → 一次 GetTensorC」一一
| 证据 | 位置 |
|---|---|
| UnitFlag 三条约束官方原文(在线) | 昇腾社区官网 → CANN → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → 《Matmul 高阶 API 开启 UnitFlag》社区版/ 《Matmul 高阶 API 使能 UnitFlag》商用版。社区版直达链接:见任务输入所给 UnitFlag.md 页面 |
| UnitFlag 三条约束官方原文(本地) | `昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md`第19/21行`昇腾NPU知识库/CANN商用版9.0.0/01_AscendC算子开发/203_..._使能UnitFlag.md`第21/23行表述最完整 |
| 高阶 API UnitFlag 约束(在线) | 昇腾社区官网 → CANN → 算子实践参考 → 优秀实践 → Matmul 性能调优案例 → 《Matmul 高阶 API 开启 UnitFlag》社区版/《Matmul 高阶 API 使能 UnitFlag》商用版"约束条件"一节 |
| 高阶 API UnitFlag 约束(本地) | `昇腾NPU知识库/CANN社区版9.2.0-beta.1/01_AscendC算子开发/292_..._开启UnitFlag.md`第15-21行`昇腾NPU知识库/CANN商用版9.0.0/01_AscendC算子开发/203_..._使能UnitFlag.md`(第17/21/23行表述最完整 |
| **基础 API UnitFlag 机制unitFlag=2/3、K 分段累加实例,在线)** | 昇腾社区官网 → CANN → API 参考 → Ascend C API → SIMD API → 基础 API → 矩阵计算Tensor API→ Mmad 计算关键特性说明 → **《UnitFlag》**用户所给链接hiascend.com/.../Mmad计算关键特性说明/UnitFlag.md |
| 基础 API UnitFlag 机制(本地缓存) | `昇腾NPU/.tmp/unitflag_raw2.txt`(已抓取的页面正文,含 128×1024 八次迭代实例原文) |
| 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` |
| 351x 删除 L1→GM / GM→L0 直达通路 | `昇腾NPU知识库/00_硬件/昇腾950_NPU架构白皮书.txt`CV融合/数据通路章节);同仓 `昇腾950PR/昇腾950PR架构解读.md` §5.1 |
| MM_CFG_NO_PRELOAD 显式开 enUnitFlag | `ops-nn/matmul/mat_mul_v3/op_kernel/mat_mul_v3_common.h:72` |
| 普通 ASWT Iterate/GetTensorC 一一配对 | `ops-nn/matmul/mat_mul_v3/op_kernel/arch35/mat_mul_asw_kernel.h:140-141` |