Hopp til hovedinnhold
OpenAI

4. mai 2026

Teknisk arbeid

Slik leverer OpenAI tale-AI med lav latenstid i stor skala

Av Yi Zhang og William McDonald, medlemmer av teknisk stab

Tale-AI føles bare naturlig hvis samtalen beveger seg i samme tempo som talen. Når nettverket kommer i veien, merker folk det umiddelbart som pinlige pauser, avklipte avbrytelser eller forsinket innbryting. Det er viktig for ChatGPT Voice, for utviklere som bygger med Realtime API, for agenter som jobber i interaktive arbeidsflyter, og for modeller som må behandle lyd mens brukeren fortsatt snakker.

I OpenAIs skala gir det tre konkrete krav:

  • Global rekkevidde for mer enn 900 millioner ukentlige aktive brukere
  • Rask oppkobling slik at brukeren kan begynne å snakke så snart en økt starter
  • Lav og stabil rundturstid for media, med lav jitter og pakketap, slik at turtaking føles presis

Teamet i OpenAI som har ansvar for AI-interaksjoner i sanntid, har nylig redesignet WebRTC-stakken vår for å håndtere tre begrensninger som begynte å kollidere i stor skala: medieterminering med én port per økt passer dårlig med OpenAIs infrastruktur, tilstandsfulle ICE-økter (Interactive Connectivity Establishment) og DTLS-økter (Datagram Transport Layer Security) trenger stabilt eierskap, og global ruting må holde latenstiden i første hopp lav. I dette innlegget går vi gjennom den delte arkitekturen med relay pluss transceiver som vi bygget for å bevare standard WebRTC-atferd for klienter, samtidig som vi endret hvordan pakker rutes internt i OpenAIs infrastruktur.

WebRTC lar oss bygge AI-produkter i sanntid

WebRTC er en åpen standard for sending av lyd, video og data med lav latenstid mellom nettlesere, mobilapper og servere. Den forbindes ofte med peer-to-peer-samtaler, men er også et praktisk grunnlag for sanntidssystemer for klient-til-server fordi den standardiserer de vanskelige delene av interaktive medier: ICE for etablering av tilkobling og NAT-traversering (Network Address Translation), DTLS og SRTP (Secure Real-time Transport Protocol) for kryptert transport, kodekforhandling for komprimering og dekoding av lyd, RTCP (Real-time Transport Control Protocol) for kvalitetskontroll, og funksjoner på klientsiden som ekkokansellering og jitterbuffer.

Denne standardiseringen er viktig for AI-produkter. Uten WebRTC ville hver klient trenge et eget svar på hvordan tilkobling skal etableres på tvers av NAT-er, hvordan medier skal krypteres, hvordan kodeker skal forhandles (koderen-dekoderne som velges for overføring og dekomprimering), og hvordan man skal tilpasse seg endrede nettverksforhold. Med WebRTC kan vi bygge på en protokollstakk som allerede er implementert på tvers av nettlesere og mobile plattformer, og fokusere vårt eget arbeid på infrastrukturen som kobler sanntidsmedier til modeller.

Vi bygger også på selve WebRTC-økosystemet, inkludert modne implementeringer med åpen kildekode og standardiseringsarbeidet som holder nettlesere, mobilapper og servere interoperable. Grunnleggende arbeid av Justin Uberti (en av de opprinnelige arkitektene bak WebRTC) og Sean DuBois (skaper og vedlikeholder av Pion) gjorde det mulig for team som vårt å bygge på gjennomprøvd medieinfrastruktur i stedet for å gjenoppfinne lavnivåatferd for transport, kryptering og overbelastningskontroll. Vi er heldige som nå har både Justin og Sean som kolleger her i OpenAI, der de bidrar til å forme hvordan vi bringer WebRTC og AI i sanntid tettere sammen.

For AI er den viktigste egenskapen at lyden kommer som en kontinuerlig strøm. En talende agent kan begynne å transkribere, resonnere, kalle verktøy eller generere tale mens brukeren fortsatt snakker, i stedet for å vente på en full opplasting. Det er forskjellen mellom et system som føles samtalebasert og et som føles som trykk-for-å-snakke.

Valg av mediearkitektur

Da vi hadde valgt WebRTC, var neste spørsmål hvor vi skulle terminere det (hvor vi skulle ta imot og eie WebRTC-tilkoblingen, for eksempel ved kanten) og hvordan vi skulle koble disse øktene til inferens-backenden. Terminering er viktig fordi den avgjør hvordan vi håndterer øktstatus i sanntid, medieoverføring, ruting, latenstid og feilisolering.

Alternativ 1: SFU-tilnærmingen inkluderer AI som en WebRTC-deltaker

En SFU, eller selektiv videresendingsenhet, er en medieserver som mottar én WebRTC-strøm fra hver deltaker og selektivt videresender strømmer til de andre. I denne modellen terminerer SFU-en en separat WebRTC-tilkobling for hver deltaker, og KI-en blir med som egen deltaker i økten. Det kan passe godt for produkter som i utgangspunktet er flerpartsbaserte, som gruppesamtaler, undervisning eller samarbeidsmøter. Det samler lydkodeker, RTCP-meldinger, datakanaler, opptak og retningslinjer per strøm på ett sted.1

Selv i produkter der brukeren snakker med KI, er en SFU ofte standard utgangspunkt fordi den lar team gjenbruke ett utprøvd system for signalering, medieruting, opptak, observerbarhet og fremtidige utvidelser som overføring til menneskelig operatør eller å legge til flere deltakere.

Alternativ 2: Transceiver-tilnærmingen terminerer WebRTC ved kanten og konverterer til en backend-protokoll

Vår arbeidslast er annerledes. De fleste økter er 1:1—én bruker som snakker med én modell, eller én applikasjon som snakker med én agent i sanntid—med følsomhet for latenstid i hver tur. For denne trafikktype valgte vi en transceiver-modell: en WebRTC-kanttjeneste terminerer klienttilkoblingen og konverterer deretter medier og hendelser til enklere interne protokoller for modellinferens, transkripsjon, talegenerering, verktøybruk og orkestrering.

I dette designet er transceiveren den eneste tjenesten som eier WebRTC-økttilstanden, inkludert ICE-tilkoblingssjekker, DTLS-håndtrykket, SRTP-krypteringsnøkler og øktens livssyklus. «Terminering» betyr her at transceiveren er endepunktet som fullfører disse håndtrykkene og krypterer eller dekrypterer mediene. Å holde denne tilstanden på ett sted gjorde det enklere å forstå økteierskap, og det lot backend-tjenester skalere som vanlige tjenester i stedet for å opptre som WebRTC-peere selv.

Kjernproblemet i utrullingen: WebRTC møter Kubernetes

Etter å ha valgt transceiver-modellen var vår første implementering én enkelt Go-tjeneste bygget på Pion som håndterte både signalering og medieterminering. Den driver ChatGPT Voice, WebRTC-endepunktet i Realtime API og en rekke forskningsprosjekter.

Operasjonelt gjør transceiver-tjenesten to jobber:

  • Signalering: SDP-forhandling, valg av kodek, ICE-legitimasjon og oppsett av økt
  • Media: Terminering av nedstrøms WebRTC-tilkoblinger og opprettholdelse av oppstrømstilkoblinger til backend-tjenester for inferens og orkestrering

Vi ønsket at tjenesten skulle kjøre som resten av infrastrukturen vår: på Kubernetes, der arbeidslaster kan skaleres opp og ned, og flyttes mellom verter når etterspørselen endrer seg. Men den konvensjonelle WebRTC-modellen med én port per økt passer dårlig inn i dette miljøet, fordi den er avhengig av store offentlige UDP-portområder som er vanskelige å eksponere, sikre og bevare når pods legges til, fjernes eller omplanlegges.2

Portutmattelse

Det første problemet var selve modellen med én port per økt. Ved høy samtidighet betyr det å eksponere og administrere svært store UDP-portområder.

  • Skylastbalanserere og Kubernetes-tjenester er ikke utformet rundt titusenvis av offentlige UDP-porter per tjeneste. Hvert ekstra område øker den operative kompleksiteten i lastbalansererkonfigurasjon, helsesjekker, brannmurpolicy og sikker utrulling.3
  • Store UDP-portområder er vanskelige å sikre fordi de utvider den eksternt tilgjengelige flaten og gjør nettverkspolicy vanskeligere å revidere.
  • De passer også dårlig til automatisk skalering. Pods blir kontinuerlig lagt til, fjernet og omplanlagt i Kubernetes. Å kreve at hver pod reserverer og annonserer et stort stabilt portområde gjør denne elastisiteten skjør.4

Det er derfor mange WebRTC-systemer beveger seg mot én enkelt UDP-port per server, med demultipleksing på applikasjonsnivå bak den porten.5

Tilstandsklebrighet

Design med én port per server løser portantallet, men introduserer et annet problem: å bevare eierskapet til hver økt på tvers av en flåte.

ICE og DTLS er tilstandsfulle protokoller. Prosessen som opprettet en økt må fortsette å motta pakkene for den økten slik at den kan validere tilkoblingssjekker, fullføre DTLS-håndtrykket, dekryptere SRTP og behandle senere endringer i økten som ICE-omstarter. Hvis pakker for samme økt havner i en annen prosess, kan oppsettet mislykkes eller mediene bryte sammen.

Det ga oss et konkret mål: eksponere en liten, fast UDP-flate mot det offentlige internettet, samtidig som hver pakke fortsatt rutes til transceiveren som eier den tilhørende WebRTC-økten.

Sammenligning av WebRTC-mediearkitekturer

Vi evaluerte flere måter å gjøre dette på, inkludert TURN (Traversal Using Relays around NAT), der en kantrelé terminerer klientallokeringer og videresender trafikk på deres vegne.2

Tilnærming

Fordeler

Ulemper

Unik IP:port per økt (også kjent som direkte innebygd UDP)

Direkte mediebane fra klient til server

Ingen videresendingslag i datastien

Krever én offentlig UDP-port per økt

Store portområder er vanskelige å eksponere og sikre

Passer dårlig for Kubernetes og skylastbalanserere

Unik IP:port per server

Mye mindre offentlig UDP-flate enn eksponering per økt

Én delt socket per server kan demultiplekse mange økter

Fungerer godt på én enkelt vert, men ikke alene i en delt lastbalansert flåte

Demultipleksing av økter på én enkelt vert hjelper først etter at en pakke har nådd den aktuelle verten; i en lastbalansert flåte kan den første pakken fortsatt havne på feil instans, så du trenger fortsatt en deterministisk måte å styre hver økt til prosessen som eier den


TURN-relay (protokollterminerende)

Klienter trenger bare å nå TURN-relayens adresse og port

Kan sentralisere policy ved kanten

TURN-allokeringer legger til ekstra rundturstid i oppsettet

Det er fortsatt vanskelig å flytte eller gjenopprette allokeringer på tvers av TURN-servere

Tilstandsløs videresender + tilstandsfull terminator (OpenAIs relay + transceiver)

Liten offentlig UDP-flate

Transceiveren eier fortsatt hele WebRTC-økten

Legger til ett videresendingshopp før mediet når den eierende transceiveren

Krever tilpasset koordinering mellom relay og transceiver

Arkitekturoversikt: relay + transceiver

Arkitekturen vi satte i produksjon skiller pakkeruting fra protokollterminering. Signalisering går fortsatt til transceiveren for oppsett av økt, mens medier først går inn gjennom relayen. Relayen er et lettvektslag for UDP-videresending med en liten offentlig flate, og transceiveren er det tilstandsfulle WebRTC-endepunktet bak den.

Relay videresender pakker tilstandsløst til transceiver

Relayen dekrypterer ikke medier, kjører ikke ICE-tilstandsmaskiner og deltar ikke i kodekforhandling. Den leser nok pakkemetadata til å velge en destinasjon og videresender deretter pakken til transceiveren som eier økten. Transceiveren ser fortsatt en normal WebRTC-flyt og eier fortsatt all protokolltilstand. Fra klientens perspektiv endres ingenting ved WebRTC-økten.

Ruting basert på ICE-legitimasjon

Ruting av første pakke er nøkkelsteget i dette oppsettet. En relay må rute den første pakken fra en klient før det finnes noen økt på selve pakkestien, i stedet for å stoppe opp ved en ekstern oppslagstjeneste.

Hver WebRTC-økt har allerede en innebygd rutingsmekanisme i protokollen: ICE-brukernavnfragmentet, eller ufrag, en kort identifikator som utveksles under øktoppsett og gjentas i STUN-tilkoblingssjekker. Vi genererer ufrag på serversiden slik at det inneholder akkurat nok rutingsmetadata til at relayen kan utlede destinasjonsklyngen og den tilhørende transceiveren.

Sekvensdiagrammet viser hvordan tilkoblingen opprettes

Under signaleringen allokerer transceiveren økttilstand og returnerer en delt relay-VIP og UDP-port i SDP-svaret. En VIP er en virtuell IP-adresse foran relay-flåten; kombinert med porten gir den klienten én stabil destinasjon, som `203.0.113.10:3478`, selv om mange relay-instanser ligger bak den. Den første pakken på mediastien fra klienten er vanligvis en STUN-binding request, som ICE bruker for å verifisere at pakker kan nå den annonserte adressen.

Relayen parser akkurat nok av denne første STUN-pakken til å lese serverens ufrag, dekode rutingshintet og videresende pakken til den eierende transceiveren. Hver transceiver lytter på en delt UDP-socket, altså ett operativsystemendepunkt bundet til en intern IP:port, ikke én socket per økt. Etter at relayen oppretter en økt fra klientens kilde-IP:port til denne transceiver-destinasjonen, flyter påfølgende DTLS-, RTP- og RTCP-pakker innenfor økten uten å dekode ufrag på nytt.

Reléøkten er bevisst minimal og består kun av én økt i minnet for å styre pakkevideresending, sammen med nødvendige tellere for overvåking og timere for utløp og opprydding av økter. Dette designvalget holder pakkerutingen direkte i pakkestien. Hvis et relé starter på nytt og mister økten, bygger neste STUN-pakke opp økten på nytt fra rutingshintet i ufrag. For å gjøre det enda mer pålitelig brukes en Redis-cache til å lagre mappingen av <klient-IP + port, transceiver-IP + port> når ruten er etablert, slik at den kan gjenopprettes mye tidligere, før neste STUN-pakke kommer.

Global Relay og geografisk styrt signalering

Da vi hadde redusert den offentlige UDP-flaten til et lite antall stabile adresser og porter, kunne vi distribuere det samme relay-mønsteret globalt. Global Relay er vår flåte av geografisk distribuerte relay-inngangspunkter som alle implementerer den samme atferden for pakkevideresending.

Bred geografisk ingress forkorter det første hoppet fra klient til OpenAI fordi en pakke kan komme inn i nettverket vårt ved en relay nær brukeren, både geografisk og i nettverkstopologi, i stedet for å krysse det offentlige internettet til en fjern region først. I praksis betyr det lavere latenstid, mindre jitter og færre tapstopper som kan unngås før trafikken når ryggradsnettet vårt.6

Global Relay-laget mottar pakker fra klienten og videresender dem til transceiver-klyngen

Vi bruker Cloudflare geo- og nærhetsstyring for signalering slik at den første HTTP- eller WebSocket-forespørselen når en transceiver-klynge i nærheten. Konteksten til forespørselen avgjør plasseringen til økten og hvilket Global Relay-inngangspunkt som annonseres til klienten. SDP-svaret oppgir Global Relay-adressen, mens ufrag inneholder nok informasjon til at Global Relay kan rute medier til den angitte klyngen og relayen kan rute til måltransceiveren.

Sammen gjør geografisk styrt signalering og Global Relay at både oppsett og medier får en inngangsvei i nærheten, samtidig som økten holdes forankret til én transceiver. Det reduserer rundturstiden for signalering og for den første ICE-tilkoblingssjekken, noe som direkte forkorter hvor lenge en bruker må vente før tale kan starte.

Implementering og ytelse for relay

Vi skrev relay-tjenesten i Go og holdt implementeringen bevisst smal. På Linux mottar kjernens nettverksstakk UDP-pakker fra maskinens nettverksgrensesnitt og leverer dem til en socket, operativsystemendepunktet som en prosess leser fra etter å ha bundet en IP:Port. Relay kjører i brukermodus, så en vanlig Go-prosess leser pakkehoder fra denne socketen, oppdaterer en liten mengde flyttilstand og videresender pakker uten å terminere WebRTC. Vi trengte ikke noe rammeverk for kernel bypass, som ville la en prosess i brukermodus polle nettverkskøer direkte for høyere pakkerater, men som også ville legge til operativ kompleksitet.

Viktige designvalg:

  • Ingen protokollterminering: Relay parser bare STUN-hoder/ufrag; den bruker cachet tilstand for påfølgende DTLS, RTP og RTCP, og holder pakkene ugjennomsiktige.
  • Flyktig tilstand: Den opprettholder et lite, kortvarig kart i minnet fra klientadresse til transceiver-destinasjon for flyttilstand og observabilitet.
  • Horisontal skalerbarhet: Flere reléinstanser kjører parallelt bak en lastbalanserer. Tilstanden er ikke en fast WebRTC-tilstand, så omstarter gir minimale trafikktap og rask gjenoppretting av flyt.

Tiltak for effektivitet:

  • SO_REUSEPORT er et socket-alternativ i Linux som lar flere relay-arbeidere på samme maskin binde til samme UDP-port. Kjernen fordeler deretter innkommende pakker mellom disse arbeiderne, noe som unngår en flaskehals i én enkelt leseløkke.
  • runtime.LockOSThread fester hver goroutine som leser UDP til en bestemt OS-tråd. Kombinert med SO_REUSEPORT gjør det at pakker fra samme flyt (kilde- og destinasjons-IP:Port pluss protokoll) ofte holdes på samme CPU-kjerne, noe som forbedrer cache-lokalitet og reduserer kontekstbytter.
  • Forhåndsallokerte buffere og minimal kopiering holder parserings- og allokeringsoverhead lav for å unngå søppelinnsamling i Go.

Denne implementeringen håndterte vår globale sanntidstrafikk for medier med et relativt lite relay-avtrykk, så vi beholdt det enklere designet i stedet for å ta på oss en vei med kernel bypass.

Resultater og læring

Denne arkitekturen lar oss kjøre WebRTC-medier i Kubernetes uten å eksponere tusenvis av UDP-porter. Det er viktig fordi en mindre og fast UDP-flate er enklere å sikre og lastbalansere, og lar infrastrukturen skalere uten å reservere store offentlige portområder. Med bedre infrastrukturstøtte fra Kubernetes og mer sikkerhet på grunn av mindre angrepsflate bevarer dette designet også standard WebRTC-atferd for klienter og bekrefter at et design uten SFU var riktig standardvalg for vår arbeidslast. De fleste av øktene våre er punkt-til-punkt, følsomme for latenstid og enklere å skalere når inferenstjenester ikke trenger å oppføre seg som WebRTC-peere.

Den bredere lærdommen er at det beste stedet å legge til kompleksitet er i et tynt rutingslag, ikke i hver backend-tjeneste, og ikke i tilpasset klientatferd. Å kode rutingsmetadata inn i et felt som allerede finnes i protokollen ga oss deterministisk ruting av første pakke, en liten offentlig UDP-flate og nok fleksibilitet til å plassere inngangspunkter nær brukere over hele verden.

Noen valg var spesielt viktige:

  • Bevar protokollsemantikken ved kanten. Klienter bruker fortsatt standard WebRTC, noe som bevarer interoperabilitet på tvers av nettlesere og mobil.
  • Hold vanskelig økttilstand på ett sted. Transceiveren eier ICE, DTLS, SRTP og øktens livssyklus; relayen videresender bare pakker.
  • Rute basert på informasjon som allerede finnes i oppsettet. ICE-ufrag ga oss en rutingsmekanisme for første pakke uten å legge til en oppslagsavhengighet i hot path.
  • Optimaliser for det vanlige tilfellet før du tyr til kernel bypass. En smal Go-implementering med nøye bruk av SO_REUSEPORT, trådfesting og parsering med lav allokering var nok for vår arbeidslast.

Sanntids tale-AI fungerer bare når infrastrukturen gjør at latenstid føles usynlig. For oss betydde det å endre formen på WebRTC-distribusjonen vår uten å endre det klienter forventer av selve WebRTC.