From 967045082faaaaf3d653bfe665080fd746b2bb60 Mon Sep 17 00:00:00 2001 From: eustlb <94853470+eustlb@users.noreply.github.com> Date: Fri, 18 Jul 2025 02:02:04 +0200 Subject: [PATCH] Add voxtral (#39429) * draft * draft update (conversion working) * mend * draft update * draft update: working generate * refactor * VoxtralProcessor draft * processor update * update convert_tekken_tokenizer * refactor processor * update convert * make style * better handle prefil * make style * add tests * add mistral_common audio loading * processor update * revert changes * audio utils update * add audio to apply chat template mistral update * voxtral processor update * fix * udpate converstion script * make mistral tokenier from pretrain work from local dir * fix udpates * add integration tests * add batched version * processor docstring * make style * revert convert_tekken_tokenizer changes * revert processing_qwen2.5 changes * add multi-turn test * processor improvements * address review changes * Update src/transformers/tokenization_mistral_common.py Co-authored-by: Julien Denize <40604584+juliendenize@users.noreply.github.com> * update audio utils * nits * integration test update * correct _support * update tests * test update * update integration tests * fix * fix * fix * add test_apply_chat_template_with_audio * add model doc * model doc * nit * doc uptade * nit * processor improvement * ensure default is 3B * nits * make * make * convert modular * update checkpoint * fix test * make * make * autos * make * make * nit * nit * nit --------- Co-authored-by: Julien Denize <40604584+juliendenize@users.noreply.github.com> Co-authored-by: Arthur <48595927+ArthurZucker@users.noreply.github.com> --- docs/source/en/_toctree.yml | 2 + docs/source/en/model_doc/voxtral.md | 300 ++++++++++ src/transformers/audio_utils.py | 90 ++- src/transformers/models/__init__.py | 1 + .../models/auto/configuration_auto.py | 5 + src/transformers/models/auto/modeling_auto.py | 4 + .../models/auto/processing_auto.py | 1 + src/transformers/models/voxtral/__init__.py | 29 + .../models/voxtral/configuration_voxtral.py | 203 +++++++ .../voxtral/convert_voxtral_weights_to_hf.py | 302 ++++++++++ .../models/voxtral/modeling_voxtral.py | 542 ++++++++++++++++++ .../models/voxtral/modular_voxtral.py | 276 +++++++++ .../models/voxtral/processing_voxtral.py | 449 +++++++++++++++ .../tokenization_mistral_common.py | 63 +- tests/models/voxtral/__init__.py | 0 tests/models/voxtral/test_modeling_voxtral.py | 472 +++++++++++++++ tests/test_tokenization_mistral_common.py | 74 ++- 17 files changed, 2806 insertions(+), 7 deletions(-) create mode 100644 docs/source/en/model_doc/voxtral.md create mode 100644 src/transformers/models/voxtral/__init__.py create mode 100644 src/transformers/models/voxtral/configuration_voxtral.py create mode 100644 src/transformers/models/voxtral/convert_voxtral_weights_to_hf.py create mode 100644 src/transformers/models/voxtral/modeling_voxtral.py create mode 100644 src/transformers/models/voxtral/modular_voxtral.py create mode 100644 src/transformers/models/voxtral/processing_voxtral.py create mode 100644 tests/models/voxtral/__init__.py create mode 100644 tests/models/voxtral/test_modeling_voxtral.py diff --git a/docs/source/en/_toctree.yml b/docs/source/en/_toctree.yml index 733db09240..2c43679820 100644 --- a/docs/source/en/_toctree.yml +++ b/docs/source/en/_toctree.yml @@ -1095,6 +1095,8 @@ title: Vision Text Dual Encoder - local: model_doc/visual_bert title: VisualBERT + - local: model_doc/voxtral + title: Voxtral - local: model_doc/xclip title: X-CLIP title: Multimodal models diff --git a/docs/source/en/model_doc/voxtral.md b/docs/source/en/model_doc/voxtral.md new file mode 100644 index 0000000000..365c19b281 --- /dev/null +++ b/docs/source/en/model_doc/voxtral.md @@ -0,0 +1,300 @@ + + +# Voxtral + +Voxtral is an upgrade of [Ministral 3B and Mistral Small 3B](https://mistral.ai/news/ministraux), extending its language capabilities with audio input support. It is designed to handle tasks such as speech transcription, translation, and audio understanding. + +You can read more in Mistral's [realease blog post](https://mistral.ai/news/voxtral). + +The model is available in two checkpoints: +- 3B: [mistralai/Voxtral-Mini-3B-2507](https://huggingface.co/mistralai/Voxtral-Mini-3B-2507) +- 24B: [mistralai/Voxtral-Small-24B-2507](https://huggingface.co/mistralai/Voxtral-Small-24B-2507) + +## Key Features + +Voxtral builds on Ministral-3B by adding audio processing capabilities: + +- **Transcription mode**: Includes a dedicated mode for speech transcription. By default, Voxtral detects the spoken language and transcribes it accordingly. +- **Long-form context**: With a 32k token context window, Voxtral can process up to 30 minutes of audio for transcription or 40 minutes for broader audio understanding. +- **Integrated Q&A and summarization**: Supports querying audio directly and producing structured summaries without relying on separate ASR and language models. +- **Multilingual support**: Automatically detects language and performs well across several widely spoken languages, including English, Spanish, French, Portuguese, Hindi, German, Dutch, and Italian. +- **Function calling via voice**: Can trigger functions or workflows directly from spoken input based on detected user intent. +- **Text capabilities**: Maintains the strong text processing performance of its Ministral-3B foundation. + +## Usage + +Let's first load the model! +```python +from transformers import VoxtralForConditionalGeneration, AutoProcessor +import torch + +device = "cuda" if torch.cuda.is_available() else "cpu" +repo_id = "mistralai/Voxtral-Mini-3B-2507" + +processor = AutoProcessor.from_pretrained(repo_id) +model = VoxtralForConditionalGeneration.from_pretrained(repo_id, torch_dtype=torch.bfloat16, device_map=device) +``` + +### Audio Instruct Mode + +The model supports audio-text instructions, including multi-turn and multi-audio interactions, all processed in batches. + +➡️ audio + text instruction +```python +conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "url": "https://huggingface.co/datasets/eustlb/audio-samples/resolve/main/dude_where_is_my_car.wav", + }, + {"type": "text", "text": "What can you tell me about this audio?"}, + ], + } +] + +inputs = processor.apply_chat_template(conversation) +inputs = inputs.to(device, dtype=torch.bfloat16) + +outputs = model.generate(**inputs, max_new_tokens=500) +decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + +print("\nGenerated response:") +print("=" * 80) +print(decoded_outputs[0]) +print("=" * 80) +``` + +➡️ multi-audio + text instruction +```python +conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/mary_had_lamb.mp3", + }, + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/winning_call.mp3", + }, + {"type": "text", "text": "What sport and what nursery rhyme are referenced?"}, + ], + } +] + +inputs = processor.apply_chat_template(conversation) +inputs = inputs.to(device, dtype=torch.bfloat16) + +outputs = model.generate(**inputs, max_new_tokens=500) +decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + +print("\nGenerated response:") +print("=" * 80) +print(decoded_outputs[0]) +print("=" * 80) +``` + +➡️ multi-turn: +```python +conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3", + }, + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/bcn_weather.mp3", + }, + {"type": "text", "text": "Describe briefly what you can hear."}, + ], + }, + { + "role": "assistant", + "content": "The audio begins with the speaker delivering a farewell address in Chicago, reflecting on his eight years as president and expressing gratitude to the American people. The audio then transitions to a weather report, stating that it was 35 degrees in Barcelona the previous day, but the temperature would drop to minus 20 degrees the following day.", + }, + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/dude_where_is_my_car.wav", + }, + {"type": "text", "text": "Ok, now compare this new audio with the previous one."}, + ], + }, +] + +inputs = processor.apply_chat_template(conversation) +inputs = inputs.to(device, dtype=torch.bfloat16) + +outputs = model.generate(**inputs, max_new_tokens=500) +decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + +print("\nGenerated response:") +print("=" * 80) +print(decoded_outputs[0]) +print("=" * 80) +``` + +➡️ text only: +```python +conversation = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "What if a cyber brain could possibly generate its own ghost, and create a soul all by itself?", + }, + ], + } +] + +inputs = processor.apply_chat_template(conversation) +inputs = inputs.to(device, dtype=torch.bfloat16) + +outputs = model.generate(**inputs, max_new_tokens=500) +decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + +print("\nGenerated response:") +print("=" * 80) +print(decoded_outputs[0]) +print("=" * 80) +``` + +➡️ audio only: +```python +conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/dude_where_is_my_car.wav", + }, + ], + } +] + +inputs = processor.apply_chat_template(conversation) +inputs = inputs.to(device, dtype=torch.bfloat16) + +outputs = model.generate(**inputs, max_new_tokens=500) +decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + +print("\nGenerated response:") +print("=" * 80) +print(decoded_outputs[0]) +print("=" * 80) +``` + +➡️ batched inference! +```python +conversations = [ + [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3", + }, + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/bcn_weather.mp3", + }, + { + "type": "text", + "text": "Who's speaking in the speach and what city's weather is being discussed?", + }, + ], + } + ], + [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/winning_call.mp3", + }, + {"type": "text", "text": "What can you tell me about this audio?"}, + ], + } + ], +] + +inputs = processor.apply_chat_template(conversations) +inputs = inputs.to(device, dtype=torch.bfloat16) + +outputs = model.generate(**inputs, max_new_tokens=500) +decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + +print("\nGenerated responses:") +print("=" * 80) +for decoded_output in decoded_outputs: + print(decoded_output) + print("=" * 80) +``` + +### Transcription Mode + +Use the model to transcribe audio (supports English, Spanish, French, Portuguese, Hindi, German, Dutch, Italian)! + +```python +inputs = processor.apply_transcrition_request(language="en", audio="https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3") +inputs = inputs.to(device, dtype=torch.bfloat16) + +outputs = model.generate(**inputs, max_new_tokens=500) +decoded_outputs = processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + +print("\nGenerated responses:") +print("=" * 80) +for decoded_output in decoded_outputs: + print(decoded_output) + print("=" * 80) +``` + +This model was contributed by [Eustache Le Bihan](https://huggingface.co/eustlb). + +## VoxtralConfig + +[[autodoc]] VoxtralConfig + +## VoxtralEncoderConfig + +[[autodoc]] VoxtralEncoderConfig + +## VoxtralProcessor + +[[autodoc]] VoxtralProcessor + +## VoxtralEncoder + +[[autodoc]] VoxtralEncoder + - forward + +## VoxtralForConditionalGeneration + +[[autodoc]] VoxtralForConditionalGeneration + - forward diff --git a/src/transformers/audio_utils.py b/src/transformers/audio_utils.py index e8684ab702..cb607e3fc9 100644 --- a/src/transformers/audio_utils.py +++ b/src/transformers/audio_utils.py @@ -16,10 +16,12 @@ Audio processing functions to extract features from audio waveforms. This code i and remove unnecessary dependencies. """ +import base64 +import io import os import warnings from io import BytesIO -from typing import Optional, Union +from typing import Any, Optional, Union import numpy as np import requests @@ -27,14 +29,21 @@ import requests from .utils import ( is_librosa_available, is_numpy_array, + is_soundfile_available, is_torch_tensor, requires_backends, ) +if is_soundfile_available(): + import soundfile as sf + if is_librosa_available(): import librosa + # TODO: @eustlb, we actually don't need librosa but soxr is installed with librosa + import soxr + def load_audio(audio: Union[str, np.ndarray], sampling_rate=16000, timeout=None) -> np.ndarray: """ @@ -69,6 +78,85 @@ def load_audio(audio: Union[str, np.ndarray], sampling_rate=16000, timeout=None) return audio +def load_audio_as( + audio: str, + return_format: str, + timeout: Optional[int] = None, + force_mono: bool = False, + sampling_rate: Optional[int] = None, +) -> Union[str, dict[str, Any], io.BytesIO, None]: + """ + Load audio from either a local file path or URL and return in specified format. + + Args: + audio (`str`): Either a local file path or a URL to an audio file + return_format (`str`): Format to return the audio in: + - "base64": Base64 encoded string + - "dict": Dictionary with data and format + - "buffer": BytesIO object + timeout (`int`, *optional*): Timeout for URL requests in seconds + force_mono (`bool`): Whether to convert stereo audio to mono + sampling_rate (`int`, *optional*): If provided, the audio will be resampled to the specified sampling rate. + + Returns: + `Union[str, Dict[str, Any], io.BytesIO, None]`: + - `str`: Base64 encoded audio data (if return_format="base64") + - `dict`: Dictionary with 'data' (base64 encoded audio data) and 'format' keys (if return_format="dict") + - `io.BytesIO`: BytesIO object containing audio data (if return_format="buffer") + """ + # TODO: @eustlb, we actually don't need librosa but soxr is installed with librosa + requires_backends(load_audio_as, ["librosa"]) + + if return_format not in ["base64", "dict", "buffer"]: + raise ValueError(f"Invalid return_format: {return_format}. Must be 'base64', 'dict', or 'buffer'") + + try: + # Load audio bytes from URL or file + audio_bytes = None + if audio.startswith(("http://", "https://")): + response = requests.get(audio, timeout=timeout) + response.raise_for_status() + audio_bytes = response.content + elif os.path.isfile(audio): + with open(audio, "rb") as audio_file: + audio_bytes = audio_file.read() + else: + raise ValueError(f"File not found: {audio}") + + # Process audio data + with io.BytesIO(audio_bytes) as audio_file: + with sf.SoundFile(audio_file) as f: + audio_array = f.read(dtype="float32") + original_sr = f.samplerate + audio_format = f.format + if sampling_rate is not None and sampling_rate != original_sr: + # Resample audio to target sampling rate + audio_array = soxr.resample(audio_array, original_sr, sampling_rate, quality="HQ") + else: + sampling_rate = original_sr + + # Convert to mono if needed + if force_mono and audio_array.ndim != 1: + audio_array = audio_array.mean(axis=1) + + buffer = io.BytesIO() + sf.write(buffer, audio_array, sampling_rate, format=audio_format.upper()) + buffer.seek(0) + + if return_format == "buffer": + return buffer + elif return_format == "base64": + return base64.b64encode(buffer.read()).decode("utf-8") + elif return_format == "dict": + return { + "data": base64.b64encode(buffer.read()).decode("utf-8"), + "format": audio_format.lower(), + } + + except Exception as e: + raise ValueError(f"Error loading audio: {e}") + + AudioInput = Union[ np.ndarray, "torch.Tensor", list[np.ndarray], tuple[np.ndarray], list["torch.Tensor"], tuple["torch.Tensor"] # noqa: F821 ] diff --git a/src/transformers/models/__init__.py b/src/transformers/models/__init__.py index 4dcc2150ec..ec8c4dfc8f 100644 --- a/src/transformers/models/__init__.py +++ b/src/transformers/models/__init__.py @@ -335,6 +335,7 @@ if TYPE_CHECKING: from .vits import * from .vivit import * from .vjepa2 import * + from .voxtral import * from .wav2vec2 import * from .wav2vec2_bert import * from .wav2vec2_conformer import * diff --git a/src/transformers/models/auto/configuration_auto.py b/src/transformers/models/auto/configuration_auto.py index e3bd2b253f..e97217ab2f 100644 --- a/src/transformers/models/auto/configuration_auto.py +++ b/src/transformers/models/auto/configuration_auto.py @@ -389,6 +389,8 @@ CONFIG_MAPPING_NAMES = OrderedDict[str, str]( ("vits", "VitsConfig"), ("vivit", "VivitConfig"), ("vjepa2", "VJEPA2Config"), + ("voxtral", "VoxtralConfig"), + ("voxtral_encoder", "VoxtralEncoderConfig"), ("wav2vec2", "Wav2Vec2Config"), ("wav2vec2-bert", "Wav2Vec2BertConfig"), ("wav2vec2-conformer", "Wav2Vec2ConformerConfig"), @@ -798,6 +800,8 @@ MODEL_NAMES_MAPPING = OrderedDict[str, str]( ("vits", "VITS"), ("vivit", "ViViT"), ("vjepa2", "VJEPA2Model"), + ("voxtral", "Voxtral"), + ("voxtral_encoder", "Voxtral Encoder"), ("wav2vec2", "Wav2Vec2"), ("wav2vec2-bert", "Wav2Vec2-BERT"), ("wav2vec2-conformer", "Wav2Vec2-Conformer"), @@ -864,6 +868,7 @@ SPECIAL_MODEL_TYPE_TO_MODULE_NAME = OrderedDict[str, str]( ("xclip", "x_clip"), ("clip_vision_model", "clip"), ("qwen2_audio_encoder", "qwen2_audio"), + ("voxtral_encoder", "voxtral"), ("clip_text_model", "clip"), ("aria_text", "aria"), ("gemma3_text", "gemma3"), diff --git a/src/transformers/models/auto/modeling_auto.py b/src/transformers/models/auto/modeling_auto.py index dcf80c3d9f..7e43e68c9a 100644 --- a/src/transformers/models/auto/modeling_auto.py +++ b/src/transformers/models/auto/modeling_auto.py @@ -359,6 +359,8 @@ MODEL_MAPPING_NAMES = OrderedDict( ("vits", "VitsModel"), ("vivit", "VivitModel"), ("vjepa2", "VJEPA2Model"), + ("voxtral", "VoxtralForConditionalGeneration"), + ("voxtral_encoder", "VoxtralEncoder"), ("wav2vec2", "Wav2Vec2Model"), ("wav2vec2-bert", "Wav2Vec2BertModel"), ("wav2vec2-conformer", "Wav2Vec2ConformerModel"), @@ -458,6 +460,7 @@ MODEL_FOR_PRETRAINING_MAPPING_NAMES = OrderedDict( ("vipllava", "VipLlavaForConditionalGeneration"), ("visual_bert", "VisualBertForPreTraining"), ("vit_mae", "ViTMAEForPreTraining"), + ("voxtral", "VoxtralForConditionalGeneration"), ("wav2vec2", "Wav2Vec2ForPreTraining"), ("wav2vec2-conformer", "Wav2Vec2ConformerForPreTraining"), ("xlm", "XLMWithLMHeadModel"), @@ -1078,6 +1081,7 @@ MODEL_FOR_SEQ_TO_SEQ_CAUSAL_LM_MAPPING_NAMES = OrderedDict( ("t5", "T5ForConditionalGeneration"), ("t5gemma", "T5GemmaForConditionalGeneration"), ("umt5", "UMT5ForConditionalGeneration"), + ("voxtral", "VoxtralForConditionalGeneration"), ("xlm-prophetnet", "XLMProphetNetForConditionalGeneration"), ] ) diff --git a/src/transformers/models/auto/processing_auto.py b/src/transformers/models/auto/processing_auto.py index 2ead4a1800..5b73e1a137 100644 --- a/src/transformers/models/auto/processing_auto.py +++ b/src/transformers/models/auto/processing_auto.py @@ -132,6 +132,7 @@ PROCESSOR_MAPPING_NAMES = OrderedDict( ("vilt", "ViltProcessor"), ("vipllava", "LlavaProcessor"), ("vision-text-dual-encoder", "VisionTextDualEncoderProcessor"), + ("voxtral", "VoxtralProcessor"), ("wav2vec2", "Wav2Vec2Processor"), ("wav2vec2-bert", "Wav2Vec2Processor"), ("wav2vec2-conformer", "Wav2Vec2Processor"), diff --git a/src/transformers/models/voxtral/__init__.py b/src/transformers/models/voxtral/__init__.py new file mode 100644 index 0000000000..fa7fc1b411 --- /dev/null +++ b/src/transformers/models/voxtral/__init__.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# Copyright 2025 The HuggingFace Inc. team. 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 of the License 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. +# See the License for the specific language governing permissions and +# limitations under the License. +from typing import TYPE_CHECKING + +from ...utils import _LazyModule +from ...utils.import_utils import define_import_structure + + +if TYPE_CHECKING: + from .configuration_voxtral import * + from .modeling_voxtral import * + from .processing_voxtral import * +else: + import sys + + _file = globals()["__file__"] + sys.modules[__name__] = _LazyModule(__name__, _file, define_import_structure(_file), module_spec=__spec__) diff --git a/src/transformers/models/voxtral/configuration_voxtral.py b/src/transformers/models/voxtral/configuration_voxtral.py new file mode 100644 index 0000000000..72c986da37 --- /dev/null +++ b/src/transformers/models/voxtral/configuration_voxtral.py @@ -0,0 +1,203 @@ +# coding=utf-8 +# Copyright 2025 The HuggingFace Inc. team. 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 of the License 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. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ...configuration_utils import PretrainedConfig +from ..auto import CONFIG_MAPPING, AutoConfig + + +class VoxtralEncoderConfig(PretrainedConfig): + r""" + This is the configuration class to store the configuration of a [`VoxtralEncoder`]. It is used to instantiate a + Voxtral audio encoder according to the specified arguments, defining the model architecture. Instantiating a + configuration with the defaults will yield a similar configuration to that of the audio encoder of the Voxtral + architecture. + + e.g. [mistralai/Voxtral-Mini-3B-2507](https://huggingface.co/mistralai/Voxtral-Mini-3B-2507) + + Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the + documentation from [`PretrainedConfig`] for more information. + + Args: + vocab_size (`int`, *optional*, defaults to 51866): + Vocabulary size of the model. + hidden_size (`int`, *optional*, defaults to 1280): + Dimensionality of the hidden representations. + intermediate_size (`int`, *optional*, defaults to 5120): + Dimension of the MLP representations. + num_hidden_layers (`int`, *optional*, defaults to 32): + Number of hidden layers in the Transformer encoder. + num_attention_heads (`int`, *optional*, defaults to 20): + Number of attention heads for each attention layer in the Transformer encoder. + scale_embedding (`bool`, *optional*, defaults to `False`): + Scale embeddings by dividing by sqrt(hidden_size) if True. + activation_function (`str`, *optional*, defaults to `"gelu"`): + The non-linear activation function (function or string) in the encoder and pooler. If string, "gelu", + num_mel_bins (`int`, *optional*, defaults to 128): + Number of mel features used per input features. Should correspond to the value used in the + `VoxtralProcessor` class. + max_source_positions (`int`, *optional*, defaults to 1500): + The maximum sequence length of log-mel filter-bank features that this model might ever be used with. + initializer_range (`float`, *optional*, defaults to 0.02): + The standard deviation of the truncated_normal_initializer for initializing all weight matrices. + attention_dropout (`float`, *optional*, defaults to 0.0): + The dropout ratio for the attention probabilities. + + ```python + >>> from transformers import VoxtralEncoderConfig, VoxtralEncoder + + >>> # Initializing a VoxtralEncoderConfig + >>> configuration = VoxtralEncoderConfig() + + >>> # Initializing a VoxtralEncoder (with random weights) + >>> model = VoxtralEncoder(configuration) + + >>> # Accessing the model configuration + >>> configuration = model.config + ```""" + + model_type = "voxtral_encoder" + + attribute_map = { + "d_model": "hidden_size", + "encoder_layers": "num_hidden_layers", + "encoder_attention_heads": "num_attention_heads", + "encoder_ffn_dim": "intermediate_size", + "encoder_layerdrop": "layerdrop", + } + + def __init__( + self, + vocab_size=51866, + hidden_size=1280, + intermediate_size=5120, + num_hidden_layers=32, + num_attention_heads=20, + scale_embedding=False, + activation_function="gelu", + num_mel_bins=128, + max_source_positions=1500, + initializer_range=0.02, + attention_dropout=0.0, + **kwargs, + ): + super().__init__(**kwargs) + + self.vocab_size = vocab_size + self.hidden_size = hidden_size + self.intermediate_size = intermediate_size + self.num_hidden_layers = num_hidden_layers + + self.num_attention_heads = num_attention_heads + self.scale_embedding = scale_embedding # scale factor will be sqrt(hidden_size) if True + self.activation_function = activation_function + self.num_mel_bins = num_mel_bins + self.max_source_positions = max_source_positions + self.initializer_range = initializer_range + + # TODO: @eustlb, we do not use dropout and layerdrop, yet we need to hardcode them + # to be able to use Whisper with modular (here actually from Qwen2-Audio and copied from). + # After a future Whisper refactor, we should remove this. + self.dropout = 0.0 + self.layerdrop = 0.0 + self.activation_dropout = 0.0 + + self.attention_dropout = attention_dropout + + +class VoxtralConfig(PretrainedConfig): + r""" + This is the configuration class to store the configuration of a [`VoxtralForConditionalGeneration`]. It is used to instantiate an + Voxtral model according to the specified arguments, defining the model architecture. Instantiating a configuration + with the defaults will yield a similar configuration to that of the Voxtral-Mini-3B. + + e.g. [mistralai/Voxtral-Mini-3B-2507](https://huggingface.co/mistralai/Voxtral-Mini-3B-2507) + + Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the + documentation from [`PretrainedConfig`] for more information. + + Args: + audio_config (`Union[AutoConfig, dict]`, *optional*): + The config object or dictionary of the audio encoder. + text_config (`Union[AutoConfig, dict]`, *optional*): + The config object or dictionary of the text model. + audio_token_id (`int`, *optional*): + The image token index to encode the image prompt. + projector_hidden_act (`str`, *optional*, defaults to `"gelu"`): + The activation function (function or string) in the multi-modal projector. + + ```python + >>> from transformers import VoxtralForConditionalGeneration, VoxtralConfig + + >>> # Initializing a Voxtral configuration + >>> configuration = VoxtralConfig(audio_token_id=24, projector_hidden_act="gelu") + + >>> # Initializing a 3B model with random weights + >>> model = VoxtralForConditionalGeneration(configuration) + + >>> # Accessing the model configuration + >>> configuration = model.config + ```""" + + model_type = "voxtral" + sub_configs = {"text_config": AutoConfig, "audio_config": AutoConfig} + + _default_text_config_kwargs = { + "vocab_size": 131072, + "hidden_size": 3072, + "intermediate_size": 8192, + "num_hidden_layers": 30, + "num_key_value_heads": 8, + "max_position_embeddings": 131072, + "rms_norm_eps": 1e-05, + "use_cache": True, + "rope_theta": 100000000.0, + "head_dim": 128, + } + + def __init__( + self, + audio_config=None, + text_config=None, + audio_token_id=None, + projector_hidden_act="gelu", + **kwargs, + ): + if isinstance(audio_config, dict): + audio_config["model_type"] = ( + audio_config["model_type"] if "model_type" in audio_config else "voxtral_encoder" + ) + audio_config = CONFIG_MAPPING[audio_config["model_type"]](**audio_config) + elif audio_config is None: + audio_config = CONFIG_MAPPING["voxtral_encoder"]() + self.audio_config = audio_config + + if isinstance(text_config, dict): + text_config["model_type"] = text_config["model_type"] if "model_type" in text_config else "llama" + text_config = CONFIG_MAPPING[text_config["model_type"]]( + **{**self._default_text_config_kwargs, **text_config} + ) + elif text_config is None: + text_config = CONFIG_MAPPING["llama"](**self._default_text_config_kwargs) + self.text_config = text_config + + self.vocab_size = text_config.vocab_size + self.hidden_size = text_config.hidden_size + self.audio_token_id = audio_token_id + self.projector_hidden_act = projector_hidden_act + + super().__init__(**kwargs) + + +__all__ = ["VoxtralEncoderConfig", "VoxtralConfig"] diff --git a/src/transformers/models/voxtral/convert_voxtral_weights_to_hf.py b/src/transformers/models/voxtral/convert_voxtral_weights_to_hf.py new file mode 100644 index 0000000000..b2bc8f0162 --- /dev/null +++ b/src/transformers/models/voxtral/convert_voxtral_weights_to_hf.py @@ -0,0 +1,302 @@ +# coding=utf-8 +# Copyright 2025 HuggingFace Inc. team. 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 of the License 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. +# See the License for the specific language governing permissions and +# limitations under the License. +import argparse +import gc +import json +import os +import re + +import torch +from safetensors.torch import load_file + +from transformers import ( + MistralCommonTokenizer, + VoxtralConfig, + VoxtralForConditionalGeneration, + VoxtralProcessor, + WhisperFeatureExtractor, +) +from transformers.models.whisper.modeling_whisper import sinusoids +from transformers.utils.hub import cached_file + + +# fmt: off +STATE_DICT_MAPPING = { + # Text model keys + r"^output.weight": r"language_model.lm_head.weight", + r"^norm.weight": r"language_model.model.norm.weight", + r"^tok_embeddings.weight": r"language_model.model.embed_tokens.weight", + r"^layers.(\d+).attention_norm.weight": r"language_model.model.layers.\1.input_layernorm.weight", + r"^layers.(\d+).ffn_norm.weight": r"language_model.model.layers.\1.post_attention_layernorm.weight", + r"^layers.(\d+).attention.w(q|k|v|o).weight": r"language_model.model.layers.\1.self_attn.\2_proj.weight", + r"^layers.(\d+).feed_forward.w1.weight": r"language_model.model.layers.\1.mlp.gate_proj.weight", + r"^layers.(\d+).feed_forward.w2.weight": r"language_model.model.layers.\1.mlp.down_proj.weight", + r"^layers.(\d+).feed_forward.w3.weight": r"language_model.model.layers.\1.mlp.up_proj.weight", + + r"mm_whisper_embeddings.tok_embeddings.weight": r"language_model.model.embed_tokens.weight", + + # audio model keys + r"mm_whisper_embeddings.whisper_encoder\.conv_layers\.0\.(weight|bias)": r"audio_tower.conv1.\1", + r"mm_whisper_embeddings.whisper_encoder\.conv_layers\.1\.(weight|bias)": r"audio_tower.conv2.\1", + + r"mm_whisper_embeddings.whisper_encoder\.transformer\.norm\.(weight|bias)": r"audio_tower.layer_norm.\1", + + r"mm_whisper_embeddings.whisper_encoder\.transformer\.layers\.(\d+)\.attention\.w([qkv])\.(weight|bias)": r"audio_tower.layers.\1.self_attn.\2_proj.\3", + r"mm_whisper_embeddings.whisper_encoder\.transformer\.layers\.(\d+)\.attention\.wo\.(weight|bias)": r"audio_tower.layers.\1.self_attn.out_proj.\2", + r"mm_whisper_embeddings.whisper_encoder\.transformer\.layers\.(\d+)\.attention_norm\.(weight|bias)": r"audio_tower.layers.\1.self_attn_layer_norm.\2", + + r"mm_whisper_embeddings.whisper_encoder\.transformer\.layers\.(\d+)\.feed_forward\.w1\.(weight|bias)": r"audio_tower.layers.\1.fc1.\2", + r"mm_whisper_embeddings.whisper_encoder\.transformer\.layers\.(\d+)\.feed_forward\.w2\.(weight|bias)": r"audio_tower.layers.\1.fc2.\2", + + r"mm_whisper_embeddings.whisper_encoder\.transformer\.layers\.(\d+)\.ffn_norm\.(weight|bias)": r"audio_tower.layers.\1.final_layer_norm.\2", + + r"mm_whisper_embeddings.audio_language_projection\.0\.weight": r"multi_modal_projector.linear_1.weight", + r"mm_whisper_embeddings.audio_language_projection\.2\.weight": r"multi_modal_projector.linear_2.weight", +} +# fmt: on + + +def convert_config(original_config: dict, max_position_embeddings: int = 131072): + original_audio_config = original_config.pop("multimodal") + original_audio_config = original_audio_config["whisper_model_args"]["encoder_args"] + original_text_config = original_config + + # Text config + text_key_mapping = { + "hidden_size": "dim", + "num_hidden_layers": "n_layers", + "intermediate_size": "hidden_dim", + "num_attention_heads": "n_heads", + "num_key_value_heads": "n_kv_heads", + "rms_norm_eps": "norm_eps", + } + similar_text_keys_to_keep = [ + "head_dim", + "vocab_size", + "rope_theta", + ] + new_text_config_kwargs = {k: original_text_config[v] for k, v in text_key_mapping.items()} + new_text_config_kwargs.update({k: v for k, v in original_text_config.items() if k in similar_text_keys_to_keep}) + # These are not always defined depending on `params.json` + new_text_config_kwargs["sliding_window"] = original_text_config.get("sliding_window", None) + new_text_config_kwargs["max_position_embeddings"] = original_text_config.get( + "max_seq_len", max_position_embeddings + ) + # This may sometimes be a string in `params.json` + if new_text_config_kwargs["sliding_window"] is not None: + new_text_config_kwargs["sliding_window"] = int(new_text_config_kwargs["sliding_window"]) + + # Audio config + audio_key_mapping = { + "hidden_size": "dim", + "num_hidden_layers": "n_layers", + "intermediate_size": "hidden_dim", + "num_attention_heads": "n_heads", + "num_key_value_heads": "n_heads", + } + similar_audio_keys_to_keep = [ + "head_dim", + "vocab_size", + ] + new_audio_config_kwargs = {k: original_audio_config[v] for k, v in audio_key_mapping.items()} + new_audio_config_kwargs.update({k: v for k, v in original_audio_config.items() if k in similar_audio_keys_to_keep}) + + new_config = VoxtralConfig( + audio_config=new_audio_config_kwargs, + text_config=new_text_config_kwargs, + audio_token_id=24, + projector_hidden_act="gelu", + ) + + return new_config + + +def map_old_key_to_new(old_key): + """Map of a key of the original state dict to the equivalent key in HF format""" + for pattern, replacement in STATE_DICT_MAPPING.items(): + new_key, n_replace = re.subn(pattern, replacement, old_key) + # Early exit of the loop + if n_replace > 0: + return new_key + + raise ValueError(f"Key: {old_key} could not be mapped (check the mapping).") + + +def permute_for_rope(tensor, n_heads, dim1, dim2): + """Permute the weights for the ROPE formulation.""" + tensor = tensor.view(n_heads, dim1 // n_heads // 2, 2, dim2) + tensor = tensor.transpose(1, 2) + tensor = tensor.reshape(dim1, dim2) + return tensor + + +def convert_state_dict(original_state_dict, config): + """Convert a state dict file, when a single `nn.Module` is never sharded in different files (usual case).""" + new_dict = {} + + num_attention_heads = config.num_attention_heads + hidden_size = config.hidden_size + head_dim = config.head_dim + num_key_value_heads = config.num_key_value_heads + key_value_dim = head_dim * num_key_value_heads + query_dim = head_dim * num_attention_heads + + for old_key, tensor in original_state_dict.items(): + new_key = map_old_key_to_new(old_key) + + if "audio_tower" not in new_key: + if "q_proj" in new_key: + tensor = tensor.view(num_attention_heads, head_dim, hidden_size).reshape(query_dim, hidden_size) + tensor = permute_for_rope(tensor, num_attention_heads, query_dim, hidden_size) + elif "k_proj" in new_key: + tensor = tensor.view(num_key_value_heads, head_dim, hidden_size).reshape(key_value_dim, hidden_size) + tensor = permute_for_rope(tensor, num_key_value_heads, key_value_dim, hidden_size) + elif "v_proj" in new_key: + tensor = tensor.view(num_key_value_heads, head_dim, hidden_size).reshape(key_value_dim, hidden_size) + + new_dict[new_key] = tensor + return new_dict + + +def write_model( + input_path_or_repo, + model_name, + config_name, + output_dir, + safe_serialization=True, +): + print("Converting the model.") + os.makedirs(output_dir, exist_ok=True) + + # -------------- + # convert config + # -------------- + + config_path = cached_file(input_path_or_repo, config_name) + with open(config_path, "r") as f: + original_config = json.load(f) + + config = convert_config(original_config) + model = VoxtralForConditionalGeneration(config) + + # --------------- + # convert weights + # --------------- + + model_path = cached_file(input_path_or_repo, model_name) + print(f"Fetching all parameters from the checkpoint at {model_path}...") + state_dict = load_file(model_path) + print("Converting model...") + converted_state_dict = convert_state_dict(state_dict, config.text_config) + + # we need to add embed positions as they are not in the state dict + with torch.no_grad(), torch.device("cuda"): + # TODO: @eustlb, we are here creating on GPU + # vllm initalizes on device, while we save in state dict + embed_positions_weight = sinusoids(config.audio_config.max_source_positions, config.audio_config.hidden_size) + converted_state_dict["audio_tower.embed_positions.weight"] = embed_positions_weight.cpu() + + # ------------------------- + # load the weights and save + # ------------------------- + + print("Loading the checkpoint in a Voxtral model.") + with torch.device("meta"): + model = VoxtralForConditionalGeneration(config) + model.load_state_dict(converted_state_dict, strict=True, assign=True) + print("Checkpoint loaded successfully.") + del model.config._name_or_path + + del model.generation_config._from_model_config + model.generation_config.pad_token_id = 11 + + print("Saving the model.") + model.save_pretrained(output_dir, safe_serialization=safe_serialization) + del state_dict, model + + # Safety check: reload the converted model + gc.collect() + print("Reloading the model to check if it's saved correctly.") + VoxtralForConditionalGeneration.from_pretrained(output_dir, torch_dtype=torch.bfloat16, device_map="auto") + print("Model reloaded successfully.") + + +def write_processor(input_path_or_repo: str, feature_extractor_path_or_repo: str, output_dir: str): + tokenizer = MistralCommonTokenizer.from_pretrained(input_path_or_repo) + feature_extractor = WhisperFeatureExtractor.from_pretrained(feature_extractor_path_or_repo) + + print("Creating the processor...") + # Create the processor and save it + processor = VoxtralProcessor( + feature_extractor=feature_extractor, + tokenizer=tokenizer, + ) + processor.save_pretrained(output_dir) + print("Processor saved successfully.") + + +def main(): + parser = argparse.ArgumentParser(description="Convert Voxtral weights to Hugging Face format") + parser.add_argument( + "--input_path_or_repo", + type=str, + required=True, + help="Path or repo containing Csm weights", + ) + parser.add_argument( + "--model_name", + type=str, + required=True, + help="Name of the model in input_path_or_repo", + ) + parser.add_argument( + "--config_name", + type=str, + required=True, + help="Name of the config in input_path_or_repo", + ) + parser.add_argument( + "--feature_extractor_path_or_repo", + type=str, + required=True, + help="Path or repo containing the feature extractor", + ) + parser.add_argument( + "--output_dir", + help="Location to write HF model and tokenizer", + ) + parser.add_argument( + "--safe_serialization", action="store_true", default=True, help="Whether or not to save using `safetensors`." + ) + args = parser.parse_args() + + write_model( + args.input_path_or_repo, + args.model_name, + args.config_name, + args.output_dir, + safe_serialization=args.safe_serialization, + ) + + write_processor( + args.input_path_or_repo, + args.feature_extractor_path_or_repo, + args.output_dir, + ) + + +if __name__ == "__main__": + main() diff --git a/src/transformers/models/voxtral/modeling_voxtral.py b/src/transformers/models/voxtral/modeling_voxtral.py new file mode 100644 index 0000000000..b2350310a8 --- /dev/null +++ b/src/transformers/models/voxtral/modeling_voxtral.py @@ -0,0 +1,542 @@ +# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 +# This file was automatically generated from src/transformers/models/voxtral/modular_voxtral.py. +# Do NOT edit this file manually as any edits will be overwritten by the generation of +# the file from the modular. If any change should be done, please apply the change to the +# modular_voxtral.py file directly. One of our CI enforces this. +# 🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨🚨 +# coding=utf-8 +# Copyright 2025 The HuggingFace Inc. team. 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 of the License 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. +# See the License for the specific language governing permissions and +# limitations under the License. + +import math +from typing import Callable, Optional, Union + +import torch +from torch import nn + +from ...activations import ACT2FN +from ...cache_utils import Cache +from ...generation import GenerationMixin +from ...modeling_layers import GradientCheckpointingLayer +from ...modeling_outputs import BaseModelOutput, BaseModelOutputWithPast, CausalLMOutputWithPast +from ...modeling_utils import ALL_ATTENTION_FUNCTIONS, PreTrainedModel +from ...processing_utils import Unpack +from ...utils import TransformersKwargs, auto_docstring, can_return_tuple, logging +from ...utils.generic import check_model_inputs +from ..auto import AutoModel, AutoModelForCausalLM +from .configuration_voxtral import VoxtralConfig, VoxtralEncoderConfig + + +logger = logging.get_logger(__name__) + + +def eager_attention_forward( + module: nn.Module, + query: torch.Tensor, + key: torch.Tensor, + value: torch.Tensor, + attention_mask: Optional[torch.Tensor], + scaling: Optional[float] = None, + dropout: float = 0.0, + head_mask: Optional[torch.Tensor] = None, + **kwargs, +): + if scaling is None: + scaling = query.size(-1) ** -0.5 + + attn_weights = torch.matmul(query, key.transpose(2, 3)) * scaling + if attention_mask is not None and attention_mask.ndim == 4: + attn_weights = attn_weights + attention_mask[:, :, :, : key.shape[-2]] + + attn_weights = nn.functional.softmax(attn_weights, dim=-1) + + if head_mask is not None: + attn_weights = attn_weights * head_mask.view(1, -1, 1, 1) + + attn_weights = nn.functional.dropout(attn_weights, p=dropout, training=module.training) + attn_output = torch.matmul(attn_weights, value) + attn_output = attn_output.transpose(1, 2).contiguous() + + return attn_output, attn_weights + + +class VoxtralAttention(nn.Module): + """Multi-headed attention from 'Attention Is All You Need' paper""" + + def __init__( + self, + embed_dim: int, + num_heads: int, + dropout: float = 0.0, + is_decoder: bool = False, + bias: bool = True, + is_causal: bool = False, + layer_idx: Optional[int] = None, + config: Optional[VoxtralConfig] = None, + ): + super().__init__() + self.embed_dim = embed_dim + self.num_heads = num_heads + self.dropout = dropout + self.head_dim = embed_dim // num_heads + self.config = config + + if (self.head_dim * num_heads) != self.embed_dim: + raise ValueError( + f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim}" + f" and `num_heads`: {num_heads})." + ) + self.scaling = self.head_dim**-0.5 + self.is_decoder = is_decoder + self.is_causal = is_causal + + if layer_idx is None and is_decoder: + logger.warning_once( + f"Instantiating a decoder {self.__class__.__name__} without passing `layer_idx` is not recommended and " + "will to errors during the forward call, if caching is used. Please make sure to provide a `layer_idx` " + "when creating this class." + ) + self.layer_idx = layer_idx + + self.k_proj = nn.Linear(embed_dim, embed_dim, bias=False) + self.v_proj = nn.Linear(embed_dim, embed_dim, bias=bias) + self.q_proj = nn.Linear(embed_dim, embed_dim, bias=bias) + self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias) + + def _shape(self, tensor: torch.Tensor, seq_len: int, bsz: int): + return tensor.view(bsz, seq_len, self.num_heads, self.head_dim).transpose(1, 2).contiguous() + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: Optional[torch.Tensor] = None, + layer_head_mask: Optional[torch.Tensor] = None, + output_attentions: bool = False, + **kwargs, + ) -> tuple[torch.Tensor, Optional[torch.Tensor], Optional[tuple[torch.Tensor]]]: + """Input shape: Batch x Time x Channel""" + + bsz, tgt_len, _ = hidden_states.size() + + # Scaling is susceptible to floating point arithmetics' inprecisions + # which can lead to different results (this is dependent from model + # to model, e.g. whisper is one such case). We therefore keep the + # original order of scaling to follow the original implementation + # and enforce no scaling (1.0) in the attention call below. + query_states = self._shape(self.q_proj(hidden_states) * self.scaling, tgt_len, bsz) + key_states = self._shape(self.k_proj(hidden_states), -1, bsz) + value_states = self._shape(self.v_proj(hidden_states), -1, bsz) + + attention_interface: Callable = eager_attention_forward + if self.config._attn_implementation != "eager": + attention_interface = ALL_ATTENTION_FUNCTIONS[self.config._attn_implementation] + + attn_output, attn_weights = attention_interface( + self, + query_states, + key_states, + value_states, + attention_mask, + dropout=0.0 if not self.training else self.dropout, + scaling=1.0, + output_attentions=output_attentions, + head_mask=layer_head_mask, + **kwargs, + ) + + attn_output = attn_output.reshape(bsz, tgt_len, -1).contiguous() + attn_output = self.out_proj(attn_output) + + return attn_output, attn_weights + + +class VoxtralEncoderLayer(GradientCheckpointingLayer): + def __init__(self, config: VoxtralConfig): + super().__init__() + self.embed_dim = config.d_model + + self.self_attn = VoxtralAttention( + embed_dim=self.embed_dim, + num_heads=config.encoder_attention_heads, + dropout=config.attention_dropout, + config=config, + ) + self.self_attn_layer_norm = nn.LayerNorm(self.embed_dim) + self.dropout = config.dropout + self.activation_fn = ACT2FN[config.activation_function] + self.activation_dropout = config.activation_dropout + self.fc1 = nn.Linear(self.embed_dim, config.encoder_ffn_dim) + self.fc2 = nn.Linear(config.encoder_ffn_dim, self.embed_dim) + self.final_layer_norm = nn.LayerNorm(self.embed_dim) + + def forward( + self, + hidden_states: torch.Tensor, + attention_mask: torch.Tensor, + layer_head_mask: torch.Tensor, + output_attentions: bool = False, + ) -> torch.Tensor: + """ + Args: + hidden_states (`torch.FloatTensor`): input to the layer of shape `(batch, seq_len, embed_dim)` + attention_mask (`torch.FloatTensor`): attention mask of size + `(batch, 1, tgt_len, src_len)` where padding elements are indicated by very large negative values. + layer_head_mask (`torch.FloatTensor`): mask for attention heads in a given layer of size + `(encoder_attention_heads,)`. + output_attentions (`bool`, *optional*): + Whether or not to return the attentions tensors of all attention layers. See `attentions` under + returned tensors for more detail. + """ + residual = hidden_states + hidden_states = self.self_attn_layer_norm(hidden_states) + hidden_states, attn_weights = self.self_attn( + hidden_states=hidden_states, + attention_mask=attention_mask, + layer_head_mask=layer_head_mask, + output_attentions=output_attentions, + ) + hidden_states = nn.functional.dropout(hidden_states, p=self.dropout, training=self.training) + hidden_states = residual + hidden_states + + residual = hidden_states + hidden_states = self.final_layer_norm(hidden_states) + hidden_states = self.activation_fn(self.fc1(hidden_states)) + hidden_states = nn.functional.dropout(hidden_states, p=self.activation_dropout, training=self.training) + hidden_states = self.fc2(hidden_states) + hidden_states = nn.functional.dropout(hidden_states, p=self.dropout, training=self.training) + hidden_states = residual + hidden_states + + if hidden_states.dtype == torch.float16: + clamp_value = torch.finfo(hidden_states.dtype).max - 1000 + hidden_states = torch.clamp(hidden_states, min=-clamp_value, max=clamp_value) + + return hidden_states, attn_weights + + +@auto_docstring +class VoxtralPreTrainedModel(PreTrainedModel): + config: VoxtralConfig + base_model_prefix = "model" + supports_gradient_checkpointing = True + _no_split_modules = ["VoxtralAttention"] + _skip_keys_device_placement = "past_key_values" + _supports_flash_attn = True + _supports_sdpa = True + _supports_flex_attn = True + _supports_cache_class = True + _supports_attention_backend = True + _supports_static_cache = True + + def _init_weights(self, module): + # important: this ported version of Voxtral isn't meant for training from scratch - only + # inference and fine-tuning - so the proper init weights code has been removed + std = ( + self.config.initializer_range + if hasattr(self.config, "initializer_range") + else self.config.audio_config.initializer_range + ) + + if isinstance(module, (nn.Linear, nn.Conv1d)): + module.weight.data.normal_(mean=0.0, std=std) + if module.bias is not None: + module.bias.data.zero_() + elif isinstance(module, nn.LayerNorm): + module.weight.data.fill_(1.0) + module.bias.data.zero_() + elif isinstance(module, nn.Embedding): + module.weight.data.normal_(mean=0.0, std=std) + if module.padding_idx is not None: + module.weight.data[module.padding_idx].zero_() + + +@auto_docstring( + custom_intro=""" + The Voxtral encoder, which is a Whisper encoder. + """ +) +class VoxtralEncoder(VoxtralPreTrainedModel): + """ + Transformer encoder consisting of *config.encoder_layers* self attention layers. Each layer is a + [`VoxtralEncoderLayer`]. + + Args: + config: VoxtralEncoderConfig + """ + + # Ignore copy + config: VoxtralEncoderConfig + main_input_name = "input_features" + _no_split_modules = ["VoxtralEncoderLayer"] + _can_record_outputs = { + "attentions": VoxtralAttention, + "hidden_states": VoxtralEncoderLayer, + } + + def __init__(self, config: VoxtralEncoderConfig): + super().__init__(config) + self.dropout = config.dropout + self.layerdrop = config.encoder_layerdrop + + embed_dim = config.d_model + self.num_mel_bins = config.num_mel_bins + self.padding_idx = config.pad_token_id + self.max_source_positions = config.max_source_positions + self.embed_scale = math.sqrt(embed_dim) if config.scale_embedding else 1.0 + + self.conv1 = nn.Conv1d(self.num_mel_bins, embed_dim, kernel_size=3, padding=1) + self.conv2 = nn.Conv1d(embed_dim, embed_dim, kernel_size=3, stride=2, padding=1) + + self.embed_positions = nn.Embedding(self.max_source_positions, embed_dim) + self.embed_positions.requires_grad_(False) + + self.layers = nn.ModuleList([VoxtralEncoderLayer(config) for _ in range(config.encoder_layers)]) + self.layer_norm = nn.LayerNorm(config.d_model) + # Ignore copy + self.avg_pooler = nn.AvgPool1d(2, stride=2) + + self.gradient_checkpointing = False + # Initialize weights and apply final processing + self.post_init() + + def _freeze_parameters(self): + for param in self.parameters(): + param.requires_grad = False + self._requires_grad = False + + def get_input_embeddings(self) -> nn.Module: + return self.conv1 + + def set_input_embeddings(self, value: nn.Module): + self.conv1 = value + + @check_model_inputs + def forward( + self, + input_features, + attention_mask=None, + **kwargs: Unpack[TransformersKwargs], + ): + r""" + Args: + input_features (`torch.LongTensor` of shape `(batch_size, feature_size, sequence_length)`): + Float values of mel features extracted from the raw speech waveform. Raw speech waveform can be + obtained by loading a `.flac` or `.wav` audio file into an array of type `list[float]` or a + `numpy.ndarray`, *e.g.* via the soundfile library (`pip install soundfile`). To prepare the array into + `input_features`, the [`AutoFeatureExtractor`] should be used for extracting the mel features, padding + and conversion into a tensor of type `torch.FloatTensor`. See [`~WhisperFeatureExtractor.__call__`] + attention_mask (`torch.Tensor`)`, *optional*): + Voxtral does not support masking of the `input_features`, this argument is preserved for compatibility, + but it is not used. By default the silence in the input log mel spectrogram are ignored. + """ + expected_seq_length = self.config.max_source_positions * self.conv1.stride[0] * self.conv2.stride[0] + if input_features.shape[-1] != expected_seq_length: + raise ValueError( + f"Qwen2Audio expects the mel input features to be of length {expected_seq_length}, but found {input_features.shape[-1]}. Make sure to pad the input mel features to {expected_seq_length}." + ) + + input_features = input_features.to(dtype=self.conv1.weight.dtype, device=self.conv1.weight.device) + inputs_embeds = nn.functional.gelu(self.conv1(input_features)) + inputs_embeds = nn.functional.gelu(self.conv2(inputs_embeds)) + inputs_embeds = inputs_embeds.permute(0, 2, 1) + + embed_pos = self.embed_positions.weight + hidden_states = (inputs_embeds + embed_pos).to(inputs_embeds.dtype) + hidden_states = nn.functional.dropout(hidden_states, p=self.dropout, training=self.training) + + for idx, encoder_layer in enumerate(self.layers): + layer_outputs = encoder_layer( + hidden_states, + attention_mask=attention_mask, + layer_head_mask=None, + ) + hidden_states = layer_outputs[0] + + hidden_states = self.layer_norm(hidden_states) + + return BaseModelOutput( + last_hidden_state=hidden_states, + ) + + # Ignore copy + def _get_feat_extract_output_lengths(self, input_lengths: torch.LongTensor): + """ + Computes the output length of the convolutional layers and the output length of the audio encoder + """ + input_lengths = (input_lengths - 1) // 2 + 1 + output_lengths = (input_lengths - 2) // 2 + 1 + return input_lengths, output_lengths + + +class VoxtralMultiModalProjector(nn.Module): + def __init__(self, config: VoxtralConfig): + super().__init__() + self.linear_1 = nn.Linear(config.audio_config.intermediate_size, config.text_config.hidden_size, bias=False) + self.act = ACT2FN[config.projector_hidden_act] + self.linear_2 = nn.Linear(config.text_config.hidden_size, config.text_config.hidden_size, bias=False) + + def forward(self, audio_features): + hidden_states = self.linear_1(audio_features) + hidden_states = self.act(hidden_states) + hidden_states = self.linear_2(hidden_states) + return hidden_states + + +@auto_docstring( + custom_intro=""" + The Voxtral model, which consists of Whisper encoder, a multi-modal projector and a LLama language model. + """ +) +class VoxtralForConditionalGeneration(VoxtralPreTrainedModel, GenerationMixin): + _tied_weights_keys = ["lm_head.weight"] + _tp_plan = {"lm_head": "colwise_rep"} + _pp_plan = {"lm_head": (["hidden_states"], ["logits"])} + _keep_in_fp32_modules_strict = ["embed_positions"] + + def __init__(self, config): + super().__init__(config) + self.vocab_size = config.text_config.vocab_size + self.audio_tower = AutoModel.from_config(config.audio_config) + self.language_model = AutoModelForCausalLM.from_config(config.text_config) + self.multi_modal_projector = VoxtralMultiModalProjector(config) + + # Initialize weights and apply final processing + self.post_init() + + def get_input_embeddings(self): + return self.language_model.get_input_embeddings() + + def set_input_embeddings(self, value): + self.language_model.set_input_embeddings(value) + + def get_output_embeddings(self): + return self.language_model.get_output_embeddings() + + def set_output_embeddings(self, new_embeddings): + self.language_model.set_output_embeddings(new_embeddings) + + def set_decoder(self, decoder): + self.language_model.set_decoder(decoder) + + def get_decoder(self): + return self.language_model.get_decoder() + + def get_audio_embeds(self, input_features: torch.FloatTensor): + """ + This method is used to get the audio embeddings from input features (a log mel spectrogram), meaning inferring the audio encoder and the multi-modal projector. + Args: + input_features (`torch.FloatTensor`): + Float values of mel features extracted from the raw speech waveform. Raw speech waveform can be + obtained by loading a `.flac` or `.wav` audio file into an array of type `list[float]` or a + `numpy.ndarray`, *e.g.* via the soundfile library (`pip install soundfile`). To prepare the array into + `input_features`, the [`AutoFeatureExtractor`] should be used for extracting the mel features, padding + and conversion into a tensor of type `torch.FloatTensor`. See [`~WhisperFeatureExtractor.__call__`] + + Returns: + `torch.FloatTensor`: + The audio embeddings. + """ + audio_outputs = self.audio_tower(input_features) + audio_hidden_states = audio_outputs.last_hidden_state + audio_hidden_states = audio_hidden_states.reshape(-1, self.config.audio_config.intermediate_size) + audio_embeds = self.multi_modal_projector(audio_hidden_states) + return audio_embeds + + @can_return_tuple + @auto_docstring + def forward( + self, + input_ids: Optional[torch.LongTensor] = None, + input_features: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_values: Optional[Cache] = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + labels: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = None, + cache_position: Optional[torch.LongTensor] = None, + logits_to_keep: Union[int, torch.Tensor] = 0, + **kwargs: Unpack[TransformersKwargs], + ) -> CausalLMOutputWithPast: + r""" + Example: + + ```python + >>> from transformers import VoxtralForConditionalGeneration, AutoProcessor + >>> import torch + + >>> device = "cuda" if torch.cuda.is_available() else "cpu" + >>> repo_id = "mistralai/Voxtral-Mini-3B-2507" + + >>> processor = AutoProcessor.from_pretrained(repo_id) + >>> model = VoxtralForConditionalGeneration.from_pretrained(repo_id, torch_dtype=torch.bfloat16, device_map=device) + + >>> conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "url": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/dude_where_is_my_car.wav", + }, + {"type": "text", "text": "What can you tell me about this audio?"}, + ], + } + ] + + >>> inputs = processor.apply_chat_template(conversation) + >>> inputs = inputs.to(device, dtype=torch.bfloat16) + + >>> outputs = model.generate(**inputs, max_new_tokens=30) + >>> processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + ["This audio is a humorous conversation between two friends, likely in English, where one of them is trying to figure out what the other's tattoo says."] + ```""" + if inputs_embeds is None: + inputs_embeds = self.get_input_embeddings()(input_ids) + + if input_features is not None: + audio_embeds = self.get_audio_embeds(input_features) + + # replace text-audio token placeholders with audio embeddings + audio_token_mask = input_ids == self.config.audio_token_id + inputs_embeds[audio_token_mask] = audio_embeds + + outputs: BaseModelOutputWithPast = self.language_model( + attention_mask=attention_mask, + position_ids=position_ids, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + labels=labels, + use_cache=use_cache, + cache_position=cache_position, + logits_to_keep=logits_to_keep, + **kwargs, + ) + return outputs + + def prepare_inputs_for_generation(self, *args, **kwargs): + # Overwritten -- we should not pass input_features when we are in cached decoding stage + + input_features = kwargs.pop("input_features", None) + cache_position = kwargs.get("cache_position") + + model_inputs = super().prepare_inputs_for_generation(*args, **kwargs) + + if cache_position is not None and cache_position[0] == 0: + # input_features should only be passed when we are not in cached decoding stage + model_inputs["input_features"] = input_features + + return model_inputs + + +__all__ = ["VoxtralPreTrainedModel", "VoxtralEncoder", "VoxtralForConditionalGeneration"] diff --git a/src/transformers/models/voxtral/modular_voxtral.py b/src/transformers/models/voxtral/modular_voxtral.py new file mode 100644 index 0000000000..fdb9862ad5 --- /dev/null +++ b/src/transformers/models/voxtral/modular_voxtral.py @@ -0,0 +1,276 @@ +# coding=utf-8 +# Copyright 2025 The HuggingFace Inc. team. 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 of the License 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. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional, Union + +import torch +from torch import nn + +from ...activations import ACT2FN +from ...cache_utils import Cache +from ...generation import GenerationMixin +from ...modeling_outputs import BaseModelOutput, BaseModelOutputWithPast, CausalLMOutputWithPast +from ...processing_utils import Unpack +from ...utils import TransformersKwargs, auto_docstring, can_return_tuple +from ...utils.generic import check_model_inputs +from ..auto import AutoModel, AutoModelForCausalLM +from ..qwen2_audio.modeling_qwen2_audio import ( + Qwen2AudioAttention, + Qwen2AudioEncoder, + Qwen2AudioEncoderLayer, + Qwen2AudioPreTrainedModel, +) +from .configuration_voxtral import VoxtralConfig + + +class VoxtralAttention(Qwen2AudioAttention): + pass + + +class VoxtralEncoderLayer(Qwen2AudioEncoderLayer): + pass + + +class VoxtralPreTrainedModel(Qwen2AudioPreTrainedModel): + _supports_flex_attn = True + _supports_cache_class = True + _supports_attention_backend = True + _supports_static_cache = True + _supports_attention_backend = True + + +# TODO: @eustlb, I would really prefer to use WhisperEncoder but it's messing with modular +@auto_docstring( + custom_intro=""" + The Voxtral encoder, which is a Whisper encoder. + """ +) +class VoxtralEncoder(Qwen2AudioEncoder): + _can_record_outputs = { + "attentions": VoxtralAttention, + "hidden_states": VoxtralEncoderLayer, + } + + @check_model_inputs + def forward( + self, + input_features, + attention_mask=None, + **kwargs: Unpack[TransformersKwargs], + ): + r""" + Args: + input_features (`torch.LongTensor` of shape `(batch_size, feature_size, sequence_length)`): + Float values of mel features extracted from the raw speech waveform. Raw speech waveform can be + obtained by loading a `.flac` or `.wav` audio file into an array of type `list[float]` or a + `numpy.ndarray`, *e.g.* via the soundfile library (`pip install soundfile`). To prepare the array into + `input_features`, the [`AutoFeatureExtractor`] should be used for extracting the mel features, padding + and conversion into a tensor of type `torch.FloatTensor`. See [`~WhisperFeatureExtractor.__call__`] + attention_mask (`torch.Tensor`)`, *optional*): + Voxtral does not support masking of the `input_features`, this argument is preserved for compatibility, + but it is not used. By default the silence in the input log mel spectrogram are ignored. + """ + expected_seq_length = self.config.max_source_positions * self.conv1.stride[0] * self.conv2.stride[0] + if input_features.shape[-1] != expected_seq_length: + raise ValueError( + f"Qwen2Audio expects the mel input features to be of length {expected_seq_length}, but found {input_features.shape[-1]}. Make sure to pad the input mel features to {expected_seq_length}." + ) + + input_features = input_features.to(dtype=self.conv1.weight.dtype, device=self.conv1.weight.device) + inputs_embeds = nn.functional.gelu(self.conv1(input_features)) + inputs_embeds = nn.functional.gelu(self.conv2(inputs_embeds)) + inputs_embeds = inputs_embeds.permute(0, 2, 1) + + embed_pos = self.embed_positions.weight + hidden_states = (inputs_embeds + embed_pos).to(inputs_embeds.dtype) + hidden_states = nn.functional.dropout(hidden_states, p=self.dropout, training=self.training) + + for idx, encoder_layer in enumerate(self.layers): + layer_outputs = encoder_layer( + hidden_states, + attention_mask=attention_mask, + layer_head_mask=None, + ) + hidden_states = layer_outputs[0] + + hidden_states = self.layer_norm(hidden_states) + + return BaseModelOutput( + last_hidden_state=hidden_states, + ) + + +class VoxtralMultiModalProjector(nn.Module): + def __init__(self, config: VoxtralConfig): + super().__init__() + self.linear_1 = nn.Linear(config.audio_config.intermediate_size, config.text_config.hidden_size, bias=False) + self.act = ACT2FN[config.projector_hidden_act] + self.linear_2 = nn.Linear(config.text_config.hidden_size, config.text_config.hidden_size, bias=False) + + def forward(self, audio_features): + hidden_states = self.linear_1(audio_features) + hidden_states = self.act(hidden_states) + hidden_states = self.linear_2(hidden_states) + return hidden_states + + +@auto_docstring( + custom_intro=""" + The Voxtral model, which consists of Whisper encoder, a multi-modal projector and a LLama language model. + """ +) +class VoxtralForConditionalGeneration(VoxtralPreTrainedModel, GenerationMixin): + _tied_weights_keys = ["lm_head.weight"] + _tp_plan = {"lm_head": "colwise_rep"} + _pp_plan = {"lm_head": (["hidden_states"], ["logits"])} + _keep_in_fp32_modules_strict = ["embed_positions"] + + def __init__(self, config): + super().__init__(config) + self.vocab_size = config.text_config.vocab_size + self.audio_tower = AutoModel.from_config(config.audio_config) + self.language_model = AutoModelForCausalLM.from_config(config.text_config) + self.multi_modal_projector = VoxtralMultiModalProjector(config) + + # Initialize weights and apply final processing + self.post_init() + + def get_input_embeddings(self): + return self.language_model.get_input_embeddings() + + def set_input_embeddings(self, value): + self.language_model.set_input_embeddings(value) + + def get_output_embeddings(self): + return self.language_model.get_output_embeddings() + + def set_output_embeddings(self, new_embeddings): + self.language_model.set_output_embeddings(new_embeddings) + + def set_decoder(self, decoder): + self.language_model.set_decoder(decoder) + + def get_decoder(self): + return self.language_model.get_decoder() + + def get_audio_embeds(self, input_features: torch.FloatTensor): + """ + This method is used to get the audio embeddings from input features (a log mel spectrogram), meaning inferring the audio encoder and the multi-modal projector. + Args: + input_features (`torch.FloatTensor`): + Float values of mel features extracted from the raw speech waveform. Raw speech waveform can be + obtained by loading a `.flac` or `.wav` audio file into an array of type `list[float]` or a + `numpy.ndarray`, *e.g.* via the soundfile library (`pip install soundfile`). To prepare the array into + `input_features`, the [`AutoFeatureExtractor`] should be used for extracting the mel features, padding + and conversion into a tensor of type `torch.FloatTensor`. See [`~WhisperFeatureExtractor.__call__`] + + Returns: + `torch.FloatTensor`: + The audio embeddings. + """ + audio_outputs = self.audio_tower(input_features) + audio_hidden_states = audio_outputs.last_hidden_state + audio_hidden_states = audio_hidden_states.reshape(-1, self.config.audio_config.intermediate_size) + audio_embeds = self.multi_modal_projector(audio_hidden_states) + return audio_embeds + + @can_return_tuple + @auto_docstring + def forward( + self, + input_ids: Optional[torch.LongTensor] = None, + input_features: Optional[torch.FloatTensor] = None, + attention_mask: Optional[torch.Tensor] = None, + position_ids: Optional[torch.LongTensor] = None, + past_key_values: Optional[Cache] = None, + inputs_embeds: Optional[torch.FloatTensor] = None, + labels: Optional[torch.LongTensor] = None, + use_cache: Optional[bool] = None, + cache_position: Optional[torch.LongTensor] = None, + logits_to_keep: Union[int, torch.Tensor] = 0, + **kwargs: Unpack[TransformersKwargs], + ) -> CausalLMOutputWithPast: + r""" + Example: + + ```python + >>> from transformers import VoxtralForConditionalGeneration, AutoProcessor + >>> import torch + + >>> device = "cuda" if torch.cuda.is_available() else "cpu" + >>> repo_id = "mistralai/Voxtral-Mini-3B-2507" + + >>> processor = AutoProcessor.from_pretrained(repo_id) + >>> model = VoxtralForConditionalGeneration.from_pretrained(repo_id, torch_dtype=torch.bfloat16, device_map=device) + + >>> conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "url": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/dude_where_is_my_car.wav", + }, + {"type": "text", "text": "What can you tell me about this audio?"}, + ], + } + ] + + >>> inputs = processor.apply_chat_template(conversation) + >>> inputs = inputs.to(device, dtype=torch.bfloat16) + + >>> outputs = model.generate(**inputs, max_new_tokens=30) + >>> processor.batch_decode(outputs[:, inputs.input_ids.shape[1]:], skip_special_tokens=True) + ["This audio is a humorous conversation between two friends, likely in English, where one of them is trying to figure out what the other's tattoo says."] + ```""" + if inputs_embeds is None: + inputs_embeds = self.get_input_embeddings()(input_ids) + + if input_features is not None: + audio_embeds = self.get_audio_embeds(input_features) + + # replace text-audio token placeholders with audio embeddings + audio_token_mask = input_ids == self.config.audio_token_id + inputs_embeds[audio_token_mask] = audio_embeds + + outputs: BaseModelOutputWithPast = self.language_model( + attention_mask=attention_mask, + position_ids=position_ids, + past_key_values=past_key_values, + inputs_embeds=inputs_embeds, + labels=labels, + use_cache=use_cache, + cache_position=cache_position, + logits_to_keep=logits_to_keep, + **kwargs, + ) + return outputs + + def prepare_inputs_for_generation(self, *args, **kwargs): + # Overwritten -- we should not pass input_features when we are in cached decoding stage + + input_features = kwargs.pop("input_features", None) + cache_position = kwargs.get("cache_position") + + model_inputs = super().prepare_inputs_for_generation(*args, **kwargs) + + if cache_position is not None and cache_position[0] == 0: + # input_features should only be passed when we are not in cached decoding stage + model_inputs["input_features"] = input_features + + return model_inputs + + +__all__ = ["VoxtralPreTrainedModel", "VoxtralEncoder", "VoxtralForConditionalGeneration"] diff --git a/src/transformers/models/voxtral/processing_voxtral.py b/src/transformers/models/voxtral/processing_voxtral.py new file mode 100644 index 0000000000..f684466874 --- /dev/null +++ b/src/transformers/models/voxtral/processing_voxtral.py @@ -0,0 +1,449 @@ +# coding=utf-8 +# Copyright 2025 Sesame and The HuggingFace Inc. team. 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 of the License 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. +# See the License for the specific language governing permissions and +# limitations under the License. + +import io +from typing import Optional, Union + +from ...utils import is_mistral_common_available, is_soundfile_available, is_torch_available, logging + + +if is_torch_available(): + import torch + +if is_soundfile_available(): + import soundfile as sf + +if is_mistral_common_available(): + from mistral_common.protocol.transcription.request import TranscriptionRequest + +from ...audio_utils import AudioInput, load_audio_as, make_list_of_audio +from ...feature_extraction_utils import BatchFeature +from ...processing_utils import AllKwargsForChatTemplate, AudioKwargs, ProcessingKwargs, ProcessorMixin, Unpack +from ...tokenization_utils_base import PreTokenizedInput, TextInput + + +logger = logging.get_logger(__name__) + + +class VoxtralAudioKwargs(AudioKwargs, total=False): + max_source_positions: Optional[int] + + +class VoxtralProcessorKwargs(ProcessingKwargs, total=False): + _defaults = { + "text_kwargs": { + "padding": True, + }, + "audio_kwargs": { + "sampling_rate": 16000, + "padding": True, + "truncation": False, + "pad_to_multiple_of": 480000, + "max_source_positions": 3000, + }, + "common_kwargs": { + "return_tensors": "pt", + "return_dict": True, + "tokenize": True, + }, + } + + +class VoxtralProcessor(ProcessorMixin): + r""" + Constructs a Voxtral processor which wraps [`WhisperFeatureExtractor`] and + [`MistralCommonTokenizer`] into a single processor that inherits both the audio feature extraction and + tokenizer functionalities. + + Args: + feature_extractor ([`WhisperFeatureExtractor`]): + The feature extractor is a required input. + tokenizer ([`MistralCommonTokenizer`]): + The tokenizer is a required input. + """ + + attributes = ["feature_extractor", "tokenizer"] + feature_extractor_class = "WhisperFeatureExtractor" + tokenizer_class = "MistralCommonTokenizer" + + def __init__( + self, + feature_extractor, + tokenizer, + ): + self.audio_token_id = 24 + self.audio_token = tokenizer.convert_ids_to_tokens(self.audio_token_id) + + super().__init__(feature_extractor, tokenizer) + + def _retreive_input_features(self, audio, max_source_positions, **kwargs): + """ + Handles specific logic of Voxtral expected input features: audio arrays should be padded to next multiple of 480000 (duration is a multiple of 30s), see VoxtralProcessorKwargs' default audio_kwargs. + Then mel input features are extracted and stacked along batch dimension, splitting into chunks of max_source_positions. + """ + input_features_list = [] + for audio_array in audio: + audio_inputs = self.feature_extractor(audio_array, **kwargs) + + # let's split into chunks of max_source_positions, and then stack them along batch dimension + input_features = audio_inputs["input_features"].reshape( + self.feature_extractor.feature_size, -1, max_source_positions + ) + input_features_list.append(input_features.transpose(0, 1)) + + return torch.cat(input_features_list) + + def apply_chat_template( + self, + conversation: Union[list[dict[str, str]], list[list[dict[str, str]]]], + **kwargs: Unpack[AllKwargsForChatTemplate], + ) -> str: + """ + This method applies the model's chat completion template given a conversation. It relies on MistralCommonTokenizer's + [`~MistralCommonTokenizer.apply_chat_template`] to prepare input ids to the model and on WhisperFeatureExtractor's + [`~WhisperFeatureExtractor.__call__`] to prepare input features to the model. + + Note that audio is padded to the nearest 30-second multiple prior to mel feature extraction. + + A `conversation` is a list of messages, where each message is a dictionary with a `role` and a `content` field. + For Voxtral, `role` can be `"user"` or `"assistant"`. + The `content` field can be a string or a list of dictionaries with a `type` field. See example below. + + ```python + from huggingface_hub import hf_hub_download + from transformers.audio_utils import load_audio_as + + audio_url = "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/bcn_weather.mp3" + audio_path = hf_hub_download(repo_id="hf-internal-testing/dummy-audio-samples", filename="bcn_weather.mp3", repo_type="dataset") + audio_base64 = load_audio_as(audio_path, return_format="base64", force_mono=True) + + # audio + text + conversation = [ + { + "role": "user", + "content": [ + {"type": "audio", "url": audio_url}, + {"type": "audio", "path": audio_path}, + {"type": "audio", "base64": audio_base64}, + {"type": "text", "text": "How many audio do you hear?"}, + ], + }, + ] + + processor = VoxtralProcessor.from_pretrained("mistralai/Voxtral-Mini-3B-2507") + inputs = processor.apply_chat_template(conversation) + ``` + + Args: + conversation (`Union[list[Dict, [str, str]], list[list[dict[str, str]]]]`): + The conversation to format. + """ + if kwargs.get("continue_final_message", False): + if kwargs.get("add_generation_prompt", False): + raise ValueError( + "continue_final_message and add_generation_prompt are not compatible. Use continue_final_message when you want the model to continue the final message, and add_generation_prompt when you want to add a header that will prompt it to start a new assistant message instead." + ) + if kwargs.get("return_assistant_tokens_mask", False): + raise ValueError("continue_final_message is not compatible with return_assistant_tokens_mask.") + + # Fill sets of kwargs that should be used by different parts of template + processed_kwargs = { + "mm_load_kwargs": {}, + "template_kwargs": {}, + } + + for kwarg_type in processed_kwargs: + for key in AllKwargsForChatTemplate.__annotations__[kwarg_type].__annotations__.keys(): + kwarg_type_defaults = AllKwargsForChatTemplate.__annotations__[kwarg_type] + default_value = getattr(kwarg_type_defaults, key, None) + value = kwargs.pop(key, default_value) + if value is not None and not isinstance(value, dict): + processed_kwargs[kwarg_type][key] = value + + # Pass unprocessed custom kwargs + processed_kwargs["template_kwargs"].update(kwargs) + + if isinstance(conversation, (list, tuple)) and ( + isinstance(conversation[0], (list, tuple)) or hasattr(conversation[0], "content") + ): + is_batched = True + conversations = conversation + else: + is_batched = False + conversations = [conversation] + + # Check for any overlapping keys between mm_load_kwargs and kwargs + mm_load_kwargs = processed_kwargs["mm_load_kwargs"] + if any(key in kwargs for key in mm_load_kwargs): + overlapping_keys = [key for key in mm_load_kwargs if key in kwargs] + logger.warning( + f"{overlapping_keys[0] if len(overlapping_keys) == 1 else ', '.join(overlapping_keys)} load multimodal data kwarg{'s' if len(overlapping_keys) > 1 else ''} {'have' if len(overlapping_keys) > 1 else 'has'} been passed to the processor, but {'they are' if len(overlapping_keys) > 1 else 'it is'} not supported for VoxtralProcessor since it relies on mistral_common directly. {'They' if len(overlapping_keys) > 1 else 'It'} will be ignored." + ) + + output_kwargs = self._merge_kwargs( + VoxtralProcessorKwargs, + **kwargs, + ) + text_kwargs = output_kwargs["text_kwargs"] + audio_kwargs = output_kwargs["audio_kwargs"] + common_kwargs = output_kwargs["common_kwargs"] + + return_tensors = common_kwargs.pop("return_tensors", None) + if return_tensors != "pt": + raise ValueError(f"{self.__class__.__name__} only supports `return_tensors='pt'`.") + + tokenizer_kwargs = {**processed_kwargs["template_kwargs"], **text_kwargs} + tokenizer_kwargs["return_tensors"] = None # let's not return tensors here + tokenize = tokenizer_kwargs.pop("tokenize", False) + return_dict = tokenizer_kwargs.pop("return_dict", False) + + encoded_instruct_inputs = self.tokenizer.apply_chat_template( + conversations, + tokenize=tokenize, + return_dict=return_dict, + **tokenizer_kwargs, + ) + + if tokenize: + if return_dict: + audio = encoded_instruct_inputs.pop("audio", None) + data = dict(encoded_instruct_inputs) + if audio is not None: + max_source_positions = audio_kwargs.pop("max_source_positions") + data["input_features"] = self._retreive_input_features(audio, max_source_positions, **audio_kwargs) + + return BatchFeature(data=data, tensor_type=return_tensors) + + if not is_batched: + return encoded_instruct_inputs[0] + + return encoded_instruct_inputs + + def __call__( + self, + text: Optional[Union[TextInput, PreTokenizedInput, list[TextInput], list[PreTokenizedInput]]], + **kwargs: Unpack[VoxtralProcessorKwargs], + ): + r""" + Method to prepare text to be fed as input to the model. This method forwards the `text` + arguments to MistralCommonTokenizer's [`~MistralCommonTokenizer.__call__`] to encode + the text. Please refer to the docstring of the above methods for more information. + This methods does not support audio. To prepare the audio, please use: + 1. `apply_chat_template` [`~VoxtralProcessor.apply_chat_template`] method. + 2. `apply_transcrition_request` [`~VoxtralProcessor.apply_transcrition_request`] method. + + Args: + text (`str`, `list[str]`, `list[list[str]]`): + The sequence or batch of sequences to be encoded. Each sequence can be a string or a list of strings + (pretokenized string). If the sequences are provided as list of strings (pretokenized), you must set + `is_split_into_words=True` (to lift the ambiguity with a batch of sequences). + return_tensors (`str` or [`~utils.TensorType`], *optional*): + If set, will return tensors of a particular framework. Acceptable values are: + - `'tf'`: Return TensorFlow `tf.constant` objects. + - `'pt'`: Return PyTorch `torch.Tensor` objects. + - `'np'`: Return NumPy `np.ndarray` objects. + - `'jax'`: Return JAX `jnp.ndarray` objects. + Returns: + [`BatchFeature`]: A [`BatchFeature`] with the following fields: + + - **input_ids** -- List of token ids to be fed to a model. Returned when `text` is not `None`. + - **input_features** -- List of audio values to be fed to a model. Returned when `audio` is not `None`. + - **attention_mask** -- List of indices specifying which tokens should be attended to by the model (when + `return_attention_mask=True` or if *"attention_mask"* is in `self.model_input_names` and if `text` is not + `None`). + """ + if isinstance(text, str): + text = [text] + + if any(self.audio_token in t for t in text): + raise ValueError( + f"{self.audio_token} is present in the provided text which is not supported by VoxtralProcessor. Please use the `apply_chat_template` method instead." + ) + + output_kwargs = self._merge_kwargs( + VoxtralProcessorKwargs, + **kwargs, + ) + text_kwargs = output_kwargs["text_kwargs"] + common_kwargs = output_kwargs["common_kwargs"] + + out = self.tokenizer(text, **text_kwargs) + + return BatchFeature(data=out, tensor_type=common_kwargs.pop("return_tensors", None)) + + # TODO: @eustlb, this should be moved to mistral_common + testing + def apply_transcrition_request( + self, + language: Union[str, list[str]], + audio: Union[str, list[str], AudioInput], + model_id: str, + sampling_rate: Optional[int] = None, + format: Optional[Union[str, list[str]]] = None, + **kwargs: Unpack[VoxtralProcessorKwargs], + ): + """ + This method applies the model's transcription request template given a language and audio. + It relies on MistralCommonTokenizer and WhisperFeatureExtractor to prepare input ids and input features to the model. + + ```python + from transformers import VoxtralProcessor + + model_id = "mistralai/Voxtral-Mini-3B-2507" + processor = VoxtralProcessor.from_pretrained(model_id) + + language = "en" + audio = "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3" + + inputs = processor.apply_transcrition_request(language=language, audio=audio, model_id=model_id) + ``` + + Args: + language (`str`, `list[str]`): + The language or languages of the audio. If provided as a string, will be applied uniformly to all audio. + If provided as a list, will be applied to each audio individually with a one-to-one mapping. + audio (`str`, `list[str]`, `np.ndarray`, `torch.Tensor`, `list[np.ndarray]`, `list[torch.Tensor]`): + The audio or batch of audio to be prepared. If provided as a string, it should correspond to the path or url of the audio file. + model_id (`str`: + The hub model id of the model to use for transcription. + sampling_rate (`int`, *optional*): + The sampling rate of the audio. Necessary if it is provided as `np.ndarray`, `torch.Tensor`, `list[np.ndarray]`, `list[torch.Tensor]`. + Used to avoid silent errors when passing audio that is not in the expected sampling rate. + format (`str`, `list[str]`, *optional*): + The format of the audio, necessary if is provided as `np.ndarray`, `torch.Tensor`, `list[np.ndarray]`, `list[torch.Tensor]`. + """ + output_kwargs = self._merge_kwargs( + VoxtralProcessorKwargs, + **kwargs, + ) + text_kwargs = output_kwargs["text_kwargs"] + audio_kwargs = output_kwargs["audio_kwargs"] + common_kwargs = output_kwargs["common_kwargs"] + + is_str = isinstance(audio, str) + is_list_of_str = all(isinstance(el, str) for el in audio) + is_list_of_audio = not (is_str or is_list_of_str) + + if is_list_of_audio: + if sampling_rate is None: + logger.warning_once( + f"You've provided audio without specifying the sampling rate. It will be assumed to be {audio_kwargs['sampling_rate']}, which can result in silent errors." + ) + elif sampling_rate != audio_kwargs["sampling_rate"]: + raise ValueError( + f"The sampling rate of the audio ({sampling_rate}) does not match the sampling rate of the processor ({audio_kwargs['sampling_rate']}). Please provide resampled the audio to the expected sampling rate." + ) + + sampling_rate = audio_kwargs["sampling_rate"] + return_dict = common_kwargs.pop("return_dict", False) + tokenize = common_kwargs.pop("tokenize", False) + + # make sure to remove from text_kwargs and audio_kwargs + for k in ("return_dict", "tokenize"): + text_kwargs.pop(k, None) + audio_kwargs.pop(k, None) + + return_tensors = common_kwargs.pop("return_tensors", None) + if return_tensors != "pt": + raise ValueError(f"{self.__class__.__name__} only supports `return_tensors='pt'`.") + + # validate audio input + if is_str: + audio = [load_audio_as(audio, return_format="buffer", force_mono=True, sampling_rate=sampling_rate)] + elif is_list_of_str: + audio = [ + load_audio_as(el, return_format="buffer", force_mono=True, sampling_rate=sampling_rate) for el in audio + ] + else: + audio = make_list_of_audio(audio) + if len(audio) != len(format): + raise ValueError( + f"When passed as a list of audio, the length ({len(audio)}) must match the number of format ({len(format)})" + ) + audio_buffers = [] + for array, f in zip(audio, format): + # Create new BytesIO object and write audio data to it + buffer = io.BytesIO() + # Convert to mono if needed + if array.ndim == 2: + array = array.mean(axis=1) + # Write to buffer with default format and sampling rate + sf.write(buffer, array, samplerate=audio_kwargs["sampling_rate"], format=f) + buffer.seek(0) + audio_buffers.append(buffer) + audio = audio_buffers + + # validate language input + n_audio = len(audio) + if isinstance(language, str): + language = [language] * n_audio + + if len(language) != n_audio: + raise ValueError( + f"When passed as a list of languages, the length ({len(language)}) must match the number of audio ({n_audio})" + ) + + input_ids = [] + texts = [] + audio_arrays = [] + for audio_el, language_el in zip(audio, language): + openai_transcription_request = { + "model": model_id, + "file": audio_el, + "language": language_el, + } + + transcription_request = TranscriptionRequest.from_openai(openai_transcription_request) + tokenized_transcription_request = self.tokenizer.tokenizer.encode_transcription(transcription_request) + + input_ids.append(tokenized_transcription_request.tokens) + texts.append(tokenized_transcription_request.text) + audio_arrays.extend([el.audio_array for el in tokenized_transcription_request.audios]) + + if tokenize: + if return_dict: + # text are already tokenized but we need to pad etc + encoding = self.tokenizer( + input_ids, + add_special_tokens=False, + **text_kwargs, + ) + data = dict(encoding) + + # extract the input features + max_source_positions = audio_kwargs.pop("max_source_positions") + data["input_features"] = self._retreive_input_features( + audio_arrays, max_source_positions, **audio_kwargs + ) + + return BatchFeature(data=data, tensor_type=return_tensors) + + return texts + + def batch_decode(self, *args, **kwargs): + """ + This method forwards all its arguments to MistralCommonTokenizer's [`~MistralCommonTokenizer.batch_decode`]. Please + refer to the docstring of this method for more information. + """ + return self.tokenizer.batch_decode(*args, **kwargs) + + def decode(self, *args, **kwargs): + """ + This method forwards all its arguments to MistralCommonTokenizer's [`~MistralCommonTokenizer.decode`]. Please refer to + the docstring of this method for more information. + """ + return self.tokenizer.decode(*args, **kwargs) + + +__all__ = ["VoxtralProcessor"] diff --git a/src/transformers/tokenization_mistral_common.py b/src/transformers/tokenization_mistral_common.py index bf5f61ae00..c93ed4161d 100644 --- a/src/transformers/tokenization_mistral_common.py +++ b/src/transformers/tokenization_mistral_common.py @@ -22,6 +22,7 @@ from typing import Any, Callable, Optional, Union, overload import numpy as np +from transformers.audio_utils import load_audio_as from transformers.tokenization_utils_base import ( LARGE_INTEGER, VERY_LARGE_INTEGER, @@ -41,11 +42,13 @@ from transformers.utils.import_utils import is_mistral_common_available, is_torc if is_mistral_common_available(): from mistral_common.protocol.instruct.request import ChatCompletionRequest from mistral_common.protocol.instruct.validator import ValidationMode - from mistral_common.tokens.tokenizers.base import SpecialTokenPolicy + from mistral_common.tokens.tokenizers.base import SpecialTokenPolicy, TokenizerVersion + from mistral_common.tokens.tokenizers.image import MultiModalVersion from mistral_common.tokens.tokenizers.mistral import MistralTokenizer from mistral_common.tokens.tokenizers.tekken import Tekkenizer from mistral_common.tokens.tokenizers.utils import download_tokenizer_from_hf_hub + if is_torch_available(): import torch @@ -1473,12 +1476,24 @@ class MistralCommonTokenizer(PushToHubMixin): else: raise ValueError("Image content must be specified.") normalized_content.append({"type": "image_url", "image_url": {"url": image_content}}) + elif content_type == "audio": + maybe_url: Optional[str] = content.get("url") + maybe_path: Optional[str] = content.get("path") + maybe_base64: Optional[str] = content.get("base64") + if maybe_url or maybe_path: + audio_data = load_audio_as(maybe_url or maybe_path, return_format="dict", force_mono=True) + normalized_content.append({"type": "input_audio", "input_audio": audio_data}) + continue + if not maybe_base64: + raise ValueError("Audio content must be specified.") + normalized_content.append({"type": "audio_url", "audio_url": {"url": maybe_base64}}) else: normalized_content.append(content) message["content"] = normalized_content outputs = [] images: list[np.ndarray] = [] + audios: list[np.ndarray] = [] for conversation in conversations: messages: list[dict[str, Union[str, list[dict[str, Union[str, dict[str, Any]]]]]]] = [] @@ -1498,6 +1513,7 @@ class MistralCommonTokenizer(PushToHubMixin): else: outputs.append(tokenized_request.text) images.extend(tokenized_request.images) + audios.extend([el.audio_array for el in tokenized_request.audios]) if not is_batched: outputs = outputs[0] @@ -1528,6 +1544,13 @@ class MistralCommonTokenizer(PushToHubMixin): else: raise ValueError(f"Unsupported return_tensors type: {return_tensors}") out.data["pixel_values"] = pixel_values + if audios: + if return_tensors is not None: + raise NotImplementedError( + "When passing audio content in apply_chat_template, `return_tensors` must be None since we cannot batch the audio inputs. The returned audio will be a list of numpy arrays." + ) + # Transformers convention is audio for plural audio (audio does not take a "s") + out.data["audio"] = audios return out else: return out["input_ids"] @@ -1735,12 +1758,12 @@ class MistralCommonTokenizer(PushToHubMixin): raise ValueError("`init_inputs` are not supported by `MistralCommonTokenizer.from_pretrained`.") # Handle kwargs and AutoTokenizer case - if kwargs and not kwargs.keys() == {"_from_auto"}: + if kwargs and not set(kwargs.keys()).issubset({"_from_auto", "trust_remote_code"}): raise ValueError( f"Kwargs {list(kwargs.keys())} are not supported by `MistralCommonTokenizer.from_pretrained`." ) - if not os.path.isfile(pretrained_model_name_or_path): + if not os.path.isdir(pretrained_model_name_or_path): tokenizer_path = download_tokenizer_from_hf_hub( repo_id=pretrained_model_name_or_path, cache_dir=cache_dir, @@ -1750,7 +1773,37 @@ class MistralCommonTokenizer(PushToHubMixin): local_files_only=local_files_only, ) else: - tokenizer_path = pretrained_model_name_or_path + valid_tokenizer_files = [] + tokenizer_file: str + + instruct_versions = list(TokenizerVersion.__members__) + mm_versions = list(MultiModalVersion.__members__) + [""] # allow no mm version + sentencepiece_suffixes = [f".model.{v}{m}" for v in instruct_versions for m in mm_versions] + [".model"] + + for path in os.listdir(pretrained_model_name_or_path): + pathlib_repo_file = Path(path) + file_name = pathlib_repo_file.name + suffix = "".join(pathlib_repo_file.suffixes) + if file_name == "tekken.json": + valid_tokenizer_files.append(file_name) + elif suffix in sentencepiece_suffixes: + valid_tokenizer_files.append(file_name) + + if len(valid_tokenizer_files) == 0: + raise ValueError(f"No tokenizer file found in directory: {pretrained_model_name_or_path}") + # If there are multiple tokenizer files, we use tekken.json if it exists, otherwise the versioned one. + if len(valid_tokenizer_files) > 1: + if "tekken.json" in valid_tokenizer_files: + tokenizer_file = "tekken.json" + else: + tokenizer_file = sorted(valid_tokenizer_files)[-1] + logger.warning( + f"Multiple tokenizer files found in directory: {pretrained_model_name_or_path}. Using {tokenizer_file}." + ) + else: + tokenizer_file = valid_tokenizer_files[0] + + tokenizer_path = os.path.join(pretrained_model_name_or_path, tokenizer_file) return cls( tokenizer_path=tokenizer_path, @@ -1802,6 +1855,8 @@ class MistralCommonTokenizer(PushToHubMixin): Returns: A tuple of `str`: The files saved. """ + # `save_jinja_files`` must be skipped to be able to save from a processor + kwargs.pop("save_jinja_files", None) if kwargs: raise ValueError( f"Kwargs {list(kwargs.keys())} are not supported by `MistralCommonTokenizer.save_pretrained`." diff --git a/tests/models/voxtral/__init__.py b/tests/models/voxtral/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/models/voxtral/test_modeling_voxtral.py b/tests/models/voxtral/test_modeling_voxtral.py new file mode 100644 index 0000000000..96bf971d1b --- /dev/null +++ b/tests/models/voxtral/test_modeling_voxtral.py @@ -0,0 +1,472 @@ +# Copyright 2024 The HuggingFace Inc. team. 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 of the License 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. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Testing suite for the PyTorch Voxtral model.""" + +import tempfile +import unittest + +from transformers import ( + AutoProcessor, + VoxtralConfig, + VoxtralForConditionalGeneration, + is_torch_available, +) +from transformers.testing_utils import ( + cleanup, + require_torch, + require_torch_sdpa, + slow, + torch_device, +) + +from ...generation.test_utils import GenerationTesterMixin +from ...test_configuration_common import ConfigTester +from ...test_modeling_common import ModelTesterMixin, floats_tensor, ids_tensor + + +if is_torch_available(): + import torch + + +class VoxtralModelTester: + def __init__( + self, + parent, + ignore_index=-100, + audio_token_id=0, + seq_length=35, + feat_seq_length=60, + text_config={ + "model_type": "llama", + "intermediate_size": 36, + "initializer_range": 0.02, + "hidden_size": 32, + "max_position_embeddings": 52, + "num_hidden_layers": 2, + "num_attention_heads": 4, + "num_key_value_heads": 2, + "use_labels": True, + "use_mrope": False, + "vocab_size": 99, + "head_dim": 8, + }, + is_training=True, + audio_config={ + "model_type": "voxtral_encoder", + "hidden_size": 16, + "num_attention_heads": 4, + "intermediate_size": 16, + "num_hidden_layers": 2, + "num_mel_bins": 80, + "max_source_positions": 30, + "initializer_range": 0.02, + }, + ): + self.parent = parent + self.ignore_index = ignore_index + self.audio_token_id = audio_token_id + self.text_config = text_config + self.audio_config = audio_config + self.seq_length = seq_length + self.feat_seq_length = feat_seq_length + + self.num_hidden_layers = text_config["num_hidden_layers"] + self.vocab_size = text_config["vocab_size"] + self.hidden_size = text_config["hidden_size"] + self.num_attention_heads = text_config["num_attention_heads"] + self.is_training = is_training + + self.batch_size = 3 + self.encoder_seq_length = seq_length + + def get_config(self): + return VoxtralConfig( + text_config=self.text_config, + audio_config=self.audio_config, + ignore_index=self.ignore_index, + audio_token_id=self.audio_token_id, + ) + + def prepare_config_and_inputs(self): + input_features_values = floats_tensor( + [ + self.batch_size, + self.audio_config["num_mel_bins"], + self.feat_seq_length, + ] + ) + config = self.get_config() + return config, input_features_values + + def prepare_config_and_inputs_for_common(self): + config_and_inputs = self.prepare_config_and_inputs() + config, input_features_values = config_and_inputs + num_audio_tokens_per_batch_idx = 30 + + input_ids = ids_tensor([self.batch_size, self.seq_length], config.text_config.vocab_size - 1) + 1 + attention_mask = torch.ones(input_ids.shape, dtype=torch.long).to(torch_device) + attention_mask[:, :1] = 0 + + input_ids[:, 1 : 1 + num_audio_tokens_per_batch_idx] = config.audio_token_id + inputs_dict = { + "input_ids": input_ids, + "attention_mask": attention_mask, + "input_features": input_features_values, + } + return config, inputs_dict + + +@require_torch +class VoxtralForConditionalGenerationModelTest(ModelTesterMixin, GenerationTesterMixin, unittest.TestCase): + """ + Model tester for `VoxtralForConditionalGeneration`. + """ + + all_model_classes = (VoxtralForConditionalGeneration,) if is_torch_available() else () + pipeline_model_mapping = ( + {"text-to-speech": VoxtralForConditionalGeneration, "audio-text-to-text": VoxtralForConditionalGeneration} + if is_torch_available() + else {} + ) + test_pruning = False + test_head_masking = False + _is_composite = True + + def setUp(self): + self.model_tester = VoxtralModelTester(self) + self.config_tester = ConfigTester(self, config_class=VoxtralConfig, has_text_modality=False) + + @unittest.skip( + reason="This test does not apply to Voxtral since inputs_embeds corresponding to audio tokens are replaced when input features are provided." + ) + def test_inputs_embeds_matches_input_ids(self): + pass + + @require_torch_sdpa + def test_sdpa_can_dispatch_composite_models(self): + # overwrite because Voxtral is audio+text model (not vision+text) + if not self.has_attentions: + self.skipTest(reason="Model architecture does not support attentions") + + if not self._is_composite: + self.skipTest(f"{self.all_model_classes[0].__name__} does not support SDPA") + + for model_class in self.all_model_classes: + config, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common() + model = model_class(config) + + with tempfile.TemporaryDirectory() as tmpdirname: + model.save_pretrained(tmpdirname) + model_sdpa = model_class.from_pretrained(tmpdirname) + model_sdpa = model_sdpa.eval().to(torch_device) + + text_attn = "sdpa" if model.language_model._supports_sdpa else "eager" + vision_attn = "sdpa" if model.audio_tower._supports_sdpa else "eager" + + # `None` as it is the requested one which will be assigned to each sub-config + # Sub-model will dispatch to SDPA if it can (checked below that `SDPA` layers are present) + self.assertTrue(model_sdpa.config._attn_implementation == "sdpa") + self.assertTrue(model.language_model.config._attn_implementation == text_attn) + self.assertTrue(model.audio_tower.config._attn_implementation == vision_attn) + + model_eager = model_class.from_pretrained(tmpdirname, attn_implementation="eager") + model_eager = model_eager.eval().to(torch_device) + self.assertTrue(model_eager.config._attn_implementation == "eager") + self.assertTrue(model_eager.language_model.config._attn_implementation == "eager") + self.assertTrue(model_eager.audio_tower.config._attn_implementation == "eager") + + for name, submodule in model_eager.named_modules(): + class_name = submodule.__class__.__name__ + if "SdpaAttention" in class_name or "SdpaSelfAttention" in class_name: + raise ValueError("The eager model should not have SDPA attention layers") + + +@require_torch +class VoxtralForConditionalGenerationIntegrationTest(unittest.TestCase): + def setUp(self): + self.checkpoint_name = "mistralai/Voxtral-Mini-3B-2507" + self.dtype = torch.bfloat16 + self.processor = AutoProcessor.from_pretrained(self.checkpoint_name) + + def tearDown(self): + cleanup(torch_device, gc_collect=True) + + @slow + def test_mini_single_turn_audio_only(self): + """ + reproducer: https://gist.github.com/eustlb/c5e0e0a12e84e3d575151ba63d17e4cf + disclaimer: Perfect token matching cannot be achieved due to floating-point arithmetic differences between vLLM and Transformers implementations. + """ + conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/dude_where_is_my_car.wav", + }, + ], + } + ] + + model = VoxtralForConditionalGeneration.from_pretrained( + self.checkpoint_name, torch_dtype=self.dtype, device_map=torch_device + ) + + inputs = self.processor.apply_chat_template(conversation) + inputs = inputs.to(torch_device, dtype=self.dtype) + + outputs = model.generate(**inputs, do_sample=False, max_new_tokens=500) + decoded_outputs = self.processor.batch_decode(outputs, skip_special_tokens=True) + EXPECTED_OUTPUT = [ + 'The audio is a humorous exchange between two individuals, likely friends or acquaintances, about tattoos. Here\'s a breakdown:\n\n1. **Initial Reaction**: One person (let\'s call him A) is surprised to see the other person (let\'s call him B) has a tattoo. A asks if B has a tattoo, and B confirms.\n\n2. **Tattoo Interpretation**: A then asks what B\'s tattoo says, and B responds with "sweet." This exchange is repeated multiple times, with A asking what B\'s tattoo says, and B always answering "sweet."\n\n3. **Confusion**: A seems confused and asks what B\'s tattoo says multiple times, each time getting the same response. This leads to a humorous back-and-forth.\n\n4. **Clarification**: Eventually, B clarifies that A\'s tattoo says "dude" and A\'s says "sweet." This is the punchline of the joke, as A had been asking about B\'s tattoo but not his own.\n\n5. **Final Exchange**: B then asks what A\'s tattoo says, and A responds with "sweet," leading to a final round of confusion.\n\nThe humor comes from the repetition of the word "sweet" and the confusion that arises from A\'s lack of self-awareness about his own tattoo.' + ] + self.assertEqual(decoded_outputs, EXPECTED_OUTPUT) + + @slow + def test_mini_single_turn_text_and_audio(self): + """ + reproducer: https://gist.github.com/eustlb/c5e0e0a12e84e3d575151ba63d17e4cf + disclaimer: Perfect token matching cannot be achieved due to floating-point arithmetic differences between vLLM and Transformers implementations. + """ + conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3", + }, + {"type": "text", "text": "What can you tell me about this audio?"}, + ], + } + ] + + model = VoxtralForConditionalGeneration.from_pretrained( + self.checkpoint_name, torch_dtype=self.dtype, device_map=torch_device + ) + + inputs = self.processor.apply_chat_template(conversation) + inputs = inputs.to(torch_device, dtype=self.dtype) + + outputs = model.generate(**inputs, do_sample=False, max_new_tokens=500) + decoded_outputs = self.processor.batch_decode(outputs, skip_special_tokens=True) + + EXPECTED_OUTPUT = [ + "What can you tell me about this audio?This audio is a farewell address by President Barack Obama, delivered in Chicago. In the speech, he reflects on his eight years in office, highlighting the resilience, hope, and unity of the American people. He expresses gratitude for the conversations he had with the public, which kept him honest and inspired. The president also emphasizes the importance of self-government and civic engagement, encouraging Americans to participate in their democracy actively. He concludes by expressing optimism about the country's future and his commitment to serving as a citizen." + ] + self.assertEqual(decoded_outputs, EXPECTED_OUTPUT) + + @slow + def test_mini_single_turn_text_and_multiple_audios(self): + """ + reproducer: https://gist.github.com/eustlb/c5e0e0a12e84e3d575151ba63d17e4cf + disclaimer: Perfect token matching cannot be achieved due to floating-point arithmetic differences between vLLM and Transformers implementations. + """ + conversation = [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/mary_had_lamb.mp3", + }, + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/winning_call.mp3", + }, + {"type": "text", "text": "What sport and what nursery rhyme are referenced?"}, + ], + } + ] + + model = VoxtralForConditionalGeneration.from_pretrained( + self.checkpoint_name, torch_dtype=self.dtype, device_map=torch_device + ) + + inputs = self.processor.apply_chat_template(conversation) + inputs = inputs.to(torch_device, dtype=self.dtype) + + outputs = model.generate(**inputs, do_sample=False, max_new_tokens=500) + decoded_outputs = self.processor.batch_decode(outputs, skip_special_tokens=True) + + EXPECTED_OUTPUT = [ + 'What sport and what nursery rhyme are referenced?The audio references both a nursery rhyme and a baseball game. The nursery rhyme is "Mary Had a Little Lamb," and the baseball game is a playoff game between the Baltimore Orioles and the Oakland Athletics.' + ] + self.assertEqual(decoded_outputs, EXPECTED_OUTPUT) + + @slow + def test_mini_single_turn_text_only(self): + """ + reproducer: https://gist.github.com/eustlb/c5e0e0a12e84e3d575151ba63d17e4cf + disclaimer: Perfect token matching cannot be achieved due to floating-point arithmetic differences between vLLM and Transformers implementations. + """ + conversation = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Hello, how are you doing today?"}, + ], + } + ] + + model = VoxtralForConditionalGeneration.from_pretrained( + self.checkpoint_name, torch_dtype=self.dtype, device_map=torch_device + ) + + inputs = self.processor.apply_chat_template(conversation) + inputs = inputs.to(torch_device, dtype=self.dtype) + + outputs = model.generate(**inputs, do_sample=False, max_new_tokens=500) + decoded_outputs = self.processor.batch_decode(outputs, skip_special_tokens=True) + + EXPECTED_OUTPUT = [ + "Hello, how are you doing today?Hello! I'm functioning as intended, thank you. How about you? How's your day going?" + ] + self.assertEqual(decoded_outputs, EXPECTED_OUTPUT) + + @slow + def test_mini_single_turn_text_and_multiple_audios_batched(self): + """ + reproducer: https://gist.github.com/eustlb/c5e0e0a12e84e3d575151ba63d17e4cf + disclaimer: Perfect token matching cannot be achieved due to floating-point arithmetic differences between vLLM and Transformers implementations. + """ + conversations = [ + [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3", + }, + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/bcn_weather.mp3", + }, + { + "type": "text", + "text": "Who's speaking in the speach and what city's weather is being discussed?", + }, + ], + } + ], + [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/winning_call.mp3", + }, + {"type": "text", "text": "What can you tell me about this audio?"}, + ], + } + ], + ] + + model = VoxtralForConditionalGeneration.from_pretrained( + self.checkpoint_name, torch_dtype=self.dtype, device_map=torch_device + ) + + inputs = self.processor.apply_chat_template(conversations) + inputs = inputs.to(torch_device, dtype=self.dtype) + + outputs = model.generate(**inputs, do_sample=False, max_new_tokens=500) + decoded_outputs = self.processor.batch_decode(outputs, skip_special_tokens=True) + + EXPECTED_OUTPUT = [ + "Who's speaking in the speach and what city's weather is being discussed?The speaker in the speech is Barack Obama, and the weather being discussed is in Barcelona.", + 'What can you tell me about this audio?This audio is a commentary of a baseball game, specifically a home run hit by Edgar Martinez. Here are some key points:\n\n- **Game Context**: The game is likely a playoff or championship game, as the commentator mentions the American League Championship.\n- **Play Description**: Edgar Martinez hits a home run, which is described as a "line drive" and a "base hit."\n- **Team Involvement**: The team is the Mariners, and the commentator is excited about their chances to win the championship.\n- **Emotional Tone**: The commentator expresses disbelief and excitement, using phrases like "I don\'t believe it" and "my, oh my."\n- **Player Involvement**: The commentator mentions Joy and Junior, likely referring to other players or commentators in the broadcast.', + ] + self.assertEqual(decoded_outputs, EXPECTED_OUTPUT) + + @slow + def test_mini_multi_turn_text_and_audio(self): + """ + reproducer: https://gist.github.com/eustlb/c5e0e0a12e84e3d575151ba63d17e4cf + disclaimer: Perfect token matching cannot be achieved due to floating-point arithmetic differences between vLLM and Transformers implementations. + """ + conversations = [ + [ + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3", + }, + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/bcn_weather.mp3", + }, + {"type": "text", "text": "Describe briefly what you can hear."}, + ], + }, + { + "role": "assistant", + "content": "The audio begins with the speaker delivering a farewell address in Chicago, reflecting on his eight years as president and expressing gratitude to the American people. The audio then transitions to a weather report, stating that it was 35 degrees in Barcelona the previous day, but the temperature would drop to minus 20 degrees the following day.", + }, + { + "role": "user", + "content": [ + { + "type": "audio", + "path": "https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/dude_where_is_my_car.wav", + }, + {"type": "text", "text": "Ok, now compare this new audio with the previous one."}, + ], + }, + ] + ] + + model = VoxtralForConditionalGeneration.from_pretrained( + self.checkpoint_name, torch_dtype=self.dtype, device_map=torch_device + ) + + inputs = self.processor.apply_chat_template(conversations) + inputs = inputs.to(torch_device, dtype=self.dtype) + + outputs = model.generate(**inputs, do_sample=False, max_new_tokens=500) + decoded_outputs = self.processor.batch_decode(outputs, skip_special_tokens=True) + + EXPECTED_OUTPUT = [ + 'Describe briefly what you can hear.The audio begins with the speaker delivering a farewell address in Chicago, reflecting on his eight years as president and expressing gratitude to the American people. The audio then transitions to a weather report, stating that it was 35 degrees in Barcelona the previous day, but the temperature would drop to minus 20 degrees the following day.Ok, now compare this new audio with the previous one.The new audio is a humorous conversation between two friends, one of whom has a tattoo. The speaker is excited to see the tattoo and asks what it says. The other friend repeatedly says "sweet" in response, leading to a playful exchange. The speaker then realizes the joke and says "your tattoo says dude, your tattoo says sweet, got it?" The previous audio was a farewell address by a president, reflecting on his time in office and expressing gratitude to the American people. The new audio is a casual, light-hearted conversation in contrast to the serious and reflective tone of the previous audio.' + ] + self.assertEqual(decoded_outputs, EXPECTED_OUTPUT) + + @slow + def test_transcribe_mode_audio_input(self): + """ + To test transcribe mode of the model, WER evaluation has been run to compare with the declared model performances. + see https://github.com/huggingface/transformers/pull/39429 PR's descrition. + disclaimer: Perfect token matching cannot be achieved due to floating-point arithmetic differences between vLLM and Transformers implementations. + """ + model = VoxtralForConditionalGeneration.from_pretrained( + self.checkpoint_name, torch_dtype=self.dtype, device_map=torch_device + ) + inputs = self.processor.apply_transcrition_request( + language="en", + audio="https://huggingface.co/datasets/hf-internal-testing/dummy-audio-samples/resolve/main/obama.mp3", + model_id=self.checkpoint_name, + ) + inputs = inputs.to(torch_device, dtype=self.dtype) + outputs = model.generate(**inputs, do_sample=False, max_new_tokens=500) + + decoded_outputs = self.processor.batch_decode(outputs, skip_special_tokens=True) + + EXPECTED_OUTPUT = [ + "lang:enThis week, I traveled to Chicago to deliver my final farewell address to the nation, following in the tradition of presidents before me. It was an opportunity to say thank you. Whether we've seen eye-to-eye or rarely agreed at all, my conversations with you, the American people, in living rooms and schools, at farms and on factory floors, at diners and on distant military outposts, All these conversations are what have kept me honest, kept me inspired, and kept me going. Every day, I learned from you. You made me a better president, and you made me a better man. Over the course of these eight years, I've seen the goodness, the resilience, and the hope of the American people. I've seen neighbors looking out for each other as we rescued our economy from the worst crisis of our lifetimes. I've hugged cancer survivors who finally know the security of affordable health care. I've seen communities like Joplin rebuild from disaster, and cities like Boston show the world that no terrorist will ever break the American spirit. I've seen the hopeful faces of young graduates and our newest military officers. I've mourned with grieving families searching for answers, and I found grace in a Charleston church. I've seen our scientists help a paralyzed man regain his sense of touch, and our wounded warriors walk again. I've seen our doctors and volunteers rebuild after earthquakes and stop pandemics in their tracks. I've learned from students who are building robots and curing diseases and who will change the world in ways we can't even imagine. I've seen the youngest of children remind us of our obligations to care for our refugees, to work in peace, and above all, to look out for each other. That's what's possible when we come together in the slow, hard, sometimes frustrating, but always vital work of self-government. But we can't take our democracy for granted. All of us, regardless of party, should throw ourselves into the work of citizenship. Not just when there's an election. Not just when our own narrow interest is at stake. But over the full span of a lifetime. If you're tired of arguing with strangers on the Internet, try to talk with one in real life. If something needs fixing, lace up your shoes and do some organizing. If you're disappointed by your elected officials, then grab a clipboard, get some signatures, and run for office yourself. Our success depends on our" + ] + self.assertEqual(decoded_outputs, EXPECTED_OUTPUT) diff --git a/tests/test_tokenization_mistral_common.py b/tests/test_tokenization_mistral_common.py index d225ec5570..150290cb4f 100644 --- a/tests/test_tokenization_mistral_common.py +++ b/tests/test_tokenization_mistral_common.py @@ -17,6 +17,7 @@ import unittest import numpy as np import torch +from huggingface_hub import hf_hub_download from transformers.models.auto.tokenization_auto import AutoTokenizer from transformers.testing_utils import require_mistral_common @@ -34,6 +35,12 @@ if is_mistral_common_available(): IMG_URL = "https://picsum.photos/id/237/200/300" IMG_BASE_64 = """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""" +AUDIO_NAMESPACE = "hf-internal-testing" +AUDIO_REPO_NAME = "dummy-audio-samples" +AUDIO_FILENAME = "bcn_weather.mp3" +AUDIO_URL = f"https://huggingface.co/datasets/{AUDIO_NAMESPACE}/{AUDIO_REPO_NAME}/resolve/main/{AUDIO_FILENAME}" +AUDIO_BASE_64 = """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TCB8BGPktKQFPDJZlktQqRstAgz6NqpMQU1FMy4xMDCqqqqqqqqqqqqqWFCaoAAc01MABG2hIGLlHOR52rTFi2EhbxyKm69DJ64iu/hVcrfoU5Vq//sUxPIAQ0AdHyMNImhXgeQkYIwFm9MFVaxuWqCxV1U4PjpMQU1FMy4xMDCqqqqqqqqqqqqqqqqquqoZgAAOAALlEkLBT2IspyoS6/AD5qiuztVbuyV3B4ORBNAKBSAW//sUxPEAQsg/GqMAaihdhmOgYAzEWAm9+dsbVBwrPddgrB9MQU1FMy4xMDBVVVVVVVVVVVW66higABNEC4CyBgmGwYpkDQddSESlQ+1tYQcbUsUHTfCNipnEhCWsBNxc//sUxPUAQ0wxHyEkYmhuCaRwkYj1WCdT0aTC1xor4lmUnG1MQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVWIQhIt1AACgIkrKkgTGlZ0FyEHUKLBIY85enatdC0VMSEM//sUxPaAQ6AhI4ClgGhtB6QwNIwUlU41oUS90QgtNr9VHapMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqKUDQTbAAAboJeoBgpuYgItvRxTJQOur/478LbdGkBU+mMedD//sUxPQAQxQ9ISSEYihtA+OkZIxNw4FjEIKy19by9SI2evtMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVhRBQCwQZLhQfVQMKD4P2JQR8bZAKP/a3+PVtBQXPDoeSQtyITI//sUxPOAQ2whHQYAyChbBOQkwKBE4oNgv2TAcrbu/V//i6pMQU1FMy4xMDCqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqppkalgACAJwo6zIGlWiDKB//sUxPYAw/hhHSMEaKhgB+QgYI2EhgOlH0zrG6iaFIQVuDlMQU1FMy4xMDBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxPSAQ2AhHyMkwChnhmOgZIwlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxPaAQ+QnHySwZmBmiGOgwI1dVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxPIAQ4wvIYQEZuBMAyQgkIxEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxPMAQ1AdIYGkYmhdhCPkkwDVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxPQAQwQXGQSlIkhsBCQklIwVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//sUxOkBwzwXHyGYwCAQACNUAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV""" + @require_mistral_common class TestMistralCommonTokenizer(unittest.TestCase): @@ -47,6 +54,13 @@ class TestMistralCommonTokenizer(unittest.TestCase): cls.ref_tokenizer: MistralTokenizer = MistralTokenizer.from_hf_hub( "hf-internal-testing/namespace-mistralai-repo_name-Mistral-Small-3.1-24B-Instruct-2503" ) + # cls.tokenizer_audio: MistralCommonTokenizer = AutoTokenizer.from_pretrained( + # "hf-internal-testing/namesspace-mistralai-repo_name-Voxtral-Mini-3B-2507" + # ) + cls.tokenizer_audio: MistralCommonTokenizer = AutoTokenizer.from_pretrained("mistralai/Voxtral-Mini-3B-2507") + cls.ref_tokenizer_audio: MistralCommonTokenizer = MistralTokenizer.from_hf_hub( + "mistralai/Voxtral-Mini-3B-2507" + ) cls.fixture_conversations = [ [ {"role": "system", "content": "You are a helpful assistant."}, @@ -78,8 +92,8 @@ class TestMistralCommonTokenizer(unittest.TestCase): def test_save_pretrained(self): with tempfile.TemporaryDirectory() as tmp_dir: - tmp_file = self.tokenizer.save_pretrained(tmp_dir)[0] - loaded_tokenizer = MistralCommonTokenizer.from_pretrained(tmp_file) + self.tokenizer.save_pretrained(tmp_dir) + loaded_tokenizer = MistralCommonTokenizer.from_pretrained(tmp_dir) self.assertIsNotNone(loaded_tokenizer) self.assertEqual(self.tokenizer.get_vocab(), loaded_tokenizer.get_vocab()) @@ -801,6 +815,62 @@ class TestMistralCommonTokenizer(unittest.TestCase): self.assertEqual(output_dict["input_ids"].tolist()[0], expected_tokenized.tokens) self.assertTrue(torch.allclose(output_dict["pixel_values"], torch.tensor(expected_tokenized.images))) + def test_apply_chat_template_with_audio(self): + ref_conversation = conversation = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "What is this?"}, + { + "type": "input_audio", + "input_audio": { + "data": AUDIO_BASE_64, + "format": "wav", + }, + }, + ], + }, + ] + + expected_tokenized = self.ref_tokenizer_audio.encode_chat_completion( + ChatCompletionRequest.from_openai(ref_conversation) + ) + audio_contents = [ + { + "type": "audio", + "url": AUDIO_URL, + }, + { + "type": "audio", + "path": hf_hub_download( + repo_id=f"{AUDIO_NAMESPACE}/{AUDIO_REPO_NAME}", filename=AUDIO_FILENAME, repo_type="dataset" + ), + }, + {"type": "audio", "base64": AUDIO_BASE_64}, + ] + for audio_content in audio_contents: + conversation = [ + { + "role": "user", + "content": [{"type": "text", "text": "What is this?"}, audio_content], + }, + ] + + output = self.tokenizer_audio.apply_chat_template(conversation, tokenize=True) + self.assertEqual(output, expected_tokenized.tokens) + + output_dict = self.tokenizer_audio.apply_chat_template(conversation, tokenize=True, return_dict=True) + self.assertEqual(output_dict["input_ids"], expected_tokenized.tokens) + self.assertEqual(len(output_dict["audio"]), len(expected_tokenized.audios)) + for o, e in zip(output_dict["audio"], expected_tokenized.audios): + audio_array = e.audio_array + self.assertTrue(np.allclose(o, audio_array)) + + with self.assertRaises(NotImplementedError): + output_dict = self.tokenizer_audio.apply_chat_template( + conversation, tokenize=True, return_dict=True, return_tensors="pt" + ) + def test_appsly_chat_template_with_truncation(self): conversation = [ {"role": "system", "content": "You are a helpful assistant."},