ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

CANN ops-math 按位左移算子 aclnnLeftShift 接口详解:两段式调用、参数约束与源码级实现分析

CANN ops-math 按位左移算子 aclnnLeftShift 接口详解:两段式调用、参数约束与源码级实现分析 算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载导读本文围绕 CANN ops-math 数学算子库中math/left_shift目录所实现的按位左移算子 aclnnLeftShift 接口展开完整讲解其产品支持情况、功能定义、两段式调用流程、参数规格、错误码以及底层 l0op 算子拼接实现。读完本文你将掌握如何在 Ascend NPU 上通过 aclnn 接口完成张量级按位左移计算并理解其类型推导、broadcast 与 workspace 管理机制。功能概述与计算公式aclnnLeftShift 是 CANN 提供的按位左移Bitwise Left Shift单算子 API作用于 NPU 设备。其核心语义为对于输入张量self中的每个元素按照输入张量shiftBits对应位置的取值逐元素执行按位左移输出结果张量out。计算公式为$$ out_{i} self_{i}shiftBits_{i} $$与之配套的aclnnLeftShifts参见 math/left_shift/docs/aclnnLeftShifts.md则将shiftBits从张量换为标量计算公式为 $out_{i} self_{i}shiftBits$。两者的参数校验、计算流程与错误码基本一致区别仅在于移位量的载体形式。产品支持情况aclnnLeftShift 接口并非在全部昇腾硬件上都可用其支持范围由算子库按 SoC 版本区分产品形态支持情况Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品310P不支持Atlas 训练系列产品910不支持该支持矩阵与源码中按 SoC 版本获取数据类型支持列表的设计相印证aclnn_left_shift.cpp中通过GetDtypeSupportListBySocVersion()返回支持的数据类型集合为不同硬件平台预留了差异化的能力开关。两段式接口架构aclnnLeftShift 遵循 CANN 单算子 API 的两段式接口设计。两段式接口的通用形态为aclnnStatus aclxxXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, ..., uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclxxXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream);其中aclxx是接口前缀此处为aclnnXxx为算子类型此处为LeftShift。必须先调用第一段接口aclnnLeftShiftGetWorkspaceSize其作用是完成入参校验、构建包含完整计算流程的算子执行器executor并返回本次计算所需的 workspace 临时内存大小随后调用方按workspaceSize在 NPU 侧申请内存再调用第二段接口aclnnLeftShift真正下发执行。需要注意workspace 指除输入/输出之外算子在 NPU 上完成计算所需的临时内存第二段接口aclnnLeftShift(...)不可重复调用同一 executor 只能执行一次重复调用会出现异常。函数原型第一段接口原型aclnnStatus aclnnLeftShiftGetWorkspaceSize( const aclTensor *self, const aclTensor *shiftBits, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)第二段接口原型aclnnStatus aclnnLeftShift( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)两个接口的声明位于 math/left_shift/op_api/aclnn_left_shift.h实现位于 math/left_shift/op_api/aclnn_left_shift.cpp。aclnnLeftShiftGetWorkspaceSize 参数说明第一段接口的参数规格如下参数名输入/输出描述使用说明数据类型数据格式维度非连续 tensorself输入需要进行按位左移的张量公式中的 self支持空 Tensor数据类型与 shiftBits 需满足互推导关系shape 需与 shiftBits 满足 broadcast 关系INT8、INT16、INT32、INT64、UINT8、UINT16、UINT32、UINT64ND0-8√shiftBits输入左移操作数的张量公式中的 shiftBits支持空 Tensor数据类型与 self 需满足互推导关系shape 需与 self 满足 broadcast 关系INT8、INT16、INT32、INT64、UINT8、UINT16、UINT32、UINT64ND0-8√out输出输出张量公式中的 outshape 需与 self 和 shiftBits broadcast 之后的 shape 保持一致INT8、INT16、INT32、INT64、UINT8、UINT16、UINT32、UINT64ND0-8√workspaceSize输出返回需要在 Device 侧申请的 workspace 大小-----executor输出返回 op 执行器包含算子计算流程-----返回值与错误码第一段接口返回aclnnStatus状态码具体参见 aclnn 返回码。它负责完成全部入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001self、shiftBits、out 存在空指针ACLNN_ERR_PARAM_INVALID161002self、shiftBits、out 的数据类型和数据格式不在支持范围之内ACLNN_ERR_PARAM_INVALID161002self、shiftBits 的数据类型不支持类型推导ACLNN_ERR_PARAM_INVALID161002self、shiftBits 的数据维度大于 8ACLNN_ERR_PARAM_INVALID161002self、shiftBits 的 shape 不满足 broadcast 规则ACLNN_ERR_PARAM_INVALID161002self、shiftBits 的 shape 在 broadcast 之后与 out 的 shape 不一致源码中的校验链实现上述错误码并非文档虚设而是由源码中一组静态校验函数逐一保证见 math/left_shift/op_api/aclnn_left_shift.cppCheckNotNull对 self、shiftBits、out 依次执行OP_CHECK_NULL任一为空即返回ACLNN_ERR_PARAM_NULLPTRCheckDtypeValid通过OP_CHECK_DTYPE_NOT_SUPPORT将三者与DTYPE_SUPPORT_LISTINT8/INT16/INT32/INT64/UINT8/UINT16/UINT32/UINT64比对不满足则返回ACLNN_ERR_PARAM_INVALIDCheckShape检查selfDim与shiftBitsDim均不大于MAX_INPUT_DIM源码中static constexpr int32_t MAX_INPUT_DIM 8并通过OP_CHECK_BROADCAST_AND_INFER_SHAPE推导 broadcast 后 shape再与 out 的 view shape 比对CheckFormatValid通过IsPrivateFormat排除 NC1HWC0 等私有格式仅接受 ND、NCHW、NHWC、HWCN、NDHWC、NCDHW 等公开格式CheckPromoteType调用op::PromoteType推导 self 与 shiftBits 的共同计算类型若结果为DT_UNDEFINED或无法转换为 out 的数据类型则返回ACLNN_ERR_PARAM_INVALID。最终这些检查在CheckParams中串行执行构成第一段接口的完整入参防线。aclnnLeftShift 参数说明第二段接口的参数规格如下参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnLeftShiftGetWorkspaceSize 获取executor输入op 执行器包含算子计算流程stream输入指定执行任务的 Stream第二段接口返回aclnnStatus状态码。其实现极为精简——在 aclnn_left_shift.cpp 中仅调用CommonOpExecutorRun(workspace, workspaceSize, executor, stream)完成统一执行并带有L2_DFX_PHASE_2诊断埋点说明真正的计算逻辑已被封装进第一段接口构建的 executor 之中。底层计算流程l0op 算子拼接从源码看aclnnLeftShift 并非直接调用某个 AscendC kernel而是通过 executor 将多个底层 l0op 算子按数据流图拼装而成这在头文件注释中以 mermaid 流程图给出对应到 aclnn_left_shift.cpp 中aclnnLeftShiftGetWorkspaceSize的实现其流水线依次为空 Tensor 短路self-IsEmpty() || shiftBits-IsEmpty()时直接返回workspaceSize 0并成功退出避免无效下发类型推导op::PromoteType(self-GetDataType(), shiftBits-GetDataType())得到统一的中间计算类型 promoteTypeContiguousl0op::Contiguous将可能非连续的 self 与 shiftBits 规整为连续张量这也是文档中支持非连续 tensor的落地机制Cast将两个输入分别转换到 promoteType保证同一数据类型下完成位运算BroadcastTo若输入 shape 与 out 的 shape 不一致则通过l0op::BroadcastTo扩展到目标 shapebroadcastShapeArray由 executor 的AllocIntArray分配LeftShiftl0op::LeftShift(selfBroadcast, shiftBitsdBroadcast, ...)完成逐元素按位左移该算子声明见 math/left_shift/op_api/left_shift.hCast将中间结果转换回 out 声明的数据类型ViewCopyl0op::ViewCopy(outCasted, out, ...)将结果拷贝进可能非连续的输出张量汇总 workspace*workspaceSize uniqueExecutor-GetWorkspaceSize()汇总整条流水线所需的临时内存。这种Contiguous → Cast → BroadcastTo → 核心算子 → Cast → ViewCopy的组合模式与aclnnLeftShifts标量版本的流程同构区别仅在于标量版通过ConvertToTensor将 aclScalar 转成张量参与计算参见同一文件中的aclnnLeftShiftsGetWorkspaceSize。约束说明确定性计算aclnnLeftShift 默认采用确定性实现即相同输入多次执行结果完全一致相关背景可参考确定性计算精度约束只能保证当 shiftBits 的数值小于 self 数据类型位宽即移位位数不超出该类型比特数时结果无误差。若移位位数超出位宽属于未定义/不保证精度的场景使用时需自行规避。调用示例以下完整示例演示了两段式接口的标准用法初始化设备与 Stream → 构造 aclTensor → 调用 GetWorkspaceSize → 申请 workspace → 执行 → 同步 → 取回结果 → 释放资源。代码路径为 math/left_shift/examples/test_aclnn_left_shift.cpp编译与运行方式参考编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_left_shift.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 shapeSize 1; for (auto i : shape) { shapeSize * i; } return shapeSize; } 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); // 调用aclrtMalloc申请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); // 调用aclrtMemcpy将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]; } // 调用aclCreateTensor接口创建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初始化参考acl API手册 // 根据自己的实际device填写deviceId int32_t deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出需要根据API的接口自定义构造 std::vectorint64_t selfShape {1, 4}; std::vectorint64_t shiftBitsShape {1, 4}; std::vectorint64_t outShape {1, 4}; void* selfDeviceAddr nullptr; void* shiftBitsDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* shiftBits nullptr; aclTensor* out nullptr; std::vectorint64_t selfHostData {10, 20, 30, 40}; std::vectorint64_t shiftBitsHostData {1, 2, 3, 4}; std::vectorint64_t outHostData {0, 0, 0, 0}; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_INT64, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建shiftBits aclTensor ret CreateAclTensor(shiftBitsHostData, shiftBitsShape, shiftBitsDeviceAddr, aclDataType::ACL_INT64, shiftBits); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_INT64, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API需要修改为具体的API名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnLeftShiftGetWorkspaceSize第一段接口 ret aclnnLeftShiftGetWorkspaceSize(self, shiftBits, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLeftShiftGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 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); } // 调用aclnnLeftShift第二段接口 ret aclnnLeftShift(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLeftShift 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侧需要根据具体API的接口定义修改 auto size GetShapeSize(outShape); std::vectorint64_t resultData(size, 0); ret aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(resultData[0]), 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和aclScalar需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(shiftBits); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(shiftBitsDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }以上例而言输入self {10, 20, 30, 40}、shiftBits {1, 2, 3, 4}输出应为{101, 202, 303, 404} {20, 80, 240, 640}。示例代码还提供了基于std::unique_ptr的 RAII 资源管理版本使用StreamPtr、DeviceMemPtr、TensorPtr智能指针可有效规避异常路径下的资源泄漏。单元测试验证算子库为 aclnnLeftShift 提供了完整的 L2 层 UT 用例位于 math/left_shift/tests/ut/op_api/test_aclnn_left_shift.cpp其覆盖维度与本文所述约束一一对应正常场景覆盖 INT8/ND、INT16/NHWC、INT32/HWCN、INT64/NDHWC、UINT16/ND 等不同数据类型与公开格式组合均断言ACLNN_SUCCESS空 Tensorshape 为{0}时正常返回成功印证源码中的空 Tensor 短路逻辑空指针异常self、shiftBits、out 任一为nullptr均返回ACLNN_ERR_PARAM_NULLPTR非法数据类型BF16、FLOAT、FLOAT16、DOUBLE、COMPLEX64、COMPLEX128、UNDEFINED 均返回ACLNN_ERR_PARAM_INVALID类型推导INT8 INT64 → out INT64、INT8 INT8 → out INT32等推导/转换场景正常通过shape 校验out 与 broadcast 后 shape 不一致、维度达到 10 维均返回ACLNN_ERR_PARAM_INVALID格式校验输入或输出使用私有格式 NC1HWC0 时返回ACLNN_ERR_PARAM_INVALID非连续 tensor通过自定义 strides如{4, 2}构造非连续张量正常返回成功验证了 Contiguous 预处理环节的有效性。相关资源接口文档math/left_shift/docs/aclnnLeftShift.md、math/left_shift/docs/aclnnLeftShifts.md头文件含计算流程图math/left_shift/op_api/aclnn_left_shift.h接口实现math/left_shift/op_api/aclnn_left_shift.cppl0op 算子声明math/left_shift/op_api/left_shift.h运行示例math/left_shift/examples/test_aclnn_left_shift.cpp单元测试math/left_shift/tests/ut/op_api/test_aclnn_left_shift.cpp配套概念两段式接口、互推导关系、broadcast 关系、aclnn 返回码、编译与运行样例赞分享算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载相关推荐CANN ops-math 算子 aclnnInplaceFillTensor 接口详解两段式调用、参数约束与源码实现分析CANN ops math 算子 aclnnInplaceFillTensor 接口详解两段式调用、参数约束与源码实现分析 aclnnInplaceFillT算子库人工智能CANNCANN ops-math TruncateMod 算子详解aclnnTruncateMod 两段式接口调用、参数约束与源码实现剖析CANN ops math TruncateMod 算子详解aclnnTruncateMod 两段式接口调用、参数约束与源码实现剖析 TruncateMod算子库人工智能CANNCANN ops-math aclnnRoll 算子接口全解析两段式调用、参数约束与 NPU 源码实现CANN ops math aclnnRoll 算子接口全解析两段式调用、参数约束与 NPU 源码实现 aclnnRoll 是 CANN ops math 仓算子库人工智能CANN创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表