Qdrant 1.20 — Улучшения полнотекстового поиска

Источник: Qdrant•

Qdrant 1.20 — Улучшения полнотекстового поиска

Вышел Qdrant 1.20.0! Давайте рассмотрим основные возможности этой версии: BM25 для полнотекстовых индексов и поиск по коду: ранжирование по BM25 напрямую по тексту полезной нагрузки без генерации эмбеддингов. А также улучшенный поиск по коду с новым токенизатором кода. Более быстрые вложенные…

Qdrant 1.20.0 is out! Давайте рассмотрим основные возможности этой версии:

BM25 для полнотекстовых индексов и поиск по коду: ранжирование по BM25 напрямую по тексту полезной нагрузки без генерации эмбеддингов. А также улучшенный поиск по коду с новым токенизатором кода.

Более быстрые вложенные фильтры: новый индекс полезной нагрузки для каждого элемента обрабатывает вложенные фильтры без чтения полезной нагрузки, что делает их быстрее.

16-битный и 8-битный TurboQuant: 16-битный тип данных TurboQuant, который сокращает объем хранения векторов вдвое без потери точности, а также 8-битный тип данных TurboQuant и опция квантования с полнотой поиска, близкой к float16.

Оценка BM25 по полнотекстовым индексам

Помимо векторного поиска, Qdrant также поддерживает полнотекстовый поиск, который находит результаты по точным содержащимся в них словам. До сих пор ранжирование этих результатов с помощью BM25, широко используемой функции ранжирования для поиска по ключевым словам, означало преобразование текста в разреженные векторы.

У такого подхода есть несколько недостатков. Каждый документ и каждый запрос должны проходить этап инференса для создания разреженного вектора. Вам также приходится самостоятельно отслеживать среднюю длину документов для получения точной оценки. Поскольку это значение зафиксировано в каждом сохраненном векторе, оно не может адаптироваться по мере изменения данных, а его обновление означает повторное преобразование всех документов.

Версия 1.20 решает эти проблемы с помощью оценки BM25 непосредственно по полнотекстовому индексу. Qdrant извлекает всю статистику BM25 из ваших данных. Сюда входит и средняя длина документа, поэтому оценка точна «из коробки». Вам не нужно генерировать эмбеддинги. Текст, который уже находится в вашей полезной нагрузке, становится доступным для поиска сразу после индексации.

Как это работает

Для ранжирования точек с помощью BM25 по полнотекстовому индексу используйте новый текстовый запрос. Например, чтобы выполнить поиск по набору данных книг по названию, сначала создайте полнотекстовый индекс для поля названия с типом оценки bm25:

Затем выполните запрос к индексу, передав проиндексированное поле с помощью:

Вы можете задавать параметры BM25 k и b для каждого запроса, поэтому их настройка не требует перестройки индекса. Текстовый запрос работает везде в Query API. Объедините его с поиском по плотным векторам в гибридном поиске или используйте для повторного ранжирования точек, полученных другим запросом.

Миграция с разреженного BM25

BM25 на разреженных векторах по-прежнему поддерживается. Если ваш текст уже сохранен в полезной нагрузке, вы можете переключиться на полнотекстовый индекс без повторной загрузки данных. Создайте полнотекстовый индекс с включенной оценкой, настройте ту же обработку текста, что и для вашей модели разреженного BM25, и замените запросы по разреженным векторам текстовыми запросами.

Улучшенный поиск по коду с токенизатором кода

Распространенным вариантом использования полнотекстового поиска является поиск по исходному коду. Полнотекстовый индекс использует токенизатор для разбиения текста на слова (токены), по которым вы можете вести поиск. Стандартный токенизатор слов разделяет текст по пробелам и знакам пунктуации, что плохо работает для кода, поскольку идентификаторы объединяют несколько слов в различных соглашениях об именовании. Например, он индексирует getUserName как единый токен, поэтому поиск по user name или get_user_name не находит его.

Этот релиз добавляет новый токенизатор кода, который разбивает идентификаторы по знакам подчеркивания и изменениям регистра, включая акронимы. В результате getUserName, get_user_name и user name соответствуют друг другу. Он также индексирует идентификатор целиком, поэтому hashmap по-прежнему находит HashMap. В сочетании с оценкой BM25 по полнотекстовому индексу это позволяет ранжировать результаты поиска по коду без генерации эмбеддингов.

Более быстрые вложенные фильтры с индексами для каждого элемента

Полезная нагрузка часто содержит массивы объектов, такие как товар с несколькими предложениями, каждое со своим магазином и ценой. Вложенный фильтр находит точки, где один элемент удовлетворяет всем условиям, например предложение из конкретного магазина ниже определенной цены.

До сих пор индексы полезной нагрузки не могли самостоятельно обрабатывать такие фильтры. Индексы для offers[].shop и offers[].price могли только сузить круг кандидатов. Затем Qdrant считывал полезную нагрузку каждого оставшегося кандидата, обычно с диска, чтобы проверить условия для каждого элемента. Когда оставалось много кандидатов, вложенные фильтры замедлялись.

Начиная с версии 1.20, каждый тип индекса полезной нагрузки получает новый параметр nested, который создает индекс для каждого элемента, сохраняющий каждый элемент массива отдельно. Когда каждое условие во вложенном фильтре использует поле с индексом для каждого элемента в одном и том же массиве, Qdrant может обработать фильтр только по индексам, не читая полезную нагрузку. API поиска не меняется, поэтому существующие вложенные фильтры начинают работать быстрее сразу после создания индексов.

Мы провели бенчмаркинг индексов для каждого элемента по сравнению с обычными индексами с чтением полезной нагрузки, используя три вложенных фильтра на 100 000 точках. Даже когда вся полезная нагрузка уже находилась в памяти (лучший сценарий для чтения полезной нагрузки), индексы для каждого элемента работали в 7–38 раз быстрее. Поскольку они полностью пропускают чтение полезной нагрузки, разница, вероятно, еще больше, когда полезную нагрузку приходится загружать с диска.

16-битный и 8-битный TurboQuant

Тип данных вектора определяет, как Qdrant хранит векторы. По умолчанию float32 использует 32 бита на измерение. Чтобы сэкономить память и дисковое пространство, а также ускорить поиск, вы можете использовать тип данных с меньшим количеством бит на измерение ценой некоторой потери точности.

До сих пор Qdrant предлагал три типа данных с более низкой точностью. Тип данных turbo4, представленный в версии 1.19, является наиболее компактным вариантом (4 бита), но на некоторых наборах данных он оказывает значительное негативное влияние на полноту поиска. float16 сохраняет большую точность на уровне 16 бит, но занимает в четыре раза больше места. uint8 занимает промежуточное положение на уровне 8 бит, но если ваша модель эмбеддингов еще не выводит 8-битные целые числа, вам приходится самостоятельно квантовать векторы перед их загрузкой.

Этот релиз добавляет два типа данных TurboQuant. turbo16 использует те же 16 бит, что и float16, но с меньшей погрешностью, и сокращает объем хранения векторов float32 вдвое без потери точности. turbo8 использует 8 бит на измерение, как и uint8, но берет обычные векторы с плавающей запятой и квантует их за вас. Он снова сокращает объем хранения вдвое, обеспечивая полноту поиска, близкую к float16. Версия 1.20 также приносит 8-битное кодирование в квантование TurboQuant.

Тип данных turbo16

turbo16 использует 2 байта на измерение, столько же, сколько float16. float16 сохраняет одинаковую относительную точность для каждой координаты, поэтому большая координата получает большую ошибку округления. Это снижает точность на эмбеддингах с несколькими очень большими координатами, таких как text-embedding-ada-002 от OpenAI, CLIP и Cohere. turbo16 решает эту проблему за счет предварительного применения вращения TurboQuant, которое равномерно распределяет значения по координатам, а затем сохранения каждого вектора в виде единой 16-битной целочисленной сетки.

Мы сравнили turbo16 с float16 и float32 двумя способами. В исследовании ошибки реконструкции на 13 наборах данных ошибка turbo16 была в 7,5 раз ниже, чем у float16. В бенчмарках поиска на 12 наборах данных показатель recall@10 для turbo16 при точном поиске совпадал с float32 с точностью до 0,001 на каждом наборе данных, в то время как у float16 он падал до 0,995 на наборах данных ada-002 и CLIP.

В тех же поисковых бенчмарках turbo16 также работал быстрее, чем float16 для HNSW-поиска: медианная задержка составляла 0,81 от задержки float16 на x86 и 0,83 на ARM.

Тип данных turbo8

turbo8 сохраняет каждое измерение в 1 байте, что вдвое меньше размера float16. Мы протестировали его отдельно на 11 датасетах. На следующей диаграмме показан recall@10 для turbo8 по сравнению с turbo4 и float16 на шести из них. Бенчмарки запускались на x86 с HNSW, настроенным с m=16 и ef_construct=100, с поиском через hnsw_ef=128.

Recall@10: turbo4, turbo8 и float16 для шести датасетов по измерениям, HNSW на x86, чем выше, тем лучше recall@10 H&M (2048d) DBpedia OpenAI (1536d) LAION (512d) arXiv (384d) SIFT (128d) Deep-image (96d) 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 turbo4 turbo8 float16

Показатель recall у turbo8 приближается к float16 при вдвое меньшем размере, а прирост по сравнению с turbo4 наиболее заметен на малых размерностях, таких как SIFT и Deep-image. turbo8 также работает быстрее, чем float16. На всех 11 протестированных датасетах поиск выполнялся в 1.1–2.4 раза быстрее, чем на float16, а на большинстве датасетов — в пределах 10% от turbo4.

На тех же датасетах мы также сравнили turbo8 с векторами float32 и скалярным квантованием при включенном рескоринге. turbo8 показал более высокий recall на 10 из 11 датасетов и тот же результат на оставшемся, при сопоставимой задержке. Поскольку скалярное квантование сохраняет оригиналы float32, turbo8 хранит лишь около пятой части от этого объема байт.

Квантование bits8

8-битное кодирование также доступно в качестве квантования TurboQuant для обычных векторов с плавающей запятой. Чтобы использовать его, установите значение bits в bits8 в конфигурации квантования. bits8 сжимает векторы примерно в четыре раза, по сравнению с восемью разами для bits4. bits4 остается значением по умолчанию.

Если вы в настоящее время используете скалярное квантование, рассмотрите возможность тестирования bits8 в качестве альтернативы. Оно сжимает векторы во те же четыре раза, и в бенчмарках turbo8 то же кодирование показало более высокий recall, чем скалярное квантование.

Уровни памяти для дополнительных компонентов

Версия 1.19 представила уровни памяти: единый параметр памяти, который управляет тем, закреплен ли каждый компонент коллекции в оперативной памяти, кэширован или находится в «холодном» состоянии на диске. Версия 1.20 расширяет уровни памяти на трекер идентификаторов (ID tracker) и удаляет дублирующую копию векторов, которую раньше хранило встроенное хранилище (inline storage).

Размещение трекера идентификаторов

Трекер идентификаторов сопоставляет ID каждой точки с ее внутренним расположением и версией. До сих пор Qdrant всегда хранил его в оперативной памяти, что составляло около 52 байт на точку. Для коллекции со 100 миллионами точек это около 5 ГБ оперативной памяти.

Теперь вы можете выбрать уровень памяти для трекера идентификаторов каждой коллекции. pinned сохраняет трекер идентификаторов в оперативной памяти и остается значением по умолчанию. cold хранит его на диске и считывает по запросу. cached также хранит его на диске, но загружает в страничный кэш при открытии сегмента в Qdrant.

Встроенное хранилище без дублирования векторов

Встроенное хранилище ускоряет поиск по индексу HNSW, хранящемуся на диске, размещая исходные векторы в файле индекса рядом со ссылками графа. До сих пор Qdrant хранил эти векторы дважды: один раз в обычном хранилище векторов и один раз в файле индекса. Теперь Qdrant сохраняет исходные векторы только в файле индекса HNSW, что снижает потребление дискового пространства.

Поскольку векторы теперь являются частью индекса, они следуют уровню памяти индекса, а не собственному. Например, если векторы находятся в «холодном» состоянии, а индекс закреплен в памяти, то векторы теперь закрепляются в оперативной памяти вместе с индексом, что увеличивает потребление ОЗУ. Обратное также верно: «холодный» индекс делает «холодными» свои векторы. Перед обновлением убедитесь, что уровень памяти индекса подходит для этих векторов.

Повышение безопасности кластера

В распределенной системе узлы Qdrant обмениваются данными через внутренний API gRPC. В этом релизе внутренний API становится менее доступным для внешних подключений и более защищенным от неправильного использования: теперь по умолчанию требуется аутентификация, и вы можете привязать его к отдельному сетевому интерфейсу.

Внутренняя аутентификация по умолчанию

Когда настроен ключ API, Qdrant теперь требует его для внутренних запросов по умолчанию и отклоняет запросы к внутреннему порту, не содержащие действующего ключа. Внутренний API также принимает только ключи чтения-записи, отклоняя ключ только для чтения и все JWT.

Кластеры без ключа API не затрагиваются. Если параметр api_key задан, скользящие обновления (rolling upgrades) с версии 1.19 безопасны, поскольку узлы отправляли его во внутренних запросах еще начиная с версии 1.18. Некоторые конфигурации, например кластеры только с ключом для чтения, сначала требуют внесения изменений. Перед обновлением ознакомьтесь с примечаниями к обновлению.

Привязка внутреннего порта к частному интерфейсу

До сих пор внутренний порт прослушивал тот же адрес, что и публичный API. Чтобы оградить его от публичной сети, вам требовался брандмауэр, группа безопасности (security group) или аналогичный инструмент.

Новая настройка cluster.p2p.host привязывает внутренний порт к собственному адресу, поэтому он может оставаться в частной сети, в то время как публичный API сохраняет свой текущий адрес. Значение должно быть IP-адресом, а URI узла в качестве пира должен использовать тот же адрес. Если вы не укажете этот параметр, внутренний порт продолжит прослушивать тот же адрес, что и публичный API.

Также в этом релизе

  • Снимки (snapshots) на GCS и Azure Blob Storage: в дополнение к локальной файловой системе и хранилищам, совместимым с S3, теперь вы можете сохранять снимки в Google Cloud Storage и Azure Blob Storage.
  • Общие бакеты для снимков: новая настройка префикса для S3, GCS и Azure позволяет нескольким развертываниям использовать один бакет совместно, не перезаписывая чужие снимки.
  • Журналы аудита в stdout или stderr: журналы аудита теперь могут направляться в stdout или stderr вместо файлов или в дополнение к ним. Это удобно для развертываний, которые собирают логи из вывода контейнеров.
  • Время создания коллекции: для коллекций, созданных в версии 1.20 или новее, в информации о коллекции теперь отображается время ее создания.
  • Более безопасное удаление реплики: при удалении реплики теперь можно потребовать минимальное количество других активных реплик того же шарда.
  • Метки времени в логах по местному времени: метки времени в логах теперь следуют переменной окружения TZ. UTC остается значением по умолчанию.
  • Совпадение по подстроке: новое условие сопдадения по подстроке выбирает точки, строковое значение которых содержит заданную строку, например часть URL, путь к файлу или код продукта.

Полный список всех изменений в версии 1.20 см. в руководстве.

Обновление до версии 1.20

В Qdrant Cloud перейдите на экран сведений о кластере (Cluster Details) и выберите версию 1.20 из выпадающего меню. Процесс обновления может занять некоторое время.

Мы рекомендуем обновлять версии последовательно. Qdrant Cloud делает это автоматически при выборе целевой версии. Если вы используете собственный хостинг (self-hosting), сначала обновитесь до последней патч-версии каждого промежуточного минорного релиза, например: 1.18.x→1.19.x→1.20.0.

Кластеры версии 1.20 принудительно используют аутентификацию для внутреннего взаимодействия по умолчанию, если заданы ключи API. Это влияет на обновление следующим образом: если ключ API не задан, ничего не меняется, и скользящие обновления с 1.19 безопасны. Если задан api_key, скользящие обновления с 1.19 безопасны. В кластерах, где задан только read_only_api_key, узлы больше не смогут общаться друг с другом. Чтобы исправить это, установите api_key или задайте enforce_internal_auth: false. Если в вашем кластере задан только alt_api_key и используется версия 1.19.0 или 1.19.1, сначала обновитесь до 1.19.2.

Кластеры версии 1.20 принудительно используют аутентификацию для внутреннего взаимодействия по умолчанию, если заданы ключи API. Это влияет на обновление следующим образом:

  • Если ключ API не задан, ничего не меняется, и скользящие обновления с 1.19 безопасны.
  • Если установлен api_key, плавающие обновления с версии 1.19 безопасны.
  • В кластерах, где установлен только read_only_api_key, узлы больше не смогут обмениваться данными друг с другом. Чтобы исправить это, установите api_key или задайте enforce_internal_auth: false.
  • Если в вашем кластере установлен только alt_api_key и используется версия 1.19.0 или 1.19.1, сначала обновитесь до версии 1.19.2.
Коллекции, использующие встроенное хранилище (inline storage), после обновления могут потребовать больше оперативной памяти. Начиная с версии 1.20, исходные векторы в таких коллекциях подчиняются настройке hnsw_config.memory вместо vectors.memory, так же как и квантованные векторы без собственной настройки памяти. Для индекса HNSW по умолчанию используется кэширование, поэтому при vectors.memory: cold и отсутствии других настроек исходные векторы кэшируются, а квантованные закрепляются в оперативной памяти. Перед обновлением задайте hnsw_config.memory, а также память для квантования на те уровни, которые вы хотите использовать для этих векторов.

Коллекции, использующие встроенное хранилище (inline storage), после обновления могут потребовать больше оперативной памяти. Начиная с версии 1.20, исходные векторы в таких коллекциях подчиняются настройке hnsw_config.memory вместо vectors.memory, так же как и квантованные векторы без собственной настройки памяти. Для индекса HNSW по умолчанию используется кэширование, поэтому при vectors.memory: cold и отсутствии других настроек исходные векторы кэшируются, а квантованные закрепляются в оперативной памяти. Перед обновлением задайте hnsw_config.memory, а также память для квантования на те уровни, которые вы хотите использовать для этих векторов.

Нужна помощь с обновлением? Навык агента Qdrant Advisor поможет вам разобраться с обновлениями, устранить неполадки в конфигурации и ответить на вопросы о вашей настройке Qdrant, независимо от того, используете ли вы Qdrant Cloud или развертываете систему самостоятельно.

Управляйте Qdrant Cloud из терминала

qcloud, интерфейс командной строки Qdrant Cloud, позволяет управлять кластерами, ключами API, резервными копиями и Cloud Inference прямо из терминала. Каждую команду можно использовать в скриптах, поэтому qcloud легко вписывается в CI-пайплайны и предоставляет ИИ-агентам возможность управлять вашей облачной инфраструктурой. Вы также можете использовать его для обновления: команда qcloud cluster update <cluster-id> --version v1.20.0 переводит кластер на версию 1.20 с помощью плавающего перезапуска.

Присоединяйтесь к обсуждению

Мы будем рады услышать ваши отзывы об этом релизе. Если у вас есть вопросы или пожелания, присоединяйтесь к нашему Discord или создайте задачу в GitHub.

О чём эта статья

Ещё в разделе «Разработка ПО»

Все →

Ещё от Qdrant