How we find similar photos on device

By VinciPhoto engineering. Updated .

Summary: VinciPhoto embeds each photo once with Apple’s MobileCLIP image encoder, uses that vector for both search and grouping, links photos from the same day that are close together, and treats each connected set as a group. All of it runs on the device.

The problem

People rarely have many exact copies of a photo. They have many takes of it. To group those takes, you have to tell “same moment, slightly different frame” apart from “similar-looking but unrelated”. It also has to run over a whole library, on a phone, without overheating it.

One embedding for two features

Our first version used a MobileNetV3 model for grouping. When we added search by description, which needs MobileCLIP, each photo went through two fetches, two decodes and two model runs, and we stored two sets of vectors.

We measured where the time went. Decoding the image took about 76% of the per-photo cost, so doing it twice was the obvious waste. Moving grouping onto the vectors the search index already holds changed the per-photo work:

Per photoTwo modelsOne model
Library fetches21
Decodes21
Model runs21
Vector storage6,144 bytes2,048 bytes

It also let us remove a dependency and a whole model from the app. The clustering code doesn’t generate vectors any more. It’s handed a source that returns a cached vector, or generates and stores one on a miss, so a scan where every vector is cached never has to read the original images.

From vectors to groups

  1. Bucket by day. Near-identical shots almost always come from the same day, and limiting comparisons to a day keeps the work small and stops look-alike scenes from different occasions from joining up.
  2. Build a sparse graph. Within a day, we compare vectors by cosine similarity and connect each photo to its closest neighbors above a similarity cutoff.
  3. Take connected components. Each connected set of photos becomes a group.

The risk with connected components is chaining: A resembles B, B resembles C, and you end up with one large group where A and C have little in common. Limiting neighbors and setting a strict cutoff keeps this in check.

How we checked the switch

Changing models changes every group, so we compared the two on the same test library before removing the old one. The new setup produced 452 groups against 475, or about 95% of the old volume. Its largest group held 24 photos against 26, so chaining didn’t get worse. We reviewed the 18 cases where groups merged by eye and accepted them. This is one library, so it doesn’t prove the result in general, but it was enough to act on. We ran the comparison before deleting the old store, because the old vectors were the only baseline we had.

Picking the best shot

Once a group exists, a quality pass scores each photo on signals such as sharpness, faces and exposure, and the app shows the reason for the pick. We originally decoded each full-resolution original a second time here. Now we ask the system for a smaller rendering at the size scoring needs, which removes that extra decode.

Not cooking the phone

A first scan over a large library can make a phone hot. Since heat depends on power over time, the foreground scan now reads the device’s thermal state. It slows down as the device warms up, stops if it reaches the critical state, and resumes after it cools. This changes when the work runs and not what it computes, so results stay the same.

What’s left out

We don’t publish our similarity cutoff or scoring details. They’re tuned to our data and change between releases. If you’re building something similar, measure your own: review the differences between settings by eye, and watch the size of your largest group as an early warning for chaining.

Frequently asked questions

What is on-device image similarity?

On-device image similarity means comparing photos using a model that runs on the phone itself. Each photo becomes an embedding, a list of numbers describing what it shows, and photos with close embeddings are treated as similar. No image needs to leave the device, and it works without a network connection.

Why not use perceptual hashing to find similar photos?

Perceptual hashes are cheap and work well for copies and light edits, but they compare image structure and not content. Two frames of the same scene can hash apart after small shifts in framing or lighting. Embeddings cope better with those changes, at the cost of running a model on every photo.

Does VinciPhoto publish its similarity threshold?

No. The threshold and scoring details are tuned to our own test data and may change between releases, so we don't publish them. What matters is how we choose them: by comparing the groups that each setting produces against the previous results, and reviewing the differences by eye.

Try it on your own library

Free to download. The full scan is never limited, so you can see how much space you could get back before you decide anything.

Download on the App Store