CANN ops-cv 中 aclnnUpsampleLinear1dBackward 反向算子接口解析:参数、约束与 NPU 梯度回传实战
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
本文以 ops-cv 仓库中 aclnnUpsampleLinear1dBackward 接口文档 为主体,完整讲解这一维(NCL 布局)线性上采样反向传播算子的数学原理、两段式接口签名、参数与约束规则,并结合 aclnn_upsample_linear_1d_backward.cpp 的 host 侧实现与单元测试,说明参数校验链路和不同 NPU 架构下的执行图构建方式。读完本文,你将掌握如何在 CANN ACLNN 接口下正确调用该反向算子计算 1D 特征(序列、语音、点云坐标等)插值的梯度,以及如何依据返回码快速定位参数配置问题。
1. 功能定位:aclnnUpsampleLinear1d 的反向传播
aclnnUpsampleLinear1dBackward是 aclnnUpsampleLinear1d 正向接口 的反向传播接口,属于UpsampleBilinear2dGrad算子模块对外暴露的第二个 aclnn 入口(另一个是 aclnnUpsampleBilinear2dBackwardV2)。它把正向插值输出上的梯度gradOut按插值权重回传、累加到输入侧,得到输入梯度out(即公式中的gradInput)。
典型应用是在 1D 信号上做了upsample_linear1d前向缩放(例如时间序列重采样、点云坐标插值)后,训练阶段需要把输出端损失梯度传回输入端。接口对输入的张量维度要求为 3 维(NCL),当数据格式为 ND 时默认按 NCL 处理。
1.1 产品支持情况
文档给出的支持矩阵如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | 支持 |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | 支持 |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | 支持 |
| Atlas 200I/500 A2 推理产品 | 不支持 |
| Atlas 推理系列产品 | 不支持 |
| Atlas 训练系列产品 | 支持 |
各平台的数据类型限制(详见 参数说明 一节):
- Atlas 训练系列产品:入参
gradOut和出参out的数据类型仅支持 FLOAT32、FLOAT16。 - Atlas A2 训练/推理系列、Atlas A3 训练/推理系列:当
gradOut的 shape 对应轴的值与inputSize对应轴的值不相同时,数据类型仅支持 FLOAT32、FLOAT16。
1.2 确定性计算
- Atlas A3 训练/推理系列、Atlas A2 训练/推理系列、Atlas 训练系列产品:
aclnnUpsampleLinear1dBackward默认确定性实现。 - Ascend 950PR/Ascend 950DT:默认非确定性实现,支持通过
aclrtCtxSetSysParamOpt开启确定性。
2. 计算公式:从正向映射到梯度回传
文档给出的正向核心算法逻辑分三步:
- 将目标图像的每一个点映射回原图,得到一个带小数点的坐标;
- 根据这个浮点数坐标,计算前后相邻的原始图像的点;
- 分别计算相邻点到对应目标点的权重,按照权重相乘累加即可得到目标点值。
缩放方式分为角对齐和边对齐:角对齐(alignCorners为 true)表示按照原始图片左上角像素中心点对齐;边对齐(alignCorners为 false)表示按照原始图片左上角顶点及两条边对齐,二者在计算缩放系数和坐标位置时存在差异。具体公式如下。
缩放系数:
$$ scale =\begin{cases} (inputSize[2]-1) / (outputSize[0]-1) & alignCorners=true \ 1 / scales & alignCorners=false,\ scales>0 \ inputSize[2] / outputSize[0] & alignCorners=false \end{cases} $$
因此,对于 output 某个方向上的点 p(x),映射回原始图像中的点记为 q(x'),有:
$$ x' =\begin{cases} x \cdot scale & alignCorners=true \ MAX(0,(x+0.5)\cdot scale-0.5) & alignCorners=false \end{cases} $$
记:
$$ x_{0}=int(x'),\quad x_{1}=int(x')+1,\quad lambda_{0}=x_{1}-x',\quad lambda_{1}=1-lambda_{0} $$
则正向插值:
$$ V(p_{x}) = V(p_{x0}) \cdot lambda_{0} + V(p_{x1}) \cdot lambda_{1} $$
假设正向插值的输出图像 out(x) 受原图像 input(x_i) 影响,则反向梯度回传为:
$$ gradInput(x_i) += gradOut(x) \cdot lambda(x_i) $$
需要注意的一个易错点:scales参数的语义是正向缩放的乘数(即 output 侧 L 是 input 侧 L 的scales倍),因此在反向接口里它表现为"缩小倍数"。以 调用示例 为例:gradOut的 L=3、out的 L=6、传入scales=0.5——正向是 6→3 的 0.5 倍缩放,反向则把 3 个输出梯度点按 2 倍插值回传到 6 个输入梯度点。这与源码中ComputeLinear1dBackwardScales的取值方式一致:scales > 0时直接采用scales,否则回退为outputSize / inputSize(见 aclnn_upsample_linear_1d_backward.cpp#L272-L279)。
3. 两段式接口函数原型
每个算子分为 两段式接口:必须先调用aclnnUpsampleLinear1dBackwardGetWorkspaceSize获取计算所需 workspace 大小以及包含算子计算流程的执行器(executor),再调用aclnnUpsampleLinear1dBackward执行计算。接口声明见 aclnn_upsample_linear_1d_backward.h。
3.1 第一段接口
aclnnStatus aclnnUpsampleLinear1dBackwardGetWorkspaceSize( const aclTensor * gradOut, const aclIntArray * outputSize, const aclIntArray * inputSize, bool alignCorners, double scales, aclTensor * out, uint64_t * workspaceSize, aclOpExecutor ** executor)3.2 第二段接口
aclnnStatus aclnnUpsampleLinear1dBackward( void * workspace, uint64_t workspaceSize, aclOpExecutor * executor, aclrtStream stream)4. 参数详解
4.1 aclnnUpsampleLinear1dBackwardGetWorkspaceSize 参数
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | 维度(shape) | 非连续Tensor |
|---|---|---|---|---|---|---|---|
| gradOut(aclTensor*) | 输入 | 表示反向计算的梯度 Tensor,对应公式中的gradOut。 | 不支持空 Tensor;当数据格式为 ND 时,默认按照 NCL 格式处理。 | FLOAT32、FLOAT16、BFLOAT16 | ND、NCL | 3 | √ |
| outputSize(aclIntArray*) | 输入 | 表示输入gradOut在 L 维度上的空间大小,对应公式中的outputSize。 | size 为 1,且取值大于 0。 | INT64 | - | - | - |
| inputSize(aclIntArray*) | 输入 | 表示输出 out 分别在 N、C、L 维度上的空间大小,对应公式中的inputSize。 | size 为 3,且各元素均大于零。 | INT64 | - | - | - |
| alignCorners(bool) | 输入 | BOOL 类型参数,对应公式中的alignCorners。 | True:输入和输出张量按其角像素的中心点对齐,保留角像素处的值;False:输入和输出张量通过其角像素的角点对齐,并且插值使用边缘值对边界外的值进行填充。 | - | - | - | - |
| scales(double) | 输入 | 表示输出 out 的 L 维度乘数,对应公式中的scales。 | 取值不大于 500。 | - | - | - | - |
| out(aclTensor*) | 输出 | 表示反向计算的输出 Tensor,对应公式中的gradInput。 | 不支持空 Tensor;输出维度必须是 3 维;数据类型、数据格式与入参gradOut保持一致。 | FLOAT32、FLOAT16、BFLOAT16 | NCL | 3 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小。 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含了算子计算流程。 | - | - | - | - | - |
各平台对数据类型的额外限制(与产品支持情况一节对应):
- Atlas 训练系列产品:入参
gradOut和出参out的数据类型仅支持 FLOAT32、FLOAT16。 - Atlas A2 训练/推理系列、Atlas A3 训练/推理系列:当
gradOut的 shape 对应轴的值与inputSize对应轴的值不相同时,数据类型仅支持 FLOAT32、FLOAT16。
从 aclnn_upsample_linear_1d_backward.cpp#L50-L53 的源码结构看,API 层的数据类型白名单DTYPE_SUPPORT_LIST正是{DT_FLOAT16, DT_FLOAT, DT_BF16},与文档一致。
4.2 第一段接口返回码
返回aclnnStatus状态码,完整定义参见 aclnn返回码。第一段接口完成入参校验,出现以下场景时报错:
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入参数是必选输入、输出或必选属性,且是空指针。 |
| ACLNN_ERR_PARAM_INVALID | 161002 | 出现下列任一场景: ① gradOut 的数据类型不在支持范围之内; ② gradOut 和 out 的数据类型不一致; ③ gradOut 的维度不为 3 维; ④ outputSize 的 size 不等于 1; ⑤ outputSize 的某个元素值小于 1; ⑥ inputSize 的 size 不等于 3; ⑦ inputSize 的某个元素值小于 1; ⑧ gradOut 在 L 维度上的 size 与 outputSize[0] 不同; ⑨ gradOut 和 out 的 N/C 轴的维度大小不相等; ⑩ out 的 shape 在各个维度上的大小与 inputSize 里对应元素值大小不同; ⑪ scales 的取值不满足约束。 |
这些校验规则在单元测试 test_aclnn_upsample_lineard_1d_backward.cpp 中逐一有对应用例,例如:
l2_upsamplelinear1d_backward_test_003~_012:DOUBLE、INT8、UINT8、INT32、INT64、INT16、BOOL、COMPLEX64、COMPLEX128 等不支持的 dtype 均返回ACLNN_ERR_PARAM_INVALID;_016:gradOut传入 nullptr 返回ACLNN_ERR_PARAM_NULLPTR;_017:输入 FLOAT、输出 FLOAT16,dtype 不一致返回 INVALID;_018/_020:outputSize元素个数为 2、inputSize元素个数为 5,均返回 INVALID;_019/_021:outputSize元素为 0、inputSize的 N 为 0,返回 INVALID;_023/_024:gradOut 与inputSize的 NC 不一致、与outputSize的 L 不一致,返回 INVALID;ascend910B3_scale501/ascend910B3_scale400:缩放倍数 550 返回 INVALID,400 返回 SUCCESS,验证了 scales ≤ 500 的上限(源码常量MAX_SUPPORT_SCALE = 500.0,见 aclnn_upsample_linear_1d_backward.cpp#L47)。
一个值得注意的实现细节:单元测试l2_upsamplelinear1d_backward_test_013使用inputSize={0,1,6}(N 轴为 0)的空 tensor 场景调用第一段接口并期望返回ACLNN_SUCCESS;而 aclnn_upsample_linear_1d_backward.cpp#L348-L353 中gradOut->IsEmpty()时直接置workspaceSize = 0并成功返回。从源码结构看,host 侧第一段接口对"含 0 维的空张量"会走直通返回路径,与参数表中"不支持空 Tensor"的表述存在细微差异,实际以目标硬件上运行时的返回码为准。
4.3 aclnnUpsampleLinear1dBackward 参数
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址。 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口aclnnUpsampleLinear1dBackwardGetWorkspaceSize获取。 |
| executor | 输入 | op 执行器,包含了算子计算流程。 |
| stream | 输入 | 指定执行任务的 Stream。 |
返回值为aclnnStatus状态码,同样参见 aclnn返回码。第二段接口的实现只有一行核心逻辑(aclnn_upsample_linear_1d_backward.cpp#L462-L468):
aclnnStatus aclnnUpsampleLinear1dBackward(void* workspace, uint64_t workspaceSize, aclOpExecutor* executor, aclrtStream stream) { L2_DFX_PHASE_2(aclnnUpsampleLinear1dBackward); // 固定写法,调用框架能力,完成计算 return CommonOpExecutorRun(workspace, workspaceSize, executor, stream); }即真正的算子调度由框架的CommonOpExecutorRun统一完成,第一段接口构建好的执行器在这里被一次性执行。
5. 约束说明
参数gradOut、out的 shape 约束:
每个维度的取值小于等于 2^20;
参数
out的 N 轴和 C 轴与gradOut保持一致;内存占用需小于 60GB,计算公式为:
$$ (gradOut_L + out_L + out_L) \times N \times C \times sizeof(dtype) < 60 \times 1024 \times 1024 \times 1024 $$
其中:N 代表输入和输出的 N 轴;C 代表输入和输出的 C 轴;dtype 代表输入张量的数据类型。
N × C < 2^31。
数据格式:入参gradOut和出参out的数据格式不为 NCL 或 ND 时,输入其他数据格式默认按照 NCL 处理。
针对Atlas A2 训练/推理系列、Atlas A3 训练/推理系列,反向接口的输入数据缩小倍数必须小于等于 500,即:
$$ outputSize[0] / 输出shape的长度L \le 500 $$
参数inputSize、outputSize、scales需要满足如下约束:
$$ outputSize = floor(inputSize_L \times scales) $$
源码中该约束由Check_scales实现(aclnn_upsample_linear_1d_backward.cpp#L313-L330):仅当|scales| > 1e-9时才执行output_L == floor(input_L * scales)的严格校验;scales取 0(表示未显式指定缩放倍数、由 inputSize/outputSize 反推)时跳过该校验。
6. 调用示例与完整代码
以下示例代码与仓库中的 test_aclnn_upsample_linear1d_backward.cpp 一致(该示例中scales=0.5、alignCorners=true,对应 L 从 6 缩放回 3 的反向传播),仅供参考,具体编译和执行过程请参考 编译与运行样例。
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_upsample_linear_1d_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 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 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 == ACL_SUCCESS, LOG_PRINT("Init acl failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> selfShape = {1, 1, 3}; std::vector<int64_t> outShape = {1, 1, 6}; void* selfDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* self = nullptr; aclTensor* out = nullptr; std::vector<float> selfHostData = {1, 2, 3}; std::vector<float> outHostData = {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); std::vector<int64_t> outArraySize = {3}; const aclIntArray* outputSize = aclCreateIntArray(outArraySize.data(), outArraySize.size()); CHECK_RET(outputSize != nullptr, return ACL_ERROR_INTERNAL_ERROR); std::vector<int64_t> inputArraySize = {1, 1, 6}; const aclIntArray* inputSize = aclCreateIntArray(inputArraySize.data(), inputArraySize.size()); CHECK_RET(inputSize != nullptr, return ACL_ERROR_INTERNAL_ERROR); // 3. 调用CANN算子库API,需要修改为具体的API名称 uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnUpsampleLinear1dBackward第一段接口 ret = aclnnUpsampleLinear1dBackwardGetWorkspaceSize(self, outputSize, inputSize, true, 0.5, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnUpsampleLinear1dBackwardGetWorkspaceSize 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); } // 调用aclnnUpsampleLinear1dBackward第二段接口 ret = aclnnUpsampleLinear1dBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnUpsampleLinear1dBackward 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(resultData[0]), 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(self); aclDestroyTensor(out); aclDestroyIntArray(outputSize); aclDestroyIntArray(inputSize); // 7. 释放device资源,需要根据具体API的接口定义修改 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }参数对应关系小结:selfShape={1,1,3}对应gradOut(N=1、C=1、L=3),outShape={1,1,6}对应out(即inputSize={1,1,6}中 N、C、L 三轴),outputSize={3}与gradOut的 L 维一致。这组 shape 同时满足 4.1 节参数表和第 5 节的全部约束。
7. 源码实现剖析:校验链路与架构分支
7.1 参数校验链路(CheckParams)
第一段接口的入口 aclnnUpsampleLinear1dBackwardGetWorkspaceSize 在构建执行器之后、生成算子图之前,会依次执行CheckParams中的六步校验(aclnn_upsample_linear_1d_backward.cpp#L228-L255):
| 步骤 | 函数 | 校验内容 | 与文档的对应关系 |
|---|---|---|---|
| 1 | CheckNotNull | gradOut、outputSize、inputSize、out非空 | 对应ACLNN_ERR_PARAM_NULLPTR |
| 2 | CheckShapeValid(RegBase 架构)/CheckShape(其他架构) | gradOut/out为 3 维;outputSizesize 为 1、inputSizesize 为 3;gradOut的 L 维等于outputSize[0],out的各维等于inputSize对应元素 | 对应 INVALID 表中 ③④⑤⑥⑦⑧⑩ |
| 3 | CheckDtypeValid | NPU 架构必须在支持列表内(DAV_2201/DAV_1001/RegBase 系列);gradOutdtype 属于 FP16/FP32/BF16 白名单;out与gradOutdtype 一致 | 对应 INVALID 表中 ①② |
| 4 | CheckInputElement | inputSize/outputSize元素均大于 0;gradOut的完整 shape(N、C、L)与{batch, channels, outputL}逐维一致 | 对应 INVALID 表中 ⑤⑦⑧ |
| 5 | CheckNCValid | gradOut与out的 N、C 轴相等 | 对应 INVALID 表中 ⑨ |
| 6 | CheckUplimit | 非 RegBase 架构下各维不超过 INT32_MAX(RegBase 架构跳过该检查) | 对应"每维取值小于等于 2^20 级别"的量级约束 |
7.2 执行图构建:两条架构分支
通过校验后,实现按 NPU 架构走两条不同的执行图构建路径(aclnn_upsample_linear_1d_backward.cpp#L354-L454):
分支一:RegBase 架构(含 Atlas A3/A2 系列)。由于该分支的底层 kernel 直接操作 3 维 NCL 张量,处理最简单:
- 先对
gradOut做l0op::Contiguous得到连续 tensor; - 若
gradOut的 shape 与inputSize各轴相同(CheckIOSizesIsSame为真),说明不需要插值回传,直接l0op::ViewCopy(gradOutContiguous, out)写回; - 否则分配中间 tensor
originalImage,调用l0op::ResizeLinearGrad(gradOutContiguous, originalImage, alignCorners, scales, out, executor)完成 1D 线性插值反向,最后ViewCopy回out。
分支二:其他架构(典型为 Ascend 910B 等 DAV_2201)。底层UpsampleBilinear2dGradkernel 只处理 4 维 NCHW,因此代码先做"升维":View3dAs4d通过Contiguous → Unsqueeze(2) → ReFormat(NCHW)把 3 维 NCL 张量变成 4 维 NCHW(L 维扩展为 H=L、W=1);inputSize也补维为{N, C, 1, L}。随后按三种情况分派:
- 大尺寸或尺寸/缩放约束不满足(
outputSize[0] >= 1000000,或gradOut与inputSize的 shape 不一致且不是 DAV_2201 + scale 一致的场景):走ResizeBilinearV2Grad5Hd路径——gradOut先Cast为 FLOAT32 再TransDataSpecial到 NC1HWC0,经halfPixelCenters = !alignCorners的双线性反向 kernel 计算,最后TransData回 NCHW 并Cast回目标 dtype; - DAV_2201 且
CheckScales通过(即scales ≤ 500):调用l0op::UpsampleBilinear2dGrad(gradOutCast, outputSizeArray, originSizeArray, outContiguous, alignCorners, realScales_h, realScales_w, executor),其中宽度方向缩放取1.0 / scales(scales ≤ 0 时取 0,由底层反推),高度方向固定 1.0(因为补维后 H 恒为 1); - 最后统一
View4dAs3d(squeeze 第 2 维并 reformat)降回 3 维,ViewCopy写回out。
构建完成后,*workspaceSize = uniqueExecutor->GetWorkspaceSize()汇总整张执行图的 workspace,并把 executor 释放给调用方。从源码结构看,这种"3 维 API + 4 维 kernel 复用"的设计正是aclnnUpsampleLinear1dBackward与aclnnUpsampleBilinear2dBackwardV2共用UpsampleBilinear2dGrad算子模块的原因:1D 场景等价于 W=1 的 2D 双线性插值。
7.3 测试体系
除 4.2 节列出的 host 侧 UT 外,仓库还提供:
- 端到端样例:examples/test_aclnn_upsample_linear1d_backward.cpp(即第 6 节代码);
- ST(系统测试)配置与执行器:atk_aclnnUpsampleLinear1dBackward.json、executor_aclnnUpsampleLinear1dBackward.py。从执行器脚本结构看,ST 流程会先用 PyTorch(torch_npu)在标杆侧做正向
upsample_linear1d计算,再以全 1 梯度执行output.backward(gradoutput)取得输入梯度作为参考值,与 NPU 上 aclnn 接口的输出做精度比对;脚本还负责把 PyTorch 侧的scale_factor/size参数换算成 aclnn 接口所需的scalesH_double与outputSize_int,其中"只指定 size 不指定 scale"时scales取 0,与源码中"由 outputSize/inputSize 反推 scales"的分支相呼应。
8. 常见报错与排查要点
结合文档返回码表与源码校验链路,实际调用时的排查路径如下:
| 现象 | 可能原因 | 定位建议 |
|---|---|---|
ACLNN_ERR_PARAM_NULLPTR(161001) | gradOut/outputSize/inputSize/out任一为 nullptr | 检查指针初始化;aclCreateIntArray的返回也要判空(见示例代码第 104/108 行写法)。 |
ACLNN_ERR_PARAM_INVALID(161002),dtype 类 | dtype 不在 FP16/FP32/BF16 内,或gradOut与outdtype 不一致;Atlas 训练系列传入了 BF16 | 对照 4.1 节"数据类型"列与各平台限制。 |
ACLNN_ERR_PARAM_INVALID,shape 类 | gradOut/out不是 3 维;outputSizesize≠1;inputSizesize≠3;L 维与outputSize[0]不匹配;N/C 不一致 | 逐维核对:gradOut.shape == {N, C, outputSize[0]},out.shape == inputSize。 |
ACLNN_ERR_PARAM_INVALID,scales 类 | scales > 500;或outputSize[0] != floor(inputSize[2] * scales) | 反向接口中scales是正向缩放乘数,例如正向 6→3 的 0.5 倍缩放,反向应传 0.5(而不是 2)。 |
| 运行期精度异常 | 非确定性实现平台未开启确定性;workspace 复用错误 | 950 平台可通过aclrtCtxSetSysParamOpt开启确定性;确认第二段接口使用第一段返回的同一 executor 与 workspace。 |
9. 小结
aclnnUpsampleLinear1dBackward在 ops-cv 中以"两段式 aclnn 接口 + 架构分派的执行图"方式,把 1D 线性插值的梯度回传统一到UpsampleBilinear2dGrad算子模块下:RegBase 架构直接调用 3 维的ResizeLinearGrad路径,910B 等架构则通过 NCL→NCHW 升维复用UpsampleBilinear2dGrad/ResizeBilinearV2Grad5Hdkernel。对使用者而言,只要记住三件事——gradOut为 3 维 NCL 且 L 维等于outputSize[0]、out的 shape 等于inputSize(N、C 与gradOut一致)、scales是正向缩放乘数且不超过 500——再按第 6 节示例的固定流程申请 workspace、执行第二段接口并同步流,即可在支持的 NPU 产品上稳定完成 1D 上采样的反向计算。更多接口级细节可继续查阅 正向接口文档、两段式接口说明 与 aclnn返回码。
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考