An 800-image corpus, indexed offline with up to six encoders (CLIP, SigLIP, DINOv2, Florence-2, Gemma 4, Gemini Embedding 2) and packed into one SQLite file that this page loads and queries entirely in your browser. Pick a query image, optionally crop and rotate it, and search the selected models at once: each column is ranked by that model's own cosine, every hit shows its score, and hits above the model's same-image threshold are marked. The point is to watch the columns disagree.
Drop a query image, or click to choose
Getting an untouched corpus image to score ≈1.0 against itself took closing two runtime gaps, and both are visible here. First, pixels: canvas downscaling is runtime-specific (Skia in your browser, cairo in the offline indexer), so both sides render at native resolution and downscale with the same pure-JS resampler (fitToSquare in the shared lib/); the shared-pipeline scaling toggle switches back to the browser's downscaler so the difference stays demonstrable. Second, precision: the browser's WASM backend runs q8-quantized weights while Node defaults to fp32 — the same pixels through different weights cost ~0.14 cosine — so the index is built q8 and the page forces q8 on every backend. Chapter and verse in AGENTS.md rule 4: same code, same version, same pixels, same precision.
Search here is exact: the query embedding is dot-multiplied against all 800 unit vectors per model, which takes under a millisecond in JavaScript. Approximate indexes (HNSW, IVF) exist for when exact search stops being instant, roughly beyond a few million vectors; at this scale they would add complexity and recall loss for nothing. The index is an ordinary SQLite file (one table per model, Float32 BLOBs) read with sql.js; the same file could be served to sqlite-vec or any vector store later. Query preprocessing is identical to the lab pipeline (shared lib/ render), so scores here are comparable with every number on this site. Per our threshold experiments, expect SigLIP to be the most reliable column under rotation and crop, CLIP to trail it, and DINOv2 to fall apart under rotation while matching well otherwise.
The corpus deliberately keeps picsum's photographer-series shots (several different photos of the same MacBook-on-table scene, ids 0–12). They double as a ranking smoke test: a healthy model should place the sibling shots directly behind the query's source with a visible score gap, and they demonstrate on real data that scoring above the same-image threshold is evidence, not proof.
Two of the columns deserve footnotes. Gemma 4 runs the q4 vision-encoder submodel of Gemma 4 E2B via ONNX Runtime Web — its graph takes pre-patchified input (flattened 16×16 patches plus per-patch positions), so the patchify preprocessing is ported from transformers' Gemma4Processor into the shared lib/gemma4.mjs, used identically by the index builder and this page. It is the slowest column: a query is a couple of thousand patch tokens through a 16-layer encoder in WASM. Gemini Embedding 2 is a hosted API: the index was built server-side, and querying asks for your API key (kept in your browser's localStorage, sent only to Google's endpoint).
Corpus images via picsum.photos (Unsplash-sourced, fetched by unique catalog id). Index built by scripts/build-search-index.mjs. Models run locally via Transformers.js and are cached by the service worker; the SQLite index is ~8 MB.