Automatic Speech Recognition
Transformers
Safetensors
qwen3_asr
bezzam HF Staff commited on
Commit
4094609
·
1 Parent(s): e8cb6ff

Update chat template and usage with custom hotwords.

Browse files
Files changed (2) hide show
  1. README.md +44 -16
  2. chat_template.jinja +8 -31
README.md CHANGED
@@ -112,9 +112,9 @@ Transcription: Mr. Quilter is the apostle of the middle classes, and we are glad
112
  """
113
  ```
114
 
115
- ### Language hint
116
 
117
- Pass a language hint to skip auto-detection.
118
 
119
  ```python
120
  from transformers import AutoProcessor, AutoModelForMultimodalLM
@@ -131,14 +131,36 @@ output_ids = model.generate(**inputs, max_new_tokens=256)
131
  generated_ids = output_ids[:, inputs["input_ids"].shape[1]:]
132
  print(f"Auto-detect: {processor.decode(generated_ids, return_format='transcription_only')[0]}")
133
 
134
- # With language hint (language code or full name both accepted)
135
  inputs = processor.apply_transcription_request(
136
  audio="https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_zh.wav",
137
- language="Chinese", # or "zh"
138
  ).to(model.device, model.dtype)
139
  output_ids = model.generate(**inputs, max_new_tokens=256)
140
  generated_ids = output_ids[:, inputs["input_ids"].shape[1]:]
141
- print(f"With hint: {processor.decode(generated_ids, return_format='transcription_only')[0]}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  ```
143
 
144
  ### Batch inference
@@ -171,7 +193,9 @@ for i, text in enumerate(transcriptions):
171
 
172
  ### Chat template
173
 
174
- `apply_transcription_request` is a convenience wrapper around `apply_chat_template`. Use the chat template directly for more control, such as providing a language hint via a system message.
 
 
175
 
176
  ```python
177
  from transformers import AutoProcessor, Qwen3ASRForConditionalGeneration
@@ -182,7 +206,8 @@ model = Qwen3ASRForConditionalGeneration.from_pretrained(model_id, device_map="a
182
 
183
  chat_template = [
184
  [
185
- {"role": "system", "content": [{"type": "text", "text": "English"}]},
 
186
  {
187
  "role": "user",
188
  "content": [
@@ -192,6 +217,8 @@ chat_template = [
192
  },
193
  ],
194
  },
 
 
195
  ],
196
  [
197
  {
@@ -203,11 +230,12 @@ chat_template = [
203
  },
204
  ],
205
  },
 
206
  ],
207
  ]
208
 
209
  inputs = processor.apply_chat_template(
210
- chat_template, tokenize=True, return_dict=True,
211
  ).to(model.device, model.dtype)
212
 
213
  output_ids = model.generate(**inputs, max_new_tokens=256)
@@ -217,7 +245,9 @@ for text in transcriptions:
217
  print(text)
218
  ```
219
 
220
- ### Training / Fine-tuning
 
 
221
 
222
  ```python
223
  from transformers import AutoProcessor, Qwen3ASRForConditionalGeneration
@@ -227,26 +257,24 @@ processor = AutoProcessor.from_pretrained(model_id)
227
  model = Qwen3ASRForConditionalGeneration.from_pretrained(model_id, device_map="auto")
228
  model.train()
229
 
230
- chat_template = [
 
231
  [
232
  {
233
  "role": "user",
234
  "content": [
235
- {
236
- "type": "text",
237
- "text": "Mr. Quilter is the apostle of the middle classes, and we are glad to welcome his gospel.",
238
- },
239
  {
240
  "type": "audio",
241
  "path": "https://huggingface.co/datasets/bezzam/audio_samples/resolve/main/librispeech_mr_quilter.wav",
242
  },
243
  ],
244
- }
 
245
  ],
246
  ]
247
 
248
  inputs = processor.apply_chat_template(
249
- chat_template, tokenize=True, return_dict=True, output_labels=True,
250
  ).to(model.device, model.dtype)
251
 
252
  loss = model(**inputs).loss
 
112
  """
113
  ```
114
 
115
+ ### Forcing the language
116
 
117
+ You can force the transcription language as shown below.
118
 
119
  ```python
120
  from transformers import AutoProcessor, AutoModelForMultimodalLM
 
131
  generated_ids = output_ids[:, inputs["input_ids"].shape[1]:]
132
  print(f"Auto-detect: {processor.decode(generated_ids, return_format='transcription_only')[0]}")
133
 
134
+ # With forced language
135
  inputs = processor.apply_transcription_request(
136
  audio="https://qianwen-res.oss-cn-beijing.aliyuncs.com/Qwen3-ASR-Repo/asr_zh.wav",
137
+ language="Chinese", # or language code "zh"
138
  ).to(model.device, model.dtype)
139
  output_ids = model.generate(**inputs, max_new_tokens=256)
140
  generated_ids = output_ids[:, inputs["input_ids"].shape[1]:]
141
+ print(f"Forced: {processor.decode(generated_ids, return_format='transcription_only')[0]}")
142
+ ```
143
+
144
+ ### Context / hotwords
145
+
146
+ You can pass free-form context (e.g. domain-specific vocabulary, names, or background information) via `prompt` to bias the transcription.
147
+
148
+ ```python
149
+ from transformers import AutoProcessor, AutoModelForMultimodalLM
150
+
151
+ model_id = "Qwen/Qwen3-ASR-0.6B-hf"
152
+ processor = AutoProcessor.from_pretrained(model_id)
153
+ model = AutoModelForMultimodalLM.from_pretrained(model_id, device_map="auto")
154
+
155
+ inputs = processor.apply_transcription_request(
156
+ audio="https://huggingface.co/datasets/bezzam/audio_samples/resolve/main/librispeech_mr_quilter.wav",
157
+ prompt="Vocabulary: Quilter, apostle, gospel.",
158
+ language="English",
159
+ ).to(model.device, model.dtype)
160
+
161
+ output_ids = model.generate(**inputs, max_new_tokens=256)
162
+ generated_ids = output_ids[:, inputs["input_ids"].shape[1]:]
163
+ print(processor.decode(generated_ids, return_format="transcription_only")[0])
164
  ```
165
 
166
  ### Batch inference
 
193
 
194
  ### Chat template
195
 
196
+ Qwen3 ASR also accepts chat template inputs. The `apply_transcription_request` usage [above](#simple-transcription) is a convenience wrapper for `apply_chat_template`.
197
+
198
+ The language can be forced through the chat template by *prefilling* the assistant turn with `language <NAME><asr_text>` and passing `continue_final_message=True`, which is what `apply_transcription_request` does under the hood. Note that if forcing the language, a prefill should be set for all audio in a batch (as shown below).
199
 
200
  ```python
201
  from transformers import AutoProcessor, Qwen3ASRForConditionalGeneration
 
206
 
207
  chat_template = [
208
  [
209
+ # Context/hotwords as system message
210
+ {"role": "system", "content": [{"type": "text", "text": "Vocabulary: Quilter, apostle, gospel."}]},
211
  {
212
  "role": "user",
213
  "content": [
 
217
  },
218
  ],
219
  },
220
+ # empty prefill since forcing language in the other sample
221
+ {"role": "assistant", "content": [{"type": "text", "text": ""}]},
222
  ],
223
  [
224
  {
 
230
  },
231
  ],
232
  },
233
+ {"role": "assistant", "content": [{"type": "text", "text": "language Chinese<asr_text>"}]},
234
  ],
235
  ]
236
 
237
  inputs = processor.apply_chat_template(
238
+ chat_template, tokenize=True, return_dict=True, continue_final_message=True,
239
  ).to(model.device, model.dtype)
240
 
241
  output_ids = model.generate(**inputs, max_new_tokens=256)
 
245
  print(text)
246
  ```
247
 
248
+ ### Training / fine-tuning
249
+
250
+ Qwen3 ASR can be trained with the loss outputted by the model. Put the target transcript in the assistant turn — in the model's output format `language <NAME><asr_text>...` to preserve the pretrained behavior — and pass `output_labels=True`. Audio and padding positions are masked automatically.
251
 
252
  ```python
253
  from transformers import AutoProcessor, Qwen3ASRForConditionalGeneration
 
257
  model = Qwen3ASRForConditionalGeneration.from_pretrained(model_id, device_map="auto")
258
  model.train()
259
 
260
+ transcript = "Mr. Quilter is the apostle of the middle classes, and we are glad to welcome his gospel."
261
+ conversation = [
262
  [
263
  {
264
  "role": "user",
265
  "content": [
 
 
 
 
266
  {
267
  "type": "audio",
268
  "path": "https://huggingface.co/datasets/bezzam/audio_samples/resolve/main/librispeech_mr_quilter.wav",
269
  },
270
  ],
271
+ },
272
+ {"role": "assistant", "content": [{"type": "text", "text": f"language English<asr_text>{transcript}"}]},
273
  ],
274
  ]
275
 
276
  inputs = processor.apply_chat_template(
277
+ conversation, tokenize=True, return_dict=True, processor_kwargs={"output_labels": True},
278
  ).to(model.device, model.dtype)
279
 
280
  loss = model(**inputs).loss
chat_template.jinja CHANGED
@@ -1,31 +1,8 @@
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 -%}
 
1
+ {%- set ns = namespace(system_text='') -%}{%- for m in messages -%}{%- if m.role == 'system' -%}{%- if m.content is string -%}{%- set ns.system_text = ns.system_text + m.content -%}{%- else -%}{%- for c in m.content -%}{%- if c.type == 'text' and (c.text is defined) -%}{%- set ns.system_text = ns.system_text + c.text -%}{%- endif -%}{%- endfor -%}{%- endif -%}{%- endif -%}{%- endfor -%}{%- set ns2 = namespace(audio_tokens='') -%}{%- for m in messages -%}{%- if m.content is not string -%}{%- for c in m.content -%}{%- if c.type == 'audio' or ('audio' in c) or ('audio_url' in c) -%}{%- set ns2.audio_tokens = ns2.audio_tokens + '<|audio_start|><|audio_pad|><|audio_end|>' -%}{%- endif -%}{%- endfor -%}{%- endif -%}{%- endfor -%}{{- '<|im_start|>system
2
+ ' + ns.system_text + '<|im_end|>
3
+ ' -}}{{- '<|im_start|>user
4
+ ' + ns2.audio_tokens + '<|im_end|>
5
+ ' -}}{%- for m in messages -%}{%- if m.role == 'assistant' -%}{%- set ns3 = namespace(assistant_text='') -%}{%- if m.content is string -%}{%- set ns3.assistant_text = m.content -%}{%- else -%}{%- for c in m.content -%}{%- if c.type == 'text' and (c.text is defined) -%}{%- set ns3.assistant_text = ns3.assistant_text + c.text -%}{%- endif -%}{%- endfor -%}{%- endif -%}{{- '<|im_start|>assistant
6
+ ' -}}{% generation %}{{- ns3.assistant_text + '<|im_end|>
7
+ ' -}}{% endgeneration %}{%- endif -%}{%- endfor -%}{%- if add_generation_prompt -%}{{- '<|im_start|>assistant
8
+ ' -}}{%- endif -%}