File size: 10,094 Bytes
58ba6df 843986e 58ba6df 843986e 58ba6df 843986e 58ba6df | 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 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | # 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()
|