Lokalny RAG bez wektorów: SQLite FTS5 i BM25 do wyszukiwania w kodzie
Dlaczego gęste indeksy wektorowe obciążają komputer programisty i jak SQLite FTS5 z BM25 daje szybsze, dokładne wyszukiwanie w kodzie przy ułamku pamięci.
Przez kilka lat przepis na lokalne wyszukiwanie w kodzie był ten sam: pociąć repozytorium na fragmenty, zamienić każdy fragment na embedding o 1536 wymiarach i trzymać indeks HNSW przy życiu w procesie działającym w tle. Wyglądało to nowocześnie, jakby laptop dostał małą wyszukiwarkę, która rozumie, co znaczy kod. Na mocno obciążonej stacji roboczej ten przepis ma jednak koszt, o którym rzadko pisze się w README.
Nagłówkowe liczby wokół lokalnego wyszukiwania wektorowego robią wrażenie: wysokie wyniki wyszukiwania w MTEB, miliony tokenów kodu zamienione na embeddingi w kilka minut, "natychmiastowa inteligencja semantyczna" na sprzęcie konsumenckim. Gdy jednak zamiast materiałów marketingowych przeczyta się uwagi o pamięci, obraz jest inny. Gęste indeksowanie wektorowe kupuje swoją kompletność wyników za cenę przepustowości pamięci, RAM lub VRAM i przewidywalności.
Gdy wektory wszędzie zderzają się ze ścianą pamięci
Uruchom bazę wektorową obok lokalnego LLM, IDE i kilku kontenerów Docker, a nie będzie bezczynna. Trzyma w RAM lub VRAM duże struktury grafowe, walczy z innymi narzędziami o przepustowość i wnosi dryf semantyczny: szukasz dokładnego ciągu ERR_AUTH_EXPIRED, a dostajesz kod, który tylko w jakiś sposób dotyczy uwierzytelniania.
Dlatego wiele zespołów po cichu wraca do natywnych algorytmów w C, czyli do SQLite FTS5 z BM25. Zamiast matematyki wektorowej przy lokalnych zapytaniach stosują deterministyczny ranking oparty na częstości terminów i znajdują dokładne identyfikatory w mniej niż 5 milisekund.
Dopasowanie pojęć kontra przewidywalna szybkość
To nie jest drobna poprawka. To zmienia sposób budowania narzędzi dla programistów. Zamieniając lokalny narzut wektorowy na FTS5, rezygnujesz z teoretycznego dopasowania pojęć w zamian za oszczędne i szybkie działanie.
- Wydajność: rezygnacja z gęstego indeksowania zwalnia gigabajty pamięci. Praca, która zajmowała około 200 ms na liczenie embeddingów, kończy się natywnie w mniej niż 5 ms.
- Precyzja: wyszukiwanie dokładnych słów kluczowych i nazw funkcji, takich jak
getUserById(), jest w pełni deterministyczne, więc indeks nie zwróci ciał niezwiązanych funkcji.
Oba przepływy wyglądają w porównaniu tak:
[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
Objawy, które być może już znasz
Każdy, kto uruchamia lokalnych agentów lub wyszukiwanie w kodzie, pewnie zauważył to tarcie:
- Rywalizacja o zasoby. Wentylator się rozkręca, bateria wytrzymuje dwie godziny, a lokalna inferencja się wlecze, bo silnik w tle walczy o pamięć.
- Kara za dokładne dopasowanie. Wyszukanie konkretnego komunikatu błędu lub zmiennej zwraca pięć plików, które znaczeniowo są blisko uwierzytelniania, ale nie ten jeden plik, w którym jest właściwa flaga.
Zestawienie obok siebie, z orientacyjnymi liczbami:
| Wymiar | Lokalna baza wektorowa (Chroma, Qdrant) | SQLite FTS5 (BM25) |
|---|---|---|
| Zużycie pamięci | Około 1,5 do 4 GB RAM lub VRAM | Około 15 do 30 MB RAM |
| Opóźnienie wyszukiwania | 120 do 400 ms, głównie na embeddingi | Poniżej 5 ms, natywne C |
| Dokładne dopasowanie identyfikatorów | Słabe, podatne na dryf | Mocne, deterministyczne dopasowanie słów kluczowych |
| Narzut systemowy | Demony w tle, powiązania z C++ | W bezczynności nic nie działa |
| Najlepsze do | Szerokich, tematycznych wyszukiwań w tekście | Precyzyjnych zapytań w kodzie, logach i lokalnym RAG |
Traktuj to jako wartości orientacyjne, bo wyniki zależą od wielkości korpusu, wyboru modelu i sprzętu.
Co to oznacza dla liderów zespołów
Odruch dołączania bazy wektorowej do każdego lokalnego problemu słabnie. Bardziej przydatne jest wyczucie proporcji. Umiejętność doświadczonego architekta nie polega na upychaniu coraz cięższej infrastruktury AI w laptopach programistów. Polega na rozpoznaniu, kiedy dziesięć linii natywnego kodu albo plik SQLite o rozmiarze 30 MB wygrywa z neuronowym potokiem liczonym w gigabajtach.
W miarę jak narzędzia przyspieszają, a procesy się odchudzają, wygrywać nie będą stosy z największymi modelami. Wygrają te, które dostarczają czysty kod bez balastu. Nie czyni to embeddingów bezużytecznymi: przy nieostrych pytaniach o pojęcia w zwykłym tekście nadal mają sens. Żeby jednak znaleźć dokładny identyfikator w bazie kodu, zacznij od taniego, deterministycznego narzędzia i dodawaj wektory tylko tam, gdzie ono zawodzi.