Skip to content

Import heavy ML libraries on demand to speed up startup - #1669

Merged
raivisdejus merged 6 commits into
chidiwilliams:mainfrom
doqhu:perf/lazy-imports
Oct 24, 2026
Merged

raivisdejus merged 6 commits into
chidiwilliams:mainfrom
doqhu:perf/lazy-imports

Conversation

@doqhu

@doqhu doqhu commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Problem

Opening the main window imported most of the ML stack — torch, transformers, whisper, faster_whisper, stable_whisper, openai, demucs, numba, pandas, ctranslate2, torchaudio — although none of them is needed until a transcription, recording, speaker identification or the Preferences dialog actually starts. On Linux this took about 4.2 s and 754 MB before the window appeared.

Fix

Import these where they are used instead of at module level:

  • file_transcriber_queue_worker: the transcriber classes are imported in _create_transcriber, and demucs inside the speech-extraction worker (which already imported torch locally).
  • main_window_toolbar: RecordingTranscriberWidget is imported when Record is clicked.
  • menu_bar: PreferencesDialog is imported when Preferences opens (the type hint stays via TYPE_CHECKING).
  • model_loader, translator, transcription_resizer_widget, speaker_identification_widget: whisper, openai, stable_whisper, torch and faster_whisper are imported inside the functions that use them.
  • main_window._maybe_show_cuda_prompt: CUDA_PROMPT_SHOWN is now checked before is_cuda_torch_installed(). Previously, on machines with an NVIDIA GPU, every launch imported torch only to then skip a prompt that had already been shown. The outcome of the check is unchanged.

Tests that patched a moved module-level name now patch the library attribute instead (e.g. buzz.translator.OpenAI → openai.OpenAI), which works because the function-level import looks the attribute up at call time.

Results

Time until the main window is shown, offscreen, median of 5 runs, Fedora 44:

Before After
Imports 4.1 s 0.36 s
Window shown (repeat launch) 4.2 s 0.45 s
Peak RSS (repeat launch) 754 MB 142 MB
First launch on an NVIDIA machine 4.2 s / 754 MB 1.25 s / 307 MB

Heavy modules loaded at startup go from 13 to 2 (yt_dlp, numpy).

Testing

  • Full test suite without -x: 1364 passed, 15 skipped. The one failure, test_should_run_file_transcription_task, is the existing flake on NVIDIA machines fixed by Fix CUDA prompt firing on a deleted main window #1668; with that fix applied as well, the whole suite passes (1388 passed).
  • End-to-end CLI transcription (buzz add --hide-gui) of testdata/whisper-french.mp3 with Faster Whisper tiny and Whisper tiny both produce the expected text.
  • No new ruff warnings in the changed files.

Independent of #1667 and #1668; it merges cleanly with both.

🤖 Generated with Claude Code

https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW

doqhu and others added 6 commits October 20, 2026 20:50
Opening the main window imported torch, transformers, whisper,
faster_whisper, stable_whisper, openai, demucs and numba, although none
of them is needed until a transcription, recording, speaker
identification or preferences dialog starts. Startup took about 4.2 s
and 754 MB before the window appeared.

Import them where they are used instead: the transcriber classes when
a task starts, the recording and preferences widgets when opened, and
the libraries themselves inside the functions that need them. Also
check the CUDA_PROMPT_SHOWN setting before is_cuda_torch_installed(),
so machines with an NVIDIA GPU no longer import torch on every launch
just to skip a prompt that was already shown.

The window now appears after about 0.45 s, using 142 MB (repeat
launch, offscreen, Linux). Tests that patched the moved names now
patch the library attribute instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012ozoVtp2JvfpDkZ1SpBgvW
Moving the openai import into Translator.__init__ kept it out of app
startup, but every transcription viewer creates a Translator, so opening
a transcript still paid about 0.5 s for it. Build the OpenAI client the
first time a translation is sent instead, on the translator's worker
thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnjChXaA6f83jUFbYb9k4e
yt_dlp is only needed for URL imports, so import it in _download_from_url
instead of at app startup.

Opening the live recording window imported torch, transformers, whisper,
faster_whisper and openai on the UI thread, freezing it for about 4 seconds.
Import each backend's libraries where they are used instead; that code runs
on the transcription thread and only loads what the selected backend needs.
whisper.cpp and the OpenAI API no longer import torch at all.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Closing the live recording window only hid it, so it stayed alive as a
child of the main window until the app exited, where PyQt's exit cleanup
crashed with a segmentation fault. Every opened window also leaked.

Delete the window when it is closed. As it can now go away while the
translation thread is still running, defer the close until both the
transcription and translation threads have finished, not only the
transcription one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The copy button's text was reset by QTimer.singleShot with a lambda, which
still fires after the window is closed. Now that the window is deleted on
close, that raised "wrapped C/C++ object of type QPushButton has been
deleted". Pass a bound method instead, which PyQt drops when the window is
deleted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	buzz/transcriber/file_transcriber.py
#	tests/transcriber/file_transcriber_test.py
@raivisdejus

Copy link
Copy Markdown
Collaborator

@doqhu Thanks for your contribution!

@codecov

codecov Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.77108% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.25%. Comparing base (ee01182) to head (dce5fce).

Files with missing lines Patch % Lines
buzz/widgets/recording_transcriber_widget.py 89.65% 3 Missing ⚠️
buzz/transcriber/recording_transcriber.py 91.66% 2 Missing ⚠️
buzz/widgets/main_window.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1669      +/-   ##
==========================================
+ Coverage   84.22%   84.25%   +0.03%     
==========================================
  Files         115      115              
  Lines       13931    13965      +34     
==========================================
+ Hits        11733    11766      +33     
- Misses       2198     2199       +1     
Flag Coverage Δ
Linux 83.46% <90.36%> (-0.01%) ⬇️
macOS 83.78% <92.77%> (+0.07%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@raivisdejus
raivisdejus enabled auto-merge (squash) October 24, 2026 16:14
@raivisdejus
raivisdejus merged commit efd6e2d into chidiwilliams:main Oct 24, 2026
15 of 16 checks passed
@doqhu
doqhu deleted the perf/lazy-imports branch November 3, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants