Skip to content

[feat] Native Whisper: greedy decode default hangs on long-form audio - expose beam_size / condition_on_previous_text / temperature #3703

Description

@BBC-Esq

Summary

docling's native (openai-whisper) ASR backend forwards almost none of whisper's decoding parameters to transcribe(). In particular beam_size, condition_on_previous_text, and temperature are not fields on InlineAsrNativeWhisperOptions and silently fall back to whisper's defaults. On long-form audio those defaults trigger whisper's well-known runaway-repetition failure mode — the model gets stuck re-decoding and transcription slows to roughly real-time or never finishes. Because docling exposes no override, there is no workaround through the docling API.

Root cause (isolated empirically)

I tested large-v3 on an 18.7-min clean, single-speaker TED talk (TED-LIUM, Elizabeth Gilbert — deliberately not "hard" audio), driving raw whisper.transcribe() directly while holding everything at docling's settings (auto-detected language, condition_on_previous_text=True, word_timestamps=False) and varying only beam_size:

beam_size spirals (4 runs each) completion time
None — greedy decode (whisper's default, i.e. what docling uses) 2 / 4 hung ~150 s (slow)
1 — beam search 0 / 4 ~90–150 s (faster)

The dominant, isolated trigger is whisper's default greedy decoding (beam_size=None). Beam search (even width 1) terminates sequences differently, avoids the runaway repetition, and sidesteps the costly temperature-retry fallback — so it is both more reliable and faster. The failure is non-deterministic (whisper's temperature fallback samples without a fixed seed), which is why it doesn't reproduce on every run — but at the greedy default the rate is high enough to make the native backend unreliable for long-form.

condition_on_previous_text=False is whisper's documented mitigation for this repetition mode (it stops one bad window from seeding the next); I didn't isolate it independently here because beam_size >= 1 already prevented the spiral, but it's worth exposing alongside.

Additional data:

  • Reproduces on hard audio too: native tiny on a ~19.8-min earnings22 clip couldn't finish a single pass in 17+ minutes (RTF ≈ 1×).
  • For contrast, the WhisperS2T (CTranslate2) backend transcribes the same clips in seconds and never exhibits this, because it doesn't use this decode path. (Transcription quality not assessed here.)

What docling currently does

In docling/pipeline/asr_pipeline.py (native path):

result = self.model.transcribe(str(fpath), verbose=self.verbose, word_timestamps=self.word_timestamps)

Only verbose and word_timestamps are forwarded. beam_size, condition_on_previous_text, and temperature are not fields on InlineAsrNativeWhisperOptions, so they take whisper's defaults with no way to override.

Requested change

Expose whisper's decoding parameters on InlineAsrNativeWhisperOptions and forward them in the native transcribe() call — at minimum:

  • beam_size — the most impactful; consider defaulting to 1+ rather than greedy.
  • condition_on_previous_text
  • temperature

(language would be a useful nice-to-have too — docling currently auto-detects on every call.)

Secondary

docling's native option also defaults word_timestamps=True, overriding whisper's own default of False and adding DTW word-alignment cost out of the box. Suggest exposing it but defaulting to whisper's False.

Discovered while working on #2938.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions