OpenAI 如何做低延迟规模化语音 AI(WebRTC 导读)
文章目录为什么实时语音产品会押注 WebRTC媒体架构选型SFU vs Transceiver收发器方案一SFU选择性转发单元方案二Transceiver——边缘终结 WebRTC后端走「更简单」的协议核心矛盾WebRTC 遇上 Kubernetes端口耗尽与运维面状态粘性stickiness架构路线对比Relay Transceiver把「路由」与「终结」拆开Relay 只做转发不终结 WebRTC用 ICE 凭据ufrag做首包路由Global Relay 与地理亲和信令Relay 的实现与性能取向结果与经验总结延伸阅读原文参考文献只有当对话节奏逼近 人类说话的自然节拍 时语音 AI 才会「好听、好用」。网络一旦在中间插一脚人类会立刻听成尴尬停顿、抢话不完整、打断barge-in变慢。这对 ChatGPT 语音模式、使用 Realtime API 的开发者、交互式 Agent 流水线以及「边听边推理」的模型形式都成立。在 OpenAI 自称的体量下问题被压成三条 硬指标全球可达服务 9 亿 周活 级别的用户分布连接建立要快会话一开始就能说话而不是等一串握手转圈媒体路径 RTT 低且稳抖动、丢包可控话轮切换turn-taking 才显得干脆。负责实时交互的团队近期 重做了 WebRTC 栈用来缓解三件事在规模上来以后开始「互相打架」的约束「每会话独占一个公网 UDP 端口」 的媒体终结模型和 OpenAI 的基础设施形态 并不合拍有状态的 ICE / DTLS 会话 需要 稳定 ownership归属进程全球路由 必须把 首跳延迟 压住。下文整理原文脉络在客户端仍保持标准 WebRTC 语义 的前提下分机房内部如何改包的路由形态——也就是他们所称的 split relay transceiver拆分中继 收发器 架构。为什么实时语音产品会押注 WebRTCWebRTC 是面向浏览器、移动 App 与服务器之间 低延迟音视频与数据 的开放标准。很多人把它和 P2P 通话绑在一起但它同样是 客户端—服务器实时系统 的务实底座因为把最难的几块「协议事实标准」封好了ICE建连与 NAT 穿透DTLS SRTP加密传输编解码协商发送端压缩、接收端解压RTCP质量控制与反馈以及浏览器侧的 回声消除、抖动缓冲 等能力。对 AI 产品来说关键性质是音频以连续流到达。语音 Agent 可以在用户 尚未说完 时就开始 转写、推理、调工具或合成回复——这是 真·对话感 与 对讲机式 push-to-talk 的分水岭。OpenAI 也点名了生态里的基础工作例如 Justin UbertiWebRTC 早期架构师之一与 Pion 作者 Sean DuBois 等两人现均在 OpenAI 任职——团队得以在 久经沙场的媒体栈 上继续往前推而不是从零发明低层传输、加密与拥塞控制。媒体架构选型SFU vs Transceiver收发器在哪里终结 WebRTC由谁持有连接、解密媒体、承担会话状态决定了延迟、路由、故障域与扩展方式。方案一SFU选择性转发单元SFU 从每个参与者收一路 WebRTC再 选择性转发 给其他参与者每个参与者各有一条独立 WebRTC 连接AI 也可以作为 又一个参与者 入会。适合 天然多人 场景群组通话、教室、协作会议编解码、RTCP、数据通道、录制、按流策略都能 集中在一处。即便在「人对模型」产品里SFU 也常常是默认起点一套系统同时覆盖 信令、媒体路由、观测与未来扩展例如人工接管、再加参与者。方案二Transceiver——边缘终结 WebRTC后端走「更简单」的协议OpenAI 的主体流量形态是 1:1一个人对一只模型或一个应用对一只实时 Agent每一轮都对延迟极端敏感。他们选择 transceiver 模型WebRTC 边缘服务 在边缘 终结客户端连接再把媒体与事件 转换成内部更简单的协议对接推理、转写、语音合成、工具与编排。在此设计里只有 transceiver 拥有完整 WebRTC 会话状态ICE 连通性检查、DTLS 握手、SRTP 密钥、会话生命周期。「终结」意味着 握手与加解密媒体 都在 transceiver 完成后端服务 不必再扮演 WebRTC 对端可以像普通微服务一样扩缩。核心矛盾WebRTC 遇上 Kubernetes第一版实现是 单个 Go 服务基于 Pion 同时扛 信令 媒体终结支撑 ChatGPT 语音、Realtime API 的 WebRTC 入口以及若干研究项目。从职责上 transceiver 做两件事信令SDP 协商、编解码选择、ICE 凭据、会话搭建媒体对下终结用户 WebRTC对上维护连到推理/编排后端的连接。团队希望它像其它服务一样跑在 Kubernetes 上——随负载扩缩、在节点间迁移。但 「每会话一个 UDP 端口」 的传统 WebRTC 部署方式在这里会集体踩雷。端口耗尽与运维面高并发下要对外暴露、管理 巨大范围的公网 UDP 端口。云负载均衡与 K8s Service 不是为「单服务上万 UDP 端口」 设计的每加一段端口区间都在 LB 配置、健康检查、防火墙、发布安全 上加成本。大端口面 更大攻击面网络策略审计也更痛。与弹性扩缩天然冲突Pod 频繁增删、重调度时还要求每个 Pod 稳定持有并宣告一大段端口弹性会变得 脆弱。这也是许多 WebRTC 系统最终走向 「单台服务器一个 UDP 端口 应用层多路复用」 的原因。状态粘性stickiness单端口/多路复用缓解端口问题但带来第二个问题会话归属。ICE 与 DTLS 是有状态协议。创建会话的进程必须持续收到该会话的数据包才能完成连通性检查、DTLS、SRTP 解密以及后续 ICE restart 等变更。若同一会话的包落到 另一个进程建连失败或媒体直接断。目标因此非常具体对外只暴露 少量、固定 的 UDP 入口同时 把每个包 Deterministic 地送到拥有该 WebRTC 会话的 transceiver。架构路线对比路线优点缺点每会话独立公网 IP:端口直连 UDP媒体路径直接数据面无转发层每会话一个公网 UDP 端口大端口区间难暴露与加固与 K8s / 云 LB 契合度差每服务器唯一 IP:端口公网 UDP 面远小于「每会话一端口」单进程可共享 socket 多路复用单机内好做跨负载均衡集群时首包仍可能落到「错误实例」仍需确定性 steeringTURN 中继协议终结型客户端只需打到一个中继地址策略可集中在边缘TURN allocation 增加建连 RTT跨 TURN 迁移/恢复仍难无状态转发层 有状态终结点OpenAIRelay Transceiver公网 UDP 面小transceiver 仍完整持有 WebRTC 状态媒体多一跳转发relay 与 transceiver 间的协作需自研Relay Transceiver把「路由」与「终结」拆开上线的架构 分离了包路由与协议终结信令 仍打到 transceiver 完成会话搭建媒体 先进入 Relay中继再由中继转发到对的 transceiver。Relay 是轻量 UDP 转发层对外 UDP 面很小transceiver 是躲在后面的 有状态 WebRTC 终结点。Relay 只做转发不终结 WebRTCRelay 不解密媒体、不跑 ICE 状态机、不参与编解码协商只读取 够用即可 的元数据来选择目的地然后把包交给 持有会话的 transceiver。对客户端而言WebRTC 会话语义不变。用 ICE 凭据ufrag做首包路由难点是中继必须在「路径上尚无完整会话上下文」之前就能路由客户端的第一帧有效包——而不是先卡去查中心化服务。WebRTC 里现成的挂钩是 ICE username fragmentufrag建连时在信令里交换并会出现在 STUN 连通性检测报文中。OpenAI 生成服务端 ufrag让其中编入 刚刚好 的提示信息中继据此推断 目标集群与所属 transceiver。流程概要信令阶段transceiver 分配会话状态并在 SDP Answer 里返回 共享的 Relay VIP UDP 端口VIP 是中继集群前的虚拟地址形如203.0.113.10:3478背后是多实例。客户端第一条走媒体路径的数据往往是 STUN Binding Request用于 ICE 校验可达性。Relay 只解析这层 STUN读出 服务器侧 ufrag解码路由提示把包转给 owner transceiver。各 transceiver 监听 共享 UDP socket一个 OS 级 IP:Port 端点不是每会话一个 socket。Relay 为「客户端源 IP:端口 → transceiver 目的地」建 极简转发会话后续 DTLS / RTP / RTCP 在同一 flow 上继续走无需每包重解 ufrag。Relay 的状态刻意保持 短命、内存内转发所需的最小 map、监控计数、过期清理定时器。若 Relay 重启丢状态下一帧 STUN 仍可凭 ufrag 重建路由。为更稳文中还提到用 Redis 缓存 〈客户端 IP端口, transceiver IP端口〉 映射在下一帧 STUN 到来前 就有机会恢复路径。Global Relay 与地理亲和信令把公网 UDP 收敛到少量稳定地址后同一套 Relay 模式 可以 全球复制Global Relay 地理上分散的中继入站点行为一致。地缘入站 缩短 用户 → OpenAI 的第一跳在地理与运营商拓扑上都更「就近」通常带来 更低 RTT、更少抖动与可避免丢包突刺再进入骨干网。信令侧他们使用 Cloudflare 的地理与就近 steering让首次 HTTP / WebSocket 到达 附近的 transceiver 集群请求上下文决定 会话落点 以及向客户端公布的 Global Relay 入站点。SDP Answer 给 Global Relay 地址ufrag 再携带 足够信息 让 Global Relay 把媒体送到指定集群、并由 relay 送到具体 transceiver。地理亲和信令 Global Relay 同时压缩 信令 RTT 与 首包 ICE 检测 RTT直接缩短 从点「开始说话」到媒体真正跑起来 的等待。Relay 的实现与性能取向Relay 用 Go 编写刻意保持 窄职责。Linux 内核把网卡上的 UDP 交给 socketRelay 在用户态读包头、维护少量流状态、转发 不落地终结 WebRTC。团队表示 未上 kernel bypass如 DPDK 一类——那能换更高 PPS但运维复杂度也上去对他们 当前流量形态 不划算。几个关键工程点原文要点不在热路径做协议终结除首包/必要 STUN 外DTLS/RTP/RTCP 保持 opaque不透明转发短命内存态小 map 短超时水平扩展多实例挂 LB重启影响面有限、流可快速自愈SO_REUSEPORT多 worker 同绑一个 UDP 端口由内核把包扇到不同 worker避免单读循环瓶颈runtime.LockOSThread把读 UDP 的 goroutine 钉在固定 OS 线程配合SO_REUSEPORT尽量让同一 五元组流 落在 同一 CPU 核改善缓存局部性、减少上下文切换预分配缓冲、少拷贝、少分配降低 Go GC 压力。结果与经验总结该架构让 OpenAI 能在 Kubernetes 上跑 WebRTC 媒体而 不必对公网撒成千上万 UDP 端口收敛 UDP 面 带来 更好负载均衡与更安全 的边界也让基础设施 按常规云原生方式扩缩。同时客户端仍是标准 WebRTC浏览器与移动生态的互操作性得以保留团队认为对其 点对点、延迟敏感、后端不应扮演 WebRTC peer 的 workload默认不走 SFU 是正确默认。文中提炼的几条「更要紧的选择」把复杂度加在薄路由层而不是摊进每个后端服务也不要求客户端魔改协议。边缘保持协议语义硬状态只留在一处transceiver 握 ICE/DTLS/SRTP/生命周期。路由信息来自建连阶段已有的字段ICE ufrag避免热路径依赖额外 lookup。先把常见路径优化到极致再考虑 kernel bypass。延伸阅读原文参考文献How Discord Handles Two and Half Million Concurrent Voice Users using WebRTCl7mp/stunner面向 Kubernetes 的 WebRTC 媒体网关WebRTC Ports in a nutshell - BlogGeek.meLiveKitDeploy to Kubernetesmediasoup单 UDP 端口承载媒体连接Cloudflare Calls: millions of cascading trees all the way down