ANN: приближённый поиск соседей (B3)
Two-tower заканчивается словами «замороженные эмбеддинги → ANN» — здесь этот ANN построен прозрачно: собственный IVF-индекс на numpy поверх реальных айтем-эмбеддингов, ручка nprobe, честный замер recall ↔ доля скана ↔ latency и boundary problem поимённо на конкретных фильмах.
⚠ На нашем каталоге (~10k айтемов) точный поиск и так суб-миллисекундный — ANN здесь не нужен, и продовый /similar остаётся точным. Модуль показывает механику и цену сделки, которая становится обязательной на миллионах векторов.
строим IVF-индекс и меряем…
Теория простым языком
На больших каталогах retrieval должен укладываться в latency-бюджет без полного скана — воронка обещает «отобрать сотни кандидатов из миллионов за миллисекунды», и точный перебор ломает это обещание первым; отсюда ANN, партиционирование и candidate pruning. На малых каталогах exact может быть проще и лучше — наш /similar именно такой. ANN-индекс — инженерная цена, которой two-tower-подход оплачивает скорость на масштабе.
▸Зачем ANN, если можно посчитать точно
IVF-Flat: простейший производственный индекс
Идея из двух шагов — и оба видны в лаборатории выше:
# build (офлайн, при обновлении эмбеддингов) centroids = kmeans(item_vectors, n_clusters) # сферический: всё L2-нормировано lists[c] = [i for i in items if nearest(i) == c] # inverted lists # search (онлайн, на запрос) probed = top_nprobe(centroids @ query) # дёшево: n_clusters ≪ N candidates = concat(lists[c] for c in probed) # сканируем только их return top_k(candidates @ query) # точный скор на кандидатах
Дальше по этой же оси развиваются продовые индексы: HNSW (граф «маленького мира» вместо кластеров — жадный спуск по соседям), PQ/OPQ (сжатие векторов в коды, чтобы миллиарды влезали в память; скор становится приближённым ещё и по значению), IVF-PQ / ScaNN (комбинации). Ручки другие — сделка та же: точность ↔ скорость ↔ память.
Инженерные следствия (то, что спрашивают на дизайне)
1. Recall@ANN — guardrail, а не наблюдение. Он входит в бюджет качества воронки: потолок retrieval (E3) умножается на recall индекса. Мониторить его — значит держать эталонный exact-набор запросов и сравнивать (стык с B7).
2. Индекс живёт отдельной жизнью. Эмбеддинги переобучили — индекс надо перестроить; между ретрейнами новые айтемы либо доиндексируются инкрементально, либо их нет в retrieval вовсе — это ещё одно лицо cold-start (B5).
3. Несбалансированные кластеры → хвост latency. §2: плотные регионы дают толстые inverted-lists; p50 красивый, p95 — нет. В проде лечат балансировкой, квотами на скан, ранним выходом.
⚠️ Что может пойти не так
- Мерить только скорость и не мерить recall@ANN — индекс может тихо резать лучших кандидатов, офлайн-метрики воронки съедут без видимой причины.
- Считать recall@ANN константой — он зависит от распределения запросов и дрейфует вместе с ним и с каждым ретрейном эмбеддингов.
- Забыть про обновление индекса — новые айтемы не существуют для retrieval, пока не проиндексированы (cold-start на ровном месте).
- Тянуть ANN на маленький каталог — на десятках тысяч векторов exact-поиск проще, точнее и уже укладывается в бюджет (наш /similar — точный).
- Сравнивать ANN-индексы по одной точке — это кривая recall↔latency↔память; сравнивают кривые при фиксированном recall.
🧠 Проверь себя: После ретрейна two-tower офлайн-NDCG вырос, а онлайн-метрики retrieval просели. ANN-индекс не перестраивали. Что проверить первым?
Где это в дорожной карте
Это B3 — ANN-под-этап retrieval-диагностики: последний недостающий кусок пути «эмбеддинги → кандидаты» (two-tower → ANN → воронка). Дальше по треку — B4 feature store и B6 re-rank constraints, ведущие к полному capstone.