Dragonhead commited on
Commit
2108441
·
verified ·
1 Parent(s): e3f6328

Fix loadability: add auto_map (config+preprocessor) + qwen3_asr modeling code + chat_template

Browse files
__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2026 The Alibaba Qwen team.
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ from .configuration_qwen3_asr import Qwen3ASRConfig
17
+ from .modeling_qwen3_asr import Qwen3ASRForConditionalGeneration
18
+ from .processing_qwen3_asr import Qwen3ASRProcessor
chat_template.jinja ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set ns = namespace(system_text="") -%}
2
+ {%- for m in messages -%}
3
+ {%- if m.role == 'system' -%}
4
+ {%- if m.content is string -%}
5
+ {%- set ns.system_text = ns.system_text + m.content -%}
6
+ {%- else -%}
7
+ {%- for c in m.content -%}
8
+ {%- if c.type == 'text' and (c.text is defined) -%}
9
+ {%- set ns.system_text = ns.system_text + c.text -%}
10
+ {%- endif -%}
11
+ {%- endfor -%}
12
+ {%- endif -%}
13
+ {%- endif -%}
14
+ {%- endfor -%}
15
+
16
+ {%- set ns2 = namespace(audio_tokens="") -%}
17
+ {%- for m in messages -%}
18
+ {%- if m.content is not string -%}
19
+ {%- for c in m.content -%}
20
+ {%- if c.type == 'audio' or ('audio' in c) or ('audio_url' in c) -%}
21
+ {%- set ns2.audio_tokens = ns2.audio_tokens + "<|audio_start|><|audio_pad|><|audio_end|>" -%}
22
+ {%- endif -%}
23
+ {%- endfor -%}
24
+ {%- endif -%}
25
+ {%- endfor -%}
26
+
27
+ {{- '<|im_start|>system\n' + (ns.system_text if ns.system_text is string else '') + '<|im_end|>\n' -}}
28
+ {{- '<|im_start|>user\n' + ns2.audio_tokens + '<|im_end|>\n' -}}
29
+ {%- if add_generation_prompt -%}
30
+ {{- '<|im_start|>assistant\n' -}}
31
+ {%- endif -%}
config.json CHANGED
@@ -1,224 +1,230 @@
1
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  "architectures": [
3
- "Qwen3ASRForConditionalGeneration"
4
  ],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  "dtype": "bfloat16",
6
- "eos_token_id": 151645,
7
- "model_type": "qwen3_asr",
8
- "pad_token_id": 151643,
9
- "support_languages": [
10
- "Chinese",
11
- "English",
12
- "Cantonese",
13
- "Arabic",
14
- "German",
15
- "French",
16
- "Spanish",
17
- "Portuguese",
18
- "Indonesian",
19
- "Italian",
20
- "Korean",
21
- "Russian",
22
- "Thai",
23
- "Vietnamese",
24
- "Japanese",
25
- "Turkish",
26
- "Hindi",
27
- "Malay",
28
- "Dutch",
29
- "Swedish",
30
- "Danish",
31
- "Finnish",
32
- "Polish",
33
- "Czech",
34
- "Filipino",
35
- "Persian",
36
- "Greek",
37
- "Romanian",
38
- "Hungarian",
39
- "Macedonian"
40
- ],
41
- "thinker_config": {
42
- "architectures": [
43
- "Qwen3ASRForConditionalGeneration"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  ],
45
- "audio_config": {
46
- "_name_or_path": "",
47
- "activation_dropout": 0,
48
- "activation_function": "gelu",
49
- "add_cross_attention": false,
50
- "architectures": null,
51
- "attention_dropout": 0,
52
- "bad_words_ids": null,
53
- "begin_suppress_tokens": null,
54
- "bos_token_id": null,
55
- "chunk_size_feed_forward": 0,
56
- "conv_chunksize": 500,
57
- "cross_attention_hidden_size": null,
58
- "d_model": 896,
59
- "decoder_start_token_id": null,
60
- "diversity_penalty": 0.0,
61
- "do_sample": false,
62
- "downsample_hidden_size": 480,
63
- "dropout": 0,
64
- "dtype": null,
65
- "early_stopping": false,
66
- "encoder_attention_heads": 14,
67
- "encoder_ffn_dim": 3584,
68
- "encoder_layers": 18,
69
- "encoder_no_repeat_ngram_size": 0,
70
- "eos_token_id": null,
71
- "exponential_decay_length_penalty": null,
72
- "finetuning_task": null,
73
- "forced_bos_token_id": null,
74
- "forced_eos_token_id": null,
75
- "id2label": {
76
- "0": "LABEL_0",
77
- "1": "LABEL_1"
78
- },
79
- "initializer_range": 0.02,
80
- "is_decoder": false,
81
- "is_encoder_decoder": false,
82
- "label2id": {
83
- "LABEL_0": 0,
84
- "LABEL_1": 1
85
- },
86
- "length_penalty": 1.0,
87
- "max_length": 20,
88
- "max_source_positions": 1500,
89
- "min_length": 0,
90
- "model_type": "qwen3_asr_audio_encoder",
91
- "n_window": 50,
92
- "n_window_infer": 800,
93
- "no_repeat_ngram_size": 0,
94
- "num_beam_groups": 1,
95
- "num_beams": 1,
96
- "num_hidden_layers": 18,
97
- "num_mel_bins": 128,
98
- "num_return_sequences": 1,
99
- "output_attentions": false,
100
- "output_dim": 1024,
101
- "output_hidden_states": false,
102
- "output_scores": false,
103
- "pad_token_id": null,
104
- "prefix": null,
105
- "problem_type": null,
106
- "pruned_heads": {},
107
- "remove_invalid_values": false,
108
- "repetition_penalty": 1.0,
109
- "return_dict": true,
110
- "return_dict_in_generate": false,
111
- "scale_embedding": false,
112
- "sep_token_id": null,
113
- "suppress_tokens": null,
114
- "task_specific_params": null,
115
- "temperature": 1.0,
116
- "tf_legacy_loss": false,
117
- "tie_encoder_decoder": false,
118
- "tie_word_embeddings": true,
119
- "tokenizer_class": null,
120
- "top_k": 50,
121
- "top_p": 1.0,
122
- "torchscript": false,
123
- "typical_p": 1.0,
124
- "use_bfloat16": false
125
- },
126
- "audio_end_token_id": 151670,
127
- "audio_start_token_id": 151669,
128
- "audio_token_id": 151676,
129
- "dtype": "bfloat16",
130
- "initializer_range": 0.02,
131
- "model_type": "qwen3_asr_thinker",
132
- "text_config": {
133
- "_name_or_path": "",
134
- "add_cross_attention": false,
135
- "architectures": null,
136
- "attention_bias": false,
137
- "attention_dropout": 0.0,
138
- "bad_words_ids": null,
139
- "begin_suppress_tokens": null,
140
- "bos_token_id": null,
141
- "chunk_size_feed_forward": 0,
142
- "cross_attention_hidden_size": null,
143
- "decoder_start_token_id": null,
144
- "diversity_penalty": 0.0,
145
- "do_sample": false,
146
- "dtype": null,
147
- "early_stopping": false,
148
- "encoder_no_repeat_ngram_size": 0,
149
- "eos_token_id": null,
150
- "exponential_decay_length_penalty": null,
151
- "finetuning_task": null,
152
- "forced_bos_token_id": null,
153
- "forced_eos_token_id": null,
154
- "head_dim": 128,
155
- "hidden_act": "silu",
156
- "hidden_size": 1024,
157
- "id2label": {
158
- "0": "LABEL_0",
159
- "1": "LABEL_1"
160
- },
161
- "initializer_range": 0.02,
162
- "intermediate_size": 3072,
163
- "is_decoder": false,
164
- "is_encoder_decoder": false,
165
- "label2id": {
166
- "LABEL_0": 0,
167
- "LABEL_1": 1
168
- },
169
- "length_penalty": 1.0,
170
- "max_length": 20,
171
- "max_position_embeddings": 65536,
172
- "min_length": 0,
173
- "model_type": "qwen3_asr_text",
174
- "no_repeat_ngram_size": 0,
175
- "num_attention_heads": 16,
176
- "num_beam_groups": 1,
177
- "num_beams": 1,
178
- "num_hidden_layers": 28,
179
- "num_key_value_heads": 8,
180
- "num_return_sequences": 1,
181
- "output_attentions": false,
182
- "output_hidden_states": false,
183
- "output_scores": false,
184
- "pad_token_id": null,
185
- "prefix": null,
186
- "problem_type": null,
187
- "pruned_heads": {},
188
- "remove_invalid_values": false,
189
- "repetition_penalty": 1.0,
190
- "return_dict": true,
191
- "return_dict_in_generate": false,
192
- "rms_norm_eps": 1e-06,
193
- "rope_scaling": {
194
- "interleaved": true,
195
- "mrope_interleaved": true,
196
- "mrope_section": [
197
- 24,
198
- 20,
199
- 20
200
- ],
201
- "rope_type": "default",
202
- "type": "default"
203
- },
204
- "rope_theta": 1000000,
205
- "sep_token_id": null,
206
- "suppress_tokens": null,
207
- "task_specific_params": null,
208
- "temperature": 1.0,
209
- "tf_legacy_loss": false,
210
- "tie_encoder_decoder": false,
211
- "tie_word_embeddings": true,
212
- "tokenizer_class": null,
213
- "top_k": 50,
214
- "top_p": 1.0,
215
- "torchscript": false,
216
- "typical_p": 1.0,
217
- "use_bfloat16": false,
218
- "use_cache": true,
219
- "vocab_size": 151936
220
- },
221
- "user_token_id": 872
222
  },
223
- "transformers_version": "4.57.6"
224
- }
 
 
 
 
 
 
 
 
 
1
  {
2
+ "architectures": [
3
+ "Qwen3ASRForConditionalGeneration"
4
+ ],
5
+ "dtype": "bfloat16",
6
+ "eos_token_id": 151645,
7
+ "model_type": "qwen3_asr",
8
+ "pad_token_id": 151643,
9
+ "support_languages": [
10
+ "Chinese",
11
+ "English",
12
+ "Cantonese",
13
+ "Arabic",
14
+ "German",
15
+ "French",
16
+ "Spanish",
17
+ "Portuguese",
18
+ "Indonesian",
19
+ "Italian",
20
+ "Korean",
21
+ "Russian",
22
+ "Thai",
23
+ "Vietnamese",
24
+ "Japanese",
25
+ "Turkish",
26
+ "Hindi",
27
+ "Malay",
28
+ "Dutch",
29
+ "Swedish",
30
+ "Danish",
31
+ "Finnish",
32
+ "Polish",
33
+ "Czech",
34
+ "Filipino",
35
+ "Persian",
36
+ "Greek",
37
+ "Romanian",
38
+ "Hungarian",
39
+ "Macedonian"
40
+ ],
41
+ "thinker_config": {
42
  "architectures": [
43
+ "Qwen3ASRForConditionalGeneration"
44
  ],
45
+ "audio_config": {
46
+ "_name_or_path": "",
47
+ "activation_dropout": 0,
48
+ "activation_function": "gelu",
49
+ "add_cross_attention": false,
50
+ "architectures": null,
51
+ "attention_dropout": 0,
52
+ "bad_words_ids": null,
53
+ "begin_suppress_tokens": null,
54
+ "bos_token_id": null,
55
+ "chunk_size_feed_forward": 0,
56
+ "conv_chunksize": 500,
57
+ "cross_attention_hidden_size": null,
58
+ "d_model": 896,
59
+ "decoder_start_token_id": null,
60
+ "diversity_penalty": 0.0,
61
+ "do_sample": false,
62
+ "downsample_hidden_size": 480,
63
+ "dropout": 0,
64
+ "dtype": null,
65
+ "early_stopping": false,
66
+ "encoder_attention_heads": 14,
67
+ "encoder_ffn_dim": 3584,
68
+ "encoder_layers": 18,
69
+ "encoder_no_repeat_ngram_size": 0,
70
+ "eos_token_id": null,
71
+ "exponential_decay_length_penalty": null,
72
+ "finetuning_task": null,
73
+ "forced_bos_token_id": null,
74
+ "forced_eos_token_id": null,
75
+ "id2label": {
76
+ "0": "LABEL_0",
77
+ "1": "LABEL_1"
78
+ },
79
+ "initializer_range": 0.02,
80
+ "is_decoder": false,
81
+ "is_encoder_decoder": false,
82
+ "label2id": {
83
+ "LABEL_0": 0,
84
+ "LABEL_1": 1
85
+ },
86
+ "length_penalty": 1.0,
87
+ "max_length": 20,
88
+ "max_source_positions": 1500,
89
+ "min_length": 0,
90
+ "model_type": "qwen3_asr_audio_encoder",
91
+ "n_window": 50,
92
+ "n_window_infer": 800,
93
+ "no_repeat_ngram_size": 0,
94
+ "num_beam_groups": 1,
95
+ "num_beams": 1,
96
+ "num_hidden_layers": 18,
97
+ "num_mel_bins": 128,
98
+ "num_return_sequences": 1,
99
+ "output_attentions": false,
100
+ "output_dim": 1024,
101
+ "output_hidden_states": false,
102
+ "output_scores": false,
103
+ "pad_token_id": null,
104
+ "prefix": null,
105
+ "problem_type": null,
106
+ "pruned_heads": {},
107
+ "remove_invalid_values": false,
108
+ "repetition_penalty": 1.0,
109
+ "return_dict": true,
110
+ "return_dict_in_generate": false,
111
+ "scale_embedding": false,
112
+ "sep_token_id": null,
113
+ "suppress_tokens": null,
114
+ "task_specific_params": null,
115
+ "temperature": 1.0,
116
+ "tf_legacy_loss": false,
117
+ "tie_encoder_decoder": false,
118
+ "tie_word_embeddings": true,
119
+ "tokenizer_class": null,
120
+ "top_k": 50,
121
+ "top_p": 1.0,
122
+ "torchscript": false,
123
+ "typical_p": 1.0,
124
+ "use_bfloat16": false
125
+ },
126
+ "audio_end_token_id": 151670,
127
+ "audio_start_token_id": 151669,
128
+ "audio_token_id": 151676,
129
  "dtype": "bfloat16",
130
+ "initializer_range": 0.02,
131
+ "model_type": "qwen3_asr_thinker",
132
+ "text_config": {
133
+ "_name_or_path": "",
134
+ "add_cross_attention": false,
135
+ "architectures": null,
136
+ "attention_bias": false,
137
+ "attention_dropout": 0.0,
138
+ "bad_words_ids": null,
139
+ "begin_suppress_tokens": null,
140
+ "bos_token_id": null,
141
+ "chunk_size_feed_forward": 0,
142
+ "cross_attention_hidden_size": null,
143
+ "decoder_start_token_id": null,
144
+ "diversity_penalty": 0.0,
145
+ "do_sample": false,
146
+ "dtype": null,
147
+ "early_stopping": false,
148
+ "encoder_no_repeat_ngram_size": 0,
149
+ "eos_token_id": null,
150
+ "exponential_decay_length_penalty": null,
151
+ "finetuning_task": null,
152
+ "forced_bos_token_id": null,
153
+ "forced_eos_token_id": null,
154
+ "head_dim": 128,
155
+ "hidden_act": "silu",
156
+ "hidden_size": 1024,
157
+ "id2label": {
158
+ "0": "LABEL_0",
159
+ "1": "LABEL_1"
160
+ },
161
+ "initializer_range": 0.02,
162
+ "intermediate_size": 3072,
163
+ "is_decoder": false,
164
+ "is_encoder_decoder": false,
165
+ "label2id": {
166
+ "LABEL_0": 0,
167
+ "LABEL_1": 1
168
+ },
169
+ "length_penalty": 1.0,
170
+ "max_length": 20,
171
+ "max_position_embeddings": 65536,
172
+ "min_length": 0,
173
+ "model_type": "qwen3_asr_text",
174
+ "no_repeat_ngram_size": 0,
175
+ "num_attention_heads": 16,
176
+ "num_beam_groups": 1,
177
+ "num_beams": 1,
178
+ "num_hidden_layers": 28,
179
+ "num_key_value_heads": 8,
180
+ "num_return_sequences": 1,
181
+ "output_attentions": false,
182
+ "output_hidden_states": false,
183
+ "output_scores": false,
184
+ "pad_token_id": null,
185
+ "prefix": null,
186
+ "problem_type": null,
187
+ "pruned_heads": {},
188
+ "remove_invalid_values": false,
189
+ "repetition_penalty": 1.0,
190
+ "return_dict": true,
191
+ "return_dict_in_generate": false,
192
+ "rms_norm_eps": 1e-06,
193
+ "rope_scaling": {
194
+ "interleaved": true,
195
+ "mrope_interleaved": true,
196
+ "mrope_section": [
197
+ 24,
198
+ 20,
199
+ 20
200
  ],
201
+ "rope_type": "default",
202
+ "type": "default"
203
+ },
204
+ "rope_theta": 1000000,
205
+ "sep_token_id": null,
206
+ "suppress_tokens": null,
207
+ "task_specific_params": null,
208
+ "temperature": 1.0,
209
+ "tf_legacy_loss": false,
210
+ "tie_encoder_decoder": false,
211
+ "tie_word_embeddings": true,
212
+ "tokenizer_class": null,
213
+ "top_k": 50,
214
+ "top_p": 1.0,
215
+ "torchscript": false,
216
+ "typical_p": 1.0,
217
+ "use_bfloat16": false,
218
+ "use_cache": true,
219
+ "vocab_size": 151936
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  },
221
+ "user_token_id": 872
222
+ },
223
+ "transformers_version": "4.57.6",
224
+ "auto_map": {
225
+ "AutoConfig": "configuration_qwen3_asr.Qwen3ASRConfig",
226
+ "AutoModel": "modeling_qwen3_asr.Qwen3ASRForConditionalGeneration",
227
+ "AutoModelForCausalLM": "modeling_qwen3_asr.Qwen3ASRForConditionalGeneration",
228
+ "AutoProcessor": "processing_qwen3_asr.Qwen3ASRProcessor"
229
+ }
230
+ }
configuration_qwen3_asr.py ADDED
@@ -0,0 +1,425 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2026 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ from transformers.configuration_utils import PretrainedConfig
16
+ from transformers.utils import logging
17
+
18
+
19
+ logger = logging.get_logger(__name__)
20
+
21
+
22
+ class Qwen3ASRAudioEncoderConfig(PretrainedConfig):
23
+ r"""
24
+ This is the configuration class to store the configuration of a [`Qwen3ASRAudioEncoder`]. It is used to instantiate a
25
+ Qwen3-ASR audio encoder according to the specified arguments, defining the model architecture. Instantiating a
26
+ configuration with the defaults will yield a similar configuration to that of the audio encoder of the Qwen2-Audio
27
+ architecture.
28
+
29
+ e.g. [Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B)
30
+
31
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
32
+ documentation from [`PretrainedConfig`] for more information.
33
+
34
+ Args:
35
+ num_mel_bins (`int`, *optional*, defaults to 128):
36
+ Number of mel features used per input features. Should correspond to the value used in the
37
+ `Qwen3ASRProcessor` class.
38
+ encoder_layers (`int`, *optional*, defaults to 32):
39
+ Number of encoder layers.
40
+ encoder_attention_heads (`int`, *optional*, defaults to 20):
41
+ Number of attention heads for each attention layer in the Transformer encoder.
42
+ encoder_ffn_dim (`int`, *optional*, defaults to 5120):
43
+ Dimensionality of the "intermediate" (often named feed-forward) layer in encoder.
44
+ d_model (`int`, *optional*, defaults to 1280):
45
+ Dimensionality of the layers.
46
+ dropout (`float`, *optional*, defaults to 0.0):
47
+ The dropout probability for all fully connected layers in the embeddings, encoder, and pooler.
48
+ attention_dropout (`float`, *optional*, defaults to 0.0):
49
+ The dropout ratio for the attention probabilities.
50
+ activation_function (`str`, *optional*, defaults to `"gelu"`):
51
+ The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
52
+ `"relu"`, `"silu"` and `"gelu_new"` are supported.
53
+ activation_dropout (`float`, *optional*, defaults to 0.0):
54
+ The dropout ratio for activations inside the fully connected layer.
55
+ scale_embedding (`bool`, *optional*, defaults to `False`):
56
+ Scale embeddings by diving by sqrt(d_model).
57
+ initializer_range (`float`, *optional*, defaults to 0.02):
58
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
59
+ max_source_positions (`int`, *optional*, defaults to 1500):
60
+ The maximum sequence length of log-mel filter-bank features that this model might ever be used with.
61
+ n_window (`int`, *optional*, defaults to 100):
62
+ The chunk for conv and flash attn in AudioEncoder.
63
+ output_dim (`int`, *optional*, defaults to 3584):
64
+ The output dimension of AudioEncoder.
65
+
66
+ Example:
67
+
68
+ ```python
69
+ >>> from transformers import Qwen3ASRAudioEncoderConfig, Qwen3ASRAudioEncoder
70
+
71
+ >>> # Initializing a Qwen3ASRAudioEncoderConfig
72
+ >>> configuration = Qwen3ASRAudioEncoderConfig()
73
+
74
+ >>> # Initializing a Qwen3ASRAudioEncoder (with random weights)
75
+ >>> model = Qwen3ASRAudioEncoder(configuration)
76
+
77
+ >>> # Accessing the model configuration
78
+ >>> configuration = model.config
79
+ ```"""
80
+
81
+ model_type = "qwen3_asr_audio_encoder"
82
+
83
+ def __init__(
84
+ self,
85
+ num_mel_bins=128,
86
+ encoder_layers=32,
87
+ encoder_attention_heads=20,
88
+ encoder_ffn_dim=5120,
89
+ d_model=1280,
90
+ dropout=0,
91
+ attention_dropout=0,
92
+ activation_function="gelu",
93
+ activation_dropout=0,
94
+ scale_embedding=False,
95
+ initializer_range=0.02,
96
+ max_source_positions=1500,
97
+ n_window=100,
98
+ output_dim=3584,
99
+ n_window_infer=400,
100
+ conv_chunksize=500,
101
+ downsample_hidden_size=480,
102
+ **kwargs,
103
+ ):
104
+ super().__init__(**kwargs)
105
+
106
+ self.num_mel_bins = num_mel_bins
107
+ self.d_model = d_model
108
+ self.encoder_layers = encoder_layers
109
+ self.encoder_attention_heads = encoder_attention_heads
110
+ self.encoder_ffn_dim = encoder_ffn_dim
111
+ self.dropout = dropout
112
+ self.attention_dropout = attention_dropout
113
+ self.activation_function = activation_function
114
+ self.activation_dropout = activation_dropout
115
+ self.num_hidden_layers = encoder_layers
116
+ self.initializer_range = initializer_range
117
+ self.scale_embedding = scale_embedding # scale factor will be sqrt(d_model) if True
118
+ self.max_source_positions = max_source_positions
119
+ self.n_window = n_window
120
+ self.output_dim = output_dim
121
+ self.n_window_infer = n_window_infer
122
+ self.conv_chunksize = conv_chunksize
123
+ self.downsample_hidden_size = downsample_hidden_size
124
+
125
+
126
+ class Qwen3ASRTextConfig(PretrainedConfig):
127
+ r"""
128
+ This is the configuration class to store the configuration of a [`Qwen3ASRTextModel`]. It is used to instantiate a
129
+ Qwen3-ASR model according to the specified arguments, defining the model architecture. Instantiating a configuration
130
+ with the defaults will yield a similar configuration to that of
131
+ Qwen3-ASR-1.7B [Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B)
132
+
133
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
134
+ documentation from [`PretrainedConfig`] for more information.
135
+
136
+ Args:
137
+ vocab_size (`int`, *optional*, defaults to 151936):
138
+ Vocabulary size of the Qwen3ASR model. Defines the number of different tokens that can be represented by the
139
+ `inputs_ids` passed when calling [`Qwen3ASRModel`]
140
+ hidden_size (`int`, *optional*, defaults to 4096):
141
+ Dimension of the hidden representations.
142
+ intermediate_size (`int`, *optional*, defaults to 22016):
143
+ Dimension of the MLP representations.
144
+ num_hidden_layers (`int`, *optional*, defaults to 32):
145
+ Number of hidden layers in the Transformer encoder.
146
+ num_attention_heads (`int`, *optional*, defaults to 32):
147
+ Number of attention heads for each attention layer in the Transformer encoder.
148
+ num_key_value_heads (`int`, *optional*, defaults to 32):
149
+ This is the number of key_value heads that should be used to implement Grouped Query Attention. If
150
+ `num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
151
+ `num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
152
+ converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
153
+ by meanpooling all the original heads within that group. For more details, check out [this
154
+ paper](https://huggingface.co/papers/2305.13245). If it is not specified, will default to `32`.
155
+ head_dim (`int`, *optional*, defaults to 128):
156
+ The dimension of the head. If not specified, will default to `hidden_size // num_attention_heads`.
157
+ hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
158
+ The non-linear activation function (function or string) in the decoder.
159
+ max_position_embeddings (`int`, *optional*, defaults to 128000):
160
+ The maximum sequence length that this model might ever be used with.
161
+ initializer_range (`float`, *optional*, defaults to 0.02):
162
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
163
+ rms_norm_eps (`float`, *optional*, defaults to 1e-06):
164
+ The epsilon used by the rms normalization layers.
165
+ use_cache (`bool`, *optional*, defaults to `True`):
166
+ Whether or not the model should return the last key/values attentions (not used by all models). Only
167
+ relevant if `config.is_decoder=True`.
168
+ tie_word_embeddings (`bool`, *optional*, defaults to `False`):
169
+ Whether the model's input and output word embeddings should be tied.
170
+ rope_theta (`float`, *optional*, defaults to 5000000.0):
171
+ The base period of the RoPE embeddings.
172
+ rope_scaling (`Dict`, *optional*):
173
+ Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
174
+ and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
175
+ accordingly.
176
+ Expected contents:
177
+ `rope_type` (`str`):
178
+ The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
179
+ 'llama3'], with 'default' being the original RoPE implementation.
180
+ `factor` (`float`, *optional*):
181
+ Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
182
+ most scaling types, a `factor` of x will enable the model to handle sequences of length x *
183
+ original maximum pre-trained length.
184
+ `original_max_position_embeddings` (`int`, *optional*):
185
+ Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
186
+ pretraining.
187
+ `attention_factor` (`float`, *optional*):
188
+ Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
189
+ computation. If unspecified, it defaults to value recommended by the implementation, using the
190
+ `factor` field to infer the suggested value.
191
+ `beta_fast` (`float`, *optional*):
192
+ Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
193
+ ramp function. If unspecified, it defaults to 32.
194
+ `beta_slow` (`float`, *optional*):
195
+ Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
196
+ ramp function. If unspecified, it defaults to 1.
197
+ `short_factor` (`list[float]`, *optional*):
198
+ Only used with 'longrope'. The scaling factor to be applied to short contexts (<
199
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
200
+ size divided by the number of attention heads divided by 2
201
+ `long_factor` (`list[float]`, *optional*):
202
+ Only used with 'longrope'. The scaling factor to be applied to long contexts (<
203
+ `original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
204
+ size divided by the number of attention heads divided by 2
205
+ `low_freq_factor` (`float`, *optional*):
206
+ Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
207
+ `high_freq_factor` (`float`, *optional*):
208
+ Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
209
+ attention_bias (`bool`, defaults to `False`, *optional*, defaults to `False`):
210
+ Whether to use a bias in the query, key, value and output projection layers during self-attention.
211
+ attention_dropout (`float`, *optional*, defaults to 0.0):
212
+ The dropout ratio for the attention probabilities.
213
+
214
+ ```python
215
+ >>> from transformers import Qwen3ASRTextModel, Qwen3ASRTextConfig
216
+
217
+ >>> # Initializing a Qwen3ASR style configuration
218
+ >>> configuration = Qwen3ASRTextConfig()
219
+
220
+ >>> # Initializing a model from the Qwen3-VL-7B style configuration
221
+ >>> model = Qwen3ASRTextModel(configuration)
222
+
223
+ >>> # Accessing the model configuration
224
+ >>> configuration = model.config
225
+ ```"""
226
+
227
+ model_type = "qwen3_asr_text"
228
+ base_config_key = "text_config"
229
+
230
+ def __init__(
231
+ self,
232
+ vocab_size=151936,
233
+ hidden_size=4096,
234
+ intermediate_size=22016,
235
+ num_hidden_layers=32,
236
+ num_attention_heads=32,
237
+ num_key_value_heads=32,
238
+ head_dim=128,
239
+ hidden_act="silu",
240
+ max_position_embeddings=128000,
241
+ initializer_range=0.02,
242
+ rms_norm_eps=1e-6,
243
+ use_cache=True,
244
+ tie_word_embeddings=False,
245
+ rope_theta=5000000.0,
246
+ rope_scaling=None,
247
+ attention_bias=False,
248
+ attention_dropout=0.0,
249
+ **kwargs,
250
+ ):
251
+ self.vocab_size = vocab_size
252
+ self.max_position_embeddings = max_position_embeddings
253
+ self.hidden_size = hidden_size
254
+ self.intermediate_size = intermediate_size
255
+ self.num_hidden_layers = num_hidden_layers
256
+ self.num_attention_heads = num_attention_heads
257
+
258
+ # for backward compatibility
259
+ if num_key_value_heads is None:
260
+ num_key_value_heads = num_attention_heads
261
+
262
+ self.num_key_value_heads = num_key_value_heads
263
+ self.head_dim = head_dim
264
+ self.hidden_act = hidden_act
265
+ self.initializer_range = initializer_range
266
+ self.rms_norm_eps = rms_norm_eps
267
+ self.use_cache = use_cache
268
+ self.rope_theta = rope_theta
269
+ self.rope_scaling = rope_scaling
270
+ self.attention_bias = attention_bias
271
+ self.attention_dropout = attention_dropout
272
+ # Validate the correctness of rotary position embeddings parameters
273
+ # BC: if there is a 'type' field, move it to 'rope_type'.
274
+ if self.rope_scaling is not None and "type" in self.rope_scaling:
275
+ self.rope_scaling["rope_type"] = self.rope_scaling["type"]
276
+
277
+ super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
278
+
279
+
280
+ class Qwen3ASRThinkerConfig(PretrainedConfig):
281
+ r"""
282
+ This is the configuration class to store the configuration of a [`Qwen3ASRThinker`]. It is used to instantiate a
283
+ Qwen3-ASR-Thinker model according to the specified arguments, defining the model architecture. Instantiating a
284
+ configuration with the defaults will yield a similar configuration to that of the thinker component of the Qwen3-Omni
285
+ architecture.
286
+
287
+ e.g. [Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B)
288
+
289
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
290
+ documentation from [`PretrainedConfig`] for more information.
291
+
292
+ Args:
293
+ audio_config (`dict`, *optional*):
294
+ The config dictionary of the audio backbone.
295
+ text_config (`dict`, *optional*):
296
+ The config dictionary of the text backbone.
297
+ audio_token_id (`int`, *optional*, defaults to 151646):
298
+ The audio token id to encode the audio prompt.
299
+ audio_start_token_id (`int`, *optional*, defaults to 151647):
300
+ The audio start token id to encode the audio prompt.
301
+ user_token_id (`int`, *optional*, defaults to 872):
302
+ The user token id to encode the user token.
303
+ initializer_range (`float`, *optional*, defaults to 0.02):
304
+ The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
305
+
306
+ Example:
307
+
308
+ ```python
309
+ >>> from transformers import Qwen3ASRThinkerModel, Qwen3ASRThinkerConfig
310
+
311
+ >>> # Initializing a default Qwen3ASRThinkerConfig
312
+ >>> configuration = Qwen3ASRThinkerConfig()
313
+
314
+ >>> # Initializing a model (with random weights) from the default configuration
315
+ >>> model = Qwen3ASRThinkerModel(configuration)
316
+
317
+ >>> # Accessing the model configuration
318
+ >>> configuration = model.config
319
+ ```"""
320
+
321
+ model_type = "qwen3_asr_thinker"
322
+
323
+ attribute_map = {}
324
+ sub_configs = {
325
+ "audio_config": Qwen3ASRAudioEncoderConfig,
326
+ "text_config": Qwen3ASRTextConfig,
327
+ }
328
+
329
+ def __init__(
330
+ self,
331
+ audio_config=None,
332
+ text_config=None,
333
+ audio_token_id=151646,
334
+ audio_start_token_id=151647,
335
+ user_token_id=872,
336
+ initializer_range=0.02,
337
+ **kwargs,
338
+ ):
339
+ super().__init__(**kwargs)
340
+ self.user_token_id = user_token_id
341
+ self.audio_start_token_id = audio_start_token_id
342
+ self.initializer_range = initializer_range
343
+
344
+ if isinstance(audio_config, dict):
345
+ audio_config = Qwen3ASRAudioEncoderConfig(**audio_config)
346
+ elif audio_config is None:
347
+ audio_config = Qwen3ASRAudioEncoderConfig()
348
+ self.audio_config = audio_config
349
+
350
+ if isinstance(text_config, dict):
351
+ text_config = Qwen3ASRTextConfig(**text_config)
352
+ elif text_config is None:
353
+ text_config = Qwen3ASRTextConfig()
354
+ self.text_config = text_config
355
+ self.audio_token_id = audio_token_id
356
+
357
+
358
+ class Qwen3ASRConfig(PretrainedConfig):
359
+ """
360
+ This is the configuration class to store the configuration of a [`Qwen3ASRForConditionalGeneration`]. It is used to instantiate a Qwen3ASR
361
+ model according to the specified sub-models configurations, defining the model architecture.
362
+
363
+ Instantiating a configuration with the defaults will yield a similar configuration to that of the
364
+ [Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B) architecture.
365
+
366
+ Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
367
+ documentation from [`PretrainedConfig`] for more information.
368
+
369
+ Args:
370
+ thinker_config (`dict`, *optional*): Configuration of the underlying thinker sub-model.
371
+ support_languages (`List[str]`, *optional*): The languages supported by the model.
372
+
373
+ Example:
374
+
375
+ ```python
376
+ >>> from transformers import (
377
+ ... Qwen3ASRThinkerConfig,
378
+ ... Qwen3ASRForConditionalGeneration,
379
+ ... Qwen3ASRConfig,
380
+ ... )
381
+
382
+ >>> # Initializing a Qwen3ASR style configuration
383
+ >>> configuration = Qwen3ASRConfig()
384
+
385
+ >>> # Initializing a model from the configuration
386
+ >>> model = Qwen3ASRForConditionalGeneration(configuration)
387
+
388
+ >>> # Accessing the model configuration
389
+ >>> configuration = model.config
390
+ ```"""
391
+
392
+ model_type = "qwen3_asr"
393
+ sub_configs = {
394
+ "thinker_config": Qwen3ASRThinkerConfig,
395
+ }
396
+
397
+ def __init__(
398
+ self,
399
+ thinker_config=None,
400
+ support_languages=None,
401
+ **kwargs,
402
+ ):
403
+ super().__init__(**kwargs)
404
+ if thinker_config is None:
405
+ thinker_config = {}
406
+
407
+ self.thinker_config = Qwen3ASRThinkerConfig(**thinker_config)
408
+ self.support_languages = support_languages
409
+
410
+ def get_text_config(self, decoder=False) -> "PretrainedConfig":
411
+ """
412
+ Returns the config that is meant to be used with text IO. On most models, it is the original config instance
413
+ itself. On specific composite models, it is under a set of valid names.
414
+
415
+ Args:
416
+ decoder (`Optional[bool]`, *optional*, defaults to `False`):
417
+ If set to `True`, then only search for decoder config names.
418
+ """
419
+ # Overridden for deeply nested config like Qwen2.5-Omni. We don't have any omni model
420
+ # except for Qwen yet. This has to be generalized if more deeply nested configs are
421
+ # added. NOTE: currently method used only by vLLM
422
+ return self.thinker_config.get_text_config()
423
+
424
+
425
+ __all__ = ["Qwen3ASRConfig", "Qwen3ASRThinkerConfig", "Qwen3ASRAudioEncoderConfig"]
modeling_qwen3_asr.py ADDED
@@ -0,0 +1,1365 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2026 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import math
16
+ from dataclasses import dataclass
17
+ from typing import Callable, Optional, Union
18
+
19
+ import numpy as np
20
+ import torch
21
+ from torch import nn
22
+ from torch.nn import functional as F
23
+
24
+ from transformers.activations import ACT2FN
25
+ from transformers.cache_utils import Cache, DynamicCache
26
+ from transformers.generation import GenerationMixin
27
+ from transformers.integrations import use_kernel_forward_from_hub
28
+ from transformers.masking_utils import create_causal_mask
29
+ from transformers.modeling_flash_attention_utils import FlashAttentionKwargs
30
+ from transformers.modeling_layers import GradientCheckpointingLayer
31
+ from transformers.modeling_outputs import (
32
+ BaseModelOutput,
33
+ BaseModelOutputWithPast,
34
+ MoeCausalLMOutputWithPast,
35
+ )
36
+ from transformers.modeling_rope_utils import ROPE_INIT_FUNCTIONS, dynamic_rope_update
37
+ from transformers.modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel
38
+ from transformers.processing_utils import Unpack
39
+ from transformers.utils import auto_docstring, can_return_tuple
40
+ from transformers.utils.deprecation import deprecate_kwarg
41
+ from transformers.utils.generic import TransformersKwargs, check_model_inputs
42
+
43
+ from .configuration_qwen3_asr import (
44
+ Qwen3ASRAudioEncoderConfig,
45
+ Qwen3ASRConfig,
46
+ Qwen3ASRThinkerConfig,
47
+ )
48
+
49
+
50
+ @use_kernel_forward_from_hub("RMSNorm")
51
+ class Qwen3ASRTextRMSNorm(nn.Module):
52
+ def __init__(self, hidden_size, eps: float = 1e-6) -> None:
53
+ """
54
+ Qwen3ASRTextRMSNorm is equivalent to T5LayerNorm
55
+ """
56
+ super().__init__()
57
+ self.weight = nn.Parameter(torch.ones(hidden_size))
58
+ self.variance_epsilon = eps
59
+
60
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
61
+ input_dtype = hidden_states.dtype
62
+ hidden_states = hidden_states.to(torch.float32)
63
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
64
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
65
+ return self.weight * hidden_states.to(input_dtype)
66
+
67
+ def extra_repr(self):
68
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
69
+
70
+
71
+ def rotate_half(x):
72
+ """Rotates half the hidden dims of the input."""
73
+ x1 = x[..., : x.shape[-1] // 2]
74
+ x2 = x[..., x.shape[-1] // 2 :]
75
+ return torch.cat((-x2, x1), dim=-1)
76
+
77
+
78
+ def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor:
79
+ """
80
+ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch,
81
+ num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim)
82
+ """
83
+ batch, num_key_value_heads, slen, head_dim = hidden_states.shape
84
+ if n_rep == 1:
85
+ return hidden_states
86
+ hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim)
87
+ return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim)
88
+
89
+
90
+ def eager_attention_forward(
91
+ module: nn.Module,
92
+ query: torch.Tensor,
93
+ key: torch.Tensor,
94
+ value: torch.Tensor,
95
+ attention_mask: Optional[torch.Tensor],
96
+ scaling: float,
97
+ dropout: float = 0.0,
98
+ **kwargs: Unpack[TransformersKwargs],
99
+ ):
100
+ key_states = repeat_kv(key, module.num_key_value_groups)
101
+ value_states = repeat_kv(value, module.num_key_value_groups)
102
+
103
+ attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
104
+ if attention_mask is not None:
105
+ causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
106
+ attn_weights = attn_weights + causal_mask
107
+
108
+ attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
109
+ attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training)
110
+ attn_output = torch.matmul(attn_weights, value_states)
111
+ attn_output = attn_output.transpose(1, 2).contiguous()
112
+
113
+ return attn_output, attn_weights
114
+
115
+
116
+ def apply_rotary_pos_emb(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
117
+ """Applies Rotary Position Embedding to the query and key tensors.
118
+
119
+ Args:
120
+ q (`torch.Tensor`): The query tensor.
121
+ k (`torch.Tensor`): The key tensor.
122
+ cos (`torch.Tensor`): The cosine part of the rotary embedding.
123
+ sin (`torch.Tensor`): The sine part of the rotary embedding.
124
+ position_ids (`torch.Tensor`, *optional*):
125
+ Deprecated and unused.
126
+ unsqueeze_dim (`int`, *optional*, defaults to 1):
127
+ The 'unsqueeze_dim' argument specifies the dimension along which to unsqueeze cos[position_ids] and
128
+ sin[position_ids] so that they can be properly broadcasted to the dimensions of q and k. For example, note
129
+ that cos[position_ids] and sin[position_ids] have the shape [batch_size, seq_len, head_dim]. Then, if q and
130
+ k have the shape [batch_size, heads, seq_len, head_dim], then setting unsqueeze_dim=1 makes
131
+ cos[position_ids] and sin[position_ids] broadcastable to the shapes of q and k. Similarly, if q and k have
132
+ the shape [batch_size, seq_len, heads, head_dim], then set unsqueeze_dim=2.
133
+ Returns:
134
+ `tuple(torch.Tensor)` comprising of the query and key tensors rotated using the Rotary Position Embedding.
135
+ """
136
+ cos = cos.unsqueeze(unsqueeze_dim)
137
+ sin = sin.unsqueeze(unsqueeze_dim)
138
+ q_embed = (q * cos) + (rotate_half(q) * sin)
139
+ k_embed = (k * cos) + (rotate_half(k) * sin)
140
+ return q_embed, k_embed
141
+
142
+
143
+ class Qwen3ASRTextAttention(nn.Module):
144
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
145
+
146
+ def __init__(self, config: Qwen3ASRConfig, layer_idx: int):
147
+ super().__init__()
148
+ self.config = config
149
+ self.layer_idx = layer_idx
150
+ self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
151
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
152
+ self.scaling = self.head_dim**-0.5
153
+ self.attention_dropout = config.attention_dropout
154
+ self.is_causal = True
155
+
156
+ self.q_proj = nn.Linear(
157
+ config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
158
+ )
159
+ self.k_proj = nn.Linear(
160
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
161
+ )
162
+ self.v_proj = nn.Linear(
163
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
164
+ )
165
+ self.o_proj = nn.Linear(
166
+ config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
167
+ )
168
+ self.q_norm = Qwen3ASRTextRMSNorm(
169
+ self.head_dim, eps=config.rms_norm_eps
170
+ ) # unlike olmo, only on the head dim!
171
+ self.k_norm = Qwen3ASRTextRMSNorm(
172
+ self.head_dim, eps=config.rms_norm_eps
173
+ ) # thus post q_norm does not need reshape
174
+
175
+ @deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
176
+ def forward(
177
+ self,
178
+ hidden_states: torch.Tensor,
179
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
180
+ attention_mask: Optional[torch.Tensor],
181
+ past_key_values: Optional[Cache] = None,
182
+ cache_position: Optional[torch.LongTensor] = None,
183
+ **kwargs: Unpack[FlashAttentionKwargs],
184
+ ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
185
+ input_shape = hidden_states.shape[:-1]
186
+ hidden_shape = (*input_shape, -1, self.head_dim)
187
+
188
+ query_states = self.q_norm(self.q_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
189
+ key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
190
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
191
+
192
+ cos, sin = position_embeddings
193
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
194
+
195
+ if past_key_values is not None:
196
+ # sin and cos are specific to RoPE models; cache_position needed for the static cache
197
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
198
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_kwargs)
199
+
200
+ attention_interface: Callable = eager_attention_forward
201
+ if self.config._attn_implementation != "eager":
202
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
203
+
204
+ attn_output, attn_weights = attention_interface(
205
+ self,
206
+ query_states,
207
+ key_states,
208
+ value_states,
209
+ attention_mask,
210
+ dropout=0.0 if not self.training else self.attention_dropout,
211
+ scaling=self.scaling,
212
+ **kwargs,
213
+ )
214
+
215
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
216
+ attn_output = self.o_proj(attn_output)
217
+ return attn_output, attn_weights
218
+
219
+
220
+ class Qwen3ASRTextMLP(nn.Module):
221
+ def __init__(self, config):
222
+ super().__init__()
223
+ self.config = config
224
+ self.hidden_size = config.hidden_size
225
+ self.intermediate_size = config.intermediate_size
226
+ self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
227
+ self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
228
+ self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
229
+ self.act_fn = ACT2FN[config.hidden_act]
230
+
231
+ def forward(self, x):
232
+ down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
233
+ return down_proj
234
+
235
+
236
+ class Qwen3ASRThinkerTextDecoderLayer(GradientCheckpointingLayer):
237
+ def __init__(self, config: Qwen3ASRConfig, layer_idx: int):
238
+ super().__init__()
239
+ self.hidden_size = config.hidden_size
240
+
241
+ self.self_attn = Qwen3ASRTextAttention(config=config, layer_idx=layer_idx)
242
+
243
+ self.mlp = Qwen3ASRTextMLP(config)
244
+ self.input_layernorm = Qwen3ASRTextRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
245
+ self.post_attention_layernorm = Qwen3ASRTextRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
246
+
247
+ @deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
248
+ def forward(
249
+ self,
250
+ hidden_states: torch.Tensor,
251
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
252
+ attention_mask: Optional[torch.Tensor] = None,
253
+ position_ids: Optional[torch.LongTensor] = None,
254
+ past_key_values: Optional[Cache] = None,
255
+ use_cache: Optional[bool] = False,
256
+ cache_position: Optional[torch.LongTensor] = None,
257
+ **kwargs: Unpack[TransformersKwargs],
258
+ ) -> torch.Tensor:
259
+ residual = hidden_states
260
+ hidden_states = self.input_layernorm(hidden_states)
261
+ # Self Attention
262
+ hidden_states, _ = self.self_attn(
263
+ hidden_states=hidden_states,
264
+ attention_mask=attention_mask,
265
+ position_ids=position_ids,
266
+ past_key_values=past_key_values,
267
+ use_cache=use_cache,
268
+ cache_position=cache_position,
269
+ position_embeddings=position_embeddings,
270
+ **kwargs,
271
+ )
272
+ hidden_states = residual + hidden_states
273
+
274
+ # Fully Connected
275
+ residual = hidden_states
276
+ hidden_states = self.post_attention_layernorm(hidden_states)
277
+ hidden_states = self.mlp(hidden_states)
278
+ hidden_states = residual + hidden_states
279
+ return hidden_states
280
+
281
+
282
+ @auto_docstring
283
+ class Qwen3ASRPreTrainedModel(PreTrainedModel):
284
+ config: Qwen3ASRConfig
285
+ base_model_prefix = "model"
286
+ supports_gradient_checkpointing = True
287
+ _skip_keys_device_placement = "past_key_values"
288
+ _supports_flash_attn = True
289
+ _supports_sdpa = True
290
+
291
+ _can_compile_fullgraph = True
292
+ _supports_attention_backend = True
293
+ _can_record_outputs = {
294
+ "attentions": Qwen3ASRTextAttention,
295
+ }
296
+
297
+
298
+ @dataclass
299
+ class Qwen3ASRThinkerCausalLMOutputWithPast(MoeCausalLMOutputWithPast):
300
+ r"""
301
+ Args:
302
+ rope_deltas (`torch.LongTensor` of shape `(batch_size, )`, *optional*):
303
+ The rope index difference between sequence length and multimodal rope.
304
+ """
305
+
306
+ rope_deltas: Optional[torch.LongTensor] = None
307
+
308
+
309
+ def _get_feat_extract_output_lengths(input_lengths):
310
+ """
311
+ Computes the output length of the convolutional layers and the output length of the audio encoder
312
+ """
313
+
314
+ input_lengths_leave = input_lengths % 100
315
+ feat_lengths = (input_lengths_leave - 1) // 2 + 1
316
+ output_lengths = ((feat_lengths - 1) // 2 + 1 - 1) // 2 + 1 + (input_lengths // 100) * 13
317
+ return output_lengths
318
+
319
+
320
+ class Qwen3ASRPreTrainedModelForConditionalGeneration(Qwen3ASRPreTrainedModel):
321
+ def _prepare_4d_causal_attention_mask_with_cache_position(
322
+ self,
323
+ attention_mask: torch.Tensor,
324
+ sequence_length: int,
325
+ target_length: int,
326
+ dtype: torch.dtype,
327
+ device: torch.device,
328
+ min_dtype: float,
329
+ cache_position: torch.Tensor,
330
+ batch_size: int,
331
+ ):
332
+ """
333
+ Creates a causal 4D mask of shape `(batch_size, 1, query_length, key_value_length)` from a 2D mask of shape
334
+ `(batch_size, key_value_length)`, or if the input `attention_mask` is already 4D, do nothing.
335
+
336
+ Args:
337
+ attention_mask (`torch.Tensor`):
338
+ A 2D attention mask of shape `(batch_size, key_value_length)` or a 4D attention mask of shape `(batch_size, 1, query_length, key_value_length)`.
339
+ sequence_length (`int`):
340
+ The sequence length being processed.
341
+ target_length (`int`):
342
+ The target length: when generating with static cache, the mask should be as long as the static cache, to account for the 0 padding, the part of the cache that is not filled yet.
343
+ dtype (`torch.dtype`):
344
+ The dtype to use for the 4D attention mask.
345
+ device (`torch.device`):
346
+ The device to place the 4D attention mask on.
347
+ min_dtype (`float`):
348
+ The minimum value representable with the dtype `dtype`.
349
+ cache_position (`torch.Tensor`):
350
+ Indices depicting the position of the input sequence tokens in the sequence.
351
+ batch_size (`torch.Tensor`):
352
+ Batch size.
353
+ """
354
+ if attention_mask is not None and attention_mask.dim() == 4:
355
+ # In this case we assume that the mask comes already in inverted form and requires no inversion or slicing.
356
+ causal_mask = attention_mask
357
+ else:
358
+ causal_mask = torch.full(
359
+ (sequence_length, target_length), fill_value=min_dtype, dtype=dtype, device=device
360
+ )
361
+ if sequence_length != 1:
362
+ causal_mask = torch.triu(causal_mask, diagonal=1)
363
+ causal_mask *= torch.arange(target_length, device=device) > cache_position.reshape(-1, 1)
364
+ causal_mask = causal_mask[None, None, :, :].expand(batch_size, 1, -1, -1)
365
+ if attention_mask is not None:
366
+ causal_mask = causal_mask.clone() # copy to contiguous memory for in-place edit
367
+ mask_length = attention_mask.shape[-1]
368
+ padding_mask = causal_mask[:, :, :, :mask_length] + attention_mask[:, None, None, :]
369
+ padding_mask = padding_mask == 0
370
+ causal_mask[:, :, :, :mask_length] = causal_mask[:, :, :, :mask_length].masked_fill(
371
+ padding_mask, min_dtype
372
+ )
373
+
374
+ return causal_mask
375
+
376
+
377
+ def get_chunked_index(
378
+ self, token_indices: torch.Tensor, tokens_per_chunk: int, remove_index: int
379
+ ) -> list[tuple[int, int]]:
380
+ """
381
+ Splits token index list into chunks based on token value ranges.
382
+
383
+ Given a list of token indices, returns a list of (start, end) index tuples representing
384
+ slices of the list where the token values fall within successive ranges of `t_ntoken_per_chunk`.
385
+
386
+ For example, if `t_ntoken_per_chunk` is 1000, the function will create chunks such that:
387
+ - the first chunk contains token values < 1000,
388
+ - the second chunk contains values >= 1000 and < 2000, and so on.
389
+
390
+ Parameters:
391
+ token_indices (`torch.Tensor` of shape `(seq_len, )`): A monotonically increasing list of
392
+ token index values.
393
+ t_ntoken_per_chunk (`int`): Number of tokens per chunk (used as the chunk size threshold).
394
+ remove_index (`int`) An index id to subtract from `token_indices` before chunking
395
+
396
+ Returns:
397
+ `list[tuple[int, int]]`: A list of tuples, each representing the start (inclusive)
398
+ and end (exclusive) indices of a chunk in `token_indices`.
399
+ """
400
+
401
+ def _iter():
402
+ i, start_idx = 0, 0 # skip bos token
403
+ current_chunk = 1
404
+ while i < len(token_indices): # skip eos token
405
+ if token_indices[i] - remove_index >= current_chunk * tokens_per_chunk:
406
+ yield (start_idx, i)
407
+ start_idx = i
408
+ current_chunk += 1
409
+ i += 1
410
+ yield (start_idx, len(token_indices))
411
+
412
+ return list(_iter())
413
+
414
+ def get_rope_index(
415
+ self,
416
+ attention_mask: Optional[torch.Tensor] = None,
417
+ ) -> tuple[torch.Tensor, torch.Tensor]:
418
+ """
419
+ Calculate the rope index in LLM.
420
+
421
+ Explanation:
422
+ Each embedding sequence contains text embedding.
423
+
424
+ Args:
425
+ input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
426
+ Indices of input sequence tokens in the vocabulary. Padding will be ignored by default should you provide
427
+ it.
428
+ attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*):
429
+ Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`:
430
+
431
+ - 1 for tokens that are **not masked**,
432
+ - 0 for tokens that are **masked**.
433
+ audio_seqlens (`torch.LongTensor` of shape `(num_audios)`, *optional*):
434
+ The length of feature shape of each audio in LLM.
435
+
436
+ Returns:
437
+ position_ids (`torch.LongTensor` of shape `(3, batch_size, sequence_length)`)
438
+ mrope_position_deltas (`torch.Tensor` of shape `(batch_size)`)
439
+ """
440
+ mrope_position_deltas = []
441
+
442
+ position_ids = attention_mask.float().cumsum(-1) - 1
443
+ position_ids.masked_fill_(attention_mask == 0, 1)
444
+ position_ids = position_ids.unsqueeze(0).expand(3, -1, -1).to(attention_mask.device)
445
+ max_position_ids = position_ids.max(0, keepdim=False)[0].max(-1, keepdim=True)[0]
446
+ mrope_position_deltas = max_position_ids + 1 - torch.sum(attention_mask, dim=-1, keepdim=True)
447
+
448
+ return position_ids, mrope_position_deltas
449
+
450
+
451
+ class Qwen3ASRAudioAttention(nn.Module):
452
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
453
+
454
+ def __init__(self, config):
455
+ super().__init__()
456
+ self.embed_dim = config.d_model
457
+ self.num_heads = config.encoder_attention_heads
458
+ self.dropout = config.attention_dropout
459
+ self.head_dim = self.embed_dim // self.num_heads
460
+ self.num_key_value_groups = 1 # needed for eager attention
461
+ self.config = config
462
+
463
+ if (self.head_dim * self.num_heads) != self.embed_dim:
464
+ raise ValueError(
465
+ f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim}"
466
+ f" and `num_heads`: {self.num_heads})."
467
+ )
468
+ self.scaling = self.head_dim**-0.5
469
+ self.attention_dropout = 0.0
470
+ self.is_decoder = False
471
+ self.is_causal = False
472
+ self.k_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
473
+ self.v_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
474
+ self.q_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
475
+ self.out_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
476
+
477
+ def forward(
478
+ self,
479
+ hidden_states: torch.Tensor,
480
+ cu_seqlens: Optional[torch.Tensor] = None,
481
+ attention_mask: Optional[torch.Tensor] = None,
482
+ **kwargs,
483
+ ) -> tuple[torch.Tensor, Optional[torch.Tensor], Optional[tuple[torch.Tensor]]]:
484
+ """Input shape: Batch x Time x Channel"""
485
+
486
+ seq_length, _ = hidden_states.size()
487
+
488
+ query_states = self.q_proj(hidden_states).reshape(seq_length, self.num_heads, -1)
489
+ key_states = self.k_proj(hidden_states).reshape(seq_length, self.num_heads, -1)
490
+ value_states = self.v_proj(hidden_states).reshape(seq_length, self.num_heads, -1)
491
+
492
+ query_states = query_states.transpose(0, 1).unsqueeze(0)
493
+ key_states = key_states.transpose(0, 1).unsqueeze(0)
494
+ value_states = value_states.transpose(0, 1).unsqueeze(0)
495
+ max_seqlen = (cu_seqlens[1:] - cu_seqlens[:-1]).max()
496
+
497
+ attention_interface: Callable = eager_attention_forward
498
+ if self.config._attn_implementation != "eager":
499
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
500
+
501
+ attn_output, _ = attention_interface(
502
+ self,
503
+ query_states,
504
+ key_states,
505
+ value_states,
506
+ attention_mask=attention_mask,
507
+ dropout=0.0 if not self.training else self.attention_dropout,
508
+ scaling=self.scaling,
509
+ cu_seq_lens_q=cu_seqlens, # pass cu seq lens for FA2
510
+ cu_seq_lens_k=cu_seqlens,
511
+ max_length_q=max_seqlen,
512
+ max_length_k=max_seqlen,
513
+ is_causal=False,
514
+ **kwargs,
515
+ )
516
+
517
+ attn_output = attn_output.reshape(seq_length, -1).contiguous()
518
+ attn_output = self.out_proj(attn_output)
519
+
520
+ return attn_output
521
+
522
+
523
+ class Qwen3ASRAudioEncoderLayer(GradientCheckpointingLayer):
524
+ def __init__(self, config: Qwen3ASRAudioEncoderConfig):
525
+ super().__init__()
526
+ self.embed_dim = config.d_model
527
+ self.self_attn = Qwen3ASRAudioAttention(config)
528
+ self.self_attn_layer_norm = nn.LayerNorm(self.embed_dim)
529
+ self.dropout = config.dropout
530
+ self.activation_fn = ACT2FN[config.activation_function]
531
+ self.activation_dropout = config.activation_dropout
532
+ self.fc1 = nn.Linear(self.embed_dim, config.encoder_ffn_dim)
533
+ self.fc2 = nn.Linear(config.encoder_ffn_dim, self.embed_dim)
534
+ self.final_layer_norm = nn.LayerNorm(self.embed_dim)
535
+
536
+ def forward(
537
+ self,
538
+ hidden_states: torch.Tensor,
539
+ cu_seqlens: torch.Tensor,
540
+ attention_mask: Optional[torch.Tensor] = None,
541
+ **kwargs,
542
+ ) -> torch.Tensor:
543
+ """
544
+ Args:
545
+ hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)`
546
+ attention_mask (`torch.FloatTensor`): attention mask of size
547
+ `(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values.
548
+ layer_head_mask (`torch.FloatTensor`): mask for attention heads in a given layer of size
549
+ `(encoder_attention_heads,)`.
550
+ output_attentions (`bool`, *optional*):
551
+ Whether or not to return the attentions tensors of all attention layers. See `attentions` under
552
+ returned tensors for more detail.
553
+ """
554
+ residual = hidden_states
555
+ hidden_states = self.self_attn_layer_norm(hidden_states)
556
+ hidden_states = self.self_attn(
557
+ hidden_states=hidden_states,
558
+ cu_seqlens=cu_seqlens,
559
+ attention_mask=attention_mask,
560
+ **kwargs,
561
+ )
562
+ hidden_states = residual + hidden_states
563
+ residual = hidden_states
564
+ hidden_states = self.final_layer_norm(hidden_states)
565
+ hidden_states = self.fc1(hidden_states)
566
+ hidden_states = self.activation_fn(hidden_states)
567
+ hidden_states = self.fc2(hidden_states)
568
+ hidden_states = residual + hidden_states
569
+
570
+ if hidden_states.dtype == torch.float16:
571
+ clamp_value = torch.finfo(hidden_states.dtype).max - 1000
572
+ hidden_states = torch.clamp(hidden_states, min=-clamp_value, max=clamp_value)
573
+
574
+ outputs = (hidden_states,)
575
+
576
+ return outputs
577
+
578
+
579
+ class SinusoidsPositionEmbedding(nn.Module):
580
+ def __init__(self, length, channels, max_timescale=10000):
581
+ super().__init__()
582
+ if channels % 2 != 0:
583
+ raise ValueError("SinusoidsPositionEmbedding needs even channels input")
584
+ log_timescale_increment = np.log(max_timescale) / (channels // 2 - 1)
585
+ inv_timescales = torch.exp(-log_timescale_increment * torch.arange(channels // 2).float())
586
+ scaled_time = torch.arange(length)[:, np.newaxis] * inv_timescales[np.newaxis, :]
587
+ self.register_buffer(
588
+ "positional_embedding",
589
+ torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], dim=1),
590
+ persistent=False,
591
+ )
592
+
593
+ def forward(self, seqlen: int):
594
+ return self.positional_embedding[:seqlen, :]
595
+
596
+
597
+ @auto_docstring(
598
+ custom_intro="""
599
+ Transformer encoder consisting of *config.encoder_layers* self attention layers. Each layer is a
600
+ [`Qwen3ASRAudioEncoderLayer`].
601
+ """
602
+ )
603
+ class Qwen3ASRAudioEncoder(Qwen3ASRPreTrainedModel):
604
+ config: Qwen3ASRAudioEncoderConfig
605
+ main_input_name = "input_features"
606
+ _no_split_modules = ["Qwen3ASRAudioEncoderLayer"]
607
+ _supports_sdpa = True
608
+
609
+ def __init__(self, config: Qwen3ASRAudioEncoderConfig):
610
+ super().__init__(config)
611
+ self.dropout = config.dropout
612
+
613
+ embed_dim = config.d_model
614
+ self.num_mel_bins = config.num_mel_bins
615
+ self.max_source_positions = config.max_source_positions
616
+ self.embed_scale = math.sqrt(embed_dim) if config.scale_embedding else 1.0
617
+ self.n_window = config.n_window
618
+ self.positional_embedding = SinusoidsPositionEmbedding(self.max_source_positions, embed_dim)
619
+ self.layers = nn.ModuleList([Qwen3ASRAudioEncoderLayer(config) for _ in range(config.encoder_layers)])
620
+ self.ln_post = nn.LayerNorm(config.d_model)
621
+ self.gradient_checkpointing = False
622
+ self.conv2d1 = nn.Conv2d(1, config.downsample_hidden_size, 3, 2, padding=1)
623
+ self.conv2d2 = nn.Conv2d(config.downsample_hidden_size, config.downsample_hidden_size, 3, 2, padding=1)
624
+ self.conv2d3 = nn.Conv2d(config.downsample_hidden_size, config.downsample_hidden_size, 3, 2, padding=1)
625
+ self.conv_out = nn.Linear(
626
+ config.downsample_hidden_size * ((((config.num_mel_bins + 1) // 2 + 1) // 2 + 1) // 2),
627
+ config.d_model,
628
+ bias=False,
629
+ )
630
+ self.proj1 = nn.Linear(config.d_model, config.d_model)
631
+ self.act = ACT2FN[config.activation_function]
632
+ self.proj2 = nn.Linear(config.d_model, config.output_dim)
633
+ self.n_window_infer = self.config.n_window_infer
634
+ self.conv_chunksize = self.config.conv_chunksize
635
+ # Initialize weights and apply final processing
636
+ self.post_init()
637
+
638
+ def _freeze_parameters(self):
639
+ for param in self.parameters():
640
+ param.requires_grad = False
641
+ self._requires_grad = False
642
+
643
+ def get_input_embeddings(self) -> nn.Module:
644
+ return self.conv1
645
+
646
+ def set_input_embeddings(self, value: nn.Module):
647
+ self.conv1 = value
648
+
649
+ def _prepare_attention_mask(self, inputs_tensor: torch.Tensor, cu_seqlens: torch.Tensor) -> torch.Tensor:
650
+ # Flash Attention 2 doesn't need a 4D mask and relies on `cu_seqlens/max_seqlen`
651
+ # NOTE: the created attention masl only approximates the ragged FA2 attention by
652
+ # allowing bidirectional attention within `cu_seqlens` blocks, and not attending between
653
+ # blocks. Though it will not be a 100% match for FA2's `varlen` path
654
+ if self.config._attn_implementation == "flash_attention_2":
655
+ return None
656
+
657
+ seq_length = inputs_tensor.shape[0]
658
+ attention_mask = torch.full(
659
+ [1, 1, seq_length, seq_length],
660
+ torch.finfo(inputs_tensor.dtype).min,
661
+ device=inputs_tensor.device,
662
+ dtype=inputs_tensor.dtype,
663
+ )
664
+ for i in range(1, len(cu_seqlens)):
665
+ attention_mask[..., cu_seqlens[i - 1] : cu_seqlens[i], cu_seqlens[i - 1] : cu_seqlens[i]] = 0
666
+ return attention_mask
667
+
668
+ @auto_docstring
669
+ def forward(
670
+ self,
671
+ input_features,
672
+ feature_lens=None,
673
+ aftercnn_lens=None,
674
+ ):
675
+ r"""
676
+ feature_lens (`torch.LongTensor` of shape `(batch_size,)`):
677
+ mel length
678
+ aftercnn_lens (`torch.LongTensor` of shape `(batch_size,)`):
679
+ mel length after cnn
680
+ """
681
+ aftercnn_lens = _get_feat_extract_output_lengths(feature_lens)
682
+ chunk_num = torch.ceil(feature_lens / (self.n_window * 2)).long()
683
+
684
+ chunk_lengths = torch.tensor(
685
+ [self.n_window * 2] * chunk_num.sum(),
686
+ dtype=torch.long,
687
+ device=feature_lens.device,
688
+ )
689
+ tail_chunk_index = F.pad(chunk_num, (1, 0), value=-1).cumsum(0)[1:]
690
+ chunk_lengths[tail_chunk_index] = feature_lens % (self.n_window * 2)
691
+ chunk_lengths[chunk_lengths == 0] = self.n_window * 2
692
+
693
+ chunk_list = input_features.T.split(chunk_lengths.tolist(), dim=0)
694
+ padded_feature = nn.utils.rnn.pad_sequence(chunk_list, batch_first=True).transpose(1, 2)
695
+ feature_lens_after_cnn = _get_feat_extract_output_lengths(chunk_lengths)
696
+ padded_mask_after_cnn = nn.utils.rnn.pad_sequence(
697
+ [torch.ones(length, dtype=torch.bool, device=padded_feature.device) for length in feature_lens_after_cnn],
698
+ batch_first=True,
699
+ )
700
+ padded_feature = padded_feature.unsqueeze(1)
701
+ # Split to chunk to avoid OOM during convolution
702
+ padded_embeds = []
703
+ for chunk in padded_feature.split(self.conv_chunksize, dim=0):
704
+ padded_embed = F.gelu(self.conv2d1(chunk))
705
+ padded_embed = F.gelu(self.conv2d2(padded_embed))
706
+ padded_embed = F.gelu(self.conv2d3(padded_embed))
707
+ padded_embeds.append(padded_embed)
708
+ padded_embed = torch.cat(padded_embeds, dim=0)
709
+ b, c, f, t = padded_embed.size()
710
+ padded_embed = self.conv_out(padded_embed.permute(0, 3, 1, 2).contiguous().view(b, t, c * f))
711
+
712
+ positional_embedding = (
713
+ self.positional_embedding.positional_embedding[: padded_embed.shape[1], :]
714
+ .unsqueeze(0)
715
+ .to(padded_embed.dtype)
716
+ )
717
+ padded_embed = padded_embed + positional_embedding
718
+ hidden_states = padded_embed[padded_mask_after_cnn]
719
+ cu_chunk_lens = [0]
720
+ window_aftercnn = padded_mask_after_cnn.shape[-1] * (self.n_window_infer // (self.n_window * 2))
721
+ for cnn_len in aftercnn_lens:
722
+ cu_chunk_lens += [window_aftercnn] * (cnn_len // window_aftercnn)
723
+ remainder = cnn_len % window_aftercnn
724
+ if remainder != 0:
725
+ cu_chunk_lens += [remainder]
726
+ cu_seqlens = torch.tensor(cu_chunk_lens, device=aftercnn_lens.device).cumsum(-1, dtype=torch.int32)
727
+
728
+ for encoder_layer in self.layers:
729
+ layer_outputs = encoder_layer(
730
+ hidden_states,
731
+ cu_seqlens,
732
+ )
733
+
734
+ hidden_states = layer_outputs[0]
735
+
736
+ hidden_states = self.ln_post(hidden_states)
737
+ hidden_states = self.proj1(hidden_states)
738
+ hidden_states = self.act(hidden_states)
739
+ hidden_states = self.proj2(hidden_states)
740
+ return BaseModelOutput(last_hidden_state=hidden_states)
741
+
742
+ def padded_and_mask_function(self, tensor_list, tensor_len, padding_value=0, padding_side="right"):
743
+ """
744
+ Pads a sequence of tensors to their maximum length on indicated `padding_side`.
745
+ Then prepares a mask so that pad tokens are not attended to.
746
+ """
747
+ max_len = tensor_len.max()
748
+ dim = tensor_list[0].shape[0]
749
+ padded_tensor = torch.full(
750
+ size=(len(tensor_list), dim, max_len),
751
+ fill_value=padding_value,
752
+ dtype=self.dtype,
753
+ device=tensor_list[0].device,
754
+ )
755
+
756
+ batch_mask = torch.zeros(
757
+ (len(tensor_len), max_len),
758
+ dtype=torch.long,
759
+ device=padded_tensor.device,
760
+ )
761
+ for i, length in enumerate(tensor_len):
762
+ batch_mask[i, :length] = 1
763
+ padded_tensor[i, :, :length] = tensor_list[i]
764
+
765
+ feature_lens_after_cnn = (tensor_len - 1) // 2 + 1
766
+ max_len_after_cnn = feature_lens_after_cnn.max()
767
+ batch_mask_after_cnn = torch.zeros(
768
+ (len(tensor_len), max_len_after_cnn),
769
+ dtype=torch.long,
770
+ device=padded_tensor.device,
771
+ )
772
+ for i, length in enumerate(feature_lens_after_cnn):
773
+ batch_mask_after_cnn[i, :length] = 1
774
+ return (
775
+ padded_tensor,
776
+ batch_mask.unsqueeze(1),
777
+ batch_mask_after_cnn.bool(),
778
+ )
779
+
780
+
781
+ class Qwen3ASRThinkerTextRotaryEmbedding(nn.Module):
782
+ inv_freq: torch.Tensor # fix linting for `register_buffer`
783
+
784
+ def __init__(self, config: Qwen3ASRConfig, device=None):
785
+ super().__init__()
786
+ if hasattr(config, "rope_scaling") and config.rope_scaling is not None:
787
+ self.rope_type = config.rope_scaling.get("rope_type", "default")
788
+ else:
789
+ self.rope_type = "default"
790
+ self.max_seq_len_cached = config.max_position_embeddings
791
+ self.original_max_seq_len = config.max_position_embeddings
792
+
793
+ self.config = config
794
+ self.rope_init_fn = ROPE_INIT_FUNCTIONS[self.rope_type]
795
+
796
+ inv_freq, self.attention_scaling = self.rope_init_fn(self.config, device)
797
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
798
+ self.original_inv_freq = self.inv_freq
799
+
800
+ self.mrope_section = config.rope_scaling.get("mrope_section", [24, 20, 20])
801
+
802
+ def apply_interleaved_mrope(self, freqs, mrope_section):
803
+ """Apply interleaved MRoPE to 3D rotary embeddings.
804
+ Reorganizes frequency layout from chunked [TTT...HHH...WWW] to
805
+ interleaved [THTHWHTHW...TT], preserving frequency continuity.
806
+ args:
807
+ x: (3, bs, seq_len, head_dim // 2)
808
+ mrope_section: (3,)
809
+ returns:
810
+ x_t: (bs, seq_len, head_dim // 2)
811
+ """
812
+ freqs_t = freqs[0] # just overwrite the first dimension T
813
+ for dim, offset in enumerate((1, 2), start=1): # H, W
814
+ length = mrope_section[dim] * 3
815
+ idx = slice(offset, length, 3)
816
+ freqs_t[..., idx] = freqs[dim, ..., idx]
817
+ return freqs_t
818
+
819
+ @torch.no_grad()
820
+ @dynamic_rope_update # power user: used with advanced RoPE types (e.g. dynamic rope)
821
+ def forward(self, x, position_ids):
822
+ # In contrast to other models, Qwen3ASRThinker has different position ids for the grids
823
+ # So we expand the inv_freq to shape (3, ...)
824
+ if position_ids.ndim == 2:
825
+ position_ids = position_ids[None, ...].expand(3, position_ids.shape[0], -1)
826
+ inv_freq_expanded = self.inv_freq[None, None, :, None].float().expand(3, position_ids.shape[1], -1, 1)
827
+ position_ids_expanded = position_ids[:, :, None, :].float() # shape (3, bs, 1, positions)
828
+
829
+ device_type = x.device.type if isinstance(x.device.type, str) and x.device.type != "mps" else "cpu"
830
+ with torch.autocast(device_type=device_type, enabled=False): # Force float32
831
+ freqs = (inv_freq_expanded.float() @ position_ids_expanded.float()).transpose(2, 3)
832
+ freqs = self.apply_interleaved_mrope(freqs, self.mrope_section)
833
+ emb = torch.cat((freqs, freqs), dim=-1)
834
+ cos = emb.cos() * self.attention_scaling
835
+ sin = emb.sin() * self.attention_scaling
836
+
837
+ return cos.to(dtype=x.dtype), sin.to(dtype=x.dtype)
838
+
839
+
840
+ class Qwen3ASRThinkerTextMLP(nn.Module):
841
+ def __init__(self, config, intermediate_size=None):
842
+ super().__init__()
843
+ self.config = config
844
+ self.hidden_size = config.hidden_size
845
+ self.intermediate_size = intermediate_size if intermediate_size is not None else config.intermediate_size
846
+ self.gate_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
847
+ self.up_proj = nn.Linear(self.hidden_size, self.intermediate_size, bias=False)
848
+ self.down_proj = nn.Linear(self.intermediate_size, self.hidden_size, bias=False)
849
+ self.act_fn = ACT2FN[config.hidden_act]
850
+
851
+ def forward(self, x):
852
+ down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
853
+ return down_proj
854
+
855
+
856
+ @use_kernel_forward_from_hub("RMSNorm")
857
+ class Qwen3ASRThinkerTextRMSNorm(nn.Module):
858
+ def __init__(self, hidden_size, eps=1e-6):
859
+ """
860
+ Qwen3ASRThinkerTextRMSNorm is equivalent to T5LayerNorm
861
+ """
862
+ super().__init__()
863
+ self.weight = nn.Parameter(torch.ones(hidden_size))
864
+ self.variance_epsilon = eps
865
+
866
+ def forward(self, hidden_states):
867
+ input_dtype = hidden_states.dtype
868
+ hidden_states = hidden_states.to(torch.float32)
869
+ variance = hidden_states.pow(2).mean(-1, keepdim=True)
870
+ hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
871
+ return self.weight * hidden_states.to(input_dtype)
872
+
873
+ def extra_repr(self):
874
+ return f"{tuple(self.weight.shape)}, eps={self.variance_epsilon}"
875
+
876
+
877
+ class Qwen3ASRThinkerTextAttention(nn.Module):
878
+ """Multi-headed attention from 'Attention Is All You Need' paper"""
879
+
880
+ def __init__(self, config, layer_idx):
881
+ super().__init__()
882
+ self.config = config
883
+ self.layer_idx = layer_idx
884
+ self.head_dim = getattr(config, "head_dim", config.hidden_size // config.num_attention_heads)
885
+ self.num_key_value_groups = config.num_attention_heads // config.num_key_value_heads
886
+ self.scaling = self.head_dim**-0.5
887
+ self.attention_dropout = config.attention_dropout
888
+ self.is_causal = True
889
+
890
+ self.q_proj = nn.Linear(
891
+ config.hidden_size, config.num_attention_heads * self.head_dim, bias=config.attention_bias
892
+ )
893
+ self.k_proj = nn.Linear(
894
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
895
+ )
896
+ self.v_proj = nn.Linear(
897
+ config.hidden_size, config.num_key_value_heads * self.head_dim, bias=config.attention_bias
898
+ )
899
+ self.o_proj = nn.Linear(
900
+ config.num_attention_heads * self.head_dim, config.hidden_size, bias=config.attention_bias
901
+ )
902
+ self.q_norm = Qwen3ASRThinkerTextRMSNorm(
903
+ self.head_dim, eps=config.rms_norm_eps
904
+ ) # unlike olmo, only on the head dim!
905
+ self.k_norm = Qwen3ASRThinkerTextRMSNorm(
906
+ self.head_dim, eps=config.rms_norm_eps
907
+ ) # thus post q_norm does not need reshape
908
+ self.sliding_window = None
909
+
910
+ @deprecate_kwarg("past_key_value", new_name="past_key_values", version="4.58")
911
+ def forward(
912
+ self,
913
+ hidden_states: torch.Tensor,
914
+ position_embeddings: tuple[torch.Tensor, torch.Tensor],
915
+ attention_mask: Optional[torch.Tensor],
916
+ past_key_values: Optional[Cache] = None,
917
+ cache_position: Optional[torch.LongTensor] = None,
918
+ **kwargs: Unpack[FlashAttentionKwargs],
919
+ ) -> tuple[torch.Tensor, Optional[torch.Tensor]]:
920
+ input_shape = hidden_states.shape[:-1]
921
+ hidden_shape = (*input_shape, -1, self.head_dim)
922
+
923
+ query_states = self.q_norm(self.q_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
924
+ key_states = self.k_norm(self.k_proj(hidden_states).view(hidden_shape)).transpose(1, 2)
925
+ value_states = self.v_proj(hidden_states).view(hidden_shape).transpose(1, 2)
926
+
927
+ cos, sin = position_embeddings
928
+ query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin)
929
+
930
+ if past_key_values is not None:
931
+ # sin and cos are specific to RoPE models; cache_position needed for the static cache
932
+ cache_kwargs = {"sin": sin, "cos": cos, "cache_position": cache_position}
933
+ key_states, value_states = past_key_values.update(key_states, value_states, self.layer_idx, cache_kwargs)
934
+
935
+ attention_interface: Callable = eager_attention_forward
936
+ if self.config._attn_implementation != "eager":
937
+ attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation]
938
+
939
+ attn_output, attn_weights = attention_interface(
940
+ self,
941
+ query_states,
942
+ key_states,
943
+ value_states,
944
+ attention_mask,
945
+ dropout=0.0 if not self.training else self.attention_dropout,
946
+ scaling=self.scaling,
947
+ sliding_window=self.sliding_window, # diff with Llama
948
+ **kwargs,
949
+ )
950
+
951
+ attn_output = attn_output.reshape(*input_shape, -1).contiguous()
952
+ attn_output = self.o_proj(attn_output)
953
+ return attn_output, attn_weights
954
+
955
+
956
+ @auto_docstring(
957
+ custom_intro=(
958
+ "Text part of Qwen3ASRThinker, "
959
+ )
960
+ )
961
+ class Qwen3ASRThinkerTextModel(Qwen3ASRPreTrainedModel):
962
+ config: Qwen3ASRConfig
963
+ _no_split_modules = ["Qwen3ASRThinkerTextDecoderLayer"]
964
+ config_class = Qwen3ASRConfig
965
+ _can_record_outputs = {
966
+ "hidden_states": Qwen3ASRThinkerTextDecoderLayer,
967
+ "attentions": Qwen3ASRThinkerTextAttention,
968
+ }
969
+
970
+ def __init__(self, config: Qwen3ASRConfig):
971
+ super().__init__(config)
972
+ self.padding_idx = config.pad_token_id
973
+ self.vocab_size = config.vocab_size
974
+
975
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size, self.padding_idx)
976
+ self.layers = nn.ModuleList(
977
+ [Qwen3ASRThinkerTextDecoderLayer(config, layer_idx) for layer_idx in range(config.num_hidden_layers)]
978
+ )
979
+ self.norm = Qwen3ASRTextRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
980
+ self.rotary_emb = Qwen3ASRThinkerTextRotaryEmbedding(config)
981
+ self.gradient_checkpointing = False
982
+
983
+ # Initialize weights and apply final processing
984
+ self.post_init()
985
+
986
+ @check_model_inputs()
987
+ @auto_docstring
988
+ def forward(
989
+ self,
990
+ input_ids: Optional[torch.LongTensor] = None,
991
+ attention_mask: Optional[torch.Tensor] = None,
992
+ position_ids: Optional[torch.LongTensor] = None,
993
+ past_key_values: Optional[Cache] = None,
994
+ inputs_embeds: Optional[torch.FloatTensor] = None,
995
+ use_cache: Optional[bool] = None,
996
+ cache_position: Optional[torch.LongTensor] = None,
997
+ **kwargs: Unpack[FlashAttentionKwargs],
998
+ ) -> Union[tuple, BaseModelOutputWithPast]:
999
+ if (input_ids is None) ^ (inputs_embeds is not None):
1000
+ raise ValueError("You must specify exactly one of input_ids or inputs_embeds")
1001
+
1002
+ # torch.jit.trace() doesn't support cache objects in the output
1003
+ if use_cache and past_key_values is None and not torch.jit.is_tracing():
1004
+ past_key_values = DynamicCache(config=self.config)
1005
+
1006
+ if inputs_embeds is None:
1007
+ inputs_embeds = self.embed_tokens(input_ids)
1008
+
1009
+ if cache_position is None:
1010
+ past_seen_tokens = past_key_values.get_seq_length() if past_key_values is not None else 0
1011
+ cache_position = torch.arange(
1012
+ past_seen_tokens, past_seen_tokens + inputs_embeds.shape[1], device=inputs_embeds.device
1013
+ )
1014
+
1015
+ # the hard coded `3` is for temporal, height and width.
1016
+ if position_ids is None:
1017
+ position_ids = cache_position.view(1, 1, -1).expand(3, inputs_embeds.shape[0], -1)
1018
+ elif position_ids.ndim == 2:
1019
+ position_ids = position_ids[None, ...].expand(3, position_ids.shape[0], -1)
1020
+
1021
+ if position_ids.ndim == 3 and position_ids.shape[0] == 4:
1022
+ text_position_ids = position_ids[0]
1023
+ position_ids = position_ids[1:]
1024
+ else:
1025
+ text_position_ids = position_ids[0]
1026
+
1027
+ attention_mask = create_causal_mask(
1028
+ config=self.config,
1029
+ input_embeds=inputs_embeds,
1030
+ attention_mask=attention_mask,
1031
+ cache_position=cache_position,
1032
+ past_key_values=past_key_values,
1033
+ position_ids=text_position_ids,
1034
+ )
1035
+
1036
+ hidden_states = inputs_embeds
1037
+
1038
+ # create position embeddings to be shared across the decoder layers
1039
+ position_embeddings = self.rotary_emb(hidden_states, position_ids)
1040
+
1041
+ # decoder layers
1042
+ for layer_idx, decoder_layer in enumerate(self.layers):
1043
+ layer_outputs = decoder_layer(
1044
+ hidden_states,
1045
+ attention_mask=attention_mask,
1046
+ position_ids=text_position_ids,
1047
+ past_key_values=past_key_values,
1048
+ cache_position=cache_position,
1049
+ position_embeddings=position_embeddings,
1050
+ **kwargs,
1051
+ )
1052
+ hidden_states = layer_outputs
1053
+
1054
+ hidden_states = self.norm(hidden_states)
1055
+
1056
+ return BaseModelOutputWithPast(
1057
+ last_hidden_state=hidden_states,
1058
+ past_key_values=past_key_values,
1059
+ )
1060
+
1061
+
1062
+ @auto_docstring(
1063
+ custom_intro="""
1064
+ The Qwen3ASRThinker model which consists of a audio backbone and a language model.
1065
+ """
1066
+ )
1067
+ class Qwen3ASRThinkerForConditionalGeneration(Qwen3ASRPreTrainedModelForConditionalGeneration, GenerationMixin):
1068
+ config: Qwen3ASRThinkerConfig
1069
+ base_model_prefix = "thinker"
1070
+ _tied_weights_keys = ["model.embed_tokens.weight", "lm_head.weight"]
1071
+ _no_split_modules = [
1072
+ "Qwen3ASRAudioEncoderLayer",
1073
+ "Qwen3ASRThinkerTextDecoderLayer",
1074
+ ]
1075
+ _can_record_outputs = {
1076
+ "hidden_states": Qwen3ASRThinkerTextDecoderLayer,
1077
+ "attentions": Qwen3ASRThinkerTextAttention,
1078
+ }
1079
+
1080
+ def __init__(self, config):
1081
+ super().__init__(config)
1082
+ self.audio_tower = Qwen3ASRAudioEncoder._from_config(config.audio_config)
1083
+ self.vocab_size = config.text_config.vocab_size
1084
+ self.model = Qwen3ASRThinkerTextModel._from_config(config.text_config)
1085
+ if "forced_aligner" in config.model_type:
1086
+ self.lm_head = nn.Linear(config.text_config.hidden_size, config.classify_num, bias=False)
1087
+ else:
1088
+ self.lm_head = nn.Linear(config.text_config.hidden_size, config.text_config.vocab_size, bias=False)
1089
+ self.pad_token_id = self.config.pad_token_id if self.config.pad_token_id is not None else -1
1090
+ self.rope_deltas = None
1091
+ self.post_init()
1092
+
1093
+ def get_input_embeddings(self):
1094
+ return self.model.get_input_embeddings()
1095
+
1096
+ def set_input_embeddings(self, value):
1097
+ self.model.set_input_embeddings(value)
1098
+
1099
+ def get_audio_features(
1100
+ self,
1101
+ input_features: torch.FloatTensor,
1102
+ feature_attention_mask: Optional[torch.LongTensor] = None,
1103
+ audio_feature_lengths: Optional[torch.LongTensor] = None,
1104
+ ):
1105
+ """
1106
+ Encodes audios into continuous embeddings that can be forwarded to the language model.
1107
+
1108
+ Args:
1109
+ input_features (`torch.FloatTensor`):
1110
+ The tensors corresponding to the input audios.
1111
+ feature_attention_mask (`torch.LongTensor`, *optional*):
1112
+ Mask to avoid performing attention on padding feature indices. Mask values selected in `[0, 1]`:
1113
+ audio_feature_lengths (`torch.LongTensor` of shape `(num_audios)`, *optional*):
1114
+ The length of feature shape of each audio in LLM.
1115
+ """
1116
+ if feature_attention_mask is not None:
1117
+ audio_feature_lengths = torch.sum(feature_attention_mask, dim=1)
1118
+ else:
1119
+ audio_feature_lengths = None
1120
+ feature_lens = audio_feature_lengths if audio_feature_lengths is not None else feature_attention_mask.sum(-1)
1121
+
1122
+ # audio encoder do not support batch inference to keep precision
1123
+ audio_features = []
1124
+ for input_feature, feature_len in zip(input_features, feature_lens):
1125
+ audio_output = self.audio_tower(
1126
+ input_feature[:, :feature_len],
1127
+ feature_lens=feature_len.unsqueeze(0),
1128
+ )
1129
+ audio_feature = audio_output.last_hidden_state
1130
+ audio_features.append(audio_feature)
1131
+ audio_features = torch.cat(audio_features, dim=0)
1132
+
1133
+ return audio_features
1134
+
1135
+ def get_placeholder_mask(
1136
+ self,
1137
+ input_ids: torch.LongTensor,
1138
+ inputs_embeds: torch.FloatTensor,
1139
+ ):
1140
+ """
1141
+ Obtains multimodal placeholder mask from `input_ids` or `inputs_embeds`, and checks that the placeholder token count is
1142
+ equal to the length of multimodal features. If the lengths are different, an error is raised.
1143
+ """
1144
+ if input_ids is None:
1145
+ special_audio_mask = (
1146
+ inputs_embeds
1147
+ == self.get_input_embeddings()(
1148
+ torch.tensor(self.config.audio_token_id, dtype=torch.long, device=inputs_embeds.device)
1149
+ )
1150
+ ).all(-1)
1151
+ else:
1152
+ special_audio_mask = input_ids == self.config.audio_token_id
1153
+
1154
+ special_audio_mask = special_audio_mask.unsqueeze(-1).expand_as(inputs_embeds).to(inputs_embeds.device)
1155
+ return special_audio_mask
1156
+
1157
+ @can_return_tuple
1158
+ @auto_docstring
1159
+ def forward(
1160
+ self,
1161
+ input_ids=None,
1162
+ input_features=None,
1163
+ attention_mask=None,
1164
+ feature_attention_mask=None,
1165
+ audio_feature_lengths=None,
1166
+ position_ids=None,
1167
+ past_key_values=None,
1168
+ inputs_embeds=None,
1169
+ rope_deltas=None,
1170
+ labels=None,
1171
+ use_cache=None,
1172
+ cache_position=None,
1173
+ **kwargs,
1174
+ ) -> Union[tuple, Qwen3ASRThinkerCausalLMOutputWithPast]:
1175
+ r"""
1176
+ feature_attention_mask (`torch.Tensor` of shape `(batch_size, feature_sequence_length)`, *optional*):
1177
+ Mask to avoid performing attention on padding feature indices. Mask values selected in `[0, 1]`:
1178
+ - 1 for tokens that are **not masked**,
1179
+ - 0 for tokens that are **masked**.
1180
+ audio_feature_lengths (`torch.LongTensor` of shape `(num_audios)`, *optional*):
1181
+ The length of feature shape of each audio in LLM.
1182
+ rope_deltas (`torch.LongTensor` of shape `(batch_size, )`, *optional*):
1183
+ The rope index difference between sequence length and multimodal rope.
1184
+ labels (`torch.LongTensor` of shape `(batch_size, sequence_length)`, *optional*):
1185
+ Labels for computing the masked language modeling loss. Indices should either be in `[0, ...,
1186
+ config.vocab_size]` or -100 (see `input_ids` docstring). Tokens with indices set to `-100` are ignored
1187
+ (masked), the loss is only computed for the tokens with labels in `[0, ..., config.vocab_size]`.
1188
+ """
1189
+
1190
+ if inputs_embeds is None:
1191
+ # 1. Extract the input embeddings
1192
+ inputs_embeds = self.get_input_embeddings()(input_ids)
1193
+
1194
+ # 2. Merge text, audios
1195
+ if input_features is not None:
1196
+ audio_features = self.get_audio_features(
1197
+ input_features,
1198
+ feature_attention_mask=feature_attention_mask,
1199
+ audio_feature_lengths=audio_feature_lengths,
1200
+ )
1201
+ audio_features = audio_features.to(inputs_embeds.device, inputs_embeds.dtype)
1202
+ audio_mask = self.get_placeholder_mask(input_ids, inputs_embeds=inputs_embeds)
1203
+ inputs_embeds = inputs_embeds.masked_scatter(audio_mask, audio_features)
1204
+
1205
+ if feature_attention_mask is not None:
1206
+ audio_feature_lengths = torch.sum(feature_attention_mask, dim=1)
1207
+ else:
1208
+ audio_feature_lengths = None
1209
+
1210
+ if attention_mask is not None and position_ids is None:
1211
+ if (
1212
+ cache_position is None
1213
+ or (cache_position is not None and cache_position[0] == 0)
1214
+ or self.rope_deltas is None
1215
+ ):
1216
+ delta0 = (1 - attention_mask).sum(dim=-1).unsqueeze(1)
1217
+ position_ids, rope_deltas = self.get_rope_index(
1218
+ attention_mask,
1219
+ )
1220
+ rope_deltas = rope_deltas - delta0
1221
+ self.rope_deltas = rope_deltas
1222
+ else:
1223
+ batch_size, seq_length = input_ids.shape
1224
+ delta = cache_position[0] + self.rope_deltas if cache_position is not None else 0
1225
+ position_ids = torch.arange(seq_length, device=input_ids.device)
1226
+ position_ids = position_ids.view(1, -1).expand(batch_size, -1)
1227
+ position_ids = position_ids.add(delta)
1228
+ position_ids = position_ids.unsqueeze(0).expand(3, -1, -1)
1229
+
1230
+ outputs = self.model(
1231
+ attention_mask=attention_mask,
1232
+ position_ids=position_ids,
1233
+ past_key_values=past_key_values,
1234
+ inputs_embeds=inputs_embeds,
1235
+ use_cache=use_cache,
1236
+ cache_position=cache_position,
1237
+ **kwargs,
1238
+ )
1239
+
1240
+ hidden_states = outputs[0]
1241
+ logits = self.lm_head(hidden_states)
1242
+
1243
+ loss = None
1244
+ if labels is not None:
1245
+ loss = self.loss_function(
1246
+ logits=logits, labels=labels, vocab_size=self.config.get_text_config().vocab_size
1247
+ )
1248
+
1249
+ return Qwen3ASRThinkerCausalLMOutputWithPast(
1250
+ loss=loss,
1251
+ logits=logits,
1252
+ hidden_states=outputs.hidden_states,
1253
+ attentions=outputs.attentions,
1254
+ past_key_values=outputs.past_key_values,
1255
+ rope_deltas=self.rope_deltas,
1256
+ )
1257
+
1258
+ def prepare_inputs_for_generation(
1259
+ self,
1260
+ input_ids,
1261
+ past_key_values=None,
1262
+ attention_mask=None,
1263
+ inputs_embeds=None,
1264
+ cache_position=None,
1265
+ position_ids=None,
1266
+ use_cache=True,
1267
+ input_features=None,
1268
+ feature_attention_mask=None,
1269
+ **kwargs,
1270
+ ):
1271
+ model_inputs = super().prepare_inputs_for_generation(
1272
+ input_ids,
1273
+ past_key_values=past_key_values,
1274
+ attention_mask=attention_mask,
1275
+ inputs_embeds=inputs_embeds,
1276
+ cache_position=cache_position,
1277
+ position_ids=position_ids,
1278
+ use_cache=use_cache,
1279
+ input_features=input_features,
1280
+ feature_attention_mask=feature_attention_mask,
1281
+ **kwargs,
1282
+ )
1283
+
1284
+ model_inputs["position_ids"] = None
1285
+
1286
+ if cache_position[0] != 0:
1287
+ model_inputs["input_features"] = None
1288
+
1289
+ return model_inputs
1290
+
1291
+
1292
+ @auto_docstring
1293
+ class Qwen3ASRThinkerTextPreTrainedModel(PreTrainedModel):
1294
+ config = Qwen3ASRConfig
1295
+ base_model_prefix = "model"
1296
+ supports_gradient_checkpointing = True
1297
+ _no_split_modules = ["Qwen3ASRThinkerTextDecoderLayer"]
1298
+ _skip_keys_device_placement = ["past_key_values"]
1299
+ _supports_flash_attn = True
1300
+ _supports_sdpa = True
1301
+ _supports_flex_attn = True
1302
+ _can_compile_fullgraph = False # MoE models don't work with torch.compile (`torch.where(condition)` not supported)
1303
+ _supports_attention_backend = True
1304
+ _can_record_outputs = {
1305
+ "hidden_states": Qwen3ASRThinkerTextDecoderLayer,
1306
+ "attentions": Qwen3ASRThinkerTextAttention,
1307
+ }
1308
+ config_class = Qwen3ASRConfig
1309
+
1310
+
1311
+ class Qwen3ASRForConditionalGeneration(Qwen3ASRPreTrainedModel, GenerationMixin):
1312
+ config_class = Qwen3ASRConfig
1313
+
1314
+ def __init__(self, config: Qwen3ASRConfig):
1315
+ super().__init__(config)
1316
+ self.config = config
1317
+
1318
+ self.thinker = Qwen3ASRThinkerForConditionalGeneration._from_config(config.thinker_config)
1319
+ self.post_init()
1320
+
1321
+ def get_support_languages(self):
1322
+ return self.config.support_languages
1323
+
1324
+ @torch.no_grad()
1325
+ def generate(
1326
+ self,
1327
+ input_ids: Optional[torch.Tensor] = None,
1328
+ max_new_tokens: int = 4096,
1329
+ eos_token_id: int | list[int] = [151645, 151643],
1330
+ **kwargs,
1331
+ ):
1332
+ shared_kwargs = {}
1333
+ thinker_kwargs = {
1334
+ "max_new_tokens": max_new_tokens,
1335
+ "eos_token_id": eos_token_id,
1336
+ }
1337
+
1338
+ for key, value in kwargs.items():
1339
+ # Process special input values
1340
+ if key == "feature_attention_mask":
1341
+ thinker_kwargs[key] = value
1342
+ elif key in ("input_features", "attention_mask"):
1343
+ thinker_kwargs[key] = value
1344
+ # Put other key to shared kwargs
1345
+ else:
1346
+ shared_kwargs[key] = value
1347
+
1348
+ # Merge kwargs
1349
+ for key, value in shared_kwargs.items():
1350
+ if key not in thinker_kwargs:
1351
+ thinker_kwargs[key] = value
1352
+
1353
+ thinker_result = self.thinker.generate(input_ids=input_ids, return_dict_in_generate=True, **thinker_kwargs)
1354
+
1355
+ return thinker_result
1356
+
1357
+
1358
+ __all__ = [
1359
+ "Qwen3ASRForConditionalGeneration",
1360
+ "Qwen3ASRThinkerTextModel",
1361
+ "Qwen3ASRThinkerForConditionalGeneration",
1362
+ "Qwen3ASRPreTrainedModel",
1363
+ "Qwen3ASRPreTrainedModelForConditionalGeneration",
1364
+ "Qwen3ASRThinkerTextPreTrainedModel",
1365
+ ]
preprocessor_config.json CHANGED
@@ -1,14 +1,17 @@
1
  {
2
- "chunk_length": 30,
3
- "dither": 0.0,
4
- "feature_extractor_type": "WhisperFeatureExtractor",
5
- "feature_size": 128,
6
- "hop_length": 160,
7
- "n_fft": 400,
8
- "n_samples": 480000,
9
- "nb_max_frames": 3000,
10
- "padding_side": "right",
11
- "padding_value": 0.0,
12
- "processor_class": "Qwen3ASRProcessor",
13
- "return_attention_mask": true
 
 
 
14
  }
 
1
  {
2
+ "chunk_length": 30,
3
+ "dither": 0.0,
4
+ "feature_extractor_type": "WhisperFeatureExtractor",
5
+ "feature_size": 128,
6
+ "hop_length": 160,
7
+ "n_fft": 400,
8
+ "n_samples": 480000,
9
+ "nb_max_frames": 3000,
10
+ "padding_side": "right",
11
+ "padding_value": 0.0,
12
+ "processor_class": "Qwen3ASRProcessor",
13
+ "return_attention_mask": true,
14
+ "auto_map": {
15
+ "AutoProcessor": "processing_qwen3_asr.Qwen3ASRProcessor"
16
+ }
17
  }
processing_qwen3_asr.py ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # coding=utf-8
2
+ # Copyright 2026 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ import re
16
+
17
+ import numpy as np
18
+
19
+ from transformers.audio_utils import AudioInput
20
+ from transformers.feature_extraction_utils import BatchFeature
21
+ from transformers.processing_utils import ProcessingKwargs, ProcessorMixin
22
+ from transformers.tokenization_utils_base import TextInput
23
+
24
+
25
+ class Qwen3ASRProcessorKwargs(ProcessingKwargs, total=False):
26
+ _defaults = {
27
+ "text_kwargs": {
28
+ "padding": False,
29
+ "padding_side": "left",
30
+ },
31
+ "audio_kwargs": {
32
+ "sampling_rate": 16000,
33
+ "padding": True,
34
+ "return_attention_mask": True,
35
+ },
36
+ }
37
+
38
+
39
+ def _get_feat_extract_output_lengths(input_lengths):
40
+ """
41
+ Computes the output length of the convolutional layers and the output length of the audio encoder
42
+ """
43
+
44
+ input_lengths_leave = input_lengths % 100
45
+ feat_lengths = (input_lengths_leave - 1) // 2 + 1
46
+ output_lengths = ((feat_lengths - 1) // 2 + 1 - 1) // 2 + 1 + (input_lengths // 100) * 13
47
+ return output_lengths
48
+
49
+
50
+ class Qwen3ASRProcessor(ProcessorMixin):
51
+ r"""
52
+ Constructs a Qwen3ASR processor.
53
+ [`Qwen3ASRProcessor`] offers all the functionalities of [`WhisperFeatureExtractor`], and [`Qwen2TokenizerFast`]. See the
54
+ [`~Qwen3ASRProcessor.__call__`] and [`~Qwen3ASRProcessor.decode`] for more information.
55
+
56
+ Args:
57
+ feature_extractor ([`WhisperFeatureExtractor`], *optional*):
58
+ The audio feature extractor.
59
+ tokenizer ([`Qwen2TokenizerFast`], *optional*):
60
+ The text tokenizer.
61
+ chat_template (`Optional[str]`, *optional*):
62
+ The Jinja template to use for formatting the conversation. If not provided, the default chat template is used.
63
+ """
64
+
65
+ attributes = ["feature_extractor", "tokenizer"]
66
+ feature_extractor_class = "WhisperFeatureExtractor"
67
+ tokenizer_class = ("Qwen2Tokenizer", "Qwen2TokenizerFast")
68
+
69
+ def __init__(
70
+ self, feature_extractor=None, tokenizer=None, chat_template=None
71
+ ):
72
+ super().__init__(feature_extractor, tokenizer, chat_template=chat_template)
73
+ self.audio_token = self.tokenizer.audio_token
74
+ self.audio_bos_token = self.tokenizer.audio_bos_token
75
+ self.audio_eos_token = self.tokenizer.audio_eos_token
76
+
77
+ def __call__(
78
+ self,
79
+ text: TextInput = None,
80
+ audio: AudioInput = None,
81
+ **kwargs,
82
+ ) -> BatchFeature:
83
+ """
84
+ Main method to prepare for the model one or several sequences(s) and audio(s). This method forwards the `text`
85
+ and `kwargs` arguments to Qwen2TokenizerFast's [`~Qwen2TokenizerFast.__call__`] if `text` is not `None` to encode
86
+ the text. To prepare the audio(s), this method forwards the `audio` and `kwargs` arguments to
87
+ WhisperFeatureExtractor's [`~WhisperFeatureExtractor.__call__`] if `audio` is not `None`. Please refer to the doctsring
88
+ of the above two methods for more information.
89
+
90
+ Args:
91
+ text (`str`, `List[str]`, `List[List[str]]`):
92
+ The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings
93
+ (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set
94
+ `is_split_into_words=True` (to lift the ambiguity with a batch of sequences).
95
+ audio (`np.ndarray`, `List[np.ndarray]`):
96
+ The audio or batch of audio to be prepared. Each audio can be a NumPy array.
97
+ """
98
+
99
+ if text is None:
100
+ raise ValueError("You need to specify either a `text` input to process.")
101
+
102
+ output_kwargs = self._merge_kwargs(
103
+ Qwen3ASRProcessorKwargs,
104
+ tokenizer_init_kwargs=self.tokenizer.init_kwargs,
105
+ **kwargs,
106
+ )
107
+
108
+ if audio is not None:
109
+ output_kwargs["audio_kwargs"]["padding"] = True
110
+ output_kwargs["audio_kwargs"]["truncation"] = False
111
+ audio_inputs = self.feature_extractor(audio, **output_kwargs["audio_kwargs"])
112
+ audio_inputs["feature_attention_mask"] = audio_inputs.pop(
113
+ "attention_mask"
114
+ ) # rename feature_attention_mask to prevent conflicts later on
115
+ audio_inputs["input_features"] = audio_inputs.pop(
116
+ "input_features"
117
+ ) # rename input_features to prevent conflicts later on
118
+ audio_lengths = iter(_get_feat_extract_output_lengths(audio_inputs["feature_attention_mask"].sum(-1)))
119
+ else:
120
+ audio_inputs = {}
121
+ audio_lengths = iter([])
122
+
123
+ if not isinstance(text, list):
124
+ text = [text]
125
+
126
+ text = self.replace_multimodal_special_tokens(
127
+ text,
128
+ audio_lengths,
129
+ )
130
+
131
+ texts_inputs = self.tokenizer(text, **output_kwargs["text_kwargs"])
132
+
133
+ return BatchFeature(
134
+ data={**texts_inputs, **audio_inputs},
135
+ tensor_type=kwargs.get("return_tensors"),
136
+ )
137
+
138
+ def replace_multimodal_special_tokens(
139
+ self,
140
+ text,
141
+ audio_lengths,
142
+ ):
143
+
144
+ processed_text = []
145
+ for sample in text:
146
+ positions = []
147
+ special_tokens = [re.escape(tok) for tok in [self.audio_token]]
148
+ pattern = "|".join(special_tokens)
149
+ positions = sorted([(match.start(), match.group()) for match in re.finditer(pattern, sample)])
150
+ positions.sort(key=lambda x: x[0])
151
+
152
+ for _, special_token in positions:
153
+ if special_token == self.audio_token:
154
+ sample = sample.replace(self.audio_token, "<|audio_placeholder|>" * next(audio_lengths), 1)
155
+
156
+ sample = sample.replace("<|audio_placeholder|>", self.audio_token)
157
+ processed_text.append(sample)
158
+ return processed_text
159
+
160
+ def get_chunked_index(self, token_indices: np.ndarray, tokens_per_chunk: int) -> list[tuple[int, int]]:
161
+ """
162
+ Splits token index list into chunks based on token value ranges.
163
+
164
+ Given a list of token indices, returns a list of (start, end) index tuples representing
165
+ slices of the list where the token values fall within successive ranges of `t_ntoken_per_chunk`.
166
+
167
+ For example, if `t_ntoken_per_chunk` is 1000, the function will create chunks such that:
168
+ - the first chunk contains token values < 1000,
169
+ - the second chunk contains values >= 1000 and < 2000, and so on.
170
+
171
+ Parameters:
172
+ token_indices (`np.ndarray`): A monotonically increasing list of token index values.
173
+ t_ntoken_per_chunk (`int`): Number of tokens per chunk (used as the chunk size threshold).
174
+
175
+ Returns:
176
+ `list[tuple[int, int]]`: A list of tuples, each representing the start (inclusive)
177
+ and end (exclusive) indices of a chunk in `token_indices`.
178
+ """
179
+
180
+ def _iter():
181
+ i, start_idx = 0, 0 # skip bos token
182
+ current_chunk = 1
183
+ while i < len(token_indices): # skip eos token
184
+ if token_indices[i] >= current_chunk * tokens_per_chunk:
185
+ yield (start_idx, i)
186
+ start_idx = i
187
+ current_chunk += 1
188
+ i += 1
189
+ yield (start_idx, len(token_indices))
190
+
191
+ return list(_iter())
192
+
193
+ def apply_chat_template(self, conversations, chat_template=None, **kwargs):
194
+ return super().apply_chat_template(conversations, chat_template, **kwargs)
195
+
196
+ @property
197
+ def model_input_names(self):
198
+ tokenizer_input_names = self.tokenizer.model_input_names
199
+ feature_extractor_input_names = self.feature_extractor.model_input_names
200
+ return list(
201
+ dict.fromkeys(
202
+ tokenizer_input_names
203
+ + feature_extractor_input_names
204
+ + ["feature_attention_mask"]
205
+ )
206
+ )
207
+
208
+
209
+ __all__ = ["Qwen3ASRProcessor"]