Oddelené predbežné spracovanie a dekódovanie
Oddelenie predbežného spracovania a dekódovania naprieč zariadeniami urýchľuje inferenciu LLM a zlepšuje výkon
Aby bolo možné generovať výstupné tokeny zo vstupného promptu, inferencia LLM sa rozdeľuje na dve fázy: predbežné spracovanie (prefill) a dekódovanie (decode). Predbežné spracovanie prebieha na vstupných tokenoch a napĺňa vyrovnávacie pamäte KV (KV caches) predtým, ako sa prejde do fázy dekódovania, ktorá generuje tokeny jeden po druhom. Zatiaľ čo jeden krok dekódovania zvyčajne trvá desiatky milisekúnd, predbežné spracovanie trvá podstatne dlhšie. Ak sa spúšťajú na rovnakých zariadeniach, kombinovanie predbežného spracovania s dekódovaním zhoršuje výkon dekódovania. V tomto článku skúmame zavedené riešenie vo forme oddeleného predbežného spracovania a dekódovania, pričom tieto procesy bežia na samostatných zariadeniach, aby sa maximalizovala priepustnosť predbežného spracovania a znížila latencia dekódovania.
Výkon predbežného spracovania vs. dekódovania
V typickom obslužnom engine LLM vyberá plánovač dávok (batch scheduler) požiadavky na spracovanie v každom kroku vykonávania modelu. Pri behu na jedinom zariadení alebo uzle sa požiadavky na predbežné spracovanie aj dekódovanie dávkujú spoločne. Náklady na mechanizmus pozornosť (attention), ktorý sa agreguje pozdĺž dĺžky sekvencie, rastú pre predbežné spracovanie aj dekódovanie úmerne dĺžke položiek vo vyrovnávacej pamäti KV (kv_len). Požiadavky na dekódovanie zvyčajne posielajú ďalej jeden token (qo_len=1) s minimálnymi nákladmi cez ostatné vrstvy, ktoré pracujú nezávisle s tokenmi sekvencie. Požiadavky na predbežné spracovanie posielajú tisíce až desaťtisíce tokenov s významnými nákladmi cez husté vrstvy (dense layers, veľká hodnota qo_len).
Latencia dopredného prechodu (forward pass) je viac ovplyvnená počtom nezávislých tokenov prechádzajúcich cez husté vrstvy (qo_len) než počtom tokenov získaných z vyrovnávacej pamäte KV počas fázy pozornosť (kv_len). Mechanizmus pozornosť dokáže paralelizovať prácu tak s ohľadom na počet požiadaviek, ako aj s ohľadom na hodnotu kv_len úmerne dĺžkam sekvencií, čím dosahuje dobré využitie. Predbežné spracovanie je obmedzené výpočtovým výkonom (compute bound): vzhľadom na vysokú hodnotu qo_len dokážu jadrá GEMM alokovať dostatočné bloky pozdĺž dimenzie M na plné využitie výpočtových schopností moderných GPU. Dekódovanie je obmedzené priepustnosťou pamäte (memory bound): kvôli zvyčajne nízkym veľkostiam dávok (batch sizes) je počet vstupov pozdĺž dimenzie M zvyčajne malý, čo postačuje len pre jeden blok. Hoci jadrá Split-K GEMM môžu zlepšiť využitie SM pre malé dávky tokenov, vyrovnávace pamäte a jednotky násobenia matíc spravidla ostávajú nedostatočne využité.
Pri zmiešaní týchto procesov spôsobujú dávky obsahujúce požiadavky na predbežné spracovanie vyššiu latenciu dopredného prechodu, čo negatívne ovplyvňuje priepustnosť dekódovania celej inštancie. Hoci kombinovanie požiadaviek na predbežné spracovanie s požiadavkami na dekódovanie alebo použitie deleného predbežného spracovania (chunked prefill) môže mierne zlepšiť výkon dekódovania, je náročné udržať dostatočnú priepustnosť predbežného spracovania na spracovanie dostatočného počtu požiadaviek na inštancii tak, aby sa maximalizovala priepustnosť dekódovania. V prípade veľkých modelov s typickými dĺžkami výstupu je na udržanie veľkej veľkosti dávky pre dekódovanie nutné vykonávať predbežné spracovanie dostatočne často na to, aby to výrazne zhoršovalo priemernú latenciu a spôsobovalo trhanie výstupu.
Tieto problémy možno vyriešiť použitím samostatnej sady uzlov na vykonávanie predbežného spracovania a dekódovania. Prepojením uzla predbežného spracovania s viacerými uzlami dekódovania je možné naplánovať dostatočný počet požiadaviek na predbežné spracovanie na maximalizáciu priepustnosti a zároveň zachovať dostatočne veľký počet súbežných požiadaviek na uzloch dekódovania, čo maximalizuje priepustnosť dekódovania. Uzly predbežného spracovania napĺňajú vyrovnávace pamäte KV, ktoré sa následne prenášajú do uzlov dekódovania. Keďže dekodéry už nemusí prerušovať predbežné spracovanie, latencie sa stávajú omnoho deterministickejšími, pretože celkový vplyv rastúcej hodnoty kv_len aktívnych požiadaviek je oveľa menej výrazný. Danou daňou za to je nárast metriky TTFT (Time to First Token), keďže prenos vyrovnávacích pamätí KV cez sieť môže trvať desiatky až stovky milisekúnd.
KV Messenger
V spoločnosti Perplexity je naša implementácia oddeleného predbežného spracovania a dekódovania postavená okolo komponentu KV messenger, ktorý komunikuje s enginom LLM a prostredníctvom siete riadi prenosy vyrovnávacej pamäte KV z uzlov predbežného spracovania do uzlov dekódovania. Na strane predbežného spracovania messenger prijíma požiadavky z uzlov dekódovania, odovzdáva ich plánovaču dávok a sleduje vykonávanie dopredného prechodu s cieľom odoslať vyrovnávace pamäte KV s čo najmenšou latenciou. Na strane dekódovania – po alokovaní stránok, ktoré sa nedajú vyhodiť do stránkovacieho súboru (un-evictable pages) – messenger blokuje plánovanie požiadavky na dekódovanie, kým nedostane informáciu o dokončení prenosov vyrovnávacej pamäte KV a kontextu dekodéra.
Oddelenie predbežného spracovania vyžaduje vysokú priepustnosť a pripojenie s nízkou latenciou, preto je naša implementácia prispôsobená pre technológiu RDMA a podporuje sieťové karty (NIC) EFA aj ConnectX. KV Messenger je postavený na knižnici libfabric a využíva naše obalové vrstvy fabric-lib na poskytovanie vyššie úrovňových abstrakcií s nízkou latenciou nad primitívnymi funkciami technológie Remote Direct Memory Access (RDMA), pričom implementuje efektívne prenosy stránok a metadát spolu s signalizáciou s nízkou latenciou. Na pozadí koordinuje fabric-lib grafický procesor (GPU) a priamo pripojené sieťové karty na kopírovanie údajov z uzla predbežného spracovania do uzla dekódovania.
Po prijatí uzol predbežného spracovania alokuje zodpovedajúcu sadu zdrojových stránok KV a naplánuje požiadavku na predbežné spracovanie pomocou svojho lokálneho enginu. Aby sa minimalizovala latencia, prenosy nečakajú na dokončenie dopredného prechodu: namiesto toho sa kopírovanie stránok KV spustí hneď, ako model dokončí pripájanie položiek vyrovnávacej pamäte KV k vyrovnávacej pamäti KV pre jednotlivé vrstvy. Keďže požiadavky na predbežné spracovanie možno rozdeliť na časti (chunk), plánovač dávok informuje KV messenger o aktuálne naplánovaných častiach pred ich vykonaním. S cieľom podporiť grafy CUDA a zároveň dokázať sledovať vrstvy, messenger udržiava vyhradené vlákno (thread), ktoré kontroluje čítač inkrementovaný po výstupnej projekcii mechanizmu pozornosť. Čítač sa udržiava iba na hlavnom uzle v prostredí s rozdelením na shardy (sharded environment): hoci položky vyrovnávacej pamäte KV sú platné po pripojení a pred fázou pozornosť, výstupná projekcia sa redukuje naprieč hodnosťami (ranks), čím sa implicitne synchronizujú. Akonáhle je zaznamenaná zmena v čítači, messenger je na to upozornený a zavolá funkciu fabric-lib na spustenie prenosu vrstvy.
Po dokončení prenosu poslednej časti (chunk) sa skoprujú aj všetky dodatočné metadáta: špekulatívne dekódovanie (speculative decoding) alebo metóda MTP vyžadujú presun logitov a skrytých stavov (hidden states) do dekodéra. Tieto kópie sa taktiež vykonávajú prostredníctvom technológie RDMA do a z vopred alokovaných vyrovnávacích pamätí.
Po dokončení všetkých čakajúcich prenosov poslednej časti uzol predbežného spracovania uvoľní stránky KV a dokončí požiadavku. Uzol dekódovania nie je explicitne informovaný: namiesto toho využíva okamžité čítače na sledovanie počtu dokončených operácií. Počet operácií RDMA na strane predbežného spracovania je úmerný počtu prenesených stránok. Po dokončení známeho počtu kopírovaní stránok a kontextu zavolá knižnica fabric-lib KV messenger, aby signalizovala pripravenosť požiadavky na dekódovanie. Messenger uvoľní akýkoľvek kontext a odovzdá požiadavku enginu LLM.
Prenosy rozdelených (sharded) vyrovnávacích pamätí KV
Ak predbežné spracovanie a dekódovanie spoliehajú na paralelizmus tenzorov (Tensor Parallelism – TP) a rozdeľujú alebo replikujú vyrovnávace pamäte KV identickým spôsobom, jediný prenosový engine koordinuje viacero zariadení na odosielanie a prijímanie stránok zo všetkých replík. Aby bolo možné použiť jeden messenger a prenosový engine napriek tomu, že exekútor modelu je replikovaný na viacerých zariadeniach a procesoch, používajú sa funkcie cuMem a cuMemImportFromShareableHandle na alokovanie pamäte zariadenia, ktorá podporuje vyrovnávace pamäte KV, a na jej mapovanie do hlavného procesu. Prenosový engine preskúma topológiu uzla, aby našiel sieťové karty a procesory v najbližšom uzle NUMA, ktoré sa použijú na prenosy jednotlivých častí vyrovnávacej pamäte KV.
Ak sa zdrojový a cieľový shard zhodujú, prenosy sú triviálne, pretože existuje vzťah 1:1 medzi zariadeniami a stránkami zdroja a cieľa. V tejto situácii sharding implicitne pomáha latenciám prenosu: použitím viacerých GPU možno zapojiť viac pridružených sieťových kariet, čím sa priblíži plnému využitiu šírky pásma. Ak však dôjde k nezhode, prenosový engine musí rozdeliť alebo rekonštruovať stránky v závislosti od pomeru medzi zdrojovou a cieľovou časťou.
Ak uzol predbežného spracovania rozdelí vyrovnávaciu pamäť KV na viac zariadení, úplné stránky sa na dekodéri zrekonštruujú odoslaním zodpovedajúcich polovíc zo zariadení predbežného spracovania. Ak má dekodér viac shardov, prijíma stránky z viacerých zdrojov. Dekodér musí poznať schému shardovania predbežného spracovania, aby vedel vypočítať počet zápisov RDMA, ktoré má prijať. Ak ide o replikáciu, uzol predbežného spracovania zoskupí zariadenia do sád replík, ktoré v rámci seba replikujú celú vyrovnávaciu pamäť KV. Cieľovým sadám replík sa náhodne priradí jedna zo zdrojových sád, aby sa využili všetky dostupné zariadenia na spustenie zápisov RDMA.
Rozdelené prenosy vyžadujú miernu úpravu vyrovnávacích pamätí KV. Predvolene sa knižnica FlashInfer spolieha na rozloženie NHD, ktoré usporadúva tokeny v rámci stránky vnútri hláv. Keďže vyrovnávace pamäte sú najpravdepodobnejšie rozdelené pozdĺž počtu hláv pozornosti (attention heads), vytvára to nesúvislosť v rámci hlavy. Prenosy RDMA implicitne nepodporujú zápisy s krokom (strided writes), čo vyžaduje jednu operáciu na hlavu na vykonanie prenosu. Namiesto toho – s cieľom znížiť počet interakcií s knižnicou libfabric – organizujeme vyrovnávace pamäte KV pomocou rozloženia HND, ktoré umiestňuje dimenziu hláv pred počet tokenov. To zabezpečuje spojitosť, čo umožňuje skopírovať stránku jediným zápisom.
Špekulatívne dekódovanie
Špekulatívne dekódovanie vyžaduje drobné úpravy oddeleného predbežného spracovania a dekódovania. V našej implementácii nemajú uzly predbežného spracovania povolené vzorkovať tokeny. Keďže modely Sonar od spoločnosti Perplexity podporujú štruktúrovaný výstup, nechceme preberať zložitosť synchronizácie implementácií procesora schém naprieč uzlami predbežného spracovania a dekódovania. V mechanizmoch MTP a špekulatívneho dekódovania zahŕňa predbežné spracowanie návrhu modelu (draft model) až po posledný token vzorkovanie tokenov z cieľového modelu.
Aby sme sa vyhli týmto problémom, predbežné spracovanie nezahŕňa posledný token vstupnej sekvencie. Namiesto toho sa prenesú skryté stavy alebo logity z predbežného spracovania predchádzajúceho poslednému tokenu a v ďalšom kroku na dekodéri sa s ním zaobchádza ako s tokenom dekódovania. Hoci to mierne zvyšuje latenciu – pretože po predbežnom spracovaní je nutné vykonať úplný krok dekódovania na vydanie prvého tokenu –, zložitosť implementácie sa výrazne znižuje.
Oddelené nasadenia
Nasadili sme alebo experimentovali s viacerými oddelenými konfiguráciami s rôznymi modelmi na podporu produkčnej prevádzky alebo interných vyhodnocovacích záťaží. Na základe veľkosti a mechanizmu pozornosť modelov sme zvolili vhodné schémy shardovania pre uzly predbežného spracovania a dekódovania s cieľom čo najlepšie využiť grafické procesory (GPU).
DeepSeek-R1
V prípade modelu DeepSeek sme zvážili nasadenie s paralelizmom tenzorov (TP) aj s paralelizmom údajov (DP). Ako bolo prediskutované v predchádzajúcich blogových príspevkoch, nasadenia s TP poskytujú lepšiu latenciu za cenu nižšej priepustnosti a vyžadujú viac GPU na obsluhu vysokej záťaže. Nasadenia s DP sa so záťažou škálujú oveľa lepšie, avšak ich špičková priepustnosť je nižšia kvôli nákladom na komunikáciu medzi zariadeniami alebo uzlami.
DeepSeek sa spolieha na mechanizmus Multi-Head Latent Attention, čím komprimuje vyrovnávace pamäte KV. Keďže všetky hlavy KV sú komprimované do jediného latentného vektora, TP nedokáže rozdeliť vyrovnávace pamäte KV na shardy, pretože namiesto toho musí replikovať latentné vektory na všetkých hodnostiach (ranks). Sharding prebieha až po dekompresii, pričom každá hodnosť môže extrahovať rôzne hlavy z tej istej latentnej reprezentácie. V dôsledku toho sú všetky shardy vyrovnávacej pamäte KV identické naprieč shardmi predbežného spracovania aj dekódovania.
Pri nastavení TP v rámci jedného uzla (intranode) sú uzly predbežného spracovania aj dekódovania rozdelené identicky. Prenosy sa odosielajú zo všetkých hodností, aby sa plne využili všetky dostupné sieťové karty. Avšak pri nasadení s DP, kde je veľkosť hodnosti TP menšia alebo je každej hodnosti DP priradená jediná GPU, môže akékoľvek zariadenie predbežného spracovania, ktoré drží replikovanú kópiu vyrovnávacej pamäte KV, túto kópiu odoslať. Na vyváženie požiadaviek medzi všetkými dostupnými sieťovými kartami náhodne vyberáme GPU a sieťovú kartu na odoslanie vyrovnávacej pamäte KV z uzla predbežného spracovania do dekodéra.
Pri zmiešanom predbežnom spracovaní a dekódovaní malo naše nasadenie R1 problém stabilne presahovať 50 TPS kvôli častým prerušeniam predbežného spracovania v ráde stoviek milisekúnd. Naopak, oddelením predbežného spracovania sme pre každú požiadavku zaznamenali penalizáciu približne 100 ms k metrike TTFT, avšak jediný uzol predbežného spracovania dokázal udržiavať konzistentnú veľkosť dávok na 3 uzloch dekódovania a poskytovať priepustnosť presahujúcu 90 TPS pri spracovaní záťaže približne 1 QPS na uzol dekódovania. Pri nasadeniach s paralelizmom údajov bola hodnota TPS mierne nižšia, okolo 50, avšak inštancie dokázali spracovať záťaž 1 QPS na hodnosť, pričom na jeden uzol pripadalo 8 hodností.
Qwen3-Coder
Tento model s 480 miliardami parametrov používa mechanizmus Grouped-Query Attention (GQA), takže pozornosť možno ľahko rozdeliť na shardy a ťažiť z paralelizmu tenzorov bez obetovania pamäte pre vyrovnávace pamäte KV. V dôsledku toho sme mohli model rozdeliť na 8 GPU pre predbežné spracovanie aj dekódovanie a spárovať približne 3 uzly dekódovania s jediným uzlom predbežného spracovania. Keďže pozornosť je rozdelená, spoliehamos sa na rozloženie vyrovnávacej pamäte KV typu HND na rozdelenie vyrovnávacích pamätí KV predbežného spracovania a dekódovania, pričom spárujeme hodnosti predbežného spracovania s hodnosťami dekódovania a plne využívame všetky sieťové karty na paralelný prenos častí.
Citácia
@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}
}