3
4
import math
5
from collections.abc import Mapping
6
+from typing import Literal, cast
7
8
+import numpy as np
9
import torch
10
+from mistral_common.protocol.instruct.chunk import RawAudio
11
+from mistral_common.protocol.transcription.request import (
12
+ StreamingMode,
13
+ TranscriptionRequest,
14
+)
15
+from mistral_common.tokens.tokenizers.audio import Audio
16
9
-from vllm.config.vllm import VllmConfig
17
+from vllm.config import ModelConfig, SpeechToTextConfig, VllmConfig
18
+from vllm.inputs.data import PromptType
19
from vllm.logger import init_logger
20
from vllm.model_executor.models.interfaces import MultiModalEmbeddings
21
from vllm.model_executor.models.voxtral import (
36
)
37
from vllm.multimodal.profiling import BaseDummyInputsBuilder
38
from vllm.sequence import IntermediateTensors
39
+from vllm.tokenizers import cached_tokenizer_from_config
40
41
from .utils import (
42
_flatten_embeddings,
215
"For streaming you must provide an audio input at every step."
216
)
217
208
- multiple_of = self.audio_config.raw_audio_length_per_tok
209
- assert all(
210
- (this_audio := audio.shape[0]) % multiple_of == 0 for audio in audio_inputs
211
- ), (
212
- f"Every input audio waveform has to be a multiple of {multiple_of}, but"
213
- f" one is {this_audio} with {(this_audio / multiple_of)=}."
214
- )
218
+ def _truncate_left(
219
+ sample: torch.Tensor, mult_of: int, pos: int
220
+ ) -> torch.Tensor:
221
+ assert pos in [0, 1], pos
222
+ if (ctx := sample.shape[pos] % mult_of) != 0:
223
+ sample = sample[ctx:] if pos == 0 else sample[:, ctx:]
224
+ assert sample.shape[pos] > 0, (
225
+ f"Sample is empty after truncation with ctx {ctx}"
226
+ )
227
+
228
+ return sample
229
230
mel_features = [
231
self.whisper_encoder.compute_whisper_melspec(audio).to(
233
)
234
for audio in audio_inputs
235
]
236
+
237
+ # we truncate the left most mel feature
238
+ # if the sequence length in impair
239
+ mel_features = [_truncate_left(mel, 2, 1) for mel in mel_features]
240
+
241
seq_lens = [mel.shape[1] for mel in mel_features]
242
# [total_num_20ms_frames, hidden_size]
243
audio_embeddings = self.whisper_encoder.whisper_encoder.forward_conv(
244
mel_features
226
- )[0]
245
+ )
246
conv_stride = self.whisper_encoder.whisper_encoder.total_stride
247
audio_embeddings_per_sample = audio_embeddings.split(
248
[s // conv_stride for s in seq_lens], dim=0
250
251
# audio_embeddings per sample need to be divisible by 4
252
pool_size = self.config.audio_config.block_pool_size
234
- assert all(
235
- (this_shape := sample.shape[0]) % pool_size == 0
253
+
254
+ audio_embeddings_per_sample = [
255
+ _truncate_left(sample, pool_size, 0)
256
for sample in audio_embeddings_per_sample
237
- ), f"Every audio embedding has to be a multiple of 4, but one is {this_shape}."
257
+ ]
258
259
audio_embeddings_per_sample = [
260
e.view(e.shape[0] // pool_size, e.shape[1] * pool_size)
261
for e in audio_embeddings_per_sample
262
]
263
return audio_embeddings_per_sample
264
+
265
+ @classmethod
266
+ def get_speech_to_text_config(
267
+ cls, model_config: ModelConfig, task_type: str
268
+ ) -> SpeechToTextConfig:
269
+ tokenizer = cached_tokenizer_from_config(model_config)
270
+ audio_config = tokenizer.instruct.audio_encoder.audio_config
271
+ sample_rate = audio_config.sampling_rate
272
+ return SpeechToTextConfig(
273
+ max_audio_clip_s=None, # only limited by memory
274
+ sample_rate=sample_rate,
275
+ min_energy_split_window_size=None,
276
+ )
277
+
278
+ @classmethod
279
+ # for speech-to-text transcription
280
+ def get_generation_prompt(
281
+ cls,
282
+ audio: np.ndarray,
283
+ model_config: ModelConfig,
284
+ stt_config: SpeechToTextConfig,
285
+ language: str | None,
286
+ task_type: Literal["transcribe", "translate"],
287
+ request_prompt: str,
288
+ to_language: str | None,
289
+ ) -> PromptType:
290
+ tokenizer = cached_tokenizer_from_config(model_config)
291
+ audio = Audio(audio, int(stt_config.sample_rate), format="wav") # lossless
292
+
293
+ req = TranscriptionRequest(
294
+ model=model_config.model,
295
+ audio=RawAudio.from_audio(audio),
296
+ language=language,
297
+ streaming=StreamingMode.OFFLINE,
298
+ )
299
+
300
+ tokenized = tokenizer.instruct.encode_transcription(req)
301
+ audio = (tokenized.audios[0].audio_array, stt_config.sample_rate)
302
+ prompts_dict = {"multi_modal_data": {"audio": audio}}
303
+ prompts_dict["prompt_token_ids"] = tokenized.tokens
304
+ return cast(PromptType, prompts_dict)