Vissza a blogra

Kövesd a gyártói doksit, és háromszor lassabb leszel

Két gyártó, ugyanaz a gép, ellentétes ajánlás — 10 mérési pont, 0 keresztezés

Van egy DGX Sparkod. Letöltöd a Qwen3.6-35B-A3B-t NVFP4-ben, elolvasod a model cardot, bemásolod az indítási parancsot. Kész.

Csak épp három NVFP4-kvantálás létezik ugyanabból a modellből, két különböző szervezettől, és a két kártya egymásnak ellentmondó indítási parancsot ad ugyanarra a hardverre. Nem árnyalatnyi eltérés: az egyik kifejezetten megtiltja azt, amit a másik előír.

Lemértük. A rossz választás ezen a gépen 2,0–3,3-szoros lassulás. Nyilvános a backend-mérés teljes mátrixa GB10-en.

A helyzet

Az NVIDIA saját NVFP4-kvantálásának model cardján van egy külön DGX Spark szekció. A javasolt indítás lényege:

--attention-backend flashinfer --moe-backend marlin

Az Unsloth ugyanennek a modellnek a saját NVFP4-kvantálásához, szintén a DGX Sparkot néven nevezve, azt írja, hogy a Marlin backendet ne használd, mert kétszer lassabb. Helyette:

--moe-backend flashinfer_b12x --linear-backend flashinfer_b12x

Ugyanaz a GB10 chip. Ugyanaz az alapmodell. Ugyanaz a kvantálási formátum. Ellentétes utasítás.

Az egyik téved. A kérdés csak az, melyik, és mennyivel.

Amit mértünk

Három NVFP4-checkpoint és a produkciós FP8-referencia, egy gépen (GB10, 128 GB unified), vLLM v0.24.0, rögzített image-digesttel:

kulcsméretkvantálásjellegzetesség
nv21,80 GiBmodelopt_fp4statikus aktivációs skálák
uns24,66 GiBcompressed-tensorsa 32–39. réteg expertjei FP8-on
uns-fast22,01 GiBcompressed-tensorsaz nv bájt-ikre
FP834,88 GiBfp8 (block)a jelenlegi produkciós modell

Az uns és az uns-fast közti egyetlen különbség: az utolsó nyolc réteg expertjei az előbbinél FP8-ban maradtak, +2,65 GiB. Az nv és az uns-fast viszont bájtra ikrek, 30841 vs 30840 packed FP4-tenzor, ugyanaz a 180 magas precizitáson hagyott súly. Erre még visszatérünk, mert ez a cikk legkellemetlenebb adata.

A backend-sweep konfigurációja: egy modell (uns-fast), egy instance, GPU_UTIL=0.85, 262144 kontextus, max_num_seqs=16, spekulatív dekódolás kikapcsolva, 60 másodperces mérések c=1, 2, 4, 8, 16 párhuzamosságon, mindkét backenden. Minden futás után ellenőrizve a szerver-logból, hogy ténylegesen az a backend töltődött be, amit kértünk. A vLLM ugyanis csendben visszaesik másikra, és ilyenkor nem azt méred, aminek a fejlécében hiszed.

Az eredmény

párhuzamosságb12x (tok/s)marlin (tok/s)marlin előnye
c=135,370,62,0×
c=253,3114,12,1×
c=467,8178,82,6×
c=886,2283,03,3×
c=16149,7383,92,6×
Oszlopdiagram a marlin és a flashinfer_b12x backend aggregált átbocsátásáról öt párhuzamossági szinten. A marlin minden ponton magasabb: c=1-en 2,0×, c=2-n 2,1×, c=4-en 2,6×, c=8-on 3,3×, c=16-on 2,6× az előnye. A mérés az uns-fast checkpointon, max_num_seqs=16 mellett, spekuláció nélkül készült.

Tíz mérési pont, és egyetlen olyan sincs, ahol a b12x nyerne. Nincs keresztezési pont, nincs olyan terhelési tartomány, ahol az Unsloth ajánlása megérné.

Ezt előre nem így vártam. A korábbi, gyártó-ajánlott körökben a b12x egyszer jobbnak látszott egyetlen stream mellett, de az per-token dekódolási sebesség volt, nem fenntartott átbocsátás. Amint sustained throughputot mérünk, már c=1-en is kétszeres a marlin. A „single-streamen azért nyer” mentőöv sem létezik.

Egy melléklelet: a b12x instabil is. Az első sweep c=8-on 44 tok/s-t adott, a megismételt futás 86-ot, kétszeres szórás ugyanazon a konfiguráción. A marlin ugyanitt 276 és 283, azaz 2,5%-on belül. Nem csak lassabb, hanem megbízhatatlanabb is.

Miért? — a hipotézis, ami megbukott

Volt egy tetszetős magyarázatunk. A FlashInfer autotuner indításkor „bucketeket” hangol be bizonyos tenzoralakokra, és ha a futás közben előálló batch-alakok kiesnek a behangolt tartományból, tartalék kernelre esik vissza. Az NVIDIA checkpointjának indítási logjában látszik is egy ilyen figyelmeztetés, ami szó szerint teljesítmény-szakadékot (perf cliff) emleget, és a tuning-tartomány kibővítését javasolja.

Kézenfekvő volt: c=1-en még a behangolt tartományban vagyunk, c≥4-en már nem, innen a szakadék.

Nem ez az. A teljes sweep alatt mentett szerver-logokban a szakadékra utaló minták előfordulási száma mindkét backenden nulla. A JIT-fordítási figyelmeztetések száma is azonos (5 és 5), és azok is a közös DeltaNet-kernelekhez tartoznak, nem a MoE-úthoz.

Marad a prózaibb magyarázat: a b12x kernel a B200 tenzormagjaira készült, és a GB10 sm_121a architektúrájára egyszerűen nem képződik le jól. Nem konfigurációs hiba, nem hangolási kérdés, rossz kernel a rossz chipen.

Ez fontos különbség. Ha tuning-probléma lenne, meg lehetne javítani. Így viszont az Unsloth ajánlása nem „finomhangolatlan”, hanem érvénytelen ezen a hardveren.

És akkor jön a második meglepetés

A tanulság, amit ebből az ember levonna: „használj marlint”. Ez viszont hamis általánosítás, és pont ez a cikk legfontosabb mondata.

Ugyanezt a backend-kört lefuttattuk a produkciós FP8-checkpointra is. A vLLM hat FP8 MoE-backendet kínál elvben. A valóság GB10-en:

backenderedmény
triton✅ 256,1 tok/s — a legjobb
marlin✅ 232,0 tok/s — működik, de 9%-kal rosszabb
deep_gemm❌ assert-crash sm_121a-n (és ez az alapértelmezett választás)
cutlass❌ letiltva erre a konfigurációra
flashinfer_cutlass❌ nem támogatja a block-FP8 sémát
flashinfer_trtllm❌ a kernel nem támogatja ezt az eszközt
A hat FP8 MoE backend állapota GB10-en: a triton működik 256,1 tok/s-mal (a legjobb), a marlin működik 232,0 tok/s-mal (9%-kal rosszabb), a deep_gemm, cutlass, flashinfer_cutlass és flashinfer_trtllm pedig el sem indul. Hatból négy elbukik az indításnál.

Hatból négy el sem indul. És a hatodik, ami elindulna alapértelmezésben, assert-tel elszáll, az FP8-modell GB10-en csak akkor jön fel egyáltalán, ha explicit kikapcsolod a deep_gemm-et.

De a lényeg a maradék kettő: NVFP4-en a marlin nyer, FP8-on a marlin veszít. Ugyanazon a gépen, ugyanazon a vLLM-verzión, ugyanabból a modellcsaládból. A helyes backend nem a hardver tulajdonsága, hanem a hardver és a kvantálási formátum párosáé.

Ez az, amit egyik model card sem mond ki, és amit nem lehet átvinni egyik modellről a másikra.

A gyártói dokumentáció mint aknamező

Ha már itt tartunk, néhány további dolog, amit a kártyák nem említenek:

Halott környezeti változók. Az NVIDIA DGX Spark-parancsában szereplő két környezeti változóra a vLLM v0.24.0 azt válaszolja, hogy nem ismeri őket: VLLM_FP8_MOE_BACKEND és VLLM_USE_FLASHINFER_MOE_FP4. Beállítod, exportálod, semmi nem történik. A parancs attól még lefut, csak épp nem azt csinálja, amit hiszel.

A spekulatív dekódolás draft-modellje külön backendet kíván. Mindhárom NVFP4-checkpoint kihagyja az MTP-modult a kvantálásból, a draft-fej BF16 marad. Ezt viszont sem a marlin, sem a b12x nem tudja betölteni, mert azok kvantált súlyokra valók. A megoldás egy külön kapcsoló a spekulatív configban:

--speculative-config={"method":"mtp","num_speculative_tokens":2,"moe_backend":"triton"}

Az NVIDIA kártyáján ez a triton ott szerepel. Hogy miért, azt nem magyarázza meg senki. Ha kihagyod, a szerver el sem indul, és a hibaüzenetből nem nyilvánvaló, hogy a draft modellről van szó.

A kvantálatlan draft-fej egyébként mérhető nyereség. Mindhárom NVFP4-változat 73–75%-os spekulációs elfogadási arányt ér el, a produkciós FP8 71%-ot. Megnéztük, miért: az FP8-checkpointban az MTP-modul 775 tenzora kvantálva van (F8_E4M3), az NVFP4-változatokban nem. A teljes precizitású draft-fej jobban jósol. Ez a fajta információ egyik kártyán sincs rajta, a súlyfájlok fejlécéből olvasható ki, GPU nélkül, két perc alatt.

Amit valóban futtass

Ha van DGX Sparkod és ezt a modellcsaládot szolgálod ki:

NVFP4-checkpointokhoz (mindháromhoz, gyártótól függetlenül):

--moe-backend=marlin
--speculative-config={"method":"mtp","num_speculative_tokens":2,"moe_backend":"triton"}

Környezet: CUTE_DSL_ARCH=sm_121a, FLASHINFER_DISABLE_VERSION_CHECK=1. A VLLM_USE_FLASHINFER_MOE_FP4 és a VLLM_FP8_MOE_BACKEND mehet a kukába.

Block-FP8 checkpointokhoz:

VLLM_USE_DEEP_GEMM=0

… és hagyd, hogy a vLLM válasszon, tritont fog, és az a helyes. A marlint itt ne erőltesd, 9%-ot veszítesz vele.

És mindkét esetben: indítás után nézd meg a logban, mi töltődött be ténylegesen. A vLLM kiírja. Ha nem az van ott, amit kértél, akkor a mérésed, vagy a produkciód, nem arról szól, amiről hiszed.

A teljes kép

Az azonos backenden (marlin) és azonos spekulatív beállítással mért végeredmény, párhuzamos terhelés mellett:

checkpointaggregált átbocsátás (c=8)egyszálú JSON-kinyerés
nv359,4 tok/s115,7 tok/s
uns-fast338,0 tok/s103,9 tok/s
uns326,1 tok/s99,1 tok/s
FP8 (triton)256,1 tok/s68,3 tok/s
Sávdiagram a négy checkpoint aggregált átbocsátásáról c=8 párhuzamosságon, azonos marlin backenden, MTP spekulációval: nv 359,4 tok/s (+40%), uns-fast 338,0 tok/s (+32%), uns 326,1 tok/s (+27%), a produkciós block-FP8 pedig 256,1 tok/s a referencia.

Az NVFP4 tehát +40% párhuzamos terhelésen és +69% egyszálú kinyerésen a produkciós FP8-hoz képest. Ez az összevetés gépben, vLLM-verzióban és spekulatív beállításban is tiszta: mindkettő ugyanazon a gépen, ugyanazon a motoron futott, és mindkettő a saját legjobb backendjével.

Ez a szám valós. Ami mögötte van, az viszont nem teljesen az, aminek látszik.

Amit nem tudunk megmagyarázni

Két adat, amit a szokásos modell nem ír le, és jobb kimondani, mint elhallgatni.

A bájt-ikrek nem egyformák. Az nv és az uns-fast súlytömege 1%-on belül azonos, a packed FP4-tenzorok száma 30841 és 30840. Ugyanaz a kernel, spekuláció nélkül, mégis 7,9% a különbség egyszálú dekódolásban. Sávszélességgel ezt nem lehet magyarázni: nincs mit magyarázni, azonos a beolvasandó adatmennyiség. A legvalószínűbb jelölt a kiszolgálási út: az nv statikus aktivációs skálákat használ, mindkét Unsloth-változat futásidőben számoltat. Ezt nem izoláltuk.

Fordítva ugyanez: az uns aktív expert-útja rétegenként 15,4%-kal több bájt, a sávszélesség-modell tehát −15% körüli büntetést jósolna. A mért különbség −3,0%. Négyszeres tévedés.

Vagyis: a formátumok közti ugrást (FP8 → NVFP4, 35 → 22 GiB) a súlycsökkenés rendesen megmagyarázza. A három NVFP4 közti 3–8%-os szórást nem. Ott valami más dönt, és ezzel a méréssorozattal nem tudtuk megfogni.

A vegyes terhelésű mérés instabil. A vegyes profil háromszori ismétlése 239,9, 271,2 és 292,6 tok/s-t adott, ±10% szórás ugyanazon a konfiguráción. Az egyenletes profil ehhez képest stabil. Ez azt jelenti, hogy a vegyes oszlop bármelyik két szomszédos értéke között nem lehet rangsorolni, és minden ilyen alapú állítás hibahatárral megy. A fenti táblázat ezért az egyenletes profilt mutatja.

Összefoglalva

Ha a saját gyártód dokumentációját követed, a te géped a te modelleddel három és félszer lassabb is lehet, mint amit tudna. Nem azért, mert rossz a hardver, és nem azért, mert rosszul konfiguráltad, hanem mert az ajánlás egy másik chipre készült, és senki nem mérte le a tiéden.

Négy dolog, amit érdemes hazavinni:

  1. A backend-választás 2–3,3-szeres tétel. Nagyobb, mint bármi más, amit ebben a méréssorozatban találtunk — nagyobb, mint a kvantálási variánsok közti különbség, nagyobb, mint a spekulatív dekódolás nyeresége.
  2. A helyes backend nem hordozható formátumok között. NVFP4-en marlin, block-FP8-on triton, ugyanazon a gépen.
  3. A gyártói ajánlás a gyártó hardverére vonatkozik, akkor is, ha a tiédet néven nevezi. Az Unsloth számai B200-on, nagy párhuzamosságon készültek. Ott lehet, hogy igazuk van. Nálad nincs.
  4. Indítás után ellenőrizd a logot. Ez az egyetlen hely, ahol kiderül, hogy a rendszer azt csinálja-e, amit kértél.

A méréssorozat során háromszor jósoltam meg előre, mi fog kijönni, és mindháromszor tévedtem: azt vártam, hogy lesz keresztezési pont a két backend között (nincs), hogy a tuning-bucket magyarázza a szakadékot (nem), és hogy az óvatosabb kvantálás jobban spekulál (rosszabbul). Ezért mérünk.


A méréssorozat egy GB10-alapú fejlesztői gépen készült, vLLM v0.24.0-n, rögzített image-digesttel. Minden szám hibamentes futásokból származik; a hibás futások eldobásra kerültek. A perf-mérések során mindig egyetlen modell futott a gépen. A backend-sweep 60 másodperces mérésekből áll, a mediánok egyszálú méréseknél 1 bemelegítő + 3 mért futásból. A vegyes profilú mérések ismétlési szórása 10%, ezt a szöveg jelöli, ahol releváns.