File size: 6,628 Bytes
aec3f16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# SPDX-License-Identifier: Apache-2.0
"""The CPU reference (``tt_sceneseg.reference``) against onnxruntime on the deployed ONNX: the oracle of every gate.

    PYTHONPATH=code tools/research-venv/bin/python -m pytest -q code/tt_sceneseg/tests/test_reference_cpu.py

Runs in the research venv (onnxruntime is not a dependency of the bundle; skipped where it is absent, e.g. in the
tt-metal environment and the image). ORT runs ``SceneSeg_FP32.onnx`` as shipped with every tap of ``config.TAPS``
exposed as an extra output; the torch reference runs on the same input (the node's exact pre-processing):

- before the data-dependent decision: every tap and the logits at PCC >= 0.9999 (PLAN.md 0.3 item 1; measured
  >= 0.9999999995);
- after it (the per-pixel argmax): set-based, the class maps and the published masks agree on >= 99.99 % of the
  pixels (fp32 vs fp32 differ only where two logits tie to within the accumulation-order noise).

Also: the reference built from the upstream state_dict (BN folded in fp64) against ORT, the public frames of
research/sceneseg/public_data and the research ORT goldens, and the SPEC's stored golden inputs.
"""
from __future__ import annotations

import numpy as np
import pytest

pytest.importorskip("onnxruntime")
pytest.importorskip("onnx")
torch = pytest.importorskip("torch")

from tt_sceneseg.host.postprocess import class_map_from_logits, postprocess  # noqa: E402
from tt_sceneseg.host.preprocess import prepare_bgr, prepare_image  # noqa: E402
from tt_sceneseg.reference import assets, config  # noqa: E402
from tt_sceneseg.reference.model import SceneSegReference  # noqa: E402
from tt_sceneseg.reference.ort import OrtReference  # noqa: E402
from tt_sceneseg.tests.research import PUBLIC, SAMPLE, SPEC_REF_RUNS, public_ids  # noqa: E402
from tt_sceneseg.ttaw.golden import TapRegistry  # noqa: E402
from tt_sceneseg.ttaw.metrics import error_stats, pcc  # noqa: E402

PCC_MIN = 0.9999
AGREE_MIN = 0.9999
ONNX = assets.onnx_path(required=False)
pytestmark = pytest.mark.skipif(ONNX is None, reason="SceneSeg_FP32.onnx not found (set SCENESEG_WEIGHTS_DIR)")


@pytest.fixture(scope="module", autouse=True)
def _threads():
    old = torch.get_num_threads()
    torch.set_num_threads(4)
    yield
    torch.set_num_threads(old)


@pytest.fixture(scope="module")
def ref():
    return SceneSegReference.from_onnx(ONNX)


@pytest.fixture(scope="module")
def ort_taps():
    return OrtReference(ONNX, taps=config.TAPS, threads=4)


@pytest.fixture(scope="module")
def ort_plain():
    return OrtReference(ONNX, threads=4)


def _agree(a_logits, b_logits, hw):
    a, b = postprocess(hw, logits=a_logits), postprocess(hw, logits=b_logits)
    return (a.class_map == b.class_map).mean(), (a.mask == b.mask).mean()


@pytest.mark.parametrize("order", config.CHANNEL_ORDERS)
def test_every_tap_matches_onnxruntime(order, ref, ort_taps):
    prep = prepare_image(str(SAMPLE), channel_order=order)
    x = prep.network_input()
    want = ort_taps.run(x)
    rec = TapRegistry()
    got = ref.run_numpy(x, taps=rec)
    report = {}
    for name in config.TAPS:
        assert rec[name].shape == want[name].shape, (name, rec[name].shape, want[name].shape)
        report[name] = error_stats(rec[name], want[name])
    worst = min(report.items(), key=lambda kv: kv[1]["pcc"])
    print(f"{order}: worst tap {worst[0]} pcc={worst[1]['pcc']:.12f}; logits max|d|={report['logits']['max_abs']:.2e}")
    bad = {k: v["pcc"] for k, v in report.items() if not v["pcc"] >= PCC_MIN}
    assert bad == {}, f"taps below PCC {PCC_MIN}: {bad}"
    assert pcc(got["logits"], want["logits"]) >= PCC_MIN
    cls_agree, mask_agree = _agree(got["logits"], want["logits"], prep.src_hw)        # after the decision: set-based
    assert cls_agree >= AGREE_MIN and mask_agree >= AGREE_MIN
    # the tapped session computes the same logits as the plain one
    np.testing.assert_array_equal(want["logits"], OrtReference(ONNX, threads=4).run(x)["logits"])


def test_state_dict_reference_matches_onnxruntime(ort_plain):
    """The reference built from the upstream state_dict (re-hosted safetensors or the .pth; BN folded in fp64)."""
    sd = assets.state_dict_path(required=False)
    if sd is None:
        pytest.skip("the .pth / safetensors is not available")
    prep = prepare_image(str(SAMPLE))
    x = prep.network_input()
    got = SceneSegReference.from_state_dict(sd).run_numpy(x)["logits"]
    want = ort_plain.run(x)["logits"]
    assert pcc(got, want) >= PCC_MIN
    cls_agree, mask_agree = _agree(got, want, prep.src_hw)
    assert cls_agree >= AGREE_MIN and mask_agree >= AGREE_MIN


@pytest.mark.parametrize("fid", ["ps019_f40_front_camera", "c10k_0009", "tut_highway_rain_2"])
def test_public_frames(fid, ref, ort_plain):
    """research/sceneseg/public_data frames, both orders: the reference vs ORT on the exact input, and ORT on the
    exact input vs the research ORT golden (whose input was one ulp off: float32 division)."""
    from PIL import Image

    if fid not in public_ids():
        pytest.skip(f"{fid} not converted")
    with Image.open(PUBLIC / "inputs" / fid / "source_bgr.png") as im:
        bgr = np.ascontiguousarray(np.asarray(im.convert("RGB"))[:, :, ::-1])
    gold = np.load(PUBLIC / "golden" / fid / "ort_fp32.npz")
    for order in config.CHANNEL_ORDERS:
        prep = prepare_bgr(bgr, channel_order=order)
        x = prep.network_input()
        want = ort_plain.run(x)["logits"]
        got = ref.run_numpy(x)["logits"]
        assert pcc(got, want) >= PCC_MIN, (fid, order)
        assert min(_agree(got, want, prep.src_hw)) >= AGREE_MIN, (fid, order)
        assert pcc(want, gold[f"logits_{order}"]) >= 0.9999999, (fid, order)
        assert (class_map_from_logits(want) == gold[f"cls_{order}"]).mean() >= AGREE_MIN, (fid, order)


def test_spec_golden_inputs(ref):
    """The SPEC's stored golden tensors (research/sceneseg/ref_runs/highway_normal_{1,2}: the deployed and the
    RGB-bilinear inputs with their ORT logits): the reference on the stored inputs reproduces the stored logits."""
    for name in ("highway_normal_1", "highway_normal_2"):
        for tag in ("cpp_bgr", "cpp_rgb"):
            p = SPEC_REF_RUNS / name / f"{tag}_input_f32.npy"
            if not p.is_file():
                pytest.skip("research/sceneseg/ref_runs not present")
            got = ref.run_numpy(np.load(p))["logits"]
            want = np.load(SPEC_REF_RUNS / name / f"{tag}_logits_ort_f32.npy")
            assert pcc(got, want) >= PCC_MIN, (name, tag)
            assert (class_map_from_logits(got) == class_map_from_logits(want)).mean() >= AGREE_MIN, (name, tag)