Transformers documentation
Image-texte-vers-texte
Image-texte-vers-texte
Les modèles image-texte-vers-texte, également connus sous le nom de modèles de langage visuel (VLM), sont des modèles de langage qui acceptent une image en entrée. Ces modèles peuvent traiter diverses tâches, allant de la réponse à des questions visuelles à la segmentation d’images. Cette tâche présente de nombreuses similitudes avec la tâche image-vers-texte, mais avec certains cas d’utilisation qui se chevauchent, comme la légende d’images. Les modèles image-vers-texte n’acceptent que des images en entrée et accomplissent souvent une tâche spécifique, tandis que les VLM acceptent des entrées texte et image ouvertes et sont des modèles plus généralistes.
Dans ce guide, nous présentons un bref aperçu des VLM et montrons comment les utiliser avec Transformers pour l'inférence.
Pour commencer, il existe plusieurs types de VLM :
- modèles de base utilisés pour le fine-tuning
- Modèles de chat affinés pour la conversation
- modèles affinés par instruction
Ce guide se concentre sur l'inférence avec un modèle ajusté sur des instructions.
Commençons par installer les dépendances.
pip install -q transformers accelerate pip install flash-attn --no-build-isolation
Initialisons le modèle et le processeur.
from transformers import AutoProcessor, AutoModelForImageTextToText
from accelerate import Accelerator
import torch
device = Accelerator().device
model = AutoModelForImageTextToText.from_pretrained(
"Qwen/Qwen3-VL-4B-Instruct",
dtype=torch.bfloat16,
attn_implementation="flash_attention_2",
).to(device)Les modèles à résolution dynamique tels que Qwen3-VL et Qwen3.5 utilisent par défaut une valeur d’environ 16 MP pour
longest_edge, ce qui peut générer plus de 10 000 jetons visuels par image et provoquer un blocage de l’inférence ou une erreur de mémoire insuffisante sur des GPU grand public. Définissezmin_pixelsetmax_pixelslors du chargement du processeur pour limiter cette valeur.
processor = AutoProcessor.from_pretrained(
"Qwen/Qwen3-VL-4B-Instruct",
min_pixels=224 * 224,
max_pixels=1024 * 1024,
)Ce modèle dispose d’un chat template qui aide l’utilisateur à analyser les sorties de chat. De plus, le modèle peut également accepter plusieurs images en entrée dans une seule conversation ou un seul message. Nous allons maintenant préparer les entrées.
Les entrées d’image ressemblent à ce qui suit.


Structurez votre conversation comme indiqué ci-dessous pour un prompt unique avec des entrées image et texte.
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png"},
{"type": "text", "text": "What do we see in this image?"},
]
}
]Alternez entre les rôles user et assistant pour ancrer le modèle dans le contexte antérieur et générer de meilleures réponses.
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png"},
{"type": "text", "text": "What do we see in this image?"},
]
},
{
"role": "assistant",
"content": [
{"type": "text", "text": "In this image we can see two cats on the nets."},
]
},
{
"role": "user",
"content": [
{"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg"},
{"type": "text", "text": "And how about this image?"},
]
},
]Nous allons maintenant appeler la méthode apply_chat_template() des processeurs pour prétraiter sa sortie ainsi que les entrées d’images.
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(device)Nous pouvons maintenant passer les entrées prétraitées au modèle.
input_len = len(inputs.input_ids[0])
with torch.no_grad():
generated_ids = model.generate(**inputs, max_new_tokens=200)
generated_texts = processor.batch_decode(generated_ids[:, input_len:], skip_special_tokens=True)
print(generated_texts)
## ['In this image we can see flowers, plants and insect.']Pipeline
La façon la plus rapide de commencer est d’utiliser l’API Pipeline. Spécifiez la tâche "image-text-to-text" et le modèle que vous souhaitez utiliser.
from transformers import pipeline
pipe = pipeline("image-text-to-text", model="llava-hf/llava-interleave-qwen-0.5b-hf")L'exemple ci-dessous utilise des modèles de chat pour formater les entrées textuelles.
messages = [
{
"role": "user",
"content": [
{
"type": "image",
"image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
},
{"type": "text", "text": "Describe this image."},
],
},
{
"role": "assistant",
"content": [
{"type": "text", "text": "There's a pink flower"},
],
},
]Transmettez le texte et l'image au format du modèle de chat à Pipeline et définissez return_full_text=False pour supprimer l'entrée de la sortie générée.
outputs = pipe(text=messages, max_new_tokens=20, return_full_text=False)
outputs[0]["generated_text"]
# with a yellow center in the foreground. The flower is surrounded by red and white flowers with green stemsSi vous préférez, vous pouvez également charger les images séparément et les passer au pipeline de la manière suivante :
import requests
from PIL import Image
from transformers import pipeline
pipe = pipeline("image-text-to-text", model="HuggingFaceTB/SmolVLM-256M-Instruct")
img_urls = [
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png",
"https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg",
]
images = [
Image.open(requests.get(img_urls[0], stream=True).raw),
Image.open(requests.get(img_urls[1], stream=True).raw),
]
messages = [
{
"role": "user",
"content": [
{"type": "image"},
{"type": "image"},
{"type": "text", "text": "What do you see in these images?"},
],
}
]
outputs = pipe(text=messages, images=images, max_new_tokens=50, return_full_text=False)
outputs[0]["generated_text"]
" In the first image, there are two cats sitting on a plant. In the second image, there are flowers with a pinkish hue."Les images seront toujours incluses dans le champ "input_text" de la sortie :
outputs[0]['input_text']
"""
[{'role': 'user',
'content': [{'type': 'image',
'image': <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=622x412>},
{'type': 'image',
'image': <PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=5184x3456>},
{'type': 'text', 'text': 'What do you see in these images?'}]}]
"""Streaming
Nous pouvons utiliser le streaming de texte pour améliorer l’expérience de génération. Transformers prend en charge le streaming via les classes TextStreamer ou TextIteratorStreamer. Nous utiliserons TextIteratorStreamer avec IDEFICS-8B.
Supposons que nous disposions d’une application qui conserve l’historique de la conversation et reçoit la nouvelle saisie de l’utilisateur. Nous prétraitons les entrées comme d’habitude et initialisons TextIteratorStreamer pour gérer la génération dans un thread séparé. Cela permet de diffuser en temps réel les jetons de texte générés. Tous les arguments de génération peuvent être transmis à TextIteratorStreamer.
import time
from transformers import TextIteratorStreamer
from threading import Thread
def model_inference(
user_prompt,
chat_history,
max_new_tokens,
images
):
user_prompt = {
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": user_prompt},
]
}
chat_history.append(user_prompt)
streamer = TextIteratorStreamer(
processor.tokenizer,
skip_prompt=True,
timeout=5.0,
)
generation_args = {
"max_new_tokens": max_new_tokens,
"streamer": streamer,
"do_sample": False
}
# add_generation_prompt=True makes model generate bot response
prompt = processor.apply_chat_template(chat_history, add_generation_prompt=True)
inputs = processor(
text=prompt,
images=images,
return_tensors="pt",
).to(device)
generation_args.update(inputs)
thread = Thread(
target=model.generate,
kwargs=generation_args,
)
thread.start()
acc_text = ""
for text_token in streamer:
time.sleep(0.04)
acc_text += text_token
if acc_text.endswith("<end_of_utterance>"):
acc_text = acc_text[:-18]
yield acc_text
thread.join()Appelons maintenant la fonction model_inference que nous avons créée et diffusons les valeurs.
generator = model_inference(
user_prompt="And what is in this image?",
chat_history=messages[:2],
max_new_tokens=100,
images=images
)
for value in generator:
print(value)
# In
# In this
# In this image ...Faire tenir des modèles sur du matériel plus petit
Les VLM sont souvent volumineuses et nécessitent d’être optimisées pour s’exécuter sur du matériel plus modeste. Transformers prend en charge de nombreuses bibliothèques de quantification de modèles, et nous n’illustrerons ici que la quantification int8 avec Quanto. La quantification int8 permet d’améliorer l’efficacité mémoire jusqu’à 75 % (si tous les poids sont quantifiés). Toutefois, ce n’est pas sans inconvénient : comme la précision 8 bits n’est pas une précision native de CUDA, les poids sont quantifiés et déquantifiés en continu, ce qui entraîne une latence supplémentaire.
Installez d'abord les dépendances.
pip install -U optimum-quanto bitsandbytes
Pour quantiser un modèle lors de son chargement, il faut d'abord créer QuantoConfig. Ensuite, chargez le modèle comme d'habitude, mais passez quantization_config lors de l'initialisation du modèle.
from transformers import AutoModelForImageTextToText, QuantoConfig
model_id = "Qwen/Qwen3-VL-4B-Instruct"
quantization_config = QuantoConfig(weights="int8")
quantized_model = AutoModelForImageTextToText.from_pretrained(
model_id, device_map="auto", quantization_config=quantization_config
)
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/cats.png"},
{"type": "text", "text": "What do we see in this image?"},
]
},
]
inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(quantized_model.device)
input_len = len(inputs.input_ids[0])
with torch.no_grad():
generated_ids = quantized_model.generate(**inputs, cache_implementation="static", max_new_tokens=100)
generated_texts = processor.batch_decode(generated_ids[:, input_len:], skip_special_tokens=True)
print(generated_texts[0])
## ['In this image, we see two tabby cats resting on a large, tangled pile of fishing nets. The nets are a mix of brown, orange, and red colors, with some blue and green ropes visible in the background. The cats appear relaxed and comfortable, nestled into the fibers of the nets. One cat is in the foreground, looking slightly to the side, while the other is positioned further back, looking directly at the camera. The scene suggests a coastal or fishing-related setting, possibly near']Et c’est tout, nous pouvons utiliser le modèle de la même manière, sans aucun changement.
Discussion itérative avec cache
Si vous développez des agents de chat multimodaux, vous transmettez probablement les mêmes images ou audio d’un tour à l’autre. La mise en cache vous permet de réutiliser leurs représentations encodées plutôt que de les re-traiter à chaque fois, réduisant ainsi les calculs redondants.
Comme les modèles textuels, les modèles multimodaux suivent l’historique de la conversation à l’aide d’un chat template. La différence essentielle est qu’il applique le chat template uniquement au format "text" et traite le nouveau contenu multimodal séparément. Si vous appliquez le modèle avec un traitement, l’ensemble de la conversation est retraité à chaque tour, car Jinja ne peut pas distinguer les entrées déjà encodées des nouvelles.
L’exemple ci-dessous illustre ce schéma. Pour les modèles de texte uniquement, consultez le guide sur la génération itérative.
Voici un exemple simple en Python illustrant ce motif. Pour un exemple avec des modèles textuels uniquement, reportez-vous à ce guide.
import torch
from transformers import AutoProcessor, Gemma4ForConditionalGeneration, TextStreamer
from transformers.cache_utils import DynamicCache
from transformers.image_utils import load_image
from transformers.audio_utils import load_audio
model_id = 'google/gemma-4-E2B-it'
processor = AutoProcessor.from_pretrained(model_id)
model = Gemma4ForConditionalGeneration.from_pretrained(model_id, dtype=torch.float32, device_map='cpu')
past_key_values = DynamicCache()
streamer = TextStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)
## Turn 1: multimodal input (text + image + audio)
audio = load_audio("https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/jfk.wav")
image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/bee.jpg")
messages = [
{
"role": "user",
"content": [
{"type": "text", "text": "In detail, describe the following audio and image."},
{"type": "audio"},
{"type": "image"},
],
},
]
input_string = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
inputs = processor(text=input_string, images=[image], audio=[audio], return_tensors='pt')
output = model.generate(**inputs, past_key_values=past_key_values, max_new_tokens=1024, do_sample=False, streamer=streamer)
first_response = processor.decode(output[0][inputs['input_ids'].shape[1]:], skip_special_tokens=True)
## Turn 2: text-only follow-up (reuses KV cache, no new media)
messages.append({"role": "assistant", "content": [{"type": "text", "text": first_response}]})
cached_string = processor.apply_chat_template(messages, tokenize=False)
messages.append({"role": "user", "content": [{"type": "text", "text": "Summarize the previous descriptions in one sentence."}]})
full_string = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
new_string = full_string[len(cached_string):]
new_input_ids = processor.tokenizer(new_string, add_special_tokens=False, return_tensors='pt')['input_ids']
# Build and pass an attention mask only if batched input or there is padding
# attention_mask = torch.ones(1, past_key_values.get_seq_length() + new_input_ids.shape[1], dtype=torch.long)
output2 = model.generate(input_ids=new_input_ids, past_key_values=past_key_values, max_new_tokens=1024, do_sample=False, streamer=streamer)
second_response = processor.decode(output2[0][new_input_ids.shape[1]:], skip_special_tokens=True)
## Turn 3: new image (must go through processor for image token expansion + encoding)
image2 = load_image("https://huggingface.co/datasets/Xenova/transformers.js-docs/resolve/main/artemis.jpeg")
messages.append({"role": "assistant", "content": [{"type": "text", "text": second_response}]})
cached_string2 = processor.apply_chat_template(messages, tokenize=False)
messages.append({"role": "user", "content": [{"type": "text", "text": "Describe this image."}, {"type": "image"}]})
full_string2 = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
new_string2 = full_string2[len(cached_string2):]
new_inputs2 = processor(text=new_string2, images=[image2], add_special_tokens=False, return_tensors='pt')
# new_inputs2['attention_mask'] = torch.ones(1, past_key_values.get_seq_length() + new_inputs2['input_ids'].shape[1], dtype=torch.long)
new_inputs2['past_key_values'] = past_key_values
output3 = model.generate(**new_inputs2, max_new_tokens=1024, do_sample=False, streamer=streamer)Lecture complémentaire
Voici d'autres ressources pour la tâche image-texte-vers-texte.
- La page de la tâche image-texte-vers-texte présente les types de modèles, les cas d’usage, les jeux de données et bien plus encore.
- Explication des modèles de vision et de langage est un article de blog qui couvre tout ce qu'il faut savoir sur les modèles de vision et de langage et l'ajustement fin supervisé à l'aide de TRL.
- Apprenez à affiner des modèles de vision et de langage à l'aide de TRL