CANN ops-cv ResizeBicubicV2Grad 算子深度解析:双三次插值反向传播的原理、接口与源码实现
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
导读
ResizeBicubicV2Grad 是 CANN ops-cv 算子库中图像缩放类算子的反向传播实现,用于计算双三次(Bicubic)插值调整图像在反向传播过程中的输入梯度,是训练场景中ResizeBicubicV2前向算子不可或缺的配套算子。本文以 image/resize_bicubic_v2_grad/README.md 为骨架,结合算子原型、Host 侧算子定义、Shape 推导、Tiling 策略、Kernel 多分支实现与单测用例,完整讲解该算子的数学原理、参数语义、aclnn 两段式调用方式与图模式构图方式,帮助你快速完成集成与二次开发。
功能说明与数学原理
算子定位
ResizeBicubicV2Grad 计算输入图像在双三次插值基础下的梯度。用一句话概括:输入是正向插值后的梯度图,输出是插值前的原始图像的梯度。对应关系为:
grads:正向双三次插值调整后的图(对应公式中的 Y,即反向传播中来自上游的梯度)。original_image:原图像(前向算子的输入)。y:正向 Resize 的输入梯度(即本算子的输出)。
从算子原型注释(image/resize_bicubic_v2_grad/op_graph/resize_bicubic_v2_grad_proto.h)可见,其与 PyTorch 的upsample_bicubic2d_backward算子兼容,属于第三框架兼容算子。
双三次插值核函数
双三次插值使用 4×4 邻域像素加权求和得到目标像素值,其权重核 W(x) 定义如下:
$$ W(x) = \begin{cases} (a + 2)|x|^3 - (a + 3)|x|^2 + 1 & \text{for } |x|≤1 \ a|x|^3 -5a|x|^2 + 8a|x| - 4a & \text{for } 1<|x|<2 \ 0 & \text{otherwise} \ \end{cases} $$
其中系数a = -0.75(对应 PyTorch 双三次插值的默认a取值)。核函数仅在距离小于 2 的范围内非零,这是后续 Kernel 实现中每个输出像素只需累加 4×4 邻域贡献的数学依据。
梯度传播公式
对于原始图像中的像素 (i, j),其梯度由所有参与插值的目标像素 (i', j') 的梯度按权重累加得到:
$$ \frac{\partial L}{\partial X_{i,j}} = \sum_{i'} \sum_{j'} \frac{\partial L}{\partial Y_{i',j'}} \times W(i' - i) \times W(j' - j) $$
即:上游梯度grads中的每个像素 (i', j'),按其在 H 轴与 W 轴上的核权重 W 反向摊回原始图像对应位置,H 轴权重与 W 轴权重相乘即为该目标像素对原始像素的贡献。这与 aclnn 接口文档(image/upsample_bicubic2d_grad/docs/aclnnUpsampleBicubic2dBackward.md)中描述的正向插值公式互为对偶。
坐标映射与 align_corners
梯度计算中源坐标与目标坐标的映射关系由align_corners属性决定,坐标缩放因子 scaleH / scaleW 的计算方式为:
$$ scaleH =\begin{cases} (inputSize[2]-1) / (outputSize[0]-1) & alignCorners=true \ 1 / scalesH & alignCorners=false&scalesH>0\ inputSize[2] / outputSize[0] & otherwise \end{cases} $$
W 轴同理。其中align_corners=true表示输入输出张量的角像素点对齐(保留角像素值),false表示使用半像素中心进行插值。
产品支持情况
当前仓库中 ResizeBicubicV2Grad 算子的产品支持矩阵如下(以 README 为准):
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | √ |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | × |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | × |
从算子定义(image/resize_bicubic_v2_grad/op_host/resize_bicubic_v2_grad_def.cpp)中可以印证,AICore().AddConfig("ascend950", aicoreConfig)仅为 ascend950 平台注册了 AI Core 配置;op_host/config/ascend950/ 目录下的编译配置文件也仅针对该平台存在。需要注意:本仓库内 ResizeBicubicV2Grad 与 aclnnUpsampleBicubic2dBackward 接口的支持范围并不完全一致,后者在 Atlas A2/A3、Atlas 训练系列等产品上也支持,集成时请以目标平台实际可用的接口为准。
参数说明
输入与输出
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| grads | 输入 | 正向双三次插值调整后的图,对应公式 Y。 | FLOAT16、FLOAT32、BFLOAT16 | NCHW、NHWC |
| original_image | 输入 | 原图像的高和宽。 | FLOAT16、FLOAT32、BFLOAT16 | NCHW、NHWC |
| y | 输出 | 正向 Resize 的输入梯度。 | FLOAT16、FLOAT32、BFLOAT16 | NCHW、NHWC |
属性参数
README 未单列属性,但算子原型(resize_bicubic_v2_grad_proto.h)与算子定义(resize_bicubic_v2_grad_def.cpp)明确声明了两个可选属性:
| 属性名 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| align_corners | Bool | false | 为 true 时,输入与输出张量的 4 个角像素中心对齐,保留角像素值;为 false 时,使用半像素中心计算插值。 |
| scales | ListFloat(2 个元素) | {0.0f, 0.0f} | 第一个元素表示 grads 中像素 H 轴下标与 y 中像素 H 轴下标的比值,第二个元素对应 W 轴。仅在 align_corners 为 true 且取值大于 0 时生效,否则按输入输出尺寸比值计算缩放。 |
关键约束(源码级确认)
- 输入
grads、original_image与输出y均为 4D 张量,数据格式仅支持 NCHW、NHWC。 original_image的 N、C 维度必须与grads一致,输出y的 shape、format、dtype 与original_image保持一致。- 约束说明为“无”,但结合 resize_bicubic_v2_grad_infershape.cpp 的校验逻辑,运行期仍会强校验:
grads格式必须为 NCHW/NHWC,original_image必须为 4D,且其 H、W 维(按格式区分索引:NCHW 下 hIdx=2,NHWC 下 hIdx=1)取值必须大于 0,否则返回GRAPH_FAILED。
调用说明
README 提供了两种调用方式:
| 调用方式 | 样例代码 | 说明 |
|---|---|---|
| aclnn 接口 | test_aclnn_resize_bicubic_v2_grad.cpp | 通过 aclnnUpsampleBicubic2dBackward 接口方式调用 ResizeBicubicV2Grad 算子。 |
| 图模式 | resize_bicubic_v2_grad_proto.h | 通过算子 IR 构图方式调用 ResizeBicubicV2Grad 算子。 |
aclnn 接口两段式调用
aclnn 接口遵循 CANN 统一的两段式调用范式(详见 docs/zh/context/two_phase_api.md):先调用GetWorkspaceSize接口完成入参校验并计算所需 workspace 大小,再调用执行接口真正下发计算。接口原型如下:
aclnnStatus aclnnUpsampleBicubic2dBackwardGetWorkspaceSize( const aclTensor* gradOut, const aclIntArray* outputSize, const aclIntArray* inputSize, const bool alignCorners, double scalesH, double scalesW, aclTensor* gradInput, uint64_t* workspaceSize, aclOpExecutor** executor) aclnnStatus aclnnUpsampleBicubic2dBackward( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)各参数语义:
| 参数名 | 输入/输出 | 描述 | 使用说明 |
|---|---|---|---|
| gradOut | 输入 | 反向计算的梯度 Tensor,对应公式中的gradOut(即算子输入grads)。 | 不支持空 Tensor;数据类型与gradInput一致;ND 格式默认按 NCHW 处理。 |
| outputSize | 输入 | gradOut在 H、W 维度上的空间大小。 | size 为 2,各元素大于 0。 |
| inputSize | 输入 | 输出gradInput在 N、C、H、W(或 N、H、W、C)维度上的空间大小。 | size 为 4,各元素大于 0。 |
| alignCorners | 输入 | 是否对齐角像素点。 | true 对齐角像素,false 不对齐。 |
| scalesH / scalesW | 输入 | 输出gradInput的 height / width 维度乘数。 | 对应公式中的scalesH、scalesW。 |
| gradInput | 输出 | 反向计算的输出张量(即算子输出y)。 | 数据类型、格式与gradOut一致,N、C 轴与gradOut一致。 |
| workspaceSize | 输出 | 需要在 Device 侧申请的 workspace 大小。 | 由第一段接口计算返回。 |
| executor | 输出 | op 执行器,封装了算子计算流程。 | 由第一段接口返回,供第二段接口使用。 |
第一段接口的常见返回码:ACLNN_ERR_PARAM_NULLPTR(错误码 161001,入参空指针)与ACLNN_ERR_PARAM_INVALID(错误码 161002,覆盖数据类型/格式越界、维度不为 4、outputSize 长度不为 2、元素小于 1、N/C 轴不一致等十余种入参校验场景),完整返回码说明见 docs/zh/context/aclnn_return_code.md。
完整可运行示例
仓库提供了完整可编译的示例 image/resize_bicubic_v2_grad/examples/test_aclnn_resize_bicubic_v2_grad.cpp,其调用流程可归纳为 7 步:
- 环境初始化:
aclInit→aclrtSetDevice→aclrtCreateStream。 - 构造 Tensor:将 host 数据
{1, 2, 3, 4.1}(shape{1,1,2,2},对应 2×2 的梯度图)经aclrtMalloc+aclrtMemcpy拷贝到 device 侧,并用aclCreateTensor创建grads对应的selfTensor;输出outshape 为{1,1,3,3}。 - 构造尺寸参数:
outputSize = {2, 2}、inputSize = {1, 1, 3, 3},分别通过aclCreateIntArray创建。 - 两段式调用:先调
aclnnUpsampleBicubic2dBackwardGetWorkspaceSize(self, outputSize, inputSize, /*alignCorners=*/1, /*scalesH=*/1.1, /*scalesW=*/1.1, out, &workspaceSize, &executor),按返回值申请 workspace,再调aclnnUpsampleBicubic2dBackward(workspaceAddr, workspaceSize, executor, stream)。 - 同步等待:
aclrtSynchronizeStream(stream)确保计算完成。 - 结果回拷:
aclrtMemcpy将 device 侧结果拷回 host 并逐元素打印。 - 资源释放:
aclDestroyTensor、aclDestroyIntArray、aclrtFree、aclrtDestroyStream、aclrtResetDevice、aclFinalize。
编译与运行方式可参考 docs/zh/context/compile_and_run_sample.md(需引入aclnnop/aclnn_upsample_bicubic_2d_backward.h头文件)。
图模式构图
图模式通过算子 IR 构图,直接引用REG_OP(ResizeBicubicV2Grad)注册的原型(resize_bicubic_v2_grad_proto.h)。其 IR 定义要点:
REG_OP(ResizeBicubicV2Grad) .INPUT(grads, TensorType({DT_FLOAT16, DT_FLOAT, DT_BF16})) .INPUT(original_image, TensorType({DT_FLOAT16, DT_FLOAT, DT_BF16})) .OUTPUT(y, TensorType({DT_FLOAT16, DT_FLOAT, DT_BF16})) .ATTR(align_corners, Bool, false) .ATTR(scales, ListFloat, {0.0f, 0.0f}) .OP_END_FACTORY_REG(ResizeBicubicV2Grad)源码级实现剖析
算子定义与 Shape/Dtype 推导
- 算子定义(resize_bicubic_v2_grad_def.cpp):声明两个必选输入
grads、original_image与一个必选输出y,数据类型覆盖DT_FLOAT16 / DT_FLOAT / DT_BF16,格式覆盖NCHW / NHWC;同时开启动态编译、动态 Rank 与动态 Shape 支持。 - Shape 推导(resize_bicubic_v2_grad_infershape.cpp):输出
y的 shape 直接继承original_image;对于未知 Rank 的输入则设置未知 Shape。Dtype 推导将输出类型设置为grads的类型(三者必须同为 FLOAT/FLOAT16/BF16 之一)。
Host 侧 Tiling 策略
Tiling 逻辑分布在 op_host/arch35/ 下的 4 个文件中,通过不同的 TilingKey 选择 Kernel 分支。从单测 test_resize_bicubic_v2_grad_tiling.cpp 可以清晰看到 TilingKey 的划分规律:
| TilingKey | 含义 | 单测验证场景 |
|---|---|---|
| 10000 / 10001 | SIMT 常规路径(32 位 / 64 位索引) | - |
| 20000 / 20001 | SIMT 确定性路径(32 位 / 64 位索引) | 225×32 → 113×32,align_corners=true |
| 20002 / 20003 | SIMT 确定性路径 + SplitK(32 位 / 64 位索引) | 大 shape 场景(如 32×2048×4096×32) |
| 30000 | 纯拷贝路径(AllCopy) | 输入输出尺寸完全一致(如 32×32 → 32×32) |
单测还揭示了两个实现细节:一是当grads与original_image尺寸相同时,算子退化为纯内存拷贝(TilingKey=30000);二是确定性路径的 TilingData 中新增了splitK / coresPerOutput / segsPerOutput三个字段,非 SplitK 场景取默认值。非 split-K 路径的 workspace 即系统预留区GetLibApiWorkSpaceSize(),在 ascend950 UT 环境(测试 faker 平台描述符)下为 UINT32_MAX(4294967295),与同仓 col2im 用例一致。
Kernel 多分支实现
Kernel 入口 resize_bicubic_v2_grad.cpp 依据 TilingKey 分发到 arch35/ 下的三类模板实现:
ResizeBicubicV2GradAllCopy:纯拷贝分支,直接搬移数据,无需插值计算。ResizeBicubicV2GradSimt:SIMT 常规实现,模板参数覆盖索引位宽(uint32_t/int32_t与uint64_t/int64_t)、数据格式(NCHW/NHWC)与 align_corners 开关(true/false),共计 8 种模板组合。ResizeBicubicV2GradSimtDetermine:确定性实现,支持Process()与ProcessSplitK()两种执行路径,后者用于超大 shape 下按 K 维切分、多核协作累加的场景,配合 workspace 使用。
Kernel 通过KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_MIX_AIV_1_0)声明任务类型,接收grads / originalImage / y / workspace / tiling五个 GM 地址参数,从 tiling 内存中恢复 TilingData 后按 key 分发执行。
测试与验证
算子配套的 UT 覆盖两层:
- Tiling 单测(test_resize_bicubic_v2_grad_tiling.cpp):使用
tiling_context_faker与tiling_case_executor构造运行上下文,校验不同 shape 组合(同尺寸拷贝、等比缩放、超大 shape)下生成的 TilingKey、TilingData 二进制内容与 workspace 大小是否符合预期。 - Infershape 单测(test_resize_bicubic_v2_grad_infershape.cpp):校验输出 shape 继承逻辑与非法输入(非 4D、H/W 为 0、格式不支持)的报错路径。
总结
ResizeBicubicV2Grad 是 CANN ops-cv 中一个典型的“动态 Shape + 多 Tiling 策略 + SIMT 多分支”算子:数学上严格遵循双三次核函数与梯度摊回公式,工程上通过 AllCopy / SIMT / SIMT-Determine(+SplitK) 三类路径在正确性、确定性与大 shape 性能之间取得平衡。无论你是通过aclnnUpsampleBicubic2dBackward两段式接口进行单算子调用,还是通过ResizeBicubicV2Grad算子 IR 构图接入训练图,均可从本文的参数语义与源码脉络出发,快速完成开发与问题定位。
【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库,实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考