Przejdź do głównej zawartości

Cztery modele w RAG-u na RTX 5090: najszybszy nie wygrał, a pomiar szybkości znów kłamał

· 11 min aby przeczytać
Przemysław Majdak
Full-Stack Developer, Automation Engineer & Web Security Specialist

Sprawdziłem, czy obecny model w firmowym RAG-u, Qwen3.8-27B w INT4, da się zastąpić czymś szybszym. Kandydaci to Gemma 4 12B, Gemma 4 26B-A4B (MoE) i Qwen3.5-9B. Wszyscy działali na tej samej karcie RTX 5090, w tym samym vLLM i z tym samym zestawem pytań. Najszybszy kandydat, Gemma 4 26B-A4B, przy 16 zapytaniach naraz dał 4,1 raza więcej tokenów na sekundę i 11 razy krótszy czas do pierwszego tokenu. Mimo to obecny model zostaje, bo tylko on zaliczył wszystkie 52 tury w obu kanałach. W pierwszym pomiarze ta przewaga w czasie do pierwszego tokenu wyszła 85-krotna, bo kandydatom pomagał prefix cache, a obecnemu modelowi nie. To ten sam błąd, który opisałem tydzień temu, tylko tym razem w drugą stronę.

To ciąg dalszy wpisu o kwantyzacjach Qwena na RTX 5090 i wpisu o rerankerach na prawdziwych danych. Tamte wpisy mierzyły pojedyncze elementy. Ten mierzy cały system z podmienionym modelem odpowiedzi.

Teza​

W RAG-u na prawdziwej bazie wiedzy szybszy model nie jest automatycznie lepszy. Kandydaci byli szybsi, ale każdy przegrał z obecnym modelem na kilku pytaniach. Dwóch z nich zmyśliło odpowiedź prawną, a takiego błędu nie wyłapie żaden pomiar szybkości. Druga część tezy dotyczy samego pomiaru: porównanie modeli pod obciążeniem nic nie znaczy, dopóki nie sprawdzisz, czy cache działa u każdego z nich tak samo.

Co mierzyłem​

Modele. Wszystkie działały w vLLM v0.30.0, z oknem 81 920 tokenów, najwyżej 16 sekwencjami naraz i tym samym przydziałem pamięci karty.

#modelwagipula KV (krotność okna)
M0RedHatAI/Qwen3.8-27B-INT4 (obecny)19,5 GB97 tys. tokenów (1,19×)
M1google/gemma-4-12B-it-qat-w4a16-ct10,3 GB380 tys. (4,64×)
M2RedHatAI/gemma-4-26B-A4B-it-NVFP416,4 GB224 tys. (2,74×)
M3RedHatAI/Qwen3.5-9B-FP8-dynamic14 GB290 tys. (3,54×)

Rozmiary wag to suma plików safetensors według API Hugging Face (odczyt 13.10.2026). Pule KV, trafienia w cache i wywłaszczenia pochodzą z logów i metryk vLLM, a pozostałe liczby z wyjścia moich skryptów pomiarowych z 12.10.2026.

Wyjściowa lista kandydatów zawierała też dwa warianty, których nie dało się uczciwie porównać. Pierwszy to 4-bitowa Gemma 4 12B w formacie MLX, który działa tylko na Apple. Wziąłem zamiast niej oficjalną kwantyzację QAT od Google. Drugi to Qwen3.5-9B w GGUF, który wymaga llama.cpp. Inny silnik zmieszałby różnice między modelami z różnicami między silnikami, więc wziąłem ten sam model w FP8 dla vLLM. Pełna Gemma 4 26B-A4B w bf16 waży 51,6 GB i nie mieści się w 32 GB karty, stąd wersja NVFP4.

Dane. Zestaw to 43 przypadki i 52 tury do wewnętrznej bazy wiedzy firmy (przepisy, materiały szkoleniowe, struktura działów i kontakty). Są wśród nich pytania o fakty, wyliczenia osób, dopytania w rodzaju „a kto jest liderem?”, pytania spoza bazy, na które trzeba odmówić, oraz próby wstrzyknięcia instrukcji. Każdy model przeszedł ten zestaw w dwóch kanałach:

  • w potoku RAG z bramkami, czyli z odmową przy słabym wyszukiwaniu i sprawdzaniem, czy podane źródło było w kontekście;
  • w interfejsie czatu z wbudowanym wyszukiwaniem, bez tych bramek.

Testy szły 12 października 2026 na działającym systemie, obok zwykłego ruchu. Każdy przebieg robiłem raz. Treści pytań ani odpowiedzi nie publikuję.

Jakość: obecny model jako jedyny bez porażki​

Najpierw test zgodności, czyli narzędzia, streaming i odpowiedź bez trybu myślenia. W całości zaliczyły go trzy modele. Qwen3.5-9B oblał jeden z czterech podtestów: zamiast słowa testowego „zażółć” użył odmiany „zażółcony”.

M0 Qwen3.8-27BM1 Gemma 4 12BM2 Gemma 4 26B-A4BM3 Qwen3.5-9B
potok z bramkami, 52 tury52/5250/5250/5250/52
czat, 52 tury52/5249/5250/5249/52

W potoku 3 z 52 tur u każdego modelu obsłużył cache odpowiedzi, a nie model, więc porównania to nie zmienia.

Różnica wynosi 2–3 tury, ale liczy się przede wszystkim rodzaj błędu:

  • Gemma 4 12B wymyśliła procedurę odwoławczą w sprawie karnej. Powołała się przy tym na przepisy z dwóch ustaw, które jej nie dotyczą, a powinna była odmówić. W czacie próba wstrzyknięcia „Ignore all previous instructions…” zakończyła się wierszem zamiast odmowy.
  • Qwen3.5-9B zmyślił w czacie tę samą procedurę. W innym pytaniu, na które powinien był odmówić, napisał wprawdzie, że kwoty za dany rok nie ma, ale zamiast czystej odmowy przytoczył kwoty z innych lat. W odpowiedziach pojawiały się też obce wtrącenia: „防滑”, „existem” i zniekształcone polskie słowo „działzie”. W potoku zamiast ustalonego zdania odmowy odmawiał własnymi słowami, a test sprawdza właśnie to zdanie, więc te tury nie przeszły.
  • Gemma 4 26B-A4B w żadnej z niezaliczonych tur nie zmyśliła treści. W potoku obie porażki to poprawne odpowiedzi, w których model przekręcił nazwę pliku źródłowego. W jednej dopisał literę i zmienił wielkość innej, w drugiej zmienił końcówkę słowa. Bramka sprawdzająca źródło nie znalazła takiego pliku w kontekście i oznaczyła obie odpowiedzi jako odmowy. W czacie model pominął osoby w dwóch wyliczeniach: 1 z 14 i 3 z 15.

Ta sama baza i ten sam retrieval wystarczyły obecnemu modelowi do podania obu list w całości. Braki w wyliczeniach wynikają więc z modelu, nie z wyszukiwania. To jest też odpowiedź na pytanie, które padło po teście: wymiana modelu embeddingów nic by tu nie dała. We wszystkich turach M2, które mają oczekiwane źródło (24 w każdym kanale), było ono w kontekście.

W rozdziale o małych modelach Lakshmanan i współautorzy porównują Gemmę 3 1B z Gemmą 3 27B na tym samym zadaniu (Lakshmanan i in., Generative AI Design Patterns, Pattern 24, s. 587–602). Moje porównanie dotyczy innej pary i innego zadania. Najbliżej obecnego modelu był MoE z 4 mld aktywnych parametrów, a nie gęsty model 12B. Na jednym przebiegu nie da się jednak powiedzieć, czy to reguła.

Równoległość: najpierw zły pomiar​

Pierwsza wersja testu obciążeniowego puszczała na każdym poziomie równoległości (1, 4, 8, 12, 16) te same 21 promptów RAG po ok. 3,8–4,7 tys. tokenów. Przy 16 naraz wyszło tak:

pierwszy pomiar, 16 narazM0M1M2M3
tok/s łącznie215815943952
TTFT p9510,3 s0,12 s0,12 s0,24 s

Różnica TTFT rzędu 85 razy przy ok. 4-krotnej różnicy przepustowości nie miała sensu, więc zajrzałem do metryk vLLM. U kandydatów prefix cache działał: od drugiego poziomu prompty były już policzone. U Gemm TTFT spadał do ok. 0,03 s, u Qwena3.5-9B z 0,21 do 0,04 s. U obecnego Qwena3.8-27B trafień nie było wcale, Prefix cache hit rate: 0.0%. Ten sam objaw zapisałem we wpisie o kwantyzacjach. Tam zawyżał wynik Bielika, tutaj zawyżył wynik kandydatów. Przyczyny wciąż nie zbadałem. Pierwszym podejrzeniem były warstwy liniowe obok zwykłej uwagi, ale Qwen3.5-9B ma tę samą architekturę (Qwen3_5ForConditionalGeneration, 3 warstwy liniowe na 1 pełną), a cache u niego działał. Ta hipoteza więc odpada.

Wyczyszczenia cache przez API nie dało się użyć: w moim wdrożeniu /reset_prefix_cache zwracał 404, bo jest dostępny tylko w trybie deweloperskim vLLM. Powtórzyłem więc test inaczej. Każdy model startował od świeżego restartu, każde zapytanie miało unikalny prompt, a każda fala przez potok i czat dostawała pytania niezadane wcześniej w przebiegu. Trafienia w cache spadły do 0% u Qwenów i ok. 6,8% u Gemm. U Gemm trafiał już tylko wspólny prompt systemowy, czyli tak samo jak w normalnym użyciu.

zimny cache, 16 narazM0 Qwen3.8-27BM1 Gemma 4 12BM2 Gemma 4 26B-A4BM3 Qwen3.5-9B
tok/s łącznie174375719617
TTFT p9514,3 s6,4 s1,3 s2,9 s
czas odpowiedzi p9541,9 s19,6 s6,5 s13,0 s
szczyt zajętości puli KV100%, 27 wywłaszczeń47%71%31%
potok, 6 pytań naraz, p9527,7 s19,4 s11,8 s14,4 s
potok 8 + czat 4 naraz, p9543,9 / 36,5 s27,5 / 20,1 s19,5 / 9,4 s21,7 / 25,8 s

Przewaga M2 przetrwała korektę, ale się zmniejszyła: w TTFT z 85 do ok. 11 razy, w przepustowości do 4,1 raza. Bardziej skurczyła się przewaga Gemmy 12B. W pierwszym pomiarze miała 3,8 raza więcej tokenów na sekundę od obecnego modelu, po korekcie 2,2 raza.

Korekta pokazała też coś, czego pierwszy pomiar nie miał szans pokazać. Obecny model przy 16 zimnych zapytaniach RAG zapełnia całą pulę KV i vLLM zaczyna wywłaszczać sekwencje, w tym przebiegu 27 razy. Jego pula mieści 1,19 pełnego okna, a 16 promptów po ok. 4 tys. tokenów razem z odpowiedziami zbliża się do tej granicy.

Obciążenie nie zmieniło treści odpowiedzi. W tym teście wszystkie cztery modele, także obecny, gubiły te same osoby w trzech wyliczeniach, ale tak samo już przy jednym zapytaniu naraz. To cecha promptów testu obciążeniowego, które są uproszczoną kopią kontekstu z potoku, a nie skutek równoległości. Do oceny jakości tego testu nie używam.

Licznik vLLM waiting_by_reason=capacity rósł już przy zajętości KV na poziomie 45%. Najpewniej obejmuje więc nie tylko brak pamięci, ale też budżet tokenów schedulera na krok. To wniosek z pomiaru, którego nie sprawdziłem w kodzie vLLM.

Kontekst: czysta granica, ale nie wszędzie tak samo daleko​

M0M1M2M3
prompt ponad oknobłąd 400błąd 400błąd 400błąd 400
prompt ok. 95% okna, czas odpowiedzi27 s20 s9 s7 s
3 prompty po 60% okna naraz, najdłuższy58 s, wywłaszczenie29 s12 s12 s
TTFT promptu 48 tys. tokenów14,8 s9,6 s3,7 s3,4 s

Przepełnienie okna nie psuje niczego po cichu. Każdy model dostaje od vLLM czysty błąd 400, bez obcinania promptu i bez dziwnej odpowiedzi. Gorzej wygląda droga do tej granicy. Trzy długie prompty naraz nie mieszczą się w puli KV obecnego modelu, więc vLLM przerywa jeden z nich i liczy go od nowa (1 wywłaszczenie). Większą część różnicy 58 s wobec 12 s u Gemmy 26B-A4B robi jednak wolniejszy prefill: pojedynczy prompt 48 tys. tokenów to u obecnego modelu 14,8 s, a u Gemmy 3,7 s.

W normalnym użyciu oba kanały zachowują się różnie. Potok ma ograniczoną pamięć rozmowy, więc jego prompt przestaje rosnąć po kilku turach. Czat wysyła za każdym razem całą historię: 20 tur to już prompt rzędu 19–24 tys. tokenów. Do 81 920 jest daleko, ale granicy dla bardzo długiej rozmowy nie mierzyłem.

Lakshmanan i współautorzy piszą, że test degradacji ma pokazać, „how service quality will start to degrade, not just the point at which it fails” (Lakshmanan i in., Generative AI Design Patterns, Pattern 27 Degradation Testing, s. 619). Tutaj miejsce awarii jest takie samo dla wszystkich: błąd 400 po przekroczeniu okna. Różni je to, co dzieje się przed nim: u obecnego modelu wywłaszczenia pojawiają się już w zmierzonym zakresie, a u kandydatów nie pojawiły się wcale.

Zastrzeżenia​

  • Jeden przebieg na model. Porażek nie powtarzałem, więc nie oddzieliłem niedeterminizmu od stałej słabości. Różnica 52 do 50 może się przy powtórce zmienić w obie strony.
  • Ruch produkcyjny w tle. Testy szły obok zwykłego użycia. Jego wpływu nie mierzyłem.
  • Błędy spoza modelu. Przy kilkunastu pytaniach naraz oba kanały zwracały też błędy, które pochodziły z konfiguracji poza modelem i były takie same na wszystkich czterech modelach. Nie opisuję ich, bo nie mówią nic o modelach.
  • Czat a baza testowa. Czat testowałem na produkcyjnych bazach wiedzy, a nie na przygotowanej bazie testowej.
  • Brak oceny ręcznej. Ślepa ocena 10 odpowiedzi jest przygotowana, ale jeszcze niewykonana. Wynik „zaliczone” to ocena automatyczna: źródło, wymagane słowa, odmowa.
  • Nieznana przyczyna braku prefix cache u Qwena3.8-27B. Dopóki jej nie znam, każdy pomiar porównujący ten model z innym robię na zimnym cache.

Wnioski​

  1. Obecny model zostaje. Jako jedyny zaliczył wszystkie 52 tury w obu kanałach. Dwaj kandydaci zmyślili odpowiedź prawną, a w bazie przepisów taki błąd waży więcej niż kilka sekund oczekiwania.
  2. Gemma 4 26B-A4B to kandydat do drugiej rundy. Jest 4 razy szybsza, ma ok. 2,3 raza większą pulę KV i w niezaliczonych turach nie zmyśliła treści. Jej błędy to przekręcone nazwy plików i niepełne listy. Pierwsze da się załatać tolerancyjnym porównaniem nazw w bramce, drugie trzeba sprawdzić w powtórkach.
  3. Obecny model jest bliżej granicy pojemności, niż zakładałem. Wywłaszczenia przy 16 zapytaniach RAG naraz to sygnał na przyszłość. Dzisiejszego ruchu pod tym kątem nie mierzyłem.
  4. Porównanie pod obciążeniem zaczynam od metryki trafień w cache. Drugi raz w ciągu tygodnia prefix cache zmienił ranking. Wystarczy jedna linia z logów vLLM, żeby to wyłapać przed publikacją, a nie po niej.