"""Trajectory parity: torch wrapper vs converted CoreML model, both from zero state, 18 sequential frames with real per-frame inputs. Early frames sit on the object-score decision boundary (degenerate zero memory); the test is whether late frames track once banks hold real memories. """ import numpy as np import torch import common import dense_wrapper as dw def main(): import coremltools as ct cache = torch.load("eager_cache.pt", weights_only=False) wrapper, model = dw.build_wrapper() wrapper.mem_bank.zero_(); wrapper.img_bank.zero_(); wrapper.ptr_bank.zero_() mlmodel = ct.models.MLModel("dense_sam3_trackstep.mlpackage", compute_units=ct.ComputeUnit.CPU_ONLY) in_names = [i.name for i in mlmodel.input_description._fd_spec] state = mlmodel.make_state() frames = list(range(16, 34)) print(f"{'frame':>5} {'osl_rel':>9} {'iou_rel':>9} {'mask_sign_agree':>15} " f"{'lowmask_rel':>11}") for f in frames: inputs = dw.frame_inputs(model, cache, f) with torch.no_grad(): t_low, t_high, t_osl, t_ious = [x.clone() for x in wrapper(*inputs)] feed = {n: v.numpy().astype(np.float32) for n, v in zip(in_names, inputs)} got = mlmodel.predict(feed, state=state) # map outputs by shape (names are auto-generated) by_shape = {tuple(np.asarray(v).shape): torch.from_numpy( np.asarray(v)).float() for v in got.values()} c_low = by_shape[tuple(t_low.shape)] c_osl = by_shape[tuple(t_osl.shape)] c_ious = by_shape[tuple(t_ious.shape)] def rel(a, b): return ((a - b).abs().max() / b.abs().max().clamp_min(1e-9)).item() sign_agree = ((c_low > 0) == (t_low > 0)).float().mean().item() print(f"{f:>5} {rel(c_osl, t_osl):>9.2e} {rel(c_ious, t_ious):>9.2e} " f"{sign_agree:>15.4f} {rel(c_low, t_low):>11.2e}") if __name__ == "__main__": main()