Tensor创建和操作#
Tensor用于描述GM中的多维数据,作为Kernel的输入、输出或由裸指针构造的数据视图。Tensor记录数据类型、shape、stride和layout等信息,但不负责申请GM内存。Kernel计算使用的片上缓冲区由Tile表示,相关内容请参考Tile创建和操作。
在Kernel签名中声明Tensor#
Host侧使用PyTorch在NPU上创建输入和输出数据,Kernel通过pypto_pro.language.Tensor类型标注接收这些数据。Tensor类型标注的基本形式如下:
import pypto_pro.language as pl
@pl.jit(auto_mutex=True)
def kernel(
x: pl.Tensor[[pl.DYNAMIC, 128], pl.DT_FP16],
out: pl.Tensor[[pl.DYNAMIC, 128], pl.DT_FP16],
):
...
Tensor[[shape…], dtype]中的第一个参数是shape,第二个参数是元素数据类型。还可以使用第三个参数声明GM数据的layout:
import pypto_pro.language as pl
nz_tensor: pl.Tensor[[64, 128], pl.DT_FP16, pl.NZ]
layout标注只描述GM中已有数据的排布,不执行不同layout之间的数据转换。数据转换由支持相应格式的搬运接口完成。
输入和输出方向标注#
使用Profiling采集Kernel信息时,可以通过Tensor类型标注中的pypto_pro.language.Input或pypto_pro.language.Output枚举值声明参数方向:
import pypto_pro.language as pl
@pl.jit(auto_mutex=True)
def kernel(
x: pl.Tensor[[pl.DYNAMIC, 128], pl.DT_FP16, pl.Input],
out: pl.Tensor[[pl.DYNAMIC, 128], pl.DT_FP16, pl.Output],
):
...
pypto_pro.language.Input表示输入,pypto_pro.language.Output表示输出。direction可省略,未标注时默认使用pypto_pro.language.Input;需要在Profiling结果中将Tensor上报为输出时,使用pypto_pro.language.Output。
在方括号形式中,direction可与layout、memref一起声明,推荐将direction放在标注末尾,例如pl.Tensor[[64, 128], pl.DT_FP16, pl.NZ, pl.Output]。调用式也支持将direction作为第三个参数,例如pl.Tensor([64, 128], pl.DT_FP16, pl.Output)。
方向标注只影响Profiling结果中的Tensor输入、输出信息,不改变Kernel的读写语义。完整语法和约束请参考pypto_pro.language.Tensor。
Shape声明方式#
Tensor的每一维可以使用以下方式声明:
声明方式 |
含义 |
对编译变体的影响 |
|---|---|---|
正整数 |
固定维度,启动时实际尺寸必须与声明一致 |
尺寸固定 |
pypto_pro.language.DYNAMIC |
运行时动态维度 |
维度值变化时复用同一编译变体 |
pypto_pro.language.STATIC |
编译期特化维度 |
维度值变化时生成新的编译变体 |
末尾的… |
展开剩余维度,各维均按STATIC处理 |
rank或维度值变化时生成新的编译变体 |
不同方式可以混合使用:
import pypto_pro.language as pl
# 固定、动态和编译期特化维度混合声明。
x: pl.Tensor[[64, pl.DYNAMIC, pl.STATIC], pl.DT_FP16]
# rank在调用时确定;第一个维度为DYNAMIC,其余维度按STATIC处理。
y: pl.Tensor[[pl.DYNAMIC, ...], pl.DT_FP16]
省略号最多出现一次且必须位于shape末尾。Tensor的完整类型约束请参考pypto_pro.language.Tensor。
从Ptr或Tensor创建Tensor视图#
当Kernel接收裸指针或需要用新的shape、stride解释已有Tensor时,可以调用pypto_pro.language.make_tensor创建Tensor视图:
pypto_pro.language.make_tensor(
ptr,
shape,
stride=None,
dtype=None,
)
ptr可以是pypto_pro.language.Ptr或已有Tensor。新Tensor与源对象共享同一段GM地址;make_tensor不申请内存,也不复制或重排数据。
创建连续Tensor视图#
省略stride时,框架根据shape生成连续的行主序stride:
import pypto_pro.language as pl
tensor = pl.make_tensor(ptr, [8, 16])
# 等价于:tensor = pl.make_tensor(ptr, [8, 16], [16, 1])
shape中的维度既可以是编译期整数,也可以是Kernel中的运行时整型Scalar表达式。因此,动态shape也可以通过TilingData传入后用于创建固定rank的Tensor视图:
import pypto_pro.language as pl
@pl.jit(auto_mutex=True)
def dynamic_kernel(
x: pl.Ptr[pl.DT_FP16],
out: pl.Ptr[pl.DT_FP16],
tiling: OpTiling,
):
tensor_x = pl.make_tensor(x, [tiling.m, tiling.n])
tensor_out = pl.make_tensor(out, [tiling.m, tiling.n])
...
TilingData的声明和传入方式请参考Tiling参数定义与传递。
创建带显式stride的Tensor视图#
显式传入stride可以描述行间不连续或轴交换后的GM视图。stride包含的元素个数必须与shape的维数相同,单位是元素,不是字节。对于不足8 bit的数据类型,最后一维stride必须是编译期常量1:
import pypto_pro.language as pl
# 每行包含16个连续元素,相邻两行的起始位置相隔32个元素。
pitched = pl.make_tensor(ptr, [8, 16], [32, 1])
# normal[i, j]与transposed[j, i]指向相同地址。
normal = pl.make_tensor(ptr, [8, 16], [16, 1])
transposed = pl.make_tensor(ptr, [16, 8], [1, 16])
上述操作只改变逻辑索引到GM地址的映射,不会转置或搬运原数据。调用方需要保证shape、stride和dtype描述的访问范围没有超出源内存。详细参数说明请参考pypto_pro.language.make_tensor。
读取Tensor的Shape#
Kernel内通过tensor.shape[axis]读取Tensor维度。axis必须是编译期整数,支持负索引。读取到的维度可以参与地址计算、循环边界和多核切分:
m = x.shape[0]
n = x.shape[-1]
tile_rows = (m + TILE_M - 1) // TILE_M
tile_cols = (n + TILE_N - 1) // TILE_N
对于DYNAMIC维度,shape值在运行时取得;对于STATIC维度,shape值会固化到相应编译变体中。
Tensor别名和指针转换#
创建Tensor别名#
通过Python赋值可以为Tensor创建别名。别名与原Tensor指向同一段GM内存,不产生数据复制:
original_input = input_tensor
original_input_alias = original_input
# 重新绑定原变量不会改变已有别名的指向。
input_tensor = replacement_tensor
从Tensor获取Ptr#
pypto_pro.language.make_ptr可以从Tensor提取底层指针,也可以为已有Ptr创建新的元素类型视图:
import pypto_pro.language as pl
ptr = pl.make_ptr(tensor)
fp16_ptr = pl.make_ptr(byte_ptr, dtype=pl.DT_FP16)
返回的Ptr与源对象共享地址。指定dtype只改变地址的元素类型解释,不会转换原数据;调用方需要保证地址对齐和可访问范围正确。详细说明请参考pypto_pro.language.make_ptr。
需要对Ptr按元素进行偏移时,可以使用pypto_pro.language.addptr,再通过make_tensor将偏移后的地址包装为Tensor视图。addptr不支持不足8 bit的数据类型,需先通过make_ptr重解释为DT_UINT8并按字节偏移。这种方式常用于将一块GM Workspace划分为多个区域。
Tensor与Tile之间的数据搬运#
Tensor位于GM,不能直接作为Tile计算接口的片上操作数。Kernel通常先将Tensor中的数据搬入Tile,完成计算后再将结果搬回Tensor:
import pypto_pro.language as pl
with pl.section_vector():
pl.load(input_tile, input_tensor, [row_offset, col_offset])
# Tile计算过程省略。
pl.store(output_tensor, output_tile, [row_offset, col_offset])
load和store使用Tensor中的元素坐标定位搬运起点。
load_tile和store_tile按Tile网格坐标定位数据块。
搬运范围、stride、layout和尾块受具体搬运接口约束;编译期可确定的非法参数由框架检查,动态访问范围由调用方保证。
Tensor只描述GM数据视图;Tile的创建、片上地址和缓冲区管理请参考Tile创建和操作,Tile上的矢量计算请参考Tile计算,矩阵计算请参考Cube计算。