Que fait modal ?
Aperçu
Modal est une plateforme cloud pour exécuter du code Python de manière serverless, avec un accent sur les charges de travail d'IA/ML. Capacités clés :
- Calcul GPU à la demande (T4, L4, A10, L40S, A100, H100, H200, B200)
- Fonctions serverless avec mise à l'échelle automatique de zéro à des milliers de conteneurs
- Images de conteneur personnalisées construites entièrement en Python
- Stockage persistant via des Volumes pour les poids de modèles et les jeux de données
- Points de terminaison web pour servir des modèles et des API
- Tâches planifiées via cron ou intervalles fixes
- Démarrages à froid inférieurs à la seconde pour une inférence à faible latence
Tout dans Modal est défini comme du code — pas de YAML, pas de Dockerfiles requis (bien que les deux soient pris en charge).
Quand utiliser ce skill
Utilisez ce skill quand :
- Déployer ou servir des modèles d'IA/ML dans le cloud
- Exécuter des calculs accélérés par GPU (entraînement, inférence, fine-tuning)
- Créer des API web ou des points de terminaison serverless
- Passer à l'échelle des travaux de traitement par lots en parallèle
- Planifier des tâches récurrentes (pipelines de données, réentraînement, scraping)
- Avoir besoin de stockage cloud persistant pour les poids de modèles ou les jeux de données
- Vouloir exécuter du code dans des environnements de conteneur personnalisés
- Construire des files d'attente de travaux ou des systèmes de traitement asynchrone
Installation et authentification
Installer
uv pip install modal
Le SDK Python Modal prend en charge Python 3.10–3.14. Ce skill cible l'API stable modal>=1.0 (version actuelle : 1.4.x).
S'authentifier
Privilégiez les identifiants existants avant d'en créer de nouveaux. Seules les deux variables spécifiques à Modal
ci-dessous sont pertinentes — ne lisez, chargez ou exposez aucune autre variable d'environnement
ou contenu de fichier .env :
- Vérifiez si
MODAL_TOKEN_IDetMODAL_TOKEN_SECRETsont déjà définis dans l'environnement actuel. - Sinon, recherchez uniquement ces deux clés dans un fichier
.envlocal (ignorez toutes les autres entrées) et chargez-les si cela est approprié pour le workflow. - Revenez uniquement à l'interface interactive
modal setupou à la génération de nouveaux jetons si aucune des sources ne fournit déjà ces deux valeurs.
modal setup
Cela ouvre un navigateur pour l'authentification. Pour les environnements CI/CD ou headless, utilisez des variables d'environnement :
export MODAL_TOKEN_ID=<votre-id-de-jeton>
export MODAL_TOKEN_SECRET=<votre-secret-de-jeton>
Si les jetons ne sont pas déjà disponibles dans l'environnement ou .env, générez-les sur https://modal.com/settings
Modal propose un niveau gratuit avec 30 $/mois de crédits.
Référence : Voir references/getting-started.md pour une configuration détaillée et une première application pas à pas.
Concepts de base
Application et fonctions
Une App Modal regroupe des fonctions associées. Les fonctions décorées avec @app.function() s'exécutent à distance dans le cloud :
import modal
app = modal.App("mon-app")
@app.function()
def carre(x):
return x ** 2
@app.local_entrypoint()
def main():
# .remote() s'exécute dans le cloud
print(carre.remote(42))
Exécutez avec modal run script.py. Déployez avec modal deploy script.py.
Référence : Voir references/functions.md pour les hooks de cycle de vie, les classes, .map(), .spawn(), et plus.
Images de conteneur
Modal construit des images de conteneur à partir de code Python. L'installateur de paquets recommandé est uv :
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install("torch==2.12.0", "transformers==5.9.0", "accelerate==1.13.0")
.apt_install("git")
)
@app.function(image=image)
def inference(prompt):
from transformers import pipeline
pipe = pipeline("text-generation", model="meta-llama/Llama-3-8B")
return pipe(prompt)
Méthodes d'image clés :
.uv_pip_install()— Installer des paquets Python avec uv (recommandé).pip_install()— Installer avec pip (solution de repli).apt_install()— Installer des paquets système.run_commands()— Exécuter des commandes shell pendant la construction.run_function()— Exécuter du Python pendant la construction (par exemple, télécharger des poids de modèle).add_local_python_source()— Ajouter des modules locaux.env()— Définir des variables d'environnement
Référence : Voir references/images.md pour les Dockerfiles, micromamba, la mise en cache, les étapes de construction GPU.
Calcul GPU
Demandez des GPU via le paramètre gpu :
@app.function(gpu="H100")
def train_model():
import torch
device = torch.device("cuda")
# Code d'entraînement GPU ici
# Plusieurs GPU
@app.function(gpu="H100:4")
def distributed_training():
...
# Chaîne de repli GPU
@app.function(gpu=["H100", "A100-80GB", "A100-40GB"])
def flexible_inference():
...
GPU disponibles : T4, L4, A10, L40S, A100-40GB, A100-80GB, RTX-PRO-6000, H100, H200, B200, B200+
- Les GPU sont toujours spécifiés en tant que chaînes (par exemple
gpu="H100",gpu="H100:4"). Les anciens objetsmodal.gpu.*sont dépréciés depuis la v0.73.31. - Jusqu'à 8 GPU par conteneur (sauf A10 : jusqu'à 4)
- L40S est recommandé pour l'inférence (rapport coût/performance, 48 Go de VRAM)
- H100/A100 peuvent être automatiquement mis à niveau vers H200/A100-80GB sans frais supplémentaires
- Utilisez
gpu="H100!"pour empêcher la mise à niveau automatique
Référence : Voir references/gpu.md pour des conseils sur la sélection GPU et l'entraînement multi-GPU.
Volumes (stockage persistant)
Les Volumes fournissent un stockage de fichiers distribué et persistant :
vol = modal.Volume.from_name("poids-modele", create_if_missing=True)
@app.function(volumes={"/data": vol})
def save_model():
# Écrire dans le chemin monté
with open("/data/model.pt", "wb") as f:
torch.save(model.state_dict(), f)
@app.function(volumes={"/data": vol})
def load_model():
model.load_state_dict(torch.load("/data/model.pt"))
- Optimisé pour les charges de travail à écriture unique, lectures multiples (poids de modèles, jeux de données)
- Accès CLI :
modal volume ls,modal volume put,modal volume get - Validation automatique en arrière-plan toutes les quelques secondes
- Monter en lecture seule ou limiter à un sous-répertoire avec
vol.with_mount_options(read_only=True, sub_path="subset")
Référence : Voir references/volumes.md pour les volumes v2, les écritures simultanées, et les meilleures pratiques.
Secrets
Transmettez des identifiants de manière sécurisée aux fonctions :
@app.function(secrets=[modal.Secret.from_name("mes-cles-api")])
def call_api():
import os
api_key = os.environ["API_KEY"]
# Utiliser la clé
Créez des secrets via la CLI : modal secret create mes-cles-api API_KEY=sk-xxx
Ou à partir d'un fichier .env : modal.Secret.from_dotenv()
Référence : Voir references/secrets.md pour la configuration du tableau de bord, les secrets multiples et les modèles.
Points de terminaison web
Servez des modèles et des API en tant que points de terminaison web :
@app.function()
@modal.fastapi_endpoint()
def predict(text: str):
return {"result": model.predict(text)}
modal serve script.py— Développement avec rechargement à chaud et URL temporairemodal deploy script.py— Déploiement en production avec URL permanente- Prend en charge FastAPI, ASGI (Starlette, FastHTML), WSGI (Flask, Django), WebSockets
- Corps de requête jusqu'à 4 GiB, taille de réponse illimitée
Référence : Voir references/web-endpoints.md pour les applications ASGI/WSGI, le streaming, l'authentification et les WebSockets.
Tâches planifiées
Exécutez des fonctions selon un planning :
@app.function(schedule=modal.Cron("0 9 * * *")) # Tous les jours à 9h UTC
def daily_pipeline():
# ETL, réentraînement, scraping, etc.
...
@app.function(schedule=modal.Period(hours=6))
def periodic_check():
...
Déployez avec modal deploy script.py pour activer le planning.
modal.Cron("...")— Syntaxe cron standard, stable entre les déploiementsmodal.Period(hours=N)— Intervalle fixe, se réinitialise lors du redéploiement- Surveillez les exécutions dans le tableau de bord Modal
Référence : Voir references/scheduled-jobs.md pour la syntaxe cron et la gestion.
Mise à l'échelle et concurrence
Modal met automatiquement à l'échelle les conteneurs. Configurez les limites :
@app.function(
max_containers=100, # Limite supérieure
min_containers=2, # Garder au chaud pour une faible latence
buffer_containers=5, # Réserver de la capacité
scaledown_window=300, # Secondes d'inactivité avant arrêt
)
def process(data):
...
Traitez les entrées en parallèle avec .map() :
results = list(process.map([element1, element2, element3, ...]))
Activez la gestion concurrente des requêtes par conteneur avec @modal.concurrent. Définissez
target_inputs (cible par conteneur de l'autoscaler) en dessous de max_inputs (la limite
absolue) pour conserver une marge de manœuvre lors de la mise à l'échelle :
@app.function()
@modal.concurrent(max_inputs=10, target_inputs=8)
async def handle_request(req):
...
Reconfigurez une Fonction ou une Classe déployée au moment de l'invocation sans redéployer en utilisant
Function.with_options() / Function.with_concurrency() / Function.with_batching()
(et Cls.with_options()) :
Model = modal.Cls.from_name("mon-app", "Model")
rapide = Model.with_options(gpu="H200", max_containers=20)
rapide().generate.remote(prompt)
Référence : Voir references/scaling.md pour .map(), .starmap(), .spawn() et les limites.
Configuration des ressources
@app.function(
cpu=4.0, # Cœurs physiques (pas de vCPU)
memory=16384, # Mio
ephemeral_disk=51200, # Mio (jusqu'à 3 Tio)
timeout=3600, # Secondes
)
def heavy_computation():
...
Valeurs par défaut : 0,125 cœur CPU, 128 Mio de mémoire. Facturé sur max(requête, utilisation).
Référence : Voir references/resources.md pour les limites et les détails de facturation.
Classes avec hooks de cycle de vie
Pour les charges de travail avec état (par exemple, charger un modèle une fois et servir de nombreuses requêtes) :
@app.cls(gpu="L40S", image=image)
class Predictor:
@modal.enter()
def load_model(self):
self.model = load_heavy_model() # S'exécute une fois au démarrage du conteneur
@modal.method()
def predict(self, text: str):
return self.model(text)
@modal.exit()
def cleanup(self):
... # S'exécute à l'arrêt du conteneur
Appelez avec : Predictor().predict.remote("hello")
Bacs à sable (Sandboxes)
Pour exécuter du code non fiable ou généré dynamiquement (par exemple, la sortie d'un agent IA ou un interpréteur de code), utilisez un modal.Sandbox — un conteneur isolé que vous créez et contrôlez de manière programmatique plutôt qu'une Fonction décorée :
app = modal.App.lookup("sandbox-demo", create_if_missing=True)
# Conteneur isolé ; restreindre le trafic sortant pour les charges de travail non fiables
sb = modal.Sandbox.create(
app=app,
image=modal.Image.debian_slim(),
outbound_cidr_allowlist=["10.0.0.0/8"],
)
# Diffuser des fichiers via l'API du système de fichiers (bêta)
sb.filesystem.write_text("print(2 ** 10)\n", "/tmp/job.py")
contents = sb.filesystem.read_text("/tmp/job.py")
sb.terminate()
- Exécutez des commandes à l'intérieur du bac à sable avec sa méthode
exec(par exemple, exécutezpython /tmp/job.py) et lisez stdout depuis le handle de processus retourné — voirreferences/api_reference.md - Restreignez la connectivité avec
outbound_cidr_allowlist=[...]/inbound_cidr_allowlist=[...] - Prenez un instantané du système de fichiers avec
sb.snapshot_filesystem()pour le réutiliser comme image de base - Idéal pour les interpréteurs de code, l'exécution d'outils d'agent et l'isolation par utilisateur
Modèles de workflow courants
Service d'inférence de modèle GPU
import modal
app = modal.App("llm-service")
image = (
modal.Image.debian_slim(python_version="3.11")
.uv_pip_install("vllm")
)
@app.cls(gpu="H100", image=image, min_containers=1)
class LLMService:
@modal.enter()
def load(self):
from vllm import LLM
self.llm = LLM(model="meta-llama/Llama-3-70B")
@modal.method()
@modal.fastapi_endpoint(method="POST")
def generate(self, prompt: str, max_tokens: int = 256):
outputs = self.llm.generate([prompt], max_tokens=max_tokens)
return {"text": outputs[0].outputs[0].text}
Pipeline de traitement par lots
app = modal.App("batch-pipeline")
vol = modal.Volume.from_name("pipeline-data", create_if_missing=True)
@app.function(volumes={"/data": vol}, cpu=4.0, memory=8192)
def process_chunk(chunk_id: int):
import pandas as pd
df = pd.read_parquet(f"/data/input/chunk_{chunk_id}.parquet")
result = heavy_transform(df)
result.to_parquet(f"/data/output/chunk_{chunk_id}.parquet")
return len(result)
@app.local_entrypoint()
def main():
chunk_ids = list(range(100))
results = list(process_chunk.map(chunk_ids))
print(f"Traitement de {sum(results)} lignes au total")
Pipeline de données planifié
app = modal.App("etl-pipeline")
@app.function(
schedule=modal.Cron("0 */6 * * *"), # Toutes les 6 heures
secrets=[modal.Secret.from_name("db-credentials")],
)
def etl_job():
import os
db_url = os.environ["DATABASE_URL"]
# Extraire, transformer, charger
...
Référence CLI
| Commande | Description |
|---|---|
modal setup | S'authentifier avec Modal |
modal run script.py | Exécuter le point d'entrée local d'un script |
modal serve script.py | Serveur de développement avec rechargement à chaud |
modal deploy script.py | Déployer en production |
modal volume ls <nom> | Lister les fichiers dans un volume |
modal volume put <nom> <fichier> | Télécharger un fichier vers un volume |
modal volume get <nom> <fichier> | Télécharger un fichier depuis un volume |
modal secret create <nom> C=V | Créer un secret |
modal secret list | Lister les secrets |
modal app list | Lister les applications déployées |
modal app stop <nom> | Arrêter une application déployée |
Notes de sécurité
- Identifiants : Seuls
MODAL_TOKEN_IDetMODAL_TOKEN_SECRETsont nécessaires pour s'authentifier. Ne lisez, enregistrez ou transmettez aucune autre variable d'environnement ou entrée de.env. - Sous-processus / serveurs personnalisés : Certains modèles ici (lanceurs d'entraînement multi-GPU, applications
@modal.web_server) appellentsubprocess.run/subprocess.Popenou des commandes shell pendant les constructions. Gardez les listes d'arguments fixes et codées en dur. Ne construisez jamais d'arguments de sous-processus ou de shell à partir d'entrées utilisateur non nettoyées — transmettez les valeurs non fiables sous forme de données (fichiers, variables d'environnement, stdin), pas comme arguments de commande. - Code non fiable : Exécutez le code généré par l'utilisateur ou le modèle à l'intérieur d'un
modal.Sandbox(voir ci-dessus), pas dans une Fonction régulière, et restreignez l'accès réseau avec des listes d'autorisation CIDR.
Fichiers de référence
Documentation détaillée pour chaque sujet :
references/getting-started.md— Installation, authentification, première applicationreferences/functions.md— Fonctions, classes, hooks de cycle de vie, exécution distantereferences/images.md— Images de conteneur, installation de paquets, mise en cachereferences/gpu.md— Types de GPU
…
[tronqué pour la traduction]