Softmax算子快速入门(SIMD)#
任务与目标#
本节将详细介绍如何使用PyPTO Pro SIMD编程范式实现一个简单的Softmax算子,并通过测试用例验证其正确性。通过本节的学习,您将了解如何使用PyPTO Pro SIMD相关API完成Tile定义、片上存储空间分配、数据搬运、归约计算和逐元素计算。
本示例固定处理Shape为[8, 256]、数据类型为FP32的Tensor,显式定义计算过程中使用的Tile及其片上存储空间,并通过SIMD接口沿最后一维分别对每行执行Softmax,其中第一维设置为8,以满足FP32行归约结果Tile的存储对齐要求。
算子设计规格#
表1 Softmax算子设计规格
name |
shape |
data type |
format |
|---|---|---|---|
inputs(输入) |
(8, 256) |
float32 |
ND |
outputs(输出) |
(8, 256) |
float32 |
ND |
数学表达式
\(M = \max(z),\quad \text{SoftMax}(z_i) = \frac{\exp(z_i-M)}{\sum_j \exp(z_j-M)}\)
使用的主要接口
Tile定义与分配接口:pypto_pro.language.TileType、pypto_pro.language.make_tile_group
数据搬运接口:pypto_pro.language.load、pypto_pro.language.store
基础计算接口:pypto_pro.language.maximum、pypto_pro.language.expand_sub、pypto_pro.language.exp、pypto_pro.language.sum、pypto_pro.language.expand_div
导入PyPTO Pro模块#
在开始实现Softmax算子之前,首先需要导入PyPTO Pro、PyTorch和torch_npu模块。PyPTO Pro模块用于定义和编译Kernel,PyTorch和torch_npu模块用于构造输入数据、执行NPU计算并进行结果验证。
import os
import pypto_pro.language as pl
import torch
import torch_npu
同时定义本示例处理的数据Shape:
ROWS = 8
COLS = 256
核心代码逻辑#
定义Tile类型和片上存储空间。
PyPTO Pro SIMD编程中,需要显式定义计算过程中使用的Tile类型。本示例中,输入、中间结果和输出Tile的Shape为[8, 256],最大值和指数和的归约结果Tile的Shape为[8, 1]。
tile_type = pl.TileType( shape=[ROWS, COLS], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec, ) reduce_type = pl.TileType( shape=[ROWS, 1], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec, layout=pl.DN, )
使用pypto_pro.language.make_tile_group为不同计算阶段分配Vector侧UB空间。不同Tile Group使用不同片上地址,避免存储空间重叠。
src_group = pl.make_tile_group( type=tile_type, addrs=0x0000, mutex_ids=[0], ) tmp_group = pl.make_tile_group( type=tile_type, addrs=0x2000, mutex_ids=[1], ) work_group = pl.make_tile_group( type=tile_type, addrs=0x4000, mutex_ids=[2], ) out_group = pl.make_tile_group( type=tile_type, addrs=0x6000, mutex_ids=[3], ) max_group = pl.make_tile_group( type=reduce_type, addrs=0x8000, mutex_ids=[4], ) sum_group = pl.make_tile_group( type=reduce_type, addrs=0x8100, mutex_ids=[5], )
实现Softmax Kernel函数。
通过pypto_pro.language.jit装饰器定义Softmax Kernel函数,并通过auto_mutex=True开启Tile Mutex自动管理。
Softmax计算依次完成数据搬入、最大值归约、减最大值、指数计算、求和归约、归一化以及数据搬出。
@pl.jit(auto_mutex=True) def softmax_simd_kernel( src: pl.Tensor[[ROWS, COLS], pl.DT_FP32], dst: pl.Tensor[[ROWS, COLS], pl.DT_FP32], ): tile_type = pl.TileType( shape=[ROWS, COLS], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec, ) reduce_type = pl.TileType( shape=[ROWS, 1], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec, layout=pl.DN, ) src_group = pl.make_tile_group( type=tile_type, addrs=0x0000, mutex_ids=[0], ) tmp_group = pl.make_tile_group( type=tile_type, addrs=0x2000, mutex_ids=[1], ) work_group = pl.make_tile_group( type=tile_type, addrs=0x4000, mutex_ids=[2], ) out_group = pl.make_tile_group( type=tile_type, addrs=0x6000, mutex_ids=[3], ) max_group = pl.make_tile_group( type=reduce_type, addrs=0x8000, mutex_ids=[4], ) sum_group = pl.make_tile_group( type=reduce_type, addrs=0x8100, mutex_ids=[5], ) with pl.section_vector(): src_tile = src_group.current() tmp_tile = tmp_group.current() work_tile = work_group.current() out_tile = out_group.current() max_tile = max_group.current() sum_tile = sum_group.current() # 1. GM -> UB pl.load(src_tile, src, [0, 0]) # 2. 计算每行最大值 pl.maximum(max_tile, src_tile, tmp_tile, dim=0) # 3. x - max(x) pl.expand_sub(work_tile, src_tile, max_tile, dim=0) # 4. exp(x - max(x)) pl.exp(work_tile, work_tile) # 5. sum(exp(x - max(x))) pl.sum(sum_tile, work_tile, tmp_tile, dim=0) # 6. exp(x - max(x)) / sum(exp(x - max(x))) pl.expand_div(out_tile, work_tile, sum_tile, dim=0) # 7. UB -> GM pl.store(dst, out_tile, [0, 0])
其中,pypto_pro.language.maximum和pypto_pro.language.sum沿最后一维执行归约。对于Shape为[8, 256]的输入,两次归约结果的Shape均为[8, 1]。
pypto_pro.language.expand_sub和pypto_pro.language.expand_div将Shape为[8, 1]的归约结果广播至[8, 256],分别完成减最大值和概率归一化。
测试用例#
为了验证Softmax算子的正确性,使用PyTorch Tensor作为输入,通过PyPTO Pro Kernel进行计算,并与PyTorch内置Softmax函数的结果进行对比。在开始执行PyPTO Pro和PyTorch相关代码之前,需要指定对应的Device ID。
device_id = int(os.environ.get("TILE_FWK_DEVICE_ID", 0))
device = f"npu:{device_id}"
torch.npu.set_device(device)
torch.manual_seed(0)
src = torch.randn(
ROWS,
COLS,
dtype=torch.float32,
device=device,
)
dst = torch.empty_like(src)
softmax_simd_kernel(src, dst)
torch.npu.synchronize()
golden = torch.softmax(src, dim=-1)
torch.testing.assert_close(
dst,
golden,
rtol=1e-4,
atol=1e-5,
)
print("SIMD Softmax kernel passed!")
编译与执行#
在已安装PyPTO Pro的环境中运行:
# 配置CANN环境变量
source /usr/local/Ascend/ascend-toolkit/set_env.sh
# 设置设备ID
export TILE_FWK_DEVICE_ID=0
# 执行脚本
python3 softmax_simd.py
程序执行成功后,显示以下信息:
SIMD Softmax kernel passed!