Sentence Transformers, czyli wektory bez wychodzenia na zewnątrz
Sentence Transformers to biblioteka licząca osadzenia lokalnie, na Twoim sprzęcie, bez wywołań do usług zewnętrznych. Powstała w środowisku akademickim, dziś rozwija ją zespół prowadzący największy zbiór otwartych modeli, a projekt przeniósł się pod jego repozytorium.
Alternatywa wobec płatnych usług jest tu realna, a nie tylko teoretyczna. Otwarte modele osadzeń są dziś na tyle dobre, że przy wyszukiwaniu po dokumentach firmowych różnica względem dostawców komercyjnych bywa niewielka, a koszt za token znika całkowicie.
Cena to sprzęt, czas na dobór modelu i utrzymanie. Poniżej opisuję, kiedy ten rachunek wychodzi na plus, bo odpowiedź nie jest oczywista i zależy od skali.
Trzy rodzaje modeli
Biblioteka obsługuje trzy klasy modeli o różnym przeznaczeniu i warto je rozróżniać, bo materiały w sieci często je mieszają.
Model gęsty zamienia tekst na wektor kilkuset liczb, gdzie każda coś znaczy, choć nie da się powiedzieć co. To jest klasyczne osadzenie, znane z usług komercyjnych, i punkt wyjścia dla większości zastosowań.
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("intfloat/multilingual-e5-large")
wektory = model.encode_document(fragmenty)
zapytanie = model.encode_query("Jaki jest okres wypowiedzenia?")Rozdzielenie osadzania dokumentów od osadzania zapytań, widoczne w tych dwóch metodach, jest ważniejsze, niż wygląda. Część modeli oczekuje przedrostka przed tekstem, innego dla dokumentu i innego dla pytania, a pominięcie go pogarsza trafność w sposób niewidoczny bez pomiaru. Te metody dokładają go same, ale tylko wtedy, gdy model deklaruje przedrostki w swoim pliku konfiguracyjnym dla tej biblioteki. Model z przykładu powyżej ich nie deklaruje, więc oznaczenia dla dokumentu i dla zapytania trzeba przy nim podać samemu, parametrem przyjmującym przedrostek. Zanim uznasz, że działa automatycznie, sprawdź w karcie modelu, czy przedrostki są tam zapisane.
Model rzadki zwraca wektor o ogromnej liczbie wymiarów, z których niemal wszystkie są zerami. Brzmi dziwnie i ma konkretny sens: wymiary odpowiadają słowom, więc taki wektor łączy zalety wyszukiwania po znaczeniu z dokładnym dopasowaniem słów. To rozwiązuje problem, który przy zwykłych osadzeniach wymaga budowania wyszukiwania hybrydowego z dwóch osobnych indeksów.
Model porównujący pary ocenia dopasowanie zapytania i dokumentu, widząc oba naraz. Daje wyraźnie lepszy wynik niż porównanie wektorów, kosztem czasu, bo trzeba go uruchomić dla każdej pary osobno. Stąd układ dwuetapowy: tanie wyszukiwanie wektorowe zwraca pięćdziesiąt kandydatów, a ten model wybiera z nich pięć najlepszych.
Ten trzeci rodzaj jest w praktyce najbardziej niedoceniany. Dołożenie go do istniejącego wyszukiwania to zwykle kilkanaście linii kodu i największy pojedynczy skok trafności, jaki da się osiągnąć bez zmiany modelu osadzeń ani sposobu cięcia dokumentów. Warto sprawdzić go wcześniej niż dostrajanie, bo kosztuje ułamek tej pracy.
Wybór modelu
To jest decyzja, przy której najłatwiej stracić tydzień, więc warto podejść do niej metodycznie.
Zacznij od języka. Modele trenowane wyłącznie na angielskim wypadają słabo na polskich dokumentach, nawet jeśli w zestawieniach mają wysokie wyniki. Model wielojęzyczny jest tu punktem wyjścia, a nie kompromisem.
Potem rozmiar. Model o kilkuset milionach parametrów działa na zwykłym procesorze z sensowną szybkością, model kilkukrotnie większy potrzebuje karty graficznej, żeby przetworzyć archiwum w rozsądnym czasie. Różnica w trafności bywa mniejsza, niż sugeruje różnica w rozmiarze.
Na końcu pomiar na własnych danych. Weź pięćdziesiąt realnych pytań z oczekiwanymi odpowiedziami, zaindeksuj ten sam zbiór trzema modelami i porównaj, ile razy właściwy dokument trafił do pierwszej piątki. Ta praca zajmuje popołudnie i rozstrzyga więcej niż tydzień czytania zestawień.
Publiczne rankingi warto traktować jako listę kandydatów, a nie jako odpowiedź. Mierzą zadania uśrednione po wielu dziedzinach, a Twoje dokumenty należą do jednej konkretnej.
Dostrajanie na własnych danych
Tu leży przewaga, której usługi komercyjne nie dają wcale: możliwość nauczenia modelu Twojej dziedziny.
Zysk bywa duży przy słownictwie specjalistycznym. Model ogólny nie wie, że w Twojej firmie „zgłoszenie serwisowe" i „ticket" znaczą to samo, ani że skrót używany w dokumentacji odnosi się do konkretnego produktu. Dostrojenie na kilku tysiącach par pytanie i odpowiedź z Twojego archiwum uczy go tych powiązań.
from sentence_transformers import SentenceTransformerTrainer, losses
strata = losses.MultipleNegativesRankingLoss(model)
trener = SentenceTransformerTrainer(
model=model,
train_dataset=zbior_treningowy,
loss=strata,
)
trener.train()Najprostsza i zwykle wystarczająca postać danych treningowych to pary: pytanie oraz fragment zawierający odpowiedź. Nie potrzebujesz przykładów negatywnych, bo funkcja straty traktuje pozostałe pary w partii jako negatywne.
Skąd wziąć te pary, jest pytaniem trudniejszym niż samo trenowanie. Trzy źródła sprawdzają się w praktyce: historia zapytań do wyszukiwarki razem z tym, co użytkownicy klikali, zgłoszenia obsługi klienta razem z odpowiedziami, oraz pary wygenerowane modelem językowym z Twoich dokumentów i przejrzane przez człowieka.
Zastrzeżenie, które warto postawić: dostrajanie ma sens dopiero wtedy, gdy zmierzysz, że model ogólny nie wystarcza. To jest praca na kilka dni, a poprawa cięcia fragmentów albo dołożenie kroku oceniającego kandydatów daje zwykle więcej za mniejszy koszt.
Wydajność i sprzęt
Rachunek za osadzenia lokalne wygląda inaczej niż za usługę, bo koszt jest stały, a nie zmienny.
Procesor wystarcza do zapytań przychodzących pojedynczo. Model średniej wielkości liczy wektor dla krótkiego zapytania w kilkadziesiąt milisekund, co przy typowej aplikacji ginie w czasie odpowiedzi całej reszty.
Karta graficzna staje się potrzebna przy indeksowaniu. Przetworzenie stu tysięcy fragmentów na procesorze to godziny, na karcie minuty. Jeśli robisz to raz, godziny są do przyjęcia. Jeśli archiwum aktualizuje się codziennie, karta zwraca się szybko.
Trzy ustawienia dają największą różnicę bez zmiany modelu. Pierwsze to przetwarzanie partiami zamiast pojedynczo, bo model liczy wiele tekstów naraz znacznie taniej niż jeden po drugim. Drugie to mniejsza precyzja liczb, która skraca czas mniej więcej dwukrotnie przy stracie praktycznie niezauważalnej. Trzecie to sortowanie fragmentów po długości przed przetwarzaniem, żeby w jednej partii znalazły się teksty podobnej wielkości.
Warto też pamiętać o pamięci. Model wczytany do pamięci zajmuje ją przez cały czas działania procesu, więc uruchamianie go w funkcji startującej przy każdym żądaniu jest kosztowne i wolne. Wczytaj raz przy starcie aplikacji i trzymaj.
Wdrożenie na produkcji
Biblioteka liczy wektory i na tym kończy swoją rolę, więc reszta systemu należy do Ciebie. Warto zaplanować kilka rzeczy, zanim model trafi na produkcję.
Pierwsza to sposób udostępnienia modelu. Wczytanie go w każdym procesie aplikacji oznacza tyle kopii w pamięci, ile masz procesów, a przy modelu zajmującym dwa gigabajty to szybko przestaje się mieścić. Sensowniejszy układ to osobna usługa licząca wektory, odpytywana przez resztę aplikacji, z jedną kopią modelu w pamięci.
Druga to rozgrzewka. Pierwsze wywołanie po wczytaniu modelu trwa wyraźnie dłużej niż kolejne, bo dopiero wtedy alokują się bufory i kompilują ścieżki obliczeń. Puszczenie jednego sztucznego zapytania przy starcie usuwa ten skok, który inaczej trafi w pierwszego użytkownika po każdym wdrożeniu.
Trzecia to wersjonowanie. Zapisz razem z indeksem nazwę modelu, jego wersję i ustawienia, przy których powstały wektory. Bez tego dołożenie dokumentów po aktualizacji biblioteki albo po pobraniu nowszej wersji modelu daje wpisy nieporównywalne z resztą, a objawem są po prostu gorsze wyniki, bez żadnego błędu.
Czwarta to pobieranie modelu przy starcie. Domyślnie biblioteka ściąga wagi z sieci przy pierwszym użyciu, co w środowisku bez dostępu do internetu albo przy nagłym skalowaniu jest problemem. Wbuduj model w obraz kontenera albo zamontuj z wolumenu, a wtedy start jest przewidywalny.
Ile to naprawdę kosztuje
Porównanie z usługą płatną warto zrobić na liczbach, bo intuicja myli się tu w obie strony.
Po stronie usługi rachunek jest prosty: liczba tokenów razy stawka. Zaindeksowanie miliona fragmentów po pięćset tokenów to pięćset milionów tokenów, czyli od dziesięciu do kilkudziesięciu dolarów jednorazowo, plus grosze za zapytania.
Po stronie własnej infrastruktury koszt składa się z trzech pozycji, z których dwie są pomijane w takich porównaniach. Pierwsza to maszyna, licząca się w dziesiątkach albo setkach dolarów miesięcznie zależnie od tego, czy potrzebujesz karty graficznej. Druga to czas na dobór modelu i wdrożenie, czyli kilka dni pracy. Trzecia to utrzymanie: aktualizacje, monitorowanie i reagowanie, gdy coś przestanie działać.
Z tego wynika prosty próg. Przy jednorazowym zaindeksowaniu małego archiwum usługa wygrywa bezapelacyjnie. Przy stałym, wysokim wolumenie własna infrastruktura zaczyna wygrywać, bo koszt przestaje rosnąć razem z ruchem. Przy danych, które nie mogą opuścić firmy, rachunek nie ma znaczenia, bo alternatywy po prostu nie ma.
Warto policzyć to raz, na własnych liczbach, zamiast opierać się na przekonaniu, że lokalnie zawsze taniej. Przy typowej aplikacji z kilkoma tysiącami zapytań dziennie rachunek za osadzenia u dostawcy bywa niższy niż koszt jednej maszyny.
Sentence Transformers kontra alternatywy
| Rozwiązanie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Sentence Transformers | Dane nie wychodzą, zero kosztu za token, dostrajanie | Sprzęt, utrzymanie, dobór modelu po Twojej stronie | Dane wrażliwe albo duży stały wolumen |
| OpenAI | Najprostszy start, przewidywalność | Koszt rośnie z wolumenem, dane wychodzą | Prototyp i średnie wolumeny |
| Voyage AI | Wspólna przestrzeń w rodzinie modeli | Kolejny dostawca do rozliczenia | Projekt, w którym model może się zmienić |
| Cohere | Obrazy i tekst w jednej przestrzeni | Droższy przy obrazach | Dokumenty z układem graficznym |
Pierwszy wiersz wygrywa w dwóch sytuacjach. Pierwsza to dane, które nie mogą opuścić firmy, i wtedy nie ma o czym dyskutować. Druga to duży stały wolumen, gdzie koszt za token narasta miesiąc po miesiącu, a maszyna kosztuje tyle samo niezależnie od ruchu.
Przegrywa przy prototypie i przy małej skali. Postawienie modelu, dobranie go i utrzymanie to praca, która przy tysiącu dokumentów kosztuje więcej niż kilka dolarów za wywołania API.
Typowe błędy
Pierwszy to pominięcie przedrostków wymaganych przez model. Część modeli oczekuje innego oznaczenia dla dokumentu i dla zapytania, a metody przeznaczone do jednego i drugiego dokładają je same tylko wtedy, gdy model ma je zapisane w konfiguracji. Przy modelu, który ich nie deklaruje, musisz podać je sam.
Drugi to wczytywanie modelu przy każdym żądaniu. Trwa to sekundy i zajmuje pamięć, a wystarczy wczytać raz przy starcie procesu.
Trzeci to przetwarzanie fragmentów pojedynczo. Partie są wielokrotnie szybsze i to jedna z niewielu optymalizacji, która nic nie kosztuje.
Czwarty to wybór modelu z rankingu bez sprawdzenia na własnych danych. Zestawienia mierzą uśrednione zadania, a Twoje dokumenty należą do jednej dziedziny.
Piąty to dostrajanie przed zmierzeniem, czy model ogólny wystarcza. Poprawa cięcia fragmentów zwykle daje więcej za ułamek pracy.
Szósty to mieszanie wektorów z różnych modeli albo z różnych wersji tego samego modelu w jednym indeksie. Są nieporównywalne i wyszukiwanie zwraca wtedy przypadkowe wyniki, bez żadnego komunikatu o błędzie.
Siódmy to pobieranie wag modelu z sieci przy starcie usługi. W środowisku bez dostępu do internetu albo przy nagłym zwiększeniu liczby instancji kończy się to nieudanym uruchomieniem w najgorszym możliwym momencie.
FAQ
Czy modele lokalne dorównują płatnym usługom?
Przy wyszukiwaniu po dokumentach tekstowych różnica bywa niewielka, zwłaszcza po dostrojeniu na własnych danych. Przy zadaniach wymagających obsługi obrazów albo najwyższej trafności w wąskiej dziedzinie usługi komercyjne nadal wypadają lepiej. Rozstrzyga pomiar na własnym zestawie pytań.
Jaki sprzęt jest potrzebny?
Do obsługi zapytań wystarcza procesor, bo pojedyncze zapytanie liczy się w kilkadziesiąt milisekund. Karta graficzna przydaje się przy indeksowaniu dużych zbiorów, gdzie skraca godziny do minut, i zwraca się szybko przy archiwum aktualizowanym codziennie.
Czym różni się model porównujący pary od zwykłego?
Zwykły model liczy wektor dokumentu raz, bez znajomości pytania. Model porównujący pary widzi zapytanie i dokument razem, więc ocenia dopasowanie dokładniej, kosztem czasu. Stąd układ dwuetapowy: szybkie wyszukiwanie wektorowe, potem ocena kilkudziesięciu kandydatów.
Kiedy warto dostroić model?
Gdy zmierzysz, że model ogólny nie radzi sobie z Twoim słownictwem, a poprawa cięcia fragmentów i dołożenie oceny kandydatów nie wystarczyły. Potrzebujesz kilku tysięcy par pytanie i fragment z odpowiedzią, najlepiej z historii własnego wyszukiwania albo zgłoszeń obsługi.
Gdzie trzymać policzone wektory?
W dowolnej bazie wektorowej, na przykład opisanej w tekście o Qdrancie. Biblioteka liczy wektory i na tym kończy swoją rolę, więc przechowywanie i wyszukiwanie należą do osobnej warstwy, którą dobierasz niezależnie.
Dokumentacja stoi na stronie projektu, a kod w repozytorium na GitHubie.