- Pradžia
- Atvejų analizės
- Ilgai trunkančios inferencijos orkestravimas be serverių veikiančiuose GPU
Ilgai trunkančios inferencijos orkestravimas be serverių veikiančiuose GPU
Laikinų serverless GPU pavertimas patikimais ilgoms DI inferencijos užduotims: pažangos išsaugojimas, nutrūkusių WebSocket atkūrimas ir darbininkų valymas.
Paskelbta 2026-06-30Peržiūrėta 2026-07-02
Trumpai
- Problema
- Ilgos vaizdų generavimo užduotys galėjo prarasti darbą arba nutekinti GPU atmintį, kai nutrūkdavo WebSocket ar dingdavo laikinas darbininkas.
- Apribojimas
- Orkestravimas turėjo tinklo gedimą ir darbininko išjungimą laikyti įprastais įvykiais, o ne naudotojų atrandamomis išimtimis.
- Sukurta sistema
- Užduočių orkestravimo sluoksnis aplink RunPod ir ComfyUI su pažangos apklausa, atkuriama būsena, ribotu atidėjimu, S3 iškėlimu ir aiškiu išjungimu.
- Rezultatas
- Trumpalaikiai atsijungimai nebežudo naudingo darbo, o baigtos ar paliktos užduotys paleidžia GPU išteklius vietoj brangiai kabančių darbininkų.
- Kur tai pritaikoma
- Aktualu DI medijos ar inferencijos sistemoms, kur nuomojamiems GPU aplink modelį reikia eilių, atkūrimo, sąnaudų kontrolės ir valymo.
- Technologijos
- RunPod · ComfyUI · WebSockets · S3 offload
Kodėl tai svarbu
Jei jūsų komanda planuoja sistemą su tais pačiais gedimo scenarijais — būtent nuo tokio mąstymo prasideda pirmas pokalbis.


Apžvalga
Aukščiau aprašytas apribojimas — tai visa užduotis: nuomojamas GPU darbininkas pigus būtent todėl, kad yra vienkartinis, o sistema, pastatyta virš jo, negali jo dingimo — ar tuo metu atviro lizdo praradimo — laikyti išimtiniu atveju, kurį galima sutvarkyti vėliau. Viena inferencijos užduotis čia gali trukti kelias minutes, generuoti didelį dvejetainį rezultatą, o tuo metu praeiti per mobiliojo ryšio perjungimą tarp bokštų, nešiojamojo kompiuterio užmigdymą ar apkrovos balansavimo įrenginio neveiklumo laiko limitą. Nė vienas iš šių atvejų nėra retas — tai vidutinis rezultatas, paleidus bet ką pakankamai ilgai ant nuomojamos, laikinos aparatinės įrangos. Orkestravimo sluoksnis stovi tarp kliento ir RunPod talpinamų ComfyUI darbininkų su viena užduotimi: padaryti taip, kad atsijungimas, darbininkų kaita ir apleistos užduotys atrodytų kaip įprasta veikimo būsena, o ne kaip incidentas, apie kurį turi pranešti naudotojas.
Įtraukimas į eilę ir apklausa vietoj pasitikėjimo lizdu
Užduotis neprasideda nuo lizdo atvėrimo ir tikėjimosi geriausio. Ji prasideda įtraukimu į eilę: klientas pateikia užduotį, gauna jos ID, ir būtent šis ID — o ne konkretus ryšys — yra tai, ką seka likusi sistemos dalis. Pažanga saugoma serverio pusėje, susieta su užduoties ID, ir yra užklausiama, o ne laikoma, kad siuntimas yra vienintelis tiesos šaltinis. „WebSocket“ prisegtas virš to grynai kaip mažos delsos pranešimų kanalas: kai jis atviras ir sveikas, pažangos atnaujinimai atkeliauja greitai; kai ne — klientas pereina prie to paties užduoties ID apklausos fiksuotu intervalu ir mato lygiai tą pačią būseną, kurią būtų pateikęs gyvas lizdas, tik šiek tiek vėliau.
Būtent šis atskyrimas leidžia visa, kas seka toliau. Kadangi užduoties būsena — tai eilutė, apie kurią serveris gali atsakyti į klausimus, „ar ši užduotis dar vykdoma“ tampa užklausa, o ne kažkokio naršyklės skirtuko atvirumo savybe. Klientas gali užsidaryti, atsidaryti iš naujo, atnaujinti puslapį ar perduoti darbą visai kitam įrenginiui, o atsakymas į „kur mano užduotis“ nuo to nepasikeičia.
Kodėl nepasitikėti vien WebSocket ryšiu
Laikyti lizdą vieninteliu tiesos šaltiniu — užduotis egzistuoja lygiai tiek, kiek egzistuoja lizdas — reiškia pririšti kelias minutes trunkančią GPU užduotį prie mažiausiai patikimos visos sistemos dalies. Lizdai užsidaro dėl priežasčių, neturinčių nieko bendra su užduoties būkle: užsidaro nešiojamojo kompiuterio dangtis, mobiliojo ryšio operatorius perjungia įrenginį prie kito bokšto, įmonės tarpinis serveris ar apkrovos balansavimo įrenginys taiko savo neveiklumo laiko limitą, nepaisant to, kas tuo metu dar vykdoma. Jei užduotis gyventų tik to ryšio atmintyje, kiekvienas iš šių įprastų įvykių tyliai nužudytų užduotį, dėl kurios naudotojas neturėtų jokio pagrindo manyti, kad ji nepavyko. Dar blogiau — pats darbininkas neturėtų jokio būdo atskirti „klientas vis dar stebi, tiesiog laikinai atsijungęs“ nuo „kliento nebėra, išjunk mane“ — būtent nuo šio atskyrimo teisingumo priklauso visa sistema. Padarius įtraukimą-į-eilę-ir-apklausą tiesos šaltiniu, o lizdą — vien patogumu virš jo, ryšio nutrūkimas tampa tik naudotojo sąsajos nepatogumu — pažangos indikatorius akimirkai sustingsta — o ne prarasta užduotimi.
Atkuriama, kontroliniais taškais pažymėta būsena
Įtraukimas-į-eilę-ir-apklausa išsprendžia užduoties suradimą iš naujo; savaime tai neišsprendžia jau atlikto darbo neprarandimo. Ilga inferencijos užduotis — daugiapakopė difuzija, kadras po kadro vykdomas vaizdo apdorojimas, bet kas, kas trunka minutes, o ne sekundes — turi realią, brangią pažangą, kelyje į pabaigą esančią GPU atmintyje, o startavimas iš nulio kiekvieną kartą nutrūkus ryšiui švaisto lygiai tą skaičiavimo galią, kurios ir siekiama nešvaistyti.
Sprendimas — padaryti pažangą patvarią kiekviename reikšmingame žingsnyje, o ne tik gyvoje darbininko atmintyje. Užduočiai judant į priekį, darbininkas įrašo kontrolinį tašką — kuriame žingsnyje jis yra, koks tarpinis rezultatas ar dalinis artefaktas egzistuoja, kas dar liko atlikti — į saugyklą, kuri egzistuoja nepriklausomai nuo bet kokio lizdo, bet kokio vieno darbininko proceso ar bet kokio kliento ryšio. Klientas, kuris prisijungia iš naujo — ar tai po dviejų sekundžių trikdžio, ar po penkių minučių pertraukos — neklausia „ar mano užduotis išgyveno“, jis klausia „koks dabartinis kontrolinis taškas“, ir tęsia savo rodomą būseną — o ten, kur tai leidžia pati užduotis, ir skaičiavimą — nuo to taško, o ne nuo nulio.
checkpoint = {
job_id: "job_8f2c",
step: 34,
total_steps: 50,
last_artifact_ref: "s3://inference-out/job_8f2c/step_34.png",
updated_at: "2026-06-30T14:22:07Z",
}
Tipinis patvaraus kontrolinio taško įrašo pavidalas
Kontrolinio taško formatui nebūtina būti egzotiškam — žingsnio skaitiklio, nuorodos į paskutinį patvarų artefaktą ir laiko žymos paprastai pakanka teisingai atsakyti „nuo kur tęsti“. Svarbu tai, kad įrašas gyvena už ribų tos dalies, kuri labiausiai linkusi sugesti — gyvo ryšio.
Ribotas eksponentinis atidėjimas jungiantis iš naujo
Nutrūkęs ryšys vis tiek turi sugrįžti, ir kaip jis sugrįžta, svarbu beveik tiek pat, kiek ir pats faktas, kad sugrįžta. Bandyti jungtis iš naujo iškart ir tvirtu ciklu — klaidingas numatytasis elgesys: darbininkas, atsigaunantis po savo paties trikdžio, ar tinklo kelias, kuris ne miręs, o tiesiog pablogėjęs, apiberiamas pakartotinių bandymų srautu tiksliai tą akimirką, kai mažiausiai gali jį sugerti. Laukti be galo — vienodai klaidinga priešinga kryptimi: klientas, kuris niekada nepasiduoda dėl iš tikrųjų dingusio darbininko, tiesiog kabo ten, o naudotojas, tai stebintis, neturi jokio signalo, kad kažkas tikrai negerai.
Vidurinis kelias — ribotas eksponentinis atidėjimas: kiekvienas nepavykęs bandymas jungtis iš naujo laukia ilgiau nei ankstesnis, iki tam tikros viršutinės ribos, todėl pats pirmasis bandymas yra greitas — tikras trikdis atsigauna beveik nepastebimai — o užsitęsęs sutrikimas atsitraukia iki protingo intervalo, vietoj to, kad toliau apiberinėtų sistemą. Didžiausias bandymų skaičius ar didžiausia praėjusi trukmė riboja visą procesą, todėl iš tikrųjų mirusi užduotis anksčiau ar vėliau, ir sąmoningai, iškyla kaip nesėkmė, o ne bando jungtis iš naujo amžinai fone.
delay = min(base_delay * 2^attempt, max_delay)
delay = delay * random(0.5, 1.0) # jitter
if elapsed > backoff_ceiling: surface_as_failed(job_id)
Prisijungimo iš naujo atidėjimas: eksponentinis augimas, viršutinė riba ir „jitter“, kad bandymai nesutaptų vienu metu
Schema: inferencijos užduoties atkūrimas — užduotis įtraukiama į eilę, o pažanga apklausiama, o ne pasitikima vienu lizdu; užduoties viduryje nutrūkęs „WebSocket“ suveda ribotą eksponentinį atidėjimą jungiantis iš naujo; klientas tęsia nuo paskutinio patvaraus kontrolinio taško, o ne pradeda iš naujo; didelis dvejetainis rezultatas iškeliamas į S3, vos jis baigiamas; o darbininkas aiškiai išjungiamas užbaigus ar apleidus užduotį, kad atlaisvintų GPU/VRAM.
„Jitter“ lengva praleisti ir brangu praleisti: be jo tinklo trikdis, tuo pačiu metu paveikiantis daug lygiagrečių užduočių, sukelia sinchronizuotas pakartotinių bandymų bangas, kurios atakuoja galinę sistemą vienu ritmu, o ne tolygiai pasiskirsto laike.
Didelių dvejetainių rezultatų iškėlimas į S3
Kai užduotis pasibaigia, ryšys, buvęs mažiausiai patikima visos sistemos dalimi, yra paskutinė vieta, kuriai turėtų tekti atsakomybė pristatyti didžiausią naudingąją apkrovą. Sugeneruotas vaizdas, o ypač sugeneruotas vaizdo įrašas ar kadrų paketas, gali siekti nuo kelių megabaitų iki kelių šimtų megabaitų — lygiai tokio dydžio apkrova, kuri ribinį ryšį paverčia iš „tikriausiai atsigaus“ į „nutrūks kelias minutes trunkančio perdavimo viduryje“.
Vietoj to, kad baigtas dvejetainis rezultatas būtų srautu siunčiamas atgal tuo pačiu kanalu, kuris naudojamas pažangos atnaujinimams, darbininkas jį tiesiogiai įkelia į objektų saugyklą, vos jis sugeneruojamas, o užduoties kontrolinio taško įraše atnaujinama nuoroda į tą objektą, o ne patys baitai. Kliento darbas susiaurėja iki nuorodos parsisiuntimo įprastu, atkuriamu HTTP užklausu, kai užduotis praneša, kad baigta — gerai žinoma problema su brandžiais įrankiais, o ne pasirinktinis perdavimas tuo pačiu trapiu kanalu, kuriam jau ir taip prireikė pakartojimo logikos.
Tai taip pat reiškia, kad užduotis, generuojanti rezultatą palaipsniui — pavyzdžiui, kadras po kadro — neturi laikyti viso rezultato atmintyje ar viename ryšyje iki pačios pabaigos. Kiekviena baigta dalis gali būti iškelta ir susieta nuoroda vos ji atsiranda, todėl vėlyva nesėkmė praranda tik tai, kas dar nebuvo iškelta, o ne viską, kas sugeneruota iki tol.
Aiškus darbininkų išjungimas
Serverless GPU kainodara — tiesioginė funkcija to, kiek laiko darbininkas veikia, todėl darbininkas, kuriam aiškiai nepasakyta sustoti, nesustoja — jis tiesiog kabo, apmokestinamas ir laikantis VRAM, kol kas nors tai pastebi. Palikus tai numanomu dalyku, tas „kas nors“ dažniausiai būna žmogus, patikrinantis sąskaitą — o tai per vėlu.
Išjungimas suveikiamas aiškiai, ir iš daugiau nei vienos krypties. Užduotis, kuri baigiasi, išjungia savo darbininką, kai patvirtinama, kad rezultatas iškeltas. Užduotis, kuri nepavyksta, iškart išjungia savo darbininką, o ne palieka jo veikti „tam atvejui“. O užduotis, kurios klientas taip ir nebesugrįžta — jokio prisijungimo iš naujo, jokios apklausos, nieko ilgiau nei tas pats ribotas laiko langas, kurį jau naudoja prisijungimo iš naujo atidėjimas — laikoma apleista ir išjungiama pasibaigus laiko limitui, o ne paliekama veikti neribotai, darant prielaidą, kad kažkas galbūt vis dar stebi.
Būtent pastarasis atvejis vertas sąmoningo dėmesio. Apleidimo laiko limitas turi būti pakankamai ilgas, kad klientas, teisėtai atsigaunantis po lėto tinklo ar atidėjimo ciklo, nebūtų išjungtas iš po savęs pačiu atkūrimo momentu, bet pakankamai trumpas, kad tikrai apleista užduotis nekabotų, užimdama brangų GPU tuo atveju, jei kas nors sugrįš. Susiejus jį su tuo pačiu laiko horizontu, kurį jau naudoja prisijungimo iš naujo logika, abi taisyklės nepradeda veikti viena prieš kitą.
Kompromisai ir apribojimai
Nė vienas iš šių sprendimų nėra nemokamas, ir verta aiškiai įvardyti, ką jie kainuoja ir ko neišsprendžia:
- Apklausa turi delsos apatinę ribą, kurios neturi gyvas siuntimo kanalas: net trumpu intervalu pažangos atnaujinimai šiek tiek atsilieka nuo to, ką sveikas lizdas pateiktų akimirksniu.
- Kontroliniai taškai turi granuliuotumo kompromisą — žymėk juos per retai, ir nutrūkimas praranda daugiau darbo; žymėk per dažnai, ir patys įrašymai tampa papildoma apkrova užduotims, kurios jau ir taip apribotos GPU.
- Ribotas atidėjimas reiškia, kad pakankamai ilgas sutrikimas anksčiau ar vėliau vis tiek tampa nesėkme. Tai suteikia laiko laikiniems trikdžiams išsispręsti; tai nepaverčia iš tikrųjų mirusio tinklo kelio ar visam laikui dingusio darbininko atkuriamu.
- Aiškus išjungimas tiek geras, kiek geras už jo stovintis apleidimo aptikimas — suklysk viena kryptimi, ir lėtas, bet teisėtas klientas išjungiamas atkūrimo viduryje; suklysk kita kryptimi, ir iš tikrųjų apleista užduotis toliau apmokestinama ilgiau, nei turėtų.
- S3 iškėlimas pašalina ryšį kaip kliūtį, bet prideda naują priklausomybę su savo pačios gedimo scenarijumi: pats įkėlimas gali nepavykti ar užstrigti, o užduotis gali pasiekti būseną „baigta“ anksčiau, nei patvirtinama, kad jos rezultatas patvariai išsaugotas, jei ta tvarka sąmoningai neužtikrinama.
- Visa tai prideda realaus sudėtingumo — eilė, kontrolinių taškų saugykla, atidėjimo logika, iškėlimo taikinys, išjungimo taisyklės — virš to, kas kitu atveju būtų viena užklausa ir vienas atsakymas; tai pateisinama todėl, kad alternatyva — tyliai prarasti jau apmokėtą GPU skaičiavimo laiką, o ne todėl, kad taip paprasčiau.
Šaltiniai ir tolesnis skaitymas
Susijusios atvejų analizės
Sistemos, kurias su šia sieja bendras apribojimas — pakartojimai, izoliacija, kaina, atkūrimas.
Kadrų tikslumo vaizdo įrašų lokalizavimas
Ilgi klipai lieka pririšti prie šaltinio laiko žymų, o originalus nevokalinis garsas išlieka po sintezuotu įgarsinimu.
WhisperX · XTTS voice cloning · Demucs · FFmpeg · CTranslate2Kelių tiekėjų DI vartai su integruota sąnaudų apskaita
Modelio pasirinkimas tapo konfigūracija, nauji tiekėjai paveldi bendras kontroles, o išlaidos matomos dar užklausoms vykstant.
Go adapters · gRPC / protobuf · persisted in-memory queue