MODA Duo

Two open constituents, one answer per query. Duo routes each text query to whichever open MODA system suits its shape — short catalogue titles to MODA Pro Lite+, longer descriptions to MODA — and runs one encoder and one nearest-neighbour query per search.

Duo adds zero parameters. It is a serving recipe over two open systems, not a new model.

Why

Fashion search queries come in two shapes, and no single small model is best at both:

query shape example best open system ≤250M
catalogue title buckle round toe flat shoes MODA Pro Lite+
natural description When warm weekends are abound, make sure your closet… MODA

Duo picks per query. On a mixed workload it beats both constituents.

Results

MAP@10, full corpus, all ground-truth queries, one evaluator (pytrec_eval map_cut.10), paired bootstrap 10,000 resamples.

benchmark MODA MODA Pro Lite+ MODA Duo
KAGL 0.2887 0.3201 0.3201
Polyvore 0.3726 0.4049 0.4049
Atlas 0.1862 0.1904 0.1904
Fashion200K 0.1946 0.1846 0.1866
DeepFashion In-Shop 0.1642 0.1026 0.1640
DeepFashion Multimodal 0.0147 0.0133 0.0159
pooled, 12,000 queries 0.2035 0.2026 0.2137

Pooled across all six benchmarks — the mixed workload a router exists for — Duo is +5.0% over MODA and +5.4% over MODA Pro Lite+, both significant.

Fashion200K is the honest miss: its queries sit where the two constituents are hardest to tell apart, and Duo trails MODA there by 4%. Where a workload is known to be all long descriptions, use MODA directly.

Serving cost

indexes                  2     one per constituent, built offline
stored vectors per item  2
encoders run per query   1     only the routed constituent's text tower
ANN queries per search   1
re-ranking               none

Compared with a single open model, Duo costs one extra index at build time and nothing extra at query time.

Use

pip install open_clip_torch pillow numpy hnswlib
python serving_ann.py --demo
from serving_ann import Duo
duo = Duo()                 # loads both constituents
duo.build(images)           # encodes the catalogue with both, builds two indexes
ids, scores, routes = duo.search(["black leather ankle boots"], k=10)

The router is a callable — replace it with any policy that maps a query to a constituent:

duo = Duo(router=lambda q: "moda" if looks_like_a_description(q) else "moda_pro_lite_plus")

Evaluation

All figures are full corpus, all ground-truth queries, MAP@10 under one evaluator (pytrec_eval map_cut.10), paired bootstrap with 10,000 resamples. Per-query results are in the repository.

Related

  • MODA — FashionSigLIP with a serving harness. Open source, open weights.
  • MODA Pro Lite — a trained fashion encoder. Open weights.
  • MODA Pro — hosted. Fuses both constituents rather than choosing between them.
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including HopitAI/moda-duo