常州企业建站系统,合肥 网站建设公司,wordpress主题next推荐,wordpress live训练营简介 2025年昇腾CANN训练营第二季#xff0c;基于CANN开源开放全场景#xff0c;推出0基础入门系列、码力全开特辑、开发者案例等专题课程#xff0c;助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证#xff0c;即可领取精美证书#xff0c;完成…训练营简介2025年昇腾CANN训练营第二季基于CANN开源开放全场景推出0基础入门系列、码力全开特辑、开发者案例等专题课程助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证即可领取精美证书完成社区任务更有机会赢取华为手机平板、开发板等大奖。报名链接https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro前言在 YOLO、SSD、Faster R-CNN 等检测网络中NMS 是最后一道关卡。 它的算法逻辑非常“贪心”Sort按置信度从高到低排序。Pick拿出得分最高的框 A。Compare计算 A 与剩余所有框的 IoU交并比。Suppress如果 IoU 阈值删掉那个框。Loop从剩下的框里再选最高的重复步骤 2。这个过程之所以难优化是因为第 i 轮的结果依赖于第 i-1 轮的删除操作。这直接打破了 SIMD 的并行性。 但在 Ascend C 中我们可以利用Vector 强大的批量计算能力来加速最耗时的IoU 计算环节——虽然挑选框是串行的但“拿一个框跟一万个框比”是可以并行的一、 核心图解像剥洋葱一样过滤NMS 的过程就像是剥洋葱一层一层去掉外面的皮重叠框只留下核心。二、 算法优化并行 IoU 串行 Mask既然外层循环逻辑无法避免我们就优化循环体内部。 在每一次迭代中我们需要计算1 个主框 vs N 个剩余框的 IoU。这是一个典型的向量运算Ascend C 策略预处理先完成 Sort利用第 41 期学的 Bitonic Sort 或 AI CPU或者假设输入已排序。Kernel 侧将排好序的框搬入 UB。循环取出一个有效框广播Broadcast一次性计算它与剩余所有框的 IoU。更新生成 Suppression Mask更新状态位。三、 实战Ascend C 实现 NMS3.1 Kernel 类定义输入是boxes$[N, 4]$ 和scores$[N]$假设已排序。输出是keep_indices。class KernelNMS { public: __aicore__ inline void Init(GM_ADDR boxes, GM_ADDR output, uint32_t num_boxes, float iou_threshold) { // ... Init ... this-num_boxes num_boxes; this-thresh iou_threshold; // 申请 UB 空间存放 Boxes (Proposal) // 假设 N 较小 (如 2048)可以一次性放入 UB // 如果 N 很大需要分块 NMS (这里演示一次性加载逻辑) } __aicore__ inline void Process() { // 1. CopyIn Boxes // boxesLoc: [num_boxes, 4] DataCopy(boxesLoc, boxesGm, num_boxes * 4); // 2. 初始化状态 (Keep Mask) // 全部置为 1 (有效) // keepMaskLoc: [num_boxes] (使用 half 或 uint8 存储 0/1) Duplicate(keepMaskLoc, (half)1.0, num_boxes); // 3. 执行 NMS ComputeNMS(); // 4. CopyOut Indices // 根据 keepMask 导出索引 (这一步通常涉及 Select 或 Compact) } };3.2 Compute 核心逻辑 (IoU 计算)IoU 计算公式 $$ \text{IoU} \frac{\text{Inter}}{\text{Area}_A \text{Area}_B - \text{Inter}} $$ 其中 $\text{Inter} \max(0, \min(x2_a, x2_b) - \max(x1_a, x1_b)) \times \dots$我们需要用到大量的Min,Max,Sub,Mul指令。__aicore__ inline void ComputeNMS() { // 预计算所有框的 Area // Area (x2 - x1) * (y2 - y1) // 这一步是 Vector 并行的一次算完所有 Sub(tmpW, boxesX2, boxesX1, num_boxes); Sub(tmpH, boxesY2, boxesY1, num_boxes); Mul(areasLoc, tmpW, tmpH, num_boxes); // 主循环串行遍历每一个框 for (int i 0; i num_boxes; i) { // 1. 检查当前框 i 是否已被抑制 (Scalar Check) // 这是一个标量操作GetValue 会打断流水线但在 NMS 中无法避免 if (keepMaskLoc.GetValue(i) 0) continue; // 2. 准备广播当前框 i (Current Box) // currentBox: [x1, y1, x2, y2] // Ascend C 提供了 Brcb (Broadcast from Block) 或 Duplicate 指令 // 将第 i 个框的坐标广播成向量与所有框对齐 half curX1 boxesX1.GetValue(i); half curY1 boxesY1.GetValue(i); half curX2 boxesX2.GetValue(i); half curY2 boxesY2.GetValue(i); Duplicate(vecCurX1, curX1, num_boxes); Duplicate(vecCurY1, curY1, num_boxes); Duplicate(vecCurX2, curX2, num_boxes); Duplicate(vecCurY2, curY2, num_boxes); // 3. 并行计算 IoU (Vector) // 计算 Intersection 坐标 // inter_x1 max(cur_x1, all_x1) Max(interX1, vecCurX1, boxesX1, num_boxes); Max(interY1, vecCurY1, boxesY1, num_boxes); Min(interX2, vecCurX2, boxesX2, num_boxes); Min(interY2, vecCurY2, boxesY2, num_boxes); // 计算 Inter Area Sub(interW, interX2, interX1, num_boxes); Sub(interH, interY2, interY1, num_boxes); // relu: 宽高必须 0 Max(interW, interW, (half)0.0, num_boxes); Max(interH, interH, (half)0.0, num_boxes); Mul(interArea, interW, interH, num_boxes); // IoU inter_area / (area_i area_all - inter_area) Duplicate(vecCurArea, areasLoc.GetValue(i), num_boxes); Add(unionArea, vecCurArea, areasLoc, num_boxes); Sub(unionArea, unionArea, interArea, num_boxes); // 避免除零加个 epsilon Adds(unionArea, unionArea, (half)1e-6, num_boxes); Div(iouVec, interArea, unionArea, num_boxes); // 4. 生成抑制掩码 (Suppress Mask) // mask (IoU thresh) // Compare 生成的是 cmpMask (bit mask) Compare(cmpMask, iouVec, threshVec, CMP_GT, num_boxes); // 5. 更新全局 Keep Mask // 如果 IoU thresh则对应的框应当被抑制 (置0) // 注意只更新 i 之后的框且不能把当前框 i 给抑制了 // 这里可以使用 Select 指令配合 cmpMask 更新 keepMaskLoc // keepMaskLoc select(cmpMask, 0, keepMaskLoc) Select(keepMaskLoc, cmpMask, (half)0.0, keepMaskLoc, SEL_MODE_NE, num_boxes); } }四、 进阶性能优化的深水区NMS 的性能瓶颈在于标量循环控制与向量IoU 计算的频繁交互。4.1 减少 Scalar 交互在上面的代码中keepMask.GetValue(i)和boxesX1.GetValue(i)是标量读取操作非常慢。优化策略延迟判断不每次都 check keepMask而是每隔 K 个比如 32才回读一次状态虽然多算了一些无效 IoU但流水线更顺畅。Block NMS一次处理一个 Block 的主框计算 Block vs Block 的 IoU 矩阵利用 Cube 单元但这比较复杂。4.2 坐标精度 (FP16 vs FP32)检测框坐标对精度敏感。 如果用 FP16 计算x2 - x1当物体很小或坐标很大时可能会因为精度不够导致 IoU 计算偏差。建议在计算 IoU 的中间步骤特别是 Area 和 Sub 操作临时转为FP32。4.3 硬件加速指令部分昇腾芯片如 Ascend 310 系列内置了专门的Region Proposal硬件单元。 如果你的场景是标准的检测后处理可以考虑直接调用acl.op.NMSWithMask等融合大算子接口而不是自己手写 Kernel。但如果你需要魔改 NMS如 Soft-NMS手写 Kernel 是唯一的路。五、 总结NMS 是算法逻辑与硬件特性的博弈。矛盾串行贪心逻辑 vs 并行硬件架构。解法“串行遍历主框并行计算 IoU”。关键利用 Vector 单元一次算完 128 个框的重叠度极大地摊薄了标量循环的开销。攻克了 NMS你就能处理几乎所有 CV 领域的后处理逻辑让 AI Core 真正接管端到端流程。