Closed
fix: 正确比较Promote有限参考值与低精度输出 #483
raoliang_sacAdd on 20 days agoClosed at 20 days ago
fix: 正确比较Promote有限参考值与低精度输出 #483
Closed
Total: 2 files changed+32-3
| @@ -10,6 +10,7 @@ | |||
| 10 | """Tests for CrossCheckComparison: pass/fail/ratio/small_value/nan + safe_div.""" | 10 | """Tests for CrossCheckComparison: pass/fail/ratio/small_value/nan + safe_div.""" |
| 11 | 11 | ||
| 12 | import numpy as np | 12 | import numpy as np |
| 13 | +import pytest | ||
| 13 | 14 | ||
| 14 | from ttk.core_modules.common.comparison.cross_check import CrossCheckComparison, safe_div | 15 | from ttk.core_modules.common.comparison.cross_check import CrossCheckComparison, safe_div |
| 15 | 16 | ||
| @@ -18,6 +19,24 @@ def _make(output, golden, third_party, params): | |||
| 18 | return CrossCheckComparison(output, golden, 0, "float32", params, third_party=third_party) | 19 | return CrossCheckComparison(output, golden, 0, "float32", params, third_party=third_party) |
| 19 | 20 | ||
| 20 | 21 | ||
| 22 | +@pytest.mark.parametrize(("dtype", "bits"), [(np.float16, 11), (np.float32, 24)]) | ||
| 23 | +@pytest.mark.parametrize("fraction", [1.0, 1.5]) | ||
| 24 | +@pytest.mark.parametrize("sign", [1.0, -1.0]) | ||
| 25 | +@pytest.mark.parametrize("competitor", [np.inf, -np.inf, np.nan]) | ||
| 26 | +def test_promoted_finite_reference_with_nonfinite_competitor(dtype, bits, fraction, sign, competitor): | ||
| 27 | + """Correct output rounding must survive an independently overflowing GPU.""" | ||
| 28 | + golden = np.array([sign * (1.0 + fraction * 2.0**-bits)], dtype=np.float64) | ||
| 29 | + output = golden.astype(dtype) | ||
| 30 | + third = np.array([competitor], dtype=dtype) | ||
| 31 | + c = CrossCheckComparison(output, golden, 0, np.dtype(dtype).name, _NAN_PARAMS, third_party=third) | ||
| 32 | + assert c.compare()[2] | ||
| 33 | + # One adjacent representable value is already wrong: this is not a relaxed | ||
| 34 | + # atol/rtol fallback, nor a blanket exemption for a non-finite competitor. | ||
| 35 | + wrong = np.nextafter(output, np.array([np.inf], dtype=dtype)) | ||
| 36 | + c = CrossCheckComparison(wrong, golden, 0, np.dtype(dtype).name, _NAN_PARAMS, third_party=third) | ||
| 37 | + assert not c.compare()[2] | ||
| 38 | + | ||
| 39 | + | ||
| 21 | def test_pass_when_all_good(): | 40 | def test_pass_when_all_good(): |
| 22 | """三方误差一致 → PASS + metrics 结构。""" | 41 | """三方误差一致 → PASS + metrics 结构。""" |
| 23 | params = { | 42 | params = { |
| @@ -154,7 +154,7 @@ class CrossCheckComparison(ComparisonBase): | |||
| 154 | """单块三路径扫描:special 交叉校验 / large 比值累计 / small 误差计数。""" | 154 | """单块三路径扫描:special 交叉校验 / large 比值累计 / small 误差计数。""" |
| 155 | special = np.isnan(tc) | np.isinf(tc) | np.isnan(gc) | np.isinf(gc) | np.isnan(bc) | np.isinf(bc) | 155 | special = np.isnan(tc) | np.isinf(tc) | np.isnan(gc) | np.isinf(gc) | np.isnan(bc) | np.isinf(bc) |
| 156 | finite = ~special | 156 | finite = ~special |
| 157 | - if state.special_ok and special.any() and self._special_fail(tc, gc, bc, special).any(): | 157 | + if state.special_ok and special.any() and self._special_fail(tc, gc, bc, special, self.output.dtype).any(): |
| 158 | state.special_ok = False | 158 | state.special_ok = False |
| 159 | large = finite & (np.abs(gc) >= sv["small_value"]) | 159 | large = finite & (np.abs(gc) >= sv["small_value"]) |
| 160 | small = finite & (np.abs(gc) < sv["small_value"]) | 160 | small = finite & (np.abs(gc) < sv["small_value"]) |
| @@ -188,12 +188,12 @@ class CrossCheckComparison(ComparisonBase): | |||
| 188 | state.top_small.add(d[mis], (np.flatnonzero(small) + s)[mis]) | 188 | state.top_small.add(d[mis], (np.flatnonzero(small) + s)[mis]) |
| 189 | 189 | ||
| 190 | @staticmethod | 190 | @staticmethod |
| 191 | - def _special_fail(t, g, b, special) -> np.ndarray: | 191 | + def _special_fail(t, g, b, special, output_dtype=None) -> np.ndarray: |
| 192 | """Path A: 特殊位(NaN/Inf)三方交叉校验——失败位掩码。 | 192 | """Path A: 特殊位(NaN/Inf)三方交叉校验——失败位掩码。 |
| 193 | 193 | ||
| 194 | 规则(满足任一即通过): | 194 | 规则(满足任一即通过): |
| 195 | 1. NPU 与 third_party 一致(同为 nan/+inf/-inf,或同为有限且数值相等)→ 通过 | 195 | 1. NPU 与 third_party 一致(同为 nan/+inf/-inf,或同为有限且数值相等)→ 通过 |
| 196 | - 2. NPU 与 golden 一致(同为 nan/+inf/-inf,或同为有限且数值相等)→ 通过 | 196 | + 2. NPU 与 golden 一致(同为 nan/+inf/-inf,或有限 golden 舍入到输出类型后相等)→ 通过 |
| 197 | 以上都不满足(NPU 与 golden、third_party 双方均不一致)→ 不通过 | 197 | 以上都不满足(NPU 与 golden、third_party 双方均不一致)→ 不通过 |
| 198 | 198 | ||
| 199 | 规则2 不要求 golden 非有限:golden 有限时真值明确,NPU 与其一致即无偏差, | 199 | 规则2 不要求 golden 非有限:golden 有限时真值明确,NPU 与其一致即无偏差, |
| @@ -210,6 +210,16 @@ class CrossCheckComparison(ComparisonBase): | |||
| 210 | tb_match = (t_nan & b_nan) | (t_pinf & b_pinf) | (t_ninf & b_ninf) | (t_fin & b_fin & (t == b)) | 210 | tb_match = (t_nan & b_nan) | (t_pinf & b_pinf) | (t_ninf & b_ninf) | (t_fin & b_fin & (t == b)) |
| 211 | # 规则2:NPU 与 golden 一致(同为 nan/+inf/-inf,或同为有限且数值相等) | 211 | # 规则2:NPU 与 golden 一致(同为 nan/+inf/-inf,或同为有限且数值相等) |
| 212 | tg_match = (t_nan & g_nan) | (t_pinf & g_pinf) | (t_ninf & g_ninf) | (t_fin & g_fin & (t == g)) | 212 | tg_match = (t_nan & g_nan) | (t_pinf & g_pinf) | (t_ninf & g_ninf) | (t_fin & g_fin & (t == g)) |
| 213 | + # Promote retains a higher-precision reference. In this special-value | ||
| 214 | + # path, a finite output must equal its correctly rounded reference, | ||
| 215 | + # rather than an unrepresentable FP64 value. Normal finite comparisons | ||
| 216 | + # still use the original reference and the unchanged error ratios. | ||
| 217 | + if output_dtype is not None: | ||
| 218 | + candidates = special & t_fin & g_fin & ~b_fin | ||
| 219 | + if candidates.any(): | ||
| 220 | + with np.errstate(over="ignore", invalid="ignore"): | ||
| 221 | + rounded = g[candidates].astype(output_dtype).astype(g.dtype) | ||
| 222 | + tg_match[candidates] |= t[candidates] == rounded | ||
| 213 | 223 | ||
| 214 | return special & ~(tb_match | tg_match) | 224 | return special & ~(tb_match | tg_match) |
| 215 | 225 | ||