Урок курса
FAISS: архитектура и типы индексов
Vector DB & RAG DeveloperПочему начинаем с FAISS
В предыдущем модуле мы разобрались с тем, как устроены эмбеддинги, познакомились с алгоритмами поиска — IVF, PQ, HNSW — и даже реализовали простой IVF своими руками. Теперь переходим к практике: нам нужно научиться работать с реальными инструментами векторного поиска.
В обзоре решений мы видели три варианта: FAISS, Qdrant и Weaviate. Qdrant и Weaviate — полноценные серверные базы данных. Мы до них дойдём. Но сначала полезно увидеть алгоритмы поиска в чистом виде, без инфраструктурного шума. FAISS ставится одной командой через pip, работает прямо внутри вашего Python-процесса и позволяет сосредоточиться на самом поиске. Это идеальная стартовая точка.
Что такое FAISS
FAISS (Facebook AI Similarity Search) — это библиотека от команды Meta AI для эффективного поиска по векторам. Написана на C++ с Python-обёрткой, что даёт высокую производительность при удобном интерфейсе.
Ключевое отличие от Qdrant и Weaviate: FAISS — это библиотека, которая живёт внутри вашего приложения. Поиск происходит в памяти того же процесса, что и ваш код. Это устраняет любые сетевые задержки и даёт максимальную гибкость в выборе алгоритмов.
Именно поэтому FAISS часто называют «движком поиска», а не «базой данных»: он отвечает за алгоритмическую часть, а всё остальное — хранение метаданных, авторизацию, масштабирование — разработчик строит вокруг него сам.
За что платим этой простотой
У такой архитектуры есть закономерные ограничения. FAISS хранит только векторы. Если нужно к каждому вектору прикрепить текст документа, ссылку, дату или любые другие метаданные — это придётся организовать самостоятельно: хранить в отдельной структуре или базе данных и связывать по индексу после поиска.
Фильтрации по метаданным «из коробки» нет. В Qdrant можно написать запрос вида «найди похожие документы с тегом python, созданные после января 2025» — и система обработает это внутри. В FAISS нужно либо строить несколько отдельных индексов для разных групп данных, либо делать поиск с запасом и фильтровать результаты вручную.
Встроенного горизонтального масштабирования тоже нет. Если данные не помещаются на одну машину — нужно самостоятельно реализовать шардирование: разбить базу на части, держать отдельные индексы, объединять результаты.
GPU: возможности и ограничения по платформам
Одно из сильных преимуществ FAISS — поддержка GPU-ускорения через CUDA. На хорошей видеокарте поиск, занимавший 100 мс на CPU, может выполняться за 5–10 мс. Это особенно важно при обработке больших батчей запросов.
Здесь есть важный нюанс по платформам. faiss-gpu нативно работает только на Linux — там он устанавливается стандартным pip install faiss-gpu и сразу готов к работе. На Windows нативной поддержки нет: нужно либо запускать через Docker-контейнер с Linux, либо через WSL (Windows Subsystem for Linux).
На практике это не такая большая проблема: рабочие серверы почти всегда работают на Linux, так что в продакшене всё будет как надо. Для локальной разработки на Windows проще поставить faiss-cpu — скорости CPU вполне достаточно для отладки и проверки логики. Когда придёт время деплоить на сервер, можно будет перенести на gpu.
# CPU-версия: работает везде — Windows, macOS, Linux
pip install -U faiss-cpu==1.13.2
# GPU-версия: Linux нативно. рекомендуется стаить через conda (Windows — только через Docker/WSL)
$ conda install -c pytorch -c nvidia -c conda-forge faiss-gpu=1.13.2
# GPU-версия с NVIDIA cuVS
$ conda install -c pytorch -c nvidia -c rapidsai -c conda-forge libnvjitlink faiss-gpu-cuvs=1.13.2
На следующем шаге разберём, какие именно типы индексов предоставляет FAISS — это важно понять до того, как писать первые строки кода.
Типы индексов FAISS
FAISS предоставляет несколько типов индексов — каждый реализует свой алгоритм поиска и делает разный выбор между скоростью, точностью и потреблением памяти. Ещё раз разберём их, уже в разрезе faiss решений.
IndexFlat — точный перебор
IndexFlatL2 и IndexFlatIP — плоские индексы, которые хранят векторы как есть и при поиске сравнивают запрос со всеми векторами в базе. Первый использует евклидово расстояние (L2), второй — скалярное произведение (IP, inner product).
Такой поиск даёт абсолютно точные результаты: мы гарантированно находим ближайших соседей. Но цена — линейная сложность: если в индексе миллион векторов, каждый запрос требует миллиона операций сравнения. На небольших коллекциях (до нескольких сотен тысяч векторов) это работает вполне приемлемо, на десятках миллионов — становится узким местом.
IndexFlat удобен как эталон: когда нужно проверить, насколько приближённые методы теряют в точности, сравниваем их результаты с IndexFlat как «истиной».
Обратите внимание: IndexFlatL2 возвращает в матрице расстояний квадрат евклидовой дистанции, а не само расстояние. Это сделано намеренно — вычисление квадратного корня дороже, а порядок соседей от его отсутствия не меняется.
Отдельного индекса для косинусной близости в FAISS нет. Его получают через IndexFlatIP, предварительно нормализовав все векторы по L2-норме — тогда скалярное произведение нормированных векторов эквивалентно косинусному сходству. Нормализацию нужно применять и к базе, и к запросам.
IndexIVFFlat — кластеризация пространства
В предыдущем модуле мы реализовывали IVF своими руками — здесь принцип тот же. IndexIVFFlat делит пространство на nlist кластеров с помощью k-means. При добавлении каждый вектор попадает в ближайший кластер. При поиске мы находим nprobe ближайших к запросу кластеров и ищем только внутри них.
Это приближённый поиск: если ближайший сосед оказался в кластере, который мы не просматривали, мы его пропустим. Но при разумных параметрах вероятность такого пропуска мала, а скорость значительно выше — на миллионе векторов с nprobe=10 при nlist=1000 поиск обрабатывает около 1% базы.
nprobe — главный рычаг компромисса: больше значение → выше полнота (recall) → дольше поиск. При nprobe = nlist получается полный перебор, как в IndexFlat.
Важная особенность: перед добавлением данных IVF-индекс нужно обучить — вызвать train() на репрезентативной выборке, чтобы k-means нашёл центроиды кластеров.
IndexHNSW — графовый поиск
IndexHNSWFlat — реализация алгоритма HNSW (Hierarchical Navigable Small World). Индекс строит многоуровневый граф: вершины — это векторы, рёбра — связи с ближайшими соседями. Верхние уровни разрежены и позволяют быстро «приблизиться» к нужной зоне пространства, нижний уровень плотный — здесь происходит финальный поиск.
Три параметра определяют поведение. M — количество связей у каждой вершины (обычно 16–48): больше связей — плотнее граф, выше точность, но больше памяти. efConstruction — глубина поиска при построении графа, влияет на качество индекса. efSearch — глубина поиска при запросе, главный рычаг после построения.
HNSW не требует этапа обучения — векторы добавляются сразу. Он даёт очень высокую точность при хорошей скорости, но хранит граф целиком в памяти, что делает его тяжелее по RAM, чем IVF-индексы.
Product Quantization — сжатие векторов
Product Quantization (PQ) позволяет резко уменьшить объём памяти. Каждый вектор делится на m равных частей (субвекторов), для каждой части строится словарь из 2^nbits центроидов. Вместо исходных координат хранится только индекс ближайшего центроида в каждом словаре.
Например, вектор размерности 768 делим на 16 частей. Для каждой части — словарь из 256 центроидов (8 бит). Итого на вектор уходит 16 байт вместо 768 × 4 = 3072 байт — сжатие почти в 200 раз. При поиске расстояния вычисляются по сжатым кодам через предрассчитанные таблицы.
Точность снижается: чем агрессивнее сжатие, тем больше погрешность. Но для многих приложений 90–95% правильных результатов вполне достаточно, а возможность уместить сотни миллионов векторов в оперативную память — существенное преимущество.
В чистом виде IndexPQ всё равно перебирает все векторы, просто каждое сравнение происходит по сжатым кодам. Реальная мощь PQ раскрывается в комбинации с IVF.
IndexIVFPQ — комбинация кластеризации и сжатия
IndexIVFPQ объединяет оба подхода: пространство делится на кластеры (IVF), а внутри каждого кластера векторы хранятся в сжатом виде (PQ). При поиске мы выбираем несколько ближайших кластеров и вычисляем приближённые расстояния по кодам.
Это самый популярный выбор для больших коллекций — он экономит и время, и память. Параметров больше: nlist и nprobe для кластеризации, m и nbits для PQ. Требуется обучение.
Улучшенная версия — OPQ (Optimized Product Quantization). Перед квантованием применяется обучаемое ортогональное преобразование, которое лучше распределяет информацию по субвекторам. OPQ даёт заметный прирост точности при той же длине кода практически без дополнительных затрат памяти. Если уже идёте в IVFPQ — стоит сразу рассматривать OPQ.
IndexIVFSQ — скалярное квантование
IndexIVFSQ (Scalar Quantization) — промежуточный вариант между IVFFlat и IVFPQ. Вместо product quantization он просто квантует каждую компоненту вектора независимо: приводит значения к 8-битному целому (или 4-битному, в зависимости от настройки). Это проще и быстрее в вычислении, чем PQ, но и сжимает меньше — примерно в 4 раза против 10–200 раз у PQ.
IndexIVFSQ удобен тогда, когда нужна лёгкая экономия памяти без существенной потери точности и без сложной настройки параметров PQ. Хороший промежуточный шаг перед переходом на полноценный IVFPQ.
IndexIVFPQFastScan — ускоренная версия IVFPQ
IndexIVFPQFastScan — современный вариант IVFPQ, оптимизированный под SIMD-инструкции современных процессоров (AVX2/AVX-512). Он обрабатывает несколько PQ-кодов одновременно в рамках одной процессорной инструкции, что даёт существенное ускорение на тех же параметрах без потери точности. На современном железе это фактически рекомендуемая замена стандартного IVFPQ.
IndexLSH — хэш-поиск
IndexLSH реализует Locality Sensitive Hashing — подход, при котором векторы проецируются в пространство бинарных хэшей так, чтобы похожие векторы с высокой вероятностью получали близкие хэши. Это узкоспециализированный индекс: он работает с бинарными кодами и подходит для сценариев, где данные изначально представлены в бинарном виде или нужна очень быстрая грубая предварительная фильтрация.
Композиция через index_factory
Помимо отдельных классов, FAISS предоставляет index_factory — способ собирать составные индексы из строки-описания. Например, строка "PCA256,IVF4096,PQ16x8" описывает сначала снижение размерности через PCA до 256, затем IVF с 4096 кластерами, затем PQ с 16 субвекторами по 8 бит. Это удобно для быстрого экспериментирования с разными конфигурациями.
В следующем шаге посмотрим на практическую сторону выбора: какой индекс брать под конкретные ограничения.
Как выбирать индекс и когда использовать FAISS
Разобрав устройство каждого индекса, посмотрим на практическую сторону: как принимать решение о выборе и в каких ситуациях FAISS оказывается лучшим инструментом.
Объём данных и память
Если коллекция небольшая — до нескольких сотен тысяч векторов — и памяти достаточно, IndexFlatL2 или IndexFlatIP остаются вполне разумным выбором. Никаких параметров, никакого обучения, гарантированная точность. Особенно это удобно на этапе прототипирования, когда важно убедиться в правильности логики, а не в производительности.
Когда коллекция вырастает до миллионов векторов, полный перебор становится слишком медленным. Здесь подключается IndexIVFFlat: кластеризация позволяет обрабатывать только небольшую долю базы на каждый запрос, при этом оригинальные векторы хранятся без сжатия — точность остаётся высокой.
Если же память — узкое место, а данных десятки и сотни миллионов, включаем квантование. IndexIVFPQ позволяет уместить коллекцию, которая в исходном виде не влезла бы в RAM никакого разумного сервера. Коллекция в 100 миллионов векторов размерности 768, занимающая исходно около 280 ГБ, с агрессивным PQ сжатием может уместиться в несколько гигабайт. Когда важен каждый гигабайт — рассматривайте сразу OPQ вместо стандартного IVFPQ: прирост точности почти бесплатный.
Требования к задержке и точности
Если задержка критична, а данные помещаются в память — лучший кандидат IndexHNSWFlat. Графовый алгоритм даёт очень высокую точность при низкой латентности и не требует этапа обучения. Главный минус — граф занимает дополнительную память сверх самих векторов, и чем плотнее граф, тем больше эта надбавка.
Важно понимать, что в IVF-семействе и в HNSW есть параметры, которые управляют этим балансом уже после построения индекса — без перестройки. Это значит, что одни и те же данные можно одновременно обслуживать в «быстром, но менее точном» режиме и в «медленном, но точном» режиме, просто меняя одно число. Этим FAISS выгодно отличается от некоторых других решений, где параметры поиска жёстко зафиксированы.
Частота обновлений
FAISS — не база данных с транзакциями. Понятие «обновить вектор» здесь отсутствует: если нужно изменить запись, удаляем старую и добавляем новую. Элемент при удалении помечается как удалённый, но физически остаётся в индексе до перестройки. При интенсивных удалениях индекс постепенно деградирует по точности — особенно HNSW, где граф при массовых удалениях нарушается.
Если данные практически статичны — загрузили коллекцию однажды и редко меняете, — FAISS работает отлично. Если данные активно обновляются, часто строят двухуровневую схему: свежие данные накапливаются в небольшом быстром индексе, а основная коллекция живёт в IVFPQ и периодически перестраивается целиком при слиянии.
Карта решений
| Сценарий | Рекомендация |
|---|---|
| Прототип, небольшая коллекция, память не ограничена | IndexFlatL2 / IndexFlatIP |
| Миллионы векторов, нужна скорость без сжатия | IndexIVFFlat |
| Минимальная задержка, высокий recall, RAM достаточно | IndexHNSWFlat |
| Лёгкая экономия памяти без сложной настройки | IndexIVFSQ |
| Десятки миллионов+ векторов, RAM ограничена | IndexIVFPQ / OPQ |
| Большие объёмы, современное железо с AVX2/AVX-512 | IndexIVFPQFastScan |
| Косинусная близость | IP-метрика + L2-нормализация векторов |
Почему FAISS остаётся актуальным
FAISS появился в 2017 году и с тех пор остаётся одним из наиболее используемых инструментов в ML-инфраструктуре — не потому что прост, а потому что даёт несколько вещей, которые сложно получить иначе.
Первое — прямой доступ к алгоритмам. Большинство серверных векторных баз данных дают один-два варианта индекса с ограниченными настройками. FAISS открывает весь спектр: можно выбирать алгоритм, настраивать каждый параметр, комбинировать подходы. Это особенно ценно в исследовательских проектах и в задачах, где стандартные конфигурации не дают нужного баланса.
Второе — производительность без посредников. Работая в памяти процесса, FAISS исключает сетевые задержки, сериализацию данных и накладные расходы HTTP или gRPC. На больших объёмах и высоких нагрузках это превращается в реальное преимущество. GPU-ускорение дополнительно поднимает планку производительности — причём FAISS умеет работать с несколькими GPU одновременно, распределяя по ним либо данные, либо запросы.
Третье — встраиваемость. FAISS не требует отдельного сервиса. Его можно упаковать внутрь любого приложения, библиотеки или микросервиса. Именно поэтому FAISS стал де-факто движком поиска для LangChain и многих других фреймворков — он решает задачу без инфраструктурного оверхеда.
FAISS или серверная база: когда переключаться
Пока работаем в рамках одного процесса и одной машины — FAISS справляется отлично. Сигналы к переходу на Qdrant или Weaviate появляются тогда, когда нужна фильтрация по метаданным прямо внутри поискового запроса; когда данные нужно хранить персистентно с автоматическим управлением; когда команда не хочет самостоятельно решать вопросы масштабирования; когда нужен REST API для доступа из нескольких сервисов.
Хорошая новость — концепции одинаковые. Понимая, как работают IVF, PQ и HNSW в FAISS, вы без труда разберётесь с тем, как эти же алгоритмы работают под капотом Qdrant и Weaviate. Просто там они обёрнуты в удобный сервер с готовой инфраструктурой.
Частая стратегия на практике: прототип строят на FAISS, потому что это быстро и просто. Когда система вырастает и появляются требования к API, фильтрации и масштабированию — переходят на серверное решение. Или наоборот: используют готовую базу для большинства запросов, а тяжёлые аналитические задачи (кластеризация, офлайн-поиск по большим массивам) отдают в FAISS, который живёт рядом как инструмент пакетной обработки.
Итоги урока
В этом уроке мы разобрались с архитектурой FAISS: почему это библиотека, а не сервер, что из этого следует по части ограничений и преимуществ. Познакомились со всей линейкой индексов — от точного IndexFlat до комбинированного IVFPQ и современного FastScan-варианта. Поняли, какой алгоритм подходит под какие ограничения по объёму данных, памяти и требованиям к задержке. И обсудили, в каких ситуациях FAISS остаётся лучшим выбором, а когда стоит смотреть в сторону серверных решений.
В следующем уроке переходим к коду: установка, первые индексы, поиск по реальным данным.
Задача 1
В чём принципиальное отличие IndexIVFFlat от IndexFlatL2?
Задача 2
Что содержит матрица D, которую возвращает IndexFlatL2 при поиске?
Задача 3
Почему для косинусного поиска через IndexFlatIP необходимо нормализовать векторы?
Задача 4
Какие из следующих индексов FAISS требуют вызова train() перед добавлением векторов? (Выберите все верные ответы)
Задача 5
Какой параметр IndexHNSWFlat можно менять после построения индекса без его перестройки?
Задача 6
Сопоставьте индекс FAISS с его основной характеристикой.
Сопоставьте элементы
Задача 7
В чём принципиальное отличие OPQ от стандартного Product Quantization?
Задача 8
Что происходит с IndexHNSWFlat после серии массовых удалений векторов?
Задача 9
Что такое IndexIVFSQ и чем он отличается от IndexIVFPQ?
Задача 10
Какие утверждения об IndexPQ верны? (Выберите все верные ответы)
Продолжить с сохранением прогресса
Перейдите к уроку в учебном кабинете.
