File size: 13,177 Bytes
ac1609a | 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 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 | #!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Goldens of streampetr-p150 from the bundle's fp32 CPU reference (``tt_streampetr.reference``), cross-checked
against the research ORT goldens (research/streampetr/public_data/golden).
OMP_NUM_THREADS=3 PYTHONPATH=$PWD/code python code/scripts/make_goldens.py [--threads 3] [--out DIR]
# research venv or tt-env (torch + onnx); the weights from $STREAMPETR_WEIGHTS_DIR or the workspace download
Writes (``--out``, default ``research/streampetr/goldens``; large, never shipped):
- ``pandaset_019/f40.npz``: the shipped sample's cold-start frame with the network input, the PE inputs / outputs /
taps, every stage-level backbone tap, every head tap (tokens, temporal, the 6 decoder layers, branches,
selection) and the 14 head outputs; ``f41..f43.npz``: the gate taps of the next frames of the same stream
(input, ``img_feats``, memory inputs, ``outs_dec``, head outputs, selection) + ``objects.json``;
- ``public/<run>_tf_<frame>.npz``: the head taps of research teacher-forcing records (PandaSet 019 frames 1 / 40,
nuScenes scene-0103 frame 111: internal validation data, CC BY-NC-SA, never shipped), teacher-forced from the
record's inputs, plus the port's relative memory state of the record (``reference.port_form.global_to_relative``);
- ``report.json``: generator, versions, sha256 of every file, and the cross-checks against the research ORT goldens.
And the small goldens of the PandaSet sample (PandaSet-derived, CC BY 4.0; written beside the sample, i.e. into the
git-ignored ``staging_samples_pandaset/`` until the user approves shipping it: ``tests/paths.py`` PANDASET_*; the
``code/tt_streampetr/...`` paths below are relative to that root):
- ``code/tt_streampetr/tests/goldens/pandaset_019_f40_head.npz``: ``all_cls_scores`` / ``all_bbox_preds`` of frame 40;
- ``code/tt_streampetr/tests/goldens/pandaset_019_f40-43.json``: per frame the objects, counts, the sha256 of the
network input / PE / head outputs and the decoder-layer histogram;
- ``code/tt_streampetr/samples/pandaset_019_f40.reference.json``: the ``to_dict()`` of the reference on the sample
request of frame 40 (a cold start): the container smoke test's stored reference.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import platform
import sys
import time
from pathlib import Path
import numpy as np
BUNDLE = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(BUNDLE / "code"))
from tt_streampetr.host import inputs as hin # noqa: E402
from tt_streampetr.host.agreement import detection_f1, recover_topk_indices, topk_overlap # noqa: E402
from tt_streampetr.reference import port_form as pf # noqa: E402
from tt_streampetr.reference.config import CAMERA_ORDER, default_config # noqa: E402
from tt_streampetr.tests import paths # noqa: E402
from tt_streampetr.ttaw.golden import TapRegistry, save_goldens # noqa: E402
from tt_streampetr.ttaw.metrics import pcc # noqa: E402
CFG = default_config()
FRAMES = (40, 41, 42, 43)
STAGE_TAPS = ("backbone.stem", "backbone.stage?", "neck.*", "img_feats", "pe.*", "pos_embed", "cone", "head.*",
"all_cls_scores", "all_bbox_preds")
GATE_TAPS = ("img_feats", "head.outs_dec", "head.cls", "head.reg", "head.rec_score", "head.topk_idx",
"head.reference_points", "head.mem_ego", "head.mem_ref", "all_cls_scores", "all_bbox_preds")
TF_TAPS = ("head.*", "all_cls_scores", "all_bbox_preds")
TF_EXCLUDE = ("head.tokens", "head.memory_embed", "head.sa_gamma", "head.sa_beta") # [6000, 256] intermediates
OUT_KEYS = ("all_cls_scores", "all_bbox_preds", "post_memory_embedding", "post_memory_reference_point",
"post_memory_timestamp", "post_memory_egopose", "post_memory_velo", "reference_points", "outs_dec",
"topk_idx", "rec_score")
def sha(a) -> str:
return hashlib.sha256(np.ascontiguousarray(a).tobytes()).hexdigest()
def file_sha(p: Path) -> str:
h = hashlib.sha256()
with open(p, "rb") as f:
for chunk in iter(lambda: f.read(1 << 20), b""):
h.update(chunk)
return h.hexdigest()
def bundle_commit() -> str:
import subprocess
try:
return subprocess.run(["git", "-C", str(BUNDLE), "rev-parse", "HEAD"], capture_output=True, text=True,
check=True).stdout.strip()
except (OSError, subprocess.CalledProcessError):
return "unknown"
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--threads", type=int, default=3)
ap.add_argument("--out", type=Path, default=paths.golden_dir())
ap.add_argument("--skip-public", action="store_true")
a = ap.parse_args()
import torch
from tt_streampetr.reference.pipeline import StreamPETRReference
from tt_streampetr.ttaw import __version__ as ttaw_version
torch.set_num_threads(a.threads)
wdir = paths.weights_dir()
if wdir is None:
sys.exit("weights not found (STREAMPETR_WEIGHTS_DIR)")
ref = StreamPETRReference(wdir, CFG, threads=a.threads, preprocess_workers=min(a.threads, 4))
out_dir = a.out / "pandaset_019"
out_dir.mkdir(parents=True, exist_ok=True)
small = paths.PANDASET_GOLDENS
small.mkdir(parents=True, exist_ok=True)
meta_common = {"generator": "code/scripts/make_goldens.py", "bundle_commit": bundle_commit(),
"ttaw": ttaw_version, "torch": torch.__version__, "numpy": np.__version__,
"python": platform.python_version(), "weights": ref.net.weights.sha256(),
"normalization": CFG.normalization, "threads": a.threads}
report = {"meta": meta_common, "files": {}, "checks": {}}
# ---- A. the shipped sample: one stream from a cold start --------------------------------------------------
ref.streams.clear()
frames_json = []
research_cold = paths.public_golden("ps019_f40_43_cold")
gold_head = np.load(research_cold / "head_cls_bbox.npz") if research_cold else None
gold_dets = ({r["frame"]: r for r in json.loads((research_cold / "detections.json").read_text())["results"]}
if research_cold else {})
gold_hash = ({r["frame"]: r["img_f32_sha256"] for r in json.loads((research_cold / "inputs_sha256.json")
.read_text())} if research_cold else {})
checks = []
for k, f in enumerate(FRAMES):
t0 = time.time()
req = hin.load_sample(paths.pandaset_sample(f))
taps = TapRegistry(include=STAGE_TAPS if f == 40 else GATE_TAPS)
det, outs, plan, img = ref(**req, taps=taps, return_outputs=True)
frame = hin.prepare_frame(**req, cfg=CFG)
tensors = {"input.img": img, "input.data_ego_pose": plan.E, "input.data_ego_pose_inv": plan.Einv,
"input.ts": np.float32(plan.ts), "input.M": plan.M.astype(np.float64)}
tensors.update({f"input.{kk}": v for kk, v in plan.pre.items()})
tensors.update({f"input.pe.{kk}": v for kk, v in frame.rig.pe_inputs().items()})
tensors.update({f"output.{kk}": outs[kk] for kk in OUT_KEYS})
tensors.update(taps.to_dict())
objs = ref.postprocess(outs)
meta = dict(meta_common, frame=f, stream_frame=plan.frame_index, fresh=bool(plan.fresh),
sample=f"samples/pandaset_019_f{f}.json", num_objects=len(objs["objects"]))
path = out_dir / f"f{f}.npz"
save_goldens(path, tensors, meta)
recs = objs["objects"].to_records(CFG.class_names)
entry = {"frame": f, "fresh": bool(plan.fresh), "ts": float(plan.ts), "num_pre_nms": objs["num_pre_nms"],
"num_objects": len(recs), "counts": {n: sum(r["label_name"] == n for r in recs)
for n in CFG.class_names},
"decoder_layer_histogram": np.bincount(objs["objects"].layer, minlength=6).tolist(),
"from_propagated_queries": int(np.sum(objs["objects"].query >= CFG.num_query)),
"sha256": {"img": {c: sha(img[0, i]) for i, c in enumerate(CAMERA_ORDER)},
"all_cls_scores": sha(outs["all_cls_scores"]), "all_bbox_preds": sha(outs["all_bbox_preds"])},
"objects": [{kk: (round(v, 5) if isinstance(v, float) else v) for kk, v in r.items()} for r in recs]}
frames_json.append(entry)
if f == 40:
np.savez_compressed(small / "pandaset_019_f40_head.npz", all_cls_scores=outs["all_cls_scores"],
all_bbox_preds=outs["all_bbox_preds"])
pos, cone = ref.position_embedding(frame.rig)
entry["sha256"].update({"pos_embed": sha(pos), "cone": sha(cone)})
body = det.to_dict()
body["timing_ms"] = {}
(paths.PANDASET_SAMPLES / "pandaset_019_f40.reference.json").write_text(json.dumps(body, indent=1) + "\n")
c = {"frame": f, "seconds": round(time.time() - t0, 1), "golden_bytes": path.stat().st_size}
if gold_head is not None:
i = list(gold_head["frame"]).index(f)
c["inputs_bit_identical"] = entry["sha256"]["img"] == gold_hash[f]
c["cls_pcc_vs_research_ort"] = pcc(outs["all_cls_scores"], gold_head["all_cls_scores"][i])
c["bbox_pcc_vs_research_ort"] = pcc(outs["all_bbox_preds"], gold_head["all_bbox_preds"][i])
c["objects_vs_research_ort"] = detection_f1(objs["objects"], gold_dets[f]["dets"], dist=1.0)
checks.append(c)
print(json.dumps(c), flush=True)
(out_dir / "objects.json").write_text(json.dumps({"meta": meta_common, "frames": frames_json}, indent=1))
(small / "pandaset_019_f40-43.json").write_text(json.dumps(
{"meta": dict(meta_common, source="PandaSet sequence 019 frames 40-43 (CC BY 4.0 + PandaSet Dataset Terms; "
"samples/pandaset_019/LICENSE.txt), one stream from a cold start"),
"frames": frames_json}, indent=1) + "\n")
report["checks"]["pandaset_019_sample"] = checks
# ---- B. teacher-forced head taps of research records (public datasets) ------------------------------------
if not a.skip_public:
pub = a.out / "public"
pub.mkdir(exist_ok=True)
tf_checks = []
for run, f in (("ps019", 1), ("ps019", 40), ("ns0103", 111)):
g = paths.public_golden(run)
if g is None or not (g / f"tf_{f:04d}.npz").is_file():
continue
rec, pe = np.load(g / f"tf_{f:04d}.npz"), np.load(g / "pe_inputs_outputs.npz")
pre = {kk: rec[kk] for kk in rec.files if kk.startswith("pre_")}
taps = TapRegistry(include=TF_TAPS, exclude=TF_EXCLUDE)
out = ref.head(rec["img_feats"], pe["pos_embed"], pe["cone"], rec["data_ego_pose"],
rec["data_ego_pose_inv"], pre, taps)
gold_idx = recover_topk_indices(rec["post_memory_embedding"], rec["outs_dec"])
st, M = pf.global_to_relative(pre, rec["data_ego_pose_inv"])
tensors = {"input.img_feats": rec["img_feats"], "input.pos_embed": pe["pos_embed"], "input.cone": pe["cone"],
"input.data_ego_pose": rec["data_ego_pose"], "input.data_ego_pose_inv": rec["data_ego_pose_inv"],
"input.ts": rec["ts"], "input.golden_topk_idx": gold_idx,
"input.relative.emb": st.emb, "input.relative.ref": st.ref, "input.relative.ego": st.ego,
"input.relative.velo": st.velo, "input.relative.M": M}
tensors.update({f"input.{kk}": v for kk, v in pre.items()})
tensors.update({f"output.{kk}": out[kk] for kk in OUT_KEYS})
tensors.update(taps.to_dict())
path = pub / f"{run}_tf_{f:04d}.npz"
save_goldens(path, tensors, dict(meta_common, run=run, frame=f, source=str(g / f"tf_{f:04d}.npz"),
license="nuScenes CC BY-NC-SA 4.0: internal only" if run.startswith("ns")
else "PandaSet CC BY 4.0 + Dataset Terms"))
c = {"run": run, "frame": f, "golden_bytes": path.stat().st_size,
"cls_pcc_vs_research_ort": pcc(out["all_cls_scores"], rec["all_cls_scores"]),
"bbox_pcc_vs_research_ort": pcc(out["all_bbox_preds"], rec["all_bbox_preds"]),
"outs_dec_pcc_vs_research_ort": pcc(out["outs_dec"], rec["outs_dec"]),
"top256_overlap_vs_research_ort": topk_overlap(out["topk_idx"].reshape(-1), gold_idx)}
tf_checks.append(c)
print(json.dumps(c), flush=True)
report["checks"]["teacher_forced_public"] = tf_checks
for p in sorted(a.out.rglob("*")):
if p.is_file() and p.name != "report.json":
report["files"][str(p.relative_to(a.out))] = {"bytes": p.stat().st_size, "sha256": file_sha(p)}
(a.out / "report.json").write_text(json.dumps(report, indent=1, default=float))
print(f"wrote {len(report['files'])} files to {a.out}")
if __name__ == "__main__":
os.environ.setdefault("OMP_NUM_THREADS", "3")
main()
|