尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Bug已解决】[ORT GPU (DML)][WebNN] wrong results when running the “ l2Pool2d float16 4D tensor options.…

【Bug已解决】[ORT GPU (DML)][WebNN] wrong results when running the “ l2Pool2d float16 4D tensor options.… 【Bug已解决】[ORT GPU (DML)][WebNN] wrong results when running the l2Pool2d float16 4D tensor options.padding test 解决方案一、现象长什么样在 DirectML (DML) / WebNN 后端上跑l2Pool2dL2 范数池化算子输入是float16 的 4D 张量NCHW并且设置了padding选项。结果数值错误与参考实现差很多// WebNN / ORT DML const op builder.l2Pool2d(input, { padding: [1, 1, 1, 1], strides: [2, 2] }); // float16 4D 输入 padding - 输出数值错最小信号float16 4D 有 padding - 输出错 float16 4D 无 padding - 正常 float32 4D 有 padding - 正常注意内核能跑、不报错只是float16 padding 组合下 L2 池化结果错。这是 DML 后端对 fp16 下带 padding 的 L2 池化的实现 bug。二、背景l2Pool2d的计算是对池化窗口内的元素先平方、求和、再开平方根即out sqrt( sum_{w} x_w^2 )。padding 用于在边缘补数据通常补 0保证输出尺寸符合预期。在 WebNN / DML 上float16 是 GPU 原生类型为性能默认走 fp16。问题出在带 padding 的窗口里padding 区域补 0参与平方和的计算方式在 fp16 精度下被错误处理正确做法padding 补 0窗口平方和sum(x^2)只累加真实像素0 不影响和再sqrt。有 bug 的实现在 fp16 下要么 padding 区域的“0”没有被正确当作 0 参与比如用了一个非 0 的 pad 值或 padding 应用到了错误的维度要么sum(x^2)的 fp16 累加溢出/精度丢失后sqrt得到错误值尤其是在 4D 张量的 padding 解析NCHW 的 H/W 维上处理不对。三、根因根因是DML/WebNN 的l2Pool2d在 float16 4D padding 组合下padding 应用到错误维度或 pad 值非 0且平方和 fp16 累加精度丢失导致 sqrt 结果错padding 维度解析错l2Pool2d的 padding 应只作用于空间维H、W但 DML 后端在 4D NCHW 上可能把 padding 也应用到了 N 或 C 维或 padding 数量[top,bottom,left,right]映射到 H/W 时错位导致窗口取到了错误位置的元素。pad 值非 0L2 池化的 padding 必须是 0平方和里 0 不影响但后端可能用了默认 pad 值如极小值或 NaN 填充污染平方和。fp16 平方和精度/溢出sum(x^2)在 fp16 下累加大图或激活值大时平方和溢出 65504sqrt 得到 inf/错值无 padding 时窗口小、累加少侥幸正确。只影响 fp16 paddingfloat32 路径平方和不易溢出、padding 解析可能走不同代码所以正常无 padding 时窗口规整不触发错位。所以这不是逻辑错而是fp16 下带 padding 的 L2 池化在 padding 应用与累加精度上的实现 bug。四、最小可运行复现下面用 NumPy 模拟“L2 池化 padding 错误 fp16 平方和溢出”的偏差import numpy as np def l2pool2d_correct(x, pad): 正确padding 补 0窗口平方和用 fp32 累加再 sqrt。 # 简化 1D 演示在 x 两侧补 pad 个 0 xp np.pad(x.astype(np.float32), pad) window xp # 演示单窗口 s np.sum(window.astype(np.float32) ** 2) return float(np.sqrt(s)) def l2pool2d_buggy_fp16(x, pad): 有 bug用 fp16 累加平方和大值溢出。 xp np.pad(x.astype(np.float32), pad) acc np.float16(0.0) for v in xp.flatten(): acc acc np.float16(v) ** 2 # fp16 累加易溢出 return float(np.sqrt(acc)) if __name__ __main__: x np.full(8, 100.0, dtypenp.float32) # 激活值 100平方 10000 correct l2pool2d_correct(x, 1) buggy l2pool2d_buggy_fp16(x, 1) print(正确(fp32累加):, correct) print(buggy(fp16累加):, buggy, (溢出则 inf)) assert np.isfinite(correct) # buggy 在 fp16 下 sum(10000*10) 远超 65504 - inf跑出来正确写法得到有限值buggy 的 fp16 累加因平方和溢出得到 inf。这复现了“fp16 下 L2 池化平方和溢出导致结果错”padding 错位则会让窗口取到错误元素进一步放大偏差。五、解决方案第一层最小直接修复最小修复让 L2 池化的平方和在 fp32 下累加、padding 严格补 0 且仅作用于空间维。对使用者若 ORT 版本未修可临时用GlobalLpPool/等价子图或在导出时把l2Pool2d替换为MulReduceSumSqrt的 fp32 子图# 等价正确实现fp32out sqrt( reduceSum( x*x , axesspatial ) ) # 用 onnx 构造Mul(x,x) - ReduceSum(axesH,W) - Sqrt # padding 通过 Pad 算子显式补 0 到空间维后再 ReduceSum对 ORT 仓库侧修复是改 DML 的l2Pool2d内核(1) padding 仅作用于 H/W 且 pad 值恒为 0(2) 平方和用 fp32 累加最后再转 fp16 输出。这一层立刻让 float16 4D padding 结果正确。六、解决方案第二层结构性改进把“L2 池化在 fp16 padding 下如何数值稳定”收口成唯一的配置对象OrtDmlL2PoolFp16PolicyWebNN 内核选择读它from dataclasses import dataclass, field from typing import Tuple, Literal dataclass(frozenTrue) class OrtDmlL2PoolFp16Policy: DML/WebNN l2Pool2d fp16padding 数值稳定的单一事实来源。 # 平方和必须用 fp32 累加防 fp16 溢出 sum_of_squares_dtype: Literal[fp32, fp16] fp32 # padding 仅作用于空间维H,W且 pad 值恒为 0 pad_only_spatial: bool True pad_value: float 0.0 # 受影响组合 affected_combo: Tuple[str, ...] (float16, 4D, padding) # 是否对 fp16 路径强制 fp32 中间累加 force_fp32_intermediate: bool True def describe(self) - str: return l2Pool2d 在 fp16padding 下用 fp32 平方和、padding 仅补 0 到空间维 POLICY OrtDmlL2PoolFp16Policy() def plan_l2pool(policy: OrtDmlL2PoolFp16Policy POLICY) - dict: return { sq_dtype: policy.sum_of_squares_dtype, pad_spatial: policy.pad_only_spatial, pad_value: policy.pad_value, }所有 WebNN 加载与内核选择读同一份POLICYfp16 padding 的 L2 池化自动走 fp32 平方和 正确 padding。七、解决方案第三层断言 / CI 守护把“L2 池化 fp16padding 数值正确”做成断言。下面用 pytest 风格守护复用第四节逻辑import numpy as np def test_l2pool_fp32_accum_finite(): x np.full(8, 100.0, dtypenp.float32) assert np.isfinite(l2pool2d_correct(x, 1)) def test_fp16_accum_overflow(policy): # 证明 fp16 平方和会溢出 - 必须用 fp32 assert policy.sum_of_squares_dtype fp32 def test_padding_only_spatial(policy): assert policy.pad_only_spatial is True assert policy.pad_value 0.0 def test_affected_combo_covered(policy): assert float16 in policy.affected_combo assert padding in policy.affected_combo这四组断言锁住(1) fp32 平方和有限(2) 强制 fp32 累加证明 fp16 会溢出(3) padding 仅空间维且值为 0(4) 受影响组合已覆盖。CI 跑通即代表 L2 池化不再错。八、排查清单遇到 DML/WebNN l2Pool2d float16 4D padding 结果错先去掉 padding 试无 padding 正常、有 padding 错 - 锁定 padding 处理。换 float32 试float32 正常、float16 错 - 锁定 fp16 精度/溢出。查 padding 应用维度是否只作用于 H/Wpad 值是否 0。查平方和累加精度fp16 累加平方和是否溢出应改 fp32。临时规避导出时展开成 MulReduceSumSqrt 的 fp32 子图padding 用 Pad 显式补 0。统一策略对象用OrtDmlL2PoolFp16Policy固化。CI 守护断言 fp32 平方和、padding 仅空间维且为 0。九、小结[ORT GPU (DML)][WebNN] wrong results when running the l2Pool2d float16 4D tensor options.padding test的根因是DML/WebNN 的l2Pool2d在 float16 4D padding 组合下padding 被错误应用维度错位或非 0 pad 值污染了窗口平方和且平方和在 fp16 下累加溢出导致sqrt结果错误无 padding 或 float32 时侥幸正常。最小修复是让 L2 池化的平方和用 fp32 累加、padding 严格补 0 且仅作用于空间维必要时把 op 展开成 MulReduceSumSqrt 的 fp32 子图结构性改进是用唯一的OrtDmlL2PoolFp16Policy固化精度与 padding 策略CI 用四组断言守护“fp32 平方和、padding 仅空间维且为 0、组合已覆盖”。记住L2 池化的平方和必须在 fp32 下累加padding 必须补 0 且仅作用于空间维否则 fp16 下溢出、padding 错位一起把结果搞错。
返回列表