CANN ops-math 正切算子 aclnnTan / aclnnInplaceTan 两段式接口详解与调用指南
2026/9/21 14:09:24 网站建设 项目流程

CANN ops-math 正切算子 aclnnTan / aclnnInplaceTan 两段式接口详解与调用指南

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

导读

aclnnTanaclnnInplaceTan是 CANN ops-math 数学算子库中用于逐元素计算输入张量正切值的两对单算子(Single Op)接口,分别对应"新建输出张量"与"原位改写输入内存"两种使用场景。本文以 math/tan/docs/aclnnTan&aclnnInplaceTan.md 为骨架,结合 math/tan 目录下的 op_api、op_host、op_kernel、op_kernel_aicpu 源码与测试用例,完整讲解算子功能、两段式接口的函数原型与全部参数语义、错误码校验规则、AI Core 与 AICPU 双路径实现原理,并给出可直接编译运行的两个 C++ 调用示例。读完本文,你将能够在昇腾 NPU 上正确使用这两套接口完成张量正切计算,并理解其从 host 侧参数校验、tiling 切核到 kernel 分发的完整执行链路。

一、产品支持情况

aclnnTanaclnnInplaceTan在同一份接口文档中发布,二者的产品支持情况完全一致,如下表所示:

产品系列是否支持
Ascend 950PR / Ascend 950DT支持
Atlas A3 训练系列产品 / Atlas A3 推理系列产品支持
Atlas A2 训练系列产品 / Atlas A2 推理系列产品支持
Atlas 200I/500 A2 推理产品不支持
Atlas 推理系列产品支持
Atlas 训练系列产品支持

注意:aclnnTan/aclnnInplaceTan的接口文档(math/tan/docs/aclnnTan&aclnnInplaceTan.md)中标注"Atlas 200I/500 A2 推理产品:不支持",而算子级 README(math/tan/README.md)中该产品标注为支持,两者差异来自发布口径的不同,实际使用时请以目标环境的产品规格为准。

二、功能说明

2.1 算子功能与计算公式

该算子对输入张量self中的每个元素计算正切值,结果逐元素写入输出张量out,输出张量的 shape 与输入张量保持一致。计算公式为:

$$ out[i] = \tan(self[i]) $$

其中self[i]表示输入张量self的第i个元素,out[i]表示输出张量out的第i个元素。

2.2 计算示例

以一个 2×2 的整型输入为例:

输入self: tensor([[1, 2], [3, 4]]) 输出out: tensor([[ 1.5574, -2.1850], [-0.1425, 1.1578]])

tan(1) ≈ 1.5574tan(2) ≈ -2.1850tan(3) ≈ -0.1425tan(4) ≈ 1.1578。需要注意,tan是以 π 为周期的周期函数,在 π/2 附近存在间断点,输入元素越靠近π/2 + kπ,结果的绝对值越大、精度越敏感。

2.3 平台相关的实现差异

从源码看,Tan 算子在 math/tan/op_api/tan.cpp 中按平台与数据类型分流到不同计算路径:

  • AI Core 路径IsAiCoreSupport()根据当前 SoC 版本选择支持列表。默认平台(含 Atlas 训练/推理系列等)支持FLOAT16、FLOAT、INT32;Ascend 910B / Ascend 910_93 / 寄存器化(RegBase)平台额外支持BFLOAT16。AI Core 路径通过ADD_TO_LAUNCHER_LIST_AICORE下发 AscendC kernel(math/tan/op_kernel/tan_apt.cpp)。
  • AICPU 路径DOUBLE、COMPLEX64、COMPLEX128等 AI Core 不支持的浮点/复数类型走 AICPU 计算(math/tan/op_kernel_aicpu/tan_aicpu.cpp),使用 Eigen 的Eigen::numext::tan<T>实现,并在元素数超过阈值时通过ParallelFor多核并行。

此外,从 math/tan/op_host/tan_def.cpp 的算子定义看,AI Core 侧原生算子注册的数据类型为FLOAT16、FLOAT、BF16、INT32,且声明了动态 shape、动态 rank 支持以及PrecisionReduceFlag(true)精度优化开关。

三、两段式接口与函数原型

3.1 两段式调用模型

与 CANN 其他单算子接口一致,aclnnTanaclnnInplaceTan采用两段式接口(详细规范参见 两段式接口说明):

  1. 第一段(GetWorkspaceSize):调用aclnnTanGetWorkspaceSizeaclnnInplaceTanGetWorkspaceSize,完成入参校验、构图与 workspace 大小计算,返回计算所需的workspaceSize(Device 侧临时内存大小)以及封装了算子计算流程的executor(aclOpExecutor 执行器)。
  2. 第二段(执行):根据第一段返回的workspaceSize在 Device 侧申请内存后,调用aclnnTanaclnnInplaceTan,传入 workspace、executor 与 stream 真正执行计算。

3.2 aclnnTan 与 aclnnInplaceTan 的差异

两对接口实现完全相同的数学功能,区别仅在于结果的存放方式,请根据实际场景选择:

  • aclnnTan:需要额外新建一个输出张量对象out来存储计算结果,输入self保持不变。
  • aclnnInplaceTan:无需新建输出张量对象,直接在输入张量selfRef的 Device 内存上原地写入计算结果,可节省一份输出内存。由于是原地改写,调用前需确认输入数据不再被其他逻辑使用。

3.3 四个函数原型

aclnnStatus aclnnTanGetWorkspaceSize( const aclTensor* self, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)
aclnnStatus aclnnTan( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)
aclnnStatus aclnnInplaceTanGetWorkspaceSize( const aclTensor* selfRef, uint64_t* workspaceSize, aclOpExecutor** executor)
aclnnStatus aclnnInplaceTan( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)

四、第一段接口参数详解

4.1 aclnnTanGetWorkspaceSize

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
self(aclTensor*)输入公式中的输入 selfshape 需要与 out 一致,和 out 的数据类型满足互推导关系FLOAT、FLOAT16、DOUBLE、INT8、INT16、INT32、INT64、UINT8、BOOL、COMPLEX64、COMPLEX128、BFLOAT16ND不大于 8
out(aclTensor*)输出公式中的 outshape 需要与 self 一致,和 self 的数据类型满足互推导关系FLOAT、FLOAT16、DOUBLE、COMPLEX64、COMPLEX128、BFLOAT16ND-
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含了算子计算流程-----

平台附加限制:Atlas 训练系列产品、Atlas 推理系列产品上,不支持 BFLOAT16、COMPLEX32、COMPLEX64。

关于参数语义,可以结合 math/tan/op_api/aclnn_tan.cpp 的CheckParamsCompute实现进一步理解:

  • 类型扩展与互推导self侧允许INT8/INT16/INT32/INT64/UINT8/BOOL等整型与布尔类型,其原因是这些类型无法直接参与浮点三角运算。源码中NEED_CAST_TO_FLOAT_DTYPE_LIST将这些类型统一视为可"cast 到 FLOAT",在Compute阶段先通过l0op::Cast转成DT_FLOAT计算,最后再Castout的目标数据类型。out侧仅允许浮点/复数类型,且必须能与self满足互推导关系。
  • shape 与维度约束CheckShape中通过OP_CHECK_MAX_DIM(self, MAX_SUPPORT_DIMS_NUMS)校验维度不大于 8,并通过OP_CHECK_SHAPE_NOT_EQUAL校验selfoutshape 完全一致。
  • 格式约束:算子仅支持 ND 格式。CheckParams中对非 ND 存储格式打印警告日志,且在寄存器化(RegBase)架构上直接拒绝私有格式(Private format)输入。
  • 非连续 Tensor:接口标注支持非连续输入,Compute中首先调用l0op::Contiguous将非连续输入规整为连续张量,再执行 Tan,最后用l0op::ViewCopy将结果拷贝回用户提供的out
  • 空张量短路:当selfout为空张量(如 shape 为{0})时,aclnnTanGetWorkspaceSize直接返回workspaceSize = 0与合法 executor,不构造计算图(math/tan/op_api/aclnn_tan.cpp 中self->IsEmpty() || out->IsEmpty()分支),对应 UT 用例case_empty_tensor(tests/ut/op_host/op_api/test_aclnn_tan.cpp)。

4.2 aclnnInplaceTanGetWorkspaceSize

参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续Tensor
selfRef(aclTensor*)输入/输出公式中的 self-FLOAT、FLOAT16、DOUBLE、COMPLEX64、COMPLEX128、BFLOAT16ND不大于 8
workspaceSize(uint64_t*)输出返回需要在 Device 侧申请的 workspace 大小-----
executor(aclOpExecutor**)输出返回 op 执行器,包含了算子计算流程-----

平台附加限制:Atlas 训练系列产品、Atlas 推理系列产品上,不支持 BFLOAT16、COMPLEX32、COMPLEX64。

从源码看(math/tan/op_api/aclnn_tan.cpp 中aclnnInplaceTanGetWorkspaceSize),原位版本没有独立的out参数:CheckParamsInplace(selfRef, selfRef)将输入同时作为校验目标,随后auto out = const_cast<aclTensor*>(selfRef)让计算结果直接复用输入张量对象,即计算与输出共用同一块 Device 内存。因此selfRef被标记为"输入/输出",其数据类型列表与aclnnTanout一致(仅限浮点/复数),整型输入需要用户先自行转换。

五、第二段接口参数详解

aclnnTanaclnnInplaceTan第二段接口的参数完全一致:

参数名输入/输出描述
workspace输入在 Device 侧申请的 workspace 内存地址
workspaceSize输入在 Device 侧申请的 workspace 大小,由第一段接口(aclnnTanGetWorkspaceSize / aclnnInplaceTanGetWorkspaceSize)获取
executor输入op 执行器,包含了算子计算流程,由第一段接口返回
stream输入指定执行任务的 Stream

第二段接口的实现非常薄:从源码看,aclnnTanaclnnInplaceTan主体只是调用CommonOpExecutorRun(workspace, workspaceSize, executor, stream),由统一的 executor 运行时负责把第一段构图时记录的算子计算流程真正下发到指定 Stream(math/tan/op_api/aclnn_tan.cpp)。workspaceSize为 0 时传入nullptr作为 workspace 是合法的(示例代码中仅在workspaceSize > 0时才申请内存)。

六、返回值与错误码

两个第一段接口的返回值类型均为aclnnStatus,完整状态码定义参见 aclnn返回码。

第一段接口会完成全部入参校验,校验失败的典型场景与返回码如下:

aclnnTanGetWorkspaceSize:

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 self 或 out 是空指针时
ACLNN_ERR_PARAM_INVALID161002以下任一场景:
1. self 的数据类型不在支持的范围之内;
2. self 不能转为 out 的数据类型,或 self 和 out 的数据类型不满足互推导关系;
3. self 和 out 的维度大于 8;
4. self 和 out 的 shape 不一致

aclnnInplaceTanGetWorkspaceSize:

返回值错误码描述
ACLNN_ERR_PARAM_NULLPTR161001传入的 selfRef 是空指针时
ACLNN_ERR_PARAM_INVALID161002selfRef 的数据类型不在支持的范围之内

这些错误码与 math/tan/op_api/aclnn_tan.cpp 中的校验流程一一对应:CheckNotNull失败返回ACLNN_ERR_PARAM_NULLPTRCheckDtypeValidCheckShapeCheckDtypeCanCastCheckFormat任一失败均返回ACLNN_ERR_PARAM_INVALID。仓库 UT(tests/ut/op_host/op_api/test_aclnn_tan.cpp)中有case_nullptr_input/case_nullptr_output用例直接断言空指针入参返回ACLNN_ERR_PARAM_NULLPTR,可作为行为参考。

七、约束说明

  • 确定性计算aclnnTanaclnnInplaceTan默认即为确定性实现,多次调用在相同输入下结果可复现。
  • 数值范围(Atlas A3 训练/推理系列产品):FLOAT、BFLOAT16、FLOAT16、INT32、INT64 数据类型的输入数据范围为[-65504, 65504]时满足精度要求;超过该数值范围无法保证精度,请使用 CPU 进行计算。
  • 执行超时风险(Atlas A3 训练/推理系列产品):如果计算量过大可能导致算子执行超时,报错类型为 aicore error,errorStrtimeout or trap error。典型触发场景为:最后 2 轴合轴小于 16,而前面的轴合轴超大。此时应拆分输入张量或调整数据布局后再执行。
  • NaN/Inf 行为(Ascend 950PR/950DT):从 math/tan/README.md 的约束说明看,Ascend 950 系列 AI Core 实现中,输入为 NaN、Inf 或绝对值大于等于 1e7 时,输出为 NaN。

八、底层实现链路(源码级解读)

8.1 host 侧:参数校验 → 构图 → workspace 计算

第一段接口的执行链路集中在 math/tan/op_api/aclnn_tan.cpp:

  1. CREATE_EXECUTOR()创建唯一的 aclOpExecutor;
  2. CheckParams(self, out)依次完成空指针、数据类型、shape、类型互推导、私有格式校验(详见第六节);
  3. 空张量短路返回workspaceSize = 0
  4. Compute(self, out, executor)l0op低级算子原语构图:Contiguous(规整非连续输入)→ 若为整型/布尔则Cast到 FLOAT →l0op::Tan(真正计算)→Castout数据类型 →ViewCopy写回输出;
  5. 从 executor 获取构图后的总workspaceSize返回给调用方。

构图的核心计算原语l0op::Tan定义在 math/tan/op_api/tan.cpp:先按输入 shape/数据类型分配输出张量y,再由IsAiCoreSupport(x)决定走 AI Core(ADD_TO_LAUNCHER_LIST_AICORE)还是 AICPU(ADD_TO_LAUNCHER_LIST_AICPU)路径。

8.2 设备侧:tiling 切核与 kernel 分发

  • tiling:Ascend 950(arch35)架构的 tiling 实现在 math/tan/op_host/arch35/tan_tiling_arch35.cpp。TanTilingFunc动态获取 AIV 核数,以THREAD_NUM(512) × MIN_ELEMENTS_PER_THREAD(4)为每核元素粒度估算所需核数并取 min 到可用核数,随后把totalElements / perCoreElements / needCoreNum / dataType写入TanTilingData,并依据数据类型(FLOAT16/FLOAT/BF16/INT32)设置TAN_TPL_SCH_MODE_0~3对应的 tiling key 与 blockDim,同时将局部内存设置为 128KB、workspace 设置为 0。
  • kernel:AI Core kernel 入口 math/tan/op_kernel/tan_apt.cpp 通过编译期if constexpr按 tiling key 把数据类型映射到halffloatbfloat16_tint32_t,统一调用 arch35 的NsTan::Process<T>(x, y, &tilingData)完成逐元素正切计算(math/tan/op_kernel/arch35/tan_simt.h)。
  • AICPU kernel:对 DOUBLE/COMPLEX64/COMPLEX128 等类型,math/tan/op_kernel_aicpu/tan_aicpu.cpp 先校验输入输出数据类型、数据大小、维度完全一致,再按元素总数决定是否多核并行,使用 Eigen 的Eigen::numext::tan<T>逐元素计算。

8.3 图模式(GE)入口

除 aclnn 单算子接口外,Tan 算子还支持 GE 图模式:通过算子 IR math/tan/op_graph/tan_proto.h 构图,shape 推导与数据类型推导实现在 math/tan/op_graph/tan_graph_infer.cpp(输出数据类型继承输入),示例见 examples/test_geir_tan.cpp。框架侧另有 TensorFlow 插件入口 math/tan/framework/tan_tf_plugin.cpp。

九、调用示例(aclnnTan)

以下代码摘自 math/tan/docs/aclnnTan&aclnnInplaceTan.md 的调用示例,完整展示了从资源初始化、构造 aclTensor、两段式调用、同步取回结果到资源释放的全流程。编译与运行方式请参考 编译与运行样例。

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_tan.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 shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } 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::vector<T>& hostData, const std::vector<int64_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::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]; } // 调用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根据自己的需要处理 CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2.构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> selfShape = {4, 2}; std::vector<int64_t> outShape = {4, 2}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0, 1, 2, 3, 4, 5, 6, 7}; std::vector<float> outHostData = {0, 0, 0, 0, 0, 0, 0, 0}; // 创建self aclTensor ret = CreateAclTensor(selfHostData, selfShape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建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; // 调用aclnnTan第一段接口 ret = aclnnTanGetWorkspaceSize(self, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTanGetWorkspaceSize 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;); } // 调用aclnnTan第二段接口 ret = aclnnTan(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnTan 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::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,需要根据具体API的接口定义修改 aclDestroyTensor(self); aclDestroyTensor(out); // 7.释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

对示例代码的几个要点说明:

  • 对 shape 为{4, 2}、8 个float元素(0~7)的输入,输出应为tan(0)~tan(7)的近似值,其中tan(0)=0,可用来快速核对结果是否合理;
  • 示例采用裸指针 + 手动释放的写法。仓库 examples/test_aclnn_tan.cpp 中另提供了一份使用std::unique_ptr(自定义 deleter)管理aclrtStream、Device 内存与aclTensor生命周期的 RAII 风格版本,异常安全更好,推荐在实际工程中参考;
  • aclnnTanGetWorkspaceSize返回ACLNN_ERR_PARAM_INVALID(161002)时,可按第六节的错误场景逐一排查 dtype、shape 与格式问题。

十、调用示例(aclnnInplaceTan)

原位版本的调用流程与aclnnTan基本一致,差异在于:不需要构造out张量selfRef同时充当输入与输出,因此最后直接从selfRef的 Device 内存中拷贝结果。

#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_tan.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 shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } 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::vector<T>& hostData, const std::vector<int64_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::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]; } // 调用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根据自己的需要处理 CHECK_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2.构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> selfRefShape = {4, 2}; void* selfRefDeviceAddr = nullptr; aclTensor* selfRef = nullptr; std::vector<float> selfRefHostData = {0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}; // 创建self aclTensor ret = CreateAclTensor(selfRefHostData, selfRefShape, &selfRefDeviceAddr, aclDataType::ACL_FLOAT, &selfRef); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3.调用CANN算子库API,需要修改为具体的API uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnInplaceTan第一段接口 ret = aclnnInplaceTanGetWorkspaceSize(selfRef, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceTanGetWorkspaceSize 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;); } // 调用aclnnInplaceTan第二段接口 ret = aclnnInplaceTan(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceTan 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(selfRefShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), selfRefDeviceAddr, 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,需要根据具体API的接口定义修改 aclDestroyTensor(selfRef); // 7.释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfRefDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }

使用aclnnInplaceTan时请特别注意:由于结果是原地写入,selfRef的原数据会被覆盖;如果后续还需要原始输入,请先做数据备份,或者改用非原位的aclnnTan

十一、测试与验证

仓库为 Tan 算子提供了从单测到端到端的完整验证手段,可用于验证本文描述的行为:

  • op_api UT:tests/ut/op_host/op_api/test_aclnn_tan.cpp 覆盖了aclnnTan的 FP32 精度测试(shape{1,16,1,1}、输入范围 [-2,2]、精度容差 1e-4)、空张量用例以及空指针入参返回ACLNN_ERR_PARAM_NULLPTR的错误路径。
  • AICPU UT:tests/ut/op_kernel_aicpu/test_tan.cpp 覆盖 DOUBLE 等 AICPU 路径数据类型的计算。
  • ST 用例:tests/st/aclnnTan/atk_aclnnTan.json(ATK 算子测试描述)与 tests/st/arch35/ttk_kernel_tan_st.csv(arch35 内核场景)用于端到端场景验证。
  • 示例程序:examples/test_aclnn_tan.cpp(aclnn 方式)与 examples/test_geir_tan.cpp(GE 图模式)提供可直接编译运行的参考实现。

十二、总结

aclnnTanaclnnInplaceTan是 CANN ops-math 中实现逐元素正切计算的标准单算子接口,二者功能等价、仅在输出内存的分配方式上不同:前者需要显式创建输出张量,后者在输入内存上原地计算、可节省一份显存。两套接口都遵循"GetWorkspaceSize + 执行"的两段式调用模型,第一段完成参数校验与构图并返回 workspace 大小,第二段在指定 Stream 上执行。底层实现上,算子根据平台与数据类型在 AI Core(AscendC kernel,按 tiling key 分派)与 AICPU(Eigen 实现,支持 DOUBLE/复数)两条路径间自动选择,host 侧还会对整型输入自动执行 cast 到 FLOAT 的预处理。需要进一步了解的读者,可以继续阅读接口文档原文 math/tan/docs/aclnnTan&aclnnInplaceTan.md、算子级说明 math/tan/README.md 以及 两段式接口说明、编译与运行样例 等配套文档。

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

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

立即咨询