main
py 120 lines 4.18 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Inference-only Qwen3-ASR ForcedAligner model (token classification)."""
4
5 from collections.abc import Iterable
6
7 import torch
8 import torch.nn as nn
9
10 from vllm.config import VllmConfig
11 from vllm.model_executor.layers.pooler.tokwise import pooler_for_token_classify
12 from vllm.model_executor.models.interfaces_base import default_pooling_type
13 from vllm.model_executor.models.qwen3_asr import (
14 Qwen3ASRDummyInputsBuilder,
15 Qwen3ASRForConditionalGeneration,
16 Qwen3ASRMultiModalProcessor,
17 Qwen3ASRProcessingInfo,
18 )
19 from vllm.model_executor.models.utils import AutoWeightsLoader, WeightsMapper
20 from vllm.multimodal import MULTIMODAL_REGISTRY
21 from vllm.sequence import IntermediateTensors
22
23
24 @default_pooling_type(tok_pooling_type="ALL")
25 @MULTIMODAL_REGISTRY.register_processor(
26 Qwen3ASRMultiModalProcessor,
27 info=Qwen3ASRProcessingInfo,
28 dummy_inputs=Qwen3ASRDummyInputsBuilder,
29 )
30 class Qwen3ASRForcedAlignerForTokenClassification(
31 Qwen3ASRForConditionalGeneration,
32 ):
33 """Qwen3-ASR Forced Aligner model for per-token timestamp classification.
34
35 This model shares the audio tower and language model backbone with
36 Qwen3-ASR, but replaces the LM head with a classification head that
37 predicts time bins at ``<timestamp>`` token positions.
38
39 Usage::
40
41 llm = LLM(
42 model="Qwen/Qwen3-ForcedAligner-0.6B",
43 runner="pooling",
44 hf_overrides={
45 "architectures": ["Qwen3ASRForcedAlignerForTokenClassification"]
46 },
47 )
48 outputs = llm.encode(
49 [{"prompt": prompt, "multi_modal_data": {"audio": audio}}],
50 pooling_task="token_classify",
51 )
52 """
53
54 is_pooling_model = True
55
56 # Map thinker.lm_head -> classifier (not language_model.lm_head)
57 hf_to_vllm_mapper = WeightsMapper(
58 orig_to_new_prefix={
59 "thinker.lm_head.": "classifier.",
60 "thinker.model.": "language_model.model.",
61 "thinker.": "",
62 "talker.": None,
63 "code2wav.": None,
64 }
65 )
66
67 def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""):
68 super().__init__(vllm_config=vllm_config, prefix=prefix)
69
70 config = vllm_config.model_config.hf_config
71 thinker_config = config.thinker_config
72
73 # Remove the unused generation head created by the base class;
74 # the forced aligner uses a classifier head instead.
75 language_model: nn.Module = self.language_model
76 language_model.lm_head = None
77 language_model.logits_processor = None
78
79 self.classify_num = thinker_config.classify_num
80
81 # Classification head replaces lm_head for time-bin prediction.
82 # Use model dtype (not head_dtype which defaults to float32 for
83 # pooling models) to match the hidden state dtype.
84 self.classifier = nn.Linear(
85 thinker_config.text_config.hidden_size,
86 self.classify_num,
87 bias=False,
88 dtype=vllm_config.model_config.dtype,
89 )
90
91 # Token-level pooler to split per-token logits per request
92 pooler_config = vllm_config.model_config.pooler_config
93 assert pooler_config is not None
94 self.pooler = pooler_for_token_classify(pooler_config)
95
96 def forward(
97 self,
98 input_ids: torch.Tensor,
99 positions: torch.Tensor,
100 intermediate_tensors: IntermediateTensors | None = None,
101 inputs_embeds: torch.Tensor | None = None,
102 **kwargs: object,
103 ) -> torch.Tensor:
104 if intermediate_tensors is not None:
105 inputs_embeds = None
106
107 # Run through language model backbone (transformer layers only)
108 hidden_states = self.language_model.model(
109 input_ids,
110 positions,
111 intermediate_tensors,
112 inputs_embeds=inputs_embeds,
113 )
114
115 # Apply classification head -> [num_tokens, classify_num]
116 return self.classifier(hidden_states)
117
118 def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
119 loader = AutoWeightsLoader(self)
120 return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)