Turbovec: Rust-версия Google TurboQuant обгоняет FAISS в поиске векторов

Turbovec: Rust-версия Google TurboQuant обгоняет FAISS в поиске векторов

Разработчик, выступающий на Hacker News под ником fittingopposite, опубликовал turbovec, открытую библиотеку векторного поиска на Rust с обвязкой для Python. В её основе лежит TurboQuant, алгоритм квантования векторов, разработанный Google Research: он не привязан к конкретным данным (data-oblivious), даёт почти оптимальные искажения и не требует отдельного этапа обучения индекса. Заявленный результат из README: корпус в 10 млн документов, который в формате float32 занимает 31 ГБ оперативной памяти, в turbovec умещается в 4 ГБ, и при этом ищется быстрее, чем в FAISS.

По скорости поиска turbovec в среднем обгоняет FAISS IndexPQFastScan на всех восьми измеренных конфигурациях каждой битности, на обеих архитектурах: в среднем в 3,4 раза при 4-битном квантовании и на 23%, при 2-битном. По архитектурам отдельно: на ARM, в 3,5 раза (разброс 3,4, 3,7х) при 4 битах и на 26% (22, 29%) при 2 битах, за счёт ядер SDOT/SMMLA, которые считают скалярное произведение прямо по векторно-мажорному расположению данных; на x86, в 3,4 раза (3,2, 3,5х) при 4 битах и на 20% (5, 32%) при 2 битах, где короткий цикл накопления на 2 битах несёт LUT-скан на инструкции vpermb.

Вставка и удаление векторов у turbovec тоже быстрее: одиночный вызов add() занимает 6,3, 19,7 мкс в зависимости от конфигурации, это в 7,6, 13,9 раза быстрее одиночной вставки в FAISS, а пакет из 100 векторов амортизируется до 4,6, 16,3 мкс на вектор (в 4,6, 15,1 раза быстрее аналогичного пакета в FAISS). Удаление по id через IdMapIndex.remove(), операция O(1), занимает 0,44, 1,22 мкс на устойчивом потоке из 1000 удалений и 0,59, 1,37 мкс на первых 100 удалениях свежего индекса; у FAISS то же по смыслу удаление (remove_ids на IndexIDMap поверх IndexPQFastScan, которое каждый раз перепаковывает хранимые коды) на 100 тыс. векторов занимает 0,19, 1,02 секунды на одно удаление, и стоимость растёт вдвое с ростом размера кода.

По точности поиска (recall) отдельно сравнивалась калиброванная версия алгоритма, TQ+, с FAISS IndexPQ (LUT256, nbits=8), которую авторы называют более сильным базовым вариантом, чем PQ-реализация из оригинальной статьи о TurboQuant. На эмбеддингах OpenAI (d=1536 и d=3072) TQ+ обгоняет FAISS по R@1 на трёх из четырёх конфигураций (на 0,9, 2,9 пункта), но уступает на 0,7 пункта на d=1536 при 4 битах; при этом обе системы достигают recall 1.0 к k=8 (и уже ≥0,997 при k≤4). На эмбеддингах GloVe (d=200), более сложном для алгоритма низкоразмерном режиме, TQ+ впереди FAISS по R@1 на обеих битностях (+1,9 при 4 битах, +0,8 при 2 битах), но при 2 битах FAISS отыгрывает небольшое преимущество начиная примерно с k=8; калибровка доводит R@1 при 2 битах до 0,572 против 0,564 у FAISS. В целом калибровка TQ+ даёт прирост recall до 2,2 процентного пункта на R@1 там, где отклонение от модели наибольшее (например, на GloVe при 2 битах), включается она одним вызовом calibrate() на выборке примерно из 1024 векторов, после чего повторного обучения не требуется.

Технически TurboQuant устроен так: каждый вектор нормализуется, его длина (норма) отделяется и хранится отдельным числом, а сам вектор превращается в единичное направление на гиперсфере. Затем все векторы умножаются на одну и ту же случайную ортогональную матрицу, после такого поворота каждая координата независимо подчиняется бета-распределению, которое в высокой размерности сходится к гауссовскому N(0, 1/d), причём это верно для любых исходных данных. На калиброванном шаге (TQ+) для каждой координаты подбираются сдвиг и масштаб, переводящие её эмпирические квантили в крайние центроиды кодовой книги; целевой уровень вероятности задаётся самой кодовой книгой и зависит от битности (~0,933 при 2 битах, ~0,996 при 4 битах). Дальше применяется скалярное квантование Лойда-Макса, которое заранее считает оптимальное разбиение каждой координаты на корзины: 4 при 2 битах и 16 при 4 битах, на этом месте текст источника обрывается, дальнейшие детали недоступны.

Дополнительно turbovec поддерживает фильтрацию прямо во время поиска: в search() можно передать allowlist id или битовую маску слотов, и ядро учитывает её на уровне блоков по 32 вектора, блоки без разрешённых слотов пропускаются ещё до обращения к таблице поиска, а отдельные неразрешённые слоты внутри посчитанных блоков отбрасываются при вставке в кучу результатов; итоговая длина ответа всегда равна min(k, n_allowed), без лишней выборки и без потери recall на избирательных фильтрах. Инкрементальное сохранение через sync(path) фиксирует только изменения с прошлого вызова, один fsync за вызов, устойчивость к сбою на любом байте, а удаление или небольшое добавление стоит миллисекунды независимо от размера индекса (полные снимки остаются за write()/load()). Библиотека работает полностью локально, без управляемого облачного сервиса и без выхода данных за пределы машины или VPC, что в связке с любой open-source моделью эмбеддингов даёт полностью изолированный (air-gapped) стек для RAG. Для миграции с существующих пайплайнов есть готовые drop-in замены: turbovec[langchain] заменяет InMemoryVectorStore в LangChain, turbovec[llama-index], SimpleVectorStore в LlamaIndex, turbovec[haystack], InMemoryDocumentStore в Haystack, turbovec[agno], LanceDb в Agno; во всех случаях сохраняются тот же публичный интерфейс, та же семантика хранения и то же подключение к retriever и пайплайну, нужно только поменять импорт.

Ключевые факты

  • Библиотека turbovec на алгоритме Google Research TurboQuant хранит корпус из 10 млн документов в 4 ГБ памяти вместо 31 ГБ при float32.
  • По скорости поиска в среднем обгоняет FAISS IndexPQFastScan: на 4 битах, в 3,4 раза, на 2 битах, на 23% (среднее по восьми конфигурациям на ARM и x86).
  • Вставка и удаление векторов быстрее FAISS, удаление, на порядки: одиночная вставка занимает 6,3, 19,7 мкс (в 7,6, 13,9 раза быстрее), удаление по id, 0,44, 1,22 мкс против 0,19, 1,02 секунды у FAISS.
  • По точности поиска (recall) калиброванная версия TQ+ обгоняет FAISS IndexPQ на большинстве конфигураций эмбеддингов OpenAI и на обоих битовых режимах GloVe.
  • Готовые drop-in замены хранилищ векторов для LangChain, LlamaIndex, Haystack и Agno, для миграции достаточно поменять импорт.

Почему это важно

Векторные индексы, узкое место любого RAG-конвейера: при float32 корпус из 10 млн документов занимает 31 ГБ оперативной памяти, и рост базы упирается в объём RAM сервера. turbovec решает это не отдельным этапом сжатия, а квантованием на лету: алгоритм TurboQuant, разработанный Google Research, не требует отдельного обучения индекса, параметров и пересборок по мере роста корпуса, вектор добавляется и сразу индексируется. Тот же корпус из 10 млн документов умещается в 4 ГБ, а поиск по нему в среднем быстрее, чем у промышленного FAISS IndexPQFastScan.

Кому это важно

В первую очередь, разработчикам, которые строят RAG-системы с ограничениями по памяти, задержке или приватности: turbovec не требует внешнего managed-сервиса и не отправляет данные за пределы машины или VPC, что подходит для air-gapped-развёртываний с любой open-source моделью эмбеддингов. Отдельно это касается тех, кто уже использует LangChain, LlamaIndex, Haystack или Agno, для них turbovec заявлен как drop-in замена штатных in-memory хранилищ векторов с тем же публичным интерфейсом, форматом хранения и подключением к retriever и пайплайну.

Как это применить

Библиотека ставится как pip install turbovec (Python-обвязка) или cargo add turbovec (Rust). Базовый сценарий: создать TurboQuantIndex(dim, bit_width), добавлять векторы через add(), искать через search(query, k), сохранять индекс через write()/load() или инкрементально через sync(), последний фиксирует только изменения с прошлого вызова, с одним fsync и устойчивостью к сбою на любом байте, а удаление или небольшое добавление занимает миллисекунды независимо от размера индекса. Для стабильных внешних id, переживающих удаления, есть IdMapIndex с add_with_ids() и O(1)-удалением по id. Поиск можно ограничить allowlist'ом id или битовой маской слотов, фильтр применяется прямо внутри SIMD-ядра, поэтому результат, ровно min(k, n_allowed) без лишней выборки и без потери recall на избирательных фильтрах. Для интеграции с фреймворками достаточно поставить turbovec[langchain], turbovec[llama-index], turbovec[haystack] или turbovec[agno] и заменить импорт, публичный интерфейс и семантика хранения совпадают со штатным хранилищем. Для точности на нетипичных эмбеддингах (в первую очередь на низкой размерности) можно один раз вызвать index.calibrate(sample) на репрезентативной выборке из ~1024 строк, это включает калиброванный режим TQ+ без отдельного переобучения при последующих добавлениях. Векторы и запросы должны быть двумерными float32-массивами, другие типы данных библиотека отклоняет, а не приводит молча, поэтому их нужно явно привести через np.asarray(x, dtype=np.float32).

Можно ли доверять

Все цифры в статье, собственные бенчмарки проекта: сравнение идёт с FAISS IndexPQFastScan (поиск, вставка, удаление) и с FAISS IndexPQ (LUT256, nbits=8) для recall, авторы прямо называют последний более сильным базовым вариантом, чем PQ-реализация из оригинальной статьи про TurboQuant, поскольку в FAISS выше точность LUT при подсчёте очков и используется k-means++ для обучения кодовой книги. Замеры воспроизводят числа из статьи про TurboQuant на эмбеддингах OpenAI (d=1536 и d=3072) и совпадают с другими независимыми реализациями TurboQuant на низкой размерности. Независимой проверки третьей стороной в источнике нет, это самоотчёт разработчика библиотеки, чьё имя в тексте не указано (назван только Google Research как автор лежащего в основе алгоритма).

Риски и подводные камни

В открытом тексте не хватает части сведений: не указаны ни дата релиза или номер версии turbovec, ни модель процессора, на которой считались ARM- и x86-бенчмарки, ни лицензия, под которой распространяется код. Выигрыш в памяти (31 ГБ → 4 ГБ) приведён только для одного конкретного примера на 10 млн документов и не обобщается на произвольный размер корпуса. По точности поиска картина неоднородна: на низкоразмерных эмбеддингах GloVe (d=200), самом сложном для алгоритма режиме, при 2 битах FAISS отыгрывает небольшое преимущество начиная примерно с k=8, и только калибровка TQ+ выравнивает разрыв (0,572 против 0,564 у FAISS). На отдельных сочетаниях параметров при 4 битах turbovec тоже уступает FAISS в recall, например, на эмбеддингах OpenAI d=1536, на 0,7 пункта. Текст источника обрывается на середине объяснения последнего шага квантования (Лойда-Макса), так что часть деталей алгоритма недоступна.