Bagaimana OpenAI menyampaikan AI suara kependaman rendah pada skala
Oleh Yi Zhang dan William McDonald, Ahli Kakitangan Teknikal
AI Voice hanya terasa semula jadi jika perbualan bergerak sepantas pertuturan. Apabila rangkaian mengganggu, orang akan segera menyedarinya sebagai jeda janggal, gangguan terpotong, atau kelewatan apabila mencelah dalam perbualan. Ini penting untuk ChatGPT Voice, untuk pembangun yang membina dengan Realtime API, untuk ejen yang bekerja dalam aliran kerja interaktif, dan untuk model yang perlu memproses audio ketika pengguna masih bercakap.
Pada skala OpenAI, ini diterjemahkan kepada tiga keperluan kukuh:
- Capaian global untuk lebih daripada 900 juta pengguna aktif mingguan
- Penyediaan sambungan pantas supaya pengguna boleh mula bercakap sebaik sesi bermula
- Masa pergi balik media yang rendah dan stabil, dengan ketar dan kehilangan paket yang rendah, supaya pertukaran giliran terasa lancar
Pasukan di OpenAI yang bertanggungjawab terhadap interaksi AI masa nyata baru-baru ini mereka bentuk semula tindanan WebRTC kami untuk menangani tiga kekangan yang mula bertembung pada skala: penamatan media satu-port-setiap-sesi tidak sesuai dengan infrastruktur OpenAI, sesi ICE (Penubuhan Ketersambungan Interaktif) dan DTLS (Keselamatan Lapisan Pengangkutan Datagram) yang berkeadaan memerlukan pemilikan yang stabil, dan penghalaan global mesti mengekalkan kependaman lompatan pertama yang rendah. Dalam siaran ini, kami membincangkan seni bina geganti serta penghantar-terima terpisah yang kami bina untuk mengekalkan tingkah laku WebRTC standard bagi klien, sambil mengubah cara pakej dihalakan dalam infrastruktur OpenAI.
WebRTC ialah standard terbuka untuk menghantar audio, video dan data kependaman rendah antara pelayar, aplikasi mudah alih dan pelayan. Ia sering dikaitkan dengan panggilan rakan sebaya, tetapi ia juga asas praktikal untuk sistem masa nyata klien-ke-pelayan kerana ia menyeragamkan bahagian sukar media interaktif: ICE untuk pembentukan sambungan dan rentasan lintang NAT (Terjemahan Alamat Rangkaian), DTLS dan SRTP (Protokol Pengangkutan Masa Nyata Selamat) untuk pengangkutan yang disulitkan, rundingan codec untuk memampat dan menyahkod audio, RTCP (Protokol Kawalan Pengangkutan Masa Nyata) untuk kawalan kualiti, dan ciri pihak klien seperti pembatalan gema dan penimbalan ketar.
Penyeragaman ini penting untuk produk AI. Tanpa WebRTC, setiap klien memerlukan jawapan berbeza tentang cara mewujudkan sambungan merentas NAT, menyulitkan media, merundingkan codec (pengekod-penyahkod yang dipilih untuk penghantaran dan penyahmampatan) dan menyesuaikan diri dengan keadaan rangkaian yang berubah. Dengan WebRTC, kami boleh membina di atas tindanan protokol yang sudah dilaksanakan merentas pelayar dan platform mudah alih, sambil menumpukan kerja kami sendiri pada infrastruktur yang menghubungkan media masa nyata kepada model.
Kami juga membina di atas ekosistem WebRTC itu sendiri, termasuk pelaksanaan sumber terbuka yang matang dan kerja piawaian yang memastikan pelayar, aplikasi mudah alih dan pelayan saling boleh kendali. Usaha asas oleh Justin Uberti (salah seorang arkitek asal WebRTC) dan Sean DuBois (pencipta dan penyelenggara Pion) memungkinkan pasukan seperti kami membina di atas infrastruktur media yang telah teruji, bukannya mencipta semula tingkah laku pengangkutan aras rendah, penyulitan dan kawalan kesesakan. Kami bertuah kerana Justin dan Sean kini ialah rakan sekerja di OpenAI, membantu membimbing cara kami merapatkan WebRTC dan AI masa nyata.
Bagi AI, sifat yang paling penting ialah audio tiba sebagai satu aliran berterusan. Ejen pertuturan boleh mula membuat transkripsi, membuat penaakulan, memanggil alat, atau menjana pertuturan ketika pengguna masih bercakap, bukannya menunggu muat naik penuh. Itulah perbezaan antara sistem yang terasa seperti perbualan semula jadi dan sistem yang terasa seperti tekan-untuk-bercakap.
Setelah kami memilih WebRTC, soalan seterusnya ialah di mana untuk menamatkannya (tempat kami menerima dan memiliki sambungan WebRTC—contohnya, di pinggir) dan bagaimana menghubungkan sesi tersebut kepada bahagian belakang inferens. Penamatan penting kerana ia menentukan cara kami mengendalikan keadaan sesi masa nyata, pengangkutan media, penghalaan, kependaman, dan pengasingan kegagalan.
SFU, atau unit pemajuan selektif, ialah pelayan media yang menerima satu strim WebRTC daripada setiap peserta dan memajukan strim secara selektif kepada peserta lain. Dalam model ini, SFU menamatkan sambungan WebRTC yang berasingan untuk setiap peserta, dan AI menyertai sesi sebagai seorang lagi peserta. Ini boleh menjadi pendekatan yang sesuai untuk produk yang sememangnya berbilang pihak, seperti panggilan kumpulan, bilik darjah, atau mesyuarat kolaboratif. Ia menghimpunkan codec audio, mesej RTCP, saluran data, rakaman dan dasar per strim di satu tempat.1
Walaupun dalam produk klien-ke-AI, SFU sering menjadi titik permulaan lalai kerana ia membolehkan pasukan menggunakan semula satu sistem terbukti untuk isyarat, penghalaan media, rakaman, kebolehcerapan, dan peluasan masa depan seperti penyerahan kepada manusia atau menambah lebih ramai peserta.
Beban kerja kita berbeza. Kebanyakan sesi bersifat 1:1—seorang pengguna bercakap dengan satu model, atau satu aplikasi bercakap dengan satu agen masa nyata—dengan kepekaan terhadap kependaman pada setiap giliran. Untuk bentuk trafik ini, kami memilih model penghantar-terima: perkhidmatan sisi WebRTC menamatkan sambungan klien dan kemudian menukar media dan peristiwa kepada protokol dalaman yang lebih ringkas untuk inferens model, transkripsi, penjanaan pertuturan, penggunaan alat dan orkestrasi.
Dalam reka bentuk ini, penghantar-terima ialah satu-satunya perkhidmatan yang memiliki keadaan sesi WebRTC, termasuk pemeriksaan sambungan ICE, jabat tangan DTLS, kunci penyulitan SRTP, dan kitar hayat sesi. “Penamatan” di sini bermaksud penghantar-terima ialah titik akhir yang melengkapkan jabat tangan tersebut dan menyulitkan atau menyahsulit media. Mengekalkan keadaan itu di satu tempat menjadikan pemilikan sesi lebih mudah untuk difahami, dan membolehkan perkhidmatan bahagian belakang diskalakan seperti perkhidmatan biasa, bukannya bertindak sebagai rakan WebRTC sendiri.
Selepas memilih model penghantar-terima, pelaksanaan pertama kami ialah satu perkhidmatan Go yang dibina di atas Pion yang mengendalikan kedua-dua syarat dan penamatan media. Ia menggerakkan ChatGPT Voice, titik akhir WebRTC Realtime API, dan beberapa projek penyelidikan.
Dari segi operasi, perkhidmatan penghantar-terima menjalankan dua tugas:
- Isyarat: rundingan SDP, pemilihan codec, bukti kelayakan ICE, dan penetapan sesi
- Media: Menamatkan sambungan WebRTC hiliran dan mengekalkan sambungan huluan kepada perkhidmatan bahagian belakang untuk inferens dan orkestrasi
Kami mahu perkhidmatan ini berjalan seperti infrastruktur kami yang lain: pada Kubernetes, tempat beban kerja boleh meningkatkan atau mengurangkan skala, serta berpindah merentas hos apabila permintaan berubah. Tetapi model WebRTC konvensional satu-port-setiap-sesi tidak sesuai dengan persekitaran itu, kerana ia bergantung pada julat port UDP awam yang besar yang sukar didedahkan, dilindungi dan dikekalkan apabila pod ditambah, dibuang, atau dijadualkan semula.2
Masalah pertama ialah model satu-port-setiap-sesi itu sendiri. Pada keserentakan tinggi, ini bermakna mendedahkan dan mengurus julat port UDP yang sangat besar.
- Pengimbang beban awan dan perkhidmatan Kubernetes tidak direka bentuk untuk puluhan ribu port UDP awam bagi setiap perkhidmatan. Setiap julat tambahan menambah kerumitan operasi dalam konfigurasi pengimbang beban, pemeriksaan kesihatan, dasar tembok api, dan keselamatan pelancaran.3
- Julat port UDP yang besar sukar dilindungi kerana ia meluaskan permukaan yang boleh dicapai dari luar dan menjadikan dasar rangkaian lebih sukar diaudit.
- Ia juga kurang sesuai untuk penskalaan automatik. Pod sentiasa ditambah, dialih keluar dan dijadualkan semula dalam Kubernetes. Keperluan untuk setiap pod memperuntukkan dan mengiklankan julat port yang stabil dan besar, menjadikan keanjalan itu rapuh.4
Inilah sebabnya banyak sistem WebRTC beralih ke satu port UDP bagi setiap pelayan, dengan penyahmultipleksan aras aplikasi di belakang port itu.5
Reka bentuk satu-port-setiap-pelayan menyelesaikan bilangan port, tetapi ia memperkenalkan masalah kedua: mengekalkan pemilikan setiap sesi merentas satu armada.
ICE dan DTLS ialah protokol berkeadaan. Proses yang mencipta sesi perlu terus menerima paket sesi itu supaya ia boleh mengesahkan pemeriksaan sambungan, melengkapkan jabat tangan DTLS, menyahsulit SRTP, dan memproses perubahan sesi kemudian seperti mula semula ICE. Jika paket bagi sesi yang sama mendarat pada proses berbeza, penyediaan boleh gagal atau media boleh rosak.
Ini memberi kami sasaran khusus: dedahkan permukaan UDP kecil dan tetap kepada Internet awam, sambil tetap menghalakan setiap paket ke penghantar-terima yang memiliki sesi WebRTC yang sepadan.
Kami menilai beberapa cara untuk mencapainya, termasuk TURN (Traversal Using Relays around NAT), di mana geganti hujung menamatkan peruntukan klien dan memajukan trafik bagi pihak mereka.2
Pendekatan | Kelebihan | Kekurangan |
IP Unik:port bagi setiap sesi (juga dikenali sebagai UDP langsung natif) | Laluan media langsung klien-ke-pelayan Tiada lapisan pemajuan dalam laluan data | Memerlukan satu port UDP awam bagi setiap sesi Julat port yang besar sukar didedahkan dan dilindungi Kurang sesuai untuk Kubernetes dan pengimbang beban awan |
IP Unik:port bagi setiap pelayan | Jejak UDP awam jauh lebih kecil berbanding pendedahan setiap sesi Satu soket yang dikongsi bagi setiap pelayan boleh menyahmultipleks banyak sesi | Berfungsi dengan baik pada hos tunggal, tetapi tidak merentasi sekumpulan hos bersama yang diimbang beban dengan sendirinya Penyahmultipleksan sesi pada satu hos hanya membantu selepas paket sampai ke hos tersebut; merentas armada yang seimbang beban, paket pertama masih boleh mendarat pada instans yang salah, jadi anda masih memerlukan cara berketentuan untuk mengarahkan setiap sesi ke proses yang memilikinya |
Relay TURN (menamatkan protokol) | Pelanggan hanya perlu menyambung ke alamat dan port relay TURN Dapat memusatkan dasar di pinggir rangkaian | Peruntukan TURN menambah perjalanan pergi-balik untuk penyediaan Memindahkan atau memulihkan peruntukan merentas pelayan TURN masih sukar |
Pemaju tanpa keadaan + penamat berkeadaan (relay + penghantar-terima OpenAI) | Jejak UDP awam yang kecil Penghantar-terima masih memiliki sesi WebRTC penuh | Menambah satu lompatan pemajuan sebelum media sampai ke penghantar-terima pemilik Memerlukan penyelarasan tersuai antara relay dan penghantar-terima |
Seni bina yang kami lancarkan memisahkan penghalaan paket daripada penamatan protokol. Isyarat masih sampai ke penghantar-terima untuk penyediaan sesi, manakala media masuk melalui geganti terlebih dahulu. Geganti ialah lapisan pemajuan UDP ringan dengan jejak awam yang kecil, dan penghantar-terima ialah titik akhir WebRTC berkeadaan di belakangnya.
Geganti tidak menyahsulit media, tidak menjalankan mesin keadaan ICE, dan tidak menyertai rundingan codec. Ia membaca metadata paket secukupnya untuk memilih destinasi, kemudian memajukan paket ke penghantar-terima yang memiliki sesi itu. Penghantar-terima masih melihat aliran WebRTC biasa dan masih memiliki semua keadaan protokol. Dari perspektif klien, tiada apa yang berubah tentang sesi WebRTC.
Penghalaan paket pertama ialah langkah utama dalam persediaan ini. Geganti perlu menghalakan paket pertama daripada klien sebelum sebarang sesi wujud pada laluan paket itu sendiri, bukannya berhenti pada perkhidmatan carian luaran.
Setiap sesi WebRTC sudah membawa cangkuk penghalaan natif protokol: fragmen nama pengguna ICE, atau ufrag, pengecam pendek yang ditukar semasa penyediaan sesi dan diulang semula dalam pemeriksaan sambungan STUN. Kami menjana ufrag pada bahagian pelayan supaya ia mengandungi metadata penghalaan yang secukupnya untuk relay menyimpulkan kluster destinasi dan penghantar-terima pemilik.
Semasa proses isyarat, penghantar-terima memperuntukkan keadaan sesi dan memulangkan satu VIP geganti bersama serta port UDP dalam jawapan SDP. VIP ialah alamat IP maya yang menjadi hadapan kepada armada geganti; digabungkan dengan port, ia memberi klien satu destinasi stabil tunggal, seperti `203.0.113.10:3478`, walaupun banyak keadaan geganti berada di belakangnya. Paket pertama laluan media klien biasanya ialah permintaan pengikatan STUN (Utiliti Penyusuran Sesi untuk NAT), yang digunakan ICE untuk mengesahkan bahawa paket boleh mencapai alamat yang diiklankan.
Geganti menghuraikan secukupnya paket STUN pertama itu untuk membaca ufrag pelayan, menyahkod petunjuk penghalaan, dan memajukan paket ke penghantar-terima pemilik. Setiap penghantar-terima mendengar pada soket UDP bersama, iaitu satu titik akhir sistem operasi yang terikat kepada IP:port dalaman, bukannya satu soket bagi setiap sesi. Selepas relay mencipta sesi daripada IP:port sumber klien ke destinasi penghantar-terima itu, paket DTLS, RTP dan RTCP seterusnya mengalir dalam sesi tanpa perlu menyahkod semula ufrag.
Sesi geganti ini sengaja diminimumkan, hanya terdiri daripada sesi dalam memori untuk memaklumkan pemajuan paket, bersama kaunter yang diperlukan untuk pemantauan serta pemasa untuk tamat tempoh sesi dan pembersihan. Pilihan reka bentuk ini mengekalkan penghalaan paket secara langsung pada laluan paket. Jika geganti dimulakan semula dan kehilangan sesi, paket STUN seterusnya membina semula sesi daripada petunjuk penghalaan ufrag. Untuk menjadikannya lebih boleh dipercayai, cache Redis digunakan untuk menyimpan pemetaan <IP klien + Port, penghantar-terima IP + Port> sebaik sahaja laluan diwujudkan supaya ia boleh dipulihkan lebih awal, sebelum paket STUN seterusnya tiba.
Setelah kami mengurangkan permukaan UDP awam kepada sejumlah kecil alamat dan port yang stabil, kami boleh menerapkan corak geganti yang sama secara global. Global Relay ialah armada titik masuk ingress relay teragih secara geografi kami yang semuanya melaksanakan tingkah laku pemajuan paket yang sama.
Liputan titik kemasukan geografi yang luas memendekkan lompatan pertama klien-ke-OpenAI kerana paket boleh memasuki rangkaian kami pada geganti yang dekat dengan pengguna, dari segi geografi dan topologi rangkaian, bukannya melintasi Internet awam ke rantau yang jauh terlebih dahulu. Dari segi praktikal, ini bermakna kependaman yang lebih rendah, kurang jitter, dan lebih sedikit ledakan kehilangan yang boleh dielakkan sebelum trafik sampai ke rangkaian tulang belakang kami.6
Kami menggunakan geo dan steering kedekatan Cloudflare untuk isyarat supaya permintaan HTTP atau WebSocket awal sampai ke kluster penghantar-terima berdekatan. Konteks permintaan menentukan lokasi sesi dan titik kemasukan Global Relay mana yang diiklankan kepada klien. Jawapan SDP memberikan alamat Global Relay, manakala ufrag mengandungi maklumat yang mencukupi untuk Global Relay menghalakan media ke kluster yang ditetapkan dan untuk geganti menghalakannya ke penghantar-terima destinasi.
Bersama-sama, isyarat dipacu geo dan Global Relay meletakkan kedua-dua penetapan dan media pada laluan masuk berdekatan sambil mengekalkan sesi terikat pada satu penghantar-terima. Ini mengurangkan masa perjalanan pergi balik untuk isyarat dan untuk pemeriksaan sambungan ICE pertama, yang secara langsung memendekkan tempoh pengguna menunggu sebelum pertuturan boleh bermula.
Kami menulis perkhidmatan geganti dalam Go dan sengaja mengekalkan pelaksanaannya sempit. Pada Linux, tindanan rangkaian kernel menerima paket UDP daripada antara muka rangkaian mesin dan menghantarnya ke soket, titik akhir sistem operasi yang dibaca proses selepas mengikat IP:Port. Geganti berjalan dalam ruang pengguna, jadi proses Go biasa membaca pengepala paket daripada soket itu, mengemas kini sedikit keadaan aliran, dan memajukan paket tanpa menamatkan WebRTC. Kami tidak memerlukan sebarang rangka kerja pintasan kernel, yang membolehkan proses ruang pengguna meninjau terus baris gilir rangkaian untuk kadar paket lebih tinggi tetapi juga menambah kerumitan operasi.
Pilihan reka bentuk utama:
- Tiada penamatan protokol: Geganti menghuraikan pengepala/ufrag STUN sahaja; ia menggunakan keadaan cache untuk DTLS, RTP dan RTCP seterusnya, memastikan kelegapan paket.
- Keadaan sementara: Ia mengekalkan peta kecil dalam memori dengan tamat masa singkat bagi alamat klien ke destinasi penghantar-terima untuk keadaan aliran dan kebolehcerapan.
- Kebolehskalaan mendatar: Beberapa instans relay dijalankan secara selari di belakang pengimbang beban. Keadaan itu bukan keadaan WebRTC keras, jadi mula semula menyebabkan gangguan trafik minimum dan pemulihan aliran yang cepat.
Langkah kecekapan:
SO_REUSEPORTialah pilihan soket Linux yang membolehkan berbilang pekerja geganti pada mesin yang sama mengikat port UDP yang sama. Kernel kemudian mengagihkan paket masuk merentas pekerja tersebut, yang mengelakkan kesesakan pada satu gelung baca.runtime.LockOSThreadmengikat setiap goroutine pembaca UDP kepada utas OS tertentu. Digabungkan denganSO_REUSEPORT, yang cenderung mengekalkan paket daripada aliran yang sama (IP:Port sumber dan destinasi serta protokol) pada teras CPU yang sama, meningkatkan lokaliti cache dan mengurangkan pertukaran konteks.- Penimbal yang telah diperuntukkan dan penyalinan minimum memastikan beban tambahan penghuraian dan peruntukan yang rendah untuk mengelakkan pengumpulan sampah dalam Go.
Pelaksanaan ini mengendalikan trafik media masa nyata global kami dengan jejak geganti yang agak kecil, jadi kami mengekalkan reka bentuk yang lebih ringkas ini berbanding mengambil laluan pintasan kernel.
Seni bina ini membolehkan kami menjalankan media WebRTC dalam Kubernetes tanpa mendedahkan ribuan port UDP. Ini penting kerana permukaan UDP yang lebih kecil dan tetap lebih mudah dilindungi serta diseimbangkan bebannya, dan ia membolehkan infrastruktur diskala tanpa memperuntukkan julat port awam yang besar. Dengan sokongan infra yang lebih baik daripada Kubernetes dan keselamatan yang lebih tinggi hasil permukaan yang lebih kecil, reka bentuk ini juga mengekalkan tingkah laku WebRTC standard untuk pelanggan dan mengesahkan bahawa reka bentuk tanpa SFU adalah tetapan lalai yang tepat untuk beban kerja kami. Kebanyakan sesi kami ialah titik-ke-titik, sensitif terhadap kependaman, dan lebih mudah diskala apabila perkhidmatan inferens tidak perlu berkelakuan seperti rakan WebRTC.
Pengajaran yang lebih luas ialah tempat terbaik untuk menambah kerumitan adalah pada lapisan penghalaan nipis, bukan pada setiap perkhidmatan bahagian belakang dan bukan pada tingkah laku klien tersuai. Mengekod metadata penghalaan ke dalam medan natif protokol memberi kami penghalaan paket pertama yang berketentuan, jejak UDP awam yang kecil, dan fleksibiliti yang mencukupi untuk meletakkan kemasukan dekat dengan pengguna di seluruh dunia.
Beberapa pilihan sangat penting:
- Kekalkan semantik protokol di pinggir. Pelanggan masih menggunakan WebRTC standard, yang mengekalkan kesalingoperasian pelayar dan mudah alih.
- Simpan keadaan sesi yang sukar di satu tempat. Penghantar-terima memiliki ICE, DTLS, SRTP, dan kitar hayat sesi; geganti hanya memajukan paket.
- Halakan berdasarkan maklumat yang sedia ada dalam penetapan. ICE ufrag memberikan cangkuk penghalaan paket pertama tanpa menambah kebergantungan carian laluan utama.
- Optimumkan untuk kes lazim sebelum menggunakan pintasan kernel. Pelaksanaan Go yang terhad dengan penggunaan teliti
SO_REUSEPORT, penetapan benang, dan penguraian peruntukan rendah sudah memadai untuk beban kerja kami.
AI suara masa nyata hanya berfungsi apabila infrastruktur menjadikan kependaman terasa tidak kelihatan. Bagi kami, itu bermaksud mengubah bentuk penerapan WebRTC kami tanpa mengubah apa yang pelanggan jangkakan daripada WebRTC itu sendiri.
Penulis
Rujukan
2. GitHub - l7mp/stunner: Gerbang media Kubernetes untuk WebRTC(dibuka dalam tetingkap baru)
3. Ringkasan Port WebRTC [Contoh] - BlogGeek.me(dibuka dalam tetingkap baru)
4. Laksanakan ke Kubernetes - dokumen LiveKit(dibuka dalam tetingkap baru)
6. Cloudflare Calls: berjuta-juta pokok bertingkat hingga ke bawah(dibuka dalam tetingkap baru)


