ops-cv 三维最近邻精确上采样反向算子:aclnnUpsampleNearestExact3dBackward API 详解与 NPU 实现解析
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
本文基于 ops-cv 仓库中的接口文档,系统讲解aclnnUpsampleNearestExact3dBackward这一 5D 张量三维最近邻"精确"上采样反向(梯度)接口的数学定义、两段式调用方式、全部参数与错误码、容量与缩放约束,并结合仓库内主机侧校验链路、L0 封装与 kernel 级 SIMT 实现,帮助你既能直接写出可运行的调用代码,又能理解其在 NPU 上的实际执行路径。
算子定位与产品支持情况
aclnnUpsampleNearestExact3dBackward是 aclnnUpsampleNearestExact3d(三维最近邻精确上采样正向算子)的反向计算接口,用于将上采样输出侧的梯度gradOut累加回输入侧的gradInput。仓库中该算子的实现与文档位于 image/upsample_nearest_exact3d_grad 目录,接口文档即 aclnnUpsampleNearestExact3dBackward.md。
按文档与 算子 README 的产品支持表,各产品的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | √ |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | √ |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | × |
| Atlas 推理系列产品 | × |
| Atlas 训练系列产品 | × |
在 算子定义文件 中可以看到算子为ascend910b、ascend910_93、ascend950三类平台分别注册了配置,与上表支持的产品线相对应;其中ascend950配置开启了动态编译、动态 Rank 与动态 Shape 支持(regbase 平台走动态编译静态标志,扩展文件指向upsample_nearest_exact3d_grad_aptkernel 源文件)。
数学原理:梯度累加窗口与 -0.5 偏移
理解这个"Backward"接口的关键,是它不是简单的逐点缩放,而是对输出侧梯度在一个三维窗口内做累加。对于输入gradOut(N, C, d, h, w),输出gradInput上任意一点(N, C, D, H, W)满足:
$$ gradInput(N, C, D, H, W) = \sum_{d = srcD}^{srcDUp - 1}\sum_{h = srcH}^{srcHUp - 1}\sum_{w = srcW}^{srcWUp - 1}gradOut(N, C, d, h, w) $$
其中缩放因子按如下规则确定:当scalesD、scalesH、scalesW三者均大于 0 时直接取用户传入值;否则用输出/输入空间尺寸之比回退计算:
$$ scaleD =\begin{cases} scalesD & scalesD>0&scalesH>0&scalesW>0\ outputSize[0]/inputSize[2] & Otherwise\ \end{cases} $$
$$ scaleH =\begin{cases} scalesH & scalesD>0&scalesH>0&scalesW>0\ outputSize[1]/inputSize[3] & Otherwise\ \end{cases} $$
$$ scaleW =\begin{cases} scalesW & scalesD>0&scalesH>0&scalesW>0\ outputSize[2]/inputSize[4] & Otherwise\ \end{cases} $$
输入点(D, H, W)对应的输出侧累加窗口边界为(outputSize[0/1/2]分别是输出 D、H、W 维长度):
$$ srcD = Min(ceil(scaleD * D - 0.5), outputSize[0]) $$
$$ srcDUp = Min(ceil(scaleD * (D + 1) - 0.5), outputSize[0]) $$
$$ srcH = Min(ceil(scaleH * H - 0.5), outputSize[1]) $$
$$ srcHUp = Min(ceil(scaleH * (H + 1) - 0.5), outputSize[1]) $$
$$ srcW = Min(ceil(scaleW * W - 0.5), outputSize[2]) $$
$$ srcWUp = Min(ceil(scaleW * (W + 1) - 0.5), outputSize[2]) $$
这里-0.5的偏移正是"Exact"(精确)模式的语义所在:它对应 PyTorchupsample_nearest_exact3d的反向定义,与普通的upsample_nearest3d(无 -0.5 偏移)形成对照。这一细节可以在 kernel 共享实现中得到直接印证:upsample_nearest3d_grad_simt_base.h 中ComputeOrig函数按isExtra模板开关选择ceil(idx * scale - 0.5)或ceil(idx * scale),本算子固定以isExtra = true实例化(见 upsample_nearest_exact3d_grad_apt.cpp 中Nearest3dGradSimt<DTYPE_GRAD_OUTPUT, uint32_t, true, schId>的第三个模板参数)。
两段式接口与函数原型
按 CANN 的两段式接口规范,该算子分为两个接口:必须先调用第一段接口完成入参校验并计算所需 workspace 大小、拿到aclOpExecutor,再调用第二段接口在指定流上执行计算。
aclnnStatus aclnnUpsampleNearestExact3dBackwardGetWorkspaceSize( const aclTensor *gradOut, const aclIntArray *outputSize, const aclIntArray *inputSize, double scalesD, double scalesH, double scalesW, aclTensor *gradInput, uint64_t *workspaceSize, aclOpExecutor **executor)aclnnStatus aclnnUpsampleNearestExact3dBackward( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)第一段接口 aclnnUpsampleNearestExact3dBackwardGetWorkspaceSize 参数说明
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| gradOut(aclTensor*) | 输入 | 表示反向计算的梯度Tensor,对应公式中的gradOut。 | 不支持空Tensor;gradOut的所有维度取值均小于等于(2^31-1)。 | FLOAT32、FLOAT16、BFLOAT16 | NCDHW、NDHWC | 5 | √ |
| outputSize(aclIntArray*) | 输入 | 表示输入gradOut在D、H和W维度上的空间大小,对应公式中的outputSize。 | size为3,且各元素均大于零。 | INT64 | - | - | - |
| inputSize(aclIntArray*) | 输入 | 表示输出gradInput分别在N、C、D、H和W维度上的空间大小,对应公式中的inputSize。 | size为5,且各元素均大于零。 | INT64 | - | - | - |
| scalesD(double) | 输入 | 表示输出gradInput的depth维度乘数,对应公式中的scalesD。 | 取值小于等于50。 | - | - | - | - |
| scalesH(double) | 输入 | 表示输出gradInput的height维度乘数,对应公式中的scalesH。 | 取值小于等于50。 | - | - | - | - |
| scalesW(double) | 输入 | 表示输出gradInput的width维度乘数,对应公式中的scalesW。 | 取值小于等于50。 | - | - | - | - |
| gradInput(aclTensor*) | 输出 | 表示反向计算的输出张量,对应公式中的输出gradInput。 | 不支持空Tensor;数据类型、数据格式、shape与入参gradOut保持一致(指 N、C 维度一致);gradInput的所有维度取值均小于等于(2^31-1)。 | FLOAT32、FLOAT16、BFLOAT16 | NCDHW、NDHWC | 5 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在Device侧申请的workspace大小。 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回op执行器,包含了算子计算流程。 | - | - | - | - | - |
返回值:aclnnStatus状态码,取值参见 aclnn 返回码说明。第一段接口完成入参校验,出现以下场景时报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的gradOut、outputSize、inputSize或gradInput是空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOut的数据类型不在支持的范围内。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOut和gradInput的数据类型不一致。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOut的维度不为5维。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | outputSize的size不等于3。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | outputSize的某个元素值不大于0。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | inputSize的size不等于5。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOut与inputSize在N、C维度上的size不同。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOut在D、H、W维度上的size与outputSize[0]、outputSize[1]、outputSize[2]不一致。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradInput在N、C维度的size与inputSize[0]、inputSize[1]不一致。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradInput在D、H、W维度上的size与inputSize[2]、inputSize[3]、inputSize[4]不一致。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | scalesD、scalesH、scalesW的取值不满足约束要求。 |
这些校验项与源码中的检查函数一一对应:aclnn_upsample_nearest_exact3d_backward.cpp 中的CheckParams按固定顺序执行CheckNotNull(空指针检查)、CheckDtypeValid(类型白名单且输入输出类型一致)、CheckShape(5 维检查、outputSize 长度为 3、inputSize 长度为 5)、CheckInputElement(各元素大于 0、shape 与 inputSize/outputSize 的逐维一致性、scale 上限)以及CheckUplimit(各维度不超过 INT32_MAX)。其中类型白名单DTYPE_SUPPORT_LIST仅包含DT_FLOAT、DT_FLOAT16、DT_BF16(同文件 L33-L34),缩放上限常量MAX_SUPPORT_SCALE = 50(L37)。
第二段接口 aclnnUpsampleNearestExact3dBackward 参数说明
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在Device侧申请的workspace内存地址。 |
| workspaceSize | 输入 | 在Device侧申请的workspace大小,由第一段接口aclnnUpsampleNearestExact3dBackwardGetWorkspaceSize获取。 |
| executor | 输入 | op执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的Stream。 |
返回值:aclnnStatus,参见 aclnn 返回码说明。
约束说明
文档给出的 shape 与容量约束如下,调用前必须逐一核对:
- 参数
gradOut、gradInput的 shape 约束:- 每个维度的取值小于等于 2^20。
- 参数
gradInput的 N 轴和 C 轴与gradOut保持一致。 - 内存占用需小于 60GB,计算公式:
$$ N * C * (gradOut_D * gradOut_H * gradOut_W + gradInput_D * gradInput_H * gradInput_W + gradOut_D * gradOut_H * gradInput_W + gradOut_D * gradInput_H * gradInput_W) * sizeof(dtype) < 60 * 1024 * 1024 * 1024 $$
其中 N 为输入和输出的 N 轴,C 为输入和输出的 C 轴,dtype 为输入张量的数据类型。- N * C * gradOut_D * gradOut_H < 2^31
- gradInput_W * gradInput_H < 2^31
- 参数
gradOut、gradInput的数据格式不为 NCDHW 或 NDHWC 时,输入的其他数据格式默认按 NCDHW 处理。 - 反向接口的输入数据缩小倍数必须小于等于 50,即:
$$ outputSize_D / 输出shape的深度D <= 50 $$
$$ outputSize_H / 输出shape的高度H <= 50 $$
$$ outputSize_W / 输出shape的宽度W <= 50 $$
- 当
scalesD、scalesH、scalesW的取值均大于 0 时,参数inputSize、outputSize、scalesD、scalesH、scalesW需满足如下约束:
$$ outputSize_D = floor(inputSize_D * scalesD) $$
$$ outputSize_H = floor(inputSize_H * scalesH) $$
$$ outputSize_W = floor(inputSize_W * scalesW) $$
- 确定性计算:
aclnnUpsampleNearestExact3dBackward默认确定性实现。
从源码结构看,"缩小倍数不超过 50"的限制对应主机侧CheckInputElement中对回退 scale(ComputeNearestExact3dGradScales,即 scale ≤ 0 时用output_size / input_size反推)的三重校验,见 aclnn_upsample_nearest_exact3d_backward.cpp L128-L135;这也解释了为什么文档把"scales 取值不满足约束"与 shape 校验统一归入 161002 错误码。
源码实现链路解析
第一段接口的内部编排:Contiguous → Transpose → 计算 → ViewCopy
阅读 GetWorkspaceSize 实现 可以看到该接口并非单一 kernel 调用,而是一条 L0 算子编排链:
- 空 Tensor 短路:
gradOut为空时直接返回workspaceSize = 0; - scales 归一化:当
scalesD/H/W均大于 0 时透传用户值、并把outputSize置为空数组;否则向 kernel 传{0, 0, 0}的 scales,由 kernel 侧依据outputSize/inputSize反推(L222-L234)。这与"功能说明"中 scale 的分段定义严格一致。注意:文档参数表中gradOut、gradInput标记"不支持空Tensor",此处短路分支可理解为框架层的防御性处理。 - 非连续 Tensor 拉直:对
gradOut调用l0op::Contiguous; - 数据格式归一:若
gradOut存储格式为 NDHWC,则先用l0op::Transpose(permute 为{0, 4, 1, 2, 3})转为 NCDHW 再进入 kernel;计算结果再按{0, 2, 3, 4, 1}转回 NDHWC(L236-L256)。从源码结构看,kernel 本体只处理 NCDHW 布局,NDHWC 通过前后两次转置支持,这正是参数表同时列出 NCDHW、NDHWC 两种格式的原因。 - 核心计算:调用
l0op::UpsampleNearestExact3dGradNcdhw生成gradInput中间结果; - 结果写回:
l0op::ViewCopy把结果拷贝到可能非连续的gradInput输出张量上;最后由GetWorkspaceSize()汇总整条链路的 workspace 需求。
L0 封装 UpsampleNearestExact3dGradNcdhw 还负责输出 shape 构造(以inputSize的 D、H、W 覆盖gradOut的对应维度)以及低精度适配:在非 regbase 平台上,BFLOAT16/FLOAT16 输入会先Cast为 FLOAT 参与计算,结果再Cast回原类型(L58-L73),保证半精度下的累加精度行为一致。
Kernel 级实现:SIMT 三级切分与累加窗口
device 侧入口 upsample_nearest_exact3d_grad_apt.cpp 按调度模板schId分支:schId == 0时退化为纯拷贝(输出完全等于输入);否则实例化Nearest3dGradSimt<DTYPE_GRAD_OUTPUT, uint32_t/uint64_t, true, schId>执行真正的梯度累加。该实现复用了 upsample_nearest3d_grad 的 arch35 共享代码。
从 upsample_nearest3d_grad_simt_base.h 可以读出与文档公式逐式对应的三层设计:
- 窗口边界计算:
ComputeOrig<T2, isExtra>(D, lenSrcD, scaleD, origD)依次求出origD/origH/origW(即 srcD/srcH/srcW)与D+1/H+1/W+1对应的上界(srcDUp/srcHUp/srcWUp),并用min(orig, limit)钳位到输出尺寸——对应公式中Min(ceil(scale*(D+1)-0.5), outputSize[0])的形式; - 累加实现:
ComputeForSplitNcdhw(L45-L60)对d ∈ [origD, origDUp)、h ∈ [origH, origHUp)、w ∈ [origW, origWUp)三重循环求和gradOut,累加过程使用float中间量,最后写回outputGm[yGmIdx]——这正是文档中三重求和公式的逐行实现; - 数据并行切分:
SimtCompute(L104-L154)将输出体展平索引按schId分为三类任务:SCH_ID_1固定 N、C 只对 D/H/W 切分,SCH_ID_2固定 N 按 C 切分,SCH_ID_3对 N 切分;每个任务块内部再以魔数除法(GetUintDivMagicAndShift)快速还原(D, H, W)坐标。线程数按索引位宽选择:uint32 索引 2048 线程、uint64 索引 1024 线程(upsample_nearest3d_grad_simt.h L81-L91)。
结合文档的 60GB 内存公式可以推断,公式中额外的两项gradOut_D*gradOut_H*gradInput_W、gradOut_D*gradInput_H*gradInput_W即为主机侧为 NDHWC 前后转置与中间结果准备的临时缓冲预算上限。
图模式(IR)调用与属性默认值
除 aclnn 接口外,该算子也可通过算子 IR 以图模式构图调用。算子名为UpsampleNearestExact3dGrad,其输入输出与属性定义为:
| 参数名 | 类型 | 描述 | 数据类型 |
|---|---|---|---|
| grad_output | 输入 | 反向计算的梯度 Tensor,对应公式中的gradOutput。 | FLOAT32、FLOAT16、BFLOAT16 |
| input_size | 属性(必填) | 输出分别在 N、C、D、H、W 维度上的空间大小,size 为 5 且各元素大于零;必须满足input_size[0] == grad_output 的 N、input_size[1] == grad_output 的 C。 | LISTINT |
| output_size | 属性(可选) | 输入grad_output在 D、H、W 维度上的空间大小,size 为 3 且各元素大于零;默认值{0, 0, 0}。 | LISTINT |
| scales | 属性(可选) | 沿每个维度的缩放数组,3 个元素分别对应scalesD、scalesH、scalesW;默认值{0.0, 0.0, 0.0}。 | LISTFLOAT |
| y | 输出 | 反向计算的输出张量,对应公式中的gradInput,类型与格式与grad_output一致,shape 由input_size决定。 | FLOAT32、FLOAT16、BFLOAT16 |
可见 aclnn 接口中scalesD/H/W为 0 且使用outputSize的用法,等价于图模式下只填input_size(scales取默认值)的场景,两条路径语义一致。
调用示例
以下示例代码继承自接口文档,可直接作为接入模板;具体编译与执行流程请参考编译与运行样例。示例选取了一个 2 倍下采样反向前向的场景:gradOut形状为{2, 2, 2, 2, 2}(NCDHW),gradInput形状为{2, 2, 1, 1, 1},scales全 0 走outputSize反推路径,期望每个输入点累加对应 2×2×2=8 个输出梯度。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_upsample_nearest_exact3d_backward.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_NCDHW, 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> gradOutShape = {2, 2, 2, 2, 2}; std::vector<int64_t> gradInputShape = {2, 2, 1, 1, 1}; void* gradOutDeviceAddr = nullptr; void* gradInputDeviceAddr = nullptr; aclTensor* gradOut = nullptr; aclTensor* gradInput = nullptr; std::vector<float> gradOutHostData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}; std::vector<float> gradInputHostData = {2.0, 2, 2, 2}; std::vector<int64_t> outputSizeData = {2, 2, 2}; std::vector<int64_t> inputSizeData = {2, 2, 1, 1, 1}; double scalesD = 0.0; double scalesH = 0.0; double scalesW = 0.0; // 创建gradOut aclTensor ret = CreateAclTensor(gradOutHostData, gradOutShape, &gradOutDeviceAddr, aclDataType::ACL_FLOAT, &gradOut); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建gradInput aclTensor ret = CreateAclTensor(gradInputHostData, gradInputShape, &gradInputDeviceAddr, aclDataType::ACL_FLOAT, &gradInput); CHECK_RET(ret == ACL_SUCCESS, return ret); const aclIntArray* outputSize = aclCreateIntArray(outputSizeData.data(), outputSizeData.size()); CHECK_RET(outputSize != nullptr, return ACL_ERROR_INTERNAL_ERROR); const aclIntArray* inputSize = aclCreateIntArray(inputSizeData.data(), inputSizeData.size()); CHECK_RET(inputSize != nullptr, return ACL_ERROR_INTERNAL_ERROR); // 3. 调用CANN算子库API,需要修改为具体的API uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnUpsampleNearestExact3dBackward第一段接口 ret = aclnnUpsampleNearestExact3dBackwardGetWorkspaceSize(gradOut, outputSize, inputSize, scalesD, scalesH, scalesW, gradInput, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnUpsampleNearestExact3dBackwardGetWorkspaceSize 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); } // 调用aclnnUpsampleNearestExact3dBackward第二段接口 ret = aclnnUpsampleNearestExact3dBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnUpsampleNearestExact3dBackward 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(gradInputShape); std::vector<float> resultData(size, 0); ret = aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), gradInputDeviceAddr, 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,需要根据具体API的接口定义修改 aclDestroyTensor(gradOut); aclDestroyTensor(gradInput); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(gradOutDeviceAddr); aclrtFree(gradInputDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }该示例的完整可编译版本(含资源释放与错误处理细节)见仓库样例 test_aclnn_upsample_nearest_exact3d_grad.cpp。
测试体系与正确性验证
仓库围绕该算子提供了分层测试,可作为行为验证的依据:
- 主机侧接口 UT:test_aclnn_upsample_nearest_exact3d_backward.cpp 覆盖第一段接口的参数校验路径;
- Kernel UT:test_upsample_nearest_exact3d_grad.cpp 配合数据生成与对比脚本(gen_data.py、compare_data.py)对 kernel 数值进行比对;
- 系统级(ST)标杆测试:executor_aclnnUpsampleNearestExact3dBackward.py 以 PyTorch 的
upsample_nearest_exact3d_backward(torch_npu 后端)作为标杆正向计算,与算子输出做一致性对比,进一步印证该算子与框架语义(含 -0.5 偏移)对齐。
小结
aclnnUpsampleNearestExact3dBackward是 5D 张量(NCDHW/NDHWC)最近邻精确上采样的梯度接口,数学本质是对输出侧梯度在srcD..srcDUp等三维窗口内求和,-0.5偏移是其"Exact"语义的来源;- 调用遵循两段式接口:第一段完成入参校验(161001/161002 错误码体系)、workspace 计算与 L0 算子链编排,第二段在指定 stream 上执行;
- 实现上支持 FLOAT32/FLOAT16/BF16 三种类型与 NCDHW/NDHWC 两种格式(NDHWC 经前后转置支持),scales 与 outputSize 两种指定方式语义等价,缩小倍数上限 50,shape 与 60GB 内存约束需在调用前自查;
- 深入阅读入口:主机侧校验与编排在 op_host/op_api/aclnn_upsample_nearest_exact3d_backward.cpp,L0 封装在 op_host/op_api/upsample_nearest_exact3d_grad.cpp,kernel 计算核心在 arch35/upsample_nearest3d_grad_simt_base.h。
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考