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
Upload Main Files
Browse files- configuration_mini_whisper.py +167 -0
- feature_extraction_mini_whisper.py +253 -0
- mini_whisper_model.py +16 -0
- modeling_mini_whisper.py +738 -0
- processing_mini_whisper.py +73 -0
- tokenization_mini_whisper.py +396 -0
configuration_mini_whisper.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# configuration_mini_whisper.py
|
| 2 |
+
from transformers import PretrainedConfig
|
| 3 |
+
|
| 4 |
+
NON_SPEECH_TOKENS = [
|
| 5 |
+
1, 2, 7, 8, 9, 10, 14, 25,
|
| 6 |
+
26, 27, 28, 29, 31, 58, 59, 60, 61, 62,
|
| 7 |
+
63, 90, 91, 92, 93, 357, 366, 438, 532, 685,
|
| 8 |
+
705, 796, 930, 1058, 1220, 1267, 1279, 1303, 1343, 1377,
|
| 9 |
+
1391, 1635, 1782, 1875, 2162, 2361, 2488, 3467, 4008, 4211,
|
| 10 |
+
4600, 4808, 5299, 5855, 6329, 7203, 9609, 9959, 10563, 10786,
|
| 11 |
+
11420, 11709, 11907, 13163, 13697, 13700, 14808, 15306, 16410, 16791,
|
| 12 |
+
17992, 19203, 19510, 20724, 22305, 22935, 27007, 30109, 30420, 33409,
|
| 13 |
+
34949, 40283, 40493, 40549, 47282, 49146, 50257, 50359, 50360, 50361
|
| 14 |
+
]
|
| 15 |
+
|
| 16 |
+
NON_SPEECH_TOKENS_MULTI = [
|
| 17 |
+
1, 2, 7, 8, 9, 10, 14, 25,
|
| 18 |
+
26, 27, 28, 29, 31, 58, 59, 60, 61, 62,
|
| 19 |
+
63, 90, 91, 92, 93, 359, 503, 522, 542, 873,
|
| 20 |
+
893, 902, 918, 922, 931, 1350, 1853, 1982, 2460, 2627,
|
| 21 |
+
3246, 3253, 3268, 3536, 3846, 3961, 4183, 4667, 6585, 6647,
|
| 22 |
+
7273, 9061, 9383, 10428, 10929, 11938, 12033, 12331, 12562, 13793,
|
| 23 |
+
14157, 14635, 15265, 15618, 16553, 16604, 18362, 18956, 20075, 21675,
|
| 24 |
+
22520, 26130, 26161, 26435, 28279, 29464, 31650, 32302, 32470, 36865,
|
| 25 |
+
42863, 47425, 49870, 50254, 50258, 50360, 50361, 50362
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
class MiniWhisperConfig(PretrainedConfig):
|
| 29 |
+
model_type = "mini_whisper"
|
| 30 |
+
keys_to_ignore_at_inference = ["past_key_values"]
|
| 31 |
+
attribute_map = {
|
| 32 |
+
"num_key_value_heads": "encoder_attention_heads",
|
| 33 |
+
"num_attention_heads": "encoder_attention_heads",
|
| 34 |
+
"hidden_size": "d_model",
|
| 35 |
+
"num_hidden_layers": "encoder_layers",
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
vocab_size: int = 51865
|
| 39 |
+
num_mel_bins: int = 80
|
| 40 |
+
encoder_layers: int = 4
|
| 41 |
+
encoder_attention_heads: int = 6
|
| 42 |
+
decoder_layers: int = 4
|
| 43 |
+
decoder_attention_heads: int = 6
|
| 44 |
+
decoder_ffn_dim: int = 1536
|
| 45 |
+
encoder_ffn_dim: int = 1536
|
| 46 |
+
encoder_layerdrop: float | int = 0.0
|
| 47 |
+
decoder_layerdrop: float | int = 0.0
|
| 48 |
+
decoder_start_token_id: int = 50257
|
| 49 |
+
use_cache: bool = True
|
| 50 |
+
is_encoder_decoder: bool = True
|
| 51 |
+
activation_function: str = "gelu"
|
| 52 |
+
d_model: int = 384
|
| 53 |
+
dropout: float | int = 0.0
|
| 54 |
+
attention_dropout: float | int = 0.0
|
| 55 |
+
activation_dropout: float | int = 0.0
|
| 56 |
+
init_std: float = 0.02
|
| 57 |
+
scale_embedding: bool = False
|
| 58 |
+
max_source_positions: int = 1500
|
| 59 |
+
max_target_positions: int = 448
|
| 60 |
+
pad_token_id: int | None = 50256
|
| 61 |
+
bos_token_id: int | None = 50256
|
| 62 |
+
eos_token_id: int | list[int] | None = 50256
|
| 63 |
+
suppress_tokens: list | None = None
|
| 64 |
+
begin_suppress_tokens: list[int] | tuple[int, ...] | None = (220, 50256)
|
| 65 |
+
use_weighted_layer_sum: bool = False
|
| 66 |
+
classifier_proj_size: int = 256
|
| 67 |
+
apply_spec_augment: bool = False
|
| 68 |
+
mask_time_prob: float | int = 0.05
|
| 69 |
+
mask_time_length: int = 10
|
| 70 |
+
mask_time_min_masks: int = 2
|
| 71 |
+
mask_feature_prob: float | int = 0.0
|
| 72 |
+
mask_feature_length: int = 10
|
| 73 |
+
mask_feature_min_masks: int = 0
|
| 74 |
+
median_filter_width: int = 7
|
| 75 |
+
tie_word_embeddings: bool = True
|
| 76 |
+
|
| 77 |
+
def __init__(
|
| 78 |
+
self,
|
| 79 |
+
vocab_size=51865,
|
| 80 |
+
num_mel_bins=80,
|
| 81 |
+
d_model=384,
|
| 82 |
+
encoder_layers=4,
|
| 83 |
+
decoder_layers=4,
|
| 84 |
+
encoder_attention_heads=6,
|
| 85 |
+
decoder_attention_heads=6,
|
| 86 |
+
encoder_ffn_dim=1536,
|
| 87 |
+
decoder_ffn_dim=1536,
|
| 88 |
+
encoder_layerdrop=0.0,
|
| 89 |
+
decoder_layerdrop=0.0,
|
| 90 |
+
decoder_start_token_id=50257,
|
| 91 |
+
use_cache=True,
|
| 92 |
+
is_encoder_decoder=True,
|
| 93 |
+
activation_function="gelu",
|
| 94 |
+
dropout=0.0,
|
| 95 |
+
attention_dropout=0.0,
|
| 96 |
+
activation_dropout=0.0,
|
| 97 |
+
init_std=0.02,
|
| 98 |
+
scale_embedding=False,
|
| 99 |
+
max_source_positions=1500,
|
| 100 |
+
max_target_positions=448,
|
| 101 |
+
pad_token_id=50256,
|
| 102 |
+
bos_token_id=50256,
|
| 103 |
+
eos_token_id=50256,
|
| 104 |
+
suppress_tokens=None,
|
| 105 |
+
begin_suppress_tokens=(220, 50256),
|
| 106 |
+
use_weighted_layer_sum=False,
|
| 107 |
+
classifier_proj_size=256,
|
| 108 |
+
apply_spec_augment=False,
|
| 109 |
+
mask_time_prob=0.05,
|
| 110 |
+
mask_time_length=10,
|
| 111 |
+
mask_time_min_masks=2,
|
| 112 |
+
mask_feature_prob=0.0,
|
| 113 |
+
mask_feature_length=10,
|
| 114 |
+
mask_feature_min_masks=0,
|
| 115 |
+
median_filter_width=7,
|
| 116 |
+
tie_word_embeddings=True,
|
| 117 |
+
**kwargs
|
| 118 |
+
):
|
| 119 |
+
self.vocab_size = vocab_size
|
| 120 |
+
self.num_mel_bins = num_mel_bins
|
| 121 |
+
self.d_model = d_model
|
| 122 |
+
self.encoder_layers = encoder_layers
|
| 123 |
+
self.decoder_layers = decoder_layers
|
| 124 |
+
self.encoder_attention_heads = encoder_attention_heads
|
| 125 |
+
self.decoder_attention_heads = decoder_attention_heads
|
| 126 |
+
self.encoder_ffn_dim = encoder_ffn_dim
|
| 127 |
+
self.decoder_ffn_dim = decoder_ffn_dim
|
| 128 |
+
self.encoder_layerdrop = encoder_layerdrop
|
| 129 |
+
self.decoder_layerdrop = decoder_layerdrop
|
| 130 |
+
self.decoder_start_token_id = decoder_start_token_id
|
| 131 |
+
self.use_cache = use_cache
|
| 132 |
+
self.is_encoder_decoder = is_encoder_decoder
|
| 133 |
+
self.activation_function = activation_function
|
| 134 |
+
self.dropout = dropout
|
| 135 |
+
self.attention_dropout = attention_dropout
|
| 136 |
+
self.activation_dropout = activation_dropout
|
| 137 |
+
self.init_std = init_std
|
| 138 |
+
self.scale_embedding = scale_embedding
|
| 139 |
+
self.max_source_positions = max_source_positions
|
| 140 |
+
self.max_target_positions = max_target_positions
|
| 141 |
+
self.pad_token_id = pad_token_id
|
| 142 |
+
self.bos_token_id = bos_token_id
|
| 143 |
+
self.eos_token_id = eos_token_id
|
| 144 |
+
self.suppress_tokens = suppress_tokens if suppress_tokens is not None else NON_SPEECH_TOKENS
|
| 145 |
+
self.begin_suppress_tokens = list(begin_suppress_tokens)
|
| 146 |
+
self.use_weighted_layer_sum = use_weighted_layer_sum
|
| 147 |
+
self.classifier_proj_size = classifier_proj_size
|
| 148 |
+
self.apply_spec_augment = apply_spec_augment
|
| 149 |
+
self.mask_time_prob = mask_time_prob
|
| 150 |
+
self.mask_time_length = mask_time_length
|
| 151 |
+
self.mask_time_min_masks = mask_time_min_masks
|
| 152 |
+
self.mask_feature_prob = mask_feature_prob
|
| 153 |
+
self.mask_feature_length = mask_feature_length
|
| 154 |
+
self.mask_feature_min_masks = mask_feature_min_masks
|
| 155 |
+
self.median_filter_width = median_filter_width
|
| 156 |
+
self.tie_word_embeddings = tie_word_embeddings
|
| 157 |
+
|
| 158 |
+
super().__init__(
|
| 159 |
+
pad_token_id=pad_token_id,
|
| 160 |
+
bos_token_id=bos_token_id,
|
| 161 |
+
eos_token_id=eos_token_id,
|
| 162 |
+
decoder_start_token_id=decoder_start_token_id,
|
| 163 |
+
tie_word_embeddings=tie_word_embeddings,
|
| 164 |
+
**kwargs,
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
__all__ = ["MiniWhisperConfig"]
|
feature_extraction_mini_whisper.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# feature_extraction_mini_whisper.py
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class MiniWhisperFeatureExtractor:
|
| 7 |
+
"""Feature extractor matching Whisper exactly."""
|
| 8 |
+
|
| 9 |
+
model_input_names = ["input_features"]
|
| 10 |
+
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
feature_size=80,
|
| 14 |
+
sampling_rate=16000,
|
| 15 |
+
hop_length=160,
|
| 16 |
+
chunk_length=30,
|
| 17 |
+
n_fft=400,
|
| 18 |
+
padding_value=0.0,
|
| 19 |
+
dither=0.0,
|
| 20 |
+
return_attention_mask=False,
|
| 21 |
+
**kwargs,
|
| 22 |
+
):
|
| 23 |
+
self.feature_size = feature_size
|
| 24 |
+
self.sampling_rate = sampling_rate
|
| 25 |
+
self.hop_length = hop_length
|
| 26 |
+
self.chunk_length = chunk_length
|
| 27 |
+
self.n_fft = n_fft
|
| 28 |
+
self.n_samples = chunk_length * sampling_rate
|
| 29 |
+
self.nb_max_frames = self.n_samples // hop_length
|
| 30 |
+
self.padding_value = padding_value
|
| 31 |
+
self.dither = dither
|
| 32 |
+
self.return_attention_mask = return_attention_mask
|
| 33 |
+
|
| 34 |
+
self.mel_filters = self._mel_filter_bank()
|
| 35 |
+
|
| 36 |
+
def _mel_filter_bank(self):
|
| 37 |
+
"""Create mel filter bank matching Whisper's parameters."""
|
| 38 |
+
try:
|
| 39 |
+
from torchaudio.functional import melscale_fbanks
|
| 40 |
+
n_freq_bins = 1 + self.n_fft // 2
|
| 41 |
+
mel_filters = melscale_fbanks(
|
| 42 |
+
n_freqs=n_freq_bins,
|
| 43 |
+
f_min=0.0,
|
| 44 |
+
f_max=8000.0,
|
| 45 |
+
n_mels=self.feature_size,
|
| 46 |
+
sample_rate=self.sampling_rate,
|
| 47 |
+
norm="slaney",
|
| 48 |
+
mel_scale="slaney",
|
| 49 |
+
)
|
| 50 |
+
return mel_filters.numpy()
|
| 51 |
+
except ImportError:
|
| 52 |
+
return self._numpy_mel_filter_bank()
|
| 53 |
+
|
| 54 |
+
def _numpy_mel_filter_bank(self):
|
| 55 |
+
"""NumPy fallback for mel filter bank."""
|
| 56 |
+
def hz_to_mel(hz):
|
| 57 |
+
return 2595 * np.log10(1 + hz / 700)
|
| 58 |
+
|
| 59 |
+
def mel_to_hz(mel):
|
| 60 |
+
return 700 * (10 ** (mel / 2595) - 1)
|
| 61 |
+
|
| 62 |
+
n_freq_bins = 1 + self.n_fft // 2
|
| 63 |
+
fmin, fmax = 0.0, 8000.0
|
| 64 |
+
|
| 65 |
+
mel_min = hz_to_mel(fmin)
|
| 66 |
+
mel_max = hz_to_mel(fmax)
|
| 67 |
+
mel_points = np.linspace(mel_min, mel_max, self.feature_size + 2)
|
| 68 |
+
hz_points = mel_to_hz(mel_points)
|
| 69 |
+
|
| 70 |
+
bin_points = np.floor((self.n_fft + 1) * hz_points / self.sampling_rate).astype(int)
|
| 71 |
+
|
| 72 |
+
filters = np.zeros((self.feature_size, n_freq_bins))
|
| 73 |
+
for i in range(self.feature_size):
|
| 74 |
+
left, center, right = bin_points[i], bin_points[i + 1], bin_points[i + 2]
|
| 75 |
+
for j in range(left, center):
|
| 76 |
+
filters[i, j] = (j - left) / (center - left)
|
| 77 |
+
for j in range(center, right):
|
| 78 |
+
filters[i, j] = (right - j) / (right - center)
|
| 79 |
+
|
| 80 |
+
filters = filters / (filters.sum(axis=1, keepdims=True) + 1e-10)
|
| 81 |
+
return filters
|
| 82 |
+
|
| 83 |
+
def _np_extract_fbank_features(self, waveform_batch, device="cpu"):
|
| 84 |
+
"""NumPy implementation matching Whisper exactly."""
|
| 85 |
+
log_spec_batch = []
|
| 86 |
+
for waveform in waveform_batch:
|
| 87 |
+
# Apply dither
|
| 88 |
+
if self.dither != 0.0:
|
| 89 |
+
waveform = waveform + self.dither * np.random.randn(*waveform.shape).astype(np.float32)
|
| 90 |
+
|
| 91 |
+
# Compute STFT
|
| 92 |
+
n_frames = 1 + (len(waveform) - self.n_fft) // self.hop_length
|
| 93 |
+
spec = np.zeros((self.n_fft // 2 + 1, n_frames), dtype=np.float32)
|
| 94 |
+
|
| 95 |
+
window = np.hanning(self.n_fft).astype(np.float32)
|
| 96 |
+
for i in range(n_frames):
|
| 97 |
+
start = i * self.hop_length
|
| 98 |
+
frame = waveform[start:start + self.n_fft]
|
| 99 |
+
if len(frame) < self.n_fft:
|
| 100 |
+
frame = np.pad(frame, (0, self.n_fft - len(frame)))
|
| 101 |
+
spec[:, i] = np.abs(np.fft.rfft(frame * window)) ** 2
|
| 102 |
+
|
| 103 |
+
# Apply mel filters: mel_filters is [n_mels, n_freqs]
|
| 104 |
+
mel_spec = self.mel_filters @ spec
|
| 105 |
+
|
| 106 |
+
# Log10 with floor
|
| 107 |
+
log_spec = np.log10(np.maximum(mel_spec, 1e-10))
|
| 108 |
+
|
| 109 |
+
# Whisper uses: np.maximum(log_spec, log_spec.max() - 8.0)
|
| 110 |
+
log_spec = np.maximum(log_spec, log_spec.max() - 8.0)
|
| 111 |
+
|
| 112 |
+
# Normalize: (log_spec + 4.0) / 4.0
|
| 113 |
+
log_spec = (log_spec + 4.0) / 4.0
|
| 114 |
+
|
| 115 |
+
# Remove last frame to match Whisper
|
| 116 |
+
log_spec = log_spec[:, :-1]
|
| 117 |
+
|
| 118 |
+
log_spec_batch.append(log_spec)
|
| 119 |
+
|
| 120 |
+
return np.array(log_spec_batch)
|
| 121 |
+
|
| 122 |
+
def _torch_extract_fbank_features(self, waveform, device="cpu"):
|
| 123 |
+
"""PyTorch implementation matching Whisper exactly."""
|
| 124 |
+
waveform = torch.from_numpy(waveform).to(device, torch.float32)
|
| 125 |
+
window = torch.hann_window(self.n_fft, device=device)
|
| 126 |
+
|
| 127 |
+
if self.dither != 0.0:
|
| 128 |
+
waveform += self.dither * torch.randn(waveform.shape, dtype=waveform.dtype, device=waveform.device)
|
| 129 |
+
|
| 130 |
+
stft = torch.stft(waveform, self.n_fft, self.hop_length, window=window, return_complex=True)
|
| 131 |
+
magnitudes = stft[..., :-1].abs() ** 2 # Remove last frame
|
| 132 |
+
|
| 133 |
+
# mel_filters shape: [n_mels, n_freqs] from mel_filter_bank
|
| 134 |
+
mel_filters = torch.from_numpy(self.mel_filters).to(device, torch.float32)
|
| 135 |
+
# Transpose and matmul: mel_filters.T @ magnitudes
|
| 136 |
+
mel_spec = mel_filters.T @ magnitudes
|
| 137 |
+
|
| 138 |
+
log_spec = torch.clamp(mel_spec, min=1e-10).log10()
|
| 139 |
+
|
| 140 |
+
if waveform.dim() == 2:
|
| 141 |
+
max_val = log_spec.max(dim=2, keepdim=True)[0].max(dim=1, keepdim=True)[0]
|
| 142 |
+
log_spec = torch.maximum(log_spec, max_val - 8.0)
|
| 143 |
+
else:
|
| 144 |
+
log_spec = torch.maximum(log_spec, log_spec.max() - 8.0)
|
| 145 |
+
|
| 146 |
+
log_spec = (log_spec + 4.0) / 4.0
|
| 147 |
+
|
| 148 |
+
if device != "cpu":
|
| 149 |
+
log_spec = log_spec.detach().cpu()
|
| 150 |
+
|
| 151 |
+
return log_spec.numpy()
|
| 152 |
+
|
| 153 |
+
@staticmethod
|
| 154 |
+
def zero_mean_unit_var_norm(input_values, attention_mask, padding_value=0.0):
|
| 155 |
+
if attention_mask is not None:
|
| 156 |
+
attention_mask = np.array(attention_mask, np.int32)
|
| 157 |
+
normed = []
|
| 158 |
+
for vector, length in zip(input_values, attention_mask.sum(-1)):
|
| 159 |
+
normed_slice = (vector - vector[:length].mean()) / (np.sqrt(vector[:length].var() + 1e-7))
|
| 160 |
+
if length < normed_slice.shape[0]:
|
| 161 |
+
normed_slice[length:] = padding_value
|
| 162 |
+
normed.append(normed_slice)
|
| 163 |
+
else:
|
| 164 |
+
normed = [(x - x.mean()) / (np.sqrt(x.var() + 1e-7)) for x in input_values]
|
| 165 |
+
return normed
|
| 166 |
+
|
| 167 |
+
def __call__(
|
| 168 |
+
self,
|
| 169 |
+
raw_speech,
|
| 170 |
+
sampling_rate=None,
|
| 171 |
+
truncation=True,
|
| 172 |
+
return_tensors="pt",
|
| 173 |
+
return_attention_mask=None,
|
| 174 |
+
do_normalize=False,
|
| 175 |
+
**kwargs,
|
| 176 |
+
):
|
| 177 |
+
if sampling_rate is not None and sampling_rate != self.sampling_rate:
|
| 178 |
+
raise ValueError(f"Expected {self.sampling_rate}, got {sampling_rate}")
|
| 179 |
+
|
| 180 |
+
is_batched = isinstance(raw_speech, list) or (isinstance(raw_speech, np.ndarray) and raw_speech.ndim > 1)
|
| 181 |
+
|
| 182 |
+
if not is_batched:
|
| 183 |
+
raw_speech = [raw_speech]
|
| 184 |
+
|
| 185 |
+
# Convert to numpy float32
|
| 186 |
+
processed = []
|
| 187 |
+
for audio in raw_speech:
|
| 188 |
+
if isinstance(audio, np.ndarray) and audio.dtype == np.float64:
|
| 189 |
+
audio = audio.astype(np.float32)
|
| 190 |
+
elif not isinstance(audio, np.ndarray):
|
| 191 |
+
audio = np.array(audio, dtype=np.float32)
|
| 192 |
+
|
| 193 |
+
# Pad or truncate
|
| 194 |
+
if len(audio) > self.n_samples:
|
| 195 |
+
if truncation:
|
| 196 |
+
audio = audio[:self.n_samples]
|
| 197 |
+
elif len(audio) < self.n_samples:
|
| 198 |
+
audio = np.pad(audio, (0, self.n_samples - len(audio)), constant_values=self.padding_value)
|
| 199 |
+
|
| 200 |
+
processed.append(audio)
|
| 201 |
+
|
| 202 |
+
waveform_batch = np.stack(processed, axis=0)
|
| 203 |
+
|
| 204 |
+
# Use torch if available
|
| 205 |
+
if torch.cuda.is_available():
|
| 206 |
+
input_features = self._torch_extract_fbank_features(waveform_batch, device="cuda")
|
| 207 |
+
# Result is [batch, n_mels, n_frames]
|
| 208 |
+
else:
|
| 209 |
+
input_features = self._np_extract_fbank_features(waveform_batch)
|
| 210 |
+
|
| 211 |
+
result = {"input_features": input_features}
|
| 212 |
+
|
| 213 |
+
if return_attention_mask is None:
|
| 214 |
+
return_attention_mask = self.return_attention_mask
|
| 215 |
+
|
| 216 |
+
if return_attention_mask:
|
| 217 |
+
result["attention_mask"] = np.ones(input_features.shape[0], dtype=np.int64)
|
| 218 |
+
|
| 219 |
+
if return_tensors == "pt":
|
| 220 |
+
result["input_features"] = torch.from_numpy(input_features).float()
|
| 221 |
+
if "attention_mask" in result:
|
| 222 |
+
result["attention_mask"] = torch.from_numpy(result["attention_mask"]).long()
|
| 223 |
+
|
| 224 |
+
return result
|
| 225 |
+
|
| 226 |
+
def to_dict(self):
|
| 227 |
+
return {
|
| 228 |
+
"feature_size": self.feature_size,
|
| 229 |
+
"sampling_rate": self.sampling_rate,
|
| 230 |
+
"hop_length": self.hop_length,
|
| 231 |
+
"n_fft": self.n_fft,
|
| 232 |
+
"chunk_length": self.chunk_length,
|
| 233 |
+
"padding_value": self.padding_value,
|
| 234 |
+
"dither": self.dither,
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
def save_pretrained(self, save_directory):
|
| 238 |
+
import json, os
|
| 239 |
+
os.makedirs(save_directory, exist_ok=True)
|
| 240 |
+
with open(os.path.join(save_directory, "preprocessor_config.json"), "w") as f:
|
| 241 |
+
json.dump(self.to_dict(), f, indent=2)
|
| 242 |
+
|
| 243 |
+
@classmethod
|
| 244 |
+
def from_pretrained(cls, save_directory):
|
| 245 |
+
import json, os
|
| 246 |
+
path = os.path.join(save_directory, "preprocessor_config.json")
|
| 247 |
+
if os.path.exists(path):
|
| 248 |
+
with open(path) as f:
|
| 249 |
+
return cls(**json.load(f))
|
| 250 |
+
return cls()
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
__all__ = ["MiniWhisperFeatureExtractor"]
|
mini_whisper_model.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# mini_whisper_model.py - Backwards compatibility alias
|
| 2 |
+
from configuration_mini_whisper import MiniWhisperConfig
|
| 3 |
+
from modeling_mini_whisper import (
|
| 4 |
+
MiniWhisperForConditionalGeneration,
|
| 5 |
+
MiniWhisperModel,
|
| 6 |
+
MiniWhisperEncoder,
|
| 7 |
+
MiniWhisperDecoder,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
__all__ = [
|
| 11 |
+
"MiniWhisperConfig",
|
| 12 |
+
"MiniWhisperForConditionalGeneration",
|
| 13 |
+
"MiniWhisperModel",
|
| 14 |
+
"MiniWhisperEncoder",
|
| 15 |
+
"MiniWhisperDecoder",
|
| 16 |
+
]
|
modeling_mini_whisper.py
ADDED
|
@@ -0,0 +1,738 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# modeling_mini_whisper.py
|
| 2 |
+
import math
|
| 3 |
+
from collections.abc import Callable
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
from torch.nn import functional as F
|
| 8 |
+
from transformers import PreTrainedModel, GenerationMixin
|
| 9 |
+
from transformers.modeling_outputs import (
|
| 10 |
+
BaseModelOutput,
|
| 11 |
+
BaseModelOutputWithPastAndCrossAttentions,
|
| 12 |
+
CausalLMOutputWithCrossAttentions,
|
| 13 |
+
Seq2SeqLMOutput,
|
| 14 |
+
Seq2SeqModelOutput,
|
| 15 |
+
)
|
| 16 |
+
from transformers.activations import ACT2FN
|
| 17 |
+
from transformers.utils import logging
|
| 18 |
+
from configuration_mini_whisper import MiniWhisperConfig
|
| 19 |
+
|
| 20 |
+
logger = logging.get_logger(__name__)
|
| 21 |
+
|
| 22 |
+
_HIDDEN_STATES_START_POSITION = 1
|
| 23 |
+
|
| 24 |
+
def sinusoids(length: int, channels: int, max_timescale: float = 10000) -> torch.Tensor:
|
| 25 |
+
"""Returns sinusoids for positional embedding"""
|
| 26 |
+
if channels % 2 != 0:
|
| 27 |
+
raise ValueError(f"Number of channels has to be divisible by 2, got {channels} channels.")
|
| 28 |
+
log_timescale_increment = math.log(max_timescale) / (channels // 2 - 1)
|
| 29 |
+
inv_timescales = torch.exp(-log_timescale_increment * torch.arange(channels // 2))
|
| 30 |
+
scaled_time = torch.arange(length).view(-1, 1) * inv_timescales.view(1, -1)
|
| 31 |
+
return torch.cat([scaled_time.sin(), scaled_time.cos()], dim=1)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def shift_tokens_right(input_ids: torch.Tensor, pad_token_id: int, decoder_start_token_id: int):
|
| 35 |
+
"""Shift input ids one token to the right."""
|
| 36 |
+
shifted_input_ids = input_ids.new_zeros(input_ids.shape)
|
| 37 |
+
shifted_input_ids[:, 1:] = input_ids[:, :-1].clone()
|
| 38 |
+
shifted_input_ids[:, 0] = decoder_start_token_id
|
| 39 |
+
|
| 40 |
+
if pad_token_id is None:
|
| 41 |
+
raise ValueError("pad_token_id has to be defined.")
|
| 42 |
+
shifted_input_ids.masked_fill_(shifted_input_ids == -100, pad_token_id)
|
| 43 |
+
|
| 44 |
+
return shifted_input_ids
|
| 45 |
+
|
| 46 |
+
def _compute_mask_indices(
|
| 47 |
+
shape: tuple[int, int],
|
| 48 |
+
mask_prob: float,
|
| 49 |
+
mask_length: int,
|
| 50 |
+
attention_mask: torch.LongTensor | None = None,
|
| 51 |
+
min_masks: int = 0,
|
| 52 |
+
):
|
| 53 |
+
"""Computes random mask spans for SpecAugment"""
|
| 54 |
+
import numpy as np
|
| 55 |
+
|
| 56 |
+
batch_size, sequence_length = shape
|
| 57 |
+
|
| 58 |
+
if mask_length < 1:
|
| 59 |
+
raise ValueError(f"mask_length has to be bigger than 0.")
|
| 60 |
+
if mask_length > sequence_length:
|
| 61 |
+
raise ValueError(f"mask_length has to be smaller than sequence_length.")
|
| 62 |
+
|
| 63 |
+
epsilon = np.random.rand(1).item()
|
| 64 |
+
|
| 65 |
+
def compute_num_masked_span(input_length):
|
| 66 |
+
num_masked_span = int(mask_prob * input_length / mask_length + epsilon)
|
| 67 |
+
num_masked_span = max(num_masked_span, min_masks)
|
| 68 |
+
if num_masked_span * mask_length > sequence_length:
|
| 69 |
+
num_masked_span = sequence_length // mask_length
|
| 70 |
+
if input_length - (mask_length - 1) < num_masked_span:
|
| 71 |
+
num_masked_span = max(input_length - (mask_length - 1), 0)
|
| 72 |
+
return num_masked_span
|
| 73 |
+
|
| 74 |
+
input_lengths = (
|
| 75 |
+
attention_mask.detach().sum(-1).tolist()
|
| 76 |
+
if attention_mask is not None
|
| 77 |
+
else [sequence_length for _ in range(batch_size)]
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
spec_aug_mask = np.zeros((batch_size, sequence_length), dtype=bool)
|
| 81 |
+
spec_aug_mask_idxs = []
|
| 82 |
+
max_num_masked_span = compute_num_masked_span(sequence_length)
|
| 83 |
+
|
| 84 |
+
if max_num_masked_span == 0:
|
| 85 |
+
return spec_aug_mask
|
| 86 |
+
|
| 87 |
+
for input_length in input_lengths:
|
| 88 |
+
num_masked_span = compute_num_masked_span(input_length)
|
| 89 |
+
spec_aug_mask_idx = np.random.choice(
|
| 90 |
+
np.arange(input_length - (mask_length - 1)), num_masked_span, replace=False
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
if len(spec_aug_mask_idx) == 0:
|
| 94 |
+
dummy_mask_idx = sequence_length - 1
|
| 95 |
+
else:
|
| 96 |
+
dummy_mask_idx = spec_aug_mask_idx[0]
|
| 97 |
+
|
| 98 |
+
spec_aug_mask_idx = np.concatenate(
|
| 99 |
+
[spec_aug_mask_idx, np.ones(max_num_masked_span - num_masked_span, dtype=np.int32) * dummy_mask_idx]
|
| 100 |
+
)
|
| 101 |
+
spec_aug_mask_idxs.append(spec_aug_mask_idx)
|
| 102 |
+
|
| 103 |
+
spec_aug_mask_idxs = np.array(spec_aug_mask_idxs)
|
| 104 |
+
spec_aug_mask_idxs = np.broadcast_to(
|
| 105 |
+
spec_aug_mask_idxs[:, :, None], (batch_size, max_num_masked_span, mask_length)
|
| 106 |
+
)
|
| 107 |
+
spec_aug_mask_idxs = spec_aug_mask_idxs.reshape(batch_size, max_num_masked_span * mask_length)
|
| 108 |
+
|
| 109 |
+
offsets = np.arange(mask_length)[None, None, :]
|
| 110 |
+
offsets = np.broadcast_to(offsets, (batch_size, max_num_masked_span, mask_length)).reshape(
|
| 111 |
+
batch_size, max_num_masked_span * mask_length
|
| 112 |
+
)
|
| 113 |
+
spec_aug_mask_idxs = spec_aug_mask_idxs + offsets
|
| 114 |
+
|
| 115 |
+
if spec_aug_mask_idxs.max() > sequence_length - 1:
|
| 116 |
+
spec_aug_mask_idxs[spec_aug_mask_idxs > sequence_length - 1] = sequence_length - 1
|
| 117 |
+
|
| 118 |
+
np.put_along_axis(spec_aug_mask, spec_aug_mask_idxs, 1, -1)
|
| 119 |
+
|
| 120 |
+
return spec_aug_mask
|
| 121 |
+
|
| 122 |
+
class WhisperPositionalEmbedding(nn.Module):
|
| 123 |
+
def __init__(self, num_positions: int, embedding_dim: int, padding_idx: int | None = None):
|
| 124 |
+
super().__init__()
|
| 125 |
+
self.num_positions = num_positions
|
| 126 |
+
self.embedding_dim = embedding_dim
|
| 127 |
+
self.weight = nn.Parameter(torch.zeros(num_positions, embedding_dim))
|
| 128 |
+
self._init_weights()
|
| 129 |
+
|
| 130 |
+
def _init_weights(self):
|
| 131 |
+
with torch.no_grad():
|
| 132 |
+
self.weight.copy_(sinusoids(self.num_positions, self.embedding_dim))
|
| 133 |
+
|
| 134 |
+
def forward(self, input_ids, past_key_values_length=0, position_ids=None):
|
| 135 |
+
if position_ids is None:
|
| 136 |
+
return self.weight[past_key_values_length : past_key_values_length + input_ids.shape[1]]
|
| 137 |
+
else:
|
| 138 |
+
return self.weight[position_ids]
|
| 139 |
+
|
| 140 |
+
class MiniWhisperAttention(nn.Module):
|
| 141 |
+
"""Multi-headed attention from 'Attention Is All You Need' paper"""
|
| 142 |
+
|
| 143 |
+
def __init__(
|
| 144 |
+
self,
|
| 145 |
+
embed_dim: int,
|
| 146 |
+
num_heads: int,
|
| 147 |
+
dropout: float = 0.0,
|
| 148 |
+
is_decoder: bool = False,
|
| 149 |
+
bias: bool = True,
|
| 150 |
+
is_causal: bool = False,
|
| 151 |
+
layer_idx: int | None = None,
|
| 152 |
+
config: MiniWhisperConfig | None = None,
|
| 153 |
+
):
|
| 154 |
+
super().__init__()
|
| 155 |
+
self.embed_dim = embed_dim
|
| 156 |
+
self.num_heads = num_heads
|
| 157 |
+
self.dropout = dropout
|
| 158 |
+
self.head_dim = embed_dim // num_heads
|
| 159 |
+
self.config = config
|
| 160 |
+
|
| 161 |
+
if (self.head_dim * num_heads) != self.embed_dim:
|
| 162 |
+
raise ValueError(f"embed_dim must be divisible by num_heads")
|
| 163 |
+
self.scaling = self.head_dim ** -0.5
|
| 164 |
+
self.is_decoder = is_decoder
|
| 165 |
+
self.is_causal = is_causal
|
| 166 |
+
self.layer_idx = layer_idx
|
| 167 |
+
|
| 168 |
+
self.k_proj = nn.Linear(embed_dim, embed_dim, bias=False)
|
| 169 |
+
self.v_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
|
| 170 |
+
self.q_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
|
| 171 |
+
self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias)
|
| 172 |
+
|
| 173 |
+
def forward(
|
| 174 |
+
self,
|
| 175 |
+
hidden_states: torch.Tensor,
|
| 176 |
+
key_value_states: torch.Tensor | None = None,
|
| 177 |
+
past_key_value: tuple[torch.Tensor, torch.Tensor] | None = None,
|
| 178 |
+
attention_mask: torch.Tensor | None = None,
|
| 179 |
+
output_attentions: bool = False,
|
| 180 |
+
):
|
| 181 |
+
"""Input shape: Batch x Time x Channel"""
|
| 182 |
+
is_cross_attention = key_value_states is not None
|
| 183 |
+
input_shape = hidden_states.shape[:-1]
|
| 184 |
+
hidden_shape = (*input_shape, -1, self.head_dim)
|
| 185 |
+
|
| 186 |
+
query_states = (self.q_proj(hidden_states) * self.scaling).view(hidden_shape).transpose(1, 2).contiguous()
|
| 187 |
+
|
| 188 |
+
if past_key_value is not None and not is_cross_attention:
|
| 189 |
+
key_states = past_key_value[0]
|
| 190 |
+
value_states = past_key_value[1]
|
| 191 |
+
else:
|
| 192 |
+
current_states = key_value_states if key_value_states is not None else hidden_states
|
| 193 |
+
kv_shape = (input_shape[0], -1, self.num_heads, self.head_dim)
|
| 194 |
+
key_states = self.k_proj(current_states).view(kv_shape).transpose(1, 2).contiguous()
|
| 195 |
+
value_states = self.v_proj(current_states).view(kv_shape).transpose(1, 2).contiguous()
|
| 196 |
+
|
| 197 |
+
if past_key_value is not None and is_cross_attention:
|
| 198 |
+
key_states = torch.cat([past_key_value[0], key_states], dim=2)
|
| 199 |
+
value_states = torch.cat([past_key_value[1], value_states], dim=2)
|
| 200 |
+
|
| 201 |
+
attn_weights = torch.matmul(query_states, key_states.transpose(-2, -1)) * 1.0
|
| 202 |
+
if attention_mask is not None:
|
| 203 |
+
attn_weights = attn_weights + attention_mask
|
| 204 |
+
|
| 205 |
+
attn_weights = F.softmax(attn_weights, dim=-1)
|
| 206 |
+
attn_weights = F.dropout(attn_weights, p=self.dropout, training=self.training)
|
| 207 |
+
|
| 208 |
+
attn_output = torch.matmul(attn_weights, value_states)
|
| 209 |
+
attn_output = attn_output.reshape(*input_shape, -1).contiguous()
|
| 210 |
+
attn_output = self.out_proj(attn_output)
|
| 211 |
+
|
| 212 |
+
return attn_output, None
|
| 213 |
+
|
| 214 |
+
class MiniWhisperEncoderLayer(nn.Module):
|
| 215 |
+
def __init__(self, config: MiniWhisperConfig):
|
| 216 |
+
super().__init__()
|
| 217 |
+
self.embed_dim = config.d_model
|
| 218 |
+
|
| 219 |
+
self.self_attn = MiniWhisperAttention(
|
| 220 |
+
embed_dim=self.embed_dim,
|
| 221 |
+
num_heads=config.encoder_attention_heads,
|
| 222 |
+
dropout=config.attention_dropout,
|
| 223 |
+
is_decoder=False,
|
| 224 |
+
config=config,
|
| 225 |
+
)
|
| 226 |
+
self.self_attn_layer_norm = nn.LayerNorm(self.embed_dim)
|
| 227 |
+
self.dropout = config.dropout
|
| 228 |
+
self.activation_fn = ACT2FN[config.activation_function]
|
| 229 |
+
self.activation_dropout = config.activation_dropout
|
| 230 |
+
self.fc1 = nn.Linear(self.embed_dim, config.encoder_ffn_dim)
|
| 231 |
+
self.fc2 = nn.Linear(config.encoder_ffn_dim, self.embed_dim)
|
| 232 |
+
self.final_layer_norm = nn.LayerNorm(self.embed_dim)
|
| 233 |
+
|
| 234 |
+
def forward(
|
| 235 |
+
self,
|
| 236 |
+
hidden_states: torch.Tensor,
|
| 237 |
+
attention_mask: torch.Tensor,
|
| 238 |
+
):
|
| 239 |
+
residual = hidden_states
|
| 240 |
+
hidden_states = self.self_attn_layer_norm(hidden_states)
|
| 241 |
+
hidden_states, _ = self.self_attn(
|
| 242 |
+
hidden_states=hidden_states,
|
| 243 |
+
attention_mask=attention_mask,
|
| 244 |
+
)
|
| 245 |
+
hidden_states = F.dropout(hidden_states, p=self.dropout, training=self.training)
|
| 246 |
+
hidden_states = residual + hidden_states
|
| 247 |
+
|
| 248 |
+
residual = hidden_states
|
| 249 |
+
hidden_states = self.final_layer_norm(hidden_states)
|
| 250 |
+
hidden_states = self.activation_fn(self.fc1(hidden_states))
|
| 251 |
+
hidden_states = F.dropout(hidden_states, p=self.activation_dropout, training=self.training)
|
| 252 |
+
hidden_states = self.fc2(hidden_states)
|
| 253 |
+
hidden_states = F.dropout(hidden_states, p=self.dropout, training=self.training)
|
| 254 |
+
hidden_states = residual + hidden_states
|
| 255 |
+
|
| 256 |
+
if hidden_states.dtype == torch.float16:
|
| 257 |
+
clamp_value = torch.finfo(hidden_states.dtype).max - 1000
|
| 258 |
+
hidden_states = torch.clamp(hidden_states, min=-clamp_value, max=clamp_value)
|
| 259 |
+
|
| 260 |
+
return hidden_states
|
| 261 |
+
|
| 262 |
+
class MiniWhisperDecoderLayer(nn.Module):
|
| 263 |
+
def __init__(self, config: MiniWhisperConfig, layer_idx: int | None = None):
|
| 264 |
+
super().__init__()
|
| 265 |
+
self.embed_dim = config.d_model
|
| 266 |
+
|
| 267 |
+
self.self_attn = MiniWhisperAttention(
|
| 268 |
+
embed_dim=self.embed_dim,
|
| 269 |
+
num_heads=config.decoder_attention_heads,
|
| 270 |
+
dropout=config.attention_dropout,
|
| 271 |
+
is_decoder=True,
|
| 272 |
+
is_causal=True,
|
| 273 |
+
layer_idx=layer_idx,
|
| 274 |
+
config=config,
|
| 275 |
+
)
|
| 276 |
+
self.dropout = config.dropout
|
| 277 |
+
self.activation_fn = ACT2FN[config.activation_function]
|
| 278 |
+
self.activation_dropout = config.activation_dropout
|
| 279 |
+
|
| 280 |
+
self.self_attn_layer_norm = nn.LayerNorm(self.embed_dim)
|
| 281 |
+
self.encoder_attn = MiniWhisperAttention(
|
| 282 |
+
self.embed_dim,
|
| 283 |
+
config.decoder_attention_heads,
|
| 284 |
+
dropout=config.attention_dropout,
|
| 285 |
+
is_decoder=True,
|
| 286 |
+
layer_idx=layer_idx,
|
| 287 |
+
config=config,
|
| 288 |
+
)
|
| 289 |
+
self.encoder_attn_layer_norm = nn.LayerNorm(self.embed_dim)
|
| 290 |
+
self.fc1 = nn.Linear(self.embed_dim, config.decoder_ffn_dim)
|
| 291 |
+
self.fc2 = nn.Linear(config.decoder_ffn_dim, self.embed_dim)
|
| 292 |
+
self.final_layer_norm = nn.LayerNorm(self.embed_dim)
|
| 293 |
+
|
| 294 |
+
def forward(
|
| 295 |
+
self,
|
| 296 |
+
hidden_states: torch.Tensor,
|
| 297 |
+
attention_mask: torch.Tensor | None = None,
|
| 298 |
+
encoder_hidden_states: torch.Tensor | None = None,
|
| 299 |
+
encoder_attention_mask: torch.Tensor | None = None,
|
| 300 |
+
past_key_value: tuple[tuple[torch.Tensor, torch.Tensor]] | None = None,
|
| 301 |
+
use_cache: bool | None = True,
|
| 302 |
+
):
|
| 303 |
+
residual = hidden_states
|
| 304 |
+
hidden_states = self.self_attn_layer_norm(hidden_states)
|
| 305 |
+
|
| 306 |
+
self_attn_past = past_key_value[0] if past_key_value is not None else None
|
| 307 |
+
hidden_states, _ = self.self_attn(
|
| 308 |
+
hidden_states,
|
| 309 |
+
past_key_value=self_attn_past,
|
| 310 |
+
attention_mask=attention_mask,
|
| 311 |
+
)
|
| 312 |
+
hidden_states = F.dropout(hidden_states, p=self.dropout, training=self.training)
|
| 313 |
+
hidden_states = residual + hidden_states
|
| 314 |
+
|
| 315 |
+
if encoder_hidden_states is not None:
|
| 316 |
+
residual = hidden_states
|
| 317 |
+
hidden_states = self.encoder_attn_layer_norm(hidden_states)
|
| 318 |
+
cross_attn_past = past_key_value[1] if past_key_value is not None else None
|
| 319 |
+
hidden_states, _ = self.encoder_attn(
|
| 320 |
+
hidden_states,
|
| 321 |
+
key_value_states=encoder_hidden_states,
|
| 322 |
+
attention_mask=encoder_attention_mask,
|
| 323 |
+
past_key_value=cross_attn_past,
|
| 324 |
+
)
|
| 325 |
+
hidden_states = F.dropout(hidden_states, p=self.dropout, training=self.training)
|
| 326 |
+
hidden_states = residual + hidden_states
|
| 327 |
+
|
| 328 |
+
residual = hidden_states
|
| 329 |
+
hidden_states = self.final_layer_norm(hidden_states)
|
| 330 |
+
hidden_states = self.activation_fn(self.fc1(hidden_states))
|
| 331 |
+
hidden_states = F.dropout(hidden_states, p=self.activation_dropout, training=self.training)
|
| 332 |
+
hidden_states = self.fc2(hidden_states)
|
| 333 |
+
hidden_states = F.dropout(hidden_states, p=self.dropout, training=self.training)
|
| 334 |
+
hidden_states = residual + hidden_states
|
| 335 |
+
|
| 336 |
+
return hidden_states, None
|
| 337 |
+
|
| 338 |
+
class MiniWhisperEncoder(PreTrainedModel):
|
| 339 |
+
config_class = MiniWhisperConfig
|
| 340 |
+
main_input_name = "input_features"
|
| 341 |
+
|
| 342 |
+
def __init__(self, config: MiniWhisperConfig):
|
| 343 |
+
super().__init__(config)
|
| 344 |
+
self.dropout = config.dropout
|
| 345 |
+
self.layerdrop = config.encoder_layerdrop
|
| 346 |
+
|
| 347 |
+
embed_dim = config.d_model
|
| 348 |
+
self.num_mel_bins = config.num_mel_bins
|
| 349 |
+
self.padding_idx = config.pad_token_id
|
| 350 |
+
self.max_source_positions = config.max_source_positions
|
| 351 |
+
self.embed_scale = math.sqrt(embed_dim) if config.scale_embedding else 1.0
|
| 352 |
+
|
| 353 |
+
self.conv1 = nn.Conv1d(self.num_mel_bins, embed_dim, kernel_size=3, padding=1)
|
| 354 |
+
self.conv2 = nn.Conv1d(embed_dim, embed_dim, kernel_size=3, stride=2, padding=1)
|
| 355 |
+
|
| 356 |
+
self.embed_positions = nn.Embedding(self.max_source_positions, embed_dim)
|
| 357 |
+
self.embed_positions.requires_grad_(False)
|
| 358 |
+
|
| 359 |
+
self.layers = nn.ModuleList([MiniWhisperEncoderLayer(config) for _ in range(config.encoder_layers)])
|
| 360 |
+
self.layer_norm = nn.LayerNorm(config.d_model)
|
| 361 |
+
|
| 362 |
+
self.gradient_checkpointing = False
|
| 363 |
+
self.post_init()
|
| 364 |
+
|
| 365 |
+
def _init_weights(self, module):
|
| 366 |
+
if isinstance(module, MiniWhisperEncoder):
|
| 367 |
+
with torch.no_grad():
|
| 368 |
+
module.embed_positions.weight.copy_(sinusoids(*module.embed_positions.weight.shape))
|
| 369 |
+
|
| 370 |
+
def forward(
|
| 371 |
+
self,
|
| 372 |
+
input_features,
|
| 373 |
+
attention_mask=None,
|
| 374 |
+
**kwargs,
|
| 375 |
+
):
|
| 376 |
+
x = F.gelu(self.conv1(input_features))
|
| 377 |
+
x = F.gelu(self.conv2(x))
|
| 378 |
+
|
| 379 |
+
x = x.permute(0, 2, 1)
|
| 380 |
+
|
| 381 |
+
seq_len = x.shape[1]
|
| 382 |
+
positions = torch.arange(seq_len, device=x.device)
|
| 383 |
+
hidden_states = x + self.embed_positions(positions)
|
| 384 |
+
hidden_states = F.dropout(hidden_states, p=self.dropout, training=self.training)
|
| 385 |
+
|
| 386 |
+
for idx, encoder_layer in enumerate(self.layers):
|
| 387 |
+
if self.training:
|
| 388 |
+
dropout_probability = torch.rand([])
|
| 389 |
+
if dropout_probability < self.layerdrop:
|
| 390 |
+
continue
|
| 391 |
+
|
| 392 |
+
hidden_states = encoder_layer(hidden_states, None)
|
| 393 |
+
|
| 394 |
+
hidden_states = self.layer_norm(hidden_states)
|
| 395 |
+
|
| 396 |
+
return BaseModelOutput(last_hidden_state=hidden_states)
|
| 397 |
+
|
| 398 |
+
def _get_feat_extract_output_lengths(self, input_lengths: torch.LongTensor):
|
| 399 |
+
input_lengths = (input_lengths - 1) // 2 + 1
|
| 400 |
+
return input_lengths
|
| 401 |
+
|
| 402 |
+
class MiniWhisperDecoder(PreTrainedModel):
|
| 403 |
+
config_class = MiniWhisperConfig
|
| 404 |
+
main_input_name = "input_ids"
|
| 405 |
+
|
| 406 |
+
def __init__(self, config: MiniWhisperConfig):
|
| 407 |
+
super().__init__(config)
|
| 408 |
+
self.dropout = config.dropout
|
| 409 |
+
self.layerdrop = config.decoder_layerdrop
|
| 410 |
+
self.padding_idx = config.pad_token_id
|
| 411 |
+
self.max_target_positions = config.max_target_positions
|
| 412 |
+
self.max_source_positions = config.max_source_positions
|
| 413 |
+
self.embed_scale = math.sqrt(config.d_model) if config.scale_embedding else 1.0
|
| 414 |
+
|
| 415 |
+
self.embed_tokens = nn.Embedding(config.vocab_size, config.d_model, self.padding_idx)
|
| 416 |
+
self.embed_positions = WhisperPositionalEmbedding(self.max_target_positions, config.d_model)
|
| 417 |
+
|
| 418 |
+
self.layers = nn.ModuleList(
|
| 419 |
+
[MiniWhisperDecoderLayer(config, layer_idx) for layer_idx in range(config.decoder_layers)]
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
self.layer_norm = nn.LayerNorm(config.d_model)
|
| 423 |
+
|
| 424 |
+
self.gradient_checkpointing = False
|
| 425 |
+
self.post_init()
|
| 426 |
+
|
| 427 |
+
def forward(
|
| 428 |
+
self,
|
| 429 |
+
input_ids=None,
|
| 430 |
+
attention_mask=None,
|
| 431 |
+
encoder_hidden_states=None,
|
| 432 |
+
encoder_attention_mask=None,
|
| 433 |
+
past_key_values=None,
|
| 434 |
+
inputs_embeds=None,
|
| 435 |
+
position_ids=None,
|
| 436 |
+
use_cache=None,
|
| 437 |
+
**kwargs,
|
| 438 |
+
):
|
| 439 |
+
if input_ids is not None and inputs_embeds is not None:
|
| 440 |
+
raise ValueError("You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time")
|
| 441 |
+
elif input_ids is not None:
|
| 442 |
+
inputs_embeds = self.embed_tokens(input_ids)
|
| 443 |
+
elif inputs_embeds is None:
|
| 444 |
+
raise ValueError("You have to specify either decoder_input_ids or decoder_inputs_embeds")
|
| 445 |
+
|
| 446 |
+
past_key_values_length = 0
|
| 447 |
+
if past_key_values is not None and len(past_key_values) > 0:
|
| 448 |
+
if past_key_values[0] is not None and past_key_values[0][0] is not None:
|
| 449 |
+
past_key_values_length = past_key_values[0][0].shape[2]
|
| 450 |
+
|
| 451 |
+
if position_ids is None:
|
| 452 |
+
position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + past_key_values_length
|
| 453 |
+
position_ids = position_ids.unsqueeze(0).repeat(inputs_embeds.shape[0], 1)
|
| 454 |
+
|
| 455 |
+
if input_ids is not None:
|
| 456 |
+
positions = self.embed_positions(input_ids, past_key_values_length=past_key_values_length, position_ids=position_ids)
|
| 457 |
+
else:
|
| 458 |
+
positions = self.embed_positions(inputs_embeds, past_key_values_length=past_key_values_length, position_ids=position_ids)
|
| 459 |
+
|
| 460 |
+
hidden_states = inputs_embeds + positions
|
| 461 |
+
hidden_states = F.dropout(hidden_states, p=self.dropout, training=self.training)
|
| 462 |
+
|
| 463 |
+
seq_len = inputs_embeds.shape[1]
|
| 464 |
+
causal_mask = torch.triu(
|
| 465 |
+
torch.ones(seq_len, seq_len, device=hidden_states.device) * float('-inf'),
|
| 466 |
+
diagonal=1
|
| 467 |
+
).unsqueeze(0).unsqueeze(0)
|
| 468 |
+
|
| 469 |
+
if attention_mask is not None:
|
| 470 |
+
padding_mask = (1 - attention_mask) * float('-inf')
|
| 471 |
+
padding_mask = padding_mask.unsqueeze(1).unsqueeze(2)
|
| 472 |
+
attention_mask = causal_mask + padding_mask
|
| 473 |
+
else:
|
| 474 |
+
attention_mask = causal_mask
|
| 475 |
+
|
| 476 |
+
for idx, decoder_layer in enumerate(self.layers):
|
| 477 |
+
if self.training:
|
| 478 |
+
dropout_probability = torch.rand([])
|
| 479 |
+
if dropout_probability < self.layerdrop:
|
| 480 |
+
continue
|
| 481 |
+
|
| 482 |
+
layer_past = past_key_values[idx] if past_key_values is not None and idx < len(past_key_values) else None
|
| 483 |
+
hidden_states, _ = decoder_layer(
|
| 484 |
+
hidden_states,
|
| 485 |
+
attention_mask=attention_mask,
|
| 486 |
+
encoder_hidden_states=encoder_hidden_states,
|
| 487 |
+
encoder_attention_mask=encoder_attention_mask,
|
| 488 |
+
past_key_value=layer_past,
|
| 489 |
+
use_cache=use_cache,
|
| 490 |
+
)
|
| 491 |
+
|
| 492 |
+
hidden_states = self.layer_norm(hidden_states)
|
| 493 |
+
|
| 494 |
+
return BaseModelOutputWithPastAndCrossAttentions(
|
| 495 |
+
last_hidden_state=hidden_states,
|
| 496 |
+
past_key_values=None,
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
class MiniWhisperModel(PreTrainedModel):
|
| 500 |
+
config_class = MiniWhisperConfig
|
| 501 |
+
|
| 502 |
+
def __init__(self, config: MiniWhisperConfig):
|
| 503 |
+
super().__init__(config)
|
| 504 |
+
|
| 505 |
+
self.encoder = MiniWhisperEncoder(config)
|
| 506 |
+
self.decoder = MiniWhisperDecoder(config)
|
| 507 |
+
self.post_init()
|
| 508 |
+
|
| 509 |
+
def get_input_embeddings(self):
|
| 510 |
+
return self.decoder.embed_tokens
|
| 511 |
+
|
| 512 |
+
def set_input_embeddings(self, value):
|
| 513 |
+
self.decoder.embed_tokens = value
|
| 514 |
+
|
| 515 |
+
def _mask_input_features(
|
| 516 |
+
self,
|
| 517 |
+
input_features: torch.FloatTensor,
|
| 518 |
+
attention_mask: torch.LongTensor | None = None,
|
| 519 |
+
):
|
| 520 |
+
"""Apply SpecAugment"""
|
| 521 |
+
import numpy as np
|
| 522 |
+
|
| 523 |
+
if not getattr(self.config, "apply_spec_augment", True):
|
| 524 |
+
return input_features
|
| 525 |
+
|
| 526 |
+
batch_size, hidden_size, sequence_length = input_features.size()
|
| 527 |
+
|
| 528 |
+
if self.config.mask_time_prob > 0 and self.training:
|
| 529 |
+
mask_time_indices = _compute_mask_indices(
|
| 530 |
+
(batch_size, sequence_length),
|
| 531 |
+
mask_prob=self.config.mask_time_prob,
|
| 532 |
+
mask_length=self.config.mask_time_length,
|
| 533 |
+
attention_mask=attention_mask,
|
| 534 |
+
min_masks=self.config.mask_time_min_masks,
|
| 535 |
+
)
|
| 536 |
+
mask_time_indices = torch.tensor(mask_time_indices, device=input_features.device, dtype=torch.bool)
|
| 537 |
+
mask_time_indices = mask_time_indices[:, None].expand(-1, hidden_size, -1)
|
| 538 |
+
input_features[mask_time_indices] = 0
|
| 539 |
+
|
| 540 |
+
if self.config.mask_feature_prob > 0 and self.training:
|
| 541 |
+
mask_feature_indices = _compute_mask_indices(
|
| 542 |
+
(batch_size, hidden_size),
|
| 543 |
+
mask_prob=self.config.mask_feature_prob,
|
| 544 |
+
mask_length=self.config.mask_feature_length,
|
| 545 |
+
min_masks=self.config.mask_feature_min_masks,
|
| 546 |
+
)
|
| 547 |
+
mask_feature_indices = torch.tensor(mask_feature_indices, device=input_features.device, dtype=torch.bool)
|
| 548 |
+
input_features[mask_feature_indices] = 0
|
| 549 |
+
|
| 550 |
+
return input_features
|
| 551 |
+
|
| 552 |
+
def forward(
|
| 553 |
+
self,
|
| 554 |
+
input_features: torch.FloatTensor | None = None,
|
| 555 |
+
attention_mask: torch.LongTensor | None = None,
|
| 556 |
+
decoder_input_ids: torch.LongTensor | None = None,
|
| 557 |
+
decoder_attention_mask: torch.LongTensor | None = None,
|
| 558 |
+
encoder_outputs: tuple[torch.FloatTensor] | None = None,
|
| 559 |
+
past_key_values: tuple[tuple[torch.FloatTensor]] | None = None,
|
| 560 |
+
decoder_inputs_embeds: tuple[torch.FloatTensor] | None = None,
|
| 561 |
+
decoder_position_ids: tuple[torch.LongTensor] | None = None,
|
| 562 |
+
use_cache: bool | None = None,
|
| 563 |
+
**kwargs,
|
| 564 |
+
):
|
| 565 |
+
if encoder_outputs is None:
|
| 566 |
+
input_features = self._mask_input_features(input_features, attention_mask=attention_mask)
|
| 567 |
+
|
| 568 |
+
encoder_outputs = self.encoder(input_features, **kwargs)
|
| 569 |
+
elif not isinstance(encoder_outputs, BaseModelOutput):
|
| 570 |
+
encoder_outputs = BaseModelOutput(
|
| 571 |
+
last_hidden_state=encoder_outputs[0],
|
| 572 |
+
hidden_states=encoder_outputs[1] if len(encoder_outputs) > 1 else None,
|
| 573 |
+
attentions=encoder_outputs[2] if len(encoder_outputs) > 2 else None,
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
decoder_outputs = self.decoder(
|
| 577 |
+
input_ids=decoder_input_ids,
|
| 578 |
+
attention_mask=decoder_attention_mask,
|
| 579 |
+
encoder_hidden_states=encoder_outputs.last_hidden_state,
|
| 580 |
+
past_key_values=past_key_values,
|
| 581 |
+
inputs_embeds=decoder_inputs_embeds,
|
| 582 |
+
position_ids=decoder_position_ids,
|
| 583 |
+
use_cache=use_cache,
|
| 584 |
+
**kwargs,
|
| 585 |
+
)
|
| 586 |
+
|
| 587 |
+
return Seq2SeqModelOutput(
|
| 588 |
+
last_hidden_state=decoder_outputs.last_hidden_state,
|
| 589 |
+
past_key_values=decoder_outputs.past_key_values,
|
| 590 |
+
decoder_hidden_states=decoder_outputs.hidden_states,
|
| 591 |
+
decoder_attentions=decoder_outputs.attentions,
|
| 592 |
+
cross_attentions=decoder_outputs.cross_attentions,
|
| 593 |
+
encoder_last_hidden_state=encoder_outputs.last_hidden_state,
|
| 594 |
+
encoder_hidden_states=encoder_outputs.hidden_states,
|
| 595 |
+
encoder_attentions=encoder_outputs.attentions,
|
| 596 |
+
)
|
| 597 |
+
|
| 598 |
+
class MiniWhisperForConditionalGeneration(PreTrainedModel, GenerationMixin):
|
| 599 |
+
config_class = MiniWhisperConfig
|
| 600 |
+
base_model_prefix = "model"
|
| 601 |
+
_tied_weights_keys = ["proj_out.weight"]
|
| 602 |
+
|
| 603 |
+
def __init__(self, config: MiniWhisperConfig):
|
| 604 |
+
super().__init__(config)
|
| 605 |
+
self.model = MiniWhisperModel(config)
|
| 606 |
+
self.proj_out = nn.Linear(config.d_model, config.vocab_size, bias=False)
|
| 607 |
+
self.max_target_positions = config.max_target_positions
|
| 608 |
+
|
| 609 |
+
self.post_init()
|
| 610 |
+
|
| 611 |
+
def get_output_embeddings(self):
|
| 612 |
+
return self.proj_out
|
| 613 |
+
|
| 614 |
+
def set_output_embeddings(self, new_embeddings):
|
| 615 |
+
self.proj_out = new_embeddings
|
| 616 |
+
|
| 617 |
+
def get_input_embeddings(self):
|
| 618 |
+
return self.model.get_input_embeddings()
|
| 619 |
+
|
| 620 |
+
def forward(
|
| 621 |
+
self,
|
| 622 |
+
input_features: torch.FloatTensor | None = None,
|
| 623 |
+
attention_mask: torch.LongTensor | None = None,
|
| 624 |
+
decoder_input_ids: torch.LongTensor | None = None,
|
| 625 |
+
decoder_attention_mask: torch.LongTensor | None = None,
|
| 626 |
+
encoder_outputs: tuple[torch.FloatTensor] | None = None,
|
| 627 |
+
past_key_values: tuple[tuple[torch.FloatTensor]] | None = None,
|
| 628 |
+
decoder_inputs_embeds: tuple[torch.FloatTensor] | None = None,
|
| 629 |
+
decoder_position_ids: tuple[torch.LongTensor] | None = None,
|
| 630 |
+
labels: torch.LongTensor | None = None,
|
| 631 |
+
use_cache: bool | None = None,
|
| 632 |
+
**kwargs,
|
| 633 |
+
):
|
| 634 |
+
if labels is not None:
|
| 635 |
+
if labels.shape[1] > self.max_target_positions:
|
| 636 |
+
raise ValueError(f"Labels' sequence length {labels.shape[1]} cannot exceed max_target_positions")
|
| 637 |
+
if decoder_input_ids is None and decoder_inputs_embeds is None:
|
| 638 |
+
decoder_input_ids = shift_tokens_right(
|
| 639 |
+
labels, self.config.pad_token_id, self.config.decoder_start_token_id
|
| 640 |
+
)
|
| 641 |
+
|
| 642 |
+
outputs = self.model(
|
| 643 |
+
input_features,
|
| 644 |
+
attention_mask=attention_mask,
|
| 645 |
+
decoder_input_ids=decoder_input_ids,
|
| 646 |
+
encoder_outputs=encoder_outputs,
|
| 647 |
+
decoder_attention_mask=decoder_attention_mask,
|
| 648 |
+
past_key_values=past_key_values,
|
| 649 |
+
decoder_inputs_embeds=decoder_inputs_embeds,
|
| 650 |
+
decoder_position_ids=decoder_position_ids,
|
| 651 |
+
use_cache=use_cache,
|
| 652 |
+
**kwargs,
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
lm_logits = self.proj_out(outputs.last_hidden_state)
|
| 656 |
+
|
| 657 |
+
loss = None
|
| 658 |
+
if labels is not None:
|
| 659 |
+
loss_fct = nn.CrossEntropyLoss()
|
| 660 |
+
labels = labels.to(lm_logits.device)
|
| 661 |
+
loss = loss_fct(lm_logits.view(-1, self.config.vocab_size), labels.view(-1))
|
| 662 |
+
|
| 663 |
+
return Seq2SeqLMOutput(
|
| 664 |
+
loss=loss,
|
| 665 |
+
logits=lm_logits,
|
| 666 |
+
past_key_values=outputs.past_key_values,
|
| 667 |
+
decoder_hidden_states=outputs.decoder_hidden_states,
|
| 668 |
+
decoder_attentions=outputs.decoder_attentions,
|
| 669 |
+
cross_attentions=outputs.cross_attentions,
|
| 670 |
+
encoder_last_hidden_state=outputs.encoder_last_hidden_state,
|
| 671 |
+
encoder_hidden_states=outputs.encoder_hidden_states,
|
| 672 |
+
encoder_attentions=outputs.encoder_attentions,
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
def generate(
|
| 676 |
+
self,
|
| 677 |
+
input_features,
|
| 678 |
+
generation_config=None,
|
| 679 |
+
max_new_tokens=50,
|
| 680 |
+
**kwargs,
|
| 681 |
+
):
|
| 682 |
+
self.eval()
|
| 683 |
+
batch_size = input_features.shape[0]
|
| 684 |
+
device = input_features.device
|
| 685 |
+
|
| 686 |
+
decoder_input_ids = torch.full(
|
| 687 |
+
(batch_size, 1), self.config.decoder_start_token_id, dtype=torch.long, device=device
|
| 688 |
+
)
|
| 689 |
+
|
| 690 |
+
encoder_outputs = self.model.encoder(input_features)
|
| 691 |
+
|
| 692 |
+
for _ in range(max_new_tokens):
|
| 693 |
+
outputs = self.forward(
|
| 694 |
+
decoder_input_ids=decoder_input_ids,
|
| 695 |
+
encoder_outputs=encoder_outputs,
|
| 696 |
+
use_cache=True,
|
| 697 |
+
)
|
| 698 |
+
|
| 699 |
+
next_token_logits = outputs.logits[:, -1, :]
|
| 700 |
+
next_token = torch.argmax(next_token_logits, dim=-1, keepdim=True)
|
| 701 |
+
decoder_input_ids = torch.cat([decoder_input_ids, next_token], dim=1)
|
| 702 |
+
|
| 703 |
+
if (next_token == self.config.eos_token_id).all():
|
| 704 |
+
break
|
| 705 |
+
|
| 706 |
+
return decoder_input_ids
|
| 707 |
+
|
| 708 |
+
def prepare_inputs_for_generation(
|
| 709 |
+
self,
|
| 710 |
+
decoder_input_ids,
|
| 711 |
+
past_key_values=None,
|
| 712 |
+
encoder_outputs=None,
|
| 713 |
+
attention_mask=None,
|
| 714 |
+
use_cache=None,
|
| 715 |
+
**kwargs,
|
| 716 |
+
):
|
| 717 |
+
return {
|
| 718 |
+
"decoder_input_ids": decoder_input_ids,
|
| 719 |
+
"past_key_values": past_key_values,
|
| 720 |
+
"encoder_outputs": encoder_outputs,
|
| 721 |
+
"attention_mask": attention_mask,
|
| 722 |
+
"use_cache": use_cache,
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
def get_encoder(self):
|
| 726 |
+
return self.model.get_encoder()
|
| 727 |
+
|
| 728 |
+
__all__ = [
|
| 729 |
+
"MiniWhisperConfig",
|
| 730 |
+
"MiniWhisperModel",
|
| 731 |
+
"MiniWhisperForConditionalGeneration",
|
| 732 |
+
"MiniWhisperEncoder",
|
| 733 |
+
"MiniWhisperDecoder",
|
| 734 |
+
"MiniWhisperEncoderLayer",
|
| 735 |
+
"MiniWhisperDecoderLayer",
|
| 736 |
+
"MiniWhisperAttention",
|
| 737 |
+
"WhisperPositionalEmbedding",
|
| 738 |
+
]
|
processing_mini_whisper.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# processing_mini_whisper.py
|
| 2 |
+
from typing import Union, List
|
| 3 |
+
from transformers import ProcessorMixin
|
| 4 |
+
from .feature_extraction_mini_whisper import MiniWhisperFeatureExtractor
|
| 5 |
+
from .tokenization_mini_whisper import MiniWhisperTokenizer
|
| 6 |
+
|
| 7 |
+
class MiniWhisperProcessor(ProcessorMixin):
|
| 8 |
+
"""Combines feature extractor and tokenizer."""
|
| 9 |
+
|
| 10 |
+
attributes = ["feature_extractor", "tokenizer"]
|
| 11 |
+
feature_extractor_class = "MiniWhisperFeatureExtractor"
|
| 12 |
+
tokenizer_class = "MiniWhisperTokenizer"
|
| 13 |
+
|
| 14 |
+
def __init__(self, feature_extractor, tokenizer):
|
| 15 |
+
super().__init__(feature_extractor, tokenizer)
|
| 16 |
+
|
| 17 |
+
def __call__(
|
| 18 |
+
self,
|
| 19 |
+
audio=None,
|
| 20 |
+
text=None,
|
| 21 |
+
sampling_rate=None,
|
| 22 |
+
return_tensors="pt",
|
| 23 |
+
**kwargs,
|
| 24 |
+
):
|
| 25 |
+
if audio is not None and text is not None:
|
| 26 |
+
raise ValueError("Cannot specify both audio and text")
|
| 27 |
+
|
| 28 |
+
if audio is not None:
|
| 29 |
+
inputs = self.feature_extractor(
|
| 30 |
+
audio,
|
| 31 |
+
sampling_rate=sampling_rate,
|
| 32 |
+
return_tensors=return_tensors,
|
| 33 |
+
**kwargs,
|
| 34 |
+
)
|
| 35 |
+
return inputs
|
| 36 |
+
|
| 37 |
+
if text is not None:
|
| 38 |
+
encodings = self.tokenizer(
|
| 39 |
+
text,
|
| 40 |
+
return_tensors=return_tensors,
|
| 41 |
+
**kwargs,
|
| 42 |
+
)
|
| 43 |
+
return encodings
|
| 44 |
+
|
| 45 |
+
def get_decoder_prompt_ids(self, task=None, language=None, no_timestamps=True):
|
| 46 |
+
return self.tokenizer.get_decoder_prompt_ids(task=task, language=language, no_timestamps=no_timestamps)
|
| 47 |
+
|
| 48 |
+
def batch_decode(self, sequences, **kwargs):
|
| 49 |
+
return self.tokenizer.batch_decode(sequences, **kwargs)
|
| 50 |
+
|
| 51 |
+
def decode(self, token_ids, **kwargs):
|
| 52 |
+
return self.tokenizer.decode(token_ids, **kwargs)
|
| 53 |
+
|
| 54 |
+
def save_pretrained(self, save_directory):
|
| 55 |
+
self.feature_extractor.save_pretrained(save_directory)
|
| 56 |
+
self.tokenizer.save_pretrained(save_directory)
|
| 57 |
+
|
| 58 |
+
import json
|
| 59 |
+
import os
|
| 60 |
+
config = {
|
| 61 |
+
"feature_extractor_type": "MiniWhisperFeatureExtractor",
|
| 62 |
+
"tokenizer_type": "MiniWhisperTokenizer",
|
| 63 |
+
}
|
| 64 |
+
with open(os.path.join(save_directory, "preprocessor_config.json"), "w") as f:
|
| 65 |
+
json.dump(config, f, indent=2)
|
| 66 |
+
|
| 67 |
+
@classmethod
|
| 68 |
+
def from_pretrained(cls, pretrained_model_name_or_path, **kwargs):
|
| 69 |
+
feature_extractor = MiniWhisperFeatureExtractor.from_pretrained(pretrained_model_name_or_path)
|
| 70 |
+
tokenizer = MiniWhisperTokenizer.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
| 71 |
+
return cls(feature_extractor=feature_extractor, tokenizer=tokenizer)
|
| 72 |
+
|
| 73 |
+
__all__ = ["MiniWhisperProcessor"]
|
tokenization_mini_whisper.py
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# tokenization_mini_whisper.py
|
| 2 |
+
"""Tokenization classes for MiniWhisper"""
|
| 3 |
+
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import re
|
| 7 |
+
from functools import lru_cache
|
| 8 |
+
from typing import List, Optional, Tuple, Union
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
from transformers import PreTrainedTokenizer
|
| 12 |
+
from transformers.tokenization_utils_base import AddedToken
|
| 13 |
+
from transformers.utils import logging
|
| 14 |
+
|
| 15 |
+
logger = logging.get_logger(__name__)
|
| 16 |
+
|
| 17 |
+
VOCAB_FILES_NAMES = {
|
| 18 |
+
"vocab_file": "vocab.json",
|
| 19 |
+
"tokenizer_file": "tokenizer.json",
|
| 20 |
+
"merges_file": "merges.txt",
|
| 21 |
+
"normalizer_file": "normalizer.json",
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
LANGUAGES = {
|
| 25 |
+
"en": "english",
|
| 26 |
+
"zh": "chinese",
|
| 27 |
+
"de": "german",
|
| 28 |
+
"es": "spanish",
|
| 29 |
+
"ru": "russian",
|
| 30 |
+
"ko": "korean",
|
| 31 |
+
"fr": "french",
|
| 32 |
+
"ja": "japanese",
|
| 33 |
+
"pt": "portuguese",
|
| 34 |
+
"tr": "turkish",
|
| 35 |
+
"pl": "polish",
|
| 36 |
+
"ca": "catalan",
|
| 37 |
+
"nl": "dutch",
|
| 38 |
+
"ar": "arabic",
|
| 39 |
+
"sv": "swedish",
|
| 40 |
+
"it": "italian",
|
| 41 |
+
"id": "indonesian",
|
| 42 |
+
"hi": "hindi",
|
| 43 |
+
"fi": "finnish",
|
| 44 |
+
"vi": "vietnamese",
|
| 45 |
+
"he": "hebrew",
|
| 46 |
+
"uk": "ukrainian",
|
| 47 |
+
"el": "greek",
|
| 48 |
+
"ms": "malay",
|
| 49 |
+
"cs": "czech",
|
| 50 |
+
"ro": "romanian",
|
| 51 |
+
"da": "danish",
|
| 52 |
+
"hu": "hungarian",
|
| 53 |
+
"ta": "tamil",
|
| 54 |
+
"no": "norwegian",
|
| 55 |
+
"th": "thai",
|
| 56 |
+
"ur": "urdu",
|
| 57 |
+
"hr": "croatian",
|
| 58 |
+
"bg": "bulgarian",
|
| 59 |
+
"lt": "lithuanian",
|
| 60 |
+
"la": "latin",
|
| 61 |
+
"mi": "maori",
|
| 62 |
+
"ml": "malayalam",
|
| 63 |
+
"cy": "welsh",
|
| 64 |
+
"sk": "slovak",
|
| 65 |
+
"te": "telugu",
|
| 66 |
+
"fa": "persian",
|
| 67 |
+
"lv": "latvian",
|
| 68 |
+
"bn": "bengali",
|
| 69 |
+
"sr": "serbian",
|
| 70 |
+
"az": "azerbaijani",
|
| 71 |
+
"sl": "slovenian",
|
| 72 |
+
"kn": "kannada",
|
| 73 |
+
"et": "estonian",
|
| 74 |
+
"mk": "macedonian",
|
| 75 |
+
"br": "breton",
|
| 76 |
+
"eu": "basque",
|
| 77 |
+
"is": "icelandic",
|
| 78 |
+
"hy": "armenian",
|
| 79 |
+
"ne": "nepali",
|
| 80 |
+
"mn": "mongolian",
|
| 81 |
+
"bs": "bosnian",
|
| 82 |
+
"kk": "kazakh",
|
| 83 |
+
"sq": "albanian",
|
| 84 |
+
"sw": "swahili",
|
| 85 |
+
"gl": "galician",
|
| 86 |
+
"mr": "marathi",
|
| 87 |
+
"pa": "punjabi",
|
| 88 |
+
"si": "sinhala",
|
| 89 |
+
"km": "khmer",
|
| 90 |
+
"sn": "shona",
|
| 91 |
+
"yo": "yoruba",
|
| 92 |
+
"so": "somali",
|
| 93 |
+
"af": "afrikaans",
|
| 94 |
+
"oc": "occitan",
|
| 95 |
+
"ka": "georgian",
|
| 96 |
+
"be": "belarusian",
|
| 97 |
+
"tg": "tajik",
|
| 98 |
+
"sd": "sindhi",
|
| 99 |
+
"gu": "gujarati",
|
| 100 |
+
"am": "amharic",
|
| 101 |
+
"yi": "yiddish",
|
| 102 |
+
"lo": "lao",
|
| 103 |
+
"uz": "uzbek",
|
| 104 |
+
"fo": "faroese",
|
| 105 |
+
"ht": "haitian creole",
|
| 106 |
+
"ps": "pashto",
|
| 107 |
+
"tk": "turkmen",
|
| 108 |
+
"nn": "nynorsk",
|
| 109 |
+
"mt": "maltese",
|
| 110 |
+
"sa": "sanskrit",
|
| 111 |
+
"lb": "luxembourgish",
|
| 112 |
+
"my": "myanmar",
|
| 113 |
+
"bo": "tibetan",
|
| 114 |
+
"tl": "tagalog",
|
| 115 |
+
"mg": "malagasy",
|
| 116 |
+
"as": "assamese",
|
| 117 |
+
"tt": "tatar",
|
| 118 |
+
"haw": "hawaiian",
|
| 119 |
+
"ln": "lingala",
|
| 120 |
+
"ha": "hausa",
|
| 121 |
+
"ba": "bashkir",
|
| 122 |
+
"jw": "javanese",
|
| 123 |
+
"su": "sundanese",
|
| 124 |
+
"yue": "cantonese",
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
TO_LANGUAGE_CODE = {
|
| 128 |
+
**{language: code for code, language in LANGUAGES.items()},
|
| 129 |
+
"burmese": "my",
|
| 130 |
+
"valencian": "ca",
|
| 131 |
+
"flemish": "nl",
|
| 132 |
+
"haitian": "ht",
|
| 133 |
+
"letzeburgesch": "lb",
|
| 134 |
+
"pushto": "ps",
|
| 135 |
+
"panjabi": "pa",
|
| 136 |
+
"moldavian": "ro",
|
| 137 |
+
"moldovan": "ro",
|
| 138 |
+
"sinhalese": "si",
|
| 139 |
+
"castilian": "es",
|
| 140 |
+
"mandarin": "zh",
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
TASK_IDS = ["translate", "transcribe"]
|
| 144 |
+
|
| 145 |
+
class MiniWhisperTokenizer(PreTrainedTokenizer):
|
| 146 |
+
"""
|
| 147 |
+
Construct a MiniWhisper tokenizer.
|
| 148 |
+
|
| 149 |
+
For actual use, load pretrained:
|
| 150 |
+
tokenizer = WhisperTokenizer.from_pretrained("openai/whisper-tiny")
|
| 151 |
+
|
| 152 |
+
Or provide path to trained vocab/merges files:
|
| 153 |
+
tokenizer = MiniWhisperTokenizer(vocab_file="vocab.json", merges_file="merges.txt")
|
| 154 |
+
"""
|
| 155 |
+
|
| 156 |
+
vocab_files_names = VOCAB_FILES_NAMES
|
| 157 |
+
model_input_names = ["input_ids", "attention_mask"]
|
| 158 |
+
|
| 159 |
+
def __init__(
|
| 160 |
+
self,
|
| 161 |
+
vocab_file=None,
|
| 162 |
+
merges_file=None,
|
| 163 |
+
normalizer_file=None,
|
| 164 |
+
tokenizer_file=None,
|
| 165 |
+
unk_token="<|endoftext|>",
|
| 166 |
+
bos_token="<|endoftext|>",
|
| 167 |
+
eos_token="<|endoftext|>",
|
| 168 |
+
pad_token="<|endoftext|>",
|
| 169 |
+
add_prefix_space=False,
|
| 170 |
+
language=None,
|
| 171 |
+
task=None,
|
| 172 |
+
predict_timestamps=False,
|
| 173 |
+
**kwargs,
|
| 174 |
+
):
|
| 175 |
+
bos_token = AddedToken(bos_token, lstrip=False, rstrip=False, normalized=False, special=True) if isinstance(bos_token, str) else bos_token
|
| 176 |
+
eos_token = AddedToken(eos_token, lstrip=False, rstrip=False, normalized=False, special=True) if isinstance(eos_token, str) else eos_token
|
| 177 |
+
unk_token = AddedToken(unk_token, lstrip=False, rstrip=False, normalized=False, special=True) if isinstance(unk_token, str) else unk_token
|
| 178 |
+
pad_token = AddedToken(pad_token, lstrip=False, rstrip=False, normalized=False, special=True) if isinstance(pad_token, str) else pad_token
|
| 179 |
+
|
| 180 |
+
self._vocab = {}
|
| 181 |
+
self._merges = []
|
| 182 |
+
|
| 183 |
+
if vocab_file is not None and os.path.isfile(vocab_file):
|
| 184 |
+
with open(vocab_file, encoding="utf-8") as f:
|
| 185 |
+
self._vocab = json.load(f)
|
| 186 |
+
|
| 187 |
+
if merges_file is not None and os.path.isfile(merges_file):
|
| 188 |
+
with open(merges_file, encoding="utf-8") as f:
|
| 189 |
+
lines = f.read().splitlines()
|
| 190 |
+
if lines and lines[0] == "#version: 0.2":
|
| 191 |
+
self._merges = lines[1:]
|
| 192 |
+
else:
|
| 193 |
+
self._merges = lines
|
| 194 |
+
|
| 195 |
+
self.english_spelling_normalizer = None
|
| 196 |
+
if normalizer_file is not None and os.path.isfile(normalizer_file):
|
| 197 |
+
with open(normalizer_file, encoding="utf-8") as f:
|
| 198 |
+
self.english_spelling_normalizer = json.load(f)
|
| 199 |
+
|
| 200 |
+
self.add_prefix_space = add_prefix_space
|
| 201 |
+
self.language = language
|
| 202 |
+
self.task = task
|
| 203 |
+
self.predict_timestamps = predict_timestamps
|
| 204 |
+
self.timestamp_pat = re.compile(r"<\|(\d+\.\d+)\|>")
|
| 205 |
+
|
| 206 |
+
super().__init__(
|
| 207 |
+
unk_token=unk_token,
|
| 208 |
+
bos_token=bos_token,
|
| 209 |
+
eos_token=eos_token,
|
| 210 |
+
pad_token=pad_token,
|
| 211 |
+
add_prefix_space=add_prefix_space,
|
| 212 |
+
**kwargs,
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
self.set_prefix_tokens()
|
| 216 |
+
|
| 217 |
+
@property
|
| 218 |
+
def vocab_size(self) -> int:
|
| 219 |
+
if self._vocab:
|
| 220 |
+
return len(self._vocab)
|
| 221 |
+
return 51865
|
| 222 |
+
|
| 223 |
+
def get_vocab(self):
|
| 224 |
+
if self._vocab:
|
| 225 |
+
return dict(self._vocab, **self.added_tokens_encoder)
|
| 226 |
+
return {self.unk_token: 0, self.bos_token: 1, self.eos_token: 2, self.pad_token: 3}
|
| 227 |
+
|
| 228 |
+
def _tokenize(self, text, **kwargs):
|
| 229 |
+
"""Tokenize a string using BPE"""
|
| 230 |
+
if not self._vocab or not self._merges:
|
| 231 |
+
return list(text)
|
| 232 |
+
|
| 233 |
+
tokens = []
|
| 234 |
+
i = 0
|
| 235 |
+
text = text.lower()
|
| 236 |
+
|
| 237 |
+
while i < len(text):
|
| 238 |
+
if i < len(text) - 1:
|
| 239 |
+
bigram = text[i:i+2]
|
| 240 |
+
if bigram in self._merges:
|
| 241 |
+
tokens.append(bigram)
|
| 242 |
+
i += 2
|
| 243 |
+
continue
|
| 244 |
+
tokens.append(text[i])
|
| 245 |
+
i += 1
|
| 246 |
+
|
| 247 |
+
return tokens
|
| 248 |
+
|
| 249 |
+
def _convert_token_to_id(self, token):
|
| 250 |
+
if self._vocab:
|
| 251 |
+
return self._vocab.get(token, self._vocab.get(self.unk_token))
|
| 252 |
+
index = len(self._vocab) if self._vocab else 0
|
| 253 |
+
if token == self.unk_token:
|
| 254 |
+
return index
|
| 255 |
+
if token == self.bos_token:
|
| 256 |
+
return index + 1
|
| 257 |
+
if token == self.eos_token:
|
| 258 |
+
return index + 2
|
| 259 |
+
if token == self.pad_token:
|
| 260 |
+
return index + 3
|
| 261 |
+
return index
|
| 262 |
+
|
| 263 |
+
def _convert_id_to_token(self, index):
|
| 264 |
+
if hasattr(self, 'ids_to_tokens') and self.ids_to_tokens:
|
| 265 |
+
return self.ids_to_tokens.get(index, self.unk_token)
|
| 266 |
+
return super()._convert_id_to_token(index)
|
| 267 |
+
|
| 268 |
+
def convert_tokens_to_string(self, tokens):
|
| 269 |
+
return "".join(tokens)
|
| 270 |
+
|
| 271 |
+
@property
|
| 272 |
+
def prefix_tokens(self) -> List[int]:
|
| 273 |
+
bos_token_id = self.convert_tokens_to_ids("<|startoftranscript|>")
|
| 274 |
+
transcribe_token_id = self.convert_tokens_to_ids("<|transcribe|>")
|
| 275 |
+
translate_token_id = self.convert_tokens_to_ids("<|translate|>")
|
| 276 |
+
notimestamps_token_id = self.convert_tokens_to_ids("<|notimestamps|>")
|
| 277 |
+
langs = tuple(LANGUAGES.keys())
|
| 278 |
+
|
| 279 |
+
if self.language is not None:
|
| 280 |
+
self.language = self.language.lower()
|
| 281 |
+
if self.language in TO_LANGUAGE_CODE:
|
| 282 |
+
language_id = TO_LANGUAGE_CODE[self.language]
|
| 283 |
+
elif self.language in TO_LANGUAGE_CODE.values():
|
| 284 |
+
language_id = self.language
|
| 285 |
+
else:
|
| 286 |
+
language_id = "en"
|
| 287 |
+
|
| 288 |
+
bos_sequence = [bos_token_id]
|
| 289 |
+
|
| 290 |
+
if self.language is not None:
|
| 291 |
+
bos_sequence.append(bos_token_id + 1 + langs.index(language_id))
|
| 292 |
+
|
| 293 |
+
if self.task is not None:
|
| 294 |
+
if self.task == "transcribe":
|
| 295 |
+
bos_sequence.append(transcribe_token_id)
|
| 296 |
+
elif self.task == "translate":
|
| 297 |
+
bos_sequence.append(translate_token_id)
|
| 298 |
+
|
| 299 |
+
if not self.predict_timestamps:
|
| 300 |
+
bos_sequence.append(notimestamps_token_id)
|
| 301 |
+
|
| 302 |
+
return bos_sequence
|
| 303 |
+
|
| 304 |
+
def set_prefix_tokens(self, language=None, task=None, predict_timestamps=None):
|
| 305 |
+
if language is not None:
|
| 306 |
+
self.language = language
|
| 307 |
+
if task is not None:
|
| 308 |
+
self.task = task
|
| 309 |
+
if predict_timestamps is not None:
|
| 310 |
+
self.predict_timestamps = predict_timestamps
|
| 311 |
+
|
| 312 |
+
def build_inputs_with_special_tokens(self, token_ids_0, token_ids_1=None) -> List[int]:
|
| 313 |
+
prefix = self.prefix_tokens
|
| 314 |
+
eos = [self.eos_token_id]
|
| 315 |
+
|
| 316 |
+
if token_ids_1 is None:
|
| 317 |
+
return prefix + token_ids_0 + eos
|
| 318 |
+
return prefix + token_ids_0 + token_ids_1 + eos
|
| 319 |
+
|
| 320 |
+
def get_special_tokens_mask(
|
| 321 |
+
self, token_ids_0: List[int], token_ids_1: Optional[List[int]] = None,
|
| 322 |
+
already_has_special_tokens: bool = False
|
| 323 |
+
) -> List[int]:
|
| 324 |
+
if already_has_special_tokens:
|
| 325 |
+
return super().get_special_tokens_mask(
|
| 326 |
+
token_ids_0=token_ids_0, token_ids_1=token_ids_1,
|
| 327 |
+
already_has_special_tokens=True
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
prefix_ones = [1] * len(self.prefix_tokens)
|
| 331 |
+
suffix_ones = [1]
|
| 332 |
+
|
| 333 |
+
if token_ids_1 is None:
|
| 334 |
+
return prefix_ones + ([0] * len(token_ids_0)) + suffix_ones
|
| 335 |
+
return prefix_ones + ([0] * len(token_ids_0)) + ([0] * len(token_ids_1)) + suffix_ones
|
| 336 |
+
|
| 337 |
+
def get_decoder_prompt_ids(self, task=None, language=None, no_timestamps=True):
|
| 338 |
+
self.set_prefix_tokens(task=task, language=language, predict_timestamps=not no_timestamps)
|
| 339 |
+
forced_tokens = self.prefix_tokens[1:]
|
| 340 |
+
forced_decoder_ids = [(rank + 1, token_id) for rank, token_id in enumerate(forced_tokens)]
|
| 341 |
+
return forced_decoder_ids
|
| 342 |
+
|
| 343 |
+
def _decode(
|
| 344 |
+
self, token_ids, skip_special_tokens=False, clean_up_tokenization_spaces=True,
|
| 345 |
+
normalize=False, basic_normalize=False, remove_diacritics=False, **kwargs
|
| 346 |
+
) -> str:
|
| 347 |
+
filtered_ids = token_ids
|
| 348 |
+
if skip_special_tokens:
|
| 349 |
+
filtered_ids = [t for t in token_ids if t not in self.all_special_ids]
|
| 350 |
+
|
| 351 |
+
tokens = self.convert_ids_to_tokens(filtered_ids)
|
| 352 |
+
text = self.convert_tokens_to_string(tokens)
|
| 353 |
+
|
| 354 |
+
if clean_up_tokenization_spaces:
|
| 355 |
+
text = re.sub(r"\s+", " ", text).strip()
|
| 356 |
+
|
| 357 |
+
text = re.sub(self.timestamp_pat, "", text)
|
| 358 |
+
|
| 359 |
+
return text
|
| 360 |
+
|
| 361 |
+
def decode(
|
| 362 |
+
self, token_ids, skip_special_tokens=False, clean_up_tokenization_spaces=True,
|
| 363 |
+
output_offsets=False, time_precision=0.02, decode_with_timestamps=False, **kwargs
|
| 364 |
+
) -> str:
|
| 365 |
+
return self._decode(token_ids, skip_special_tokens, clean_up_tokenization_spaces, **kwargs)
|
| 366 |
+
|
| 367 |
+
def save_vocabulary(self, save_directory: str, filename_prefix: Optional[str] = None) -> Tuple[str]:
|
| 368 |
+
if not os.path.isdir(save_directory):
|
| 369 |
+
logger.error(f"Vocabulary path ({save_directory}) should be a directory")
|
| 370 |
+
return
|
| 371 |
+
|
| 372 |
+
vocab_file = os.path.join(
|
| 373 |
+
save_directory,
|
| 374 |
+
(filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["vocab_file"]
|
| 375 |
+
)
|
| 376 |
+
merges_file = os.path.join(
|
| 377 |
+
save_directory,
|
| 378 |
+
(filename_prefix + "-" if filename_prefix else "") + VOCAB_FILES_NAMES["merges_file"]
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
if self._vocab:
|
| 382 |
+
with open(vocab_file, "w", encoding="utf-8") as f:
|
| 383 |
+
json.dump(self._vocab, f, indent=2, ensure_ascii=False)
|
| 384 |
+
|
| 385 |
+
if self._merges:
|
| 386 |
+
with open(merges_file, "w", encoding="utf-8") as f:
|
| 387 |
+
f.write("#version: 0.2\n")
|
| 388 |
+
f.writelines(merge + "\n" for merge in self._merges)
|
| 389 |
+
|
| 390 |
+
return (vocab_file, merges_file)
|
| 391 |
+
|
| 392 |
+
@lru_cache
|
| 393 |
+
def timestamp_ids(self, time_precision=0.02):
|
| 394 |
+
return self.convert_tokens_to_ids([("<|%.2f|>" % (i * time_precision)) for i in range(1500 + 1)])
|
| 395 |
+
|
| 396 |
+
__all__ = ["MiniWhisperTokenizer"]
|