File size: 9,006 Bytes
4d9b003
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Goldens of the fp32 CPU reference (no device): per-module taps and final outputs per scene.

    # research venv (torch + onnx; onnxruntime only for --ort), from the bundle root:
    PYTHONPATH=code python code/scripts/ref_golden.py --threads 4 [--ort] \
        [--full-dir ../../research/diffusion-planner/goldens] [--scenes kashiwanoha_dense straight_road ...]

Scenes: the shipped samples (``code/tt_diffusion_planner/samples/*.npz``: small goldens -> ``tests/goldens``, and the
stored ``/predict`` body of the reference -> ``samples/<stem>.reference.json``) and, when the workspace research
directory is present, the seven ORT golden scenes of ``research/diffusion-planner/ort/golden_<scene>.npz`` and any
public-dataset scene in ``research/diffusion-planner/public_data/*.npz`` (full goldens only). ``--ort`` also runs ONNX
Runtime on every scene and writes ``<full-dir>/<scene>.ort_agreement.json`` (reference vs ORT on the deployed ONNX).
"""
from __future__ import annotations

import argparse
import hashlib
import json
import sys
import time
from pathlib import Path

import numpy as np

CODE = Path(__file__).resolve().parents[1]
if str(CODE) not in sys.path:
    sys.path.insert(0, str(CODE))

from tt_diffusion_planner.reference import config as C  # noqa: E402
from tt_diffusion_planner.reference.goldens import write_scene  # noqa: E402
from tt_diffusion_planner.reference.pipeline import ReferencePlanner  # noqa: E402
from tt_diffusion_planner.ttaw.metrics import pcc  # noqa: E402

PKG = CODE / "tt_diffusion_planner"
SAMPLES = PKG / "samples"
SMALL_DIR = PKG / "tests" / "goldens"
RESEARCH = CODE.parents[2] / "research" / "diffusion-planner"


def public_ids(dataset: str, which: str):
    """Instant ids of ``public_data/inputs/<dataset>``: ``all``, or ``core`` (the ids the public-data goldens keep
    solver iterates for: ``public_data/goldens/<dataset>/core_ids.json``)."""
    root = RESEARCH / "public_data"
    ids = sorted(p.stem for p in (root / "inputs" / dataset).glob("*.npz"))
    if which == "core":
        core = root / "goldens" / dataset / "core_ids.json"
        keep = set(json.loads(core.read_text()).get("core_ids_with_denoising_steps", [])) if core.is_file() else set()
        ids = [i for i in ids if i in keep]
    return ids


def scene_sources(selected, public=None, public_which="core"):
    """``{scene: (raw source, kind)}``: shipped samples first, then research ORT scenes, then (``--public``) the
    public-data instants of one dataset (nuScenes-derived: CC BY-NC-SA, local goldens only)."""
    out = {}
    if public is None:
        for p in sorted(SAMPLES.glob("*.npz")):
            out[p.stem] = (p, "sample")
        for p in sorted((RESEARCH / "ort").glob("golden_*.npz")):
            out.setdefault(p.stem[len("golden_"):], (p, "research"))
    else:
        for i in public_ids(public, public_which):
            out[f"public/{public}/{i}"] = (RESEARCH / "public_data" / "inputs" / public / f"{i}.npz", "public")
    if selected:
        missing = sorted(set(selected) - set(out))
        if missing:
            raise SystemExit(f"unknown scenes {missing}; have {sorted(out)}")
        out = {k: v for k, v in out.items() if k in selected}
    return out


def load_raw(path: Path, kind: str):
    with np.load(path, allow_pickle=False) as z:
        if kind in ("research", "public"):  # dp_reference.py / nuscenes_dp.py layout: raw/<name>
            return {k: np.asarray(z["raw/" + k], np.float32) for k in C.INPUT_NAMES}
        return {k: np.asarray(z[k], np.float32) for k in C.INPUT_NAMES}


def ort_agreement(weights_dir: Path, raw, g, threads: int):
    from tt_diffusion_planner.reference.ort import OrtPlanner

    o = OrtPlanner(weights_dir, threads=threads).run(raw)
    rows = g["dec.rows"]
    enc_rows = np.flatnonzero(g["host.token_valid"])
    return {"encoding_pcc_valid": pcc(g["enc.encoding"][enc_rows], o.encoding[0][enc_rows]),
            "encoding_max_abs": float(np.abs(g["enc.encoding"] - o.encoding[0]).max()),
            "final_x0_pcc_valid": pcc(g["final_x0"][rows], o.final_x0[0][rows]),
            "final_x0_max_abs_valid": float(np.abs(g["final_x0"][rows] - o.final_x0[0][rows]).max()),
            "logit_max_abs": float(np.abs(g["turn.logit"] - o.logit[0]).max()),
            "turn_logit_ref": g["turn.logit"].tolist(), "turn_logit_ort": o.logit[0].tolist()}


def stored_ort_agreement(g, golden: Path):
    """Reference vs a stored ORT golden in the ``dp_reference.py`` layout (``encoding``, ``final_x_normalized``,
    ``logit_multi``): the public-data goldens of ``research/diffusion-planner/public_data/goldens/<dataset>``."""
    with np.load(golden, allow_pickle=False) as z:
        enc, fx, lg = z["encoding"][0], z["final_x_normalized"][0], z["logit_multi"][0]
    rows = g["dec.rows"]
    tok = np.flatnonzero(g["host.token_valid"])
    return {"ort_golden": str(golden),
            "encoding_pcc_valid": pcc(g["enc.encoding"][tok], enc[tok]),
            "encoding_max_abs": float(np.abs(g["enc.encoding"][tok] - enc[tok]).max()),
            "final_x0_pcc_valid": pcc(g["final_x0"][rows], fx[rows]),
            "final_x0_max_abs_valid": float(np.abs(g["final_x0"][rows] - fx[rows]).max()),
            "logit_max_abs": float(np.abs(g["turn.logit"] - lg).max())}


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    ap.add_argument("--weights-dir", default=None)
    ap.add_argument("--full-dir", type=Path, default=RESEARCH / "goldens",
                    help="where the large per-scene goldens go (never inside the bundle)")
    ap.add_argument("--scenes", nargs="*", default=None)
    ap.add_argument("--threads", type=int, default=4)
    ap.add_argument("--ort", action="store_true", help="also compare with ONNX Runtime (research venv)")
    ap.add_argument("--no-reference-json", action="store_true")
    ap.add_argument("--public", default=None, help="a public_data/inputs/<dataset> (e.g. nuscenes): its instants "
                    "instead of the samples / research scenes, as lite goldens")
    ap.add_argument("--public-ids", default="core", choices=["core", "all"])
    a = ap.parse_args(argv)
    if a.full_dir.resolve().is_relative_to(CODE.parent.resolve()):
        raise SystemExit("--full-dir must be outside the bundle: the full goldens are 10-25 MB per scene")
    ref = ReferencePlanner(a.weights_dir, threads=a.threads)
    report, seen = {}, {}
    for scene, (path, kind) in scene_sources(a.scenes, a.public, a.public_ids).items():
        t0 = time.perf_counter()
        raw = load_raw(path, kind)
        digest = hashlib.sha256(b"".join(raw[k].tobytes() for k in C.INPUT_NAMES)).hexdigest()
        if digest in seen:  # e.g. research golden_straight == the shipped straight_road sample
            report[scene] = {"same_inputs_as": seen[digest]}
            print(scene, "skipped: same inputs as", seen[digest], flush=True)
            continue
        seen[digest] = scene
        small = SMALL_DIR if kind == "sample" else None
        r = write_scene(ref, raw, scene, a.full_dir, small, meta={"source": str(path), "kind": kind},
                        lite=(kind == "public"))
        entry = {"paths": r["paths"], "valid_counts": r["info"]["valid_counts"]}
        if kind == "sample" and not a.no_reference_json:
            body = ref(inputs=raw).to_dict()
            body["timing_ms"] = {}
            ref_path = SAMPLES / f"{scene}.reference.json"
            ref_path.write_text(json.dumps(body, indent=1) + "\n")
            entry["reference_json"] = str(ref_path)
        stored = None
        if kind == "public":
            stored = RESEARCH / "public_data" / "goldens" / a.public / f"golden_{path.stem}.npz"
        if stored is not None and stored.is_file():  # the dataset work already ran ORT on this instant
            agree = stored_ort_agreement(r["goldens"], stored)
            (a.full_dir / f"{scene}.ort_agreement.json").write_text(json.dumps(agree, indent=1) + "\n")
            entry["ort"] = {k: v for k, v in agree.items() if k != "ort_golden"}
        elif a.ort:
            agree = ort_agreement(ref.weights.path, raw, r["goldens"], a.threads)
            a.full_dir.mkdir(parents=True, exist_ok=True)
            (a.full_dir / f"{scene}.ort_agreement.json").write_text(json.dumps(agree, indent=1) + "\n")
            entry["ort"] = {k: v for k, v in agree.items() if not k.startswith("turn_logit")}
        entry["seconds"] = round(time.perf_counter() - t0, 1)
        report[scene] = entry
        print(scene, json.dumps(entry), flush=True)
    a.full_dir.mkdir(parents=True, exist_ok=True)
    index = a.full_dir / "index.json"
    merged = json.loads(index.read_text()) if index.is_file() else {}
    merged.update(report)
    index.write_text(json.dumps(merged, indent=1, sort_keys=True) + "\n")
    return 0


if __name__ == "__main__":
    sys.exit(main())