- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
本文围绕 CANN asc-devkit 开源仓库中 BitwiseNot 接口文档 及其关联的 BitwiseNot、GetBitwiseNotMaxMinTmpSize、GetBitwiseNotTmpBufferFactorSize 三篇接口说明,完整讲解昇腾 AI 处理器上对 LocalTensor 进行逐比特取反(Bitwise Not)的高阶数学计算接口:包括函数原型、模板与实参配置、支持的数据类型与产品范围、调用示例与结果语义、配套的 Host 侧 Tiling 临时空间规划接口,并结合仓库源码(接口头文件、内核实现、参数校验、Tiling 实现与单元测试)剖析其底层原理。读完本文,你将掌握 BitwiseNot 的正确调用方式、UB 临时空间的规划方法,以及从"文档到源码到测试"的完整证据链。
接口定位:数学计算高阶 API 家族中的按位运算成员
BitwiseNot 属于 asc-devkit 中 SIMD-API 高阶数学计算(math_compute)API 家族。该家族除 BitwiseNot 外,还包含同为按位运算的 BitwiseAnd、BitwiseOr、BitwiseXor,以及逻辑运算 LogicalNot、LogicalAnd、LogicalOr、LogicalXor 等接口。
BitwiseNot 的功能非常直接:逐比特对输入数据进行取反(按位非运算),即对每个元素的二进制表示执行~x操作(0 变 1,1 变 0)。这一操作常用于掩码生成、补码计算、图像处理中的颜色反相、以及各类位操作场景,是典型的单目(单输入)矢量运算。
产品支持情况:目前仅限 Ascend 950 系列
根据 BitwiseNot.md 中的产品支持矩阵,BitwiseNot 接口目前仅支持:
- Ascend 950PR & 950DT 系列产品:支持
其余产品线均不支持:
- Atlas A3 系列产品:不支持
- Atlas A2 系列产品:不支持
- Atlas 200I/500 A2 推理产品:不支持
- Atlas 推理系列产品 AI Core / Vector Core:不支持
- Atlas 训练系列产品:不支持
从源码侧可以得到一致的印证:内核实现头文件 bitwise_not.h 通过编译期宏__NPU_ARCH__判断,仅当架构为3510、9201、5102、5162、3003、3113时才包含并编译 bitwise_not_3510_impl.h 中的实现;同时该头文件还在文件顶部明确禁止了与--enable-simt编译标志混用(#error "bitwise_not.h cannot be used with compile flag --enable-simt enabled."),即 BitwiseNot 只能用于纯 SIMD(矢量)编程场景。
函数原型与参数详解
BitwiseNot 的完整函数原型如下(模板参数在前,普通参数在后):
template <const BitwiseNotConfig& config = DEFAULT_BITWISE_NOT_CONFIG, typename T> __aicore__ inline void BitwiseNot(const LocalTensor<T>& dst, const LocalTensor<T>& src, const uint32_t count)模板参数说明
表1 模板参数说明
| 参数名 | 描述 |
|---|---|
| config | BitwiseNot 算法的相关配置,可选配,类型为BitwiseNotConfig,具体定义如下方代码所示。其中参数的含义为:isReuseSource:该参数预留,传入默认值 false 即可。 |
| T | 源操作数、目的操作数数据类型。支持的数据类型为:int8_t、uint8_t、int16_t、uint16_t、int32_t、uint32_t、int64_t、uint64_t。 |
BitwiseNotConfig的定义(见 bitwise_not_utils.h 源码):
struct BitwiseNotConfig { bool isReuseSource; }; constexpr BitwiseNotConfig DEFAULT_BITWISE_NOT_CONFIG = {false};从源码可以看到,仓库提供了编译期常量DEFAULT_BITWISE_NOT_CONFIG = {false},因此模板参数config在绝大多数情况下都可以省略(使用默认值)。isReuseSource字段当前为预留项,用于标识是否复用源操作数的输入空间,目前必须传入false——内核参数校验(见下文"参数校验"小节)会对该字段做强校验。
实参(普通参数)说明
表2 参数说明
| 参数名称 | 类型 | 说明 |
|---|---|---|
| dst | 输出 | 目的操作数。类型为 LocalTensor,支持的 TPosition 为 VECIN/VECCALC/VECOUT。 |
| src | 输入 | 源操作数。类型为 LocalTensor,支持的 TPosition 为 VECIN/VECCALC/VECOUT。数据类型需要与目的操作数保持一致。 |
| count | 输入 | 参与计算的元素个数。 |
需要说明的是,TPosition::VECIN/VECCALC/VECOUT三个逻辑位置在物理上都映射到Unified Buffer(UB)(参见 通用说明和约束 中的 TPosition 与物理内存映射关系表)。也就是说,参与 BitwiseNot 计算的 src 与 dst 都必须位于 AI Core 的 UB 上,计算前通常需要先用DataCopy将 Global Memory(GM)中的输入搬入 UB,计算完成后再将结果搬回 GM(完整流程见下文"Kernel 侧完整调用流程")。
返回值说明
无返回值(void),计算结果直接写入dst。
约束说明
使用 BitwiseNot 时必须遵守以下约束:
- 不支持源操作数与目的操作数地址重叠。即
src与dst不能指向 UB 中的同一段地址空间,这与基础 API 中"单次迭代内支持源目的 100% 完全重叠"的通用约定不同(通用说明和约束),属于该接口的额外限制,需要开发者额外注意。 - 操作数地址偏移对齐要求请参见通用说明和约束。其中与 BitwiseNot 直接相关的是:UB 存储单元的起始地址要求 32 Byte 对齐(L1 Buffer 同样为 32 Byte 对齐,L0A/L0B 为 512 Byte 对齐,L0C 为 64 Byte 对齐)。
调用示例与结果语义
接口文档给出了最简调用示例:
AscendC::LocalTensor<uint32_t> dst, src; uint32_t count = 512; // 参与计算的元素个数 AscendC::BitwiseNot(dst, src, count);示例运行结果(接口文档原样摘录):
输入数据(src): [ 91, 48, -71, 112, 64, -107, 74, -121, -103, -50, 112, -23, 15, ... -74] 输出数据(dst): [-92, -49, 70, -113, -65, 106, -75, 120, 102, 49, -113, 22, -16, ... 73]结果的二进制语义解读
注意示例中src的数据类型是uint32_t,但输入值以带符号形式书写(如-71、-107)。逐比特取反的结果需要结合二进制的补码表示来理解:
- 对于无符号数,
~x等价于2^N - 1 - x(N 为位宽,如uint32_t的 N=32)。例如91(0x0000005B)取反后为0xFFFFFFA4,按有符号int32_t解读即为-92,与示例输出一致。 - 对于有符号负数,其二进制即补码,逐比特取反后等价于
-x - 1。例如-71(0xFFFFFFB9)取反为0x00000046,即70,与示例输出一致。
因此,BitwiseNot 对源数据的每一位(含符号位)统一翻转,不区分符号,这正是"逐比特取反"与"数值取负(Neg)"的本质区别。
Kernel 侧完整调用流程
在实际算子开发中,BitwiseNot 需要与数据搬运、队列管理配合使用。仓库测试用例 test_operator_bitwise_not.cpp 给出了一个完整的可参考模板:使用TQue<TPosition::VECIN, 1>输入队列和TQue<TPosition::VECOUT, 1>输出队列,CopyIn阶段用DataCopy把 GlobalTensor 数据搬入 UB,Compute阶段调用BitwiseNot(dstLocal, srcLocal, this->dataSize),CopyOut阶段把结果搬回 GM,并对count不能按32/sizeof(T)整除的尾数部分做逐元素SetValue/GetValue回写:
__aicore__ inline void Compute() { PipeBarrier<PIPE_ALL>(); LocalTensor<T> dstLocal = outQueue.AllocTensor<T>(); LocalTensor<T> srcLocal = inQueue.DeQue<T>(); BitwiseNot(dstLocal, srcLocal, this->dataSize); outQueue.EnQue<T>(dstLocal); inQueue.FreeTensor(srcLocal); }源码级实现原理
接口入口与编译期分派
公开接口声明位于 include/adv_api/math/bitwise_not.h。该头文件通过__NPU_ARCH__编译期宏判断目标架构,仅对支持的架构(3510、9201、5102、5162、3003、3113)引入内核实现 bitwise_not_3510_impl.h,并把真正的计算逻辑转发给BitwiseNotImpl<config, T>(dst, src, count)。
内核实现:类型分派与矢量寄存器运算
bitwise_not_3510_impl.h 中的BitwiseNotImpl首先通过static_assert+SupportType<T, uint8_t, int8_t, uint16_t, int16_t, uint32_t, int32_t, uint64_t, int64_t>()在编译期强制校验数据类型必须是文档声明的 8 种类型之一,随后按数据类型字节数做两条计算路径的分派:
sizeof(T) == 8(int64_t/uint64_t):使用双寄存器特性Reg::RegTraitNumTwo,单次repeat处理GetVecLen() / sizeof(T) * 2个元素;- 其他类型:使用单寄存器
Reg::RegTensor<T>,单次repeat处理GetVecLen() / sizeof(T)个元素。
真正的计算在BitwiseNotCompute中完成,核心调用链为寄存器级的Reg::Not(dstVreg, srcVreg, mask)矢量取反指令,配合Reg::LoadAlign/Reg::StoreAlign完成 UB 数据的加载与回存,并用Reg::UpdateMask<T, Trait>(count)生成尾数掩码来处理不能被单次 repeat 整除的剩余元素:
Reg::LoadAlign(srcVreg, src + i * oneRepElm); Reg::Not(dstVreg, srcVreg, mask); Reg::StoreAlign(dst + i * oneRepElm, dstVreg, mask);可以看到,整条计算被划分为repeatTime次循环(每次双份处理)加一次尾部处理,以充分利用矢量单元的计算宽度。
参数校验(API Check)
在ASCENDC_CPU_DEBUG等调试路径下,bitwise_not_check_c310.h 中的CheckFuncClassBitwiseNot会组合多组基础校验类完成运行期检查:
CalCountVerifyingParameters:校验count(参与计算元素个数)的合法性;TensorVerifyingParameters:校验 dst/src 的 TPosition 必须是VECIN/VECOUT/VECCALC(即必须位于 UB);TensorReuseVerifyingParameters:校验 dst 与 src 不得地址重叠(对应上文"不支持源操作数与目的操作数地址重叠"约束);IsReuseSourceVerifyingParameters<false>:校验isReuseSource必须为 false(预留参数强约束)。
Tiling 配套接口(一):GetBitwiseNotMaxMinTmpSize
BitwiseNot 的核函数(Kernel)侧计算需要开发者预留/申请临时空间。GetBitwiseNotMaxMinTmpSize 用于在 Host 侧获取预留/申请的最大和最小临时空间大小,开发者基于此范围选择合适的空间大小作为 Tiling 参数传递到核函数侧使用。
使用要点:
- 为保证功能正确,预留/申请的临时空间大小不能小于最小临时空间大小;
- 在最小临时空间~最大临时空间范围内,随着临时空间增大,核函数侧接口计算性能会有一定程度的优化提升。为了达到更好的性能,开发者可以根据实际的内存使用情况进行空间预留/申请。
函数原型
void GetBitwiseNotMaxMinTmpSize(const platform_ascendc::PlatformAscendC& ascendcPlatform, const AscendC::TensorShape& srcShape, const uint32_t typeSize, const bool isReuseSource, uint32_t& maxValue, uint32_t& minValue)参数说明
表1 接口参数列表
| 参数名 | 输入/输出 | 功能 |
|---|---|---|
| ascendcPlatform | 输入 | 输入的平台信息。PlatformAscendC 的定义请参见构造及析构函数。 |
| srcShape | 输入 | 输入的 shape 信息,参数类型为 AscendC::TensorShape(ge::Shape的类型别名)。 |
| typeSize | 输入 | 输入的数据类型大小,单位为字节。比如输入的数据类型为 uint16_t,此处应传入 2。 |
| isReuseSource | 输入 | 该参数预留,传入默认值 false 即可。 |
| maxValue | 输出 | BitwiseNot 接口能完成计算所需的最大临时空间大小,超出该值的空间不会被该接口使用。最大空间大小为 0 表示计算不需要临时空间。请注意,maxValue 仅作为参考值,有可能大于 Unified Buffer(UB)剩余空间的大小,该场景下,开发者需要根据 UB 剩余空间的大小来选取合适的临时空间大小。 |
| minValue | 输出 | BitwiseNot 接口能完成计算所需最小临时空间大小。最小空间大小为 0 表示计算不需要临时空间。 |
调用示例
std::vector<int64_t> shape_vec = {1024}; AscendC::TensorShape shape(shape_vec); uint32_t maxValue = 0; uint32_t minValue = 0; auto plat = platform_ascendc::PlatformAscendC(context->GetPlatformInfo()); AscendC::GetBitwiseNotMaxMinTmpSize(plat, shape, 2, false, maxValue, minValue);Tiling 实现侧的证据
Host 侧实现位于 bitwise_not_tiling_impl.cpp:
- 内部维护
SUPPORT_TYPESIZE = {1, 2, 4, 8},与 BitwiseNot 支持的 8 种整数类型(1/2/4/8 字节)一一对应,typeSize不在该集合时会触发TypeSizeVerifyingParameters校验失败; - 通过
ASCENDC_HOST_ASSERT((npuArch == NpuArch::DAV_3510 || npuArch == NpuArch::DAV_9201), ...)校验 NPU 架构,与接口文档"仅 Ascend 950 系列支持"的产品矩阵一致; GetBitwiseNotMaxTmpSize与GetBitwiseNotMinTmpSize当前均返回 0,即该接口当前版本的 BitwiseNot 计算不需要额外临时空间,与测试用例的断言一致(见下文"测试用例验证")。
Tiling 配套接口(二):GetBitwiseNotTmpBufferFactorSize
GetBitwiseNotTmpBufferFactorSize 用于获取maxLivedNodeCount和extraBuf,在固定空间大小的情况下,通过这两个输出值可以推算算子单次最大计算元素数量:
maxLivedNodeCount:最大存活节点数,表示临时空间是单次计算数据量所占空间的多少倍;extraBuf:使用的额外临时空间大小,单位为字节。
单接口推算公式
算子实现需要调用 BitwiseNot 接口,开发者为其预留currBuff大小的空间,利用 GetBitwiseNotTmpBufferFactorSize 接口得到maxLivedNodeCount、extraBuf输出值,可推导算子单次最大计算元素数量为:
currentShapeSize = (currBuff - extraBuf) / maxLivedNodeCount / typeSize多接口组合推算公式
算子实现需要调用两个核函数侧 API(如 KernelIntf1、KernelIntf2),利用两个 GetXxxTmpBufferFactorSize(其中 Xxx 为需要调用的两个高阶 API)接口的两组输出值(maxLivedNodeCount、extraBuf)以及当前现有的临时空间,分别推导单次最大计算元素数量,再取较小值作为最终的单次计算规模:
currentShapeSize1 = (currBuff - extraBuf1) / maxLivedNodeCount1 / typeSize currentShapeSize2 = (currBuff - extraBuf2) / maxLivedNodeCount2 / typeSize currentShapeSize = min(currentShapeSize1, currentShapeSize2)注意:上文中的
currBuff表示接口计算可用的空间,需要去除用户输入输出等空间;另外,接口获取的maxLivedNodeCount值可能为 0,计算时需要判断该值非 0,避免除零错误。
函数原型
void GetBitwiseNotTmpBufferFactorSize(const platform_ascendc::PlatformAscendC& ascendcPlatform, const uint32_t typeSize, uint32_t& maxLivedNodeCount, uint32_t& extraBuf)参数说明
表1 参数列表
| 参数名 | 输入/输出 | 功能 |
|---|---|---|
| ascendcPlatform | 输入 | 输入的平台信息。PlatformAscendC 的定义请参见构造及析构函数。 |
| typeSize | 输入 | 输入的数据类型大小,单位为字节。 |
| maxLivedNodeCount | 输出 | 最大存活节点数,表示临时空间是单次计算数据量所占空间的多少倍。 |
| extraBuf | 输出 | 使用的额外临时空间大小,单位为字节。 |
约束说明
当利用 maxLivedNodeCount、extraBuf 反推出的currentShapeSize * typeSize < 256B时,currentShapeSize 按照256B / typeSize的值向上取整。这一约束与矢量计算以 256 Byte 为基本处理粒度的硬件特性相吻合。
调用示例
uint32_t maxLivedNodeCount = 0; uint32_t extraBuf = 0; auto plat = platform_ascendc::PlatformAscendC(context->GetPlatformInfo()); AscendC::GetBitwiseNotTmpBufferFactorSize(plat, typeSize, maxLivedNodeCount, extraBuf);Host 侧 Tiling 与 Kernel 侧的配套使用流程
GetXxxMaxMinTmpSize 系列 Tiling 接口的完整配套使用方法,可参考更多样例(该文档以同类数学接口 Asin 为例说明通用流程,同样适用于 BitwiseNot 系列接口):
- Host 侧:在 Tiling 函数中构造
AscendC::TensorShape shape(srcDims)描述输入形状,调用GetBitwiseNotMaxMinTmpSize获取maxValue/minValue;再通过platform_ascendc::PlatformAscendC(platformInfo).GetCoreMemSize(platform_ascendc::CoreMemType::UB, tailSize)获取 UB 剩余空间(实际场景需减去已使用的 UB 空间),并以tailSize >= maxValue ? maxValue : tailSize确定最终临时空间大小tmpSize,写入 TilingData(如srcSize、tmpBufferSize字段)传给 Kernel; - Kernel 侧:通过
GET_TILING_DATA读取 TilingData,用pipe.InitBuffer(tmpBuf, tmpBufferSize)按计算出的空间大小申请临时 Buffer,之后即可安全调用 BitwiseNot 完成计算。
这种"Host 侧算空间、Kernel 侧按空间计算"的配合方式,能够避免临时空间申请不足导致的错误,也能在 UB 空间允许的范围内最大化单次计算规模。
测试用例验证
仓库为 BitwiseNot 提供了两层测试验证:
内核功能测试test_operator_bitwise_not.cpp:以
BitwiseNotTestParams{dataTypeSize, inDataSize, calFunc}参数化方式覆盖8 种数据类型 × 3 种数据规模(32 / 256 / 1024 元素),即 uint8_t、int8_t、uint16_t、int16_t、uint32_t、int32_t、uint64_t、int64_t 全类型组合。测试将输入初始化为1(二进制最低位为 1,其余位为 0),调用 BitwiseNot 后校验输出字节全为0x00,精确验证了"逐比特取反"的语义(~0x01 = 0xFE,取uint8_t首字节即0x00)。Tiling 接口测试test_tiling_v310.cpp 中的
TestBitwiseNotTiling:构造shapeDims = {128}的 shape 与sizeof(float)(4 字节)的 typeSize,断言GetBitwiseNotMaxMinTmpSize返回的maxVal、minVal均为 0,GetBitwiseNotTmpBufferFactorSize返回的extraBuf、maxLivedNodesCnt均为 0——印证了当前版本 BitwiseNot 计算无需额外临时空间,开发者调用这两个 Tiling 接口时可直接将返回值作为 0 处理,或按通用流程将其作为"空间下限/上限"参考继续参与 UB 规划。
关键源码路径速览
| 内容 | 仓库相对路径 |
|---|---|
| 公开接口声明 | include/adv_api/math/bitwise_not.h |
| 配置结构体定义 | include/adv_api/math/bitwise_not_utils.h |
| Tiling 接口声明 | include/adv_api/math/bitwise_not_tiling.h |
| 内核实现(Reg::Not 矢量取反) | impl/adv_api/detail/math/bitwise_not/bitwise_not_3510_impl.h |
| Tiling 实现(临时空间计算) | impl/adv_api/tiling/math/bitwise_not_tiling_impl.cpp |
| 参数校验实现 | impl/adv_api/detail/api_check/kernel_check/math/bitwise_not/bitwise_not_check_c310.h |
| 内核功能测试 | tests/api/adv_api/math/bitwise_not/test_operator_bitwise_not.cpp |
| Tiling 接口测试 | tests/api/adv_api/tiling/test_tiling_v310.cpp |
继续阅读
- BitwiseNot 接口页(接口索引入口)
- BitwiseAnd、BitwiseOr、BitwiseXor 等同类按位运算接口
- 数学计算接口总览
- 通用说明和约束(TPosition 映射、地址对齐)
- LocalTensor 数据结构
- 人工智能
- 深度学习
- 算子库
- CANN
- Ascend
【免费下载链接】asc-devkit
本项目是CANN 推出的昇腾AI处理器专用的算子程序开发语言,原生支持C和C++标准规范,主要由类库和语言扩展层构成,提供多层级API,满足多维场景算子开发诉求。
相关推荐
asc-devkit 高阶 API 实战:BitwiseOr 逐比特或运算接口与 Host 侧临时空间规划指南
asc devkit 高阶 API 实战:BitwiseOr 逐比特或运算接口与 Host 侧临时空间规划指南 导读 本文围绕 CANN 昇腾 AI 处理器算子
人工智能深度学习算子库CANNAscendUltimate Vocal Remover v5.6 安装教程:30 分钟装好免费人声分离工具,拿到干净伴奏
Ultimate Vocal Remover v5.6 安装教程:30 分钟装好免费人声分离工具,拿到干净伴奏 Ultimate Vocal Remover(U
人工智能深度学习算子库CANNAscendasc-devkit LogicalNot 高阶接口实战指南:逐元素逻辑取反算子与临时空间规划
asc devkit LogicalNot 高阶接口实战指南:逐元素逻辑取反算子与临时空间规划 导读 LogicalNot 是 CANN asc devkit
人工智能深度学习算子库CANNAscend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考