PyPTO 中 AccPhase 与 STPhase 配合使用指南:Cube 与 Fixpipe 的 unit_flag 硬件握手详解
【免费下载链接】pyptoPyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto
本文是 PyPTO(Parallel Tensor/Tile Operation 编程范式)中AccPhase与STPhase两个阶段枚举的配合使用说明,而非独立接口文档。它聚焦于 matmul 系列接口(matmul/matmul_acc/matmul_mx/matmul_mx_acc)与 store 系列接口(store/store_tile/move)之间通过硬件unit_flag实现的流水握手机制。读者读完本文后,将掌握 phase 参数的配对规则、Final/Partial 的取舍依据,以及如何在 K 维分块累加、Flash Attention 等典型场景中写出既无精度问题、又不会卡死的高并行度 Cube 流水代码。
一、为什么需要 phase:软件同步 vs 硬件握手
在 PyPTO 的片上存储体系中,matmul 系列接口的计算结果首先写入L0C Buffer(累加器/Acc),随后由Fixpipe负责把 L0C 中的数据搬运到 GM(通过store/store_tile)或 UB(通过move的 Acc→Vec 路径)。Cube 计算流水与 Fixpipe 是两条独立流水,二者对 L0C 的访问天然存在读写竞争:
- 若 Fixpipe 在 matmul 尚未完成写入时就读取 L0C,会读到未完成的数据,产生精度问题;
- 若 Fixpipe 迟迟不读取、matmul 又等待搬运结果,则可能设备卡死。
默认情况下,PyPTO 框架会在 matmul 计算流水与 Fixpipe 之间自动插入软件同步,以保证两者按顺序执行;但软件同步会牺牲流水并行度。而通过phase参数使能硬件的unit_flag机制,可以让两条流水以硬件握手的方式松耦合并行,省去软件同步,提升整体吞吐。
二、产品支持情况
AccPhase与STPhase的硬件unit_flag握手能力在不同产品上的支持情况如下:
- Ascend 950PR / Ascend 950DT:支持
- Atlas A3 训练系列产品 / Atlas A3 推理系列产品:不支持
- Atlas A2 训练系列产品 / Atlas A2 推理系列产品:不支持
也就是说,phase 机制是 Ascend 950 系列特有的硬件能力,在 A2/A3 产品上使用将无法获得预期效果,请务必以目标设备的产品支持矩阵为准。
三、接口形态:哪些接口带 phase 参数
从 PyPTO 前端 DSL 的声明可以确认 phase 参数的完整分布。AccPhase用于 matmul 系列(生产方),STPhase用于数据搬出(消费方)。
3.1 matmul 系列接口(phase: Optional[AccPhase])
在 python/pypto_pro/language/_api.py 中,四个矩阵乘接口均接收phase关键字参数:
| 接口 | 语义 | phase 类型 |
|---|---|---|
pl.matmul(dst, lhs, rhs, *, phase=...) | dst = lhs @ rhs(L0A × L0B → L0C) | AccPhase |
pl.matmul_acc(dst, acc, lhs, rhs, *, phase=...) | dst = acc + lhs @ rhs(K 维分块累加) | AccPhase |
pl.matmul_mx(dst, lhs, rhs, scale_a, scale_b, *, phase=...) | MX 格式(FP8/FP4 + E8M0 scale)矩阵乘 | AccPhase |
pl.matmul_mx_acc(dst, acc, lhs, rhs, scale_a, scale_b, *, phase=...) | MX 格式带累加矩阵乘 | AccPhase |
3.2 store / store_tile / move(phase: Optional[STPhase])
同样在 python/pypto_pro/language/_api.py 中:
pl.store(dst_tensor, src_tile, offsets, *, phase=...):按绝对元素坐标把片上 Tile 写回 GM;pl.store_tile(dst_tensor, src_tile, tile_offsets, *, phase=...):按tile 块索引写回 GM(内部按 tile 形状换算偏移);pl.move(dst_tile, src_tile, *, acc_to_vec_mode=..., phase=...):片上 Tile 间搬运,其中phase仅对 Acc→Vec 路径生效(即 L0C→UB),且不能与offset参数同时使用(源码注释 明确说明)。
3.3 枚举定义
AccPhase与STPhase均为三值枚举,由 IR 层绑定导出(见 python/src/bindings/ir/ir.cpp):
ir::STPhase: Unspecified / Partial / Final ir::AccPhase: Unspecified / Partial / Final前端以pl.STPhase.XXX、pl.AccPhase.XXX形式引用(见 python/pypto_pro/language/init.py)。
3.4 代码生成层面的印证
phase 不是运行期开关,而是编译期模板参数。在 CCE 后端代码生成器 framework/src/interface/pypto_pro/backend/backend_cce_block_out_ops.cpp 中,phase 被提取为模板参数并下发:
TSTORE<[STPhase,] TileData, GlobalData, ...>:当store/store_tile带 phase 时,STPhase作为 TSTORE 的第一个模板参数(同文件 L476-L486);TMATMUL<AccPhase::Phase>、TMATMUL_ACC<AccPhase::Phase>、TMATMUL_BIAS<AccPhase::Phase>(同文件 L763-L808);TMOV/TEXTRACT同样以STPhase为首个模板参数(同文件 L641-L687)。
这说明 unit_flag 握手是在底层算子模板实例化层面使能的硬件行为。
四、硬件 unit_flag 机制
4.1 matmul 系列接口(AccPhase 视角)
phase=pl.AccPhase.Partial或phase=pl.AccPhase.Final均会使能硬件的 unitFlag 功能:
- unit_flag = 0:硬件直接写入 L0C Buffer;
- unit_flag = 1:硬件写入 L0C Buffer 的操作会被暂停,直到 unit_flag 被设置回 0。
两种模式的差异在于是否设置unit_flag:
| 模式 | 检查 unit_flag | 设置 unit_flag |
|---|---|---|
| Partial | 是(等待 unit_flag = 0 才写入) | 否(不改变 unit_flag) |
| Final | 是(等待 unit_flag = 0 才写入) | 是(写入后将 unit_flag 置为 1) |
4.2 store / store_tile / move(STPhase 视角)
phase=pl.STPhase.Partial或phase=pl.STPhase.Final均会使能硬件的 unitFlag 功能(语义与 AccPhase 恰好互补):
- unit_flag = 1:硬件直接读取 L0C Buffer;
- unit_flag = 0:硬件读取 L0C Buffer 的操作会被暂停,直到 unit_flag 被设置为 1。
| 模式 | 检查 unit_flag | 设置 unit_flag |
|---|---|---|
| Partial | 是(等待 unit_flag = 1 才读取) | 否(不改变 unit_flag) |
| Final | 是(等待 unit_flag = 1 才读取) | 是(读取后将 unit_flag 置为 0) |
将两张表对照即可看出握手闭环:matmul 的 Final 置 1,store 的 Final 清零,一次完整的"写 L0C → 读 L0C"由 unit_flag 的 0→1→0 变化串起来,两条流水无需软件同步即可安全交接。
五、phase 与自动同步的关系
是否配置 phase,决定了框架在 matmul 计算流水与 Fixpipe 之间采用哪种同步策略:
| 配置 | L0C Buffer 同步 | 同步机制 |
|---|---|---|
| 配置了 phase | 不插入 matmul 计算流水与 Fixpipe 之间的软件同步 | 靠硬件 unit_flag 实现 matmul 计算流水与 Fixpipe 之间的同步;L1 Buffer、L0A Buffer 和 L0B Buffer 等其他 Tile 的自动同步不受影响 |
| 未配置 phase | 插入 matmul 计算流水与 Fixpipe 之间的软件同步 | 框架通过软件同步保证 matmul 计算流水与 Fixpipe 的执行顺序 |
需要特别强调的是:phase 只接管"matmul 计算流水 ↔ Fixpipe"这一对关系的同步。L1、L0A、L0B 等其它 Tile 的自动同步照常生效,不会被 phase 关闭。因此配置 phase 并不会把整个程序的同步全部交给用户,用户只需对 L0C 相关路径负责。
六、使用约束
如果 phase 使用不当,可能会导致精度问题或者卡死现象。使用时必须保证:
- 配对使用:如果任一 matmul 系列接口使用了 phase,对应的 store、store_tile 或 move也必须使用 phase。两条流水必须同时切换到硬件握手模式,任何一边仍走软件同步都会造成机制不匹配。
- Final 收尾:对于同一块 L0C,matmul 系列接口的最后一轮写操作,以及 store、store_tile 或 move 的最后一轮读操作,必须使用Final模式,以保证 unit_flag 状态机能够正确闭合、复位。
七、错误案例
下面三个错误案例来自官方文档,均给出了完整的错误代码、现象与根因,是理解 phase 语义的"反面教材"。
7.1 错误案例一:matmul 无 Final 导致卡死
pl.matmul(ac, al, br, phase=pl.AccPhase.Partial) pl.store(out, ac, [0, 0], phase=pl.STPhase.Final)现象:卡死。
原因:matmul 使用 Partial 只检查 unit_flag、不设置 unit_flag,unit_flag 始终为 0。store 使用 Final 必须等待 unit_flag 被设置成 1 才能读取,但 unit_flag 永远不会被置 1,Fixpipe 一直等待 → 卡死。
7.2 错误案例二:store 未配置 phase 导致精度问题
for ki in pl.range(0, K_SQ, TILE_SQ): ... if ki == 0: pl.matmul(ac, al, br, phase=pl.AccPhase.Partial) else: pl.matmul_acc(ac, ac, al, br, phase=pl.AccPhase.Final) pl.store(out, ac, [0, 0])现象:精度问题。
原因:
- 软件同步角度:store 未配置 phase,框架会自动插入 Fixpipe 同步;但 matmul 配置了 phase,不会自动插入 matmul 计算流水同步。两种同步机制不匹配。
- 硬件 unit_flag 角度:store 未配置 phase,不受硬件 unit_flag 值影响,Fixpipe 不会等待 unit_flag。
上述两种情况,Fixpipe 搬运 L0C 数据都不会严格等待 Matmul 计算完成,导致读到未完成的数据。
7.3 错误案例三:循环内 store(Final) 后 matmul 卡死
for ki in pl.range(0, K_SQ, TILE_SQ): ... pl.matmul(ac, al, br, phase=pl.AccPhase.Final) pl.store(out, ac, [0, 0], phase=pl.AccPhase.Partial)现象:卡死。
原因:
- 第一轮循环:matmul(Final) 将 unit_flag 设置成 1,store(Partial) 能将 L0C 数据搬运出去,但未改变 unit_flag 的值(仍为 1)。
- 第二轮循环:由于共用同一块 L0C 内存,matmul 等待 unit_flag 变更为 0,但 unit_flag 始终为 1 → 卡死。
注意:案例三中 store 误用了
pl.AccPhase.Partial(应为pl.STPhase.Partial)。这里的关键教训是——Partial 永远不会复位 unit_flag,所以"Partial 读 + Final 写"的收尾组合必须在循环的最后一次迭代完成状态闭合。
八、正确用法示例
8.1 单次 matmul(无 K 维累加)
不传 phase,框架自动插入同步,最简单也最安全:
pl.matmul(ac, al, br) pl.store(out, ac, [0, 0])8.2 K 维分块累加(多块)
首块 Partial、中间块 Partial、末块 Final,store 用STPhase.Final收尾。这是经典的"多轮累加 + 单次搬出"模式:前几轮 matmul 都是 Partial(只等不置位),只有末轮 Final 把 unit_flag 置 1 通知 Fixpipe;store 全程只需要一个 Final 读取并清零:
with pl.section_cube(): ac = acc.current() for k in pl.range(0, K_TOTAL, TILE_K): ... if k == 0: pl.matmul(ac, al, br, phase=pl.AccPhase.Partial) # 首块 elif k < K_TOTAL - TILE_K: pl.matmul_acc(ac, ac, al, br, phase=pl.AccPhase.Partial) # 中间块 else: pl.matmul_acc(ac, ac, al, br, phase=pl.AccPhase.Final) # 末块 pl.store(out, ac, [0, 0], phase=pl.STPhase.Final) # Final 收尾这种写法在仓库的 ST 测试中有大量同构实现可对照,例如 python/tests/st/pypto_pro/frontend/datacopy/test_cube_datacopy_api_basic.py 与 python/tests/st/pypto_pro/frontend/datacopy/test_nz_datacopy.py(后者在 NZ 布局下使用store_tile+STPhase.Final,并支持scale量化参数,见同文件 L979-L981)。
8.3 Flash Attention
当 matmul 结果需要 vector 核做后处理(如 softmax)时,必须通过move将累加器数据搬到 UB——store只能直接写 GM,无法在 UB 上做后续计算。以 Flash Attention 的 QK matmul 为例:
with pl.section_cube(): ac = acc.current() for k in pl.range(0, K_TOTAL, TILE_K): ... if k == 0: pl.matmul(ac, q, k, phase=pl.AccPhase.Partial) else: pl.matmul_acc(ac, ac, q, k, phase=pl.AccPhase.Final) # 搬到 UB 供 vector 核做 softmax(store 做不到 L0C→UB) pl.move(qk_vec, ac, acc_to_vec_mode=pl.AccToVecMode.DualModeSplitN, phase=pl.STPhase.Final) with pl.section_vector(): # softmax: row max → sub → exp → sum → scale pl.maximum(reduce_max, qk_vec, tmp, dim=1) pl.expand_sub(tmp, qk_vec, reduce_max, dim=1) # ... exp / sum / scale ... pl.store(p_buf, qk_vec, [...])这里move的phase=pl.STPhase.Final与acc_to_vec_mode=pl.AccToVecMode.DualModeSplitN组合使用:Fixpipe 通过硬件握手等待 matmul 末轮 Final 置位后,以 DualModeSplitN 模式把 L0C 拆分为 N 方向两份送入 UB,交给 vector 核做 softmax。仓库中 python/tests/st/pypto_pro/frontend/datacopy/test_move_phase.py 正是针对"pl.move配合STPhase.Final正确完成 Acc→Vec 搬运"的专门验证用例。
九、进一步阅读
AccPhase枚举的独立接口说明:AccPhaseSTPhase枚举的独立接口说明:STPhase- matmul 系列接口文档:matmul、matmul_acc、matmul_mx、matmul_mx_acc
move接口的 Acc→Vec 路径与AccToVecMode说明见 python/pypto_pro/language/_api.py- phase 在 CCE 后端的模板参数下发实现见 framework/src/interface/pypto_pro/backend/backend_cce_block_out_ops.cpp
十、总结
PyPTO 的AccPhase/STPhase硬件握手为 Cube 与 Fixpipe 两条流水提供了软件同步之外的高并行度选项,但它是一套需要严格配对的状态机:
- Partial = 只等不置位,负责多轮流水中的中间环节;
- Final = 等 + 置位,负责开启/闭合每一轮 unit_flag 交接;
- 任一 matmul 系列接口使用 phase,对应的 store/store_tile/move 必须同步使用 phase;
- 同一块 L0C 的最后一轮写(matmul)与最后一轮读(store/move)必须都是 Final。
只要守住"配对使用 + Final 收尾"两条红线,就能在 Ascend 950 系列上安全地把 L0C 上的软同步替换为硬件握手,从而让 matmul 计算与 Fixpipe 搬运真正重叠起来,榨出更高的流水并行度。
【免费下载链接】pyptoPyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。项目地址: https://gitcode.com/cann/pypto
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考