Closed
fix: 正确比较Promote有限参考值与低精度输出 #483
raoliang_sacAdd on 20 days agoClosed at 20 days ago
fix: 正确比较Promote有限参考值与低精度输出 #483
Closed
raoliang_sacAdd on 20 days agoClosed at 20 days ago
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 
12import numpy as np12import numpy as np
13+import pytest
13 14 
14from ttk.core_modules.common.comparison.cross_check import CrossCheckComparison, safe_div15from 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+ 
21def test_pass_when_all_good():40def 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 = ~special156 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 = False158 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 @staticmethod190 @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