main
py 1,008 lines 32.4 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 Iterable, Mapping, Sequence
6 from typing import Annotated, cast
7
8 import torch
9 import torch.nn.functional as F
10 from torch import nn
11 from transformers import (
12 BatchFeature,
13 Qwen3Config,
14 )
15
16 from vllm.config import ModelConfig, SpeechToTextConfig, VllmConfig
17 from vllm.config.multimodal import MultiModalDummyOptions
18 from vllm.config.speech_to_text import SpeechToTextParams
19 from vllm.distributed import get_tensor_model_parallel_world_size
20 from vllm.inputs import MultiModalDataDict, PromptType
21 from vllm.logger import init_logger
22 from vllm.model_executor.layers.activation import _ACTIVATION_REGISTRY
23 from vllm.model_executor.layers.attention.mm_encoder_attention import (
24 MMEncoderAttention,
25 )
26 from vllm.model_executor.layers.linear import (
27 ColumnParallelLinear,
28 QKVParallelLinear,
29 ReplicatedLinear,
30 RowParallelLinear,
31 )
32 from vllm.model_executor.layers.logits_processor import LogitsProcessor
33 from vllm.model_executor.layers.vocab_parallel_embedding import ParallelLMHead
34 from vllm.model_executor.models.whisper_utils import (
35 ISO639_1_SUPPORTED_LANGS,
36 )
37 from vllm.multimodal import MULTIMODAL_REGISTRY
38 from vllm.multimodal.inputs import (
39 MultiModalFieldConfig,
40 MultiModalKwargsItems,
41 )
42 from vllm.multimodal.parse import MultiModalDataItems, MultiModalDataParser
43 from vllm.multimodal.processing import (
44 BaseDummyInputsBuilder,
45 BaseMultiModalProcessor,
46 BaseProcessingInfo,
47 ProcessorInputs,
48 PromptReplacement,
49 PromptUpdate,
50 TimingContext,
51 )
52 from vllm.multimodal.processing.processor import (
53 HFMultiModalInputs,
54 MultiModalProcessingResult,
55 )
56 from vllm.transformers_utils.processor import cached_processor_from_config
57 from vllm.transformers_utils.processors.funasr import FunASRFeatureExtractor
58 from vllm.utils.tensor_schema import TensorSchema, TensorShape
59
60 from .interfaces import (
61 MultiModalEmbeddings,
62 SupportsMultiModal,
63 SupportsTranscription,
64 _require_is_multimodal,
65 )
66 from .qwen3 import Qwen3Model
67 from .utils import (
68 AutoWeightsLoader,
69 WeightsMapper,
70 _merge_multimodal_embeddings,
71 maybe_prefix,
72 )
73
74 logger = init_logger(__name__)
75
76
77 def sequence_mask(lengths, maxlen=None, dtype=torch.float32, device=None):
78 if maxlen is None:
79 maxlen = lengths.max()
80 row_vector = torch.arange(0, maxlen, 1).to(lengths.device)
81 matrix = torch.unsqueeze(lengths, dim=-1)
82 mask = row_vector < matrix
83 mask = mask.detach()
84
85 return mask.type(dtype).to(device) if device is not None else mask.type(dtype)
86
87
88 class LayerNorm(torch.nn.LayerNorm):
89 def __init__(self, nout, dim=-1):
90 super().__init__(nout, eps=1e-12)
91 self.dim = dim
92
93 def forward(self, x: torch.Tensor):
94 if self.dim == -1:
95 return super().forward(x)
96 return super().forward(x.transpose(self.dim, -1)).transpose(self.dim, -1)
97
98
99 class EncoderLayerSANM(nn.Module):
100 def __init__(
101 self,
102 in_size: int,
103 size: int,
104 self_attn: nn.Module,
105 feed_forward: nn.Module,
106 normalize_before=True,
107 ):
108 super().__init__()
109 self.self_attn = self_attn
110 self.feed_forward = feed_forward
111 self.norm1 = LayerNorm(in_size)
112 self.norm2 = LayerNorm(size)
113 self.in_size = in_size
114 self.size = size
115 self.normalize_before = normalize_before
116
117 def forward(
118 self,
119 hidden_states: torch.Tensor,
120 mask: torch.Tensor | None = None,
121 cache=None,
122 mask_shift_chunk=None,
123 mask_att_chunk_encoder=None,
124 ):
125 residual = hidden_states
126 hidden_states = self.norm1(hidden_states)
127
128 if self.in_size == self.size:
129 hidden_states = residual + self.self_attn(
130 hidden_states,
131 mask,
132 mask_shift_chunk=mask_shift_chunk,
133 mask_att_chunk_encoder=mask_att_chunk_encoder,
134 )
135 else:
136 hidden_states = self.self_attn(
137 hidden_states,
138 mask,
139 mask_shift_chunk=mask_shift_chunk,
140 mask_att_chunk_encoder=mask_att_chunk_encoder,
141 )
142
143 residual = hidden_states
144 hidden_states = self.norm2(hidden_states)
145 hidden_states = residual + self.feed_forward(hidden_states)
146
147 return hidden_states, mask, cache, mask_shift_chunk, mask_att_chunk_encoder
148
149
150 class MultiHeadedAttentionSANM(nn.Module):
151 def __init__(
152 self,
153 n_head: int,
154 in_feat: int,
155 n_feat: int,
156 kernel_size: int,
157 sanm_shift: int = 0,
158 ):
159 super().__init__()
160 assert n_feat % n_head == 0
161 # We assume d_v always equals d_k
162 self.d_k = n_feat // n_head
163 self.h = n_head
164 self.out_proj = ReplicatedLinear(
165 input_size=n_feat,
166 output_size=n_feat,
167 bias=True,
168 )
169 self.linear_q_k_v = ReplicatedLinear(
170 input_size=in_feat,
171 output_size=n_feat * 3,
172 bias=True,
173 )
174 self.attn = None
175
176 self.fsmn_block = nn.Conv1d(
177 n_feat, n_feat, kernel_size, stride=1, padding=0, groups=n_feat, bias=False
178 )
179 # padding
180 left_padding = (kernel_size - 1) // 2
181 if sanm_shift > 0:
182 left_padding = left_padding + sanm_shift
183 right_padding = kernel_size - 1 - left_padding
184 self.pad_fn = nn.ConstantPad1d((left_padding, right_padding), 0.0)
185
186 def forward_fsmn(
187 self,
188 inputs: torch.Tensor,
189 mask: torch.Tensor,
190 mask_shift_chunk: torch.Tensor = None,
191 ):
192 b, t, d = inputs.size()
193 if mask is not None:
194 mask = torch.reshape(mask, (b, -1, 1))
195 if mask_shift_chunk is not None:
196 mask = mask * mask_shift_chunk
197 inputs = inputs * mask
198
199 x = inputs.transpose(1, 2)
200 x = self.pad_fn(x)
201 x = self.fsmn_block(x)
202 x = x.transpose(1, 2)
203 x += inputs
204 if mask is not None:
205 x = x * mask
206 return x
207
208 def forward_qkv(self, x: torch.Tensor):
209 b, t, d = x.size()
210
211 q_k_v, _ = self.linear_q_k_v(x)
212 q, k, v = torch.split(q_k_v, int(self.h * self.d_k), dim=-1)
213 q_h = torch.reshape(q, (b, t, self.h, self.d_k)).transpose(1, 2)
214 k_h = torch.reshape(k, (b, t, self.h, self.d_k)).transpose(1, 2)
215 v_h = torch.reshape(v, (b, t, self.h, self.d_k)).transpose(1, 2)
216
217 return q_h, k_h, v_h, v
218
219 def forward_attention(
220 self,
221 value: torch.Tensor,
222 scores: torch.Tensor,
223 mask: torch.Tensor,
224 mask_att_chunk_encoder: torch.Tensor = None,
225 ):
226 n_batch = value.size(0)
227 if mask is not None:
228 if mask_att_chunk_encoder is not None:
229 mask = mask * mask_att_chunk_encoder
230
231 mask = mask.unsqueeze(1).eq(0)
232
233 min_value = -float("inf")
234 scores = scores.masked_fill(mask, min_value)
235 attn = torch.softmax(scores, dim=-1).masked_fill(mask, 0.0)
236 else:
237 attn = torch.softmax(scores, dim=-1)
238
239 p_attn = attn
240 x = torch.matmul(p_attn, value)
241 x = x.transpose(1, 2).contiguous().view(n_batch, -1, self.h * self.d_k)
242
243 out, _ = self.out_proj(x)
244 return out
245
246 def forward(
247 self,
248 hidden_states: torch.Tensor,
249 mask: torch.Tensor,
250 mask_shift_chunk: torch.Tensor = None,
251 mask_att_chunk_encoder: torch.Tensor = None,
252 ):
253 q_h, k_h, v_h, v = self.forward_qkv(hidden_states)
254 fsmn_memory = self.forward_fsmn(v, mask, mask_shift_chunk)
255 q_h = q_h * self.d_k ** (-0.5)
256 scores = torch.matmul(q_h, k_h.transpose(-2, -1))
257 att_outs = self.forward_attention(v_h, scores, mask, mask_att_chunk_encoder)
258 return att_outs + fsmn_memory
259
260
261 class SinusoidalPositionEncoder(torch.nn.Module):
262 def __init__(self, d_model=80):
263 super().__init__()
264
265 def encode(
266 self,
267 positions: torch.Tensor,
268 depth: int,
269 dtype: torch.dtype = torch.float32,
270 ):
271 batch_size = positions.size(0)
272 positions = positions.type(dtype)
273 device = positions.device
274 log_timescale_increment = torch.log(
275 torch.tensor([10000], dtype=dtype, device=device)
276 ) / (depth / 2 - 1)
277 inv_timescales = torch.exp(
278 torch.arange(depth / 2, device=device).type(dtype)
279 * (-log_timescale_increment)
280 )
281 inv_timescales = torch.reshape(inv_timescales, [batch_size, -1])
282 scaled_time = torch.reshape(positions, [1, -1, 1]) * torch.reshape(
283 inv_timescales, [1, 1, -1]
284 )
285 encoding = torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], dim=2)
286 return encoding.type(dtype)
287
288 def forward(self, hidden_states: torch.Tensor):
289 batch_size, timesteps, input_dim = hidden_states.size()
290 positions = torch.arange(1, timesteps + 1, device=hidden_states.device)[None, :]
291 position_encoding = self.encode(positions, input_dim, hidden_states.dtype).to(
292 hidden_states.device
293 )
294
295 return hidden_states + position_encoding
296
297
298 class SenseVoiceEncoderSmall(nn.Module):
299 def __init__(
300 self,
301 input_size: int,
302 output_size: int = 256,
303 attention_heads: int = 4,
304 linear_units: int = 2048,
305 num_blocks: int = 6,
306 tp_blocks: int = 0,
307 attention_dropout_rate: float = 0.0,
308 normalize_before: bool = True,
309 kernel_size: int = 11,
310 sanm_shift: int = 0,
311 **kwargs,
312 ):
313 super().__init__()
314 self._output_size = output_size
315 self.embed = SinusoidalPositionEncoder()
316
317 self.normalize_before = normalize_before
318
319 positionwise_layer = PositionwiseFeedForward
320 positionwise_layer_args = (
321 output_size,
322 linear_units,
323 )
324
325 encoder_selfattn_layer = MultiHeadedAttentionSANM
326 encoder_selfattn_layer_args0 = (
327 attention_heads,
328 input_size,
329 output_size,
330 kernel_size,
331 sanm_shift,
332 )
333 encoder_selfattn_layer_args = (
334 attention_heads,
335 output_size,
336 output_size,
337 kernel_size,
338 sanm_shift,
339 )
340
341 self.encoders0 = nn.ModuleList(
342 [
343 EncoderLayerSANM(
344 input_size,
345 output_size,
346 encoder_selfattn_layer(*encoder_selfattn_layer_args0),
347 positionwise_layer(*positionwise_layer_args),
348 )
349 for i in range(1)
350 ]
351 )
352 self.encoders = nn.ModuleList(
353 [
354 EncoderLayerSANM(
355 output_size,
356 output_size,
357 encoder_selfattn_layer(*encoder_selfattn_layer_args),
358 positionwise_layer(*positionwise_layer_args),
359 )
360 for i in range(num_blocks - 1)
361 ]
362 )
363
364 self.tp_encoders = nn.ModuleList(
365 [
366 EncoderLayerSANM(
367 output_size,
368 output_size,
369 encoder_selfattn_layer(*encoder_selfattn_layer_args),
370 positionwise_layer(*positionwise_layer_args),
371 )
372 for i in range(tp_blocks)
373 ]
374 )
375
376 self.after_norm = LayerNorm(output_size)
377
378 self.tp_norm = LayerNorm(output_size)
379
380 def output_size(self) -> int:
381 return self._output_size
382
383 def forward(
384 self,
385 xs_pad: torch.Tensor,
386 ilens: torch.Tensor,
387 ):
388 maxlen = xs_pad.shape[1]
389 masks = sequence_mask(
390 ilens, maxlen=maxlen, dtype=ilens.dtype, device=ilens.device
391 )[:, None, :]
392
393 xs_pad *= self.output_size() ** 0.5
394
395 xs_pad = self.embed(xs_pad)
396
397 for layer_idx, encoder_layer in enumerate(self.encoders0):
398 encoder_outs = encoder_layer(xs_pad, masks)
399 xs_pad, masks = encoder_outs[0], encoder_outs[1]
400
401 for layer_idx, encoder_layer in enumerate(self.encoders):
402 encoder_outs = encoder_layer(xs_pad, masks)
403 xs_pad, masks = encoder_outs[0], encoder_outs[1]
404
405 xs_pad = self.after_norm(xs_pad)
406
407 olens = masks.squeeze(1).sum(1).int()
408
409 for layer_idx, encoder_layer in enumerate(self.tp_encoders):
410 encoder_outs = encoder_layer(xs_pad, masks)
411 xs_pad, masks = encoder_outs[0], encoder_outs[1]
412
413 xs_pad = self.tp_norm(xs_pad)
414 return xs_pad, olens
415
416
417 class PositionwiseFeedForward(nn.Module):
418 def __init__(self, idim: int, hidden_units: int):
419 super().__init__()
420 self.w_1 = ColumnParallelLinear(
421 input_size=idim,
422 output_size=hidden_units,
423 bias=True,
424 )
425 self.w_2 = RowParallelLinear(
426 input_size=hidden_units,
427 output_size=idim,
428 bias=True,
429 )
430 self.activation = _ACTIVATION_REGISTRY["relu"]
431
432 def forward(self, hidden_states: torch.Tensor):
433 hidden_states, _ = self.w_1(hidden_states)
434 hidden_states = self.activation(hidden_states)
435 hidden_states, _ = self.w_2(hidden_states)
436 return hidden_states
437
438
439 class EncoderLayer(nn.Module):
440 def __init__(
441 self,
442 size: int,
443 self_attn: nn.Module,
444 feed_forward: nn.Module,
445 ):
446 super().__init__()
447 self.self_attn = self_attn
448 self.feed_forward = feed_forward
449 self.norm1 = LayerNorm(size)
450 self.norm2 = LayerNorm(size)
451
452 def forward(self, hidden_states: torch.Tensor):
453 residual = hidden_states
454 hidden_states = self.norm1(hidden_states)
455 hidden_states = residual + self.self_attn(hidden_states, None, None)
456 residual = hidden_states
457 hidden_states = self.norm2(hidden_states)
458 hidden_states = residual + self.feed_forward(hidden_states)
459
460 return hidden_states
461
462
463 class FunASRAudioAttention(nn.Module):
464 def __init__(
465 self,
466 num_heads: int,
467 embed_dim: int,
468 prefix: str = "",
469 ):
470 super().__init__()
471 self.embed_dim = embed_dim
472 self.num_heads = num_heads
473 self.head_dim = self.embed_dim // self.num_heads
474 tp_size = get_tensor_model_parallel_world_size()
475 self.num_local_heads = self.num_heads // tp_size
476
477 if (self.head_dim * self.num_heads) != self.embed_dim:
478 raise ValueError(
479 f"embed_dim must be divisible by num_heads (got `embed_dim`: "
480 f"{self.embed_dim} and `num_heads`: {self.num_heads})."
481 )
482
483 self.scaling = self.head_dim**-0.5
484
485 self.qkv = QKVParallelLinear(
486 hidden_size=self.embed_dim,
487 head_size=self.head_dim,
488 total_num_heads=self.num_heads,
489 total_num_kv_heads=self.num_heads,
490 bias=True,
491 prefix=f"{prefix}.qkv",
492 )
493
494 self.out_proj = RowParallelLinear(
495 input_size=self.embed_dim,
496 output_size=self.embed_dim,
497 bias=True,
498 prefix=f"{prefix}.out_proj",
499 )
500
501 self.attn = MMEncoderAttention(
502 num_heads=self.num_local_heads,
503 head_size=self.head_dim,
504 scale=self.scaling,
505 )
506
507 def forward(
508 self,
509 hidden_states: torch.Tensor,
510 cu_seqlens: torch.Tensor,
511 max_seqlen: torch.Tensor | None,
512 ) -> torch.Tensor:
513 bs, seq_length, _ = hidden_states.size()
514 qkv, _ = self.qkv(hidden_states)
515 q, k, v = qkv.chunk(3, dim=-1)
516 q = q.view(bs, seq_length, -1, self.head_dim)
517 k = k.view(bs, seq_length, -1, self.head_dim)
518 v = v.view(bs, seq_length, -1, self.head_dim)
519
520 attn_output = self.attn(
521 query=q,
522 key=k,
523 value=v,
524 cu_seqlens=cu_seqlens,
525 max_seqlen=max_seqlen,
526 )
527
528 attn_output = attn_output.view(bs, seq_length, -1)
529 output, _ = self.out_proj(attn_output)
530 return output
531
532
533 class Transformer(nn.Module):
534 def __init__(
535 self,
536 downsample_rate=2,
537 encoder_dim=1280,
538 llm_dim=4096,
539 ffn_dim: int = 2048,
540 prefix: str = "",
541 **kwargs,
542 ):
543 super().__init__()
544 self.k = downsample_rate
545 self.encoder_dim = encoder_dim
546 self.llm_dim = llm_dim
547 self.linear1 = ColumnParallelLinear(
548 input_size=self.encoder_dim * self.k,
549 output_size=ffn_dim,
550 bias=True,
551 )
552 self.relu = nn.ReLU()
553 self.linear2 = RowParallelLinear(
554 input_size=ffn_dim,
555 output_size=self.llm_dim,
556 bias=True,
557 )
558
559 self.blocks = None
560 if kwargs.get("n_layer", 2) > 0:
561 self.blocks = nn.ModuleList(
562 [
563 EncoderLayer(
564 llm_dim,
565 FunASRAudioAttention(
566 kwargs.get("attention_heads", 8),
567 llm_dim,
568 prefix=f"{prefix}.self_attn",
569 ),
570 PositionwiseFeedForward(
571 llm_dim,
572 llm_dim // 4,
573 ),
574 )
575 for _ in range(kwargs.get("n_layer", 2))
576 ]
577 )
578
579 def forward(self, hidden_states: torch.Tensor, ilens: torch.Tensor):
580 max_len = ilens.max()
581 hidden_states = hidden_states[:, :max_len, :]
582 batch_size, seq_len, dim = hidden_states.size()
583 chunk_num = (seq_len - 1) // self.k + 1
584 pad_num = chunk_num * self.k - seq_len
585 hidden_states = F.pad(hidden_states, (0, 0, 0, pad_num, 0, 0), value=0.0)
586 seq_len = hidden_states.size(1)
587
588 hidden_states = hidden_states.contiguous()
589 hidden_states = hidden_states.view(batch_size, chunk_num, dim * self.k)
590 hidden_states, _ = self.linear1(hidden_states)
591 hidden_states = self.relu(hidden_states)
592 hidden_states, _ = self.linear2(hidden_states)
593
594 olens = None
595 olens = (ilens - 1) // self.k + 1
596
597 if self.blocks is not None:
598 for layer, block in enumerate(self.blocks):
599 hidden_states = block(hidden_states)
600 return hidden_states, olens
601
602
603 class FunASRAudioInputs(TensorSchema):
604 """Dimensions:
605 - b: Batch size
606 - nmb: Number of mel bins
607 - t: Time frames (M)
608 """
609
610 input_features: Annotated[
611 list[torch.Tensor] | None,
612 TensorShape("b", "nmb", "t"),
613 ]
614 speech_lengths: Annotated[
615 list[torch.Tensor] | None,
616 TensorShape("b"),
617 ]
618 fake_token_lengths: Annotated[
619 list[torch.Tensor] | None,
620 TensorShape("b"),
621 ]
622
623
624 class FunASREncoder(nn.Module):
625 hf_to_vllm_mapper = WeightsMapper(
626 orig_to_new_stacked={
627 ".self_attn.q_proj": (".self_attn.qkv", "q"),
628 ".self_attn.k_proj": (".self_attn.qkv", "k"),
629 ".self_attn.v_proj": (".self_attn.qkv", "v"),
630 }
631 )
632
633 def __init__(
634 self, *, vllm_config: VllmConfig, prefix: str = "", init_in_fp32: bool = False
635 ):
636 super().__init__()
637 self.audio_encoder = SenseVoiceEncoderSmall(
638 input_size=560, **vllm_config.model_config.hf_config.audio_encoder_conf
639 )
640 self.audio_adaptor = Transformer(
641 downsample_rate=1,
642 use_low_frame_rate=True,
643 ffn_dim=2048,
644 llm_dim=1024,
645 encoder_dim=512,
646 n_layer=2,
647 freeze=True,
648 prefix=maybe_prefix(prefix, "audio_encoder"),
649 )
650
651 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
652 loader = AutoWeightsLoader(self)
653 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
654
655
656 class FunASRModel(nn.Module):
657 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
658 super().__init__()
659 self.encoder = FunASREncoder(
660 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "encoder")
661 )
662 self.decoder = Qwen3Model(
663 vllm_config=vllm_config, prefix=maybe_prefix(prefix, "decoder")
664 )
665
666 def forward(
667 self,
668 input_ids: torch.Tensor | None,
669 positions: torch.Tensor,
670 inputs_embeds: torch.Tensor | None = None,
671 ) -> torch.Tensor:
672 decoder_outputs = self.decoder(
673 input_ids=input_ids,
674 positions=positions,
675 inputs_embeds=inputs_embeds,
676 )
677 return decoder_outputs
678
679 def get_encoder_outputs(
680 self,
681 speech: torch.Tensor | list[torch.Tensor] | None,
682 speech_lengths: torch.Tensor | list[torch.Tensor] | None,
683 ) -> torch.Tensor | None:
684 self.feat_permute = False
685
686 if self.feat_permute:
687 if not isinstance(speech, torch.Tensor):
688 raise NotImplementedError("List audio inputs cannot be permuted")
689 encoder_out, encoder_out_lens = self.encoder.audio_encoder(
690 speech.permute(0, 2, 1), speech_lengths
691 )
692 else:
693 encoder_out, encoder_out_lens = self.encoder.audio_encoder(
694 speech, speech_lengths
695 )
696
697 encoder_out, encoder_out_lens = self.encoder.audio_adaptor(
698 encoder_out, encoder_out_lens
699 )
700 return encoder_out
701
702
703 class FunASRProcessingInfo(BaseProcessingInfo):
704 def get_hf_config(self) -> Qwen3Config:
705 return self.ctx.get_hf_config(Qwen3Config)
706
707 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
708 return {"audio": 1}
709
710 def get_feature_extractor(self, **kwargs: object) -> FunASRFeatureExtractor:
711 hf_processor = self.get_hf_processor(**kwargs)
712 feature_extractor = hf_processor.feature_extractor # type: ignore
713 assert isinstance(feature_extractor, FunASRFeatureExtractor)
714 return feature_extractor
715
716 def get_data_parser(self) -> MultiModalDataParser:
717 feature_extractor = self.get_feature_extractor()
718 return MultiModalDataParser(
719 target_sr=feature_extractor.sampling_rate,
720 target_channels=self.get_target_channels(),
721 )
722
723 def get_target_channels(self) -> int:
724 return 1
725
726
727 class FunASRDummyInputsBuilder(BaseDummyInputsBuilder[FunASRProcessingInfo]):
728 def get_dummy_text(self, mm_counts: Mapping[str, int]) -> str:
729 num_audios = mm_counts.get("audio", 0)
730
731 return "<|AUDIO|>" * num_audios
732
733 def get_dummy_mm_data(
734 self,
735 seq_len: int,
736 mm_counts: Mapping[str, int],
737 mm_options: MultiModalDummyOptions,
738 ) -> MultiModalDataDict:
739 feature_extractor = self.info.get_feature_extractor()
740
741 sampling_rate = feature_extractor.sampling_rate
742 audio_len = feature_extractor.chunk_length * sampling_rate
743
744 return {
745 "audio": self._get_dummy_audios(
746 length=audio_len,
747 num_audios=mm_counts.get("audio", 0),
748 overrides=mm_options.get("audio"),
749 ),
750 }
751
752
753 class FunASRMultiModalProcessor(BaseMultiModalProcessor[FunASRProcessingInfo]):
754 def _get_hf_mm_text(self, mm_counts: Mapping[str, int]) -> str:
755 return self.dummy_inputs.get_dummy_text(mm_counts)
756
757 def _get_hf_mm_inputs(
758 self,
759 mm_items: MultiModalDataItems,
760 hf_kwargs: Mapping[str, object],
761 ) -> HFMultiModalInputs:
762 hf_inputs = super()._get_hf_mm_inputs(mm_items, hf_kwargs)
763
764 feature_extractor = self.info.get_feature_extractor(**hf_kwargs)
765 return hf_inputs._replace(
766 hf_kwargs=dict(
767 hf_inputs.hf_kwargs,
768 sampling_rate=feature_extractor.sampling_rate,
769 )
770 )
771
772 def _cached_apply_hf_processor(
773 self,
774 inputs: ProcessorInputs,
775 timing_ctx: TimingContext,
776 ) -> MultiModalProcessingResult:
777 # Dithering injects noise into the extracted features, so the
778 # feature extractor is not a pure function of its input. Since the
779 # processing cache assumes that processor outputs are invariant
780 # across calls, bypass the cache when dithering is active.
781 if self.info.get_feature_extractor().dither > 0:
782 return self._apply_hf_processor(inputs, timing_ctx)
783
784 return super()._cached_apply_hf_processor(inputs, timing_ctx)
785
786 def _get_mm_fields_config(
787 self,
788 hf_inputs: BatchFeature,
789 hf_processor_mm_kwargs: Mapping[str, object],
790 ) -> Mapping[str, MultiModalFieldConfig]:
791 return dict(
792 input_features=MultiModalFieldConfig.batched("audio"),
793 speech_lengths=MultiModalFieldConfig.batched("audio"),
794 fake_token_lengths=MultiModalFieldConfig.batched("audio", keep_on_cpu=True),
795 )
796
797 def _get_prompt_updates(
798 self,
799 mm_items: MultiModalDataItems,
800 hf_processor_mm_kwargs: Mapping[str, object],
801 out_mm_kwargs: MultiModalKwargsItems,
802 ) -> Sequence[PromptUpdate]:
803 processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
804 audio_token_id = processor.audio_token_id
805
806 out_mm_data = out_mm_kwargs.get_data()
807
808 fake_token_lengths = out_mm_data.get("fake_token_lengths")
809 if fake_token_lengths is None:
810 audio_output_lengths = []
811 else:
812 assert isinstance(fake_token_lengths, torch.Tensor)
813
814 audio_output_lengths = fake_token_lengths.tolist()
815
816 def get_replacement_qwen2_audio(item_idx: int):
817 num_features = audio_output_lengths[item_idx]
818 return [audio_token_id] * num_features
819
820 return [
821 PromptReplacement(
822 modality="audio",
823 target=[audio_token_id],
824 replacement=get_replacement_qwen2_audio,
825 )
826 ]
827
828
829 @MULTIMODAL_REGISTRY.register_processor(
830 FunASRMultiModalProcessor,
831 info=FunASRProcessingInfo,
832 dummy_inputs=FunASRDummyInputsBuilder,
833 )
834 class FunASRForConditionalGeneration(
835 nn.Module, SupportsTranscription, SupportsMultiModal
836 ):
837 hf_to_vllm_mapper = WeightsMapper(
838 orig_to_new_substr={
839 "linear_q.": "q_proj.",
840 "linear_k.": "k_proj.",
841 "linear_v.": "v_proj.",
842 "linear_out.": "out_proj.",
843 "audio_adaptor.": "model.encoder.audio_adaptor.",
844 "audio_encoder.": "model.encoder.audio_encoder.",
845 "llm.model.": "model.decoder.",
846 "llm.lm_head": "lm_head",
847 }
848 )
849
850 supports_transcription_only = True
851 supports_segment_timestamp = True
852 supported_languages = ISO639_1_SUPPORTED_LANGS
853
854 @classmethod
855 def validate_language(cls, language: str | None) -> str | None:
856 if language is None:
857 # TODO language should be optional and can be guessed.
858 # For now we default to en. See
859 # https://github.com/huggingface/transformers/blob/main/src/transformers/models/whisper/generation_whisper.py#L1520
860 logger.warning(
861 "Defaulting to language='en'. If you wish to transcribe "
862 "audio in a different language, pass the `language` field "
863 "in the TranscriptionRequest."
864 )
865 language = "en"
866 return super().validate_language(language)
867
868 @classmethod
869 def get_generation_prompt(
870 cls,
871 stt_params: SpeechToTextParams,
872 ) -> PromptType:
873 audio = stt_params.audio
874 stt_config = stt_params.stt_config
875 language = stt_params.language
876 hotwords = stt_params.hotwords
877
878 if language is None:
879 raise ValueError(
880 "Language must be specified when creating the funasr prompt"
881 )
882
883 if hotwords is not None:
884 funasr_prompt = "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n请结合上下文信息,更加准确地完成语音转写任务。如果没有相关信息,我们会留空。\n\n\n**上下文信息:**\n\n\n热词列表:[{}]\n语音转写:<|AUDIO|><|im_end|>\n<|im_start|>assistant\n".format( # noqa: E501
885 hotwords
886 )
887 else:
888 funasr_prompt = "<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n语音转写:<|AUDIO|><|im_end|>\n<|im_start|>assistant\n" # noqa: E501
889
890 prompt = {
891 "prompt": funasr_prompt,
892 "multi_modal_data": {
893 "audio": (audio, stt_config.sample_rate),
894 },
895 }
896 return cast(PromptType, prompt)
897
898 @classmethod
899 def get_speech_to_text_config(
900 cls, model_config: ModelConfig, task_type: str
901 ) -> SpeechToTextConfig:
902 processor = cached_processor_from_config(model_config)
903
904 return SpeechToTextConfig(
905 max_audio_clip_s=processor.feature_extractor.chunk_length,
906 sample_rate=processor.feature_extractor.sampling_rate,
907 )
908
909 @classmethod
910 def get_num_audio_tokens(
911 cls,
912 audio_duration_s: float,
913 stt_config: SpeechToTextConfig,
914 model_config: ModelConfig,
915 ) -> int | None:
916 processor = cached_processor_from_config(model_config)
917 hop_length = processor.feature_extractor.hop_length
918 assert hop_length is not None
919 return math.ceil(audio_duration_s * stt_config.sample_rate / hop_length)
920
921 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
922 super().__init__()
923 config = vllm_config.model_config.hf_config
924 quant_config = vllm_config.quant_config
925 self.config = config
926 self.dtype = vllm_config.model_config.dtype
927
928 self.model = FunASRModel(
929 vllm_config=vllm_config,
930 prefix=maybe_prefix(prefix, "model"),
931 )
932 logit_scale = getattr(config, "logit_scale", 1.0)
933
934 self.lm_head = ParallelLMHead(
935 config.vocab_size,
936 config.hidden_size,
937 quant_config=quant_config,
938 prefix=maybe_prefix(prefix, "lm_head"),
939 )
940 if config.tie_word_embeddings:
941 self.lm_head = self.lm_head.tie_weights(self.model.decoder.embed_tokens)
942 self.logits_processor = LogitsProcessor(config.vocab_size, scale=logit_scale)
943
944 def get_language_model(self) -> torch.nn.Module:
945 # Required as part of SupportsMultiModal interface.
946 return self.model.decoder
947
948 def forward(
949 self,
950 input_ids: torch.Tensor,
951 positions: torch.Tensor,
952 inputs_embeds: torch.Tensor | None = None,
953 **kwargs,
954 ) -> torch.Tensor:
955 decoder_outputs = self.model(
956 input_ids=input_ids,
957 positions=positions,
958 inputs_embeds=inputs_embeds,
959 )
960 return decoder_outputs
961
962 def embed_multimodal(self, **kwargs: object) -> MultiModalEmbeddings:
963 audio_input = self._parse_and_validate_audio_input(**kwargs)
964
965 speech = audio_input["input_features"]
966 speech_lengths = audio_input["speech_lengths"]
967 enc_output = self.model.get_encoder_outputs(
968 speech=speech, speech_lengths=speech_lengths
969 )
970
971 return enc_output
972
973 def embed_input_ids(
974 self,
975 input_ids: torch.Tensor,
976 multimodal_embeddings: MultiModalEmbeddings | None = None,
977 *,
978 is_multimodal: torch.Tensor | None = None,
979 ) -> torch.Tensor:
980 inputs_embeds = self.model.decoder.embed_input_ids(input_ids)
981
982 return _merge_multimodal_embeddings(
983 inputs_embeds=inputs_embeds,
984 multimodal_embeddings=multimodal_embeddings,
985 is_multimodal=_require_is_multimodal(is_multimodal),
986 )
987
988 def _parse_and_validate_audio_input(self, **kwargs: object) -> FunASRAudioInputs:
989 input_features = kwargs.pop("input_features", None)
990 speech_lengths = kwargs.pop("speech_lengths", None)
991 fake_token_lengths = kwargs.pop("fake_token_lengths", None)
992
993 return FunASRAudioInputs(
994 input_features=input_features,
995 speech_lengths=speech_lengths,
996 fake_token_lengths=fake_token_lengths,
997 )
998
999 def compute_logits(self, hidden_states: torch.Tensor) -> torch.Tensor:
1000 logits = self.logits_processor(self.lm_head, hidden_states)
1001 return logits
1002
1003 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
1004 loader = AutoWeightsLoader(
1005 self,
1006 )
1007
1008 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)