Опубликовано
Как мы мигрировали с ChromaDB на Zvec и проиграли Xeon 2013 года
Автор: Гусев Николай [портфолио]
Дата обновления не указана в исходнике.
Как мы мигрировали с ChromaDB на Zvec и проиграли Xeon 2013 года
Сначала хорошая новость. Потом - правдивая.
Часть 1. Где всё пошло по плану
На сервере kanban (документы, демо, продакшен) миграция прошла гладко. 35 722 чанка ChromaDB переехали в Zvec. Гибридный поиск заработал: FTS + вектор + RRF reranker. Девять из десяти тестовых запросов вернулись со скором 0.85-0.91. Десятый ("поставщик материалов") вернул ноль - но это не проблема Zvec, это intent router так отфутболил запрос. ChromaDB осталась как fallback внутри query_zvec_safe на случай ядерной зимы.
Полчаса работы. Готово.
А потом мы посмотрели на главный сервер.
Часть 2. Xeon E5-2680 v2 и невозможность победить
Главный сервер крутится на Xeon E5-2680 v2. Процессор 2013 года. Ivy Bridge. Хороший процессор. Надёжный. Ему 13 лет, и он до сих пор тянет nginx, VPN, почтовый сервер, бота и агента одновременно.
У него нет AVX2.
Все 8 версий zvec на PyPI собраны с глобальным флагом -march=core-avx2. Это значит, что при первом же обращении к SIMD-инструкциям процессор говорит "что это?" и убивает процесс. Illegal instruction. Exit code 132. Мягко, но безапелляционно.
Мы попробовали 8 версий. Все восемь. 0.1.0, 0.2.0, 0.3.0, 0.4.0, 0.5.0 и так далее. Результат одинаковый: SIGILL на import.
Это был момент, когда нормальный человек сказал бы "ну нет так нет". Но у нас была идея.
Часть 3. Сборка из исходников как акт отчаяния
Ключевое открытие: zvec использует runtime CPU dispatch. Внутри есть файл cpu_features.cc, который во время исполнения проверяет, поддерживает ли процессор AVX2, AVX-512, SSE4.1. Если не поддерживает - переключается на безопасный путь.
Значит, рассуждали мы, можно собрать из исходников с базовым x86-64 кодом. Без -march=core-avx2. Без принудительного включения Haswell-путей. И тогда zvec сам определит, что Xeon 2013 года умеет, а что нет.
Для верности отключили RaBitQ - единственный компонент, который требует AVX2 на этапе компиляции. Пропатчили CMakeLists.txt. Установили cmake 3.31, ninja, pybind11, scikit-build-core. Скачали 16 сабмодулей: Apache Arrow (87 MB), RocksDB (41 MB), protobuf (45 MB), antlr4 (19 MB).
1249 целей компиляции. На двухядерном Xeon 2013 года.
Сборка шла час. Потом SSH отвалился по таймауту (exit 255). Мы запаниковали - но процесс жил: 512 из 1249, потом 883 из 1249. RAM держался на 3.3 ГБ свободных. OOM не было.
Сборка завершилась успешно.
import zvec - Illegal instruction.
Часть 4. Постмортем
Оказалось, что runtime dispatch работает не везде. Базовые функции импортируются. Но стоит дотронуться до HNSW или IVF - процессы падают. Некоторые кодовые пути внутри zvec не runtime-gated. Они содержат compile-time AVX2 инструкции, которые нельзя обойти патчем CMakeLists или флагами сборки.
Мы собрали из исходников. Мы отключили RaBitQ. Мы пропатчили 16 сабмодулей. Мы ждали час на двухядерном процессоре 2013 года.
Процессор всё равно победил.
Часть 5. Цифры (бенчмарк ADR-007)
Прежде чем решить, что делать дальше - посмотрели на цифры. Бенчмарк гонял 15 запросов (фактические, аналитические, синтез) на каждом узле федерации.
Latency (мс, ниже = лучше):
ChromaDB на главном сервере: ~115 мс медиана. Из них ~80 мм - запрос к LM Studio за эмбеддингом, ~35 мс - поиск по векторам.
ChromaDB на рабочем сервере (Autolycus): ~110 мс медиана. Те же ~80 мс на эмбеддинг.
Zvec на рабочем сервере: ~30 мс медиана. Без внешнего вызова за эмбеддингом - векторы хранятся вместе с документами, гибрид FTS+vector+RRF работает локально.
Zvec на kanban: ~25 мс медиана. Та же архитектура, чуть быстрее железо.
Качество поиска (score, выше = лучше):
ChromaDB: 0.77-0.78 средний score.
Zvec: 0.82-0.84 средний score.
Итог по цифрам:
На узлах с Zvec - latency в 3-4 раза ниже, качество поиска на 6-8% выше. Причина: гибридный поиск (FTS + vector + RRF reranker) против чисто векторного поиска у ChromaDB. Плюс отсутствие сетевого прыжка к LM Studio.
Но на главном сервере Zvec не запустился. Поэтому цифры остались как были - ~115 мс, 0.77 score. И это нормально.
Часть 6. Гетерогенная федерация
И тут мы вспомнили, что живём не в мире, где все сервера одинаковые. ADR-002 явно разрешает разные бэкенды на разных узлах федерации. Federation router прозрачен к выбору движка.
Решение оказалось простым и отрезвляющим:
Kanban - Zvec. Гибридный поиск, локальные эмбеддинги, FTS+vector+RRF. ~25 мс, score 0.84.
Рабочий сервер - Zvec. То же самое. ~30 мс, score 0.82.
Главный сервер - ChromaDB. Как было. Работает? Работает. ~115 мс, score 0.77. Трогать не надо.
LM Studio на главном сервере остался как провайдер эмбеддингов для векторного поиска через ChromaDB. Это не SPOF для всей федерации - только один узел зависит от него. Остальные два узла используют sentence-transformers локально.
Что мы вынесли
Runtime dispatch - не панацея. Библиотека может обещать runtime-gated пути, но всегда найдётся код, который скомпилирован с AVX2 намертво. Единственный способ проверить - запустить на целевом процессоре.
pip wheel на современной машине + перенос на старую - рабочий Plan B. Если бы мы начали с этого вместо сборки на самом слабом сервере, сэкономили бы час. Правда, HNSW всё равно бы упал.
Гетерогенная федерация - это не компромисс, это архитектура. Разные узлы с разными возможностями - норма. Не надо насильно унифицировать то, что работает. Два узла на Zvec дают ~25-30 мс и score 0.82-0.84. Один на ChromaDB даёт ~115 мс и score 0.77. И вместе они работают лучше, чем каждый по отдельности.
Процессору 2013 года всё равно. Он не читает ваши ADR. Он не знает про runtime dispatch. Он просто говорит Illegal instruction и продолжает крутить nginx.
Федерация работает. Два узла на Zvec, один на ChromaDB. Каждый делает то, что умеет, на железе, которое у него есть.