File size: 9,402 Bytes
51defdc | 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 | # SPDX-License-Identifier: Apache-2.0
"""CPU emulation of the device's activation precision on the class decisions (PORT_LOG.md E20; no device).
cd bundles/meteor-p150 && OMP_NUM_THREADS=4 PYTHONPATH=$PWD/code \\
/home/ubuntu/experiments/tt-models/tools/research-venv/bin/python code/scripts/precision_emulation.py image bb tt Bt
... precision_emulation.py bev
... precision_emulation.py det meteor_valday_f040 fff bff fbf ffb bbb nbb bFb nFF
... precision_emulation.py absent ff ft fT TT tt bb
``image``: the CPU reference's image branch with every conv / resize output rounded to bf16 (``b``), TF32 (``t``: 10
explicit mantissa bits, what the device's fp32 conv path computes: its unpacker rounds fp32 activations to TF32) or
kept fp32 (``f``), per (encoder, heads) pair; ``B`` = the stem output alone in bf16 (``ttnn.max_pool2d`` is bf16
only). Prints the depth / seg2d argmax agreement with the fp32 reference (PLAN 2.13 gate: >= 0.99) and the depth
top-2 logit margins. ``bev``: the lane decoder + seg refiner from the golden raw BEV with bf16 activations (the convs
whose outputs the device keeps fp32 stay fp32) -> lane agreement. ``det FRAME RDB ...`` (PORT_LOG.md E22): the det
stem + merged heads (``D``) and the box refiner (``B``) from the golden raw BEV (``R``) of FRAME, each letter one of
``f`` (fp32), ``b`` (bf16 conv / resize outputs), ``t`` (TF32), ``F`` (fp32 convs = three bf16 terms, TF32 resizes:
the device's terms3 path), and for ``R`` also ``n`` (bf16 after 0.3 % relative noise: the chained device raw) -> the
strict det3d recall / precision of the decoded boxes vs the golden outputs (``tests/box_agreement.py``) and the hm
error. ``absent CR ...`` (PORT_LOG.md E23 / I7): the image branch on an absent camera (the all-zero normalised input
of ``input_norm=imagenet``) with conv outputs ``C`` and resize outputs ``R`` rounded as above, plus ``T`` (TF32 by
TRUNCATION) -> seg2d / depth agreement with the variant golden's absent camera and with the device's argmax maps
(``logs/meteor/variants_device_absent_argmax.npz``, written by ``tests/test_variants_device.py``).
Goldens: ``research/meteor/goldens``.
Measured (PandaSet 019 f40): image bb 0.9819 / 0.9908, bt 0.9856 / 0.9948, tb 0.9913 / 0.9917, tt 0.9981 / 0.9988,
Bt 0.9874 / 0.9951; bev lane 0.99966 (bf16) / 0.99996 (fp32). det (valday #40, strict recall / precision): fff
1.0 / 1.0, bff 1.0 / 1.0, fbf 0.971 / 1.0, ffb 1.0 / 1.0, bbb 0.914 / 1.0, nbb 0.914 / 1.0, bFb 1.0 / 1.0, nFF
1.0 / 1.0 (nuScenes nbb 0.821 / 0.885, nFF 1.0 / 1.0): the bf16 det stem is the loss. absent (seg2d / depth vs the
reference; vs the device): ff 1.0 / 1.0; fT 0.9975 / 0.9993; tt 0.9966 / 0.9981; TT 0.9856 / 0.9901 (0.9927 / 0.9933
vs the device); bb 0.9724 / 0.9800; the device 0.9832 / 0.9913 (camera 6): it behaves like TF32-truncated conv outputs.
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
GOLD = Path(os.environ.get("METEOR_GOLDENS", "/home/ubuntu/experiments/tt-models/research/meteor/goldens"))
FP32_OUT_BEV = {"dec/out/out.3", "lane_branch/lane_branch.6", "refiner/seg/out/out.3"}
def rnd(t, m):
import torch
if m == "f":
return t
if m == "T": # TF32 by truncation
a = t.contiguous().numpy().view(np.uint32) & np.uint32(0xFFFFE000)
return torch.from_numpy(a.view(np.float32).copy())
if m == "b":
return t.to(torch.bfloat16).to(torch.float32)
a = t.contiguous().numpy().view(np.uint32).astype(np.uint64)
a = ((a + 0x1000) & 0xFFFFE000).astype(np.uint32) # round to 10 explicit mantissa bits (TF32)
return torch.from_numpy(a.view(np.float32))
def main() -> None:
import torch
from tt_meteor.reference.model import MeteorNet
from tt_meteor.reference.weights import MeteorWeights
torch.set_num_threads(int(os.environ.get("OMP_NUM_THREADS", "4")))
what = sys.argv[1] if len(sys.argv) > 1 else "image"
net = MeteorNet(MeteorWeights(), check_graph=False)
z = np.load(GOLD / "pandaset_019_f40" / "taps.npz")
cur = {"m": "b", "stem": None}
conv0, resize0 = net.conv, net.resize
def conv(x, module, relu=False):
y = conv0(x, module, relu)
if what == "bev" and module in FP32_OUT_BEV:
return y
if module == "stem/stem.0" and cur["stem"]:
return rnd(y, cur["stem"])
return rnd(y, cur["m"])
if what == "det":
return det(net, sys.argv[2], sys.argv[3:])
if what == "absent":
return absent(net, sys.argv[2:])
net.conv = conv
net.resize = lambda x, hw: rnd(resize0(x, hw), cur["m"])
with torch.inference_mode():
if what == "bev":
raw = rnd(torch.from_numpy(z["bev.raw"].astype(np.float32)), "b")
for m in ("b", "f"):
cur["m"] = m
ll = net.seg_refiner(net.lane_decoder(raw))
lane = torch.argmax(ll, dim=1).numpy().astype(np.uint8)
print(f"bev {m}: lane agreement {(lane == z['lane']).mean():.5f}")
return
x = net.normalize(torch.from_numpy(z["input.imgs"]))
for cfg in sys.argv[2:] or ["bb", "tt"]:
enc, head = cfg[0], cfg[1]
cur["stem"] = "b" if enc == "B" else None
cur["m"] = "t" if enc == "B" else enc
f = net.image_encoder(x)
cur["m"], cur["stem"] = head, None
dl, _ = net.depth_head(f)
seg = net.seg2d_head(f)
d = torch.argmax(dl.reshape(1, 8, 64, 108, 192), dim=2).numpy()
s = torch.argmax(torch.clamp(seg, -30, 30).reshape(1, 8, 21, 108, 192), dim=2).numpy()
print(f"encoder {enc} heads {head}: depth {(d == z['depth']).mean():.4f} seg2d {(s == z['seg2d']).mean():.4f}",
flush=True)
srt = np.sort(z["depth.logits"].astype(np.float32), axis=1)
margin = srt[:, -1] - srt[:, -2]
print("depth top-2 margin quantiles (0.05, 0.1, 0.25, 0.5):",
np.round(np.quantile(margin, [0.05, 0.1, 0.25, 0.5]), 4).tolist())
def det(net, frame: str, configs) -> None:
"""``det`` mode (module docstring)."""
import torch
from tt_meteor.host.postprocess import PostConfig
from tt_meteor.tests.box_agreement import box_agreement, decode_3d
g = np.load(GOLD / frame / "taps.npz")
conv0, resize0 = net.conv, net.resize
cur = {"m": "f"}
conv_mode = lambda m: "f" if m == "F" else m # noqa: E731
resize_mode = lambda m: "t" if m == "F" else m # noqa: E731
net.conv = lambda x, module, relu=False: rnd(conv0(x, module, relu), conv_mode(cur["m"]))
net.resize = lambda x, hw: rnd(resize0(x, hw), resize_mode(cur["m"]))
cfg = PostConfig()
ref = decode_3d({"hm": g["hm"], "reg": g["reg"]}, cfg)
torch.manual_seed(0)
for c in configs or ["bbb", "bFF"]:
r_m, d_m, b_m = c
with torch.inference_mode():
raw = torch.from_numpy(g["bev.raw"].astype(np.float32))
raw = rnd(raw + torch.randn_like(raw) * raw.abs() * 0.003, "b") if r_m == "n" else rnd(raw, r_m)
cur["m"] = d_m
_, hm_pre, reg_pre = net.det_stem(raw)
cur["m"] = b_m
hm, reg = net.box_refiner(hm_pre, reg_pre)
r = box_agreement(decode_3d({"hm": hm.numpy(), "reg": reg.numpy()}, cfg), ref, cfg)
err = np.abs(hm.numpy() - g["hm"]).ravel()
print(f"{frame} raw {r_m} det {d_m} refiner {b_m}: recall {r['recall']:.4f} precision {r['precision']:.4f} "
f"hm max abs {err.max():.4f}", flush=True)
def absent(net, configs) -> None:
"""``absent`` mode (module docstring)."""
import torch
g = np.load(GOLD / "pandaset_019_f40_imagenet_linear" / "taps.npz")
cam = int(np.flatnonzero(~np.asarray(g["input.present"], bool))[0])
ref_s, ref_d = g["seg2d"][0][cam], g["depth"][0][cam]
dev_path = Path(os.environ.get("TT_MODELS_ROOT", "/home/ubuntu/experiments/tt-models")) / "logs" / "meteor" / \
"variants_device_absent_argmax.npz"
dev = None
if dev_path.is_file():
with np.load(dev_path) as d:
i = int(np.flatnonzero(d["absent"] == cam)[0])
dev = {"seg2d": d["seg2d"][i], "depth": d["depth"][i]}
conv0, resize0 = net.conv, net.resize
cur = {"c": "f", "r": "f"}
net.conv = lambda x, module, relu=False: rnd(conv0(x, module, relu), cur["c"])
net.resize = lambda x, hw: rnd(resize0(x, hw), cur["r"])
x = torch.zeros(1, 3, *g["input.imgs"].shape[-2:]) # imagenet: absent = zero AFTER normalising
for c in configs or ["ff", "TT"]:
cur["c"], cur["r"] = c[0], c[1]
with torch.inference_mode():
f = net.image_encoder(x)
dl, _ = net.depth_head(f)
seg = net.seg2d_head(f)
s = torch.argmax(torch.clamp(seg, -30, 30), dim=1).numpy()[0]
d = torch.argmax(dl.reshape(1, -1, *s.shape), dim=1).numpy()[0]
msg = f"absent cam {cam} conv {c[0]} resize {c[1]}: seg2d {(s == ref_s).mean():.4f} depth {(d == ref_d).mean():.4f}"
if dev is not None:
msg += f" | vs device seg2d {(s == dev['seg2d']).mean():.4f} depth {(d == dev['depth']).mean():.4f}"
print(msg, flush=True)
if __name__ == "__main__":
main()
|