본문으로 건너뛰기
김신건의 로그

[Voice] Whisper 내부: encoder-decoder, log-mel, tokenizer

· 수정 · 📖 약 1분 · 440자/단어 #whisper #stt #asr #transformer #encoder-decoder
Whisper architecture, Whisper encoder, Whisper decoder, log-mel spectrogram, Whisper tokenizer, Whisper timestamps, faster-whisper CTranslate2

정의

Whisper = OpenAI 의 encoder-decoder Transformer STT. 680K 시간 다국어 supervised training. 2022 공개, 2023 v2, 2023 v3.

개요는 stt-models-overview 참고. 본 페이지는 내부 아키텍처 + 코드.

아키텍처

flowchart TB
    Audio["30s audio (16kHz mono)"] --> LMS["Log-Mel Spectrogram<br/>(80 channels × 3000 frames)"]
    LMS --> Enc["Transformer Encoder<br/>(N layers, self-attention)"]
    Enc --> KV["Encoder hidden states"]
    KV --> Dec["Transformer Decoder<br/>(cross-attention to encoder)"]
    Tokens["Special + text tokens"] --> Dec
    Dec --> Out["다음 token 예측"]

모델 사이즈

모델LayersWidthHeadsParams
tiny4384639M
base6512874M
small1276812244M
medium24102416769M
large-v3321280201550M

Log-Mel Spectrogram

import numpy as np
import librosa

# 16kHz, 30초 = 480000 samples
audio, sr = librosa.load('sample.wav', sr=16000)
audio = audio[:16000 * 30]   # 30초 padding/trim

# Log-mel spectrogram
mel = librosa.feature.melspectrogram(
    y=audio,
    sr=16000,
    n_fft=400,       # window 25ms
    hop_length=160,  # stride 10ms
    n_mels=80,       # 80 mel channels
)
log_mel = np.log10(np.clip(mel, 1e-10, None))
# shape: (80, 3000)
파라미터
Window25ms (400 samples at 16kHz)
Hop10ms (160 samples)
Mel channels80
Input size80 × 3000

Encoder (self-attention)

mel (80, 3000) → conv1d → conv1d → transformer layers
  • 양방향 self-attention (모든 시간대 봄).
  • Output: (1500, d_model) (2x downsample).

Decoder (autoregressive)

<|startoftranscript|> <|ko|> <|transcribe|> <|notimestamps|> "안" "녕" "하세요" <|endoftext|>

각 토큰 예측:

  • cross-attention: encoder hidden states 참조.
  • causal self-attention: 옛 토큰만 봄.

Special Tokens

Token의미
<|startoftranscript|>시작
<|en|>, <|ko|>, …언어 (99 개)
<|transcribe|>전사 mode
<|translate|>번역 mode (→ 영어)
<|notimestamps|>timestamp 생략
<|0.00|> ~ <|30.00|>timestamp (0.02s 간격)
<|endoftext|>

Timestamp 생성

# 예 (with timestamps):
<|startoftranscript|> <|ko|> <|transcribe|>
<|0.00|> 안녕하세요 <|1.50|>
<|1.50|> 오늘 <|2.00|>
<|2.00|> 날씨가 좋네요 <|3.50|>
<|endoftext|>

모델이 timestamp token 을 다른 vocab 처럼 예측. 정확도는 word-level ~200ms 오차.

30초 chunking

def transcribe_long_audio(audio, model):
    chunk_size = 30 * 16000   # 30초
    stride = 25 * 16000        # 25초 (5초 overlap)

    results = []
    for start in range(0, len(audio), stride):
        chunk = audio[start:start + chunk_size]
        chunk = pad_or_trim(chunk, chunk_size)

        mel = log_mel_spectrogram(chunk)
        result = model.decode(mel, options)
        results.append(result)

    return merge_with_overlap(results)

Whisper 는 30초 단위 만 처리. 긴 오디오는 chunking + overlap.

Hallucination 문제

무음 구간 → 모델이 *없는 문장 생성*

해결:

  1. VAD 전처리 (Silero) - 무음 제거
  2. Temperature 0 - 결정론적
  3. compression_ratio_threshold - 반복 감지
  4. logprob_threshold - 확신 낮으면 skip
result = model.transcribe(
    audio,
    language='ko',
    temperature=0,
    compression_ratio_threshold=2.4,
    logprob_threshold=-1.0,
    no_speech_threshold=0.6,   # VAD 임계
    condition_on_previous_text=False,   # hallucination 방지
)

Faster-Whisper (CTranslate2)

from faster_whisper import WhisperModel

model = WhisperModel(
    "large-v3",
    device="cuda",
    compute_type="float16",   # 또는 int8_float16
)

segments, info = model.transcribe(
    "audio.wav",
    language="ko",
    beam_size=5,
    vad_filter=True,   # 내장 Silero VAD!
    vad_parameters={"min_silence_duration_ms": 500},
)

for seg in segments:
    print(f"[{seg.start:.2f}{seg.end:.2f}] {seg.text}")
최적화배속
CTranslate2~4x
INT8 quantization+2x
Beam search skip (greedy)+1.5x
VAD filter노이즈 절감

Distil-Whisper

Teacher (large-v3) → Distillation → Student
- Decoder layers 축소 (32 → 2)
- ~6x 빠름, 49% 작음
- 영어 정확도 유지, 다국어 약함

Whisper 는 batch 모델 → streaming 은 별도 wrapper:

flowchart LR
    Mic --> Buffer["Buffer (수 초)"]
    Buffer --> VAD
    VAD -->|"음성 감지"| Chunk["30초 chunk"]
    Chunk --> Whisper
    Whisper --> Merge["overlap merge"]
    Merge --> Text
  • partial transcript 를 30초 chunk 마다 갱신.
  • Overlap 으로 문장 경계 매끄럽게.
  • 완전 real-time 은 아님 (500ms-1.5s 지연).

Vocab (Multilingual Tokenizer)

# GPT-2 style BPE (Byte-Pair Encoding)
tokenizer.encode("안녕하세요")
# → [23283, 9573, 7539, ...] (subword tokens)

len(tokenizer.vocab)   # 51864 tokens

Multilingual model = 다국어 subword 학습. 한국어도 자연스러운 tokenization.

흔한 함정

WARNING

  1. 긴 오디오 그대로 넣음 = 30초 이상 무시. Chunking 필수.
  2. VAD 없이 = 무음 구간 hallucination.
  3. condition_on_previous_text=True (기본) = 옛 오류가 다음 chunk 로 전파.
  4. CTranslate2 없이 batch = 4배 느림. Faster-whisper 권장.

관련 위키

이 글의 용어 (4개)
[Voice AI] LLM Serving: vLLM, PagedAttention, continuous batchingai
정의 LLM Serving = 자체 호스팅 LLM 을 낮은 TTFT + 높은 throughput 으로 제공. 음성 에이전트의 LLM 단계 지연 결정. | 도구 | 특징 | |--…
[Voice AI] STT 모델: Whisper, Deepgram, AssemblyAI, CLOVAvoice
정의 STT (Speech-to-Text) / ASR (Automatic Speech Recognition) = 음성 → 텍스트 변환. 2026 시점 전사 정확도 인간 수준, 실…
[Voice AI] STT 스트리밍: partial vs final, endpointing, gRPC/WSvoice
정의 스트리밍 STT = 오디오 청크 가 들어올 때마다 부분 결과 (partial) 를 즉시 반환 + 발화 종료 시 최종 결과 (final) 확정. [!IMPORTANT] 대화형…
[Voice AI] VAD: Silero, 에너지 기반, endpointing 임계값voice
정의 VAD (Voice Activity Detection) = 오디오 스트림에서 음성 vs 비음성 (침묵, 노이즈) 구분. 음성 에이전트의 발화 시작 / 종료 감지에 필수. 종…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기