Download code/tt_sceneseg/tests/test_reference_host.py from changh95/sceneseg-p150: direct link, hf CLI and curl.
- Browser
- Download file 11.5 kB
-
https://huggingface.co/changh95/sceneseg-p150/resolve/main/code/tt_sceneseg/tests/test_reference_host.py
- Command line
-
hf download hf://changh95/sceneseg-p150/code/tt_sceneseg/tests/test_reference_host.py
-
curl -L -o test_reference_host.py https://huggingface.co/changh95/sceneseg-p150/resolve/main/code/tt_sceneseg/tests/test_reference_host.py
11.5 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """The CPU reference without onnxruntime (host suite: tt-env, research venv): architecture, weights loaders, the | |
| fp32 forward against the stored goldens, teacher-forced partial runs and the stored smoke references. | |
| TT_VISIBLE_DEVICES=none python -m pytest -q code/tt_sceneseg/tests/test_reference_host.py | |
| The weights are found without downloading (``reference.assets``: ``SCENESEG_WEIGHTS_DIR``, the staged | |
| ``weights_upload/``, the workspace download); tests that need them skip elsewhere. The goldens were written in the | |
| research venv (torch 2.14) by ``code/scripts/ref_golden.py``; this suite may run on another torch (tt-env: 2.11). | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import numpy as np | |
| import pytest | |
| from tt_sceneseg.reference import assets, config | |
| from tt_sceneseg.reference.weights import ARCH, ARCH_BY_KEY, WeightsError, load_weights, param_count | |
| from tt_sceneseg.tests.research import GOLDENS, LARGE_GOLDENS, SAMPLE, SAMPLES | |
| ONNX = assets.onnx_path(required=False) | |
| needs_onnx = pytest.mark.skipif(ONNX is None, reason="SceneSeg_FP32.onnx not found (set SCENESEG_WEIGHTS_DIR)") | |
| def _macs(spec, hw): | |
| """MACs of one layer at its input resolution ``hw``; returns (macs, output hw).""" | |
| h, w = hw | |
| if spec.kind == "linear": | |
| return spec.cin * spec.cout, hw | |
| if spec.kind == "conv_transpose": | |
| return spec.cin * spec.cout * spec.k * spec.k * h * w, (h * spec.stride, w * spec.stride) | |
| oh, ow = (h + 2 * spec.pad - spec.k) // spec.stride + 1, (w + 2 * spec.pad - spec.k) // spec.stride + 1 | |
| return spec.cout * oh * ow * (spec.cin // spec.groups) * spec.k * spec.k, (oh, ow) | |
| def test_architecture_counts(): | |
| """108 layers (100 Conv, 5 ConvTranspose, 3 Gemm), 16 depthwise, 48,341,495 parameters and 183.514 GMAC at | |
| batch 1 (research/sceneseg/SPEC.md 4.1, verify_onnx.json).""" | |
| assert len(ARCH) == 108 and len(ARCH_BY_KEY) == 108 | |
| kinds = [s.kind for s in ARCH] | |
| assert (kinds.count("conv"), kinds.count("conv_transpose"), kinds.count("linear")) == (100, 5, 3) | |
| dw = [s for s in ARCH if s.depthwise] | |
| assert len(dw) == 16 and sorted({(s.k, s.stride) for s in dw}) == [(3, 1), (3, 2), (5, 1), (5, 2)] | |
| params = sum(int(np.prod(s.weight_shape)) + s.cout for s in ARCH) | |
| assert params == 48_341_495 | |
| # MACs: walk the layers at their resolutions (backbone strides, context 10x20, decoder up-sampling) | |
| res = {"Backbone": (320, 640)} | |
| total = 0 | |
| hw = (320, 640) | |
| for s in ARCH: | |
| if s.key.startswith("Backbone"): | |
| if s.key.endswith((".fc1", ".fc2")): | |
| m, _ = _macs(s, (1, 1)) | |
| else: | |
| m, hw = _macs(s, hw) | |
| res["Backbone"] = hw | |
| elif s.kind == "linear": | |
| m, _ = _macs(s, (1, 1)) | |
| elif s.key.startswith("SceneContext"): | |
| m, _ = _macs(s, config.CONTEXT_HW) | |
| hw = config.CONTEXT_HW | |
| elif s.kind == "conv_transpose": | |
| m, hw = _macs(s, hw) | |
| else: | |
| m, _ = _macs(s, hw) | |
| total += m | |
| assert res["Backbone"] == (10, 20) and hw == (320, 640) | |
| assert total == pytest.approx(183.514e9, rel=1e-5) | |
| def test_onnx_weights(): | |
| w = load_weights(ONNX) | |
| assert len(w) == 108 and param_count(w) == 48_341_495 | |
| assert all(lw.folded_bn is None for lw in w.values()) # BN was folded at export | |
| assert all(lw.weight.dtype == np.float32 and lw.weight.flags["C_CONTIGUOUS"] for lw in w.values()) | |
| sd = assets.state_dict_path(required=False) | |
| if sd is not None: # the .pth / safetensors cross-check | |
| from tt_sceneseg.reference.weights import load_state_dict_weights | |
| p = load_state_dict_weights(sd) | |
| for k, lw in w.items(): | |
| ref = p[k] | |
| assert np.abs(lw.weight - ref.weight).max() <= 2e-7 * max(np.abs(ref.weight).max(), 1.0), k | |
| assert np.abs(lw.bias - ref.bias).max() <= 1e-5, k | |
| if lw.spec.bn is None: # unfolded layers: the same numbers | |
| np.testing.assert_array_equal(lw.weight, ref.weight) | |
| np.testing.assert_array_equal(lw.bias, ref.bias) | |
| def test_loader_refuses_other_files(tmp_path, monkeypatch): | |
| bad = tmp_path / config.ONNX_FILE | |
| bad.write_bytes(b"not an onnx file") | |
| with pytest.raises(Exception): | |
| load_weights(bad) | |
| if ONNX is not None: # another export: refused by its sha256 | |
| monkeypatch.setattr(config, "ONNX_SHA256", "0" * 64) | |
| with pytest.raises(WeightsError, match="sha256"): | |
| load_weights(ONNX) | |
| assert len(load_weights(ONNX, check_sha256=False)) == len(ARCH) # ... unless asked not to check | |
| def _small_golden(name="highway_normal_1"): | |
| path = GOLDENS / f"{name}.npz" | |
| if not path.is_file(): | |
| pytest.skip("tests/goldens not generated (code/scripts/ref_golden.py)") | |
| return np.load(path) | |
| def test_forward_matches_stored_goldens(order): | |
| """The fp32 forward on the shipped sample against the stored goldens of the research venv run: logits at the | |
| sampled pixels PCC >= 0.99999, class-map agreement >= 0.9999, tap statistics within 1e-4 relative.""" | |
| torch = pytest.importorskip("torch") | |
| from tt_sceneseg.host.postprocess import class_map_from_logits | |
| from tt_sceneseg.host.preprocess import prepare_image | |
| from tt_sceneseg.reference.model import SceneSegReference | |
| from tt_sceneseg.ttaw.golden import TapRegistry | |
| from tt_sceneseg.ttaw.metrics import pcc | |
| torch.set_num_threads(4) | |
| g = _small_golden() | |
| meta = json.loads((GOLDENS / "highway_normal_1.json").read_text()) | |
| x = prepare_image(str(SAMPLE), channel_order=order).network_input() | |
| taps = TapRegistry(exclude=config.LARGE_TAPS) | |
| logits = SceneSegReference.from_onnx(ONNX).run_numpy(x, taps=taps)["logits"] | |
| yx = g["sample_yx"] | |
| assert pcc(logits[0][:, yx[0], yx[1]], g[f"{order}.logits_samples"]) >= 0.99999 | |
| assert (class_map_from_logits(logits) == g[f"{order}.class_map"]).mean() >= 0.9999 | |
| for name, st in meta["variants"][order]["taps"].items(): | |
| if name in taps.names(): | |
| v = taps[name] | |
| assert list(v.shape) == st["shape"], name | |
| scale = max(st["absmax"], 1e-6) | |
| assert abs(float(v.mean()) - st["mean"]) <= 1e-4 * scale and abs(float(v.std()) - st["std"]) <= 1e-4 * scale | |
| def test_teacher_forced_runs_equal_the_full_forward(): | |
| """``run_from`` (the inputs of a TT module case are reference taps) reproduces the full forward.""" | |
| pytest.importorskip("torch") | |
| from tt_sceneseg.host.preprocess import prepare_image | |
| from tt_sceneseg.reference.model import SceneSegReference | |
| from tt_sceneseg.ttaw.golden import TapRegistry | |
| ref = SceneSegReference.from_onnx(ONNX) | |
| taps = TapRegistry(include=("l0", "l2", "l3", "l4", "l8", "context", "neck", "logits")) | |
| full = ref.run_numpy(prepare_image(str(SAMPLE)).network_input(), taps=taps)["logits"] | |
| t = taps.to_dict() | |
| for start in ("context", "neck", "head"): | |
| got = ref.run_from(start, t)["logits"] | |
| np.testing.assert_allclose(got, full, rtol=0, atol=1e-4, err_msg=start) | |
| with pytest.raises(ValueError): | |
| ref.run_from("backbone", t) | |
| def test_pipeline_equals_stored_smoke_reference(order): | |
| """``samples/highway_normal_1.<variant>.reference.json`` (the container smoke's reference) is what the | |
| reference pipeline returns: the same published mask and class map (agreement >= 0.9999 across torch builds).""" | |
| pytest.importorskip("torch") | |
| from tt_sceneseg.reference.pipeline import ReferencePipeline | |
| from tt_sceneseg.ttaw.server import smoke | |
| path = SAMPLES / f"highway_normal_1.{order}.reference.json" | |
| if not path.is_file(): | |
| pytest.skip("stored reference not generated (code/scripts/ref_golden.py)") | |
| stored = json.loads(path.read_text()) | |
| body = ReferencePipeline(ONNX, order, threads=4).reference_body(str(SAMPLE)) | |
| metrics, fails = smoke.compare_with_reference(body, stored, gates={"min_label_agreement": 0.9999}) | |
| assert fails == [], fails | |
| assert set(metrics) == {"mask", "class_map"} | |
| assert body["meta"]["channel_order"] == order and body["class_names"] == list(config.CLASS_NAMES) | |
| def test_large_goldens_index(): | |
| """research/sceneseg/goldens/index.json (written with the goldens): the module taps of the gate samples exist | |
| with the recorded sha256 (sizes only checked here; the device tests read them).""" | |
| idx = LARGE_GOLDENS / "index.json" | |
| if not idx.is_file(): | |
| pytest.skip("research/sceneseg/goldens not generated") | |
| meta = json.loads(idx.read_text()) | |
| for rel, rec in meta["files"].items(): | |
| p = LARGE_GOLDENS / rel | |
| assert p.is_file() and p.stat().st_size == rec["bytes"], rel | |
| def test_comma10k_goldens_reproduce_the_spec(): | |
| """The comma10k ground-truth metric (tests/sceneseg_eval.py) on the stored fp32 class maps (shipped copies in | |
| tests/goldens/comma10k/): the per-image IoUs of | |
| the index, and the SPEC section 3 means (training ROI crop: foreground 0.729 bgr / 0.765 rgb, road 0.907 / | |
| 0.919): the FP32 side of the PLAN 2.2 comma10k gate.""" | |
| from tt_sceneseg.tests.sceneseg_eval import COMMA10K_GOLDENS, comma10k_ious | |
| rec = json.loads((COMMA10K_GOLDENS / "index.json").read_text()) # shipped since the release docs | |
| for name, r in rec["images"].items(): | |
| with np.load(COMMA10K_GOLDENS / f"{name[:4]}.npz") as z: | |
| for order in config.CHANNEL_ORDERS: | |
| assert comma10k_ious(z[f"cls_{order}"], z["gt"]) == pytest.approx(r[order], nan_ok=True), (name, order) | |
| spec = {"bgr": (0.729, 0.907), "rgb": (0.765, 0.919)} | |
| for order, (fg, road) in spec.items(): | |
| assert rec["mean"][order]["foreground"] == pytest.approx(fg, abs=1e-3) | |
| assert rec["mean"][order]["road"] == pytest.approx(road, abs=1e-3) | |
| def test_shipped_comma10k_matches_the_gate_inputs(): | |
| """The comma10k images, masks and fp32 goldens shipped since the release docs are byte-identical to the ones the | |
| frozen comma10k gate was set on (sha256 of research/sceneseg/goldens/index.json, recorded with the goldens), and | |
| the workspace copies (when present) still match.""" | |
| import hashlib | |
| from tt_sceneseg.tests.sceneseg_eval import COMMA10K_GOLDENS, comma10k_samples | |
| def sha(p): | |
| return hashlib.sha256(p.read_bytes()).hexdigest() | |
| rec = json.loads((COMMA10K_GOLDENS / "index.json").read_text()) | |
| samples = comma10k_samples() | |
| assert sorted(samples) == sorted(rec["images"]) | |
| for name, (img, mask) in samples.items(): | |
| assert sha(img) == rec["images"][name]["image_sha256"], name | |
| assert sha(mask) == rec["images"][name]["mask_sha256"], name | |
| for fname, f in rec["files"].items(): | |
| assert sha(COMMA10K_GOLDENS / fname) == f["sha256"], fname | |
| idx = LARGE_GOLDENS / "index.json" | |
| if idx.is_file(): | |
| large = json.loads(idx.read_text()) | |
| assert large["comma10k"]["images"] == rec["images"] and large["comma10k"]["mean"] == rec["mean"] | |
| for fname, f in rec["files"].items(): | |
| assert large["files"][f"comma10k/{fname}"] == f, fname | |