- OpenAI ออกแบบการส่งต่อแพ็กเก็ตภายในใหม่เป็นโครงสร้าง relay + transceiver โดยยังคงพฤติกรรม WebRTC มาตรฐานไว้ เพื่อให้การสนทนาด้วยเสียงที่เป็นธรรมชาติแก่ผู้ใช้ที่ใช้งานต่อสัปดาห์ มากกว่า 900 ล้านคน
- WebRTC ทำให้ ICE, DTLS/SRTP, การเจรจา codec, การควบคุมคุณภาพด้วย RTCP, การตัดเสียงสะท้อน และการบัฟเฟอร์ jitter เป็นมาตรฐาน จึงเหมาะกับการจัดการสตรีมเสียงต่อเนื่องแบบหน่วงต่ำระหว่างเบราว์เซอร์ แอปมือถือ และเซิร์ฟเวอร์
- เซสชันส่วนใหญ่ของ OpenAI เป็น เซสชันแบบ 1:1 ที่มีผู้ใช้ 1 คนคุยกับโมเดล 1 ตัว หรือแอปพลิเคชัน 1 ตัวคุยกับเอเจนต์แบบเรียลไทม์ 1 ตัว จึงทำให้โมเดล transceiver เหมาะกว่า SFU ที่ออกแบบมาสำหรับการโทรหลายฝ่าย ทั้งในด้าน latency และ scalability
- โมเดล WebRTC ที่เปิดพอร์ต UDP 1 พอร์ตต่อ 1 เซสชันบน Kubernetes ทำให้เกิดความซับซ้อนด้านช่วงพอร์ตสาธารณะขนาดใหญ่ การตั้งค่า load balancer การทำ health check นโยบายไฟร์วอลล์ และความปลอดภัยของ rollout จึงจำเป็นต้องมี พื้นผิว UDP ที่เล็กและคงที่
- relay จะค้นหา transceiver เจ้าของเซสชันจาก hint สำหรับการ routing ที่บรรจุอยู่ใน ICE ufrag ของแพ็กเก็ต STUN แรก แล้วส่งต่อไปยังปลายทาง โดย transceiver เป็นผู้ดูแล ICE, DTLS, SRTP และวงจรชีวิตของเซสชัน ทำให้ยังคงความเข้ากันได้กับ WebRTC มาตรฐาน พร้อมมีเส้นทาง ingress ที่อยู่ใกล้ผู้ใช้ทั่วโลก
ข้อกำหนดของ Voice AI แบบหน่วงต่ำ
- Voice AI จะให้ความรู้สึกเป็นธรรมชาติเมื่อบทสนทนาเดินไปด้วยความเร็วระดับการพูด และความหน่วงของเครือข่ายจะเผยให้เห็นทันทีในรูปของช่วงเงียบที่ไม่เป็นธรรมชาติ การแทรกจังหวะที่ขาดหาย หรือการตอบสนองต่อการพูดแทรกที่ช้าเกินไป
- ในระดับของ OpenAI จำเป็นต้องรองรับการเข้าถึงจากทั่วโลกสำหรับผู้ใช้ที่ใช้งานต่อสัปดาห์ มากกว่า 900 ล้านคน ต้องตั้งค่าการเชื่อมต่อได้รวดเร็วพอให้พูดได้ทันทีหลังเริ่มเซสชัน มีเวลาหน่วงไปกลับของสื่อที่ต่ำและสม่ำเสมอ รวมถึงมี jitter และ packet loss ต่ำ
- ทั้ง ChatGPT voice, Realtime API, เอเจนต์ใน workflow แบบสนทนา และโมเดลที่ต้องประมวลผลเสียงขณะที่ผู้ใช้กำลังพูด ล้วนได้รับผลจากคุณลักษณะด้าน latency นี้
- OpenAI จึงออกแบบ WebRTC stack ใหม่ด้วยโครงสร้างแยก relay + transceiver เพื่อเปลี่ยนวิธี routing แพ็กเก็ตภายใน ขณะยังคงพฤติกรรม WebRTC มาตรฐานของฝั่งไคลเอนต์ไว้
เหตุผลที่เลือก WebRTC
- WebRTC เป็นมาตรฐานเปิดสำหรับการส่งเสียง วิดีโอ และข้อมูลแบบหน่วงต่ำระหว่างเบราว์เซอร์ แอปมือถือ และเซิร์ฟเวอร์ และยังเหมาะเป็นพื้นฐานของระบบเรียลไทม์แบบไคลเอนต์ถึงเซิร์ฟเวอร์
- WebRTC ทำให้การตั้งค่าการเชื่อมต่อและการทะลุ NAT ผ่าน ICE, การรับส่งที่เข้ารหัสด้วย DTLS/SRTP, การเจรจา codec, การควบคุมคุณภาพด้วย RTCP และความสามารถฝั่งไคลเอนต์อย่างการตัดเสียงสะท้อนและการบัฟเฟอร์ jitter กลายเป็นมาตรฐาน
- หากไม่มี WebRTC ไคลเอนต์แต่ละตัวจะต้องแก้ปัญหาการสร้างการเชื่อมต่อในสภาพแวดล้อม NAT การเข้ารหัสสื่อ การเจรจา codec และการรับมือกับการเปลี่ยนแปลงของเครือข่ายด้วยตนเอง
- คุณสมบัติสำคัญสำหรับผลิตภัณฑ์ AI คือเสียงมาถึงในรูป สตรีมต่อเนื่อง จึงเริ่มการถอดเสียง การอนุมาน การเรียกใช้เครื่องมือ และการสร้างเสียงพูดได้โดยไม่ต้องรอให้การอัปโหลดทั้งหมดจากผู้ใช้เสร็จสิ้นก่อน
- ความแตกต่างนี้เป็นตัวแบ่งระหว่างระบบที่ให้ความรู้สึกโต้ตอบได้จริงกับระบบที่ให้ความรู้สึกเหมือน push-to-talk
- OpenAI ใช้ระบบนิเวศ WebRTC เป็นฐาน ซึ่งรวมถึง implementation แบบโอเพนซอร์สที่สุกงอมและงานมาตรฐานต่าง ๆ โดยอาศัยงานพื้นฐานจาก Justin Uberti และ Sean DuBois ทำให้สามารถสร้างบนโครงสร้างพื้นฐานสื่อที่ผ่านการพิสูจน์แล้ว แทนที่จะต้องสร้างชั้นการรับส่ง การเข้ารหัส และการควบคุม congestion ระดับล่างขึ้นใหม่
สถาปัตยกรรมที่เลือก transceiver แทน SFU
-
กรณีที่ SFU เหมาะสม
- SFU คือ media server ที่รับสตรีม WebRTC ของผู้เข้าร่วมแต่ละคน แล้วส่งต่อแบบเลือกได้ไปยังผู้เข้าร่วมคนอื่น
- ในโมเดล SFU จะมีการสิ้นสุดการเชื่อมต่อ WebRTC แยกกันสำหรับผู้เข้าร่วมแต่ละราย และ AI ก็เข้าร่วมเป็นผู้เข้าร่วมอีกคนหนึ่งของเซสชัน
- SFU อาจเหมาะกับผลิตภัณฑ์ที่เป็นการสื่อสารหลายฝ่ายโดยธรรมชาติ เช่น การโทรกลุ่ม ห้องเรียน หรือการประชุมร่วมงาน
- สามารถรวม audio codec, ข้อความ RTCP, data channel, การบันทึก และนโยบายรายสตรีมไว้ในที่เดียว
- แม้ในผลิตภัณฑ์แบบไคลเอนต์ต่อ AI ก็ยังมักถูกใช้เป็นจุดเริ่มต้น เพราะสามารถนำระบบเดียวกันกลับมาใช้กับการประมวลผลสัญญาณ การ routing สื่อ การบันทึก การสังเกตการณ์ และการขยายในอนาคต เช่น การส่งต่อให้มนุษย์รับช่วงหรือเพิ่มผู้เข้าร่วม
-
เวิร์กโหลดของ OpenAI
- เซสชันส่วนใหญ่ของ OpenAI เป็น เซสชันแบบ 1:1 ที่ผู้ใช้ 1 คนคุยกับโมเดล 1 ตัว หรือแอปพลิเคชัน 1 ตัวคุยกับเอเจนต์แบบเรียลไทม์ 1 ตัว
- รูปแบบทราฟฟิกนี้ไวต่อ latency ของแต่ละรอบการโต้ตอบ จึงเลือกโมเดล transceiver
- ในโมเดล transceiver บริการ WebRTC edge จะสิ้นสุดการเชื่อมต่อจากไคลเอนต์ แล้วแปลงสื่อและเหตุการณ์ให้เป็นโปรโตคอลภายในที่เรียบง่ายสำหรับการอนุมานของโมเดล การถอดเสียง การสร้างเสียงพูด การใช้เครื่องมือ และ orchestration
- transceiver เป็นบริการเพียงตัวเดียวที่ครอบครองสถานะของเซสชัน WebRTC รวมถึงการตรวจสอบการเชื่อมต่อ ICE, DTLS handshake, คีย์เข้ารหัส SRTP และวงจรชีวิตของเซสชัน
- เมื่อรวมสถานะของเซสชันไว้ที่เดียว จะเข้าใจความเป็นเจ้าของเซสชันได้ง่ายขึ้น และบริการ backend ก็ขยายขนาดได้เหมือนบริการทั่วไป โดยไม่ต้องทำตัวเป็น WebRTC peer
implementation เริ่มต้นและข้อจำกัดที่พบใน Kubernetes
- implementation แรกของ OpenAI เป็นบริการ Go เดี่ยวที่อิงกับ Pion และรับผิดชอบทั้ง signaling และการสิ้นสุดสื่อ
- บริการ transceiver นี้ขับเคลื่อนทั้ง ChatGPT voice, WebRTC endpoint ของ Realtime API และโครงการวิจัยหลายอย่าง
- ในเชิงปฏิบัติการ transceiver จัดการ signaling เช่น การเจรจา SDP, การเลือก codec, ข้อมูลรับรอง ICE และการตั้งค่าเซสชัน
- ในส่วนของ media จะสิ้นสุดการเชื่อมต่อ WebRTC ฝั่ง downstream และคงการเชื่อมต่อ upstream กับบริการ backend สำหรับการอนุมานและ orchestration
- OpenAI ต้องการรันบริการนี้บน Kubernetes ที่สามารถขยายและหดตามความต้องการ และย้ายข้ามโฮสต์ได้
- แต่โมเดล WebRTC แบบเดิมที่ใช้ 1 พอร์ตต่อ 1 เซสชัน ไม่เข้ากับสภาพแวดล้อม Kubernetes เพราะต้องเปิด ป้องกัน และดูแลช่วงพอร์ต UDP สาธารณะขนาดใหญ่
- เมื่อมี concurrency สูง การใช้ 1 พอร์ตต่อ 1 เซสชันทำให้ต้องเปิดเผยและจัดการช่วงพอร์ต UDP ขนาดใหญ่มาก
- cloud load balancer และ Kubernetes service ไม่ได้ถูกออกแบบมาบนสมมติฐานว่าจะต้องรองรับพอร์ต UDP สาธารณะหลายหมื่นพอร์ตต่อบริการ และยิ่งช่วงพอร์ตกว้างขึ้น การตั้งค่า load balancer, health check, นโยบายไฟร์วอลล์ และความปลอดภัยของ rollout ก็ยิ่งซับซ้อน
- ช่วงพอร์ต UDP ขนาดใหญ่ยังขยายพื้นผิวที่เข้าถึงได้จากภายนอก และทำให้การตรวจสอบนโยบายเครือข่ายเพื่อความปลอดภัยยากขึ้น
- ใน Kubernetes มีการเพิ่ม ลบ และ reschedule pod อยู่ตลอด หากแต่ละ pod ต้องจองและประกาศช่วงพอร์ตขนาดใหญ่ที่เสถียร ความยืดหยุ่นก็จะอ่อนแอลง
ปัญหา single port และความเป็นเจ้าของเซสชัน
- ระบบ WebRTC จำนวนมากลดปัญหาจำนวนพอร์ตด้วยการใช้พอร์ต UDP เดียวต่อเซิร์ฟเวอร์และทำ application-level demultiplexing
- การออกแบบแบบใช้พอร์ตเดียวต่อเซิร์ฟเวอร์ช่วยลดจำนวนพอร์ต แต่สร้างปัญหาที่สองขึ้นมา คือ ต้องรักษา ความเป็นเจ้าของ ของแต่ละเซสชันไว้ตลอดทั้ง fleet
- ICE และ DTLS เป็นโปรโตคอลที่มีสถานะ ดังนั้น process ที่สร้างเซสชันขึ้นมาจะต้องเป็นตัวที่รับแพ็กเก็ตของเซสชันนั้นต่อไป เพื่อยืนยันการตรวจสอบการเชื่อมต่อ ทำ DTLS handshake ให้เสร็จ ถอดรหัส SRTP และจัดการการเปลี่ยนแปลงของเซสชันภายหลัง เช่น ICE restart
- หากแพ็กเก็ตของเซสชันเดียวกันไปถึง process อื่น การตั้งค่าอาจล้มเหลวหรือสื่ออาจเสียหายได้
- เป้าหมายของ OpenAI คือเปิดเผยพื้นผิว UDP ต่ออินเทอร์เน็ตสาธารณะให้น้อยและคงที่ ขณะเดียวกันก็ routing แพ็กเก็ตทั้งหมดไปยัง transceiver ที่เป็นเจ้าของเซสชัน WebRTC นั้น
-
แนวทางที่พิจารณา
- IP:port เฉพาะต่อเซสชัน ให้เส้นทางสื่อแบบไคลเอนต์ถึงเซิร์ฟเวอร์โดยตรง และไม่มีชั้น forwarding อยู่ใน data path แต่ต้องใช้พอร์ต UDP สาธารณะ 1 พอร์ตต่อเซสชัน ซึ่งไม่เข้ากับ Kubernetes, cloud load balancer และข้อกำหนดด้านความปลอดภัย
- IP:port เฉพาะต่อเซิร์ฟเวอร์ มีรอยเท้า UDP สาธารณะเล็กกว่าการเปิดเผยต่อเซสชันมาก และใช้ shared socket เดียวทำ demultiplex หลายเซสชันได้ แต่เมื่อมี fleet ที่ใช้ load balancing ร่วมกัน แพ็กเก็ตแรกอาจไปถึง instance ผิดตัว จึงต้องมีวิธีส่งไปยัง process ที่เป็นเจ้าของเซสชันอย่างแน่นอน
- TURN relay ทำให้ไคลเอนต์ต้องเข้าถึงเพียง address และ port ของ TURN relay เท่านั้น และสามารถรวมศูนย์นโยบายไว้ที่ edge ได้ แต่การทำ TURN allocation เพิ่มรอบการตั้งค่า และการย้ายหรือกู้คืน allocation ระหว่างเซิร์ฟเวอร์ TURN ก็ยังทำได้ยาก
- โครงสร้าง relay + transceiver ของ OpenAI ซึ่งเป็นแบบ stateless forwarder + stateful terminator ช่วยคงรอยเท้า UDP สาธารณะให้เล็ก และให้ transceiver เป็นเจ้าของเซสชัน WebRTC ทั้งหมด แต่เพิ่ม forwarding hop อีก 1 ชั้นก่อนที่สื่อจะไปถึง transceiver เจ้าของเซสชัน และต้องมีการประสานงานแบบกำหนดเองระหว่าง relay กับ transceiver
สถาปัตยกรรม relay + transceiver
- โครงสร้างที่ OpenAI นำไปใช้งานแยก การ routing แพ็กเก็ต ออกจาก การสิ้นสุดโปรโตคอล
- signaling จะไปถึง transceiver เพื่อใช้ตั้งค่าเซสชัน ส่วน media จะเข้า relay ก่อน
- relay เป็นชั้น UDP forwarding น้ำหนักเบาที่มี footprint สาธารณะขนาดเล็ก ส่วน transceiver คือ WebRTC endpoint แบบมีสถานะที่อยู่ด้านหลัง
- relay จะไม่ถอดรหัส media ไม่รัน ICE state machine และไม่เข้าร่วมในการเจรจา codec
- relay อ่านเพียง metadata ของแพ็กเก็ตเท่าที่จำเป็นต่อการเลือกปลายทาง แล้วส่งแพ็กเก็ตต่อไปยัง transceiver ที่เป็นเจ้าของเซสชัน
- transceiver ยังคงเห็นลำดับการทำงานของ WebRTC ตามปกติ และยังคงครอบครองสถานะของโปรโตคอลทั้งหมด
- จากมุมมองของไคลเอนต์ เซสชัน WebRTC ไม่ได้เปลี่ยนไป
การ routing แพ็กเก็ตแรกและการใช้ ICE ufrag
- หัวใจของสถาปัตยกรรมนี้คือ relay ทำการ routing แพ็กเก็ตแรกจากไคลเอนต์ภายในเส้นทางแพ็กเก็ตเอง
- ในเซสชัน WebRTC มี hook สำหรับ routing ที่มีอยู่ในระดับโปรโตคอลอยู่แล้ว นั่นคือ ICE username fragment หรือ ufrag
- ufrag เป็นตัวระบุสั้น ๆ ที่มีการแลกเปลี่ยนกันระหว่างการตั้งค่าเซสชัน และจะถูกส่งซ้ำมาอีกครั้งใน STUN connection check
- OpenAI สร้าง ufrag ฝั่งเซิร์ฟเวอร์ให้บรรจุ metadata สำหรับ routing มากพอที่ relay จะอนุมานได้ทั้ง cluster ปลายทางและ transceiver เจ้าของเซสชัน
- ระหว่าง signaling, transceiver จะจัดสรรสถานะของเซสชันและส่งกลับ shared relay VIP กับ UDP port ใน SDP answer
- VIP คือ virtual IP address ด้านหน้า relay fleet ซึ่งเมื่อจับคู่กับ port แล้ว จะทำให้ไคลเอนต์มีปลายทางที่เสถียรเพียงจุดเดียว เช่น
203.0.113.10:3478แม้อยู่หลัง relay หลาย instance - แพ็กเก็ตแรกบน media path จากไคลเอนต์มักเป็น STUN binding request และ ICE จะใช้แพ็กเก็ตนี้เพื่อตรวจสอบว่าสามารถส่งแพ็กเก็ตไปถึง address ที่ประกาศไว้ได้จริง
- relay จะ parse เฉพาะเท่าที่จำเป็นจากแพ็กเก็ต STUN แรกเพื่ออ่าน server ufrag ถอดรหัส hint สำหรับ routing และส่งแพ็กเก็ตต่อไปยัง transceiver เจ้าของเซสชัน
- transceiver แต่ละตัวรับข้อมูลผ่าน shared UDP socket ซึ่งเป็น endpoint ของระบบปฏิบัติการที่ bind กับ IP:port ภายใน ไม่ได้ใช้หนึ่ง socket ต่อหนึ่งเซสชัน
- เมื่อ relay สร้างเซสชันจาก source IP:port ของไคลเอนต์ไปยังปลายทาง transceiver แล้ว แพ็กเก็ต DTLS, RTP และ RTCP ถัดไปจะไหลอยู่ในเซสชันนั้นโดยไม่ต้องถอดรหัส ufrag ซ้ำ
- เซสชันของ relay มีสถานะน้อยที่สุด โดยเก็บเพียง in-memory session สำหรับ packet forwarding, monitoring counter และ timer สำหรับหมดอายุและล้างเซสชัน
- แม้ relay จะรีสตาร์ตและสูญเสียเซสชันไป แพ็กเก็ต STUN ถัดไปก็จะสร้างเซสชันขึ้นใหม่ได้จาก hint สำหรับ routing ใน ufrag
- เมื่อเส้นทางถูกตั้งขึ้นแล้ว Redis cache จะเก็บ mapping ของ
<client IP + Port, transceiver IP + Port>เพื่อให้กู้คืนได้เร็วขึ้นแม้ก่อน STUN แพ็กเก็ตถัดไปจะมาถึง
Global Relay และเส้นทาง ingress ที่อยู่ใกล้ผู้ใช้
- หลังจากลดพื้นผิว UDP สาธารณะให้เหลือ address และ port จำนวนเล็กน้อยที่เสถียรแล้ว OpenAI ก็สามารถกระจายรูปแบบ relay เดียวกันนี้ไปทั่วโลกได้
- Global Relay คือ fleet ของ relay ingress point ที่กระจายเชิงภูมิศาสตร์และทำงาน packet-forwarding แบบเดียวกัน
- การมี ingress กระจายครอบคลุมกว้างช่วยให้แพ็กเก็ตจากผู้ใช้เข้าสู่เครือข่ายของ OpenAI ผ่าน relay ที่อยู่ใกล้กว่าในเชิงภูมิศาสตร์และ topology ของเครือข่าย แทนที่จะต้องวิ่งข้ามอินเทอร์เน็ตสาธารณะไปยัง region ที่ไกลก่อน
- วิธีนี้ช่วยลด latency, jitter และ burst ของ packet loss ที่หลีกเลี่ยงได้ก่อนที่ทราฟฟิกจะเข้าสู่ backbone
- OpenAI ใช้ Cloudflare geo และ proximity steering ใน signaling เพื่อให้คำขอ HTTP หรือ WebSocket แรกไปถึง transceiver cluster ที่อยู่ใกล้ที่สุด
- context ของคำขอจะใช้กำหนดตำแหน่งของเซสชันและ Global Relay ingress point ที่จะประกาศให้ไคลเอนต์ใช้
- SDP answer จะให้ address ของ Global Relay และ ufrag จะบรรจุข้อมูลเพียงพอให้ Global Relay routing media ไปยัง cluster ที่กำหนด และให้ relay routing ต่อไปยัง transceiver ปลายทางได้
- เมื่อใช้ signaling ที่ทำ geo steering ร่วมกับ Global Relay ทั้งการตั้งค่าและ media จะอยู่บนเส้นทางเข้าที่ใกล้ผู้ใช้ ขณะเดียวกันเซสชันก็ยังถูกตรึงไว้กับ transceiver ตัวเดียว
- โครงสร้างนี้ลดเวลาไปกลับของ signaling และ ICE connection check แรก จึงลดเวลาที่ผู้ใช้ต้องรอก่อนเริ่มพูดได้โดยตรง
วิธี implementation ของ relay
- บริการ relay เขียนด้วย Go และตั้งใจจำกัดขอบเขตของ implementation ให้แคบ
- บน Linux, kernel networking stack จะรับ UDP packet จาก network interface แล้วส่งต่อไปยัง socket จากนั้น relay ซึ่งเป็น Go process ปกติใน userspace จะอ่าน header ของแพ็กเก็ตจาก socket
- relay อัปเดต flow state เพียงเล็กน้อยแล้วส่งต่อแพ็กเก็ต โดยไม่สิ้นสุด WebRTC
- OpenAI ไม่ได้ใช้ kernel-bypass framework ที่ให้ userspace process polling network queue โดยตรงเพื่อรองรับ packet rate สูงขึ้น และมองว่าวิธีนั้นจะเพิ่มความซับซ้อนด้านปฏิบัติการ
-
ตัวเลือกการออกแบบหลัก
- ไม่มีการสิ้นสุดโปรโตคอล: relay parse แค่ STUN header และ ufrag แล้วใช้ cached state สำหรับ DTLS, RTP และ RTCP ที่ตามมา เพื่อคงแพ็กเก็ตไว้ในรูป opaque
- สถานะชั่วคราว: เก็บ in-memory map ขนาดเล็กและมี timeout สั้น จาก address ของไคลเอนต์ไปยังปลายทาง transceiver สำหรับ flow state และ observability
- การขยายแนวนอน: relay หลาย instance ทำงานขนานกันหลัง load balancer และเพราะ relay state ไม่ใช่ hard WebRTC state ผลกระทบจากการรีสตาร์ตจึงต่ำและการกู้คืน flow ก็รวดเร็ว
-
มาตรการเพิ่มประสิทธิภาพ
SO_REUSEPORTคือ Linux socket option ที่ให้ relay worker หลายตัว bind กับ UDP port เดียวกันบนเครื่องเดียวได้ โดย kernel จะกระจายแพ็กเก็ตขาเข้าให้แต่ละ worker เพื่อลดคอขวดจาก read loop เดียวruntime.LockOSThreadใช้ตรึงแต่ละ goroutine ที่อ่าน UDP ไว้กับ OS thread เฉพาะ- เมื่อใช้
SO_REUSEPORTร่วมกับ thread pinning แพ็กเก็ตของ flow เดียวกันจะมีแนวโน้มอยู่บน CPU core เดิม ทำให้ cache locality ดีขึ้นและลด context switching - buffer ที่ pre-allocated และการคัดลอกข้อมูลให้น้อยที่สุด ช่วยลด overhead จากการ parse และ allocation และช่วยเลี่ยง garbage collection ของ Go
- implementation นี้สามารถรองรับ global real-time media traffic ของ OpenAI ได้ด้วย relay footprint ที่ค่อนข้างเล็ก จึงทำให้ OpenAI เลือกคงดีไซน์ที่เรียบง่ายกว่าแทนการไปใช้แนวทาง kernel bypass
ผลลัพธ์และบทเรียนที่ได้
- สถาปัตยกรรมนี้ทำให้สามารถรัน WebRTC media บน Kubernetes ได้โดยไม่ต้องเปิดพอร์ต UDP หลายพันพอร์ต
- พื้นผิว UDP ที่เล็กและคงที่ช่วยให้การรักษาความปลอดภัยและการทำ load balancing ง่ายขึ้น และทำให้ infrastructure ขยายได้โดยไม่ต้องจองช่วงพอร์ตสาธารณะขนาดใหญ่
- การออกแบบนี้ยังคงพฤติกรรม WebRTC มาตรฐานของไคลเอนต์ไว้ และยืนยันว่าในเวิร์กโหลดของ OpenAI การออกแบบแบบไม่มี SFU เป็นค่าพื้นฐานที่เหมาะสม
- เซสชันส่วนใหญ่เป็นแบบ point-to-point มีความไวต่อ latency และขยายได้ง่ายกว่าเมื่อ inference service ไม่ต้องทำตัวเป็น WebRTC peer
- ความซับซ้อนควรอยู่ในชั้น routing ที่บาง แทนที่จะกระจายไปยังบริการ backend ทั้งหมดหรือพฤติกรรมไคลเอนต์แบบกำหนดเอง
- ด้วยการเข้ารหัส metadata สำหรับ routing ลงใน field ที่เป็น native ของโปรโตคอล จึงได้ทั้งการ routing แพ็กเก็ตแรกอย่างแน่นอน รอยเท้า UDP สาธารณะที่เล็ก และความยืดหยุ่นในการวาง ingress ใกล้ผู้ใช้ทั่วโลก
-
ตัวเลือกที่สำคัญเป็นพิเศษ
- คง semantics ของโปรโตคอลไว้ที่ edge: ไคลเอนต์ยังคงใช้ WebRTC มาตรฐาน จึงรักษาความเข้ากันได้ระหว่างเบราว์เซอร์และมือถือไว้ได้
- เก็บสถานะเซสชันที่ยากไว้ที่เดียว: transceiver เป็นผู้ดูแล ICE, DTLS, SRTP และ session lifecycle ส่วน relay ทำหน้าที่ส่งต่อแพ็กเก็ตเท่านั้น
- routing ด้วยข้อมูลที่มีอยู่แล้วในการตั้งค่า: ICE ufrag ให้ hook สำหรับ first-packet routing โดยไม่ต้องพึ่ง hot-path lookup dependency
- ให้ความสำคัญกับการ optimize กรณีใช้งานหลักมากกว่า kernel bypass: implementation Go ที่แคบซึ่งใช้
SO_REUSEPORT, thread pinning และ low-allocation parsing อย่างรอบคอบ ก็เพียงพอสำหรับเวิร์กโหลดของ OpenAI - Voice AI แบบเรียลไทม์จะทำงานได้เมื่อโครงสร้างพื้นฐานทำให้ไม่รู้สึกถึง latency และ OpenAI เลือกเปลี่ยนวิธี deploy WebRTC โดยไม่เปลี่ยนพฤติกรรมที่ไคลเอนต์คาดหวังจาก WebRTC
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ขอบคุณมากที่ OpenAI เผยแพร่กรณีใช้งานของ Pion ไลบรารีที่ผมทำอยู่
ถ้ายังไม่ค่อยรู้จัก WebRTC นี่เป็นสายที่น่าสนใจมากทีเดียว และผมก็กำลังทำหนังสือ WebRTC for the Curious ที่อธิบายวิธีการทำงานของมันอยู่ด้วย
https://github.com/pion/webrtc
https://webrtcforthecurious.com
มันดูเหมือนเพิ่มความซับซ้อนขึ้นมหาศาลเพื่อจะลดส่วนที่ก็เร็วอยู่แล้วในสแตกเสียง AI ลงอีกนิดเดียว ดูเหมือนว่าโมเดลที่เร็วและ การตรวจจับกิจกรรมเสียง (VAD) ที่แม่นยำจะสำคัญกว่าการจูนเวลาในการส่งผ่านของ WebRTC แบบละเอียดมาก
ก่อนหน้านี้ผมเคยอ่านไปบางส่วนเพราะมีไอเดียจะส่งข้อมูลจากฐานข้อมูลไปยังไคลเอนต์บนเบราว์เซอร์ผ่าน CLI โดยใช้ WebRTC data channel แล้วก็ทำให้เข้าใจว่ามันคงไม่เหมาะกับงานของผม สุดท้ายเลยใช้ control plane แบบรวมศูนย์กับ WebSocket
ถึงอย่างนั้นก็ยังรู้สึกว่าน่าจะทำอะไรสนุก ๆ ได้กับการจับคู่ WebRTC data channel + Apache Arrow ArrayBuffer แบบไม่ต้องคัดลอกข้อมูล + duckdb WASM แต่ตอนนี้ยังนึกไม่ออกว่าจะเอาไปทำอะไร
srcซ้อนกันมันทำให้เข้าไปที่ README ยากขึ้นเยอะ
latency ต่ำเป็นความทรมานมากกว่าจะเป็นข้อดีของการออกแบบ
เวลาคุยสบาย ๆ คนเรามักหยุดคิดสั้น ๆ ตามธรรมชาติ แต่ GPT กลับตีความว่านั่นคือ “พูดจบแล้ว” แล้วก็เริ่มพูดรัวทันที
พออายุมากขึ้น การหาคำที่อยากใช้ก็ช้าลง และ voice GPT ที่เร็วแบบนี้ให้ความหงุดหงิดมากกว่าความช่วยเหลือ ต้องคิดทั้งประโยคให้จบในหัวก่อนพูด ซึ่งไม่เป็นธรรมชาติเลย
latency ที่บทความพูดถึงคือความหน่วงของการส่งสตรีมเสียงเอง ส่วน latency ในกรณีนี้ใกล้เคียงกับการเริ่มตอบกลับได้เร็วแค่ไหนภายในสตรีมเสียงมากกว่า
มันทำให้รู้สึกกดดันว่าต้องพูดต่อไปเรื่อย ๆ ทั้งที่ยังคิดไม่เสร็จ เลยรู้สึกไม่เป็นธรรมชาติพอสมควร ถ้ายังหาคำที่เหมาะอยู่ก็ต้องมีเวลาให้หาคำนั้น
ผมคิดว่าทางแก้ไม่ใช่โปรโตคอลที่หน่วงสูงขึ้น แต่เป็นการ จัดการช่วงหยุดให้ฉลาดขึ้น มากกว่า เพราะถ้า latency ต่ำ บอตก็สามารถหยุดพูดได้ทันทีเมื่อผู้ใช้พูดแทรก
ไม่ได้สมบูรณ์แบบ แต่ขัดจังหวะน้อยลง
อีกอย่างคือมันดูเหมือนใช้ความฉลาดส่วนใหญ่ไปกับการทำให้ฟังดูเหมือนคน มากกว่าจะเอาไปคิดปัญหาจริง ๆ เช่น “ครับ แน่นอน ผมเข้าใจว่าทำไมคุณถึงต้องการแบบนั้น...” น่าจะเพราะมีข้อจำกัดด้านเวลาและการประมวลผลเสียงก็แพงกว่า ส่วนการตอบแบบข้อความมีเวลาใช้กับตัวงานมากกว่า
คำว่า “ผู้ใช้ที่ใช้งานรายสัปดาห์มากกว่า 900 ล้านคน” ฟังดูน่าจะหมายถึงจำนวนผู้ใช้ ChatGPT ทั้งหมดอยู่แล้ว และสัดส่วนที่ใช้ ฟีเจอร์เสียง จริง ๆ ก็น่าจะเล็กกว่านั้นมาก
ตัวเลขแบบนี้มีผลต่อการตัดสินใจทางธุรกิจ เช่น จะทุ่มฮาร์ดแวร์และการปรับแต่งซอฟต์แวร์ให้กับปัญหานี้มากแค่ไหน
หมายถึงจำนวนผู้ใช้ทั้งหมดที่มีโอกาสเห็นฟีเจอร์นี้ โดยไม่เกี่ยวว่าจะได้ใช้งานจริงหรือไม่
ดีใจมากที่มีการแชร์เรื่องนี้ แต่ก็ควรจำไว้ว่า โมเดลเสียงแบบเรียลไทม์ ของ OpenAI ยังอยู่แค่ระดับความสามารถของสาย 4o
ถึงอย่างนั้นมันก็ยังมีประโยชน์มาก และก็น่าเสียดายที่แทบไม่มีคู่แข่งจริงจังในด้านนี้ ประสบการณ์ที่เหมือนคุยกันจริงช่วยให้ถ่ายทอดไอเดียและแนวคิดได้มาก
แค่ควรจำไว้ว่าตอนนี้มันไม่ใช่โมเดลระดับแนวหน้าสุดแล้วเหมือนตอนเปิดตัว ถ้า Sam มาเห็นก็หวังว่าจะปล่อยโมเดลเสียงเรียลไทม์ตัวใหม่ออกมา
Gemini flash live 3.1 ของ Google ดีกว่า โดยเฉพาะเวลาใช้ผ่าน API เพราะเรียกใช้เครื่องมือได้ และถ้าประกอบระบบเองก็ยังพ่วงเข้ากับ LLM ที่ฉลาดกว่าตัวอื่นได้ รวมทั้งตั้งระดับการใช้เหตุผลได้ด้วย ระดับการใช้เหตุผลสูงก็ยังใกล้เคียงเรียลไทม์พอสมควร และยังเสริมคำตอบด้วย Google Search ได้อีก ถ้าคุณชอบเสียงแบบโต้ตอบสองทาง ตอนนี้นี่น่าจะเป็นตัวเลือกที่ดีที่สุด และลองเล่นได้ใน AI Studio
ถ้าใครอยากเข้ามาในสายนี้ pipecat เป็นทั้งโอเพนซอร์สรีโพซิทอรีและคอมมูนิตี้ที่ดี
https://github.com/pipecat-ai/pipecat
เพิ่งรู้จักเมื่อไม่กี่สัปดาห์ก่อน และหลังจาก Gemma 4 ออก ผมก็กำลังสร้างผู้ช่วยเสียงที่รันในเครื่องทั้งหมดตั้งแต่ศูนย์ด้วย Gemma 4 + Kokoro TTS + Whisper: https://github.com/pncnmnp/strawberry
smart turn model ของ Pipecat นั้นดีมากสำหรับ การตรวจจับกิจกรรมเสียง (VAD): https://huggingface.co/pipecat-ai/smart-turn-v3
ถ้าโมเดลที่ดีกว่าต้องใช้เวลาคิดมากขึ้นก่อนตอบ ผมก็โอเคที่จะ รอคำตอบนานขึ้น
แต่ต้องรองรับการพูดแทรกได้ดี ไม่ใช่พอหยุดไป 1 วินาทีก็เริ่มตอบทันที และต้องตัดสินอย่างฉลาดว่าผมพูดจบหรือยัง
มันอาจไม่ใช่แค่เรื่อง latency อย่างเดียว
ถ้าทำให้ผู้ใช้อยู่กับ การสนทนาด้วยเสียง ได้นาน ก็จะได้ข้อมูลฝึกสอนที่ไม่มีทางได้จากข้อความเลย เลยสงสัยว่าหรือเพราะแบบนี้ถึงเลือกแนวทาง transceiver แทน SFU และแทบจะมองข้ามการคุยหลายฝ่ายไปเลยก็ได้
ตีความได้ไหมว่า OpenAI ไม่ได้ใช้ LiveKit สำหรับ WebRTC/เสียงอีกต่อไปแล้ว?
ในสถาปัตยกรรมแบบนี้ เซิร์ฟเวอร์ของ LiveKit คงไม่ใช่รูปแบบที่ต้องการนัก ซึ่งในการพูดถึง SFU ในบทความก็สื่อแบบนั้นอยู่เหมือนกัน แต่ในฝั่ง client SDK ก็ยังมีของที่มีประโยชน์อยู่เยอะ
ถ้า transceiver ล่มกลางสตรีม เซสชันที่กำลังใช้งานอยู่จะกู้กลับมายังไง?
ระบบจะสร้างคอนเท็กซ์ขึ้นมาใหม่อัตโนมัติบน WebRTC session ใหม่หรือเปล่า?
สามารถบันทึกหรือพักสถานะ WebRTC ทั้งหมดไว้ แล้วปลุกกลับขึ้นมาในโปรเซสถัดไปได้
ถ้าอยากมีเพื่อน ผมคิดว่าการเข้า ชมรมหรืองานพบปะ ไม่ว่าจะรูปแบบไหน น่าจะดีกว่า