PyPTO Professional模式: PyPTO算子开发体验增强
PyPTO-Tensor 让你「快速写出算子」,PyPTO Professional模式让你「进一步挖掘性能」。
1. 背景:我们为什么需要PyPTO Professional模式
先说一个几乎所有算子开发者都经历过的场景。
你接到一个融合算子需求,用PyPTO Tensor DSL 写了两百行,一天跑通,精度对齐,性能达到了0.6x的理论值,非常开心的搞定了训练和后训练。但是到了推理部署阶段,性能团队永远是希望尽可能的压榨硬件,拿到更优的性能,减少推理的成本,这就需要算子对硬件更极致的控制。
于是你开始「往下走」—— 手写指令级 Kernel。这一走,画风突变:
// 熟悉的味道:一半代码在算 offset,另一半在填指令参数
uint64_t src_offset = (b * n_head + h) * seq_len * head_dim
+ (s_outer * TILE_S + s_inner) * head_dim + d_off;
uint16_t nBurst = tile_rows;
uint16_t lenBurst = tile_cols * sizeof(half) / 32;
uint16_t srcGap = (stride_row - tile_cols) * sizeof(half) / 32;
uint16_t dstGap = 0;
copy_gm_to_cbuf(dst + dst_offset, src + src_offset, 0, nBurst, lenBurst, srcGap, dstGap, ...);
set_flag(PIPE_MTE2, PIPE_MTE1, EVENT_ID0); // 这三行写错一个 event id
wait_flag(PIPE_MTE2, PIPE_MTE1, EVENT_ID0); // 就是一个下午的 debug
// ... 还有 ping-pong buffer 的地址轮转、跨核 flag、preload 展开
这里有两个真实的痛点,它们是 PyPTO Professional 推出的理由:
痛点一:性能。 基于 Tensor 抽象的编程范式(PyPTO-Tensor)在表达力和开发效率上极好,但抽象层次越高,编译器需要替你做的决策就越多——多核切分、Buffer 复用、流水排布,任何一个决策不是你想要的,性能就大打折扣。同时MPMD的调度使用了AICPU,调度开销在小算子上不可忽略。而越来越多的客户场景(大模型的 FA、MLA、SFA 路由,以及各种奇形怪状的融合算子)明确提出需要更高的性能。到了极限的性能场景,「编译器帮我猜」或者「抽象的性能旋钮」不够用了,开发者必须能亲手按住每一块片上 Buffer、每一条流水。
痛点二:传统算子语言的「体感」。 能达到极致性能的路径一直存在——就是上面那段代码。但它的编码风格要求多维坐标要手工摊平成一维 offset,指令参数要按硬件手册逐个填,double buffer 要手工轮转地址,流水同步要手工 set_flag/wait_flag 配对,跨核流水还要手工展开 preload。这些工作没有创造性、极易出错、且与算法逻辑完全无关,却占掉了算子开发 70% 的时间和 90% 的 debug 精力。
结论很清楚:我们要的是在不交出硬件控制权的前提下,把那些机械、易错的活儿交给框架。
这就是 PyPTO Professional。
2. PyPTO Professional模式 是什么
PyPTO Professional模式 是一门以 Python 为前端、构建在 PTO-ISA 高性能指令集之上的 DSL,与 PyPTO-Tensor 是同一个家族的两位成员:
| PyPTO-Tensor | PyPTO Professional | |
|---|---|---|
| 抽象载体 | Tensor 图 | Tile(二维数据块)+ RegTensor |
| 谁做多核切分 | 框架 | 开发者(和算子强相关,通用算法性能有上限) |
| 谁做 Buffer 复用 | 框架 | 开发者(和Tiling强绑定,通用算法性能有上限) |
| 谁算 offset / 填指令参数 | 框架 | 框架 |
| 谁插核内同步 | 框架 | 框架(auto_mutex=True) |
| 谁排核间流水 | 框架 | 框架(@pl.pipeline.stage) |
| 定位 | 算法开发者快速实现 | 在高易用的前提下开放几乎全部硬件控制力 |
他两从架构上是共后端IR和PTO-ISA,前端表达上略有不同,架构图如下:

PyPTO Professional 的设计哲学是立足易用性,并尽可能的提供更强的硬件控制力:
把「决定性能的决策」留给开发者,把「不决定性能但极易出错的苦力」交给框架。

多核怎么切、Tile 开多大、Buffer 摆在 UB 的哪个地址、流水开几级——这些直接决定你能不能摸到 0.9x+ 峰值,框架绝不替你猜。而 offset 换算、指令参数、event id 分配、同步配对、preload 展开——这些写对了性能不会更好、写错了直接精度出错或死锁,框架全包。
先看它长什么样。一个完整可运行的 Add 算子:
import pypto_pro.language as pl
import torch
@pl.jit
def add_kernel(
x: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP16],
y: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP16],
z: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP16],
):
tile_type = pl.TileType(shape=[128, 128], dtype=pl.DT_FP16, target_memory=pl.MemorySpace.Vec)
a_db = pl.make_tile_group(type=tile_type, addrs=0x0000, mutex_ids=[0, 1]) # 地址手动指定, 使能double buffer
b_db = pl.make_tile_group(type=tile_type, addrs=0x10000, mutex_ids=[2, 3])
c_db = pl.make_tile_group(type=tile_type, addrs=0x20000, mutex_ids=[30, 31])
with pl.section_vector():
num_cores = pl.get_block_num()
core_id = pl.get_block_idx()
m_tile_num = x.shape[0] // 128
n_tile_num = x.shape[1] // 128
for i in pl.range(core_id, m_tile_num, num_cores):
for j in pl.range(0, n_tile_num, 1):
tile_a = a_db.next() # 自动选择下一块buffer,并插入对应的同步
tile_b = b_db.next()
tile_c = c_db.next()
pl.load_tile(tile_a, x, [i, j]) # 根据坐标自动计算offset
pl.load_tile(tile_b, y, [i, j])
pl.add(tile_c, tile_a, tile_b)
pl.store_tile(z, tile_c, [i, j])
device = "npu:0"
torch.npu.set_device(device)
m_size = 8192
n_size = 4096
num_cores = m_size // 128
torch.manual_seed(0)
dtype = torch.float16
# Host 侧就是普通 PyTorch
x = torch.rand([m_size, n_size], device=device, dtype=dtype)
y = torch.rand([m_size, n_size], device=device, dtype=dtype)
z = torch.empty([m_size, n_size], device=device, dtype=dtype)
add_kernel[None, num_cores](x, y, z) # 首次调用触发 JIT
torch.npu.synchronize()
没有 .cpp,没有 CMake,没有算子工程,没有 set_flag。 一个 .py 文件里 Host 侧和 Device 侧代码并排放着,python add.py 直接跑。这是 PyPTO Professional 给人的第一印象。
3. 架构总览:从 Python 到二进制发生了什么
add_kernel(a, b, out) 这一行调用背后,是一条完整的编译流水线:

几个值得注意的设计点:
- 前端是「解析」而不是「追踪」。 PyPTO Professional 真正解析你的 Python AST(
if/elif/else、for、函数调用、闭包变量都按 Python 语义处理),因此报错能精确到源码行列,而不是给你一堆运行时 trace 的天书。 - Pipeline 变换在前端完成,且产物可读。 流水变换后的等价 Python 源码会被 dump 成
pipeline_generated.py写进 build 目录——框架替你展开的 preload 和同步长什么样,你打开文件就能看,而不是只能靠猜。这一点对建立信任非常重要。 - Cube 和 Vector 是两个独立 Program。
section_cube()/section_vector()标记的代码段在 IR 层就被拆成两份,分别 codegen、分别编译,最后 fatobj 链接成一个 Kernel。这也是核间(Cube↔Vector)流水能被自动编排的结构基础。 - 编译期特化取代 C++ 模板。 TilingKey 的每个取值组合会把字段折成 IR 常量,生成一份专属的
kernel.cpp。你不需要写template<int TILING_KEY>,也不需要和模板实例化爆炸搏斗。
4. 一句话总结
PyPTO Professional 的赌注是:算子开发者应该把时间花在「切多大的块、开几级流水、Buffer 怎么摆」上,而不是花在「这个 offset 算对了吗、这个 event id 配对了吗」上。
前者是创造性的、决定性能上限的工作;后者是机械的、只决定你今晚能不能下班的工作。PyPTO Professional 把后者全部接管,同时一寸不让地把前者留给你——因为想摸到理论峰值 0.9x 以上,方向盘就必须在人手里。
如果你符合下面任意一条,那么欢迎使用PyPTO Professional,他会给你舒适的算子编程体验:
- ✅ 手写过指令级 Kernel,被
set_flag/wait_flag折磨过; - ✅ 被流水编排,核间同步插入折磨过(精度问题、卡死等);
- ✅ 需要开发 Cube + Vector 融合算子(FA / MLA / SFA / 各种自定义融合);
- ✅ 已有PyPTO-Tensor算子性能不满意,想要更细粒度的硬件控制;
- ✅ 想要一条从「快速实验」直达「二进制发布」且不修改任何kernel源码的完整路径。
更多推荐




所有评论(0)