main
py 882 lines 31.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import math
5 from collections.abc import Callable, Iterable, Mapping, Sequence
6 from functools import partial
7 from typing import cast
8
9 import numpy as np
10 import regex as re
11 import torch
12 import torch.nn as nn
13 from mistral_common.audio import mel_filter_bank
14 from mistral_common.protocol.instruct.chunk import AudioChunk, TextChunk
15 from mistral_common.protocol.instruct.messages import UserMessage
16 from mistral_common.protocol.instruct.request import ChatCompletionRequest
17 from mistral_common.protocol.transcription.request import TranscriptionRequest
18 from mistral_common.tokens.tokenizers.audio import Audio
19 from transformers import WhisperConfig
20
21 from vllm.config import ModelConfig, SpeechToTextConfig, VllmConfig
22 from vllm.config.multimodal import MultiModalDummyOptions
23 from vllm.config.speech_to_text import SpeechToTextParams
24 from vllm.inputs import MultiModalDataDict, PromptType, TokensPrompt
25 from vllm.logger import init_logger
26 from vllm.model_executor.layers.quantization import QuantizationConfig
27 from vllm.model_executor.model_loader.weight_utils import default_weight_loader
28 from vllm.model_executor.models import SupportsPP
29 from vllm.model_executor.models.module_mapping import MultiModelKeys
30 from vllm.model_executor.models.whisper import (
31 WhisperEncoder,
32 _create_fake_bias_for_k_proj,
33 )
34 from vllm.model_executor.models.whisper_causal import WhisperCausalEncoder
35 from vllm.multimodal import MULTIMODAL_REGISTRY
36 from vllm.multimodal.inputs import (
37 MultiModalFieldConfig,
38 MultiModalKwargsItems,
39 NestedTensors,
40 )
41 from vllm.multimodal.parse import (
42 AudioProcessorItems,
43 MultiModalDataItems,
44 MultiModalDataParser,
45 )
46 from vllm.multimodal.processing import BaseDummyInputsBuilder
47 from vllm.multimodal.processing.processor import (
48 BaseMultiModalProcessor,
49 BaseProcessingInfo,
50 HFMultiModalInputs,
51 MultiModalProcessingResult,
52 PlaceholderFeaturesInfo,
53 ProcessorInputs,
54 PromptReplacement,
55 PromptUpdate,
56 )
57 from vllm.sequence import IntermediateTensors
58 from vllm.tokenizers import cached_tokenizer_from_config
59 from vllm.tokenizers.mistral import MistralTokenizer
60 from vllm.transformers_utils.processors.voxtral import (
61 MistralCommonFeatureExtractor,
62 MistralCommonVoxtralProcessor,
63 )
64
65 from .interfaces import SupportsLoRA, SupportsMultiModal, SupportsTranscription
66 from .utils import init_vllm_registered_model, maybe_prefix
67
68 logger = init_logger(__name__)
69
70 ISO639_1_SUPPORTED_LANGS = {
71 "ar": "Arabic",
72 "nl": "Dutch",
73 "en": "English",
74 "fr": "French",
75 "de": "German",
76 "hi": "Hindi",
77 "it": "Italian",
78 "pt": "Portuguese",
79 "es": "Spanish",
80 }
81
82
83 class VoxtralProcessingInfo(BaseProcessingInfo):
84 def get_tokenizer(self) -> MistralTokenizer:
85 tokenizer = cached_tokenizer_from_config(self.ctx.model_config)
86 if not isinstance(tokenizer, MistralTokenizer):
87 raise ValueError("This model requires `--tokenizer-mode mistral`")
88
89 return tokenizer
90
91 def get_feature_extractor(self) -> MistralCommonFeatureExtractor:
92 return MistralCommonFeatureExtractor(
93 self.get_tokenizer().instruct.audio_encoder
94 )
95
96 def get_hf_processor(self, **kwargs) -> MistralCommonVoxtralProcessor:
97 return MistralCommonVoxtralProcessor(
98 tokenizer=self.get_tokenizer(),
99 feature_extractor=self.get_feature_extractor(),
100 )
101
102 def get_data_parser(self):
103 feature_extractor = self.get_feature_extractor()
104
105 return MultiModalDataParser(
106 target_sr=feature_extractor.sampling_rate,
107 target_channels=1,
108 expected_hidden_size=self._get_expected_hidden_size(),
109 )
110
111 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
112 return {"audio": 5} # Performance tends to degrade after 5
113
114 def get_mm_max_tokens_per_item(
115 self,
116 seq_len: int,
117 mm_counts: Mapping[str, int],
118 ) -> Mapping[str, int]:
119 return {"audio": self.get_max_audio_tokens()}
120
121 def get_max_audio_tokens(self) -> int:
122 return self.ctx.model_config.max_model_len
123
124 def get_max_audio_array_len(self) -> int:
125 feature_extractor = self.get_feature_extractor()
126
127 return self.get_max_audio_tokens() * int(
128 feature_extractor.sampling_rate // feature_extractor.frame_rate
129 )
130
131
132 class VoxtralDummyInputsBuilder(BaseDummyInputsBuilder[VoxtralProcessingInfo]):
133 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
134 return ""
135
136 def get_dummy_mm_data(
137 self,
138 seq_len: int,
139 mm_counts: Mapping[str, int],
140 mm_options: MultiModalDummyOptions,
141 ) -> MultiModalDataDict:
142 target_length = self.info.get_max_audio_array_len()
143
144 return {
145 "audio": self._get_dummy_audios(
146 length=target_length,
147 num_audios=mm_counts.get("audio", 0),
148 overrides=mm_options.get("audio"),
149 )
150 }
151
152
153 class VoxtralMultiModalProcessor(BaseMultiModalProcessor[VoxtralProcessingInfo]):
154 def get_dummy_inputs(
155 self,
156 seq_len: int,
157 mm_counts: Mapping[str, int],
158 mm_options: MultiModalDummyOptions,
159 # For test_common.py only
160 mm_data: MultiModalDataDict | None = None,
161 ) -> ProcessorInputs:
162 builder = self.dummy_inputs
163 tokenizer = self.info.get_tokenizer()
164 feature_extractor = self.info.get_feature_extractor()
165
166 dummy_text = builder.get_dummy_text(mm_counts)
167 dummy_mm_data = (
168 builder.get_dummy_mm_data(seq_len, mm_counts, mm_options)
169 if mm_data is None
170 else mm_data
171 )
172 dummy_mm_items = self.info.parse_mm_data(dummy_mm_data)
173 dummy_audios = (
174 [] if "audio" not in dummy_mm_data else dummy_mm_items["audio"].get_all()
175 )
176
177 audio_chunks: list[AudioChunk] = []
178 format = "wav"
179 for audio in dummy_audios:
180 audio_item = Audio(
181 audio_array=audio,
182 sampling_rate=feature_extractor.sampling_rate,
183 format=format,
184 )
185 chunk = AudioChunk.from_audio(audio_item)
186 audio_chunks.append(chunk)
187
188 request = ChatCompletionRequest(
189 messages=[
190 UserMessage(content=[TextChunk(text=dummy_text), *audio_chunks]),
191 ]
192 )
193 res = tokenizer.mistral.encode_chat_completion(request)
194 dummy_tokens = res.tokens
195
196 dummy_mm_items = self.info.parse_mm_data(
197 # whixtral tokenizer adds padding to the audio
198 # so we need to update the audio arrays
199 {**dummy_mm_data, "audio": [a.audio_array for a in res.audios]},
200 )
201
202 return ProcessorInputs(prompt=dummy_tokens, mm_data_items=dummy_mm_items)
203
204 # The tokens are already inserted by the chat template,
205 # so we just double check that they exist
206 def _maybe_apply_prompt_updates(
207 self,
208 mm_items: MultiModalDataItems,
209 mm_res: MultiModalProcessingResult,
210 ) -> tuple[list[int], Mapping[str, list[PlaceholderFeaturesInfo]]]:
211 mm_item_counts = mm_items.get_all_counts()
212 self._validate_mm_kwargs(mm_res.kwargs, mm_item_counts)
213 self._validate_mm_updates(mm_res.prompt_updates, mm_item_counts)
214
215 mm_placeholders = self._find_mm_placeholders(
216 mm_res.prompt_ids,
217 mm_res.prompt_updates,
218 )
219 self._validate_mm_placeholders(mm_placeholders, mm_item_counts)
220
221 return mm_res.prompt_ids, mm_placeholders
222
223 def _get_mm_fields_config(
224 self,
225 hf_inputs: Mapping[str, NestedTensors],
226 hf_processor_mm_kwargs: Mapping[str, object],
227 ) -> Mapping[str, MultiModalFieldConfig]:
228 return dict(audio_arrays=MultiModalFieldConfig.batched("audio"))
229
230 def _validate_mm_placeholders(
231 self,
232 mm_placeholders: Mapping[str, list[PlaceholderFeaturesInfo]],
233 mm_item_counts: Mapping[str, int],
234 ) -> None:
235 # mistral_common's tokenizer's does not follow HF's placeholder norms
236 # skip validation here
237 pass
238
239 def _get_hf_mm_text(self, mm_counts: Mapping[str, int]) -> str:
240 return self.dummy_inputs.get_dummy_text(mm_counts)
241
242 def _get_hf_mm_inputs(
243 self,
244 mm_items: MultiModalDataItems,
245 hf_kwargs: Mapping[str, object],
246 ) -> HFMultiModalInputs:
247 hf_inputs = super()._get_hf_mm_inputs(mm_items, hf_kwargs)
248
249 # Avoid padding issue
250 return hf_inputs._replace(
251 hf_kwargs=dict(hf_inputs.hf_kwargs, return_tensors=None)
252 )
253
254 def _get_prompt_updates(
255 self,
256 mm_items: MultiModalDataItems,
257 hf_processor_mm_kwargs: Mapping[str, object],
258 out_mm_kwargs: MultiModalKwargsItems,
259 ) -> Sequence[PromptUpdate]:
260 processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
261 feature_extractor = processor.feature_extractor
262
263 audio_id = processor.audio_token_id
264 out_mm_data = out_mm_kwargs.require_data()
265 out_audio_items = out_mm_data.get("audio", [])
266
267 def get_replacement(item_idx: int):
268 if item_idx < len(out_audio_items):
269 out_audio_data = out_audio_items[item_idx].get_data()
270 audio_arr = out_audio_data["audio_arrays"]
271 if isinstance(audio_arr, (torch.Tensor, np.ndarray)):
272 audio_len = len(audio_arr)
273 else:
274 raise TypeError(
275 "Unexpected type for audio_arrays in out_mm_kwargs: "
276 f"{type(audio_arr)}"
277 )
278 else:
279 # Fallback for unexpected processor outputs.
280 audios = mm_items.get_items("audio", AudioProcessorItems)
281 audio_len = audios.get_audio_length(item_idx)
282
283 nb_audio_tokens = feature_extractor.get_num_audio_tokens(audio_len)
284
285 return [audio_id] * nb_audio_tokens
286
287 return [
288 PromptReplacement(
289 modality="audio",
290 target=[], # Never match the prompt (see below note)
291 replacement=get_replacement,
292 ),
293 ]
294
295
296 @MULTIMODAL_REGISTRY.register_processor(
297 VoxtralMultiModalProcessor,
298 info=VoxtralProcessingInfo,
299 dummy_inputs=VoxtralDummyInputsBuilder,
300 )
301 class VoxtralForConditionalGeneration(
302 nn.Module, SupportsMultiModal, SupportsPP, SupportsLoRA, SupportsTranscription
303 ):
304 supported_languages = ISO639_1_SUPPORTED_LANGS
305 # transformers' currently has limited support for MistralCommon backend
306 # and cached_get_processor. Let's skip until fixed
307 skip_warmup_audio_preprocessing = True
308
309 packed_modules_mapping = {
310 "qkv_proj": ["q_proj", "k_proj", "v_proj"],
311 "gate_up_proj": ["gate_proj", "up_proj"],
312 }
313
314 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
315 super().__init__()
316 self.tokenizer = cached_tokenizer_from_config(vllm_config.model_config)
317
318 # update quant config to so that ignored module and target module names
319 # match the vLLM model names
320 if vllm_config.quant_config is not None:
321 vllm_config.quant_config = self.maybe_update_quant_config(
322 vllm_config.quant_config
323 )
324
325 config = vllm_config.model_config.hf_config
326 self.config = config
327 self.downsample_factor = self.config.audio_config.downsample_factor
328
329 with self._mark_language_model(vllm_config):
330 self.language_model = init_vllm_registered_model(
331 vllm_config=vllm_config,
332 hf_config=config.text_config,
333 prefix=maybe_prefix(prefix, "language_model"),
334 )
335
336 with self._mark_tower_model(vllm_config, "audio"):
337 self.whisper_encoder = VoxtralEncoderModel(
338 vllm_config.with_hf_config(config.audio_config),
339 prefix=maybe_prefix(prefix, "whisper_encoder"),
340 )
341 self.audio_language_adapter = AudioLanguageAdapter(
342 hidden_size=config.audio_config.d_model * self.downsample_factor,
343 dim=config.text_config.hidden_size,
344 )
345
346 self.make_empty_intermediate_tensors = (
347 self.language_model.make_empty_intermediate_tensors
348 )
349
350 def get_mm_mapping(self) -> MultiModelKeys:
351 """Get module prefix for multimodal models to filter LoRA modules."""
352 return MultiModelKeys.from_string_field(
353 language_model="language_model",
354 connector="audio_language_adapter",
355 tower_model=["whisper_encoder"],
356 )
357
358 def forward(
359 self,
360 input_ids: torch.Tensor | None,
361 positions: torch.Tensor,
362 intermediate_tensors: IntermediateTensors | None = None,
363 inputs_embeds: torch.Tensor | None = None,
364 **kwargs: object,
365 ) -> torch.Tensor | IntermediateTensors:
366 if intermediate_tensors is not None:
367 inputs_embeds = None
368
369 hidden_states = self.language_model.model(
370 input_ids, positions, intermediate_tensors, inputs_embeds=inputs_embeds
371 )
372
373 return hidden_states
374
375 def embed_multimodal(
376 self, **kwargs
377 ) -> list[torch.Tensor] | torch.Tensor | tuple[torch.Tensor, ...] | None:
378 audio_inputs = self._parse_and_validate_audio_arrays(**kwargs)
379 if audio_inputs is None:
380 return None
381
382 audio_embeddings = self.whisper_encoder(audio_inputs)
383
384 for i, audio_embedding in enumerate(audio_embeddings):
385 seq_len, dim = audio_embedding.shape
386 # Pad such that seq_len is divisible by downsample_factor
387 target_seq_len = self.downsample_factor * math.ceil(
388 seq_len / self.downsample_factor
389 )
390 audio_embedding = torch.nn.functional.pad(
391 audio_embedding,
392 (0, 0, 0, target_seq_len - seq_len),
393 )
394 audio_embeddings[i] = audio_embedding.reshape(
395 target_seq_len // self.downsample_factor, dim * self.downsample_factor
396 )
397
398 # Concat, project and resplit
399 audio_embeddings_packed = torch.cat(audio_embeddings, dim=0)
400 audio_embeddings_packed = self.audio_language_adapter(audio_embeddings_packed)
401 audio_embeddings = torch.split(
402 audio_embeddings_packed, [a.shape[0] for a in audio_embeddings], dim=0
403 )
404
405 return audio_embeddings
406
407 def _parse_and_validate_audio_arrays(
408 self, **kwargs: object
409 ) -> list[torch.Tensor] | None:
410 audio_arrays = kwargs.pop("audio_arrays", None)
411 if audio_arrays is None:
412 return None
413
414 if not isinstance(audio_arrays, (torch.Tensor, list)):
415 raise ValueError(
416 f"Incorrect type of audio_arrays. Got type: {type(audio_arrays)}"
417 )
418
419 if isinstance(audio_arrays, torch.Tensor):
420 audio_arrays = list(audio_arrays.unbind(0))
421 return audio_arrays
422
423 def compute_logits(
424 self,
425 hidden_states: torch.Tensor,
426 ) -> torch.Tensor | None:
427 return self.language_model.compute_logits(hidden_states)
428
429 @classmethod
430 def get_speech_to_text_config(
431 cls, model_config: ModelConfig, task_type: str
432 ) -> SpeechToTextConfig:
433 tokenizer = cached_tokenizer_from_config(model_config)
434 audio_config = tokenizer.instruct.audio_encoder.audio_config
435 max_audio_clip_s = audio_config.chunk_length_s
436 sample_rate = audio_config.sampling_rate
437 return SpeechToTextConfig(
438 max_audio_clip_s=max_audio_clip_s,
439 sample_rate=sample_rate,
440 # mistral_common and whisper encoder take care of chunking
441 min_energy_split_window_size=None,
442 )
443
444 @classmethod
445 # for speech-to-text transcription
446 def get_generation_prompt(
447 cls,
448 stt_params: SpeechToTextParams,
449 ) -> PromptType:
450 audio = stt_params.audio
451 model_config = stt_params.model_config
452 stt_config = stt_params.stt_config
453 language = stt_params.language
454
455 tokenizer = cached_tokenizer_from_config(model_config)
456 audio = Audio(audio, int(stt_config.sample_rate), format="wav") # lossless
457 req = TranscriptionRequest(
458 model=model_config.model,
459 audio=audio.to_base64(audio.format),
460 language=language,
461 )
462
463 tokenized = tokenizer.instruct.encode_transcription(req)
464
465 return TokensPrompt(
466 prompt_token_ids=tokenized.tokens,
467 multi_modal_data={
468 "audio": [
469 (audio.audio_array, stt_config.sample_rate)
470 for audio in tokenized.audios
471 ],
472 },
473 )
474
475 @classmethod
476 def get_num_audio_tokens(
477 cls,
478 audio_duration_s: float,
479 stt_config: SpeechToTextConfig,
480 model_config: ModelConfig,
481 ) -> int | None:
482 """Map from audio duration to number of audio tokens produced by the ASR
483 model, without running a forward pass.
484 This is used for estimating the amount of processing for this audio.
485 """
486 tokenizer = cached_tokenizer_from_config(model_config)
487 feature_extractor = MistralCommonFeatureExtractor(
488 tokenizer.instruct.audio_encoder
489 )
490 return feature_extractor.get_num_audio_tokens(
491 int(audio_duration_s * stt_config.sample_rate)
492 )
493
494 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
495 remapping_rules = [
496 (r"mm_streams_embeddings.embedding_module\.(.*)", r"\1"),
497 (r"mm_whisper_embeddings\.(.*)", r"\1"),
498 (r"audio_language_projection\.(.*)", r"audio_language_adapter.\1"),
499 (
500 r"audio_language_adapter\.0\.weight",
501 r"audio_language_adapter.w_in.weight",
502 ),
503 (
504 r"audio_language_adapter\.2\.weight",
505 r"audio_language_adapter.w_out.weight",
506 ),
507 ]
508
509 audio_params = dict(
510 nn.ModuleDict(
511 {
512 "audio_language_adapter": self.audio_language_adapter,
513 }
514 ).named_parameters()
515 )
516 weights = _create_fake_bias_for_k_proj(weights, ".wk.weight")
517
518 loaded_weights = set()
519
520 def llm_weights_generator():
521 nonlocal loaded_weights
522 for name, w in weights:
523 is_encoder = False
524 for k in [
525 "mm_whisper_embeddings",
526 "mm_streams_embeddings.embedding_module",
527 ]:
528 is_encoder |= (
529 name.startswith(k)
530 and not name.startswith(f"{k}.tok_embeddings")
531 and not name.startswith(f"{k}.audio_language_projection")
532 )
533
534 for pattern, repl in remapping_rules:
535 if re.fullmatch(pattern, name):
536 name = re.sub(pattern, repl, name)
537
538 if is_encoder:
539 name = self.whisper_encoder.load_weight((name, w))
540 loaded_weights.add(f"whisper_encoder.{name}")
541 continue
542
543 if name in audio_params:
544 param = audio_params[name]
545 with torch.no_grad():
546 default_weight_loader(param, w)
547 loaded_weights.add(name)
548 else:
549 yield (name, w)
550
551 for name in self.language_model.load_weights(llm_weights_generator()):
552 loaded_weights.add(f"language_model.{name}")
553
554 # potentially manually add position embeddings
555 sin_key = "whisper_encoder.whisper_encoder.embed_positions.weight"
556 if sin_key not in loaded_weights:
557 # make sure we don't hit an error here
558 loaded_weights.add(sin_key)
559
560 return loaded_weights
561
562 def maybe_update_quant_config(
563 self, quant_config: QuantizationConfig
564 ) -> QuantizationConfig:
565 """Update quant config to so that ignored module and target module names
566 match the vLLM model names.
567 Right now this is specific for compressed-tensors format and
568 load_format mistral.
569 """
570 remapping_rules = [
571 (r"output", r"language_model.lm_head"),
572 (
573 r"layers\.(\d+)\.attention\.wo",
574 r"language_model.model.layers.\1.self_attn.out_proj",
575 ),
576 (
577 r"layers\.(\d+)\.attention\.w(.*)",
578 r"language_model.model.layers.\1.self_attn.\2_proj",
579 ),
580 (
581 r"layers\.(\d+)\.feed_forward\.w1",
582 r"language_model.model.layers.\1.mlp.gate_proj",
583 ),
584 (
585 r"layers\.(\d+)\.feed_forward\.w2",
586 r"language_model.model.layers.\1.mlp.down_proj",
587 ),
588 (
589 r"layers\.(\d+)\.feed_forward\.w3",
590 r"language_model.model.layers.\1.mlp.up_proj",
591 ),
592 (
593 r"mm_whisper_embeddings\.whisper_encoder\.transformer\.layers\.(\d+)\.attention.w(.*)",
594 r"whisper_encoder.whisper_encoder.layers.\1.layers.self_attn.\2_proj",
595 ),
596 (
597 r"mm_whisper_embeddings\.whisper_encoder\.transformer\.layers\.(\d+)\.attention.wo",
598 r"whisper_encoder.whisper_encoder.layers.\1.layers.self_attn.out_proj",
599 ),
600 (
601 r"mm_whisper_embeddings\.whisper_encoder\.transformer\.layers\.(\d+)\.feed_forward.w(\d+)",
602 r"whisper_encoder.whisper_encoder.layers.\1.layers.mlp.fc\2",
603 ),
604 (
605 r"mm_whisper_embeddings\.whisper_encoder\.conv_layers\.0",
606 r"whisper_encoder.whisper_encoder.conv1",
607 ),
608 (
609 r"mm_whisper_embeddings\.whisper_encoder\.conv_layers\.1",
610 r"whisper_encoder.whisper_encoder.conv2",
611 ),
612 (
613 r"mm_whisper_embeddings\.audio_language_projection\.0",
614 r"audio_language_adapter.w_in",
615 ),
616 (
617 r"mm_whisper_embeddings\.audio_language_projection\.2",
618 r"audio_language_adapter.w_out",
619 ),
620 ]
621
622 # Update ignore list
623 if hasattr(quant_config, "ignore"):
624 mistral_ignore = []
625 for name in quant_config.ignore:
626 mistral_name = name
627 for pattern, repl in remapping_rules:
628 if re.fullmatch(pattern, name):
629 mistral_name = re.sub(pattern, repl, name)
630 mistral_ignore.append(mistral_name)
631 quant_config.ignore = mistral_ignore
632
633 # Update target list
634 if hasattr(quant_config, "config_groups"):
635 config_groups = quant_config.config_groups
636 for group_name in config_groups:
637 if "targets" in config_groups[group_name]:
638 targets = []
639 for name in config_groups[group_name]["targets"]:
640 mistral_name = name
641 for pattern, repl in remapping_rules:
642 if re.fullmatch(pattern, name):
643 mistral_name = re.sub(pattern, repl, name)
644 targets.append(mistral_name)
645 config_groups[group_name]["targets"] = targets
646 quant_config.config_groups = config_groups
647
648 return quant_config
649
650
651 class AudioLanguageAdapter(nn.Module):
652 def __init__(self, hidden_size: int, dim: int) -> None:
653 super().__init__()
654 self.w_in = nn.Linear(hidden_size, dim, bias=False)
655 self.gelu = nn.GELU()
656 self.w_out = nn.Linear(dim, dim, bias=False)
657
658 def forward(self, x: torch.Tensor) -> torch.Tensor:
659 return self.w_out(self.gelu(self.w_in(x)))
660
661
662 class VoxtralEncoderModel(nn.Module):
663 packed_modules_mapping = {"qkv_proj": ["q_proj", "k_proj", "v_proj"]}
664
665 mistral_remapping = [
666 (r"mm_streams_embeddings.embedding_module\.(.*)", r"\1"),
667 (
668 r"whisper_encoder\.conv_layers\.0\.(weight|bias)",
669 r"whisper_encoder.conv1.\1",
670 ),
671 (
672 r"whisper_encoder\.conv_layers\.1\.(weight|bias)",
673 r"whisper_encoder.conv2.\1",
674 ),
675 (
676 r"whisper_encoder\.conv_layers\.0\.conv\.(weight|bias)",
677 r"whisper_encoder.conv1.\1",
678 ), # noqa: E501
679 (
680 r"whisper_encoder\.conv_layers\.1\.conv\.(weight|bias)",
681 r"whisper_encoder.conv2.\1",
682 ), # noqa: E501
683 (
684 r"whisper_encoder\.transformer\.layers\.(\d+)\.attention\.w([qkv])\.(weight|bias)", # noqa: E501
685 r"whisper_encoder.layers.\1.self_attn.\2_proj.\3",
686 ),
687 (
688 r"whisper_encoder\.transformer\.layers\.(\d+)\.attention\.wo\.(weight|bias)", # noqa: E501
689 r"whisper_encoder.layers.\1.self_attn.out_proj.\2",
690 ),
691 (
692 r"whisper_encoder\.transformer\.layers\.(\d+)\.attention_norm\.(weight|bias)", # noqa: E501
693 r"whisper_encoder.layers.\1.self_attn_layer_norm.\2",
694 ),
695 (
696 r"whisper_encoder\.transformer\.layers\.(\d+)\.feed_forward\.w1\.(weight|bias)", # noqa: E501
697 r"whisper_encoder.layers.\1.mlp.fc1.\2",
698 ),
699 (
700 r"whisper_encoder\.transformer\.layers\.(\d+)\.feed_forward\.w2\.(weight|bias)", # noqa: E501
701 r"whisper_encoder.layers.\1.mlp.fc2.\2",
702 ),
703 (
704 r"whisper_encoder\.transformer\.layers\.(\d+)\.feed_forward\.w3\.(weight|bias)",
705 r"whisper_encoder.layers.\1.mlp.fc3.\2",
706 ), # noqa: E501
707 (
708 r"whisper_encoder\.transformer\.layers\.(\d+)\.ffn_norm\.(weight|bias)",
709 r"whisper_encoder.layers.\1.final_layer_norm.\2",
710 ),
711 (
712 r"whisper_encoder\.transformer\.norm\.(weight|bias)",
713 r"whisper_encoder.layer_norm.\1",
714 ),
715 ]
716
717 def __init__(
718 self,
719 vllm_config: VllmConfig,
720 *,
721 prefix: str = "",
722 ) -> None:
723 super().__init__()
724 self.config = cast(WhisperConfig, vllm_config.model_config.hf_config)
725 self.dtype: torch.dtype = vllm_config.model_config.dtype
726 self.is_causal = getattr(self.config, "is_causal", False)
727 WhisperEncoderCls: Callable[..., WhisperEncoder | WhisperCausalEncoder]
728 if self.is_causal:
729 WhisperEncoderCls = WhisperCausalEncoder
730 else:
731 WhisperEncoderCls = partial(WhisperEncoder, init_in_fp32=True)
732
733 self.whisper_encoder = WhisperEncoderCls(
734 vllm_config=vllm_config,
735 prefix=maybe_prefix(prefix, "whisper_encoder"),
736 )
737 mel_filters = mel_filter_bank(
738 num_frequency_bins=1 + self.config.window_size // 2,
739 num_mel_bins=self.config.num_mel_bins,
740 min_frequency=0.0,
741 max_frequency=8000.0,
742 sampling_rate=self.config.sampling_rate,
743 )
744 self.register_buffer(
745 "mel_filters",
746 torch.tensor(mel_filters, dtype=torch.float32),
747 persistent=False,
748 )
749
750 def compute_whisper_melspec(
751 self,
752 audio_waveforms: torch.Tensor,
753 ) -> torch.Tensor:
754 input_dtype = audio_waveforms.dtype
755 window = torch.hann_window(
756 self.config.window_size, device=audio_waveforms.device
757 )
758 stft = torch.stft(
759 audio_waveforms,
760 self.config.window_size,
761 self.config.hop_length,
762 window=window,
763 return_complex=True,
764 )
765 magnitudes = stft[..., :-1].abs() ** 2
766 mel_spec = self.mel_filters.T @ magnitudes
767 log_spec = torch.clamp(mel_spec, min=1e-10).log10()
768
769 if global_log_mel_max := self.config.global_log_mel_max:
770 if not isinstance(global_log_mel_max, float):
771 raise TypeError(f"{global_log_mel_max=} needs to be of type float.")
772 # Use `clamp` to avoid gpu<->cpu sync.
773 log_spec = torch.clamp(log_spec, min=global_log_mel_max - 8.0)
774 else:
775 log_spec_max = log_spec.max()
776 log_spec = torch.maximum(log_spec, log_spec_max - 8.0)
777 log_spec = (log_spec + 4.0) / 4.0
778 return log_spec.to(input_dtype)
779
780 @property
781 def downsample_factor(self) -> int:
782 return (
783 self.whisper_encoder.conv1.stride[0] * self.whisper_encoder.conv2.stride[0]
784 )
785
786 @property
787 def chunk_size(self) -> int:
788 return self.config.max_source_positions * self.downsample_factor
789
790 def prepare_inputs_for_conv(
791 self,
792 audio_waveforms: list[torch.Tensor],
793 ) -> tuple[torch.Tensor, list[int]]:
794 assert isinstance(audio_waveforms, list)
795 # list[num_mel_bins, seq_len]
796 input_features = [
797 self.compute_whisper_melspec(audio).to(self.dtype)
798 for audio in audio_waveforms
799 ]
800
801 chunked_features: list[torch.Tensor] = []
802 chunks_per_example: list[int] = []
803 for feature in input_features:
804 chunks = feature.split(self.chunk_size, dim=-1)
805 chunked_features += chunks
806 chunks_per_example.append(len(chunks))
807
808 # [total_num_chunks, num_mel_bins, chunk_size]
809 return torch.stack(chunked_features), chunks_per_example
810
811 def forward(
812 self, input_features: torch.Tensor | list[torch.Tensor]
813 ) -> list[torch.Tensor]:
814 if not isinstance(input_features, list):
815 input_features = [input_features]
816
817 # Split long inputs into chunks
818 input_embeds, chunks_per_example = self.prepare_inputs_for_conv(input_features)
819
820 # [total_num_chunks, ceil(chunk_size / downsample_factor), hidden_size]
821 out = self.whisper_encoder([input_embeds])
822
823 # Re-concatenate the chunks
824 chunk_idx = 0
825 results = []
826 for n_chunks in chunks_per_example:
827 result = out[chunk_idx : chunk_idx + n_chunks].flatten(0, 1)
828 results.append(result)
829 chunk_idx += n_chunks
830
831 return results
832
833 def load_weight(self, weight: tuple[str, torch.Tensor]) -> str:
834 stacked_params_mapping: list[tuple[str, str, str | int]] = [
835 # (param_name, shard_name, shard_id)
836 ("qkv_proj", "q_proj", "q"),
837 ("qkv_proj", "k_proj", "k"),
838 ("qkv_proj", "v_proj", "v"),
839 ]
840 params_mapping = []
841
842 if self.is_causal:
843 # For `WhisperCausalEncoder` we need
844 # some more renaming
845 stacked_params_mapping.extend(
846 [
847 (".mlp.gate_up_proj", ".mlp.fc1", 0),
848 (".mlp.gate_up_proj", ".mlp.fc3", 1),
849 ]
850 )
851 params_mapping.extend(
852 [
853 (".mlp.down_proj", ".mlp.fc2"),
854 ]
855 )
856 params_dict = dict(self.named_parameters())
857
858 name, loaded_weight = weight
859 for pattern, repl in self.mistral_remapping:
860 if re.fullmatch(pattern, name):
861 name = re.sub(pattern, repl, name)
862
863 for param_name, weight_name, shard_id in stacked_params_mapping:
864 if weight_name not in name:
865 continue
866 name = name.replace(weight_name, param_name)
867
868 param = params_dict[name]
869 weight_loader = param.weight_loader
870 weight_loader(param, loaded_weight, shard_id)
871 break
872 else:
873 for param_name, weight_name in params_mapping:
874 if weight_name not in name:
875 continue
876 name = name.replace(weight_name, param_name)
877
878 param = params_dict[name]
879 weight_loader = getattr(param, "weight_loader", default_weight_loader)
880 weight_loader(param, loaded_weight)
881
882 return name