feat: real-time observability for GPU transcription pipeline - #39
Merged
ojfbot merged 4 commits intoApr 12, 2026
Merged
Conversation
Replaces blind 60-second PID-alive polling with structured progress
tracking, pre-flight validation, and stall detection. Motivated by a
production incident where a model download hung silently for 60+ minutes
across 2 episodes before manual SSH investigation caught it.
Remote side (transcribe_episodes.py):
- ProgressReporter class writes progress.json atomically at each of 8
stage transitions (model_loading → transcription → intro_outro_tagging
→ topic_tagging → diarization → speaker_embedding → chapter_generation
→ write_outputs) with GPU metrics and segment heartbeats every 50
segments
- --progress-file flag for explicit path, auto-defaults to
{episode_dir}/progress.json for single-episode runs
- --strict-diarize flag fails hard if diarization can't load instead of
silently falling back to disabled
Local side (aws_runner.py):
- _poll_with_progress() replaces _poll_until_done(): 15s interval (was
60s), reads progress.json via SSH, logs current stage + GPU metrics
- Per-stage stall thresholds (model_loading: 5min warn/10min kill,
transcription: 10/20min, diarization: 10/25min)
- preflight() method runs before any transcription: verifies model cache
(>3GB, no .incomplete files), CUDA/cuDNN tensor allocation, pyannote
import (when --diarize), GPU memory (>4GB free), swap (>8GB)
- --strict-diarize passthrough and --skip-preflight flag
- Falls back to legacy PID polling if progress.json is unavailable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ojfbot
force-pushed
the
feat/transcription-observability
branch
from
April 11, 2026 02:03
9d08407 to
7f3375c
Compare
…ngine Tests use __new__() to bypass __init__ when testing _load_diarization patches. The new strict_diarize attribute must be set explicitly since __init__ is never called. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No tests are async, but pytest-asyncio with asyncio_mode="auto" wraps every test function, causing 9+ minute hangs on tests that use monkeypatch + module reloading. Remove the dep and config entirely. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
process_episode now calls _poll_with_progress, but two tests still mocked _poll_until_done. The unmocked real method ran actual time.sleep(15) + SSH calls, causing 7+ minute hangs in CI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Apr 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ProgressReportertotranscribe_episodes.py— writes structuredprogress.jsonat each of 8 pipeline stages with GPU metrics and segment heartbeatspreflight()toaws_runner.py— validates model cache, CUDA/cuDNN, pyannote, GPU memory, and swap before transcription starts_poll_with_progress()that reads progress.json, logs stage/GPU info, and detects stalls with per-stage thresholds--strict-diarizeflag to fail hard on diarization load failures (no silent fallback)--skip-preflightflag for known-good instancesStacked on #38 (torch pin fix) — merge that first.
Motivation
During a transcription run on 2026-04-10, the whisper model download hung silently for 60+ minutes across 2 episodes. The old polling loop (
kill -0 PIDevery 60s) had zero visibility into what was happening. A cuDNN mismatch also silently disabled diarization. Both issues were only discovered by manual SSH investigation.What this changes
Remote side (
transcribe_episodes.py)ProgressReporterclassprogress.jsonatomically (tmp + rename) at each stage transitionmodel_loading→transcription→intro_outro_tagging→topic_tagging→diarization→speaker_embedding→chapter_generation→write_outputsnvidia-smiutilization/VRAM via subprocess, cached 5s--progress-file{episode_dir}/progress.jsonfor single-episode runs--strict-diarizeRuntimeErrorif pyannote/cuDNN fails instead of silent fallbackLocal side (
aws_runner.py)preflight()_poll_with_progress()Stage: X (detail) | GPU: Y% ZMB | N/8 stagesTest plan
--strict-diarizecatches diarization failures--skip-preflightbypasses checks🤖 Generated with Claude Code