frnet-p150 / code /scripts /diag_frame_taps.py
changh95's picture
tt-model push frnet-p150 (container)
fe51a63 verified
Raw History Blame Contribute Delete
6.42 kB
#!/usr/bin/env python3
# SPDX-License-Identifier: Apache-2.0
"""Device diagnostic: per-module error of the device graph on one catalogued frame, next to the CPU emulation.
cd bundles/frnet-p150 && bin/devrun -t 1200 -- env PYTHONPATH=code python code/scripts/diag_frame_taps.py \\
--frame nuscenes_scene-0916_40.ot128 --out /home/ubuntu/experiments/tt-models/logs/frnet/m1b/diag_0916_40.json
The exact fp32 TT dataflow (``reference/rewrites.py FRNetPlanTorch``) is the reference of every tap; the CPU
emulation (``--emulation``, default the ``plan_exact_points`` row of ``code/scripts/precision_study.py``: bf16 conv
operands, gathers and segment tables, exact point layers) and the device ``taps`` variant (one replay; knobs from the
``FRNET_*`` environment) are compared with it tap by tap: where the device's ``1 - PCC`` grows faster than the
emulation's is where the device loses accuracy that the emulation does not model. Also reported: ``pred_probs`` PCC
and raw-point argmax agreement vs ONNX Runtime for the device and the emulation. The CPU work runs first (in the
device lock: ~1-2 min at OT128).
"""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
import numpy as np
HERE = Path(__file__).resolve()
sys.path.insert(0, str(HERE.parents[1]))
sys.path.insert(0, str(HERE.parent))
TAPS = ("encoder.pf3", "encoder.voxel_feats", "backbone.stem", "backbone.point_stem", "backbone.fusion_stem",
*(f"backbone.{k}{i}" for i in range(1, 5) for k in ("layer", "point_fusion", "pixel_fusion", "attention",
"stage")),
"decoder.fuse1", "decoder.fuse2", "decoder.point_fuse1", "decoder.point_fuse2",
*(f"head.mlp{i}" for i in range(4)), "head.logits", "pred_probs")
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--frame", required=True)
ap.add_argument("--out", type=Path, required=True)
ap.add_argument("--emulation", default="plan_exact_points")
ap.add_argument("--threads", type=int, default=4)
ap.add_argument("--cpu-only", action="store_true", help="stop after the CPU part (a dry run without a device)")
args = ap.parse_args()
import torch
torch.set_num_threads(args.threads)
from precision_study import CONFIGS
from tt_frnet.host.preprocess import build_plan
from tt_frnet.reference.config import FRNetConfig
from tt_frnet.reference.rewrites import Emulation, FRNetPlanTorch
from tt_frnet.reference.weights import load_weights
from tt_frnet.tests import _data
from tt_frnet.ttaw.golden import TapRegistry
from tt_frnet.ttaw.metrics import argmax_agreement, pcc
variant = args.frame.rpartition(".")[2]
wdir = _data.weights_dir()
cfg = FRNetConfig.from_model_dir(wdir, variant)
w = load_weights(wdir, variant)
inputs, ort_probs, _ = _data.load_frame(args.frame)
plan = build_plan(inputs, cfg, pre_norm=(w.pre_norm_scale, w.pre_norm_shift))
n, n_raw = plan.n, inputs.num_points_raw
H, W = plan.frustum_hw
t0 = time.time()
exact = TapRegistry(include=list(TAPS))
FRNetPlanTorch(w)(plan, taps=exact)
emu = TapRegistry(include=list(TAPS))
emu_probs = plan.unsort(FRNetPlanTorch(w, emulation=Emulation.parse(CONFIGS[args.emulation]))(plan, taps=emu))
cpu_s = time.time() - t0
def rows(name, a):
"""point taps: the first N sorted rows; voxel taps: the first M rows."""
a = np.asarray(a)
if a.shape[0] == plan.n_cap:
return a[:n]
return a[: plan.m] if a.shape[0] in (cfg.m_cap, cfg.m_cap + 1) else a
if args.cpu_only:
ort = np.asarray(ort_probs, np.float32)
print(f"cpu {cpu_s:.0f} s; emulation pred_probs pcc {pcc(emu_probs, ort):.6f} argmax "
f"{argmax_agreement(emu_probs[:n_raw], ort[:n_raw]):.5f}")
return 0
from tt_frnet.device import device_session
from tt_frnet.tt.model import TtFRNet, build_runner, frame_arrays
report = {"frame": args.frame, "N": n, "M": plan.m, "emulation": args.emulation, "cpu_s": round(cpu_s, 1),
"taps": {}}
with device_session() as device:
graph = TtFRNet(device, w, cfg)
arrays = frame_arrays(plan, cfg.m_cap)
runner = build_runner(graph, variants=("taps",), name="frnet_diag", init=arrays)
try:
runner.capture()
dev = runner("taps", inputs=arrays)
report["knobs"] = graph.knobs.as_dict()
for name in TAPS:
if name not in dev or name not in exact:
continue
ref = np.asarray(exact[name], np.float32)
d = np.asarray(dev[name], np.float32)
if ref.ndim == 4:
_, c, h, ww = ref.shape
d = d.reshape(-1, d.shape[-1])[: h * ww, :c].reshape(1, h, ww, c).transpose(0, 3, 1, 2)
e = np.asarray(emu[name], np.float32)
else:
ref = rows(name, ref)[:, : ref.shape[-1]]
d = rows(name, d.reshape(-1, d.shape[-1]))[:, : ref.shape[-1]]
e = rows(name, np.asarray(emu[name], np.float32))[:, : ref.shape[-1]]
p_dev, p_emu = float(pcc(d, ref)), float(pcc(e, ref))
ratio = (1 - p_dev) / max(1 - p_emu, 1e-12)
report["taps"][name] = {"pcc_dev": p_dev, "pcc_emu": p_emu, "err_ratio": ratio}
print(f"{name:26s} dev {p_dev:.6f} emu {p_emu:.6f} (1-pcc) ratio {ratio:6.2f}", flush=True)
probs = plan.unsort(np.asarray(dev["pred_probs"], np.float32).reshape(-1, 32)[:, :27])
finally:
runner.release()
graph.release()
ort = np.asarray(ort_probs, np.float32)
report["device"] = {"pred_probs_pcc": float(pcc(probs, ort)),
"argmax_agreement_raw": float(argmax_agreement(probs[:n_raw], ort[:n_raw]))}
report["emulation_result"] = {"pred_probs_pcc": float(pcc(emu_probs, ort)),
"argmax_agreement_raw": float(argmax_agreement(emu_probs[:n_raw], ort[:n_raw]))}
print("device", report["device"], "emulation", report["emulation_result"])
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(json.dumps(report, indent=1))
return 0
if __name__ == "__main__":
sys.exit(main())