Download code/scripts/bench.py from changh95/hamer-p150: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/changh95/hamer-p150/resolve/main/code/scripts/bench.py
- Command line
-
hf download hf://changh95/hamer-p150/code/scripts/bench.py
-
curl -L -o bench.py https://huggingface.co/changh95/hamer-p150/resolve/main/code/scripts/bench.py
10.1 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Baseline perf breakdown of the served HaMeR path on one Blackhole chip. | |
| Measures, on the REAL checkpoint and the media/ sample crops (fresh input every request, | |
| no same-input hash cache): | |
| device_trace_ms execute_trace back-to-back (non-blocking, one sync at the end) / N | |
| = pure device time of the traced ViT + MANO head graph | |
| per-request stage timings of the served path (median of N): | |
| im2col host input prep: F.unfold -> fp32 [1,1,192,768] (patch=fp32w), or just the | |
| fp32 contiguous crop (patch=device: the im2col runs on device) | |
| h2d ttnn.from_torch(host) + copy_host_to_device_tensor (590 KB fp32) | |
| exec execute_trace(blocking=True) | |
| d2h ttnn.to_torch(dec [1,1,1,109]) | |
| finalize host init-param add + rot6d -> rotmat | |
| total sum (what one served request costs on the device side) | |
| PCC vs the torch CPU reference per sample (trace replay with fresh input). | |
| Grid / dispatch selection (must be decided before ttnn creates its runtime options): | |
| --dispatch eth|worker ETH dispatch (default, patched tree, 1 CQ) or Tensix-column dispatch (opt-in) | |
| --grid XxY (<=12x10) cap the compute grid via TT_METAL_CORE_GRID_OVERRIDE_TODEPRECATE | |
| (11x10 = p150 stock, 12x10 = p150 + ETH dispatch / Galaxy stock) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import statistics | |
| import sys | |
| import time | |
| from pathlib import Path | |
| _REPO = Path(__file__).resolve().parents[1] | |
| sys.path.insert(0, str(_REPO)) | |
| def parse(): | |
| p = argparse.ArgumentParser() | |
| p.add_argument("--dispatch", default="eth", choices=["eth", "worker"], | |
| help="eth (default: p150-equivalent, 1 CQ, 12x10) | worker (Galaxy-only opt-in; 11x10 on a p150)") | |
| p.add_argument("--grid", default="12x10", help="12x10 (policy grid) or smaller, e.g. 11x10 = stock p150") | |
| p.add_argument("--iters", type=int, default=50) | |
| p.add_argument("--ckpt", default=None) | |
| p.add_argument("--mean", default=None) | |
| p.add_argument("--json", default=None, help="write results JSON here") | |
| return p.parse_args() | |
| def main(): | |
| a = parse() | |
| # POLICY: tune/report on the 12x10 grid only (13x10 Galaxy-only grid is forbidden). | |
| x, y = (int(v) for v in a.grid.lower().split("x")) | |
| if x > 12 or y > 10: | |
| raise SystemExit("grid larger than 12x10 is not allowed (p150 12x10 policy)") | |
| os.environ["TT_METAL_CORE_GRID_OVERRIDE_TODEPRECATE"] = f"{x - 1},{y - 1}" | |
| os.environ.setdefault("TT_FUSED_HASH", "0") # served config: fresh image every request | |
| import torch | |
| import ttnn | |
| from hamer import Hamer, build_from_reference, load_hamer_checkpoint | |
| from hamer import model as hm | |
| from hamer import mano_head as mh | |
| from hamer.fused import host_im2col | |
| from hamer.preprocess import preprocess | |
| hf = Path.home() / ".cache/huggingface/hub/models--changh95--hamer-weights/snapshots/e8217b46d54b4e8afaaf0dd6efb6b62341c97d32" | |
| ckpt = Path(a.ckpt) if a.ckpt else hf / "hamer.ckpt" | |
| mean = Path(a.mean) if a.mean else hf / "mano_mean_params.npz" | |
| torch.manual_seed(0) | |
| ref = Hamer().eval() | |
| load_hamer_checkpoint(ref, ckpt, mean) | |
| # dispatch core type: the model's own HAMER_DISPATCH switch (worker | eth) | |
| os.environ["HAMER_DISPATCH"] = a.dispatch | |
| tt = build_from_reference(ref, device_id=0, strict=True) | |
| g = tt.device.compute_with_storage_grid_size() | |
| print(f"[bench] dispatch={tt.dispatch or a.dispatch} cqs=1 grid={g.x}x{g.y} fused={tt.fused_opts.describe()}") | |
| media = _REPO.parent / "media" | |
| boxes = json.loads((media / "bboxes.json").read_text()) | |
| xs = [] | |
| xs_raw = [] # uint8 crops: what the server hands a TT_FUSED_INPUT=u8 model | |
| for name in sorted(boxes): | |
| e = boxes[name] | |
| if isinstance(e, list) and e and isinstance(e[0], dict): | |
| bbox, right = e[0]["bbox"], bool(e[0].get("is_right", 1)) | |
| elif isinstance(e, dict): | |
| bbox, right = e["bbox"], bool(e.get("is_right", 1)) | |
| else: | |
| bbox, right = e, True | |
| x, _ = preprocess(media / name, bbox_xyxy=tuple(bbox), is_right=right) | |
| xr, _ = preprocess(media / name, bbox_xyxy=tuple(bbox), is_right=right, raw=True) | |
| xs.append((name, x)) | |
| xs_raw.append(xr) | |
| def pcc(u, v): | |
| u = u.reshape(-1).float(); v = v.reshape(-1).float() | |
| u = u - u.mean(); v = v - v.mean() | |
| return float((u @ v) / (u.norm() * v.norm()).clamp_min(1e-12)) | |
| with torch.no_grad(): | |
| refs = [ref(x) for _, x in xs] | |
| tt(xs[0][1]) # eager (JIT) | |
| tt(xs[1][1]) # trace capture + replay | |
| assert tt.trace_active | |
| pccs = {n: pcc(tt(x), r) for (n, x), r in zip(xs, refs)} | |
| print("[bench] PCC (trace replay, fresh input):", {k: round(v, 6) for k, v in pccs.items()}) | |
| trace_id, dec_buf = tt._trace | |
| dev = tt.device | |
| # pure device time: back-to-back replays | |
| for _ in range(5): | |
| ttnn.execute_trace(dev, trace_id, cq_id=0, blocking=False) | |
| ttnn.synchronize_device(dev) | |
| t0 = time.perf_counter() | |
| for _ in range(a.iters): | |
| ttnn.execute_trace(dev, trace_id, cq_id=0, blocking=False) | |
| ttnn.synchronize_device(dev) | |
| dev_ms = (time.perf_counter() - t0) / a.iters * 1e3 | |
| # 10 chunks of back-to-back replays -> median / min of the chunk means (noise on the shared host) | |
| chunks = [] | |
| for _ in range(10): | |
| t0 = time.perf_counter() | |
| for _ in range(20): | |
| ttnn.execute_trace(dev, trace_id, cq_id=0, blocking=False) | |
| ttnn.synchronize_device(dev) | |
| chunks.append((time.perf_counter() - t0) / 20 * 1e3) | |
| dev_med, dev_min = statistics.median(chunks), min(chunks) | |
| print(f"[bench] device_trace_ms={dev_ms:.3f} chunk median={dev_med:.3f} min={dev_min:.3f}") | |
| if not tt.fused_opts.device_patch_embed: # stage loop below assumes the fused im2col input | |
| print("RESULT " + json.dumps({"dispatch": a.dispatch, "grid": f"{g.x}x{g.y}", "device_trace_ms": dev_ms, "pcc": pccs})) | |
| tt.close() | |
| return | |
| # served path, stage by stage, fresh input each request | |
| u8 = tt.fused_opts.input == "u8" # served input = the uint8 crop (server: preprocess_pil(raw=True)) | |
| hp = tt._head_params | |
| io = tt._io | |
| if io is not None: | |
| # round 13 fast host I/O (TtHamer._forward_fast, stage by stage): prep = centring into the persistent host | |
| # buffer, h2d = copy_host_to_device_tensor, exec = replay + readback into the persistent host tensor + the | |
| # wait (spin on a CQ event, or a blocking read with HAMER_SPIN_WAIT=0), finalize = HostFinalizer | |
| st = {k: [] for k in ("prep", "h2d", "exec", "finalize", "total")} | |
| for i in range(a.iters): | |
| x = xs_raw[i % len(xs)] if u8 else xs[i % len(xs)][1] | |
| c0 = time.perf_counter() | |
| tt._write_input(io, x) # centring (round 14: + the tiled im2col with TT_FUSED_TILE_IN) | |
| c1 = time.perf_counter() | |
| ttnn.copy_host_to_device_tensor(io["host_in"], tt._tokens_dev) | |
| c2 = time.perf_counter() | |
| ttnn.execute_trace(dev, trace_id, cq_id=0, blocking=False) | |
| if tt.spin_wait: | |
| ttnn.copy_device_to_host_tensor(dec_buf, io["host_out"], blocking=False) | |
| ttnn.event_synchronize(ttnn.record_event(dev, 0)) | |
| else: | |
| ttnn.copy_device_to_host_tensor(dec_buf, io["host_out"], blocking=True) | |
| c3 = time.perf_counter() | |
| out = io["fin"](io["out_np"]) | |
| c4 = time.perf_counter() | |
| for k, (s_, e_) in zip(st, [(c0, c1), (c1, c2), (c2, c3), (c3, c4), (c0, c4)]): | |
| st[k].append((e_ - s_) * 1e3) | |
| else: | |
| st = {k: [] for k in ("im2col", "h2d", "exec", "d2h", "finalize", "total")} | |
| for i in range(a.iters): | |
| x = xs_raw[i % len(xs)] if u8 else xs[i % len(xs)][1] | |
| c0 = time.perf_counter() | |
| host, hdt, lay = tt._host_input(x) | |
| c1 = time.perf_counter() | |
| h = ttnn.from_torch(host, device=None, layout=lay, dtype=hdt, # round 15: the input layout (TILE with TT_FUSED_TILE_IN) | |
| mesh_mapper=ttnn.ReplicateTensorToMesh(dev)) | |
| ttnn.copy_host_to_device_tensor(h, tt._tokens_dev) | |
| c2 = time.perf_counter() | |
| ttnn.execute_trace(dev, trace_id, cq_id=0, blocking=True) | |
| c3 = time.perf_counter() | |
| dec_h = tt._dec_host(ttnn.to_torch(dec_buf), 1) | |
| c4 = time.perf_counter() | |
| out = mh.host_finalize(dec_h, hp["dec_split"], tt._init_pose, tt._init_betas, tt._init_cam, init=tt._fin_init) | |
| c5 = time.perf_counter() | |
| for k, (s, e) in zip(st, [(c0, c1), (c1, c2), (c2, c3), (c3, c4), (c4, c5), (c0, c5)]): | |
| st[k].append((e - s) * 1e3) | |
| med = {k: statistics.median(v) for k, v in st.items()} | |
| # last output sanity vs reference | |
| last = (a.iters - 1) % len(xs) | |
| med_pcc_last = pcc(out, refs[last]) | |
| # full TtHamer.__call__ (what the server calls), fresh input | |
| call = [] | |
| with torch.no_grad(): | |
| for i in range(a.iters): | |
| x = xs_raw[i % len(xs)] if u8 else xs[i % len(xs)][1] | |
| c0 = time.perf_counter(); tt(x); call.append((time.perf_counter() - c0) * 1e3) | |
| res = { | |
| "dispatch": a.dispatch, "grid": f"{g.x}x{g.y}", "device_trace_ms": dev_ms, | |
| "device_trace_chunk_median_ms": dev_med, "device_trace_chunk_min_ms": dev_min, | |
| "stages_ms": med, "ttHamer_call_ms": statistics.median(call), "ttHamer_call_min_ms": min(call), | |
| "pcc": pccs, "pcc_bench_last": med_pcc_last, | |
| } | |
| print(f"[bench] device_trace_ms={dev_ms:.3f} (med {dev_med:.3f} min {dev_min:.3f}) served-call={res['ttHamer_call_ms']:.3f} ms (min {res['ttHamer_call_min_ms']:.3f})") | |
| print("[bench] stages (median ms): " + " ".join(f"{k}={v:.3f}" for k, v in med.items())) | |
| print("RESULT " + json.dumps(res)) | |
| if a.json: | |
| Path(a.json).write_text(json.dumps(res, indent=2)) | |
| tt.close() | |
| if __name__ == "__main__": | |
| main() | |