W świecie inżynierii oprogramowania od lat pokutuje przekonanie, że złożoność obliczeniowa algorytmów jest najważniejszym wyznacznikiem wydajności. Już w latach 2000. programiści systemów wbudowanych z dystansem podchodzili do akademickich teorii, przypominając słynne spostrzeżenie Raymonda Chena: dobrze zoptymalizowany algorytm o złożoności liniowej O(n) potrafi w praktyce działać znacznie szybciej niż teoretycznie wydajniejszy algorytm O(log n). Ta zasada, choć znana od lat, znajduje dziś nowe zastosowanie w dobie popularyzacji sztucznej inteligencji i wyszukiwania wektorowego.
Czy zawsze potrzebujesz bazy wektorowej?
Wielu twórców systemów AI zakłada, że do obsługi wyszukiwania semantycznego niezbędna jest dedykowana baza danych wektorowych. W rzeczywistości jednak, jeśli operujemy na zbiorze rzędu miliona dokumentów, często okazuje się, że tak zaawansowana infrastruktura jest zbędnym obciążeniem. Zamiast inwestować miesiące w naukę obsługi skomplikowanych systemów lub wydawać fortunę na licencje (często liczone w milionach dolarów, czyli w przeliczeniu na miliony złotych), warto rozważyć prostsze rozwiązania.
Okazuje się, że biblioteka NumPy, wykorzystywana w języku Python, pozwala na błyskawiczne przeszukiwanie metodą brute-force. Wykonanie iloczynu skalarnego dla 384-wymiarowych osadzeń (embeddings) na standardowym procesorze Apple M4 jest zaskakująco efektywne. Poniższa tabela przedstawia wydajność takiego podejścia przy różnej liczbie dokumentów:
Prostota jako strategia
Kluczem do sukcesu jest zrozumienie, że dla wielu zespołów technicznych, które dysponują ograniczonym ruchem i statycznym zestawem danych, „brutalna siła” jest w zupełności wystarczająca. Jak zauważa ekspert w dziedzinie wyszukiwania, Jo Kristian Bergum:
Wykorzystanie wyszukiwania wyczerpującego (exhaustive search) może być wszystkim, czego faktycznie potrzebujesz.
Jeśli zbiór danych przekroczy możliwości prostego skryptu w NumPy, kolejnym logicznym krokiem nie musi być od razu pełnoprawna baza danych. Warto rozważyć załadowanie wektorów bezpośrednio do pamięci operacyjnej przy użyciu biblioteki FAISS. Pozwala to na zachowanie wysokiej wydajności przy minimalnym narzucie operacyjnym.
Warto również pamiętać, że powyższe wyniki opierają się na naiwnych operacjach NumPy, co oznacza, że istnieje spory margines na dalszą optymalizację. Przepustowość można zwiększyć poprzez lepsze zarządzanie wątkami czy implementację wydajniejszego zbierania wyników do struktury typu heap, zamiast polegania na standardowych funkcjach biblioteki. W wielu przypadkach inżynieria polegająca na upraszczaniu architektury przynosi znacznie lepsze rezultaty niż przedwczesne wdrażanie złożonych systemów bazodanowych.
