Przejdź do głównej zawartości

Qwen3.8-27B w NVFP4 i INT4 oraz Bielik 11B na RTX 5090: natywne FP4 przegrało z INT4

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

Karta z rodziny Blackwell obsługuje FP4 sprzętowo, więc kwantyzacja NVFP4 powinna być na niej najszybsza. U mnie nie była. Qwen3.8-27B w kwantyzacji INT4 od Red Hata generował na RTX 5090 o 19–28% więcej tokenów na sekundę niż ten sam model w NVFP4 od NVIDII. Do tego zostawił ponad dwa razy więcej pamięci na KV cache. Bielik 11B w FP8 był szybszy od obu, ale ma ponad dwa razy mniej parametrów, więc ta różnica niewiele mówi. Ten wpis pokazuje liczby, warunki pomiaru i to, czego z nich nie da się wyczytać. W pomiarze TTFT znalazłem też błąd, który sam zrobiłem.

Teza artykułu​

Na RTX 5090 i w vLLM v0.30.0 Qwen3.8-27B w INT4 (W4A16, kernel Marlin) był szybszy niż w NVFP4 przy każdej testowanej współbieżności. Miał też dwa razy więcej miejsca na kontekst. Etykieta „natywne FP4 na Blackwellu” nie przełożyła się w tym zestawie na prędkość. Test mierzy wyłącznie prędkość generowania. O jakości odpowiedzi, w tym o tym, czy któraś kwantyzacja traci więcej wiedzy, nie mówi nic.

Sprzęt i oprogramowanie​

  • GPU: NVIDIA GeForce RTX 5090, 32 607 MiB (odczyt nvidia-smi), sterownik 595.91.07.
  • Serwer inferencji: obraz vllm/vllm-openai:v0.30.0 w Dockerze z NVIDIA Container Toolkit. Port publikowany tylko na 127.0.0.1.
  • Wagi w lokalnym cache Hugging Face, telemetria vLLM i HF wyłączona. Kontenery Qwena dodatkowo z HF_HUB_OFFLINE=1.
  • Na karcie mieści się tylko jeden z tych modeli naraz. Przed każdym testem poprzedni kontener był zatrzymywany.

Modele​

Qwen3.8-27B w pełnej precyzji nie zmieści się w 32 GB. Rozmiary policzyłem z API Hugging Face (odczyt 2026-10-06). Wszystkie warianty mają licencję Apache 2.0.

RepozytoriumWagi na dyskuCzy wchodzi w 32 GB
Qwen/Qwen3.8-27B (BF16)55,6 GBnie
Qwen/Qwen3.8-27B-FP8 (od Qwen)30,9 GBwagi tak, ale bez miejsca na KV cache
nvidia/Qwen3.8-27B-NVFP421,9 GBtak
RedHatAI/Qwen3.8-27B-INT419,5 GBtak

Obie testowane kwantyzacje nie pochodzą od Qwena, tylko od NVIDII i Red Hata. To nadal lepsze źródło niż anonimowe repozytorium, ale ich jakości względem oryginału nie weryfikowałem. Punktem odniesienia był speakleash/Bielik-11B-v3.0-Instruct-FP8-Dynamic, zmierzony dzień wcześniej tym samym skryptem.

Qwen3.8-27B ma w konfiguracji architekturę Qwen3_5ForConditionalGeneration. To model multimodalny i hybrydowy: obok zwykłej uwagi ma warstwy liniowe (w logach vLLM jako GDN i „Mamba cache”). To będzie miało znaczenie przy TTFT.

Pierwszy start: OOM przy profilowaniu CUDA graphs​

Pierwsze uruchomienie NVFP4 skończyło się brakiem pamięci. Log raportował 19,92 GiB po załadowaniu modelu, przy drugim, udanym starcie 19,05 GiB (różnicy nie badałem):

torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 12.25 GiB.
GPU 0 has a total capacity of 31.36 GiB of which 9.78 GiB is free.

Błąd padł w profile_cudagraph_memory. vLLM domyślnie przygotowuje się na 256 równoległych sekwencji (max_num_seqs) i pod tę liczbę szacuje pamięć grafów. Benchmark idzie najwyżej do 16 równoległych zapytań, więc ograniczyłem to wprost. Wyłączyłem też wejście obrazowe, bo test dotyczy tylko tekstu. Obie zmiany weszły naraz, więc nie wiem, która z nich usunęła OOM. Wariant INT4 uruchamiałem od razu z tymi flagami:

docker run -d --name vllm-qwen-int4 --gpus all --shm-size 8g \
-p 127.0.0.1:8000:8000 \
-v ~/models/hf:/root/.cache/huggingface \
-e VLLM_NO_USAGE_STATS=1 -e DO_NOT_TRACK=1 -e HF_HUB_DISABLE_TELEMETRY=1 -e HF_HUB_OFFLINE=1 \
vllm/vllm-openai:v0.30.0 \
--model RedHatAI/Qwen3.8-27B-INT4 --served-model-name qwen-27b \
--gpu-memory-utilization 0.9 --max-model-len 16384 --max-num-seqs 16 \
--limit-mm-per-prompt '{"image":0,"video":0}'

Z tymi flagami oba warianty wystartowały bez błędów, w ok. 2 minuty przy wagach już na dysku. Wariant NVFP4 uruchamiałem tym samym poleceniem, zmieniając tylko --model.

Metoda pomiaru​

Skrypt w Pythonie (tylko biblioteka standardowa) wysyła zapytania do API zgodnego z OpenAI w trybie strumieniowym i mierzy:

  • TTFT, czyli czas do pierwszego tokenu treści,
  • tok/s na zapytanie, liczone od pierwszego tokenu do końca odpowiedzi,
  • tok/s łącznie, czyli sumę tokenów ze wszystkich zapytań podzieloną przez czas całej rundy.

Każda runda to 1, 4, 8 albo 16 jednoczesnych zapytań z tym samym krótkim poleceniem po polsku. Polecenie prosi o około 200-słowne podsumowanie przepisów, limit to 400 tokenów, temperatura 0,2. Przed pomiarem idzie jedno zapytanie rozgrzewające.

Przy Qwenie trzeba było zmienić jedną rzecz. Qwen3.x domyślnie zaczyna od rozumowania w bloku <think>, więc przy limicie 400 tokenów większość odpowiedzi poszłaby na rozumowanie, a „pierwszy token treści” przyszedłby po kilku sekundach. Skrypt dostał opcję NO_THINK=1, która dokłada do zapytania:

"chat_template_kwargs": {"enable_thinking": false}

Moc i temperaturę GPU zbierał nvidia-smi co 0,5 s, zapisując do pliku przez cały czas testu.

Wyniki​

Qwen NVFP4 (NVIDIA)Qwen INT4 (Red Hat)Bielik 11B FP8
Wagi w VRAM (log vLLM)19,05 GiB16,84 GiB10,67 GiB
KV cache74 638 tokenów161 912 tokenów37 184 tokeny
Równoległych zapytań po 16k (wg vLLM)4,56×9,88×2,27×
VRAM łącznie po starcie26,9 GB27,2 GB19,6 GB
Kernel wagFlashInferCutlassNvFp4 (+ część warstw FP8)Marlin (W4A16)CutlassFP8
1 zapytanie68 tok/s, TTFT 90 ms86 tok/s, TTFT 43 ms112 tok/s
4 równolegle, łącznie237 tok/s286 tok/s434 tok/s
8 równolegle, łącznie474 tok/s605 tok/s854 tok/s
16 równolegle, łącznie906 tok/s (59 na zapytanie)1076 tok/s (71 na zapytanie)1643 tok/s (106 na zapytanie)
TTFT przy 16 równolegle (mediana / maks.)169 / 170 ms285 / 287 msnie zapisano
GPU maks. pod testem421 W, 50 °C526 W, 55 °C412 W, 46 °C

Źródła liczb: logi vLLM (wagi, KV cache, kernele), nvidia-smi (VRAM, moc, temperatura) i wyjście skryptu pomiarowego. Qwen mierzony 2026-10-06, Bielik 2026-10-05, rozmiary repozytoriów z API Hugging Face odczytane 2026-10-06.

Uwaga do kolumny Bielika: działał z --gpu-memory-utilization 0.6 i domyślnym max_num_seqs, a Qwen z 0,9 i 16. Liczby KV cache i VRAM nie są więc porównywalne między Bielikiem a Qwenem. Prędkości generowania są bliższe porównywalności, ale też nie w pełni.

INT4 kontra NVFP4​

INT4 był szybszy w każdej rundzie:

  • 1 zapytanie: 86 vs 68 tok/s, +26%,
  • 4 równolegle: 286 vs 237 tok/s, +21%,
  • 8 równolegle: 605 vs 474 tok/s, +28%,
  • 16 równolegle: 1076 vs 906 tok/s, +19%.

Jednocześnie wagi INT4 zajęły o 2,2 GiB mniej, a KV cache wyszedł 2,17 raza większy przy tym samym gpu_memory_utilization. Przy długich dokumentach to ważniejsze niż sama prędkość. Lakshmanan wymienia „więcej pamięci GPU pod KV cache” jako jedną z dźwigni czasu do pierwszego tokenu (Lakshmanan i in., Generative AI Design Patterns, Pattern 27 Degradation Testing, s. 620–624). To ogólna zasada, nie pomiar INT4 kontra FP4.

Dlaczego NVFP4 przegrał, nie wiem. W logu vLLM widać, że w wariancie NVIDII część warstw liniowych (QKVParallelLinear, MergedColumnParallelLinear, RowParallelLinear) dostała kernel FlashInferFP8ScaledMMLinearKernel, a nie FP4. To tłumaczyłoby większe wagi w VRAM, ale związku z prędkością nie badałem. Wynik dotyczy tej konkretnej kombinacji: tych dwóch repozytoriów, vLLM v0.30.0 i sterownika 595. Nowsza wersja vLLM albo inne kernele FP4 mogą go odwrócić.

Słaby punkt INT4: pobór mocy. Pod pełnym obciążeniem karta doszła do 526 W (przy NVFP4 do 421 W). Więcej tokenów na sekundę kosztuje tu więcej watów. Tokenów na wat nie liczyłem, bo pomiar mocy był próbkowany, a nie całkowany.

Bielik kontra Qwen​

Bielik 11B jest szybszy przy jednym zapytaniu (112 vs 86 tok/s) i przy 16 (1643 vs 1076 tok/s łącznie). Nie świadczy to o tym, że jest „lepszy”, bo ma 11 mld parametrów wobec 27 mld. Lakshmanan opisuje właśnie tę pułapkę przy wzorcu Small Language Model. W ich pomiarze mniejsza Gemma była prawie trzykrotnie szybsza od większej, ale na zadaniu dokumentowania kodu zwracała prozę zamiast docstringów (Lakshmanan i in., Generative AI Design Patterns, Pattern 24, s. 587–602). Prędkość mniejszego modelu jest spodziewana, a jej cenę w jakości trzeba zmierzyć osobno.

Po polsku obie wersje Qwena i Bielik odpowiedziały językowo poprawnie. Merytoryki nie oceniałem. Jak bardzo „płynnie” nie znaczy „prawdziwie”, pokazałem w rankingu jakości siedmiu lokalnych modeli. Tam najszybszy model wypadł najgorzej.

Błąd w pomiarze TTFT: prefix cache nie działał tam, gdzie zakładałem​

Po teście Bielika zapisałem, że TTFT 15–53 ms jest zaniżony, bo krótki, powtarzany prompt trafia w prefix cache. Po teście Qwena dopisałem to samo zastrzeżenie i do niego. Dopiero sprawdzenie logów pokazało, że przy Qwenie to nieprawda:

vllm-qwen-int4 Prefix cache hit rate: 0.0% (4 odczyty)
vllm-qwen-nvfp4 Prefix cache hit rate: 0.0% (4 odczyty)
vllm-bielik Prefix cache hit rate: 66.7–77.3%

Dla modelu hybrydowego vLLM ustawia w logu Mamba cache mode 'align' i przy tym krótkim prompcie nic nie trafiło do cache. Przyczyny nie badałem. Moja hipoteza: prompt jest za krótki, żeby wypełnić blok w tym trybie. Skutki:

  • TTFT Qwena to zimny start na krótkim prompcie, nie trafienie w cache,
  • TTFT Bielika był liczony głównie na trafieniach w cache, więc porównanie TTFT faworyzuje Bielika,
  • żadna z tych liczb nie mówi, ile się czeka na pierwszy token przy długim dokumencie. Prefill 10 tys. tokenów to inna praca niż prefill 66.

Enberg opisuje tę samą zależność ogólniej: cache poprawia średnią, ale wysokie percentyle zostają czasem chybienia (Enberg, Latency, s. 207–208; tam o cache aplikacyjnym, przeniesienie na prefix cache w vLLM to mój wniosek). Gdybym mierzył tylko Bielika, opublikowałbym czas trafienia jako czas odpowiedzi.

Czego ten test nie mierzy​

  • Jakości. Ani bezwzględnej, ani różnicy między NVFP4 a INT4.
  • Długiego wejścia. Prompt miał 66 tokenów (licznik prompt_tokens vLLM dla Qwena, z szablonem czatu), a KV cache 75–162 tys. tokenów nie był nawet w części wykorzystany.
  • Degradacji pod obciążeniem. Cztery punkty (1, 4, 8, 16) to za mało, żeby znaleźć punkt nasycenia. Lakshmanan zwraca uwagę, że trzeba ustalić, „how service quality will start to degrade, not just the point at which it fails” (Pattern 27, s. 619). To osobny test.
  • Rozkładu opóźnień. Każda runda to jedna fala jednoczesnych zapytań, więc mediana i maksimum TTFT pochodzą z 1–16 próbek, bez p95/p99.
  • Wpływu samego pomiaru. Generator zapytań działał na tej samej maszynie co vLLM. Enberg opisuje ten efekt obserwatora jako źródło zniekształceń pomiaru opóźnień (Latency, s. 71). Przy najwyżej 16 wątkach Pythona wobec obciążonego GPU spodziewam się małego wpływu, ale go nie zmierzyłem.

Dla porównania budżetów: Osmani podaje jako cel dla aplikacji z LLM TTFT poniżej 500 ms przy ciepłym starcie i poniżej 1 s przy zimnym (Osmani, Web Performance Engineering in the Age of AI, s. 210). Mediany i maksima TTFT Qwena z tego testu (najwyżej 287 ms) mieszczą się w tym z zapasem, ale tylko dlatego, że prompt był krótki.

Wnioski​

  1. Na RTX 5090 z vLLM v0.30.0 Qwen3.8-27B w INT4 (Red Hat) był szybszy od NVFP4 (NVIDIA) o 19–28% i miał 2,17 raza więcej KV cache. Etykieta „natywne FP4” nie wystarczy, żeby wybrać kwantyzację. Trzeba to zmierzyć na własnej karcie i własnej wersji silnika.
  2. 27B w 4 bitach mieści się w 32 GB z dużym zapasem na kontekst. NVFP4 na domyślnych ustawieniach skończył się OOM przy profilowaniu grafów. Start przeszedł po ograniczeniu max_num_seqs do 16 i wyłączeniu wejścia obrazowego, bez rozdzielenia, która zmiana zadziałała.
  3. Przy modelach z rozumowaniem trzeba jawnie wyłączyć tryb myślenia, inaczej TTFT i tok/s mierzą coś innego niż u modelu bez niego.
  4. Zanim zapiszesz „TTFT zaniżony przez cache”, sprawdź hit rate w logach. U mnie to samo zdanie było prawdziwe dla jednego modelu i fałszywe dla drugiego.

Następny krok to test na prawdziwych, długich dokumentach: jakość odpowiedzi po polsku, realny TTFT przy zimnym prefillu i punkt, w którym przepustowość przestaje rosnąć. Dopiero wtedy będzie wiadomo, czy jakość Qwena 27B jest warta utraty 23–35% prędkości względem Bielika 11B (86 vs 112 tok/s przy jednym zapytaniu, 1076 vs 1643 przy szesnastu).