CANN ops-cv ResizeBilinearV2 算子全解析:双线性插值原理、aclnnResize 两段式接口与 NPU 实现剖析 CANN ops-cv ResizeBilinearV2 算子全解析双线性插值原理、aclnnResize 两段式接口与 NPU 实现剖析【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cvResizeBilinearV2 是 CANN ops-cv 算子库中基于双线性插值将图像缩放到指定大小的图像处理算子可在 Ascend NPU 上以 AICORE 或 AICPU 两种方式加速执行。本文以image/resize_bilinear_v2目录下的算子 README 与 aclnnResize 接口文档 为主体结合算子定义、InferShape、tiling 与 kernel 源码系统讲解其插值数学原理、参数与约束、aclnnResize 两段式调用实战以及底层实现机制帮助你掌握在 CANN 环境下完成图像缩放算子的接入、调用与验证。产品支持情况ResizeBilinearV2 算子在不同昇腾产品上的支持情况如下表所示产品是否支持Ascend 950PR/Ascend 950DT√Atlas A3 训练系列产品/Atlas A3 推理系列产品√Atlas A2 训练系列产品/Atlas A2 推理系列产品√Atlas 200I/500 A2 推理产品×Atlas 推理系列产品√Atlas 训练系列产品√从 算子定义 可以看到算子通过AICore().AddConfig(ascend950, aicoreConfig)与AddConfig(mc62, ...)注册了 AICore 实现与上表中950 系列与 A2/A3 系列支持、310BAtlas 200I/500 A2不支持的能力矩阵对应。功能说明与双线性插值原理算子功能ResizeBilinearV2 的功能是使用双线性插值Bilinear Interpolation将输入图像调整到指定的大小。输入为四维 TensorNCHW 或 NHWC 布局通过输入size指定输出图像的高H和宽W。计算公式假设输入图像中存在四个已知像素点 $Q_{11}(x_1, y_1)$、$Q_{12}(x_1, y_2)$、$Q_{21}(x_2, y_1)$、$Q_{22}(x_2, y_2)$需要对其中间某点 $P(a, b)$ 进行插值。双线性插值分两步完成先在 x 方向对上下两条水平边做线性插值得到 $R_1$、$R_2$ 两点$$ f(R_1) \frac{x_2 - a}{x_2 - x_1}f(Q_{11}) \frac{a - x_1}{x_2 - x_1}f(Q_{21}) $$$$ f(R_2) \frac{x_2 - a}{x_2 - x_1}f(Q_{12}) \frac{a - x_1}{x_2 - x_1}f(Q_{22}) $$再在 y 方向对 $R_1$、$R_2$ 做线性插值得到目标点 $P$ 的值$$ f(P) \frac{y_2 - b}{y_2 - y_1}f(R_{1}) \frac{b - y_1}{y_2 - y_1}f(R_{2}) $$从 kernel 源码 可以看到NPU 侧计算正是围绕左上、右上、左下、右下四个源点POS_LU/POS_RU/POS_LD/POS_RD来组织数据的ComputeSrcIdx负责根据目标坐标反推源坐标及插值权重ComputeDeltaArgus负责确定参与插值的四个邻域点ComputeDstValueWith4SrcDot用四个源点加权求和得到目标像素值。坐标映射相关的属性算子原型 resize_bilinear_v2_proto.h 中定义了控制坐标映射与输出类型的可选属性align_cornersbool默认false若为true输入与输出四角像素中心对齐角点像素值保持不变若为false按标准像素中心half-pixel规则映射。half_pixel_centersbool默认false若为true像素中心定位在[0.5, 0.5]处当align_corners为true时half_pixel_centers不能同时为true。dtypeType默认DT_FLOAT输出 y 的数据类型支持uint8、float32、float16、bfloat16。scalesListFloat默认{0.0f, 0.0f}空间尺寸的缩放倍数主要在 L0 接口的 RegBase 路径下参与计算。属性与 IR 定义与 TensorFlow、PyTorch 的 ResizeBilinear 语义保持一致见 resize_bilinear_v2_proto.h 中的第三方框架兼容性说明便于从主流框架迁移模型。参数说明下表为算子的输入输出参数对应 README 参数说明部分参数名输入/输出/属性描述数据类型数据格式x输入输入图像的四维 Tensor对应公式中 xFLOAT16、FLOAT32、BFLOAT16NDsize输入输出图像的高和宽INT32NDy输出双线性插值调整后图像对应公式中 yFLOAT16、FLOAT32、BFLOAT16ND在 算子定义 中这些端口被细化为更严格的约束输入xParamType(REQUIRED)支持的数据类型列表实际为 10 组FLOAT16、FLOAT、BF16 的组合数据格式支持 NCHW 与 NHWC。输入sizeParamType(REQUIRED)且ValueDepend(OPTIONAL)数据类型仅 INT32格式为 ND。ValueDepend表示 shape 推导依赖该输入的具体数值。输出yParamType(REQUIRED)支持 FLOAT16、FLOAT、BF16其中部分组合允许输出精度高于输入如 FLOAT16 输入配 FLOAT 输出。属性align_corners、half_pixel_centers、dtype、scales均为可选含义见上文。InferShape 的 shape 推导规则resize_bilinear_v2_infershape.cpp 给出了图模式下的推导逻辑输入x与输出y必须为 4 维NCHW/NHWC格式仅支持 NCHW、NHWC。输出y的 N、C 维与输入x保持一致H、W 维取自size输入size必须为包含 2 个元素的常量 Tensornew_height, new_width见 GetSizeValueFor2D。若size不是常量 Tensor输出 H/W 会被置为UNKNOWN_DIM动态 shape 场景。输出数据类型默认推导为 FLOAT若显式配置了dtype属性则支持float32/float16/bfloat16/uint8并校验输入输出类型的组合合法性例如 x 为 FLOAT 时输出不允许是 FLOAT16/BF16见 InferDtype4ResizeBilinearV2。约束说明算子本体无额外约束README 中约束说明无。需要留意的是当通过aclnnResize 接口调用时参数校验较为严格详见下文aclnnResize 接口约束一节而在L0/IR 图模式下x的输入类型范围更宽proto 中允许 INT8/UINT8/INT16/UINT16/INT32/INT64/FLOAT16/FLOAT/FLOAT64/BF16见 resize_bilinear_v2_proto.h输出支持 UINT8/FLOAT/FLOAT16/BF16且要求输出与输入格式一致、N/C 维一致。调用方式一aclnnResize 两段式接口README 的调用说明中推荐通过aclnnResize接口docs/aclnnResize.md调用 ResizeBilinearV2 算子。该接口属于 CANN 的两段式接口详见 两段式接口说明先调用aclnnResizeGetWorkspaceSize获取 workspace 大小并完成参数校验、构建执行器再调用aclnnResize真正下发计算。接口功能与计算公式aclnnResize 的功能是根据 scales 调整输入张量的大小输出维度由如下公式决定$$ out_dimension floor(self_dimension \times scales) $$其中self_dimension为输入 self 的 H、W 维尺寸scales为 H、W 维对应的缩放倍数。函数原型aclnnStatus aclnnResizeGetWorkspaceSize( const aclTensor* self, const aclFloatArray* scales, const char* mode, aclTensor* out, uint64_t* workspaceSize, aclOpExecutor** executor)aclnnStatus aclnnResize( void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream)aclnnResizeGetWorkspaceSize 参数说明参数名输入/输出描述使用说明数据类型数据格式维度(shape)非连续 TensorselfaclTensor*输入输入的张量对应公式中的 self不支持空 TensorFLOAT16、FLOAT32、BFLOAT16NCHW、NHWC4√scalesaclFloatArray*输入指定 self 张量调整的倍数对应公式中的 scales长度和 self 维度相同当前仅支持调整 self 的 H 与 W 维scales 其他 N 与 C 维度需保持为 1FloatArray---modechar*输入表示插值模式只支持 nearest 或 bilinear----outaclTensor*输出输出张量对应公式中的 out不支持空 Tensor数据格式、数据类型与 self 保持一致shape 的 N 轴、C 轴与 self 保持一致H 轴和 W 轴符合 out_dimension floor(self_dimension * scales)FLOAT16、FLOAT32、BFLOAT16NCHW、NHWC4√workspaceSizeuint64_t*输出返回需要在 Device 侧申请的 workspace 大小-----executoraclOpExecutor**输出返回 op 执行器包含了算子计算流程-----需要特别留意产品差异在Atlas 推理系列产品、Atlas 训练系列产品、Atlas A2/A3 训练与推理系列产品上参数self、out的数据类型不支持 BFLOAT16数据格式不支持 NHWC。返回码与错误场景两段式接口的返回值为aclnnStatus具体返回码含义参见 aclnn返回码说明。第一段接口aclnnResizeGetWorkspaceSize会完成入参校验出现以下场景时报错返回码错误码描述ACLNN_ERR_PARAM_NULLPTR161001传入的 self、scales、mode、out 中有空指针ACLNN_ERR_PARAM_INVALID161002mode 不在支持列表之内数据类型不在支持的范围之内out 与 self 的数据格式或数据类型不一致shape 不满足要求self 和 out 必须为 4 维、N/C 维必须相同、out 的 H/W 维 size 必须等于 self 对应维 size 乘以 scales 对应值这些校验逻辑在 aclnn_resize.cpp 的CheckParams中依次落地CheckNotNull空指针→CheckModeStrmode 仅接受nearest/bilinear前缀匹配→CheckDtypeValiddtype 白名单→CheckFormatNCHW/NHWC 且 self/out 一致→CheckInputElementN/C 一致H/W 在self_dim * (scales ± EPSILON)容差范围内EPSILON1e-5→CheckShape4 维且 scales 长度必须为 4。aclnnResize 参数说明参数名输入/输出描述workspace输入在 Device 侧申请的 workspace 内存地址workspaceSize输入在 Device 侧申请的 workspace 大小由第一段接口 aclnnResizeGetWorkspaceSize 获取executor输入op 执行器包含了算子计算流程stream输入指定执行任务的 Stream确定性计算约束aclnnResize默认是确定性实现即相同输入、相同环境下多次执行的数值结果一致这对训练可复现与算子精度对齐场景很重要。完整调用示例以下代码来自 examples/test_aclnn_resize.cpp将[1, 1, 2, 2]的输入放大为[1, 1, 4, 4]scales 的 N/C 维为 1H/W 维为 2mode 为nearest#include unistd.h #include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_resize.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 0, 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, 4, 4}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {1.0, 2.0, 3.0, 4.0}; std::vectorfloat scalesData {1.0, 1.0, 2.0, 2.0}; std::vectorfloat outHostData(16); // 创建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); aclFloatArray* scales nullptr; scales aclCreateFloatArray(scalesData.data(), scalesData.size()); CHECK_RET(scales ! nullptr, return ACL_ERROR_INTERNAL_ERROR); // 3. 调用CANN算子库API uint64_t workspaceSize 0; aclOpExecutor* executor; const char* mode nearest; // 调用aclnnResize第一段接口 ret aclnnResizeGetWorkspaceSize(self, scales, mode, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnResizeGetWorkspaceSize 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); printf(workspaceSize: %ld\n, workspaceSize); } // 调用aclnnResize第二段接口 ret aclnnResize(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnResize 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::vectorfloat resultData(size, 0); ret aclrtMemcpy(resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, 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和aclFloatArray aclDestroyTensor(self); aclDestroyTensor(out); aclDestroyFloatArray(scales); // 7. 释放device资源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); sleep(10); return 0; }该示例的完整编译与运行流程可参考 编译与运行样例。其中需要注意mode传nearest时实际会走最近邻插值分支复用 ResizeNearestNeighborV2 的 L0 算子传bilinear时才走双线性插值分支见 aclnn_resize.cpp。调用方式二算子 IR 图模式README 还提供了图模式调用方式通过算子 IR resize_bilinear_v2_proto.h 构图来调用 ResizeBilinearV2 算子。该头文件使用REG_OP(ResizeBilinearV2)注册算子原型定义了输入x、size输出y以及align_corners、half_pixel_centers、dtype、scales四个属性供 GE 图编译链路使用。此外image/resize_bilinear_v2/framework/resize_bilinear_v2_tf_plugin.cpp提供了 TensorFlow 框架插件说明该算子可直接承接 TensorFlow 图如tf.image.resize_bilinear在 NPU 上的下沉执行。源码级实现剖析从接口到 NPU 算子L0 算子与 AICORE/AICPU 双路径aclnnResize 的第一段接口在完成参数校验后会根据当前 NPU 架构选择执行路径GetExtendPathFlagRegBase 路径IsRegBase或 DAV_2201/DAV_1001/DAV_2002直接调用带 scales 的l0op::ResizeBilinearV2With4d无需转到 NC1HWC0V35 路径其他架构先将数据经TransDataSpecial转到NC1HWC0格式调用l0op::ResizeBilinearV2计算后再TransData转回 NCHW/NHWC最后通过ViewCopy写入输出。L0 层 resize_bilinear_v2.cpp 的ResizeBilinearV2入口会根据输入 dtype 决定执行单元支持 FLOAT/FLOAT16/BF16 时走AICOREADD_TO_LAUNCHER_LIST_AICORE否则回退AICPUADD_TO_LAUNCHER_LIST_AICPUAICPU 路径固定输出 FLOAT且half_pixel_centers !align_corners。Tiling 策略与 Kernel 分形Host 侧 resize_bilinear_v2_tiling_arch35.cpp 定义了多种 tiling 策略并按输入特性自动匹配tilingKey策略适用场景部分10000C_PARALLELNHWC、C 维字节数 ≥ 128、H/W 缩放倍数均 ≥ 230000~30005SIMT_NHWC / SIMT_NCHW / SIMT_HW 等通用 SIMT 路径含 int64 索引版本40000ALL_COPY输入输出 dtype 一致且 H/W 尺寸相等无需插值40001POINT_COPYNHWC、整数倍放大且满足条件时的快速拷贝40002/40003NCHW/NHWC_BROADCAST源图 HW1 的广播放大场景对应 Kernel 侧 op_kernel/arch35 提供了resize_bilinear_v2_nc.h、resize_bilinear_v2_simt_nchw.h、resize_bilinear_v2_simt_nhwc.h、resize_bilinear_v2_c_parallel.h、resize_bilinear_v2_all_copy.h、resize_bilinear_v2_point_copy.h、resize_bilinear_v2_broadcast_nchw.h等实现通过 tiling 数据见 resize_bilinear_v2_tiling_arch35.h包含lenSrcH/lenDesH/scaleW/scaleH/alignCorners/halfPixelCenters等字段驱动核内计算。tiling 会自动按 N/H/W/C 维度切分任务到多个 AI Core并利用 UBUnified Buffer双缓冲流水隐藏搬运开销。测试与验证仓库为算子提供了完整的 ST 与 UT 测试支撑可用于验证上述调用路径ST系统级用例atk_aclnnResize.json 定义了 200 组用例将aclnnResize与torch.nn.functional.interpolate对齐覆盖 fp16/fp32 输入、bilinear/nearest两种模式、以及从[1,1,2,2]到[122,1,5,1]、[1,7,55,10]等丰富的 4 维 shape 组合与 1~4 的缩放倍数对应的执行脚本为 executor_aclnnResize.py。UT单元级用例Host 侧test_resize_bilinear_v2_infershape.cpp 覆盖 InferShape/InferDtype 推导arch35/test_resize_bilinear_v2_tiling.cpp 覆盖 tiling 策略选择Kernel 侧test_resize_bilinear_v2.cpp 覆盖 AICORE 核函数API 侧op_api/test_aclnn_resize.cpp 覆盖两段式接口的参数校验与错误码。小结ResizeBilinearV2 是 ops-cv 中一个实现简洁但工程完备的图像缩放算子数学上基于标准双线性插值公式接口上同时提供 aclnnResize 两段式接口与 IR 图模式实现上覆盖 AICORE/AICPU 双执行路径与多种 tiling 策略并有 ST/UT 用例与 PyTorchinterpolate对齐验证。开发者可按先aclnnResizeGetWorkspaceSize后aclnnResize的固定模式快速接入也可借助 IR 原型与 TF 插件在框架图中直接使用是实现图像预处理、数据增强等 CV 流水线的高效选择。【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考