十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CANN ops-cv 算子开发指南:aclnnUpsampleBicubic2dBackward 双三次上采样反向传播接口深度解析

CANN ops-cv 算子开发指南:aclnnUpsampleBicubic2dBackward 双三次上采样反向传播接口深度解析 CANN ops-cv 算子开发指南aclnnUpsampleBicubic2dBackward 双三次上采样反向传播接口深度解析【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv本文基于 CANN 开源算子仓库 ops-cv 中的 image/upsample_bicubic2d_grad 算子模块系统讲解aclnnUpsampleBicubic2dBackward两段式接口的功能原理、数学公式、参数约束、返回码与确定性计算规则并结合 op_api、tiling、kernel 源码剖析其底层实现路径。读完本文你将能够理解该梯度算子的完整调用契约掌握在 Atlas 训练/推理产品上编写正确、高效调用代码的方法。一、算子功能与产品支持情况aclnnUpsampleBicubic2dBackward是 aclnnUpsampleBicubic2d2D 双三次上采样的反向传播接口。在前向过程中输入张量self的 shape 为 (N, C, H, W)输出out的 shape 为 (N, C, outputSize[0], outputSize[1])反向时接口接收上采样结果处的梯度gradOut将其散射回原始输入尺寸输出gradInput的 shape 为 (N, C, inputSize[2], inputSize[3])。该算子在仓库 image/upsample_bicubic2d_grad/README.md 中注册的产品支持情况如下产品是否支持Ascend 950PR/Ascend 950DT不支持Atlas A3 训练系列产品/Atlas A3 推理系列产品支持Atlas A2 训练系列产品/Atlas A2 推理系列产品支持Atlas 200I/500 A2 推理产品支持Atlas 推理系列产品支持Atlas 训练系列产品不支持说明README 中的支持矩阵与接口文档aclnnUpsampleBicubic2dBackward.md的产品列表存在差异接口文档标注 Ascend 950PR/950DT 与 Atlas 训练系列产品“支持”。两者口径不同README 反映算子静态注册的 AICore 配置接口文档反映 aclnn 层接口能力实际使用请以所安装 CANN 版本的产品兼容性说明为准。从算子定义文件 upsample_bicubic2d_grad_def.cpp 可以看到算子UpsampleBicubic2dGrad的 AICore 配置仅注册了ascend910b与ascend910_93两类 SoC这与 README 中“Atlas 训练系列产品不支持”的结论一致。二、反向传播数学原理2.1 梯度散射公式对于一个二维插值点 (N, C, h, w)反向传播的梯度gradInput(N, C, h, w)表示为$$ {gradInput(N, C, h, w)}\sum_{i0}^{3}\sum_{j0}^{3}{W(i, j)}*{f(h_i, w_j)} $$其中f(h_i, w_j)是gradOut在 (h_i, w_j) 处的像素值W(i, j)是双三次抗锯齿插值权重i、j 是权重矩阵的索引变量。2.2 尺度因子计算scaleH 与 scaleW 决定插值时的采样映射关系按alignCorners与scalesH/scalesW的取值分三种情况$$ scaleH \begin{cases} (inputSize[2]-1) / (outputSize[0]-1) alignCornerstrue \ 1 / scalesH alignCornersfalsescalesH0\ inputSize[2] / outputSize[0] otherwise \end{cases} $$$$ scaleW \begin{cases} (inputSize[3]-1) / (outputSize[1]-1) alignCornerstrue \ 1 / scalesW alignCornersfalsescalesW0\ inputSize[3] / outputSize[1] otherwise \end{cases} $$alignCorners 为 true输入与输出张量的角像素点对齐尺度由 (尺寸-1) 的比值决定alignCorners 为 false 且 scales 0使用调用者传入的缩放因子的倒数其他情况使用尺寸直接比值。这一分支逻辑在 op_api 源码 aclnn_upsample_bicubic_2d_backward.cpp 的computeScalelambda 中逐行对应实现alignCorner为真时返回(inputSizes-1)/(outputSizes-1)outputSizes 为 1 时返回 0scale 0时返回1/scale否则返回inputSizes/outputSizes。2.3 双三次插值核权重权重函数W(d)即 Catmull-Rom 样条核a -0.75$$ W(d) \begin{cases} (a2)|d|^3-(a3)|d|^21 |d|\leq1 \ a|d|^3-5a|d|^28a|d|-4a 1|d|2 \ 0 otherwise \end{cases} $$其中 a -0.75d |(h, w) - (h_i, w_j)| 为目标采样点与邻近像素的距离。反向传播正是把输出梯度按这套权重散射回输入空间。三、两段式接口与函数原型每个 aclnn 算子都遵循两段式接口设计必须先调用 GetWorkspaceSize 段接口完成入参校验、推导计算流程并返回所需 workspace 大小与执行器再调用执行段接口传入 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)四、aclnnUpsampleBicubic2dBackwardGetWorkspaceSize 参数详解4.1 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续TensorgradOutaclTensor*输入反向计算的梯度 Tensor对应公式中的gradOut不支持空 Tensor数据类型与gradInput一致数据格式为 ND 时默认按 NCHW 处理FLOAT32、FLOAT16、BFLOAT16NCHW、ND、NHWC4√outputSizeaclIntArray*输入gradOut在 H 和 W 维上的空间大小对应公式outputSizesize 为 2且各元素均大于零INT64---inputSizeaclIntArray*输入gradInput在 N、C、H、W 或 N、H、W、C 维上的空间大小对应公式inputSizesize 为 4且各元素均大于零INT64---alignCornersbool输入是否对齐角像素点对应公式alignCornersTrue 时输入输出张量角像素点对齐否则不对齐----scalesHdouble输入gradInput的 height 维度乘数对应公式scalesH-----scalesWdouble输入gradInput的 width 维度乘数对应公式scalesW-----gradInputaclTensor*输出反向计算的输出张量对应公式gradInput不支持空 Tensor数据类型、数据格式与gradOut一致N、C 轴与gradOut保持一致FLOAT32、FLOAT16、BFLOAT16NCHW、ND、NHWC4√workspaceSizeuint64_t*输出需在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出op 执行器包含算子计算流程-----平台差异化限制Atlas 训练系列产品gradOut、gradInput的数据类型不支持 BFLOAT16数据格式不支持 NHWC。Atlas A2 训练/推理系列产品、Atlas A3 训练/推理系列产品gradOut、gradInput的数据格式不支持 NHWC。从源码 aclnn_upsample_bicubic_2d_backward.cpp 可以看到数据类型支持列表按平台区分非 910B 平台如 Atlas 训练系列仅支持DT_FLOAT16与DT_FLOAT910B 及更新平台额外支持DT_BF16。格式校验逻辑CheckShape同样按平台区分非 910B 平台仅接受 ND 与 NCHW。4.2 返回值与第一段接口报错场景返回值类型为aclnnStatus具体参见 aclnn返回码。第一段接口完成入参校验以下场景报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001gradOut、outputSize、inputSize 或 gradInput 为空指针ACLNN_ERR_PARAM_INVALID161002gradOut 的数据类型和数据格式不在支持范围内gradOut 和 gradInput 的数据类型不一致gradOut 的维度不为 4 维outputSize 的 size 不等于 2outputSize 的某个元素值小于 1inputSize 的 size 不等于 4inputSize 的某个元素值小于 1gradOut 与 inputSize 在 N、C 维度上的 size 不同gradOut 在 H、W 维上的 size 与 outputSize[0]、outputSize[1] 不一致gradOut 和 gradInput 的 N/C 轴维度大小不相等gradOut 和 gradInput 的数据格式不在支持范围之内这些校验在源码中被拆分为CheckNotNull、CheckInputElement、CheckShape、CheckDtypeValid、CheckUplimit等静态函数见 aclnn_upsample_bicubic_2d_backward.cpp并在第一段接口入口依次执行。五、aclnnUpsampleBicubic2dBackward 执行段接口参数名输入/输出描述workspace输入Device 侧申请的 workspace 内存地址workspaceSize输入Device 侧 workspace 大小由第一段接口获取executor输入op 执行器包含算子计算流程stream输入指定执行任务的 Stream执行段接口内部仅调用CommonOpExecutorRun完成异步下发见 aclnn_upsample_bicubic_2d_backward.cpp真正的计算由第一段接口构建的算子图在 NPU 上执行。六、约束说明6.1 维度上限Atlas A2/A3 系列Atlas A3 与 Atlas A2 训练/推理系列产品需满足gradOut、gradInput 的所有维度取值均小于等于 (2^31 - 1)N * C * outputSize_H 2^31 - 1N、C 为输入/输出的 N、C 轴。该约束在 CheckUplimit 中实现针对 910BDAV_2201架构逐维检查是否超过INT32_MAX并检查N * C * outputH的上限。6.2 scale 与 shape 的一致性约束参数 inputSize、outputSize、scalesH、scalesW 需满足$$ outputSize_H floor(inputSize_H * scalesH) $$$$ outputSize_W floor(inputSize_W * scalesW) $$6.3 确定性计算约束aclnnUpsampleBicubic2dBackward默认是非确定性实现支持通过aclrtCtxSetSysParamOpt开启确定性计算。开启后当满足以下任一条件时不支持确定性计算inputSize_W 130000inputSize_H / outputSize_H 50inputSize_W / outputSize_W 50 且 inputSize_N * inputSize_C * inputSize_H inputSize_W * 0.5inputSize_H / outputSize_H 0.02 且 inputSize_W / outputSize_W 0.02 且 inputSize_N * inputSize_C * inputSize_H inputSize_W * 10000。这些阈值的常量MAX_W 130000、MAX_SCALE 50、HALF_ONE 0.5、MAX_W_RADIO 10000定义在源码 aclnn_upsample_bicubic_2d_backward.cpp由CheckCanCalc函数第 248-273 行逐条判断。确定性开关通过aclrtCtxGetSysParamOpt(ACL_OPT_DETERMINISTIC, ...)读取第 417 行读取失败时降级为非确定性。七、源码实现路径剖析7.1 op_api 层的多分支执行策略第一段接口按平台与条件选择三条计算路径aclnn_upsample_bicubic_2d_backward.cppRegBase910_93 等新平台路径将 gradOut、gradInput 转为连续 Tensor 后直接调用l0op::ResizeBicubicV2Grad内核算子再经ViewCopy写回 gradInput910B 快速路径当满足CheckCanCalcscale 与 shape 在安全区间内时若输入输出 H/W 完全相同则直接ViewCopy拷贝否则将 FP16/BF16 先 Cast 为 FP32 计算以保证精度调用l0op::UpsampleBicubic2dGrad后 Cast 回原类型通用路径将 gradOut 转置为 (H, W, N, C) 布局permuteHWNCList {2, 3, 0, 1}调用l0op::ResizeGradDCUBIC_MODE完成梯度散射最后经 Reshape、Transpose、ViewCopy 恢复输出布局该路径下若开启确定性且输入为 FP16会先 Cast 到 FP32 再计算FP16_DETERMINSTIC_CAST。7.2 tiling 层的双模式调度tiling 实现 upsample_bicubic2d_grad_tiling.cpp 依据IsDeterministicCalc选择两种调度模式BASE_MODETilingKey 10000001将 H、W 维度按 fractalNUM_FRACTAL切块通过matmul_tiling::MatmulApiTiling为 H、W 两个方向的矩阵乘分别生成 tiling 参数MMParamH / MMParamW并计算 workspace含 ratio 矩阵与中间结果缓冲见 SetLaunchInfoDETERMINISTIC_MODETilingKey 10000002采用滑动窗口slideSize切分计算单核 K 维singleCoreKH/KW、中间矩阵尺寸intermediateMatrixSize与 ratio 矩阵尺寸workspace 相应放大见 SetLaunchInfoDC。内核入口 upsample_bicubic2d_grad.cpp 按数据类型FLOAT/FLOAT16/BF16与 TilingKey 分别实例化UpsampleBicubic2dGradBase基础模式和UpsampleBicubic2dGradDCND确定性模式两个算子类。7.3 算子静态配置ascend910b 下的 binary.json 为每种 dtypefloat16/float32/bfloat16注册了独立 kernel 二进制属性默认值分别为align_cornersfalse、scales_h0.0、scales_w0.0与 op_def 中的可选属性声明一致scales_h/scales_w 默认 0.0align_corners 默认 false。八、完整调用示例以下示例代码同时存在于接口文档与 examples/test_aclnn_upsample_bicubic2d_grad.cpp展示了完整的“初始化 → 建 Tensor → 两段式调用 → 同步 → 取结果 → 释放资源”流程。具体编译与执行过程请参考编译与运行样例。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_upsample_bicubic_2d_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::vectorint64_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::vectorT hostData, const std::vectorint64_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::vectorint64_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手册 // 根据自己的实际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::vectorint64_t selfShape {1, 1, 2, 2}; std::vectorint64_t outShape {1, 1, 3, 3}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {1, 2, 3, 4.1}; std::vectorfloat outHostData {0, 0, 0, 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::vectorint64_t outArraySize {2, 2}; const aclIntArray* outputSize aclCreateIntArray(outArraySize.data(), outArraySize.size()); CHECK_RET(outputSize ! nullptr, return ACL_ERROR_INTERNAL_ERROR); std::vectorint64_t inputArraySize {1, 1, 3, 3}; 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; const double scalesH 1.1; const double scalesW 1.1; // 调用aclnnUpsampleBicubic2dBackward第一段接口 ret aclnnUpsampleBicubic2dBackwardGetWorkspaceSize(self, outputSize, inputSize, 1, scalesH, scalesW, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnUpsampleBicubic2dBackwardGetWorkspaceSize 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); } // 调用aclnnUpsampleBicubic2dBackward第二段接口 ret aclnnUpsampleBicubic2dBackward(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnUpsampleBicubic2dBackward 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::vectorfloat 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需要根据具体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; }示例要点解读示例中输入self即 gradOutshape 为 (1, 1, 2, 2)inputSize {1, 1, 3, 3}指定输出 gradInput 的 shape 为 (1, 1, 3, 3)outputSize {2, 2}与 gradOut 的 H/W 维一致alignCorners传 1truescalesH/scalesW 传 1.1此时因 alignCorners 为 true实际 scale 由(inputSize-1)/(outputSize-1)决定scales 仅作占位由于 gradInputout的 shape 与 inputSize 在 N、C 轴一致满足 4.1 节的校验要求。九、测试用例与验证仓库为算子提供了两层测试支撑op_api 单测test_aclnn_upsample_bicubic_2d_backward.cpp覆盖 float32/float16 正常路径期望ACLNN_SUCCESS以及 double、int8、uint8 等不支持类型期望ACLNN_ERR_PARAM_INVALID可用于验证第一段接口的入参校验行为Python 单测aclnnUpsampleBicubic2dBackward.py 与 ST 级测试tests/st 目录下的 atk JSON 与 executor 脚本用于端到端数值验证tiling 单测test_upsample_bicubic2d_grad_tiling.cpp 与kernel 单测test_upsample_bicubic2d_grad.cpp 分别校验 tiling 数据与内核计算逻辑。十、总结aclnnUpsampleBicubic2dBackward是 ops-cv 中实现双三次上采样梯度回传的标准 aclnn 接口其核心价值在于以2^31-1维度上限和确定性开关为边界通过 op_api 层的多平台路径选择与 tiling 层的双模式调度在 Atlas A2/A3 等产品上高效完成梯度散射。开发者只需严格遵循两段式调用流程、保证inputSize/outputSize/scales之间的一致性约束即可获得正确、可复现的反向梯度结果。如需深入算子图融合或确定性实现的细节可继续阅读仓库 image/upsample_bicubic2d_grad 目录下的 op_host、op_kernel 与 tests 源码。【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表