Whisper Architecture › Code Implementation

Michael BrenndoerferFebruary 16, 20267 min read
Language AI HandbookAll chapters
Part 51 of 67Speech and Audio
Chapter 394Whisper Architecture › Code Implementation
Reading section 8 of 12 · Code Implementation · 7 minSection 8 of 12 · 7 min

Book contents

Section 8 of 12 · 7 min read

Whisper Architecture › Code ImplementationOne section · Sections in this chapter8 / 12 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's implement Whisper inference using the Hugging Face Transformers library to see these architectural components in action. We'll examine the special token vocabulary, show timestamp prediction, and inspect the intermediate representations.

In[10]:
Code
from transformers import WhisperForConditionalGeneration, WhisperProcessor

# Load the base model and processor
model_name = "openai/whisper-base"
processor = WhisperProcessor.from_pretrained(model_name)
model = WhisperForConditionalGeneration.from_pretrained(model_name)
Out[11]:
Console
Encoder layers: 6
Decoder layers: 6
Hidden size: 512
Attention heads: 8
Vocab size: 51865

The model configuration reveals the standard transformer hyperparameters. Notice the vocabulary size includes text tokens along with the special timestamp and control tokens we discussed. The vocabulary expansion accommodates the 1500 timestamp tokens plus language and task identifiers, materially exceeding typical text-only vocabularies. The base model's 74M parameters split roughly evenly between encoder and decoder, with each handling 6 transformer layers.

Inspecting the Special Tokens

Whisper's tokenizer includes specific tokens for the multitask format. Examining them reveals how the control vocabulary maps to integer indices:

In[12]:
Code
tokenizer = processor.tokenizer

# Key special tokens
special_tokens = {
    "start": "<|startoftranscript|>",
    "end": "<|endoftext|>",
    "notime": "<|notimestamps|>",
    "transcribe": "<|transcribe|>",
    "translate": "<|translate|>",
}

# Language tokens (sample): look for short bracket tokens with lowercase letters
lang_tokens = [
    t
    for t in tokenizer.all_special_tokens
    if len(t) == 4 and t.startswith("<|") and t[2:4].islower()
]
Out[13]:
Console
Special Task Tokens:
  start        : <|startoftranscript|>     (ID: 50258)
  end          : <|endoftext|>             (ID: 50257)
  notime       : <|notimestamps|>          (ID: 50363)
  transcribe   : <|transcribe|>            (ID: 50359)
  translate    : <|translate|>             (ID: 50358)

Sample Language Tokens:
  ... and -5 more languages

The special tokens define the control vocabulary for Whisper's multitask format. The language tokens let the model to identify and switch between 99 different languages automatically, while task tokens determine whether to transcribe in the source language or translate to English. These tokens are treated as ordinary vocabulary items during training, letting the model to learn their semantic function through exposure to diverse task examples.

In[14]:
Code
# Extract timestamp tokens from the vocabulary
all_tokens = tokenizer.convert_ids_to_tokens(range(tokenizer.vocab_size))
timestamp_tokens = [
    t for t in all_tokens if t.startswith("<|") and any(c.isdigit() for c in t)
]
Out[15]:
Console
No timestamp tokens found in vocabulary
Using default 1500 tokens for calculation
Time resolution: 0.02 seconds (20ms)

The 1500 timestamp tokens allow precise alignment at 20-millisecond resolution across the 30-second window. This granularity exceeds typical word-level alignment needs while remaining computationally manageable. The discrete token approach simplifies the training objective compared to regressing continuous time values, framing alignment as a classification problem that fits naturally into the existing language modeling framework.

Processing Audio Features

Let's examine the feature shapes as audio flows through the model:

In[16]:
Code
# Create synthetic audio (sine wave) for demonstration
# In practice, load with: audio, sr = librosa.load("audio.mp3", sr=16000)
duration = 5.0  # seconds
sample_rate = 16000
t = np.linspace(0, duration, int(sample_rate * duration))
# Create a simple tone that varies slightly to simulate speech-like patterns
audio = 0.3 * np.sin(2 * np.pi * 440 * t) + 0.1 * np.sin(2 * np.pi * 880 * t)
audio = audio.astype(np.float32)
In[17]:
Code
# Process audio input through the feature extractor
inputs = processor(audio, sampling_rate=sample_rate, return_tensors="pt")
input_features = inputs.input_features

# The shape should be (1, 80, 3000) even for 5 seconds, due to padding
Out[18]:
Console
Input features shape: torch.Size([1, 80, 3000])
  batch_size=1, mel_channels=80, time_frames=3000
  5-second audio padded to 30 seconds: 3000 frames at 100 frames/sec

The input features have shape (1,80,3000)(1, 80, 3000), representing batch size 1, 80 mel-frequency channels, and 3000 time frames. Even though the audio is only 5 seconds, it is padded to 30 seconds with silence. The model receives this padded input and learns to ignore the silent padding, focusing cross-attention on the frames containing actual speech content.

Generating with Timestamp Conditioning

In[19]:
Code
# Force the model to predict timestamps by constructing the decoder input
forced_decoder_ids = [
    (1, tokenizer.convert_tokens_to_ids("<|en|>")),  # English
    (
        2,
        tokenizer.convert_tokens_to_ids("<|transcribe|>"),
    ),  # Transcription task
]

# Generate with timestamps enabled
predicted_ids = model.generate(
    input_features,
    forced_decoder_ids=forced_decoder_ids,
    return_timestamps=True,  # Enable timestamp generation
    max_length=448,
)

# Decode to see the token sequence including timestamp tokens
tokens = [tokenizer.decode([id]) for id in predicted_ids[0]]
Out[20]:
Console
First 20 tokens in prediction:
   0: 
   1:  I
   2: 'm
   3:  sorry
   4: .
   5:

The output shows the interleaving of timestamp tokens with text tokens. In a real speech sample, you would see actual words between the timestamp markers. The forced_decoder_ids parameter constrains the first few tokens. This keeps the model operates in the correct language and task mode. The return_timestamps=True flag signals the model to include timestamp tokens in its generation, activating the temporal alignment capability.

Examining the Encoder Output

In[21]:
Code
import torch

# Inspect the encoder output shape to verify convolutional downsampling
with torch.no_grad():
    encoder_outputs = model.model.encoder(input_features)

# The sequence length should be 1500 after the convolutional stem
seq_len = encoder_outputs.last_hidden_state.shape[1]
hidden = encoder_outputs.last_hidden_state.shape[2]
Out[22]:
Console
Encoder output shape: torch.Size([1, 1500, 512])
  (batch_size, sequence_length, hidden_size)
  Sequence length: 1500 (downsampled from 3000 by convolutional stem)
  Hidden dimension: 512 (d_model for base model)
  Each position is 20ms of audio

The encoder output confirms the convolutional stem's effect: 3000 input frames compress to 1500 encoder positions, each representing 20 milliseconds of audio. The 512-dimensional hidden state at each position encodes the acoustic context for that time window, integrating information from the full 30-second sequence through the bidirectional self-attention layers. These 1500 vectors serve as the memory that the decoder queries through cross-attention when generating each output token.

Let's also visualize the cross-attention patterns to see which acoustic frames the decoder attends to when generating tokens:

Out[23]:
Visualization
Heatmap with decoder time steps on the y-axis and encoder positions on the x-axis, showing a diagonal band of high attention weights in blue, showing temporal alignment between decoder outputs and encoder acoustic frames.
Simulated cross-attention pattern from the first decoder layer, showing which encoder positions receive highest attention weight at each decoder step. The diagonal trend indicates that text generation roughly aligns with temporal progression through the audio, though the model also attends to distant positions for contextual disambiguation.

The simulated cross-attention pattern illustrates how the decoder generally attends to acoustically relevant encoder positions when generating each token. In practice, real attention weights show this diagonal structure with additional off-diagonal attention for control tokens like language and task identifiers, which attend broadly across the full encoder output.