ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

CANN ops-math 数学算子 BitwiseAnd 深度解析:aclnn 接口调用、两段式 API 与 AscendC Kernel 实现

CANN ops-math 数学算子 BitwiseAnd 深度解析:aclnn 接口调用、两段式 API 与 AscendC Kernel 实现 CANN ops-math 数学算子 BitwiseAnd 深度解析aclnn 接口调用、两段式 API 与 AscendC Kernel 实现【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math本篇技术指南聚焦 CANN 开源算子库 ops-math 中的数学类基础算子 BitwiseAnd按位与/逻辑与。文章以 experimental/math/bitwise_and/README.md 为主线完整覆盖产品支持范围、算子功能与计算公式、参数说明、aclnn 调用方式并深入其 op_api / op_host / op_kernel 三层源码与示例代码帮助读者掌握在 Atlas A2 系列产品上通过 aclnnBitwiseAndTensor / aclnnBitwiseAndScalar 两段式接口完成按位与计算的全流程理解其底层类型推导与计算图实现原理。一、算子概览与产品支持情况BitwiseAnd 是 CANN ops-math 中实现与运算的数学类基础算子在开源仓库中以两个形态存在experimental实验态目录experimental/math/bitwise_and即本文关联文档所在位置提供完整的 aclnn 接口、算子定义、tiling 与 kernel 实现math正式态目录math/bitwise_and提供了更全的产品支持矩阵与接口变体含 arch35 内核与 TensorOut 接口。依据 experimental 版 README当前实验态算子支持的产品如下产品是否支持Atlas A2 训练系列产品 / Atlas 800I A2 推理产品 / A200I A2 Box 异构组件√正式态的 math/bitwise_and/README.md 进一步将支持范围扩展至 Atlas A3 训练/推理系列产品、Ascend 950PR / Ascend 950DT、Atlas 推理系列产品与 Atlas 训练系列产品Atlas 200I/500 A2 推理产品除外。读者在使用时应以各自环境实际安装的算子包版本为准。二、功能说明与计算公式BitwiseAnd 算子功能是对两个输入逐元素做按位与bitwise AND运算。当输入为整型时执行位与当输入为 BOOL 型时执行逻辑与语义与 PyTorch 等框架中的bitwise_and保持一致。计算公式算子级$$ y x1 ; and ; x2 $$在 aclnn 接口层面逐元素公式为$$ out_i self_i ; ; other_i $$例如5 3 1、6 3 2对于 BOOL 输入true false false。三、参数说明算子层依据 experimental/math/bitwise_and/README.md 的参数表算子层对应底层算子定义输入输出如下参数名输入/输出/属性描述数据类型数据格式x1输入待进行 BitwiseAnd 计算的入参公式中的 x1。int16, uint16, int32NDx2输入待进行 BitwiseAnd 计算的入参公式中的 x2。int16, uint16, int32NDy输出待进行 BitwiseAnd 计算的出参公式中的输出。int16, uint16, int32ND上述数据类型与 bitwise_and_def.cpp 中算子定义完全一致x1、x2 为必填REQUIRED输入y 为输出均声明为ge::DT_INT16 / ge::DT_UINT16 / ge::DT_INT32三种类型格式限定FORMAT_ND并通过this-AICore().AddConfig(ascend910b)注册 AICore 后端配置。约束说明为无。需要注意算子底层定义支持 3 种整型而 aclnn 接口层经过类型推导后支持更宽的输入范围见下文第五节这也是 CANN 算子分层设计的特点——接口层负责类型推导与转换底层算子只负责核心计算。四、调用方式概览调用方式调用样例说明aclnn 调用test_aclnn_bitwise_and_tensor.cpp通过 aclnnBitwiseAndTensor 接口方式调用 BitwiseAnd 算子。aclnnAscend CANN Lightweight Neural Network是 CANN 提供的轻量级算子调用接口调用方只需在 Host 侧完成资源初始化、张量构造与两段式接口调用即可在 NPU 上执行算子。实验态目录下共提供 4 个接口文档构成完整的 BitwiseAnd 接口家族aclnnBitwiseAndTensor.mdTensor 与 Tensor 逐元素按位与aclnnBitwiseAndScalar.mdTensor 与 Scalar标量按位与aclnnInplaceBitwiseAndTensor.mdInplace 版结果直接写回输入张量aclnnInplaceBitwiseAndScalar.mdInplace 版 Scalar 计算。五、aclnn 接口层函数原型与参数语义5.1 两段式接口CANN 的 aclnn 算子接口统一采用两段式two-phase调用模型参见 两段式接口说明第一段 GetWorkspaceSize完成入参校验、构图与 workspace 大小计算返回workspaceSize与封装了计算流程的executor第二段执行接口传入 workspace 与 executor在指定 stream 上异步执行计算。以 Tensor 形态为例两个接口的原型如下aclnnStatus aclnnBitwiseAndTensorGetWorkspaceSize(const aclTensor *self, const aclTensor *other, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnBitwiseAndTensor(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream);Scalar 形态将第二个输入替换为aclScalar*aclnnStatus aclnnBitwiseAndScalarGetWorkspaceSize(const aclTensor *self, const aclScalar *other, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnBitwiseAndScalar(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream);5.2 第一段接口参数语义以 aclnnBitwiseAndTensor.md 为准第一段接口参数说明如下selfaclTensor*计算输入公式中的self。数据类型支持 BOOL、INT8、INT16、INT32、INT64、UINT8、UINT16且需要与 other 满足数据类型互推导关系shape 需要与 other 满足 broadcast 关系。支持非连续 Tensor数据格式支持 ND数据维度不支持 8 维以上。otheraclTensor* / aclScalar*计算输入公式中的other。Tensor 形态下类型与约束同 selfScalar 形态下为标量值。outaclTensor*计算输出数据类型需要是 self 与 other 推导后可转换的数据类型参见互转换关系Tensor 形态下 shape 为两者 broadcast 之后的 shapeScalar 形态下 shape 与 self 保持一致。支持非连续 Tensor、ND 格式、维度不超过 8 维。Atlas 训练/A2/推理系列产品输出支持 BOOL、INT8、INT16、INT32、INT64、UINT8、UINT16、UINT32、UINT64。workspaceSizeuint64_t*出参需要在 Device 侧申请的 workspace 大小。executoraclOpExecutor**出参包含算子计算流程的 op 执行器。5.3 返回值与常见报错接口统一返回aclnnStatus状态码各返回码含义参见 aclnn 返回码。第一段接口完成入参校验典型报错场景161001(ACLNN_ERR_PARAM_NULLPTR)1. 传入的 self、other 或 out 是空指针。 161002(ACLNN_ERR_PARAM_INVALID)1. self 和 other 的数据类型不在支持的范围之内。 2. self 和 other 无法做数据类型推导。 3. self 和 other 推导出的数据类型无法转换为指定输出 out 的类型。 4. Tensor 形态self 和 other 的 shape 无法做 broadcast Scalar 形态self 和 out 的 shape 不同。 5. self、other、out 的维度超过 8 维。5.4 Inplace 与确定性Inplace 变体aclnnInplaceBitwiseAndTensor/aclnnInplaceBitwiseAndScalar与常规接口功能相同区别在于无需新建输出张量对象计算结果直接写入输入张量self的内存适合内存受限或就地更新场景。从 aclnn_bitwise_and_tensor.cpp 可以看到Inplace 版本实际上是把self直接当作out复用同一套 GetWorkspaceSize 逻辑。确定性计算aclnnBitwiseAndTensor / aclnnBitwiseAndScalar 默认均为确定性实现逐元素运算天然确定相关背景可参考 确定性计算说明。六、完整调用示例可编译运行以下示例整理自 examples/test_aclnn_bitwise_and_tensor.cpp展示了 Tensor 形态的完整调用流程初始化 → 构造张量 → 两段式调用 → 同步 → 取回结果 → 资源释放。Scalar 形态的完整代码见 examples/test_aclnn_bitwise_and_scalar.cpp二者差异仅在构造aclScalar输入aclCreateScalar(alphaValue, aclDataType::ACL_INT64)。#include iostream #include vector #include acl/acl.h #include aclnn_bitwise_and_tensor.h #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vectorint64_t shape) { int64_t shape_size 1; for (auto i : shape) { shape_size * i; } return shape_size; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法资源初始化 auto ret aclInit(nullptr); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclInit failed. ERROR: %d\n, ret); return ret); ret aclrtSetDevice(deviceId); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSetDevice failed. ERROR: %d\n, ret); return ret); ret aclrtCreateStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtCreateStream failed. ERROR: %d\n, ret); return ret); return 0; } template typename T int CreateAclTensor(const std::vectorT hostData, const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); // 申请 device 侧内存 auto ret aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMalloc failed. ERROR: %d\n, ret); return ret); // 将 host 侧数据拷贝到 device 侧 ret aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMemcpy failed. ERROR: %d\n, ret); return ret); // 计算连续 tensor 的 strides std::vectorint64_t strides(shape.size(), 1); for (int64_t i shape.size() - 2; i 0; i--) { strides[i] shape[i 1] * strides[i 1]; } // 创建 aclTensor *tensor aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. device/stream 初始化 int32_t deviceId 0; // 按实际 device 填写 aclrtStream stream; auto ret Init(deviceId, stream); CHECK_RET(ret 0, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出 std::vectorint64_t selfShape {4, 2}; std::vectorint64_t otherShape {4, 2}; std::vectorint64_t outShape {4, 2}; void* selfDeviceAddr nullptr; void* otherDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* other nullptr; aclTensor* out nullptr; std::vectorint64_t selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorint64_t otherHostData {1, 1, 2, 3, 3, 3, 4, 4}; std::vectorint64_t outHostData {0, 0, 0, 0, 0, 0, 0, 0}; ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_INT64, self); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(otherHostData, otherShape, otherDeviceAddr, aclDataType::ACL_INT64, other); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_INT64, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 两段式调用 CANN 算子库 API uint64_t workspaceSize 0; aclOpExecutor* executor; ret aclnnBitwiseAndTensorGetWorkspaceSize(self, other, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnBitwiseAndTensorGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); void* workspaceAddr nullptr; if (workspaceSize 0) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(allocate workspace failed. ERROR: %d\n, ret); return ret); } ret aclnnBitwiseAndTensor(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnBitwiseAndTensor failed. ERROR: %d\n, ret); return ret); // 4. 同步等待任务执行结束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); // 5. 将 device 侧结果拷贝回 host 并打印 auto size GetShapeSize(outShape); std::vectorint64_t resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(int64_t), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(copy result from device to host failed. ERROR: %d\n, ret); return ret); for (int64_t i 0; i size; i) { LOG_PRINT(result[%ld] is: %ld\n, i, resultData[i]); } // 6. 释放 aclTensor aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyTensor(out); // 7. 释放 device 资源 aclrtFree(selfDeviceAddr); aclrtFree(otherDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例输入输出对应关系INT64 形态逐元素| self | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | ---- | - | - | - | - | - | - | - | - | | other | 1 | 1 | 2 | 3 | 3 | 3 | 4 | 4 | | out | 0 | 1 | 2 | 3 | 0 | 1 | 4 | 4 |样例的具体编译与运行方法可参考 编译与运行样例。七、源码级实现原理7.1 op_api 层两段式接口的计算图aclnn_bitwise_and_tensor.cpp 是 Tensor 形态接口的核心实现其第一段接口aclnnBitwiseAndTensorGetWorkspaceSize的执行流程为参数校验CheckParams依次检查空指针ACLNN_ERR_PARAM_NULLPTR、数据类型是否在DTYPE_SUPPORT_LISTINT16/INT32/INT64/INT8/UINT8/BOOL/UINT16内、类型推导与输出转换可行性op::PromoteType、格式拒绝私有格式、shape 的 broadcast 与最大维度BITWISE_AND_MAX_TENSOR_DIM 8空 tensor 短路若 self 或 other 为空 tensor直接返回 workspaceSize 0交由 kernel 处理构图调用l0op::Contiguous将非连续输入转为连续l0op::Cast将两个输入统一转换到推导类型promoteType随后根据推导类型分流推导类型为BOOL→ 调用l0op::LogicalAnd逻辑与推导类型为整型→ 调用l0op::BitwiseAnd位与收尾对结果再次l0op::Cast转换到 out 的数据类型再用l0op::ViewCopy写入输出支持非连续输出汇总 executor 的 workspace 大小并返回。对应的数据流BOOL 场景与 INT 场景结构一致仅中间计算节点不同self ──► Contiguous ──► Cast ──┐ ├──► LogicalAnd / BitwiseAnd ──► Cast ──► ViewCopy ──► out other ─► Contiguous ──► Cast ──┘第二段接口aclnnBitwiseAndTensor则通过CommonOpExecutorRun统一完成异步执行。7.2 op_host 层算子定义与 tiling算子定义bitwise_and_def.cpp 通过OP_ADD(BitwiseAnd)注册算子信息库声明 x1/x2/y 三个端口及其类型与 ND 格式约束。tiling 计算bitwise_and_tiling.cpp 在 Host 侧完成切分策略计算通过PlatformAscendC获取 UB 大小与核数结合输入元素数、数据类型字节数计算每个核处理的数据量smallCoreDataNum/bigCoreDataNum、单次搬运的 tile 数据量tileDataNum与尾块处理tailBlockNum等最终通过context-SetBlockDim(coreNum)与SetTilingKey将切分参数写入 tiling 数据结构定义见 bitwise_and_tiling_data.h。对 int32 类型还额外预留了双倍 tmp 缓冲区数据量用于计算过程中的临时结果缓冲。7.3 op_kernel 层AscendC 内核bitwise_and.cpp 是内核入口按 tiling 数据实例化 KernelBitwiseAnd 并执行Process()。内核使用 AscendC 的 TPipe 流水线模型通过TQueVECIN, BUFFER_NUM双缓冲队列完成 x1/x2 的DataCopy搬入、向量指令计算、结果搬出核心计算指令为AscendC::And(yLocal, x1Local, x2Local, processDataNum)实现向量化的逐元素按位与。不同核按GetBlockIdx()划分各自负责的连续数据区间实现多核并行。八、贡献信息依据 experimental/math/bitwise_and/README.md 的贡献说明BitwiseAnd 算子的开源适配贡献信息如下贡献者贡献方贡献算子贡献时间贡献内容ilovescrapy个人开发者BitwiseAnd2025/12/26BitwiseAnd 算子适配开源仓九、小结BitwiseAnd 算子结构清晰、链路完整是学习 CANN 算子开发全流程aclnn 接口 → 算子定义 → tiling → AscendC kernel的典型样例。开发者可直接复用 examples 下的两个示例程序配合 接口文档 中给出的两段式调用模板与返回码表即可在 Atlas A2 系列环境上快速完成按位与 / 逻辑与计算的落地验证。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表