OpenAIが大規模・低遅延の音声AIを提供する方法
(openai.com)- OpenAIは、週次アクティブユーザー9億人超に自然な音声対話を提供するため、標準的なWebRTCの挙動を維持しつつ、内部のパケットルーティングをrelay + transceiver構成へと再設計した
- WebRTCは、ICE、DTLS/SRTP、コーデックネゴシエーション、RTCPによる品質制御、エコー除去、ジッターバッファリングを標準化しており、ブラウザ・モバイルアプリ・サーバー間で低遅延の連続オーディオストリームを扱うのに適している
- OpenAIの大半のセッションは、ユーザー1人とモデル1つ、あるいはアプリケーション1つとリアルタイムエージェント1つが対話する1:1セッションであるため、多者通話向けのSFUよりもtransceiverモデルのほうが遅延とスケーラビリティの面で適していた
- Kubernetes上でセッションごとにUDPポートを1つ公開するWebRTCモデルは、大きな公開ポート範囲、ロードバランサー設定、ヘルスチェック、ファイアウォールポリシー、ロールアウト時の安全性を複雑にするため、小さく固定されたUDPの公開面が必要だった
- relayは、最初のSTUNパケットのICE ufragに含まれたルーティングヒントから所有するtransceiverを見つけて転送し、transceiverはICE・DTLS・SRTP・セッションライフサイクルを管理することで、標準WebRTC互換性と世界中で近いingress経路を両立している
低遅延音声AIの要件
- 音声AIは、会話が話す速度で進むときに自然に感じられ、ネットワーク遅延は不自然な沈黙、途切れた割り込み、遅れた割り込み応答としてすぐに表れる
- OpenAI規模では、週次アクティブユーザー9億人超に向けたグローバルな到達性、セッション開始直後から話し始められる高速な接続確立、低く安定したメディア往復時間、低ジッターと低パケット損失が必要になる
- ChatGPT voice、Realtime API、対話型ワークフローのエージェント、ユーザーが話している最中に音声を処理する必要があるモデルは、いずれもこの遅延特性の影響を受ける
- OpenAIは、クライアント側の標準WebRTC動作を維持しながら内部のパケットルーティングを変更するため、relay + transceiverの分離構造へWebRTCスタックを再設計した
WebRTCを選んだ理由
- WebRTCは、ブラウザ、モバイルアプリ、サーバー間で低遅延の音声・映像・データを送るための公開標準であり、クライアント-サーバー型のリアルタイムシステムの基盤としても適している
- WebRTCは、ICEによる接続確立とNAT越え、DTLS/SRTPによる暗号化転送、コーデックネゴシエーション、RTCPによる品質制御、エコー除去やジッターバッファリングといったクライアント機能を標準化している
- WebRTCがなければ、各クライアントがNAT環境での接続確立、メディア暗号化、コーデックネゴシエーション、ネットワーク変化への対応を個別に解決しなければならない
- AI製品で重要なのは、音声が連続ストリームとして到着する点であり、ユーザーのアップロード全体が終わるのを待たずに、文字起こし、推論、ツール呼び出し、音声生成を開始できる
- この違いが、対話的に感じられるシステムと、push-to-talkのように感じられるシステムを分ける
- OpenAIは、成熟したオープンソース実装と標準化作業を含むWebRTCエコシステムを基盤にしており、Justin UbertiとSean DuBoisによる土台となる取り組みのおかげで、低レベルの転送、暗号化、輻輳制御を新たに作ることなく、実証済みのメディア基盤の上に構築できた
SFUではなくtransceiverを選んだ構成
-
SFUが適している場合
- SFUは、各参加者のWebRTCストリームを受け取り、他の参加者へ選択的に転送するメディアサーバーである
- SFUモデルでは、各参加者ごとに個別のWebRTC接続を終端し、AIもセッションのもう1人の参加者として参加する
- グループ通話、教室、共同会議のように本質的に多者間である製品には、SFUが適している場合がある
- 音声コーデック、RTCPメッセージ、データチャネル、録画、ストリーム単位のポリシーを1か所に集約できる
- クライアント対AI製品でも、信号処理、メディアルーティング、録画、可観測性、人への引き継ぎや参加者追加といった将来の拡張を1つのシステムで再利用できるため、出発点として選ばれやすい
-
OpenAIのワークロード
- OpenAIの大半のセッションは、ユーザー1人とモデル1つ、またはアプリケーション1つとリアルタイムエージェント1つが対話する1:1セッションである
- このトラフィック形態は各ターンの遅延に敏感なため、transceiverモデルが選ばれた
- transceiverモデルでは、WebRTC edgeサービスがクライアント接続を終端した後、メディアとイベントを、モデル推論、文字起こし、音声生成、ツール利用、オーケストレーションのための単純な内部プロトコルへ変換する
- transceiverは、ICE接続チェック、DTLSハンドシェイク、SRTP暗号鍵、セッションライフサイクルを含むWebRTCセッション状態を管理する唯一のサービスである
- セッション状態を1か所に集約すると、セッションの所有権を理解しやすくなり、バックエンドサービスはWebRTCピアとしてではなく通常のサービスとしてスケールできる
初期実装とKubernetesで直面した制約
- OpenAIの最初の実装はPionベースの単一Goサービスで、シグナリング処理とメディア終端の両方を担っていた
- このtransceiverサービスは、ChatGPT voice、Realtime APIのWebRTC endpoint、複数の研究プロジェクトを支えていた
- 運用上、transceiverはsignalingにおいてSDPネゴシエーション、コーデック選択、ICE資格情報、セッション設定を処理していた
- mediaでは、downstreamのWebRTC接続を終端し、推論とオーケストレーションのためのbackendサービスとのupstream接続を維持していた
- OpenAIはこのサービスを、需要変動に応じてスケールし、ホスト間を移動できるKubernetes上で運用しようとしていた
- 従来のセッションごとに1ポートのWebRTCモデルはKubernetes環境に適合せず、大きな公開UDPポート範囲を公開・保護・維持する必要があった
- 高い同時接続数では、セッションごとに1ポートという方式は非常に大きなUDPポート範囲を公開し、管理しなければならない
- クラウドロードバランサーやKubernetesサービスは、サービスごとに数万の公開UDPポートを前提に設計されておらず、ポート範囲が広がるほどロードバランサー設定、ヘルスチェック、ファイアウォールポリシー、ロールアウト時の安全性は複雑になる
- 大きなUDPポート範囲は、外部から到達可能な公開面を広げ、ネットワークポリシー監査も難しくするため、セキュリティ上も不利になる
- Kubernetesではpodが継続的に追加・削除・再スケジュールされるため、各podが大きく安定したポート範囲を予約・告知しなければならない構成では弾力性が損なわれる
単一ポートとセッション所有権の問題
- 多くのWebRTCシステムは、ポート数の問題を減らすため、サーバーごとに単一のUDPポートとアプリケーション層の多重化を用いている
- サーバーごとの単一ポート設計はポート数を減らせる一方で、fleet全体で各セッションの所有権を維持しなければならないという第2の問題を生む
- ICEとDTLSは状態を持つプロトコルであるため、セッションを作成したプロセスがそのセッションのパケットを継続的に受信しなければ、接続チェックの検証、DTLSハンドシェイクの完了、SRTPの復号、ICE restartのような後続のセッション変更に対応できない
- 同じセッションのパケットが別のプロセスに届くと、セットアップが失敗したり、メディアが壊れたりする可能性がある
- OpenAIの目標は、公開インターネットには小さく固定されたUDPの公開面だけを見せつつ、すべてのパケットをそのWebRTCセッションを所有するtransceiverへルーティングすることだった
-
検討したアプローチ
- セッションごとの一意なIP:portは、クライアント-サーバー間の直接的なメディア経路を提供し、データ経路に転送レイヤーを持たないが、セッションごとに1つの公開UDPポートが必要であり、大きなポート範囲はKubernetes・クラウドロードバランサー・セキュリティと相性が悪い
- サーバーごとの一意なIP:portは、セッション単位の公開よりも公開UDPフットプリントを大幅に小さくでき、共有ソケットで多数のセッションを多重化できるが、共有ロードバランシングされたfleet全体では最初のパケットが誤ったインスタンスに届く可能性があり、セッションを所有するプロセスへ決定的に送る方法が必要になる
- TURN relayは、クライアントがTURN relayのアドレスとポートにのみ到達できればよく、edgeでポリシーを集中管理できるが、TURN allocationがセットアップ往復を追加し、TURNサーバー間でのallocation移動や復旧も依然として難しい
- OpenAIのrelay + transceiver構造は、stateless forwarder + stateful terminatorとして、小さな公開UDPフットプリントを維持しつつtransceiverがWebRTCセッション全体を管理するが、メディアが所有するtransceiverに到達する前に転送ホップが1つ増え、relayとtransceiverの間にカスタムな調整が必要になる
relay + transceiverアーキテクチャ
- OpenAIが展開した構造は、パケットルーティングとプロトコル終端を分離している
- シグナリングはセッション設定のためにtransceiverへ到達し、メディアはまずrelayに入る
- relayは小さな公開フットプリントを持つ軽量なUDP転送レイヤーであり、transceiverはその背後にある状態を持つWebRTC endpointである
- relayはメディアを復号せず、ICEステートマシンも実行せず、コーデックネゴシエーションにも関与しない
- relayは、宛先を選ぶのに十分なパケットmetadataだけを読み、そのセッションを所有するtransceiverへパケットを転送する
- transceiverは引き続き通常のWebRTCフローを見て、すべてのプロトコル状態を管理する
- クライアント視点ではWebRTCセッションは変わらない
最初のパケットルーティングとICE ufragの活用
- この構造の中核は、relayがパケット経路そのものの中で最初のクライアントパケットをルーティングすることにある
- WebRTCセッションには、すでにプロトコルネイティブなルーティングフックがあり、それがICE username fragment、すなわちufragである
- ufragは、セッション設定時に交換され、STUN接続チェックにも再び載せられる短い識別子である
- OpenAIは、relayが宛先クラスタと所有するtransceiverを推定できるだけのルーティングmetadataをserver-side ufragに含める形で生成している
- signaling中にtransceiverはセッション状態を割り当て、SDP answerに共有relay VIPとUDP portを返す
- VIPはrelay fleetの前段にある仮想IPアドレスで、portと組み合わせることで、複数のrelayインスタンスの背後にあってもクライアントに
203.0.113.10:3478のような単一で安定した宛先を提供する - クライアントの最初のmedia-pathパケットは通常STUN binding requestであり、ICEはこれを使って、告知されたアドレスにパケットが到達できるかを検証する
- relayは最初のSTUNパケットからserver ufragを読み、ルーティングヒントを解釈し、セッションを所有するtransceiverへパケットを転送するのに必要な範囲だけを解析する
- 各transceiverは、セッションごとのソケットではなく、内部IP:portにバインドされたOS endpointである共有UDPソケットで受信する
- relayがクライアントのsource IP:portからtransceiver宛先までのセッションを作成すると、その後のDTLS、RTP、RTCPパケットはufragを再解釈することなく、そのセッション内を流れる
- relayのセッションは、パケット転送のためのin-memory session、monitoring counter、セッションの期限切れとクリーンアップのためのtimerだけを持つ最小状態に保たれる
- relayが再起動してセッションを失っても、次のSTUNパケットがufragルーティングヒントによってセッションを再作成する
- 経路が設定されると、Redis cacheが
<client IP + Port, transceiver IP + Port>のmappingを保持し、次のSTUNパケットが来る前でもより早い復旧を可能にする
Global Relayと近接した流入経路
- 公開UDPの表面を小さく安定したアドレス・ポート数に縮小した後、OpenAIは同じrelayパターンを世界中に展開できた
- Global Relayは、同じpacket-forwarding動作を実装する地理的に分散したrelay ingress point fleetである
- 地理的に広いingressを持つことで、ユーザーのパケットは遠いregionまで先に公衆インターネットを横断するのではなく、地理的・ネットワークトポロジ的に近いrelayからOpenAIネットワークへ入るため、最初のclient-to-OpenAIホップを短縮できる
- この方式は、トラフィックがbackboneに到達する前の遅延、ジッター、回避可能なloss burstを低減する
- OpenAIはsignalingにCloudflare geoとproximity steeringを用い、初期のHTTPまたはWebSocketリクエストが近いtransceiver clusterへ到達するようにしている
- リクエストcontextが、セッション配置とクライアントに告知するGlobal Relay ingress pointを決定する
- SDP answerはGlobal Relayのアドレスを提供し、ufragには、Global Relayが指定clusterへmediaをルーティングし、relayが目的のtransceiverへルーティングするために十分な情報が含まれる
- geo-steered signalingとGlobal Relayを組み合わせることで、setupとmediaの両方を近いentry pathに置きつつ、セッションは1つのtransceiverに固定できる
- この構造は、signalingと最初のICE接続チェックの往復時間を短縮し、ユーザーが話し始めるまでの待ち時間を直接減らす
relayの実装方式
- relayサービスはGoで書かれており、意図的に実装範囲を狭く保っている
- Linuxでは、kernel networking stackがネットワークインターフェースからUDP packetを受け取ってsocketへ渡し、relayはuserspaceの通常のGo processとしてsocketからpacket headerを読む
- relayは少量のflow stateを更新し、WebRTCを終端しないままパケットを転送する
- OpenAIは、より高いpacket rateのためにuserspace processがnetwork queueを直接pollingするkernel-bypass frameworkは採用せず、この方式は運用の複雑性を増すと判断した
-
主な設計選択
- プロトコル終端なし: relayはSTUN headerとufragだけを解析し、その後のDTLS、RTP、RTCPについてはcached stateを使ってpacketをopaqueなまま扱う
- 一時的な状態: flow stateと可観測性のため、client addressからtransceiver destinationへの小さく短いtimeoutのin-memory mapを保持する
- 水平スケーラビリティ: 複数のrelay instanceがload balancerの背後で並列に動作し、relay stateは厳密なWebRTC stateではないため、再起動時のtraffic dropが小さく、flow recoveryも速い
-
効率化の施策
SO_REUSEPORTは、複数のrelay workerが同じmachine上で同じUDP portにbindできるようにするLinux socket optionで、kernelが流入packetをworkerへ分配し、単一read loopのボトルネックを回避するruntime.LockOSThreadは、各UDP-reading goroutineを特定のOS threadに固定するSO_REUSEPORTとthread pinningを併用すると、同じflowのpacketが同じCPU coreに留まりやすくなり、cache localityが向上し、context switchingが減る- pre-allocated bufferと最小コピーは、parsingとallocationのオーバーヘッドを減らし、Goのgarbage collection回避に役立つ
- この実装は、比較的小さなrelayフットプリントでOpenAIのグローバルなリアルタイムメディアトラフィックを処理できたため、OpenAIはkernel bypass経路を選ばず、より単純な設計を維持した
結果と教訓
- このアーキテクチャにより、数千のUDPポートを公開することなく、Kubernetes上でWebRTCメディアを実行できるようになった
- 小さく固定されたUDPの公開面は、セキュリティとload balancingを容易にし、大きな公開ポート範囲を予約しなくてもinfrastructureを拡張可能にする
- この設計は、クライアントの標準WebRTC動作を保ちつつ、OpenAIのワークロードではSFUなし設計がデフォルトとして妥当であることを確認した
- 大半のセッションはpoint-to-pointで遅延に敏感であり、inference serviceがWebRTC peerとして動作しない場合のほうがスケールさせやすい
- 複雑性は、すべてのbackend serviceやcustom client behaviorではなく、薄いrouting layerに置くほうが適切だった
- protocol-nativeなfieldにrouting metadataをencodingすることで、決定的なfirst-packet routing、小さな公開UDPフットプリント、世界中のユーザー近くにingressを配置する柔軟性を得られた
-
特に重要だった選択
- edgeでプロトコルセマンティクスを維持すること: クライアントは引き続き標準WebRTCを使うため、browserとmobileの相互運用性が保たれる
- 難しいセッション状態を1か所に集約すること: transceiverがICE、DTLS、SRTP、session lifecycleを管理し、relayはpacketだけを転送する
- セットアップ時にすでに存在する情報でroutingすること: ICE ufragが、hot-path lookup dependencyなしにfirst-packet routingフックを提供する
- kernel bypassよりcommon caseの最適化を優先すること:
SO_REUSEPORT、thread pinning、low-allocation parsingを慎重に用いた狭いGo実装だけで、OpenAIのワークロードには十分だった - リアルタイム音声AIは、infrastructureが遅延を感じさせないときに機能し、OpenAIはクライアントがWebRTCに期待する動作を変えるのではなく、WebRTCの展開形態を変える道を選んだ
1件のコメント
Hacker Newsのコメント
OpenAIが、私が取り組んでいるライブラリ Pion のユースケースを公開してくれて本当にありがたい
WebRTCをよく知らないならかなり面白い分野だし、その仕組みを説明する本 WebRTC for the Curious も執筆中です
https://github.com/pion/webrtc
https://webrtcforthecurious.com
音声AIの構成ですでに高速な部類に入る部分を削るために、複雑さを大きく増やしたように見えました。高速なモデルと正確な 音声活動検出(VAD) のほうが、WebRTCの転送時間を微調整することよりずっと重要に思えます
以前、WebRTCのデータチャネルでデータベースからブラウザクライアントへCLI経由でデータを送るアイデアがあって一部を読んだのですが、自分の用途にはあまり合わないと理解できました。結局、集中型の コントロールプレーン とWebSocketを使いました
それでも、WebRTCデータチャネル + コピーなしのApache Arrow ArrayBuffer + duckdb WASM の組み合わせで何か面白いことができそうですが、まだ何をするかは見つかっていません
srcフォルダではなく ルートディレクトリ に置いている理由が気になりますREADMEにたどり着くのがずっと大変になります
低遅延は、実装上の利点というより苦痛に近いです
軽く会話しようとすると、人は自然に少し間を置きますが、GPTはそれを「話が終わった」と受け取ってすぐに話し始めます
年を取るにつれて欲しい単語を見つけるのに時間がかかるようになりますが、こういう高速な音声GPTは助けになるよりイライラのほうが大きいです。話す前に頭の中で文全体を組み立てておかなければならず、まったく自然ではありません
記事で言っている遅延は音声ストリーム自体の伝送遅延で、この状況での遅延は音声ストリームの中でどれだけ早く応答を始めるかに近いです
まだ考え終わっていないのに話し続けなければならないという圧迫感が生まれて、かなり不自然に感じます。適切な単語を探しているなら、それを探す機会が必要です
解決策は、より高遅延のプロトコルではなく 間をもっと賢く扱う ことだと思います。遅延が低ければ、ユーザーが割り込んだときにボットが即座に話すのをやめられます
完璧ではありませんが、割り込みは減ります
また、たいていの知性を問題を考えることより、もっともらしく聞こえることに使っているようにも感じます。「はい、もちろんです。なぜそうしたいのか理解できます…」のような感じです。おそらく時間制限があり、音声処理のほうがコストが高いからでしょう。テキスト応答は作業そのものにもっと時間を使っています
「週次アクティブユーザー9億人超」は、当然ChatGPT全体のユーザー数を指しているのであって、そのうち 音声機能 を使う割合はずっと小さいのではないかと思います
こうした数字は、この問題にどの程度のハードウェアとソフトウェアの最適化を投入するかという事業判断に影響します
実際の利用有無にかかわらず、その機能に接触しうる全ユーザー数を意味します
共有してくれるのは本当にうれしいですが、OpenAIの リアルタイム音声モデル は、能力面ではまだ4o系にとどまっていることは覚えておくべきです
それでも依然として非常に有用ですし、この分野に実質的な競合がいないのは残念です。実際の会話に近い体験は、アイデアや概念を表現するのに大いに役立ちました
リリース当時と違って今は最前線のモデルではない、という点は念頭に置く価値があります。Samがこれを見ているなら、新しいリアルタイム音声モデルを出してほしいです
Googleの Gemini flash live 3.1 のほうが優れていて、特にAPIで使うとよいです。ツール呼び出しも可能で、自分で組めばより賢い別のLLMにもつなげられますし、推論レベルも設定できます。高い推論レベルでも十分リアルタイムに近く、Google検索ベースで回答を補強することもできます。双方向音声が好きなら、今のところおそらく最良の選択肢で、AI Studioで試せます
この分野に入ろうとしている人にとって、pipecat は良いオープンソースのリポジトリでありコミュニティです
https://github.com/pipecat-ai/pipecat
数週間前にようやく知って、Gemma 4 のリリース後、Gemma 4 + Kokoro TTS + Whisper で完全ローカル動作の音声アシスタントをゼロから作っています: https://github.com/pncnmnp/strawberry
Pipecatの smart turn モデルは 音声活動検出(VAD) に本当に優秀です: https://huggingface.co/pipecat-ai/smart-turn-v3
より良いモデルがもっと考えてから答えるなら、応答を長く待っても かまいません
ただし、割り込みをしっかりサポートし、1秒止まったからといってすぐ答え始めず、私が話し終えたかどうかを賢く判断してほしいです
これは単に遅延時間だけの問題ではないかもしれません
ユーザーを 音声会話 に留めておけば、テキストでは決して得られない学習データを得られます。だからこそSFUよりトランシーバー方式を選び、マルチパーティ会話をほとんど無視してもよかったのではないかと思います
これは、OpenAIがもうWebRTC/オーディオに LiveKit を使っていないと読んでいいのでしょうか?
このアーキテクチャでは、LiveKitサーバーは特に望ましい形ではなさそうです。記事のSFUの議論でも、実質的にそう言っています。ただ、クライアントSDKには有用なものがたくさんあります
ストリーム途中で トランシーバー がクラッシュしたら、アクティブなセッションはどう復旧するのでしょうか?
システムは新しいWebRTCセッション上でコンテキストを自動的に再確立するのでしょうか?
すべてのWebRTC状態を保存または一時停止して、次のプロセスで復元できます
友達を作りたいなら、どんな形であれ サークルや集まり に入るほうがいいと思います