Quand j’ai commencé à m’intéresser aux modèles d’IA dits “any-to-any” (capables de traiter et générer simultanément du texte, du son, des images et de la vidéo), j’ai vite compris que le véritable défi ne résidait pas tant dans la conception des architectures que dans l’enfer de leur mise en production. Faire cohabiter des modèles de langage autorégressifs avec des architectures non autorégressives comme les Diffusion Transformers (DiT) engendre des goulots d’étranglement majeurs. (Et croyez-moi, configurer manuellement ces étapes sans outil dédié donne vite des migraines).
C’est dans ce contexte que j’ai commencé à tester vLLM-Omni, une extension open source du célèbre projet vLLM, spécialement conçue pour relever ces défis d’inférence omni-modale distribuée. Grâce à son architecture segmentée, j’ai pu constater des réductions du temps d’exécution des requêtes allant jusqu’à 91,4 %. Voici un retour d’expérience complet et un guide pratique pour installer, configurer et optimiser ce framework sur vos propres infrastructures.
Ce qu’il faut retenir de vLLM-Omni
- Serveur désagrégé (Fully Disaggregated Serving) : Le framework découpe les architectures complexes en plusieurs étapes distinctes (un graphe d’exécution). Chaque étape, par exemple la compréhension textuelle ou la synthèse audio, s’exécute de manière autonome sur des processus, des GPU ou des machines différentes.
- Support des architectures non autorégressives : vLLM-Omni adapte la gestion efficace du KV cache (propre à vLLM) aux modèles de génération parallèle comme les DiT ou les VAE.
- OmniConnector : Un système de routage unifié qui gère les transferts de données et les interactions entre les différentes étapes du pipeline.
- Déchargement de la mémoire (Memory Offloading) : Des stratégies intégrées permettent de décharger les modèles ou les couches (par exemple, en alternant l’utilisation des GPU entre les DiT et les encodeurs) pour maximiser l’usage du matériel disponible.
- API compatible OpenAI : Le framework fournit des points d’accès REST standards pour faciliter l’intégration de tâches de synthèse vocale, de génération d’images ou de vidéos.
Comprendre le fonctionnement par étapes : l’exemple de Qwen3-Omni
Pour comprendre l’intérêt de cette approche, il faut se pencher sur la manière dont s’exécute un modèle multi-étape comme Qwen3-Omni. vLLM-Omni gère nativement ce processus via une interface en ligne de commande basée sur des étapes (stages) :
- Étape 0 (Thinker) : Reçoit et traite les entrées multimodales (texte, audio, vidéo) pour produire du texte.
- Étape 1 (Talker) : Convertit les représentations vectorielles (embeddings) de ce texte en codes de codecs audio bruts.
- Étape 2 (Code2Wav) : Assemble ces codes pour générer la forme d’onde audio finale audible.
En production, je peux isoler et déclencher chacun de ces processus de manière indépendante en utilisant simplement les paramètres --stage-id et --omni.
Guide d’installation pas à pas avec le gestionnaire de paquets uv
Pour mettre en place mon environnement, j’utilise le gestionnaire de paquets Python uv, que je trouve particulièrement rapide et fiable. Avant de commencer, assurez-vous de disposer d’un environnement Linux (ou WSL 2 sous Windows), d’un GPU NVIDIA avec des pilotes CUDA fonctionnels, et de Python 3.12.
vLLM-Omni appliquant des correctifs (patches) directement sur les points d’entrée du moteur, il est crucial de l’installer aux côtés d’une version de base compatible de vLLM.
Étape 1 : Installer les dépendances audio du système
La manipulation des flux audio (notamment pour les modèles Speech-to-Text et TTS) requiert des bibliothèques système spécifiques. Je commence par mettre à jour mon système et installer ffmpeg et sox :
sudo apt-get update && sudo apt-get install ffmpeg sox -y
Étape 2 : Créer et activer l’environnement uv
J’initialise ensuite un environnement virtuel propre sous Python 3.12 :
# Création de l'environnement virtuel
uv venv --python 3.12 --seed
# Activation de l'environnement
source .venv/bin/activate
Étape 3 : Installer le paquet vLLM de base
J’installe le moteur vLLM standard. L’utilisation du paramètre --torch-backend=auto permet à uv de récupérer automatiquement la version de PyTorch adaptée à ma configuration CUDA locale :
uv pip install vllm==0.24.0 --torch-backend=auto
Note personnelle : veillez à bien verrouiller la version majeure/mineure recommandée par l’écosystème vLLM-Omni pour vous éviter des erreurs d’importation de modules (ModuleNotFoundError) au moment de l’exécution.
Étape 4 : Installer vLLM-Omni à partir des sources
À l’heure actuelle, compiler depuis les sources reste la méthode la plus sûre pour s’assurer du bon chaînage de toutes les configurations omni-modales. Je clone le dépôt officiel GitHub de vLLM-Omni et procède à l’installation en mode éditable :
# Clonage du dépôt
git clone https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
# Installation en mode éditable
uv pip install -e .
Étape 5 : Valider l’installation
Pour m’assurer que les correctifs d’intégration multimodale sont correctement appliqués, je lance un test rapide en Python :
python3 -c "import vllm; import vllm_omni; print('vLLM-Omni installation successful!')"
Si la console renvoie le message de succès sans erreur, l’environnement est prêt à accueillir nos premiers modèles.
Démarrer le serveur et exécuter nos premières requêtes
Une fois l’installation validée, l’étape suivante consiste à démarrer le serveur API compatible avec le protocole OpenAI, puis à l’interroger à l’aide d’un script d’inférence.
Étape 1 : Lancement du serveur vLLM-Omni
J’exécute la commande de service. Ici, l’option --omni indique au moteur de configurer le traitement multi-étape désagrégé nécessaire pour analyser les images ou générer des flux audio. Nous allons déployer ici le modèle Qwen3.5-Omni :
# Lancement du serveur localement sur le port 8000
vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct --omni --port 8000
Astuce : si vous disposez de plusieurs GPU ou si vous souhaitez configurer un découpage matériel précis, vous pouvez ajouter des arguments de pipeline comme --stage-id ou spécifier un fichier de déploiement via --deploy-config.
Étape 2 : Installation du client Python OpenAI
Puisque vLLM-Omni expose une API identique à celle d’OpenAI, pas besoin de réécrire des architectures de requêtes complexes. J’installe simplement le client HTTP officiel dans mon environnement virtuel :
uv pip install openai
Étape 3 : Écriture du script d’inférence multimodale
Je crée un fichier nommé omni_client.py. Voici deux exemples d’implémentation selon le type de média à analyser. Tous deux s’appuient sur un encodage en Base64 pour faire transiter proprement les fichiers dans le corps de la requête.
Option A : Analyse et compréhension d’image (Vision / Texte)
import base64
from openai import OpenAI
# 1. Initialisation du client pointant vers notre instance locale vLLM-Omni
client = OpenAI(base_url="http://localhost:8000/v1", api_key="local-token")
# 2. Encodage de l'image locale en Base64
with open("test_image.jpg", "rb") as img_file:
encoded_image = base64.b64encode(img_file.read()).decode("utf-8")
# 3. Requête d'analyse d'image
response = client.chat.completions.create(
model="Qwen/Qwen3.5-Omni-30B-A3B-Instruct",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Décris les sujets principaux et les détails de l'éclairage dans cette image."},
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{encoded_image}"
}
}
]
}
]
)
print(response.choices[0].message.content)
Option B : Compréhension et génération audio (Speech-to-Text / Text-to-Speech)
Si vous souhaitez envoyer un fichier audio pour obtenir une transcription ou une réponse parlée, vous pouvez structurer la requête de cette manière :
import base64
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="local-token")
# Encodage du fichier audio local
with open("sample.mp3", "rb") as audio_file:
encoded_audio = base64.b64encode(audio_file.read()).decode("utf-8")
response = client.chat.completions.create(
model="Qwen/Qwen3.5-Omni-30B-A3B-Instruct",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "Transcris cet enregistrement audio avec précision et réponds à la question qui y est posée."},
{
"type": "input_audio",
"input_audio": {
"data": encoded_audio,
"format": "mp3"
}
}
]
}
]
)
print(response.choices[0].message.content)
Il ne reste plus qu’à lancer le script choisi depuis le terminal :
python3 omni_client.py
Optimisation : Parallélisme de tenseurs et flux audio
Le passage à l’échelle de modèles omni-modaux nécessite des ajustements de bas niveau. Les charges de travail variant fortement d’une étape à l’autre, une allocation uniforme des ressources matérielles s’avère souvent inefficace.
Configuration du parallélisme de tenseurs (Multi-GPU)
Le parallélisme de tenseurs (TP) divise les couches des modèles (comme les matrices d’attention) sur plusieurs cartes graphiques pour augmenter la mémoire globale disponible et réduire les temps de décodage.
Pour appliquer un partitionnement global uniforme sur 4 GPU locaux, j’utilise l’argument suivant :
vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct --omni --tensor-parallel-size 4
Attention toutefois : votre paramètre tensor_parallel_size doit diviser de façon égale la dimension cachée du modèle, et tous les GPU ciblés doivent idéalement posséder des caractéristiques de VRAM identiques.
Pour aller plus loin, vLLM-Omni permet de configurer le niveau de parallélisme par étape d’exécution via un fichier de configuration YAML. Cela évite de monopoliser inutilement des ressources sur des sous-modèles plus légers. J’écris pour cela un fichier omni_stages.yaml :
# Paramètres globaux partagés
trust_remote_code: true
dtype: "bfloat16"
stages:
- stage_id: 0 # Étape Thinker (Compréhension Texte & Visuelle)
devices: "0,1,2,3" # Assignation stricte aux GPU 0, 1, 2 et 3
tensor_parallel_size: 4 # L'empreinte du modèle 30B impose un fort partitionnement
gpu_memory_utilization: 0.85 # Marge de sécurité pour le KV cache dynamique
- stage_id: 1 # Étape Talker (Génération Audio)
devices: "4" # Isolation sur le GPU 4 pour éviter les interférences de latence
tensor_parallel_size: 1 # Le modèle de génération compact ne nécessite pas de partitionnement
gpu_memory_utilization: 0.10 # Empreinte mémoire minimale
Je lance ensuite le serveur en lui transmettant ce fichier de configuration :
vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct --omni --config omni_stages.yaml
Configuration du format de sortie audio
Dans le cas d’applications de synthèse vocale (TTS), on peut exiger du serveur qu’il retourne un profil vocal spécifique ainsi qu’un encodage précis en adaptant les structures modalities et audio du client :
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="local-token")
response = client.chat.completions.create(
model="Qwen/Qwen3.5-Omni-30B-A3B-Instruct",
modalities=["text", "audio"], # Demande explicite d'un retour texte et audio
audio={
"voice": "alloy", # Choix du timbre vocal (alloy, echo, onyx, etc.)
"format": "wav" # Formats acceptés : wav, mp3, ou flac
},
messages=[
{
"role": "user",
"content": "Explique l'informatique quantique en une seule phrase simple et conversationnelle."
}
]
)
# Récupération et écriture du flux audio brut renvoyé par vLLM-Omni
audio_bytes = response.choices[0].message.audio.data
with open("output_speech.wav", "wb") as f:
f.write(audio_bytes)
print("Fichier audio généré et sauvegardé avec succès.")
Optimiser le temps d’attente avant le premier paquet audio (TTFP)
Dans un pipeline multi-étape (Thinker → Talker → Code2Wav), attendre que l’ensemble du texte soit généré avant de lancer l’audio est une hérésie en termes d’expérience utilisateur. J’utilise trois leviers pour réduire cette latence au minimum :
- Activer le découpage asynchrone (Async Chunking) : Assurez-vous que l’option
async_chunkest activée dans la configuration de votre moteur. Cela permet d’envoyer les morceaux de jetons de texte à peine décodés vers le générateur audio, sans attendre la fin de la génération textuelle globale. - Ajuster la taille de la fenêtre de traitement (Chunk Window) : Si le flux audio saccade, la taille de votre fenêtre est trop petite. Si le délai avant le premier son est trop long, elle est trop grande. On ajuste ce paramètre via la ligne de commande :
# Force le système à générer des trames audio tous les 20 à 30 jetons textuels vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct --omni --chunked-prefill-size 32 - Activer CUDA Graph : Pour éviter les délais de répartition des noyaux (kernels) côté CPU lors de la phase finale d’assemblage de la forme d’onde, l’activation des graphes CUDA sur les cibles de décodage s’avère indispensable pour éliminer de précieux millisecondes de surcharge système.
Passage à l’échelle multi-nœuds avec Ray
Lorsque la mémoire d’un seul serveur s’avère insuffisante pour héberger de gros modèles omni-modaux, nous devons répartir les couches de manière séquentielle sur plusieurs machines physiques en exploitant le parallélisme de pipeline (PP). Cette architecture nécessite l’utilisation du moteur d’exécution distribué Ray.
Étape 1 : Initialiser le cluster Ray distribué
Sur votre machine principale (Head Node), par exemple à l’adresse IP 10.0.0.1 :
ray start --head --port=6379
Sur vos machines secondaires (Worker Nodes) :
ray start --address='10.0.0.1:6379'
Étape 2 : Déploiement avec parallélisme 3D
Lors d’un déploiement multi-nœuds, on configure la valeur --tensor-parallel-size pour qu’elle corresponde au nombre de GPU présents sur chaque machine individuelle, tandis que la valeur --pipeline-parallel-size doit égaler le nombre total de nœuds du cluster.
Si je dispose par exemple de 2 serveurs embarquant chacun 8 GPU, je lance cette commande sur mon nœud principal :
vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct \
--omni \
--distributed-executor-backend ray \
--tensor-parallel-size 8 \
--pipeline-parallel-size 2
Étape 3 : Partitionnement personnalisé des couches (Optionnel)
Si vos machines sont hétérogènes (par exemple, si le premier nœud dispose de GPU plus puissants ou mieux pourvus en mémoire), vous pouvez manuellement définir la répartition des couches réseau de chaque étape de pipeline via une variable d’environnement :
export VLLM_PP_LAYER_PARTITION="40,24" # 40 couches sur le nœud 0, 24 couches sur le nœud 1
Note d’expérience : pour maintenir des débits de traitement viables en multi-nœuds, l’utilisation d’une interconnexion à très faible latence comme InfiniBand ou RoCE est impérative pour éviter que le transfert de données d’activation entre les étapes ne paralyse l’ensemble du cluster.
Configuration pour la production et guide de survie face aux erreurs de mémoire
Un déploiement robuste en production passe par la rédaction de fichiers de configuration stricts pour encadrer la consommation de nos ressources.
Exemple de spécification de déploiement multi-nœuds
Voici le fichier deploy_production.yaml que j’utilise pour orchestrer un environnement à deux nœuds. Ce schéma isole l’analyse visuelle et textuelle lourde (Étape 0) sur le premier nœud et la génération sonore (Étape 1) sur le second nœud afin d’éliminer tout risque de conflit de ressources :
# =====================================================================
# Configuration de l'infrastructure globale
# =====================================================================
distributed_executor_backend: "ray"
pipeline_parallel_size: 2 # Répartition séquentielle sur 2 nœuds physiques
async_chunk: true # Découpage asynchrone activé pour optimiser la latence
dtype: "bfloat16"
# =====================================================================
# Définition des étapes et des limites de ressources par étape
# =====================================================================
stages:
- stage_id: 0 # Étape Thinker (Texte / Image / Vidéo)
tensor_parallel_size: 8 # Utilisation des 8 GPU du nœud 1
devices: "0,1,2,3,4,5,6,7" # Assignation physique fixe des cartes
gpu_memory_utilization: 0.85 # 85% de la VRAM allouée au modèle et au KV cache principal
max_model_len: 32768 # Limitation stricte de la longueur du contexte
max_num_seqs: 64 # Limitation du traitement par lots pour éviter les pics de charge
- stage_id: 1 # Étape Talker & Code2Wav (Synthèse Audio)
tensor_parallel_size: 2 # Les structures DiT/Audio n'ont besoin que de 2 cartes locales
devices: "0,1" # Assignation aux deux premiers GPU du nœud 2
gpu_memory_utilization: 0.40 # Consommation mémoire réduite pour laisser de la place aux buffers audio
max_model_len: 4096 # Le traitement de l'audio nécessite des contextes beaucoup plus courts
max_num_seqs: 16 # Limitation agressive pour garantir un flux audio stable sans coupure
J’initie le service en ciblant directement ce fichier de manifeste :
vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct --omni --deploy-config deploy_production.yaml
Résolution des erreurs de saturation de mémoire (Out Of Memory – OOM)
La manipulation de fichiers volumineux (images haute définition, longs flux audio) combinée à la conservation des caches de jetons (tokens) expose fréquemment les infrastructures aux plantages de type OOM. D’après mon expérience, ces crashs se classent en deux catégories bien distinctes :
Scénario A : Crash de la mémoire système de la machine hôte (OOMKilled / Code d’erreur 137)
- La cause : Les fichiers multimédias bruts (images, vidéos, audio) sont traités et décodés par des tokeniseurs et des encodeurs s’exécutant sur le CPU (par exemple SigLIP ou Whisper) avant d’être envoyés vers le GPU. Lors de pics d’appels simultanés, la mémoire vive (RAM) de la machine sature.
- Les solutions : Limiter la taille de déchargement mémoire et forcer un seuil maximal pour éviter les fuites de mémoire système :
vllm serve ... --cpu-offload-gb 8 --swap-space 4Il est également recommandé de restreindre le débit de requêtes concurrentes au niveau de votre répartiteur de charge externe (comme Nginx ou Envoy) plutôt que de laisser le moteur interne saturer.
Scénario B : Crash de la mémoire VRAM du GPU (CUDA out of memory)
- La cause : Les poids du modèle tiennent sur la carte, mais le traitement d’éléments multimodaux lourds provoque une hausse soudaine des activations, ou votre cache KV grandit de manière excessive dans un contexte trop large.
- Les solutions à appliquer dans l’ordre :
- Réduire la longueur maximale du contexte en ajustant le paramètre
--max-model-len. Les modèles omni-modaux acceptent par défaut des fenêtres immenses (par exemple 128 000 jetons). Ramener cette valeur à 16 000 ou 32 000 permet de récupérer immédiatement plusieurs gigaoctets de VRAM sur chaque carte. - Réduire le paramètre
--max-num-seqsà 32 ou 16 pour limiter le nombre de requêtes d’images ou de vidéos traitées simultanément. - Ajuster la valeur de
--gpu-memory-utilizationà 0.80 ou 0.75. Cela laisse une réserve de VRAM libre pour que l’environnement d’exécution PyTorch puisse manipuler les données multimédias lourdes sans planter. - Activer la mise en cache des préfixes communs pour mutualiser le stockage des jetons visuels lors de conversations à plusieurs tours :
vllm serve ... --enable-prefix-caching
- Réduire la longueur maximale du contexte en ajustant le paramètre
Résolution de pannes, alertes et résilience avec Systemd
Faire tourner un service en production implique également d’interpréter ses journaux d’erreurs, de mettre en œuvre des sondes de disponibilité et d’assurer une reprise automatique en cas de défaillance matérielle.
Décoder les erreurs courantes du terminal
ValueError: Model architecture QwenOmniForCausalLM not supported.La cause : Le moteur tente de s’exécuter sur la bibliothèque vllm générique au lieu de charger les correctifs propres à vllm-omni. La solution : Vérifiez que la commande de démarrage inclut bien le paramètre--omniet assurez-vous que vllm-omni est correctement installé en mode éditable (uv pip install -e .) dans votre environnement virtuel actif.RuntimeError: Address already in use / Ray client connection failedLa cause : Une précédente instance du service s’est arrêtée de façon anormale, laissant des processus fantômes s’exécuter en arrière-plan, occupant la VRAM des GPU ou bloquant les ports d’échange (comme le port 6379 de Ray). La solution : Forcez l’arrêt de tous les processus orphelins avant de relancer votre serveur :ray stop --force pkill -f vllm pkill -f raytorch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate...La solution : Réduisez immédiatement la taille des allocations dynamiques de mémoire en imposant des limites de contexte plus strictes au démarrage :vllm serve ... --max-model-len 16384 --max-num-seqs 16 --gpu-memory-utilization 0.80
Configuration des sondes de santé (Health Checks)
Le serveur de vLLM-Omni expose nativement des points d’accès HTTP légers qui facilitent le suivi de l’état de santé de l’instance par vos répartiteurs de charge ou vos orchestrateurs :
- Sonde de survie (Liveness Probe –
/health) : Confirme que le processus serveur tourne et répond aux requêtes. - Sonde de disponibilité (Readiness Probe –
/v1/models) : S’assure que les poids du modèle, les pipelines des différentes étapes et les structures mémoires des GPU sont intégralement chargés et prêts à traiter des flux multimodaux complexes.
Exemple de vérification via un appel curl standard :
curl -i http://localhost:8000/health
# Doit renvoyer : HTTP/1.1 200 OK
curl -i http://localhost:8000/v1/models
# Doit renvoyer : HTTP/1.1 200 OK accompagné de la liste JSON de vos modèles actifs
Automatiser la reprise de service sous Linux avec Systemd
Si vous exécutez votre serveur sur une machine virtuelle ou un serveur physique dédié sans orchestrateur de conteneurs, l’utilisation de Systemd reste l’approche la plus robuste pour gérer les redémarrages automatiques après un incident de type mémoire saturée.
Je crée pour cela un fichier de service à l’emplacement /etc/systemd/system/vllm-omni.service :
[Unit]
Description=Moteur d'inférence haute performance vLLM-Omni
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/vllm-omni
# Injection des chemins de l'environnement virtuel uv dans la commande d'exécution
ExecStart=/home/ubuntu/vllm-omni/.venv/bin/vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct --omni --config deploy_production.yaml
# Déclaration des variables d'environnement indispensables
Environment="CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7"
Environment="PYTHONUNBUFFERED=1"
# Stratégies de redémarrage automatique en cas de crash
Restart=always
RestartSec=10s
# Arrête de tenter un redémarrage si le service crash plus de 5 fois en l'espace de 5 minutes
StartLimitIntervalSec=300
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
J’active et démarre mon nouveau service :
sudo systemctl daemon-reload
sudo systemctl enable vllm-omni.service
sudo systemctl start vllm-omni.service
Pour surveiller en temps réel le comportement et les redémarrages de mon service, j’utilise la commande suivante :
sudo journalctl -u vllm-omni.service -f
Mesure des performances et orchestration Kubernetes
Dans cette section, nous allons voir comment évaluer précisément les performances de nos flux audio et comment industrialiser notre service sous Kubernetes.
Évaluer le débit de diffusion vocale (Tokens-par-seconde)
Dans un système omni-modal, le calcul classique du débit ne suffit pas : la synthèse vocale s’effectuant de manière séquentielle après la phase textuelle, il faut mesurer individuellement chaque étape d’exécution. vLLM-Omni propose pour cela l’outil en ligne de commande vllm bench serve adapté aux métriques multimodales clés, comme le délai d’émission du premier jeton audio (Talker TTFT) et le débit d’émission des jetons audio (Talker TPOT).
1. Je lance d’abord mon instance de test locale :
vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct --omni --port 8000
2. Dans une autre console, je lance l’outil d’évaluation en spécifiant le protocole d’échange audio :
vllm bench serve \
--omni \
--backend openai-chat-omni \
--endpoint http://localhost:8000/v1 \
--model Qwen/Qwen3.5-Omni-30B-A3B-Instruct \
--dataset-path sharegpt \
--num-prompts 500 \
--num-warmups 5 \
--enable-multimodal-chat
3. Analyse des résultats obtenus :
- Stage 0 Gen Time (Thinker) : Indique la vitesse de traitement de vos requêtes texte et image initiales.
- Stage 1 TTFT (Talker) : Il s’agit du délai précis en millisecondes avant que le synthétiseur ne commence à produire le premier son audible. Cette métrique est cruciale pour la sensation de fluidité d’un assistant vocal.
- Audio TPOT (Tokens per Output Token) : Indique la vitesse de génération de votre flux audio. Pour s’assurer de l’absence de coupures ou de saccades durant la lecture, cette valeur doit impérativement rester supérieure à la vitesse d’échantillonnage de votre lecteur de destination (l’objectif type étant d’avoir un débit supérieur à 24 jetons audio par seconde).
Déploiement à grande échelle via l’opérateur KubeRay sur Kubernetes
Pour orchestrer un déploiement distribué sur un cluster d’entreprise, je recommande d’utiliser l’opérateur KubeRay pour automatiser la gestion des pods, la topologie des puces graphiques et les règles d’accès réseau.
Étape 1 : Installer l’opérateur KubeRay via le gestionnaire Helm :
helm repo add kuberay https://github.io
helm repo update
helm install kuberay-operator kuberay/kuberay-operator --version 1.1.0
Étape 2 : Définir la configuration globale via la ressource personnalisée (CRD) RayService. J’enregistre ce fichier sous le nom de vllm-omni-k8s.yaml. Ce schéma prend en compte la redirection automatique des flux réseaux et les mises à jour sans interruption de service :
apiVersion: ray.io/v1
kind: RayService
metadata:
name: vllm-omni-service
namespace: default
spec:
serviceUnhealthyThreshold: 300
rayClusterConfig:
rayVersion: '2.35.0'
headGroupSpec:
rayStartParams:
dashboard-host: '0.0.0.0'
template:
spec:
containers:
- name: ray-head
image: vllm/vllm-omni:latest # Utilisation de l'image officielle multi-plateforme
resources:
limits:
cpu: "8"
memory: "32Gi"
requests:
cpu: "4"
memory: "16Gi"
ports:
- containerPort: 6379
name: gcs
- containerPort: 8265
name: dashboard
- containerPort: 8000
name: serve
workerGroupSpecs:
- replicas: 2 # Nombre d'instances workers à lancer en parallèle
minReplicas: 1
maxReplicas: 4
groupName: gpu-workers
rayStartParams: {}
template:
spec:
containers:
- name: ray-worker
image: vllm/vllm-omni:latest
command: ["/bin/sh", "-c"]
args:
- |
vllm serve Qwen/Qwen3.5-Omni-30B-A3B-Instruct \
--omni \
--distributed-executor-backend ray \
--tensor-parallel-size 4 \
--max-model-len 32768 \
--port 8000
resources:
limits:
cpu: "32"
memory: "128Gi"
nvidia.com/gpu: "4" # Allocation de 4 GPU par nœud worker
requests:
cpu: "16"
memory: "64Gi"
nvidia.com/gpu: "4"
Étape 3 : Déployer et exposer notre instance de service :
# Création des ressources sur le cluster Kubernetes
kubectl apply -f vllm-omni-k8s.yaml
# Redirection du flux du nœud principal vers ma machine locale pour réaliser nos tests d'appels
kubectl port-forward service/vllm-omni-service-head-svc 8000:8000
Une fois les pods passés au statut actif (Running), votre infrastructure cloud est prête à traiter et répondre aux appels de vos clients OpenAI à l’échelle mondiale.
Supervision et mise à l’échelle automatique
Afin de suivre l’état de nos pipelines distribués, vLLM-Omni expose des mesures télémétriques précises via son point d’accès standard /metrics au format Prometheus.
Indicateurs Prometheus à surveiller en priorité
En introduisant le label stage_id dans ses rapports d’exécution, vLLM-Omni permet de cibler des points précis de notre graphe de traitement :
vllm:time_to_first_token_seconds: Mesure le délai de réponse initial (TTFT) du texte sur l’étape 0.vllm:omni_audio_ttfp_s: Mesure le délai précis (en secondes) nécessaire avant que l’étape 1 ne commence à produire les premiers paquets sonores.vllm:num_requests_waiting: Indique le volume de requêtes multimodales actuellement bloquées dans la file d’attente globale d’ordonnancement.vllm:gpu_cache_usage_factor: Permet de suivre l’évolution dynamique de l’utilisation de la mémoire vive des GPU pour chaque étape d’exécution du pipeline.
Configuration du fichier d’intégration de Prometheus (prometheus.yaml)
J’ajoute simplement la déclaration suivante à mon collecteur global pour commencer à récupérer les journaux d’activité de mes instances Kubernetes ou de mes machines physiques :
scrape_configs:
- job_name: 'vllm-omni-cluster'
scrape_interval: 5s # Un intervalle court permet de bien repérer les pics d'allocation lors du traitement des médias
metrics_path: /metrics
static_configs:
- targets: ['vllm-omni-service.default.svc.cluster.local:8000']
Requêtes de tableaux de bord Grafana en langage PromQL
Pour construire mes visualisations de suivi de production sous Grafana, j’écris ces formules afin de traquer l’état de santé de mes pipelines :
1. Débit de production de jetons (Texte vs Audio)
sum(rate(vllm:num_tokens_total[1m])) by (stage_id)
2. Saturation mémoire par étape du pipeline
avg(vllm:gpu_cache_usage_factor) by (stage_id)
3. Latence globale d’émission vocale perçue par l’utilisateur final (P95 TTFP)
histogram_quantile(0.95, sum(rate(vllm:omni_audio_ttfp_s_bucket[5m])) by (le))
Configuration du redimensionnement automatique des pods (HPA)
Surveiller simplement l’utilisation du processeur ou de la mémoire vive d’une machine ne permet pas de gérer correctement la charge de modèles d’IA complexes. Un serveur peut se retrouver complètement bloqué par le traitement simultané de plusieurs vidéos lourdes tout en affichant un usage CPU relativement modeste.
C’est pourquoi je configure un outil de liaison (Prometheus Adapter) pour pouvoir baser mon autoscaling Kubernetes sur l’état de la file d’attente globale de vLLM-Omni.
Étape 1 : Création de la règle personnalisée d’exposition dans le fichier de valeurs (values.yaml) de votre adaptateur :
rules:
custom:
- seriesQuery: 'vllm:num_requests_waiting'
resources:
template: "<<.Resource>>"
name:
matches: "^vllm:num_requests_waiting$"
as: "omni_queue_depth"
metricsQuery: 'sum(vllm:num_requests_waiting{namespace="<<.Namespace>>",pod="<<.Pod>>"})'
Étape 2 : Déploiement de la configuration de mise à l’échelle automatique (hpa-omni.yaml). Cette règle va automatiquement démarrer de nouveaux pods de serveurs dès que l’attente moyenne dépasse 10 requêtes par pod actif :
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: vllm-omni-autoscaler
namespace: default
spec:
scaleTargetRef:
apiVersion: ray.io/v1
kind: RayService
name: vllm-omni-service
minReplicas: 1
maxReplicas: 10
metrics:
- type: Pods
pods:
metric:
name: omni_queue_depth
target:
type: AverageValue
averageValue: "10" # Ajout d'une nouvelle instance si la file d'attente moyenne dépasse 10 requêtes par pod
behavior:
scaleUp:
stabilizationWindowSeconds: 0 # Réactivité immédiate lors des pics d'appels multimédias
policies:
- type: Percent
value: 100
periodSeconds: 15
scaleDown:
stabilizationWindowSeconds: 300 # Descente progressive sur une période de 5 minutes pour éviter l'instabilité du cluster
policies:
- type: Percent
value: 10
periodSeconds: 60
J’applique enfin cette règle sur mon cluster Kubernetes :
kubectl apply -f hpa-omni.yaml
Modèles supportés nativement
Vous pouvez retrouver la liste complète et actualisée des modèles compatibles directement sur la documentation officielle de vLLM-Omni. Le framework prend actuellement en charge plusieurs grandes familles de modèles open-source :
1. Compréhension et génération omni-modale (Any-to-Any)
Ces modèles gèrent en direct la transcription, la compréhension et la synthèse croisée des flux d’images, de textes, de vidéos et d’enregistrements audio :
- Gamme Qwen-Omni : Qwen/Qwen3-Omni-30B-A3B-Instruct (architecture MoE), Qwen/Qwen2.5-Omni-7B et Qwen/Qwen2.5-Omni-3B.
- Gamme Cosmos : nvidia/Cosmos3-Nano et nvidia/Cosmos3-Super (gestion du texte, de l’image, de la vidéo, des bandes-son et des politiques d’action).
- BAGEL : ByteDance-Seed/BAGEL-7B-MoT (framework s’appuyant uniquement sur des Diffusion Transformers).
- Ming-Flash : Jonathan1909/Ming-flash-omni-2.0 (génération voix et images).
- Robotique et vision spécialisée : nvidia/GR00T-N1.7-3B et InternRobotics/InternVLA-A1-3B.
2. Modèles de synthèse vocale (TTS) et audio
Ces architectures se concentrent principalement sur l’émission de flux de paroles, le clonage de voix ou la synthèse vocale :
- Qwen3-TTS : Qwen/Qwen3-TTS-12Hz-* (avec support des fonctions de clonage CustomVoice et de VoiceDesign).
- CosyVoice : FunAudioLLM/Fun-CosyVoice3-0.5B-2512.
- Fish Speech : Fish Speech S2 Pro.
- Autres architectures compatibles : k2-fsa/OmniVoice, openbmb/VoxCPM2, et Voxtral TTS.
- Génération de musique / Chant : stabilityai/stable-audio-open-1.0 et Soul-AILab/SoulX-Singer.
3. Pipelines de diffusion d’images et de vidéos
Exploités à travers la structure visuelle multi-étape pour la génération spatiale haute-fidélité et l’édition d’images :
- Systèmes d’images Qwen : Qwen/Qwen-Image, Qwen/Qwen-Image-Edit et Qwen/Qwen-Image-Layered.
- Génération vidéo Wan : Wan2.1-T2V, Wan2.2-T2V, Wan2.2-TI2V, Wan2.2-I2V, Wan2.1-VACE et Wan2.2-VACE.
- Famille FLUX : black-forest-labs/FLUX.1-dev, black-forest-labs/FLUX.1-schnell, black-forest-labs/FLUX.2-klein-4B et FLUX.1-Kontext-dev.
- Gamme LTX : Lightricks/LTX-2 (architectures Text-to-Video et Image-to-Video) et dg845/LTX-2.3-Diffusers.
- Systèmes Hunyuan : tencent/HunyuanImage-3.0 et HunyuanVideo-1.5.
- Autres modèles de diffusion : stabilityai/stable-diffusion-xl-base-1.0, stabilityai/stable-diffusion-3.5-medium, OmniGen2/OmniGen2 et BestWishYsh/Helios.
4. Modèles avancés de compréhension multimodale
- Ovis-Image : OvisAI/Ovis-Image.
- LongCat : meituan-longcat/LongCat-Image et LongCat-Image-Edit.
- MammothModa : bytedance-research/MammothModa2-Preview.
Astuce pratique : pour vérifier si un modèle personnalisé ou finement ajusté que vous hébergez est compatible nativement avec vLLM-Omni, inspectez simplement son fichier config.json sur Hugging Face. Si le champ de déclaration des architectures correspond à l’une des définitions internes du projet (comme Qwen3OmniMoeForConditionalGeneration ou FluxPipeline), il démarrera sans modification.