中文 | English
附录 I:性能差异分析
对真实 ascendc-to-rs 转译批次中 998 个 CANN 8.5 内核往返性能模式的分析(与 附录 G 同一语料库)。
ascend-rs 编译管线(Rust → MLIR → C++ → bisheng)相对手写 AscendC C++ 引入了特定的代码生成模式。本附录识别这些模式、对其影响分类并提出通用的优化建议。
范围说明。 998 个内核中,247 个为 Transpiled(真实计算主体),751 个为 Registered(identity stub 主体)。§I.2 的减速模式(
TBuf对比TQue、PIPE_ALL屏障、无双缓冲、统一缓冲区尺寸)是 codegen 路径 的属性 —— 是mlir_to_cpp.rs对任何含 DMA+计算主体发射的固定模式。Registered 内核所发射的 stub 从技术上也呈现TBuf模式,但由于 stub 主体只做一次拷贝,2% 的减速数字仅对 247 个 Transpiled 内核才有意义。表格计数以全部 998 为分母,因为 codegen 路径是统一的;若读者关心的是实际运行时差距,应将分母限定为 247。
I.1 性能分类
| 分类 | 数量 | % | 描述 |
|---|---|---|---|
| EQUIVALENT | 121 | 12% | 生成代码与原始 C++ 性能一致 |
| SLOW_1.02X | 877 | 88% | 约 2% 慢,来自屏障和缓冲区开销模式 |
| SLOW_1.2X | 0 | 0% | 约 20% 慢(未观测到) |
| SLOW_1.5X | 0 | 0% | 约 50% 慢(未观测到) |
| SLOW_2X+ | 0 | 0% | 2× 或更慢(未观测到) |
备注:2% 开销源于 TBuf + PIPE_ALL 模式;在 NPU kernel-launch 粒度上实际运行时差通常在测量噪声之内。
I.2 减速模式
TBuf 取代 TQue(HIGH)
受影响内核: 998/998
问题: 使用 TBuf<VECCALC> 而非 TQue<VECIN/VECOUT>。TBuf 在每个同步点都需要显式的 pipe_barrier(PIPE_ALL),而 TQue 通过硬件 flag 实现细粒度 pipe 重叠。
修复: 生成 TQue<QuePosition::VECIN, depth>,用 AllocTensor / FreeTensor 生命周期替换 TBuf.Get / TBuf.Get 模式。
PIPE_ALL 屏障(整流水线停顿)(HIGH)
受影响内核: 998/998
问题: 每次 ascend_pipe_barrier() 都生成 pipe_barrier(PIPE_ALL),同时阻塞 所有 硬件 pipe。原始 C++ 通过 TQue 或选择性 PIPE_V / PIPE_MTE2 flag 进行 per-pipe 同步。
修复: 对仅计算同步使用 pipe_barrier(PIPE_V),对 DMA 同步使用 PIPE_MTE2,或用 TQue 彻底消除屏障。
无双缓冲(HIGH)
受影响内核: 998/998
问题: DMA 与计算完全串行化:load → barrier → compute → barrier → store。原始 C++ 以 TQue depth = 2 实现 tile N+1 DMA 与 tile N 计算的重叠。
修复: 检测 tiling 循环,生成 depth = 2 的 TQue。使用 EnQue / DeQue 在多个 tile 之间重叠 DMA 与计算。
统一最大缓冲区尺寸(LOW)
受影响内核: 998/998
问题: 所有 TBuf 分配相同的最大尺寸 =(UB_SIZE − 8 KB)/ num_bufs。原始 C++ 按实际数据需要调整各缓冲区大小。当缓冲区实际用量不同时会浪费 UB 空间。
修复: 在 MLIR 中追踪实际缓冲区用量并按比例分配。
标量数学向量化变通(MEDIUM)
受影响内核: 1/998
问题: 标量 log / exp / sqrt 通过一个 1 KB 临时缓冲区被向量化,因为在某些 NPU 型号上标量流水线会挂起。为每个标量数学 op 增加 DMA + 缓冲区开销。
修复: 在支持的型号上使用标量 pipe;在不支持的型号上通过批处理标量 op 来摊销。
I.3 优化机会
屏障消除机会(MEDIUM)
适用内核: 998/998
描述: 针对 不同 缓冲区的连续向量 op 之间不需要屏障。当前 codegen 只要 dirty_bufs 有重叠就插入屏障,但许多 op 相互独立。
实现: 在 MLIR 层实现 per-buffer dirty 追踪。仅当 同一 缓冲区存在读后写冒险时才插入屏障。
循环展开候选(LOW)
适用内核: 998/998
描述: 固定小迭代次数循环(例如 softmax 的 2 次 reduce)可以展开。当前 codegen 发射通用 while (true) 循环。
实现: 检测已知小迭代次数的循环并展开。
算子融合候选(MEDIUM)
适用内核: 0/998(未来)
描述: 对同一缓冲区的连续向量 op(例如 Sub → Exp 或 Div → Cast)可以融合为单个向量指令,或至少共享一次屏障。
实现: 检测同一缓冲区上的一元/二元 op 链并融合为复合 AscendC 指令。
I.4 通用优化计划
基于模式分析,三个优化可以为大多数内核关闭性能差距:
优先级 1:TQue 迁移(关闭 ~50% 的差距)
在 MLIR → C++ codegen 中用 TQue<VECIN/VECOUT> 替换 TBuf<VECCALC>。这消除了 PIPE_ALL 屏障,改用基于硬件 flag 的同步,并启用 DMA / 计算双缓冲重叠。
受影响文件: crates/rustc_codegen_mlir/src/mlir_to_cpp.rs
需要的改动:
- 将缓冲区声明从
TBuf<TPosition::VECCALC>改为TQue<QuePosition::VECIN>/TQue<QuePosition::VECOUT>。 - 将
tbuf.Get<T>()替换为inQueue.AllocTensor<T>()/inQueue.DeQue<T>()。 - 添加
inQueue.EnQue(tensor)/outQueue.FreeTensor(tensor)生命周期。 - 将
pipe_barrier(PIPE_ALL)替换为隐式TQue同步。
优先级 2:屏障消除(关闭 ~20% 的差距)
实现 per-buffer dirty 追踪以消除独立向量操作之间的屏障。
当前行为: 任何读取 dirty 缓冲区的向量 op 都触发 PIPE_ALL。
目标行为: per-buffer 追踪 dirty 状态。仅在以下情况插入屏障:
- DMA load 写入缓冲区 B,后续向量 op 读取 B;
- 向量 op 写入缓冲区 B,后续 DMA store 读取 B;
- 跳过
Add(buf0, buf1, buf2)与Mul(buf3, buf0, buf4)之间的屏障(buf0非 dirty)。
优先级 3:算子融合(关闭 ~10% 的差距)
将同一缓冲区上的连续向量 op 融合为复合操作:
Sub(buf, x, max)→Exp(buf, buf)→ 合为单次 Sub+Exp AscendC 调用;Muls(buf, buf, scale)→Adds(buf, buf, bias)→ MulAdd 复合;- 消除融合 op 之间的中间屏障。
I.5 按类别性能汇总
按真实 ascendc-to-rs 批次类别缩放。每个类别都具有相同的二分,EQUIVALENT 比例在单向量 op 模式主导的类别中更高(尤其是 ops_transformer,因为 attention / MLP 内核倾向于复用一个缓冲区、不触发 DMA / compute 重叠路径)。
| 类别 | 总计 | Equivalent | Slow 1.02× | Slow 1.2× | Slow 1.5× | Slow 2×+ |
|---|---|---|---|---|---|---|
| ops_cv | 41 | 4 | 37 | 0 | 0 | 0 |
| ops_legacy | 343 | 0 | 343 | 0 | 0 | 0 |
| ops_math | 155 | 12 | 143 | 0 | 0 | 0 |
| ops_nn | 306 | 6 | 300 | 0 | 0 | 0 |
| ops_oam | 3 | 0 | 3 | 0 | 0 | 0 |
| ops_transformer | 150 | 99 | 51 | 0 | 0 | 0 |
| 总计 | 998 | 121 | 877 | 0 | 0 | 0 |
ops_transformer 类别的 EQUIVALENT 占比最高(66%),因为 transformer attention / MLP 内核倾向使用单向量 op 模式,不会触发 DMA / compute 流水线重叠 —— TBuf 对比 TQue 的差别因此影响较小。
I.6 逐内核详情
全量逐内核性能报告(所有 998 个真实批次内核)作为机器生成的配套文件维护:仓库中的 blog/appendix_perf_report.md。文件列出每个内核的性能分类(EQUIVALENT / SLOW_1.02X)以及其所适用的具体减速模式(S1_TBUF_NOT_TQUE、S2_PIPE_ALL_BARRIERS 等)。
I.7 PTO 路径:双缓冲已解决(2026-04-02)
上文三个 “HIGH” 减速模式(TBuf、PIPE_ALL、无双缓冲)仅适用于 mlir_to_cpp codegen 路径。PTO tile 路径(mlir_to_pto.rs → ptoas)同时解决了三者:
| 减速模式 | mlir_to_cpp 状态 | PTO tile 路径状态 |
|---|---|---|
| TBuf 取代 TQue | 影响 998/998 内核 | 不适用 —— PTO 使用 tile 缓冲区,非 TBuf/TQue |
| PIPE_ALL 屏障 | 影响 998/998 内核 | 已消除 —— ptoas 对 softmax 只插入 2 个细粒度 flag |
| 无双缓冲 | 影响 998/998 内核 | 已解决 —— GEP 偏移修复启用了并发 tload 调度 |
tile_softmax_double_buf 示例在 Ascend 910B2 上达到 每 tile 1.62× 吞吐(0.0034 ms 对比 0.0055 ms 基线)。mlir_to_pto.rs 中的 GEP 偏移修复(commits bea12b77、9537834a)是并发调度得以工作的关键 —— 修复前,所有 partition_view op 都发射 offsets=[%c0,%c0],使两次 load 引用同一 tensor 行。见 §4.7 的结果表,以及 附录 J §J.4 的完整实现细节。