Kiired manused GPU-del

Kiire ja täpne otsing on kogu Perplexity jaoks ülioluline, alates otsingust ja arvutist kuni meie API platvormini. Kulisside taga teevad raske töö manustamis- ja järjestusmudelid, mis aitavad meie süsteemidel antud päringu jaoks kõige asjakohasemad tulemused tuvastada. Saavutame tipptasemel

AutoridPerplexity Engineering

Kiire ja täpne otsing on kogu Perplexity jaoks ülioluline, alates otsingust ja arvutist kuni meie API platvormini. Kulisside taga teevad raske töö manustamis- ja järjestusmudelid, mis aitavad meie süsteemidel antud päringu jaoks kõige asjakohasemad tulemused tuvastada. Saavutame tipptasemel kvaliteedi ja latentsuse, treenides ja serveerides oma mudeleid, näiteks pplx-embed.

See artikkel annab kapoti-aluse vaate Perplexity serveerimistaristule selle spetsiaalse mudeliklassi jaoks. Arutame oma tehnikaid, et tõhusalt lahendada AI-omase otsingu järeldusvajadusi, võimaldades mudelite kiiret prototüüpimist ja hindamist, toites samal ajal meie eksabaidi-skaala otsinguindeksit. Need tehnikad laiendavad kollektiivselt otsingu kvaliteedi ja tõhususe Pareto piiri, võimaldades meil teenindada agente ja kasutajaid parimate võimalike tulemustega madalaima kuluga ja latentsusega.

Otsingu manused

Tüüpilises otsinguseadistuses kaardistatakse indekseeritud dokumendid manusemudeli abil kõrmemõõtmelisse vektorruumi ja salvestatakse vektorandmebaasi. Manustades päringu sama mudeli abil, saab sarnaseid dokumente leida, leides vektorid, mis on päringu omale kõige lähemal. See tekitab järeldusmootori jaoks kaks erinevat liiklusmustrit, mida teenindada:

  • Pakettmanus: andmebaasi ehitamisel, laiendamisel või ümberindekseerimisel tuleb hulgidokumendid vektorruumi manustada, maksimeerides läbilaskevõimet kulude minimeerimiseks.

Pärast vektorotsingut tuleb suured dokumendipartiid skoorida, saavutades tasakaalu läbilaskevõime ja latentsuse vahel.

  • Veebipõhine manustamine: andmebaasi pärimisel tuleb otsingute jaoks manustada lühike päring, minimeerides latentsust.

Ehitasime oma järeldustaristu nii, et see kasutaks kasutusjuhtude lõikes ära võimalikult palju ühiseid komponente. Kuna me kasutame manuste tootmiseks tavaliselt väikseid Transformer-mudeleid, jagame suurema osa rakendamisest oma LLM järelduskoodiga: pakettmanused on sarnased arvutusmahuka eeltäitmisega, samas kui veebipõhised manused, mis töötavad sageli mõnel tokenil, on arvutuslikult sarnased mälumahuka dekodeerimisega. Taaskasutame seega oma optimeeritud eeltäitmise ja dekodeerimise kerneteid, et manuste mudeleid teenindada. Tulemusena saavutame tohutu partii järelduse läbilaskevõime minimaalse täiendava inseneritööga, säilitades samal ajal veebipõhiste manuste töökoormuste madala latentsuse.

Tulbid, roosid ja veidi luuderohust Ivy

Pakume järeldust standardiseeritud API-de kaudu nii siselt kui ka väliselt meie API platvormi kaudu. Kulisside taga on manusepäringu töötlemises seotud mitu teenust:

  • Ivy on Rusti HTTP-lüüv, mida Perplexity teenused kutsuvad.

See käsitleb päringute CPU-poolset tööd, nagu JSON-i parsimine, tokeniseerimine, sisendi mallimine ja partiide jagamine, teisendades päringud kohandatud gRPC-protokolliks allavoolu serveritele. See eraldatus võimaldab meil konfigureerida teatud tokeniseerimise ja sisendi vormindamisega seotud parameetreid, ilma et peaksime puudutama raskemaid järelduse instantsioone.

  • Tulip on järeldusserveri liides.

See on Rusti, tokio ja `tonic` abil rakendatud gRPC-server. Tulip võtab vastu gRPC järelduse päringuid, käsitledes ajastust ja partiisid. Seejärel saadab see partiid ROSE mootorile, tagastades valmis vastused klientidele.

  • **ROSE** (Runtime-Optimized Serving Engine) rakendab mudeli järeldust.

See on määratletud peamiselt Pythonis, pakkudes kerneteid, kihte ja definitsioone mitmesuguste mudelite jaoks. ROSE rakendab mudelite läbipääse, pakkudes ka manuste jaoks spetsialiseeritud CUDA graafiku haldust. See on ühendatud Tulipiga step() funktsiooni kaudu, mis võtab vastu partii ja tagastab viite arvutusele, mida see kiirendil teostab.

Serveerimisarhitektuur manusepäringust Ivy kaudu replikeeritud Tulip serveriteni

Tähelepanu pööramine kerneli taha

Nii Transformer-põhised mudelid kui ka aluseks olevad Hopper/Blackwell arhitektuurid on küpsed tehnoloogiad, mistõttu järelduse manustamine GPU poolel on erinevates järeldusmootorites koondunud suures osas optimaalseks rakenduseks. Sellegipoolest avastasime täiendavaid võimalusi käitusaegade ja rakenduste parandamiseks, mis toovad mudelid kliendile otsast lõpuni nähtavale. Eelkõige leidsime, et saame parandada latentstunde CUDA graafikute hoolika haldamise ja LazyTensori abstraktsiooni loomisega, et asünkroonselt jälgida GPU-poolset tulemust natiivses Rusti mootoris. Rakendasime need funktsioonid Tulibis, et see saaks tõhusalt suhelda ROSE mudelirakendustega.

Tulip

Kujundasime Tulipi võimalikult kergekaaluliseks liideseks meie mudelite serveerimisel. See käsitleb sissetulevaid päringuid Tokio asünkroonsetes ülesannetes, säilitades päringute basseini, mida see jälgib ja millest ajastab partiisid kiirendile saatmiseks. Ajastamismehhanism Tulibis on väga lihtne: päringud kogunevad ajal, mil Tulip saadab tööd välja või ootab tulemusi. Kogunenud päringute hulgast valitakse jadad mudeli kaudu käitamiseks põhimõttel „kes ees, mixts mees“.

Lihtne ajastamismehhanism on motiveeritud mudeli jõudluse vaatlusest. Väikeste manusemudelite puhul oleme täheldanud jada pikkustel, mida teenindame, et tihedate kihtide lineaarne kulu on ülekaalukas võrreldes tähelepanu ruutkuluga. Seega on latentsus enamasti proportsionaalne tokenite arvuga, mitte jadade arvuga. Järgmiselt, kui partii on GPU küllastamiseks piisavalt suur, mis on alla miljardi parameetriga mudelil umbes 512 tokenit, ei paranda sellesse rohkemate jadade pakkimine tõhusust.

Mudeliga tõhusaks suhtlemiseks tugineb Tulip CUDA graafikutele ja laisale tulemuste jälgimisele, et kattuda GPU ja CPU tööga ning kasutada täielikult ära olemasolevaid ressursse.

CUDA graafiku haldus

Mudeli edasipääsu käitamine hõlmab nii CPU-poolset kui ka GPU-poolset tööd. CPU vastutab partiide ajastamise ja kernelite käivitamise eest vastavate parameetritega, samas kui GPU täidab asjakohased maatrikskorrutuse, tähelepanu, normi või aktiveerimise kernelid. Kõrge läbilaskevõimega töökoormuste, näiteks treenimise ja ümberindekseerimise korral on CPU-poolsed üldkulud tühised, kuna partiide suurused ja GPU-poolne latentsus on mõlemad suured. Väiksemate partiisuuruste korral võib aga CPU-poolne töö GPU-poolse töö ületada.

Igasugune edasipääs: hosti väljakutsed vaheldumisi seadme kernelitega

Üldkulude leevendamiseks saab sõltumatute kernelite käivitamise asemel ehitada CUDA graafiku, et jäädvustada metaandmed, mis on vajalikud edasipääsu kõigi kernelite käivitamiseks üheainsa CUDA draiveri väljakutsega. See välistab vajaduse käivitada uuesti kalleid Pythoni ja PyTorch koodi konfiguratsioonide jaoks, mille jaoks CUDA graafikuid saab jäädvustada.

Iga mudeli puhul jälgime paindepunkti, määrates kindlaks minimaalse tokenite arvu, mille korral GPU täitmine on kulukam kui CPU-poolne kerneli käivitamine. Kuna manusemudelid on väikesed, märkame, et see paindepunkt saabub tuhandete tokenite ja kümnete jadade partiide korral. Mõned tähelepanu rakendused tuginevad dünaamilistele hostipoolsetele sisenditele kernelite käivitamise konfigureerimiseks, takistades täismudeli eeltäitmise/tihedaid CUDA graafikuid. Viisime üles muudatused asjakohastesse kernelitesse, et võimaldada neid meie järeldusmootoris.

Üldkulude lahendamiseks ehitame kogu mudeli CUDA graafikud kõigi manusemudelite jaoks ja katame CPU töö GPU tööga. Kuna CUDA graafikud minimeerivad CPU-poolseid üldkulusid, on meil pärast graafiku käivitamist vaba aeg järgmise partii täitmise käivitamiseks ja järjekorda panemiseks, kui see on saadaval. Ootel oleva partii tulemusi jälgitakse LazyTensoriga, mis võimaldab Rusti asünkroonsel ülesandel blokeerida, kuni eelmine partii lõpetab täitmise. CUDA graafikud aitavad madala latentsusega serveerimisel, tagades, et meid ei hoia tagasi kernelite käivitamise kulu, ja hõlbustavad paremat ajastust kõrge läbilaskevõimega juhul, vabastades CPU järgmise partii kallal varem tööd tegema.

Cudagraphi edasipääs

CUDA graafikud tuleb jäädvustada iga eraldiseisva konfiguratsiooni jaoks, mis manuste puhul tähendab graafikut jadaarvu ja tokenite arvu kombinatsiooni kohta. Kuna see ruudustik on ulatuslik, polsterdame tokenite arvud ämbriteni, mis on 64 või 256 kordsed. See toob endaga kaasa siiski tuhandeid graafikuid, mille jäädvustamiseks võib tüüpilise mudeli puhul kuluda mitu minutit. Jäädvustamise kulu tuleneb kahest allikast: innukas edasipääs, mis tuleb kernelite kompileerimiseks ja puhvrite seadistamiseks käivitada neist puudust tunnvate kernelite jaoks, millele järgneb jäädvustamiskäivitus, mis käivitab uuesti Pythoni koodi.

Leevendame käivitamiskulusid, jäädvustades CUDA graafikuid laisalt mootori teenindamise ajal. Jälgime iga konfiguratsiooni ja tagame, et see läbib innuka soojenduskäivituse enne graafiku jäädvustamise ja taasesituse käivitamist teisel tabamusel. Kõik sama graafiku konfiguratsiooni järgnevad täitmised läbivad seejärel CUDA graafiku taasesituse. Laisel graafiku jäädvustamisel on mõju p99 latentsusele käivitamise ajal; see on siiski väärtuslik mitme minuti innuka töö levitamisel mitme tunni peale. Kiiremad käivitusajad võimaldavad meil manusejuurutusi paremini skaleerida ja hallata.

Laisad tensorid

CUDA kaudu on GPU töö asünkroonne. Kuna kerneli käivitamine asünkroonselt paneb selle voogu järjekorda, peab hostikood tulemusvektorite lugemiseks selgesõnaliselt sünkroonima. Suurema paralleelsuse astme hõlbustamiseks ja selleks, et saaksime käivitada tulevasi partiisid, oodates samal ajal eelmise valmimist seadmes, tuginev väärtuste jälgimiseks LazyTensori abstraktsioonile.

LazyTensor jälgib leheküljelukustatud mälus asuvat hosti puhvrit ja sündmuse kaudu cudaMemcpyAsync operatsiooni, mis kopeerib andmeid seadmest. See käivitatakse pärast edasipääsu käivitamist samas voos. Kuna kopeerimisoperatsioon peab ootama kõigi voos olevate varasemate kernelite täitmist, jälgib seotud sündmus nii edasipääsu valmimist kui ka tulemuse kättesaadavust CPU-s.

LazyTensori täitmine

Kasutame ROSE kodeerimismootoris LazyTensoreid, et kattuda GPU ja CPU tööga. Selle asemel, et iga step() väljakutse käivitaks CUDA graafiku ja ootaks selle valmimist, tagastab step() LazyTensori, et oma tulemust asünkroonselt jälgida. Kombineerituna CUDA graafikutega aitab see meil saavutada madala latentsuse ja parema läbilaskevõime.

Ajaskaala, mis näitab CPU ettevalmistust ja sünkroonimist, mis katavad järjestikuseid GPU partiisid

ROSE

Kohandasime oma ROSE mootori, mille ehitasime algselt LLM serveerimiseks, käsitlema ka manusemudelite täitmist. Manusemudelite toetamiseks vajaliku vaeva minimeerimiseks taaskasutab ROSE agressiivselt koodi LLM-ide ja manuste vahel. Näiteks pplx-embed serveerimine ja Qwen3.5 LLM dekodeerimine läbivad kõik samad kernelid. See jagamine võimaldab meil hõlpsasti teenindada manusemudelit, mis peenhäälestati algselt LLM-ist prototüüpimise, hindamise ja tootmisjärelduse jaoks.

Tihedate kihtide puhul on manuste ja LLM järeldus identsed, kuna tokeni vektoreid töödeldakse iseseisvalt. Tähelepanukihtides lahendatakse erinevused rebitud sisendite toe lisamisega ning LLM-ide poolt nõutavate lehekülgedega eeltäitmise ja dekodeerimise seadistustega. Manusemudeli serveerimisel me KV vahemälu ei instantsiiri ja suuname edasi tähelepanukernelite variatsioonidele, mis toetavad rebitud vormingut polsterduse vältimiseks. Toetavad teisenduse ja kalibreerimise rutiinid jagatakse samuti LLM-idega.

Ivy

Ivy, meie järelduse HTTP proxy kiht, mängib samuti olulist rolli jõudluses. Kuna päringute koormused on tootmises erinevad, võib üksikute päringute suunamine üksikutele koopiatele põhjustada koormuse tasakaalustamatust. Ivy jagab suured partiipäringud tükkideks ja tasakaalustab nende koormuse koopiate vahel, parandades kasutusastet ja ühtlustades latentsust. Meie hiljutine töö majasisese unigram-tokeniseerimisega, mis on Ivy-s täielikult kasutusele võetud, parandab drastiliselt latentsust võrreldes valmistoote tokeniseerijatega.

...kuid kernelitel on siiski tähtsus

ROSE toetab erinevaid tähelepanu taustaprogramme. Erinevad kernelid võivad sobida konkreetsete probleemisuuruste jaoks. Aja jooksul integreerisime FlashInfer 2, FlashInfer 3 ja FlashAttention 4 kernelid rebitud tähelepanu rakendamiseks.

Tähelepanu kerneli jõudlus mudeli kuju ja probleemi suuruse järgi

Üldiselt märkame, et FlashAttention 4 on kiirem. Siiski edestab FlashInfer 3 seda Qwen-põhistel mudelitel väga pikkade jadade pikkuste korral. Kuna jõudlus ja häälestus võivad tähelepanupeade arvu ja dimensiooniga erineda, säilitame toe mitmele konfiguratsioonile ja teeme serveerimisel otsuse juhtumipõhiselt.

Võrdlusuuringud

Võrdleme vLLM v0.22.0-ga, käivitades järelduse BF16 täpsusega tegelikel mudelikaaludel ja hindamisandmestikust tuletatud sisenditel. Kõigile ajastuse käivitustele eelnesid soojenduskäivitused, mis kinnitasid, et koosinussarnasuse divergents jääb 0,1% piiresse.

Madala latentsusega manused (p50 / p90 / p99 / max ms)

Raportitest näeme käitusaegu eeltokeniseeritud päringu partiisuurusele 1, täielikult järjestikustele päringutele, jada pikkustele 128, 512 ja 4096 tokenit.

Madala latentsusega manuste võrdlusuuringu tulemused

Madala latentsusega skoorimine (p50 / p90 / p99 / max ms)

Eeltokeniseeritud päringu partiisuurused 5, 25 ja 50, jada pikkus 512 tokenit.

Madala latentsusega skoorimise võrdlusuuringu tulemused

Kõrge läbilaskevõimega manused (emb/s)

Päringu paksu suurus 100, neli samaaegset protsessi päringute esitamisel, jada pikkused 512, 1024 ja 4096 tokenit.

Kõrge läbilaskevõimega manuste võrdlusuuringu tulemused

Kõrge samaaegsusega manused (p50 / p90 / p99 / max ms)

Jada pikkus 512, partiisuurus 1, kuid saadame 1, 2, 4, 8 ja 16 samaaegset päringut. See võrdlusuuring sisaldab ka tokeniseerimiskulusid Ivy kaudu koos võrguülekande üldkuludega Ivy ja Tulipi vahel.

Kõrge samaaegsusega manuste võrdlusuuringu tulemused

Kokkuvõte ja tulevane töö

Serveerimistaristu, mis koosneb Ivy-st, Tulipist ja ROSE-st, võimaldab meil teenindada Perplexity manuseid madalama latentsuse ja parema läbilaskevõimega, mille tulemuseks on täpsem otsing vähendatud kuluga võrreldes valmistoote lahendustega.

Keskendudes konkreetsetele mudelitele ja võttes vastutuse kogu pinu eest, saavutame vajaliku vabaduse, et leida tõhus tasakaal jõudluse ja paindlikkuse vahel, segades kõrgelt taaskasutatavaid ja tõhusaid Rusti primitiivseid elemente üldisema Pythoni modelleerimiskoodiga. Paljud avatud lähtekoodiga järeldusmootorid, nagu vLLM, SGLang ja TokenSpeed, integreerivad oma pinu keeli nagu Rust ja C++. Oleme viimase kahe aasta jooksul investeerinud Rusti ja lõiganud suurt kasu nii jõudluse kui ka hooldatavuse osas. Jagades suuremat osa manuste rakendamisest meie LLM serveerimise pinuga, saavutame kasu ka läbilaskevõimes, ilma et manuste mudelite hooldamiseks oleks vaja kulutada märkimisväärset inseneritööd.

Mudelite arenedes jätkame oma pinu iga kihi täiustamist, et vähendada nii CPU-le kui ka GPU-le tuginevat latentsust. Meie kohandatud gRPC-põhised protokollid Ivy ja Tulipi sees võimaldavad meil sidet näppida võrgulatentsuse vähendamiseks, samas kui ROSE pakub alust arvutusliku läbilaskevõime parandamiseks. Lisaks, kuna vaba keermega Pythoni tugi kogu ökosüsteemis kasvab, saame veelgi parandada Pythoni ja Rusti koostalitlusvõimet, et vähendada üldkulusid.

Viited