从 PyPTO 到硬件指令:PTO、PTOAS 与 PTX 的抽象层级和编译链路
整理日期:2026-09-10。本文根据一次关于 PyPTO、PTO、PTOAS 与 PTX 的技术讨论整理,结合对话中查阅的公开项目文档。代码用于解释概念,未进行编译或性能测试。
1. 从一个疑问出发:PTO 不是虚拟指令集吗?
理解昇腾编程生态时,很容易形成这样的直觉:PyPTO 是上层开发框架,PTO 类似 NVIDIA PTX,那么 PTO 的下一步应该就是硬件机器码。
但查看公开的 PTOAS 工具链,会发现它可以将 PTO IR 转换为调用 PTO C++ 库的代码。这带来了两个值得区分的问题:为什么编译过程又出现了 C++?这是否意味着 PTO 比 PTX 更高层?
答案是:在面向 Ascend NPU 的目标路径上,PTO 还会继续经过 lowering、目标侧编译与链接,最终形成设备可执行代码;C++ 可以只是其中一种中间载体。PTO 整体偏向更高层的块级抽象,但不能仅凭“生成 C++”判断抽象层级。 PTO 生态同时提供 CPU 模拟等路径,因此不能把所有后端都概括成“最终一定生成 NPU 机器码”。
2. 先分清 PyPTO、PTO 与 PTOAS
| 名称 | 定位 | 主要职责 |
|---|---|---|
| PyPTO | 上层编程框架与编译系统 | 用 Python 接口表达张量或块级计算,支持融合算子和模型计算开发 |
| PTO | 块级虚拟指令集与编程抽象 | 规定 Tile 计算、数据搬运等操作的语义与约束 |
| PTO Tile Library | PTO 指令的 C++ 接口及实现 | 提供模板和头文件,根据编译配置选择对应后端 |
| PTOAS | PTO Assembler & Optimizer | 解析、验证和优化 PTO IR,并在公开路径中生成使用 PTO 库的 C++ 代码 |
| 昇腾设备编译器 | 后续目标代码编译环节 | 将设备侧代码进一步编译为目标 NPU 可执行代码 |
PyPTO 的公开说明强调多层中间表示和 Tile 编程。它与 Triton 在“降低高性能计算开发门槛”这一目标上相近,但 PyPTO 具有自己的表达、转换和调度设计,不能直接把它理解成 Triton 的另一个名字。
PTO ISA / pto-kernels 公开资料常把 PTO 展开为 Parallel Tile Operation,并把它描述为面向 Ascend 的 Tile-level virtual ISA;而当前 PTOAS README 又使用 PTO Bytecode(Programming Tiling Operator Bytecode) 这一表述。公开仓库的术语存在演进或组件口径差异,因此学习时不要只依赖缩写全称判断层级,重点看它实际定义的 Tile 操作语义、PTO Dialect/Bytecode,以及后续 lowering 路径。本文讨论“PTO ISA”时沿用 Parallel Tile Operation 的名称。
另外,当前公开 PyPTO 文档把 .pto 视为 PyPTO 与 PTOAS 之间的接口契约:PyPTO 生成 PTO Dialect IR,PTOAS 负责解析、优化并下降;这比简单理解成“Python 代码直接翻译成一条条硬件指令”更准确。
3. Tile 到底是什么?
Tile 可以理解为参与一次块级操作的数据区域。以矩阵乘法为例,大矩阵往往需要分块处理,使数据适配片上存储和计算单元,并提高复用率。
写出矩阵乘公式,只回答了“算什么”;高性能实现还需要回答:块的形状与大小是什么、如何布局、何时搬运、哪些数据能够复用,以及计算和搬运如何形成流水线。
Tile 抽象把一部分编程关注点从逐个元素提升到数据块,但没有消除存储、布局和同步问题。
例如,一个逻辑上为 16×16 的 Tile 包含 256 个元素。对两个这样的 Tile 做逐元素加法,语义上包含 256 对元素相加。**这不等于硬件在一个周期中完成全部计算,也不保证只生成一条硬件指令。**实际执行取决于目标硬件、数据类型、布局和实现方式。
4. PTO 已公开哪些指令?
公开手册可以直接查看操作含义和接口约束:
| 类别 | 示例 | 用途 |
|---|---|---|
| 全局内存搬运 | TLOAD、TSTORE | 在全局内存与 Tile 之间传输数据 |
| 逐元素计算 | TADD、TMUL、TEXP | 块内逐元素加法、乘法、指数计算 |
| 矩阵计算 | TMATMUL、TMATMUL_ACC | 矩阵乘与矩阵乘累加 |
| 归约 | TROWSUM、TROWMAX | 每行求和、求最大值 |
| 类型转换 | TCVT | 转换元素类型 |
| 数据组织 | TMOV、TEXTRACT | 块间搬移、提取子块 |
下面展示加法的核心意图,省略 Tile 类型、全局张量定义、地址绑定和同步,不是完整可运行程序:
TLOAD(aTile, aGlobal);
TLOAD(bTile, bGlobal);
TADD(cTile, aTile, bTile);
TSTORE(cGlobal, cTile);
这段代码描述“加载两块数据、整块相加、写回”。真实内核还必须满足数据就绪和存储生命周期等要求,不能因示例中省略同步就认为同步不重要。
5. 为什么 PTOAS 会生成 C++?
5.1 编译通常由多个阶段协作完成
当前公开 PTOAS / PyPTO 文档中的一条主要路径可以概括为:
PyPTO / PTODSL → .pto(PTO Dialect / Bytecode)→ PTOAS 验证、优化与 lowering → 调用 pto-isa Tile 指令接口的 C++ → 目标侧编译与链接 → NPU 可执行代码
这里应把它理解为多级 lowering 链而不是“PTO 必须直接一跳变成机器码”。PTOAS 当前还公开了 EmitC / Linalg、VPTO 等后端或中间路径,因此具体编译链会随版本、后端和目标硬件变化。
PTOAS 基于 LLVM/MLIR,能够解析 PTO Dialect 操作并进行转换;README 描述了经 EmitC 等表示生成 C++ 的路径。MLIR 是容纳和转换多种中间表示的基础设施,EmitC 则服务于生成 C/C++ 代码。
这条链路说明:PTOAS 负责其中一段编译工作,而后续设备编译器继续完成目标代码生成。不能把 PTOAS 的输出误当成整个编译过程的终点,也不能据此假设所有前端和版本都只能采用同一条路径。
参考:PTOAS 编译流程。
5.2 “调用库”不等于运行时回到 CPU
PTO Tile Library 主要采用头文件和模板实现。编译时会根据目标选择 NPU 实现或 CPU 模拟等后端。
看起来像函数调用的代码:
TADD(cTile, aTile, bTile);
可以通过模板实例化、内联及后续编译转为设备操作。它不意味着每次加法都从 NPU 返回 CPU,由 CPU 调一个函数再重新启动 NPU。
CPU 模拟后端是一条开发验证路径,也不意味着 NPU 执行必须经过 CPU 模拟。
参考:PTO Tile Library 的头文件与后端说明。
5.3 为什么选择这种工程路线?
从工程角度推断,生成 C++ 有利于复用已有的硬件适配实现和设备编译器,减少重新实现底层代码生成的工作。这是对实现方式的解释,并非已经核实的项目设计者原话。
可以类比 C++ 中的 SIMD intrinsic:源代码呈现为函数形式,最终却可能落实为处理器指令。源代码的外观与最终执行方式,是两个不同维度。
经过更多编译阶段也不意味着运行时必然更慢。性能应当通过最终代码、存储访问、同步和实测判断;C++ 中间层同样不能自动保证零开销。
6. PTO 是否比 PTX 更高层?
整体而言,可以说 PTO 更偏块级抽象,PTX 更偏线程与虚拟寄存器抽象。这个判断来自编程模型,而不是来自输出文件扩展名。
| 比较维度 | PTO | PTX |
|---|---|---|
| 核心模型 | Tile 计算、数据搬运与同步 | 线程执行、虚拟寄存器和内存空间 |
| 常见加法表达 | 对一个 Tile 逐元素相加 | 线程内部的寄存器加法 |
| 后端继续解决的问题 | 将块级操作落实到目标硬件实现 | 将虚拟指令落实到目标 GPU 指令 |
| 与机器指令是否一一对应 | 不保证 | 不保证 |
| 跨代作用 | 为不同昇腾代际提供统一块级接口 | 为 NVIDIA GPU 提供跨代虚拟指令接口 |
但不能说“每条 PTO 指令都比每条 PTX 指令高一级”。PTX 也包含协作矩阵运算和异步数据搬运等操作,并非只有标量运算。
同样,“虚拟 ISA”不意味着它必须直接对应某种裸硬件编码。这里的关键是定义可被实现的操作语义与约束,并为上层编译器和下层目标实现提供接口。
7. 把这套认识连接到 Attention 优化
以 Attention 为例,数学上涉及 QK 矩阵乘、缩放与掩码、Softmax,以及与 V 的矩阵乘。落到块级实现后,可以把问题拆成矩阵运算、行归约、逐元素变换、数据搬运与同步。
理解 PTO 指令后,值得继续追问:
- Q、K、V 各自如何分块,哪些块被重复使用?
- 中间结果放在片上还是写回全局内存?
- 搬运与计算能否重叠,什么时候必须等待?
- Softmax 跨多个块时,如何维护正确的归约状态?
- Tile 大小变化后,局部存储占用、并行度和搬运效率如何变化?
这些问题把“认识指令名”推进到“理解算子性能”。实际融合 Attention 比简单串联几个指令复杂,尤其需要处理数值稳定性、跨块归约和流水线。
建议从加法理解数据流,再用 GEMM 学习分块与复用,最后研究融合 Attention;在这个过程中同时查看生成代码和性能数据,而不是仅凭框架层数推测效率。
8. 本次讨论修正了哪些认识?
- **PyPTO 与 PTO 不是同一个层面的东西。**前者是上层编程框架,后者提供块级指令抽象。
- **PTOAS 生成 C++ 不代表编译结束。**后面仍由设备编译器生成 NPU 代码。
- **C++ 函数形式不代表运行时跨设备调用。**模板和 intrinsic 可以在编译时展开。
- **PTO 整体更偏块级,但不是严格处处高于 PTX。**应比较具体操作暴露和隐藏了哪些细节。
- 虚拟指令条数不能直接当作机器指令条数或周期数。
- **编译链长短不能直接推出运行速度。**最终性能需要代码分析与测量。
9. 相关笔记与阅读入口
阅读项目时应结合具体版本。本文记录的是本次讨论所查阅的公开实现,不将某一条后端路径推广为整个生态永久不变的规定。