The bank that ran the backbone five times
Chasing a matcher bottleneck turned up a defect worth more than the optimisation I went looking for — a per-match cost that barely moved with `top_k`, which is the tell that something else is the real cost.
The number that should not have been flat was flat: 42.3 ms per lookup at top_k=1024, 36.3 ms at 512, 13.3 ms at 256. A quarter of the multiply-accumulates should have cost far less than 86% of the time it was taking. When a number refuses to move with the thing that should dominate it, the thing that should dominate it isn't the cost — something fixed is.
Anchor.locate() detects features internally, which is fine for one reference. But CheckpointBank.locate() asks several references about the same frame, and each one called the full detector on identical pixels. A four-wide shortlist ran the backbone four times on the same image, plus once more for the signature path — five backbone passes per lookup, four of them computing something already sitting in memory.
The fix is smaller than the bug
Anchor.locate_features(k1, d1) now takes features already extracted, and the bank detects once and hands them to every reference. At the shipping width that took the lookup from 169.1 ms to 94.4 ms — 1.8× — with zero accuracy cost, because the arithmetic is identical. Nothing got approximated. It just stopped being repeated.
The saving on the Hailo-8 changes too, and not the way you'd guess.
An earlier section had concluded the chip saves a fixed ~31.7 ms regardless of bank size, on the assumption that detection happens once per lookup. It doesn't — it happens k+1 times — so the saving actually scales with the shortlist width. Removing the redundancy on CPU alone recovered 75 ms; the remaining chip work is what moves onto the accelerator next. The profiling number that looked boring — flat against top_k — was the whole finding.