Modèles multimodaux (images et vidéos)¶
vllm-mlx prend en charge les VLM pour la compréhension des images et des vidéos.
Modèles pris en charge¶
- Qwen3-VL (recommandé)
- Qwen2-VL
- Gemma 3
- LLaVA
- Idefics
- PaliGemma
- Pixtral
- Molmo
- DeepSeek-VL
Démarrer un serveur multimodal¶
Les modèles dont le nom contient « VL », « Vision » ou « mllm » sont automatiquement détectés comme multimodaux.
Analyse d'images¶
Via le SDK OpenAI¶
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
# Image depuis une URL
response = client.chat.completions.create(
model="default",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What's in this image?"},
{"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
]
}],
max_tokens=256
)
print(response.choices[0].message.content)
Images en Base64¶
import base64
def encode_image(path):
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
base64_image = encode_image("photo.jpg")
response = client.chat.completions.create(
model="default",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Describe this image"},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"}}
]
}]
)
Via curl¶
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "default",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{"type": "image_url", "image_url": {"url": "https://example.com/image.jpg"}}
]
}],
"max_tokens": 256
}'
Analyse de vidéos¶
Via le SDK OpenAI¶
response = client.chat.completions.create(
model="default",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What happens in this video?"},
{"type": "video_url", "video_url": {"url": "https://example.com/video.mp4"}}
]
}],
max_tokens=512
)
Paramètres vidéo¶
Contrôlez l'extraction des images via les paramètres du corps étendu :
response = client.chat.completions.create(
model="default",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "Describe this video"},
{"type": "video_url", "video_url": {"url": "video.mp4"}}
]
}],
extra_body={
"video_fps": 2.0,
"video_max_frames": 32
}
)
Via curl¶
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "default",
"messages": [{
"role": "user",
"content": [
{"type": "text", "text": "Describe this video"},
{"type": "video_url", "video_url": {"url": "https://example.com/video.mp4"}}
]
}],
"video_fps": 2.0,
"video_max_frames": 16
}'
Formats pris en charge¶
Images¶
| Format | Exemple |
|---|---|
| URL | {"type": "image_url", "image_url": {"url": "https://..."}} |
| Fichier local | {"type": "image_url", "image_url": {"url": "/path/to/image.jpg"}} |
| Base64 | {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}} |
Vidéos¶
| Format | Exemple |
|---|---|
| URL | {"type": "video_url", "video_url": {"url": "https://..."}} |
| Fichier local | {"type": "video", "video": "/path/to/video.mp4"} |
| Base64 | {"type": "video_url", "video_url": {"url": "data:video/mp4;base64,..."}} |
API Python¶
from vllm_mlx.models import MLXMultimodalLM
mllm = MLXMultimodalLM("mlx-community/Qwen3-VL-4B-Instruct-3bit")
mllm.load()
# Image
description = mllm.describe_image("photo.jpg")
# Vidéo
description = mllm.describe_video("video.mp4", fps=2.0)
# Prompt personnalisé
output = mllm.generate(
prompt="Compare these images",
images=["img1.jpg", "img2.jpg"]
)
Conseils de performance¶
Images¶
- Les résolutions plus petites sont traitées plus rapidement (224x224 vs 1920x1080)
- Utilisez la résolution adaptée à votre tâche
Vidéos¶
- Un FPS plus bas accélère le traitement
- Moins d'images signifie moins de mémoire utilisée
- 64 images est le maximum pratique (96 et plus provoque un timeout GPU)
Benchmarks¶
Testés sur Apple M4 Max avec 128 Go de mémoire unifiée.
Qwen3-VL-4B-Instruct-3bit¶
| Résolution | Temps | Tokens | Vitesse | Mémoire |
|---|---|---|---|---|
| 224x224 | 0.87s | 124 | 143 tok/s | 2.6 Go |
| 448x448 | 1.01s | 107 | 106 tok/s | 3.1 Go |
| 768x768 | 1.42s | 127 | 89 tok/s | 3.4 Go |
| 1024x1024 | 1.85s | 116 | 63 tok/s | 3.6 Go |
Qwen3-VL-8B-Instruct-4bit¶
| Résolution | Temps | Tokens | Vitesse | Mémoire |
|---|---|---|---|---|
| 224x224 | 1.08s | 78 | 73 tok/s | 5.6 Go |
| 448x448 | 1.41s | 70 | 50 tok/s | 6.1 Go |
| 768x768 | 2.06s | 91 | 44 tok/s | 6.5 Go |
| 1024x1024 | 3.02s | 76 | 25 tok/s | 7.6 Go |
Gemma 3 4B 4bit¶
| Résolution | Temps | Tokens | Vitesse | Mémoire |
|---|---|---|---|---|
| 224x224 | 0.95s | 30 | 32 tok/s | 5.2 Go |
| 448x448 | 0.99s | 34 | 34 tok/s | 5.2 Go |
| 768x768 | 0.99s | 32 | 32 tok/s | 5.2 Go |
| 1024x1024 | 0.95s | 28 | 29 tok/s | 5.2 Go |
Lancer les benchmarks¶
# Benchmark rapide
vllm-mlx-bench --model mlx-community/Qwen3-VL-4B-Instruct-3bit --quick
# Benchmark complet avec plus de résolutions
vllm-mlx-bench --model mlx-community/Qwen3-VL-4B-Instruct-3bit
# Benchmark vidéo
vllm-mlx-bench --model mlx-community/Qwen3-VL-4B-Instruct-3bit --video
Cache MLLM¶
vllm-mlx inclut un système de prefix cache pour les modèles multimodaux, capable d'accélérer significativement les requêtes répétées utilisant les mêmes images.
Fonctionnement¶
Lorsque vous envoyez une image au modèle, l'encodeur de vision la traite en embeddings. Ce traitement prend 1 à 2 secondes. Le cache MLLM stocke ces embeddings ainsi que l'état du KV cache, de sorte que les requêtes ultérieures avec la même image contournent entièrement l'encodeur de vision.
Le cache utilise un hachage basé sur le contenu (similaire à LMCache) pour identifier les images identiques, quelle que soit leur forme de transmission (URL, base64 ou chemin de fichier).
Activer le cache¶
# Activer avec les paramètres par défaut (512 Mo maximum)
vllm-mlx serve mlx-community/Qwen3-VL-4B-Instruct-3bit --enable-mllm-cache
# Avec une limite mémoire personnalisée
vllm-mlx serve mlx-community/Qwen3-VL-4B-Instruct-3bit \
--enable-mllm-cache \
--mllm-cache-max-mb 1024
API Python¶
from vllm_mlx.mllm_cache import MLLMPrefixCacheManager
# Créer le gestionnaire de cache
cache = MLLMPrefixCacheManager(max_memory_mb=512)
# Stocker les embeddings et le KV cache après traitement
cache.store(
images=["photo.jpg"],
prompt="Describe this image",
vision_embeddings=embeddings,
kv_cache=kv_state,
num_tokens=128
)
# Récupérer depuis le cache lors des requêtes suivantes
entry, match_len = cache.fetch(images=["photo.jpg"], prompt="Describe this image")
if entry:
# Utiliser les embeddings mis en cache, contourner l'encodeur de vision
embeddings = entry.vision_embeddings
kv_state = entry.kv_cache
Statistiques du cache¶
stats = cache.get_stats()
print(f"Hit rate: {stats.hit_rate:.1%}")
print(f"Memory used: {stats.memory_used_mb:.1f} MB")
print(f"Tokens saved: {stats.tokens_saved}")
Gestion de la mémoire¶
Le cache utilise une éviction LRU (Least Recently Used) lorsque la limite mémoire est atteinte. Chaque entrée suit :
- La taille des embeddings de vision
- La taille du KV cache par couche
- La fréquence d'accès pour l'ordonnancement LRU
En cas de pression mémoire, les entrées les moins récemment consultées sont évincées en premier.
Interface de chat Gradio¶
Pour un chat multimodal interactif :
Prend en charge le glisser-déposer d'images et de vidéos.