已关闭
[Bug]: npu_benchmark中level 1的31_IOU的 `torch_npu.npu_iou` 和docstring中PyTorch数据精度无法对齐 #5
Xiangyu Chang创建于 5月13日关闭于 6月1日
5月13日 修改了issue 的描述
5月13日 修改了issue 的描述
5月13日 修改了issue 的描述
5月13日 修改了issue 的描述
5月14日 添加了label:triaged
zhaolinlin
6月1日 评论:
6月1日 评论:
请参照最新精度标准测试,fp16 atol采用9e-2本地验证通过~
https://github.com/ElleElleWu/AscendOpGenAgent/blob/main/skills/triton/kernel-verifier/SKILL.md


6月1日 issue状态由 TODO 改变为 DONE
6月1日 关闭了 issue
在提交新问题之前,请确保您已经在社区中搜索过相关问题,并使用了社区中提供的资源/工具后,仍未找到满意的解决方式。
⚠️ 安全信息提醒:请仔细检查提供的文本内容,确保其不包含敏感数据信息,包括但不限于:
在分享配置信息或代码示例时,请将敏感信息脱敏处理,或使用
<TOKEN>等占位符替代原有内容。环境信息
🐛 问题描述
一、问题简述
https://gitcode.com/Ascend/ascendc-kernelgen-data/blob/master/npu_benchmark/level1/31_IOU.py
torch_npu.npu_iou内部走 ACL"Iou"自定义算子(CANN runtime 里的预编译二进制 kernel),源码不在任何公开仓里。op-plugin 的 cpp wrapper 是开源的,但只是 dispatch 层,看不到
真正算 overlap / union / divide 的 AscendC 实现。
影响:
eps=0.01、输出 cast 回原 dtype)写出一个 Python 模拟版,相对
npu_iou输出的max_abs_err仍然在 e-3 ~ e-2 量级,无法过 fp16 默认
rtol=1e-3, atol=1e-3allclose 精度检查。二、环境
三、最小复现
import torch, torch_npu torch.manual_seed(0) torch_npu.npu.set_device(0) def make_boxes(n, dtype): pts = torch.rand(n, 4) * 100 x1 = torch.minimum(pts[:, 0], pts[:, 2]) x2 = torch.maximum(pts[:, 0], pts[:, 2]) + 1.0 y1 = torch.minimum(pts[:, 1], pts[:, 3]) y2 = torch.maximum(pts[:, 1], pts[:, 3]) + 1.0 return torch.stack([x1, y1, x2, y2], dim=1).to(dtype) def torch_ref_iou(bboxes, gtboxes, mode=0, min=0.0): """Standard PyTorch IoU formula (fp32 internal).""" if mode not in [0, 1]: raise ValueError(f"mode must be 0 (IoU) or 1 (IoF), got {mode}") n = bboxes.shape[0] m = gtboxes.shape[0] bboxes_float = bboxes.float() gtboxes_float = gtboxes.float() lt = torch.max(bboxes_float[:, :2].unsqueeze(1), gtboxes_float[:, :2].unsqueeze(0)) rb = torch.min(bboxes_float[:, 2:].unsqueeze(1), gtboxes_float[:, 2:].unsqueeze(0)) wh = (rb - lt).clamp(min=0) inter = wh[:, :, 0] * wh[:, :, 1] area1 = (bboxes_float[:, 2] - bboxes_float[:, 0]) * (bboxes_float[:, 3] - bboxes_float[:, 1]) area2 = (gtboxes_float[:, 2] - gtboxes_float[:, 0]) * (gtboxes_float[:, 3] - gtboxes_float[:, 1]) if mode == 0: union = area1.unsqueeze(1) + area2.unsqueeze(0) - inter iou = inter / union.clamp(min=1e-10) else: iou = inter / area2.unsqueeze(0).clamp(min=1e-10) return iou.t().to(bboxes.dtype) def torch_ref_npu_contract(bboxes, gtboxes, mode=0): """Mimic the contract inferred from op-plugin cpp: cast inputs to fp16, divide with eps=0.01, cast back.""" b16 = bboxes.to(torch.float16) g16 = gtboxes.to(torch.float16) out = torch_ref_iou(b16, g16, mode, eps=0.01) return out.to(bboxes.dtype) for shape, dtype in [ ((1024, 4), torch.float16), ((256, 4), torch.float16), ((1024, 4), torch.float32), ((512, 4), torch.float32), ]: b = make_boxes(shape[0], dtype).npu() g = make_boxes(shape[0] // 2, dtype).npu() out_npu = torch_npu.npu_iou(b, g, 0) out_std = torch_ref_iou(b.float(), g.float(), 0, eps=0).to(dtype) out_ctr = torch_ref_npu_contract(b, g, 0) e_std = (out_npu.float() - out_std.float()).abs() e_ctr = (out_npu.float() - out_ctr.float()).abs() print(f"dtype={dtype} shape={shape}:") print(f" vs PyTorch (fp32, eps=0): max={e_std.max():.3e} mean={e_std.mean():.3e} " f"allclose@1e-3={torch.allclose(out_npu, out_std, rtol=1e-3, atol=1e-3)}") print(f" vs fp16+eps=0.01 contract: max={e_ctr.max():.3e} mean={e_ctr.mean():.3e} " f"allclose@1e-3={torch.allclose(out_npu, out_ctr, rtol=1e-3, atol=1e-3)}")实测输出(910B3 / CANN 25.3.rc1 / torch_npu 2.7.1):
四、定位过程
op-plugin( https://github.com/Ascend/op-plugin/blob/master/op_plugin/ops/aclops/IouKernelNpu.cpp ) 里的 wrapper(acl_op::npu_iou)能看到的全部细节:at::Tensor bboxes_fp16 = bboxes; if (bboxes.scalar_type() != at::ScalarType::Half) { bboxes_fp16 = at_npu::native::custom_ops::_npu_dtype_cast(bboxes, at::kHalf); } // ...同样把 gtboxes cast 成 fp16... at_npu::native::OpCommand cmd; cmd.Name("Iou") .Input(bboxes_fp16) .Input(gtboxes_fp16) .Output(overlap) .Attr("mode", mode_str) .Attr("eps", static_cast<float>(0.01)) .Run(); if (overlap.scalar_type() != bboxes.scalar_type()) { overlap = at_npu::native::custom_ops::_npu_dtype_cast(overlap, bboxes.scalar_type()); }可见的契约(上面 Python
torch_ref_npu_contract就是按这套写的):eps = 0.01加在分母(位置和写法看不到)看不到的契约:
IouACL op 内部的 op 序列(先算 overlap 再 divide?还是 fused?fma 还是分立 mul+add?)eps加在哪:area_union + eps还是max(area_union, eps)还是 clamp?五、影响
HW 内部序列差异仍贡献 e-3 量级误差。
npu_iou时拿不到稳定的对齐基线,只能改 ref 或宽松 atol/rtol,失去和原
npu_iou的语义等价性。欢迎加入社区,感谢您对社区的贡献 🎉!