尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CANN ops-math 算子深度解析:Addcdiv 乘除加融合计算与 aclnnAddcdiv 两段式调用实战

CANN ops-math 算子深度解析:Addcdiv 乘除加融合计算与 aclnnAddcdiv 两段式调用实战 CANN ops-math 算子深度解析Addcdiv 乘除加融合计算与 aclnnAddcdiv 两段式调用实战【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math导读本文以 CANN ops-math 仓库中的 Addcdiv 算子为核心系统讲解其除-乘-加融合的数学语义、NPU 上的数据类型与格式约束、aclnnAddcdiv/aclnnInplaceAddcdiv两段式接口的完整参数与错误码规则并深入到 tiling 分片、AscendC 核函数与 aclnn 参数校验的源码实现最后给出可直接编译运行的调用示例与快速验证命令。读完本文你将掌握在 Atlas A2 训练系列等昇腾产品上正确调用 Addcdiv 算子、理解其内部分核与流水机制并能独立完成该类基础算子的 aclnn 调用开发。一、算子功能与计算公式Addcdiv 是 CANN ops-math 中一个典型的数学类基础算子用于执行乘除加组合操作将张量除法带缩放 张量加法合并为单个算子执行避免在图中展开为多个算子的调度开销。根据 experimental/math/addcdiv/README.md 的功能描述Addcdiv算子实现了向量 x1 除以向量 x2、乘以标量 value 后的结果再加上向量 input_data返回计算结果的功能。仓库正式目录 math/addcdiv/README.md 给出了统一的数学表达$$out self value \times (input_1 / input_2)$$逐元素展开即为$$out_i self_i value \times \frac{tensor1_i}{tensor2_i}$$即对每个元素位置执行先做tensor1 / tensor2元素级除法再乘以标量value最后与self即 input_data相加得到输出out。该语义与 PyTorch 等主流框架中的addcdiv接口保持一致是归一化、权重更新等训练场景中常见的除后缩放再累加模式的硬件级融合实现。二、产品支持情况根据 math/addcdiv/README.md 的产品支持表Addcdiv 算子支持情况如下产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品×Atlas 训练系列产品√其中 experimental/math/addcdiv/README.mdexperimental 贡献目录标注其算子包当前已验证 Atlas A2 训练系列产品。从算子定义源码 experimental/math/addcdiv/op_host/addcdiv_def.cpp 可以看到该算子在算子信息库中通过this-AICore().AddConfig(ascend910b).AddConfig(ascend310b)注册了 AICore 侧的硬件配置与文档中支持的 Atlas A2 训练/推理系列基于 Ascend 910B 系列芯片及 Atlas 推理系列产品形态对应。注两种文档口径并不冲突——experimental目录面向社区贡献算子其 README 记录的是贡献时的验证范围math正式目录记录的是产品矩阵层面的完整支持情况。具体以您所使用 CANN 版本的发布说明为准。三、算子原型与参数说明3.1 原型信息根据 experimental/math/addcdiv/README.md 的原型信息表项目说明算子类型OpTypeAddcdiv算子输入input_datatensorfloat32/float16/bfloat16NDx1tensorfloat32/float16/bfloat16NDx2tensorfloat32/float16/bfloat16NDvaluescalarfloat32/float16/bfloat16-算子输出ytensorfloat32/float16/bfloat16ND核函数名addcdiv3.2 参数明细math/addcdiv/README.md 给出了 aclnn 接口层完整的参数说明与公式一一对应参数名输入/输出/属性描述数据类型数据格式self输入待进行 addcdiv 计算的入参公式中的 selfFLOAT16、BFLOAT16、FLOATNDinput_1输入待进行 addcdiv 计算的入参公式中的 input_1FLOAT16、BFLOAT16、FLOATNDinput_2输入待进行 addcdiv 计算的入参公式中的 input_2FLOAT16、BFLOAT16、FLOATNDvalue输入待进行 addcdiv 计算的入参公式中的 valueFLOAT16、BFLOAT16、FLOATNDout输出待进行 addcdiv 计算的出参公式中的 outFLOAT16、BFLOAT16、FLOATND其中self、input_1、input_2为张量tensorvalue为标量scalar。约束注意点数据类型仅支持 float32、float16、bfloat16数据格式仅支持 NDAtlas 训练系列产品、Atlas 推理系列产品不支持 BFLOAT16。上述约束在算子定义源码中有对应实现addcdiv_def.cpp中每个输入/输出都通过.DataType({ge::DT_FLOAT16, ge::DT_FLOAT, ge::DT_BF16})声明支持类型并通过.Format({ge::FORMAT_ND, ...})与.UnknownShapeFormat({ge::FORMAT_ND, ...})声明 ND 格式含动态 shape 场景下的格式。value额外调用了.Scalar()将其声明为标量输入。四、调用方式aclnn 两段式接口Addcdiv 算子通过 aclnnAscend CANN Language Neural NetworkC 语言接口对外提供其详细 API 说明见 math/addcdiv/docs/aclnnAddcdivaclnnInplaceAddcdiv.md与 experimental/math/addcdiv/docs/aclnnAddcdivaclnnInplaceAddcdiv.md 内容一致。aclnnAddcdiv与aclnnInplaceAddcdiv实现相同的功能区别在于aclnnAddcdiv需新建一个输出张量对象存储计算结果out 与 self 是不同张量aclnnInplaceAddcdiv无需新建输出张量对象直接在输入张量 self 的内存中就地存储计算结果self 同时扮演输出 out 的角色。4.1 两段式接口协议每个算子遵循 docs/zh/context/two_phase_api.md 描述的两段式接口模式第一段GetWorkspaceSize先调用aclnnAddcdivGetWorkspaceSize或aclnnInplaceAddcdivGetWorkspaceSize完成入参校验并计算算子执行所需的 workspace 大小同时构造包含算子计算流程的aclOpExecutor执行器第二段执行再调用aclnnAddcdiv或aclnnInplaceAddcdiv传入第一段申请到的 workspace 内存、执行器与 Stream真正下发计算。四个接口的函数原型如下aclnnStatus aclnnAddcdivGetWorkspaceSize( const aclTensor* self, const aclTensor* tensor1, const aclTensor* tensor2, const aclScalar* value, const aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnAddcdiv( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)aclnnStatus aclnnInplaceAddcdivGetWorkspaceSize( const aclTensor* selfRef, const aclTensor* tensor1, const aclTensor* tensor2, const aclScalar* value, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnInplaceAddcdiv( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)4.2 aclnnAddcdivGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensorself输入公式中的 selfself 与 tensor1、tensor2 的数据类型满足互推导关系且推导后的类型需在支持的输入类型里三者 shape 满足broadcast 关系BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64ND-√tensor1输入公式中的输入 tensor1同上BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64ND-√tensor2输入公式中的输入 tensor2同上BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64ND-√value输入公式中的输入 value数据类型需可转换成 self 与 tensor1、tensor2 推导后的数据类型参见互转换关系BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64---out输出公式中的输出 out数据类型是 self 与 tensor1、tensor2 推导之后可转换的数据类型out 与三者 broadcast 之后的 tensor 的 shape 一致BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64ND-√workspaceSize输出返回需要在 Device 侧申请的 workspace 大小-----executor输出返回 op 执行器包含了算子计算流程-----接口层支持的输入类型在算子定义基础上扩展了 DOUBLE、INT64原因在于 aclnn 接口内部会先做**隐式数据类型提升promote**再进入内核计算——见下文源码解析。所有 tensor 参数均支持非连续non-contiguous输入接口内部会通过Contiguous与ViewCopy自动处理。4.3 aclnnInplaceAddcdivGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorselfRef输入/输出公式中的 self 与 outselfRef 与 tensor1、tensor2 的数据类型满足互推导关系且推导后的数据类型可以转换为 selfRef 的数据类型参见互转换关系selfRef 与 tensor1、tensor2 broadcast 之后的 tensor 的 shape 满足broadcast 关系BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64ND-√tensor1输入公式中的输入 tensor1与 selfRef 的数据类型满足互推导关系且推导后的类型需在支持的输入类型里tensor1 与 tensor2 的 shape 满足 broadcast 关系BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64ND-√tensor2输入公式中的输入 tensor2同上BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64ND-√value输入公式中的输入 value数据类型需可转换成 selfRef 与 tensor1、tensor2 推导后的数据类型BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64---workspaceSize输出返回需要在 Device 侧申请的 workspace 大小-----executor输出返回 op 执行器包含了算子计算流程-----4.4 第二段接口参数说明aclnnAddcdiv / aclnnInplaceAddcdiv 相同参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口aclnnAddcdivGetWorkspaceSize / aclnnInplaceAddcdivGetWorkspaceSize获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream五、返回值与错误码两个第一段接口GetWorkspaceSize都会完成入参校验返回aclnnStatus状态码具体规则参见 docs/zh/context/aclnn_return_code.md。出错场景与错误码如下aclnnAddcdiv 侧以 self 描述aclnnInplaceAddcdiv 侧以 selfRef 描述语义一致返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self(selfRef)、tensor1、tensor2、value、out 是空指针ACLNN_ERR_PARAM_INVALID161002self(selfRef) 和 tensor1、tensor2 的数据类型和数据格式不在支持的范围之内ACLNN_ERR_PARAM_INVALID161002self(selfRef) 和 tensor1、tensor2 无法做数据类型推导ACLNN_ERR_PARAM_INVALID161002推导出的数据类型无法转换为指定输出 out(selfRef) 的类型ACLNN_ERR_PARAM_INVALID161002self(selfRef) 或 tensor1、tensor2 的 shape 超过 8 维ACLNN_ERR_PARAM_INVALID161002self(selfRef) 和 tensor1、tensor2 的 shape 不满足 broadcast 推导关系ACLNN_ERR_PARAM_INVALID161002out(selfRef) 的 shape 与 self(selfRef) 和 tensor1、tensor2 做 broadcast 后的 shape 不一致第二段接口aclnnAddcdiv / aclnnInplaceAddcdiv同样返回aclnnStatus状态码参考同一份返回码文档。六、源码级实现解析6.1 aclnn 接口层参数校验与隐式类型提升experimental/math/addcdiv/op_api/aclnn_addcdiv.cpp 完整实现了两段式接口其第一段aclnnAddcdivGetWorkspaceSize的核心流程如下空指针检查对 self、tensor1、tensor2、value、out 逐一做OP_CHECK_NULL数据类型检查CheckDtypeValid依据当前 NPU 架构GetCurNpuArch()选择支持列表——非 910B 架构DAV_2201/DAV_3510支持DT_FLOAT/DT_FLOAT16/DT_INT64/DT_DOUBLE910B 架构额外增加DT_BF16与文档中Atlas 训练系列产品、Atlas 推理系列产品不支持 BFLOAT16的约束对应类型推导检查CheckPromoteType依次对 tensor1 与 tensor2、再与 self 做op::PromoteType推导推导结果必须能转换为输出 out 的类型对应错误码 161002 的无法做数据类型推导无法转换场景维度检查CheckMaxDimension使用MAX_SUPPORT_DIM 8限制所有输入输出不超过 8 维对应shape 超过 8 维错误shape 与 broadcast 检查CheckInAndOutShape先对 tensor1、tensor2 做 broadcast 推导再与 self 做 broadcast最后校验 out 的 shape 与 broadcast 结果一致对应两个 broadcast 相关错误空 tensor 短路任一输入为空 tensor 时直接返回workspaceSize 0隐式转换与计算图构建通过l0op::Contiguous将非连续输入转连续l0op::Cast将三个输入统一 cast 到 promote 后的类型对混合精度场景self 为 fp16/bf16 且 value 为 fp32 时在DAV_3510架构上保持 value 为 float32做特殊处理随后调用l0op::Addcdiv完成计算最后CastViewCopy将结果写回 out支持非连续输出workspace 计算通过uniqueExecutor-GetWorkspaceSize()返回所需 workspace 大小并将 executor 转移给调用方。值得注意的实现细节aclnnInplaceAddcdivGetWorkspaceSize在源码中就是直接复用aclnnAddcdivGetWorkspaceSize只是将输出参数 out 用 selfRef 自身填充从而天然实现就地写回的语义aclnnStatus aclnnInplaceAddcdivGetWorkspaceSize( const aclTensor* selfRef, const aclTensor* tensor1, const aclTensor* tensor2, const aclScalar* value, uint64_t* workspaceSize, aclOpExecutor** executor) { return aclnnAddcdivGetWorkspaceSize(selfRef, tensor1, tensor2, value, selfRef, workspaceSize, executor); }6.2 算子执行器AICore / AICPU 双路分派experimental/math/addcdiv/op_api/addcdiv.cpp 中l0op::Addcdiv负责实际的算子调度分派先通过BroadcastInferShape对 tensor1、tensor2、self 依次做 broadcast 推导得到最终输出 shape并用executor-AllocTensor在计算图中分配输出张量然后依据IsAiCoreSupport根据当前 NPU 架构与支持列表判断决定走AICore 路径AddcdivAiCore调用ADD_TO_LAUNCHER_LIST_AICORE将算子注册进 AICore 计算图还是AICPU 路径AddcdivAiCpu调用ADD_TO_LAUNCHER_LIST_AICPU走 AICPU 执行。这正是Atlas 训练系列产品不支持 BFLOAT16等平台差异在调度层的体现不支持 AICore 加速的类型会回退到 AICPU 实现保证接口可用。6.3 tiling 层基于 UB 容量与核数的分片计算experimental/math/addcdiv/op_host/addcdiv_tiling.cpp 中的AddcdivTilingFunc在 Host 侧完成切分策略计算获取平台信息通过platform_ascendc::PlatformAscendC拿到当前核的UBUnified Buffer容量ubSize与可用核数coreNum计算单元素字节数与总输入字节数inputLength inputNum * typeLength其中typeLength由ge::TypeUtils::GetDataTypeLength按数据类型取得TilingKey 分发根据value输入第 4 个输入的 shape 大小区分两种计算分支——valueSize 1时SetTilingKey(0)标量 value 场景对应核函数中的KernelAddcdiv否则SetTilingKey(1)value 作为 tensor 场景对应KernelAddcdivTensorUB 分块以 32B 为基本块BLOCK_SIZE 32考虑 DOUBLE BUFFER 双缓冲BUFFER_NUM 2并依据数据类型预留数据块数非 bf16 为 4bf16 为 6——因为 bf16 路径需要额外的 float 临时缓冲区计算出单次搬运的数据个数tileDataNum核数收敛若单核即可容纳全部数据则coreNum 1否则按每核至少 32B 数据的原则限制核数保证核间负载均衡大小核负载均衡将数据按 32B 对齐后的块数均分到各核得到大核/小核两组参数bigCoreDataNum/smallCoreDataNum并为每组计算搬运次数finalBigTileNum/finalSmallTileNum与尾块数据个数bigTailDataNum/smallTailDataNum最终写入AddcdivTilingData结构体并通过context-SetBlockDim(coreNum)设置实际分核数。最终通过IMPL_OP_OPTILING(Addcdiv).Tiling(AddcdivTilingFunc)将 tiling 函数注册到算子。tiling 数据结构定义在 experimental/math/addcdiv/op_kernel/addcdiv_tiling_data.hstruct AddcdivTilingData { uint32_t smallCoreDataNum; uint32_t bigCoreDataNum; uint32_t finalBigTileNum; uint32_t finalSmallTileNum; uint32_t tileDataNum; uint32_t smallTailDataNum; uint32_t bigTailDataNum; uint32_t tailBlockNum; };6.4 核函数层AscendC 向量流水计算experimental/math/addcdiv/op_kernel/addcdiv.cpp 定义了核函数入口addcdiv通过TILING_KEY_IS(0)/TILING_KEY_IS(1)选择标量或 tensor 两种内核类experimental/math/addcdiv/op_kernel/addcdiv.h 中KernelAddcdiv与KernelAddcdivTensor的实现展示了标准的 AscendC 向量算子流水Init根据GetBlockIdx()与 tiling 参数计算当前核的全局偏移globalBufferIndex大核在前、小核在后保证各核数据不重叠并初始化input_data/x1/x2/value/y五个 GlobalTensor 与 2 路BUFFER_NUM 2深度的 VECIN/VECOUT 队列Process循环执行CopyIn → Compute → CopyOut最后一块按tailDataNum处理尾块Compute 计算语义非 bf16 路径直接Div(x1, x1, x2)后Muls(x1, x1, valueGm.GetValue(0))乘标量再Add(y, x1, input_data)bf16 路径由于精度需要先Cast到 float 临时缓冲tmp1/tmp2完成Div → Muls → Add最后再Cast(..., RoundMode::CAST_RINT)转回 bf16 输出。tensor-value 分支KernelAddcdivTensor则多搬运一路 value 张量用Mul取代Muls完成元素级相乘。从源码结构可以看出Addcdiv 在 NPU 上是一个元素级element-wise向量算子数据按 32B 对齐、按核均分、UB 内分块双缓冲搬运计算本身不存在跨元素依赖因此可以充分发挥多核并行与流水线吞吐能力。七、运行验证编译、调用与示例7.1 快速调用build.shAddcdiv 算子属于experimental贡献目录下的算子其 README 推荐的验证方式是调用项目统一构建脚本完整命令说明见 docs/zh/invocation/quick_op_invocation.md。针对 experimental 目录下的算子执行方式为bash build.sh --experimental --run_example addcdiv eager cust --vendor_namecustom [--soc${soc_version}]其中--experimental表示执行用户保存在 experimental 贡献目录下的算子${op}算子名小写下划线形式此处为addcdiv${mode}调用方式目前支持eageraclnn 调用、graph图模式调用Addcdiv 当前提供的是 aclnn 接口${pkg_mode}包模式目前仅支持cust自定义算子包${vendor_name}可选与构建的自定义算子包设置一致默认custom${soc_version}可选表示 NPU 型号例如 Atlas A2 训练系列对应的 SoC 版本。7.2 调用样例算子仓库中提供了通过 aclnn 调用的完整可执行样例experimental/math/addcdiv/examples/test_aclnn_addcdiv.cppexperimental 目录experimental/math/addcdiv/examples/test_aclnn_Inplace_addcdiv.cppinplace 版本样例正式目录 math/addcdiv/examples/test_aclnn_addcdiv.cpp 与 math/addcdiv/examples/test_aclnn_Inplace_addcdiv.cpp编译与运行样式的完整流程请参考 docs/zh/context/compile_and_run_sample.md。下面给出aclnnAddcdiv的完整调用示例来源于 API 文档可直接套用#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_addcdiv.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 {4, 2}; std::vectorint64_t tensor1Shape {4, 2}; std::vectorint64_t tensor2Shape {4, 2}; std::vectorint64_t outShape {4, 2}; void* selfDeviceAddr nullptr; void* tensor1DeviceAddr nullptr; void* tensor2DeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* tensor1 nullptr; aclTensor* tensor2 nullptr; aclScalar* value nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {0, 1, 2, 3, 4, 5, 6, 7}; std::vectorfloat tensor1HostData {2, 2, 2, 2, 2, 2, 2, 2}; std::vectorfloat tensor2HostData {2, 2, 2, 2, 2, 2, 2, 2}; std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; float scalarValue 1.2f; // 创建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建tensor1 aclTensor ret CreateAclTensor(tensor1HostData, tensor1Shape, tensor1DeviceAddr, aclDataType::ACL_FLOAT, tensor1); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建tensor2 aclTensor ret CreateAclTensor(tensor2HostData, tensor2Shape, tensor2DeviceAddr, aclDataType::ACL_FLOAT, tensor2); CHECK_RET(ret ACL_SUCCESS, return ret); // 创建value aclScalar value aclCreateScalar(scalarValue, aclDataType::ACL_FLOAT); // 创建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; // 调用aclnnAddcdiv第一段接口 ret aclnnAddcdivGetWorkspaceSize(self, tensor1, tensor2, value, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnAddcdivGetWorkspaceSize 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); } // 调用aclnnAddcdiv第二段接口 ret aclnnAddcdiv(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnAddcdiv 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 resultData from device to host failed. ERROR: %d\n, ret); return ret); for (int64_t i 0; i size; i) { LOG_PRINT(resultData[%ld] is: %f\n, i, resultData[i]); } // 6. 释放aclTensor和aclScalar需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(tensor1); aclDestroyTensor(tensor2); aclDestroyTensor(out); aclDestroyScalar(value); // 7.释放device资源需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(tensor1DeviceAddr); aclrtFree(tensor2DeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }以上示例输入为self {0,1,2,3,4,5,6,7}、tensor1 {2,...}、tensor2 {2,...}、value 1.2根据公式out self value × (tensor1/tensor2)期望输出为每个元素self[i] 1.2 × 1 self[i] 1.2。aclnnInplaceAddcdiv的调用方式与之几乎一致核心区别在于不需要单独创建 out 张量第一段接口传参为aclnnInplaceAddcdivGetWorkspaceSize(self, tensor1, tensor2, value, workspaceSize, executor)结果直接写回 self 所在内存回拷结果时读取selfDeviceAddr即可释放资源时无需释放 out只需aclDestroyTensor(self)与aclrtFree(selfDeviceAddr)。完整 inplace 示例可参考 math/addcdiv/examples/test_aclnn_Inplace_addcdiv.cpp。7.3 约束说明小结算子级约束input_data、x1、x2、value、y 的数据类型仅支持 float32/float16/bfloat16数据格式仅支持 NDAtlas 训练系列产品、Atlas 推理系列产品不支持 BFLOAT16aclnn 接口级约束self/tensor1/tensor2/value/out 支持 BFLOAT16、FLOAT16、FLOAT、DOUBLE、INT64内部经类型推导与隐式转换后进入内核所有张量支持非连续存储维度上限 8 维tensor1、tensor2、self 三者需满足 broadcast 关系out 的 shape 需与 broadcast 结果一致确定性计算aclnnAddcdiv与aclnnInplaceAddcdiv默认确定性实现。八、总结Addcdiv 是 CANN ops-math 中实现out self value × (tensor1/tensor2)融合语义的基础数学算子从 experimental/math/addcdiv/README.md 与 math/addcdiv/README.md 的算子描述到 aclnnAddcdiv 接口文档 的两段式调用协议再到 aclnn_addcdiv.cpp 的参数校验、addcdiv_tiling.cpp 的 UB 分片与 addcdiv.h 的向量流水计算构成了一条完整可追溯的实现链路。对于想要在昇腾 NPU 上使用或贡献该算子的开发者建议按以下路径实践通过bash build.sh --experimental --run_example addcdiv eager cust --vendor_namecustom快速验证算子功能阅读 docs/zh/context/two_phase_api.md 掌握两段式接口通用范式再对照本文参数表编写自己的调用代码结合 docs/zh/context/broadcast_relationship.md、docs/zh/context/deduction_relationship.md 理解类型推导与 broadcast 规则避免踩到 161002 类参数错误。若您希望为该算子贡献完整的 AscendC 实现或补充图模式调用请参考仓库的贡献流程。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表