PTO-ISA TMINS 指令详解:Tile 与标量逐元素最小值运算实战指南
【免费下载链接】pto-isaParallel Tile Operation (PTO) is a virtual instruction set architecture designed by Ascend CANN, focusing on tile-level operations. This repository offers high-performance, cross-platform tile operations across Ascend platforms.项目地址: https://gitcode.com/cann/pto-isa
TMINS 是 CANN PTO-ISA(Parallel Tile Operation 虚拟指令集)中用于执行Tile 与标量逐元素最小值运算的向量指令,在算子开发中常被用于数值截断、下限裁剪(clamp/floor)、去噪与归一化等场景。本文以官方指令手册 TMINS.md(及对应 TMINS_zh.md)为骨架,结合仓库内 A2A3、A5、CPU 三套后端实现与各平台 ST 测试用例,系统讲解 TMINS 的数学语义、三级汇编形式、C++ 内建接口、平台差异约束与底层向量化实现,读完即可在自动(Auto)与手动(Manual)两种算子开发模式下正确使用该指令。
指令总览与数学语义
TMINS(Tile-MIN-Scalar)属于 PTO 指令集中的标量二元(BinS)向量指令,其功能为:将 Tile 中有效区域内的每一个元素与一个同数据类型的标量逐元素比较,取较小者写入目标 Tile。
对有效区域内每个元素(i, j),其数学定义为:
$$ \mathrm{dst}{i,j} = \min(\mathrm{src}{i,j}, \mathrm{scalar}) $$
即dst中任意位置的结果等于src对应位置元素与scalar两者间的较小值。当src[i][j]小于scalar时结果取src[i][j],否则取scalar。该指令在数值算法中的典型用途包括:
- 对激活值、梯度做上限截断(min-clamp),配合 TMAXS 可实现
clamp(x, lo, hi)双端裁剪; - 防止数值溢出或去除异常大值(如注意力分数裁剪);
- 归一化统计中的极值压缩。
TMINS 的迭代域由dst的 ValidRow / ValidCol决定,也就是说指令只处理dst.GetValidRow() × dst.GetValidCol()范围内的元素,Tile 的 Valid 区域之外不参与计算。
汇编语法与三级指令表示
PTO-ISA 将一条指令按照抽象层次分为多种表示形式,TMINS 在汇编层面的同步形式为:
%dst = tmins %src, %scalar : !pto.tile<...>, f32AS Level 1(SSA 形式)
SSA(Static Single Assignment)形式以pto.前缀标识,操作数与结果均为!pto.tile<...>类型,标量作为第二个输入:
%dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...>AS Level 2(DPS 形式)
DPS(Data-Parallel Style)形式使用ins(...)/outs(...)显式区分输入与输出缓冲区,操作数类型为!pto.tile_buf<...>:
pto.tmins ins(%src, %scalar : !pto.tile_buf<...>, dtype) outs(%dst : !pto.tile_buf<...>)三种表示描述的是同一条指令在不同编译阶段/抽象层的形态:SSA 形式便于编译优化与数据流分析,DPS 形式贴近实际片上缓冲(tile_buf)的资源绑定视图。
自动模式与手动模式的 ASM 差异
在自动(Auto)模式下,Tile 的放置与指令调度由编译器/运行时托管,开发者只需给出 SSA 指令:
# Auto mode: compiler/runtime-managed placement and scheduling. %dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...>在手动(Manual)模式下,资源必须先显式绑定再发射指令——通常通过pto.tassign为 Tile 操作数指定片上地址(如@tile(0x1000)):
# Manual mode: resources must be bound explicitly before issuing the instruction. # Optional for tile operands: # pto.tassign %arg0, @tile(0x1000) # pto.tassign %arg1, @tile(0x2000) %dst = pto.tmins %src, %scalar : (!pto.tile<...>, dtype) -> !pto.tile<...>C++ 内建接口
TMINS 的 C++ 内建接口声明于 include/pto/common/pto_instr.hpp,公共包含头为<pto/pto-inst.hpp>,内部声明位于pto/common/pto_instr.hpp(文档原文见 TMINS_zh.md)。其签名如下:
template <typename TileDataDst, typename TileDataSrc, typename... WaitEvents> PTO_INST RecordEvent TMINS(TileDataDst &dst, TileDataSrc &src, typename TileDataSrc::DType scalar, WaitEvents &... events);接口要点:
- 模板参数:
TileDataDst为目标 Tile 类型,TileDataSrc为源 Tile 类型,两者可以是不同模板实例(如行数/列数不同),但元素类型必须一致; scalar参数:类型为typename TileDataSrc::DType,即源 Tile 的数据类型,必须与 Tile 元素类型一致;WaitEvents...可变参数:可传入一个或多个Event作为依赖事件,实现指令间流水依赖。从源码看(pto_instr.hpp),接口内部先调用detail::PtoWaitEvents(events...)等待前置事件,再通过宏MAP_INSTR_IMPL(TMINS, dst, src, scalar)分发到具体后端的TMINS_IMPL实现,最后返回一个空的RecordEvent供后续指令依赖。
事件驱动的用法模式
TMINS 常与 TLOAD / TSTORE 组成完整的数据搬运-计算-写回流水。以 tests/npu/a5/src/st/testcase/tmins/tmins_kernel.cpp 中的核函数为例:
Event<Op::TLOAD, Op::TMINS> event0; Event<Op::TMINS, Op::TSTORE_VEC> event1; event0 = TLOAD(src0Tile, src0Global); event1 = TMINS(dstTile, src0Tile, scalar[0], event0); // 依赖 TLOAD 完成 TSTORE(dstGlobal, dstTile, event1); // 依赖 TMINS 完成这里Event<Op::TLOAD, Op::TMINS>声明了一个"TLOAD 之后、TMINS 之前"的同步点,TMINS通过event0与TLOAD建立依赖,TSTORE再依赖event1,从而保证同一块 buffer 上"先装载、再计算、后写回"的顺序正确性。
约束与限制
TMINS 的合法性与平台密切相关,文档明确区分了 A2A3 与 A5 两套实现检查,并在源码的static_assert/PTO_ASSERT中落地。
实现检查(Atlas A2/A3 训练系列产品 / Atlas A2/A3 推理系列产品)
依据 include/pto/npu/a2a3/TMins.hpp:
TileData::DType必须是以下之一:int32_t、int、int16_t、half、float16_t、float、float32_t;- 运行时:
src.GetValidRow() == dst.GetValidRow()且src.GetValidCol() == dst.GetValidCol(),即 src 与 dst 的有效行数、有效列数必须完全一致,否则触发PTO_ASSERT断言失败。
实现检查(Ascend 950PR / Ascend 950DT,即 A5)
依据 include/pto/npu/a5/TMins.hpp:
TileData::DType支持范围更广,包括:uint8_t、int8_t、uint16_t、int16_t、uint32_t、int32_t、int64_t、uint64_t、half、float、bfloat16_t;- 运行时:仅要求
src.GetValidCol() == dst.GetValidCol(),有效行数可以不同(行维不再要求严格相等); - 编译期额外检查:
ValidCol <= Cols、ValidRow <= Rows(有效尺寸不得超过 Tile 物理尺寸)。
通用约束
dst与src必须使用相同的元素类型(源码中通过std::is_same_v静态断言强制);- 标量类型必须与 Tile 数据类型一致;
- Tile 位置必须是向量:
TileData::Loc == TileType::Vec,即 TMINS 是向量(Vector)类指令,不允许作用于标量/矩阵类 Tile。
有效区域
指令以dst.GetValidRow()/dst.GetValidCol()作为迭代域(即结果区域的尺寸决定计算范围),这一约定与 TMINS.md 文档中的 Constraints 章节一致。
底层实现原理:三个后端的源码级解析
A2A3 后端:基于 vmins 的 repeat 展开
A2A3 实现位于 include/pto/npu/a2a3/TMins.hpp。核心是MinSOp<T>仿函数,其BinSInstr直接调用昇腾向量指令vmins:
vmins(dst, src0, src1, repeats, 1, 1, 8, 8);repeats为 repeat 次数,后四个参数为 block/repeat 步长。数据在 ubuf 中按 repeat 组织:elementsPerRepeat = REPEAT_BYTE / sizeof(T),blockSizeElem = BLOCK_BYTE_SIZE / sizeof(T),行间按TileDataDst::RowStride/TileDataSrc::RowStride跳跃。
真正的循环/分段调度由公共模板引擎 include/pto/npu/a2a3/TBinSOp.hpp 的TBinSInstr完成,它根据 Tile 是否连续(Cols == ValidCol或单行)自动选择多种发射策略:
- BinS1LCountMode:当列宽非 VL(向量长度)对齐或总 repeat 数超过
REPEAT_MAX时,使用SetVectorCount(validRow * validCol)计数模式一次性发射; - BinS1LNormMode:常规模式,按
elementsPerRepeat整除拆分 head 部分,剩余 tail 用SetContMaskByDType<T>(tailElements)连续掩码单独发射一次; - BinS2LCountMode / BinS2LNormModeColVLAlign / BinS2LNormModeRowRpt:针对多行、列维优先或行 repeat(RowRpt)的优化路径,其中
dstRepeatStride/srcRepeatStride在不超过REPEAT_STRIDE_MAX时可合并为单条 repeat 展开指令,否则退化为逐元素循环。
也就是说,同样的TMINS(dst, src, scalar)调用在 A2A3 上会根据 Tile 形状自动挑选最合适的掩码/计数/步长组合,这对理解手册中"运行时检查"之外的性能行为很有帮助。
A5 后端:掩码向量指令 + int64 特化
A5 实现位于 include/pto/npu/a5/TMins.hpp,对常规类型使用带 Predicate 掩码的vmins:
vmins(reg_dst, reg_src0, src1, preg, MODE_ZEROING);指令带MODE_ZEROING零化模式,谓词寄存器preg控制有效元素。对于 A5 独有的 64 位整数类型(int64_t/uint64_t,A2A3 不支持),实现特化到Int64Scalar<Int64Op::Min, T, ...>路径,通过 64 位比较逻辑逐元素计算最小值。A5 的TMinS还带有VFImplKind version参数,说明该指令在 A5 上同时接入向量函数(VF)实现框架,可配合 include/pto/costmodel/a5/vf_costmodel.hpp 等成本模型做性能建模。
CPU 后端:单指令多后端复用的证据
在 CPU 模拟后端中,TMINS 被实现为通用的标量二元模板 include/pto/cpu/TBinSOps.hpp:
template <typename TileDst, typename TileSrc> PTO_INTERNAL void TMINS_IMPL(TileDst& dst, TileSrc& src, typename TileSrc::DType scalar) { UnaryTileScalarOpImpl<TileDst, TileSrc, ElementOp::OP_MINS>(dst, src, scalar); }它通过ElementOp::OP_MINS逐元素计算min(x, scalar),并支持行主序/列主序两种 Tile 布局,配合cpu::parallel_for_rows做行级并行。CPU 实现的存在意味着同一份TMINS调用可以不经修改地在 CPU 模拟环境上验证正确性,再部署到 NPU,这正是 PTO 跨平台设计的一个缩影。
编程示例:Auto 与 Manual 两种模式
文档提供了两种开发模式的完整示例,均使用 16×16 的 float 向量 Tile。
自动模式(Auto)
#include <pto/pto-inst.hpp> using namespace pto; void example_auto() { using TileT = Tile<TileType::Vec, float, 16, 16>; TileT src, dst; TMINS(dst, src, 0.0f); }自动模式下无需关心 Tile 的片上地址,TMINS(dst, src, 0.0f)完成"dst = min(src, 0.0f)"的全部语义,资源放置与调度交给编译器/运行时。
手动模式(Manual)
#include <pto/pto-inst.hpp> using namespace pto; void example_manual() { using TileT = Tile<TileType::Vec, float, 16, 16>; TileT src, dst; TASSIGN(src, 0x1000); TASSIGN(dst, 0x2000); TMINS(dst, src, 0.0f); }手动模式通过TASSIGN显式把src、dst绑定到 ubuf 地址0x1000、0x2000,之后再发射 TMINS。对应汇编层面即为前文"手动模式"中pto.tassign的 C++ 形态。
值得注意的是,文档示例中的 scalar 以字面量0.0f传入;在真实核函数中,scalar 也可来自全局内存(如 tests/npu/a5/src/st/testcase/tmins/tmins_kernel.cpp 中的scalar[0]),此时需注意保证其数据类型与 Tile 类型一致。
测试与验证
仓库为 TMINS 提供了覆盖多平台、多数据类型的完整测试矩阵:
- A5 ST 测试:tests/npu/a5/src/st/testcase/tmins/ 包含
tmins_kernel.cpp、main.cpp与gen_data.py。测试核runTMINS通过GenericDataSelector模板同时覆盖PadValue::Null(无填充)与PadValue::Max(填充值为极大值)两种填充语义,并显式实例化了 float(含 64×64、128×128 大 Tile 与 1×3600 长条 Tile)、aclFloat16、int32/uint32、int16/uint16、int8/uint8、int64/uint64(含宽 16364/16368 列的大 Tile)等组合;其中 64 位宽 Tile 由runTMINSWideInt64以 64 列为单位分块循环处理; - A2A3 ST 测试:tests/npu/a2a3/src/st/testcase/tmins/ 结构与 A5 对应,覆盖 A2A3 支持的整型与浮点类型;
- CPU 测试:tests/cpu/st/testcase/tmins/ 验证 CPU 模拟后端的逐元素语义;
- 成本模型测试:tests/costmodel/st_a5/testcase/tmins/main.cpp、tests/costmodel/st/testcase/tmins/main.cpp 与
st_a5_fit目录下的拟合测试,用于校验 TMINS 在 include/pto/costmodel/a5/vf_costmodel.hpp 中的周期估算与实测拟合。
此外,include/pto/costmodel/a5/formula_costmodel/formula_params.csv与include/pto/costmodel/a2a3/formula_costmodel/formula_params.csv中均登记了 TMINS 的性能公式参数,说明该指令已被纳入 PTO 的周期成本模型体系,可在算子性能预估时被直接查询。
小结
TMINS 是 PTO-ISA 标量二元指令族(TADDS、TSUBS、TMULS、TDIVS、TMAXS 等)中负责"逐元素取小"的一员。本文覆盖了它的数学定义、三级汇编形式、C++ 接口与事件依赖写法、A2A3/A5 平台差异约束,并从 a2a3 实现、a5 实现、cpu 实现 三个后端以及 ST 测试用例出发还原了其底层行为。开发者可直接参照文档示例与测试核,在 Auto 模式下用TMINS(dst, src, scalar)完成下限截断,或配合 TLOAD/TSTORE 与事件依赖构建完整的向量计算流水;在 Manual 模式下则需先TASSIGN绑定地址再发射指令。使用前请务必确认目标平台的类型支持范围与 Valid 区域约束(详见 TMINS.md 与 TMINS_zh.md),以获得可移植且正确的算子行为。
【免费下载链接】pto-isaParallel Tile Operation (PTO) is a virtual instruction set architecture designed by Ascend CANN, focusing on tile-level operations. This repository offers high-performance, cross-platform tile operations across Ascend platforms.项目地址: https://gitcode.com/cann/pto-isa
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考