Les modèles visuo-langagiers (VLM) sont des architectures neuronales conçues pour traiter simultanément les données visuelles et textuelles, permettant d'aborder des tâches variées comme la réponse à des questions visuelles ou la génération de légendes d'images. Cet article explore leurs composants fondamentaux, leur fonctionnement, les critères de sélection, ainsi que les méthodes d'inférence et de fine-tuning à l'aide de la bibliothèque TRL.
Définition des modèles visuo-langagiers
Un modèle visuo-langagier désigne un système multimodal capable d'extraire des informations à partir d'images et de texte pour produire des sorties textuelles. Les grandes versions (LVLM) démontrent une excellente capacité de généralisation hors distribution et traitent efficacement divers types d'imgaes, y compris les documents numérisés et les captures d'écran web. Leurs applications couvrent la description d'images, la reconnaissance visuelle guidée par instructions, la compréhension documentaire et l'analyse spatiale (localisation d'objets par coordonnées ou masques de segmentation).
Panorama des modèles open source
Le Hub Hugging Face héberge de nombreux modèles visuo-langagiers open source. Voici un échantillon représentatif :
| Modèle | Licence permissive | Taille | Résolution image | Fonctionnalités supplémentaires |
|---|---|---|---|---|
| LLaVA 1.6 (Hermes 34B) | 34B | 672x672 | ||
| deepseek-vl-7b-base | 7B | 384x384 | ||
| DeepSeek-VL-Chat | 7B | 384x384 | Mode conversation | |
| moondream2 | ~2B | 378x378 | ||
| CogVLM-Chat | 17B | 490x490 | Ancrage spatial, chat | |
| Fuyu-8B | 8B | 300x300 | Détection de texte intégré | |
| KOSMOS-2 | ~2B | 224x224 | Ancrage spatial, détection zero-shot | |
| Qwen-VL-Chat | 4B | 448x448 | Mode conversation | |
| Yi-VL-34B | 34B | 448x448 | Bilingue (EN, ZH) |
Stratégies de sélection de modèle
Pour identifier le modèle adapté à votre cas d'usage, plusieurs ressources d'évaluation existent :
- Vision Arena : classement basé sur des votes humains anonymes où les utilisateurs comparent des sorties générées par deux modèles différents.
- Open VLM Leaderboard : évaluation automatisée selon des métriques standardisées avec filtrage par taille ou type de licence.
- Benchmarks spécialisés : MMMU (raisonnement expert multisectoriel), MMBench (évaluation de 20 compétences visuelles), MathVista (mathématiques visuelles), AI2D (compréhension de schémas).
Un exemple d'évaluation avec LMMS-Eval :
accelerate launch --num_processes=8 -m lmms_eval \
--model llava \
--model_args pretrained="liuhaotian/llava-v1.5-7b" \
--tasks mme,mmbench_en \
--batch_size 1 \
--log_samples \
--output_path ./logs/
Architectures techniques
Les modèles visuo-langagiers combinent généralement trois composants : un encodeur d'image (souvent basé sur CLIP), un projecteur d'alignement (réseau dense) et un décodeur textuel autorégressif. Deux stratégies d'entraînement dominent :
- Entraînement séquentiel : gel de l'encodeur et du décodeur pendant la phase d'alignement (projecteur uniquement), puis dégel partiel pour le fine-tuning global.
- Apprentissage de bout en bout : entraînement simultané de tous les composants, plus coûteux en calcul mais potentiellement plus performant.
Certains modèles comme Fuyu-8B adoptent une approche alternative en injectant directement les patchs d'image dans le décodeur sans encodeur intermédiaire.
Inférence avec Transformers
Voici comment effectuer une inférence avec LLaVA-NeXT :
from transformers import LlavaNextProcessor, LlavaNextForConditionalGeneration
import torch
from PIL import Image
import requests
# Configuration initiale
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
processor = LlavaNextProcessor.from_pretrained("llava-hf/llava-v1.6-mistral-7b-hf")
model = LlavaNextForConditionalGeneration.from_pretrained(
"llava-hf/llava-v1.6-mistral-7b-hf",
torch_dtype=torch.float16,
low_cpu_mem_usage=True
).to(device)
# Chargement image et formulation du prompt
image_url = "https://github.com/haotian-liu/LLaVA/blob/1a91fc274d7c35a9b50b3cb29c4247ae5837ce39/images/llava_v1_5_radar.jpg?raw=true"
img = Image.open(requests.get(image_url, stream=True).raw)
text_prompt = "[INST] <image>\nDécrivez le contenu de cette image. [/INST]"
# Traitement et génération
inputs = processor(text_prompt, img, return_tensors="pt").to(device)
generated = model.generate(**inputs, max_new_tokens=150)
print(processor.decode(generated[0], skip_special_tokens=True))
</image>
Fine-tuning avec TRL
La bibliothèque TRL intègre désormais le support expérimental des modèles visuo-langagiers via SFTTrainer. Voici un exemple de fine-tuning sur LLaVA 1.5 avec le jeu de données llava-instruct :
from transformers import AutoTokenizer, AutoProcessor, LlavaForConditionalGeneration
from trl import SFTTrainer
import torch
# Définition du template de chat
CHAT_TEMPLATE = """Un dialogue entre un utilisateur curieux et un assistant IA. L'assistant fournit des réponses détaillées et polies.
{% for message in messages %}
{% if message['role'] == 'user' %}
UTILISATEUR: {% else %}
ASSISTANT: {% endif %}
{% for item in message['content'] %}
{% if item['type'] == 'text' %}{{ item['text'] }}
{% elif item['type'] == 'image' %}<image>
{% endif %}
{% endfor %}
{% if message['role'] == 'user' %} {% else %}{{eos_token}}
{% endif %}
{% endfor %}"""
# Initialisation modèle et tokenizer
model_id = "llava-hf/llava-1.5-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_id)
tokenizer.chat_template = CHAT_TEMPLATE
processor = AutoProcessor.from_pretrained(model_id)
model = LlavaForConditionalGeneration.from_pretrained(model_id, torch_dtype=torch.float16)
# Fonction de collecte de données
class VLMCollator:
def __init__(self, proc):
self.processor = proc
def __call__(self, batch):
text_sequences = []
image_list = []
for ex in batch:
formatted_text = self.processor.tokenizer.apply_chat_template(
ex["messages"], tokenize=False, add_generation_prompt=False
)
text_sequences.append(formatted_text)
image_list.append(ex["images"][0])
processed = self.processor(
text_sequences, image_list, return_tensors="pt", padding=True
)
labels = processed["input_ids"].clone()
labels[labels == self.processor.tokenizer.pad_token_id] = -100
processed["labels"] = labels
return processed
# Entraînement
trainer = SFTTrainer(
model=model,
train_dataset=dataset["train"],
eval_dataset=dataset["test"],
data_collator=VLMCollator(processor),
dataset_kwargs={"skip_prepare_dataset": True},
)
trainer.train()
trainer.push_to_hub()
</image>