Automatic Speech Recognition
Transformers
Safetensors
Chinese
English
ming_v3_asr
text-generation
audio
speech-recognition
contextual-asr
custom_code
Instructions to use ArchiveStudio/Ming-3.0-asr-tiny with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ArchiveStudio/Ming-3.0-asr-tiny with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("automatic-speech-recognition", model="ArchiveStudio/Ming-3.0-asr-tiny", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("ArchiveStudio/Ming-3.0-asr-tiny", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Download processing_ming_v3_asr.py from ArchiveStudio/Ming-3.0-asr-tiny: direct link, hf CLI and curl.
- Browser
- Download file 10.7 kB
-
https://huggingface.co/ArchiveStudio/Ming-3.0-asr-tiny/resolve/main/processing_ming_v3_asr.py
- Command line
-
hf download hf://ArchiveStudio/Ming-3.0-asr-tiny/processing_ming_v3_asr.py
-
curl -L -o processing_ming_v3_asr.py https://huggingface.co/ArchiveStudio/Ming-3.0-asr-tiny/resolve/main/processing_ming_v3_asr.py
10.7 kB
| # Copyright 2026 Ant Group. All rights reserved. | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy at http://www.apache.org/licenses/LICENSE-2.0 | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| """Hugging Face processor for Ming V3 ASR transcription.""" | |
| import re | |
| import numpy as np | |
| import torch | |
| from transformers.feature_extraction_utils import BatchFeature | |
| from transformers.processing_utils import ProcessingKwargs, ProcessorMixin | |
| from .audio_spec import MingAudioSpec # noqa: F401 - include transitive dependency in HF's local module cache | |
| from .feature_extraction_ming_v3_asr import MingV3ASRFeatureExtractor | |
| _AUDIO_FORMS = ( | |
| ("<audio>", "<audioPatch>", "</audio>"), | |
| ("<|audio_start|>", "<|audio_pad|>", "<|audio_end|>"), | |
| ) | |
| _PLACEHOLDER = "<AUDIO>" | |
| _ASR_INSTRUCTION = ( | |
| "Please recognize the language of this speech and transcribe it, " | |
| "placing a `\t` between the identified language and the transcribe." | |
| ) | |
| class MingV3ASRProcessorKwargs(ProcessingKwargs, total=False): | |
| _defaults = { | |
| "text_kwargs": { | |
| "padding": False, | |
| "padding_side": "left", | |
| "return_attention_mask": True, | |
| "return_token_type_ids": False, | |
| }, | |
| } | |
| class MingV3ASRProcessor(ProcessorMixin): | |
| """Expand audio tokens and combine an unchanged tokenizer with log-mel. | |
| ``audio`` (or the compatible ``audios`` spelling) is a flat list of clips in | |
| prompt-placeholder order. ``audio_num_clips`` records each text request's | |
| clip count, including zero. Audio features and raw lengths are flattened | |
| independently of the text batch. Image/video inputs are not supported. | |
| Chat-template support is inherited from ``ProcessorMixin`` for tokenizer | |
| compatibility. ASR inference uses ``apply_transcription_request`` instead. | |
| """ | |
| feature_extractor_class = "AutoFeatureExtractor" | |
| tokenizer_class = "AutoTokenizer" | |
| valid_processor_kwargs = MingV3ASRProcessorKwargs | |
| def __init__( | |
| self, | |
| feature_extractor: MingV3ASRFeatureExtractor, | |
| tokenizer, | |
| chat_template=None, | |
| ): | |
| template = tokenizer.chat_template if chat_template is None else chat_template | |
| super().__init__(feature_extractor, tokenizer, chat_template=template) | |
| vocabulary = tokenizer.get_vocab() | |
| self.audio_start_token, self.audio_token, self.audio_end_token = next( | |
| ( | |
| form | |
| for form in _AUDIO_FORMS | |
| if all(token in vocabulary for token in form) | |
| ), | |
| (None, None, None), | |
| ) | |
| if self.audio_token is None: | |
| raise ValueError("tokenizer lacks Ming audio start/patch/end tokens") | |
| self.audio_token_id = tokenizer.convert_tokens_to_ids(self.audio_token) | |
| def _normalize_prompt(text): | |
| for start, patch, end in _AUDIO_FORMS: | |
| text = re.sub( | |
| re.escape(start) + "(?:" + re.escape(patch) + ")+" + re.escape(end), | |
| _PLACEHOLDER, | |
| text, | |
| ) | |
| return text | |
| def __call__( | |
| self, text=None, audio=None, *, images=None, videos=None, audios=None, **kwargs | |
| ): | |
| if images is not None or videos is not None: | |
| raise ValueError("MingV3ASRProcessor supports audio and text only") | |
| if audio is not None and audios is not None: | |
| raise ValueError("Pass either audio or audios, not both") | |
| clips = self.feature_extractor.as_clips(audio if audio is not None else audios) | |
| if text is None: | |
| if not clips: | |
| raise ValueError("Provide text or audio") | |
| text = " ".join([_PLACEHOLDER] * len(clips)) | |
| texts = [text] if isinstance(text, str) else text | |
| if ( | |
| not isinstance(texts, (list, tuple)) | |
| or not texts | |
| or not all(isinstance(t, str) for t in texts) | |
| ): | |
| raise ValueError("text must be a string or a nonempty list of strings") | |
| texts = [self._normalize_prompt(t) for t in texts] | |
| clip_counts = [t.count(_PLACEHOLDER) for t in texts] | |
| if sum(clip_counts) != len(clips): | |
| raise ValueError( | |
| f"Audio placeholder count {sum(clip_counts)} does not match {len(clips)} clips" | |
| ) | |
| options = self._merge_kwargs( | |
| MingV3ASRProcessorKwargs, | |
| tokenizer_init_kwargs=self.tokenizer.init_kwargs, | |
| **kwargs, | |
| ) | |
| text_options = options["text_kwargs"] | |
| audio_options = options["audio_kwargs"] | |
| return_tensors = text_options.get("return_tensors") | |
| audio_return_tensors = audio_options.get("return_tensors") | |
| if ( | |
| return_tensors is not None | |
| and audio_return_tensors is not None | |
| and return_tensors != audio_return_tensors | |
| ): | |
| raise ValueError("text and audio return_tensors must match") | |
| if return_tensors is None: | |
| return_tensors = audio_return_tensors | |
| if return_tensors is not None: | |
| text_options["return_tensors"] = return_tensors | |
| audio_options["return_tensors"] = return_tensors | |
| if text_options.get("return_overflowing_tokens"): | |
| raise ValueError("Audio token overflow windows are not supported") | |
| # Top-level max_length/truncation refer to text; audio is always chunked. | |
| # Explicit nested audio options retain extractor validation. | |
| for key in ("max_length", "truncation", "padding"): | |
| if key not in kwargs.get("audio_kwargs", {}): | |
| audio_options.pop(key, None) | |
| audio_inputs = self.feature_extractor(clips, **audio_options) if clips else {} | |
| lengths = audio_inputs.get("audio_feats_lengths", []) | |
| token_counts = [ | |
| self.feature_extractor.spec.token_count(int(length)) for length in lengths | |
| ] | |
| expected_counts = [] | |
| clip_index = 0 | |
| for index, count in enumerate(clip_counts): | |
| expected_counts.append(sum(token_counts[clip_index : clip_index + count])) | |
| for token_count in token_counts[clip_index : clip_index + count]: | |
| replacement = ( | |
| self.audio_start_token | |
| + self.audio_token * token_count | |
| + self.audio_end_token | |
| ) | |
| texts[index] = texts[index].replace(_PLACEHOLDER, replacement, 1) | |
| clip_index += count | |
| encoded = self.tokenizer(texts, **text_options) | |
| for ids, expected in zip(encoded["input_ids"], expected_counts): | |
| actual = sum(int(token) == self.audio_token_id for token in ids) | |
| if actual != expected: | |
| raise ValueError( | |
| "Audio token count changed during tokenization; do not truncate audio placeholders" | |
| ) | |
| if clips: | |
| if return_tensors == "pt": | |
| audio_inputs["audio_num_clips"] = torch.tensor( | |
| clip_counts, dtype=torch.long | |
| ) | |
| elif return_tensors == "np": | |
| audio_inputs["audio_num_clips"] = np.asarray( | |
| clip_counts, dtype=np.int64 | |
| ) | |
| else: | |
| audio_inputs["audio_num_clips"] = clip_counts | |
| return BatchFeature(data={**encoded, **audio_inputs}) | |
| def apply_transcription_request( | |
| self, audio, *, prompt=None, language_hint=None, | |
| audio_chunk_frames=None, return_tensors="pt", | |
| ): | |
| """Prepare one audio request in the released ASR input format. | |
| ``prompt`` starts with the base ASR instruction and may append topic | |
| and hotwords without a newline. A known language can be prefixed to | |
| the generated answer with ``language_hint='zh'`` or ``'en'``. | |
| """ | |
| if prompt is None: | |
| prompt = _ASR_INSTRUCTION | |
| if not isinstance(prompt, str) or not prompt.startswith(_ASR_INSTRUCTION): | |
| raise ValueError("Prompt must start with the base ASR instruction") | |
| if "\n" in prompt or "\r" in prompt: | |
| raise ValueError("Prompt metadata must be contiguous, without newlines") | |
| if _PLACEHOLDER in prompt: | |
| raise ValueError("Prompt must not contain <AUDIO>; it is appended here") | |
| if audio_chunk_frames is not None: | |
| if (isinstance(audio_chunk_frames, bool) | |
| or not isinstance(audio_chunk_frames, int) | |
| or audio_chunk_frames < 1): | |
| raise ValueError("audio_chunk_frames must be a positive integer") | |
| extractor = self.feature_extractor | |
| original_config = extractor.audio_config | |
| extractor.audio_config = {**original_config, "chunk_frames": audio_chunk_frames} | |
| try: | |
| _ = extractor.spec | |
| inputs = self( | |
| text=prompt + _PLACEHOLDER, audio=audio, | |
| return_tensors=return_tensors, | |
| ) | |
| finally: | |
| extractor.audio_config = original_config | |
| else: | |
| inputs = self( | |
| text=prompt + _PLACEHOLDER, audio=audio, | |
| return_tensors=return_tensors, | |
| ) | |
| if language_hint is not None: | |
| label = {"zh": "Chinese\t", "en": "English\t"}.get(language_hint) | |
| if label is None: | |
| raise ValueError("language_hint must be 'zh', 'en', or None") | |
| if return_tensors != "pt": | |
| raise ValueError("language_hint requires return_tensors='pt'") | |
| label_ids = self.tokenizer( | |
| label, add_special_tokens=False, return_tensors="pt", | |
| )["input_ids"] | |
| if inputs.input_ids.shape[0] != 1: | |
| raise ValueError("language_hint requires exactly one audio request") | |
| inputs["input_ids"] = torch.cat((inputs.input_ids, label_ids), dim=1) | |
| if "attention_mask" in inputs: | |
| inputs["attention_mask"] = torch.cat( | |
| (inputs.attention_mask, torch.ones_like(label_ids)), dim=1, | |
| ) | |
| return inputs | |
| def model_input_names(self): | |
| return [ | |
| "input_ids", | |
| "attention_mask", | |
| "audio_feats", | |
| "audio_feats_lengths", | |
| "audio_num_clips", | |
| ] | |
| MingV3ASRProcessor.register_for_auto_class() | |