transfusion-p150 / code /tt_transfusion /tests /test_reference_cpu.py
changh95's picture
tt-model push transfusion-p150 (container)
68a3286 verified
Raw History Blame Contribute Delete
9.95 kB
# SPDX-License-Identifier: Apache-2.0
"""The CPU reference (``reference/``: ONNX initializers by node, BN folded where the port folds it, torch fp32) vs
ONNX Runtime on the deployed ONNX (PLAN.md 0.3 item 1; BUNDLE_CONVENTIONS.md section 10), on identical voxel tensors
(the host pre-processing of ``host/``):
- **before the data-dependent selection** every tap (decorated PFN input, PFN layer 0, PFN, canvas, every backbone
conv, the deblocks, the neck concat, shared conv, heatmap head, the local-max heatmap, the input-independent key
position embedding) at PCC >= 0.9999 against the same tensor of the deployed graph (exposed as an extra ORT output);
- **the top-500 selection set-based**: >= 99 % of the set shared, and every ORT proposal with heat >= 0.1 (the
"strong proposals" of PLAN.md 2.5) in the reference's set (two fp32 implementations may swap low-score tail entries,
S:transfusion:221);
- **after it teacher-forced**: the reference run with ORT's own top-k indices reproduces every downstream tap
(query features / positions / heat, decoder layers, prediction heads, the three outputs) at PCC >= 0.9999,
position by position;
- **end to end set-based**: the same detections after Autoware's post-processing (same label, centre within 1 cm,
score within 1e-4) and the same counts at every stage;
- the plain ORT session (no extra outputs) reproduces the research's bitwise output goldens on their voxel tensors,
and the reference reproduces the research's expected detections of the shipped PandaSet sample.
Cases: the shipped samples (PandaSet 019 f40, test.pcd), the Autoware sample-rosbag frame 150 (62,527 pillars: above
Autoware's 60,000, kept whole by N_MAX = 65,536; research data, license unstated) and the same frame through the
``autoware_compat`` shuffle emulation. Runs in the research venv (onnxruntime + torch, CPU only; threads <= 4)::
cd bundles/transfusion-p150 && OMP_NUM_THREADS=4 PYTHONPATH=code \\
/home/ubuntu/experiments/tt-models/tools/research-venv/bin/python -m pytest -q -p no:cacheprovider \\
code/tt_transfusion/tests/test_reference_cpu.py
Skipped where onnxruntime or the weights are absent.
"""
from __future__ import annotations
import json
import numpy as np
import pytest
pytest.importorskip("onnxruntime")
pytest.importorskip("onnx")
torch = pytest.importorskip("torch")
from tt_transfusion.reference.model import SELECTION_TAPS # noqa: E402
from tt_transfusion.reference.ort import ALL_TAPS # noqa: E402
from tt_transfusion.reference.pipeline import TransFusionReference # noqa: E402
from tt_transfusion.tests import _data # noqa: E402
from tt_transfusion.ttaw.golden import TapRegistry # noqa: E402
from tt_transfusion.ttaw.metrics import match_detections, pcc # noqa: E402
PCC_MIN = 0.9999 # PLAN.md 0.3 item 1
THREADS = 4
STRONG = 0.1 # PLAN.md 2.5: overlap of strong proposals (heat >= 0.1) 100 %
CASES = {
"pandaset_019_f40": ("sample", "pandaset_019_f40.npz", None),
"test_pcd": ("sample", "test_pcd.npz", None),
"rosbag_0150": ("research", "frame_0150", None),
"rosbag_0150_compat": ("research", "frame_0150", {"mode": "autoware_compat", "offset": 12345}),
}
@pytest.fixture(scope="module")
def references():
w = _data.weights_dir()
torch.set_num_threads(THREADS)
return (TransFusionReference(w, backend="torch", threads=THREADS),
TransFusionReference(w, backend="ort", threads=THREADS, intermediates=True))
def _source(case):
kind, name, sampling = CASES[case]
src = _data.sample_path(name) if kind == "sample" else _data.research_cloud(name)
return src, sampling
_RUNS = {}
def _runs(references, case):
if case not in _RUNS:
rt, ro = references
src, sampling = _source(case)
tt, to = TapRegistry(), TapRegistry() # every tap (module and per layer)
a = rt.run(src, sampling=sampling, taps=tt)
b = ro.run(src, sampling=sampling, taps=to)
_RUNS.clear() # keep one case in memory (~1.5 GB of taps)
_RUNS[case] = (a, b, tt, to)
return _RUNS[case]
@pytest.mark.parametrize("case", list(CASES))
def test_taps_before_the_selection_match_onnxruntime(references, case):
a, b, tt, to = _runs(references, case)
assert np.array_equal(a.prepared.features, b.prepared.features) # identical decorated features
names = [n for n in ALL_TAPS if n not in SELECTION_TAPS]
assert set(names) <= set(to.names()) and set(names) <= set(tt.names())
worst = {}
for name in names:
x, y = tt[name], to[name]
assert x.size == y.size, name
worst[name] = pcc(x, y.reshape(x.shape))
print(case, json.dumps({k: round(v, 10) for k, v in sorted(worst.items(), key=lambda kv: kv[1])[:5]}))
bad = {k: v for k, v in worst.items() if not v >= PCC_MIN}
assert not bad, bad
@pytest.mark.parametrize("case", list(CASES))
def test_topk_selection_set_based(references, case):
a, b, tt, to = _runs(references, case)
mine, ref = tt["head.topk_indices"].ravel(), to["head.topk_indices"].ravel()
k = ref.size
shared = len(set(mine.tolist()) & set(ref.tolist()))
strong = ref[to["head.topk_values"].ravel() >= STRONG]
print(case, f"top-{k} shared {shared}, strong {len(strong)}, positional {np.mean(mine == ref):.4f}")
assert shared >= 0.99 * k
assert set(strong.tolist()) <= set(mine.tolist())
np.testing.assert_allclose(np.sort(tt["head.topk_values"].ravel()), np.sort(to["head.topk_values"].ravel()),
atol=1e-4)
@pytest.mark.parametrize("case", list(CASES))
def test_teacher_forced_decoder_matches_onnxruntime(references, case):
rt, _ = references
_, b, _, to = _runs(references, case)
tf = TapRegistry(include=SELECTION_TAPS)
rt.network(b.prepared, tf, topk_indices=to["head.topk_indices"])
assert np.array_equal(tf["head.topk_indices"], to["head.topk_indices"])
worst = {}
for name in SELECTION_TAPS:
if name == "head.topk_indices":
continue
x, y = tf[name], to[name]
assert x.size == y.size, name
worst[name] = pcc(x, y.reshape(x.shape))
print(case, json.dumps({k: round(v, 10) for k, v in sorted(worst.items(), key=lambda kv: kv[1])[:5]}))
bad = {k: v for k, v in worst.items() if not v >= PCC_MIN}
assert not bad, bad
assert np.array_equal(tf["query.pos"], to["query.pos"]) # exact arithmetic query_pos
@pytest.mark.parametrize("case", list(CASES))
def test_detections_match_onnxruntime_set_based(references, case):
a, b, _, _ = _runs(references, case)
assert a.counts == b.counts
da, db = a.output, b.output
m = match_detections(da.boxes, da.label_ids, da.scores, db.boxes, db.label_ids, db.scores, max_dist=0.01)
assert m.recall == 1.0 and m.precision == 1.0, m.to_dict()
assert max(m.score_errors, default=0.0) < 1e-4
assert len(da) == len(db) and len(da) > 0
def test_compat_voxels_reproduce_the_research_shuffle_emulation(references):
"""``autoware_compat`` with offset 12345 = research ``run_ort_reference.py --autoware-shuffle`` (seed 0) on frame
150: the voxel tensors of ``ort_sample_0150_awshuffle/io.npz`` bit for bit, and ORT's outputs on them."""
a, b, _, _ = _runs(references, "rosbag_0150_compat")
gold = np.load(_data.research_dir() / "ort_sample_0150_awshuffle" / "io.npz")
mine = a.prepared.onnx_inputs()
for k in ("voxels", "num_points", "coors"):
assert np.array_equal(mine[k], gold[k]), k
assert a.prepared.sampling["voxelizer_points"] == 2_000_000 and a.prepared.skipped is None
def test_plain_onnxruntime_reproduces_the_research_output_goldens(references):
"""The plain session (no extra outputs) on the research voxel tensors gives the research's bitwise ORT goldens
(S:transfusion:221: public_data goldens and ``ort_sample_0250``)."""
from tt_transfusion.reference.ort import OrtTransFusion
rt, _ = references
plain = OrtTransFusion(_data.weights_dir(), threads=THREADS, weights=rt.weights)
r = _data.research_dir()
cases = [(_data.pandaset_sample(), r / "public_data" / "goldens" / "ps019_f40" / "io.npz"),
(_data.research_cloud("frame_0250"), r / "ort_sample_0250" / "io.npz")]
for src, golden in cases:
gold = np.load(golden)
prep = rt.pre(src)
mine = prep.onnx_inputs()
for k in ("voxels", "num_points", "coors"):
assert np.array_equal(mine[k], gold[k]), (golden, k)
out = plain.run(mine)
for k in ("cls_score0", "bbox_pred0", "dir_cls_pred0"):
assert np.array_equal(out[k], gold[k]), (golden, k)
def test_pandaset_sample_reproduces_the_research_detections(references):
"""research/transfusion/public_data/shippable/pandaset_019_f40/expected_detections_cpu_fp32.json (ORT + the
research post-processing, values rounded to 4-5 decimals): the same objects, labels after the remapper, scores and
boxes."""
from tt_transfusion.ttaw.decode import LABEL_IDS
a, _, _, _ = _runs(references, "pandaset_019_f40")
gold = json.loads((_data.research_dir() / "public_data" / "shippable" / "pandaset_019_f40" /
"expected_detections_cpu_fp32.json").read_text())["objects"]
out = a.output
assert len(out) == len(gold) == 80
ref = np.array([[o[k] for k in ("x", "y", "z", "length", "width", "height", "yaw")] for o in gold], np.float64)
labels = np.array([LABEL_IDS[o["label_remapped"]] for o in gold])
m = match_detections(out.boxes, out.label_ids, out.scores, ref, labels, np.array([o["score"] for o in gold]),
max_dist=0.01)
assert m.recall == 1.0 and m.precision == 1.0, m.to_dict()
assert max(m.score_errors, default=0.0) < 1e-4
for i, j in m.pairs:
np.testing.assert_allclose(out.boxes[i, 2:6], ref[j, 2:6], atol=2e-4)