Szétválasztott előkitöltés és dekódolás

Az előkitöltés és a dekódolás eszközök közötti szétválasztása felgyorsítja az LLM-következtetést és javítja a teljesítményt

SzerzőkPerplexity Research

Annak érdekében, hogy a bemeneti promptból kimeneti tokeneket generáljunk, az LLM-következtetés két szakaszra oszlik: előkitöltésre (prefill) és dekódolásra. Az előkitöltés a bemeneti tokeneken fut, feltöltve a KV-gyorsítótárakat, mielőtt belépne a dekódolási szakaszba, amely tokenenként generál. Míg egyetlen dekódolási lépés általában néhány tíz milliszekundumig tart, az előkitöltés lényegesen hosszabb ideig. Ugyanazon az eszközön futtatva az előkitöltés és a dekódolás keverése rontja a dekódolási teljesítményt. Ebben a cikkben egy bevált megoldást vizsgálunk meg a szétválasztott előkitöltés és dekódolás formájában, amelyek külön eszközökön futnak, hogy maximalizálják az előkitöltési áteresztőképességet és a dekódolási késleltetéseket.

Előkitöltés vs. dekódolási teljesítmény

Egy tipikus LLM-kiszolgáló motorban a kötegelő-ütemező kiválasztja a feldolgozandó kéréseket a modell egyes végrehajtási lépéseiben. Egyetlen eszközön vagy csomóponton futtatva mind az előkitöltési, mind a dekódolási kérések kötegelve vannak. A szekvenciahossz mentén összesítő figyelem költsége mind az előkitöltés, mind a dekódolás esetében nő, arányosan a KV-gyorsítótár bejegyzéseinek hosszával (kv_len). A dekódolási kérések általában egyetlen tokent továbbítanak (qo_len=1), minimális költséggel a többi rétegen keresztül, amelyek függetlenül működnek a szekvencia tokenein. Az előkitöltési kérések ezer vagy tízezer tokent továbbítanak jelentős költséggel a sűrű rétegeken keresztül (nagy qo_len).

Az előrehaladó menet (forward pass) késleltetését erősebben befolyásolja a sűrű rétegeken átadott független tokenek száma (qo_len), mint a figyelem során a KV-gyorsítótárból lekért tokenek száma (kv_len). A figyelem képes párhuzamosítani mind a kérések száma, mind a szekvenciahosszal arányos kv_len szerint, jó kihasználtságot elérve. Az előkitöltés számítási korlátos (compute bound): mivel a qo_len magas, a GEMM-magok elegendő blokkot tudnak lefoglalni az M dimenzió mentén a modern GPU-k számítási képességeinek teljes kihasználásához. A dekódolás memóriakorlátos (memory bound): a jellemzően alacsony kötegméretek miatt az M mentén lévő bemenetek száma általában kicsi, ami csak egy blokkhoz elegendő. Míg a Split-K GEMM-magok javíthatják az SM kihasználtságát alacsony token-kötegméretek esetén, a gyorsítótárak és a mátrixszorzó egységek általában alulhasználtak maradnak.

Kérések kötegelése egy transzformerrétegen keresztül, a lekérdezés- és kulcs/érték-projekciók, a figyelem és a KV-cache-frissítések bemutatásával.

Összekeverésük esetén az előkitöltési kéréseket tartalmazó kötegek nagyobb késleltetést okoznak az előrehaladó meneten keresztül, negatívan befolyásolva a teljes példány dekódolási áteresztőképességét. Míg az előkitöltési kérések és a dekódolási kérések keverése vagy a darabolt előkitöltés (chunked prefill) alkalmazása kissé javíthatja a dekódolási teljesítményt, nehéz elegendő előkitöltési áteresztőképességet fenntartani ahhoz, hogy elég kérést dolgozzunk fel egy példányon a dekódolási áteresztőképesség maximalizálásához. Nagy modellek és tipikus kimeneti hosszak esetén a dekódoláshoz szükséges nagy kötegméret fenntartásához az előkitöltést elég gyakran kell elvégezni ahhoz, hogy az jelentősen rontsa az átlagos késleltetést, és akadozást okozzon a kimenetben.

Vegyes prefill–decode ütemezés három kérésthez, megosztott idővonalon bemutatva a prefill- és decode-időközöket.
Szétválasztott prefill–decode ütemezés, dedikált prefillert használva, amely három kérés dekódolási munkáját táplálja.

Ezeket a problémákat különálló csomópontkészlet használatával lehet kezelni az előkitöltés és a dekódolás elvégzéséhez. Ha egy előkitöltő csomópontot több dekódoló csomóponttal társítunk, elegendő kérés ütemezhető az előkitöltéshez az áteresztőképesség maximalizálása és a dekódoló csomópontokon elég sok egyidejű kérés fenntartása érdekében a dekódolási áteresztőképesség maximalizálásához is. Az előkitöltő csomópontok feltöltik a KV-gyorsítótárakat, amelyeket aztán átvisznek a dekódoló csomópontokra. Mivel a dekódolóknak többé nem kell megszakítaniuk a folyamatot az előkitöltés miatt, a késleltetések sokkal determinisztikusabbá válnak, mivel az aktív kérések növekvő kv_len-ének összhatása sokkal kevésbé szembetűnő. Ennek ára az első token generálásáig eltelt idő (TTFT) növekedése, mivel a KV-gyorsítótárak hálózaton keresztüli átvitele tíz- vagy százmilliszekundumot is igénybe vehet.

KV Messenger

A Perplexitynél a szétválasztott előkitöltés és dekódolás megvalósítása egy KV-üzenetküldő (messenger) köré épül, amely interakcióba lép az LLM-motorral, hogy hálózaton keresztül vezérelje a KV-gyorsítótár-átviteleket az előkitöltő csomópontokról a dekódoló csomópontokra. Az előkitöltő oldalon az üzenetküldő elfogadja a kéréseket a dekódoló csomópontoktól, átadja azokat a kötegelő-ütemezőnek, és nyomon követi az előrehaladó menet végrehajtását, hogy a lehető legkisebb késleltetéssel indítsa el a KV-gyorsítótárakat. A dekódoló oldalon, a ki nem lakoltatható lapok lefoglalása után, az üzenetküldő blokkolja a kérés dekódolásra történő ütemezését mindaddig, amíg értesítést nem kap a KV-gyorsítótár és a dekódoló kontextus átvitelének befejezéséről.

Egy szétválasztott kérés-életciklus, amely a dekóder és a prefiller közötti koordinációt, valamint a KV-cache-adatok átvitelét mutatja.

Az előkitöltés szétválasztása nagy áteresztőképességű, alacsony késleltetésű kapcsolatokat igényel, ezért megvalósításunkat RDMA-ra szabtuk, támogatva mind az EFA, mind a ConnectX hálózati illesztővezérlőket (NIC). A KV Messenger a libfabric-re épül, a fabric-lib burkolóinkat (wrapper) használva, hogy magasabb szintű, alacsony késleltetésű absztrakciókat biztosítson a távoli közvetlen memóriaelérés (RDMA) primitívjei felett, megvalósítva a hatékony lap- és metaadat-átviteleket, valamint az alacsony késleltetésű jelzést. A háttérben a fabric-lib koordinálja a GPU-t és a közvetlenül csatlakoztatott NIC-eket az adatok előkitöltő csomópontról a dekódoló csomópontra történő másolásához.

A kézhezvétel után az előkitöltő csomópont lefoglalja a forrás KV-lapok megfelelő készletét, és a helyi motor használatával ütemezi a kérést előkitöltésre. A késleltetés minimalizálása érdekében az átvitelek nem várják meg az előrehaladó menetet: ehelyett a KV-lapok másolása azonnal elindul, amint a modell befejezi a KV-gyorsítótár bejegyzések hozzáfűzését az egyes rétegek KV-gyorsítótárához. Mivel az előkitöltési kérések darabolhatók, a kötegelő-ütemező végrehajtás előtt értesíti a KV-üzenetküldőt az aktuálisan ütemezett darabokról. Annak érdekében, hogy támogatni tudja a CUDA-gráfokat, miközben képes nyomon követni a rétegeket, az üzenetküldő fenntart egy erre a célra szolgáló szálat, amely egy számlálót kérdez le (polling), amelyet a figyelem kimeneti vetülete után növelnek. A számláló csak a sharded környezet fő csomópontján van karbantartva: annak ellenére, hogy a KV-gyorsítótár bejegyzések érvényesek a hozzáfűzés után és a figyelem előtt, a kimeneti vetület csökkentve van a rangok között, implicit módon szinkronizálva azokat. Amikor a számláló változása észlelhető, az üzenetküldő értesítést kap, és meghívja a fabric-lib-et egy réteg átvitelének elindításához.

Rétegről rétegre történő KV-cache-átvitelek, amelyek a lapozott és az egyszeri írásokat, valamint a befejezési számlálókat mutatják a prefiller és a dekóder között.

Az utolsó darab átvitelének befejezése után a további metaadatok másolása is megtörténik: a spekulatív dekódolás vagy az MTP megköveteli a logitek és rejtett állapotok átmozgatását a dekódolóra. Ezeket a másolatokat szintén RDMA-n keresztül hajtják végre, az előre lefoglalt pufferekbe és azokból.

Az utolsó darab összes függőben lévő átvitelének befejezésekor az előkitöltő csomópont felszabadítja (de-allocates) a KV-lapokat, és befejezi a kérést. A dekódoló csomópont nem kap explicit értesítést: ehelyett azonnali számlálókat használ a befejezett műveletek számának nyomon követésére. Az RDMA-műveletek száma az előkitöltő oldalon arányos átvitt lapok számával. A lap- és kontextusmásolások ismert számának befejezésekor a fabric-lib hívja a KV-üzenetküldőt, jelezve, hogy egy kérés készen áll a dekódolásra. Az üzenetküldő felszabadít minden kontextust, és átadja a kérést az LLM-motornak.

Sharded KV-gyorsítótár-átvitelek

Ha az előkitöltő és a dekódoló tenzorpárhuzamosságra (Tensor Parallelism - TP) támaszkodik, és azonos módon shardolja vagy replikálja a KV-gyorsítótárakat, egyetlen átviteli motor koordinálja több eszköz küldését és fogadását az összes replika lapjaihoz. Annak érdekében, hogy egyetlen üzenetküldő és átviteli motor legyen használható annak ellenére, hogy a modell végrehajtója több eszközön és folyamaton van replikálva, a cuMem és a cuMemImportFromShareableHandle szolgál a KV-gyorsítótárakat alátámasztó eszközmemória lefoglalására és a fő folyamatba való leképezésére. Az átviteli motor megvizsgálja a csomópont topológiáját, hogy megtalálja a KV-gyorsítótár-szeletek mindegyikének átviteléhez használható, a legközelebbi NUMA-csomópontban lévő NIC-eket és CPU-kat.

Ha a forrás és a cél azonos módon shardolt, az átvitelek triviálisak, mivel egy-az-egyhez leképezés létezik a forrás és a cél eszközei és lapjai között. Ebben a helyzetben a sharding implicit módon segíti az átviteli késleltetéseket: több GPU használatával több társított NIC alkalmazható, ami közelebb kerül a teljes sávszélesség-kihasználtsághoz. Ha azonban eltérés van, az átviteli motornak szét kell osztania vagy rekonstruálnia kell a lapokat a forrás- és célszeletek közötti arány függvényében.

KV-cache-átviteli topológia egy TP/EP prefiller és DP/EP dekóderek között, a NIC-kapcsolatok bemutatásával.

Ha az előkitöltő több eszköz között osztja szét a KV-gyorsítótárat, a teljes lapok a dekódolón rekonstruálódnak a megfelelő feleknek az előkitöltő eszközökről történő elküldésével. Ha a dekódolónak több shardja van, akkor több forrástól fogad lapokat. A dekódolónak ismernie kell az előkitöltő sharding sémaszerkezetét ahhoz, hogy ki tudja számítani a várhatóan fogadandó RDMA-írások számát. Replikáció esetén az előkitöltő replikakészletekbe csoportosítja az eszközöket, amelyek magukon belül replikálják a teljes KV-gyorsítótárat. A cél-replikakészletekhez véletlenszerűen hozzárendelik az egyik forráskészletet, hogy az összes elérhető eszközt fel tudják használni az RDMA-írások elindításához.

NHD és HND KV-cache-elrendezések, összehasonlítva a tokenek és fejek sorrendjét az egyes rétegekben és lapokban.

A sharded átvitelek enyhe módosítást igényelnek a KV-gyorsítótárakban. Alapértelmezés szerint a FlashInfer az NHD elrendezésre támaszkodik, amely a fejeken belül rendezi a tokeneket egy lapon belül. Mivel a gyorsítótárak valószínűleg a figyelemfejek száma mentén vannak shardolva, ez egyenetlenséget hoz létre a fejen belül. Az RDMA-átvitelek nem támogatják implicit módon a lépésközös (strided) írásokat, fejenként egy műveletet igényelve az átvitel elvégzéséhez. Ehelyett, a libfabric-kel való interakciók számának csökkentése érdekében a KV-gyorsítótárakat az HND elrendezés használatával szervezzük meg, amely a fejdimenziót a tokenek száma elé helyezi. Ez biztosítja a folytonosságot, lehetővé téve egy lap átmásolását egyetlen írással.

Spekulatív dekódolás

A spekulatív dekódolás enyhe módosításokat igényel a szétválasztott előkitöltés-dekódolásban. Megvalósításunkban az előkitöltő csomópontok nem mintavételezhetnek tokeneket. Mivel a Perplexity Sonar modelljei támogatják a strukturált kimenetet, nem akarjuk felvállalni a sémaprocesszor-implementációk előkitöltők és dekódolók közötti szinkronizálásának összetértségét. Az MTP- és spekulatív dekódolási mechanizmusokban a vázlatmodell (draft model) utolsó tokenig történő előkitöltése magában foglalja a tokenek mintavételezését a célymodelltől.

Spekulatív dekódolás a prefiller és a dekóder között, bemutatva a KV-cache hosszát, a logiteket és a tokenmintavételezést.

Ezen problémák áthidalása érdekében az előkitöltés nem tartalmazza a bemeneti szekvencia utolsó tokenjét. Ehelyett az utolsó tokent megelőző előkitöltésből származó rejtett állapotok vagy logitek kerülnek átvitelre, és a dekódolón a következő lépésben dekódolási tokenként van kezelve. Bár ez kissé növeli a késleltetéseket, mivel az első token kiadásához teljes dekódolási lépést kell végrehajtani az előkitöltés után, a megvalósítás összetértsége nagymértékben lecsökken.

Szétválasztott üzembe helyezések

Több szétválasztott konfigurációt is üzembe helyeztünk, vagy kísérleteztünk velük különböző modellekkel, hogy támogatjuk a éles (production) adatforgalmat vagy a házon belüli kiértékelési munkaterheléseket. A modellek mérete és figyelemmechanizmusa alapján megfelelő sharding sémákat választottunk az előkitöltő és dekódoló csomópontok számára a GPU-k legjobb kihasználása érdekében.

DeepSeek-R1

A DeepSeek esetében mind a tenzorpárhuzamos (TP), mind az adatpárhuzamos (DP) üzembe helyezést megfontoltuk. Ahogy a korábbi blogbejegyzésekben tárgyaltuk, a TP üzembe helyezések jobb késleltetést biztosítanak az alacsonyabb áteresztőképesség árával, és több GPU-t igényelnek a nagy terhelés kiszolgálásához. A DP üzembe helyezések sokkal jobban skálázódnak a terheléssel, azonban a csúcs-áteresztőképességük alacsonyabb az eszközök vagy csomópontok közötti kommunikáció költsége miatt.

A DeepSeek többfejű látens figyelmre (Multi-Head Latent Attention) támaszkodik, tömörítve a KV-gyorsítótárakat. Mivel az összes KV-fej egyetlen látens vektorba van tömörítve, a TP nem tudja shardolni a KV-gyorsítótárakat, mivel ehelyett replikálnia kell a látens vektorokat az összes rangon. A sharding a dekompresszió után történik, mivel minden rang különböző fejeket tud kinyerni ugyanabból a látens reprezentációból. Következésképpen az összes KV-gyorsítótár-shard azonos az előkitöltő és a dekódoló shardok között.

Csomóponton belüli (intranode) TP-beállítással mind az előkitöltők, mind a dekódolók azonos módon vannak shardolva. Az átvitelek az összes rangról elindulnak az összes elérhető NIC teljes kihasználása érdekében. DP üzembe helyezés esetén azonban, ahol a TP-rang mérete kisebb, vagy minden DP-rang egyetlen GPU-hoz van rendelve, bármely olyan előkitöltő eszköz elküldheti, amely rendelkezik a KV-gyorsítótár replikált másolatával. A kérések összes elérhető NIC közötti kiegyensúlyozásához véletlenszerűen kiválasztunk egy GPU-t és egy NIC-et a KV-gyorsítótárnak az előkitöltőről a dekódolóra történő küldéséhez.

Vegyes előkitöltés-dekódolás mellett az R1 üzembe helyezésünk azzal küzdött, hogy következetesen túllépje az 50 TPS-t a százmilliszekundumos nagyságrendű, gyakori előkitöltési megszakítások miatt. Ezzel szemben az előkitöltés szétválasztásával kérésenként körülbelül 100 ms TTFT-büntetést szenvedtünk el, de egyetlen előkitöltő csomópont képes volt fenntartani a konzisztens kötegméreteket 3 dekódoló csomóponton, 90 TPS-t meghaladó áteresztőképességet biztosítva, miközben dekódoló csomópontonként körülbelül 1 QPS terhelést kezel. Adatpárhuzamos üzembe helyezések esetén a TPS kicsit alacsonyabb volt, 50 körül, azonban a példányok rangonként 1 QPS terhelést tudtak kezelni, csomópontonként 8 ranggal.

Qwen3-Coder

Ez a 480B-s modell csoportos lekérdezési figyelmet (Grouped-Query Attention - GQA) használ, így a figyelem könnyen shardolható, és élvezheti a tenzorpárhuzamosság előnyeit anélkül, hogy a KV-gyorsítótárak memóriáját feláldozná. Következésképpen a modellt 8 GPU között shardolhatjuk mind az előkitöltéshez, mind a dekódoláshoz, és körülbelül 3 dekódoló csomópontot párosíthatunk egyetlen előkitöltő csomóponttal. Mivel a figyelem shardolva van, az HND KV-gyorsítótár-elrendezésre támaszkodunk az előkitöltő és a dekódoló KV-gyorsítótárak shardolásához, párosítva az előkitöltő rangokat a dekódoló rangokkal, és teljesen kihasználva az összes NIC-et a szeletek párhuzamos átviteléhez.

Idézet

BibTeX
@online{perplexity2025DisaggregatedPrefillAndDecode,
  author = {{Perplexity} and Nandor Licker and Lequn Chen and Kevin Hu and Erin McKnight and Phi Hoang},
  title = {{Disaggregated prefill and decode}},
  date = {2025-08-01},
  url = {https://perplexity.coolkeypoint.com/hub/blog/disaggregated-prefill-and-decode}
}