CANN ops-cv 算子深度解析:UpsampleBicubic2dAAGrad 双三次抗锯齿上采样反向传播算子使用指南
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
导读
UpsampleBicubic2dAAGrad是 CANN ops-cv 图像算子库中双三次抗锯齿(Bicubic with Anti-Aliasing)上采样算子UpsampleBicubic2dAA的反向传播(Gradient)算子,用于在 NPU 上完成上采样梯度回传,是训练场景下图像缩放链路的关键一环。本文以 image/upsample_bicubic2d_aa_grad/README.md 为主体,结合仓库内 aclnn 接口文档、调用示例与算子源码,完整讲解其计算公式、参数语义、约束条件、两段式 aclnn 调用流程以及 Host 侧 Tiling 与 Kernel 侧的底层实现。读完本文,你将能够正确理解该算子的梯度传播原理,并通过aclnnUpsampleBicubic2dAAGrad接口在 Ascend 平台上完成反向计算的调用与验证。
一、产品支持情况
根据 image/upsample_bicubic2d_aa_grad/README.md 与 aclnnUpsampleBicubic2dAAGrad 接口文档,该算子在不同硬件平台上的支持情况如下:
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR / Ascend 950DT | √ |
| Atlas A3 训练系列产品 / Atlas A3 推理系列产品 | √ |
| Atlas A2 训练系列产品 / Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | × |
| Atlas 推理系列产品 | × |
| Atlas 训练系列产品 | × |
从源码的算子注册配置也可以印证这一点。在 upsample_bicubic2d_aa_grad_def.cpp 中,通过AICore().AddConfig("ascend910b")、AddConfig("ascend910_93")(对应 Atlas A2/A3 系列)以及带DynamicCompileStaticFlag、DynamicRankSupportFlag、DynamicShapeSupportFlag的ascend950配置注册了 AICore 算子实现,而 op_host/config 下也仅存在ascend910_93、ascend910b、ascend950三套编译配置目录,与文档中"三种产品支持、三种产品不支持"的结论完全对应。需要特别说明的是,与正向算子 UpsampleBicubic2dAA 相比,该反向算子不支持 Kirin X90 / Kirin 9030 处理器系列。
二、功能说明与计算原理
2.1 算子定位:双三次抗锯齿上采样的反向传播
UpsampleBicubic2dAAGrad是 UpsampleBicubic2dAA(仓库内路径为 image/upsample_bicubic2d_aa/README.md)的反向传播算子。若输入张量gradOutput的 shape 为(N, C, H, W),则输出张量gradInput的 shape 为(N, C, inputSize[2], inputSize[3]),即反向把 H、W 维度恢复为前向输入的空间尺寸。
2.2 计算公式
对于一个二维插值点(N, C, h, w),其反向梯度gradInput(N, C, h, w)的计算公式为:
$$ {gradInput(N, C, h, w)}=\sum_{i=0}^{3}\sum_{j=0}^{3}{W(i, j)}*{f(h_i, w_j)} $$
其中:
i和j是权重W(i, j)的索引变量;f(h_i, w_j)是gradOutput在(h_i, w_j)处的像素值;W(i, j)是双三次抗锯齿插值的权重函数。
2.3 缩放比例 scaleH / scaleW
正向与反向计算共享同一套缩放比例定义,scaleH与scaleW由alignCorners与scalesH/scalesW联合决定:
$$ scaleH =\begin{cases} (inputSize[2]-1) / (outputSize[0]-1) & alignCorners=true \ 1 / scalesH & alignCorners=false\ &\ scalesH>0\ inputSize[2] / outputSize[0] & otherwise \end{cases} $$
$$ scaleW =\begin{cases} (inputSize[3]-1) / (outputSize[1]-1) & alignCorners=true \ 1 / scalesW & alignCorners=false\ &\ scalesW>0\ inputSize[3] / outputSize[1] & otherwise \end{cases} $$
这里需要注意两点:
- alignCorners 的语义:
alignCorners为true表示输入和输出张量的角像素点对齐(缩放比例基于"尺寸减一"计算);为false表示输入和输出张量的边像素点对齐(缩放比例基于原始尺寸计算)。这一语义与 PyTorch 中F.interpolate的align_corners参数一致。 - 反向算子的缩放比例是倒数关系:在 aclnn_upsample_bicubic2d_aa_grad.cpp 中,
ComputeBicubic2dAABackwardScales函数实现了1.0 / scale(当scale > 0时)的换算逻辑;若scale未指定(≤ 0),则退化为input_size / output_size的尺寸比值。反向缩放比例越小,意味着前向上采样倍数越大。
2.4 权重函数 W(d)
双三次抗锯齿插值的权重函数使用经典的a = -0.5三次样条核:
$$ W(d) =\begin{cases} (a+2)|d|^3-(a+3)|d|^2+1 & |d|\leq1 \ a|d|^3-5a|d|^2+8a|d|-4a & 1<|d|<2 \ 0 & otherwise \end{cases} $$
其中:
- $a=-0.5$(抗锯齿场景下的标准取值,对应三次样条核的 Catmull-Rom 变体);
- $d = |(h, w) - (h_i, w_j)|$,即插值点与采样点之间的距离。
a = -0.5与正向算子 UpsampleBicubic2dAA README 中的权重定义完全一致,确保了前反向计算的一致性。从权重公式可以看出,每个输出像素的反向梯度由周围至多 4×4 邻域内的梯度像素加权累加得到,这与正向计算中每个输出点对 4×4 邻域采样点加权求和是对偶的。
三、参数说明
以下参数表格来自 image/upsample_bicubic2d_aa_grad/README.md 的"参数说明"章节,并结合 aclnnUpsampleBicubic2dAAGrad 接口文档 中的接口语义做了补充:
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| grad_output | 输入 | 表示反向计算的梯度 Tensor,对应公式中的gradOutput。数据类型与输出grad_input一致 | FLOAT32、FLOAT16、BFLOAT16 | ND |
| output_size | 属性 | 表示输入grad_output在 H 和 W 维度上的空间大小,对应公式中的outputSize。size 为 2,且各元素均大于零 | INT64 | - |
| input_size | 属性 | 表示输出grad_input分别在 N、C、H 和 W 维度上的空间大小,对应公式中的inputSize。size 为 4,且各元素均大于零 | INT64 | - |
| align_corners | 可选属性 | 决定是否对齐角像素点,对应公式中的alignCorners。为true时输入和输出张量的角像素点被对齐,否则不对齐。默认值为false | BOOL | - |
| scales_h | 可选属性 | 表示输出grad_input的 height 维度乘数,对应公式中的scalesH。默认值为空 | FLOAT32 | - |
| scales_w | 可选属性 | 表示输出grad_input的 width 维度乘数,对应公式中的scalesW。默认值为空 | FLOAT32 | - |
| grad_input | 输出 | 表示反向计算的输出张量,对应公式中的gradInput。数据类型与输入grad_output一致 | FLOAT32、FLOAT16、BFLOAT16 | ND |
在 aclnn 接口层面,上述属性会以入参形式出现在函数签名中:outputSize与inputSize封装为aclIntArray,alignCorners为bool,scalesH/scalesW为double(详见 aclnn_upsample_bicubic2d_aa_grad.h)。算子定义文件 upsample_bicubic2d_aa_grad_def.cpp 中的注册信息与文档一致:grad_output/grad_input支持DT_FLOAT16、DT_FLOAT、DT_BF16三种数据类型,格式为 ND;output_size、input_size为必选ListInt属性;align_corners为可选 BOOL 属性(默认false);scales_h、scales_w为可选 Float 属性。
数据格式说明:当数据格式为 ND 时,默认按照 NCHW 格式处理;接口层面也接受显式的 NCHW 格式(ACL_FORMAT_NCHW)。
四、约束说明
4.1 算子级约束
image/upsample_bicubic2d_aa_grad/README.md 中标注算子本身"无"额外约束,但接口文档在更细的粒度上给出了如下限制,这些约束在调用时同样必须满足:
shape 与内存约束(适用于全部支持产品):
gradOutput、out每个维度的取值小于等于2^20;- 参数
out的 N 轴和 C 轴与gradOutput保持一致; - 内存占用需满足:
$$ (gradOutput_H \times gradOutput_W + out_H \times out_W + gradOutput_H \times out_W) \times N \times C \times sizeof(dtype) < 60 \times 1024 \times 1024 \times 1024 $$
其中N、C为输入输出张量的 N、C 轴大小,dtype为输入张量的数据类型;
- 额外要求 $N \times C \times gradOutput_H < 2^{31}$。
缩放比例一致性约束:参数inputSize、outputSize、scalesH、scalesW需要满足:
$$ outputSize_H = floor(inputSize_H \times scalesH) $$
$$ outputSize_W = floor(inputSize_W \times scalesW) $$
Atlas A2/A3 系列缩小倍数约束:反向接口的输入数据缩小倍数必须小于等于 50:
$$ outputSize_H / 输出shape的高度H \le 50 $$
$$ outputSize_W / 输出shape的宽度W \le 50 $$
这一约束与 Host 侧参数校验代码中MIN_SUPPORT_SCALE = 0.02(即最小支持缩放比例,aclnn_upsample_bicubic2d_aa_grad.cpp)的检查相互呼应:反向缩放比例不能过小,否则会触发ACLNN_ERR_PARAM_INVALID错误。
4.2 确定性计算
- Atlas A3 / Atlas A2 系列:
aclnnUpsampleBicubic2dAAGrad默认使用确定性实现; - Ascend 950PR / Ascend 950DT:默认使用非确定性实现,支持通过
aclrtCtxSetSysParamOpt开启确定性计算。
关于确定性计算的更多背景可参考 docs/zh/context/determinism_compute.md。
五、调用方式:两段式 aclnn 接口
5.1 两段式接口总览
该算子遵循 CANN 算子库统一的两段式接口调用规范:必须先调用aclnnUpsampleBicubic2dAAGradGetWorkspaceSize接口获取计算所需 workspace 大小以及包含算子计算流程的执行器,再调用aclnnUpsampleBicubic2dAAGrad接口执行计算。
第一段接口的函数原型:
aclnnStatus aclnnUpsampleBicubic2dAAGradGetWorkspaceSize( const aclTensor *gradOutput, const aclIntArray *outputSize, const aclIntArray *inputSize, bool alignCorners, double scalesH, double scalesW, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor)第二段接口的函数原型:
aclnnStatus aclnnUpsampleBicubic2dAAGrad( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)两个接口均返回aclnnStatus状态码,具体取值参见 aclnn 返回码。
5.2 第一段接口参数详解
| 参数名 | 输入/输出 | 描述 | 使用说明 | 数据类型 | 数据格式 | shape | 非连续 Tensor |
|---|---|---|---|---|---|---|---|
| gradOutput(aclTensor*) | 输入 | 反向计算的梯度 Tensor,对应gradOutput | 不支持空 Tensor;ND 格式下默认按 NCHW 处理 | FLOAT32、FLOAT16、BFLOAT16 | NCHW、ND | 4 | √ |
| outputSize(aclIntArray*) | 输入 | gradOutput在 H、W 维的空间大小 | size 为 2,各元素大于零 | INT64 | - | - | - |
| inputSize(aclIntArray*) | 输入 | 输出out在 N、C、H、W 维的空间大小 | size 为 4,各元素大于零 | INT64 | - | - | - |
| alignCorners(bool) | 输入 | 是否对齐角像素点 | true对齐,否则不对齐 | - | - | - | - |
| scalesH(double) | 输入 | 输出out的 height 维度乘数 | 不能传入负值 | - | - | - | - |
| scalesW(double) | 输入 | 输出out的 width 维度乘数 | 不能传入负值 | - | - | - | - |
| out(aclTensor*) | 输出 | 反向计算的输出张量,对应gradInput | 不支持空 Tensor;数据类型与格式与gradOutput保持一致 | FLOAT32、FLOAT16、BFLOAT16 | NCHW、ND | 4 | √ |
| workspaceSize(uint64_t*) | 输出 | 返回需要在 Device 侧申请的 workspace 大小 | - | - | - | - | - |
| executor(aclOpExecutor**) | 输出 | 返回 op 执行器,包含算子计算流程 | - | - | - | - | - |
第一段接口的常见报错场景(返回码与错误码参见 aclnn 返回码):
| 返回码 | 错误码 | 描述 |
|---|---|---|
| ACLNN_ERR_PARAM_NULLPTR | 161001 | 传入的 gradOutput、inputSize 或 out 是空指针 |
| ACLNN_ERR_PARAM_INVALID | 161002 | gradOutput 或 out 的数据类型不在支持范围内;gradOutput 与 out 数据类型不一致;gradOutput 的 shape 不是 4 维;inputSize 的 H 维或 W 维取值小于 1;inputSize 的 size 不为 4;outputSize 的 size 不为 2;scalesH 或 scalesW 为负值 |
这些校验逻辑都可以在源码 aclnn_upsample_bicubic2d_aa_grad.cpp 中逐一对号入座:CheckNotNull负责空指针检查、CheckDtypeValid负责数据类型与一致性检查、CheckShape负责维度与 size 检查、CheckScalesValid负责负数检查、CheckInputElement负责元素非零与格式检查。
5.3 第二段接口参数详解
| 参数名 | 输入/输出 | 描述 |
|---|---|---|
| workspace | 输入 | 在 Device 侧申请的 workspace 内存地址 |
| workspaceSize | 输入 | 在 Device 侧申请的 workspace 大小,由第一段接口获取 |
| executor | 输入 | op 执行器,包含算子计算流程 |
| stream | 输入 | 指定执行任务的 AscendCL Stream |
六、完整调用示例
仓库在 examples/test_aclnn_upsample_bicubic2d_aa_grad.cpp 提供了完整的可直接参考的调用样例。完整代码框架如下(编译与执行步骤请参考 编译与运行样例):
#include <iostream> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_upsample_bicubic2d_aa_grad.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 CreateAclNchTensor(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_NCHW, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. (固定写法)device/stream初始化,参考acl API手册 int32_t deviceId = 0; // 根据自己的实际device填写 aclrtStream stream; auto ret = Init(deviceId, &stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclInit failed. ERROR: %d\n", ret); return ret); // 2. 构造输入与输出,需要根据API的接口自定义构造 std::vector<int64_t> inputShape = {1, 1, 4, 2}; // gradOutput shape: NCHW std::vector<int64_t> outShape = {1, 1, 8, 4}; // gradInput shape: NCHW void* inputDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclTensor* input = nullptr; aclTensor* out = nullptr; std::vector<float> inputHostData = {0, 1, 2, 3, 4, 5, 6, 7}; const size_t kSize32 = 32U; std::vector<float> outHostData(kSize32, 0); const size_t kSize4 = 4U; std::vector<int64_t> outputSize = {kSize4, 2}; // gradOutput的H、W std::vector<int64_t> inputSize = {1, 1, 8, 4}; // gradInput的N、C、H、W bool alignCorners = true; double scalesH = 0.5; double scalesW = 0.5; // 创建input aclTensor ret = CreateAclNchTensor(inputHostData, inputShape, &inputDeviceAddr, aclDataType::ACL_FLOAT, &input); CHECK_RET(ret == ACL_SUCCESS, return ret); // 创建input aclIntArray auto outputSizeArray = aclCreateIntArray(outputSize.data(), 2); auto inputSizeArray = aclCreateIntArray(inputSize.data(), 4); // 创建out aclTensor ret = CreateAclNchTensor(outHostData, outShape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out); CHECK_RET(ret == ACL_SUCCESS, return ret); // 3. 调用CANN算子库API uint64_t workspaceSize = 0; aclOpExecutor* executor; // 调用aclnnUpsampleBicubic2dAAGrad第一段接口 ret = aclnnUpsampleBicubic2dAAGradGetWorkspaceSize(input, outputSizeArray, inputSizeArray, alignCorners, scalesH, scalesW, out, &workspaceSize, &executor); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnUpsampleBicubic2dAAGradGetWorkspaceSize 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); } // 调用aclnnUpsampleBicubic2dAAGrad第二段接口 ret = aclnnUpsampleBicubic2dAAGrad(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret == ACL_SUCCESS, LOG_PRINT("aclnnUpsampleBicubic2dAAGrad 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(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和aclIntArray aclDestroyTensor(input); aclDestroyTensor(out); // 7. 释放device资源 aclrtFree(inputDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize > 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }该示例的关键调用步骤可以归纳为 7 步,与 CANN aclnn 接口的标准调用范式一致:
- 初始化:
aclInit→aclrtSetDevice→aclrtCreateStream; - 构造张量:通过
aclrtMalloc申请 Device 内存、aclrtMemcpy拷贝 Host 数据,再以aclCreateTensor创建aclTensor(注意按连续 NCHW 布局计算 strides);outputSize/inputSize用aclCreateIntArray封装; - 第一段接口:
aclnnUpsampleBicubic2dAAGradGetWorkspaceSize完成参数校验并返回workspaceSize与executor; - 申请 workspace:当
workspaceSize > 0时用aclrtMalloc申请 Device 侧 workspace; - 第二段接口:
aclnnUpsampleBicubic2dAAGrad在指定 stream 上执行计算; - 同步与取数:
aclrtSynchronizeStream等待计算完成,aclrtMemcpy将结果从 Device 拷贝回 Host 并打印; - 资源释放:
aclDestroyTensor→aclrtFree→aclrtDestroyStream→aclrtResetDevice→aclFinalize。
示例中的具体参数为:gradOutputshape(1,1,4,2)(FLOAT32),outputSize = [4, 2],inputSize = [1,1,8,4],alignCorners = true,scalesH = scalesW = 0.5,输出gradInputshape(1,1,8,4),即把4×2的梯度张量反向传播回8×4的输入空间。
七、源码级实现剖析
为了让读者对该算子有更深层的理解,下面结合仓库源码,从算子注册、Host 侧 Tiling 到 Kernel 执行梳理其完整实现链路。
7.1 算子定义与注册
upsample_bicubic2d_aa_grad_def.cpp 通过OP_ADD(UpsampleBicubic2dAAGrad)注册算子,核心声明为:
Input("grad_output"):必选,支持 FLOAT16/FLOAT/BF16,格式 ND;Output("grad_input"):必选,数据类型与输入一致;Attr("output_size")(必选 ListInt)、Attr("input_size")(必选 ListInt)、Attr("align_corners")(可选 BOOL,默认 false)、Attr("scales_h")/Attr("scales_w")(可选 Float);- AICore 配置:
ascend910b、ascend910_93使用基础配置;ascend950使用开启动态编译、动态 Rank、动态 Shape 的regbaseConfig,并指定 op 文件为upsample_bicubic2d_aa_grad_apt。
7.2 Host 侧 Tiling:数据切分与 workspace 规划
Tiling 逻辑位于 upsample_bicubic2d_aa_grad_tiling.cpp 与 upsample_bicubic2d_aa_grad_tiling.h。从 Tiling 数据结构UpsampleBicubicAAGradTilingData可以看出该算子的实现策略:
- 记录
scale_w/scale_h及其倒数invscale_w/invscale_h、支撑半径support_w/support_h、最大插值尺寸max_interp_size_w/h,供 Kernel 重建插值权重; - 维护
slideStartList/slideEndList/tailSlideStartList/tailSlideEndList/tailRowStartList/tailRowEndList等 H/W 两个方向上的滑窗区间列表(MAX_CORE_CONT = 50,对应文档中缩小倍数上限 50 的实现约束),用于多核并行切分; - 内嵌两个
TCubeTiling(matmulTiling_w与matmulTiling_h),从源码结构可以推断该算子在 Kernel 侧通过 Cube 矩阵乘单元(Matmul)分别对 W 与 H 方向的权重矩阵与梯度数据做矩阵乘累加,把"4×4 邻域加权求和"转化为矩阵乘形式以提升计算效率。
此外,WORK_SPACE_SIZE = 32 * 1024 * 1024(32MB)等常量定义了 workspace 的规划基准,Tiling 阶段按核数将中间矩阵大小写入 tilingData,供 Kernel 侧通过GetUserWorkspace获取。
7.3 Kernel 侧执行入口
upsample_bicubic2d_aa_grad.cpp 是 Kernel 入口函数,通过TILING_KEY_IS(1/2/3)分别派发到UpSampleBicubic2dAAGradND<half>、UpSampleBicubic2dAAGradND<float>、UpSampleBicubic2dAAGradND<bfloat16_t>三个模板实例(对应 FLOAT16、FLOAT32、BFLOAT16),并通过REGIST_MATMUL_OBJ注册 W/H 两个方向的 Matmul 对象后执行Init与Process。arch35 目录下(arch35)存放了 Ascend 950 架构(ascend950配置)的 SIMT 与 DataCopy 相关实现,体现了不同架构下的 Kernel 差异化适配。
7.4 测试验证
仓库为该算子提供了完整的测试覆盖:
- Host 侧单测:test_aclnn_upsample_bicubic2d_aa_grad.cpp(aclnn 接口层)与 test_upsample_bicubic2d_aa_grad_tiling.cpp(Tiling 层);
- Kernel 侧单测:test_upsample_bicubic2d_aa_grad.cpp,配套 gen_data.py 与 compare_data.py 进行数据生成与结果比对;
- ST 测试:atk_aclnnUpsampleBicubic2dAAGrad.json 与 executor_aclnnUpsampleBicubic2dAAGrad.py 提供了基于 ATK 框架的端到端验证入口。
八、使用建议与总结
- 前反向配套使用:
UpsampleBicubic2dAAGrad与 UpsampleBicubic2dAA 共享同一套align_corners、scales_h/scales_w语义与a = -0.5的权重定义,训练场景中反向传播时应保持与前向一致的属性配置,避免梯度数值偏差; - 注意反向缩小倍数限制:Atlas A2/A3 系列上反向输入缩小倍数须 ≤ 50(等价于反向缩放比例 ≥ 0.02),超出会触发参数校验错误;
- 充分利用非连续 Tensor 支持:
gradOutput与out均支持非连续 Tensor(如切片、转置视图),可直接接入更复杂的自动求导图,无需手动contiguous(相关背景可参考 docs/zh/context/non_contiguous_tensor.md); - 确定性要求:若训练过程需要严格的逐次可复现结果,在 Atlas A2/A3 上默认即为确定性实现;在 Ascend 950 上需通过
aclrtCtxSetSysParamOpt显式开启。
总之,UpsampleBicubic2dAAGrad 是 CANN ops-cv 中实现"抗锯齿双三次上采样"梯度回传的标准算子:理解其 4×4 邻域加权求和的计算本质、掌握align_corners与scales的参数语义,再配合两段式 aclnn 接口的标准调用范式,即可在 Atlas A2/A3 与 Ascend 950 系列平台上正确、高效地完成图像上采样训练链路的反向计算。
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考