Download code/scripts/probe_conv.py from changh95/bevdet-p150: direct link, hf CLI and curl.
- Browser
- Download file 11 kB
-
https://huggingface.co/changh95/bevdet-p150/resolve/main/code/scripts/probe_conv.py
- Command line
-
hf download hf://changh95/bevdet-p150/code/scripts/probe_conv.py
-
curl -L -o probe_conv.py https://huggingface.co/changh95/bevdet-p150/resolve/main/code/scripts/probe_conv.py
11 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Probe (OPT round 3, R3.1): per-conv ``Conv2dConfig`` sweep of the model's kxk convs at their real shapes. | |
| The encoder and the decoder are built at the defaults and run once eagerly (random crop / gates / ``bev_in``) with | |
| ``Conv2d.__call__`` hooked to record each kxk conv (its settled slicing and config overrides, its input spec). Every | |
| distinct conv (by geometry + settled config) is then rebuilt with the same weights, bias, activation, precision and | |
| output dtype and called on a random DRAM TILE input of that spec with candidate configs: activation / weight double | |
| buffering, other shard layouts, activation block heights, ``full_inner_dim``, split reader / activation reuse. Per | |
| candidate: the bf16 / fp32 output bit for bit against the settled config on the same input, and the device time per | |
| call (one trace of ``reps`` calls, best of 3 blocking replays: the conv's whole call incl. its I2S / halo / S2I). | |
| bin/devrun -t 1500 -- python -u code/scripts/probe_conv.py --part enc --json logs/bevdet/opt_r3/probe_conv_enc.json | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| def bits(t): | |
| import torch | |
| import ttnn | |
| x = ttnn.to_torch(ttnn.from_device(t)) | |
| if x.dtype == torch.float32: | |
| return x.view(torch.int32).numpy() | |
| return x.to(torch.bfloat16).view(torch.int16).numpy() | |
| def timed(device, fn, reps): | |
| import ttnn | |
| outs = [] | |
| fn().deallocate() | |
| ttnn.synchronize_device(device) | |
| tid = ttnn.begin_trace_capture(device, cq_id=0) | |
| for _ in range(reps): | |
| outs.append(fn()) | |
| ttnn.end_trace_capture(device, tid, cq_id=0) | |
| best = 1e9 | |
| for _ in range(3): | |
| ttnn.synchronize_device(device) | |
| t0 = time.perf_counter() | |
| ttnn.execute_trace(device, tid, cq_id=0, blocking=True) | |
| best = min(best, (time.perf_counter() - t0) * 1e3 / reps) | |
| ttnn.release_trace(device, tid) | |
| for o in outs: | |
| o.deallocate() | |
| return best | |
| def candidates(base_slicing, base_ov, cin, rows): | |
| """(label, slicing, overrides) list; the first is the settled config.""" | |
| import ttnn | |
| HS, BS, WS = (ttnn.TensorMemoryLayout.HEIGHT_SHARDED, ttnn.TensorMemoryLayout.BLOCK_SHARDED, | |
| ttnn.TensorMemoryLayout.WIDTH_SHARDED) | |
| b = dict(base_ov) | |
| out = [("base", base_slicing, b)] | |
| db = {"enable_act_double_buffer": True, "enable_weights_double_buffer": True} | |
| out += [("adb", base_slicing, {**b, "enable_act_double_buffer": True}), | |
| ("wdb", base_slicing, {**b, "enable_weights_double_buffer": True}), | |
| ("db", base_slicing, {**b, **db})] | |
| if base_slicing == "l1": | |
| out += [("db+fid", "l1", {**b, **db, "full_inner_dim": True})] | |
| else: | |
| out += [(f"db+abh{h}", base_slicing, {**b, **db, "act_block_h_override": h}) for h in (256, 128, 64)] | |
| out += [(f"hs+db{h or ''}", base_slicing, {**b, **db, "shard_layout": HS, **({"act_block_h_override": h} if h else {})}) | |
| for h in (0, 128, 64)] | |
| l1 = {k: v for k, v in b.items() if k != "act_block_h_override"} | |
| for name, lay in (("hs", HS), ("bs", BS), ("ws", WS)): | |
| for abh in (0, 128, 64): | |
| if name == "ws" and abh: | |
| continue | |
| ov = {**l1, "shard_layout": lay} | |
| if abh: | |
| ov["act_block_h_override"] = abh | |
| out.append((f"{name}{abh or ''}", "l1", ov)) | |
| out.append((f"{name}{abh or ''}+db", "l1", {**ov, **db})) | |
| if name == "hs": | |
| out.append(("hs+db+reuse", "l1", {**l1, "shard_layout": HS, **db, "enable_activation_reuse": True})) | |
| out.append(("hs+db+split", "l1", {**l1, "shard_layout": HS, **db, "force_split_reader": True})) | |
| if name == "bs": | |
| out.append(("bs+db+fid", "l1", {**l1, "shard_layout": BS, **db, "full_inner_dim": True})) | |
| return out | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--part", choices=("enc", "dec"), required=True) | |
| ap.add_argument("--json") | |
| ap.add_argument("--only", default="", help="comma list of conv-name substrings") | |
| ap.add_argument("--reps", type=int, default=8) | |
| ap.add_argument("--quick", action="store_true", help="only base / double-buffer candidates") | |
| a = ap.parse_args() | |
| import torch | |
| import ttnn | |
| from tt_bevdet.api import KNOBS | |
| from tt_bevdet.device import close_device, open_device | |
| from tt_bevdet.reference.config import default_config | |
| from tt_bevdet.reference.weights import BEVDetWeights | |
| from tt_bevdet.tests import paths | |
| from tt_bevdet.tt.params import BEVDetParams | |
| from tt_bevdet.ttaw.ops import conv as convmod | |
| from tt_bevdet.ttaw.ops.conv import Conv2d, FeatureMap | |
| cfg = default_config() | |
| params = BEVDetParams(BEVDetWeights(paths.weights_onnx()), cfg) | |
| k = KNOBS.read() | |
| prec = str(k.PRECISION) | |
| rec = {} | |
| orig = Conv2d.__call__ | |
| def hook(self, x, **kw): | |
| out = orig(self, x, **kw) | |
| if not self.is_matmul and isinstance(x, FeatureMap): | |
| t = x.tensor | |
| rec[self.name] = {"conv": self, "spec": (x.batch, x.height, x.width, x.channels), | |
| "slicing": self.slicing, "ov": dict(self.conv_config_overrides), | |
| "dram_in": bool(convmod.is_dram(t)), "dtype": str(t.dtype)} | |
| return out | |
| dev = open_device(trace_region_size=1024 << 20) | |
| rng = np.random.default_rng(0) | |
| res = [] | |
| try: | |
| Conv2d.__call__ = hook | |
| if a.part == "enc": | |
| from tt_bevdet.tt.image_encoder import TtImageEncoder | |
| mod = TtImageEncoder(params, precision=prec, stem_in="kin", conv_l1=str(k.CONV_L1), | |
| backbone_res=str(k.BACKBONE_RES)) | |
| n = cfg.n_cams | |
| ih, iw = cfg.input_hw | |
| crop = ttnn.from_torch(torch.from_numpy(rng.integers(0, 256, (1, 1, n * ih, iw * 3), dtype=np.uint8)), | |
| dtype=ttnn.uint8, layout=ttnn.ROW_MAJOR_LAYOUT, device=dev, | |
| memory_config=ttnn.DRAM_MEMORY_CONFIG) | |
| g = ttnn.from_torch(torch.full((n, 1, 1, 512), 0.5), dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, | |
| device=dev, memory_config=ttnn.DRAM_MEMORY_CONFIG) | |
| d, f = mod(crop, g, g) | |
| d.deallocate() | |
| f.deallocate() | |
| else: | |
| from tt_bevdet.tt.bev_decoder import TtBEVDecoder | |
| from tt_bevdet.tt.placement import parse_groups | |
| dg = parse_groups(str(k.CONV_L1)) | |
| mod = TtBEVDecoder(params, precision=prec, | |
| conv_l1="all" if "decall" in dg else ("exact" if "dec" in dg else False), | |
| merge=str(k.DEC_MERGE) not in ("0", "false", "False")) | |
| ny, nx = cfg.bev_hw | |
| x = ttnn.from_torch(torch.randn(1, 1, ny * nx, 864).relu(), dtype=ttnn.bfloat16, layout=ttnn.TILE_LAYOUT, | |
| device=dev, memory_config=ttnn.DRAM_MEMORY_CONFIG) | |
| mod(FeatureMap(x, 1, ny, nx, 864)).deallocate() | |
| ttnn.deallocate(x) | |
| Conv2d.__call__ = orig | |
| mod.release() | |
| seen = {} | |
| for name, r in rec.items(): | |
| if not r["dram_in"]: # e.g. the K-IN stem conv on its own L1 shards | |
| continue | |
| c = r["conv"] | |
| key = (r["spec"], c.out_channels, c.kernel, c.stride, c.padding, c.dilation, c.activation, | |
| c.output_dtype, r["slicing"], tuple(sorted(r["ov"].items()))) | |
| seen.setdefault(key, []).append(name) | |
| print(f"{len(rec)} kxk convs, {len(seen)} distinct", flush=True) | |
| only = [s for s in a.only.split(",") if s] | |
| for key, names in seen.items(): | |
| if only and not any(o in nm for o in only for nm in names): | |
| continue | |
| r = rec[names[0]] | |
| c = r["conv"] | |
| bsz, h, w, cin = r["spec"] | |
| rows = bsz * h * w | |
| xin = ttnn.from_torch(torch.randn(1, 1, rows, cin).relu(), dtype=ttnn.bfloat16, | |
| layout=ttnn.TILE_LAYOUT, device=dev, memory_config=ttnn.DRAM_MEMORY_CONFIG) | |
| fm = FeatureMap(xin, bsz, h, w, cin) | |
| row = {"names": names, "spec": list(r["spec"]), "cout": c.out_channels, "kernel": list(c.kernel), | |
| "stride": list(c.stride), "dilation": list(c.dilation), "slicing": r["slicing"], | |
| "ov": {kk: str(v) for kk, v in r["ov"].items()}, "cands": []} | |
| print(f"== {names} {r['spec']} -> {c.out_channels} k{c.kernel} s{c.stride} d{c.dilation} " | |
| f"slicing={r['slicing']} ov={r['ov']}", flush=True) | |
| ref = None | |
| cands = candidates(r["slicing"], r["ov"], cin, rows) | |
| if a.quick: | |
| cands = cands[:4] | |
| for label, slicing, ov in cands: | |
| cc = Conv2d(c.weight, c.bias, stride=c.stride, padding=c.padding, dilation=c.dilation, | |
| groups=c.groups, activation=c.activation, precision=c.precision, | |
| output_dtype=c.output_dtype, slicing=slicing, conv_config=ov, name=c.name + "." + label) | |
| try: | |
| o = cc(fm) | |
| got = bits(o.tensor) | |
| o.deallocate() | |
| if ref is None: | |
| ref = got | |
| nbad = int(np.count_nonzero(got != ref)) if got.shape == ref.shape else -1 | |
| t = timed(dev, lambda: cc(fm), a.reps) | |
| row["cands"].append({"label": label, "ms": round(t, 4), "bad": nbad}) | |
| print(f" {label:14s} {t * 1e3:8.1f} us {nbad} differ", flush=True) | |
| except Exception as e: # noqa: BLE001 - rejected config / L1 clash | |
| msg = str(e).splitlines()[0][:150] | |
| row["cands"].append({"label": label, "error": msg}) | |
| print(f" {label:14s} error: {msg}", flush=True) | |
| if label == "base": | |
| break | |
| finally: | |
| cc.release() | |
| dev.clear_program_cache() # frees the cached programs' L1_SMALL config tensors | |
| ok = [x for x in row["cands"] if "ms" in x and x["bad"] == 0] | |
| if ok: | |
| bst = min(ok, key=lambda x: x["ms"]) | |
| row["best_exact"] = bst | |
| print(f" best exact: {bst['label']} {bst['ms'] * 1e3:.1f} us (base " | |
| f"{row['cands'][0].get('ms', 0) * 1e3:.1f}) x{len(names)}", flush=True) | |
| res.append(row) | |
| ttnn.deallocate(xin) | |
| if a.json: | |
| Path(a.json).write_text(json.dumps(res, indent=1)) | |
| finally: | |
| Conv2d.__call__ = orig | |
| close_device(dev) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |