Kako OpenAI isporučuje glasovni AI s niskom latencijom na globalnoj razini
Autori: Yi Zhang i William McDonald, članovi tehničkog osoblja
Glasovni AI djeluje prirodno samo ako se razgovor odvija brzinom govora. Kad mreža predstavlja smetnju, korisnici to odmah primijete kroz neugodne stanke, prekinute rečenice ili zakašnjelo upadanje u riječ. To je važno za ChatGPT Glas, za razvojne programere koji rade s Realtime API-jem, za agente u interaktivnim tijekovima rada i za modele koji moraju obrađivati zvuk dok korisnik još govori.
Na razini OpenAI-ja to znači tri konkretna zahtjeva:
- Globalni doseg za više od 900 milijuna tjedno aktivnih korisnika
- Brzo uspostavljanje veze kako bi korisnik mogao početi govoriti čim sesija započne
- Niska i stabilna latencija medijskog prometa, uz mali jitter i gubitak paketa, kako bi izmjena govora djelovala prirodno
Tim u OpenAI-ju odgovoran za AI interakcije u stvarnom vremenu nedavno je redizajnirao naš WebRTC stog kako bi riješio tri ograničenja koja su se počela sudarati pri velikom opsegu: model završavanja medija s jednim portom po sesiji ne uklapa se dobro u infrastrukturu OpenAI-ja, ICE (Interactive Connectivity Establishment) i DTLS (Datagram Transport Layer Security) sesije sa stanjem zahtijevaju stabilno upravljanje, a globalno usmjeravanje mora održavati nisku latenciju prvog skoka. U ovom tekstu objašnjavamo podijeljenu arhitekturu relay plus primopredajnik koju smo izgradili kako bismo zadržali standardno ponašanje WebRTC-a za klijente, a pritom promijenili način usmjeravanja paketa unutar infrastrukture OpenAI-ja.
WebRTC je otvoreni standard za prijenos zvuka, videa i podataka s niskom latencijom između preglednika, mobilnih aplikacija i poslužitelja. Često se povezuje s pozivima među ravnopravnim sudionicima, ali je i praktična osnova za klijent-poslužitelj sustave u stvarnom vremenu jer standardizira složene dijelove interaktivnih medija: ICE za uspostavu povezivosti i prolazak kroz NAT (Network Address Translation), DTLS i SRTP (Secure Real-time Transport Protocol) za šifrirani prijenos, pregovaranje o kodecima za kompresiju i dekodiranje zvuka, RTCP (Real-time Transport Control Protocol) za kontrolu kvalitete te značajke na strani klijenta poput uklanjanja jeke i međuspremnika za jitter.
Ta je standardizacija važna za AI proizvode. Bez WebRTC-a svaki bi klijent trebao drukčije rješenje za uspostavu povezivosti preko NAT-ova, šifriranje medija, pregovaranje o kodecima (koderima-dekoderima odabranima za prijenos i dekompresiju) i prilagodbu promjenjivim mrežnim uvjetima. Uz WebRTC možemo graditi na protokolnom stogu koji je već implementiran u preglednicima i mobilnim platformama te se usredotočiti na infrastrukturu koja povezuje medije u stvarnom vremenu s modelima.
Također se oslanjamo na sam WebRTC ekosustav, uključujući zrele open-source implementacije i standardizacijski rad koji održava interoperabilnost preglednika, mobilnih aplikacija i poslužitelja. Temeljni rad Justina Ubertija (jednog od izvornih arhitekata WebRTC-a) i Seana DuBoisa (stvoritelja i održavatelja Piona) omogućio je timovima poput našega da grade na provjerenoj medijskoj infrastrukturi umjesto da iznova razvijaju prijenos niske razine, šifriranje i mehanizme kontrole zagušenja. Sretni smo što su Justin i Sean danas naši kolege u OpenAI-ju i što pomažu usmjeravati približavanje WebRTC-a i AI-ja u stvarnom vremenu.
Za AI je najvažnije to da zvuk stiže kao neprekidan tok. Govorni agent može početi transkribirati, rasuđivati, pozivati alate ili generirati govor dok korisnik još govori, umjesto da čeka dovršetak prijenosa. To je razlika između sustava koji djeluje razgovorno i sustava koji funkcionira poput push-to-talk komunikacije.
Kad smo odabrali WebRTC, sljedeće je pitanje bilo gdje završiti vezu (gdje bismo prihvatili i preuzeli WebRTC vezu — primjerice, na rubu mreže) i kako te sesije povezati s pozadinskim sustavom za inferenciju. To je važno jer određuje kako upravljamo stanjem sesija u stvarnom vremenu, prijenosom medija, usmjeravanjem, latencijom i izolacijom kvarova.
SFU, (selective forwarding unit) medijski je poslužitelj koji prima jedan WebRTC tok od svakog sudionika i selektivno prosljeđuje tokove ostalima. U tom modelu SFU završava zasebnu WebRTC vezu za svakog sudionika, a AI se pridružuje kao još jedan sudionik sesije. To može dobro odgovarati proizvodima koji su po prirodi višestranački, poput grupnih poziva, učionica ili suradničkih sastanaka. Na jednom mjestu objedinjuje audio kodeke, RTCP poruke, podatkovne kanale, snimanje i pravila za pojedini tok.1
Čak je i u proizvodima klijent-prema-AI-ju SFU često zadana početna točka jer timovima omogućuje ponovnu upotrebu jednog provjerenog sustava za signalizaciju, usmjeravanje medija, snimanje, nadzor i buduća proširenja poput uključivanja čovjeka u razgovor ili dodavanja više sudionika.
Naše je opterećenje drukčije. Većina sesija je 1:1 – jedan korisnik razgovara s jednim modelom ili jedna aplikacija s jednim agentom u stvarnom vremenu – uz osjetljivost na latenciju pri svakoj izmjeni. Za takav oblik prometa odabrali smo model primopredajnika: rubna WebRTC usluga završava vezu s klijentom, a zatim pretvara medije i događaje u jednostavnije interne protokole za inferenciju modela, transkripciju, generiranje govora, upotrebu alata i orkestraciju.
U ovom je dizajnu primopredajnik jedina usluga koja upravlja stanjem WebRTC sesije, uključujući ICE provjere povezivosti, DTLS rukovanje, ključeve SRTP šifriranja i životni ciklus sesije. „Završavanje” ovdje znači da je primopredajnik krajnja točka koja dovršava ta rukovanja te šifrira i dešifrira medije. Zadržavanje tog stanja na jednom mjestu olakšalo je upravljanje vlasništvom nad sesijama i omogućilo da se pozadinske usluge skaliraju kao obične usluge umjesto da same funkcioniraju kao WebRTC ravnopravni sudionici.
Nakon odabira modela primopredajnika, naša je prva implementacija bila jedinstvena Go usluga izgrađena na Pionu koja je upravljala i signalizacijom i završavanjem medija. Ona pokreće ChatGPT Glas, WebRTC krajnju točku Realtime API-ja i niz istraživačkih projekata.
Operativno gledano, usluga primopredajnika obavlja dvije funkcije:
- Signalizacija: SDP pregovaranje, odabir kodeka, ICE vjerodajnice i postavljanje sesije
- Mediji: završavanje silaznih WebRTC veza i održavanje uzlaznih veza prema pozadinskim uslugama za inferenciju i orkestraciju
Željeli smo da usluga radi poput ostatka naše infrastrukture: na Kubernetesu, gdje se radna opterećenja mogu skalirati gore-dolje i premještati između hostova kako se potražnja mijenja. No konvencionalni WebRTC model jednog porta po sesiji slabo se uklapa u takvo okruženje jer ovisi o velikim rasponima javnih UDP portova koje je teško izložiti, zaštititi i zadržati dok se podovi dodaju, uklanjaju ili ponovno raspoređuju.2
Prvi je problem bio sam model jednog porta po sesiji. Pri velikoj istodobnosti to znači izlaganje i upravljanje vrlo velikim rasponima UDP portova.
- Load balanceri u oblaku i Kubernetes usluge nisu dizajnirani za desetke tisuća javnih UDP portova po usluzi. Svaki dodatni raspon povećava operativnu složenost konfiguracije load balancera, provjera zdravlja, pravila vatrozida i sigurnosti uvođenja.3
- Velike raspone UDP portova teško je zaštititi jer proširuju izvana dostupnu površinu i otežavaju reviziju mrežnih pravila.
- To je također loše za automatsko skaliranje. Podovi se na Kubernetesu stalno dodaju, uklanjaju i ponovno raspoređuju. Ako svaki pod mora rezervirati i oglašavati velik stabilan raspon portova, takva elastičnost postaje krhka.4
Zato se mnogi WebRTC sustavi okreću jednom UDP portu po poslužitelju, uz demultipleksiranje na razini aplikacije iza tog porta.5
Dizajni s jednim portom po poslužitelju rješavaju problem broja portova, ali uvode drugi problem: održavanje vlasništva nad svakom sesijom u cijeloj floti.
ICE i DTLS su protokoli koji ovise o stanju sesije. Proces koji je stvorio sesiju mora nastaviti primati pakete te sesije kako bi mogao potvrditi provjere povezivosti, dovršiti DTLS rukovanje, dešifrirati SRTP i obraditi kasnije promjene sesije poput ponovnog pokretanja ICE-a. Ako paketi iste sesije završe u drugom procesu, postavljanje može propasti ili se prijenos medija može prekinuti.
To nam je dalo jasan cilj: izložiti malu, fiksnu UDP površinu javnom internetu, a istodobno i dalje usmjeravati svaki paket prema primopredajniku koji upravlja odgovarajućom WebRTC sesijom.
Procijenili smo nekoliko načina kako to postići, uključujući TURN (Traversal Using Relays around NAT), gdje rubni relay završava alokacije klijenta i prosljeđuje promet u njegovo ime.2
Pristup | Prednosti | Nedostaci |
Jedinstveni IP:port po sesiji (poznato i kao izvorni izravni UDP) | Izravna medijska putanja klijent – poslužitelj Bez sloja za prosljeđivanje u putanji prijenosa podataka | Zahtijeva jedan javni UDP port po sesiji Velike raspone portova teško je izložiti i zaštititi Nije dobro prilagođeno Kubernetesu i load balancerima u oblaku |
Jedinstveni IP:port po poslužitelju | Znatno manja javna UDP površina nego kod izlaganja po sesiji Jedan zajednički socket po poslužitelju može demultipleksirati mnogo sesija | Dobro funkcionira na jednom hostu, ali ne i samostalno preko zajedničke flote iza load balancera Demultipleksiranje sesija na jednom hostu pomaže tek nakon što paket stigne na taj host; u floti iza load balancera prvi paket i dalje može završiti na pogrešnoj instanci, pa je i dalje potreban deterministički način usmjeravanja svake sesije prema procesu koji njome upravlja |
TURN relay (završava protokol) | Klijenti trebaju pristupiti samo adresi i portu TURN relaya Moguća je centralizacija pravila na rubu | TURN alokacije dodaju dodatna kružna putovanja pri uspostavi veze Premještanje ili oporavak alokacija između TURN poslužitelja i dalje je teško |
Forwarder bez stanja + terminator sa stanjem (OpenAI-jev relay + primopredajnik) | Mali javni UDP otisak Primopredajnik i dalje upravlja cijelom WebRTC sesijom | Dodaje jedan skok prosljeđivanja prije nego što mediji stignu do primopredajnika koji upravlja sesijom Zahtijeva prilagođenu koordinaciju između relaya i primopredajnika |
Arhitektura koju smo implementirali razdvaja usmjeravanje paketa od završavanja protokola. Signalizacija i dalje dolazi do primopredajnika radi postavljanja sesije, dok mediji najprije prolaze kroz relay. Relay je lagani sloj za UDP prosljeđivanje s malim javnim otiskom, a primopredajnik je WebRTC krajnja točka sa stanjem koja stoji iza njega.
Relay ne dešifrira medije, ne pokreće ICE strojeve stanja i ne sudjeluje u pregovaranju o kodecima. Čita dovoljno metapodataka paketa da odabere odredište, a zatim prosljeđuje paket primopredajniku koji upravlja sesijom. primopredajnik i dalje vidi normalan WebRTC tok i i dalje upravlja cjelokupnim stanjem protokola. Iz perspektive klijenta ništa se u WebRTC sesiji ne mijenja.
Usmjeravanje prvog paketa ključan je korak u ovom sustavu. Relay mora usmjeriti prvi paket klijenta prije nego što sesija uopće postoji na samoj putanji paketa, umjesto da zastaje zbog vanjske usluge pretraživanja.
Svaka WebRTC sesija već nosi ugrađeni mehanizam usmjeravanja na razini protokola: ICE fragment korisničkog imena ili ufrag, kratki identifikator koji se razmjenjuje tijekom postavljanja sesije i ponavlja u STUN provjerama povezivosti. Generiramo ufrag na strani poslužitelja tako da sadrži tek toliko metapodataka usmjeravanja da relay može odrediti odredišni klaster i primopredajnik koji upravlja sesijom.
Tijekom signalizacije primopredajnik alocira stanje sesije i vraća zajednički relay VIP i UDP port u SDP odgovoru. VIP je virtualna IP adresa ispred flote relay instanci; u kombinaciji s portom daje klijentu jedno stabilno odredište, kao što je '203.0.113.10:3478', iako iza njega stoji mnogo relay instanci. Prvi paket na medijskoj putanji od klijenta obično je STUN (Session Traversal Utilities for NAT) zahtjev za povezivanje kojim ICE provjerava mogu li paketi dosegnuti oglašenu adresu.
Relay analizira tek onoliko prvog STUN paketa koliko je potrebno da pročita poslužiteljski ufrag, dekodira podatke za usmjeravanje i proslijedi paket primopredajniku koji upravlja sesijom. Svaki primopredajnik osluškuje na zajedničkom UDP socketu, što znači jednu krajnju točku operativnog sustava vezanu uz interni IP:port, a ne jedan socket po sesiji. Nakon što relay uspostavi sesiju od izvornog IP-a i porta klijenta do odredišta tog primopredajnika, naknadni DTLS, RTP i RTCP paketi prolaze unutar sesije bez ponovnog dekodiranja ufraga.
Sesija u relayu namjerno je minimalna i sastoji se samo od sesije u memoriji za potrebe prosljeđivanja paketa, zajedno s potrebnim brojačima za nadzor i mjeračima vremena za istjecanje i čišćenje sesije. Ovakav dizajn održava usmjeravanje paketa izravno na njihovoj putanji. Ako se relay ponovno pokrene i izgubi sesiju, sljedeći STUN paket ponovno uspostavlja sesiju iz podataka za usmjeravanje u ufragu. Kako bi sustav bio još pouzdaniji, koristi se Redis predmemorija koja zadržava mapiranje <IP klijenta + port, IP primopredajnika + port> nakon uspostave rute, tako da se ono može obnoviti mnogo ranije, prije dolaska sljedećeg STUN paketa.
Kad smo smanjili javnu UDP površinu na mali broj stabilnih adresa i portova, isti smo relay obrazac mogli implementirati globalno. Global Relay naša je flota geografski distribuiranih relay ulaznih točaka koje sve primjenjuju isti način prosljeđivanja paketa.
Široka geografska raspoređenost ulaznih točaka skraćuje prvi skok između klijenta i OpenAI-ja jer paket može ući u našu mrežu preko relaya bliskog korisniku, i zemljopisno i po mrežnoj topologiji, umjesto da najprije prelazi javni internet do udaljene regije. U praksi to znači manju latenciju, manje jittera i manje nepotrebnih gubitaka paketa prije nego što promet stigne do naše glavne mreže.6
Za signalizaciju koristimo Cloudflare geo i proximity steering kako bi početni HTTP ili WebSocket zahtjev stigao do obližnjeg klastera primopredajnika. Kontekst zahtjeva određuje lokaciju sesije i koja se ulazna točka Global Relaya oglašava klijentu. SDP odgovor pruža adresu Global Relaya, dok ufrag sadrži dovoljno informacija da Global Relay usmjeri medije prema određenom klasteru, a relay prema odredišnom primopredajniku.
Zajedno, geografski usmjerena signalizacija i Global Relay usmjeravaju i postavljanje veze i medije kroz obližnju ulaznu točku, a pritom sesiju zadržavaju vezanom uz jedan primopredajnik. Time se smanjuje vrijeme kružnog puta za signalizaciju i prvu ICE provjeru povezivosti, što izravno skraćuje vrijeme koje korisnik čeka prije nego što govor može započeti.
Uslugu relay napisali smo u Go-u i namjerno zadržali jednostavnu implementaciju. Na Linuxu mrežni stog jezgre prima UDP pakete s mrežnog sučelja računala i dostavlja ih socketu – krajnjoj točki operativnog sustava koju proces koristi nakon vezanja na IP:port. Relay radi u korisničkom prostoru pa običan Go proces čita zaglavlja paketa s tog socketa, ažurira malu količinu stanja toka i prosljeđuje pakete bez završavanja WebRTC-a. Nije nam bio potreban framework za zaobilaženje jezgre, koji bi korisničkom procesu omogućio izravno ispitivanje mrežnih redova radi veće brzine obrade paketa, ali bi pritom povećao operativnu složenost.
Ključni dizajnerski odabiri:
- Bez završavanja protokola: Relay analizira samo STUN zaglavlja i ufrag; za kasnije DTLS, RTP i RTCP koristi predmemorirano stanje, pa paketi ostaju neprozirni.
- Privremeno stanje: održava malu mapu u memoriji s kratkim istekom koja povezuje adresu klijenta s odredištem primopredajnika radi praćenja stanja toka i nadzora.
- Horizontalna skalabilnost: više relay instanci radi paralelno iza load balancera. Ne radi se o trajnom WebRTC stanju, pa ponovno pokretanje uzrokuje minimalne gubitke prometa i brz oporavak toka.
Mjere učinkovitosti:
SO_REUSEPORTje opcija socketa u Linuxu koja omogućuje da više relay radnika na istom računalu veže isti UDP port. Jezgra tada raspodjeljuje dolazne pakete među tim radnicima, čime se izbjegava usko grlo jedne petlje čitanja.runtime.LockOSThreadpovezuje svaki goroutine koji čita UDP s određenom niti operativnog sustava. U kombinaciji saSO_REUSEPORTto obično zadržava pakete istog toka (izvorni i odredišni IP:port plus protokol) na istoj CPU jezgri, poboljšava lokalnost predmemorije i smanjuje promjene konteksta.- Unaprijed alocirani međuspremnici i minimalno kopiranje održavaju nisku režiju analize i alokacije kako bi se izbjeglo automatsko oslobađanje memorije u Go-u.
Ova je implementacija obradila naš globalni promet medija u stvarnom vremenu uz relativno malu relay infrastrukturu, pa smo zadržali jednostavniji dizajn umjesto da krenemo putem zaobilaženja jezgre.
Ova nam arhitektura omogućuje da pokrećemo WebRTC medije na Kubernetesu bez izlaganja tisuća UDP portova. To je važno jer je manju i fiksnu UDP površinu lakše zaštititi i raspodijeliti opterećenje, a infrastruktura se može skalirati bez rezerviranja velikih raspona javnih portova. Uz bolju infrastrukturnu podršku Kubernetesa i veću sigurnost zbog manje površine, ovaj dizajn također čuva standardno ponašanje WebRTC-a za klijente i potvrđuje da je dizajn bez SFU-a bio pravi zadani izbor za naše opterećenje. Većina naših sesija je točka – točka, osjetljiva na latenciju i lakše se skalira kada usluge inferencije ne moraju funkcionirati kao WebRTC ravnopravni sudionici.
Šira pouka jest da je složenost najbolje dodati u tanki sloj usmjeravanja, a ne u svaku pozadinsku uslugu i ne u prilagođeno ponašanje klijenta. Kodiranje metapodataka usmjeravanja u polje ugrađeno u sam protokol omogućilo nam je determinističko usmjeravanje prvog paketa, malu javnu UDP površinu i dovoljno fleksibilnosti da ulazne točke postavimo blizu korisnika diljem svijeta.
Nekoliko je odluka bilo posebno važno:
- Očuvati semantiku protokola na rubu. Klijenti i dalje govore standardni WebRTC, što održava interoperabilnost preglednika i mobilnih uređaja.
- Držati trajna stanja sesije na jednom mjestu. Primopredajnik posjeduje ICE, DTLS, SRTP i životni ciklus sesije; relay samo prosljeđuje pakete.
- Usmjeravati prema informacijama koje su već prisutne u postavljanju. ICE ufrag omogućio nam je usmjeravanje prvog paketa bez potrebe za dodatnim pretraživanjem tijekom obrade paketa.
- Optimizirati za najčešći slučaj prije posezanja za zaobilaženjem jezgre. Uska Go implementacija uz pažljivo korištenje
SO_REUSEPORTA, povezivanje niti i analizu s malo alokacija bila je dovoljna za naše opterećenje.
Glasovni AI u stvarnom vremenu funkcionira samo kada infrastruktura čini latenciju nevidljivom. čini latenciju gotovo neprimjetnom. Za nas je to značilo promijeniti način implementacije WebRTC-a bez mijenjanja onoga što klijenti očekuju od samog WebRTC-a.
Autor
Reference
2. GitHub - l7mp/stunner: Kubernetes medijski pristupnik za WebRTC(otvara se u novom prozoru)
3. WebRTC portovi ukratko [primjeri] - BlogGeek.me(otvara se u novom prozoru)
4. Implementacija u Kubernetesu - LiveKit dokumentacija(otvara se u novom prozoru)
6. Cloudflare Calls: millions of cascading trees all the way down(otvara se u novom prozoru)


