3 puntos por GN⁺ 7 시간 전 | 1 comentarios | Compartir por WhatsApp
  • OpenAI rediseñó el enrutamiento interno de paquetes con una arquitectura de relay + transceiver, manteniendo el comportamiento estándar de WebRTC, para ofrecer conversaciones de voz naturales a más de 900 millones de usuarios activos semanales
  • WebRTC estandariza ICE, DTLS/SRTP, la negociación de códecs, el control de calidad con RTCP, la cancelación de eco y el búfer contra jitter, por lo que es adecuado para procesar flujos de audio continuos de baja latencia entre navegadores, apps móviles y servidores
  • La mayoría de las sesiones de OpenAI son sesiones 1:1 en las que conversa 1 usuario con 1 modelo, o 1 aplicación con 1 agente en tiempo real, así que el modelo de transceiver resultó más adecuado para latencia y escalabilidad que un SFU pensado para llamadas multiparte
  • En Kubernetes, el modelo WebRTC de exponer 1 puerto UDP por sesión complicaba los grandes rangos de puertos públicos, la configuración del balanceador de carga, los health checks, las políticas de firewall y la seguridad de los despliegues, por lo que se necesitaba una superficie UDP pequeña y fija
  • El relay usa la ICE ufrag incluida en el primer paquete STUN como pista de enrutamiento para encontrar el transceiver propietario y reenviar allí el tráfico, mientras que el transceiver conserva ICE, DTLS, SRTP y el ciclo de vida de la sesión para mantener compatibilidad estándar con WebRTC junto con rutas de ingreso cercanas a nivel global

Requisitos de la IA de voz de baja latencia

  • La IA de voz se siente natural cuando la conversación avanza a la velocidad del habla, y la latencia de red se hace evidente enseguida en silencios incómodos, interrupciones cortadas o reacciones tardías a una interrupción
  • A la escala de OpenAI, se requiere alcance global para más de 900 millones de usuarios activos semanales, establecimiento rápido de conexión para poder hablar justo después de iniciar la sesión, tiempos de ida y vuelta de medios bajos y estables, y poco jitter y pérdida de paquetes
  • ChatGPT voice, la Realtime API, los agentes en flujos de trabajo conversacionales y todos los modelos que deben procesar audio mientras el usuario habla se ven afectados por estas características de latencia
  • OpenAI rediseñó el stack de WebRTC con una arquitectura separada de relay + transceiver para cambiar el enrutamiento interno de paquetes sin alterar el comportamiento estándar de WebRTC del cliente

Por qué eligieron WebRTC

  • WebRTC es un estándar abierto para enviar audio, video y datos de baja latencia entre navegadores, apps móviles y servidores, y también es adecuado como base para sistemas en tiempo real cliente-servidor
  • WebRTC estandariza el establecimiento de conexión y el cruce de NAT mediante ICE, el transporte cifrado con DTLS/SRTP, la negociación de códecs y funciones del cliente como el control de calidad con RTCP, la cancelación de eco y el búfer contra jitter
  • Sin WebRTC, cada cliente tendría que resolver por su cuenta el establecimiento de conexiones en entornos NAT, el cifrado de medios, la negociación de códecs y la respuesta a cambios de red
  • Una característica importante en productos de IA es que el audio llega como un flujo continuo, por lo que se puede iniciar la transcripción, la inferencia, las llamadas a herramientas y la generación de voz sin esperar a que termine toda la subida del usuario
  • Esa diferencia separa a un sistema que se siente conversacional de uno que se siente como push-to-talk
  • OpenAI se apoya en el ecosistema de WebRTC, incluidas implementaciones open source maduras y el trabajo de estandarización, y gracias a la base creada por Justin Uberti y Sean DuBois pudo construir sobre una infraestructura de medios probada sin tener que reinventar el transporte de bajo nivel, el cifrado ni el control de congestión

La arquitectura que eligió transceiver en lugar de SFU

  • Cuándo sí encaja un SFU

    • Un SFU es un servidor de medios que recibe los flujos WebRTC de cada participante y los reenvía selectivamente a otros participantes
    • En el modelo SFU, se termina una conexión WebRTC separada por cada participante y la IA también se une como otro participante de la sesión
    • SFU puede encajar bien en productos inherentemente multiparte, como llamadas grupales, clases o reuniones colaborativas
    • Puede centralizar en un solo lugar códecs de audio, mensajes RTCP, canales de datos, grabación y políticas por flujo
    • Incluso en productos cliente-a-IA, puede parecer un punto de partida natural porque permite reutilizar en un solo sistema el procesamiento de señales, el enrutamiento de medios, la grabación, la observabilidad y futuras extensiones para transferir a un humano o agregar participantes
  • La carga de trabajo de OpenAI

    • La mayoría de las sesiones de OpenAI son sesiones 1:1 donde conversa 1 usuario con 1 modelo, o 1 aplicación con 1 agente en tiempo real
    • Como esta forma de tráfico es sensible a la latencia en cada turno, eligieron el modelo de transceiver
    • En este modelo, el servicio edge de WebRTC termina la conexión del cliente y luego convierte los medios y eventos a un protocolo interno simple para inferencia del modelo, transcripción, generación de voz, uso de herramientas y orquestación
    • El transceiver es el único servicio que posee el estado de la sesión WebRTC, incluidas las verificaciones de conectividad ICE, el handshake de DTLS, las claves de cifrado SRTP y el ciclo de vida de la sesión
    • Mantener el estado de la sesión en un solo lugar hace más clara la propiedad de la sesión y permite que los servicios backend escalen como servicios normales en lugar de comportarse como peers de WebRTC

Implementación inicial y restricciones encontradas en Kubernetes

  • La primera implementación de OpenAI fue un único servicio en Go basado en Pion, encargado tanto del signaling como de la terminación de medios
  • Este servicio transceiver impulsa ChatGPT voice, el endpoint WebRTC de la Realtime API y varios proyectos de investigación
  • Operativamente, el transceiver se encarga en signaling de la negociación SDP, la selección de códecs, las credenciales ICE y la configuración de la sesión
  • En media, termina la conexión WebRTC downstream y mantiene la conexión upstream con servicios backend para inferencia y orquestación
  • OpenAI quería operar este servicio sobre Kubernetes para que pudiera escalar hacia arriba o hacia abajo con la demanda y moverse entre hosts
  • El modelo tradicional de WebRTC de 1 puerto por sesión no encajaba bien con Kubernetes y obligaba a exponer, proteger y mantener un gran rango de puertos UDP públicos
  • Con alta concurrencia, 1 puerto por sesión obliga a exponer y administrar rangos UDP extremadamente grandes
  • Los balanceadores de carga en la nube y los servicios de Kubernetes no están diseñados bajo la suposición de decenas de miles de puertos UDP públicos por servicio, y mientras más crece el rango de puertos, más complejas se vuelven la configuración del balanceador, los health checks, las políticas de firewall y la seguridad de los despliegues
  • Un gran rango de puertos UDP también es peor para la seguridad porque amplía la superficie accesible desde el exterior y dificulta la auditoría de políticas de red
  • En Kubernetes, los pods se agregan, eliminan y reprograman constantemente, así que si cada pod tiene que reservar y anunciar un rango grande y estable de puertos, la elasticidad se debilita

El problema del puerto único y de la propiedad de la sesión

  • Muchos sistemas WebRTC usan un único puerto UDP por servidor y demultiplexación a nivel de aplicación para reducir el problema del número de puertos
  • Un diseño de puerto único por servidor reduce la cantidad de puertos, pero crea un segundo problema: preservar la propiedad de cada sesión en toda la flota
  • ICE y DTLS son protocolos con estado, por lo que el proceso que creó la sesión debe seguir recibiendo sus paquetes para validar verificaciones de conexión, completar el handshake de DTLS, descifrar SRTP y manejar cambios posteriores de sesión como ICE restart
  • Si los paquetes de la misma sesión llegan a otro proceso, la configuración puede fallar o el medio puede romperse
  • El objetivo de OpenAI era exponer solo una superficie UDP pequeña y fija a Internet pública mientras seguía enrutando todos los paquetes al transceiver propietario de esa sesión WebRTC
  • Enfoques evaluados

    • IP:puerto único por sesión ofrece una ruta directa cliente-servidor para medios y no tiene una capa de forwarding en la ruta de datos, pero requiere 1 puerto UDP público por sesión y un gran rango de puertos que no encaja con Kubernetes, los balanceadores de carga en la nube ni la seguridad
    • IP:puerto único por servidor tiene una huella UDP pública mucho menor que exponer por sesión y permite que un socket compartido demultiplexe muchas sesiones, pero en una flota con balanceo de carga compartido el primer paquete puede llegar a la instancia incorrecta, por lo que se necesita una forma determinista de enviarlo al proceso propietario de la sesión
    • TURN relay solo exige que el cliente pueda alcanzar la dirección y el puerto del TURN relay y permite centralizar políticas en el edge, pero la asignación TURN agrega un viaje extra de ida y vuelta al setup y sigue siendo difícil mover o recuperar asignaciones entre servidores TURN
    • La arquitectura de OpenAI de stateless forwarder + stateful terminator, es decir, relay + transceiver, mantiene una huella UDP pública pequeña y deja que el transceiver posea la sesión WebRTC completa, aunque agrega un salto de forwarding antes de que los medios lleguen al transceiver propietario y requiere coordinación personalizada entre relay y transceiver

Arquitectura relay + transceiver

  • La arquitectura desplegada por OpenAI separa el enrutamiento de paquetes de la terminación del protocolo
  • El signaling llega al transceiver para configurar la sesión, y los medios entran primero al relay
  • El relay es una capa ligera de forwarding UDP con una huella pública pequeña, y el transceiver es el endpoint WebRTC con estado que está detrás
  • El relay no descifra medios, no ejecuta la máquina de estados de ICE ni participa en la negociación de códecs
  • El relay solo lee la metadata de paquetes suficiente para elegir el destino y reenviar el tráfico al transceiver propietario de la sesión
  • El transceiver sigue viendo un flujo WebRTC normal y conserva todo el estado del protocolo
  • Desde la perspectiva del cliente, la sesión WebRTC no cambia

Enrutamiento del primer paquete y uso de ICE ufrag

  • La clave de esta arquitectura es que el relay enruta el primer paquete del cliente dentro de la propia ruta de paquetes
  • Una sesión WebRTC ya tiene un gancho de enrutamiento nativo del protocolo: el ICE username fragment, o ufrag
  • El ufrag es un identificador corto que se intercambia durante la configuración de la sesión y vuelve a transportarse en las verificaciones de conectividad STUN
  • OpenAI genera el ufrag del lado del servidor para incluir suficiente metadata de enrutamiento como para que el relay pueda inferir el cluster de destino y el transceiver propietario
  • Durante signaling, el transceiver asigna el estado de la sesión y devuelve en el SDP answer un VIP de relay compartido y un puerto UDP
  • El VIP es una dirección IP virtual delante de la flota de relay y, combinada con el puerto, ofrece al cliente un único destino estable como 203.0.113.10:3478, incluso detrás de múltiples instancias de relay
  • El primer paquete de la ruta de medios del cliente suele ser una solicitud STUN binding, e ICE la usa para verificar que los paquetes pueden llegar a la dirección anunciada
  • El relay analiza solo lo necesario del primer paquete STUN para leer el ufrag del servidor, decodificar la pista de enrutamiento y reenviar el paquete al transceiver propietario de la sesión
  • Cada transceiver recibe en un socket UDP compartido enlazado a una IP:puerto interna, es decir, un endpoint del sistema operativo, en lugar de usar un socket por sesión
  • Una vez que el relay crea la sesión desde el IP:puerto de origen del cliente hasta el destino del transceiver, los paquetes posteriores de DTLS, RTP y RTCP fluyen dentro de esa sesión sin volver a decodificar el ufrag
  • La sesión del relay mantiene un estado mínimo: solo una sesión en memoria para packet forwarding, contadores de monitoreo y timers para expiración y limpieza de sesión
  • Si el relay se reinicia y pierde la sesión, el siguiente paquete STUN recrea la sesión con la pista de enrutamiento del ufrag
  • Una vez establecido el camino, un caché de Redis guarda el mapeo <IP + puerto del cliente, IP + puerto del transceiver> para permitir una recuperación más temprana incluso antes de que llegue el siguiente paquete STUN

Global Relay y una ruta de ingreso más cercana

  • Después de reducir la superficie UDP pública a un número pequeño y estable de direcciones y puertos, OpenAI pudo desplegar el mismo patrón de relay en todo el mundo
  • Global Relay es una flota geográficamente distribuida de puntos de ingreso relay que implementa el mismo comportamiento de packet forwarding
  • Un ingreso geográfico amplio hace que los paquetes del usuario entren a la red de OpenAI desde un relay cercano en geografía y topología de red, en lugar de cruzar primero el Internet público hasta una región lejana, reduciendo así el primer salto entre el cliente y OpenAI
  • Este enfoque disminuye la latencia, el jitter y las ráfagas de pérdida evitables antes de que el tráfico llegue al backbone
  • OpenAI usa geolocalización de Cloudflare y proximity steering en signaling para que la solicitud inicial por HTTP o WebSocket llegue a un cluster de transceiver cercano
  • El contexto de la solicitud determina la ubicación de la sesión y el punto de ingreso de Global Relay que se anunciará al cliente
  • El SDP answer proporciona la dirección de Global Relay, y el ufrag contiene suficiente información para que Global Relay enrute los medios al cluster asignado y luego el relay los enrute al transceiver de destino
  • Usar juntos signaling guiado por geolocalización y Global Relay coloca tanto el setup como los medios en una ruta de entrada cercana, mientras la sesión sigue anclada a un solo transceiver
  • Esta estructura reduce el tiempo de ida y vuelta en signaling y en la primera verificación de conectividad ICE, reduciendo directamente el tiempo de espera antes de que el usuario pueda empezar a hablar

Cómo está implementado el relay

  • El servicio relay fue escrito en Go y deliberadamente mantiene un alcance de implementación limitado
  • En Linux, el stack de red del kernel recibe paquetes UDP desde la interfaz de red y los entrega al socket, y el relay funciona como un proceso Go normal en userspace que lee los headers de los paquetes desde el socket
  • El relay actualiza una pequeña cantidad de estado de flujo y reenvía los paquetes sin terminar WebRTC
  • OpenAI no usó un kernel-bypass framework en el que un proceso en userspace sondea directamente la cola de red para lograr tasas de paquetes más altas, porque consideró que ese enfoque podía agregar complejidad operativa
  • Decisiones principales de diseño

    • Sin terminación de protocolo: el relay solo analiza el header STUN y el ufrag, y después usa estado en caché para tratar DTLS, RTP y RTCP como paquetes opacos
    • Estado transitorio: mantiene un mapa en memoria pequeño y con timeout corto desde la dirección del cliente hasta el destino del transceiver para el estado del flujo y la observabilidad
    • Escalabilidad horizontal: múltiples instancias de relay corren en paralelo detrás de un balanceador de carga y, como el estado del relay no es estado WebRTC duro, al reiniciarse la pérdida de tráfico es pequeña y la recuperación de flujos es rápida
  • Medidas de optimización

    • SO_REUSEPORT es una opción de socket de Linux que permite que varios workers de relay se enlacen al mismo puerto UDP en la misma máquina, y el kernel distribuye los paquetes entrantes entre ellos para evitar el cuello de botella de un único loop de lectura
    • runtime.LockOSThread fija cada goroutine que lee UDP a un thread específico del sistema operativo
    • Al usar juntos SO_REUSEPORT y thread pinning, los paquetes del mismo flujo tienden a quedarse en el mismo núcleo de CPU, mejorando la localidad de caché y reduciendo los cambios de contexto
    • Los buffers preasignados y la copia mínima reducen la sobrecarga de parsing y asignación, ayudando a evitar la recolección de basura de Go
    • Como esta implementación pudo manejar el tráfico global de medios en tiempo real de OpenAI con una huella de relay relativamente pequeña, OpenAI mantuvo un diseño más simple en lugar de optar por una ruta de kernel bypass

Resultados y aprendizajes

  • Esta arquitectura permite ejecutar medios WebRTC en Kubernetes sin exponer miles de puertos UDP
  • Una superficie UDP pequeña y fija facilita la seguridad y el balanceo de carga, y permite que la infraestructura escale sin reservar grandes rangos de puertos públicos
  • El diseño conserva el comportamiento estándar de WebRTC del cliente y confirmó que una arquitectura sin SFU era la opción predeterminada adecuada para la carga de trabajo de OpenAI
  • La mayoría de las sesiones son point-to-point, son sensibles a la latencia y escalan más fácilmente cuando los servicios de inferencia no tienen que comportarse como peers de WebRTC
  • Fue más adecuado concentrar la complejidad en una capa delgada de enrutamiento, en lugar de distribuirla entre todos los servicios backend o requerir comportamiento personalizado en el cliente
  • Al codificar metadata de enrutamiento en un campo nativo del protocolo, obtuvieron enrutamiento determinista del primer paquete, una huella UDP pública pequeña y la flexibilidad de ubicar puntos de ingreso cerca de usuarios en todo el mundo
  • Elecciones especialmente importantes

    • Preservar la semántica del protocolo en el edge: el cliente sigue usando WebRTC estándar, así que se mantiene la interoperabilidad entre navegadores y móviles
    • Mantener el estado complejo de la sesión en un solo lugar: el transceiver posee ICE, DTLS, SRTP y el ciclo de vida de la sesión, mientras el relay solo reenvía paquetes
    • Enrutar con información que ya existe en el setup: ICE ufrag ofrece un gancho de enrutamiento del primer paquete sin depender de lookups en la ruta crítica
    • Priorizar optimizar el caso común antes que kernel bypass: una implementación Go acotada, usando cuidadosamente SO_REUSEPORT, thread pinning y parsing de baja asignación, fue suficiente para la carga de trabajo de OpenAI
    • La IA de voz en tiempo real funciona cuando la infraestructura logra que la latencia no se perciba, y OpenAI eligió cambiar la forma de desplegar WebRTC sin cambiar el comportamiento que el cliente espera de WebRTC

1 comentarios

 
GN⁺ 7 시간 전
Comentarios en Hacker News
  • Muy agradecido de que OpenAI haya mostrado un caso de uso de Pion, la librería en la que trabajo
    Si no conoces bien WebRTC, es un campo bastante interesante, y también estoy trabajando en un libro que explica cómo funciona: WebRTC for the Curious
    https://github.com/pion/webrtc
    https://webrtcforthecurious.com

    • Uso Pion. Pero me pregunto si el enfoque de OpenAI realmente era necesario
      Parecía que aumentaron muchísimo la complejidad para reducir una parte que ya está entre las más rápidas en una arquitectura de IA de voz. Da la impresión de que un modelo rápido y una detección de actividad de voz (VAD) precisa son mucho más importantes que ajustar al milímetro el tiempo de transporte de WebRTC
    • Gracias por poner todo el libro en línea
      Hace tiempo leí una parte porque tenía la idea de enviar datos desde una base de datos a un cliente de navegador por medio de un CLI usando canales de datos de WebRTC, y me ayudó a entender que no encajaba bien con mi caso de uso. Al final usé un plano de control centralizado y WebSocket
      Aun así, siento que se podría hacer algo interesante con la combinación de canal de datos de WebRTC + Apache Arrow ArrayBuffer sin copias + duckdb WASM, aunque todavía no encuentro exactamente qué
    • Esto ya es un tema un poco distinto, pero me da curiosidad por qué ponen todo el codebase en el directorio raíz en vez de usar carpetas src anidadas
      Hace mucho más difícil llegar al README
  • La baja latencia, más que una ventaja de implementación, se siente como un dolor de cabeza
    Cuando uno intenta conversar de forma casual, las personas naturalmente hacen pausas breves, pero GPT lo interpreta como “ya terminó de hablar” y empieza a hablar de inmediato
    A medida que uno envejece, tarda más en encontrar la palabra que quiere, y estos GPT de voz tan rápidos resultan más irritantes que útiles. Uno tiene que tener pensada toda la frase en la cabeza antes de hablar, así que no se siente nada natural

    • Aquí se están mezclando dos capas distintas de latencia
      La latencia de la que habla el artículo es la del transporte del propio stream de audio, mientras que la latencia en esta situación tiene más que ver con qué tan rápido empieza a responder dentro del stream de audio
    • A mí también me ha pasado y de verdad es molesto
      Se siente bastante poco natural esa presión de tener que seguir hablando aunque todavía no hayas terminado de pensar. Si estás buscando la palabra adecuada, necesitas la oportunidad de encontrarla
      Creo que la solución no va por un protocolo de mayor latencia, sino por manejar mejor las pausas. Si la latencia es baja, el bot puede dejar de hablar inmediatamente cuando el usuario lo interrumpe
    • En conversaciones por voz, le digo que no responda en absoluto hasta que use cierta palabra clave, o que solo diga “entendido”
      No es perfecto, pero interrumpe menos
    • Esto tiene más que ver con la detección de actividad de voz (VAD) que con la latencia de la que habla el artículo
    • Es un problema difícil. Sin darme cuenta termino agregando muletillas para evitar que el bot se ponga a hablar
      Además, parece que la mayor parte de su inteligencia se usa no para pensar el problema, sino para sonar plausible. Algo como: “Sí, claro. Entiendo por qué quiere eso...”. Supongo que será por límites de tiempo y porque el procesamiento de voz es más caro. Las respuestas en texto dedican más tiempo a la tarea en sí
  • “Más de 900 millones de usuarios activos semanales” claramente parece referirse al total de usuarios de ChatGPT, y supongo que la proporción que usa la función de voz debe ser mucho menor
    Este tipo de cifras influye en decisiones de negocio, como cuánto hardware y optimización de software vale la pena dedicarle al problema

    • Sí. Por eso seguramente usaron la palabra “reach”
      Se refiere al total de usuarios que podrían estar expuestos a la función, independientemente de si realmente la usan
  • Da gusto que compartan esto, pero hay que recordar que los modelos de audio en tiempo real de OpenAI siguen más o menos al nivel de la familia 4o en capacidad
    Aun así, siguen siendo muy útiles, y es una pena que no haya competencia real en este espacio. La experiencia parecida a una conversación real me ha ayudado mucho a expresar ideas y conceptos
    Vale la pena tener presente que, a diferencia del momento de su lanzamiento, ya no son modelos de frontera. Si Sam llega a ver esto, ojalá saque un nuevo modelo de audio en tiempo real

    • En el modo realtime/voice de OpenAI, la parte de voz es excelente, pero comparado con los modelos más recientes es bastante tonto y a veces repite lo mismo
      Gemini flash live 3.1 de Google es mejor, sobre todo si lo usas por API. También permite llamadas a herramientas, y si lo montas tú mismo puedes conectarlo a otros LLM más inteligentes, además de ajustar el nivel de razonamiento. Incluso un nivel alto de razonamiento sigue siendo lo bastante cercano al tiempo real, y se pueden reforzar las respuestas con búsqueda de Google. Si te gusta la voz bidireccional, probablemente sea la mejor opción ahora mismo, y puedes probarlo en AI Studio
  • Para quien quiera entrar en este campo, pipecat es un muy buen repositorio open source y una buena comunidad
    https://github.com/pipecat-ai/pipecat

  • Si un mejor modelo responde después de pensar más, me parece bien esperar más tiempo la respuesta
    Pero debe manejar bien las interrupciones, no empezar a responder apenas pasa 1 segundo de silencio y decidir de forma inteligente si ya terminé de hablar

  • Puede que esto no sea solo un tema de latencia
    Si mantienes al usuario dentro de una conversación por voz, obtienes datos de entrenamiento que jamás conseguirías con texto. Por eso me pregunto si eligieron el enfoque de transceptor en vez de SFU y les dio igual ignorar en gran medida las conversaciones multiusuario

  • ¿Se puede leer esto como que OpenAI ya no usa LiveKit para WebRTC/audio?

    • Eso parece
      No parece que un servidor de LiveKit encaje realmente con lo que quieren en esta arquitectura. De hecho, la discusión sobre SFU en el artículo prácticamente lo dice. Aun así, en el SDK del cliente hay cosas útiles
  • Si el transceptor se cae en medio del stream, ¿cómo se recupera una sesión activa?
    ¿El sistema vuelve a establecer automáticamente el contexto en una nueva sesión de WebRTC?

  • Si quieres hacer amigos, creo que es mejor entrar a algún club o grupo de encuentros, de la forma que sea