PyPTO vf.reduce_min 寄存器最小值归约算子详解:从掩码筛选到索引回传的完整实现
2026/9/20 3:34:14 网站建设 项目流程
  • 人工智能
  • 编译器
  • 模型编译
  • 高性能计算
  • 深度学习
  • CANN

【免费下载链接】pypto

PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。

项目地址:https://gitcode.com/cann/pypto
点击查看免费下载

导读

vf.reduce_min是 PyPTO 向量函数库(Vector Function,vf)中用于寄存器内(in-register)最小值归约的核心原语,对应硬件vcmin(全寄存器归约)与vcgmin(datablock 粒度归约)指令。它能够在单个寄存器内完成"掩码筛选 → 求最小值 → 回写结果与首个最小值索引"的全流程,是 softmax、min-pooling、动态规划等需要跨通道/跨行求极值的算子中高频使用的基础构件。阅读本文后,你将掌握reduce_min的语义、参数与边界行为(NaN、±0、空掩码),并能在 Ascend 950PR/950DT 上直接落地 FP32 与 INT64 两种可运行示例。

产品支持情况

产品形态支持状态
Ascend 950PR / Ascend 950DT支持
Atlas A3 训练系列产品 / Atlas A3 推理系列产品不支持
Atlas A2 训练系列产品 / Atlas A2 推理系列产品不支持

从代码仓库的平台配置也能印证这一点:vcgmin指令能力仅出现在 Ascend950DT 与 Ascend950PR 的模拟平台配置(如Ascend950DT_9572.iniAscend950PR_9579.ini)中,而 A2/A3 系列配置中未声明该指令,与文档的"产品支持情况"完全一致。

功能说明

reg_tensor最小值归约的执行语义为:

  1. 遍历源寄存器src中所有被谓词寄存器preg选中的有效元素;
  2. 求出其中的最小值;
  3. 将最小值写入目标寄存器的第一个元素dst[0]
  4. 第一个最小值所在的下标写入dst[1]
  5. 其余元素全部置零。

归约过程中"值"与"索引"的保存方式如下图所示:

其中索引语义需要注意:当存在多个相等的最小值时,保存的是首次出现(下标最小)的那个索引;例如src = [5, 3, 7, 3]时,dst[0] = 3dst[1] = 1

函数原型

reduce_min(src, preg, datablock: bool = False, merge_mode: Optional[MergeMode] = None)

该接口在源码中的声明位于 python/pypto_pro/language/_vf_api.py#L628-L650,docstring 中明确标注其对应硬件指令为vcmin / vcgmin,并将行为形式化为:

dstReg_0 = min_{i ∈ active} srcReg_i

参数说明

参数输入/输出说明
src输入源操作数,reg_tensor。源操作数src与目的操作数dst的数据类型保持一致。支持的数据类型为:DT_INT16DT_UINT16DT_FP16DT_INT32DT_UINT32DT_FP32DT_INT64DT_UINT64
preg输入mask_reg 谓词掩码寄存器。当所有元素均不参与计算(mask 为空)时,将该数据类型的最大值写入dst[0]
datablock输入可选,决定接口工作模式。True时按 datablock 粒度归约(对应vcgmin指令),默认False(对应vcmin指令)。当datablock=True时,每个 datablock 独立归约:32 位宽(DT_INT32DT_UINT32DT_FP32)类型每 16 个元素为一个 datablock,16 位宽(DT_INT16DT_UINT16DT_FP16)类型每 32 个元素为一个 datablock,各 datablock 分别求最小值并将结果依次写入dst的最低位
merge_mode输入可选,对应 MergeMode 类型。
-pypto_pro.language.MergeMode.ZEROING(默认):preg未筛选的元素在dst中置 0。
-pypto_pro.language.MergeMode.MERGING:当前不支持。

参数底层实现(源码佐证)

  • 在 IR 层,vf.reduce_min注册为VFOp类别算子,携带dstsrcmask三个操作数以及datablock(bool)与merge_mode(int)两个属性,见 framework/src/interface/ir/op/vf_ops.cpp#L247-L255:
REGISTER_OP("vf.reduce_min") .set_op_category("VFOp") .set_description("Min reduction across all lanes (vcmin/vcgmin)") .add_argument("dst", "Destination register") .add_argument("src", "Source register") .add_argument("mask", "Mask register") .set_attr<bool>("datablock") .set_attr<int>("merge_mode") .f_deduce_type(DeduceVFFromDstArg);
  • 其中f_deduce_type(DeduceVFFromDstArg)表明:目标寄存器dst的数据类型由源寄存器src推导而来,二者保持一致,这正是"src 与 dst 类型一致"约束的机制来源。
  • 后端生成阶段,该算子注册到 CCE 后端发射器(见 framework/src/interface/pypto_pro/backend/backend_cce_vf_ops.cpp#L5836),由后端将datablock属性翻译为vcminvcgmin两条不同的硬件指令。
  • 在 Python 前端解析层,reduce_min被注册为单源操作数调用(见 python/pypto_pro/language/parser/_call_parser.py#L512),确保调用时参数个数与类型检查与声明一致。

约束说明

  • datablock=True时,支持的数据类型收窄为:DT_INT16DT_UINT16DT_FP16DT_INT32DT_UINT32DT_FP32
  • DT_INT64DT_UINT64仅支持全寄存器归约模式(datablock=False)。

返回值说明

返回目标 reg_tensor,支持的数据类型与src一致,归约结果写入第一个元素dst[0],索引写入dst[1]。需特别关注的边界行为:

  • 多最小值并列:存在多个最小值时,将第一个(下标最小)最小值的索引保存在dst[1]中。
  • NaN 输入:如果输入数据存在 NaN,将该数据类型的 NaN 写入dst[0],并将第一个 NaN 的索引保存在dst[1]中。
  • 符号零min(-0, +0) = -0,即负零在比较中优先于正零。
  • 空掩码:当preg未选中任何元素时,将对应数据类型的最大值(如DT_FP32+InfDT_INT32INT32_MAX)写入dst[0]

调用示例

基本调用示例(DT_FP32)

以下示例展示完整的"加载 → 归约 → 存储"链路:通过vf.create_mask生成全 1 掩码,vf.load_align将 tile 数据对齐加载为寄存器,reduce_min求最小值,再vf.store_align写回 tile:

import os import pypto_pro.language as pl import torch import torch_npu @pl.vector_function def example_vf(src_tile, dst_tile): preg_all = vf.create_mask(pattern=pl.MaskPattern.ALL, dtype=pl.DT_FP32) src0 = vf.load_align(src_tile, 0) min0 = vf.reduce_min(src0, preg_all) vf.store_align(dst_tile, min0, preg_all) @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_kernelNone, core_nums torch.npu.synchronize() torch.testing.assert_close(out[0, 0], torch.min(a), rtol=1e-5, atol=1e-5) if __name__ == "__main__": test_example() print("PASSED")

示例中的关键点:

  • out[0, 0]对应归约结果dst[0],与torch.min(a)做数值比对(rtol/atol=1e-5),验证最小值语义;
  • 设备号通过环境变量TILE_FWK_DEVICE_ID指定(默认 0),核数为 1;
  • 输入[1, 64]的 tile 恰好对应 64 个 FP32 元素的寄存器归约,注意reduce_min只使用dst[0]dst[1],其余位置按ZEROING语义被置零。

INT64 数据类型示例

DT_INT64reduce_min的特色能力(同类归约接口如reduce_sum/reduce_max亦支持),注意此类型不能datablock=True组合使用:

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) reg_out = vf.reduce_min(reg_a, preg) 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.zeros([1, 32], device=device, dtype=torch.int64) example_kernel_int64None, core_nums torch.npu.synchronize() torch.testing.assert_close(out[0, 0], torch.min(a), rtol=0, atol=0) if __name__ == "__main__": test_example_int64() print("PASSED")

INT64 示例的两个细节:

  • tile 形状为[1, 32]:64 位类型每个元素占 8 字节,32 个元素正好对应一个寄存器的数据宽度;
  • torch.testing.assert_close使用rtol=0, atol=0精确比对,因为整数最小值归约结果必须是位级精确的。

与相邻归约接口的对照

reduce_min属于 PyPTO 归约族(reduction 目录),与以下接口共享相同的签名与掩码语义,便于对比记忆:

  • vf.reduce_max:求最大值,对应vcmax / vcgmaxdatablock=True时每 datablock 求最大值写入dst最低位;
  • vf.reduce_sum:求和,对应vcadd / vcgadd,注意其累加顺序与浮点舍入相关,实际应用时可通过文档中的reduce_sum_accum_order图理解累加次序对结果精度的影响。

三者均以dst[0]承载归约值,且datablock=True时的数据类型约束一致(16 位宽类型每 32 元素一个 datablock,32 位宽类型每 16 元素一个 datablock)。

典型应用场景与注意事项

应用场景:在矢量计算中,reduce_min适合将"按行/按组求极小值"操作下沉到寄存器级完成,例如:

  • softmax / log-softmax 中先求最大值(reduce_max)再做指数归一化(最大值更利于数值稳定,最小值归约则常用于 min-max 归一化的下界计算);
  • 池化层 min-pooling 的列/行窗口约简;
  • 动态规划或图算法中跨状态维度的最小值松弛。

注意事项

  1. 掩码是语义核心:preg决定参与归约的元素集合,务必与src的数据类型匹配生成(create_maskdtype参数与src一致);
  2. 空掩码、NaN、±0 三类边界行为需在算子正确性验证中单独用例覆盖;
  3. datablock=True模式下输出是多个datablock 结果依次写入dst低位,此时dst不再是单值语义,读取结果时需要按 16/32 元素步长解析;
  4. 当前仅在 Ascend 950PR/950DT 上可用,跨产品移植前需先核对目标平台的指令集支持。
  • 人工智能
  • 编译器
  • 模型编译
  • 高性能计算
  • 深度学习
  • CANN

【免费下载链接】pypto

PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。

项目地址:https://gitcode.com/cann/pypto
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询