编程范式#
PyPTO采用PTO编程范式,核心思想是使用Tensor作为数据的基本表达方式,通过一系列对Tensor的基本运算来描述并组装完整的计算流程(或计算图)。在PyPTO中,所有运算都以Tensor为输入或输出,形成可追溯的计算图结构,方便后续的调试、优化以及在特定硬件上的编译和执行。
PTO编程范式概述#
PTO编程范式的核心设计理念包括:
Tensor级别抽象:以Tensor而非单个元素描述计算,贴近算法设计者的数学表达式。
声明式编程:开发者只需描述“做什么”,框架自动处理“怎么做”。
基于Tile的计算:所有计算最终都基于Tile(硬件感知的数据块)进行,充分利用硬件并行计算能力。
计算图驱动:通过构建计算图,框架可以自动进行优化、调度和执行。
PyPTO提供了三种层次的编程接口:
Tensor层次编程:直接使用Tensor 和 Tensor Operation 构建计算图。
Tile层次编程:以Tile 和Tile Operation表达完整的计算,显式体现访存与依赖。
Block层次编程:定义单个处理器核执行的计算图,并通过多次实例化实现整体计算。
当前版本仅开放Tensor层次编程,这是最常用和推荐的编程方式。
核心数据结构#
Tensor:是PyPTO中最基本的数据结构,表示一个多维数组。Tensor包含以下信息:
数据类型(dtype):如FP32、FP16、INT32、BOOL等
形状(shape):用一个整型数组描述各维度长度,例如(32, 64)、(1, 32, 128) 等
格式(format):数据在内存中的排布格式
名称(name):用于在计算图中标识该Tensor,便于调试与可视化
Tensor之间可以通过各种操作(如加法、乘法、索引操作、归约操作等)进行组合,这些操作通常会生成新的Tensor,或者改变其在计算图中的引用方式(例如创建视图、改变Shape、转置等)。
Tile:是Tensor的子区间(sub-Tensor),通过Tiling(切分)将大的Tensor切分为多个子块。Tile的设计目的是:
使其能够存放在处理器核内私有缓存(如UB,L1)中,以提升数据局部性
充分利用硬件并行计算能力
优化内存访问模式
在Tensor层次编程中,Tiling由框架自动完成,开发者只需通过配置接口指定TileShape,框架会自动进行切分。
View与Assemble:提供对子Tensor的视图和组合操作,在处理动态Shape和循环计算中非常有用。
View:提供对子Tensor的视图操作,允许在不复制数据的情况下访问Tensor的子区间。
Assemble:将多个子Tensor组合成一个更大的Tensor。
Tensor层次编程#
Tensor层次编程是PyPTO当前主要支持的编程方式,开发者直接使用Tensor和Tensor Operation构建计算图,无需关心底层的Tile切分和硬件细节。
基本编程模式
典型的Tensor层次编程模式如下,Kernel入口函数通过@pypto.frontend.jit装饰器定义,在第一次调用时会进行JIT编译。
import pypto # 1. 配置 Tiling(可选,框架会提供默认值) pypto.set_vec_tile_shapes(64) # 2. 定义计算函数 @pypto.frontend.jit def my_operator(a: pypto.Tensor(shape, dtype), b: pypto.Tensor(shape, dtype), output: pypto.Tensor(shape, dtype)): # Tensor 操作 result = a + b # 或使用 pypto.add(a, b) output[:] = result # 3. 执行 my_operator(tensor_a, tensor_b, output_tensor)
Tensor操作
PyPTO提供了丰富的Tensor操作,包括:
数学运算:add、sub、mul、div、matmul等
逻辑运算:logical_not等
结构变换:reshape、transpose、view、unsqueeze等
归约操作:sum、amax、amin、topk等
激活函数:sigmoid、softmax等
超越函数:exp、log等
其他操作:gather、scatter、concat、assemble等
控制流
PyPTO支持控制流操作,用于处理动态Shape和条件执行:
循环(Loop)
# 处理动态维度数据 tile_size = pypto.symbolic_scalar(64) loop_count = dynamic_shape / tile_size for idx in pypto.loop(0, loop_count, 1, name="LOOP_BATCH"): offset = idx * tile_size end = (idx + 1) * tile_size input_view = input_tensor[offset:end, :] output_tensor[offset:end, :] = process_tile(input_view)
条件分支(Conditional)
for idx in pypto.loop(b_loop): t3_sub = t0_sub + t1_sub if pypto.cond(idx < 2): # 动态条件判断 t2[b_offset:b_offset_end, ...] = t3_sub + 1 else: t2[b_offset:b_offset_end, ...] = t3_sub
符号化编程
PyPTO支持符号化标量(SymbolicScalar),用于支持动态Shape Tensor的表达和处理,使得框架可以在编译时进行Shape推断和优化。
# 创建动态Shape Tensor tensor = pypto.tensor([-1, 32], pypto.DT_FP16, "dynamic") # 获取动态维度的符号化标量,在运行时获取具体数值 b = pypto.symbolic_scalar(tensor_shape[0])
计算图#
计算图的组成
PyPTO的计算图由以下元素组成:
Tensor:数据节点。
Operation(Op):对数据的操作,分为Tensor Op与Tile Op。
Tensor Op:作用于Tensor,逻辑上不受存储位置和规模约束。
Tile Op:Tensor Op的子集,限定输入输出位于同一核的L1内存,确保数据局部性。
计算图的转换流程
将用户定义的计算图计算图最终转换成可执行代码:

计算图的查看
PyPTO提供了多种方式查看计算图:
JSON格式:导出为JSON格式,便于程序分析
可视化工具:通过PyPTO Toolkit插件可视化计算图结构
MPMD执行模型#
PyPTO基于MPMD(Multiple Program Multiple Data)执行模型,与传统的SPMD(Single Program Multiple Data)模型相比:
SPMD:用户需编写单一内核逻辑并实例化到多个处理器核上运行,带来同步开销和性能瓶颈
MPMD:计算被抽象为一组异构任务,任务之间通过依赖关系组织。运行时调度器根据依赖关系将任务分配到合适的执行单元,避免了全局同步限制,提升了整体利用率与效率
MPMD执行模型的优势包括:
灵活的调度:不同任务可以分配到不同的处理器核,避免全局同步
更好的资源利用:根据任务特性选择合适的执行单元
细粒度并行:计算负载既可在细粒度上并行切分,又能在任务级别灵活调度
适配多核架构:更好地适配NPU的多核架构
执行流程为:

编程示例#
依托PTO编程范式,开发者可高效开发多样化算子,并与PyTorch无缝集成。
向量加法(Vector Add)
import pypto # 配置 Tiling pypto.set_vec_tile_shapes(64) # 定义计算函数 @pypto.frontend.jit def vector_add(a: pypto.Tensor(shape, dtype), b: pypto.Tensor(shape, dtype), output: pypto.Tensor(shape, dtype)): # Tensor 操作:向量加法 output[:] = a + b # 输出结果 # 执行 vector_add(tensor_a, tensor_b, output_tensor)
矩阵乘法(Matrix Multiplication)
import pypto # 配置 Cube Tiling(用于矩阵乘法) pypto.set_cube_tile_shapes([64, 64], [128, 128], [128, 128]) @pypto.frontend.jit def matmul(a: pypto.Tensor(shape_a, dtype), b: pypto.Tensor(shape_b, dtype), output: pypto.Tensor(shape_c, dtype)): outputs[:] = pypto.matmul(a, b) # 矩阵乘法 # 执行 matmul(matrix_a, matrix_b, output_matrix)
动态Shape处理
import pypto def softmax_core(x: pypto.Tensor) -> pypto.Tensor: row_max = pypto.amax(x, dim=-1, keepdim=True) # 计算行最大值 sub = x - row_max # 值归一化 exp = pypto.exp(sub) # 指数运算 esum = pypto.sum(exp, dim=-1, keepdim=True) # 求和 return exp / esum # 概率归一化 @pypto.frontend.jit def dynamic_softmax(input_tensor : pypto.Tensor(in_shape, dtype), output_tensor: pypto.Tensor(out_shape, dtype)): # 获取动态维度 batch_size = input_tensor.shape[0] tile_size = pypto.symbolic_scalar(64) loop_count = batch_size // tile_size # 循环处理 for idx in pypto.loop(0, loop_count, 1, name="LOOP_BATCH"): offset = idx * tile_size end = (idx + 1) * tile_size # 提取当前 Tile x_view = input_tensor[offset:end, :] # 计算 Softmax softmax_out = softmax_core(x_view) # 组装结果 output_tensor[offset:end, :] = softmax_out # 执行 dynamic_softmax(input_tensor, output_tensor)
与PyTorch集成
import pypto import torch @pypto.frontend.jit def my_operator(x: pypto.Tensor(in_shape, dtype), output: pypto.Tensor(out_shape, dtype)): result = pypto.matmul(x, weight) output[:] = result # 使用 PyTorch Tensor input_torch = torch.randn(32, 128, device='npu') output_torch = torch.zeros(32, 64, device='npu') # 执行 my_operator(input_torch, output_torch)
总结#
PTO编程范式通过Tensor级别的抽象,使开发者能够以更直观的方式表达计算逻辑,而框架则自动处理底层的优化、调度和执行。这种设计不仅保证了开发的简洁性,还充分利用了硬件的并行计算能力,为AI加速器编程提供了一个高效且灵活的解决方案。