
CANN ops-nn ForeachAbs 算子详解aclnnForeachAbs 两段式接口调用与张量列表逐元素绝对值实现【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn本文以 CANN ops-nn 仓库中 foreach_abs 模块 的官方接口文档为主体系统讲解aclnnForeachAbs算子的功能定义、产品支持情况、两段式接口aclnnForeachAbsGetWorkspaceSizeaclnnForeachAbs的参数与返回码约束并结合仓库内的算子定义、Tiling 与 Kernel 源码剖析其底层实现。读完本文你将掌握在 NPU 上对张量列表逐元素求绝对值的完整调用流程并能够独立阅读与复用该算子的工程代码。功能说明与计算公式ForeachAbs是 CANN ops-nn 中一类典型的Foreach 系列算子它的输入与输出均为张量列表Tensor List对列表中的每一个张量分别执行逐元素element-wise的绝对值运算最终返回与原列表一一对应的输出张量列表。设输入张量列表为$$ x [{x_0}, {x_1}, ... {x_{n-1}}] $$输出张量列表为$$ y [{y_0}, {y_1}, ... {y_{n-1}}] $$则每个元素的运算规则为$$ y_i|{x_i}| \quad (i0,1,...n-1) $$即列表内第 $i$ 个输入张量的每一个元素取绝对值后写入第 $i$ 个输出张量列表长度 $n$ 保持不变。该接口与 PyTorch 的torch._foreach_abs语义对齐见下文测试用例章节常用于需要对一批张量同时执行相同一元运算、希望减少逐张量启动开销的训练/推理场景。产品支持情况根据 aclnnForeachAbs 接口文档 与 ForeachAbs README该算子的产品支持情况如下产品是否支持Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品不支持Kirin X90 处理器系列产品支持Kirin 9030 处理器系列产品支持需要注意的是README 中的产品清单比接口文档多出 Kirin X90 与 Kirin 9030 两项且额外说明Kirin X90 / Kirin 9030 处理器系列产品不支持 BFLOAT16。这一差异与源码中算子定义的平台配置一致详见下文“从源码看实现”章节。两段式接口总览aclnnForeachAbs属于 CANN 标准的两段式接口Two-Phase API通用机制参见 两段式接口说明。调用时必须先调用第一段接口aclnnForeachAbsGetWorkspaceSize完成入参校验、计算所需 workspace 大小并获取算子执行器aclOpExecutor再根据返回的 workspace 大小在 Device 侧申请内存最后调用第二段接口aclnnForeachAbs真正下发计算。两段接口的函数原型如下aclnnStatus aclnnForeachAbsGetWorkspaceSize( const aclTensorList *x, const aclTensorList *out, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnForeachAbs( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)第一段接口aclnnForeachAbsGetWorkspaceSize参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorxaclTensorList*输入进行绝对值计算的输入张量列表对应公式中的x支持空 Tensor列表中所有 Tensor 的数据类型保持一致BFLOAT16、FLOAT16、FLOAT32ND0-8√outaclTensorList*输出进行绝对值计算的输出张量列表对应公式中的y支持空 Tensor列表中所有 Tensor 的数据类型保持一致数据类型和数据格式与入参x一致shape size 大于等于入参x的 shape sizeBFLOAT16、FLOAT16、FLOAT32ND0-8×workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----对参数约束的解读数据类型x与out均支持 BFLOAT16、FLOAT16、FLOAT32 三种类型且两者类型必须一致。结合 README 参数说明 中的补充Kirin X90 / Kirin 9030 平台仅支持 FLOAT16 与 FLOAT32。数据格式仅支持 ND 格式。维度单个 Tensor 支持 0~8 维。空 Tensorx、out均允许空 Tensor列表中元素个数为 0此时算子按空列表处理。非连续 Tensorx支持非连续 Tensor表中为 √而out不支持非连续 Tensor表中为 ×。这也解释了文档“约束说明”中的对应条目。返回值与错误码第一段接口返回aclnnStatus状态码通用返回码定义参见 aclnn 返回码说明。第一段接口完成入参校验以下场景会报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 x 或 out 是空指针ACLNN_ERR_PARAM_INVALID161002x 或 out 的数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002x 和 out 的数据类型不一致ACLNN_ERR_INNER_TILING_ERROR561002x 与 out 的 shape 不满足约束ACLNN_ERR_INNER_TILING_ERROR561002x 或 out 中的 Tensor 的数据类型不一致ACLNN_ERR_INNER_TILING_ERROR561002x 或 out 中的 Tensor 维度超过 8 维其中 161002 覆盖两类场景数据类型超出支持范围、输入输出列表之间类型不一致561002 覆盖三类场景输入输出 shape 约束不满足、列表内部 Tensor 类型不一致、维度超过 8 维。第二段接口aclnnForeachAbs参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnForeachAbsGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值返回aclnnStatus状态码具体参见 aclnn 返回码说明。约束说明确定性计算aclnnForeachAbs默认采用确定性实现deterministic implementation同一输入在相同环境下多次运行结果可复现。关于确定性计算的通用约定可参考 确定性计算说明。调用示例下面给出完整的调用示例代码与 examples/test_aclnn_foreach_abs.cpp 中的样例一致。示例对包含两个张量的列表执行绝对值计算input1形状为{2, 3}6 个元素input2形状为{1, 3}3 个元素数据类型均为 FLOAT32。具体编译与执行流程请参考 编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_foreach_abs.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 selfShape1 {2, 3}; std::vectorint64_t selfShape2 {1, 3}; std::vectorint64_t outShape1 {2, 3}; std::vectorint64_t outShape2 {1, 3}; void* input1DeviceAddr nullptr; void* input2DeviceAddr nullptr; void* out1DeviceAddr nullptr; void* out2DeviceAddr nullptr; aclTensor* input1 nullptr; aclTensor* input2 nullptr; aclTensor* out1 nullptr; aclTensor* out2 nullptr; std::vectorfloat input1HostData {1, 2, 3, 4, 5, 6}; std::vectorfloat input2HostData {7, 8, 9}; std::vectorfloat out1HostData(6, 0); std::vectorfloat out2HostData(3, 0); // 创建input1 aclTensor ret CreateAclTensor(input1HostData, selfShape1, input1DeviceAddr, aclDataType::ACL_FLOAT, input1); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建input2 aclTensor ret CreateAclTensor(input2HostData, selfShape2, input2DeviceAddr, aclDataType::ACL_FLOAT, input2); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out1 aclTensor ret CreateAclTensor(out1HostData, outShape1, out1DeviceAddr, aclDataType::ACL_FLOAT, out1); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建out2 aclTensor ret CreateAclTensor(out2HostData, outShape2, out2DeviceAddr, aclDataType::ACL_FLOAT, out2); CHECK_RET(ret ACL_SUCCESS, return ret); std::vectoraclTensor* tempInput{input1, input2}; aclTensorList* tensorListInput aclCreateTensorList(tempInput.data(), tempInput.size()); std::vectoraclTensor* tempOutput{out1, out2}; aclTensorList* tensorListOutput aclCreateTensorList(tempOutput.data(), tempOutput.size()); // 3. 调用CANN算子库API需要修改为具体的API名称 uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnForeachAbs第一段接口 ret aclnnForeachAbsGetWorkspaceSize(tensorListInput, tensorListOutput, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnForeachAbsGetWorkspaceSize 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); } // 调用aclnnForeachAbs第二段接口 ret aclnnForeachAbs(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnForeachAbs 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(outShape1); std::vectorfloat out1Data(size, 0); ret aclrtMemcpy(out1Data.data(), out1Data.size() * sizeof(out1Data[0]), out1DeviceAddr, size * sizeof(out1Data[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(out1 result[%ld] is: %f\n, i, out1Data[i]); } size GetShapeSize(outShape2); std::vectorfloat out2Data(size, 0); ret aclrtMemcpy(out2Data.data(), out2Data.size() * sizeof(out2Data[0]), out2DeviceAddr, size * sizeof(out2Data[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(out2 result[%ld] is: %f\n, i, out2Data[i]); } // 6. 释放aclTensor需要根据具体API的接口定义修改 aclDestroyTensorList(tensorListInput); aclDestroyTensorList(tensorListOutput); // 7.释放device资源需要根据具体API的接口定义修改 aclrtFree(input1DeviceAddr); aclrtFree(input2DeviceAddr); aclrtFree(out1DeviceAddr); aclrtFree(out2DeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }对示例代码的要点拆解资源初始化固定写法aclInit→aclrtSetDevice→aclrtCreateStream为后续任务执行准备运行时环境。构造张量CreateAclTensor模板函数完成 host 数据到 Device 内存的拷贝aclrtMemcpy、连续 strides 计算并通过aclCreateTensor创建aclTensor随后用aclCreateTensorList将多个aclTensor组装为aclTensorList分别作为输入与输出列表。两段式调用先调用aclnnForeachAbsGetWorkspaceSize获得workspaceSize与executor当workspaceSize 0时用aclrtMalloc申请 workspace再调用aclnnForeachAbs(workspaceAddr, workspaceSize, executor, stream)执行计算。从 Kernel 源码 的注释foreach(vector) not need workspace可以看到ForeachAbs 属于无需额外 workspace 的算子——Tiling 阶段也会将 workspace 大小置 0见下文因此本例中workspaceSize通常为 0示例代码对 0 的分支做了防御性处理。同步与取数aclrtSynchronizeStream等待任务完成再通过aclrtMemcpyACL_MEMCPY_DEVICE_TO_HOST将结果回拷到 host 并逐元素打印。资源释放依次销毁 Tensor List、释放 Device 内存、销毁 Stream、Reset Device、aclFinalize。从源码看实现原理接口文档之外foreach_abs模块的源码完整呈现了算子的定义、调度与内核实现可以直接印证文档中的约束与能力。算子 IR 定义图模式构图foreach_abs_proto.h 通过REG_OP(ForeachAbs)注册算子使用DYNAMIC_INPUT(x, ...)与DYNAMIC_OUTPUT(y, ...)声明动态输入输出列表支持的数据类型为DT_FLOAT、DT_FLOAT16、DT_BF16。这对应了 README 中“图模式”调用方式可通过该算子 IR 构图来调用 ForeachAbs。算子注册与平台差异化配置foreach_abs_def.cpp 中定义ForeachAbs : public OpDef核心信息包括输入x、输出y均为ParamType(DYNAMIC)数据类型列表为{ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16}格式统一为FORMAT_ND开启DynamicRankSupportFlag(true)与DynamicShapeSupportFlag(true)对应文档中“0-8 维”“支持空 Tensor”等动态能力AutoContiguous()声明自动处理连续性这与文档中“输入支持非连续 Tensor、输出不支持非连续 Tensor”的约束直接相关为ascend950显式添加 AICore 配置并为ascend910_93、ascend910b添加配置与文档“Ascend 950PR/DT、Atlas A3、Atlas A2 支持”的产品清单一一对应通过GetKirinCoreConfig()为kirinx90、kirin9030单独构造配置其数据类型列表缩减为{ge::DT_FLOAT16, ge::DT_FLOAT}且开启DynamicFormatFlag(true)。这正是 README 中“Kirin X90/Kirin 9030 不支持 BFLOAT16”的源码依据。Tiling 策略Arch35foreach_abs_tiling_arch35.cpp 实现了 host 侧 Tiling 逻辑可提炼出的关键策略平台信息获取优先从编译期ForeachAbsCompileInfo取核数与 UB 大小否则回退到PlatformAscendC运行时查询GetCoreNumAiv、GetCoreMemSizeUB 空间规划从 UB 总大小中扣除 128KB 的 DCACHE 预留DCACHE_SIZE 128 * 1024后作为可用本地内存元素切分SINGLE_CORE_MIN_ELEMENTS 1024保证单核至少处理 1024 个元素避免小任务过度拆核perCoreElements按 32 对齐ALIGN_SIZE 32needCoreNum min(max(ceil(totalElements / perCoreElements), 1), coreNum)计算实际需要启动的核数并通过context-SetBlockDim(needCoreNum)下发累计偏移遍历每个输入张量把cumulativeOffsets[i]记录为前 i1 个张量的元素累计和供 Kernel 侧按张量边界切分计算同时校验tensorNum不超过MAX_TENSOR_NUM_FOREACH_ABS见 foreach_abs_tiling_data.h 中MAX_TENSOR_NUM 256TilingKey 选择按首张量数据类型映射 TilingKeyFLOAT160、FLOAT1、BF162与 foreach_abs_tiling_key.h 中FOREACH_ABS_TPL_SCH_MODE_FLOAT16/32/BFLOAT16的声明对应workspace 归零GetWorkspaceSizes(1)并将currentWorkspace[0] 0与 Kernel 侧“不需要 workspace”的实现一致空输入处理当totalElements 0时只启动 1 个核、perCoreElements 0保证空 Tensor 场景正确执行。Kernel 内核实现foreach_abs.cpp 是算子的 AICore 内核入口foreach_abs(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling)通过GET_TILING_DATA(tilingData, tiling)读取 Tiling 数据按TILING_KEY_IS(...)分派Key 1 走ForeachTrianglehalf, half, AbsAdapterhalf, 2, 1FLOAT16、Key 2 走ForeachTrianglefloat, float, AbsAdapterfloat, 2, 1FLOAT32、Key 4 走ForeachTrianglebfloat16_t, float, AbsAdapterfloat, 2, 1BFLOAT16且要求 AICore 版本 ≥ 220 且非 3003/3113 架构AbsAdapter将逐元素绝对值映射到底层AscendC::Abs内建指令ForeachTriangle是 foreach 系列共用的三角形切分执行模板位于 foreach_utils 共享目录负责把按张量累计偏移划分的数据段分发到多核执行。算子二进制配置各平台的op_host/config/arch/foreach_abs_binary.json如 ascend910b 配置为每种数据类型float16 / float32 / bfloat16各注册一个动态 shapeshape: [-2]、paramType: dynamic、格式 ND 的二进制形态与 TilingKey 的三档分派一一对应构成了“类型 → 二进制”的分发表。测试与验证仓库为 ForeachAbs 提供了 ST 与 UT 两级测试ST系统测试executor_aclnnForeachAbs.py 中aclnnForeachAbsExecutor的基准实现直接调用torch._foreach_abs(x_input)将 PyTorch 的 Foreach 接口结果作为期望值用于与 NPU 上的aclnnForeachAbs输出做数值比对配套的 atk_aclnnForeachAbs.json 定义测试用例参数。UT单元测试tests/ut/op_host 下包含 arch22 / arch35 两个平台的test_foreach_abs_tiling.cpp与test_foreach_abs_infershape.cpp分别校验 Tiling 数据计算与动态 shape 推导tests/ut/op_kernel 下提供test_foreach_abs.cpp及abs_data数据生成/比对脚本gen_data.py、compare_data.py用于内核级数值验证。这些测试从“接口语义对齐torch._foreach_abs”和“底层 Tiling/Kernel 正确性”两个维度共同保障了接口文档描述行为的可靠性。总结aclnnForeachAbs是 CANN ops-nn 中 Foreach 系列算子族的典型代表通过两段式接口对张量列表执行逐元素绝对值计算。本文完整覆盖了其功能公式、产品支持矩阵、两段接口的参数与错误码约束、确定性计算约束、可直接复制的完整调用示例并从算子注册定义、平台差异化配置、Tiling 切分策略、Kernel 内建指令分派与二进制配置等源码层面还原了实现机理。开发者可依据本文在支持的 NPU 平台上完成从接口调用到结果验证的完整闭环如需进一步理解两段式接口的通用约定、返回码语义或样例编译方法可继续阅读 两段式接口说明、aclnn 返回码说明 与 编译与运行样例。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考