Download code/scripts/first_call.py from changh95/hamer-p150: direct link, hf CLI and curl.
- Browser
- Download file 8.93 kB
-
https://huggingface.co/changh95/hamer-p150/resolve/main/code/scripts/first_call.py
- Command line
-
hf download hf://changh95/hamer-p150/code/scripts/first_call.py
-
curl -L -o first_call.py https://huggingface.co/changh95/hamer-p150/resolve/main/code/scripts/first_call.py
8.93 kB
| # SPDX-FileCopyrightText: © 2026 Tenstorrent Inc. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """First-call latency of the Python API, one call variant per FRESH process. | |
| python code/scripts/first_call.py --variant right [--mano PATH] [--out .work/first_call] [-- from_pretrained kwargs as JSON] | |
| It times ``HamerModel.from_pretrained(device_id=0)``, then 30 real calls of the variant. Every call has a NEW real input | |
| (the 8 demo hands of ``media/``, the box shifted by ``k`` px from call 9 on), never the warm-up input. It prints and | |
| writes ``<out>/<variant>.json``: boot seconds, the 1st / 2nd / 10th call, the median of calls 11..30, the | |
| first-call penalty (1st - steady median), the median of the later calls on the same demo hand as call 1 (calls 9, 17, 25: | |
| the crop size, and so the host time, depends on the hand), and a control: one more new call after a 0.5 s pause in the | |
| warm process (the cost of the first call after ANY idle gap: cold CPU caches; warm-up cannot remove it). The raw outputs of every call go to ``<out>/<variant>.npz`` so two runs | |
| (before / after a change) can be compared bit for bit. | |
| Variants (the device graph has ONE shape: a batch-1 256x192 crop; every variant below is a host-side code path): | |
| right model(path, bbox, is_right=True) single right hand, JPEG file path (the quickstart) | |
| left model(path, bbox, is_right=False) single left hand (mirrored crop) | |
| multi model(path, [box_a, box_b], is_right=[R, L]) two hands of one frame | |
| array model(np.ndarray, bbox) decoded uint8 RGB frame | |
| tensor model(torch.Tensor (3,H,W) uint8, bbox) | |
| center model(path, bbox=None) centre-square crop | |
| mano model(path, bbox, is_right=R/L) with MANO mesh, joints, 2D keypoints (needs --mano) | |
| crops model.forward_crops(uint8 (2,3,256,256)) upstream-style batch, B=2 | |
| crops_float model.forward_crops(normalized float (2,3,256,256)) | |
| run_crop model.run_crop(uint8 (1,3,256,192)) | |
| big model(path, bbox) on 1920x1252 JPEG frames (the demo frames upscaled; written to <out>/big_frames) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import statistics | |
| import sys | |
| import time | |
| from pathlib import Path | |
| import numpy as np | |
| REPO = Path(__file__).resolve().parents[2] | |
| MEDIA = REPO / "media" | |
| VARIANTS = ("right", "left", "multi", "array", "tensor", "center", "mano", "crops", "crops_float", "run_crop", "big") | |
| BIG_SCALE = 3.75 # "big": the demo frames upscaled 512x334 -> 1920x1252 (larger than the 640x480 warm-up frame) | |
| N_CALLS = 30 | |
| def hands(): | |
| boxes = json.loads((MEDIA / "bboxes.json").read_text()) | |
| return [(MEDIA / name, e["bbox"], bool(e.get("is_right", 1))) for name in sorted(boxes) for e in boxes[name]] | |
| def inputs(variant: str, k: int, out: Path): | |
| """The k-th real input (k = 0..N_CALLS-1); distinct for every k.""" | |
| hs = hands() | |
| if variant == "big": | |
| from PIL import Image | |
| path, box, right = hs[k % len(hs)] | |
| d = k // len(hs) | |
| big = out / "big_frames" / path.name | |
| if not big.exists(): | |
| big.parent.mkdir(parents=True, exist_ok=True) | |
| im = Image.open(path).convert("RGB") | |
| im.resize((round(im.width * BIG_SCALE), round(im.height * BIG_SCALE)), Image.BICUBIC).save(big, quality=92) | |
| return (big,), dict(bbox=[(v + d) * BIG_SCALE for v in box], is_right=right) | |
| path, box, right = hs[k % len(hs)] | |
| d = k // len(hs) # shift the box by d px on later rounds: a new crop every call | |
| box = [box[0] + d, box[1] + d, box[2] + d, box[3] + d] | |
| if variant == "right": | |
| return (path,), dict(bbox=box, is_right=True) | |
| if variant == "left": | |
| return (path,), dict(bbox=box, is_right=False) | |
| if variant == "mano": | |
| return (path,), dict(bbox=box, is_right=right) | |
| if variant == "multi": | |
| p = sorted({h[0] for h in hs})[k % 4] | |
| bs = [[b[0] + d, b[1] + d, b[2] + d, b[3] + d] for q, b, _ in hs if q == p] | |
| return (p,), dict(bbox=bs, is_right=[r for q, _, r in hs if q == p]) | |
| if variant == "center": | |
| return (hs[(k + d) % len(hs)][0],), dict(bbox=None) if k < 4 else dict(bbox=None, rescale_factor=2.0 - 0.01 * k) | |
| from PIL import Image | |
| a = np.asarray(Image.open(path).convert("RGB")) | |
| if variant == "array": | |
| return (a,), dict(bbox=box, is_right=right) | |
| if variant == "tensor": | |
| import torch | |
| return (torch.from_numpy(a.copy()).permute(2, 0, 1),), dict(bbox=box, is_right=right) | |
| from hamer.preprocess import crop_bbox | |
| import torch | |
| def crop256(i): | |
| p, b, r = hs[(k * 2 + i) % len(hs)] | |
| b = [b[0] + d, b[1] + d, b[2] + d, b[3] + d] | |
| c, _ = crop_bbox(Image.open(p).convert("RGB"), b) | |
| if not r: | |
| c = c.transpose(Image.FLIP_LEFT_RIGHT) | |
| return torch.from_numpy(np.asarray(c).copy()).permute(2, 0, 1) | |
| if variant == "run_crop": | |
| return (crop256(0)[None, :, :, 32:224].contiguous(),), {} | |
| u = torch.stack([crop256(0), crop256(1)]) | |
| if variant == "crops": | |
| return (u,), {} | |
| if variant == "crops_float": | |
| from hamer.preprocess import normalize_raw | |
| return (normalize_raw(u),), {} | |
| raise ValueError(variant) | |
| def flat(out) -> np.ndarray: | |
| """All numbers of an output, for a bit-exact comparison.""" | |
| import torch | |
| from hamer import HandPrediction | |
| if isinstance(out, list): | |
| return np.concatenate([flat(o) for o in out]) | |
| if isinstance(out, HandPrediction): | |
| parts = [out.rotmats, out.betas, out.cam, out.cam_t_full, out.bbox_center, [out.bbox_size, out.focal_length]] | |
| parts += [p for p in (out.vertices, out.joints, out.keypoints_2d) if p is not None] | |
| return np.concatenate([np.asarray(p, np.float64).ravel() for p in parts]) | |
| if isinstance(out, dict): | |
| return np.concatenate([flat(out[k]) for k in sorted(out)]) | |
| if isinstance(out, torch.Tensor): | |
| return out.detach().double().numpy().ravel() | |
| raise TypeError(type(out)) | |
| def main() -> None: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--variant", required=True, choices=VARIANTS) | |
| ap.add_argument("--mano", default=None) | |
| ap.add_argument("--out", default=str(REPO / ".work" / "first_call")) | |
| ap.add_argument("--kwargs", default="{}", help="extra from_pretrained kwargs as JSON (e.g. warmup_variants)") | |
| a = ap.parse_args() | |
| if a.variant == "mano" and not a.mano: | |
| sys.exit("--variant mano needs --mano") | |
| real = [inputs(a.variant, k, Path(a.out)) for k in range(N_CALLS + 1)] # host-side input preparation is not part of the timing | |
| t0 = time.perf_counter() | |
| import torch # noqa: F401 (timed as part of a user's start-up) | |
| from hamer import HamerModel | |
| t_imp = time.perf_counter() - t0 | |
| t0 = time.perf_counter() | |
| model = HamerModel.from_pretrained(device_id=0, mano_path=a.mano, **json.loads(a.kwargs)) | |
| t_boot = time.perf_counter() - t0 | |
| fn = {"crops": model.forward_crops, "crops_float": model.forward_crops, "run_crop": model.run_crop}.get(a.variant, model) | |
| ts, outs = [], [] | |
| for args, kw in real[:N_CALLS]: | |
| t = time.perf_counter() | |
| out = fn(*args, **kw) | |
| ts.append((time.perf_counter() - t) * 1e3) | |
| outs.append(flat(out)) | |
| time.sleep(0.5) | |
| t = time.perf_counter() | |
| fn(*real[N_CALLS][0], **real[N_CALLS][1]) | |
| idle_ms = (time.perf_counter() - t) * 1e3 | |
| model.close() | |
| steady = statistics.median(ts[10:]) | |
| per = 4 if a.variant in ("multi", "crops", "crops_float") else 8 # inputs repeat (box shifted) with this period | |
| same = statistics.median(ts[per::per][:3]) | |
| res = {"variant": a.variant, "import_s": round(t_imp, 2), "from_pretrained_s": round(t_boot, 2), | |
| "call1_ms": round(ts[0], 2), "call2_ms": round(ts[1], 2), "call10_ms": round(ts[9], 2), | |
| "steady_median_ms": round(steady, 2), "first_call_penalty_ms": round(ts[0] - steady, 2), | |
| "call1_over_steady": round(ts[0] / steady, 3), "same_input_median_ms": round(same, 2), | |
| "call1_over_same_input": round(ts[0] / same, 3), "after_idle_ms": round(idle_ms, 2), | |
| "after_idle_over_same_input": round(idle_ms / statistics.median(ts[N_CALLS % per + per::per]), 3), | |
| "warmup": model.info.get("warmup"), | |
| "warmup_variants": model.info.get("warmup_variants"), "chip": os.environ.get("TT_CHIP"), | |
| "host_load": [round(v, 1) for v in os.getloadavg()], "all_ms": [round(v, 3) for v in ts]} | |
| out = Path(a.out) | |
| out.mkdir(parents=True, exist_ok=True) | |
| (out / f"{a.variant}.json").write_text(json.dumps(res, indent=1)) | |
| np.savez(out / f"{a.variant}.npz", *outs) | |
| print("[first_call] " + json.dumps({k: v for k, v in res.items() if k not in ("all_ms", "warmup")}), flush=True) | |
| if __name__ == "__main__": | |
| main() | |