System Info
- transformers 5.7.0
- torch 2.11.0, numpy 1.26.4
- Python 3.12, macOS, CPU
Who can help?
Whisper / audio feature extraction.
Reproduction
One non-finite sample anywhere in the waveform makes WhisperFeatureExtractor return an
input-feature matrix that is entirely NaN, and the ASR pipeline then produces a
confident transcription from it with no error and no warning.
import numpy as np
from transformers import WhisperFeatureExtractor, pipeline
fe = WhisperFeatureExtractor.from_pretrained("openai/whisper-tiny")
rng = np.random.default_rng(0)
audio = (rng.standard_normal(16000) * 0.05).astype(np.float32)
audio[8000] = np.nan # one non-finite sample in 16,000
feats = fe(audio, sampling_rate=16000, return_tensors="np")["input_features"]
print("input non-finite samples :", int((~np.isfinite(audio)).sum()), "/", audio.size)
print("output NaN fraction :", float(np.isnan(feats).mean()))
asr = pipeline("automatic-speech-recognition", model="openai/whisper-tiny", device="cpu")
print("transcription :", repr(asr(audio)["text"]))
Output:
input non-finite samples : 1 / 16000
output NaN fraction : 1.0
transcription : '0'
Expected behavior
A single corrupt sample is 0.006% of the input and destroys 100% of the output.
The amplification happens at the dynamic-range floor in _np_extract_fbank_features:
log_spec = np.maximum(log_spec, log_spec.max() - 8.0)
log_spec.max() is NaN if any element is NaN, and np.maximum(x, nan) is NaN elementwise,
so one bad sample propagates to every one of the 80 x 3000 bins. The torch path has the
same line. Without that floor the damage would stay local to the affected frames.
What makes this worth reporting is not the NaN itself but that it is silent. generate()
runs on all-NaN features and emits '0', which is indistinguishable from a real
transcription downstream. Non-finite samples are not exotic in production audio: packet
loss, a failed resample, division by a zero-energy normalisation window, or a truncated
decode all produce them, and a caller batching thousands of files has no signal that one
of them silently returned garbage instead of failing.
Same behaviour for all-NaN and all-inf input. float("inf") input also yields '0'.
Possible fixes, in rough order of how invasive they are:
- Validate in the feature extractor: raise
ValueError if the waveform is not finite.
Clearest failure, but it is a behaviour change for anyone currently relying on
garbage-in-garbage-out.
- Keep the damage local: compute the floor with
np.nanmax so only the affected frames
are NaN rather than the whole matrix. This alone still transcribes silently.
- Warn rather than raise, on the same footing as the existing sampling-rate mismatch
warning.
I would suggest 1, with 3 as the conservative alternative. Happy to open a PR for whichever
you prefer, including tests.
Related but separate, so I have not folded it in: a zero-length array is padded to 30s and
transcribed as ' you' rather than rejected.
System Info
Who can help?
Whisper / audio feature extraction.
Reproduction
One non-finite sample anywhere in the waveform makes
WhisperFeatureExtractorreturn aninput-feature matrix that is entirely NaN, and the ASR pipeline then produces a
confident transcription from it with no error and no warning.
Output:
Expected behavior
A single corrupt sample is 0.006% of the input and destroys 100% of the output.
The amplification happens at the dynamic-range floor in
_np_extract_fbank_features:log_spec.max()is NaN if any element is NaN, andnp.maximum(x, nan)is NaN elementwise,so one bad sample propagates to every one of the 80 x 3000 bins. The torch path has the
same line. Without that floor the damage would stay local to the affected frames.
What makes this worth reporting is not the NaN itself but that it is silent.
generate()runs on all-NaN features and emits
'0', which is indistinguishable from a realtranscription downstream. Non-finite samples are not exotic in production audio: packet
loss, a failed resample, division by a zero-energy normalisation window, or a truncated
decode all produce them, and a caller batching thousands of files has no signal that one
of them silently returned garbage instead of failing.
Same behaviour for all-NaN and all-inf input.
float("inf")input also yields'0'.Possible fixes, in rough order of how invasive they are:
ValueErrorif the waveform is not finite.Clearest failure, but it is a behaviour change for anyone currently relying on
garbage-in-garbage-out.
np.nanmaxso only the affected framesare NaN rather than the whole matrix. This alone still transcribes silently.
warning.
I would suggest 1, with 3 as the conservative alternative. Happy to open a PR for whichever
you prefer, including tests.
Related but separate, so I have not folded it in: a zero-length array is padded to 30s and
transcribed as
' you'rather than rejected.