LabHub
시작하기
배우기 러닝패스 코스

음성 AI 에이전트 — 듣고, 찾고, 말하는 파이프라인

지연 예산 — 말을 멈춘 순간부터 첫 소리까지

LabHub 에서 이어서 보기

한 줄 요약

사용자가 느끼는 지연은 말을 멈춘 순간부터 첫 소리가 나오기까지다. 이것은 끝점 판정(오디오 시간) + 확정 + 검색 + LLM 첫 조각 + TTS 첫 소리(벽시계 시간)의 합이다. 직렬로 이으면 LLM 전체와 TTS 전체가 더해지고, 겹쳐 돌리면 첫 조각의 시간만 더해진다. 단계마다 예산을 적고 분포(p50·p95) 로 대조해야 어디를 줄일지 보인다.

왜 이게 필요했나

앞 모듈들에서 부품마다 잰 숫자는 따로 보면 모두 작다 — 확정 수십 ms, 첫 토큰 백여 ms, TTS RTF 0.04. 그런데 이어 붙이면 초 단위가 된다. LabHub 회화 연습의 3.3초도 부품 셋(ASR 0.4 + 모델 0.6 + TTS 2.3)의 직렬 합이다(backend/app/lang_talk.py). 어느 부품이 느린가보다 어떻게 이었는가가 더 큰 차이를 만든다.

어떻게 동작하나

두 가지 시계. 사용자가 말하는 동안 스트리밍 ASR 은 이미 받아 적고 있다. 그러니 그 시간은 지연이 아니다. 지연은 말이 끝난 뒤부터다. 그런데 '말이 끝났다' 를 아는 순간(끝점)은 실제 말 끝보다 늦다 — VAD 가 끝을 본 뒤 gap 만큼 더 조용해야 확신한다(모듈 2). 이 몫은 오디오 시간으로 잰다: 끝점 − 실제 말 끝. 이 코스의 질의에서 gap 0.5초로 중앙값 약 0.57초였다. 끝점 이후의 일(확정·검색·LLM·TTS)은 벽시계로, 끝점 순간을 0 으로 잰다. 두 시계를 섞지 않고, 마지막에 더한다.

직렬과 겹침. 직렬은 LLM 이 답을 다 쓴 뒤 TTS 가 전체를 합성한다 — 첫 소리 = 확정 + 검색 + LLM 전체 + TTS 전체. 겹침은 LLM 이 쓰는 동안 조각이 생기는 대로 TTS 에 넘긴다 — 첫 소리 = 확정 + 검색 + (첫 조각이 생길 때까지) + (첫 조각 합성). 같은 질의 다섯 개에서 첫 소리 중앙값이 직렬 1,814 ms, 겹침 1,593 ms 였다(클러스터 nuc1 노드의 CPU 2개 파드에서 잰 값 — 노드마다 다르므로 실습에서 여러분이 잰 숫자를 보라). 이득이 기대보다 작은 까닭은 모듈 7 에서 본 대로 LLM 과 TTS 가 같은 2코어를 나눠 쓰기 때문이다.

추적으로 그린다. 숫자 표보다 막대 그림이 빨리 읽힌다. 크롬 추적 형식(Trace Event Format)은 {"name", "ph": "X", "ts", "dur", "tid"} 사건 목록이고 ts·dur 는 마이크로초다. chrome://tracing 이나 Perfetto 에 열면 LLM 막대와 TTS 막대가 겹친 구간이 보인다. 이 형식은 모듈 9 의 구간 기록(span)으로 이어진다.

평균이 아니라 분포. 대화의 불쾌감은 가끔 오는 긴 침묵에서 온다. 그래서 중앙값(p50)과 함께 p95 를 본다. 표본이 10개면 p95 는 가장 느린 것이다(가까운 순위). 표본이 적을 때 보간한 백분위는 존재하지 않는 값을 만들어 내므로, 이 코스는 가까운 순위를 쓴다.

예산. 사람 사이 대화의 차례 간격이 대체로 수백 ms 라는 관찰(모듈 2)에서 거꾸로 나눠, 단계마다 예산을 적는다 — 예: 끝점 700 · 확정 150 · 검색 100 · 첫 토큰 400 · 첫 소리 600 ms. 측정한 중앙값이 예산을 넘는 단계가 줄일 곳이다. 어떤 몫은 모델로 줄지 않는다(끝점). 어떤 몫은 프롬프트로 준다(첫 토큰 — 캐시와 짧은 문맥). 어떤 몫은 이음새로 준다(첫 소리 — 겹치기와 짧은 첫 조각).

메모리도 예산이다. 이 파드의 한도는 2 GiB 다. 파이썬 프로세스(스트리밍 ASR·TTS·임베딩)가 최대 약 370 MB, LLM 서버가 약 730 MB 를 썼다(nuc1 실측, 요청이 몰리면 LLM 서버는 830 MB 까지 올랐다) — 합쳐 절반을 조금 넘는다. 모델 하나를 더 올리거나 LLM 을 1.5B 로 키우는 순간 한도에 닿는다. 넘으면 커널이 프로세스를 죽이고(OOM), 사용자는 설명 없는 침묵을 듣는다.

현장에서 만나는 모습

LabHub 회화 연습은 한 턴을 '녹음 전체 → ASR → 모델 → TTS' 로 잇고, GPU 를 다투지 않게 자리를 한 번에 한 사람만 받는다(lang_talk.py). 그 구조에서 3.3초를 줄이려면 부품을 빠르게 하는 것보다 스트리밍 입력(끝점을 기계가 판정), 모델 스트림과 TTS 겹치기, 짧은 첫 문장이 먼저다. 이 모듈에서 그 효과를 작은 모델로 직접 잰다.

잴 때 조심할 것이 하나 있다. 이 파드는 CPU 2코어 한도인데 컨테이너 안의 nproc 은 노드의 코어 수(24)를 말한다. numpy 가 쓰는 BLAS 는 그 수만큼 스레드를 띄워 2코어 안에서 서로 밀어낸다 — 에코 지연을 찾는 짧은 np.dot 3,200번이 0.11초에서 52초가 됐다(이 이미지를 만들며 실측). 그래서 이미지가 OPENBLAS_NUM_THREADS=2 를 못박아 둔다. 지연을 재는 실험은 이런 환경 차이 하나로 몇백 배가 틀어진다.

다음 실습에서 할 것

질의 12개의 끝점 지연을 오디오 시간으로 잰다. 확정 → 검색 → LLM → TTS 를 직렬로 이어 재고, 같은 질의로 겹쳐 재서 비교한다. 겹친 실행 하나를 크롬 추적 형식으로 그리고, 질의 10개의 사용자 체감 지연 p50/p95 를 낸다. 단계별 예산과 측정을 대조하고, 두 프로세스의 메모리를 재 파드 한도와 비교한다.