CANN pyasc 算子编程接口详解:asc.language.basic.sub 按元素求差全指南
2026/9/18 22:54:43 网站建设 项目流程

CANN pyasc 算子编程接口详解:asc.language.basic.sub 按元素求差全指南

【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc

asc.language.basic.sub是 CANN pyasc 为 Python 开发者提供的矢量向量减法接口,与 Ascend C 中的Sub一一对应,用于在昇腾 AI 处理器的 Vector 核上按元素计算dst = src0 - src1。本文以该接口的官方 API 文档为骨架,结合 vec_binary.py 等仓库源码与单测用例,系统讲解三种重载签名的使用方式、参数语义、底层 IR 分发机制与约束条件,帮助读者在算子内核(Kernel)中正确、高效地完成向量减法运算。

接口概述:一次调用,三种形态

sub接口将 Ascend C 的Sub指令封装为符合 Python 原生语法的函数,核心能力是按元素求差dst[i] = src0[i] - src1[i]。它在源码 vec_binary.py 中通过@overload声明了三种重载形态,分别覆盖"整段连续计算"与"高维切分计算"两类场景:

# 形态一:tensor 前 n 个数据计算 asc.language.basic.sub(dst: LocalTensor, src0: LocalTensor, src1: LocalTensor, count: int, is_set_mask: bool = True) -> None # 形态二:tensor 高维切分计算(mask 连续模式) asc.language.basic.sub(dst: LocalTensor, src0: LocalTensor, src1: LocalTensor, mask: int, repeat_times: int, repeat_params: BinaryRepeatParams, is_set_mask: bool = True) -> None # 形态三:tensor 高维切分计算(mask 逐 bit 模式) asc.language.basic.sub(dst: LocalTensor, src0: LocalTensor, src1: LocalTensor, mask: List[int], repeat_times: int, repeat_params: BinaryRepeatParams, is_set_mask: bool = True) -> None

三种形态的运行期实现统一收敛到op_impl("sub", ...)(见 utils.py),Python 层会根据实参形态自动完成分派,无需开发者手动判断。

对应的 Ascend C 函数原型

sub接口与昇腾原生算子开发接口中的Sub模板函数一一对应,其 C++ 原型如下:

template <typename T> __aicore__ inline void Sub(const LocalTensor<T>& dst, const LocalTensor<T>& src0, const LocalTensor<T>& src1, const int32_t& count);
template <typename T, bool isSetMask = true> __aicore__ inline void Sub(const LocalTensor<T>& dst, const LocalTensor<T>& src0, const LocalTensor<T>& src1, uint64_t mask[], const uint8_t repeatTimes, const BinaryRepeatParams& repeatParams);
template <typename T, bool isSetMask = true> __aicore__ inline void Sub(const LocalTensor<T>& dst, const LocalTensor<T>& src0, const LocalTensor<T>& src1, uint64_t mask, const uint8_t repeatTimes, const BinaryRepeatParams& repeatParams);

其中第一个原型对应 Python 的count形态,第二个对应mask: List[int]逐 bit 模式,第三个对应mask: int连续模式。这组 C++ 原型同样由 set_binary_docstring 统一生成,与addmulmaxmin等二元矢量运算接口保持一致。

参数说明

参数类型语义
dstLocalTensor目的操作数,保存减法结果。支持的 TPosition 为VECIN/VECCALC/VECOUT
src0, src1LocalTensor源操作数。支持的 TPosition 为VECIN/VECCALC/VECOUT
countint参与计算的元素个数(形态一)
maskint/List[int]控制每次迭代内参与计算的元素。int为连续模式,List[int]为逐 bit 模式(形态二、三)
repeat_timesint重复迭代次数(形态二、三)
repeat_paramsBinaryRepeatParams控制操作数地址步长的参数(形态二、三)
is_set_maskbool是否在接口内部设置 mask,默认True

关于 LocalTensor

dstsrc0src1均为 LocalTensor 类型,即算子内核中位于本地内存(UB)的矢量张量。三种 TPosition 的含义为:VECIN表示矢量输入、VECCALC表示矢量计算中间缓冲、VECOUT表示矢量输出。

关于 BinaryRepeatParams:地址步长控制

BinaryRepeatParams是形态二、三中控制地址步长的核心结构体,其定义位于 types.py。构造签名为:

asc.BinaryRepeatParams(dst_blk_stride=1, src0_blk_stride=1, src1_blk_stride=1, dst_rep_stride=8, src0_rep_stride=8, src1_rep_stride=8)

六个字段的职责可归纳为两组:

  • blk_stride(块内步长)dst_blk_stridesrc0_blk_stridesrc1_blk_stride控制单次迭代内相邻数据块(DataBlock)之间的地址步长。设为 1 表示迭代内数据连续读取/写入。
  • rep_stride(迭代间步长)dst_rep_stridesrc0_rep_stridesrc1_rep_stride控制相邻两次迭代之间的地址步长。默认值 8 恰好对应 fp16 场景下一次迭代 256 字节的数据跨度(128 个 fp16 元素 × 2 字节 = 256 字节 = 8 个 32 字节 DataBlock)。

从源码看,BinaryRepeatParams最终通过builder.create_asc_ConstructOp构造为 IR 值,字段统一以uint8类型编码(见 types.py)。正是因为该结构承载了地址编排信息,高维、非连续张量才能通过"迭代 + 步长"的方式分片参与运算。

约束说明

  • 地址对齐约束:操作数地址对齐要求遵循昇腾社区《Ascend C 算子开发接口》中"通用说明和约束-通用地址对齐约束"的规定,涉及矢量指令对起始地址的字节对齐要求。
  • 地址重叠约束:操作数地址重叠约束同样参考《Ascend C 算子开发接口》中"通用说明和约束-通用地址重叠约束"章节,使用时应避免dstsrc0/src1出现非法的读写重叠。
  • 符号重载运算量:使用整个 tensor 参与计算的接口(对应count形态)时,运算量为目的LocalTensor的总长度。
  • 数据类型一致性(来自源码校验,见 utils.py):sub的源操作数与目的操作数支持float16float32int16int32四种数据类型;src0src1必须同类型,且dst与二者类型必须一致,否则抛出TypeError。调用前请确保三个 tensor 使用同一 dtype。

调用示例

以下示例均可在内核函数中直接使用(ascimport ascimport asc.language as asc后的命名空间)。

1. tensor 前 n 个数据计算(count 形态)

# 一次性对 dst 的前 512 个元素执行 src0 - src1 asc.sub(dst, src0, src1, count=512)

这是最简洁的形态,适合连续内存块的整体运算,运算量为count指定的元素个数。

2. tensor 高维切分计算- mask 连续模式

mask = 128 # repeat_times = 4,一次迭代计算128个数,共计算512个数 # dst_blk_stride, src0_blk_stride, src1_blk_stride = 1,单次迭代内数据连续读取和写入 # dst_rep_stride, src0_rep_stride, src1_rep_stride = 8,相邻迭代间数据连续读取和写入 params = asc.BinaryRepeatParams(1, 1, 1, 8, 8, 8) asc.sub(dst, src0, src1, mask=mask, repeat_times=4, repeat_params=params)

连续模式下,mask为整数,表示每次迭代从起始位置连续参与计算的元素个数。示例中一次迭代处理 128 个元素,重复 4 次,共计算 512 个数。

3. tensor 高维切分计算- mask 逐 bit 模式

mask = [uint64_max, uint64_max] # repeat_times = 4,一次迭代计算128个数,共计算512个数 # dst_blk_stride, src0_blk_stride, src1_blk_stride = 1,单次迭代内数据连续读取和写入 # dst_rep_stride, src0_rep_stride, src1_rep_stride = 8,相邻迭代间数据连续读取和写入 params = asc.BinaryRepeatParams(1, 1, 1, 8, 8, 8) asc.sub(dst, src0, src1, mask=mask, repeat_times=4, repeat_params=params)

逐 bit 模式下,maskuint64列表,列表中每个 bit 对应一个元素,bit 为 1 表示该元素参与计算,bit 为 0 则跳过。示例中mask = [uint64_max, uint64_max]即两个 64 位全 1 掩码,共覆盖 128 个元素(64 + 64 bit),配合repeat_times = 4同样完成 512 个元素的计算,但元素筛选粒度更细。

源码级实现剖析:三种形态如何映射到底层算子

sub的 Python 实现非常精简,真正的逻辑在公共函数op_impl中(见 utils.py)。调用asc.sub(dst, src0, src1, ...)后发生如下过程:

  1. 类型校验:进入check_type,校验dst/src0/src1的数据类型是否落在{float16, float32, int16, int32}集合内,且三者类型一致(utils.py 中"sub": valids即指向该集合)。
  2. 重载分派OverloadDispatcher依据关键字参数匹配三种注册分支:
    • mask=RuntimeInt+repeat_times+repeat_params→ 调用builder.create_asc_SubL0Op(连续模式);
    • mask=list+repeat_times+repeat_params→ 调用builder.create_asc_SubL1Op(逐 bit 模式,mask 列表逐项转换为uint64IR 值);
    • count=RuntimeInt→ 调用builder.create_asc_SubL2Op(count 被转换为int32IR 值)。
  3. IR 构建:三种形态最终在 vec_binary.py 的sub函数中,通过create_asc_SubL0Op/create_asc_SubL1Op/create_asc_SubL2Op构造出对应的 Asc IR 算子节点,进入后续编译流水线。

也就是说,L0/L1/L2后缀对应了昇腾矢量指令的三种编程视图:连续 mask、逐 bit mask、以及按 count 的整段计算。这一设计在addmulmaxmin等二元矢量接口中完全复用,因此理解sub的实现即可触类旁通。

另外值得留意的是,接口的 docstring(即 asc.language.basic.sub.md 的生成来源)由 set_binary_docstring 以cpp_name="Sub"统一模板化生成,这也是subadd等接口文档结构高度一致的原因。

测试用例佐证

仓库单测 test_vector_binary.py 中的test_sub_kernel覆盖了sub的全部三种调用形态:

@asc.jit def sub_kernel(): x_local = asc.LocalTensor(dtype=asc.float16, pos=asc.TPosition.VECIN, addr=0, tile_size=512) y_local = asc.LocalTensor(dtype=asc.float16, pos=asc.TPosition.VECIN, addr=0, tile_size=512) z_local = asc.LocalTensor(dtype=asc.float16, pos=asc.TPosition.VECOUT, addr=0, tile_size=512) asc.sub(z_local, x_local, y_local, count=512) params = asc.BinaryRepeatParams(1, 1, 1, 8, 8, 8) asc.sub(z_local, x_local, y_local, mask=512, repeat_times=1, repeat_params=params) uint64_max = 2**64 - 1 mask = [uint64_max, uint64_max] asc.sub(z_local, x_local, y_local, mask=mask, repeat_times=1, repeat_params=params) sub_kernel[1]()

该用例给出了一个可直接套用的完整内核骨架:以asc.LocalTensor声明VECIN源张量与VECOUT目的张量,在@asc.jit修饰的核函数中依次调用三种形态的sub。开发者可参照此结构,结合 examples 目录下的算子样例(如 01_add/add.py)快速搭建自己的减法算子。

相关接口与延伸

  • sub_relu:按元素求差后再与 0 比较取较大值(ReLU),实现见 vec_binary.py,对应 Ascend CSubRelu
  • sub_relu_cast:求差 + ReLU + 类型转换的组合运算,见 vec_vconv.py。
  • 同一族二元接口addmulmaxmindiv等共享相同的重载结构与op_impl分发机制,掌握sub后可直接套用其调用约定。

在算子开发实践中,建议优先使用count形态处理连续整块数据以获取最佳可读性;当张量维度较高或需要按块跳过部分元素时,再选用mask+repeat_times+BinaryRepeatParams的高维切分形态,并通过调整blk_striderep_stride精确控制地址布局。

【免费下载链接】pyasc本项目为Python用户提供算子编程接口,支持在昇腾AI处理器上加速计算,接口与Ascend C一一对应并遵守Python原生语法。项目地址: https://gitcode.com/cann/pyasc

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

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

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

立即咨询