| """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) |
| |
| 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() |
|
|