Automatic Speech Recognition
Transformers
Safetensors
PyTorch
mini_whisper
text2text-generation
custom_code
Instructions to use NeuraCraft/MiniWhisper-ASR with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use NeuraCraft/MiniWhisper-ASR with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("automatic-speech-recognition", model="NeuraCraft/MiniWhisper-ASR", trust_remote_code=True)# Load model directly from transformers import AutoModelForSeq2SeqLM model = AutoModelForSeq2SeqLM.from_pretrained("NeuraCraft/MiniWhisper-ASR", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
| # feature_extraction_mini_whisper.py | |
| import numpy as np | |
| import torch | |
| class MiniWhisperFeatureExtractor: | |
| """Feature extractor matching Whisper exactly.""" | |
| model_input_names = ["input_features"] | |
| def __init__( | |
| self, | |
| feature_size=80, | |
| sampling_rate=16000, | |
| hop_length=160, | |
| chunk_length=30, | |
| n_fft=400, | |
| padding_value=0.0, | |
| dither=0.0, | |
| return_attention_mask=False, | |
| **kwargs, | |
| ): | |
| self.feature_size = feature_size | |
| self.sampling_rate = sampling_rate | |
| self.hop_length = hop_length | |
| self.chunk_length = chunk_length | |
| self.n_fft = n_fft | |
| self.n_samples = chunk_length * sampling_rate | |
| self.nb_max_frames = self.n_samples // hop_length | |
| self.padding_value = padding_value | |
| self.dither = dither | |
| self.return_attention_mask = return_attention_mask | |
| self.mel_filters = self._mel_filter_bank() | |
| def _mel_filter_bank(self): | |
| """Create mel filter bank matching Whisper's parameters.""" | |
| try: | |
| from torchaudio.functional import melscale_fbanks | |
| n_freq_bins = 1 + self.n_fft // 2 | |
| mel_filters = melscale_fbanks( | |
| n_freqs=n_freq_bins, | |
| f_min=0.0, | |
| f_max=8000.0, | |
| n_mels=self.feature_size, | |
| sample_rate=self.sampling_rate, | |
| norm="slaney", | |
| mel_scale="slaney", | |
| ) | |
| return mel_filters.numpy() | |
| except ImportError: | |
| return self._numpy_mel_filter_bank() | |
| def _numpy_mel_filter_bank(self): | |
| """NumPy fallback for mel filter bank.""" | |
| def hz_to_mel(hz): | |
| return 2595 * np.log10(1 + hz / 700) | |
| def mel_to_hz(mel): | |
| return 700 * (10 ** (mel / 2595) - 1) | |
| n_freq_bins = 1 + self.n_fft // 2 | |
| fmin, fmax = 0.0, 8000.0 | |
| mel_min = hz_to_mel(fmin) | |
| mel_max = hz_to_mel(fmax) | |
| mel_points = np.linspace(mel_min, mel_max, self.feature_size + 2) | |
| hz_points = mel_to_hz(mel_points) | |
| bin_points = np.floor((self.n_fft + 1) * hz_points / self.sampling_rate).astype(int) | |
| filters = np.zeros((self.feature_size, n_freq_bins)) | |
| for i in range(self.feature_size): | |
| left, center, right = bin_points[i], bin_points[i + 1], bin_points[i + 2] | |
| for j in range(left, center): | |
| filters[i, j] = (j - left) / (center - left) | |
| for j in range(center, right): | |
| filters[i, j] = (right - j) / (right - center) | |
| filters = filters / (filters.sum(axis=1, keepdims=True) + 1e-10) | |
| return filters | |
| def _np_extract_fbank_features(self, waveform_batch, device="cpu"): | |
| """NumPy implementation matching Whisper exactly.""" | |
| log_spec_batch = [] | |
| for waveform in waveform_batch: | |
| # Apply dither | |
| if self.dither != 0.0: | |
| waveform = waveform + self.dither * np.random.randn(*waveform.shape).astype(np.float32) | |
| # Compute STFT | |
| n_frames = 1 + (len(waveform) - self.n_fft) // self.hop_length | |
| spec = np.zeros((self.n_fft // 2 + 1, n_frames), dtype=np.float32) | |
| window = np.hanning(self.n_fft).astype(np.float32) | |
| for i in range(n_frames): | |
| start = i * self.hop_length | |
| frame = waveform[start:start + self.n_fft] | |
| if len(frame) < self.n_fft: | |
| frame = np.pad(frame, (0, self.n_fft - len(frame))) | |
| spec[:, i] = np.abs(np.fft.rfft(frame * window)) ** 2 | |
| # Apply mel filters: mel_filters is [n_mels, n_freqs] | |
| mel_spec = self.mel_filters @ spec | |
| # Log10 with floor | |
| log_spec = np.log10(np.maximum(mel_spec, 1e-10)) | |
| # Whisper uses: np.maximum(log_spec, log_spec.max() - 8.0) | |
| log_spec = np.maximum(log_spec, log_spec.max() - 8.0) | |
| # Normalize: (log_spec + 4.0) / 4.0 | |
| log_spec = (log_spec + 4.0) / 4.0 | |
| # Remove last frame to match Whisper | |
| log_spec = log_spec[:, :-1] | |
| log_spec_batch.append(log_spec) | |
| return np.array(log_spec_batch) | |
| def _torch_extract_fbank_features(self, waveform, device="cpu"): | |
| """PyTorch implementation matching Whisper exactly.""" | |
| waveform = torch.from_numpy(waveform).to(device, torch.float32) | |
| window = torch.hann_window(self.n_fft, device=device) | |
| if self.dither != 0.0: | |
| waveform += self.dither * torch.randn(waveform.shape, dtype=waveform.dtype, device=waveform.device) | |
| stft = torch.stft(waveform, self.n_fft, self.hop_length, window=window, return_complex=True) | |
| magnitudes = stft[..., :-1].abs() ** 2 # Remove last frame | |
| # mel_filters shape: [n_mels, n_freqs] from mel_filter_bank | |
| mel_filters = torch.from_numpy(self.mel_filters).to(device, torch.float32) | |
| # Transpose and matmul: mel_filters.T @ magnitudes | |
| mel_spec = mel_filters.T @ magnitudes | |
| log_spec = torch.clamp(mel_spec, min=1e-10).log10() | |
| if waveform.dim() == 2: | |
| max_val = log_spec.max(dim=2, keepdim=True)[0].max(dim=1, keepdim=True)[0] | |
| log_spec = torch.maximum(log_spec, max_val - 8.0) | |
| else: | |
| log_spec = torch.maximum(log_spec, log_spec.max() - 8.0) | |
| log_spec = (log_spec + 4.0) / 4.0 | |
| if device != "cpu": | |
| log_spec = log_spec.detach().cpu() | |
| return log_spec.numpy() | |
| def zero_mean_unit_var_norm(input_values, attention_mask, padding_value=0.0): | |
| if attention_mask is not None: | |
| attention_mask = np.array(attention_mask, np.int32) | |
| normed = [] | |
| for vector, length in zip(input_values, attention_mask.sum(-1)): | |
| normed_slice = (vector - vector[:length].mean()) / (np.sqrt(vector[:length].var() + 1e-7)) | |
| if length < normed_slice.shape[0]: | |
| normed_slice[length:] = padding_value | |
| normed.append(normed_slice) | |
| else: | |
| normed = [(x - x.mean()) / (np.sqrt(x.var() + 1e-7)) for x in input_values] | |
| return normed | |
| def __call__( | |
| self, | |
| raw_speech, | |
| sampling_rate=None, | |
| truncation=True, | |
| return_tensors="pt", | |
| return_attention_mask=None, | |
| do_normalize=False, | |
| **kwargs, | |
| ): | |
| if sampling_rate is not None and sampling_rate != self.sampling_rate: | |
| raise ValueError(f"Expected {self.sampling_rate}, got {sampling_rate}") | |
| is_batched = isinstance(raw_speech, list) or (isinstance(raw_speech, np.ndarray) and raw_speech.ndim > 1) | |
| if not is_batched: | |
| raw_speech = [raw_speech] | |
| # Convert to numpy float32 | |
| processed = [] | |
| for audio in raw_speech: | |
| if isinstance(audio, np.ndarray) and audio.dtype == np.float64: | |
| audio = audio.astype(np.float32) | |
| elif not isinstance(audio, np.ndarray): | |
| audio = np.array(audio, dtype=np.float32) | |
| # Pad or truncate | |
| if len(audio) > self.n_samples: | |
| if truncation: | |
| audio = audio[:self.n_samples] | |
| elif len(audio) < self.n_samples: | |
| audio = np.pad(audio, (0, self.n_samples - len(audio)), constant_values=self.padding_value) | |
| processed.append(audio) | |
| waveform_batch = np.stack(processed, axis=0) | |
| # Use torch if available | |
| if torch.cuda.is_available(): | |
| input_features = self._torch_extract_fbank_features(waveform_batch, device="cuda") | |
| # Result is [batch, n_mels, n_frames] | |
| else: | |
| input_features = self._np_extract_fbank_features(waveform_batch) | |
| result = {"input_features": input_features} | |
| if return_attention_mask is None: | |
| return_attention_mask = self.return_attention_mask | |
| if return_attention_mask: | |
| result["attention_mask"] = np.ones(input_features.shape[0], dtype=np.int64) | |
| if return_tensors == "pt": | |
| result["input_features"] = torch.from_numpy(input_features).float() | |
| if "attention_mask" in result: | |
| result["attention_mask"] = torch.from_numpy(result["attention_mask"]).long() | |
| return result | |
| def to_dict(self): | |
| return { | |
| "feature_size": self.feature_size, | |
| "sampling_rate": self.sampling_rate, | |
| "hop_length": self.hop_length, | |
| "n_fft": self.n_fft, | |
| "chunk_length": self.chunk_length, | |
| "padding_value": self.padding_value, | |
| "dither": self.dither, | |
| } | |
| def save_pretrained(self, save_directory): | |
| import json, os | |
| os.makedirs(save_directory, exist_ok=True) | |
| with open(os.path.join(save_directory, "preprocessor_config.json"), "w") as f: | |
| json.dump(self.to_dict(), f, indent=2) | |
| def from_pretrained(cls, save_directory): | |
| import json, os | |
| path = os.path.join(save_directory, "preprocessor_config.json") | |
| if os.path.exists(path): | |
| with open(path) as f: | |
| return cls(**json.load(f)) | |
| return cls() | |
| __all__ = ["MiniWhisperFeatureExtractor"] |