Vue d’ensemble : MLOps moderne : du modèle entraîné à la production scalable
Introduction
Vous avez entraîné un modèle, ses métriques sont supérieures à celles de la version en service, et l’envie de pousser le checkpoint vers la prod est forte. C’est précisément le moment où une équipe perd du temps — ou du chiffre d’affaires. Sans Registry discipliné, deux scénarios s’enchaînent : un binaire copié à la main sur un serveur, et personne ne sait plus quelle version sert quoi le mois suivant.
MLflow Model Registry résout ce problème en transformant chaque version d’un modèle en artefact versionné, taggable, et adressable par un URI stable. Depuis MLflow 2.9 (RFC #10336), les stages historiques (None, Staging, Production, Archived) sont dépréciés au profit des aliases mutables et des tags structurés, qui acceptent A/B testing, canary, rollback et multi-environnements là où les quatre stages fixes étaient un carcan.
Ce tutoriel prend la suite de Tracking d’expériences avec MLflow. On part d’un modèle déjà loggé et enregistré, on construit un pipeline GitHub Actions qui évalue automatiquement la nouvelle version, l’alias challenger est promu champion si les tests passent, une image Docker est construite avec mlflow models build-docker, et un webhook MLflow notifie Slack à chaque transition. Le rollback se résume à une seule commande : réassigner l’alias.
Prérequis
- MLflow ≥ 3.3.0 (webhooks OSS introduits en août 2025) — version stable au moment de la rédaction : MLflow 3.12.0
- Un serveur MLflow remote accessible (tracking + registry) avec backend store relationnel et artefact store S3/MinIO/GCS
- Python 3.10+ et un environnement
mlflowinstallé (pip install mlflow==3.12.0) - Docker Engine 24+ pour
mlflow models build-docker, et un registre d’images privé (GHCR, GitLab Registry, Harbor, ECR) - Un repo GitHub avec droit d’écrire des secrets et de pousser une image, plus un workspace Slack avec un Incoming Webhook si vous activez la notification
- Lecture conseillée préalable : Tracking d’expériences avec MLflow et le guide MLOps moderne
Étape 1 — Comprendre les aliases et tags (vs stages dépréciés)
Le découpage stage Staging ↔ Production de MLflow 1.x partait d’une bonne intention : un état machine clair pour chaque version. En pratique, dès qu’on voulait du shadow traffic, deux champions sur deux régions, ou un canary à 5 %, la grille de quatre cases craquait. MLflow 2.9 a donc déprécié les stages au profit d’un modèle plus souple.
Un alias est un pointeur nommé, mutable, vers une version précise — champion, challenger, shadow, eu-west-canary. Plusieurs aliases peuvent coexister sur des versions distinctes, et l’URI models:/<name>@<alias> résout à la version pointée au moment du chargement. Un tag est une paire clé/valeur attachée à une version pour qualifier son état (validation_status=passed, owner=ml-platform, dataset=v17). Les stages restent fonctionnels par compatibilité mais émettent des warnings : il n’est pas raisonnable de bâtir un pipeline 2026 dessus.
from mlflow import MlflowClient
client = MlflowClient()
# Comparer les deux approches
# Ancien (déprécié) :
# client.transition_model_version_stage(
# name="fraud-classifier", version=7, stage="Production"
# )
# Nouveau (recommandé) :
client.set_registered_model_alias(
name="fraud-classifier", alias="champion", version=7
)
client.set_model_version_tag(
name="fraud-classifier", version=7,
key="validation_status", value="passed"
)
Une fois l’alias posé, la résolution est immédiate : mlflow.pyfunc.load_model("models:/fraud-classifier@champion") renverra toujours la version 7 tant qu’on ne réassigne pas l’alias. Signal de réussite : la commande mlflow client get-registered-model-by-alias fraud-classifier champion retourne un JSON où le champ version correspond à la version attendue, et la propriété aliases de la ModelVersion contient bien champion.
Étape 2 — Enregistrer un modèle dans le registry
On suppose que le tracking est déjà en place : runs loggés, métriques et artefact model/ sur le serveur MLflow. Si ce n’est pas le cas, repassez par Tracking d’expériences avec MLflow pour l’installation et le premier mlflow.sklearn.log_model. Ici, on enregistre uniquement la version dans le Registry à partir d’un run existant — c’est l’opération qui crée une ModelVersion nouvelle dans la RegisteredModel.
Le code ci-dessous fait deux choses : créer la RegisteredModel si elle n’existe pas, puis enregistrer l’artefact du run comme nouvelle version. La fonction mlflow.register_model retourne un objet ModelVersion dont on lira version pour la suite du pipeline.
import mlflow
import os
mlflow.set_tracking_uri(os.environ["MLFLOW_TRACKING_URI"])
RUN_ID = os.environ["RUN_ID"] # remonté par le job d'entraînement
MODEL_NAME = "fraud-classifier"
model_uri = f"runs:/{RUN_ID}/model"
mv = mlflow.register_model(model_uri=model_uri, name=MODEL_NAME)
print(f"Registered {MODEL_NAME} version {mv.version} from run {RUN_ID}")
L’output attendu est une ligne Registered fraud-classifier version 12 from run b7d2… et, côté UI MLflow, la version 12 apparaît dans l’onglet Models sans aucun alias. Signal de réussite : client.get_model_version(MODEL_NAME, mv.version).status == "READY". Si status reste à PENDING_REGISTRATION plus de quelques secondes, c’est généralement l’artefact store qui n’est pas joignable depuis le serveur MLflow.
Étape 3 — Assigner un alias « challenger » à la nouvelle version
Avant de promouvoir, on étiquette la nouvelle version comme challenger. C’est la convention qu’on adopte dans tout le pipeline : champion sert le trafic, challenger est en cours d’évaluation, shadow reçoit une copie du trafic réel sans réponse exposée. L’alias est volontairement immutable côté code applicatif — c’est le pipeline qui le déplace.
On en profite pour poser deux tags structurés : validation_status=pending qui passera à passed ou failed après l’évaluation, et git_sha pour la traçabilité côté code source. Cette double information (alias mutable + tags immutables d’audit) reconstitue ce que les stages ne faisaient qu’à moitié.
from mlflow import MlflowClient
client = MlflowClient()
client.set_registered_model_alias(
name="fraud-classifier", alias="challenger", version=mv.version
)
client.set_model_version_tag(
name="fraud-classifier", version=mv.version,
key="validation_status", value="pending"
)
client.set_model_version_tag(
name="fraud-classifier", version=mv.version,
key="git_sha", value=os.environ["GITHUB_SHA"]
)
Le retour visuel dans l’UI Registry est immédiat : la version porte un badge challenger, et la liste des tags affiche validation_status: pending. Signal de réussite : client.get_model_version_by_alias("fraud-classifier", "challenger").version retourne exactement le numéro de version enregistré à l’étape précédente. Si la commande renvoie RESOURCE_DOES_NOT_EXIST, c’est que l’alias n’a pas été créé — vérifiez les droits d’écriture sur le registry remote.
Étape 4 — Workflow GitHub Actions : tests d’évaluation automatisés
L’évaluation doit être déclenchée par le pipeline, pas par un humain qui clique sur l’UI MLflow. On définit un workflow GitHub Actions qui prend en entrée un run_id (output du job d’entraînement), enregistre le modèle, pose l’alias challenger, et exécute une suite de tests métier : seuil de performance minimal, parité avec le champion sur un dataset de référence, latence d’inférence sous une borne.
Les secrets MLFLOW_TRACKING_URI, MLFLOW_TRACKING_USERNAME et MLFLOW_TRACKING_TOKEN sont stockés dans les Repository Secrets. Le job evaluate écrit dans $GITHUB_OUTPUT un booléen passed qui pilotera les jobs suivants. On garde un seul workflow avec deux jobs dépendants plutôt que deux workflows distincts : la dépendance est explicite et la promotion ne peut pas partir avant l’évaluation.
name: mlflow-promote
on:
workflow_dispatch:
inputs:
run_id:
description: "MLflow run_id produit par l'entraînement"
required: true
env:
MODEL_NAME: fraud-classifier
MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}
MLFLOW_TRACKING_USERNAME: ${{ secrets.MLFLOW_TRACKING_USERNAME }}
MLFLOW_TRACKING_TOKEN: ${{ secrets.MLFLOW_TRACKING_TOKEN }}
jobs:
register-and-evaluate:
runs-on: ubuntu-24.04
outputs:
version: ${{ steps.register.outputs.version }}
passed: ${{ steps.eval.outputs.passed }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install mlflow==3.12.0 scikit-learn==1.5.2 pandas==2.2.3
- id: register
run: |
VERSION=$(python ci/register_challenger.py "${{ inputs.run_id }}")
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- id: eval
run: |
if python ci/evaluate_challenger.py "${{ steps.register.outputs.version }}"; then
echo "passed=true" >> "$GITHUB_OUTPUT"
else
echo "passed=false" >> "$GITHUB_OUTPUT"
exit 1
fi
Le script evaluate_challenger.py charge models:/fraud-classifier@challenger et models:/fraud-classifier@champion, calcule les métriques sur un dataset versionné, et termine en code 0 si le challenger bat le champion d’au moins ε (par exemple +0.5 point de F1) tout en respectant un plafond de latence. Output attendu : un job register-and-evaluate au vert qui expose passed=true. Signal de réussite : le workflow refuse explicitement de continuer si l’une des bornes n’est pas tenue — c’est le seul mécanisme qui garantit que rien d’évaluable manuellement ne passe en production par inadvertance.
Étape 5 — Promouvoir « challenger » → « champion » si tests OK
La promotion est l’opération la plus banale du pipeline et la plus dangereuse à automatiser. On la cantonne à une seule action atomique : réassigner l’alias champion à la version qui porte actuellement challenger. L’ancien champion garde son numéro de version intact, son alias est simplement déplacé. Une trace est laissée par tag previous_champion_version pour activer un rollback en une commande.
Le job dépend du précédent via needs: et n’est exécuté que si passed=true. La logique tient en cinq lignes Python : récupérer la version sous l’alias challenger, lire la version actuelle sous champion (s’il existe), poser l’alias champion sur la nouvelle version, écrire le tag validation_status=passed.
promote:
needs: register-and-evaluate
if: needs.register-and-evaluate.outputs.passed == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- run: pip install mlflow==3.12.0
- run: python ci/promote_to_champion.py "${{ needs.register-and-evaluate.outputs.version }}"
# ci/promote_to_champion.py
import sys
from mlflow import MlflowClient
MODEL_NAME = "fraud-classifier"
new_version = sys.argv[1]
client = MlflowClient()
try:
previous = client.get_model_version_by_alias(MODEL_NAME, "champion")
client.set_model_version_tag(
MODEL_NAME, new_version,
"previous_champion_version", str(previous.version),
)
except Exception:
pass # premier déploiement, pas de champion antérieur
client.set_registered_model_alias(MODEL_NAME, "champion", new_version)
client.set_model_version_tag(MODEL_NAME, new_version, "validation_status", "passed")
print(f"champion -> v{new_version}")
Output attendu : la console GitHub Actions affiche champion -> v12, et dans l’UI MLflow la version 12 porte désormais les deux badges champion et challenger. Signal de réussite : client.get_model_version_by_alias(MODEL_NAME, "champion").version == new_version. Le double badge est transitoire — on retirera challenger au moment du déploiement effectif.
Étape 6 — Construire l’image Docker de service via mlflow models build-docker
MLflow embarque une commande qui transforme n’importe quelle ModelVersion en image Docker autonome, équipée d’un serveur d’inférence sur le port 8080. L’image inclut Python, les dépendances déclarées dans le conda.yaml ou requirements.txt du modèle, et un wrapper qui expose les endpoints /invocations et /ping. C’est l’approche la plus directe pour déployer sans écrire de FastAPI maison ; pour des besoins plus poussés, on basculera sur BentoML.
L’URI alias est accepté tel quel par --model-uri, ce qui rend l’image agnostique au numéro de version : on construit toujours pour champion, et le contenu de l’image change automatiquement quand l’alias bouge. On tagge l’image avec le SHA Git pour la traçabilité côté registre.
build-image:
needs: promote
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.11" }
- run: pip install mlflow==3.12.0
- name: build docker image
env:
MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_TRACKING_URI }}
MLFLOW_TRACKING_TOKEN: ${{ secrets.MLFLOW_TRACKING_TOKEN }}
run: |
mlflow models build-docker \
--model-uri "models:/fraud-classifier@champion" \
--name "ghcr.io/${{ github.repository_owner }}/fraud-classifier:${{ github.sha }}"
L’output attendu est une succession de logs Docker qui se termine par Successfully tagged ghcr.io/.../fraud-classifier:<sha>. Signal de réussite : docker run --rm -p 8080:8080 ghcr.io/<owner>/fraud-classifier:<sha> puis curl localhost:8080/ping renvoie HTTP 200. Si la build échoue sur la résolution de dépendances, vérifiez que le conda.yaml packagé avec le modèle ne contient pas de versions épinglées indisponibles dans les indices publics — symptôme classique d’un environnement de training qui mélange pip privé et public.
Étape 7 — Déployer l’image (registre privé + redémarrage service)
L’image est désormais buildée localement sur le runner GitHub Actions. Il reste à la pousser vers un registre privé (ici GHCR), puis à signaler au cluster d’exécution qu’une nouvelle image est disponible. Sur Kubernetes, on patche le Deployment avec la nouvelle image et on laisse le rollout faire son travail ; sur une VM avec systemd, on redémarre l’unité après un docker pull.
Le push utilise les credentials GITHUB_TOKEN avec scope packages:write. Pour Kubernetes, kubectl set image reste plus lisible qu’un patch JSON et déclenche un rollout standard avec health checks. Le job ne se termine en succès que lorsque le rollout est marqué complete par le cluster.
deploy:
needs: build-image
runs-on: ubuntu-24.04
permissions:
packages: write
contents: read
steps:
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: docker push ghcr.io/${{ github.repository_owner }}/fraud-classifier:${{ github.sha }}
- uses: azure/setup-kubectl@v4
- run: echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > $HOME/.kube/config
- run: |
kubectl set image deployment/fraud-classifier \
api=ghcr.io/${{ github.repository_owner }}/fraud-classifier:${{ github.sha }} \
-n inference
kubectl rollout status deployment/fraud-classifier -n inference --timeout=5m
Output attendu : deployment "fraud-classifier" successfully rolled out. Signal de réussite : un appel curl https://api.example.com/fraud-classifier/ping retourne HTTP 200, et kubectl get pods -n inference -l app=fraud-classifier montre tous les pods en Running sur la nouvelle image. Pour de l’orchestration ML plus large (pipelines d’entraînement, hyperparam sweeps), regardez Kubeflow Pipelines.
Étape 8 — Webhook pour notifier les transitions
Les webhooks MLflow Registry sont arrivés en OSS avec MLflow 3.3.0 (août 2025) via le module mlflow.webhooks. La fonctionnalité reste marquée expérimentale mais elle est utilisable. Chaque webhook s’abonne à un type d’événement (model_version.created, model_version.alias_created, model_version.tag_set…) et reçoit un POST HTTP signé HMAC sur l’URL cible. On enregistre un webhook une fois pour toutes via le client Python ou l’API REST.
On vise une notification Slack à chaque réassignation de l’alias champion. Le webhook pointe vers un petit relais HTTP (Cloud Run, Lambda, ou simple endpoint Flask) qui filtre l’alias et reformatte le payload MLflow en message Slack. On ne pointe pas le webhook MLflow directement sur l’Incoming Webhook Slack : la signature HMAC de MLflow et le format JSON de Slack sont incompatibles, le relais est obligatoire.
from mlflow.webhooks import create_webhook
create_webhook(
name="slack-champion-transition",
url="https://relay.internal/mlflow/slack",
events=["model_version.alias_created"],
secret="REPLACE_WITH_STRONG_SECRET",
description="Notify Slack on champion alias movement",
)
# Côté relais (Flask) — vérification HMAC + push Slack
import hmac, hashlib, json, os, requests
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["MLFLOW_WEBHOOK_SECRET"].encode()
SLACK = os.environ["SLACK_WEBHOOK_URL"]
@app.post("/mlflow/slack")
def mlflow_slack():
sig = request.headers.get("X-Mlflow-Signature", "")
expected = hmac.new(SECRET, request.data, hashlib.sha256).hexdigest()
if not hmac.compare_digest(sig, expected):
abort(401)
payload = request.get_json()
if payload.get("alias") != "champion":
return "", 204
requests.post(SLACK, json={
"text": f":rocket: champion -> {payload['name']} v{payload['version']}"
}, timeout=5)
return "", 204
Output attendu : un message Slack :rocket: champion -> fraud-classifier v12 dans le canal cible quelques secondes après chaque promotion. Signal de réussite : la liste des webhooks renvoyée par mlflow.webhooks.list_webhooks() contient bien l’entrée slack-champion-transition en statut ACTIVE, et un test manuel set_registered_model_alias déclenche bien un POST avec signature HMAC valide. Si la signature ne match pas côté relais, vérifiez que le secret est exactement celui passé à create_webhook et qu’il n’a pas été tronqué dans un secret store.
Étape 9 — Procédure de rollback (réassigner alias)
Un rollback bien fait ne touche ni à Docker, ni à Kubernetes, ni à Git. Toute l’opération se passe dans le Registry : on lit le tag previous_champion_version posé à l’étape 5, et on réassigne l’alias champion dessus. L’image Docker construite avec l’URI models:/fraud-classifier@champion chargera désormais la version précédente au prochain restart de pod — ou immédiatement si le service rafraîchit son modèle en mémoire.
Pour rendre le rollback déclenchable en un clic, on encapsule la logique dans un workflow GitHub Actions workflow_dispatch. Une variante encore plus rapide : un script CLI que l’astreinte exécute depuis sa machine, sans intervention CI. Les deux coexistent — pipeline pour audit, CLI pour incident.
# ci/rollback_champion.py
from mlflow import MlflowClient
MODEL_NAME = "fraud-classifier"
client = MlflowClient()
current = client.get_model_version_by_alias(MODEL_NAME, "champion")
previous_tag = next(
(t.value for t in client.get_model_version(MODEL_NAME, current.version).tags
if t.key == "previous_champion_version"),
None,
)
if not previous_tag:
raise SystemExit("Aucune version précédente connue, rollback impossible")
client.set_registered_model_alias(MODEL_NAME, "champion", previous_tag)
client.set_model_version_tag(
MODEL_NAME, current.version, "validation_status", "rolled_back"
)
print(f"rollback : champion -> v{previous_tag}")
Output attendu : rollback : champion -> v11. Signal de réussite : sous 30 secondes, le webhook Slack remonte la transition, et les nouveaux pods déployés sur l’image champion chargent à nouveau la version précédente. Si vos pods caching le modèle en mémoire au démarrage uniquement, ajoutez un kubectl rollout restart deployment/fraud-classifier -n inference pour forcer le rechargement. Pour détecter en amont qu’un rollback est nécessaire, branchez un système de monitoring de drift comme Evidently.
Étape 10 — Vérification et signal de réussite
Le pipeline est terminé quand chaque maillon laisse une trace vérifiable indépendamment des autres. On dresse une checklist d’audit qu’on peut exécuter à froid après un déploiement, depuis n’importe quelle machine disposant d’un client MLflow et d’un curl.
Les cinq points ci-dessous testent la cohérence end-to-end : Registry, image Docker, déploiement Kubernetes, webhook, et tag d’audit. Si l’un échoue, le pipeline a une fuite à fermer avant le prochain entraînement.
MODEL=fraud-classifier
# 1. Le Registry expose bien champion
mlflow client get-registered-model-by-alias $MODEL champion
# 2. Le tag d'audit existe sur la version courante
VERSION=$(mlflow client get-registered-model-by-alias $MODEL champion \
--jsonpath '$.version')
mlflow client get-model-version $MODEL $VERSION --jsonpath '$.tags'
# 3. L'image Docker en prod sert bien la même version
curl -s https://api.example.com/fraud-classifier/version
# 4. Le webhook champion est ACTIVE
python -c "from mlflow.webhooks import list_webhooks; \
print([w for w in list_webhooks() if w.name == 'slack-champion-transition'])"
# 5. Un appel d'inférence retourne une prédiction valide
curl -s -X POST https://api.example.com/fraud-classifier/invocations \
-H "Content-Type: application/json" \
-d '{"inputs": [[0.1, 0.2, 0.3, 0.4, 0.5]]}'
Output attendu : cinq commandes en succès, la dernière renvoyant un JSON {"predictions": [...]}. Signal de réussite global : la version exposée par l’endpoint /version du service correspond exactement à client.get_model_version_by_alias(MODEL_NAME, "champion").version, et un grep dans les logs Slack montre la trace de la dernière transition. À ce stade, vous avez un cycle promote → deploy → notify → rollback intégralement reproductible.
Erreurs fréquentes
| Symptôme | Cause probable | Correctif |
|---|---|---|
DeprecationWarning: stage parameter is deprecated |
Code legacy qui appelle transition_model_version_stage |
Remplacer par set_registered_model_alias + tag validation_status |
RESOURCE_DOES_NOT_EXIST: Alias 'champion' not found |
Premier déploiement, aucun champion n’a encore été posé | Encapsuler get_model_version_by_alias dans try/except, ignorer au premier run |
mlflow models build-docker échoue sur résolution pip |
conda.yaml du modèle référence un index privé non joignable depuis le runner |
Re-logger le modèle avec extra_pip_requirements propre, ou builder dans un environnement qui a accès au pip privé |
| Webhook reçoit un POST mais signature HMAC invalide | Secret tronqué dans le secret store, ou encodage différent (str vs bytes) | Vérifier la longueur exacte du secret côté MLflow et côté relais, encoder explicitement en UTF-8 |
| L’image Docker tourne, mais le pod sert l’ancienne version | Modèle chargé en mémoire au démarrage uniquement, pas de rechargement à chaud | kubectl rollout restart après chaque réassignation de champion |
get_model_version_by_alias retourne version N mais l’UI affiche N-1 |
Cache navigateur ou load balancer en frontal du MLflow tracking server | Vider le cache, recharger ; pour le LB, vérifier l’absence de cache sur /api/2.0/mlflow/* |
| Webhook MLflow déclenché plusieurs fois pour une seule transition | Reprise automatique côté MLflow sur timeout du relais (> 5 s) | Idempotence côté relais : déduplication par (model_name, version, alias, timestamp) |
Ressources officielles
- MLflow Model Registry — documentation officielle
- mlflow.client API reference (set_registered_model_alias, get_model_version_by_alias)
- MLflow Webhooks (OSS, depuis 3.3.0)
- RFC #10336 — Deprecating model registry stages
- MLflow CLI reference (
mlflow models build-docker) - Release notes MLflow 3.12.0
Tutoriels associés
- MLOps moderne : du modèle entraîné à la production scalable — vue d’ensemble de la stack
- Tracking d’expériences avec MLflow — installation et premiers runs
- Servir un modèle ML en production avec BentoML — alternative à
mlflow models build-dockerpour les cas complexes - Kubeflow Pipelines : orchestration ML sur Kubernetes — orchestration amont des jobs d’entraînement
- Détection de drift avec Evidently — déclencheur naturel d’un retrain et d’une promotion
- Centraliser les features ML avec Feast
FAQ
Les stages MLflow vont-ils disparaître complètement ?
Ils sont dépréciés depuis MLflow 2.9 (RFC #10336) mais restent fonctionnels en 3.x pour compatibilité ascendante. Aucun calendrier ferme de suppression n’est publié — l’API émet des DeprecationWarning et la documentation ne les recommande plus. Tout nouveau pipeline doit s’appuyer exclusivement sur les aliases et tags ; les bases de code legacy peuvent migrer progressivement en remplaçant chaque transition_model_version_stage par un couple set_registered_model_alias + set_model_version_tag.
Combien d’aliases peut-on poser sur une version ?
Il n’y a pas de limite stricte côté MLflow OSS au-delà des contraintes de la base de données du backend store. En pratique, on reste sur une convention courte : champion, challenger, parfois shadow ou des aliases régionaux comme eu-prod, us-prod. Au-delà de cinq ou six aliases simultanés, la lisibilité du Registry se dégrade et il devient préférable de découper en plusieurs RegisteredModel.
L’URI models:/<name>@<alias> fonctionne-t-il avec toutes les flavors MLflow ?
Oui. La résolution d’URI se fait au niveau du client MLflow avant même que la flavor (sklearn, pytorch, tensorflow, pyfunc, transformers, diffusers…) entre en jeu. Toutes les fonctions mlflow.<flavor>.load_model et mlflow.pyfunc.load_model acceptent l’URI alias, de même que la CLI mlflow models serve -m et mlflow models build-docker --model-uri.
Peut-on rebuilder l’image Docker à chaque promotion ?
C’est possible et même recommandé si vos artefacts modèles changent de dépendances entre versions. La pratique alternative consiste à builder une image « modèle-agnostique » qui charge dynamiquement models:/<name>@champion au démarrage : on évite un rebuild par promotion, mais on perd l’auditabilité (l’image n’identifie plus à elle seule la version servie). Le choix dépend de la fréquence de promotion — au-delà d’une promotion par jour, le mode dynamique est plus rentable.
Le webhook MLflow OSS gère-t-il les retries automatiques ?
Oui, MLflow réessaie en cas de code HTTP non-2xx ou de timeout côté serveur cible. Cela impose côté relais une stricte idempotence : la même transition peut générer plusieurs POST. La clé de déduplication recommandée est (model_name, version, alias, event_timestamp) stockée en cache court (Redis, ou simple LRU mémoire si une seule instance de relais).
Comment gérer plusieurs environnements (staging, prod) avec les aliases ?
Deux conventions coexistent dans la communauté. La première utilise des aliases scoped : staging-champion, prod-champion sur un même RegisteredModel. La seconde, plus robuste, sépare les environnements en deux RegisteredModel distincts (fraud-classifier-staging et fraud-classifier) avec aliases simples (champion, challenger) dans chacun. La seconde approche s’aligne mieux sur des permissions IAM par environnement et évite qu’un script de prod tape par erreur sur un modèle de staging.
Pourquoi ne pas utiliser directement mlflow deployments au lieu de build-docker + Kubernetes ?
La commande mlflow deployments couvre certains targets managés (SageMaker, Azure ML, Databricks Serving). Si votre cible est l’un d’eux et que ses contraintes vous conviennent, c’est plus rapide. Pour un déploiement Kubernetes auto-hébergé, un GPU edge, ou simplement le contrôle total des ressources et de la stack d’inférence, build-docker reste l’option standard et neutre — c’est aussi celle qui s’intègre le plus facilement à un pipeline GitHub Actions générique.