- 人工智能
- 编译器
- 模型编译
- 高性能计算
- 深度学习
- CANN
【免费下载链接】pypto
PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。
导读
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.ini、Ascend950PR_9579.ini)中,而 A2/A3 系列配置中未声明该指令,与文档的"产品支持情况"完全一致。
功能说明
reg_tensor最小值归约的执行语义为:
- 遍历源寄存器
src中所有被谓词寄存器preg选中的有效元素; - 求出其中的最小值;
- 将最小值写入目标寄存器的第一个元素
dst[0]; - 将第一个最小值所在的下标写入
dst[1]; - 其余元素全部置零。
归约过程中"值"与"索引"的保存方式如下图所示:
其中索引语义需要注意:当存在多个相等的最小值时,保存的是首次出现(下标最小)的那个索引;例如src = [5, 3, 7, 3]时,dst[0] = 3、dst[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_INT16、DT_UINT16、DT_FP16、DT_INT32、DT_UINT32、DT_FP32、DT_INT64、DT_UINT64。 |
preg | 输入 | mask_reg 谓词掩码寄存器。当所有元素均不参与计算(mask 为空)时,将该数据类型的最大值写入dst[0]。 |
datablock | 输入 | 可选,决定接口工作模式。True时按 datablock 粒度归约(对应vcgmin指令),默认False(对应vcmin指令)。当datablock=True时,每个 datablock 独立归约:32 位宽(DT_INT32、DT_UINT32、DT_FP32)类型每 16 个元素为一个 datablock,16 位宽(DT_INT16、DT_UINT16、DT_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类别算子,携带dst、src、mask三个操作数以及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属性翻译为vcmin或vcgmin两条不同的硬件指令。 - 在 Python 前端解析层,
reduce_min被注册为单源操作数调用(见 python/pypto_pro/language/parser/_call_parser.py#L512),确保调用时参数个数与类型检查与声明一致。
约束说明
datablock=True时,支持的数据类型收窄为:DT_INT16、DT_UINT16、DT_FP16、DT_INT32、DT_UINT32、DT_FP32。- 即
DT_INT64、DT_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的+Inf、DT_INT32的INT32_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_INT64是reduce_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 / vcgmax,datablock=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 的列/行窗口约简;
- 动态规划或图算法中跨状态维度的最小值松弛。
注意事项:
- 掩码是语义核心:
preg决定参与归约的元素集合,务必与src的数据类型匹配生成(create_mask的dtype参数与src一致); - 空掩码、NaN、±0 三类边界行为需在算子正确性验证中单独用例覆盖;
datablock=True模式下输出是多个datablock 结果依次写入dst低位,此时dst不再是单值语义,读取结果时需要按 16/32 元素步长解析;- 当前仅在 Ascend 950PR/950DT 上可用,跨产品移植前需先核对目标平台的指令集支持。
- 人工智能
- 编译器
- 模型编译
- 高性能计算
- 深度学习
- CANN
【免费下载链接】pypto
PyPTO(发音: pai p-t-o):Parallel Tensor/Tile Operation编程范式。
相关推荐
PyPTO pypto.argmin:多维张量归约索引算子的 API 约束与源码实现详解
PyPTO pypto.argmin:多维张量归约索引算子的 API 约束与源码实现详解 pypto.argmin 是 PyPTO(Parallel Tenso
人工智能编译器模型编译高性能计算深度学习CANNPyPTO pypto.argmax:Tile 级最大值索引归约接口的语义、约束与源码实现解析
PyPTO pypto.argmax:Tile 级最大值索引归约接口的语义、约束与源码实现解析 本篇基于 PyPTO(Parallel Tensor/Tile
人工智能编译器模型编译高性能计算深度学习CANNPyAsc 算子开发实战:asc.language.basic.reduce_min 最小值归约 API 详解
PyAsc 算子开发实战:asc.language.basic.reduce_min 最小值归约 API 详解 在昇腾 AI 处理器的向量算子开发中,"从一批数
编译器编程语言人工智能CANN
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考