main
py 482 lines 17.1 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 # Copyright 2024 The Qwen team.
5 # Copyright 2023 The vLLM team.
6 # Copyright 2022 EleutherAI and the HuggingFace Inc. team. All rights reserved.
7 #
8 # This code is based on EleutherAI's GPT-NeoX library and the GPT-NeoX
9 # and OPT implementations in this library. It has been modified from its
10 # original forms to accommodate minor architectural differences compared
11 # to GPT-NeoX and OPT used by the Meta AI team that trained the model.
12 #
13 # Licensed under the Apache License, Version 2.0 (the "License");
14 # you may not use this file except in compliance with the License.
15 # You may obtain a copy of the License at
16 #
17 # http://www.apache.org/licenses/LICENSE-2.0
18 #
19 # Unless required by applicable law or agreed to in writing, software
20 # distributed under the License is distributed on an "AS IS" BASIS,
21 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 # See the License for the specific language governing permissions and
23 # limitations under the License.
24 """Inference-only Qwen2-Audio model compatible with HuggingFace weights."""
25
26 from collections.abc import Iterable, Mapping, Sequence
27 from typing import Annotated, Any, Literal, TypeAlias
28
29 import torch
30 import torch.nn as nn
31 from transformers import BatchFeature
32 from transformers.models.qwen2_audio import (
33 Qwen2AudioConfig,
34 Qwen2AudioEncoder,
35 Qwen2AudioProcessor,
36 )
37 from transformers.models.whisper import WhisperFeatureExtractor
38
39 from vllm.config import VllmConfig
40 from vllm.config.multimodal import MultiModalDummyOptions
41 from vllm.inputs import ModalityData, MultiModalDataDict
42 from vllm.multimodal import MULTIMODAL_REGISTRY
43 from vllm.multimodal.inputs import (
44 AudioItem,
45 MultiModalFieldConfig,
46 MultiModalKwargsItems,
47 )
48 from vllm.multimodal.parse import (
49 AudioProcessorItems,
50 DictEmbeddingItems,
51 ModalityDataItems,
52 MultiModalDataItems,
53 MultiModalDataParser,
54 )
55 from vllm.multimodal.processing import (
56 BaseDummyInputsBuilder,
57 BaseMultiModalProcessor,
58 BaseProcessingInfo,
59 PromptReplacement,
60 PromptUpdate,
61 )
62 from vllm.multimodal.processing.processor import HFMultiModalInputs
63 from vllm.sequence import IntermediateTensors
64 from vllm.utils.gpu_sync_debug import gpu_sync_allowed
65 from vllm.utils.tensor_schema import TensorSchema, TensorShape
66
67 from .interfaces import MultiModalEmbeddings, SupportsMultiModal, SupportsPP
68 from .utils import AutoWeightsLoader, init_vllm_registered_model, maybe_prefix
69
70
71 # # === Audio Inputs === #
72 class Qwen2AudioFeatureInputs(TensorSchema):
73 """Dimensions:
74 - na: Number of audios
75 - nmb: Number of mel bins
76 """
77
78 type: Literal["audio_features"]
79 input_features: Annotated[
80 torch.Tensor | list[torch.Tensor],
81 TensorShape("na", "nmb", 3000),
82 ]
83
84 feature_attention_mask: Annotated[
85 torch.Tensor,
86 TensorShape("na", 3000),
87 ]
88
89
90 class Qwen2AudioEmbeddingInputs(TensorSchema):
91 """Dimensions:
92 - bn: Batch size
93 - naf: Number of audio features
94 - hs: Hidden size (must match the hidden size of language model
95 backbone)
96 """
97
98 type: Literal["audio_embeds"] = "audio_embeds"
99
100 audio_embeds: Annotated[
101 list[torch.Tensor],
102 TensorShape("bn", "naf", "hs", dynamic_dims={"naf"}),
103 ]
104
105
106 Qwen2AudioInputs: TypeAlias = Qwen2AudioFeatureInputs | Qwen2AudioEmbeddingInputs
107
108 # === Audio Encoder === #
109
110
111 class Qwen2AudioMultiModalProjector(nn.Module):
112 def __init__(self, audio_hidden_size: int, text_hidden_size: int):
113 super().__init__()
114 self.linear = nn.Linear(audio_hidden_size, text_hidden_size, bias=True)
115
116 def forward(self, audio_features):
117 hidden_states = self.linear(audio_features)
118 return hidden_states
119
120
121 # From Qwen2AudioEncoder._get_feat_extract_output_lengths
122 def _get_feat_extract_output_lengths(input_lengths: torch.Tensor):
123 feat_lengths = (input_lengths - 1) // 2 + 1
124 output_lengths = (feat_lengths - 2) // 2 + 1
125 return feat_lengths, output_lengths
126
127
128 def _qwen2audio_field_config(hf_inputs: Mapping[str, torch.Tensor]):
129 return dict(
130 audio_embeds=MultiModalFieldConfig.batched("audio"),
131 audio_num_tokens=MultiModalFieldConfig.batched("audio", keep_on_cpu=True),
132 input_features=MultiModalFieldConfig.batched("audio"),
133 feature_attention_mask=MultiModalFieldConfig.batched("audio"),
134 )
135
136
137 class Qwen2AudioMultiModalDataParser(MultiModalDataParser):
138 embedding_fields = {
139 "audio": {"audio_embeds": "values", "audio_num_tokens": "metadata"},
140 }
141
142 def _parse_audio_data(
143 self,
144 data: dict[str, torch.Tensor] | ModalityData[AudioItem],
145 ) -> ModalityDataItems[Any, Any] | None:
146 if isinstance(data, dict) and "audio_num_tokens" not in data:
147 return DictEmbeddingItems(
148 data,
149 modality="audio",
150 required_fields={"audio_embeds"},
151 fields_factory=_qwen2audio_field_config,
152 )
153
154 return super()._parse_audio_data(data)
155
156
157 class Qwen2AudioProcessingInfo(BaseProcessingInfo):
158 def get_hf_config(self):
159 return self.ctx.get_hf_config(Qwen2AudioConfig)
160
161 def get_hf_processor(self, **kwargs: object) -> Qwen2AudioProcessor:
162 return self.ctx.get_hf_processor(Qwen2AudioProcessor, **kwargs)
163
164 def get_feature_extractor(self, **kwargs: object) -> WhisperFeatureExtractor:
165 hf_processor = self.get_hf_processor(**kwargs)
166 feature_extractor = hf_processor.feature_extractor # type: ignore
167 assert isinstance(feature_extractor, WhisperFeatureExtractor)
168 return feature_extractor
169
170 def get_data_parser(self):
171 feature_extractor = self.get_feature_extractor()
172
173 return Qwen2AudioMultiModalDataParser(
174 target_sr=feature_extractor.sampling_rate,
175 target_channels=self.get_target_channels(),
176 expected_hidden_size=self._get_expected_hidden_size(),
177 allow_missing_mm_embeddings=self.allow_missing_mm_embeddings,
178 )
179
180 def get_target_channels(self) -> int:
181 """Return target audio channels for Qwen2 Audio models (mono)."""
182 return 1
183
184 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
185 return {"audio": None}
186
187 def get_mm_max_tokens_per_item(
188 self,
189 seq_len: int,
190 mm_counts: Mapping[str, int] | None = None,
191 ) -> Mapping[str, int]:
192 mm_counts = mm_counts or {}
193 if mm_counts.get("audio", 0) <= 0:
194 return {}
195
196 feature_extractor = self.get_feature_extractor()
197 chunk_length = min(feature_extractor.chunk_length, 30)
198 audio_len = int(chunk_length * feature_extractor.sampling_rate)
199 hop_length = feature_extractor.hop_length
200 max_mel_seq_len = audio_len // hop_length
201
202 input_lengths = torch.tensor([max_mel_seq_len], dtype=torch.long)
203 _, output_lengths = _get_feat_extract_output_lengths(input_lengths)
204
205 return {"audio": int(output_lengths.item())}
206
207
208 class Qwen2AudioDummyInputsBuilder(BaseDummyInputsBuilder[Qwen2AudioProcessingInfo]):
209 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
210 num_audios = mm_counts.get("audio", 0)
211
212 hf_processor = self.info.get_hf_processor()
213 audio_token = hf_processor.audio_token
214 audio_bos_token = hf_processor.audio_bos_token
215 audio_eos_token = hf_processor.audio_eos_token
216
217 return (audio_bos_token + audio_token + audio_eos_token) * num_audios
218
219 def get_dummy_mm_data(
220 self,
221 seq_len: int,
222 mm_counts: Mapping[str, int],
223 mm_options: MultiModalDummyOptions,
224 ) -> MultiModalDataDict:
225 feature_extractor = self.info.get_feature_extractor()
226
227 sampling_rate = feature_extractor.sampling_rate
228 audio_len = feature_extractor.chunk_length * sampling_rate
229
230 return {
231 "audio": self._get_dummy_audios(
232 length=audio_len,
233 num_audios=mm_counts.get("audio", 0),
234 overrides=mm_options.get("audio"),
235 )
236 }
237
238
239 class Qwen2AudioMultiModalProcessor(BaseMultiModalProcessor[Qwen2AudioProcessingInfo]):
240 def _get_hf_mm_text(self, mm_counts: Mapping[str, int]) -> str:
241 return self.dummy_inputs.get_dummy_text(mm_counts)
242
243 def _get_hf_mm_inputs(
244 self,
245 mm_items: MultiModalDataItems,
246 hf_kwargs: Mapping[str, object],
247 ) -> HFMultiModalInputs:
248 hf_inputs = super()._get_hf_mm_inputs(mm_items, hf_kwargs)
249
250 feature_extractor = self.info.get_feature_extractor(**hf_kwargs)
251 return hf_inputs._replace(
252 hf_kwargs=dict(
253 hf_inputs.hf_kwargs,
254 sampling_rate=feature_extractor.sampling_rate,
255 truncation=True,
256 )
257 )
258
259 def _get_mm_fields_config(
260 self,
261 hf_inputs: BatchFeature,
262 hf_processor_mm_kwargs: Mapping[str, object],
263 ) -> Mapping[str, MultiModalFieldConfig]:
264 return _qwen2audio_field_config(hf_inputs)
265
266 def _get_prompt_updates(
267 self,
268 mm_items: MultiModalDataItems,
269 hf_processor_mm_kwargs: Mapping[str, object],
270 out_mm_kwargs: MultiModalKwargsItems,
271 ) -> Sequence[PromptUpdate]:
272 processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
273 audio_token_id = processor.audio_token_id
274
275 out_mm_data = out_mm_kwargs.get_data()
276 feature_attention_mask = out_mm_data.get("feature_attention_mask")
277 if feature_attention_mask is None:
278 audio_output_lengths = []
279 else:
280 assert isinstance(feature_attention_mask, torch.Tensor)
281 _, audio_output_lens = _get_feat_extract_output_lengths(
282 feature_attention_mask.sum(-1)
283 )
284
285 audio_output_lengths = audio_output_lens.tolist()
286
287 def get_replacement_qwen2_audio(item_idx: int):
288 if "audio_num_tokens" in out_mm_data:
289 audio_num_tokens = out_mm_data["audio_num_tokens"]
290 assert isinstance(audio_num_tokens, torch.Tensor)
291 num_features = int(audio_num_tokens[item_idx])
292 elif audio_output_lengths:
293 num_features = audio_output_lengths[item_idx]
294 else:
295 audio_embeds = out_mm_data["audio_embeds"][item_idx]
296 assert isinstance(audio_embeds, torch.Tensor)
297 assert len(audio_embeds.shape) == 2, "audio_embeds must be a 2D tensor"
298 num_features = audio_embeds.shape[0]
299
300 if num_features == 0:
301 audios = mm_items.get_items("audio", AudioProcessorItems)
302 audio_len = audios.get_audio_length(item_idx)
303
304 raise ValueError(
305 f"The audio (len={audio_len}) is too short "
306 "to be represented inside the model"
307 )
308
309 return [audio_token_id] * num_features
310
311 return [
312 PromptReplacement(
313 modality="audio",
314 target=[audio_token_id],
315 replacement=get_replacement_qwen2_audio,
316 )
317 ]
318
319
320 @MULTIMODAL_REGISTRY.register_processor(
321 Qwen2AudioMultiModalProcessor,
322 info=Qwen2AudioProcessingInfo,
323 dummy_inputs=Qwen2AudioDummyInputsBuilder,
324 )
325 class Qwen2AudioForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsPP):
326 @classmethod
327 def get_placeholder_str(cls, modality: str, i: int) -> str | None:
328 if modality.startswith("audio"):
329 return f"Audio {i}: <|audio_bos|><|AUDIO|><|audio_eos|>"
330
331 raise ValueError("Only audio modality is supported")
332
333 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
334 super().__init__()
335 config = vllm_config.model_config.hf_config
336 quant_config = vllm_config.quant_config
337 multimodal_config = vllm_config.model_config.multimodal_config
338 self.config = config
339 self.multimodal_config = multimodal_config
340 self.quant_config = quant_config
341
342 with self._mark_tower_model(vllm_config, "audio"):
343 self.audio_tower = Qwen2AudioEncoder(config.audio_config)
344 self.multi_modal_projector = Qwen2AudioMultiModalProjector(
345 config.audio_config.d_model, config.text_config.hidden_size
346 )
347
348 with self._mark_language_model(vllm_config):
349 self.language_model = init_vllm_registered_model(
350 vllm_config=vllm_config,
351 hf_config=config.text_config,
352 prefix=maybe_prefix(prefix, "language_model"),
353 architectures=["Qwen2ForCausalLM"],
354 )
355
356 self.make_empty_intermediate_tensors = (
357 self.language_model.make_empty_intermediate_tensors
358 )
359
360 def _parse_and_validate_audio_input(
361 self, **kwargs: object
362 ) -> Qwen2AudioInputs | None:
363 input_features = kwargs.pop("input_features", None)
364 audio_embeds = kwargs.pop("audio_embeds", None)
365 feature_attention_mask = kwargs.pop("feature_attention_mask", None)
366
367 if input_features is None and audio_embeds is None:
368 return None
369
370 if audio_embeds is not None:
371 return Qwen2AudioEmbeddingInputs(
372 type="audio_embeds", audio_embeds=audio_embeds
373 )
374
375 if input_features is not None:
376 return Qwen2AudioFeatureInputs(
377 type="audio_features",
378 input_features=input_features,
379 feature_attention_mask=feature_attention_mask,
380 )
381
382 raise AssertionError("This line should be unreachable.")
383
384 def _process_audio_input(
385 self, audio_input: Qwen2AudioInputs
386 ) -> torch.Tensor | tuple[torch.Tensor, ...]:
387 if audio_input["type"] == "audio_embeds":
388 audio_embeds = audio_input["audio_embeds"]
389 return tuple(audio_embeds)
390
391 input_features = audio_input["input_features"]
392 feature_attention_mask = audio_input["feature_attention_mask"]
393
394 audio_feat_lengths, audio_output_lengths = (
395 self.audio_tower._get_feat_extract_output_lengths(
396 feature_attention_mask.sum(-1)
397 )
398 )
399
400 batch_size, _, max_mel_seq_len = input_features.shape
401 max_seq_len = (max_mel_seq_len - 2) // 2 + 1
402 # Create a sequence tensor of shape (batch_size, max_seq_len)
403 seq_range = (
404 torch.arange(
405 0,
406 max_seq_len,
407 dtype=audio_feat_lengths.dtype,
408 device=audio_feat_lengths.device,
409 )
410 .unsqueeze(0)
411 .expand(batch_size, max_seq_len)
412 )
413 lengths_expand = audio_feat_lengths.unsqueeze(-1).expand(
414 batch_size, max_seq_len
415 )
416 # Create mask
417 padding_mask = seq_range >= lengths_expand
418
419 audio_attention_mask_ = padding_mask.view(batch_size, 1, 1, max_seq_len).expand(
420 batch_size, 1, max_seq_len, max_seq_len
421 )
422 audio_attention_mask = audio_attention_mask_.to(
423 dtype=self.audio_tower.conv1.weight.dtype,
424 device=self.audio_tower.conv1.weight.device,
425 )
426 audio_attention_mask[audio_attention_mask_] = float("-inf")
427
428 audio_outputs = self.audio_tower(
429 input_features, attention_mask=audio_attention_mask
430 )
431 selected_audio_feature = audio_outputs.last_hidden_state
432 audio_features = self.multi_modal_projector(selected_audio_feature)
433 num_audios, max_audio_tokens, embed_dim = audio_features.shape
434 audio_output_lengths = audio_output_lengths.unsqueeze(1)
435 audio_features_mask = (
436 torch.arange(max_audio_tokens, device=audio_output_lengths.device).expand(
437 num_audios, max_audio_tokens
438 )
439 < audio_output_lengths
440 )
441 with gpu_sync_allowed():
442 masked_audio_features = audio_features[audio_features_mask].view(
443 -1, embed_dim
444 )
445
446 # Split to tuple of embeddings for individual audio input.
447 return torch.split(
448 masked_audio_features, audio_output_lengths.flatten().tolist()
449 )
450
451 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
452 audio_input = self._parse_and_validate_audio_input(**kwargs)
453 if audio_input is None:
454 return []
455 masked_audio_features = self._process_audio_input(audio_input)
456 return masked_audio_features
457
458 def forward(
459 self,
460 input_ids: torch.Tensor | None,
461 positions: torch.Tensor,
462 intermediate_tensors: IntermediateTensors | None = None,
463 inputs_embeds: torch.Tensor | None = None,
464 **kwargs: object,
465 ) -> torch.Tensor | IntermediateTensors:
466 if intermediate_tensors is not None:
467 inputs_embeds = None
468
469 hidden_states = self.language_model.model(
470 input_ids, positions, intermediate_tensors, inputs_embeds=inputs_embeds
471 )
472 return hidden_states
473
474 def compute_logits(
475 self,
476 hidden_states: torch.Tensor,
477 ) -> torch.Tensor | None:
478 return self.language_model.compute_logits(hidden_states)
479
480 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
481 loader = AutoWeightsLoader(self)
482 return loader.load_weights(weights)