Whisper Was Slow and Garbage on Intel Mac — Here's How We Fixed It
Debugging whisper.cpp on Intel Mac: a 4-minute audio file took 5+ minutes to process and output was gibberish. Two independent bugs, one parameter that made it 25× faster, and why we bet on offline batch processing for subtitle export.
We set out to add a subtitle feature to Yfli Screen Recorder. The plan: feed audio or video files into Whisper, get timestamped SRT subtitles back. Offline, private, free.
Then reality hit: a 4-minute audio file took over 5 minutes to process, and the output was complete garbage — a string of commas instead of actual words. The 11-second JFK sample included with whisper.cpp? Same result.
What followed was a debugging session that nearly changed our entire technical direction. Here’s the full story — two independent bugs, a parameter that made everything 25× faster, and why “slow” was never the real problem.
Step 0: We Tested Every ASR Option on macOS
Before diving into Whisper debugging, we benchmarked every viable approach on a 2018 Intel Mac (i5-8500):
| Approach | Real-World Result | Verdict |
|---|---|---|
Apple SFSpeech streaming (SFSpeechRecognizer) | Realtime output, free. But segmentation is VAD-driven (by silence boundaries, not semantic boundaries). Chinese output has no punctuation. Recognition sessions are capped at ~1 minute. | Fine for live drafts. Not suitable for deliverable subtitles. |
Apple SFSpeech offline batch (SFSpeechURLRecognitionRequest) | Offline mode quality noticeably weaker. | Eliminated |
| Silero VAD (ONNX Runtime) | Loads and infers correctly (silence prob ~0.003, speech ~0.3). But segmentation is purely acoustic — no semantic boundaries. | Keep as auxiliary tool |
| whisper.cpp + medium model | 4-min audio took 5+ min. SRT never succeeded once. Even the 11-second JFK sample produced “poor results.” | Symptoms were bizarre — see below |
That last row is the subject of this article. My initial diagnosis: “The medium model is 1.4 GB — too heavy for this old Intel machine. Either use a smaller model or accept the wait.”
That intuition was wrong in a way that’s worth documenting. The slowness and the garbage output were not the model’s fault.
Step 1: Shrink Reproduction from 4 Minutes to 11 Seconds
Debugging with a 5-minute iteration cycle is impossible. First move: compress the problem to seconds.
Used whisper.cpp’s bundled 11-second JFK sample (samples/jfk.wav) with the smallest tiny model (74 MB), running the exact same parameters as in-app:
$ whisper-cli -m ggml-tiny.bin -f jfk.wav -osrt -of out \
--print-progress --no-timestamps
whisper_print_progress_callback: progress = 272%
whisper_print_timings: sample time = 756.11 ms / 1525 runs
whisper_print_timings: total time = 22983.07 ms
The generated SRT:
1
00:00:00,000 --> 00:00:30,000
,-,,,,,,-
11 seconds of audio. Smallest model. 23 seconds to process. Output: a string of commas. Entire recording collapsed into one 0→30s subtitle. Progress hit 272%. Decoder ran 1,525 tokens — a ~20-word sentence should take a few dozen.
Two symptoms: garbage output and abnormally slow processing.
Bug #1: `--no-timestamps` and `-osrt` Are Contradictory
We had passed --no-timestamps ourselves, thinking it meant “don’t print per-sentence timestamps.” But its actual semantics: disable Whisper’s timestamp computation entirely.
-osrt exports SRT files. SRT files are built entirely on timestamps. With timestamps disabled, the SRT exporter receives invalid segment times, collapsing the entire recording into one fake subtitle.
If your whisper-cli generates an SRT with a single 0→30s entry: check whether you’re passing --no-timestamps.
Removing it:
1
00:00:01,000 --> 00:00:09,560
was
Timestamps restored. But a famous quote produced a single word: “was.” Processing time: still 15 seconds. Decoder: still 1,249 runs. One real bug fixed, but the main symptom untouched — the real problem runs deeper.
Bug #2: Metal on Intel iGPU Silently Produces Wrong Results
Re-reading the log line by line, I noticed one unremarkable line near the end:
ggml_metal_free: deallocating
Metal. whisper.cpp was compiled with Metal acceleration enabled — and it was actually using it. This should be good news. Except this machine is an Intel i5-8500 with integrated UHD 630 graphics, not Apple Silicon.
ggml’s Metal backend was designed around Apple Silicon’s unified memory architecture. On Intel integrated graphics, it doesn’t just fail to accelerate — it produces mathematically wrong results — and it does so silently, without a single error message.
Verification required only one parameter. -ng (no GPU, pure CPU):
$ whisper-cli -m ggml-tiny.bin -f jfk.wav -osrt -of out -ng
whisper_print_timings: total time = 598.00 ms
1
00:00:00,000 --> 00:00:10,500
And so, my fellow Americans, ask not what your country
can do for you, ask what you can do for your country.
0.6 seconds. Word-perfect. Same binary. Same model. Same audio. One parameter disabled the “acceleration” — and went from “23 seconds of garbage” to “0.6 seconds, flawless.” 25× faster, and wrong → correct.
If you’re running whisper.cpp on an Intel Mac and getting garbled output, repeated characters, or extreme slowness: add -ng to disable GPU. It will likely fix everything at once.
Why "Wrong" Manifested as "Slow"
This is the most instructive part of the debugging session — two symptoms, one root cause:
Metal on Intel iGPU produces incorrect results — silently, with no errors
│
▼
Encoder outputs audio features ≈ noise
│
▼
Decoder faces noise, starts hallucinating — can't produce coherent sentences
│
▼
Whisper's resilience mechanisms all fire:
temperature fallback (retry with higher temperature)
per-token resampling
│
▼
Token count explodes (1525 vs normal 136) → progress hits 272%
│
▼
Manifests as: SLOW + WRONG
“Slow” was never the cause. “Slow” was the shadow of “wrong.” The decoder wasn’t computing slowly — it was thrashing against garbage input. In retrospect, every “performance” observation made sense: “4-minute song, 5+ minutes, no result” was Metal garbage + medium model hallucination loops. “Medium can’t even handle the JFK sample” — the model had been innocent the entire time.
Real benchmarks after fixing both bugs (11-second sample, pure CPU, 6 threads, i5-8500):
| Model | Size | Time | Relative to Realtime |
|---|---|---|---|
| tiny | 74 MB | 0.6 s | 18× realtime |
| medium-q5_0 (locally quantized) | 514 MB | 15.9 s | ~0.7× realtime |
| medium | 1.4 GB | 17 s | ~0.65× realtime |
Tiny transcribed the JFK sample flawlessly in English, word for word. This 2018 Intel Mac — which we had written off as too old — runs Whisper just fine on pure CPU.
Five Debugging Lessons
- Shrink reproduction to seconds first. A 4-minute feedback loop is undebuggable. The 11-second sample + tiny model turned each hypothesis test from “wait 5 minutes” to “wait 1 second.”
- Change one variable at a time. First remove
--no-timestamps(fixed SRT collapse, main symptom untouched), then disable Metal (main symptom vanished). Done separately, each bug’s contribution is unambiguous. - Anomalous numbers in logs are signposts.
progress = 272%and 1,525 decoder runs were screaming “the decoder is struggling.”ggml_metal_freeexposed the “acceleration” nobody remembered was on. - Default-on acceleration must be validated against hardware reality. “Metal compiled ✅” means acceleration on Apple Silicon and silently wrong data on Intel iGPU. The worst bugs don’t error.
- The
-lflag defaults to English when omitted. whisper-cli does NOT auto-detect language without-l auto. Miss this and every non-English recording gets transcribed as English gibberish.
Why We Chose Offline Whisper Batch Processing
With the bugs fixed and real benchmarks in hand, the decision was straightforward:
- The use case is batch, the model is batch. No sliding-window pseudo-streaming, no realtime diarization — the two heaviest engineering burdens are eliminated.
- Highest quality ceiling. Full context visibility, punctuation included, sub-second-precision segment timestamps. SRT output is directly deliverable.
- Free + offline + private. Zero API cost. Works without internet. Audio never leaves the device. For a recording product, these are homepage-worthy selling points.
- Old hardware handles it. This was the conclusion we couldn’t reach before the fix. Pre-fix data: “11 seconds of audio takes 23 seconds.” Post-fix: “0.6 seconds.” Had we not fixed this bug, we would almost certainly have misjudged on-device processing as infeasible on existing Intel hardware, been forced toward cloud APIs, and inherited per-minute billing costs plus the privacy burden of uploading user recordings. One bug nearly changed our entire technical direction — and that’s a bigger deal than losing a few days.
The final implementation is a standalone module: audio/video file in (wav/mp3/m4a/mp4/mov), SRT file out. That’s it.
VRSubtitleGenerator *gen = [[VRSubtitleGenerator alloc]
initWithConfig:[VRSubtitleGeneratorConfig defaultConfig]];
[gen generateSubtitlesForFileAtURL:mediaURL
toSRTURL:srtURL
progress:^(NSInteger pct) { /* 0~100 */ }
completion:^(BOOL ok, NSError *err) { /* … */ }];
Design decisions — all informed by the debugging session:
-ngis hardcoded internally. On Intel machines this is non-negotiable. Apple Silicon users will get architecture-specific GPU acceleration later.- Language always set to
-l auto. If the default value is a trap, hardcode the correct default and never look back. - User-selectable models. The module scans the model directory and dynamically lists available models.
medium-q5_0was quantized locally withwhisper-quantize— no additional download needed. - Progress must be visible. Polling parses Whisper’s progress output. One more small bug: when the task finishes faster than the polling interval (0.5s), the progress callback never fires, and the UI stays stuck at 0%. Fix: guaranteed 100% callback on completion. “Progress bar stuck at 0%” and “progress bar at 272%” — both ends of the spectrum, both artifacts from this project.
Quick FAQ: Common Whisper Offline Transcription Issues
Q: whisper.cpp outputs garbled text, repeated characters, or strings of commas.
If you’re on an Intel Mac with integrated graphics, add -ng to disable GPU. ggml’s Metal backend silently produces wrong results on Intel iGPUs, simultaneously causing garbage output and massive slowdown.
Q: Whisper generates an SRT with only one 0→30s subtitle entry.
Check for --no-timestamps. It disables timestamp computation entirely, directly conflicting with -osrt export.
Q: whisper-cli transcribes non-English audio as English.
The -l flag defaults to en (English) when omitted — not auto-detect. Explicitly pass -l auto.
Q: Can an Intel Mac (no dedicated GPU) run Whisper? How fast?
Absolutely. Tested on i5-8500 (6 cores, 2018): tiny model ~18× realtime; medium ~0.65× realtime (1 hour of audio takes ~1.5 hours). Pure CPU, add -t <core count> for full threading.
Q: The model is too large. How to balance speed and quality?
Use whisper.cpp’s built-in whisper-quantize to quantize medium to q5_0 (1.4 GB → 514 MB). Done locally, no network needed, quality near-original. For English-only use, consider small.en / medium.en English-specific models.
On the surface, this article is about a technical choice — why we converged from “realtime subtitles” to “offline generation.” But what I really wanted to capture is that debugging moment: something everyone assumed was a performance problem turned out to be a correctness problem. And before it was fixed, every judgment we made about the technical roadmap was built on contaminated data.
So beyond the FAQ above, here’s one more debugging heuristic: when a system is “slow AND inaccurate,” suspect “inaccurate” first, then optimize “slow.” Slow is often just wrong’s shadow.
Read Part 1 of This Series
Realtime Speech-to-Text on macOS: Cloud, On-Device, and Hybrid — Three Architectures Deep Dive
The architecture analysis that preceded this debugging session — streaming ASR, on-device Whisper, and hybrid approaches compared.
Ready to Try Yfli Screen Recorder?
Free download on the Mac App Store. No account required, no ads, no watermark.
John Li
macOS engineer with 15 years of experience in C/C++ native development. Builds high-performance media tools from the metal up.