
Déployer un LLM avec Modal : de pip install à l'endpoint de production
La plupart des guides sur l'auto-hébergement de LLM esquivent la partie la plus difficile : l'infrastructure. On se bat avec les pilotes CUDA, on gère des images Docker, on configure l'autoscaling, et on finit quand même par payer pour des GPUs inactifs à 3 h du matin. Modal élimine tout ça. Tu écris du Python, tu déploies, tu obtiens une URL.
Ce guide t'accompagne dans le déploiement d'un LLM open source sur Modal avec vLLM comme moteur d'inférence. À la fin, tu auras un endpoint d'API compatible OpenAI en direct sur des GPUs H100, qui scale à zéro quand personne ne l'utilise.
Qu'est-ce que Modal (et pourquoi l'utiliser pour les LLM) ?
Modal est une plateforme de calcul serverless conçue spécifiquement pour les workloads IA. Pense à AWS Lambda, mais avec le support GPU, une facturation à la seconde et une expérience développeur native Python. Pas de YAML, pas de Dockerfile, pas de Kubernetes — tu définis toute ton infrastructure dans un script Python et tu déploies avec une seule commande.
Voici pourquoi c'est devenu la référence pour le déploiement de LLM :
- Facturation scale-to-zero — tu ne paies rien quand ton endpoint ne traite pas de requêtes
- Tarification GPU à la seconde — H100 à ~3,70 €/h, A100 80 Go à ~2,35 €/h, facturé à la seconde
- Démarrages à froid sub-seconde — les conteneurs démarrent vite, surtout avec les snapshots mémoire
- 30 $/mois de crédits gratuits — suffisant pour expérimenter sans frais de carte bancaire
- Zéro DevOps — pas de builds Docker, pas de Terraform, pas de gestion de cluster
Si tu exécutes des LLM localement et que tu veux leur donner une vraie API sans gérer de serveurs, Modal est le chemin le plus court.
Modal vs. RunPod vs. Lambda
| Fonctionnalité | Modal | RunPod | Lambda |
|---|---|---|---|
| Modèle de facturation | À la seconde, scale-to-zero | À la seconde, charge min. | À l'heure, toujours actif |
| Démarrage à froid | 2-4 secondes | 6-12 secondes (grand) | N/A (persistant) |
| Disponibilité GPU | H100, A100, L40S, T4 | A100, H100, A6000 | H100, A100 |
| Infrastructure | Python pur, pas de config | Docker, plus de contrôle | Accès VM complet |
| Niveau gratuit | 30 $/mois de crédits | Aucun | Aucun |
| Idéal pour | Workloads en rafales/dev | Trafic d'inférence stable | Entraînement haute utilisation |
Conclusion : Modal gagne pour les workloads en rafales et le développement. Si ton utilisation GPU dépasse régulièrement 40 %, une instance dédiée sur RunPod ou Lambda est moins chère. Pour tout le reste — prototypage, APIs intermittentes, démos — le modèle scale-to-zero de Modal économise vraiment de l'argent.
Prérequis
Avant de commencer, tu as besoin de trois choses :
- Python 3.10+ installé localement
- Un compte Modal — inscription gratuite sur modal.com
- Un compte Hugging Face — pour accéder aux modèles (la plupart sont restreints)
C'est tout. Pas de GPU sur ta machine locale, pas de toolkit CUDA, pas de Docker.
Étape 1 : Installer Modal et s'authentifier
Ouvre un terminal et installe le package Python Modal :
pip install modalLance ensuite la commande de configuration pour lier ton environnement local à ton compte Modal :
modal setupCela ouvre une fenêtre de navigateur pour l'authentification. Une fois confirmé, Modal stocke un token localement. Tu n'auras plus jamais à le faire.
Étape 2 : Définir l'image conteneur
Les conteneurs Modal sont définis en Python. Tu spécifies l'image de base, tu installes les dépendances et tu définis les variables d'environnement — le tout sous forme de code. Crée un fichier appelé app.py :
import modal
# Définir l'image conteneur avec CUDA, Python et vLLM
vllm_image = (
modal.Image.from_registry(
"nvidia/cuda:12.8.0-devel-ubuntu22.04", add_python="3.12"
)
.entrypoint([])
.pip_install(
"vllm==0.13.0",
"huggingface-hub==0.36.0",
)
)
app = modal.App("llm-endpoint", image=vllm_image)Quelques points à noter. Il n'y a pas de Dockerfile — cette chaîne modal.Image le remplace entièrement. L'image de base inclut NVIDIA CUDA 12.8 avec Ubuntu 22.04, et on installe vLLM et le client Hugging Face Hub par-dessus.
Étape 3 : Configurer le stockage de modèle avec des volumes
Les poids des LLM sont volumineux (un modèle à 7 milliards de paramètres fait ~14 Go en fp16). Tu ne veux pas les télécharger à chaque démarrage de conteneur. Les Volumes Modal te donnent un stockage persistant qui se monte directement dans tes conteneurs :
# Volumes persistants pour la mise en cache des poids du modèle
hf_cache = modal.Volume.from_name("huggingface-cache", create_if_missing=True)
vllm_cache = modal.Volume.from_name("vllm-cache", create_if_missing=True)
MODEL_NAME = "Qwen/Qwen3-4B-Thinking-2507-FP8"
MODEL_REVISION = "953532f942706930ec4bb870569932ef63038fdf"On utilise ici Qwen3-4B-Thinking (FP8) — un modèle quantifié à 4 milliards de paramètres qui est rapide, capable, et tient sur un seul GPU. Tu peux le remplacer par n'importe quel modèle Hugging Face : Llama 3.1 8B, Mistral 7B, ou tout ce que vLLM supporte.
Pourquoi FP8 ? Il divise à peu près par deux l'utilisation mémoire par rapport au fp16, ce qui te permet d'exécuter des modèles plus grands sur le même GPU — ou des modèles plus petits sur des GPUs moins chers. Si tu es curieux des compromis de quantification, notre guide pour exécuter des LLM localement couvre les formats de précision en détail.
Étape 4 : Créer la fonction serveur vLLM
C'est là que la magie de Modal opère. Tu décores une fonction Python avec les exigences GPU, la configuration de scaling et une annotation de serveur web. Modal s'occupe du reste :
N_GPU = 1
MINUTES = 60
VLLM_PORT = 8000
@app.function(
gpu=f"H100:{N_GPU}",
scaledown_window=15 * MINUTES,
timeout=10 * MINUTES,
volumes={
"/root/.cache/huggingface": hf_cache,
"/root/.cache/vllm": vllm_cache,
},
)
@modal.concurrent(max_inputs=32)
@modal.web_server(port=VLLM_PORT, startup_timeout=10 * MINUTES)
def serve():
import subprocess
cmd = [
"vllm", "serve",
MODEL_NAME,
"--revision", MODEL_REVISION,
"--served-model-name", MODEL_NAME,
"--host", "0.0.0.0",
"--port", str(VLLM_PORT),
"--tensor-parallel-size", str(N_GPU),
"--enforce-eager", # Démarrages à froid plus rapides
]
subprocess.Popen(" ".join(cmd), shell=True)Décortiquons les décorateurs clés :
gpu="H100:1"— demande un seul GPU H100. Change en"A100-80GB:1"pour une inférence moins chère, ou"H100:2"pour les modèles 70B+scaledown_window=15 * MINUTES— garde le conteneur chaud pendant 15 minutes après la dernière requête, puis scale à zéro@modal.concurrent(max_inputs=32)— autorise jusqu'à 32 requêtes simultanées par conteneur (vLLM gère le batching en interne)@modal.web_server(port=8000)— expose le serveur HTTP vLLM directement comme endpoint web Modal--enforce-eager— ignore la compilation des graphes CUDA pour des démarrages à froid plus rapides (compromis : débit de pointe légèrement réduit)
Le scaledown_window est ton principal levier de coût. Règle-le à 5 minutes pour le dev, 15-30 minutes pour les APIs de production avec trafic régulier.
Étape 5 : Déployer en production
Une commande. C'est tout :
modal deploy app.pyModal construit l'image conteneur, la pousse dans leur registre et retourne une URL en direct :
✓ Created objects.
├── 🔨 Created mount /app.py
├── 🔨 Created volume huggingface-cache
├── 🔨 Created volume vllm-cache
└── 🔨 Created web function serve => https://your-workspace--llm-endpoint-serve.modal.runLe premier déploiement prend quelques minutes car il télécharge les poids du modèle dans le volume. Les déploiements suivants (et les démarrages à froid) sont beaucoup plus rapides puisque les poids sont mis en cache.
Pour le développement, utilise modal serve app.py à la place — il recharge à chaud lors des modifications de fichiers et te donne une URL temporaire.
Étape 6 : Appeler ton endpoint (compatible OpenAI)
Ton serveur vLLM déployé expose une API compatible OpenAI à /v1/chat/completions. Tu peux utiliser le SDK Python OpenAI standard pour l'appeler — il suffit de pointer l'URL de base vers ton endpoint Modal :
from openai import OpenAI
client = OpenAI(
api_key="not-needed", # vLLM ne nécessite pas d'auth par défaut
base_url="https://your-workspace--llm-endpoint-serve.modal.run/v1",
)
response = client.chat.completions.create(
model="Qwen/Qwen3-4B-Thinking-2507-FP8",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain what vLLM is in two sentences."},
],
temperature=0.7,
max_tokens=256,
)
print(response.choices[0].message.content)Ça marche aussi avec curl :
curl -X POST https://your-workspace--llm-endpoint-serve.modal.run/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-4B-Thinking-2507-FP8",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 128
}'Tout outil qui supporte une API compatible OpenAI fonctionnera — LangChain, LlamaIndex, ta propre application. Si tu routes des requêtes à travers plusieurs endpoints LLM, un outil de passerelle LLM peut t'aider à gérer le failover et l'équilibrage de charge.
Conseils d'optimisation des coûts
La facturation à la seconde de Modal est déjà plus efficiente que la tarification horaire, mais tu peux encore en tirer plus :
1. Utiliser la quantification FP8
Les modèles FP8 utilisent environ la moitié de la VRAM de leurs équivalents fp16. Un Qwen3-8B en FP8 tient sur un seul H100, tandis que la version fp16 nécessite la majeure partie des 80 Go de ce GPU. Moins de VRAM signifie que tu peux utiliser des GPUs moins chers (A100 40 Go, L40S) pour les modèles plus petits.
2. Ajuster la fenêtre de scale-down
Le paramètre scaledown_window contrôle combien de temps un conteneur reste chaud après la dernière requête :
| Scénario | Fenêtre recommandée | Pourquoi |
|---|---|---|
| Développement/tests | 5 minutes | Économiser de l'argent, les démarrages à froid sont OK |
| API interne (occasionnelle) | 10-15 minutes | Équilibre coût vs latence |
| Production (trafic régulier) | 20-30 minutes | Minimiser les démarrages à froid |
| Production haute fréquence | Utiliser min_containers=1 | Garder un toujours chaud |
3. Choisir le bon GPU
Ne prends pas toujours un H100. Les modèles plus petits n'en ont pas besoin :
| Taille du modèle | GPU recommandé | Coût approximatif/h |
|---|---|---|
| 1-4B paramètres | L4 ou T4 | 0,55 – 0,75 € |
| 7-8B paramètres | A10 ou L40S | 1,05 – 1,85 € |
| 13-14B paramètres | A100 40 Go | 2,00 € |
| 30-70B paramètres | A100 80 Go ou H100 | 2,35 – 3,70 € |
| 70B+ paramètres | H100 x2 | 7,40 € |
4. Activer le prompt caching
Si tes workloads impliquent des prompts système répétés ou des préfixes partagés, le cache de préfixes automatique de vLLM peut réduire significativement la latence et le calcul. Active-le en ajoutant --enable-prefix-caching à la commande vLLM serve. Pour un approfondissement sur le fonctionnement du caching chez différents fournisseurs, consulte notre guide de prompt caching LLM.
5. Utiliser --enforce-eager pour l'optimisation des démarrages à froid
Par défaut, vLLM compile les graphes CUDA au démarrage, ce qui prend 1-3 minutes supplémentaires. Le flag --enforce-eager ignore cette compilation. Tu échanges ~10-15 % de débit de pointe contre des démarrages à froid dramatiquement plus rapides. Pour les workloads en rafales où la latence compte plus que le débit brut, c'est presque toujours le bon choix.
Aller plus loin : modèles fine-tunés
Une fois à l'aise avec le déploiement de modèles de base, l'étape naturelle suivante est de déployer ta propre version fine-tunée. Le workflow est identique — tu pointes simplement MODEL_NAME vers ton dépôt Hugging Face ou un volume Modal contenant tes poids fine-tunés.
Modal prend également en charge l'exécution de jobs de fine-tuning directement sur leurs GPUs. Tu peux entraîner un adaptateur LoRA sur Modal, le sauvegarder dans un volume et déployer le modèle fusionné — sans quitter la plateforme. Notre guide de fine-tuning LLM couvre en profondeur la partie entraînement.
Le app.py complet
Voici le script de déploiement complet en un seul bloc prêt à copier-coller :
import modal
# --- Définition de l'image ---
vllm_image = (
modal.Image.from_registry(
"nvidia/cuda:12.8.0-devel-ubuntu22.04", add_python="3.12"
)
.entrypoint([])
.pip_install("vllm==0.13.0", "huggingface-hub==0.36.0")
)
# --- Volumes pour la mise en cache du modèle ---
hf_cache = modal.Volume.from_name("huggingface-cache", create_if_missing=True)
vllm_cache = modal.Volume.from_name("vllm-cache", create_if_missing=True)
# --- Configuration du modèle ---
MODEL_NAME = "Qwen/Qwen3-4B-Thinking-2507-FP8"
MODEL_REVISION = "953532f942706930ec4bb870569932ef63038fdf"
app = modal.App("llm-endpoint", image=vllm_image)
N_GPU = 1
MINUTES = 60
VLLM_PORT = 8000
@app.function(
gpu=f"H100:{N_GPU}",
scaledown_window=15 * MINUTES,
timeout=10 * MINUTES,
volumes={
"/root/.cache/huggingface": hf_cache,
"/root/.cache/vllm": vllm_cache,
},
)
@modal.concurrent(max_inputs=32)
@modal.web_server(port=VLLM_PORT, startup_timeout=10 * MINUTES)
def serve():
import subprocess
cmd = [
"vllm", "serve",
MODEL_NAME,
"--revision", MODEL_REVISION,
"--served-model-name", MODEL_NAME,
"--host", "0.0.0.0",
"--port", str(VLLM_PORT),
"--tensor-parallel-size", str(N_GPU),
"--enforce-eager",
]
subprocess.Popen(" ".join(cmd), shell=True)Déploie avec modal deploy app.py, remplace MODEL_NAME par n'importe quel modèle Hugging Face, et tu es en ligne.
Foire aux questions
Combien coûte l'exécution d'un LLM sur Modal ?
Ça dépend du GPU et de la durée pendant laquelle ton endpoint reste chaud. Un Qwen3-4B sur un H100 coûte ~3,70 €/h d'utilisation active. Avec scale-to-zero et une fenêtre de scale-down de 15 minutes, un endpoint peu utilisé pourrait coûter 5-15 €/mois. Le crédit gratuit de 30 $/mois couvre beaucoup d'expérimentation.
Modal scale-t-il à zéro ?
Oui — c'est l'un de ses principaux arguments de vente. Quand aucune requête n'arrive pendant la durée de ton scaledown_window, le conteneur s'arrête et tu cesses de payer. La prochaine requête déclenche un démarrage à froid (typiquement 2-10 secondes selon la taille du modèle et si tu utilises --enforce-eager).
Puis-je déployer Llama 3.1 ou Mistral sur Modal ?
Absolument. Remplace la constante MODEL_NAME par n'importe quel modèle supporté par vLLM : meta-llama/Llama-3.1-8B-Instruct, mistralai/Mistral-7B-Instruct-v0.3, ou des centaines d'autres sur Hugging Face. Pour les modèles 70B+, change N_GPU à 2 et utilise gpu="H100:2".
Comment les démarrages à froid se comparent-ils à RunPod ?
Les démarrages à froid Modal sont typiquement de 2-4 secondes pour le conteneur lui-même, plus le temps de chargement du modèle. Avec les poids du modèle en cache dans un Volume et --enforce-eager activé, on est à 10-30 secondes au total pour un modèle 7-8B. Les démarrages à froid serverless de RunPod vont de moins de 200 ms (en cache) à 6-12 secondes pour les plus grands conteneurs — bien que leur modèle always-on évite complètement les démarrages à froid.
L'endpoint vLLM de Modal est-il vraiment compatible OpenAI ?
Oui. vLLM implémente les mêmes endpoints /v1/chat/completions, /v1/completions et /v1/models qu'OpenAI utilise. Tu peux pointer le SDK Python officiel openai vers ton URL Modal et ça fonctionne immédiatement. Le streaming, le function calling et le mode JSON fonctionnent tous.
Ai-je besoin d'un GPU sur ma machine locale ?
Non. Ta machine locale exécute uniquement le CLI Modal. Tout le travail GPU se passe sur l'infrastructure cloud de Modal. Tu pourrais déployer depuis un Chromebook si tu voulais.
Comment ajouter de l'authentification à mon endpoint ?
Les endpoints web Modal sont publics par défaut. Pour la production, ajoute une vérification simple de clé API dans ton code d'application, ou utilise les fonctionnalités d'authentification web intégrées de Modal. Tu peux aussi mettre en place une couche proxy avec une passerelle LLM qui gère l'auth, la limitation de débit et le routage.
Quelle est la différence entre modal serve et modal deploy ?
modal serve crée un endpoint temporaire qui recharge à chaud quand tu modifies ton code — parfait pour le développement. modal deploy crée un endpoint persistant, prêt pour la production, avec une URL stable. Utilise serve en itérant, deploy quand tu es prêt à livrer.
Puis-je utiliser SGLang à la place de vLLM ?
Oui. La documentation de Modal inclut des exemples SGLang à côté de vLLM. SGLang a tendance à avoir moins d'overhead pour les workloads à forte décoding et les petits modèles. vLLM est généralement meilleur pour les workloads mixtes avec beaucoup de prefill. Les deux produisent des endpoints compatibles OpenAI.
Comment ça se compare au déploiement sur Railway ou Render ?
Les plateformes comme Railway, Render et Fly.io sont excellentes pour les applications web, mais elles n'offrent pas d'instances GPU. Modal est conçu spécifiquement pour les workloads GPU avec une facturation à la seconde et un autoscaling. Si tu as besoin de servir un LLM, Modal (ou RunPod) est le bon outil — les plateformes PaaS traditionnelles ne peuvent pas le faire.