Barge-In, VAD, 그리고 지연 시간 예산: 실시간 음성 엔지니어링
요약
실시간 음성 에이전트 구현 시 발생하는 지연 시간(Latency) 문제와 이를 해결하기 위한 엔지니어링 전략을 다룹니다. VAD 설정과 Barge-in 처리 등 모델 성능 외적인 요소가 사용자 경험에 미치는 영향을 분석합니다.
핵심 포인트
- 지연 시간은 마이크 캡처부터 VAD 대기까지의 합계로 결정됨
- VAD 대기 시간과 정확도 사이의 트레이드오프 관리가 핵심
- ASR 부분 가설을 활용해 발화 종료 대기 시간을 동적으로 조절 가능
- Barge-in 문제는 단순 오디오가 아닌 상태 머신의 동시성 문제로 접근해야 함
실시간 음성 (Realtime voice)은 단순히 정답을 맞히는 것만으로는 충분하지 않은 유일한 모달리티 (modality)입니다. 반드시 '지금' 정답이어야 합니다. 텍스트 모델은 4초가 걸려도 아무도 눈 하나 깜빡하지 않습니다. 하지만 말을 시작하는 데 4초가 걸리는 음성 에이전트 (voice agent)는 고장 난 것처럼 들립니다. 저는 작년의 상당 부분을 양방향 오디오 (bidirectional audio)를 연결하는 데 보냈으며, 대부분의 어려운 문제들은 모델과는 아무런 관련이 없었습니다.
지연 시간 예산 (Latency budget)은 합계이며, 당신이 제어할 수 있는 부분은 대부분이 아닙니다
사용자가 느끼는 수치는 말을 멈춘 후 '처음 들리는 바이트까지의 시간 (time to first audible byte)'입니다. 이것은 하나의 숫자가 아니라 하나의 사슬입니다:
마이크 캡처 (mic capture) + 인코딩 (encode) 15-55 ms
네트워크 업링크 RTT/2 (network uplink RTT/2) 20-150 ms (모바일의 경우 더 나쁘고 가변적임)
VAD 발화 종료 대기 (VAD end-of-turn hold) 200-700 ms <-- 이 부분은 당신이 제어할 수 있습니다
...
"빠른" 파이프라인은 수백 밀리초에서 1초 이상 사이에 위치합니다. 대화 분석 문헌에 따르면 인간의 대화 턴 간격 (conversational turn gaps)은 약 200ms로 매우 짧은 것으로 유명합니다. 이것이 바로 뛰어난 음성 에이전트조차 약간 위성 전화처럼 느껴지는 이유입니다.
당신이 제어할 수 있는 두 가지 용어는 발화 종료 대기 (end-of-turn hold)와 지터 버퍼 (jitter buffer)이며, 두 가지 모두 지연 시간 (latency)과 정확도 (correctness)를 맞교환(trade-off)하는 관계에 있습니다. 대기 시간을 줄이면 사람들이 문장 중간에 말을 끊게 됩니다. 지터 버퍼를 줄이면 불안정한 Wi-Fi 환경에서 오디오 끊김 현상이 발생합니다. 최적화하기 전에 모든 세그먼트 (segment)를 계측하십시오.
- False negative (사용자가 여전히 말하고 있다고 판단하는 경우): 에이전트가 가만히 대기합니다. 지연(Lag)이 발생하지만 복구는 가능합니다.
- False positive (사용자의 생각이 채 끝나기도 전에 말을 끊는 경우): 에이전트가 일시적인 멈춤(pause) 상태에서 끼어듭니다(Barge-in). 사용자는 이를 훨씬 더 싫어하며, 잘린 발화(utterance)를 모델로 전송하기 때문에 전사(transcript) 데이터가 오염됩니다.
따라서 대기 타이머(hold timer)는 상수가 되어서는 안 됩니다. "그래서 제 생각에는, 음—" 뒤의 멈춤은 문법적으로 불완전하지만, "내일 날씨가 어때" 뒤의 멈춤은 완전한 요청입니다. 만약 스트리밍 ASR (Automatic Speech Recognition)이 부분 가설(partial hypotheses)을 출력한다면, 해당 텍스트를 약한 발화 종료 신호(end-of-turn signal)로 사용하고, 전사 내용이 완전해 보일 때 대기 시간을 단축하십시오:
def end_of_turn_hold(partial_text: str, pitch_falling: bool) -> int:
hold = 640
# 완전해 보이는 발화 -> 더 빨리 확정(commit).
...
대단한 기술은 아니지만, 더 나은 모델을 사용하는 것보다 더 가치 있는 작업입니다.
Barge-in은 오디오 문제가 아니라 상태 머신(state machine)의 문제입니다
에이전트가 중단될 수 있는 순간, 동시성(concurrency) 문제가 발생합니다. TTS (Text-to-Speech) 오디오가 아직 전송 중인 동안 사용자가 말을 시작하면, 세 개의 버퍼에 '오래된(stale)' 오디오가 남게 됩니다: 서버의 생성 큐(generation queue), 네트워크, 그리고 클라이언트의 재생 버퍼(playback buffer)입니다. 클라이언트에 이미 800ms 분량의 데이터가 큐에 쌓여 있다면, 서버 측에서 취소(cancelling)를 수행해도 아무런 효과가 없습니다. 에이전트는 중단된 후에도 거의 1초 동안 계속 말을 하게 되며, 이는 무례하게 느껴집니다.
취소 처리는 클라이언트 우선, 서버 차순이어야 합니다:
on user_speech_detected(confidence):
if state == SPEAKING and confidence > BARGE_IN_THRESHOLD:
playback.flush() # 로컬, ~0 ms, 이것을 '가장 먼저' 수행하십시오
...
마지막 줄은 모두가 잊어버리는 부분입니다. 생성된 전체 응답을 로그에 기록하더라도 사용자가 처음 8단어만 들었다면, 당신의 히스토리는 이제 거짓이 됩니다. 그러면 모델은 아무도 듣지 못한 내용을 자신이 "말했다"고 확신하며 참조하게 될 것입니다. 재생된 밀리초(played-milliseconds)를 추적하고, 그에 맞춰 데이터를 잘라내십시오(truncate).
Barge-in(끼어들기)은 일반적인 발화 감지(turn detection)보다 더 높은 신뢰도 임계값(confidence threshold)이 필요합니다. 왜냐하면 오작동(false triggers)의 가장 큰 원인이 바로 에이전트 자신의 출력물이기 때문입니다. 에코 캔슬레이션(echo cancellation)이 없다면, 에이전트는 스피커를 통해 자신의 목소리를 듣고 스스로의 말을 끊는 루프에 빠지게 됩니다. 웹 환경에서는 echoCancellation: true 설정이 포함된 getUserMedia가 일반적인 경우를 해결해 주지만, 단단한 표면이 있는 방에서 스피커폰을 사용할 경우 브라우저의 AEC(Acoustic Echo Cancellation)는 어려움을 겪으므로, 대비책으로 반이중 게이팅(half-duplex gating)을 사용해야 합니다.
전송(Transport): 실험실을 벗어난다면 WebRTC를 선호하십시오
WebSocket을 통해 PCM을 전송하는 것은 쉽고 패킷 손실이 발생하기 전까지는 잘 작동합니다. 하지만 TCP의 헤드 오브 라인 블로킹(head-of-line blocking) 현상으로 인해 하나의 세그먼트만 손실되어도 그 뒤에 대기 중인 모든 데이터가 중단되며, 정교하게 조정된 지터 버퍼(jitter buffer)라 할지라도 300ms의 스파이크(spike)를 감당하기 어렵습니다. WebRTC는 시그널링(signaling)의 복잡성을 대가로, 브라우저 스택으로부터 자유로운 실제 지터 버퍼링, 손실 은폐(loss concealment), 그리고 AEC를 갖춘 SRTP/UDP 경로를 제공합니다. 저의 경험적 규칙(heuristic)은 다음과 같습니다. 프로토타입 단계에서는 WebSocket + Opus 조합으로 충분하지만, 모바일 네트워크로 배포되는 모든 서비스는 WebRTC를 사용해야 합니다. 어떤 방식이든 48 kHz의 가공되지 않은 PCM이 아닌, 16 kHz 모노 Opus를 전송하십시오.
이러한 구성 요소들이 한 세션 내에서 다른 모달리티(modalities)와 함께 어떻게 작동하는지 참조 모델이 필요했을 때, GeminiOmni studio는 살펴보기 유용한 시스템이었습니다. 왜냐하면 이 시스템은 문제적인 상호작용을 빠르게 드러내기 때문입니다. 음성 발화(voice turn)가 발생한 순간 느린 비음성 도구 호출(non-voice tool call)이 트리거되면, 지연 시간 예산(latency budget)은 초과되며, 이때 채우기 말(filler-speech) 전략을 사용하지 않으면 사용자는 시스템이 충돌했다고 판단하게 됩니다.
과거의 저 자신에게 남기는 한 페이지: 첫날부터 세그먼트별 타임스탬프(per-segment timestamps)를 기록하십시오. 다른 무엇을 건드리기 전에 홀드(hold)를 적응형(adaptive)으로 만들고, 히스토리는 재생된 부분까지만 잘라내며(truncate), 전화기, 셀룰러 네트워크, 그리고 선풍기가 돌아가는 방 안에서 테스트하십시오. 이 시스템들은 유선 데스크톱과 헤드셋 환경에서는 모두 아름답게 작동하지만, 사용자들이 머무는 곳은 바로 그곳이 아닙니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기