Pāriet uz galveno saturu
OpenAI

2026. gada 4. maijs

Inženierija

Kā OpenAI nodrošina zemas aiztures balss MI lielā mērogā

Autori: Yi Zhang un William McDonald, tehniskā personāla locekļi

Balss MI šķiet dabisks tikai tad, ja saruna rit runas tempā. Kad tīkls traucē, cilvēki to uzreiz dzird kā neveiklas pauzes, aprautus pārtraukumus vai aizkavētu iespēju pārtraukt sistēmu ar savu runu. Tas ir svarīgi ChatGPT Balss, izstrādātājiem, kuri veido risinājumus ar Realtime API, aģentiem, kas darbojas interaktīvās darbplūsmās, un modeļiem, kuriem jāapstrādā audio, kamēr lietotājs vēl runā.

OpenAI mērogā tas nozīmē trīs konkrētas prasības:

  • Globāla sasniedzamība vairāk nekā 900 miljoniem iknedēļas aktīvo lietotāju
  • Ātra savienojuma izveide, lai lietotājs varētu sākt runāt, tiklīdz sākas sesija
  • Zems un stabils mediju turp-atpakaļ laiks, ar zemu drebēšanu un pakešu zudumu, lai sarunas ritms būtu plūdens

OpenAI komanda, kas atbild par MI mijiedarbību reāllaikā, nesen pārveidoja mūsu WebRTC steku, lai risinātu trīs ierobežojumus, kas mēroga apstākļos sāka sadurties: mediju terminēšana ar vienu portu katrai sesijai slikti sader ar OpenAI infrastruktūru, stāvokļiem bagātām ICE (Interactive Connectivity Establishment) un DTLS (Datagram Transport Layer Security) sesijām vajag stabilu piesaisti, un globālajai maršrutēšanai jāsaglabā zema pirmā lēciena aizture. Šajā rakstā izskaidrojam izveidoto sadalīto releja un raiduztvērēja arhitektūru, kas klientiem saglabā standarta WebRTC darbību, vienlaikus mainot to, kā paketes tiek maršrutētas OpenAI infrastruktūrā.

WebRTC ļauj mums veidot reāllaika MI produktus

WebRTC ir atvērts standarts zemas aiztures audio, video un datu sūtīšanai starp pārlūkiem, mobilajām lietotnēm un serveriem. To bieži saista ar vienādranga zvaniem, taču tas ir arī praktisks pamats klienta-servera reāllaika sistēmām, jo standartizē sarežģītākās interaktīvo mediju daļas: ICE savienojamības izveidei un NAT (Network Address Translation) šķērsošanai, DTLS un SRTP (Secure Real-time Transport Protocol) šifrētai pārraidei, kodeku sarunāšanai audio saspiešanai un dekodēšanai, RTCP (Real-time Transport Control Protocol) kvalitātes kontrolei un klienta puses funkcijas, piemēram, atbalss slāpēšanu un drebēšanas buferēšanu.

Šī standartizācija MI produktiem ir svarīga. Bez WebRTC katram klientam būtu vajadzīga cita atbilde uz jautājumu, kā izveidot savienojamību caur NAT, šifrēt medijus, vienoties par kodekiem (kodētājiem-dekodētājiem, kas izvēlēti pārraidei un atspiešanai) un pielāgoties mainīgiem tīkla apstākļiem. Ar WebRTC mēs varam balstīties uz protokolu steku, kas jau ir ieviests pārlūkos un mobilajās platformās, koncentrējot savu darbu uz infrastruktūru, kas savieno reāllaika medijus ar modeļiem.

Mēs balstāmies arī uz pašu WebRTC ekosistēmu, tostarp nobriedušām atvērtā pirmkoda implementācijām un standartizācijas darbu, kas uztur pārlūku, mobilo lietotņu un serveru savietojamību. Justin Uberti (viens no WebRTC sākotnējiem arhitektiem) un Sean DuBois (Pion izveidotājs un uzturētājs) pamatdarbs ļāva tādām komandām kā mūsējā izmantot kaujās pārbaudītu mediju infrastruktūru, nevis no jauna izstrādāt zema līmeņa transportu, šifrēšanu un pārslodzes kontroles uzvedību. Mums ir paveicies, ka gan Justin, gan Sean tagad ir mūsu kolēģi OpenAI un palīdz virzīt WebRTC un reāllaika MI ciešāk kopā.

MI jomā svarīgākā īpašība ir tā, ka audio pienāk kā nepārtraukta straume. Runājošs aģents var sākt transkribēt, spriest, izsaukt rīkus vai ģenerēt runu, kamēr lietotājs vēl runā, nevis gaidīt pilnu augšupielādi. Tā ir atšķirība starp sistēmu, kas šķiet sarunvalodīga, un sistēmu, kas līdzinās push-to-talk režīmam.

Mediju arhitektūras izvēle

Kad izvēlējāmies WebRTC, nākamais jautājums bija, kur to terminēt (kur pieņemt un pārvaldīt WebRTC savienojumu, piemēram, perifērijā) un kā šīs sesijas savienot ar inferenču aizmugursistēmu. Terminēšana ir svarīga, jo tā nosaka, kā pārvaldām reāllaika sesijas stāvokli, mediju transportu, maršrutēšanu, aizturi un kļūmju izolāciju.

1. variants: SFU pieeja ietver MI kā WebRTC dalībnieku

SFU jeb selektīvās pārsūtīšanas vienība ir mediju serveris, kas no katra dalībnieka saņem vienu WebRTC straumi un selektīvi pārsūta straumes pārējiem. Šajā modelī SFU terminē atsevišķu WebRTC savienojumu katram dalībniekam, un MI pievienojas kā vēl viens dalībnieks sesijā. Tas var būt labs risinājums produktiem, kas pēc būtības ir vairāku dalībnieku, piemēram, grupu zvaniem, mācību nodarbībām vai sadarbības sanāksmēm. Tas uztur audio kodekus, RTCP ziņojumus, datu kanālus, ierakstīšanu un katras straumes politiku vienuviet.1

Pat klienta-MI produktos SFU bieži ir noklusējuma sākumpunkts, jo tas ļauj komandām atkārtoti izmantot vienu pārbaudītu sistēmu signalizācijai, mediju maršrutēšanai, ierakstīšanai, novērojamībai un nākotnes paplašinājumiem, piemēram, cilvēka pārņemšanai vai papildu dalībnieku pievienošanai.

2. variants: raiduztvērēja pieeja terminē WebRTC perifērijā un pārveido to par aizmugursistēmas protokolu

Mūsu slodze ir citāda. Lielākā daļa sesiju ir 1:1 — viens lietotājs runā ar vienu modeli vai viena lietotne runā ar vienu reāllaika aģentu — un katrā mijā ir augsta jutība pret aizturi. Šāda veida plūsmai mēs izvēlējāmies raiduztvērēja modeli: WebRTC perifērijas pakalpojums terminē klienta savienojumu un pēc tam pārveido medijus un notikumus vienkāršākos iekšējos protokolos modeļa inferencei, transkribēšanai, runas ģenerēšanai, rīku izmantošanai un orķestrēšanai.

Šajā dizainā raiduztvērējs ir vienīgais pakalpojums, kam pieder WebRTC sesijas stāvoklis, tostarp ICE savienojamības pārbaudes, DTLS rokasspiediens, SRTP šifrēšanas atslēgas un sesijas dzīvescikls. “Terminēšana” šeit nozīmē, ka raiduztvērējs ir galapunkts, kas pabeidz šos rokasspiedienus un šifrē vai atšifrē medijus. Šī stāvokļa turēšana vienuviet atviegloja izpratni par sesijas piederību un ļāva aizmugursistēmas pakalpojumiem mērogoties kā parastiem pakalpojumiem, nevis pašiem darboties kā WebRTC vienādrangiem.

Pamata izvietošanas problēma: WebRTC satiekas ar Kubernetes

Pēc raiduztvērēja modeļa izvēles mūsu pirmā implementācija bija viens Go pakalpojums uz Pion bāzes, kas apstrādāja gan signalizāciju, gan mediju terminēšanu. Tas nodrošina ChatGPT Balss, Realtime API WebRTC galapunktu un vairākus pētniecības projektus.

No darbības viedokļa raiduztvērēja pakalpojums veic divus uzdevumus:

  • Signalizācija: SDP sarunāšana, kodeku izvēle, ICE akreditācijas dati un sesijas iestatīšana
  • Mediji: lejupējās WebRTC savienojumu terminēšana un augšupējo savienojumu uzturēšana ar aizmugursistēmas pakalpojumiem inferencei un orķestrēšanai

Mēs vēlējāmies, lai pakalpojums darbotos tāpat kā pārējā mūsu infrastruktūra: uz Kubernetes, kur slodzes var mērogoties uz augšu un leju un pārvietoties starp resursdatoriem, pieprasījumam mainoties. Taču tradicionālais WebRTC modelis ar vienu portu katrai sesijai šādai videi slikti der, jo tas paļaujas uz lieliem publisko UDP portu diapazoniem, kurus ir grūti atklāt, aizsargāt un saglabāt, kad podi tiek pievienoti, noņemti vai pārsplānoti.2

Portu izsīkums

Pirmā problēma bija pats modelis ar vienu portu katrai sesijai. Pie augstas vienlaicības tas nozīmē ļoti lielu UDP portu diapazonu atvēršanu un pārvaldību.

  • Mākoņslodzes balansētāji un Kubernetes pakalpojumi nav veidoti desmitiem tūkstošu publisko UDP portu uz vienu pakalpojumu. Katrs papildu diapazons palielina darbības sarežģītību slodzes balansētāja konfigurācijā, veselības pārbaudēs, ugunsmūra politikā un drošā izvēršanā.3
  • Lielus UDP portu diapazonus ir grūti aizsargāt, jo tie paplašina no ārpuses sasniedzamo virsmu un apgrūtina tīkla politikas auditu.
  • Tie arī slikti sader ar automātisku mērogošanu. Kubernetes vidē podi tiek pastāvīgi pievienoti, noņemti un pārsplānoti. Prasība katram podam rezervēt un reklamēt lielu stabilu portu diapazonu padara šo elastību trauslu.4

Tāpēc daudzas WebRTC sistēmas virzās uz vienu UDP portu katram serverim, ar lietojumprogrammas līmeņa demultipleksēšanu aiz šī porta.5

Stāvokļa piesaiste

Risinājumi ar vienu portu uz serveri atrisina portu skaitu, taču rada otru problēmu: kā visā kopā saglabāt katras sesijas piederību.

ICE un DTLS ir stāvokļiem bagāti protokoli. Procesam, kas izveidoja sesiju, jāturpina saņemt šīs sesijas paketes, lai tas varētu pārbaudīt savienojamības pārbaudes, pabeigt DTLS rokasspiedienu, atšifrēt SRTP un apstrādāt vēlākas sesijas izmaiņas, piemēram, ICE restartus. Ja vienas un tās pašas sesijas paketes nonāk citā procesā, iestatīšana var neizdoties vai mediju plūsma var sabrukt.

Tas mums deva konkrētu mērķi: internetam atklāt nelielu, fiksētu UDP virsmu, vienlaikus joprojām maršrutējot katru paketi uz raiduztvērēju, kam pieder attiecīgā WebRTC sesija.

WebRTC mediju arhitektūru salīdzinājums

Mēs izvērtējām vairākus veidus, kā to panākt, tostarp TURN (Traversal Using Relays around NAT), kur perifērijas relejs terminē klientu piešķīrumus un pārsūta trafiku to vārdā.2

Pieeja

Priekšrocības

Trūkumi

Unikāls IP:ports katrai sesijai (zināms arī kā vietējais tiešais UDP)

Tiešs klienta-servera mediju ceļš

Datu ceļā nav pārsūtīšanas slāņa

Katrai sesijai vajadzīgs viens publisks UDP ports

Lielus portu diapazonus ir grūti atklāt un aizsargāt

Slikti sader ar Kubernetes un mākoņa slodzes balansētājiem

Unikāls IP:ports katram serverim

Daudz mazāks publiskais UDP nospiedums nekā atklājot portus katrai sesijai

Viena koplietota ligzda katram serverim var demultipleksēt daudzas sesijas

Labi darbojas vienā resursdatorā, bet pats par sevi neder koplietotai, ar slodzes balansētāju balansētai kopai

Sesiju demultipleksēšana vienā resursdatorā palīdz tikai pēc tam, kad pakete to sasniedz; kopā ar slodzes balansētāju pirmā pakete joprojām var nonākt nepareizajā instancē, tāpēc joprojām vajag deterministisku veidu, kā novirzīt katru sesiju uz procesu, kam tā pieder


TURN relejs (ar protokola terminēšanu)

Klientiem jāsasniedz tikai TURN releja adrese un ports

Var centralizēt politiku perifērijā

TURN piešķīrumi pievieno papildu iestatīšanas turp-atpakaļ ciklus

Piešķīrumu pārvietošana vai atjaunošana starp TURN serveriem joprojām ir sarežģīta

Bezstāvokļa pārsūtītājs + stāvokļiem bagāts terminētājs (OpenAI relejs + raiduztvērējs)

Neliels publiskais UDP nospiedums

Raiduztvērējs joprojām pārvalda pilnu WebRTC sesiju

Pievieno vienu pārsūtīšanas lēcienu, pirms mediji sasniedz sesijas īpašnieku raiduztvērēju

Nepieciešama pielāgota koordinācija starp releju un raiduztvērēju

Arhitektūras pārskats: relejs + raiduztvērējs

Mūsu ieviestā arhitektūra atdala pakešu maršrutēšanu no protokola terminēšanas. Signalizācija joprojām nonāk pie raiduztvērēja sesijas iestatīšanai, savukārt mediji vispirms ieiet caur releju. Relejs ir viegls UDP pārsūtīšanas slānis ar nelielu publisko virsmu, bet raiduztvērējs ir aiz tā esošais stāvokļiem bagātais WebRTC galapunkts.

Relejs bez stāvokļa pārsūta paketes raiduztvērējam

Relejs neatslēdz mediju šifrēšanu, nedarbina ICE stāvokļu mašīnas un nepiedalās kodeku sarunāšanā. Tas nolasa tik daudz pakešu metadatu, cik vajag galamērķa izvēlei, un pēc tam pārsūta paketi raiduztvērējam, kam pieder sesija. Raiduztvērējs joprojām redz normālu WebRTC plūsmu un joprojām pārvalda visu protokola stāvokli. No klienta skatpunkta WebRTC sesijā nekas nemainās.

Maršrutēšana pēc ICE akreditācijas datiem

Pirmās paketes maršrutēšana ir galvenais solis šajā uzbūvē. Relejam jāmaršrutē pirmā klienta pakete, pirms pašā pakešu ceļā vēl eksistē sesija, nevis jāaptur plūsma, gaidot ārēju uzmeklēšanas pakalpojumu.

Katrai WebRTC sesijai jau ir protokolam raksturīgs maršrutēšanas āķis: ICE lietotājvārda fragments jeb ufrag, īss identifikators, ar ko apmainās sesijas iestatīšanas laikā un ko atkārto STUN savienojamības pārbaudēs. Mēs ģenerējam servera puses ufrag tā, lai tajā būtu tieši tik daudz maršrutēšanas metadatu, cik relejam vajag, lai noteiktu galamērķa klasteri un īpašnieku raiduztvērēju.

Sekvenču diagramma rāda, kā tiek izveidots savienojums

Signalizācijas laikā raiduztvērējs piešķir sesijas stāvokli un SDP atbildē atgriež koplietotu releja VIP un UDP portu. VIP ir virtuāla IP adrese, kas ir releju kopas priekšā; kopā ar portu tā klientam nodrošina vienu stabilu galamērķi, piemēram, `203.0.113.10:3478`, lai gan aiz tā atrodas daudzas releja instances. Klienta pirmā pakete mediju ceļā parasti ir STUN (Session Traversal Utilities for NAT) piesaistes pieprasījums, ko ICE izmanto, lai pārbaudītu, vai paketes var sasniegt reklamēto adresi.

Relejs parsē tieši tik daudz no šīs pirmās STUN paketes, lai nolasītu servera ufrag, atkodētu maršrutēšanas norādi un pārsūtītu paketi īpašnieka raiduztvērējam. Katrs raiduztvērējs klausās uz koplietotas UDP ligzdas, proti, viena operētājsistēmas galapunkta, kas piesaistīts iekšējai IP:portam, nevis vienas ligzdas katrai sesijai. Pēc tam, kad relejs izveido sesiju no klienta avota IP:porta uz šo raiduztvērēja galamērķi, turpmākās DTLS, RTP un RTCP paketes plūst sesijas ietvaros bez atkārtotas ufrag atkodēšanas.

Releja sesija apzināti ir minimāla, sastāvot tikai no atmiņā glabātas sesijas, kas informē pakešu pārsūtīšanu, kā arī nepieciešamajiem skaitītājiem uzraudzībai un taimeriem sesijas termiņa beigām un tīrīšanai. Šī dizaina izvēle saglabā pakešu maršrutēšanu tieši pakešu ceļā. Ja relejs tiek restartēts un pazaudē sesiju, nākamā STUN pakete atjauno sesiju no ufrag maršrutēšanas norādes. Lai to padarītu vēl uzticamāku, tiek izmantota Redis kešatmiņa, kas glabā kartējumu <klienta IP + ports, raiduztvērēja IP + ports> pēc maršruta izveides, lai to varētu atgūt daudz agrāk — vēl pirms pienāk nākamā STUN pakete.

Global Relay un ģeogrāfiski virzīta signalizācija

Kad samazinājām publisko UDP virsmu līdz nelielam skaitam stabilu adrešu un portu, varējām izvietot to pašu releja modeli globāli. Global Relay ir mūsu ģeogrāfiski izkliedēto releja ieejas punktu kopa, un visi tie īsteno vienu un to pašu pakešu pārsūtīšanas uzvedību.

Plašs ģeogrāfisks ieejas pārklājums saīsina pirmo lēcienu no klienta uz OpenAI, jo pakete var ienākt mūsu tīklā relejā, kas atrodas lietotājam tuvu gan ģeogrāfiski, gan tīkla topoloģijas ziņā, nevis vispirms šķērsot publisko internetu līdz attālam reģionam. Praktiski tas nozīmē zemāku aizturi, mazāku drebēšanu un mazāk novēršamu zudumu uzliesmojumu, pirms trafiks sasniedz mūsu mugurkaula tīklu.6

Global Relay slānis saņem paketes no klienta un pārsūta tās uz raiduztvērēju klasteri

Signalizācijai izmantojam Cloudflare ģeogrāfisko un tuvuma virzīšanu, lai sākotnējais HTTP vai WebSocket pieprasījums nonāktu tuvējā raiduztvērēju klasterī. Pieprasījuma konteksts nosaka sesijas atrašanās vietu un to, kurš Global Relay ieejas punkts tiek reklamēts klientam. SDP atbilde sniedz Global Relay adresi, savukārt ufrag satur pietiekamu informāciju, lai Global Relay varētu maršrutēt medijus uz norādīto klasteri un relejs — uz galamērķa raiduztvērēju.

Kopā ģeogrāfiski virzītā signalizācija un Global Relay novieto gan iestatīšanu, gan medijus tuvā ieejas ceļā, vienlaikus saglabājot sesiju piesaistītu vienam raiduztvērējam. Tas samazina turp-atpakaļ laiku signalizācijai un pirmajai ICE savienojamības pārbaudei, kas tieši saīsina laiku, ko lietotājs gaida, pirms var sākties runa.

Releja implementācija un veiktspēja

Releja pakalpojumu rakstījām Go valodā un apzināti saglabājām šauru ieviešanu. Linux sistēmā kodola tīkla steks saņem UDP paketes no mašīnas tīkla interfeisa un nogādā tās ligzdā — operētājsistēmas galapunktā, ko process lasa pēc IP:porta piesaistīšanas. Relejs darbojas lietotājtelpā, tāpēc parasts Go process nolasa pakešu galvenes no šīs ligzdas, atjaunina nelielu plūsmas stāvokļa daudzumu un pārsūta paketes, neterminējot WebRTC. Mums nebija vajadzīgs neviens kodola apiešanas ietvars, kas ļautu lietotājtelpas procesam tieši aptaujāt tīkla rindas augstākiem pakešu ātrumiem, bet vienlaikus palielinātu darbības sarežģītību.

Galvenās dizaina izvēles:

  • Bez protokola terminēšanas: relejs parsē tikai STUN galvenes/ufrag; turpmākajām DTLS, RTP un RTCP paketēm tas izmanto kešotu stāvokli, saglabājot paketes necaurspīdīgas.
  • Īslaicīgs stāvoklis: tas uztur nelielu, īsa taimauta, atmiņā glabātu karti no klienta adreses uz raiduztvērēja galamērķi plūsmas stāvoklim un novērojamībai.
  • Horizontāla mērogojamība: vairākas releja instances darbojas paralēli aiz slodzes balansētāja. Šis stāvoklis nav stingrs WebRTC stāvoklis, tāpēc restarti rada minimālus trafika zudumus un ļauj ātri atjaunot plūsmas.

Efektivitātes pasākumi:

  • SO_REUSEPORT ir Linux ligzdu opcija, kas ļauj vairākiem releja darbiniekiem vienā mašīnā piesaistīt vienu un to pašu UDP portu. Pēc tam kodols sadala ienākošās paketes starp šiem darbiniekiem, kas novērš viena lasīšanas cikla pudeles kaklu.
  • runtime.LockOSThread piesaista katru UDP lasošo gorutīnu konkrētai OS pavedienam. Kombinācijā ar SO_REUSEPORT tas parasti palīdz vienas un tās pašas plūsmas paketes (avota un galamērķa IP:ports plus protokols) noturēt uz viena CPU kodola, uzlabojot kešatmiņas lokalitāti un samazinot konteksta pārslēgšanu.
  • Iepriekš piešķirti buferi un minimāla kopēšana uztur zemu parsēšanas un atmiņas piešķiršanas pieskaitāmo izmaksu līmeni, lai Go valodā izvairītos no atkritumu savākšanas.

Šī implementācija apstrādāja mūsu globālo reāllaika mediju trafiku ar salīdzinoši nelielu releja nospiedumu, tāpēc izvēlējāmies paturēt vienkāršāko dizainu, nevis iet kodola apiešanas ceļu.

Rezultāti un secinājumi

Šī arhitektūra ļauj mums darbināt WebRTC medijus Kubernetes vidē, neatklājot tūkstošiem UDP portu. Tas ir svarīgi, jo mazāku un fiksētu UDP virsmu ir vieglāk aizsargāt un balansēt, un tā ļauj infrastruktūrai mērogoties, nerezervējot lielus publisko portu diapazonus. Ar labāku Kubernetes atbalstu infrastruktūrā un lielāku drošību mazākas uzbrukuma virsmas dēļ šis dizains arī saglabā klientiem standarta WebRTC uzvedību un apstiprina, ka dizains bez SFU bija pareizā noklusējuma izvēle mūsu slodzei. Lielākā daļa mūsu sesiju ir punkts-punkts, jutīgas pret aizturi un vieglāk mērogojamas, kad inferenču pakalpojumiem nav jāuzvedas kā WebRTC vienādrangiem.

Plašāka mācība ir tāda, ka sarežģītību vislabāk pievienot plānā maršrutēšanas slānī, nevis katrā aizmugursistēmas pakalpojumā un ne pielāgotā klienta uzvedībā. Maršrutēšanas metadatu kodēšana protokolam raksturīgā laukā deva mums deterministisku pirmās paketes maršrutēšanu, nelielu publisko UDP nospiedumu un pietiekamu elastību, lai novietotu ieejas punktus tuvu lietotājiem visā pasaulē.

Dažas izvēles bija īpaši svarīgas:

  • Saglabāt protokola semantiku perifērijā. Klienti joprojām izmanto standarta WebRTC, kas uztur pārlūku un mobilo ierīču savietojamību.
  • Sarežģītos sesijas stāvokļus turēt vienuviet. Raiduztvērējs pārvalda ICE, DTLS, SRTP un sesijas dzīvesciklu; relejs tikai pārsūta paketes.
  • Maršrutēt pēc informācijas, kas jau ir iestatīšanas laikā. ICE ufrag deva mums pirmās paketes maršrutēšanas āķi, nepievienojot karstā ceļa uzmeklēšanas atkarību.
  • Optimizēt biežākajam gadījumam, pirms ķerties pie kodola apiešanas. Šaura Go implementācija ar rūpīgu SO_REUSEPORT, pavedienu piesaistes un zema piešķīrumu parsēšanas izmantošanu bija pietiekama mūsu slodzei.

Reāllaika balss MI darbojas tikai tad, ja infrastruktūra liek aizturei šķist neredzamai. Mums tas nozīmēja mainīt mūsu WebRTC izvietojuma formu, nemainot to, ko klienti sagaida no paša WebRTC.