
算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载SignBitsUnpack 是 CANN ops-math 开源算子库中面向 1 位 Adam1-bit Adam优化算法场景的数学类算子负责把按位压缩的 uint8 类型符号位数据拆包unpack为 float32 或 float16 的浮点张量。本文以仓库中的 算子 README 与 aclnnSignBitsUnpack 接口文档 为主体结合 op_api、op_host、op_kernel 与单测源码完整讲解该算子的功能语义、参数约束、内部计算流程、两段式 aclnn 调用方式以及端到端验证方法。读完本文你将能够独立完成 SignBitsUnpack 算子的环境适配、参数配置、代码编写与结果校验。功能说明什么是 1 位符号拆包在 1 位 Adam 这类分布式训练压缩方案中优化器状态通常被量化压缩为单个符号位sign bit存储每个 float 只保留其符号压缩进 uint8 字节的各个 bit 位中从而大幅降低通信与存储开销。SignBitsUnpack算子正是这一压缩流程的解码端它把 uint8 类型的输入逐位展开恢复为浮点张量。README 中对算子功能的描述为算子功能对输入进行 unpack。当位置为 1 时取 1.0位置为 0 时取 0.0。需要说明的是从仓库中实际的 kernel 实现op_kernel/sign_bits_unpack.h与单测 goldentests/ut/op_kernel/sign_bits_unpack_data/gen_data.py来看实现细节为bit 位为 1 时输出 1.0bit 位为 0 时输出 -1.0即输出值为 ±1.0 的符号表示这与 1 位 Adam 的 1/-1 符号语义一致也对应接口文档中“将 uint8 类型 1 位 Adam 拆包为 float32 或者 float16”的功能描述。拆包规则与 NumPy 的np.unpackbits(..., bitorderlittle)一致按小端位序即每个 uint8 元素展开为 8 个输出元素第 0 位最低位对应输出中的第一个位置。仓库中的配套算子 SignBitsPack 完成反向的打包pack过程二者构成“符号位打包/拆包”的完整闭环。产品支持情况根据 算子 README 中的产品支持表SignBitsUnpack 的支持范围如下产品是否支持Atlas A2 训练系列产品 / Atlas 800I A2 推理产品 / A200I A2 Box 异构组件√更细粒度的支持情况见 aclnnSignBitsUnpack 接口文档产品是否支持Ascend 950PR / Ascend 950DT×Atlas A3 训练系列产品 / Atlas A3 推理系列产品×Atlas A2 训练系列产品 / Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品×Atlas 训练系列产品×该支持范围同样可以从源码中得到印证在 op_host/sign_bits_unpack_def.cpp 中算子通过this-AICore().AddConfig(ascend910b)注册 AICore 配置ascend910b 即 Atlas A2 训练系列对应的昇腾 SoC 版本在 op_api/aclnn_sign_bits_unpack.cpp 的CheckDtypeValid中接口层会通过GetCurrentPlatformInfo().GetCurNpuArch() ! NpuArch::DAV_2201对运行设备做芯片架构检查不满足时直接报ACLNN_ERR_PARAM_INVALID日志提示SignBitsUnpack is not supported on this device.。参数说明算子IR 层参数README 给出的算子参数定义如下参数名输入/输出/属性描述数据类型数据格式self输入待进行 SignBitsUnpack 计算的入参公式中的 x1uint8NDsize参数reshape 时输出张量的第一个维度int641dtype参数决定输出的数据类型int641y输出待进行 SignBitsUnpack 计算的出参公式中的输出float16, floatND对应的算子定义注册在 op_host/sign_bits_unpack_def.cpp 中输入self支持DT_UINT8、格式FORMAT_ND输出y支持DT_FLOAT16 / DT_FLOAT、格式FORMAT_ND并显式声明了 UnknownShape 场景下的格式方便动态 shape 图编译。aclnn 接口层参数在 aclnnSignBitsUnpack 接口文档 与 aclnn_sign_bits_unpack.h 中接口参数语义如下参数方向说明self计算输入1D 的 Device 侧 aclTensor数据类型 UINT8格式 ND。支持空 tensor、支持非连续的 Tensorsize入参Host 侧 int64 整型reshape 时输出张量的第一个维度dtype入参输出 Tensor 的数据类型支持 ACL_FLOAT16、ACL_FLOATout计算输出Device 侧 aclTensor数据类型 FLOAT16/FLOAT由 dtype 决定格式 ND支持非连续的 TensorworkspaceSize出参需要在 Device 侧申请的 workspace 大小executor出参算子执行器包含算子计算流程参数校验规则与错误码第一段接口aclnnSignBitsUnpackGetWorkspaceSize会完成入参校验校验逻辑实现在 op_api/aclnn_sign_bits_unpack.cpp 的CheckNotNull、CheckDtypeValid、CheckFormat、CheckShape、CheckValue等函数中。完整错误码列表如下具体返回码含义可参考 aclnn 返回码返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self 或 out 是空指针ACLNN_ERR_PARAM_INVALID161002self、out 的数据类型 / 数据格式不在支持的范围内ACLNN_ERR_PARAM_INVALID161002size 小于等于 0或者self 的元素个数× 8 % size ! 0ACLNN_ERR_PARAM_INVALID161002out 的数据类型与 dtype 不一致ACLNN_ERR_PARAM_INVALID161002self 的维度不是 1 维ACLNN_ERR_PARAM_INVALID161002out 的第一维度与 size 不一致从源码看PACK_SIZE被定义为 8每个 uint8 展开 8 个元素校验要点为self必须是 1 维、out必须是 2 维、size 0、selfDim * 8可被size整除、out第一维必须等于size并且out的数据类型必须与dtype参数完全一致通过OP_CHECK_DTYPE_NOT_MATCH校验。源码级实现原理aclnn 接口层的完整计算流程在 op_api/aclnn_sign_bits_unpack.cpp 中以注释形式给出了算子的完整计算图self dtype size \ / / Contiguous(workspace_0) / / \ / / SignBitsUnpack(workspace_1) | ViewCopy | result即一次aclnnSignBitsUnpackGetWorkspaceSize调用内部会依次拼接三个算子l0op::Contiguous(self)将输入self转换为连续的 tensor对应 workspace_0l0op::SignBitsUnpack(selfContiguous, size, dtype)执行真正的符号位拆包计算对应 workspace_1l0op::ViewCopy(castOut, out)将计算结果写回用户提供的out从而天然支持输出为非连续 tensor 的场景。最终通过*workspaceSize uniqueExecutor-GetWorkspaceSize()汇总计算所需的临时内存大小。接口还支持空 tensor 场景当self-IsEmpty() || out-IsEmpty()时直接置workspaceSize 0并返回成功不执行任何计算。整个流程由第二段接口aclnnSignBitsUnpack通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)提交到指定的 stream 上执行。Kernel 计算Duplicate Select 实现符号展开真正的拆包逻辑位于 op_kernel/sign_bits_unpack.cpp 与 op_kernel/sign_bits_unpack.h 中。kernel 入口根据 tiling keyschMode实例化不同的输出类型模板TILING_KEY_IS(1)KernelSignBitsUnpackhalf即 float16 输出路径否则KernelSignBitsUnpackfloat即 float32 输出路径。KernelSignBitsUnpack采用经典的 CopyIn / Compute / CopyOut 三段式流水AscendC 编程范式CopyIn通过DataCopy将 Global Memory 中当前 tile 的 uint8 数据搬入VECIN队列的 LocalTensorCompute核心计算由Duplicate与Select两个向量指令完成。float 路径为AscendC::Duplicate(outLocal, static_castfloat(1.0), this-processDataNumOut); AscendC::Select(outLocal, selfLocal, outLocal, static_castfloat(-1.0), AscendC::SELMODE::VSEL_TENSOR_SCALAR_MODE, this-processDataNumOut);即先把输出全部初始化为 1.0再以 uint8 的每一位作为选择条件、以 -1.0 作为标量参与VSEL_TENSOR_SCALAR_MODE选择得到 bit1 → 1.0、bit0 → -1.0 的结果。half 路径则分成两次 Select代码注释为“数据对齐”最终语义一致。CopyOut将VECOUT队列中的结果写回 Global Memory其中输出元素个数是输入元素个数的 8 倍outCoreNum coreDataNum * 8。流水线采用双缓冲DOUBLE_BUFFER 2TQueTPosition::VECIN/VECOUT, DOUBLE_BUFFER并通过 tiling 数据中的bufferOpen字段支持在数据量较小时退化为单缓冲以节省 UB 空间。float 路径额外申请了一块half类型的VECCALC临时缓冲tmpQueue0用于中间计算。Tiling 策略按 Core 均分与尾块处理Tiling 逻辑在 op_host/sign_bits_unpack_tiling.cpp 中流程分为四步获取平台信息GetPlatformInfo通过PlatformAscendC获取 UB 大小GetCoreMemSize(UB)与核数GetCoreNum()获取 shape 与属性GetShapeAttrsInfo以BLOCK_SIZE 64字节为对齐粒度根据输入字节数、输出类型长度float 为 4、half 为 2估算单个 tile 可容纳的数据量tileDataNum并决定是否开启双缓冲获取 workspaceGetWorkspaceSize合并用户 workspace本算子为 0与框架系统 workspace计算各核负载CalculateCoreBlockNums把输入按 64 字节块均分到各核得到smallCoreDataNum/bigCoreDataNum前tailBlockNum个核多分 1 块、finalSmallTileNum/finalBigTileNum、smallTailDataNum/bigTailDataNum等参数。最终写入 sign_bits_unpack_tiling_data.h 定义的SignBitsUnpackTilingData结构体并调用context-SetBlockDim(coreNum)设置并行核数。当单个 tile 即可容纳全部输入时coreNum收敛为 1退化为单核处理。tiling key 的取值定义在 sign_bits_unpack_tiling_key.h 中ELEMENTWISE_TPL_SCH_MODE_0float 输出与ELEMENTWISE_TPL_SCH_MODE_1half 输出。算子工程接入方式见 CMakeLists.txt通过add_all_modules_sources(OPTYPE sign_bits_unpack ACLNNTYPE aclnn_exclude)自动收集各目录源文件并纳入构建。两段式接口调用说明与 CANN 其它单算子 API 一致SignBitsUnpack 采用两段式接口Two-Phase API模式必须先调用第一段接口获取 workspace 大小与执行器再调用第二段接口执行计算。两段接口的函数原型如下aclnnStatus aclnnSignBitsUnpackGetWorkspaceSize( const aclTensor* self, int64_t size, aclDataType dtype, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor); aclnnStatus aclnnSignBitsUnpack( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream);第二段接口的参数语义参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream注意事项第二段接口不可重复调用即“GetWorkspaceSize → 执行”必须成对出现第二段接口本身不校验入参所有入参合法性都在第一段接口完成。调用示例完整可运行的 aclnn 样例仓库在 examples/test_aclnn_sign_bits_unpack.cpp 提供了完整的端到端调用样例编译与运行流程可参考 编译与运行样例。以下为完整示例代码#include memory #include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_sign_bits_unpack.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 {2}; std::vectorint64_t outShape {2, 8}; int64_t outsize 2; aclDataType dataType ACL_FLOAT; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* out nullptr; std::vectoruint8_t selfHostData {128, 128}; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_UINT8, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3. 调用CANN算子库API需要修改为具体的Api名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnSignBitsUnpack第一段接口 ret aclnnSignBitsUnpackGetWorkspaceSize(self, outsize, dataType, out, workspaceSize, executor); CHECK_RET( ret ACL_SUCCESS, LOG_PRINT(aclnnSignBitsUnpackGetWorkspaceSize 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); } // 调用aclnnSignBitsUnpack第二段接口 ret aclnnSignBitsUnpack(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnSignBitsUnpack 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::vectorfloat 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: %f\n, i, resultData[i]); } // 6. 释放aclTensor需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(out); // 7. 释放device 资源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }该样例的关键点解读shape 关系输入selfShape {2}2 个 uint8输出outShape {2, 8}2 行 × 8 列。size 2对应输出第一维满足“self 元素个数× 8 % size 0”的校验条件数据验证输入{128, 128}其中 128 0b10000000按小端位序展开为[0,0,0,0,0,0,0,1]对应输出应为[-1, -1, -1, -1, -1, -1, -1, 1]bit1 → 1.0bit0 → -1.0每个 uint8 元素扩展出 8 个 float内存生命周期self/out 的 Device 内存、workspace 内存均通过aclrtMalloc申请用完后依次aclDestroyTensor释放 tensor、aclrtFree释放内存、aclrtDestroyStream销毁 stream、aclrtResetDevice复位设备、aclFinalize完成收尾。单测与数据校验如何验证拆包正确性仓库在 tests/ut/op_kernel/test_sign_bits_unpack.cpp 提供了基于 gtest 的 kernel 级单测测试链路如下通过python3 gen_data.py (128) uint8生成随机输入数据与 golden 基准生成逻辑见 sign_bits_unpack_data/gen_data.pyinput_self np.random.randint(0, 10, shape).astype(np_type) golden np.unpackbits(input_self, bitorderlittle).astype(np.float32) golden[golden 0] -1即先按小端位序做逐位拆包再把 0 位替换为 -1得到 ±1.0 的 float32 golden测试用例手动构造SignBitsUnpackTilingDatasmallCoreDataNum 128、bigCoreDataNum 160、tileDataNum 2048、bufferOpen 0等通过ICPU_RUN_KF(func, blockDim, self, out, workspace, tilingData)在 CPU 仿真环境tikicpulib中运行 kernel输出写入float_output_t_sign_bits_unpack.bin再调用compare_data.py与 golden 比对验证拆包结果逐元素一致。此外还有 Host 侧 tiling 单测tests/ut/op_host/test_sign_bits_unpack_tiling.cpp用于校验 tiling 参数计算逻辑。这套“脚本生成 golden gtest 驱动 kernel 数据比对”的组合是复现算子正确性验证的直接入口。约束说明确定性计算aclnnSignBitsUnpack默认采用确定性实现多次运行同一输入会得到逐位一致的结果不会引入随机性相关背景可参考确定性计算。README 中“约束说明无”是指算子对输入数据本身无额外的 shape/取值范围限制合法输入约束统一由第一段接口的入参校验保证。数据类型与格式约束汇总输入仅支持 uint8、ND 格式输出仅支持 float16/float由 dtype 决定、ND 格式输入必须为 1 维输出必须为 2 维且第一维等于 size。运行环境约束仅支持 Atlas A2 训练系列 / Atlas A2 推理系列ascend910b / DAV_2201 架构等产品其它昇腾产品不支持见上文产品支持表。参考文档导航SignBitsUnpack 算子 README算子功能、参数、产品支持总览aclnnSignBitsUnpack 接口文档接口原型、错误码、调用示例两段式接口说明单算子 API 的通用调用范式编译与运行样例样例工程的编译运行方法aclnn 返回码接口状态码含义非连续 Tensor 支持与数据格式接口对 tensor 形态与格式的支持说明配套算子SignBitsPack符号位打包与本文拆包互为逆过程赞分享算子库人工智能CANN【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址https://gitcode.com/cann/ops-math点击查看免费下载相关推荐CANN ops-math 算子解析aclnnSignBitsPack 符号位打包接口原理与两段式调用实战CANN ops math 算子解析aclnnSignBitsPack 符号位打包接口原理与两段式调用实战 导读 aclnnSignBitsPack 是 CA算子库人工智能CANNCANN ops-math 算子详解KLDivV2 的接口、实现原理与 aclnn 调用实战CANN ops math 算子详解KLDivV2 的接口、实现原理与 aclnn 调用实战 KLDivV2 是 CANN ops math 算子库中用于计算算子库人工智能CANNCANN ops-math IsClose 算子深度解析原理、aclnn API 调用与 AscendC 实现CANN ops math IsClose 算子深度解析原理、aclnn API 调用与 AscendC 实现 IsClose 是 CANN ops math算子库人工智能CANN创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考