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

资讯详情

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

CANN ops-cv CIoU 算子全解析:从损失函数原理到 aclnnCIoU 两段式接口实战

CANN ops-cv CIoU 算子全解析:从损失函数原理到 aclnnCIoU 两段式接口实战 CANN ops-cv CIoU 算子全解析从损失函数原理到 aclnnCIoU 两段式接口实战【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv本篇技术指南围绕 CANN ops-cv 算子库中的CIoU 算子展开完整讲解其在边界框回归中的数学原理在 IoU 基础上同时引入中心点距离、宽高比与重叠面积的惩罚项、Ascend 950 系列产品的支持情况、全部输入/输出/属性参数与约束条件并结合仓库源码剖析aclnnCIoUGetWorkspaceSize的入参校验逻辑与底层计算流程最后给出可直接复用的aclnnCIoU两段式接口调用示例。读完本文你将能够独立完成 CIoU 算子在 NPU 上的算子调用、参数配置与结果验证。一、CIoU 算子功能与数学原理CIoUComplete IoU算子是用于边界框回归的损失函数。它在 IoU 的基础上同时考虑了中心点距离、宽高比和重叠面积三个维度从而更全面地衡量预测框bBox与真实框gtBox之间的差异。相比单纯使用 IoUCIoU 能够在预测框与真实框完全没有重叠IoU 为 0时依然提供有效的梯度信号加速回归收敛。在 objdetect/ciou/README.md 中给出的计算公式如下$$ CIoU IoU - \frac{\rho^2(b^p, b^g)}{c^2} - \alpha v \ v \frac{4}{\pi^2}(arctan(\frac{w^g}{h^g}) - arctan(\frac{w^p}{h^p}))^2 \ \alpha \frac{v}{1 - IoU v} $$其中各符号含义为符号含义IoU预测框与真值框的交并比ρ²(bᵖ, bᵍ)预测框中心点与真值框中心点之间欧氏距离的平方c²同时包含预测框与真值框的最小外接矩形对角线长度的平方v衡量预测框与真值框宽高比一致性的惩罚项基于两者宽高比的arctan差值α平衡因子用于对v进行归一化当 IoU 越大时α越趋向于 1宽高比惩罚越显著从公式可以看出CIoU 在 IoU 基础上做了两项修正ρ²/c²惩罚中心点偏移αv惩罚宽高比不一致。同时该算子支持mode属性切换iou交并比与iof前景交叉比intersection over foreground两种计算方式并额外输出计算过程中的atanSub两个arctan的差值便于上层在需要时复用它构造其他变体损失。二、产品支持情况依据 objdetect/ciou/README.md 与 aclnnCIoU 接口文档 的产品支持矩阵CIoU 算子的支持情况如下产品是否支持Ascend 950PR / Ascend 950DT√Atlas A3 训练系列产品 / Atlas A3 推理系列产品×Atlas A2 训练系列产品 / Atlas A2 推理系列产品×Atlas 200I/500 A2 推理产品×Atlas 推理系列产品×Atlas 训练系列产品×适用前提当前仓库中该算子仅在RegBase寄存器基址平台实现源码 op_api/aclnn_ciou.cpp 中的CheckSocValid()通过IsRegBase()判断当前 SoC 版本是否受支持非 RegBase 平台会直接返回ACLNN_ERR_RUNTIME_ERROR。因此在非 Ascend 950 系列平台上调用aclnnCIoU会失败请务必以实际运行硬件为准。三、算子参数说明3.1 顶层算子参数CIoU 算子的完整参数定义见 objdetect/ciou/README.md 参数说明表参数名输入/输出/属性描述数据类型数据格式bboxes输入预测矩形框FLOAT32、FLOAT16NDgtboxes输入真值矩形框FLOAT32、FLOAT16NDtrans属性用于指定矩形框的格式BOOL-is_cross属性用于指定 bBoxes 与 gtBoxes 之间是否进行交叉运算BOOL-mode属性用于选择计算方式iou或iofString-atan_sub_flag属性用于指定是否输出 atan_subBOOL-overlap输出根据两个输入计算得到的交并比/前景交叉比FLOAT32、FLOAT16NDatan_sub输出计算过程中两个 arctan 的差值FLOAT32、FLOAT16ND各属性的取值语义结合 aclnn_ciou.h 的接口注释整理transtrue表示输入矩形框格式为[x, y, w, h]中心点坐标 宽高false表示输入格式为[x0, y0, x1, y1]左上角 右下角坐标。is_cross是否对 bBoxes 与 gtBoxes 做交叉运算。true时输出 shape 为[M, N]false时输出 shape 为[1, N]。当前版本仅支持false。modeiou计算交并比iof计算前景交叉比intersection over foreground即交集面积除以 bBox 面积。atan_sub_flag是否输出atanSub张量。当前版本仅支持true即必须输出两个 arctan 的差值。3.2 接口层参数aclnnCIoUGetWorkspaceSize在 aclnn 接口层属性参数直接作为函数入参传递接口原型参见 aclnnCIoU.md 与 aclnn_ciou.haclnnStatus aclnnCIoUGetWorkspaceSize( const aclTensor *bBoxes, // 预测矩形框[4, M]FLOAT32/FLOAT16ND支持非连续 const aclTensor *gtBoxes, // 真值矩形框[4, N]FLOAT32/FLOAT16ND支持非连续 bool trans, // true: [x,y,w,h]false: [x0,y0,x1,y1] bool isCross, // 是否交叉运算当前仅支持 false const char *mode, // iou 或 iof aclTensor *overlap, // 输出[1, N]与输入同 dtype支持非连续 aclTensor *atanSub, // 输出[1, N]与输入同 dtype支持非连续 uint64_t *workspaceSize, // 输出需要申请的 workspace 大小 aclOpExecutor **executor); // 输出op 执行器包含算子计算流程 aclnnStatus aclnnCIoU( void *workspace, // Device 侧 workspace 内存地址 uint64_t workspaceSize, // 由第一段接口获取 aclOpExecutor *executor, // 第一段接口返回的执行器 aclrtStream stream); // 指定执行任务的 Stream接口层各参数要点源自 aclnnCIoU.md 参数说明表bBoxes形状为[4, M]的二维 Tensor第一维固定为 4分别对应 x/y/w/h 或 x0/y0/x1/y1 四个坐标分量。gtBoxes形状为[4, N]的二维 Tensor。overlap/atanSub输出 Tensor形状为[1, N]isCrossfalse时数据类型、数据格式需与输入保持一致。输入输出均支持非连续 Tensor√标记接口内部会自动做Contiguous转换。mode在接口层类型为CHAR*对应顶层属性表的 String。四、约束说明CIoU 算子的使用约束README 与 aclnn 接口文档一致且被源码校验逻辑印证坐标有效性若输入格式为[x0, y0, x1, y1]其中(x0, y0)和(x1, y1)分别表示矩形框的左上角和右下角必须满足x1 x0, y1 y0否则矩形框无效。M 和 N 需要一致即bBoxes与gtBoxes第二维必须相等在isCrossfalse场景下。is_cross目前仅支持false传true会在参数校验阶段报错。atan_sub_flag目前仅支持true即atanSub输出张量必须提供。确定性计算aclnnCIoU默认采用确定性实现相同输入多次执行结果一致。上述约束在源码中有完整对应例如 aclnn_ciou.cpp 中定义了三个关键常量static const int64_t TENSOR_DIM_NUM 2; // 输入输出必须为二维 static const int64_t INPUT_FIRST_DIM 4; // 输入第一维必须为 4 static const int64_t INPUT_SECOND_DIM_CONSTRAINT 1024; // 输入第二维必须是 1024 的倍数其中第二维必须是 1024 的倍数是 aclnn 接口层相对 README 的进一步补充约束与示例中[4, 1024]的 shape 对应在实际调用时同样需要满足否则返回ACLNN_ERR_PARAM_INVALID。五、源码级实现解析5.1 两段式接口与底层计算流程aclnnCIoU采用 CANN 标准的两段式接口设计先调用aclnnCIoUGetWorkspaceSize完成入参校验、计算 workspace 大小并生成执行器再调用aclnnCIoU真正执行计算。在 aclnn_ciou.cpp 的第一段接口实现中核心流程依次为平台校验CheckSocValid()检查当前 SoC 是否属于 RegBaseAscend 950平台。参数校验CheckParams()按顺序执行CheckNotNull空指针检查、CheckDtypeValid数据类型与格式检查、CheckAttr属性检查、CheckShapeshape 检查。空 Tensor 短路bBoxes或gtBoxes为空 Tensor 时workspace 直接置 0 并返回成功。连续化处理通过l0op::Contiguous将可能的非连续输入转换为连续 Tensor。底层算子调用l0op::CIoU(bBoxesContiguous, gtBoxesContiguous, trans, isCross, mode, true, ...)构建计算图其中最后一个参数true即对应atanSubFlag。输出搬运通过两次l0op::ViewCopy将内部计算结果写入用户提供的overlap与atanSub张量兼容非连续输出。获取 workspace 大小executor-GetWorkspaceSize()返回计算所需 workspace 字节数。5.2 校验逻辑的代码佐证空指针检查aclnn_ciou.cppbBoxes、gtBoxes、overlap、atanSub任一为空即返回ACLNN_ERR_PARAM_NULLPTR。数据类型检查aclnn_ciou.cpp四个张量数据类型必须一致且均须属于支持列表{DT_FLOAT, DT_FLOAT16}格式必须全部为FORMAT_ND否则返回ACLNN_ERR_PARAM_INVALID。属性检查aclnn_ciou.cppmode非空且只能为iou或iofisCross必须为false。shape 检查aclnn_ciou.cpp输入输出必须为二维输入第一维必须为 4输入第二维必须是 1024 的倍数isCrossfalse时输出第一维必须为 1 且各张量第二维相等isCrosstrue时输出 shape 应为[M, N]。5.3 输出 shape 的推导在 op_api/ciou.cpp 中可以看到输出 shape 的推导逻辑if (isCross false) { outShape {1, bBoxes-GetViewShape().GetDim(1)}; } else { outShape {gtBoxes-GetViewShape().GetDim(1), bBoxes-GetViewShape().GetDim(1)}; }即isCrossfalse时输出为[1, N]与gtBoxes第二维一致isCrosstrue时输出为[M, N]M 为 gtBoxes 数量、N 为 bBoxes 数量。随后通过ADD_TO_LAUNCHER_LIST_AICORE(CIoU, ...)将算子挂载到AICore执行器列表说明 CIoU 计算在 NPU 的 AI Core 上执行。六、aclnnCIoU 调用示例与步骤拆解6.1 完整示例代码仓库提供了可直接参考的调用样例 examples/test_aclnn_ciou.cpp完整代码与 aclnnCIoU.md 调用示例一致如下#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_ciou.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 shape_size 1; for (auto i : shape) { shape_size * i; } return shape_size; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定写法AscendCL初始化 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_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1. 固定写法device/stream初始化参考AscendCL对外接口列表 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. 构造输入与输出 std::vectorfloat bBoxesHostData(4096, 1); std::vectorfloat gtBoxesHostData(4096, 2); std::vectorfloat overlapHostData(1024, 0); std::vectorfloat atanSubHostData(1024, 0); std::vectorint64_t bBoxesShape {4, 1024}; std::vectorint64_t gtBoxesShape {4, 1024}; std::vectorint64_t overlapShape {1, 1024}; std::vectorint64_t atanSubShape {1, 1024}; void* bBoxesDeviceAddr nullptr; void* gtBoxesDeviceAddr nullptr; void* overlapDeviceAddr nullptr; void* atanSubDeviceAddr nullptr; aclTensor* bBoxes nullptr; aclTensor* gtBoxes nullptr; aclTensor* overlap nullptr; aclTensor* atanSub nullptr; ret CreateAclTensor(bBoxesHostData, bBoxesShape, bBoxesDeviceAddr, aclDataType::ACL_FLOAT, bBoxes); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(gtBoxesHostData, gtBoxesShape, gtBoxesDeviceAddr, aclDataType::ACL_FLOAT, gtBoxes); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(overlapHostData, overlapShape, overlapDeviceAddr, aclDataType::ACL_FLOAT, overlap); CHECK_RET(ret ACL_SUCCESS, return ret); ret CreateAclTensor(atanSubHostData, atanSubShape, atanSubDeviceAddr, aclDataType::ACL_FLOAT, atanSub); CHECK_RET(ret ACL_SUCCESS, return ret); // attr bool trans false; bool isCross false; const char* mode iou; uint64_t workspaceSize 0; aclOpExecutor* executor; // 3. 调用CANN算子库API第一段接口 ret aclnnCIoUGetWorkspaceSize(bBoxes, gtBoxes, trans, isCross, mode, overlap, atanSub, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnCIoUGetWorkspaceSize 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); } // 4. 第二段接口执行计算 ret aclnnCIoU(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnCIoU failed. ERROR: %d\n, ret); return ret); // 5. 固定写法同步等待任务执行结束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); // 6. 获取输出值将device侧内存上的结果拷贝至host侧 auto overlapSize GetShapeSize(overlapShape); std::vectorfloat overlapData(overlapSize, 0); ret aclrtMemcpy(overlapData.data(), overlapData.size() * sizeof(overlapData[0]), overlapDeviceAddr, overlapSize * sizeof(overlapData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(copy overlapData from device to host failed. ERROR: %d\n, ret); return ret); for (int64_t i 0; i overlapSize; i) { LOG_PRINT(overlap[%ld] is: %f\n, i, overlapData[i]); } auto atanSubsize GetShapeSize(atanSubShape); std::vectorfloat atanSubData(atanSubsize, 0); ret aclrtMemcpy(atanSubData.data(), atanSubData.size() * sizeof(atanSubData[0]), atanSubDeviceAddr, atanSubsize * sizeof(atanSubData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(copy atanSubData from device to host failed. ERROR: %d\n, ret); return ret); for (int64_t i 0; i atanSubsize; i) { LOG_PRINT(atanSub[%ld] is: %f\n, i, atanSubData[i]); } // 7. 释放aclTensor aclDestroyTensor(bBoxes); aclDestroyTensor(gtBoxes); aclDestroyTensor(overlap); aclDestroyTensor(atanSub); // 8. 释放device资源 aclrtFree(bBoxesDeviceAddr); aclrtFree(gtBoxesDeviceAddr); aclrtFree(overlapDeviceAddr); aclrtFree(atanSubDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }6.2 调用步骤拆解对照源码将整个调用流程拆解为八个固定步骤可套用到绝大多数 aclnn 算子环境初始化aclInit→aclrtSetDevice→aclrtCreateStream完成 AscendCL 运行环境与 Stream 的创建。构造输入输出 Tensor通过aclrtMalloc申请 Device 侧内存、aclrtMemcpy拷贝 Host 数据、aclCreateTensor创建aclTensor。示例中 shape 取bBoxes[4,1024]、gtBoxes[4,1024]、overlap[1,1024]、atanSub[1,1024]满足第一维为 4、第二维为 1024 的倍数、MN等全部约束。调用第一段接口aclnnCIoUGetWorkspaceSize完成入参校验、生成执行器并返回 workspace 大小。申请 workspace仅当workspaceSize 0时用aclrtMalloc申请对应大小的 Device 内存。调用第二段接口aclnnCIoU传入 workspace、执行器与 Stream完成实际计算。同步等待aclrtSynchronizeStream确保算子任务执行完成。结果回读通过aclrtMemcpy(..., ACL_MEMCPY_DEVICE_TO_HOST)将overlap与atanSub从 Device 拷贝到 Host 并打印。资源释放依次销毁aclTensor、释放 Device 内存与 workspace、销毁 Stream、复位 Device、aclFinalize。编译与运行样例的完整过程可参考 编译与运行样例。示例代码中bBoxes全为 1、gtBoxes全为 2此时矩形框宽高均为 1中心点重合IoU 1因此预期overlap输出为 1、atanSub输出为 0可作为功能验证的参考预期值。七、返回值与错误码aclnnCIoUGetWorkspaceSize与aclnnCIoU均返回aclnnStatus状态码完整返回码定义参见 aclnn 返回码。第一段接口完成入参校验出现以下场景时报错返回值错误码描述ACLNN_ERR_PARAM_NULLPTR161001bBoxes、gtBoxes、overlap或atanSub是空指针ACLNN_ERR_PARAM_INVALID多场景161002bBoxes、gtBoxes、overlap、atanSub不是二维四个张量数据类型不一致数据类型或数据格式不在支持范围内bBoxes或gtBoxes第一维不是 4第二维不是 1024 的倍数overlap或atanSub第一维不是 1四个张量第二维不相等isCross不是falsemode不是iou或iof这些错误码对应关系与 aclnn_ciou.cpp 中CheckParams的校验链一一对应出现161002时可通过排查上述列表快速定位问题参数。八、单元测试对约束的验证仓库在 tests/ut/op_host/op_api/test_aclnn_ciou.cpp 中提供了完整的 gtest 单元测试覆盖了正常路径与异常路径成功用例bBoxes[4,1024]、gtBoxes[4,1024]、overlap[1,1024]、atanSub[1,1024]transfalse, isCrossfalse, modeiou在SocVersion::ASCEND950平台上调用第一段接口返回ACLNN_SUCCESS。空指针用例分别将bBoxes、gtBoxes、overlap、atanSub置空期望返回ACLNN_ERR_PARAM_NULLPTR。非法 dtype 用例输出使用ACL_INT64、ACL_BF16或输入使用ACL_BF16、输入输出 dtype 不一致期望返回ACLNN_ERR_PARAM_INVALID。非法 format 用例任一张量使用ACL_FORMAT_NCHW或ACL_FORMAT_FRACTAL_NZ期望返回ACLNN_ERR_PARAM_INVALID。非法 shape 用例输出第一维非 1、输入第一维非 4、输入第二维非 1024 倍数、输出第二维与输入不一致、维度数非二维等均期望返回ACLNN_ERR_PARAM_INVALID。非法 attr 用例isCrosstrue、modeiouf期望返回ACLNN_ERR_PARAM_INVALID。这些用例从测试侧印证了第五节源码校验逻辑的每一条规则是排查调用问题时的最佳参照。九、总结与最佳实践CIoU 算子是 CANN ops-cv 在边界框回归场景下提供的高阶损失算子通过aclnnCIoU两段式接口即可在 Ascend 950 系列 NPU 上完成计算。使用时的关键要点确认硬件平台仅 Ascend 950PR / Ascend 950DT 支持其他系列调用会直接失败。严格遵循 shape 约束输入[4, M]、输出[1, N]M 与 N 一致第二维为 1024 的倍数。属性取值固定isCross传falsemode只能取iou或iof同时记得提供atanSub输出张量。遵循两段式调用范式先取 workspace 与 executor再申请内存并执行最后同步、回读、释放。借助错误码排查161001查空指针161002对照校验链逐项排查 dtype、format、shape 与 attr。文中涉及的算子文档、接口实现、示例代码与单元测试均可在仓库objdetect/ciou/目录下找到推荐在动手开发前通读 README.md 与 aclnnCIoU.md 两份文档并结合本文的源码解析理解其内部校验与计算流程。【免费下载链接】ops-cv本项目是CANN提供的图像处理、目标检测相关的算子库实现网络在NPU上加速计算。项目地址: https://gitcode.com/cann/ops-cv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表