File size: 11,851 Bytes
ddaad99 | 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 | #!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Write the CPU fp32 goldens of the TT port with the bundle's own reference (``tt_bevformer.reference``, port form:
host geometry, rebatched SCA, rotation by gather) and cross-check them against the research goldens.
Large goldens (internal validation data, never shipped) go to ``research/bevformer/goldens/`` (``--out``):
============================================== ==================================================================
``pandaset_019/f30.npz``, ``f35.npz``, ``f40`` the shipped sample as a stateful sequence (reset at f30): outputs,
``bev_embed``, inputs, host geometry; f35 (history + ego motion)
also every per-module tap and the network input ``image``
``sample0/f1.npz``, ``f2``, ``f3`` the bevdet_vendor nuScenes key-frame (PLAN.md 2.11 detection
gates on frames 1-2; frame 3 = +2 m / +3 deg synthetic motion);
f1 also the image, backbone.layer4, neck and transformer taps
``nuscenes_scene-0103/kf01.npz`` mini_val, teacher-forced with the public golden kf00 bev_embed;
image + transformer taps (in-domain, real 4.3 m / -1.0 deg motion)
============================================== ==================================================================
Taps above 20 M elements (backbone.layer1-3) are stored as float16 (their PCC against float32 changes by < 1e-7);
everything else float32; ``__meta__`` records the dtypes, the generator, the bundle commit, the weights digest and
the PCCs against the research goldens. Small goldens (PandaSet-derived, CC BY 4.0) go to the bundle:
``staging_samples_pandaset/tests/goldens/pandaset_019_f30-40.json`` (detections, counts, PCCs; git-ignored until the PandaSet sample ships),
``pandaset_019_f30-40_last_layer.npz`` (last-layer logits / boxes) and the smoke test's stored reference
``staging_samples_pandaset/samples/pandaset_019_f30.reference.json`` (``to_dict()`` of a fresh-stream request of frame 30).
cd bundles/bevformer-p150 && OMP_NUM_THREADS=4 PYTHONPATH=$PWD/code \\
/home/ubuntu/experiments/tt-models/tools/research-venv/bin/python code/scripts/make_goldens.py --threads 4
"""
from __future__ import annotations
import argparse
import fnmatch
import hashlib
import json
import subprocess
import sys
import time
from pathlib import Path
import numpy as np
HERE = Path(__file__).resolve().parent
CODE = HERE.parent
sys.path.insert(0, str(CODE))
from tt_bevformer import __version__ # noqa: E402
from tt_bevformer.host import postprocess as hpost # noqa: E402
from tt_bevformer.host.inputs import load_frame # noqa: E402
from tt_bevformer.reference.pipeline import BEVFormerReference # noqa: E402
from tt_bevformer.tests import paths # noqa: E402
from tt_bevformer.ttaw.golden import save_goldens # noqa: E402
from tt_bevformer.ttaw.metrics import pcc # noqa: E402
FP16_ABOVE = 20_000_000
OUTS = ("bev_embed", "outputs_classes", "outputs_coords")
GEOMETRY = ("ref_cam", "sca_rows", "sca_counts", "sca_gather", "sca_scale", "shift", "rot_index")
ALL_TAPS = ["backbone.layer1", "backbone.layer2", "backbone.layer3", "backbone.layer4", "neck", "enc.*", "ref_cam",
"encoder.*", "decoder.*", "head.*"]
TRANSFORMER_TAPS = ["backbone.layer4", "neck", "enc.*", "ref_cam", "encoder.layer0.*", "encoder.layer0",
"encoder.layer1", "encoder.layer2", "decoder.*", "head.*"]
def _git_head(path: Path) -> str:
"""``<short sha>`` of the bundle, with ``+dirty`` when the working tree differs from it."""
try:
head = subprocess.run(["git", "-C", str(path), "rev-parse", "--short", "HEAD"], capture_output=True,
text=True, check=True).stdout.strip()
dirty = subprocess.run(["git", "-C", str(path), "status", "--porcelain", "--untracked-files=no"],
capture_output=True, text=True, check=True).stdout.strip()
return head + ("+dirty" if dirty else "")
except (OSError, subprocess.CalledProcessError):
return "unknown"
def _select(taps, patterns):
return {k: v for k, v in taps.items() if any(fnmatch.fnmatch(k, p) for p in patterns)}
def _pack(res, taps_patterns, with_image, meta):
"""Golden tensors of one frame + the dtype record."""
t = {name: getattr(res, name) for name in OUTS}
for k, v in res.inputs.items():
if k == "image" and not with_image:
continue
if k == "prev_bev":
meta["prev_bev_sha256"] = hashlib.sha256(np.ascontiguousarray(v).tobytes()).hexdigest()
continue
t[f"input.{k}"] = v
for k in GEOMETRY:
t[f"geometry.{k}"] = res.geometry[k]
dtypes = {}
for k, v in _select(res.taps, taps_patterns).items():
arr = np.asarray(v)
if arr.size > FP16_ABOVE:
arr = arr.astype(np.float16)
t[f"tap.{k}"] = arr
dtypes[k] = str(arr.dtype)
meta["tap_dtypes"] = dtypes
return t
def _compare(res, golden_path: Path, report: dict, key: str) -> None:
if golden_path is None or not Path(golden_path).is_file():
return
out = {}
with np.load(golden_path) as g:
for name in OUTS:
for gk in (name, f"{key}_{name}"):
if gk in g.files:
out[name] = pcc(getattr(res, name), g[gk])
for k in g.files:
if k.startswith("tap_") and k[4:] in res.taps:
out["tap." + k[4:]] = pcc(res.taps[k[4:]].reshape(g[k].shape), g[k])
report[key] = out
print(f" {key}: " + ", ".join(f"{k} {v:.10f}" for k, v in out.items()), flush=True)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--out", type=Path, default=paths.golden_dir())
ap.add_argument("--threads", type=int, default=4)
ap.add_argument("--only", nargs="*", default=["pandaset", "sample0", "nuscenes"])
a = ap.parse_args()
import torch
torch.set_num_threads(a.threads)
wpath = paths.weights_file()
if wpath is None:
raise SystemExit("weights not found (BEVFORMER_WEIGHTS_DIR)")
ref = BEVFormerReference(wpath)
base_meta = {"generator": "bundles/bevformer-p150/code/scripts/make_goldens.py", "bundle_version": __version__,
"bundle_commit": _git_head(CODE.parent), "weights": str(wpath),
"weights_sha256": ref.weights.sha256, "reference_form": "port (BEVFormerNet.forward_port)",
"fp16_taps_above_elements": FP16_ABOVE}
report: dict = {}
t_all = time.time()
if "pandaset" in a.only:
pub = paths.public_data()
ref.reset()
small = {"frames": [], "doc": "PandaSet 019 f30-40 (shipped sample, CC BY 4.0 + PandaSet Dataset Terms): "
"CPU fp32 reference (port form), stateful sequence reset at f30; Autoware "
"post-processing (score > 0.2)"}
last = {}
for f in (30, 35, 40):
t0 = time.time()
fr = load_frame(paths.PANDASET_SAMPLES / f"pandaset_019_f{f}.json")
res = ref.step(fr, port=True, taps=(f == 35), workers=4)
meta = dict(base_meta, frame=f, sequence="pandaset_019_f30-40", reset=bool(fr.stream["reset"]))
g = pub / "pandaset" / "goldens" / "ps019_f30-40" / f"f{f}.npz" if pub else None
_compare(res, g, report, f"pandaset_019/f{f}")
meta["pcc_vs_research"] = report.get(f"pandaset_019/f{f}", {})
save_goldens(a.out / "pandaset_019" / f"f{f}.npz", _pack(res, ALL_TAPS if f == 35 else [], f == 35, meta),
meta, compress=True)
dets = res.detections
small["frames"].append({"frame": f, "use_prev_bev": float(res.inputs["use_prev_bev"][0]),
"n_detections": len(dets), "pcc_vs_research": meta["pcc_vs_research"],
"detections": [{k: (round(v, 5) if isinstance(v, float) else v)
for k, v in d.items()} for d in dets.to_records()]})
last[f"f{f}_outputs_classes"] = res.outputs_classes[-1, 0]
last[f"f{f}_outputs_coords"] = res.outputs_coords[-1, 0]
if f == 30:
body = hpost.to_output(dets, model="bevformer-p150").to_dict()
(paths.PANDASET_SAMPLES / "pandaset_019_f30.reference.json").write_text(json.dumps(body, indent=1) + "\n")
print(f"pandaset f{f}: {len(dets)} detections, {time.time() - t0:.0f} s", flush=True)
paths.PANDASET_GOLDENS.mkdir(parents=True, exist_ok=True)
(paths.PANDASET_GOLDENS / "pandaset_019_f30-40.json").write_text(json.dumps(small, indent=1) + "\n")
np.savez_compressed(paths.PANDASET_GOLDENS / "pandaset_019_f30-40_last_layer.npz", **last)
if "sample0" in a.only:
g0 = paths.research_file("golden", "sample0_golden.npz")
if g0 is not None:
with np.load(g0) as g:
gs = {k: g[k] for k in g.files}
prev = gs["f1_prev_bev"]
for f in (1, 2, 3):
t0 = time.time()
res = ref.run_frame(gs["image"], prev, gs[f"f{f}_use_prev_bev"], gs[f"f{f}_can_bus"], gs["lidar2img"],
port=True, taps=(f == 1))
meta = dict(base_meta, frame=f, sequence="sample0 (bevdet_vendor nuScenes key-frame; internal only)")
_compare(res, g0, report, f"f{f}")
report[f"sample0/f{f}"] = report.pop(f"f{f}")
meta["pcc_vs_research"] = report[f"sample0/f{f}"]
meta["n_detections"] = len(res.detections)
save_goldens(a.out / "sample0" / f"f{f}.npz",
_pack(res, TRANSFORMER_TAPS if f == 1 else [], f == 1, meta), meta, compress=True)
prev = res.bev_embed
print(f"sample0 f{f}: {len(res.detections)} detections, {time.time() - t0:.0f} s", flush=True)
if "nuscenes" in a.only and paths.public_data() is not None:
P = paths.import_public_common()
pub = paths.public_data()
t0 = time.time()
x = P.load_frame_inputs(str(pub / "nuscenes" / "inputs" / "scene-0103" / "kf01.npz"))
with np.load(pub / "nuscenes" / "goldens" / "scene-0103" / "kf00.npz") as g:
prev = g["bev_embed"]
res = ref.run_frame(x["image"], prev, x["use_prev_bev"], x["can_bus"], x["lidar2img"], port=True, taps=True)
meta = dict(base_meta, frame="kf01", sequence="nuScenes mini_val scene-0103 (CC BY-NC-SA 4.0; internal only)",
teacher_forced="prev_bev = public-data golden kf00 bev_embed")
_compare(res, pub / "nuscenes" / "goldens" / "scene-0103" / "kf01.npz", report, "nuscenes_scene-0103/kf01")
meta["pcc_vs_research"] = report["nuscenes_scene-0103/kf01"]
save_goldens(a.out / "nuscenes_scene-0103" / "kf01.npz", _pack(res, TRANSFORMER_TAPS, True, meta), meta,
compress=True)
print(f"nuscenes kf01: {len(res.detections)} detections, {time.time() - t0:.0f} s", flush=True)
a.out.mkdir(parents=True, exist_ok=True)
worst = min((v for r in report.values() for v in r.values()), default=float("nan"))
(a.out / "report.json").write_text(json.dumps({"meta": base_meta, "pcc_vs_research": report,
"worst_pcc": worst, "seconds": time.time() - t_all}, indent=1))
print(f"done in {time.time() - t_all:.0f} s; worst PCC vs research {worst:.10f}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
|