拓冰建站拓冰建站
首页 / 资讯中心 / 正文

CANN ops-nn 算子 aclnnForeachAddcmulList 详解:张量列表批量 AddcMul 混合运算的两段式调用实践

CANN ops-nn 算子 aclnnForeachAddcmulList 详解张量列表批量 AddcMul 混合运算的两段式调用实践【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nnaclnnForeachAddcmulList是 CANN ops-nn 神经网络算子库中针对**张量列表Tensor List**设计的批量逐元素混合运算接口用于对一组张量统一执行x1 scalars × x2 × x3计算。本文以 foreach_addcmul_list/docs/aclnnForeachAddcmulList.md 为主线结合仓库内算子定义、kernel 实现与单元测试源码完整讲解该算子的产品支持范围、计算语义、两段式接口原型、参数约束、返回码排查以及可编译运行的完整调用示例帮助开发者在 NPU 上正确、高效地完成多张量批量更新任务。产品支持情况aclnnForeachAddcmulList针对不同 NPU 产品线有明确的使能状态当前仓库的算子定义与配置见 foreach_addcmul_list/op_host/foreach_addcmul_list_def.cpp中注册了ascend950、ascend910_93、ascend910b、kirinx90、kirin9030等 AICore 配置与文档声明的支持矩阵保持一致产品类型支持情况Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品不支持需要说明的是不同产品线的算子原型存在细微差异从 foreach_addcmul_list_def.cpp 可以看到通用路径支持FLOAT16 / FLOAT / INT32 / BF16四种数据类型而 kirin 系列kirinx90、kirin9030通过GetKirinCoreConfig()仅使能FLOAT16 / FLOAT / INT32三种数据类型同时开启了动态编译、动态 Format、动态 Rank 与动态 Shape 支持。开发者在 Kirin 平台使用时需注意这一差异。功能说明与计算公式该接口的语义是先对张量列表 x2 与 x3 逐元素相乘再将结果乘以标量列表 scalars 中对应的标量最后与张量列表 x1 逐元素相加覆盖了神经网络训练中常见的“加积”混合更新AddcMul场景等价于 PyTorch 中torch._foreach_addcmul的逐列表批量语义。设三个输入张量列表分别为$$ x1 [{x1_0}, {x1_1}, \dots, {x1_{n-1}}], \quad x2 [{x2_0}, {x2_1}, \dots, {x2_{n-1}}], \quad x3 [{x3_0}, {x3_1}, \dots, {x3_{n-1}}] $$输出张量列表为$$ y [{y_0}, {y_1}, \dots, {y_{n-1}}] $$则列表中第i个输出张量的每个元素满足$$ {\rm y}i x1{i} {\rm scalars}i \times x2{i} \times x3_{i} \quad (i0,1,\dots,n-1) $$其中scalars是一个包含 n 个元素的标量张量第i个标量作用于第i组(x1_i, x2_i, x3_i)实现“每个张量配一个独立标量”的精细化批量控制这是本算子与共享单一标量的ForeachAddcmulScalar系列算子的关键区别。函数原型两段式异步接口aclnnForeachAddcmulList采用 CANN 算子库标准的两段式接口设计详见 docs/zh/context/two_phase_api.md必须先调用aclnnForeachAddcmulListGetWorkspaceSize完成入参校验、shape 推导并获取计算所需的 workspace 大小与执行器再调用aclnnForeachAddcmulList在指定 Stream 上异步执行计算。第一段接口获取 workspace 与执行器aclnnStatus aclnnForeachAddcmulListGetWorkspaceSize( const aclTensorList *x1, const aclTensorList *x2, const aclTensorList *x3, const aclTensor *scalars, const aclTensorList *out, uint64_t *workspaceSize, aclOpExecutor **executor)第二段接口执行计算aclnnStatus aclnnForeachAddcmulList( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)两段接口均返回aclnnStatus状态码参见 docs/zh/context/aclnn_return_code.md。其中第一段接口仅在 Host 侧完成校验与执行器构建真正的算子计算发生在第二段接口提交到 Stream 之后因此调用方必须在后续通过aclrtSynchronizeStream等机制同步等待任务完成。aclnnForeachAddcmulListGetWorkspaceSize 参数说明第一段接口共 7 个参数其中x1 / x2 / x3 / scalars / out构成算子的数据通路workspaceSize / executor为输出型参数。各参数详细约束如下参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensorx1aclTensorList*输入混合运算中加法的第一个输入张量列表对应公式中的x1支持空 Tensor该参数中所有 Tensor 的数据类型保持一致FLOAT32、FLOAT16、BFLOAT16、INT32ND0-8√x2aclTensorList*输入混合运算中乘法的第二个输入张量列表对应公式中的x2支持空 Tensor所有 Tensor 数据类型一致数据类型、数据格式和 shape 与入参x1一致FLOAT32、FLOAT16、BFLOAT16、INT32ND0-8√x3aclTensorList*输入混合运算中乘法的第三个输入张量列表对应公式中的x3支持空 Tensor所有 Tensor 数据类型一致数据类型、数据格式和 shape 与入参x1一致FLOAT32、FLOAT16、BFLOAT16、INT32ND0-8√scalarsaclTensor*输入混合运算中乘法的第一个输入张量对应公式中的scalars不支持空 Tensor元素个数与x1中 Tensor 的个数相等数据类型和数据格式与入参x1一致FLOAT32、FLOAT16、BFLOAT16、INT32ND0-8√outaclTensorList*输出混合运算的输出张量列表对应公式中的y支持空 Tensor所有 Tensor 数据类型一致数据类型和数据格式与入参x1一致shape size 大于等于入参x1的 shape sizeFLOAT32、FLOAT16、BFLOAT16、INT32ND0-8×workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----上述数据类型与格式约束在算子原型层有直接对应graph 侧原型 foreach_addcmul_list_proto.h 声明了x1/x2/x3为DYNAMIC_INPUT、scalars为固定INPUT、y为DYNAMIC_OUTPUT类型集合统一为{DT_FLOAT, DT_FLOAT16, DT_INT32, DT_BF16}host 侧 foreach_addcmul_list_def.cpp 进一步将四个数据通路的 ParamType 分别标记为DYNAMICx1/x2/x3/y与REQUIREDscalars并统一声明 ND 格式与AutoContiguous。这一点与文档中“scalars 不支持空 Tensor”的约束一致——scalars 是唯一必填的 REQUIRED 参数。关于“非连续 Tensor”x1/x2/x3/scalars 均支持非连续存储stride 非标准out 不支持相关底层机制可参考 docs/zh/context/non_contiguous_tensor.md。返回值与第一段接口错误码aclnnForeachAddcmulListGetWorkspaceSize完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 x1、x2、x3、scalars 或 out 是空指针ACLNN_ERR_PARAM_INVALID161002x1、x2、x3、scalars 和 out 的数据类型不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002x1、x2、x3、scalars、out 的数据类型不一致ACLNN_ERR_PARAM_INVALID161002x1、x2、x3、out 中存在空指针 TensorACLNN_ERR_INNER_TILING_ERROR561002x1、x2、x3、out 的 shape 不满足约束ACLNN_ERR_INNER_TILING_ERROR561002x1、x2、x3 或 out 中的 Tensor 的元素数据类型不一致ACLNN_ERR_INNER_TILING_ERROR561002x1、x2、x3 或 out 中的 Tensor 维度超过 8 维ACLNN_ERR_INNER_TILING_ERROR561002scalars 的元素个数与 x1 的 Tensor 数量不一致ACLNN_ERR_INNER_TILING_ERROR561002scalars 维度超过 8 维错误码语义统一遵循 docs/zh/context/aclnn_return_code.md161001为空指针类错误161002为参数非法类错误561002为内部 tiling 推导错误多由动态 shape/rank 在推导阶段不满足约束触发。aclnnForeachAddcmulList 参数说明第二段接口的 4 个参数全部为输入型用于驱动已构建好的执行器参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口aclnnForeachAddcmulListGetWorkspaceSize获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值仍为aclnnStatus参见 aclnn_return_code.md。注意workspaceSize可能为 0例如本算子在 vector 路径下无需额外 workspacekernel 侧直接以GM_ADDR userWS nullptr处理见下文 kernel 分析调用方只需在workspaceSize 0时才调用aclrtMalloc申请内存申请后通过aclrtMemcpy等接口按需使用执行完毕后用aclrtFree释放。约束说明确定性计算aclnnForeachAddcmulList默认采用确定性实现即相同输入与运行环境下多次执行结果可复现。这与 CANN 的确定性计算整体策略一致参见 docs/zh/context/determinism_compute.md适合对数值稳定性有严格要求的训练调试场景。底层实现剖析从 kernel 看计算流水为深入理解该算子的执行语义可对照 kernel 实现 op_kernel/foreach_addcmul_list.cpp。该文件通过TILING_KEYtiling 数据类型标记在运行时选择模板实例TILING_KEY 1half输入使用AddcMulListFloatAdapterTILING_KEY 2float输入使用AddcMulListFloatAdapterTILING_KEY 3int输入使用AddcMulListNormalAdapterTILING_KEY 4bfloat16_t输入内部以float计算使用AddcMulListFloatAdapter且仅在__CCE_AICORE__ 220的平台上编译。两种 Adapter 对应两条计算流水// 浮点路径Mul 之后用 Axpy 一步完成“乘标量并累加” Mul(tensor2Local, tensor2Local, tensor3Local, uValue); // t2 x2 * x3 PipeBarrierPIPE_V(); AxpyT, T(tensor1Local, tensor2Local, scalarVal, uValue); // dst x1 scalar * t2 // 整型路径Mul → Muls → Add 三步完成 Mul(tensor2Local, tensor2Local, tensor3Local, uValue); // t2 x2 * x3 PipeBarrierPIPE_V(); Muls(tensor2Local, tensor2Local, scalarVal, uValue); // t2 t2 * scalar PipeBarrierPIPE_V(); Add(dstLocal, tensor1Local, tensor2Local, uValue); // dst x1 t2其中浮点路径通过Axpy将“乘标量 累加”融合为单条向量指令同时用PipeBarrierPIPE_V()保证向量流水线数据依赖正确若输出与x1物理地址不同还会依据 32 字节对齐ADDCMUL_LIST_BYTE_PER_BLOCK 32补足块后执行DataCopy回写。BF16 路径以float中间精度计算可降低 BF16 低尾数精度带来的累积误差。在 Ascend 950 等 arch35 平台算子走 regbaseReg-VF实现入口见 op_kernel/arch35/foreach_addcmul_list.cpp按FOREACH_TILING_KEY_HALF/FLOAT/INT/BF16分发到ForeachAddcmulListRegbaseT, ScalarT, ForeachSoloTilingDataRegbase模板。不同平台的预编译二进制在 op_host/config 目录下按ascend910_93 / ascend910b / ascend950 / kirin9030 / kirinx90分目录维护每个 JSON 以op_type: ForeachAddcmulList声明一组按(dtype, formatND, dynamic shape)组合的bin_filename映射例如 ascend910b/foreach_addcmul_list_binary.json 中分别登记了 float16 / float32 / int32 / bfloat16 四种输入组合对应的算子二进制shape 均为-2表示支持动态 shape。调用示例完整可运行下面给出可直接参考的完整示例代码其完整版本位于仓库 examples/test_aclnn_foreach_addcmul_list.cpp编译与运行的整体流程请参考 docs/zh/context/compile_and_run_sample.md。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_foreach_addcmul_list.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的接口自定义构造 // 两个输入张量列表列表1含shape为{2,3}与{1,3}的两个Tensor std::vectorint64_t selfShape1 {2, 3}; std::vectorint64_t selfShape2 {1, 3}; // scalars含2个元素分别作用于列表中的2个Tensor std::vectorint64_t scalarShape {1, 2}; std::vectorint64_t otherShape1 {2, 3}; std::vectorint64_t otherShape2 {1, 3}; std::vectorint64_t anotherShape1 {2, 3}; std::vectorint64_t anotherShape2 {1, 3}; std::vectorint64_t outShape1 {2, 3}; std::vectorint64_t outShape2 {1, 3}; void* input1DeviceAddr nullptr; void* input2DeviceAddr nullptr; void* other1DeviceAddr nullptr; void* other2DeviceAddr nullptr; void* another1DeviceAddr nullptr; void* another2DeviceAddr nullptr; void* scalarDeviceAddr nullptr; void* out1DeviceAddr nullptr; void* out2DeviceAddr nullptr; aclTensor* input1 nullptr; aclTensor* input2 nullptr; aclTensor* other1 nullptr; aclTensor* other2 nullptr; aclTensor* another1 nullptr; aclTensor* another2 nullptr; aclTensor* scalar nullptr; aclTensor* out1 nullptr; aclTensor* out2 nullptr; std::vectorfloat input1HostData {1, 2, 3, 4, 5, 6}; std::vectorfloat input2HostData {7, 8, 9}; std::vectorfloat other1HostData {4, 3, 8, 9, 3, 5}; std::vectorfloat other2HostData {5, 6, 7}; std::vectorfloat another1HostData {1, 2, 3, 4, 5, 6}; std::vectorfloat another2HostData {7, 8, 9}; std::vectorfloat scalarHostData {1, 2}; 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); // 创建scalar aclTensor ret CreateAclTensor(scalarHostData, scalarShape, scalarDeviceAddr, aclDataType::ACL_FLOAT, scalar); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建other1 aclTensor ret CreateAclTensor(other1HostData, otherShape1, other1DeviceAddr, aclDataType::ACL_FLOAT, other1); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建other2 aclTensor ret CreateAclTensor(other2HostData, otherShape2, other2DeviceAddr, aclDataType::ACL_FLOAT, other2); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建another1 aclTensor ret CreateAclTensor(another1HostData, anotherShape1, another1DeviceAddr, aclDataType::ACL_FLOAT, another1); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建another2 aclTensor ret CreateAclTensor(another2HostData, anotherShape2, another2DeviceAddr, aclDataType::ACL_FLOAT, another2); 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); // 用aclCreateTensorList将多个aclTensor封装为张量列表 std::vectoraclTensor* tempInput1{input1, input2}; aclTensorList* tensorListInput1 aclCreateTensorList(tempInput1.data(), tempInput1.size()); std::vectoraclTensor* tempInput2{other1, other2}; aclTensorList* tensorListInput2 aclCreateTensorList(tempInput2.data(), tempInput2.size()); std::vectoraclTensor* tempAnother{another1, another2}; aclTensorList* tensorListAnother aclCreateTensorList(tempAnother.data(), tempAnother.size()); std::vectoraclTensor* tempOutput{out1, out2}; aclTensorList* tensorListOutput aclCreateTensorList(tempOutput.data(), tempOutput.size()); // 3. 调用CANN算子库API两段式调用 uint64_t workspaceSize 0; aclOpExecutor* executor; // 第一段接口校验入参并获取workspace大小与执行器 ret aclnnForeachAddcmulListGetWorkspaceSize(tensorListInput1, tensorListInput2, tensorListAnother, scalar, tensorListOutput, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnForeachAddcmulListGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存可能为0此时无需申请 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); } // 第二段接口在指定stream上异步执行计算 ret aclnnForeachAddcmulList(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnForeachAddcmulList 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(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与aclTensorList aclDestroyTensorList(tensorListInput1); aclDestroyTensorList(tensorListInput2); aclDestroyTensorList(tensorListAnother); aclDestroyTensor(scalar); aclDestroyTensorList(tensorListOutput); // 7. 释放device资源 aclrtFree(input1DeviceAddr); aclrtFree(input2DeviceAddr); aclrtFree(other1DeviceAddr); aclrtFree(other2DeviceAddr); aclrtFree(another1DeviceAddr); aclrtFree(another2DeviceAddr); aclrtFree(scalarDeviceAddr); aclrtFree(out1DeviceAddr); aclrtFree(out2DeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }示例结果推演以列表第 1 个 Tensorshape 为{2,3}为例x1 [1,2,3,4,5,6]x2 [4,3,8,9,3,5]x3 [1,2,3,4,5,6]对应标量scalars[0] 1按公式y_i x1_i scalars × x2_i × x3_i计算y[0] 1 1×4×1 5y[1] 2 1×3×2 8y[2] 3 1×8×3 27y[3] 4 1×9×4 40y[4] 5 1×3×5 20y[5] 6 1×5×6 36列表第 2 个 Tensorshape 为{1,3}对应标量scalars[1] 2x1 [7,8,9]x2 [5,6,7]x3 [7,8,9]y[0] 7 2×5×7 77y[1] 8 2×6×8 104y[2] 9 2×7×9 135运行后out1 result应依次打印 5、8、27、40、20、36out2 result应打印 77、104、135读者可据此自行验证。单元测试验证仓库在 foreach/foreach_addcmul_list/tests/ut/op_kernel/test_foreach_addcmul_list.cpp 提供了基于 gtest 与 ICPU 仿真tikicpulib.h的 kernel 级单元测试覆盖test_case_float_1float32shape 列表{{128, 64}, {16, 128}, {32, 128}}tiling key 2test_case_float16_2float16同上 shapetiling key 1test_case_int32_3int32同上 shapetiling key 3test_case_bloat16_4bfloat16同上 shapetiling key 4test_case_float_5_for_not_alignedfloat32 且使用{{7}, {9}, {17}}这类非对齐shape专门验证 DataCopy 按 32 字节块补齐ADDCMUL_LIST_BYTE_PER_BLOCK分支的正确性。每个用例通过gen_data.py生成输入数据、compare_data.py与 golden 数据比对来断言结果同时覆盖了blockDim 4的多核切分路径。这套测试同时佐证了公式语义、四种数据类型支持以及非对齐 shape 的处理逻辑。常见问题与排错建议报错 161001ACLNN_ERR_PARAM_NULLPTR检查 x1/x2/x3/scalars/out 是否均为有效指针尤其是scalars在本算子中为 REQUIRED 参数不可传空。报错 161002ACLNN_ERR_PARAM_INVALID核对所有入参数据类型是否落在FLOAT32 / FLOAT16 / BFLOAT16 / INT32范围内并保证 x1/x2/x3/scalars/out 数据类型完全一致同时确认张量列表中不存在空指针 Tensor。报错 561002ACLNN_ERR_INNER_TILING_ERROR重点检查 shape 约束——x1/x2/x3 的 shape 需保持一致x2/x3 与 x1 一致out 的 shape size 需大于等于 x1所有 Tensor 维度不得超过 8 维scalars的元素个数必须与 x1 中 Tensor 个数相等且 scalars 维度不超过 8 维。结果为 0 或未更新确认第二段接口提交后调用了aclrtSynchronizeStream同步且 workspace 的申请/释放严格以第一段接口返回的workspaceSize 0为条件。平台差异Kirin 系列kirinx90/kirin9030不支持 BF16 输入跨平台迁移时需按目标平台调整数据类型。综上aclnnForeachAddcmulList为 NPU 上的多张量批量 AddcMul 场景提供了“每张量独立标量”的细粒度控制能力通过两段式接口、动态 shape 与确定性计算设计兼顾了灵活性与可复现性。开发者在实际使用中可结合 两段式接口说明、返回码说明 与本仓库的 示例代码 快速落地。【免费下载链接】ops-nn本项目是CANN提供的神经网络类计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-nn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门