SIMT计算#

SIMT计算以线程为基本执行单元,适合表达运行时索引、逐线程分支和共享地址的原子更新。PyPTO Pro支持SIMD与SIMT混合编程:外层JIT Kernel管理Tile、数据搬运和流水依赖,SIMT函数执行逐线程计算。

初次运行可先参考Add算子快速入门(SIMT);概念见SIMT编程范式,完整接口约束见SIMT API

定义与启动SIMT函数#

定义入口函数和辅助函数#

入口函数使用@pypto_pro.language.vector_function(mode=”simt”, max_threads=N)定义,每个线程执行一次函数体,结果通过Tensor或Tile写回。辅助函数使用@pypto_pro.language.vector_function(mode=”simt”)定义,在当前线程中执行,可以返回一个Scalar。

以下示例计算1000个FP32元素的output = input_tensor * scale + bias。辅助函数复用逐线程计算,入口函数通过Block和Thread索引定位元素,并检查尾部边界。

import pypto_pro.language as pl


ELEMENTS = 1000
THREADS = 256


@pl.vector_function(mode="simt")
def affine(value: pl.DT_FP32, scale: pl.DT_FP32, bias: pl.DT_FP32) -> pl.DT_FP32:
    return value * scale + bias


@pl.vector_function(mode="simt", max_threads=THREADS)
def transform(
    output: pl.Tensor[[1, ELEMENTS], pl.DT_FP32],
    input_tensor: pl.Tensor[[1, ELEMENTS], pl.DT_FP32],
    count: pl.DT_UINT32,
    scale: pl.DT_FP32,
    bias: pl.DT_FP32,
):
    index = pl.simt.block_idx().x * pl.simt.block_dim().x + pl.simt.thread_idx().x
    if index < count:
        output[0, index] = affine(input_tensor[0, index], scale, bias)

参数类型可由实参推导;Tensor和Scalar参数可以写出注解,以便阅读和类型校验,Tile参数不支持使用注解。

在外层Vector执行域调用线程块#

外层Kernel的vector section中通过simt_func[threads](...)调用SIMT入口函数。

import pypto_pro.language as pl

@pl.jit(arch="a5")
def transform_kernel(
    input_tensor: pl.Tensor[[1, ELEMENTS], pl.DT_FP32],
    output: pl.Tensor[[1, ELEMENTS], pl.DT_FP32],
    count: pl.DT_UINT32,
    scale: pl.DT_FP32,
    bias: pl.DT_FP32,
):
    with pl.section_vector():
        transform[THREADS](output, input_tensor, count, scale, bias)

max_threads声明单个线程块的上限,threads指定本次调用的实际尺寸,圆括号内按位置传入实参。入口函数不能在SIMT函数中嵌套调用。

从Host启动外层Kernel#

在A5环境中,通过Host启动外层jit kernel函数:

import torch
import torch_npu


torch.npu.set_device(0)
input_tensor = torch.arange(ELEMENTS, dtype=torch.float32, device="npu:0").reshape(1, ELEMENTS)
output = torch.empty_like(input_tensor)
scale = 2.0
bias = 1.0
blocks = (ELEMENTS + THREADS - 1) // THREADS

transform_kernel[None, blocks](input_tensor, output, ELEMENTS, scale, bias)
torch.npu.synchronize()
torch.testing.assert_close(output, input_tensor * scale + bias, rtol=0, atol=0)

本例在Host侧将block_dim设置为4,表示实际使用4个Vector核。每个Vector核调用一次transform[THREADS](...),各启动一个包含256个线程的Thread Block;最后一个Thread Block只有232个线程访问数据,其余24个线程被边界判断跳过。

Host启动参数block_dim用于配置核数;SIMT函数内的pypto_pro.language.simt.block_dim()表示Thread Block在各维度上的线程数,两者含义不同。Host启动参数的调用形式和默认值参见Kernel核函数

配置线程与映射数据索引#

方括号中的threads可以写成一至三个整数表达式,例如simt_func[256](...)simt_func[16, 16](...)simt_func[8, 8, 4](...);pypto_pro.language.simt.block_dim()返回相应的三维尺寸,未给出的维度补1。各维必须为编译期正整数,乘积不得超过max_threads,且不得超过2048。例如simt_func[8, 8, 4](...)表示每个线程块有256个线程。

一维线程块可使用上例中的全局索引。二维或三维线程块可以使用X维优先的pypto_pro.language.simt.linear_thread_idx展开,再结合线程块编号和每块线程总数计算全局索引:

import pypto_pro.language as pl

@pl.vector_function(mode="simt", max_threads=256)
def copy_3d(
    source: pl.Tensor[[1, 1024], pl.DT_FP32],
    output: pl.Tensor[[1, 1024], pl.DT_FP32],
):
    dims = pl.simt.block_dim()
    threads_per_block = dims.x * dims.y * dims.z
    index = pl.simt.block_idx().x * threads_per_block + pl.simt.linear_thread_idx()
    if index < 1024:
        output[0, index] = source[0, index]

若外层Kernel通过copy_3d[8, 8, 4](...)调用该SIMT入口函数,pypto_pro.language.simt.linear_thread_idx()给出块内0至255的编号;pypto_pro.language.simt.block_idx().x乘以256后提供块偏移。例如,当实际有4个线程块时,它们分别处理索引0至255、256至511、512至767和768至1023。

访问Scalar、GM Tensor与UB Tile#

对象

访问方式

使用要求

Scalar

局部变量、算术、比较、布尔表达式及pypto_pro.language.simt标量接口

每个线程独立计算;数学函数的多个操作数需具有相同dtype。

GM Tensor

tensor[i, j]等完整标量下标

非零Rank、静态Shape、ND布局;索引可在运行时计算,程序须保证索引有效。

UB Tile

tile[row, col]

静态二维Shape、UB、ND布局;由外层Kernel创建和管理。

Tile有效区域

tile.valid_shape[0]、tile.valid_shape[1]

读取运行期有效行列数,访问尾块时按有效范围保护下标。

Tensor/Tile须以完整对象传入,不支持元素、Slice或Tile Subview作为函数参数。元素位宽不得小于8 bit;具体Scalar计算和原子操作仍须满足各自的dtype矩阵。运行期索引不等于动态Tensor Shape,后者当前不支持。

访问Tile的有效区域#

以下入口处理形状为[2, 128]的Tile,调用方使用add_valid[128, 2](...)。线程坐标覆盖物理Shape,实际读写范围由valid_shape控制:

import pypto_pro.language as pl

@pl.vector_function(mode="simt", max_threads=256)
def add_valid(data, delta: pl.DT_FP32):
    row = pl.simt.thread_idx().y
    col = pl.simt.thread_idx().x
    if row < data.valid_shape[0] and col < data.valid_shape[1]:
        data[row, col] = data[row, col] + delta

有效区域由外层Kernel设置并传入。SIMT入口函数调用不会自动为Tensor/Tile元素读写添加边界判断。SIMT函数也不能创建Tile或调用pypto_pro.language.load、pypto_pro.language.store等块级搬运接口。

标量计算与类型转换#

SIMT函数可以使用公共Scalar表达式,还提供标量计算与类型转换API,支持以下功能:

  • 类型转换:pypto_pro.language.simt.cast用于数值转换,pypto_pro.language.simt.bitcast用于重新解释二进制位模式。

  • 基础数学运算:pypto_pro.language.simt.abs、pypto_pro.language.simt.min、pypto_pro.language.simt.max、pypto_pro.language.simt.sqrt、pypto_pro.language.simt.rsqrt和pypto_pro.language.simt.fma分别提供绝对值、最值、平方根、平方根倒数和融合乘加。

  • 指数、对数和三角函数:包括pypto_pro.language.simt.exp、pypto_pro.language.simt.exp2、pypto_pro.language.simt.log、pypto_pro.language.simt.log2、pypto_pro.language.simt.log1p、pypto_pro.language.simt.sin、pypto_pro.language.simt.cos和pypto_pro.language.simt.tanh。

  • 取整运算:pypto_pro.language.simt.rint、pypto_pro.language.simt.round、pypto_pro.language.simt.floor、pypto_pro.language.simt.ceil和pypto_pro.language.simt.trunc支持按不同规则取整。

  • 浮点数值判断:pypto_pro.language.simt.isnan和pypto_pro.language.simt.isinf分别判断数值是否为NaN或无穷。

处理数据依赖#

外层流水同步#

SIMT入口函数调用在V流水异步执行。混合计算中,MTE2搬入的数据需要就绪后才能被SIMT访问;SIMT更新的UB数据需要计算完成后才能被MTE3搬出:

load(MTE2) → MTE2/V同步 → SIMD或SIMT计算(V)→ V/MTE3同步 → store(MTE3)

普通Tile通过成对的pypto_pro.language.system.sync_src和pypto_pro.language.system.sync_dst表达依赖,完整代码见Add快速入门

使用带mutex_ids的pypto_pro.language.make_tile_group时,默认启用的Auto Mutex可管理pypto_pro.language.load、SIMT入口函数调用和pypto_pro.language.store的缓冲区依赖。仅开启auto_mutex=True不会为普通pypto_pro.language.make_tile自动补全同步。

原子更新#

多个线程操作同一元素时使用原子操作系列接口,原子操作不提供跨Block屏障,也不保证其他地址的数据已经就绪。完整类型和返回值规则见原子操作API

示例:用标量索引实现Gather#

以下示例从输入Tensor中按indices指定的行号读取数据:

output[row, col] = input_tensor[indices[0, row], col]

本例通过Tensor元素访问表达Gather语义。每个线程处理一个或多个输出行,行内使用循环复制;这是索引表达示例,实际性能还需结合数据布局和线程映射测量。

import pypto_pro.language as pl


INPUT_ROWS = 100000
WIDTH = 128
OUTPUT_ROWS = 12288
THREADS = 256
BLOCKS = (OUTPUT_ROWS + THREADS - 1) // THREADS


@pl.vector_function(mode="simt", max_threads=THREADS)
def gather_rows(
    output: pl.Tensor[[OUTPUT_ROWS, WIDTH], pl.DT_FP32],
    input_tensor: pl.Tensor[[INPUT_ROWS, WIDTH], pl.DT_FP32],
    indices: pl.Tensor[[1, OUTPUT_ROWS], pl.DT_INT32],
    row_count: pl.DT_UINT32,
):
    first_row = pl.simt.block_idx().x * pl.simt.block_dim().x + pl.simt.thread_idx().x
    row_stride = pl.simt.grid_dim().x * pl.simt.block_dim().x
    for row in pl.range(first_row, row_count, row_stride):
        input_row = indices[0, row]
        for col in pl.range(0, WIDTH, 1):
            output[row, col] = input_tensor[input_row, col]


@pl.jit(arch="a5")
def gather_kernel(
    input_tensor: pl.Tensor[[INPUT_ROWS, WIDTH], pl.DT_FP32],
    indices: pl.Tensor[[1, OUTPUT_ROWS], pl.DT_INT32],
    output: pl.Tensor[[OUTPUT_ROWS, WIDTH], pl.DT_FP32],
    row_count: pl.DT_UINT32,
):
    with pl.section_vector():
        gather_rows[THREADS](output, input_tensor, indices, row_count)

调用方须保证0 <= row_count <= OUTPUT_ROWS,且被读取的indices元素均处于[0, INPUT_ROWS)。形状固定,索引值可以在运行时变化。准备好满足注解的NPU Tensor后,从Host启动:

gather_kernel[None, BLOCKS](input_tensor, indices, output, OUTPUT_ROWS)

本例在Host侧将block_dim设置为BLOCKS=48,表示实际使用48个Vector核;每个Vector核启动一个包含256个线程的Thread Block。线程按pypto_pro.language.simt.grid_dim().x * pypto_pro.language.simt.block_dim().x跨步处理后续行,覆盖全部12288行。各线程写入不同输出行,行间没有数据依赖,不需要线程块屏障。

当前能力边界#

  • SIMT入口必须由外层A5 Vector执行域调用,不支持Host直接启动SIMT函数或在SIMT函数中嵌套调用SIMT入口函数。

  • SIMT中不支持Tile创建、SIMD Tile计算、Reg计算或System流水操作。

  • 不支持动态GM Shape、Tile Subview、L1 Buffer Tile、DN/NZ布局和通用指针参数。

  • 未提供Warp shuffle/vote/reduce、线程私有数组和显式Cached GM访问接口。