Thomas Piacentini
← Workshop

Cheat sheet

Speech Recognition and Whisper

Original text · English

Decode CTC, run and tune Whisper, gate with VAD, and ship a WAV to captions pipeline.

Tasks in code

TaskCode
Greedy CTC decodectc_greedy(frame_probs)
Beam CTC decodectc_beam(frame_probs, beam_width=8)
Word error ratewer(ref, hyp)
Plan chunks for a 10 min clipchunk_schedule(600.0, chunk_s=30.0, stride_s=5.0)
Turn-end state machineTurnDetector(silence_hangover_ms=500, min_speech_ms=250)
LoRA on attentionLoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"])
Sources

Whisper inside

1500cross-attnprefixAudio16 kHz, pad or clip to 30sclientLog-mel80 mels x 3000 framesprocessConv stemkernel 3, stride 2processEncoder1500 timestepsmodelTask tokenslanguage, task,timestampsstoreDecodercausal self-attn +cross-attnmodelBPE tokens51,865-token vocabclient
Nodes and connections
NodeRoleKind
Audio16 kHz, pad or clip to 30 sclient
Log-mel80 mels x 3000 framesprocess
Conv stemkernel 3, stride 2process
Encoder1500 timestepsmodel
Task tokenslanguage, task, timestampsstore
Decodercausal self-attn + cross-attnmodel
BPE tokens51,865-token vocabclient
FromToConnection
AudioLog-mel
Log-melConv stem
Conv stemEncoder1500
EncoderDecodercross-attn
Task tokensDecoderprefix
DecoderBPE tokens
Sources

Transcribe with Whisper

import whisper
model = whisper.load_model("large-v3-turbo")
result = model.transcribe("clip.wav",
language="en",stops LID drift
task="transcribe",or <|translate|> to English
temperature=0.0,no sampling fallback
condition_on_previous_text=False)prevents runaway repetition
print(result["text"])
for seg in result["segments"]:start, end, text per segment
print(seg["start"], seg["end"], seg["text"])
Sources

Faster runtimes and VAD

from faster_whisper import WhisperModel
model = WhisperModel("large-v3-turbo", compute_type="int8_float16")4x faster
segments, info = model.transcribe("meeting.wav", vad_filter=True)VAD gate
from silero_vad import load_silero_vad, get_speech_timestamps
vad = load_silero_vad()
segments = get_speech_timestamps(audio, vad, sampling_rate=16000, threshold=0.5,
min_speech_duration_ms=250, min_silence_duration_ms=500, speech_pad_ms=300)
Sources

ASR formulations

CTC
per-frame tokens plus a blank. No internal LM. Fast.
Greedy CTC
collapse repeats, drop blanks. a a _ _ a b b _ c gives a a b c.
RNN-T
predictor plus joiner. Streamable, with an internal LM.
Attention enc-dec
best offline quality. Cannot stream without engineering.
WER
(S + D + I) / N. Above 20% is unusable. Below 5% is human parity.
Turbo
4-layer decoder (from 32). 8× faster, <1% WER hit.
Prompt
<|startoftranscript|> <|en|> <|transcribe|> or <|translate|>, then <|notimestamps|>.
Sources

Defaults

SettingDefaultNote
temperature0.0Override the fallback chain
condition_on_previous_textFalseStops the hallucination cascade
no_speech_threshold0.6Silence detection
Silero threshold0.50.3 is sensitive
min_speech_duration_ms250Rejects coughs and chair noise
Silence hangover500-800 msShorter interrupts, longer feels sluggish
Pre-roll buffer300-500 msKeeps the first word
Sources

Run the project

python3 scripts/project_test.py voice-note-transcriber-pipeline --init my-voice-pipeline
python3 scripts/project_test.py voice-note-transcriber-pipeline --all --solution --strict
cd projects/voice-note-transcriber-pipeline/solution
python3 pipeline.py --input fixtures/repair-note.wav --transcript-file fixtures/reference.json --out voice-output
ffmpeg -i YOUR_NOTE.m4a -ac 1 -ar 16000 -c:a pcm_s16le YOUR_NOTE.wavmono 16-bit PCM
python3 pipeline.py --input YOUR_NOTE.wav --endpoint <url> --model YOUR_MODEL --out voice-output
Sources

Project pipeline

  1. decode_wav mono, 16-bit, uncompressed only. Samples divide by 32768.
  2. activity only with --segment. RMS per frame_ms=20 frame, threshold=0.02, merge gaps of gap_ms=40, drop spans under min_ms=40.
  3. transcribe encode_wav each span. Retry a TimeoutError up to retries=1. Each row keeps audio_sha256 and attempts.
  4. http_recognizer multipart model and file fields. Response limit 1 MB. Timeout 30 s.
  5. captions WEBVTT header. Cue times stamp() as hh:mm:ss.mmm. Overlaps fail.
  6. export_transcript writes transcript.json, captions.vtt, audio.wav and index.html.
Sources

Project flags

CommandWhat it does
--input <wav>Mono 16-bit PCM WAV, 20 MB at most
--out <dir>Output folder, default voice-output
--transcript-file <json>Offline reference text bound to the audio SHA256
--endpoint <url>Uploads the WAV to an audio/transcriptions endpoint
--segmentRMS activity split before recognition
--review-file <json>Rerenders an edited transcript.json
Sources

Troubleshooting

SymptomCauseFix
"Thanks for watching!" on silenceNo VADVAD-gate before you call Whisper
Noisy clip comes out in JapaneseAuto LID driftForce language="en"
One error repeats in later windowsPrevious text cascadecondition_on_previous_text=False
Text in the padding of a 2 s clipPad to 30 spad=False or VAD-gate
Near-random outputlibrosa mels, wrong statswhisper.audio.log_mel_spectrogram
Agent cuts the user offHangover too short500-800 ms hangover, semantic endpoint
"16-bit mono PCM required"Stereo or compressed inputConvert with ffmpeg -ac 1 -ar 16000
"Remote endpoints require HTTPS"HTTP to a remote hostUse HTTPS. HTTP is for loopback only
Sources

Questions

When to freeze the encoder?
With less than 10 hours of data. Tune only the decoder.
How much data to fine-tune?
10 to 100 hours of target-domain audio with transcripts.
Why CTC beam over greedy?
Beam search allows LM fusion. CTC has no internal LM.
What is the flush trick?
On end of speech, flush the STT. A 500 ms buffer ends in ~125 ms.
Energy VAD in production?
No. It fires on any noise above -40 dBFS.
Sources
Original source
---
id: asr-whisper
title: Speech Recognition and Whisper
code: SR
subtitle: Decode CTC, run and tune Whisper, gate with VAD, and ship a WAV to captions pipeline.
hue: 265
sources: phases/06-speech-and-audio/04-speech-recognition-asr, phases/06-speech-and-audio/05-whisper-architecture-finetuning, phases/07-transformers-deep-dive/10-audio-transformers-whisper, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking, projects/voice-note-transcriber-pipeline
source_hash: 778a930fb3a9e490
---

## Tasks in code
type: task-table
from: phases/06-speech-and-audio/04-speech-recognition-asr/code/main.py, phases/06-speech-and-audio/04-speech-recognition-asr/docs/en.md, phases/06-speech-and-audio/05-whisper-architecture-finetuning/code/main.py, phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md, phases/07-transformers-deep-dive/10-audio-transformers-whisper/code/main.py, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/code/main.py
span: 2

| Task | Code |
|---|---|
| Greedy CTC decode | `ctc_greedy(frame_probs)` |
| Beam CTC decode | `ctc_beam(frame_probs, beam_width=8)` |
| Word error rate | `wer(ref, hyp)` |
| Plan chunks for a 10 min clip | `chunk_schedule(600.0, chunk_s=30.0, stride_s=5.0)` |
| Turn-end state machine | `TurnDetector(silence_hangover_ms=500, min_speech_ms=250)` |
| LoRA on attention | `LoraConfig(r=16, lora_alpha=32, target_modules=["q_proj", "v_proj"])` |

## Whisper inside
type: architecture
from: phases/07-transformers-deep-dive/10-audio-transformers-whisper/docs/en.md, phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md

```arch
node | audio | 1,1 | Audio | 16 kHz, pad or clip to 30 s | client
node | mel | 2,1 | Log-mel | 80 mels x 3000 frames | process
node | stem | 3,1 | Conv stem | kernel 3, stride 2 | process
node | enc | 4,1 | Encoder | 1500 timesteps | model
node | prompt | 3,2 | Task tokens | language, task, timestamps | store
node | dec | 4,2 | Decoder | causal self-attn + cross-attn | model
node | text | 2,2 | BPE tokens | 51,865-token vocab | client
edge | audio | mel |
edge | mel | stem |
edge | stem | enc | 1500
edge | enc | dec | cross-attn
edge | prompt | dec | prefix
edge | dec | text |
```

## Transcribe with Whisper
type: annotated-code
lang: python
from: phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md

```python
import whisper
model = whisper.load_model("large-v3-turbo")
result = model.transcribe("clip.wav",
    language="en",                     # stops LID drift
    task="transcribe",                 # or <|translate|> to English
    temperature=0.0,                   # no sampling fallback
    condition_on_previous_text=False)  # prevents runaway repetition
print(result["text"])
for seg in result["segments"]:         # start, end, text per segment
    print(seg["start"], seg["end"], seg["text"])
```

## Faster runtimes and VAD
type: annotated-code
lang: python
from: phases/07-transformers-deep-dive/10-audio-transformers-whisper/docs/en.md, phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md
span: 2

```python
from faster_whisper import WhisperModel
model = WhisperModel("large-v3-turbo", compute_type="int8_float16")  # 4x faster
segments, info = model.transcribe("meeting.wav", vad_filter=True)    # VAD gate
from silero_vad import load_silero_vad, get_speech_timestamps
vad = load_silero_vad()
segments = get_speech_timestamps(audio, vad, sampling_rate=16000, threshold=0.5,
    min_speech_duration_ms=250, min_silence_duration_ms=500, speech_pad_ms=300)
```

## ASR formulations
type: concepts
from: phases/06-speech-and-audio/04-speech-recognition-asr/docs/en.md, phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md, phases/07-transformers-deep-dive/10-audio-transformers-whisper/docs/en.md

- **CTC**: per-frame tokens plus a blank. No internal LM. Fast.
- **Greedy CTC**: collapse repeats, drop blanks. `a a _ _ a b b _ c` gives `a a b c`.
- **RNN-T**: predictor plus joiner. Streamable, with an internal LM.
- **Attention enc-dec**: best offline quality. Cannot stream without engineering.
- **WER**: $(S + D + I) / N$. Above 20% is unusable. Below 5% is human parity.
- **Turbo**: 4-layer decoder (from 32). 8× faster, <1% WER hit.
- **Prompt**: `<|startoftranscript|>` `<|en|>` `<|transcribe|>` or `<|translate|>`, then `<|notimestamps|>`.

## Defaults
type: defaults
from: phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md, projects/voice-note-transcriber-pipeline/solution/pipeline.py

| Setting | Default | Note |
|---|---|---|
| `temperature` | 0.0 | Override the fallback chain |
| `condition_on_previous_text` | False | Stops the hallucination cascade |
| `no_speech_threshold` | 0.6 | Silence detection |
| Silero `threshold` | 0.5 | 0.3 is sensitive |
| `min_speech_duration_ms` | 250 | Rejects coughs and chair noise |
| Silence hangover | 500-800 ms | Shorter interrupts, longer feels sluggish |
| Pre-roll buffer | 300-500 ms | Keeps the first word |

## Run the project
type: annotated-code
lang: bash
from: projects/voice-note-transcriber-pipeline/README.md
span: 2

```bash
python3 scripts/project_test.py voice-note-transcriber-pipeline --init my-voice-pipeline
python3 scripts/project_test.py voice-note-transcriber-pipeline --all --solution --strict
cd projects/voice-note-transcriber-pipeline/solution
python3 pipeline.py --input fixtures/repair-note.wav --transcript-file fixtures/reference.json --out voice-output
ffmpeg -i YOUR_NOTE.m4a -ac 1 -ar 16000 -c:a pcm_s16le YOUR_NOTE.wav  # mono 16-bit PCM
python3 pipeline.py --input YOUR_NOTE.wav --endpoint <url> --model YOUR_MODEL --out voice-output
```

## Project pipeline
type: flow
from: projects/voice-note-transcriber-pipeline/solution/pcm.py, projects/voice-note-transcriber-pipeline/solution/activity.py, projects/voice-note-transcriber-pipeline/solution/transcribe.py, projects/voice-note-transcriber-pipeline/solution/provider.py, projects/voice-note-transcriber-pipeline/solution/captions.py, projects/voice-note-transcriber-pipeline/solution/pipeline.py

1. **decode_wav**: mono, 16-bit, uncompressed only. Samples divide by 32768.
2. **activity**: only with `--segment`. RMS per `frame_ms=20` frame, `threshold=0.02`, merge gaps of `gap_ms=40`, drop spans under `min_ms=40`.
3. **transcribe**: `encode_wav` each span. Retry a `TimeoutError` up to `retries=1`. Each row keeps `audio_sha256` and `attempts`.
4. **http_recognizer**: multipart `model` and `file` fields. Response limit 1 MB. Timeout 30 s.
5. **captions**: `WEBVTT` header. Cue times `stamp()` as hh:mm:ss.mmm. Overlaps fail.
6. **export_transcript**: writes transcript.json, captions.vtt, audio.wav and index.html.

## Project flags
type: command-table
from: projects/voice-note-transcriber-pipeline/solution/pipeline.py, projects/voice-note-transcriber-pipeline/README.md

| Command | What it does |
|---|---|
| `--input <wav>` | Mono 16-bit PCM WAV, 20 MB at most |
| `--out <dir>` | Output folder, default `voice-output` |
| `--transcript-file <json>` | Offline reference text bound to the audio SHA256 |
| `--endpoint <url>` | Uploads the WAV to an audio/transcriptions endpoint |
| `--segment` | RMS activity split before recognition |
| `--review-file <json>` | Rerenders an edited transcript.json |

## Troubleshooting
type: troubleshooting
from: phases/06-speech-and-audio/04-speech-recognition-asr/docs/en.md, phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md, projects/voice-note-transcriber-pipeline/solution/pcm.py, projects/voice-note-transcriber-pipeline/solution/provider.py, projects/voice-note-transcriber-pipeline/README.md

| Symptom | Cause | Fix |
|---|---|---|
| "Thanks for watching!" on silence | No VAD | VAD-gate before you call Whisper |
| Noisy clip comes out in Japanese | Auto LID drift | Force `language="en"` |
| One error repeats in later windows | Previous text cascade | `condition_on_previous_text=False` |
| Text in the padding of a 2 s clip | Pad to 30 s | `pad=False` or VAD-gate |
| Near-random output | librosa mels, wrong stats | `whisper.audio.log_mel_spectrogram` |
| Agent cuts the user off | Hangover too short | 500-800 ms hangover, semantic endpoint |
| "16-bit mono PCM required" | Stereo or compressed input | Convert with `ffmpeg -ac 1 -ar 16000` |
| "Remote endpoints require HTTPS" | HTTP to a remote host | Use HTTPS. HTTP is for loopback only |

## Questions
type: qa
from: phases/06-speech-and-audio/04-speech-recognition-asr/docs/en.md, phases/06-speech-and-audio/05-whisper-architecture-finetuning/docs/en.md, phases/06-speech-and-audio/14-voice-activity-detection-turn-taking/docs/en.md

- **When to freeze the encoder?** With less than 10 hours of data. Tune only the decoder.
- **How much data to fine-tune?** 10 to 100 hours of target-domain audio with transcripts.
- **Why CTC beam over greedy?** Beam search allows LM fusion. CTC has no internal LM.
- **What is the flush trick?** On end of speech, flush the STT. A 500 ms buffer ends in ~125 ms.
- **Energy VAD in production?** No. It fires on any noise above -40 dBFS.
Code and artifacts 1
What you take away

Saved in your notebook on this device.

Sources and limits

Reading checkpoints are self-reported. Test results are recorded separately and apply to the executed cases.

AI Engineering from Scratch · 3f9715c · MIT

Download this file ↓