CANN ops-math aclnnLogSpace 算子开发指南:两段式接口调用、参数约束与 Linspace + Pow 组合实现原理
CANN ops-math aclnnLogSpace 算子开发指南两段式接口调用、参数约束与 Linspace Pow 组合实现原理【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-mathaclnnLogSpace 是 CANN ops-math 仓库中数学类基础算子 LogSpace 的单算子 API 封装用于在 NPU 上生成以base为底、在[base^start, base^end]区间内对数尺度均匀间隔的一维序列张量含端点语义对齐 PyTorch 的torch.logspace。本文以 math/logspace/docs/aclnnLogSpace.md 为主体结合 op_api/aclnn_logspace.cpp 源码与单测/ST 用例完整讲解产品支持情况、计算公式、两段式接口的每个参数与返回码、约束说明、可编译运行的 C 调用示例并深入剖析其基于 Linspace 与 Pow 算子组合的底层实现与精度处理策略。产品支持情况LogSpace 算子的 NPU 适配情况在文档中按硬件产品明确列出开发者在目标平台上调用前需先确认产品系列支持情况Ascend 950PR / Ascend 950DT支持Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品不支持Atlas 推理系列产品不支持Atlas 训练系列产品不支持功能说明与计算公式接口功能创建一个大小为steps的一维张量其值在base^start到base^end上以对数尺度均匀间隔包含端点以base为底。计算公式$$ \text{result} \left(\text{base}^\text{start},\ \text{base}^{\left(\text{start} \frac{\text{end} - \text{start}}{\text{steps} - 1}\right)},\ \ldots,\ \text{base}^{\left(\text{start} (\text{steps} - 2) * \frac{\text{end} - \text{start}}{\text{steps} - 1}\right)},\ \text{base}^\text{end}\right) $$即先在指数维度上做线性linspace等分再对每个指数计算base的幂。以文档调用示例中的参数start0.0, end2.0, steps5, base10.0为例指数序列为{0, 0.5, 1.0, 1.5, 2.0}输出序列为{1, 3.1623, 10, 31.6228, 100}结果与 PyTorch 的torch.logspace(0, 2, steps5, base10.0)一致——这一对照关系在仓库的 ST 测试 tests/st/aclnnLogSpace/executor_aclnnLogSpace.py 中直接用torch.logspace作为基准实现得到验证。函数原型与两段式接口与 CANN 单算子 API 的统一约定一致aclnnLogSpace 采用两段式接口模式必须先调用第一段接口aclnnLogSpaceGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器再调用第二段接口aclnnLogSpace执行计算。aclnnStatus aclnnLogSpaceGetWorkspaceSize( const aclScalar* start, const aclScalar* end, int64_t steps, double base, const aclTensor* result, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnLogSpace( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)关于两段式接口的使用要点参见 docs/zh/context/two_phase_api.mdworkspace 是指除输入/输出外算子在 NPU 上完成计算所需的临时内存workspaceSize表示其大小必须按第一段接口计算出的workspaceSize申请 Device 侧内存后再调用第二段接口第二段接口aclnnLogSpace(...)不能重复调用同一个 executor 重复执行会产生异常接口声明位于 op_api/aclnn_logspace.h声明前缀ACLNN_API并注明domain aclnnop_ops_train。aclnnLogSpaceGetWorkspaceSize 参数说明第一段接口的入参与出参如下参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensorstart (aclScalar*)输入LogSpace 的第一个输入对数序列的起始指数-FLOAT、FLOAT16、BFLOAT16、DOUBLE、UINT64、UINT32、UINT16、UINT8、INT64、INT32、INT16、INT8、BOOLND-√end (aclScalar*)输入LogSpace 的第二个输入对数序列的结束指数-FLOAT、FLOAT16、BFLOAT16、DOUBLE、UINT64、UINT32、UINT16、UINT8、INT64、INT32、INT16、INT8、BOOLND-√steps (int64_t)输入序列中的元素数量-int64_t---base (double)输入对数空间的底数-double---result (aclTensor*)输出LogSpace 的输出输出的对数间隔序列张量-FLOAT、FLOAT16、BFLOAT16ND1√workspaceSize (uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----executor (aclOpExecutor**)输出返回 op 执行器包含了算子计算流程-----要点解读start/end均为标量aclScalar*通过aclCreateScalar创建虽然标量本身支持整数、BOOL 等宽泛类型但参与幂运算前会按计算类型统一转换result是维度为 1一维的输出张量数据格式为 ND且支持非连续 Tensor见 docs/zh/context/non_contiguous_tensor.md输出数据类型仅支持 FLOAT、FLOAT16、BFLOAT16 三种这一限制在源码 op_api/aclnn_logspace.cpp 中以LOGSPACE_DTYPE_SUPPORT_LIST明确列出并在CheckDtypeValid中做校验。返回值与错误码aclnnStatus返回状态码具体参见 aclnn返回码。第一段接口完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 start、end、steps 或 out 是空指针ACLNN_ERR_PARAM_INVALID161002out 的数据类型不在支持的范围之内或 steps 小于 0补充说明从源码实现看空指针检查走OP_CHECK_NULL路径op_api/aclnn_logspace.cpp若 start/end/result 任一为空会返回内部错误码ACLNN_ERR_INNER_NULLPTRsteps 0则经CheckStepsValid返回ACLNN_ERR_PARAM_INVALID这两条行为均被单测 tests/ut/op_api/test_aclnn_logspace.cppaclnnLogSpace_start_nullptr、aclnnLogSpace_end_nullptr、aclnnLogSpace_steps_less_than_0用例断言覆盖。另外源码中存在一个特殊分支当steps 0时不会构造任何计算图直接返回ACLNN_SUCCESS且workspaceSize 0op_api/aclnn_logspace.cpp对应单测用例aclnnLogSpace_steps_0steps 1也是合法输入输出仅包含base^start一个元素。aclnnLogSpace 参数说明第二段接口的参数如下参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnLogSpaceGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream返回值同样为aclnnStatus参见 aclnn返回码。在源码层面第二段接口通过CommonOpExecutorRun(workspace, workspaceSize, executor, stream)统一驱动执行器完成 NPU 上的实际计算op_api/aclnn_logspace.cpp。约束说明确定性计算确定性计算aclnnLogSpace 默认为确定性实现即在相同输入与相同软硬件环境下多次执行产生完全一致的结果不会引入随机性相关背景可参见 docs/zh/context/determinism_compute.md。调用示例示例代码如下仅供参考具体编译和执行过程请参考编译与运行样例。示例构造start0.0, end2.0, steps5, base10.0输出一维 float 张量并打印 5 个结果。#include iostream #include vector #include math.h #include acl/acl.h #include aclnnop/aclnn_logspace.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 CreateOutputAclTensor( const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); 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); 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]; } *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 0, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2. 构造输入与输出需要根据API的接口自定义构造 void* outDeviceAddr nullptr; aclScalar* start nullptr; aclScalar* end nullptr; aclTensor* out nullptr; float startValue 0.0f; //起始指数 float endValue 2.0f; //结束指数 int64_t steps 5; //步数 float base 10.0; //底数 std::vectorint64_t shape {steps}; // 创建start aclScalar start aclCreateScalar(startValue, aclDataType::ACL_FLOAT); CHECK_RET(start ! nullptr, LOG_PRINT(create start scalar failed\n); return -1); // 创建end aclScalar end aclCreateScalar(endValue, aclDataType::ACL_FLOAT); CHECK_RET(end ! nullptr, LOG_PRINT(create end scalar failed\n); return -1); // 创建out aclTensor ret CreateOutputAclTensorfloat(shape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(create output tensor failed\n); return ret); // 3. 调用CANN算子库API uint64_t workspaceSize 0; aclOpExecutor* executor; // 调用aclnnLogSpace第一段接口 ret aclnnLogSpaceGetWorkspaceSize(start, end, steps, base, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLogSpaceGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* workspaceAddr nullptr; if (workspaceSize static_castuint64_t(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); } // 调用aclnnLogSpace第二段接口 ret aclnnLogSpace(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnLogSpace 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(shape); std::vectorfloat resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(float), 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和aclScalar aclDestroyScalar(start); aclDestroyScalar(end); aclDestroyTensor(out); // 7. 释放Device资源需要根据具体API的接口定义修改 aclrtFree(outDeviceAddr); if (workspaceSize static_castuint64_t(0)) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }上述代码的执行流程可归纳为ACL 环境初始化 → 构造start/end标量与out张量 → 调用第一段接口获取 workspaceSize 与 executor → 按需申请 workspace → 调用第二段接口执行计算 → 同步 Stream → 回拷结果 → 依次释放标量、张量与 Device 资源。底层实现原理Linspace Pow 组合与精度策略从 op_api/aclnn_logspace.cpp 的实现可以看出aclnnLogSpace 并非独立编写数值核函数而是在宿主侧将计算流程组合为若干 L0 算子的计算图计算类型推导默认以输出张量的数据类型result_dtype作为计算类型compute_dtype特殊精度提升当start与end一个为 FP16、另一个为 BF16混合半精度输入时将计算类型提升为 FP32以避免 Linspace 与 Pow 多次 cast 带来的精度损失源码注释明确说明了这一点指数序列生成将start/end标量通过ConvertToTensor转成计算类型的一维张量调用l0op::Linspace(start_tensor, end_tensor, steps, ...)在指数域生成线性等分序列源码中steps 0的提前返回即发生在该步之前底数幂计算将base写入标量并转张量后调用l0op::InplacePow(base_tensor, linspace_result, ...)计算base^指数得到最终数值序列类型转换与写回若计算类型与输出类型不一致先经l0op::Cast转为输出类型再通过l0op::ViewCopy(pow_result, result, ...)将结果写入用户传入的result张量——这也解释了为何result支持非连续 TensorViewCopy 负责处理布局差异workspace 汇总uniqueExecutor-GetWorkspaceSize()汇总整张计算图所需临时内存返回给调用方。作为佐证仓库中的 UT 用例 tests/ut/op_api/test_aclnn_logspace.cpp 覆盖了输出类型为 FLOAT16 / FLOAT / BF16、steps0/1、start/end 空指针、start end允许降序生成、steps 0报错等典型场景ST 用例则通过 atk_aclnnLogSpace.json 配置了覆盖 FP16/FP32/BF16 各种 start/end 类型组合与不同 steps/base 取值的百余条测试用例并以torch.logspace为基准做精度比对为算子功能正确性提供了双重保障。小结aclnnLogSpace 是 CANN ops-math 中生成对数尺度等间隔序列的标准算子接口。开发者使用时需要重点把握三点一是确认目标 NPU 产品的支持情况二是严格遵循先 GetWorkspaceSize、再按需申请 workspace、最后执行计算的两段式调用约定三是注意输出张量仅支持 FLOAT / FLOAT16 / BFLOAT16 且 steps 必须非负。理解其Linspace 生成指数序列 Pow 计算底数幂 必要时 Cast ViewCopy 写回的底层组合实现有助于在混合精度输入等场景下预判其精度表现。【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考