| """Whisper Small ASR β inference example using HuggingFace Transformers. |
| |
| The optimized.pte and original.pte artifacts in the sibling pte_optimized/ and |
| pte_original/ directories are ExecuTorch-optimized models (8da8w INT8 dynamic |
| quantization) intended for on-device ARM inference (Android / Graviton). |
| Running those artifacts directly requires the ExecuTorch C++ runtime and a |
| specialized seq2seq runner β they are not suitable for a simple Python |
| ``method.execute()`` call. |
| |
| This script demonstrates equivalent inference using the HuggingFace Transformers |
| library, which is the recommended path for Python-based evaluation and prototyping. |
| |
| Requirements: |
| pip install transformers torch soundfile numpy |
| """ |
|
|
| import json |
| from pathlib import Path |
|
|
| import torch |
| import torch.nn.functional as F |
| from transformers import WhisperForConditionalGeneration, WhisperProcessor |
|
|
| |
| AUDIO_PATH = "sample_input.flac" |
| MODEL_NAME = "openai/whisper-small" |
| LANGUAGE = "en" |
| TASK = "transcribe" |
| MAX_NEW_TOKENS = 128 |
| SAMPLE_RATE = 16000 |
|
|
|
|
| |
| def load_audio(audio_path: str) -> tuple[torch.Tensor, int]: |
| """Load audio file and return (waveform_1d_float32, sample_rate).""" |
| try: |
| import soundfile as sf |
|
|
| audio_np, sr = sf.read(audio_path, dtype="float32") |
| if audio_np.ndim == 2: |
| audio_np = audio_np.mean(axis=1) |
| import numpy as np |
|
|
| return torch.from_numpy(audio_np.astype(np.float32)), int(sr) |
| except ImportError: |
| import torchaudio |
|
|
| waveform, sr = torchaudio.load(audio_path) |
| if waveform.shape[0] > 1: |
| waveform = waveform.mean(dim=0) |
| else: |
| waveform = waveform.squeeze(0) |
| return waveform.float(), int(sr) |
|
|
|
|
| |
| def resample_to_16k(waveform: torch.Tensor, sample_rate: int) -> torch.Tensor: |
| """Resample waveform to 16 kHz using linear interpolation.""" |
| if sample_rate == SAMPLE_RATE: |
| return waveform |
| new_len = int(round(len(waveform) * SAMPLE_RATE / sample_rate)) |
| return F.interpolate( |
| waveform.view(1, 1, -1), size=new_len, mode="linear", align_corners=False |
| ).view(-1) |
|
|
|
|
| |
| def preprocess(audio_path: str, processor: WhisperProcessor) -> torch.Tensor: |
| """Load audio and extract 80-bin log-mel spectrogram features [1, 80, 3000].""" |
| waveform, sr = load_audio(audio_path) |
| waveform_16k = resample_to_16k(waveform, sr) |
| features = processor.feature_extractor( |
| waveform_16k.numpy(), sampling_rate=SAMPLE_RATE, return_tensors="pt" |
| ).input_features |
| return features |
|
|
|
|
| |
| def transcribe( |
| audio_path: str, |
| model: WhisperForConditionalGeneration, |
| processor: WhisperProcessor, |
| ) -> str: |
| """Run Whisper inference and return transcribed text.""" |
| features = preprocess(audio_path, processor) |
| with torch.no_grad(): |
| output_ids = model.generate( |
| features, |
| language=LANGUAGE, |
| task=TASK, |
| max_new_tokens=MAX_NEW_TOKENS, |
| ) |
| return processor.tokenizer.batch_decode(output_ids, skip_special_tokens=True)[0].strip() |
|
|
|
|
| |
| def save_results(audio_path: str, text: str) -> None: |
| """Save transcription result to JSON in the same directory as this script.""" |
| out_dir = Path(__file__).parent |
| result = { |
| "audio_file": str(Path(audio_path).name), |
| "transcription": text, |
| "model": MODEL_NAME, |
| "language": LANGUAGE, |
| "task": TASK, |
| } |
| out_path = out_dir / "transcription.json" |
| with open(out_path, "w") as f: |
| json.dump(result, f, indent=2) |
| print(f"Transcription saved to: {out_path}") |
|
|
|
|
| |
| def main() -> None: |
| print(f"Loading model: {MODEL_NAME}") |
| processor = WhisperProcessor.from_pretrained(MODEL_NAME) |
| model = WhisperForConditionalGeneration.from_pretrained(MODEL_NAME) |
| model.eval() |
|
|
| audio_path = str(Path(__file__).parent / AUDIO_PATH) |
| print(f"Transcribing: {audio_path}") |
| text = transcribe(audio_path, model, processor) |
|
|
| print(f"\nTranscription: {text}") |
| save_results(audio_path, text) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|