CANN ops-math aclnnLogSpace 算子开发指南:两段式接口调用、参数约束与 Linspace + Pow 组合实现原理
2026/9/21 19:29:06 网站建设 项目流程

CANN ops-math aclnnLogSpace 算子开发指南:两段式接口调用、参数约束与 Linspace + Pow 组合实现原理

【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math

aclnnLogSpace 是 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^startbase^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的幂。以文档调用示例中的参数start=0.0, end=2.0, steps=5, base=10.0为例,指数序列为{0, 0.5, 1.0, 1.5, 2.0},输出序列为{1, 3.1623, 10, 31.6228, 100},结果与 PyTorch 的torch.logspace(0, 2, steps=5, base=10.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.md):

  • workspace 是指除输入/输出外,算子在 NPU 上完成计算所需的临时内存,workspaceSize表示其大小;
  • 必须按第一段接口计算出的workspaceSize申请 Device 侧内存后,再调用第二段接口;
  • 第二段接口aclnnLogSpace(...)不能重复调用,同一个 executor 重复执行会产生异常;
  • 接口声明位于 op_api/aclnn_logspace.h,声明前缀ACLNN_API,并注明@domain aclnnop_ops_train

aclnnLogSpaceGetWorkspaceSize 参数说明

第一段接口的入参与出参如下:

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
start (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.cpp(aclnnLogSpace_start_nullptraclnnLogSpace_end_nullptraclnnLogSpace_steps_less_than_0用例)断言覆盖。

另外,源码中存在一个特殊分支:当steps == 0时不会构造任何计算图,直接返回ACLNN_SUCCESSworkspaceSize = 0(op_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)。

调用示例

示例代码如下,仅供参考,具体编译和执行过程请参考编译与运行样例。示例构造start=0.0, end=2.0, steps=5, base=10.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::vector<int64_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::vector<int64_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::vector<int64_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::vector<int64_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 = CreateOutputAclTensor<float>(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_cast<uint64_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::vector<float> 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_cast<uint64_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 算子的计算图:

  1. 计算类型推导:默认以输出张量的数据类型result_dtype作为计算类型compute_dtype
  2. 特殊精度提升:当startend一个为 FP16、另一个为 BF16(混合半精度输入)时,将计算类型提升为 FP32,以避免 Linspace 与 Pow 多次 cast 带来的精度损失(源码注释明确说明了这一点);
  3. 指数序列生成:将start/end标量通过ConvertToTensor转成计算类型的一维张量,调用l0op::Linspace(start_tensor, end_tensor, steps, ...)在指数域生成线性等分序列(源码中steps == 0的提前返回即发生在该步之前);
  4. 底数幂计算:将base写入标量并转张量后,调用l0op::InplacePow(base_tensor, linspace_result, ...)计算base^指数,得到最终数值序列;
  5. 类型转换与写回:若计算类型与输出类型不一致,先经l0op::Cast转为输出类型,再通过l0op::ViewCopy(pow_result, result, ...)将结果写入用户传入的result张量——这也解释了为何result支持非连续 Tensor,ViewCopy 负责处理布局差异;
  6. workspace 汇总uniqueExecutor->GetWorkspaceSize()汇总整张计算图所需临时内存,返回给调用方。

作为佐证,仓库中的 UT 用例 tests/ut/op_api/test_aclnn_logspace.cpp 覆盖了输出类型为 FLOAT16 / FLOAT / BF16、steps=0/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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询