Локальный RAG без векторов: SQLite FTS5 и BM25 для поиска по коду
Почему плотные векторные индексы нагружают машину разработчика и как SQLite FTS5 с BM25 даёт более быстрый и точный поиск по коду при малой доле памяти.
Пару лет рецепт локального поиска по коду был один и тот же: нарезать репозиторий на фрагменты, превратить каждый фрагмент в эмбеддинг на 1536 измерений и держать индекс HNSW живым в фоновом процессе. Это казалось современным, будто у ноутбука появился маленький поисковик, понимающий смысл кода. Но на загруженной рабочей станции у этого рецепта есть цена, о которой редко пишут в README.
Громкие цифры вокруг локального векторного поиска впечатляют: высокие оценки поиска в MTEB, миллионы токенов кода, превращённые в эмбеддинги за минуты, «мгновенный семантический интеллект» на потребительском железе. Но если читать заметки о памяти, а не маркетинг, картина другая. Плотная векторная индексация покупает полноту выдачи ценой пропускной способности памяти, RAM или VRAM и предсказуемости.
Когда «векторы везде» упираются в стену памяти
Запустите векторную базу рядом с локальной LLM, IDE и парой Docker-контейнеров, и она не будет простаивать. Она держит в RAM или VRAM большие графовые структуры, борется с другими инструментами за пропускную способность и приносит семантический дрейф: ищете точную строку ERR_AUTH_EXPIRED, а получаете код, который просто как-то связан с аутентификацией.
Поэтому многие команды тихо возвращаются к нативным алгоритмам на C, а именно к SQLite FTS5 с BM25. Вместо векторной математики для локальных запросов они используют детерминированное ранжирование по частоте терминов и находят точные идентификаторы быстрее чем за 5 миллисекунд.
Сопоставление понятий против предсказуемой скорости
Это не мелкая настройка. Это меняет то, как строятся инструменты для разработчиков. Заменив локальные векторные накладные расходы на FTS5, вы отказываетесь от теоретического сопоставления по смыслу ради экономного и быстрого выполнения.
- Эффективность: отказ от плотной индексации освобождает гигабайты памяти. Работа, которая тратила около 200 мс на вычисление эмбеддингов, нативно завершается меньше чем за 5 мс.
- Точность: поиск точных ключевых слов и имён функций, например
getUserById(), полностью детерминирован, поэтому индекс не вернёт тела посторонних функций.
Два подхода сравниваются так:
[Dense Vector RAG Flow]
Code Query -> Generate Vector Embeddings -> HNSW Index Search -> Semantic Drift Risk -> LLM Context
[Deterministic BM25 Flow]
Code Query -> Native SQLite C-Index -> Exact Keyword Match (5ms) -> Minimal LLM Context
Симптомы, которые вы, возможно, уже видели
Каждый, кто запускает локальных агентов или поиск по коду, наверняка замечал трение:
- Конкуренция за ресурсы. Вентилятор раскручивается, батареи хватает на два часа, а локальный инференс еле ползёт, потому что фоновый движок отбирает память.
- Штраф за точное совпадение. Поиск конкретной строки ошибки или переменной возвращает пять файлов, близких к аутентификации по смыслу, но не тот единственный файл, где есть нужный флаг.
Сравнение бок о бок, с приблизительными цифрами:
| Параметр | Локальная векторная БД (Chroma, Qdrant) | SQLite FTS5 (BM25) |
|---|---|---|
| Потребление памяти | Около 1,5–4 ГБ RAM или VRAM | Около 15–30 МБ RAM |
| Задержка поиска | 120–400 мс, в основном на эмбеддинги | Меньше 5 мс, нативный C |
| Точное совпадение идентификаторов | Слабое, склонно к дрейфу | Сильное, детерминированное совпадение по ключевым словам |
| Системные накладные расходы | Фоновые демоны, привязки к C++ | В простое ничего не работает |
| Лучше всего подходит для | Широкого тематического поиска по тексту | Точных запросов по коду, логам и локальному RAG |
Считайте это ориентировочными цифрами: результаты зависят от размера корпуса, выбора модели и железа.
Что это значит для техлидов
Рефлекс подключать векторную базу к каждой локальной задаче угасает. Полезнее привычка соблюдать пропорции. Мастерство старшего архитектора не в том, чтобы втиснуть более тяжёлую ИИ-инфраструктуру в ноутбуки разработчиков. Оно в том, чтобы понять, когда десять строк нативного кода или SQLite-файл на 30 МБ лучше нейронного конвейера, который измеряется гигабайтами.
По мере того как инструменты ускоряются, а процессы становятся компактнее, побеждать будут не стеки с самыми большими моделями. Победят те, что выпускают чистый код без лишнего веса. Это не делает эмбеддинги бесполезными: для нечётких вопросов на уровне понятий по обычному тексту они по-прежнему оправданы. Но чтобы найти точный идентификатор в кодовой базе, начните с дешёвого детерминированного инструмента и добавляйте векторы только там, где он не справляется.