vf.load_unalign#
产品支持情况#
Ascend 950PR/Ascend 950DT:支持
Atlas A3 训练系列产品/Atlas A3 推理系列产品:不支持
Atlas A2 训练系列产品/Atlas A2 推理系列产品:不支持
功能说明#
为提升对不规则内存地址的处理能力,reg_tensor支持在数据搬运过程中对非32字节对齐的地址进行访问,降低非对齐访问带来的性能开销。vf.load_unalign能够实现数据从非对齐的地址连续搬运至reg_tensor,利用非对齐寄存器UnalignRegForLoad作为临时缓存区,暂存跨对齐边界的数据,从而实现高效的连续非对齐数据传输。
非对齐搬入有三类接口:普通搬运接口、PostUpdate扩展搬运接口、使用AddrReg寄存器存储偏移量接口。
接口类型 |
触发条件 |
说明 |
|---|---|---|
普通搬运接口 |
不传stride,post_update=False(默认) |
完成一次搬运后,Tile地址不会自动更新,每次迭代需要手动更新地址。 |
PostUpdate扩展搬运接口 |
post_update=True或传入stride |
完成一次搬运后,Tile地址会自动更新,每次迭代不需要手动更新地址。 |
AddrReg存储偏移量接口 |
offset为vf.create_addr_reg创建的AddrReg |
在每次迭代中,需要先调用vf.create_addr_reg手动设定地址偏移量,再调用搬运指令。 |
在读非对齐地址前,应该先通过vf.load_unalign_pre进行初始化,保存非32字节对齐的数据,然后再调用vf.load_unalign进行数据搬入。
非对齐搬入原理#
如下图所示,从Tile地址srcAddr ~ 304读取数据,并将其搬运至目标reg_tensor(256B)。处理流程如下:
① 调用load_unalign_pre进行非对齐搬入初始化。非对齐寄存器ureg缓存Tile地址32 ~ 64的有效数据,作为后续非对齐访问的前置数据缓存。
② 调用load_unalign,硬件指令将Tile地址64 ~ 320的对齐数据搬入临时reg_tensor,并将ureg中srcAddr ~ 64对应的数据与临时reg_tensor中地址64 ~ 304对应的数据拼接在一起,将结果写入目标reg_tensor。此外,Tile地址288 ~ 320的数据会被写入ureg。
图1 非对齐搬入示例

连续非对齐搬入搬出原理#
图2 连续非对齐搬入搬出原理(数据类型DT_UINT32)

连续非对齐搬入时,vf.load_unalign会将后续未对齐的数据缓存至vf.load_unalign_init创建的ureg,所以下一次搬入不需要再次调用vf.load_unalign_pre,只需在迭代开始前调用一次vf.load_unalign_pre,从而实现非对齐搬入的性能优化。
连续非对齐搬出时,下次迭代的vf.store_unalign会将本次迭代vf.store_unalign缓存至ureg中的数据写入Tile,所以本次迭代不需要调用vf.store_unalign_post将ureg数据写入Tile,只需在迭代结束后调用一次vf.store_unalign_post,从而实现非对齐搬出的性能优化。
如上图所示,将Tile地址48 ~ 560的DT_UINT32数据[1, 2, 3, …, 128]搬入至dstReg,再搬回Tile,需要两次搬入搬出操作,即for循环执行两次,初始化和后处理移至for循环外。stride = 256B / sizeof(T)(即每次地址偏移256B),repeatTimes = dataSize / 256B(即迭代次数=总数据量/VL)。
具体的搬运步骤如下:
非对齐搬入初始化:更新ureg = [1, 2, 3, 4];
非对齐搬入:tmpReg = [5, 6, 7, …, 68],tmpReg部分数据和ureg数据写入dstReg = [1, 2, 3, …, 64],更新ureg = [61, 62, 63, …, 68];
非对齐搬出:dstReg部分数据[1, 2, 3, …, 60]写入Tile地址48 ~ 288,更新align_reg = [61, 62, 63, 64];
非对齐搬入:tmpReg = [69, 70, 71, …, 128],tmpReg数据和ureg部分数据写dstReg = [65, 66, 67, …, 128];
非对齐搬出:align_reg数据[61, 62, 63, 64]和dstReg部分数据[65, 66, 67, …, 124]写入Tile地址288 ~ 544,更新align_reg = [125, 126, 127, 128];
非对齐搬出后处理:将align_reg中缓存的数据[125, 126, 127, 128]写入Tile地址544 ~ 560。
函数原型#
load_unalign(align_reg, tile, stride=None, post_update: bool = False) -> dst
参数说明#
参数 |
输入/输出 |
说明 |
|---|---|---|
align_reg |
输入/输出 |
非对齐寄存器,UnalignRegForLoad类型,用于存储非32字节的数据,寄存器大小为32字节(由vf.load_unalign_init()创建)。 |
tile |
输入 |
源操作数,Tile地址,起始地址需要32字节对齐。目的操作数与源操作数的数据类型需要保持一致。支持的数据类型为:DT_INT8、DT_UINT8、DT_INT16、DT_UINT16、DT_FP16、DT_BF16、DT_INT32、DT_UINT32、DT_FP32、DT_INT64、DT_UINT64、DT_FP8E4M3FN、DT_FP8E5M2、DT_FP8E8M0、DT_HF8、DT_FP4E2M1、DT_FP4E1M2。 |
stride |
输入 |
可选,地址更新步长,单位:元素个数。仅在post_update=True时有效。 |
post_update |
输入 |
可选,True时搬运后地址自动累进,默认False。 |
约束说明#
vf.load_unalign_pre与vf.load_unalign接口需要组合使用。
返回值说明#
返回dst目的操作数,reg_tensor,支持的数据类型和tile中的说明一致。
调用示例#
基本非对齐加载#
import os
import pypto_pro.language as pl
import torch
import torch_npu
@pl.vector_function
def example_vf(src_tile, dst_tile):
ureg = vf.load_unalign_init()
vf.load_unalign_pre(ureg, src_tile)
src_reg = vf.load_unalign(ureg, src_tile, post_update=True)
store_ureg = vf.unalign_reg_for_store()
vf.store_unalign(dst_tile, src_reg, store_ureg, 64, post_update=True)
vf.store_unalign_post(dst_tile, store_ureg, 0, post_update=True)
@pl.jit()
def example_kernel(
a: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP32],
out: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP32],
):
tf = pl.TileType(shape=[1, 64], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec)
in_a_grp = pl.make_tile_group(type=tf, addrs=0x0, mutex_ids=[0])
in_a = in_a_grp.current()
t_out_grp = pl.make_tile_group(type=tf, addrs=0x100, mutex_ids=[1])
t_out = t_out_grp.current()
with pl.section_vector():
pl.load(in_a, a, [0, 0])
example_vf(in_a, t_out)
pl.store(out, t_out, [0, 0])
def test_example():
device_id = int(os.environ.get("TILE_FWK_DEVICE_ID", 0))
device = f"npu:{device_id}"
core_nums = 1
torch.npu.set_device(device)
a = torch.randn([1, 64], device=device, dtype=torch.float32)
out = torch.empty([1, 64], device=device, dtype=torch.float32)
example_kernel[None, core_nums](a, out)
torch.npu.synchronize()
torch.testing.assert_close(out, a, rtol=1e-5, atol=1e-5)
if __name__ == "__main__":
test_example()
print("PASSED")
带步长的连续非对齐加载示例#
import os
import pypto_pro.language as pl
import torch
import torch_npu
@pl.vector_function
def example_vf(src_tile, dst_tile):
preg = vf.create_mask(pattern=pl.MaskPattern.ALL, dtype=pl.DT_FP32)
ureg = vf.load_unalign_init()
vf.load_unalign_pre(ureg, src_tile)
store_ureg = vf.unalign_reg_for_store()
src_reg = vf.load_unalign(ureg, src_tile, 64, post_update=True)
vf.store_unalign(dst_tile, src_reg, store_ureg, 64, post_update=True)
vf.store_unalign_post(dst_tile, store_ureg, 64, post_update=True)
@pl.jit()
def example_kernel(
a: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP32],
out: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_FP32],
):
tf = pl.TileType(shape=[1, 64], dtype=pl.DT_FP32, target_memory=pl.MemorySpace.Vec)
in_a_grp = pl.make_tile_group(type=tf, addrs=0x0, mutex_ids=[0])
in_a = in_a_grp.current()
t_out_grp = pl.make_tile_group(type=tf, addrs=0x100, mutex_ids=[1])
t_out = t_out_grp.current()
with pl.section_vector():
pl.load(in_a, a, [0, 0])
example_vf(in_a, t_out)
pl.store(out, t_out, [0, 0])
def test_example_2():
device_id = int(os.environ.get("TILE_FWK_DEVICE_ID", 0))
device = f"npu:{device_id}"
core_nums = 1
torch.npu.set_device(device)
a = torch.randn([1, 64], device=device, dtype=torch.float32)
out = torch.empty([1, 64], device=device, dtype=torch.float32)
example_kernel[None, core_nums](a, out)
torch.npu.synchronize()
torch.testing.assert_close(out, a, rtol=1e-5, atol=1e-5)
if __name__ == "__main__":
test_example_2()
print("PASSED")
INT64数据类型示例#
import os
import pypto_pro.language as pl
import torch
import torch_npu
@pl.vector_function
def example_vf_int64(src_tile, dst_tile):
preg = vf.create_mask(pattern=pl.MaskPattern.ALL, dtype=pl.DT_INT64)
reg_a = vf.load_align(src_tile, 0)
ureg = vf.load_unalign_init()
vf.load_unalign_pre(ureg, src_tile)
reg_out = vf.load_unalign(ureg, src_tile)
vf.store_align(dst_tile, reg_out, preg)
@pl.jit()
def example_kernel_int64(
a: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_INT64],
out: pl.Tensor[[pl.DYNAMIC, pl.DYNAMIC], pl.DT_INT64],
):
tf = pl.TileType(shape=[1, 32], dtype=pl.DT_INT64, target_memory=pl.MemorySpace.Vec)
in_a_grp = pl.make_tile_group(type=tf, addrs=0, mutex_ids=[0])
in_a = in_a_grp.current()
t_out_grp = pl.make_tile_group(type=tf, addrs=256, mutex_ids=[1])
t_out = t_out_grp.current()
with pl.section_vector():
pl.load(in_a, a, [0, 0])
example_vf_int64(in_a, t_out)
pl.store(out, t_out, [0, 0])
def test_example_int64():
device_id = int(os.environ.get("TILE_FWK_DEVICE_ID", 0))
device = f"npu:{device_id}"
core_nums = 1
torch.npu.set_device(device)
a = torch.randint(-100, 100, [1, 32], device=device, dtype=torch.int64)
out = torch.empty([1, 32], device=device, dtype=torch.int64)
example_kernel_int64[None, core_nums](a, out)
torch.npu.synchronize()
torch.testing.assert_close(out, a, rtol=0, atol=0)
if __name__ == "__main__":
test_example_int64()
print("PASSED")