"""Explore optimizing the SAM2 image encoder — the profiled latency bottleneck. Phase 10. First it splits the AMG wall-clock into (a) the image encoder (one `set_image`) and (b) the per-point mask decoding for the 16x16 prompt grid — because that ratio decides whether optimizing the *encoder* is even the right lever. Then it tries two CPU-friendly encoder optimizations and measures each: bf16 autocast and torch.compile. Writes docs/eval/sam2_opt.json. Run: python -m backend.optimize_sam2 """ from __future__ import annotations import json import os import time from pathlib import Path import numpy as np import torch OUT = Path("docs/eval") def med_ms(fn, n=3, warm=1): for _ in range(warm): fn() ts = [] for _ in range(n): t = time.perf_counter(); fn(); ts.append(time.perf_counter() - t) return round(sorted(ts)[len(ts) // 2] * 1000) def main(): torch.set_num_threads(os.cpu_count() or 4) from sam2.automatic_mask_generator import SAM2AutomaticMaskGenerator from sam2.build_sam import build_sam2_hf from sam2.sam2_image_predictor import SAM2ImagePredictor print("[opt] loading SAM2-Hiera-Small ...", flush=True) model = build_sam2_hf("facebook/sam2-hiera-small", device="cpu") img = np.random.default_rng(0).integers(0, 255, (512, 512, 3), dtype=np.uint8) pred = SAM2ImagePredictor(model) res: dict = {} # ---- 1. encoder (set_image) vs full AMG ---- print("[opt] timing image encoder (set_image) ...", flush=True) enc = med_ms(lambda: pred.set_image(img), n=3, warm=1) print("[opt] timing full AMG (pps=16 @ 512) ...", flush=True) amg = SAM2AutomaticMaskGenerator(model, points_per_side=16) full = med_ms(lambda: amg.generate(img), n=1, warm=0) res["encoder_ms"] = enc res["full_amg_ms"] = full res["mask_decode_ms"] = max(full - enc, 0) res["encoder_share_pct"] = round(enc / full * 100, 1) if full else None # ---- 2. bf16 autocast on the encoder ---- print("[opt] bf16 autocast ...", flush=True) try: def enc_bf16(): with torch.autocast("cpu", dtype=torch.bfloat16): pred.set_image(img) b = med_ms(enc_bf16, n=3, warm=1) res["encoder_bf16_ms"] = b res["bf16_speedup"] = round(enc / b, 2) except Exception as e: # noqa: BLE001 res["bf16_note"] = f"{type(e).__name__}: {str(e)[:100]}" # ---- 3. torch.compile the encoder ---- print("[opt] torch.compile (may take a minute to compile) ...", flush=True) try: model.image_encoder = torch.compile(model.image_encoder) c = med_ms(lambda: pred.set_image(img), n=3, warm=2) # warmups trigger compilation res["encoder_compiled_ms"] = c res["compile_speedup"] = round(enc / c, 2) except Exception as e: # noqa: BLE001 res["compile_note"] = f"{type(e).__name__}: {str(e)[:100]}" OUT.mkdir(parents=True, exist_ok=True) (OUT / "sam2_opt.json").write_text(json.dumps(res, indent=2)) print("\n[opt] RESULT\n" + json.dumps(res, indent=2)) if __name__ == "__main__": main()