CANN ops-math 数学算子 Erfc(互补误差函数):aclnnErfc 与 aclnnInplaceErfc 双段式接口使用指南
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
导读
本文基于 CANN ops-math 开源仓库中 math/erfc/docs/aclnnErfc&aclnnInplaceErfc.md 编写,系统讲解互补误差函数(erfc)算子的aclnnErfc与aclnnInplaceErfc两组两段式接口:包括产品支持情况、数学定义、函数原型、入参约束、返回码语义、确定性约束以及完整的可运行示例。读完本文,你将能够在 Ascend NPU 上通过单算子 API 完成 erfc 的前向计算,并能根据是否需要原地(in-place)写回自行选择合适接口。文中所有接口原型、约束与错误码均以 op_api/aclnn_erfc.h 与 op_api/aclnn_erfc.cpp 等仓库源码为准。
1. 算子功能与数学定义
Erfc算子返回输入 Tensor 中每个元素对应的**误差互补函数(Complementary Error Function)**的值,是 CANN ops-math 中数学类基础算子之一(算子源码目录为 math/erfc)。
计算公式为:
$$ \mathrm{erfc}(x)=1-\frac{2}{\sqrt{\pi}}\int_{0}^{x}e^{-t^{2}}\mathrm{d}t $$
从定义可以看出,erfc(x) = 1 - erf(x),它描述了标准正态分布右尾概率的倍数关系,在通信理论(高斯误差函数积分)、数值计算与深度学习激活函数变体中经常出现。与普通误差函数不同,erfc 对较大的正自变量的计算更稳定,因为它直接刻画了尾部的衰减行为。
在仓库的算子注册文件 op_host/erfc_def.cpp 中,该算子在 Ascend 图引擎侧以Erfc名称注册,输入x与输出y支持的数据类型为BF16 / FLOAT16 / FLOAT,数据格式为ND,并开启了动态 rank、动态 shape 支持以及精度缩减(PrecisionReduce)标志。
1.1 产品支持情况
根据文档与 math/erfc/README.md 中的支持矩阵,Erfc 算子在不同产品上的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR / Ascend 950DT | 支持 |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 支持 |
| Atlas 训练系列产品 | 支持 |
注意:BFLOAT16数据类型并非所有产品都支持,例如 Atlas A2 训练/推理系列产品、Atlas 推理系列产品、Atlas 训练系列产品不支持 BFLOAT16(详见 math/erfc/README.md 的“参数说明”一节)。
2. 接口选型:aclnnErfc 与 aclnnInplaceErfc
aclnnErfc和aclnnInplaceErfc实现完全相同的数学功能,二者的区别仅在于输出结果的存放方式:
- aclnnErfc:需要显式新建一个输出张量对象(
out)来存储计算结果,输入与输出分离,适合需要保留原始输入的场景; - aclnnInplaceErfc:无需新建输出张量对象,直接在输入张量(
selfRef)的内存中覆盖存储计算结果,适合不需要保留原始输入、希望节省设备内存的场景。
从实现层面看,op_api/aclnn_erfc.cpp 中aclnnInplaceErfcGetWorkspaceSize实际上是把selfRef同时作为输入和输出,复用aclnnErfcGetWorkspaceSize的内部计算流程,因此两者在计算逻辑上完全一致,只是对外暴露的接口形态不同。
与 CANN 其他单算子接口一致,每组接口都遵循两段式调用范式(参见 docs/zh/context/two_phase_api.md):
- 第一段接口:
aclnnErfcGetWorkspaceSize/aclnnInplaceErfcGetWorkspaceSize——完成入参校验,计算执行所需的 workspace 大小,并返回包含算子计算流程的执行器executor; - 第二段接口:
aclnnErfc/aclnnInplaceErfc——传入已申请的 workspace 与 executor,在指定 stream 上真正执行计算。
这种设计把“计算规划”(可能耗时较长)与“计算执行”分离,便于上层框架预先规划内存并复用执行器。
3. 函数原型详解
四个接口的原型声明位于头文件 op_api/aclnn_erfc.h,具体如下。
3.1 aclnnErfcGetWorkspaceSize
aclnnStatus aclnnErfcGetWorkspaceSize( const aclTensor* self, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)参数说明:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| self(aclTensor*) | 输入 | 待进行 erfc 计算的入参 | shape 必须和 out 一样 | DOUBLE、FLOAT32、FLOAT16、BOOL、INT64 | ND | 不超过 8 维 | √ |
| out(aclTensor*) | 输出 | erfc 计算的出参 | 数据类型默认和 self 保持一致;若 self 为 BOOL 或 INT64 时,out 默认取 FLOAT32;shape 必须和 self 一样 | DOUBLE、FLOAT32、FLOAT16 | ND | 不超过 8 维 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
产品差异补充(源自文档与 op_api/aclnn_erfc.cpp 中的支持列表):
- Ascend 950PR/Ascend 950DT、Atlas A3 训练/推理系列产品:
self额外支持BFLOAT16,out额外支持BFLOAT16; - 其余产品线(Atlas A2 训练/推理、Atlas 推理、Atlas 训练系列)的 self 与 out 支持 DOUBLE、FLOAT32、FLOAT16,不支持 BFLOAT16。
上述“产品差异”对应源码中的两张支持列表:ASCEND910_DTYPE_DTYPE_SUPPORT_LIST(DOUBLE/FLOAT/FLOAT16/BOOL/INT64)与ASCEND910B_DTYPE_DTYPE_SUPPORT_LIST(在前者基础上增加 BF16),运行时通过GetDtypeSupportListV2根据实际平台选择。
3.2 aclnnErfc
aclnnStatus aclnnErfc( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, const aclrtStream stream)参数说明:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnErfcGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
3.3 aclnnInplaceErfcGetWorkspaceSize
aclnnStatus aclnnInplaceErfcGetWorkspaceSize( const aclTensor* selfRef, uint64_t* workspaceSize, aclOpExecutor** executor)参数说明:
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| selfRef(aclTensor*) | 输入/输出 | 输入输出 Tensor | - | DOUBLE、FLOAT32、FLOAT16 | ND | 不超过 8 维 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程 | - | - | - | - | - |
产品差异补充:
- Ascend 950PR/Ascend 950DT、Atlas A3 训练/推理系列产品:
selfRef额外支持BFLOAT16; - 其余产品线的 selfRef 仅支持 DOUBLE、FLOAT32、FLOAT16。
值得注意的是,inplace 接口的selfRef支持的数据类型范围比非 inplace 接口的self更窄——不支持 BOOL 与 INT64,因为 BOOL/INT64 输入需要先 Cast 到 FLOAT32 再计算,结果无法直接覆盖回原输入的内存。这一点在 op_api/aclnn_erfc.cpp 的ASCEND910_DTYPE_SELFREF_LIST/ASCEND910B_DTYPE_SELFREF_LIST中有明确体现。
3.4 aclnnInplaceErfc
aclnnStatus aclnnInplaceErfc( void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream)参数说明:
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口 aclnnInplaceErfcGetWorkspaceSize 获取 |
| executor | 输入 | op 执行器,包含了算子计算流程 |
| stream | 输入 | 指定执行任务的 Stream |
3.5 返回值与错误码
所有接口均返回aclnnStatus状态码,完整返回码定义参见 docs/zh/context/aclnn_return_code.md。
第一段接口(GetWorkspaceSize)会完成入参校验,出现如下场景时返回对应错误码:
aclnnErfcGetWorkspaceSize 的校验逻辑:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 self 或 out 是空指针时 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 out 的数据类型和数据格式不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | erfc 计算结果不能 cast 成 out 类型 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 与 out 的 shape 不同 |
| ACLNN_ERR_PARAM_INVALID | 161002 | self 和 out 的维度大于 8 |
aclnnInplaceErfcGetWorkspaceSize 的校验逻辑:
| 返回值 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 selfRef 是空指针时 |
| ACLNN_ERR_PARAM_INVALID | 161002 | selfRef 的数据类型和数据格式不在支持的范围之内 |
| ACLNN_ERR_PARAM_INVALID | 161002 | selfRef 的数据维度大于 8 |
上述校验逻辑与源码一一对应:op_api/aclnn_erfc.cpp 中的CheckParamsErfc依次检查空指针(CheckNotNull2Tensor)、数据类型合法性(CheckDtypeValid,其中 BOOL/INT64 输入时校验结果是否能 cast 成 out 类型)以及 shape 一致性(CheckSameShape1In1Out);CheckInplaceParamsErfc则只校验 selfRef 的空指针与数据类型。
4. 内部计算流程与约束说明
4.1 源码视角的计算图
从 op_api/aclnn_erfc.h 的接口注释可以看到,非 inplace 版本在框架内部被编排为如下计算图:
Self --> l0op::Contiguous --> l0op::Erfc --> l0op::Cast --> l0op::ViewCopy --> out而当self的数据类型为BOOL 或 INT64时,会先执行一次 Cast 到 FLOAT32,再进入 Erfc 计算:
Self --> l0op::Contiguous --> l0op::Cast(FLOAT32) --> l0op::Erfc --> l0op::Cast --> l0op::ViewCopy --> out对照 op_api/aclnn_erfc.cpp 的实现,这一流程非常清晰:
- 若
self为空 Tensor(IsEmpty()),直接返回workspaceSize = 0,不进入计算; l0op::Contiguous将非连续输入转为连续 Tensor;- 若
self为 BOOL/INT64,先l0op::Cast到 FLOAT32; l0op::Erfc执行核心计算;l0op::Cast将结果转为out声明的数据类型;- 若
out是非连续 Tensor,l0op::ViewCopy将连续结果写回非连续布局。
inplace 版本的计算图相对更短:
Self --> l0op::Contiguous --> l0op::Erfc --> l0op::ViewCopy --> out4.2 Kernel 层实现
核心 kernel 位于 op_kernel/arch35/erfc.cpp,它根据输入类型在编译期选择两条 DAG 执行路径(见 op_kernel/arch35/erfc_dag.h):
- FLOAT32(无需 Cast):
CopyIn<T> -> Vec::Erfc<float> -> CopyOut<T>; - FLOAT16 / BFLOAT16(需要精度提升):
CopyIn<T> -> Cast<float, T> -> Vec::Erfc<float> -> Cast<T, float>(RINT) -> CopyOut<T>,即先升到 float32 计算、再以舍入模式降回原类型,保证低精度类型下的计算精度。
kernel 使用ElementwiseSch逐元素调度框架,内存优化级别为LEVEL_2,任务类型为KERNEL_TYPE_AIV_ONLY。仓库同时提供了对应的 kernel UT:tests/ut/op_kernel/test_erfc.cpp,覆盖了 float 等类型的逐元素正确性验证。
4.3 约束说明
- 确定性计算:
aclnnErfc与aclnnInplaceErfc默认即为确定性实现(同一输入多次运行结果一致),无需额外配置。关于确定性计算的更完整说明可参考 docs/zh/context/determinism_compute.md。 - 维度不超过 8 维、数据格式为 ND、支持非连续 Tensor(相关机制可参考 docs/zh/context/non_contiguous_tensor.md)。
5. 完整调用示例(可运行)
下面的示例代码来自 examples/test_aclnn_erfc.cpp(与文档中的示例同源),在一个main函数中同时演示了aclnnErfc与aclnnInplaceErfc两组接口的完整调用流程,输入为 shape{2, 2}的 FLOAT32 Tensor,数据为{0, 1, 2, 3}。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_erfc.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_RET(ret == 0, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> selfShape = {2, 2}; std::vector<int64_t> outShape = {2, 2}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {0, 1, 2, 3}; std::vector<float> outHostData = {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 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnErfc第一段接口 ret = aclnnErfcGetWorkspaceSize(self, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnErfcGetWorkspaceSize 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;); } // 调用aclnnErfc第二段接口 ret = aclnnErfc(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnErfc failed. ERROR: %d\n", ret); return ret); uint64_t inplaceWorkspaceSize = 0; aclOpExecutor* inplaceExecutor; // 调用aclnnInplaceErfc第一段接口 ret = aclnnInplaceErfcGetWorkspaceSize(self, &inplaceWorkspaceSize, &inplaceExecutor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceErfcGetWorkspaceSize failed. ERROR: %d\n", ret); return ret); // 根据第一段接口计算出的workspaceSize申请device内存 void* inplaceWorkspaceAddr = nullptr; if (inplaceWorkspaceSize > 0) { ret = aclrtMalloc(&inplaceWorkspaceAddr, inplaceWorkspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("allocate workspace failed. ERROR: %d\n", ret); return ret;); } // 调用aclnnInplaceErfc第二段接口 ret = aclnnInplaceErfc(inplaceWorkspaceAddr, inplaceWorkspaceSize, inplaceExecutor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnInplaceErfc 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(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]); } auto inplaceSize = GetShapeSize(selfShape); std::vector<float> inplaceResultData(inplaceSize, 0); ret = aclrtMemcpy(inplaceResultData.data(), inplaceResultData.size() * sizeof(inplaceResultData[0]), selfDeviceAddr, inplaceSize * 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 < inplaceSize; i++) { LOG_PRINT("inplaceResult[%ld] is: %f\n", i, inplaceResultData[i]); } // 6. 释放aclTensor aclDestroyTensor(self); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } if (inplaceWorkspaceSize > 0) { aclrtFree(inplaceWorkspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }5.1 结果验证
对输入{0, 1, 2, 3}执行 erfc 计算的期望结果为:
erfc(0) = 1.0 erfc(1) ≈ 0.157299 erfc(2) ≈ 0.004678 erfc(3) ≈ 0.000022上述期望值在 examples/test_aclnn_erfc.cpp 中以 1e-6 的容差参与结果比对;文档示例则在 host 侧打印result[i]与inplaceResult[i],两种方式得到的结果完全一致——这也从侧面验证了 inplace 版本与非 inplace 版本计算语义相同。
5.2 编译与运行
- 示例代码的编译与运行步骤与 CANN 其他 aclnn 单算子样例一致,请参考 docs/zh/context/compile_and_run_sample.md,其中包含环境变量设置、编译链接选项以及执行流程的完整说明。
- 示例中涉及的基础概念(device/stream 初始化、Tensor 构造、内存申请与拷贝)可参考 docs/zh/context/basic_concept.md 与 docs/zh/context/data_structure.md。
- 仓库还提供了**图模式(GEIR)**调用方式,见 examples/test_geir_erfc.cpp,通过 op_graph/erfc_proto.h 中的算子 IR 构图调用,适用于将 Erfc 嵌入整图编译的场景。
5.3 示例代码执行流程拆解
对照两段式接口的规范,示例代码实际完成了 7 个固定步骤,任何 aclnn 单算子调用都遵循这一骨架:
- 初始化:
aclInit→aclrtSetDevice→aclrtCreateStream; - 构造 Tensor:
aclrtMalloc申请 Device 内存 →aclrtMemcpy写入输入数据 → 计算连续 strides →aclCreateTensor创建 aclTensor; - 两段式调用:第一段接口拿
workspaceSize与executor,按需aclrtMallocworkspace,再调用第二段接口执行; - 同步等待:
aclrtSynchronizeStream; - 取回结果:
aclrtMemcpy(DEVICE_TO_HOST)并打印/校验; - 释放 Tensor:
aclDestroyTensor; - 释放资源:
aclrtFree内存与 workspace、aclrtDestroyStream、aclrtResetDevice、aclFinalize。
注意其中 workspace 的申请是条件性的(if (workspaceSize > 0)):当第一段接口返回的 workspace 大小为 0 时无需申请,传入空指针即可。
6. 相关资源索引
- 接口文档:math/erfc/docs/aclnnErfc&aclnnInplaceErfc.md
- 算子 README:math/erfc/README.md
- 接口声明:op_api/aclnn_erfc.h、op_api/aclnn_erfc.cpp
- 算子注册与 shape 推导:op_host/erfc_def.cpp、op_host/erfc_infershape.cpp
- Kernel 实现:op_kernel/arch35/erfc.cpp、op_kernel/arch35/erfc_dag.h
- 示例代码:examples/test_aclnn_erfc.cpp、examples/test_geir_erfc.cpp
- 测试用例:tests/ut/op_kernel/test_erfc.cpp、tests/ut/op_api/test_aclnn_erfc.cpp、tests/ut/op_host/arch35/test_erfc_tiling.cpp
- 通用概念:docs/zh/context/two_phase_api.md、docs/zh/context/aclnn_return_code.md、docs/zh/context/compile_and_run_sample.md、docs/zh/context/determinism_compute.md
【免费下载链接】ops-math本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-math
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考