ASR Whisper Real-time Translation Machine Learning Technical Architecture

Whisper 프로덕션 운영: 실시간 이중 언어 전환의 실패와 성공, 그리고 실동 아키텍처

Akira Noda - VoicePing 7 분 읽기
Whisper 프로덕션 운영: 실시간 이중 언어 전환의 실패와 성공, 그리고 실동 아키텍처

VoicePing이 커스터마이징한 Whisper V2 모델을 활용하여 단일 WebSocket 스트림 내에서 자동·저지연 언어 전환을 구현한 바이링구얼 모드의 설계 과정을 소개합니다.

VoicePing은 현재 일본어, 영어, 중국어(보통화), 광둥어, 한국어, 베트남어가 문장 중간에 뒤섞이는 프로덕션 회의에서 바이링구얼 자막을 운영하고 있습니다. 사용자들이 원하는 것은 코드 스위칭이 아무리 자주 일어나더라도 끊김 없는 음성 인식과 번역입니다. 이 글에서는 커스터마이징된 Whisper V2 모델을 탑재하고, 단일 WebSocket 스트림에서 자동·저지연 언어 전환을 수행하는 바이링구얼 모드(Bilingual Mode)의 설계 과정을 설명합니다.

목차

1. 단일 언어 베이스라인

우리는 기존의 실시간 ASR 스택에서 출발했습니다.

  1. 클라이언트가 16 kHz PCM 오디오를 스트리밍한다.
  2. VAD가 오디오를 슬라이딩 윈도우(설정 가능한 오버랩 포함, 약 X초)로 분할한다.
  3. Whisper V2 파생 모델이 고정된 language=ja로 디코딩한다.
  4. 후처리 과정에서 부분 가설의 중복 제거, 텍스트 보정을 수행하고 WebSocket을 통해 업데이트를 스트리밍한다.

이 슬라이딩 윈도우 + 연결 파이프라인은 “Streaming Whisper: Enhanced Streaming Speech Recognition”(https://arxiv.org/abs/2307.14743)에서 설명된 Streaming Whisper 접근 방식을 따른 것으로, 바이링구얼 기능을 쌓기 전의 안정적인 베이스라인이 되었습니다.

그림 1: 단일 언어 베이스라인(단일 고정 언어)

단일 언어 베이스라인 데이터 흐름

이 아키텍처는 세션 중에 언어가 바뀌지 않는다고 가정합니다. 버퍼, 구문 바이어싱 사전, 메시지 세션 모두 해당 단일 언어를 키로 사용합니다. 코드 스위칭이 발생하면 시스템이 무너졌습니다. 통화 중 고객이 일본어에서 영어로 전환했을 때 서버는 ja에 고정되어 있었기 때문에, Whisper는 깨진 텍스트나 빈 텍스트를 출력했습니다. 이를 수정하려면 누군가가 WebSocket 연결을 끊고, 세션 언어를 재설정한 다음 다시 연결해야 했습니다. 화자가 언어를 바꿀 때마다 매번 이 수동 루프와 강제 재연결 사이클이 전체 아키텍처의 병목이 되었습니다.

2. “바이링구얼 모드"가 필수인 이유

실제 회의에서 언어 전환은 가설적인 엣지 케이스가 아닙니다. 일본어 화자가 발표를 하다가 영어를 쓰는 동료가 끼어들고, 중국어를 사용하는 이해관계자가 후속 질문을 합니다. 이벤트에서는 사회자가 일본어로 진행하는 동안 참석자들이 영어로 질문을 이어갑니다. 기존 단일 언어 시스템은 대화 흐름을 산산조각 냈습니다. 전환이 일어날 때마다 누군가가 멈추고, 세션을 재설정한 후 다시 연결해야 했으며, 회의가 길어지고 조율 비용이 증가했습니다.

팀에서 반복적으로 요청한 것은 하나의 간단한 동작이었습니다. “우리가 설정한 두 언어 중 어느 것이 사용되고 있는지 자동으로 감지하고, 아무것도 건드리지 않아도 음성 인식(과 번역)이 계속되도록 해달라.” 이 단순한 요구가 바이링구얼 모드의 핵심 목표가 되었습니다.

“Whisper를 완전 다국어 모드로 그냥 두면 되지 않나?“라고 생각할 수 있지만, 프로덕션 사용자는 예측 가능한 출력을 기대합니다. Whisper를 제약 없이 실행하면 90개 이상의 언어 중 아무거나 출력해 버립니다. 특히 실시간의 짧고 잡음이 많은 버퍼에서 두드러지는데, 테스트 중에 JA/EN 회의에서 화자가 중얼거리거나 기침이 다른 언어의 음소와 비슷해서 갑자기 스페인어나 러시아어 토큰이 나타나는 것을 목격했습니다. 배치 모드에서는 더 긴 클립으로 재처리가 가능하지만, 라이브 자막에서는 치명적입니다. 사용자가 두 언어를 선택함으로써 하위 번역, UI, QA가 스트림을 신뢰할 수 있게 됩니다.

이를 실현하기 위해 요구 사항을 구체적 요건으로 정리했습니다.

  • 실시간성: 전환 오버헤드가 200ms 이내여야 UI가 멈추지 않는다.
  • 자동화: 회의 중 토글을 전환하는 사람의 개입이 필요 없다.
  • 제한된 언어 쌍: 스트림당 설정된 정확히 두 언어(현재 JA/EN/zh-Hans/zh-Hant/KO/VI 조합)만 허용하여 UX 예측 가능성을 확보한다.
  • 안정적인 세션: 잘못된 전환은 놓치는 것보다 해롭다. 모든 필러 단어마다 언어가 오가면 안 된다.

이러한 제약 조건으로 바이링구얼 모드는 “있으면 좋은” 기능에서 시스템의 필수 기능이 되었습니다.

데모: 바이링구얼 모드 실제 동작

3. 단순한 접근: 외부 언어 감지(그리고 실패한 이유)

첫 번째 프로토타입에서는 Whisper 앞에 언어 식별(LID) 모델을 삽입했습니다.

그림 2: 외부 LID를 사용한 단순 다국어 방식

단순 다국어 아키텍처

이론은 간단했습니다. 각 버퍼의 언어를 감지한 다음 해당 버퍼를 단일 언어 Whisper 인스턴스에 전달하는 것이었습니다. 하지만 현실은 달랐습니다.

핵심 문제는 해결할 수 없는 트레이드오프였습니다. 감지 빈도를 낮추면 전환을 놓치고, 높이면 약 100ms의 지연이 추가되며 GPU 사용량이 두 배로 증가했습니다. 정확도와 응답성을 동시에 확보할 수 없었습니다.

그 트레이드오프를 받아들인다 해도 다른 장애물이 쌓였습니다. 각 언어마다 별도의 Whisper 인스턴스가 필요했고, 사용 패턴은 균등하지 않았습니다. JA/EN이 압도적으로 많아 KO나 VI 모델은 GPU를 점유하면서 거의 유휴 상태였습니다. 이 아키텍처는 확장이 불가능했습니다.

필드 테스트에서 단일 언어 베이스라인보다 지연 시간과 정확도가 모두 나빠진 것이 확인되어, 이 접근 방식을 폐기했습니다.

4. 돌파구: Whisper가 직접 언어를 알려주게 하기

이전 설계에서 가장 약한 고리는 외부 언어 감지기였습니다. Whisper 내부 구조를 면밀히 조사한 결과, 모델이 이미 필요한 신호를 출력하고 있음을 발견했습니다. 단지 올바르게 활용하지 않았을 뿐입니다.

4.1 Whisper 네이티브 언어 감지

Whisper는 디코더 위치 1에서 언어 토큰(<|ja|>, <|en|> 등)을 출력합니다. 기본적으로 수십 개의 언어를 디코딩할 수 있지만, 프로덕션에서는 안정성을 위해 단일 언어로 고정해 왔습니다. 언어 토큰을 가로채고 해당 logits를 제약함으로써 정확도를 희생하지 않으면서 단일 Whisper 모델을 다국어 지원으로 만들 수 있습니다.

  • 언어 토큰 추출: 각 빔의 첫 번째 토큰을 가로채 디코딩하고, 이를 감지된 언어로 사용한다.
  • 허용 언어 제약: 위치 1에서 설정된 쌍(예: JA/EN)만 나타나도록 logits를 마스킹한다. 이를 통해 오디오에 노이즈가 있을 때 <|ru|> 같은 임의의 감지를 방지한다.
1
2
3
4
5
6
def apply_language_constraints(logits, allowed_lang_ids):
    if not allowed_lang_ids:
        return logits
    mask = torch.full_like(logits, float("-inf"))
    mask[allowed_lang_ids] = 0.0
    return logits + mask

이 제약을 적용하면 한 번의 Whisper 추론으로 음성 인식 결과와 언어 코드를 모두 얻을 수 있습니다. 추가 모델도, 라우팅도 필요 없이 오디오 윈도우당 단 한 번의 패스면 됩니다.

4.2 Whisper 네이티브 감지 기반 아키텍처

그림 3: Whisper 네이티브 언어 감지

Whisper 네이티브 언어 감지 아키텍처

이 레이아웃은 단일 언어 베이스라인과 거의 동일하게 보이지만, Whisper가 단일 세션에서 두 언어를 처리한다는 점이 다릅니다. 하나의 모델이 양쪽 언어를 모두 서비스하므로 JA/EN/KO 인스턴스를 별도로 띄우거나 라우팅을 관리할 필요가 없습니다. 단순한 설계와 비교하면, 언어 식별 단계를 완전히 제거하고, 언어별 모델 중복을 없앴으며, 지연 시간을 동일하게 유지했습니다.

5. 코드 스위치 경계 안정화

Whisper가 감지 언어 토큰을 출력하게 되었지만, 토큰마다 전환을 발행하면 트랜스크립트가 진동합니다. 실제 대화에서 언어 전환은 점진적으로 일어납니다. 필러, 머뭇거림, 혼합어가 나타난 후에야 화자가 완전히 언어를 바꿉니다. 가드레일 없이는 출력이 다음과 같았습니다.

1
2
3
4
5
6
7
Speaker switches from JA to EN mid-sentence

ja-speaking        ▶ こんにちは
transition period  ▶ I
                   ▶ いあ
                   ▶ I am
en-speaking        ▶ は

인식기가 몇 프레임마다 JA와 EN 사이를 오가며, 읽기 어렵고 하위 시스템에서도 사용할 수 없는 뒤섞인 텍스트를 생성했습니다. 먼저 히스테리시스를 추가하여 연속된 여러 프레임이 신뢰도 임계치 이상으로 동일한 언어 토큰을 출력해야만 언어 전환이 확정되도록 했습니다. 잡음이 많은 “I / いあ / I am” 같은 버스트는 이 규칙으로 사라집니다.

짧은 텍스트 해결이 가장 강력한 안정화 수단이었습니다. 6자 미만의 프래그먼트는 일본어 가나, 명확한 영문자, 한국어 한글, 베트남어 표지를 간단한 휴리스틱으로 확인합니다. 일본어와 중국어 모두 한자를 사용하므로 규칙은 의도적으로 느슨하게 설정했습니다. 신호가 모호할 경우 전환하지 않고 이전에 확정된 언어를 유지합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
그림 4: 짧은 텍스트 해결 파이프라인

Short fragment
  Rule-based checks
        ├─ clear kana/hangul/latin? ──▶ switch candidate immediately
        └─ ambiguous kanji mix ───────▶ keep previous language

이 간단한 조치로 “um"이나 “えー” 같은 필러가 세션을 반복적으로 전환하는 것을 방지합니다.

새로운 언어가 확정되면 기존 부분 트랜스크립트를 확정하고, 보정 작업을 실행하고, Whisper의 버퍼를 리셋하여 새로운 세션/메시지 ID로 시작합니다. 이 명확한 경계 덕분에 JA와 EN 텍스트가 하위 번역 및 분석에서 깔끔하게 분리됩니다. 마지막으로, VAD에서 도출된 무음 구간을 활용하여 자연스러운 일시 정지 중에 버퍼된 텍스트를 플러시함으로써 발화 중간에 단어를 분리하지 않도록 합니다.

이러한 레이어가 갖춰짐으로써, 코드 스위치가 뒤섞인 단편이 아닌 의도적인 핸드오프처럼 보이게 되며, 하위 번역과 검색의 안정성이 유지됩니다.

6. 유니버설 패딩으로 짧은 오디오 안정화

짧은 버퍼는 본래 불안정하여 Whisper가 환각을 일으키고 언어 토큰이 임의로 변동합니다. 우리의 해결책은 직관에 반하는 것이었습니다. 버퍼를 모델에 보내기 전에 언어 중립적인 패딩 클립(“Okay, bye bye.")을 앞에 붙이는 것입니다. 이 패딩 오디오는 디코더를 안정시킬 만큼 충분한 길이를 가지면서 언어적으로 중립적이며, 캡처된 오디오가 설정 가능한 컷오프(보통 수 초) 이하일 때만 삽입하므로 긴 발화에는 영향을 주지 않습니다. 또한 클립이 고정되어 있으므로 빔 서치가 빠르게 이를 무시하도록 학습하면서, 언어 토큰은 충분한 컨텍스트를 확보하여 안정됩니다.

1
2
3
4
5
그림 5: 짧은 오디오에 대한 유니버설 패딩

[ padding "Okay, bye bye." ] [ real user audio ]
<---------- ~2.5 s --------> <---------- <5 s ---------->
     language-neutral clip        actual speech to transcribe

짧은 클립에 이러한 패딩을 적용함으로써 혼합 언어를 포함한 짧은 오디오 세그먼트의 환각이 크게 줄어듭니다.

7. 운영 제약: GPU, MPS, 동시 처리

멀티 ASR은 규모 있게 운영되어야 의미가 있으므로, 소비자용 RTX 시리즈 GPU에 배포하고 CUDA Multi-Process Service를 활용하여 추론 파이프라인의 활용률을 높이고 있습니다.

일반적인 RTX 5090에서 3개의 Whisper V2 워커(각각 약 2 GB, 총 약 6-7 GB)가 카드의 약 30 GB 메모리를 공유하므로 VRAM은 병목이 되지 않습니다. 병목은 CUDA 코어입니다.

실제 운영에서 이 RTX 5090 구성은 약 20-30개의 활성 오디오 스트림을 엔드투엔드 지연 약 100ms로 처리합니다. 네 번째 워커를 추가해도 메모리에는 들어가지만 지연이 실시간 임계치를 크게 넘어서므로, CUDA 코어가 처리 용량의 상한을 결정합니다.

8. 결론

현재 시스템은 발화 중 코드 스위칭에서도 200ms 이하의 지연을 달성하고 있으며, 튜닝된 Whisper V2 모델을 자체 운영함으로써 예측 가능한 온프레미스 비용을 유지하고 있습니다.

현재 배포는 스트림당 두 언어에 최적화되어 있으며, 그 이상으로 확장하려면 UI와 정책 변경이 필요하므로 프로덕션이 아닌 로드맵에 포함되어 있습니다.

다음으로 추가적인 지연 감소를 위해 TensorRT와 VLLM 스타일 추론을 실험 중이며, 다국어 구문 바이어싱에 대한 심층 분석도 준비하고 있습니다. 바이링구얼 또는 코드 스위치가 잦은 환경에서 운영하고 계시다면 ASR 모델에서 직접 언어 토큰을 읽어보시기 바랍니다. 필요한 신호가 이미 존재할 수 있습니다.

참고 문헌

Streaming Whisper: Enhanced Streaming Speech Recognition https://arxiv.org/abs/2307.14743

바이링구얼 모드 구축 시 참고한 주요 프로젝트:

SimulStreaming https://github.com/ufal/SimulStreaming

Whisper Streaming https://github.com/ufal/whisper_streaming

WhisperLiveKit https://github.com/QuentinFuxa/WhisperLiveKit

처음에는 WhisperLiveKit + 스트리밍 Sortformer를 탐색하여 화자 분리가 언어 토큰 추론 빈도를 줄일 수 있기를 기대했습니다. 그러나 실제로는 Sortformer와 자체 언어 감지를 모두 실행해야 했고, 지연 개선은 미미했으며, Sortformer의 공격적인 동작은 회의 용도에 적합하지 않았습니다.

NVIDIA Nemotron Speech ASR(아직 시도하지 않은 최신 실시간 ASR) https://huggingface.co/blog/nvidia/nemotron-speech-asr-scaling-voice-agents

이 기사 공유

VoicePing 무료로 시작하기

AI 번역으로 언어 장벽을 넘어보세요. 지금 무료로 시작하세요.

무료로 시작