В embedding-based системах encoder - это часть контракта данных. Его нельзя обновлять как обычную ML-модель: в ANN-индексе уже лежат вектора из старого пространства, и частая ошибка - считать совместимость гарантированной из-за той же размерности и метрики.
Почему совместимость ломается
Даже если размерность та же, cosine тот же, а offline-бенчмарк лучше, новый encoder не обязан быть совместим со старым индексом.
После обновления меняются:
- геометрия пространства;
- распределение норм;
- локальные окрестности;
- ranking ближайших соседей;
- калибровка score’ов;
- поведение ANN-структуры: HNSW/IVF/PQ строились под старое распределение.
Главный анти-паттерн: писать новые документы новым encoder’ом в старый индекс со старыми embedding’ами.
Такой индекс становится смешанным: часть векторов живёт в одном пространстве, часть - в другом. ANN формально работает, но nearest neighbors уже не имеют корректной семантики.
Версионируем embedding space как production contract
Версионировать нужно не просто
model_name, а полный контракт:embedding_version = encoder + tokenizer + pooling + normalization + dim + metricЕсли поменялось что-то из этого - это новая версия пространства.
Практический совет: храните
embedding_version рядом с документом, запросом, индексом и retrieval-логами. Иначе при деградации recall или CTR вы не поймёте, какой encoder реально участвовал в выдаче.Поднимаем новый индекс и включаем dual-write
Старый путь:
docs_v1 -> embeddings_v1 -> ann_index_v1
Новый путь:
docs_v2 -> embeddings_v2 -> ann_index_v2
Даже если документы те же, embedding’и должны быть пересчитаны новым encoder’ом. Для ANN это новый corpus.
Важно: параметры индекса тоже стоит перетюнить. Например, для HNSW старые
M, efConstruction, efSearch могут быть не оптимальны для нового распределения.На время миграции новые и обновлённые документы пишем в обе версии:
on_document_upsert(doc):
emb_v1 = encoder_v1(doc)
emb_v2 = encoder_v2(doc)
index_v1.upsert(doc.id, emb_v1)
index_v2.upsert(doc.id, emb_v2)
Это дороже по compute и ingestion latency, зато старый retrieval продолжает работать, а новый индекс догоняет актуальное состояние. Если v1 скоро выключается, dual-write можно держать только до cutover плюс короткое rollback window.
Backfill, shadow-read и критерии готовности
Для v2 нужно пересчитать embedding’и всего корпуса и залить их в новый индекс. Здесь важны не ноутбучные метрики, а инженерная надёжность:
- идемпотентность задач;
- контроль lag’а;
- дедупликация upsert’ов;
- checkpoint’ы;
- отдельные лимиты на encoder и ANN ingestion;
- сверка количества документов между индексами;
- контроль доли документов без v2 embedding.
Миграция не готова, пока новый индекс не покрывает production corpus с приемлемым lag.
Перед переключением включаем shadow-read:
query -> encoder_v1 -> index_v1 -> results_v1
-> encoder_v2 -> index_v2 -> results_v2
Пользователю показываем только v1, но сравниваем:
- recall@k на размеченных данных;
- overlap@k между v1 и v2;
- NDCG/MRR, если есть клики или асессоры;
- latency p95/p99;
- tail failures;
- распределение score’ов;
- downstream-метрики в ранжировании, рекомендациях или RAG.
Предупреждение: высокий overlap@k не гарантирует улучшения продукта. Новый retrieval может менять diversity, freshness, coverage и нагрузку на следующий ranker. Cutover лучше делать через feature flag, с мониторингом качества, latency, error rate и быстрым rollback на
ann_index_v1.Вывод:
Обновление encoder’а - это миграция embedding contract и ANN-инфраструктуры, а не простая замена модели в inference path.
